Line data Source code
1 : !--------------------------------------------------------------------------------------------------!
2 : ! CP2K: A general program to perform molecular dynamics simulations !
3 : ! Copyright 2000-2026 CP2K developers group <https://cp2k.org> !
4 : ! !
5 : ! SPDX-License-Identifier: GPL-2.0-or-later !
6 : !--------------------------------------------------------------------------------------------------!
7 :
8 : ! **************************************************************************************************
9 : !> \brief Functionality for atom centered symmetry functions
10 : !> for neural network potentials
11 : !> \author Christoph Schran (christoph.schran@rub.de)
12 : !> \author Dhruv Sharma (ds2173@cam.ac.uk)
13 : !> \date 2020-10-10
14 : ! **************************************************************************************************
15 : MODULE nnp_acsf
16 : USE cp_log_handling, ONLY: cp_get_default_logger,&
17 : cp_logger_get_default_unit_nr,&
18 : cp_logger_type
19 : USE kinds, ONLY: default_string_length,&
20 : dp
21 : USE mathconstants, ONLY: pi
22 : USE message_passing, ONLY: mp_para_env_type
23 : USE nnp_cell_list, ONLY: nnp_compute_neighbors_cell_list,&
24 : nnp_prepare_cell_list_cache
25 : USE nnp_environment_types, ONLY: nnp_acsf_ang_type,&
26 : nnp_acsf_rad_type,&
27 : nnp_cut_cos,&
28 : nnp_cut_tanh,&
29 : nnp_symfgrp_type,&
30 : nnp_type
31 : USE nnp_neighbor_interface, ONLY: nnp_grp_grow_dGdr,&
32 : nnp_neighbor_interface_prepare,&
33 : nnp_neighbor_interface_reset_neighbor,&
34 : nnp_workspace_grow_caches
35 : USE periodic_table, ONLY: get_ptable_info
36 :
37 : !$ USE omp_lib, ONLY: omp_get_max_threads
38 : #include "./base/base_uses.f90"
39 :
40 : IMPLICIT NONE
41 :
42 : PRIVATE
43 :
44 : LOGICAL, PRIVATE, PARAMETER :: debug_this_module = .FALSE.
45 : CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'nnp_acsf'
46 :
47 : ! Cutoff-equality tolerance for grouping symmetry functions in
48 : ! nnp_init_acsf_groups: SFs whose cutoffs agree to within this absolute
49 : ! tolerance share a group (and a spline grid).
50 : REAL(KIND=dp), PARAMETER, PRIVATE :: cutoff_eq_tol = 1.0e-5_dp
51 :
52 : ! Public subroutines ***
53 : PUBLIC :: nnp_calc_acsf, &
54 : nnp_prepare_neighbor_cache, &
55 : nnp_init_acsf_groups, &
56 : nnp_sort_acsf, &
57 : nnp_sort_ele, &
58 : nnp_write_acsf
59 :
60 : CONTAINS
61 :
62 : ! **************************************************************************************************
63 : !> \brief Calculate atom centered symmetry functions for given atom i
64 : !>
65 : !> Per-atom symmetry-function gradients live in the per-element neighbour
66 : !> workspace as sparse per-group arrays: self_dGdr (atom i), dGdr_rad (radial
67 : !> group s), and dGdr_ang_jj / dGdr_ang_kk (angular group s, j- and k-side),
68 : !> each addressed by the neighbour's slot in workspace(ind)%neighbor. No global
69 : !> (3, n_sf, num_atoms) slab is needed: only atoms in atom i's neighbour lists
70 : !> get derivatives.
71 : !>
72 : !> \param nnp NNP environment with persistent neighbour caches populated by
73 : !> nnp_prepare_neighbor_cache (must be called once before the per-atom loop).
74 : !> \param i central-atom index in the global atom ordering; selects
75 : !> nnp%ele_ind(i) for per-element scratch routing.
76 : !> \param calc_forces if .TRUE., populate the per-element dGdr workspaces for caller-side
77 : !> force assembly via nnp_scatter_dgdr_to_forces.
78 : !> \param stress optional per-input-node stress accumulator (only valid when calc_forces).
79 : !> \date 2020-10-10
80 : !> \author Christoph Schran (christoph.schran@rub.de)
81 : !> \author Dhruv Sharma (ds2173@cam.ac.uk)
82 : ! **************************************************************************************************
83 252785 : SUBROUTINE nnp_calc_acsf(nnp, i, calc_forces, stress)
84 :
85 : TYPE(nnp_type), INTENT(INOUT), POINTER :: nnp
86 : INTEGER, INTENT(IN) :: i
87 : LOGICAL, INTENT(IN) :: calc_forces
88 : REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT), &
89 : OPTIONAL :: stress
90 :
91 : CHARACTER(len=*), PARAMETER :: routineN = 'nnp_calc_acsf'
92 :
93 : INTEGER :: handle, handle_nlist, handle_sf, ii, ind, izeta_il, j, k, l, m, n_ang1_s, &
94 : n_ang2_s, n_input_nodes, n_symf_s, nthreads_ang, off, peak, s, sf
95 : LOGICAL :: do_forces, homo_grp
96 : REAL(KIND=dp) :: angular_il, arg_il, costheta_il, cutoff_s, cutoff_sqr, dfcut3_il, &
97 : dfcutdr1_il, dfcutdr2_il, dfcutdr3_il, dgdx_t1, dgdx_t2, dsymdr1_il, dsymdr2_il, &
98 : dsymdr3_il, eta_il, f_il, fcut3_il, ftot_il, g_il, inv_g2_il, lam_il, pref_il, &
99 : pref_lam_il, prefzeta_il, r1, r1_inv, r2, r2_inv, r2sum_il, r3, r3_inv, r3_sqr, rsqr1, &
100 : rsqr2, rsqr3, sym_il, symtmp_il, tanh_il, tmp1_il, tmp2_il, tmp3_il, tmp_il, tmpzeta_il, &
101 : zeta_il
102 : REAL(KIND=dp), DIMENSION(3) :: dcosbase1_il, dcosbase2_il, &
103 : dcosbase3_il, dr1dx_il, dr2dx_il, &
104 : dr3dx_il, f_jj_il, f_kk_il, rvect1, &
105 : rvect2, rvect3
106 :
107 : ! Inlined angular kernel variables (eliminates nnp_calc_ang call overhead
108 : ! and intermediate angular_force3tmp array on the serial force path).
109 :
110 252785 : CALL timeset(routineN, handle)
111 :
112 : !determine index of atom type
113 252785 : ind = nnp%ele_ind(i)
114 252785 : do_forces = calc_forces
115 :
116 : ! Lazy one-shot build of the per-radial-group spline tables. They depend
117 : ! only on eta/rs/cutoff/cut_type, fixed once nnp_init_acsf_groups and
118 : ! nnp_sort_acsf have run; %spline_built is per-nnp so re-init is safe.
119 : ! Guard the indexed access in case this element has no radial groups.
120 252785 : IF (nnp%rad(ind)%n_symfgrp > 0) THEN
121 252785 : IF (.NOT. nnp%rad(ind)%symfgrp(1)%spline_built) CALL nnp_build_radial_splines(nnp)
122 : END IF
123 :
124 : ! Persistent per-element workspace bindings. The dGdr_* slabs grow lazily
125 : ! via nnp_grp_grow_dGdr; the fc_cache*/dfc_cache* buffers are bound in an
126 : ! inner ASSOCIATE after nnp_workspace_grow_caches has sized them, so the
127 : ! alias stays valid.
128 : ASSOCIATE (workspace => nnp%neighbor_interface_state%workspace(ind), &
129 : neighbor => nnp%neighbor_interface_state%workspace(ind)%neighbor, &
130 : radial_symtmp => nnp%neighbor_interface_state%workspace(ind)%radial_sym, &
131 : radial_forcetmp => nnp%neighbor_interface_state%workspace(ind)%radial_force, &
132 : angular_symtmp => nnp%neighbor_interface_state%workspace(ind)%angular_sym, &
133 : angular_force3tmp => nnp%neighbor_interface_state%workspace(ind)%angular_force, &
134 : self_dGdr => nnp%neighbor_interface_state%workspace(ind)%self_dGdr)
135 :
136 252785 : n_input_nodes = nnp%neighbor_interface_state%workspace(ind)%n_input_nodes
137 2487569 : IF (do_forces) self_dGdr(:, 1:n_input_nodes) = 0.0_dp
138 :
139 : ! Walk the linked-cell candidates directly. The cell-list/neighbour cache
140 : ! is prepared once per force evaluation by nnp_prepare_neighbor_cache, not
141 : ! here, so the per-atom walk stays O(neighbours).
142 252785 : CALL timeset('nnp_acsf_neighbor_fill', handle_nlist)
143 1011140 : neighbor%pbc_copies = nnp%cell_list_cache%exact_pbc_copies
144 252785 : CALL nnp_neighbor_interface_reset_neighbor(nnp, ind)
145 252785 : CALL nnp_compute_neighbors_cell_list(nnp, neighbor, i)
146 252785 : CALL timestop(handle_nlist)
147 :
148 : ! Reset y:
149 3800797 : nnp%rad(ind)%y = 0.0_dp
150 2127593 : nnp%ang(ind)%y = 0.0_dp
151 :
152 : ! Grow the per-element 1D angular cutoff caches to this atom's peak
153 : ! per-group neighbour count, then bind them in an inner ASSOCIATE.
154 : ! MAXVAL of a zero-size array is compiler-defined, so guard each axis.
155 252785 : peak = 0
156 769054 : IF (SIZE(neighbor%n_ang1) > 0) peak = MAX(peak, MAXVAL(neighbor%n_ang1))
157 769054 : IF (SIZE(neighbor%n_ang2) > 0) peak = MAX(peak, MAXVAL(neighbor%n_ang2))
158 252785 : IF (peak > 0) CALL nnp_workspace_grow_caches(workspace, peak)
159 :
160 : ASSOCIATE (fc_cache1 => workspace%fc_cache1, &
161 : dfc_cache1 => workspace%dfc_cache1, &
162 : fc_cache2 => workspace%fc_cache2, &
163 276498 : dfc_cache2 => workspace%dfc_cache2)
164 :
165 : !calc forces
166 252785 : IF (do_forces) THEN
167 : !loop over radial sym fnct grps
168 23713 : CALL timeset('nnp_acsf_radial', handle_sf)
169 74339 : DO s = 1, nnp%rad(ind)%n_symfgrp
170 50626 : n_symf_s = nnp%rad(ind)%symfgrp(s)%n_symf
171 : ! Per-group dense buffer: (3, n_symf_s, cap_s). Grown lazily.
172 50626 : CALL nnp_grp_grow_dGdr(workspace%dGdr_rad(s), neighbor%n_rad(s))
173 23713 : ASSOCIATE (rad_buf => workspace%dGdr_rad(s)%data)
174 : !loop over associated neighbors
175 1900812 : DO j = 1, neighbor%n_rad(s)
176 7400744 : rvect1 = neighbor%rad(s)%dist(1:3, j)
177 1850186 : r1 = neighbor%rad(s)%dist(4, j)
178 : CALL nnp_calc_rad(nnp, ind, s, rvect1, r1, &
179 : radial_symtmp(1:n_symf_s), &
180 1850186 : radial_forcetmp(:, 1:n_symf_s))
181 : ! Per-group dense write: rad_buf(:, sf, j) holds dG_m/dr_j.
182 16679032 : DO sf = 1, n_symf_s
183 14778220 : m = nnp%rad(ind)%symfgrp(s)%symf(sf)
184 14778220 : self_dGdr(1, m) = self_dGdr(1, m) + radial_forcetmp(1, sf)
185 14778220 : self_dGdr(2, m) = self_dGdr(2, m) + radial_forcetmp(2, sf)
186 14778220 : self_dGdr(3, m) = self_dGdr(3, m) + radial_forcetmp(3, sf)
187 14778220 : rad_buf(1, sf, j) = -radial_forcetmp(1, sf)
188 14778220 : rad_buf(2, sf, j) = -radial_forcetmp(2, sf)
189 14778220 : rad_buf(3, sf, j) = -radial_forcetmp(3, sf)
190 14778220 : IF (PRESENT(stress)) THEN
191 7184768 : DO l = 1, 3
192 23350496 : stress(:, l, m) = stress(:, l, m) + rvect1(:)*radial_forcetmp(l, sf)
193 : END DO
194 : END IF
195 16628406 : nnp%rad(ind)%y(m) = nnp%rad(ind)%y(m) + radial_symtmp(sf)
196 : END DO
197 : END DO
198 : END ASSOCIATE
199 : END DO
200 23713 : CALL timestop(handle_sf)
201 :
202 : !loop over angular sym fnct grps
203 23713 : CALL timeset('nnp_acsf_angular', handle_sf)
204 23713 : off = nnp%n_rad(ind)
205 :
206 : ! OpenMP over the angular group index s, taken only with >1 thread
207 : ! and >1 group. Angular groups partition the input-node index m
208 : ! disjointly (nnp_sort_acsf), so self_dGdr, stress and y are written
209 : ! without races. The serial path below is identical.
210 23713 : nthreads_ang = 1
211 23713 : !$ nthreads_ang = omp_get_max_threads()
212 23713 : IF (nthreads_ang > 1 .AND. nnp%ang(ind)%n_symfgrp > 1) THEN
213 0 : IF (PRESENT(stress)) THEN
214 0 : CALL nnp_acsf_angular_loop_omp(nnp, ind, self_dGdr, off, stress)
215 : ELSE
216 0 : CALL nnp_acsf_angular_loop_omp(nnp, ind, self_dGdr, off)
217 : END IF
218 : ELSE
219 76910 : DO s = 1, nnp%ang(ind)%n_symfgrp
220 53197 : cutoff_s = nnp%ang(ind)%symfgrp(s)%cutoff
221 53197 : cutoff_sqr = cutoff_s*cutoff_s
222 53197 : n_symf_s = nnp%ang(ind)%symfgrp(s)%n_symf
223 53197 : n_ang1_s = neighbor%n_ang1(s)
224 53197 : homo_grp = (nnp%ang(ind)%symfgrp(s)%ele(1) == nnp%ang(ind)%symfgrp(s)%ele(2))
225 :
226 : ! Grow per-group dense buffers. jj is always indexed in ang1.
227 : ! kk is indexed in ang1 for homo groups and in ang2 for hetero.
228 53197 : CALL nnp_grp_grow_dGdr(workspace%dGdr_ang_jj(s), n_ang1_s)
229 53197 : IF (homo_grp) THEN
230 26284 : CALL nnp_grp_grow_dGdr(workspace%dGdr_ang_kk(s), n_ang1_s)
231 26284 : n_ang2_s = 0
232 : ELSE
233 26913 : n_ang2_s = neighbor%n_ang2(s)
234 26913 : CALL nnp_grp_grow_dGdr(workspace%dGdr_ang_kk(s), n_ang2_s)
235 : END IF
236 :
237 : ! Per-group reset. Triplets accumulate into the same (sf, j) slot
238 : ! across multiple k partners, so we MUST zero before the triplet loop.
239 51620387 : IF (n_ang1_s > 0) workspace%dGdr_ang_jj(s)%data(:, 1:n_symf_s, 1:n_ang1_s) = 0.0_dp
240 53197 : IF (homo_grp) THEN
241 20770036 : IF (n_ang1_s > 0) workspace%dGdr_ang_kk(s)%data(:, 1:n_symf_s, 1:n_ang1_s) = 0.0_dp
242 : ELSE
243 15419925 : IF (n_ang2_s > 0) workspace%dGdr_ang_kk(s)%data(:, 1:n_symf_s, 1:n_ang2_s) = 0.0_dp
244 : END IF
245 :
246 : ! Precompute cutoff values and derivatives for ang1 neighbors
247 : CALL nnp_fill_fc_dfc_cache(neighbor%ang1(s)%dist, n_ang1_s, &
248 53197 : nnp%cut_type, cutoff_s, fc_cache1, dfc_cache1)
249 :
250 : ! Inlined angular kernel: compute + scatter fused. The (j,k)
251 : ! geometry is computed once, then the SF loop scatters sym
252 : ! values and forces directly into self_dGdr / jj_buf / kk_buf,
253 : ! keeping per-triple scalars in registers. Inlined equivalent
254 : ! of nnp_calc_ang; the OMP path calls it directly.
255 76910 : IF (homo_grp) THEN
256 : ASSOCIATE (jj_buf => workspace%dGdr_ang_jj(s)%data, &
257 : kk_buf => workspace%dGdr_ang_kk(s)%data, &
258 : grp_il => nnp%ang(ind)%symfgrp(s))
259 1053340 : DO j = 1, n_ang1_s
260 4108224 : rvect1 = neighbor%ang1(s)%dist(1:3, j)
261 1027056 : r1 = neighbor%ang1(s)%dist(4, j)
262 26168785 : DO k = j + 1, n_ang1_s
263 100461780 : rvect2 = neighbor%ang1(s)%dist(1:3, k)
264 25115445 : r2 = neighbor%ang1(s)%dist(4, k)
265 25115445 : rvect3(1) = rvect2(1) - rvect1(1)
266 25115445 : rvect3(2) = rvect2(2) - rvect1(2)
267 25115445 : rvect3(3) = rvect2(3) - rvect1(3)
268 25115445 : r3_sqr = rvect3(1)*rvect3(1) + rvect3(2)*rvect3(2) + rvect3(3)*rvect3(3)
269 26142501 : IF (r3_sqr < cutoff_sqr) THEN
270 11564739 : r3 = SQRT(r3_sqr)
271 :
272 : ! -- per-triple geometry (once) --
273 11564739 : rsqr1 = r1*r1; rsqr2 = r2*r2; rsqr3 = r3*r3
274 11564739 : r2sum_il = rsqr1 + rsqr2 + rsqr3
275 11564739 : f_il = rsqr3 - rsqr1 - rsqr2
276 11564739 : g_il = -2.0_dp*r1*r2
277 11564739 : costheta_il = f_il/g_il
278 :
279 11566535 : SELECT CASE (nnp%cut_type)
280 : CASE (nnp_cut_cos)
281 1796 : arg_il = pi*r3/cutoff_s
282 1796 : fcut3_il = 0.5_dp*(COS(arg_il) + 1.0_dp)
283 1796 : dfcut3_il = -0.5_dp*SIN(arg_il)*(pi/cutoff_s)
284 : CASE (nnp_cut_tanh)
285 11562943 : tanh_il = TANH(1.0_dp - r3/cutoff_s)
286 11562943 : fcut3_il = tanh_il**3
287 11562943 : dfcut3_il = (-3.0_dp/cutoff_s)*(tanh_il**2 - tanh_il**4)
288 : CASE DEFAULT
289 11564739 : CPABORT("NNP| Cutoff function unknown")
290 : END SELECT
291 :
292 11564739 : ftot_il = fc_cache1(j)*fc_cache1(k)*fcut3_il
293 11564739 : dfcutdr1_il = dfc_cache1(j)*fc_cache1(k)*fcut3_il
294 11564739 : dfcutdr2_il = fc_cache1(j)*dfc_cache1(k)*fcut3_il
295 11564739 : dfcutdr3_il = fc_cache1(j)*fc_cache1(k)*dfcut3_il
296 :
297 11564739 : r1_inv = 1.0_dp/r1; r2_inv = 1.0_dp/r2; r3_inv = 1.0_dp/r3
298 46258956 : dr1dx_il(:) = rvect1(:)*r1_inv
299 46258956 : dr2dx_il(:) = rvect2(:)*r2_inv
300 46258956 : dr3dx_il(:) = rvect3(:)*r3_inv
301 :
302 11564739 : inv_g2_il = 1.0_dp/(g_il*g_il)
303 46258956 : DO ii = 1, 3
304 34694217 : dgdx_t1 = 2.0_dp*r2*dr1dx_il(ii)
305 34694217 : dgdx_t2 = 2.0_dp*r1*dr2dx_il(ii)
306 : dcosbase1_il(ii) = -2.0_dp*(rvect1(ii) + rvect2(ii))*g_il &
307 34694217 : - f_il*(-(dgdx_t1 + dgdx_t2))
308 : dcosbase2_il(ii) = 2.0_dp*(rvect3(ii) + rvect1(ii))*g_il &
309 34694217 : - f_il*dgdx_t1
310 : dcosbase3_il(ii) = 2.0_dp*(rvect2(ii) - rvect3(ii))*g_il &
311 46258956 : - f_il*dgdx_t2
312 : END DO
313 :
314 : ! -- fused SF loop: compute + direct scatter --
315 71451823 : DO sf = 1, n_symf_s
316 59887084 : m = off + grp_il%symf(sf)
317 59887084 : lam_il = grp_il%pack_lam(sf)
318 59887084 : zeta_il = grp_il%pack_zeta(sf)
319 59887084 : eta_il = grp_il%pack_eta(sf)
320 59887084 : prefzeta_il = grp_il%pack_prefzeta(sf)
321 :
322 59887084 : tmp_il = 1.0_dp + lam_il*costheta_il
323 59887084 : IF (tmp_il <= 0.0_dp) THEN
324 : tmpzeta_il = 0.0_dp
325 : angular_il = 0.0_dp
326 : ELSE
327 59887084 : IF (grp_il%pack_use_int_zeta(sf)) THEN
328 59887084 : izeta_il = grp_il%pack_izeta(sf)
329 59887084 : tmpzeta_il = tmp_il**(izeta_il - 1)
330 : ELSE
331 0 : tmpzeta_il = tmp_il**(zeta_il - 1.0_dp)
332 : END IF
333 59887084 : angular_il = tmpzeta_il*tmp_il
334 : END IF
335 :
336 59887084 : symtmp_il = EXP(-eta_il*r2sum_il)
337 59887084 : sym_il = prefzeta_il*angular_il*symtmp_il*ftot_il
338 59887084 : nnp%ang(ind)%y(m - off) = nnp%ang(ind)%y(m - off) + sym_il
339 :
340 59887084 : pref_lam_il = zeta_il*tmpzeta_il*lam_il*inv_g2_il
341 59887084 : tmp_il = -2.0_dp*symtmp_il*eta_il
342 59887084 : dsymdr1_il = tmp_il*r1
343 59887084 : dsymdr2_il = tmp_il*r2
344 59887084 : dsymdr3_il = tmp_il*r3
345 :
346 59887084 : pref_il = prefzeta_il*symtmp_il*ftot_il
347 59887084 : tmp1_il = prefzeta_il*angular_il*(ftot_il*dsymdr1_il + dfcutdr1_il*symtmp_il)
348 59887084 : tmp2_il = prefzeta_il*angular_il*(ftot_il*dsymdr2_il + dfcutdr2_il*symtmp_il)
349 59887084 : tmp3_il = prefzeta_il*angular_il*(ftot_il*dsymdr3_il + dfcutdr3_il*symtmp_il)
350 :
351 239548336 : DO ii = 1, 3
352 : f_jj_il(ii) = pref_il*pref_lam_il*dcosbase2_il(ii) &
353 179661252 : - tmp1_il*dr1dx_il(ii) + tmp3_il*dr3dx_il(ii)
354 : f_kk_il(ii) = pref_il*pref_lam_il*dcosbase3_il(ii) &
355 179661252 : - tmp2_il*dr2dx_il(ii) - tmp3_il*dr3dx_il(ii)
356 : self_dGdr(ii, m) = self_dGdr(ii, m) &
357 : + pref_il*pref_lam_il*dcosbase1_il(ii) &
358 179661252 : + tmp1_il*dr1dx_il(ii) + tmp2_il*dr2dx_il(ii)
359 179661252 : jj_buf(ii, sf, j) = jj_buf(ii, sf, j) + f_jj_il(ii)
360 239548336 : kk_buf(ii, sf, k) = kk_buf(ii, sf, k) + f_kk_il(ii)
361 : END DO
362 71451823 : IF (PRESENT(stress)) THEN
363 29217640 : DO l = 1, 3
364 : stress(:, l, m) = stress(:, l, m) &
365 94957330 : - rvect1(:)*f_jj_il(l) - rvect2(:)*f_kk_il(l)
366 : END DO
367 : END IF
368 : END DO
369 :
370 : END IF
371 : END DO
372 : END DO
373 : END ASSOCIATE
374 : ELSE
375 : ! Precompute cutoff values for ang2 neighbors (different elements)
376 : CALL nnp_fill_fc_dfc_cache(neighbor%ang2(s)%dist, n_ang2_s, &
377 26913 : nnp%cut_type, cutoff_s, fc_cache2, dfc_cache2)
378 :
379 : ASSOCIATE (jj_buf => workspace%dGdr_ang_jj(s)%data, &
380 : kk_buf => workspace%dGdr_ang_kk(s)%data, &
381 : grp_il => nnp%ang(ind)%symfgrp(s))
382 1265047 : DO j = 1, n_ang1_s
383 4952536 : rvect1 = neighbor%ang1(s)%dist(1:3, j)
384 1238134 : r1 = neighbor%ang1(s)%dist(4, j)
385 44759548 : DO k = 1, n_ang2_s
386 173978004 : rvect2 = neighbor%ang2(s)%dist(1:3, k)
387 43494501 : r2 = neighbor%ang2(s)%dist(4, k)
388 43494501 : rvect3(1) = rvect2(1) - rvect1(1)
389 43494501 : rvect3(2) = rvect2(2) - rvect1(2)
390 43494501 : rvect3(3) = rvect2(3) - rvect1(3)
391 43494501 : r3_sqr = rvect3(1)*rvect3(1) + rvect3(2)*rvect3(2) + rvect3(3)*rvect3(3)
392 44732635 : IF (r3_sqr < cutoff_sqr) THEN
393 20226332 : r3 = SQRT(r3_sqr)
394 :
395 : ! -- per-triple geometry (once) --
396 20226332 : rsqr1 = r1*r1; rsqr2 = r2*r2; rsqr3 = r3*r3
397 20226332 : r2sum_il = rsqr1 + rsqr2 + rsqr3
398 20226332 : f_il = rsqr3 - rsqr1 - rsqr2
399 20226332 : g_il = -2.0_dp*r1*r2
400 20226332 : costheta_il = f_il/g_il
401 :
402 20230778 : SELECT CASE (nnp%cut_type)
403 : CASE (nnp_cut_cos)
404 4446 : arg_il = pi*r3/cutoff_s
405 4446 : fcut3_il = 0.5_dp*(COS(arg_il) + 1.0_dp)
406 4446 : dfcut3_il = -0.5_dp*SIN(arg_il)*(pi/cutoff_s)
407 : CASE (nnp_cut_tanh)
408 20221886 : tanh_il = TANH(1.0_dp - r3/cutoff_s)
409 20221886 : fcut3_il = tanh_il**3
410 20221886 : dfcut3_il = (-3.0_dp/cutoff_s)*(tanh_il**2 - tanh_il**4)
411 : CASE DEFAULT
412 20226332 : CPABORT("NNP| Cutoff function unknown")
413 : END SELECT
414 :
415 20226332 : ftot_il = fc_cache1(j)*fc_cache2(k)*fcut3_il
416 20226332 : dfcutdr1_il = dfc_cache1(j)*fc_cache2(k)*fcut3_il
417 20226332 : dfcutdr2_il = fc_cache1(j)*dfc_cache2(k)*fcut3_il
418 20226332 : dfcutdr3_il = fc_cache1(j)*fc_cache2(k)*dfcut3_il
419 :
420 20226332 : r1_inv = 1.0_dp/r1; r2_inv = 1.0_dp/r2; r3_inv = 1.0_dp/r3
421 80905328 : dr1dx_il(:) = rvect1(:)*r1_inv
422 80905328 : dr2dx_il(:) = rvect2(:)*r2_inv
423 80905328 : dr3dx_il(:) = rvect3(:)*r3_inv
424 :
425 20226332 : inv_g2_il = 1.0_dp/(g_il*g_il)
426 80905328 : DO ii = 1, 3
427 60678996 : dgdx_t1 = 2.0_dp*r2*dr1dx_il(ii)
428 60678996 : dgdx_t2 = 2.0_dp*r1*dr2dx_il(ii)
429 : dcosbase1_il(ii) = -2.0_dp*(rvect1(ii) + rvect2(ii))*g_il &
430 60678996 : - f_il*(-(dgdx_t1 + dgdx_t2))
431 : dcosbase2_il(ii) = 2.0_dp*(rvect3(ii) + rvect1(ii))*g_il &
432 60678996 : - f_il*dgdx_t1
433 : dcosbase3_il(ii) = 2.0_dp*(rvect2(ii) - rvect3(ii))*g_il &
434 80905328 : - f_il*dgdx_t2
435 : END DO
436 :
437 : ! -- fused SF loop: compute + direct scatter --
438 142017805 : DO sf = 1, n_symf_s
439 121791473 : m = off + grp_il%symf(sf)
440 121791473 : lam_il = grp_il%pack_lam(sf)
441 121791473 : zeta_il = grp_il%pack_zeta(sf)
442 121791473 : eta_il = grp_il%pack_eta(sf)
443 121791473 : prefzeta_il = grp_il%pack_prefzeta(sf)
444 :
445 121791473 : tmp_il = 1.0_dp + lam_il*costheta_il
446 121791473 : IF (tmp_il <= 0.0_dp) THEN
447 : tmpzeta_il = 0.0_dp
448 : angular_il = 0.0_dp
449 : ELSE
450 121791473 : IF (grp_il%pack_use_int_zeta(sf)) THEN
451 121791473 : izeta_il = grp_il%pack_izeta(sf)
452 121791473 : tmpzeta_il = tmp_il**(izeta_il - 1)
453 : ELSE
454 0 : tmpzeta_il = tmp_il**(zeta_il - 1.0_dp)
455 : END IF
456 121791473 : angular_il = tmpzeta_il*tmp_il
457 : END IF
458 :
459 121791473 : symtmp_il = EXP(-eta_il*r2sum_il)
460 121791473 : sym_il = prefzeta_il*angular_il*symtmp_il*ftot_il
461 121791473 : nnp%ang(ind)%y(m - off) = nnp%ang(ind)%y(m - off) + sym_il
462 :
463 121791473 : pref_lam_il = zeta_il*tmpzeta_il*lam_il*inv_g2_il
464 121791473 : tmp_il = -2.0_dp*symtmp_il*eta_il
465 121791473 : dsymdr1_il = tmp_il*r1
466 121791473 : dsymdr2_il = tmp_il*r2
467 121791473 : dsymdr3_il = tmp_il*r3
468 :
469 121791473 : pref_il = prefzeta_il*symtmp_il*ftot_il
470 121791473 : tmp1_il = prefzeta_il*angular_il*(ftot_il*dsymdr1_il + dfcutdr1_il*symtmp_il)
471 121791473 : tmp2_il = prefzeta_il*angular_il*(ftot_il*dsymdr2_il + dfcutdr2_il*symtmp_il)
472 121791473 : tmp3_il = prefzeta_il*angular_il*(ftot_il*dsymdr3_il + dfcutdr3_il*symtmp_il)
473 :
474 487165892 : DO ii = 1, 3
475 : f_jj_il(ii) = pref_il*pref_lam_il*dcosbase2_il(ii) &
476 365374419 : - tmp1_il*dr1dx_il(ii) + tmp3_il*dr3dx_il(ii)
477 : f_kk_il(ii) = pref_il*pref_lam_il*dcosbase3_il(ii) &
478 365374419 : - tmp2_il*dr2dx_il(ii) - tmp3_il*dr3dx_il(ii)
479 : self_dGdr(ii, m) = self_dGdr(ii, m) &
480 : + pref_il*pref_lam_il*dcosbase1_il(ii) &
481 365374419 : + tmp1_il*dr1dx_il(ii) + tmp2_il*dr2dx_il(ii)
482 365374419 : jj_buf(ii, sf, j) = jj_buf(ii, sf, j) + f_jj_il(ii)
483 487165892 : kk_buf(ii, sf, k) = kk_buf(ii, sf, k) + f_kk_il(ii)
484 : END DO
485 142017805 : IF (PRESENT(stress)) THEN
486 59415432 : DO l = 1, 3
487 : stress(:, l, m) = stress(:, l, m) &
488 193100154 : - rvect1(:)*f_jj_il(l) - rvect2(:)*f_kk_il(l)
489 : END DO
490 : END IF
491 : END DO
492 :
493 : END IF
494 : END DO
495 : END DO
496 : END ASSOCIATE
497 : END IF
498 : END DO
499 : END IF
500 23713 : CALL timestop(handle_sf)
501 : ELSE
502 : !loop over radial sym fnct grps
503 229072 : CALL timeset('nnp_acsf_radial', handle_sf)
504 794360 : DO s = 1, nnp%rad(ind)%n_symfgrp
505 : !loop over associated neighbors
506 2724876 : DO j = 1, neighbor%n_rad(s)
507 7722064 : rvect1 = neighbor%rad(s)%dist(1:3, j)
508 1930516 : r1 = neighbor%rad(s)%dist(4, j)
509 1930516 : CALL nnp_calc_rad(nnp, ind, s, rvect1, r1, radial_symtmp(1:nnp%rad(ind)%symfgrp(s)%n_symf))
510 17187322 : DO sf = 1, nnp%rad(ind)%symfgrp(s)%n_symf
511 14691518 : m = nnp%rad(ind)%symfgrp(s)%symf(sf)
512 16622034 : nnp%rad(ind)%y(m) = nnp%rad(ind)%y(m) + radial_symtmp(sf)
513 : END DO
514 : END DO
515 : END DO
516 229072 : CALL timestop(handle_sf)
517 :
518 : !loop over angular sym fnct grps
519 229072 : CALL timeset('nnp_acsf_angular', handle_sf)
520 229072 : off = nnp%n_rad(ind)
521 692144 : DO s = 1, nnp%ang(ind)%n_symfgrp
522 463072 : cutoff_s = nnp%ang(ind)%symfgrp(s)%cutoff
523 463072 : cutoff_sqr = cutoff_s*cutoff_s
524 463072 : n_symf_s = nnp%ang(ind)%symfgrp(s)%n_symf
525 463072 : n_ang1_s = neighbor%n_ang1(s)
526 :
527 : ! Precompute cutoff values for ang1 neighbors (no derivatives needed)
528 : CALL nnp_fill_fc_cache(neighbor%ang1(s)%dist, n_ang1_s, &
529 463072 : nnp%cut_type, cutoff_s, fc_cache1)
530 :
531 692144 : IF (nnp%ang(ind)%symfgrp(s)%ele(1) == nnp%ang(ind)%symfgrp(s)%ele(2)) THEN
532 1121116 : DO j = 1, n_ang1_s
533 3977040 : rvect1 = neighbor%ang1(s)%dist(1:3, j)
534 994260 : r1 = neighbor%ang1(s)%dist(4, j)
535 22659329 : DO k = j + 1, n_ang1_s
536 86152852 : rvect2 = neighbor%ang1(s)%dist(1:3, k)
537 21538213 : r2 = neighbor%ang1(s)%dist(4, k)
538 21538213 : rvect3(1) = rvect2(1) - rvect1(1)
539 21538213 : rvect3(2) = rvect2(2) - rvect1(2)
540 21538213 : rvect3(3) = rvect2(3) - rvect1(3)
541 21538213 : r3_sqr = rvect3(1)*rvect3(1) + rvect3(2)*rvect3(2) + rvect3(3)*rvect3(3)
542 22532473 : IF (r3_sqr < cutoff_sqr) THEN
543 9944571 : r3 = SQRT(r3_sqr)
544 : CALL nnp_calc_ang(nnp, ind, s, rvect1, rvect2, rvect3, r1, r2, r3, &
545 : fc_cache1(j), 0.0_dp, fc_cache1(k), 0.0_dp, &
546 9944571 : angular_symtmp(1:n_symf_s))
547 61315369 : DO sf = 1, n_symf_s
548 51370798 : m = off + nnp%ang(ind)%symfgrp(s)%symf(sf)
549 61315369 : nnp%ang(ind)%y(m - off) = nnp%ang(ind)%y(m - off) + angular_symtmp(sf)
550 : END DO
551 : END IF
552 : END DO
553 : END DO
554 : ELSE
555 : ! Precompute cutoff values for ang2 neighbors
556 336216 : n_ang2_s = neighbor%n_ang2(s)
557 : CALL nnp_fill_fc_cache(neighbor%ang2(s)%dist, n_ang2_s, &
558 336216 : nnp%cut_type, cutoff_s, fc_cache2)
559 :
560 1719216 : DO j = 1, n_ang1_s
561 5532000 : rvect1 = neighbor%ang1(s)%dist(1:3, j)
562 1383000 : r1 = neighbor%ang1(s)%dist(4, j)
563 39030025 : DO k = 1, n_ang2_s
564 149243236 : rvect2 = neighbor%ang2(s)%dist(1:3, k)
565 37310809 : r2 = neighbor%ang2(s)%dist(4, k)
566 37310809 : rvect3(1) = rvect2(1) - rvect1(1)
567 37310809 : rvect3(2) = rvect2(2) - rvect1(2)
568 37310809 : rvect3(3) = rvect2(3) - rvect1(3)
569 37310809 : r3_sqr = rvect3(1)*rvect3(1) + rvect3(2)*rvect3(2) + rvect3(3)*rvect3(3)
570 38693809 : IF (r3_sqr < cutoff_sqr) THEN
571 17429498 : r3 = SQRT(r3_sqr)
572 : CALL nnp_calc_ang(nnp, ind, s, rvect1, rvect2, rvect3, r1, r2, r3, &
573 : fc_cache1(j), 0.0_dp, fc_cache2(k), 0.0_dp, &
574 17429498 : angular_symtmp(1:n_symf_s))
575 121983157 : DO sf = 1, n_symf_s
576 104553659 : m = off + nnp%ang(ind)%symfgrp(s)%symf(sf)
577 121983157 : nnp%ang(ind)%y(m - off) = nnp%ang(ind)%y(m - off) + angular_symtmp(sf)
578 : END DO
579 : END IF
580 : END DO
581 : END DO
582 : END IF
583 : END DO
584 229072 : CALL timestop(handle_sf)
585 : END IF
586 :
587 : END ASSOCIATE
588 :
589 : ! fc_cache1/2 and dfc_cache1/2 are persistent workspace; nothing to deallocate here.
590 :
591 : END ASSOCIATE
592 :
593 : !check extrapolation
594 252785 : CALL nnp_check_extrapolation(nnp, ind)
595 :
596 252785 : IF (PRESENT(stress)) THEN
597 2112 : CALL nnp_scale_acsf(nnp, ind, do_forces, stress)
598 : ELSE
599 250673 : CALL nnp_scale_acsf(nnp, ind, do_forces)
600 : END IF
601 :
602 252785 : CALL timestop(handle)
603 :
604 252785 : END SUBROUTINE nnp_calc_acsf
605 :
606 : ! **************************************************************************************************
607 : !> \brief Fill the per-neighbour fc and dfc cutoff-function caches used by the
608 : !> force branch of the angular ACSF kernel.
609 : !> \param dist (4, :) neighbour array; column 4 holds the scalar distance
610 : !> \param n number of neighbors to process
611 : !> \param cut_type cutoff function selector (nnp_cut_cos / nnp_cut_tanh)
612 : !> \param cutoff_s per-group cutoff radius
613 : !> \param fc_cache output fc values, sized >= n
614 : !> \param dfc_cache output dfc values, sized >= n
615 : ! **************************************************************************************************
616 80110 : PURE SUBROUTINE nnp_fill_fc_dfc_cache(dist, n, cut_type, cutoff_s, fc_cache, dfc_cache)
617 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: dist
618 : INTEGER, INTENT(IN) :: n, cut_type
619 : REAL(KIND=dp), INTENT(IN) :: cutoff_s
620 : REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: fc_cache, dfc_cache
621 :
622 : INTEGER :: j
623 : REAL(KIND=dp) :: arg_tmp, r_tmp, tanh_tmp
624 :
625 2960552 : DO j = 1, n
626 2880442 : r_tmp = dist(4, j)
627 80110 : SELECT CASE (cut_type)
628 : CASE (nnp_cut_cos)
629 17790 : arg_tmp = pi*r_tmp/cutoff_s
630 17790 : fc_cache(j) = 0.5_dp*(COS(arg_tmp) + 1.0_dp)
631 17790 : dfc_cache(j) = -0.5_dp*SIN(arg_tmp)*(pi/cutoff_s)
632 : CASE (nnp_cut_tanh)
633 2862652 : tanh_tmp = TANH(1.0_dp - r_tmp/cutoff_s)
634 2862652 : fc_cache(j) = tanh_tmp**3
635 2880442 : dfc_cache(j) = (-3.0_dp/cutoff_s)*(tanh_tmp**2 - tanh_tmp**4)
636 : END SELECT
637 : END DO
638 :
639 80110 : END SUBROUTINE nnp_fill_fc_dfc_cache
640 :
641 : ! **************************************************************************************************
642 : !> \brief Fill the per-neighbour fc cutoff-function cache for the sym-only
643 : !> (no-forces) branch of the angular ACSF kernel. Derivatives are not needed.
644 : !> \param dist (4, :) neighbour array; column 4 holds the scalar distance
645 : !> \param n number of neighbors to process
646 : !> \param cut_type cutoff function selector (nnp_cut_cos / nnp_cut_tanh)
647 : !> \param cutoff_s per-group cutoff radius
648 : !> \param fc_cache output fc values, sized >= n
649 : ! **************************************************************************************************
650 799288 : PURE SUBROUTINE nnp_fill_fc_cache(dist, n, cut_type, cutoff_s, fc_cache)
651 : REAL(KIND=dp), DIMENSION(:, :), INTENT(IN) :: dist
652 : INTEGER, INTENT(IN) :: n, cut_type
653 : REAL(KIND=dp), INTENT(IN) :: cutoff_s
654 : REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: fc_cache
655 :
656 : INTEGER :: j
657 : REAL(KIND=dp) :: r_tmp, tanh_tmp
658 :
659 3831208 : DO j = 1, n
660 3031920 : r_tmp = dist(4, j)
661 799288 : SELECT CASE (cut_type)
662 : CASE (nnp_cut_cos)
663 584048 : fc_cache(j) = 0.5_dp*(COS(pi*r_tmp/cutoff_s) + 1.0_dp)
664 : CASE (nnp_cut_tanh)
665 2447872 : tanh_tmp = TANH(1.0_dp - r_tmp/cutoff_s)
666 3031920 : fc_cache(j) = tanh_tmp**3
667 : END SELECT
668 : END DO
669 :
670 799288 : END SUBROUTINE nnp_fill_fc_cache
671 :
672 : ! **************************************************************************************************
673 : !> \brief OpenMP parallelization of the angular SF group loop over s. Groups
674 : !> partition the angular SF indices disjointly, so per-group writes into
675 : !> self_dGdr / stress / nnp%ang%y and the workspace dGdr_ang accumulators
676 : !> are race-free. Per-thread scratch is PRIVATE automatic arrays; buffer
677 : !> growth and zero-init run in a serial pre-pass so the parallel region
678 : !> never touches ALLOCATABLE state.
679 : !> \param nnp ...
680 : !> \param ind ...
681 : !> \param self_dGdr ...
682 : !> \param off ...
683 : !> \param stress ...
684 : ! **************************************************************************************************
685 0 : SUBROUTINE nnp_acsf_angular_loop_omp(nnp, ind, self_dGdr, off, stress)
686 : TYPE(nnp_type), INTENT(INOUT), POINTER :: nnp
687 : INTEGER, INTENT(IN) :: ind
688 : REAL(KIND=dp), DIMENSION(:, :), INTENT(INOUT) :: self_dGdr
689 : INTEGER, INTENT(IN) :: off
690 : REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT), &
691 : OPTIONAL :: stress
692 :
693 : INTEGER :: cache_cap_loc, j, k, l, m, &
694 : max_ang_symf_loc, n_ang1_s, n_ang2_s, &
695 : n_symf_s, s, sf
696 : LOGICAL :: homo_grp
697 : REAL(KIND=dp) :: cutoff_s, cutoff_sqr, r1, r2, r3, r3_sqr
698 0 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:) :: dfc_c1_loc, dfc_c2_loc, fc_c1_loc, &
699 0 : fc_c2_loc, sym_loc
700 0 : REAL(KIND=dp), ALLOCATABLE, DIMENSION(:, :, :) :: force_loc
701 : REAL(KIND=dp), DIMENSION(3) :: rvect1, rvect2, rvect3
702 :
703 : ! Per-thread automatic scratch (see PRIVATE clause below). Sizes
704 : ! are pulled from the pre-sized workspace so they match the peak
705 : ! usage that the serial kernel would allocate into the workspace
706 : ! %fc_cache*/angular_* slabs.
707 :
708 : ASSOCIATE (workspace => nnp%neighbor_interface_state%workspace(ind), &
709 : neighbor => nnp%neighbor_interface_state%workspace(ind)%neighbor)
710 :
711 0 : cache_cap_loc = MAX(1, workspace%cache_cap)
712 0 : max_ang_symf_loc = MAX(1, workspace%max_ang_symf)
713 :
714 : ! Serial pre-pass: grow per-group accumulator slabs and zero them
715 : ! out. Doing this outside the parallel region guarantees no thread
716 : ! ever touches ALLOCATABLE components of nnp_dGdr_grp_type.
717 0 : DO s = 1, nnp%ang(ind)%n_symfgrp
718 0 : n_symf_s = nnp%ang(ind)%symfgrp(s)%n_symf
719 0 : n_ang1_s = neighbor%n_ang1(s)
720 0 : homo_grp = (nnp%ang(ind)%symfgrp(s)%ele(1) == nnp%ang(ind)%symfgrp(s)%ele(2))
721 0 : CALL nnp_grp_grow_dGdr(workspace%dGdr_ang_jj(s), n_ang1_s)
722 0 : IF (homo_grp) THEN
723 0 : CALL nnp_grp_grow_dGdr(workspace%dGdr_ang_kk(s), n_ang1_s)
724 0 : n_ang2_s = 0
725 : ELSE
726 0 : n_ang2_s = neighbor%n_ang2(s)
727 0 : CALL nnp_grp_grow_dGdr(workspace%dGdr_ang_kk(s), n_ang2_s)
728 : END IF
729 0 : IF (n_ang1_s > 0) workspace%dGdr_ang_jj(s)%data(:, 1:n_symf_s, 1:n_ang1_s) = 0.0_dp
730 0 : IF (homo_grp) THEN
731 0 : IF (n_ang1_s > 0) workspace%dGdr_ang_kk(s)%data(:, 1:n_symf_s, 1:n_ang1_s) = 0.0_dp
732 : ELSE
733 0 : IF (n_ang2_s > 0) workspace%dGdr_ang_kk(s)%data(:, 1:n_symf_s, 1:n_ang2_s) = 0.0_dp
734 : END IF
735 : END DO
736 :
737 : ! Each thread allocates its own PRIVATE scratch inside the parallel
738 : ! region: an ALLOCATABLE listed as PRIVATE enters unallocated per
739 : ! thread, so the ALLOCATE below gives each thread an independent slab.
740 : ! Angular groups partition the SF index disjointly, so the shared
741 : ! accumulators (self_dGdr, stress, nnp%ang%y, workspace dGdr) are written
742 : ! race-free. workspace/neighbor are ASSOCIATE names that inherit the
743 : ! data-sharing of their nnp selector; the OPTIONAL stress is SHARED and
744 : ! gated by PRESENT().
745 : !$OMP PARALLEL DEFAULT(NONE) &
746 : !$OMP SHARED(nnp, ind, self_dGdr, off, stress, &
747 : !$OMP cache_cap_loc, max_ang_symf_loc) &
748 : !$OMP PRIVATE(s, j, k, sf, m, l, n_symf_s, n_ang1_s, n_ang2_s, &
749 : !$OMP r1, r2, r3, r3_sqr, cutoff_s, cutoff_sqr, homo_grp, &
750 : !$OMP rvect1, rvect2, rvect3, &
751 : !$OMP fc_c1_loc, dfc_c1_loc, fc_c2_loc, dfc_c2_loc, &
752 0 : !$OMP sym_loc, force_loc)
753 : ALLOCATE (fc_c1_loc(cache_cap_loc))
754 : ALLOCATE (dfc_c1_loc(cache_cap_loc))
755 : ALLOCATE (fc_c2_loc(cache_cap_loc))
756 : ALLOCATE (dfc_c2_loc(cache_cap_loc))
757 : ALLOCATE (sym_loc(max_ang_symf_loc))
758 : ALLOCATE (force_loc(3, 3, max_ang_symf_loc))
759 :
760 : !$OMP DO SCHEDULE(dynamic)
761 : DO s = 1, nnp%ang(ind)%n_symfgrp
762 : cutoff_s = nnp%ang(ind)%symfgrp(s)%cutoff
763 : cutoff_sqr = cutoff_s*cutoff_s
764 : n_symf_s = nnp%ang(ind)%symfgrp(s)%n_symf
765 : n_ang1_s = neighbor%n_ang1(s)
766 : homo_grp = (nnp%ang(ind)%symfgrp(s)%ele(1) == nnp%ang(ind)%symfgrp(s)%ele(2))
767 :
768 : CALL nnp_fill_fc_dfc_cache(neighbor%ang1(s)%dist, n_ang1_s, &
769 : nnp%cut_type, cutoff_s, fc_c1_loc, dfc_c1_loc)
770 :
771 : IF (homo_grp) THEN
772 : DO j = 1, n_ang1_s
773 : rvect1 = neighbor%ang1(s)%dist(1:3, j)
774 : r1 = neighbor%ang1(s)%dist(4, j)
775 : DO k = j + 1, n_ang1_s
776 : rvect2 = neighbor%ang1(s)%dist(1:3, k)
777 : r2 = neighbor%ang1(s)%dist(4, k)
778 : rvect3(1) = rvect2(1) - rvect1(1)
779 : rvect3(2) = rvect2(2) - rvect1(2)
780 : rvect3(3) = rvect2(3) - rvect1(3)
781 : r3_sqr = rvect3(1)*rvect3(1) + rvect3(2)*rvect3(2) + rvect3(3)*rvect3(3)
782 : IF (r3_sqr < cutoff_sqr) THEN
783 : r3 = SQRT(r3_sqr)
784 : CALL nnp_calc_ang(nnp, ind, s, rvect1, rvect2, rvect3, &
785 : r1, r2, r3, &
786 : fc_c1_loc(j), dfc_c1_loc(j), &
787 : fc_c1_loc(k), dfc_c1_loc(k), &
788 : sym_loc(1:n_symf_s), &
789 : force_loc(:, :, 1:n_symf_s))
790 : DO sf = 1, n_symf_s
791 : m = off + nnp%ang(ind)%symfgrp(s)%symf(sf)
792 : self_dGdr(1, m) = self_dGdr(1, m) + force_loc(1, 1, sf)
793 : self_dGdr(2, m) = self_dGdr(2, m) + force_loc(2, 1, sf)
794 : self_dGdr(3, m) = self_dGdr(3, m) + force_loc(3, 1, sf)
795 : workspace%dGdr_ang_jj(s)%data(1, sf, j) = workspace%dGdr_ang_jj(s)%data(1, sf, j) + force_loc(1, 2, sf)
796 : workspace%dGdr_ang_jj(s)%data(2, sf, j) = workspace%dGdr_ang_jj(s)%data(2, sf, j) + force_loc(2, 2, sf)
797 : workspace%dGdr_ang_jj(s)%data(3, sf, j) = workspace%dGdr_ang_jj(s)%data(3, sf, j) + force_loc(3, 2, sf)
798 : workspace%dGdr_ang_kk(s)%data(1, sf, k) = workspace%dGdr_ang_kk(s)%data(1, sf, k) + force_loc(1, 3, sf)
799 : workspace%dGdr_ang_kk(s)%data(2, sf, k) = workspace%dGdr_ang_kk(s)%data(2, sf, k) + force_loc(2, 3, sf)
800 : workspace%dGdr_ang_kk(s)%data(3, sf, k) = workspace%dGdr_ang_kk(s)%data(3, sf, k) + force_loc(3, 3, sf)
801 : IF (PRESENT(stress)) THEN
802 : DO l = 1, 3
803 : stress(:, l, m) = stress(:, l, m) - rvect1(:)*force_loc(l, 2, sf)
804 : stress(:, l, m) = stress(:, l, m) - rvect2(:)*force_loc(l, 3, sf)
805 : END DO
806 : END IF
807 : nnp%ang(ind)%y(m - off) = nnp%ang(ind)%y(m - off) + sym_loc(sf)
808 : END DO
809 : END IF
810 : END DO
811 : END DO
812 : ELSE
813 : n_ang2_s = neighbor%n_ang2(s)
814 : CALL nnp_fill_fc_dfc_cache(neighbor%ang2(s)%dist, n_ang2_s, &
815 : nnp%cut_type, cutoff_s, fc_c2_loc, dfc_c2_loc)
816 :
817 : DO j = 1, n_ang1_s
818 : rvect1 = neighbor%ang1(s)%dist(1:3, j)
819 : r1 = neighbor%ang1(s)%dist(4, j)
820 : DO k = 1, n_ang2_s
821 : rvect2 = neighbor%ang2(s)%dist(1:3, k)
822 : r2 = neighbor%ang2(s)%dist(4, k)
823 : rvect3(1) = rvect2(1) - rvect1(1)
824 : rvect3(2) = rvect2(2) - rvect1(2)
825 : rvect3(3) = rvect2(3) - rvect1(3)
826 : r3_sqr = rvect3(1)*rvect3(1) + rvect3(2)*rvect3(2) + rvect3(3)*rvect3(3)
827 : IF (r3_sqr < cutoff_sqr) THEN
828 : r3 = SQRT(r3_sqr)
829 : CALL nnp_calc_ang(nnp, ind, s, rvect1, rvect2, rvect3, &
830 : r1, r2, r3, &
831 : fc_c1_loc(j), dfc_c1_loc(j), &
832 : fc_c2_loc(k), dfc_c2_loc(k), &
833 : sym_loc(1:n_symf_s), &
834 : force_loc(:, :, 1:n_symf_s))
835 : DO sf = 1, n_symf_s
836 : m = off + nnp%ang(ind)%symfgrp(s)%symf(sf)
837 : self_dGdr(1, m) = self_dGdr(1, m) + force_loc(1, 1, sf)
838 : self_dGdr(2, m) = self_dGdr(2, m) + force_loc(2, 1, sf)
839 : self_dGdr(3, m) = self_dGdr(3, m) + force_loc(3, 1, sf)
840 : workspace%dGdr_ang_jj(s)%data(1, sf, j) = workspace%dGdr_ang_jj(s)%data(1, sf, j) + force_loc(1, 2, sf)
841 : workspace%dGdr_ang_jj(s)%data(2, sf, j) = workspace%dGdr_ang_jj(s)%data(2, sf, j) + force_loc(2, 2, sf)
842 : workspace%dGdr_ang_jj(s)%data(3, sf, j) = workspace%dGdr_ang_jj(s)%data(3, sf, j) + force_loc(3, 2, sf)
843 : workspace%dGdr_ang_kk(s)%data(1, sf, k) = workspace%dGdr_ang_kk(s)%data(1, sf, k) + force_loc(1, 3, sf)
844 : workspace%dGdr_ang_kk(s)%data(2, sf, k) = workspace%dGdr_ang_kk(s)%data(2, sf, k) + force_loc(2, 3, sf)
845 : workspace%dGdr_ang_kk(s)%data(3, sf, k) = workspace%dGdr_ang_kk(s)%data(3, sf, k) + force_loc(3, 3, sf)
846 : IF (PRESENT(stress)) THEN
847 : DO l = 1, 3
848 : stress(:, l, m) = stress(:, l, m) - rvect1(:)*force_loc(l, 2, sf)
849 : stress(:, l, m) = stress(:, l, m) - rvect2(:)*force_loc(l, 3, sf)
850 : END DO
851 : END IF
852 : nnp%ang(ind)%y(m - off) = nnp%ang(ind)%y(m - off) + sym_loc(sf)
853 : END DO
854 : END IF
855 : END DO
856 : END DO
857 : END IF
858 : END DO
859 : !$OMP END DO
860 :
861 : DEALLOCATE (fc_c1_loc, dfc_c1_loc, fc_c2_loc, dfc_c2_loc, sym_loc, force_loc)
862 : !$OMP END PARALLEL
863 :
864 : END ASSOCIATE
865 :
866 0 : END SUBROUTINE nnp_acsf_angular_loop_omp
867 :
868 : ! **************************************************************************************************
869 : !> \brief Prepare or update the linked-cell / Verlet cache for the current
870 : !> geometry. Lazily allocates nnp%cell_list_cache and
871 : !> nnp%neighbor_interface_state, then delegates to
872 : !> nnp_prepare_cell_list_cache and nnp_neighbor_interface_prepare. Call
873 : !> once per force eval before the per-atom loop; cheap on re-entry when
874 : !> nothing has changed.
875 : !> \param nnp NNP environment whose persistent caches are to be (re)built.
876 : !> \author Dhruv Sharma (ds2173@cam.ac.uk)
877 : ! **************************************************************************************************
878 55492 : SUBROUTINE nnp_prepare_neighbor_cache(nnp)
879 :
880 : TYPE(nnp_type), INTENT(INOUT), POINTER :: nnp
881 :
882 56546 : IF (.NOT. ALLOCATED(nnp%cell_list_cache)) ALLOCATE (nnp%cell_list_cache)
883 55492 : IF (.NOT. ALLOCATED(nnp%neighbor_interface_state)) ALLOCATE (nnp%neighbor_interface_state)
884 55492 : CALL nnp_prepare_cell_list_cache(nnp)
885 55492 : CALL nnp_neighbor_interface_prepare(nnp)
886 :
887 55492 : END SUBROUTINE nnp_prepare_neighbor_cache
888 :
889 : ! **************************************************************************************************
890 : !> \brief Check if the nnp is extrapolating
891 : !> \param nnp ...
892 : !> \param ind ...
893 : !> \date 2020-10-10
894 : !> \author Christoph Schran (christoph.schran@rub.de)
895 : ! **************************************************************************************************
896 252785 : SUBROUTINE nnp_check_extrapolation(nnp, ind)
897 :
898 : TYPE(nnp_type), INTENT(INOUT) :: nnp
899 : INTEGER, INTENT(IN) :: ind
900 :
901 : REAL(KIND=dp), PARAMETER :: threshold = 0.0001_dp
902 :
903 : INTEGER :: j
904 : LOGICAL :: extrapolate
905 :
906 252785 : extrapolate = nnp%output_expol
907 :
908 3800797 : DO j = 1, nnp%n_rad(ind)
909 3800797 : IF (nnp%rad(ind)%y(j) - nnp%rad(ind)%loc_max(j) > threshold) THEN
910 : extrapolate = .TRUE.
911 3548012 : ELSE IF (-nnp%rad(ind)%y(j) + nnp%rad(ind)%loc_min(j) > threshold) THEN
912 164 : extrapolate = .TRUE.
913 : END IF
914 : END DO
915 2127593 : DO j = 1, nnp%n_ang(ind)
916 2127593 : IF (nnp%ang(ind)%y(j) - nnp%ang(ind)%loc_max(j) > threshold) THEN
917 : extrapolate = .TRUE.
918 1874808 : ELSE IF (-nnp%ang(ind)%y(j) + nnp%ang(ind)%loc_min(j) > threshold) THEN
919 30 : extrapolate = .TRUE.
920 : END IF
921 : END DO
922 :
923 252785 : nnp%output_expol = extrapolate
924 :
925 252785 : END SUBROUTINE nnp_check_extrapolation
926 :
927 : ! **************************************************************************************************
928 : !> \brief Scale and center symmetry functions (and gradients)
929 : !> \param nnp ...
930 : !> \param ind ...
931 : !> \param do_forces ...
932 : !> \param stress ...
933 : !> \date 2020-10-10
934 : !> \author Christoph Schran (christoph.schran@rub.de)
935 : ! **************************************************************************************************
936 252785 : SUBROUTINE nnp_scale_acsf(nnp, ind, do_forces, stress)
937 :
938 : TYPE(nnp_type), INTENT(INOUT) :: nnp
939 : INTEGER, INTENT(IN) :: ind
940 : LOGICAL, INTENT(IN) :: do_forces
941 : REAL(KIND=dp), DIMENSION(:, :, :), INTENT(INOUT), &
942 : OPTIONAL :: stress
943 :
944 : INTEGER :: j, k, m, n_ang1_s, n_ang2_s, n_symf_s, &
945 : off, s, sf
946 : LOGICAL :: homo_grp
947 : REAL(KIND=dp) :: scale
948 :
949 : ! INOUT (not OUT): stress is per-input-node and accumulates across central atoms.
950 :
951 252785 : IF (nnp%center_acsf) THEN
952 3800797 : DO j = 1, nnp%n_rad(ind)
953 3800797 : nnp%arc(ind)%layer(1)%node(j) = nnp%rad(ind)%y(j) - nnp%rad(ind)%loc_av(j)
954 : END DO
955 252785 : off = nnp%n_rad(ind)
956 2127593 : DO j = 1, nnp%n_ang(ind)
957 2127593 : nnp%arc(ind)%layer(1)%node(j + off) = nnp%ang(ind)%y(j) - nnp%ang(ind)%loc_av(j)
958 : END DO
959 :
960 252785 : IF (nnp%scale_acsf) THEN
961 3800797 : DO j = 1, nnp%n_rad(ind)
962 : nnp%arc(ind)%layer(1)%node(j) = nnp%arc(ind)%layer(1)%node(j)/ &
963 3800797 : (nnp%rad(ind)%loc_max(j) - nnp%rad(ind)%loc_min(j))*(nnp%scmax - nnp%scmin) + nnp%scmin
964 : END DO
965 252785 : off = nnp%n_rad(ind)
966 2127593 : DO j = 1, nnp%n_ang(ind)
967 : nnp%arc(ind)%layer(1)%node(j + off) = nnp%arc(ind)%layer(1)%node(j + off)/ &
968 2127593 : (nnp%ang(ind)%loc_max(j) - nnp%ang(ind)%loc_min(j))*(nnp%scmax - nnp%scmin) + nnp%scmin
969 : END DO
970 : END IF
971 0 : ELSE IF (nnp%scale_acsf) THEN
972 0 : DO j = 1, nnp%n_rad(ind)
973 : nnp%arc(ind)%layer(1)%node(j) = (nnp%rad(ind)%y(j) - nnp%rad(ind)%loc_min(j))/ &
974 : (nnp%rad(ind)%loc_max(j) - nnp%rad(ind)%loc_min(j))* &
975 0 : (nnp%scmax - nnp%scmin) + nnp%scmin
976 : END DO
977 0 : off = nnp%n_rad(ind)
978 0 : DO j = 1, nnp%n_ang(ind)
979 : nnp%arc(ind)%layer(1)%node(j + off) = (nnp%ang(ind)%y(j) - nnp%ang(ind)%loc_min(j))/ &
980 : (nnp%ang(ind)%loc_max(j) - nnp%ang(ind)%loc_min(j))* &
981 0 : (nnp%scmax - nnp%scmin) + nnp%scmin
982 : END DO
983 0 : ELSE IF (nnp%scale_sigma_acsf) THEN
984 0 : DO j = 1, nnp%n_rad(ind)
985 : nnp%arc(ind)%layer(1)%node(j) = (nnp%rad(ind)%y(j) - nnp%rad(ind)%loc_av(j))/ &
986 0 : nnp%rad(ind)%sigma(j)*(nnp%scmax - nnp%scmin) + nnp%scmin
987 : END DO
988 0 : off = nnp%n_rad(ind)
989 0 : DO j = 1, nnp%n_ang(ind)
990 : nnp%arc(ind)%layer(1)%node(j + off) = (nnp%ang(ind)%y(j) - nnp%ang(ind)%loc_av(j))/ &
991 0 : nnp%ang(ind)%sigma(j)*(nnp%scmax - nnp%scmin) + nnp%scmin
992 : END DO
993 : ELSE
994 0 : DO j = 1, nnp%n_rad(ind)
995 0 : nnp%arc(ind)%layer(1)%node(j) = nnp%rad(ind)%y(j)
996 : END DO
997 0 : off = nnp%n_rad(ind)
998 0 : DO j = 1, nnp%n_ang(ind)
999 0 : nnp%arc(ind)%layer(1)%node(j + off) = nnp%ang(ind)%y(j)
1000 : END DO
1001 : END IF
1002 :
1003 252785 : IF (do_forces .AND. (nnp%scale_acsf .OR. nnp%scale_sigma_acsf)) THEN
1004 : ! Scale the per-neighbor dGdr slabs in workspace state. Only the
1005 : ! actually-populated entries in each slab need touching: for each
1006 : ! group s the valid ranges are sf=1..n_symf_s and j=1..n_rad(s)
1007 : ! (radial) or j=1..n_ang1_s, k=1..n_ang2_s (angular). The self
1008 : ! contribution lives in self_dGdr(:, m).
1009 : ASSOCIATE (workspace => nnp%neighbor_interface_state%workspace(ind), &
1010 : neighbor => nnp%neighbor_interface_state%workspace(ind)%neighbor, &
1011 : self_dGdr => nnp%neighbor_interface_state%workspace(ind)%self_dGdr)
1012 :
1013 : ! Radial groups
1014 74339 : DO s = 1, nnp%rad(ind)%n_symfgrp
1015 50626 : n_symf_s = nnp%rad(ind)%symfgrp(s)%n_symf
1016 23713 : ASSOCIATE (rad_buf => workspace%dGdr_rad(s)%data)
1017 415634 : DO sf = 1, n_symf_s
1018 365008 : m = nnp%rad(ind)%symfgrp(s)%symf(sf)
1019 365008 : IF (nnp%scale_acsf) THEN
1020 : scale = (nnp%scmax - nnp%scmin)/ &
1021 365008 : (nnp%rad(ind)%loc_max(m) - nnp%rad(ind)%loc_min(m))
1022 : ELSE
1023 0 : scale = (nnp%scmax - nnp%scmin)/nnp%rad(ind)%sigma(m)
1024 : END IF
1025 365008 : self_dGdr(1, m) = self_dGdr(1, m)*scale
1026 365008 : self_dGdr(2, m) = self_dGdr(2, m)*scale
1027 365008 : self_dGdr(3, m) = self_dGdr(3, m)*scale
1028 15193854 : DO j = 1, neighbor%n_rad(s)
1029 14778220 : rad_buf(1, sf, j) = rad_buf(1, sf, j)*scale
1030 14778220 : rad_buf(2, sf, j) = rad_buf(2, sf, j)*scale
1031 15143228 : rad_buf(3, sf, j) = rad_buf(3, sf, j)*scale
1032 : END DO
1033 : END DO
1034 : END ASSOCIATE
1035 : END DO
1036 :
1037 : ! Angular groups
1038 23713 : off = nnp%n_rad(ind)
1039 100623 : DO s = 1, nnp%ang(ind)%n_symfgrp
1040 53197 : n_symf_s = nnp%ang(ind)%symfgrp(s)%n_symf
1041 53197 : n_ang1_s = neighbor%n_ang1(s)
1042 53197 : homo_grp = (nnp%ang(ind)%symfgrp(s)%ele(1) == nnp%ang(ind)%symfgrp(s)%ele(2))
1043 53197 : IF (homo_grp) THEN
1044 : ! kk slab is also indexed in ind_ang1 in the homo case
1045 : n_ang2_s = n_ang1_s
1046 : ELSE
1047 26913 : n_ang2_s = neighbor%n_ang2(s)
1048 : END IF
1049 : ASSOCIATE (jj_buf => workspace%dGdr_ang_jj(s)%data, &
1050 23713 : kk_buf => workspace%dGdr_ang_kk(s)%data)
1051 304153 : DO sf = 1, n_symf_s
1052 250956 : m = off + nnp%ang(ind)%symfgrp(s)%symf(sf)
1053 250956 : IF (nnp%scale_acsf) THEN
1054 : scale = (nnp%scmax - nnp%scmin)/ &
1055 250956 : (nnp%ang(ind)%loc_max(m - off) - nnp%ang(ind)%loc_min(m - off))
1056 : ELSE
1057 0 : scale = (nnp%scmax - nnp%scmin)/nnp%ang(ind)%sigma(m - off)
1058 : END IF
1059 250956 : self_dGdr(1, m) = self_dGdr(1, m)*scale
1060 250956 : self_dGdr(2, m) = self_dGdr(2, m)*scale
1061 250956 : self_dGdr(3, m) = self_dGdr(3, m)*scale
1062 12576456 : DO j = 1, n_ang1_s
1063 12325500 : jj_buf(1, sf, j) = jj_buf(1, sf, j)*scale
1064 12325500 : jj_buf(2, sf, j) = jj_buf(2, sf, j)*scale
1065 12576456 : jj_buf(3, sf, j) = jj_buf(3, sf, j)*scale
1066 : END DO
1067 8927767 : DO k = 1, n_ang2_s
1068 8623614 : kk_buf(1, sf, k) = kk_buf(1, sf, k)*scale
1069 8623614 : kk_buf(2, sf, k) = kk_buf(2, sf, k)*scale
1070 8874570 : kk_buf(3, sf, k) = kk_buf(3, sf, k)*scale
1071 : END DO
1072 : END DO
1073 : END ASSOCIATE
1074 : END DO
1075 :
1076 : END ASSOCIATE
1077 : END IF
1078 :
1079 252785 : IF (PRESENT(stress)) THEN
1080 2112 : IF (nnp%scale_acsf) THEN
1081 35904 : DO j = 1, nnp%n_rad(ind)
1082 : stress(:, :, j) = stress(:, :, j)/(nnp%rad(ind)%loc_max(j) - nnp%rad(ind)%loc_min(j))* &
1083 441408 : (nnp%scmax - nnp%scmin)
1084 : END DO
1085 2112 : off = nnp%n_rad(ind)
1086 27456 : DO j = 1, nnp%n_ang(ind)
1087 : stress(:, :, j + off) = stress(:, :, j + off)/ &
1088 : (nnp%ang(ind)%loc_max(j) - nnp%ang(ind)%loc_min(j))* &
1089 331584 : (nnp%scmax - nnp%scmin)
1090 : END DO
1091 0 : ELSE IF (nnp%scale_sigma_acsf) THEN
1092 0 : DO j = 1, nnp%n_rad(ind)
1093 0 : stress(:, :, j) = stress(:, :, j)/nnp%rad(ind)%sigma(j)*(nnp%scmax - nnp%scmin)
1094 : END DO
1095 0 : off = nnp%n_rad(ind)
1096 0 : DO j = 1, nnp%n_ang(ind)
1097 0 : stress(:, :, j + off) = stress(:, :, j + off)/nnp%ang(ind)%sigma(j)*(nnp%scmax - nnp%scmin)
1098 : END DO
1099 : END IF
1100 : END IF
1101 :
1102 252785 : END SUBROUTINE nnp_scale_acsf
1103 :
1104 : ! **************************************************************************************************
1105 : !> \brief Calculate radial symmetry function and gradient (optional)
1106 : !> \param nnp ...
1107 : !> \param ind ...
1108 : !> \param s ...
1109 : !> \param rvect ...
1110 : !> \param r ...
1111 : !> \param sym ...
1112 : !> \param force ...
1113 : !> \date 2020-10-10
1114 : !> \author Christoph Schran (christoph.schran@rub.de)
1115 : ! **************************************************************************************************
1116 3780702 : SUBROUTINE nnp_calc_rad(nnp, ind, s, rvect, r, sym, force)
1117 :
1118 : TYPE(nnp_type), INTENT(IN), TARGET :: nnp
1119 : INTEGER, INTENT(IN) :: ind, s
1120 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: rvect
1121 : REAL(KIND=dp), INTENT(IN) :: r
1122 : REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: sym
1123 : REAL(KIND=dp), DIMENSION(:, :), INTENT(OUT), &
1124 : OPTIONAL :: force
1125 :
1126 : INTEGER :: i, n_symf, sf
1127 : REAL(KIND=dp) :: dh00, dh01, dh10, dh11, drdx_x, drdx_y, &
1128 : drdx_z, dsymdr_full, dyi, dyi1, h00, &
1129 : h01, h10, h10_dx, h11, h11_dx, r_inv, &
1130 : t, t2, t3, yi, yi1
1131 : TYPE(nnp_symfgrp_type), POINTER :: grp
1132 :
1133 3780702 : grp => nnp%rad(ind)%symfgrp(s)
1134 3780702 : n_symf = grp%n_symf
1135 :
1136 : ! Group-shared Hermite cubic spline. All SFs in this radial group share
1137 : ! grp%cutoff, hence the same uniform grid, so the interpolation parameters
1138 : ! (i, t, h00..h11, dh00..dh11) are computed once outside the SF loop; the
1139 : ! inner loop then streams 4 contiguous reads per SF from spline_y/spline_dy.
1140 : !
1141 : ! Out-of-range clamping: for r past spline_x_max (= grp%cutoff) every SF
1142 : ! sees the boundary value of its tabulated y(r), which the build routine
1143 : ! pinned to 0 since fcut(cutoff) = 0, so the clamp returns sym = 0.
1144 3780702 : IF (r >= grp%spline_x_max) THEN
1145 0 : DO sf = 1, n_symf
1146 0 : sym(sf) = 0.0_dp
1147 : END DO
1148 0 : IF (PRESENT(force)) THEN
1149 0 : DO sf = 1, n_symf
1150 0 : force(1, sf) = 0.0_dp
1151 0 : force(2, sf) = 0.0_dp
1152 0 : force(3, sf) = 0.0_dp
1153 : END DO
1154 : END IF
1155 0 : RETURN
1156 : END IF
1157 :
1158 3780702 : i = INT(r*grp%spline_dx_inv) + 1
1159 3780702 : IF (i < 1) i = 1
1160 3780702 : IF (i > grp%spline_n - 1) i = grp%spline_n - 1
1161 :
1162 3780702 : t = (r - REAL(i - 1, KIND=dp)*grp%spline_dx)*grp%spline_dx_inv
1163 3780702 : t2 = t*t
1164 3780702 : t3 = t2*t
1165 :
1166 3780702 : h00 = 2.0_dp*t3 - 3.0_dp*t2 + 1.0_dp
1167 3780702 : h10 = t3 - 2.0_dp*t2 + t
1168 3780702 : h01 = -2.0_dp*t3 + 3.0_dp*t2
1169 3780702 : h11 = t3 - t2
1170 3780702 : h10_dx = h10*grp%spline_dx
1171 3780702 : h11_dx = h11*grp%spline_dx
1172 :
1173 3780702 : IF (PRESENT(force)) THEN
1174 1850186 : dh00 = 6.0_dp*(t2 - t)
1175 1850186 : dh10 = 3.0_dp*t2 - 4.0_dp*t + 1.0_dp
1176 1850186 : dh01 = -dh00
1177 1850186 : dh11 = 3.0_dp*t2 - 2.0_dp*t
1178 :
1179 1850186 : r_inv = 1.0_dp/r
1180 1850186 : drdx_x = rvect(1)*r_inv
1181 1850186 : drdx_y = rvect(2)*r_inv
1182 1850186 : drdx_z = rvect(3)*r_inv
1183 :
1184 : ! Vectorizable inner loop: contiguous reads on sf, no branches,
1185 : ! no function calls, no transcendentals.
1186 : ASSOCIATE (spy_i => grp%spline_y(:, i), spy_i1 => grp%spline_y(:, i + 1), &
1187 : spdy_i => grp%spline_dy(:, i), spdy_i1 => grp%spline_dy(:, i + 1))
1188 1850186 : !$OMP SIMD PRIVATE(yi, yi1, dyi, dyi1, dsymdr_full)
1189 : DO sf = 1, n_symf
1190 14778220 : yi = spy_i(sf)
1191 14778220 : yi1 = spy_i1(sf)
1192 14778220 : dyi = spdy_i(sf)
1193 14778220 : dyi1 = spdy_i1(sf)
1194 14778220 : sym(sf) = h00*yi + h10_dx*dyi + h01*yi1 + h11_dx*dyi1
1195 14778220 : dsymdr_full = (dh00*yi + dh01*yi1)*grp%spline_dx_inv + dh10*dyi + dh11*dyi1
1196 14778220 : force(1, sf) = dsymdr_full*drdx_x
1197 14778220 : force(2, sf) = dsymdr_full*drdx_y
1198 14778220 : force(3, sf) = dsymdr_full*drdx_z
1199 : END DO
1200 : END ASSOCIATE
1201 : ELSE
1202 : ASSOCIATE (spy_i => grp%spline_y(:, i), spy_i1 => grp%spline_y(:, i + 1), &
1203 : spdy_i => grp%spline_dy(:, i), spdy_i1 => grp%spline_dy(:, i + 1))
1204 1930516 : !$OMP SIMD
1205 : DO sf = 1, n_symf
1206 : sym(sf) = h00*spy_i(sf) + h10_dx*spdy_i(sf) + &
1207 14691518 : h01*spy_i1(sf) + h11_dx*spdy_i1(sf)
1208 : END DO
1209 : END ASSOCIATE
1210 : END IF
1211 :
1212 : END SUBROUTINE nnp_calc_rad
1213 :
1214 : ! **************************************************************************************************
1215 : !> \brief Build sf-first (n_symf, n_grid) Hermite cubic spline tables for radial SFs.
1216 : !> Uses a custom sf-first layout (not splines_methods) so nnp_calc_rad can
1217 : !> stream contiguous SF values under !$OMP SIMD without indirect addressing.
1218 : !> \param nnp ...
1219 : ! **************************************************************************************************
1220 17 : SUBROUTINE nnp_build_radial_splines(nnp)
1221 :
1222 : TYPE(nnp_type), INTENT(INOUT), POINTER :: nnp
1223 :
1224 : CHARACTER(len=*), PARAMETER :: routineN = 'nnp_build_radial_splines'
1225 :
1226 : INTEGER :: handle, ind, k, n_symf, p, s, sf
1227 : REAL(KIND=dp) :: arg, cutoff, dfcutdr, dr, eta, exp_term, &
1228 : fcut, r, rs, tanh_tmp
1229 :
1230 17 : CALL timeset(routineN, handle)
1231 :
1232 52 : DO ind = 1, nnp%n_ele
1233 123 : DO s = 1, nnp%rad(ind)%n_symfgrp
1234 35 : ASSOCIATE (grp => nnp%rad(ind)%symfgrp(s))
1235 71 : n_symf = grp%n_symf
1236 71 : cutoff = grp%cutoff
1237 71 : dr = cutoff/REAL(nnp%rad_spline_n - 1, KIND=dp)
1238 :
1239 0 : IF (ALLOCATED(grp%spline_y)) DEALLOCATE (grp%spline_y)
1240 71 : IF (ALLOCATED(grp%spline_dy)) DEALLOCATE (grp%spline_dy)
1241 284 : ALLOCATE (grp%spline_y(MAX(1, n_symf), nnp%rad_spline_n))
1242 284 : ALLOCATE (grp%spline_dy(MAX(1, n_symf), nnp%rad_spline_n))
1243 71 : grp%spline_n = nnp%rad_spline_n
1244 71 : grp%spline_dx = dr
1245 71 : grp%spline_dx_inv = 1.0_dp/dr
1246 71 : grp%spline_x_max = cutoff
1247 :
1248 : ! Fill the table SF-by-SF. The grid loop is the inner one
1249 : ! during build only; runtime nnp_calc_rad reads sf-first.
1250 621 : DO sf = 1, n_symf
1251 550 : k = grp%symf(sf)
1252 550 : eta = nnp%rad(ind)%eta(k)
1253 550 : rs = nnp%rad(ind)%rs(k)
1254 :
1255 4506221 : DO p = 1, nnp%rad_spline_n
1256 4505600 : r = REAL(p - 1, KIND=dp)*dr
1257 :
1258 4816896 : SELECT CASE (nnp%cut_type)
1259 : CASE (nnp_cut_cos)
1260 311296 : arg = pi*r/cutoff
1261 311296 : fcut = 0.5_dp*(COS(arg) + 1.0_dp)
1262 311296 : dfcutdr = -0.5_dp*SIN(arg)*(pi/cutoff)
1263 : CASE (nnp_cut_tanh)
1264 4194304 : tanh_tmp = TANH(1.0_dp - r/cutoff)
1265 4194304 : fcut = tanh_tmp**3
1266 4194304 : dfcutdr = (-3.0_dp/cutoff)*(tanh_tmp**2 - tanh_tmp**4)
1267 : CASE DEFAULT
1268 4505600 : CPABORT("NNP| Cutoff function unknown")
1269 : END SELECT
1270 :
1271 4505600 : exp_term = EXP(-eta*(r - rs)**2)
1272 :
1273 4505600 : grp%spline_y(sf, p) = exp_term*fcut
1274 : grp%spline_dy(sf, p) = exp_term*(-2.0_dp*eta*(r - rs))*fcut + &
1275 4506150 : exp_term*dfcutdr
1276 : END DO
1277 : END DO
1278 :
1279 : ! Pin the boundary so the runtime out-of-range branch can
1280 : ! return zeros without re-checking each SF.
1281 621 : DO sf = 1, n_symf
1282 550 : grp%spline_y(sf, nnp%rad_spline_n) = 0.0_dp
1283 621 : grp%spline_dy(sf, nnp%rad_spline_n) = 0.0_dp
1284 : END DO
1285 :
1286 142 : grp%spline_built = .TRUE.
1287 : END ASSOCIATE
1288 : END DO
1289 : END DO
1290 :
1291 17 : CALL timestop(handle)
1292 :
1293 17 : END SUBROUTINE nnp_build_radial_splines
1294 :
1295 : ! **************************************************************************************************
1296 : !> \brief Calculate angular symmetry function and gradient (optional)
1297 : !>
1298 : !> Vectorized SF-batched form. The original eta-sorted exp-skip loop has
1299 : !> been replaced by a sequence of SF passes:
1300 : !> 1. Branchless angular base: clamps tmp at the cusp (tmp <= 0), then
1301 : !> computes tmpzeta via integer or real pow per SF. Stays scalar
1302 : !> because the int/real switch and integer pow cannot SIMD.
1303 : !> 2. Vectorized EXP: symtmp_arr(sf) = EXP(-eta(sf)*r2sum). Marked
1304 : !> !$OMP SIMD so the compiler maps it to libmvec/SVML vector EXP.
1305 : !> Vector libm hides EXP latency across the SF loop without needing
1306 : !> an eta-dedup pre-pass.
1307 : !> 3. SIMD sym scatter: sym(sf) = prefzeta*angular*symtmp*ftot.
1308 : !> 4. Force scatter (only when forces requested). The clamp in pass 1
1309 : !> forces tmpzeta_arr(sf) = 0 at the cusp, which makes both angular
1310 : !> AND pref_lam vanish naturally -- no per-iter branch.
1311 : !>
1312 : !> Caller passes precomputed cutoff values for the j/k legs; the j-k leg
1313 : !> is computed inline. Geometry-only derivative bases are hoisted outside
1314 : !> the SF loops as before.
1315 : !> \param nnp ...
1316 : !> \param ind ...
1317 : !> \param s ...
1318 : !> \param rvect1 ...
1319 : !> \param rvect2 ...
1320 : !> \param rvect3 ...
1321 : !> \param r1 ...
1322 : !> \param r2 ...
1323 : !> \param r3 ...
1324 : !> \param fcut_j ...
1325 : !> \param dfcut_j ...
1326 : !> \param fcut_k ...
1327 : !> \param dfcut_k ...
1328 : !> \param sym ...
1329 : !> \param force ...
1330 : !> \date 2020-10-10
1331 : !> \author Christoph Schran (christoph.schran@rub.de)
1332 : !> \note This kernel is also inlined into nnp_calc_acsf on the serial force
1333 : !> path; any change to the computation or force scatter here must be
1334 : !> mirrored at that inlining site (see the comment there).
1335 : ! **************************************************************************************************
1336 27374069 : SUBROUTINE nnp_calc_ang(nnp, ind, s, rvect1, rvect2, rvect3, r1, r2, r3, &
1337 27374069 : fcut_j, dfcut_j, fcut_k, dfcut_k, sym, force)
1338 :
1339 : TYPE(nnp_type), INTENT(IN), TARGET :: nnp
1340 : INTEGER, INTENT(IN) :: ind, s
1341 : REAL(KIND=dp), DIMENSION(3), INTENT(IN) :: rvect1, rvect2, rvect3
1342 : REAL(KIND=dp), INTENT(IN) :: r1, r2, r3, fcut_j, dfcut_j, fcut_k, &
1343 : dfcut_k
1344 : REAL(KIND=dp), DIMENSION(:), INTENT(OUT) :: sym
1345 : REAL(KIND=dp), DIMENSION(:, :, :), INTENT(OUT), &
1346 : OPTIONAL :: force
1347 :
1348 : INTEGER :: ii, izeta, n_symf, sf
1349 : LOGICAL :: do_forces
1350 : REAL(KIND=dp) :: angular, arg_tmp, costheta, dfcut3, dfcutdr1, dfcutdr2, dfcutdr3, dsymdr1, &
1351 : dsymdr2, dsymdr3, eta, f, fcut3, fcut_rc, ftot, g, inv_g2, lam, pref_lam, prefzeta, &
1352 : r2sum, rsqr1, rsqr2, rsqr3, symtmp, tanh_tmp, tmp, tmp1, tmp2, tmp3, tmpzeta, zeta
1353 : REAL(KIND=dp), DIMENSION(3) :: dcosbase1, dcosbase2, dcosbase3, dgdx1, &
1354 : dgdx2, dgdx3, dr1dx, dr2dx, dr3dx
1355 : TYPE(nnp_symfgrp_type), POINTER :: grp
1356 : REAL(KIND=dp), &
1357 54748138 : DIMENSION(nnp%ang(ind)%symfgrp(s)%n_symf) :: angular_arr, symtmp_arr, tmpzeta_arr
1358 :
1359 : ! Per-SF scratch (automatic arrays sized to the current group). For
1360 : ! typical n2p2 ACSF n_symf is 8-32, so this is ~256-1024 bytes on
1361 : ! stack per call. Holds the staged outputs of pass 1 + pass 2 so
1362 : ! pass 3 streams them contiguously.
1363 :
1364 27374069 : do_forces = PRESENT(force)
1365 27374069 : grp => nnp%ang(ind)%symfgrp(s)
1366 27374069 : n_symf = grp%n_symf
1367 27374069 : fcut_rc = grp%cutoff
1368 :
1369 27374069 : rsqr1 = r1*r1
1370 27374069 : rsqr2 = r2*r2
1371 27374069 : rsqr3 = r3*r3
1372 27374069 : r2sum = rsqr1 + rsqr2 + rsqr3
1373 :
1374 27374069 : f = rsqr3 - rsqr1 - rsqr2
1375 27374069 : g = -2.0_dp*r1*r2
1376 27374069 : costheta = f/g
1377 :
1378 : ! Compute fcut3 for r3 (j-k distance -- cannot be precomputed by caller)
1379 27573631 : SELECT CASE (nnp%cut_type)
1380 : CASE (nnp_cut_cos)
1381 199562 : arg_tmp = pi*r3/fcut_rc
1382 199562 : fcut3 = 0.5_dp*(COS(arg_tmp) + 1.0_dp)
1383 199562 : IF (do_forces) dfcut3 = -0.5_dp*SIN(arg_tmp)*(pi/fcut_rc)
1384 : CASE (nnp_cut_tanh)
1385 27174507 : tanh_tmp = TANH(1.0_dp - r3/fcut_rc)
1386 27174507 : fcut3 = tanh_tmp**3
1387 27174507 : IF (do_forces) dfcut3 = (-3.0_dp/fcut_rc)*(tanh_tmp**2 - tanh_tmp**4)
1388 : CASE DEFAULT
1389 27374069 : CPABORT("NNP| Cutoff function unknown")
1390 : END SELECT
1391 :
1392 : ! Use precomputed fcut values for j and k neighbors
1393 27374069 : ftot = fcut_j*fcut_k*fcut3
1394 :
1395 27374069 : IF (do_forces) THEN
1396 : ! Combined cutoff derivatives (product rule)
1397 0 : dfcutdr1 = dfcut_j*fcut_k*fcut3
1398 0 : dfcutdr2 = fcut_j*dfcut_k*fcut3
1399 0 : dfcutdr3 = fcut_j*fcut_k*dfcut3
1400 :
1401 0 : dr1dx(:) = rvect1(:)/r1
1402 0 : dr2dx(:) = rvect2(:)/r2
1403 0 : dr3dx(:) = rvect3(:)/r3
1404 :
1405 : ! Hoist geometry-only parts of costheta derivatives outside SF loop.
1406 : ! Full angular derivative: dangulardx = (zeta*tmpzeta*lam/g^2) * dcosbase
1407 : ! where dcosbase factors out lam from dfdx and the f*dgdx term.
1408 : ! inv_g2 = 1/g^2 is hoisted: one divide per triplet, one mul per SF.
1409 0 : inv_g2 = 1.0_dp/(g*g)
1410 0 : DO ii = 1, 3
1411 0 : tmp1 = 2.0_dp*r2*dr1dx(ii)
1412 0 : tmp2 = 2.0_dp*r1*dr2dx(ii)
1413 0 : dgdx1(ii) = -(tmp1 + tmp2)
1414 : dgdx2(ii) = tmp1
1415 : dgdx3(ii) = tmp2
1416 :
1417 0 : dcosbase1(ii) = -2.0_dp*(rvect1(ii) + rvect2(ii))*g - f*dgdx1(ii)
1418 0 : dcosbase2(ii) = 2.0_dp*(rvect3(ii) + rvect1(ii))*g - f*dgdx2(ii)
1419 0 : dcosbase3(ii) = 2.0_dp*(rvect2(ii) - rvect3(ii))*g - f*dgdx3(ii)
1420 : END DO
1421 : ELSE
1422 : inv_g2 = 0.0_dp
1423 : END IF
1424 :
1425 : ! Pass 1: branchless cusp clamp -- zero tmpzeta propagates to sym and pref_lam.
1426 183298526 : DO sf = 1, n_symf
1427 155924457 : tmp = 1.0_dp + grp%pack_lam(sf)*costheta
1428 183298526 : IF (tmp <= 0.0_dp) THEN
1429 0 : tmpzeta_arr(sf) = 0.0_dp
1430 0 : angular_arr(sf) = 0.0_dp
1431 : ELSE
1432 155924457 : IF (grp%pack_use_int_zeta(sf)) THEN
1433 155924457 : izeta = grp%pack_izeta(sf)
1434 155924457 : tmpzeta_arr(sf) = tmp**(izeta - 1)
1435 : ELSE
1436 0 : tmpzeta_arr(sf) = tmp**(grp%pack_zeta(sf) - 1.0_dp)
1437 : END IF
1438 155924457 : angular_arr(sf) = tmpzeta_arr(sf)*tmp
1439 : END IF
1440 : END DO
1441 :
1442 : ! ---- Pass 2: vectorized EXP via libmvec/SVML ----
1443 : ! Single SIMD loop computing symtmp_arr(sf) = EXP(-eta(sf)*r2sum).
1444 : ! With -fopenmp-simd this maps to vector libm (e.g. _ZGVdN4v_exp on
1445 : ! AVX2, _ZGVeN8v_exp on AVX-512), hiding EXP latency across 4-8 SFs
1446 : ! per iter -- ~10 cycles/element vs ~30 for scalar EXP, which beats
1447 : ! any eta-dedup pre-pass.
1448 183298526 : !$OMP SIMD
1449 : DO sf = 1, n_symf
1450 155924457 : symtmp_arr(sf) = EXP(-grp%pack_eta(sf)*r2sum)
1451 : END DO
1452 :
1453 : ! ---- Pass 3: sym scatter (vectorizable) ----
1454 27374069 : !$OMP SIMD
1455 : DO sf = 1, n_symf
1456 155924457 : sym(sf) = grp%pack_prefzeta(sf)*angular_arr(sf)*symtmp_arr(sf)*ftot
1457 : END DO
1458 :
1459 : ! ---- Pass 4: force scatter (only when do_forces) ----
1460 27374069 : IF (do_forces) THEN
1461 0 : DO sf = 1, n_symf
1462 0 : symtmp = symtmp_arr(sf)
1463 0 : angular = angular_arr(sf)
1464 0 : tmpzeta = tmpzeta_arr(sf)
1465 0 : eta = grp%pack_eta(sf)
1466 0 : lam = grp%pack_lam(sf)
1467 0 : zeta = grp%pack_zeta(sf)
1468 0 : prefzeta = grp%pack_prefzeta(sf)
1469 :
1470 : ! pref_lam carries tmpzeta, so it vanishes at the cusp without
1471 : ! an explicit branch (tmpzeta_arr was clamped to 0 in pass 1).
1472 0 : pref_lam = zeta*tmpzeta*lam*inv_g2
1473 :
1474 0 : tmp = -2.0_dp*symtmp*eta
1475 0 : dsymdr1 = tmp*r1
1476 0 : dsymdr2 = tmp*r2
1477 0 : dsymdr3 = tmp*r3
1478 :
1479 0 : tmp = prefzeta*symtmp*ftot
1480 0 : tmp1 = prefzeta*angular*(ftot*dsymdr1 + dfcutdr1*symtmp)
1481 0 : tmp2 = prefzeta*angular*(ftot*dsymdr2 + dfcutdr2*symtmp)
1482 0 : tmp3 = prefzeta*angular*(ftot*dsymdr3 + dfcutdr3*symtmp)
1483 0 : DO ii = 1, 3
1484 0 : force(ii, 1, sf) = tmp*pref_lam*dcosbase1(ii) + tmp1*dr1dx(ii) + tmp2*dr2dx(ii)
1485 0 : force(ii, 2, sf) = tmp*pref_lam*dcosbase2(ii) - tmp1*dr1dx(ii) + tmp3*dr3dx(ii)
1486 0 : force(ii, 3, sf) = tmp*pref_lam*dcosbase3(ii) - tmp2*dr2dx(ii) - tmp3*dr3dx(ii)
1487 : END DO
1488 : END DO
1489 : END IF
1490 :
1491 27374069 : END SUBROUTINE nnp_calc_ang
1492 :
1493 : ! **************************************************************************************************
1494 : !> \brief Sort an (ele, nuc_ele) pair of arrays in ascending order of atomic number.
1495 : !> Used to canonicalise element ordering inside the NNP environment so the
1496 : !> same model file produces the same per-element index layout regardless
1497 : !> of input ordering.
1498 : !> \param ele element-symbol array, sorted in place to match nuc_ele.
1499 : !> \param nuc_ele per-element atomic number, sorted in place.
1500 : !> \author Christoph Schran (christoph.schran@rub.de)
1501 : ! **************************************************************************************************
1502 17 : SUBROUTINE nnp_sort_ele(ele, nuc_ele)
1503 : CHARACTER(len=2), DIMENSION(:), INTENT(INOUT) :: ele
1504 : INTEGER, DIMENSION(:), INTENT(INOUT) :: nuc_ele
1505 :
1506 : CHARACTER(len=2) :: tmp_ele
1507 : INTEGER :: i, j, loc, minimum, tmp_nuc_ele
1508 :
1509 52 : DO i = 1, SIZE(ele)
1510 52 : CALL get_ptable_info(ele(i), number=nuc_ele(i))
1511 : END DO
1512 :
1513 35 : DO i = 1, SIZE(ele) - 1
1514 18 : minimum = nuc_ele(i)
1515 18 : loc = i
1516 37 : DO j = i + 1, SIZE(ele)
1517 37 : IF (nuc_ele(j) < minimum) THEN
1518 18 : loc = j
1519 18 : minimum = nuc_ele(j)
1520 : END IF
1521 : END DO
1522 18 : tmp_nuc_ele = nuc_ele(i)
1523 18 : nuc_ele(i) = nuc_ele(loc)
1524 18 : nuc_ele(loc) = tmp_nuc_ele
1525 :
1526 18 : tmp_ele = ele(i)
1527 18 : ele(i) = ele(loc)
1528 35 : ele(loc) = tmp_ele
1529 : END DO
1530 :
1531 17 : END SUBROUTINE nnp_sort_ele
1532 :
1533 : ! **************************************************************************************************
1534 : !> \brief Sort radial and angular symmetry functions in canonical order.
1535 : !> Radial SFs are sorted by eta (ascending) then rcut; angular SFs by
1536 : !> eta, lambda, zeta. This is the order downstream code in nnp_init_acsf_groups
1537 : !> relies on for run-length-style group packing.
1538 : !> \param nnp NNP environment whose rad/ang SF arrays will be reordered in place.
1539 : !> \author Christoph Schran (christoph.schran@rub.de)
1540 : ! **************************************************************************************************
1541 17 : SUBROUTINE nnp_sort_acsf(nnp)
1542 : TYPE(nnp_type), INTENT(INOUT) :: nnp
1543 :
1544 : INTEGER :: i, j, k, loc
1545 :
1546 52 : DO i = 1, nnp%n_ele
1547 550 : DO j = 1, nnp%n_rad(i) - 1
1548 515 : loc = j
1549 4591 : DO k = j + 1, nnp%n_rad(i)
1550 4591 : IF (nnp%rad(i)%funccut(loc) > nnp%rad(i)%funccut(k)) THEN
1551 6 : loc = k
1552 : END IF
1553 : END DO
1554 550 : CALL nnp_swaprad(nnp%rad(i), j, loc)
1555 : END DO
1556 :
1557 550 : DO j = 1, nnp%n_rad(i) - 1
1558 515 : loc = j
1559 4591 : DO k = j + 1, nnp%n_rad(i)
1560 4076 : IF (nnp%rad(i)%funccut(loc) == nnp%rad(i)%funccut(k) .AND. &
1561 515 : nnp%rad(i)%eta(loc) > nnp%rad(i)%eta(k)) THEN
1562 552 : loc = k
1563 : END IF
1564 : END DO
1565 550 : CALL nnp_swaprad(nnp%rad(i), j, loc)
1566 : END DO
1567 :
1568 550 : DO j = 1, nnp%n_rad(i) - 1
1569 515 : loc = j
1570 4591 : DO k = j + 1, nnp%n_rad(i)
1571 : IF (nnp%rad(i)%funccut(loc) == nnp%rad(i)%funccut(k) .AND. &
1572 4076 : nnp%rad(i)%eta(loc) == nnp%rad(i)%eta(k) .AND. &
1573 515 : nnp%rad(i)%rs(loc) > nnp%rad(i)%rs(k)) THEN
1574 64 : loc = k
1575 : END IF
1576 : END DO
1577 550 : CALL nnp_swaprad(nnp%rad(i), j, loc)
1578 : END DO
1579 :
1580 550 : DO j = 1, nnp%n_rad(i) - 1
1581 515 : loc = j
1582 4591 : DO k = j + 1, nnp%n_rad(i)
1583 : IF (nnp%rad(i)%funccut(loc) == nnp%rad(i)%funccut(k) .AND. &
1584 : nnp%rad(i)%eta(loc) == nnp%rad(i)%eta(k) .AND. &
1585 4076 : nnp%rad(i)%rs(loc) == nnp%rad(i)%rs(k) .AND. &
1586 515 : nnp%rad(i)%nuc_ele(loc) > nnp%rad(i)%nuc_ele(k)) THEN
1587 0 : loc = k
1588 : END IF
1589 : END DO
1590 550 : CALL nnp_swaprad(nnp%rad(i), j, loc)
1591 : END DO
1592 :
1593 420 : DO j = 1, nnp%n_ang(i) - 1
1594 385 : loc = j
1595 2778 : DO k = j + 1, nnp%n_ang(i)
1596 2778 : IF (nnp%ang(i)%funccut(loc) > nnp%ang(i)%funccut(k)) THEN
1597 3 : loc = k
1598 : END IF
1599 : END DO
1600 420 : CALL nnp_swapang(nnp%ang(i), j, loc)
1601 : END DO
1602 :
1603 420 : DO j = 1, nnp%n_ang(i) - 1
1604 385 : loc = j
1605 2778 : DO k = j + 1, nnp%n_ang(i)
1606 2393 : IF (nnp%ang(i)%funccut(loc) == nnp%ang(i)%funccut(k) .AND. &
1607 385 : nnp%ang(i)%eta(loc) > nnp%ang(i)%eta(k)) THEN
1608 448 : loc = k
1609 : END IF
1610 : END DO
1611 420 : CALL nnp_swapang(nnp%ang(i), j, loc)
1612 : END DO
1613 :
1614 420 : DO j = 1, nnp%n_ang(i) - 1
1615 385 : loc = j
1616 2778 : DO k = j + 1, nnp%n_ang(i)
1617 : IF (nnp%ang(i)%funccut(loc) == nnp%ang(i)%funccut(k) .AND. &
1618 2393 : nnp%ang(i)%eta(loc) == nnp%ang(i)%eta(k) .AND. &
1619 385 : nnp%ang(i)%zeta(loc) > nnp%ang(i)%zeta(k)) THEN
1620 7 : loc = k
1621 : END IF
1622 : END DO
1623 420 : CALL nnp_swapang(nnp%ang(i), j, loc)
1624 : END DO
1625 :
1626 420 : DO j = 1, nnp%n_ang(i) - 1
1627 385 : loc = j
1628 2778 : DO k = j + 1, nnp%n_ang(i)
1629 : IF (nnp%ang(i)%funccut(loc) == nnp%ang(i)%funccut(k) .AND. &
1630 : nnp%ang(i)%eta(loc) == nnp%ang(i)%eta(k) .AND. &
1631 2393 : nnp%ang(i)%zeta(loc) == nnp%ang(i)%zeta(k) .AND. &
1632 385 : nnp%ang(i)%lam(loc) > nnp%ang(i)%lam(k)) THEN
1633 168 : loc = k
1634 : END IF
1635 : END DO
1636 420 : CALL nnp_swapang(nnp%ang(i), j, loc)
1637 : END DO
1638 :
1639 420 : DO j = 1, nnp%n_ang(i) - 1
1640 385 : loc = j
1641 2778 : DO k = j + 1, nnp%n_ang(i)
1642 : IF (nnp%ang(i)%funccut(loc) == nnp%ang(i)%funccut(k) .AND. &
1643 : nnp%ang(i)%eta(loc) == nnp%ang(i)%eta(k) .AND. &
1644 : nnp%ang(i)%zeta(loc) == nnp%ang(i)%zeta(k) .AND. &
1645 2393 : nnp%ang(i)%lam(loc) == nnp%ang(i)%lam(k) .AND. &
1646 385 : nnp%ang(i)%nuc_ele1(loc) > nnp%ang(i)%nuc_ele1(k)) THEN
1647 48 : loc = k
1648 : END IF
1649 : END DO
1650 420 : CALL nnp_swapang(nnp%ang(i), j, loc)
1651 : END DO
1652 :
1653 437 : DO j = 1, nnp%n_ang(i) - 1
1654 385 : loc = j
1655 2778 : DO k = j + 1, nnp%n_ang(i)
1656 : IF (nnp%ang(i)%funccut(loc) == nnp%ang(i)%funccut(k) .AND. &
1657 : nnp%ang(i)%eta(loc) == nnp%ang(i)%eta(k) .AND. &
1658 : nnp%ang(i)%zeta(loc) == nnp%ang(i)%zeta(k) .AND. &
1659 : nnp%ang(i)%lam(loc) == nnp%ang(i)%lam(k) .AND. &
1660 2393 : nnp%ang(i)%nuc_ele1(loc) == nnp%ang(i)%nuc_ele1(k) .AND. &
1661 385 : nnp%ang(i)%nuc_ele2(loc) > nnp%ang(i)%nuc_ele2(k)) THEN
1662 33 : loc = k
1663 : END IF
1664 : END DO
1665 420 : CALL nnp_swapang(nnp%ang(i), j, loc)
1666 : END DO
1667 : END DO
1668 :
1669 17 : END SUBROUTINE nnp_sort_acsf
1670 :
1671 : ! **************************************************************************************************
1672 : !> \brief Swap two radial symmetry functions
1673 : !> \param rad ...
1674 : !> \param i ...
1675 : !> \param j ...
1676 : !> \date 2020-10-10
1677 : !> \author Christoph Schran (christoph.schran@rub.de)
1678 : ! **************************************************************************************************
1679 2060 : SUBROUTINE nnp_swaprad(rad, i, j)
1680 : TYPE(nnp_acsf_rad_type), INTENT(INOUT) :: rad
1681 : INTEGER, INTENT(IN) :: i, j
1682 :
1683 : CHARACTER(len=2) :: tmpc
1684 : INTEGER :: tmpi
1685 : REAL(KIND=dp) :: tmpr
1686 :
1687 2060 : tmpr = rad%funccut(i)
1688 2060 : rad%funccut(i) = rad%funccut(j)
1689 2060 : rad%funccut(j) = tmpr
1690 :
1691 2060 : tmpr = rad%eta(i)
1692 2060 : rad%eta(i) = rad%eta(j)
1693 2060 : rad%eta(j) = tmpr
1694 :
1695 2060 : tmpr = rad%rs(i)
1696 2060 : rad%rs(i) = rad%rs(j)
1697 2060 : rad%rs(j) = tmpr
1698 :
1699 2060 : tmpc = rad%ele(i)
1700 2060 : rad%ele(i) = rad%ele(j)
1701 2060 : rad%ele(j) = tmpc
1702 :
1703 2060 : tmpi = rad%nuc_ele(i)
1704 2060 : rad%nuc_ele(i) = rad%nuc_ele(j)
1705 2060 : rad%nuc_ele(j) = tmpi
1706 :
1707 2060 : END SUBROUTINE nnp_swaprad
1708 :
1709 : ! **************************************************************************************************
1710 : !> \brief Swap two angular symmetry functions
1711 : !> \param ang ...
1712 : !> \param i ...
1713 : !> \param j ...
1714 : !> \date 2020-10-10
1715 : !> \author Christoph Schran (christoph.schran@rub.de)
1716 : ! **************************************************************************************************
1717 2310 : SUBROUTINE nnp_swapang(ang, i, j)
1718 : TYPE(nnp_acsf_ang_type), INTENT(INOUT) :: ang
1719 : INTEGER, INTENT(IN) :: i, j
1720 :
1721 : CHARACTER(len=2) :: tmpc
1722 : INTEGER :: tmpi
1723 : REAL(KIND=dp) :: tmpr
1724 :
1725 2310 : tmpr = ang%funccut(i)
1726 2310 : ang%funccut(i) = ang%funccut(j)
1727 2310 : ang%funccut(j) = tmpr
1728 :
1729 2310 : tmpr = ang%eta(i)
1730 2310 : ang%eta(i) = ang%eta(j)
1731 2310 : ang%eta(j) = tmpr
1732 :
1733 2310 : tmpr = ang%zeta(i)
1734 2310 : ang%zeta(i) = ang%zeta(j)
1735 2310 : ang%zeta(j) = tmpr
1736 :
1737 2310 : tmpr = ang%prefzeta(i)
1738 2310 : ang%prefzeta(i) = ang%prefzeta(j)
1739 2310 : ang%prefzeta(j) = tmpr
1740 :
1741 2310 : tmpr = ang%lam(i)
1742 2310 : ang%lam(i) = ang%lam(j)
1743 2310 : ang%lam(j) = tmpr
1744 :
1745 2310 : tmpc = ang%ele1(i)
1746 2310 : ang%ele1(i) = ang%ele1(j)
1747 2310 : ang%ele1(j) = tmpc
1748 :
1749 2310 : tmpi = ang%nuc_ele1(i)
1750 2310 : ang%nuc_ele1(i) = ang%nuc_ele1(j)
1751 2310 : ang%nuc_ele1(j) = tmpi
1752 :
1753 2310 : tmpc = ang%ele2(i)
1754 2310 : ang%ele2(i) = ang%ele2(j)
1755 2310 : ang%ele2(j) = tmpc
1756 :
1757 2310 : tmpi = ang%nuc_ele2(i)
1758 2310 : ang%nuc_ele2(i) = ang%nuc_ele2(j)
1759 2310 : ang%nuc_ele2(j) = tmpi
1760 :
1761 2310 : END SUBROUTINE nnp_swapang
1762 :
1763 : ! **************************************************************************************************
1764 : !> \brief Pack symmetry functions into groups that share input parameters.
1765 : !> Builds nnp%rad(i)%symfgrp(:) and nnp%ang(i)%symfgrp(:) so that all
1766 : !> radial / angular SFs with identical (eta, rcut [, lambda, zeta])
1767 : !> live in one group; downstream the descriptor pass evaluates each
1768 : !> group's shared cutoff/exponent once and applies it across the
1769 : !> per-element neighbour slab.
1770 : !> \param nnp NNP environment with rad/ang SF metadata already populated by nnp_init_model.
1771 : !> \author Christoph Schran (christoph.schran@rub.de)
1772 : ! **************************************************************************************************
1773 17 : SUBROUTINE nnp_init_acsf_groups(nnp)
1774 :
1775 : TYPE(nnp_type), INTENT(INOUT) :: nnp
1776 :
1777 : INTEGER :: ang, i, izeta_tmp, j, k, m, n_symf, rad, &
1778 : s, sf
1779 : REAL(KIND=dp) :: eta_tmp, funccut, zeta_tmp
1780 :
1781 52 : DO i = 1, nnp%n_ele
1782 35 : nnp%rad(i)%n_symfgrp = 0
1783 35 : nnp%ang(i)%n_symfgrp = 0
1784 108 : DO j = 1, nnp%n_ele
1785 73 : funccut = -1.0_dp
1786 1246 : DO s = 1, nnp%n_rad(i)
1787 1211 : IF (nnp%rad(i)%ele(s) == nnp%ele(j)) THEN
1788 550 : IF (ABS(nnp%rad(i)%funccut(s) - funccut) > cutoff_eq_tol) THEN
1789 71 : nnp%rad(i)%n_symfgrp = nnp%rad(i)%n_symfgrp + 1
1790 71 : funccut = nnp%rad(i)%funccut(s)
1791 : END IF
1792 : END IF
1793 : END DO
1794 : END DO
1795 125 : DO j = 1, nnp%n_ele
1796 222 : DO k = j, nnp%n_ele
1797 114 : funccut = -1.0_dp
1798 1507 : DO s = 1, nnp%n_ang(i)
1799 : IF ((nnp%ang(i)%ele1(s) == nnp%ele(j) .AND. &
1800 1320 : nnp%ang(i)%ele2(s) == nnp%ele(k)) .OR. &
1801 : (nnp%ang(i)%ele1(s) == nnp%ele(k) .AND. &
1802 114 : nnp%ang(i)%ele2(s) == nnp%ele(j))) THEN
1803 420 : IF (ABS(nnp%ang(i)%funccut(s) - funccut) > cutoff_eq_tol) THEN
1804 86 : nnp%ang(i)%n_symfgrp = nnp%ang(i)%n_symfgrp + 1
1805 86 : funccut = nnp%ang(i)%funccut(s)
1806 : END IF
1807 : END IF
1808 : END DO
1809 : END DO
1810 : END DO
1811 : END DO
1812 :
1813 52 : DO i = 1, nnp%n_ele
1814 176 : ALLOCATE (nnp%rad(i)%symfgrp(nnp%rad(i)%n_symfgrp))
1815 191 : ALLOCATE (nnp%ang(i)%symfgrp(nnp%ang(i)%n_symfgrp))
1816 106 : DO j = 1, nnp%rad(i)%n_symfgrp
1817 106 : nnp%rad(i)%symfgrp(j)%n_symf = 0
1818 : END DO
1819 138 : DO j = 1, nnp%ang(i)%n_symfgrp
1820 121 : nnp%ang(i)%symfgrp(j)%n_symf = 0
1821 : END DO
1822 : END DO
1823 :
1824 52 : DO i = 1, nnp%n_ele
1825 : rad = 0
1826 108 : ang = 0
1827 108 : DO j = 1, nnp%n_ele
1828 73 : funccut = -1.0_dp
1829 1246 : DO s = 1, nnp%n_rad(i)
1830 1211 : IF (nnp%rad(i)%ele(s) == nnp%ele(j)) THEN
1831 550 : IF (ABS(nnp%rad(i)%funccut(s) - funccut) > cutoff_eq_tol) THEN
1832 71 : rad = rad + 1
1833 71 : funccut = nnp%rad(i)%funccut(s)
1834 71 : nnp%rad(i)%symfgrp(rad)%cutoff = funccut
1835 71 : ALLOCATE (nnp%rad(i)%symfgrp(rad)%ele(1))
1836 71 : ALLOCATE (nnp%rad(i)%symfgrp(rad)%ele_ind(1))
1837 71 : nnp%rad(i)%symfgrp(rad)%ele(1) = nnp%ele(j)
1838 71 : nnp%rad(i)%symfgrp(rad)%ele_ind(1) = j
1839 : END IF
1840 550 : nnp%rad(i)%symfgrp(rad)%n_symf = nnp%rad(i)%symfgrp(rad)%n_symf + 1
1841 : END IF
1842 : END DO
1843 : END DO
1844 125 : DO j = 1, nnp%n_ele
1845 222 : DO k = j, nnp%n_ele
1846 114 : funccut = -1.0_dp
1847 1507 : DO s = 1, nnp%n_ang(i)
1848 : IF ((nnp%ang(i)%ele1(s) == nnp%ele(j) .AND. &
1849 1320 : nnp%ang(i)%ele2(s) == nnp%ele(k)) .OR. &
1850 : (nnp%ang(i)%ele1(s) == nnp%ele(k) .AND. &
1851 114 : nnp%ang(i)%ele2(s) == nnp%ele(j))) THEN
1852 420 : IF (ABS(nnp%ang(i)%funccut(s) - funccut) > cutoff_eq_tol) THEN
1853 86 : ang = ang + 1
1854 86 : funccut = nnp%ang(i)%funccut(s)
1855 86 : nnp%ang(i)%symfgrp(ang)%cutoff = funccut
1856 86 : ALLOCATE (nnp%ang(i)%symfgrp(ang)%ele(2))
1857 86 : ALLOCATE (nnp%ang(i)%symfgrp(ang)%ele_ind(2))
1858 86 : nnp%ang(i)%symfgrp(ang)%ele(1) = nnp%ele(j)
1859 86 : nnp%ang(i)%symfgrp(ang)%ele(2) = nnp%ele(k)
1860 86 : nnp%ang(i)%symfgrp(ang)%ele_ind(1) = j
1861 86 : nnp%ang(i)%symfgrp(ang)%ele_ind(2) = k
1862 : END IF
1863 420 : nnp%ang(i)%symfgrp(ang)%n_symf = nnp%ang(i)%symfgrp(ang)%n_symf + 1
1864 : END IF
1865 : END DO
1866 : END DO
1867 : END DO
1868 : END DO
1869 :
1870 52 : DO i = 1, nnp%n_ele
1871 106 : DO j = 1, nnp%rad(i)%n_symfgrp
1872 213 : ALLOCATE (nnp%rad(i)%symfgrp(j)%symf(nnp%rad(i)%symfgrp(j)%n_symf))
1873 71 : rad = 0
1874 1223 : DO s = 1, nnp%n_rad(i)
1875 1188 : IF (nnp%rad(i)%ele(s) == nnp%rad(i)%symfgrp(j)%ele(1)) THEN
1876 550 : IF (ABS(nnp%rad(i)%funccut(s) - nnp%rad(i)%symfgrp(j)%cutoff) <= cutoff_eq_tol) THEN
1877 550 : rad = rad + 1
1878 550 : nnp%rad(i)%symfgrp(j)%symf(rad) = s
1879 : END IF
1880 : END IF
1881 : END DO
1882 : END DO
1883 138 : DO j = 1, nnp%ang(i)%n_symfgrp
1884 258 : ALLOCATE (nnp%ang(i)%symfgrp(j)%symf(nnp%ang(i)%symfgrp(j)%n_symf))
1885 86 : ang = 0
1886 1185 : DO s = 1, nnp%n_ang(i)
1887 : IF ((nnp%ang(i)%ele1(s) == nnp%ang(i)%symfgrp(j)%ele(1) .AND. &
1888 1064 : nnp%ang(i)%ele2(s) == nnp%ang(i)%symfgrp(j)%ele(2)) .OR. &
1889 : (nnp%ang(i)%ele1(s) == nnp%ang(i)%symfgrp(j)%ele(2) .AND. &
1890 86 : nnp%ang(i)%ele2(s) == nnp%ang(i)%symfgrp(j)%ele(1))) THEN
1891 420 : IF (ABS(nnp%ang(i)%funccut(s) - nnp%ang(i)%symfgrp(j)%cutoff) <= cutoff_eq_tol) THEN
1892 420 : ang = ang + 1
1893 420 : nnp%ang(i)%symfgrp(j)%symf(ang) = s
1894 : END IF
1895 : END IF
1896 : END DO
1897 : END DO
1898 : END DO
1899 :
1900 : ! Populate packed parameter arrays for the angular SF group inner loop
1901 : ! so it reads contiguous memory rather than chasing
1902 : ! ang(i)%{eta,zeta,lam,prefzeta}(symf(sf)) on every iteration.
1903 : ! pack_use_int_zeta and pack_izeta let the inner loop skip NINT and
1904 : ! dispatch a tight integer power when zeta is integral. The radial
1905 : ! group does not need packed parameters since nnp_calc_rad reads
1906 : ! pretabulated splines.
1907 52 : DO i = 1, nnp%n_ele
1908 138 : DO j = 1, nnp%ang(i)%n_symfgrp
1909 86 : n_symf = nnp%ang(i)%symfgrp(j)%n_symf
1910 258 : ALLOCATE (nnp%ang(i)%symfgrp(j)%pack_eta(n_symf))
1911 172 : ALLOCATE (nnp%ang(i)%symfgrp(j)%pack_zeta(n_symf))
1912 172 : ALLOCATE (nnp%ang(i)%symfgrp(j)%pack_lam(n_symf))
1913 172 : ALLOCATE (nnp%ang(i)%symfgrp(j)%pack_prefzeta(n_symf))
1914 258 : ALLOCATE (nnp%ang(i)%symfgrp(j)%pack_izeta(n_symf))
1915 172 : ALLOCATE (nnp%ang(i)%symfgrp(j)%pack_use_int_zeta(n_symf))
1916 541 : DO sf = 1, n_symf
1917 420 : m = nnp%ang(i)%symfgrp(j)%symf(sf)
1918 420 : eta_tmp = nnp%ang(i)%eta(m)
1919 420 : zeta_tmp = nnp%ang(i)%zeta(m)
1920 420 : nnp%ang(i)%symfgrp(j)%pack_eta(sf) = eta_tmp
1921 420 : nnp%ang(i)%symfgrp(j)%pack_zeta(sf) = zeta_tmp
1922 420 : nnp%ang(i)%symfgrp(j)%pack_lam(sf) = nnp%ang(i)%lam(m)
1923 420 : nnp%ang(i)%symfgrp(j)%pack_prefzeta(sf) = nnp%ang(i)%prefzeta(m)
1924 420 : izeta_tmp = NINT(zeta_tmp)
1925 420 : nnp%ang(i)%symfgrp(j)%pack_izeta(sf) = izeta_tmp
1926 : nnp%ang(i)%symfgrp(j)%pack_use_int_zeta(sf) = &
1927 506 : (REAL(izeta_tmp, dp) == zeta_tmp)
1928 : END DO
1929 : END DO
1930 : END DO
1931 :
1932 17 : END SUBROUTINE nnp_init_acsf_groups
1933 :
1934 : ! **************************************************************************************************
1935 : !> \brief Print a summary of the active symmetry-function set on the source rank.
1936 : !> Emits one line per element listing the per-element n_rad / n_ang counts
1937 : !> and the per-group breakdown; used at NNP init time and after sort/group
1938 : !> passes for traceability.
1939 : !> \param nnp NNP environment whose SF metadata is to be printed.
1940 : !> \param para_env parallel environment; only the source rank emits output.
1941 : !> \param printtag log-line prefix (typically "NNP" or "HELIUM_NNP").
1942 : !> \author Christoph Schran (christoph.schran@rub.de)
1943 : ! **************************************************************************************************
1944 17 : SUBROUTINE nnp_write_acsf(nnp, para_env, printtag)
1945 : TYPE(nnp_type), INTENT(INOUT) :: nnp
1946 : TYPE(mp_para_env_type), POINTER :: para_env
1947 : CHARACTER(LEN=*), INTENT(IN) :: printtag
1948 :
1949 : CHARACTER(len=default_string_length) :: my_label
1950 : INTEGER :: i, j, unit_nr
1951 : TYPE(cp_logger_type), POINTER :: logger
1952 :
1953 17 : NULLIFY (logger)
1954 17 : logger => cp_get_default_logger()
1955 :
1956 17 : my_label = TRIM(printtag)//"| "
1957 17 : IF (para_env%is_source()) THEN
1958 9 : unit_nr = cp_logger_get_default_unit_nr(logger)
1959 9 : WRITE (unit_nr, '(1X,A,1X,10(I2,1X))') TRIM(my_label)//" Activation functions:", nnp%actfnct(:)
1960 28 : DO i = 1, nnp%n_ele
1961 : WRITE (unit_nr, *) TRIM(my_label)//" short range atomic symmetry functions element "// &
1962 19 : nnp%ele(i)//":"
1963 313 : DO j = 1, nnp%n_rad(i)
1964 294 : WRITE (unit_nr, '(1X,A,1X,I3,1X,A2,1X,I2,1X,A2,11X,3(F6.3,1X))') TRIM(my_label), j, nnp%ele(i), 2, &
1965 294 : nnp%rad(i)%ele(j), nnp%rad(i)%eta(j), &
1966 607 : nnp%rad(i)%rs(j), nnp%rad(i)%funccut(j)
1967 : END DO
1968 248 : DO j = 1, nnp%n_ang(i)
1969 : WRITE (unit_nr, '(1X,A,1X,I3,1X,A2,1X,I2,2(1X,A2),1X,4(F6.3,1X))') &
1970 220 : TRIM(my_label), j, nnp%ele(i), 3, &
1971 220 : nnp%ang(i)%ele1(j), nnp%ang(i)%ele2(j), &
1972 220 : nnp%ang(i)%eta(j), nnp%ang(i)%lam(j), &
1973 459 : nnp%ang(i)%zeta(j), nnp%ang(i)%funccut(j)
1974 : END DO
1975 : END DO
1976 : END IF
1977 :
1978 17 : END SUBROUTINE nnp_write_acsf
1979 :
1980 : END MODULE nnp_acsf
|