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