@@ -13,6 +13,16 @@ module simple_commanders_test_single
1313 procedure :: execute = > exec_test_detect_atoms
1414end type commander_test_detect_atoms
1515
16+ type, extends(commander_base) :: commander_test_detect_calpha
17+ contains
18+ procedure :: execute = > exec_test_detect_calpha
19+ end type commander_test_detect_calpha
20+
21+ type, extends(commander_base) :: commander_test_detect_calpha_molecules
22+ contains
23+ procedure :: execute = > exec_test_detect_calpha_molecules
24+ end type commander_test_detect_calpha_molecules
25+
1626type, extends(commander_base) :: commander_test_simulate_nanoparticle
1727 contains
1828 procedure :: execute = > exec_test_simulate_nanoparticle
@@ -93,6 +103,202 @@ subroutine exec_test_detect_atoms( self, cline )
93103 call simple_end(' **** SIMPLE_TEST_DETECT_ATOMS NORMAL STOP ****' )
94104end subroutine exec_test_detect_atoms
95105
106+ subroutine exec_test_detect_calpha ( self , cline )
107+ use simple_atoms, only: atoms
108+ use simple_calpha_finder, only: calpha_finder
109+ class(commander_test_detect_calpha), intent (inout ) :: self
110+ class(cmdline), intent (inout ) :: cline
111+ character (len=* ), parameter :: PDB_FILE = ' test_calpha_candidates.pdb'
112+ character (len=* ), parameter :: CSV_FILE = ' test_calpha_candidates.csv'
113+ character (len=* ), parameter :: MRC_FILE = ' test_calpha_scores.mrc'
114+ integer , parameter :: TEST_BOX = 32 , NRES = 3
115+ type (image) :: workvol
116+ type (atoms) :: candidates
117+ type (calpha_finder) :: finder
118+ real (kind= c_float), pointer :: density(:,:,:)
119+ real :: centers(3 ,NRES), atom_sites(3 ,3 ), amplitudes(3 ), rotation(3 ,3 )
120+ real :: xyz(3 ), site(3 ), delta(3 ), distance, closest
121+ integer :: ldim(3 ), ires, iatom, ix, iy, iz
122+
123+ write (logfhandle,' (A)' ) ' >>> TEST_DETECT_CALPHA:'
124+ ldim = [TEST_BOX,TEST_BOX,TEST_BOX]
125+ centers(:,1 ) = [8 .,8 .,8 .]
126+ centers(:,2 ) = [16 .,16 .,16 .]
127+ centers(:,3 ) = [24 .,24 .,24 .]
128+ atom_sites(:,1 ) = [0 .,0 .,0 .]
129+ atom_sites(:,2 ) = [1.458 * cos (111.2 * PI/ 180 .), 1.458 * sin (111.2 * PI/ 180 .), 0 .]
130+ atom_sites(:,3 ) = [1.525 ,0 .,0 .]
131+ amplitudes = [1.25 ,1.0 ,1.0 ]
132+ rotation(:,1 ) = [0 .,1 .,0 .]
133+ rotation(:,2 ) = [- 0.5 ,0 .,sqrt (0.75 )]
134+ rotation(:,3 ) = [sqrt (0.75 ),0 .,0.5 ]
135+ call workvol% new (ldim, 1.0 )
136+ call workvol% get_rmat_ptr(density)
137+ density = 0 .
138+ do ires = 1 , NRES
139+ do iatom = 1 , size (atom_sites,2 )
140+ site = centers(:,ires) + matmul (rotation, atom_sites(:,iatom))
141+ do iz = 1 , TEST_BOX
142+ do iy = 1 , TEST_BOX
143+ do ix = 1 , TEST_BOX
144+ xyz = real ([ix,iy,iz] - 1 )
145+ delta = xyz - site
146+ density(ix,iy,iz) = density(ix,iy,iz) + amplitudes(iatom) * &
147+ exp (- 0.5 * sum (delta * delta) / 0.85 ** 2 )
148+ enddo
149+ enddo
150+ enddo
151+ enddo
152+ enddo
153+
154+ call finder% new (1.0 , 4.0 )
155+ call finder% search(workvol, 180.0 , 10 , 0.5 , string (PDB_FILE), string (MRC_FILE))
156+ if (nlines(string (PDB_FILE)) == 0 ) THROW_HARD(' TEST_DETECT_CALPHA FAILED: no candidates' )
157+ call candidates% new (string (PDB_FILE))
158+ closest = huge (1 .)
159+ do iatom = 1 , candidates% get_n()
160+ do ires = 1 , NRES
161+ distance = sqrt (sum ((candidates% get_coord(iatom) - centers(:,ires))** 2 ))
162+ closest = min (closest, distance)
163+ enddo
164+ enddo
165+ if ( closest > 1.5 ) THROW_HARD(' TEST_DETECT_CALPHA FAILED: peak is displaced' )
166+
167+ call candidates% kill()
168+ call finder% kill()
169+ call workvol% kill()
170+ if (file_exists(PDB_FILE)) call del_file(PDB_FILE)
171+ if (file_exists(CSV_FILE)) call del_file(CSV_FILE)
172+ if (file_exists(MRC_FILE)) call del_file(MRC_FILE)
173+ write (logfhandle,' (A,F7.3,A)' ) ' >>> TEST_DETECT_CALPHA: PASS (closest peak ' , closest, ' A)'
174+ call simple_end(' **** SIMPLE_TEST_DETECT_CALPHA NORMAL STOP ****' )
175+
176+ end subroutine exec_test_detect_calpha
177+
178+ subroutine exec_test_detect_calpha_molecules ( self , cline )
179+ use simple_atoms, only: atoms
180+ use simple_calpha_finder, only: calpha_finder
181+ use simple_molecule_data, only: molecule_data, betagal_1jyx, sars_cov2_spkgp_6vxx
182+ class(commander_test_detect_calpha_molecules), intent (inout ) :: self
183+ class(cmdline), intent (inout ) :: cline
184+ type (parameters) :: params
185+ type (molecule_data) :: mol
186+
187+ if ( .not. cline% defined(' smpd' ) ) call cline% set(' smpd' , 1.3 )
188+ if ( .not. cline% defined(' angstep' ) ) call cline% set(' angstep' , 45 )
189+ if ( .not. cline% defined(' thres' ) ) call cline% set(' thres' , 0.25 )
190+ call params% new (cline)
191+
192+ write (logfhandle,' (A)' ) ' >>> C-ALPHA MOLECULE BENCHMARK:'
193+ write (logfhandle,' (A,F6.2,A,I0,A,F6.3)' ) ' smpd=' , params% smpd, &
194+ ' A, angstep=' , params% angstep, ' degrees, threshold=' , params% thres
195+ mol = sars_cov2_spkgp_6vxx()
196+ call evaluate_molecule(' 6VXX' , mol, 2916 , params% smpd, params% angstep, params% thres)
197+ mol = betagal_1jyx()
198+ call evaluate_molecule(' 1JYX' , mol, 4044 , params% smpd, params% angstep, params% thres)
199+ call simple_end(' **** SIMPLE_TEST_DETECT_CALPHA_MOLECULES NORMAL STOP ****' )
200+
201+ contains
202+
203+ subroutine evaluate_molecule ( label , molecule_data_in , expected_truth , smpd , angstep , threshold )
204+ character (len=* ), intent (in ) :: label
205+ type (molecule_data), intent (in ) :: molecule_data_in
206+ integer , intent (in ) :: expected_truth, angstep
207+ real , intent (in ) :: smpd, threshold
208+ real , parameter :: MAP_PADDING = 12.0 , MATCH_RADIUS = 2.0
209+ type (atoms) :: molecule, candidates
210+ type (calpha_finder) :: finder
211+ type (image) :: workvol
212+ type (string) :: source_file, truth_file, vol_file, candidate_file, score_file
213+ real , allocatable :: truth_xyz(:,:)
214+ logical , allocatable :: truth_matched(:)
215+ real :: span(3 ), delta(3 ), best_distance_sq, recall, precision
216+ real :: recall_top_n, precision_top_n
217+ integer :: ldim(3 ), iatom, itruth, ipred, ntruth, npred, nmatched, best_truth
218+ integer :: top_n_count, nmatched_top_n
219+
220+ source_file = trim (label)// ' .pdb'
221+ truth_file = trim (label)// ' _calpha_truth.pdb'
222+ vol_file = trim (label)// ' _calpha_input.mrc'
223+ candidate_file = trim (label)// ' _calpha_candidates.pdb'
224+ score_file = trim (label)// ' _calpha_scores.mrc'
225+ span = maxval (molecule_data_in% xyz, dim= 1 ) - minval (molecule_data_in% xyz, dim= 1 )
226+ ldim = max (round2even((span + 2 . * MAP_PADDING) / smpd), 16 )
227+ call molecule% pdb2mrc(pdbfile= source_file, volfile= vol_file, smpd= smpd, &
228+ center_pdb= .true. , pdb_out= truth_file, vol_dim= ldim, mol= molecule_data_in)
229+
230+ ntruth = 0
231+ do iatom = 1 , molecule% get_n()
232+ if (molecule% get_name(iatom) == ' CA ' .and. molecule% get_element(iatom) == ' C ' ) &
233+ ntruth = ntruth + 1
234+ enddo
235+ if (ntruth /= expected_truth) THROW_HARD(' Unexpected built-in C-alpha count' )
236+ allocate (truth_xyz(3 ,ntruth), source= 0 .)
237+ allocate (truth_matched(ntruth), source= .false. )
238+ itruth = 0
239+ do iatom = 1 , molecule% get_n()
240+ if (molecule% get_name(iatom) /= ' CA ' .or. molecule% get_element(iatom) /= ' C ' ) cycle
241+ itruth = itruth + 1
242+ truth_xyz(:,itruth) = molecule% get_coord(iatom)
243+ enddo
244+
245+ call workvol% new (ldim, smpd)
246+ call workvol% read (vol_file)
247+ call finder% new (smpd, 4.0 )
248+ call finder% search(workvol, real (angstep), 2 * ntruth, threshold, candidate_file, score_file)
249+
250+ npred = 0
251+ if (nlines(candidate_file) > 0 )then
252+ call candidates% new (candidate_file)
253+ npred = candidates% get_n()
254+ endif
255+ nmatched = 0
256+ nmatched_top_n = 0
257+ top_n_count = min (ntruth, npred)
258+ do ipred = 1 , npred
259+ best_truth = 0
260+ best_distance_sq = huge (1 .)
261+ do itruth = 1 , ntruth
262+ if (truth_matched(itruth)) cycle
263+ delta = candidates% get_coord(ipred) - truth_xyz(:,itruth)
264+ if (sum (delta * delta) < best_distance_sq)then
265+ best_distance_sq = sum (delta * delta)
266+ best_truth = itruth
267+ endif
268+ enddo
269+ if (best_truth > 0 .and. best_distance_sq <= MATCH_RADIUS** 2 )then
270+ truth_matched(best_truth) = .true.
271+ nmatched = nmatched + 1
272+ endif
273+ if (ipred == top_n_count) nmatched_top_n = nmatched
274+ enddo
275+ recall_top_n = real (nmatched_top_n) / real (ntruth)
276+ precision_top_n = 0 .
277+ if (top_n_count > 0 ) precision_top_n = real (nmatched_top_n) / real (top_n_count)
278+ recall = real (nmatched) / real (ntruth)
279+ precision = 0 .
280+ if (npred > 0 ) precision = real (nmatched) / real (npred)
281+
282+ write (logfhandle,' (A,A)' ) ' >>> ' , trim (label)
283+ write (logfhandle,' (A,I0,A,I0)' ) ' truth=' , ntruth, ' , candidate cap=' , 2 * ntruth
284+ write (logfhandle,' (A,I0,A,I0,A,F7.3,A,F7.3)' ) ' top-N: predicted=' , top_n_count, &
285+ ' , matched=' , nmatched_top_n, ' , recall=' , recall_top_n, ' , precision=' , precision_top_n
286+ write (logfhandle,' (A,I0,A,I0,A,I0,A,F7.3,A,F7.3)' ) ' top-2N: predicted=' , npred, &
287+ ' , matched=' , nmatched, ' , missed=' , ntruth - nmatched, ' , recall=' , recall, &
288+ ' , precision=' , precision
289+ write (logfhandle,' (A,3(I0,1X))' ) ' map dimensions=' , ldim
290+ write (logfhandle,' (A,A)' ) ' candidates: ' , candidate_file% to_char()
291+ write (logfhandle,' (A,A)' ) ' score volume: ' , score_file% to_char()
292+
293+ if (npred > 0 ) call candidates% kill()
294+ call finder% kill()
295+ call workvol% kill()
296+ call molecule% kill()
297+ deallocate (truth_xyz, truth_matched)
298+ end subroutine evaluate_molecule
299+
300+ end subroutine exec_test_detect_calpha_molecules
301+
96302subroutine exec_test_simulate_nanoparticle ( self , cline )
97303 use simple_commanders_sim, only: commander_simulate_nanoparticle
98304 class(commander_test_simulate_nanoparticle), intent (inout ) :: self
0 commit comments