1use crate::cell::{index_cell, setcell};
5use crate::constraints::{EvalMode, EvalOutput};
6use crate::context::{ATOM_FLAG_FIXED, ATOM_FLAG_SHORT, NONE_IDX, PackContext};
7use crate::euler::{compcart, eulerrmat, eulerrmat_derivatives};
8use molrs::types::F;
9#[cfg(feature = "rayon")]
10use rayon::prelude::*;
11
12#[derive(Clone, Copy)]
13enum ExpandMode {
14 F,
15 G,
16 FG,
17}
18
19#[derive(Clone, Copy)]
24struct PbcConstants {
25 length: [F; 3],
26 inv_length: [F; 3],
27 any_active: bool,
28}
29
30#[inline(always)]
31fn pbc_constants(sys: &PackContext) -> PbcConstants {
32 let length = sys.pbc_length;
33 let mut inv_length = [0.0 as F; 3];
34 let mut any_active = false;
35 for k in 0..3 {
36 if sys.pbc_periodic[k] && length[k] > 0.0 {
37 inv_length[k] = 1.0 / length[k];
38 any_active = true;
39 }
40 }
41 PbcConstants {
42 length,
43 inv_length,
44 any_active,
45 }
46}
47
48#[inline(always)]
49fn pbc_wrap_delta(dx: F, dy: F, dz: F, pbc: &PbcConstants) -> (F, F, F) {
50 if !pbc.any_active {
51 return (dx, dy, dz);
52 }
53 (
54 dx - (dx * pbc.inv_length[0]).round() * pbc.length[0],
55 dy - (dy * pbc.inv_length[1]).round() * pbc.length[1],
56 dz - (dz * pbc.inv_length[2]).round() * pbc.length[2],
57 )
58}
59
60#[derive(Clone, Copy)]
80struct AtomHotState {
81 props: crate::context::AtomProps,
82 has_short: bool,
84 fixed_i: bool,
87 use_short_i: bool,
90 shrad_i: F,
91 shscl_i: F,
92}
93
94impl AtomHotState {
95 #[inline(always)]
96 fn load(icart: usize, sys: &PackContext) -> Self {
97 let props = sys.atom_props[icart];
98 let has_short = sys.any_short_radius;
99 let has_fixed = sys.any_fixed_atoms;
100 let fixed_i = has_fixed && (props.flags & ATOM_FLAG_FIXED != 0);
101 let use_short_i = has_short && (props.flags & ATOM_FLAG_SHORT != 0);
102 let shrad_i = if has_short {
107 sys.short_radius[icart]
108 } else {
109 0.0
110 };
111 let shscl_i = if has_short {
112 sys.short_radius_scale[icart]
113 } else {
114 0.0
115 };
116 Self {
117 props,
118 has_short,
119 fixed_i,
120 use_short_i,
121 shrad_i,
122 shscl_i,
123 }
124 }
125}
126
127pub fn compute_f(x: &[F], sys: &mut PackContext) -> F {
139 sys.debug_assert_atom_props_sync();
140 sys.increment_ncf();
141 sys.fdist = 0.0;
142 sys.frest = 0.0;
143
144 if matches_cached_geometry(x, sys) {
145 let mut f = accumulate_constraint_values_from_xcart(sys);
146 if !sys.init1 {
147 f += accumulate_pair_f(sys);
148 f += accumulate_collective_f(sys);
149 }
150 return f;
151 }
152
153 if !sys.init1 {
154 sys.resetcells();
155 }
156
157 let mut f = expand_molecules(x, sys, ExpandMode::F);
158
159 if sys.init1 {
160 update_cached_geometry(x, sys);
161 return f;
162 }
163
164 f += accumulate_pair_f(sys);
165 f += accumulate_collective_f(sys);
166 update_cached_geometry(x, sys);
167
168 f
169}
170
171pub fn compute_fg(x: &[F], sys: &mut PackContext, g: &mut [F]) -> F {
177 sys.debug_assert_atom_props_sync();
178 sys.increment_ncf();
179 sys.increment_ncg();
180 sys.fdist = 0.0;
181 sys.frest = 0.0;
182 sys.work.gxcar.fill([0.0; 3]);
183
184 if matches_cached_geometry(x, sys) {
185 let mut f = accumulate_constraint_values_and_gradients_from_xcart(sys);
186 if !sys.init1 {
187 f += accumulate_pair_fg(sys);
188 f += accumulate_collective_fg(sys);
189 }
190 project_cartesian_gradient(x, sys, g);
191 return f;
192 }
193
194 if !sys.init1 {
195 sys.resetcells();
196 }
197
198 let mut f = expand_molecules(x, sys, ExpandMode::FG);
199
200 if !sys.init1 {
201 f += accumulate_pair_fg(sys);
202 f += accumulate_collective_fg(sys);
203 }
204
205 update_cached_geometry(x, sys);
206 project_cartesian_gradient(x, sys, g);
207
208 f
209}
210
211#[derive(Clone, Copy)]
220struct PairContribution {
221 energy: F,
222 grad: [F; 3],
223 violation: F,
224 overlap: bool,
225}
226
227#[inline(always)]
241fn pair_term<const GRAD: bool, const VIOLATION: bool>(
242 hot: &AtomHotState,
243 xi: [F; 3],
244 jcart: usize,
245 sys: &PackContext,
246 pbc: &PbcConstants,
247) -> Option<PairContribution> {
248 let props_j = sys.atom_props[jcart];
249 if hot.props.ibmol == props_j.ibmol && hot.props.ibtype == props_j.ibtype {
251 return None;
252 }
253 if hot.fixed_i && (props_j.flags & ATOM_FLAG_FIXED != 0) {
255 return None;
256 }
257
258 let xj = sys.xcart[jcart];
259 let (dx, dy, dz) = pbc_wrap_delta(xi[0] - xj[0], xi[1] - xj[1], xi[2] - xj[2], pbc);
260 let datom = dx * dx + dy * dy + dz * dz;
261 let rsum = hot.props.radius + props_j.radius;
262 let tol = rsum * rsum;
263
264 let mut energy: F = 0.0;
265 let mut grad = [0.0; 3];
266 let overlap = datom < tol;
267
268 if overlap {
269 let penalty = datom - tol;
270 let scale = hot.props.fscale * props_j.fscale;
271 energy += scale * penalty * penalty;
272 if GRAD {
273 let dtemp = scale * 4.0 * penalty;
274 grad[0] += dtemp * dx;
275 grad[1] += dtemp * dy;
276 grad[2] += dtemp * dz;
277 }
278 if hot.has_short && (hot.use_short_i || (props_j.flags & ATOM_FLAG_SHORT != 0)) {
279 let short_rsum = hot.shrad_i + sys.short_radius[jcart];
280 let short_tol = short_rsum * short_rsum;
281 if datom < short_tol {
282 let short_penalty = datom - short_tol;
283 let mut sr_scale = (hot.shscl_i * sys.short_radius_scale[jcart]).sqrt();
284 sr_scale *= (tol * tol) / (short_tol * short_tol);
285 let sr_pair_scale = scale * sr_scale;
286 energy += sr_pair_scale * short_penalty * short_penalty;
287 if GRAD {
288 let dtemp2 = sr_pair_scale * 4.0 * short_penalty;
289 grad[0] += dtemp2 * dx;
290 grad[1] += dtemp2 * dy;
291 grad[2] += dtemp2 * dz;
292 }
293 }
294 }
295 }
296
297 let violation = if VIOLATION {
298 let rsum_ini = hot.props.radius_ini + props_j.radius_ini;
299 let tol_ini = rsum_ini * rsum_ini;
300 tol_ini - datom
301 } else {
302 0.0
303 };
304
305 Some(PairContribution {
306 energy,
307 grad,
308 violation,
309 overlap,
310 })
311}
312
313#[inline(always)]
325fn fparc(icart: usize, first_jcart: u32, sys: &mut PackContext, pbc: &PbcConstants) -> (F, F) {
326 let mut result = 0.0;
327 let mut local_fdist: F = 0.0;
328 let mut jcart_id = first_jcart;
329 let xi = sys.xcart[icart];
330 let hot = AtomHotState::load(icart, sys);
331 let move_flag = sys.move_flag;
332
333 while jcart_id != NONE_IDX {
334 let jcart = jcart_id as usize;
335 let next = sys.latomnext[jcart];
336 if let Some(c) = pair_term::<false, true>(&hot, xi, jcart, sys, pbc) {
337 result += c.energy;
338 if c.violation > local_fdist {
339 local_fdist = c.violation;
340 }
341 if move_flag {
342 if c.violation > sys.fdist_atom[icart] {
343 sys.fdist_atom[icart] = c.violation;
344 }
345 if c.violation > sys.fdist_atom[jcart] {
346 sys.fdist_atom[jcart] = c.violation;
347 }
348 }
349 }
350 jcart_id = next;
351 }
352
353 (result, local_fdist)
354}
355
356pub fn compute_g(x: &[F], sys: &mut PackContext, g: &mut [F]) {
359 sys.debug_assert_atom_props_sync();
360 sys.increment_ncg();
361 sys.work.gxcar.fill([0.0; 3]);
363
364 if matches_cached_geometry(x, sys) {
365 accumulate_constraint_gradient_from_xcart(sys);
366 if !sys.init1 {
367 accumulate_pair_g(sys);
368 let _ = accumulate_collective_fg(sys);
369 }
370 project_cartesian_gradient(x, sys, g);
371 return;
372 }
373
374 if !sys.init1 {
375 sys.resetcells();
376 }
377
378 expand_molecules(x, sys, ExpandMode::G);
379
380 if !sys.init1 {
381 accumulate_pair_g(sys);
382 let _ = accumulate_collective_fg(sys);
383 }
384
385 update_cached_geometry(x, sys);
386 project_cartesian_gradient(x, sys, g);
387}
388
389fn expand_molecules(x: &[F], sys: &mut PackContext, mode: ExpandMode) -> F {
404 let mut descs = std::mem::take(&mut sys.work.mol_descs);
407 fill_active_mol_descs(&mut descs, sys);
408
409 let mut xcart = std::mem::take(&mut sys.xcart);
415 let mut gxcar = std::mem::take(&mut sys.work.gxcar);
416 let mut frest_atom = std::mem::take(&mut sys.frest_atom);
417 let scale = sys.scale;
418 let scale2 = sys.scale2;
419 let move_flag = sys.move_flag;
420 let parallel = sys.parallel_pair_eval && !move_flag;
423
424 #[derive(Clone, Copy)]
425 struct Slots {
426 xcart: *mut [F; 3],
427 gxcar: *mut [F; 3],
428 frest_atom: *mut F,
429 }
430 unsafe impl Send for Slots {}
433 unsafe impl Sync for Slots {}
434 impl Slots {
435 #[inline(always)]
441 unsafe fn xcart_at(self, i: usize) -> *mut [F; 3] {
442 unsafe { self.xcart.add(i) }
443 }
444 #[inline(always)]
447 unsafe fn gxcar_at<'a>(self, i: usize) -> &'a mut [F; 3] {
448 unsafe { &mut *self.gxcar.add(i) }
449 }
450 #[inline(always)]
453 unsafe fn frest_atom_at(self, i: usize) -> *mut F {
454 unsafe { self.frest_atom.add(i) }
455 }
456 }
457 let slots = Slots {
458 xcart: xcart.as_mut_ptr(),
459 gxcar: gxcar.as_mut_ptr(),
460 frest_atom: frest_atom.as_mut_ptr(),
461 };
462
463 let (f_total, frest_max) = {
466 let sys_ro: &PackContext = sys;
467 let body = |&(itype, icart0, ilubar, ilugan): &(usize, usize, usize, usize)| -> (F, F) {
469 let (v1, v2, v3) = eulerrmat(x[ilugan], x[ilugan + 1], x[ilugan + 2]);
470 let xcm = [x[ilubar], x[ilubar + 1], x[ilubar + 2]];
471 let idbase = sys_ro.idfirst[itype];
472 let na = sys_ro.natoms[itype];
473 let mut f_local: F = 0.0;
474 let mut frest_local: F = 0.0;
475 for iatom in 0..na {
476 let icart = icart0 + iatom;
477 let pos = compcart(&xcm, &sys_ro.coor[idbase + iatom], &v1, &v2, &v3);
478 unsafe {
480 *slots.xcart_at(icart) = pos;
481 }
482 let start = sys_ro.iratom_offsets[icart];
483 let end = sys_ro.iratom_offsets[icart + 1];
484 if start == end {
485 continue;
486 }
487 if matches!(mode, ExpandMode::F | ExpandMode::FG) {
489 let mut fplus = 0.0;
490 for &irest in &sys_ro.iratom_data[start..end] {
491 fplus += sys_ro.restraints[irest].f(&pos, scale, scale2);
492 }
493 f_local += fplus;
494 if fplus > frest_local {
495 frest_local = fplus;
496 }
497 if move_flag {
498 unsafe {
500 *slots.frest_atom_at(icart) += fplus;
501 }
502 }
503 }
504 if matches!(mode, ExpandMode::G | ExpandMode::FG) {
506 let gc = unsafe { slots.gxcar_at(icart) };
508 for &irest in &sys_ro.iratom_data[start..end] {
509 let _ = sys_ro.restraints[irest].fg(&pos, scale, scale2, gc);
510 }
511 }
512 }
513 (f_local, frest_local)
514 };
515 expand_reduce(&descs, &body, parallel)
516 };
517
518 sys.xcart = xcart;
520 sys.work.gxcar = gxcar;
521 sys.frest_atom = frest_atom;
522 if frest_max > sys.frest {
523 sys.frest = frest_max;
524 }
525
526 if !sys.init1 {
529 for &(itype, icart0, _, _) in &descs {
530 let na = sys.natoms[itype];
531 for iatom in 0..na {
532 let icart = icart0 + iatom;
533 let pos = sys.xcart[icart];
534 insert_atom_in_cell(icart, &pos, sys);
535 }
536 }
537 }
538
539 sys.work.mol_descs = descs;
540 f_total
541}
542
543#[cfg(feature = "rayon")]
548fn expand_reduce<B>(descs: &[(usize, usize, usize, usize)], body: &B, parallel: bool) -> (F, F)
549where
550 B: Fn(&(usize, usize, usize, usize)) -> (F, F) + Sync,
551{
552 use rayon::prelude::*;
553 if parallel {
554 descs
555 .par_iter()
556 .map(body)
557 .reduce(|| (0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)))
558 } else {
559 descs
560 .iter()
561 .map(body)
562 .fold((0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)))
563 }
564}
565
566#[cfg(not(feature = "rayon"))]
568fn expand_reduce<B>(descs: &[(usize, usize, usize, usize)], body: &B, _parallel: bool) -> (F, F)
569where
570 B: Fn(&(usize, usize, usize, usize)) -> (F, F),
571{
572 descs
573 .iter()
574 .map(body)
575 .fold((0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)))
576}
577
578fn fill_active_mol_descs(descs: &mut Vec<(usize, usize, usize, usize)>, sys: &PackContext) {
585 descs.clear();
586 let mut ilubar = 0usize;
587 let mut ilugan = sys.ntotmol * 3;
588 let mut icart = 0usize;
589 for itype in 0..sys.ntype {
590 if !sys.comptype[itype] {
591 icart += sys.nmols[itype] * sys.natoms[itype];
592 continue;
593 }
594 for _ in 0..sys.nmols[itype] {
595 descs.push((itype, icart, ilubar, ilugan));
596 icart += sys.natoms[itype];
597 ilubar += 3;
598 ilugan += 3;
599 }
600 }
601}
602
603#[inline(always)]
604fn accumulate_constraint_value(icart: usize, pos: &[F; 3], sys: &mut PackContext) -> F {
605 let mut fplus = 0.0;
606 let start = sys.iratom_offsets[icart];
607 let end = sys.iratom_offsets[icart + 1];
608 for &irest in &sys.iratom_data[start..end] {
609 fplus += sys.restraints[irest].f(pos, sys.scale, sys.scale2);
610 }
611 if fplus > sys.frest {
612 sys.frest = fplus;
613 }
614 if sys.move_flag {
615 sys.frest_atom[icart] += fplus;
616 }
617 fplus
618}
619
620#[inline(always)]
621fn accumulate_constraint_gradient(icart: usize, pos: &[F; 3], sys: &mut PackContext) {
622 let start = sys.iratom_offsets[icart];
623 let end = sys.iratom_offsets[icart + 1];
624 let scale = sys.scale;
625 let scale2 = sys.scale2;
626 let gc = &mut sys.work.gxcar[icart];
627 for &irest in &sys.iratom_data[start..end] {
628 let _ = sys.restraints[irest].fg(pos, scale, scale2, gc);
630 }
631}
632
633#[inline]
634fn accumulate_constraint_gradient_from_xcart(sys: &mut PackContext) {
635 let mut icart = 0usize;
636
637 for itype in 0..sys.ntype {
638 if !sys.comptype[itype] {
639 icart += sys.nmols[itype] * sys.natoms[itype];
640 continue;
641 }
642
643 for _imol in 0..sys.nmols[itype] {
644 for _iatom in 0..sys.natoms[itype] {
645 let pos = sys.xcart[icart];
646 accumulate_constraint_gradient(icart, &pos, sys);
647 icart += 1;
648 }
649 }
650 }
651}
652
653#[inline]
658fn accumulate_constraint_values_from_xcart(sys: &mut PackContext) -> F {
659 let mut f = 0.0;
660 let mut icart = 0usize;
661
662 for itype in 0..sys.ntype {
663 if !sys.comptype[itype] {
664 icart += sys.nmols[itype] * sys.natoms[itype];
665 continue;
666 }
667
668 for _imol in 0..sys.nmols[itype] {
669 for _iatom in 0..sys.natoms[itype] {
670 let pos = sys.xcart[icart];
671 f += accumulate_constraint_value(icart, &pos, sys);
672 icart += 1;
673 }
674 }
675 }
676
677 f
678}
679
680#[inline]
683fn accumulate_constraint_values_and_gradients_from_xcart(sys: &mut PackContext) -> F {
684 let mut f = 0.0;
685 let mut icart = 0usize;
686
687 for itype in 0..sys.ntype {
688 if !sys.comptype[itype] {
689 icart += sys.nmols[itype] * sys.natoms[itype];
690 continue;
691 }
692
693 for _imol in 0..sys.nmols[itype] {
694 for _iatom in 0..sys.natoms[itype] {
695 let pos = sys.xcart[icart];
696 f += accumulate_constraint_value(icart, &pos, sys);
697 accumulate_constraint_gradient(icart, &pos, sys);
698 icart += 1;
699 }
700 }
701 }
702
703 f
704}
705
706#[inline]
709fn type_icart_start(sys: &PackContext, itype: usize) -> usize {
710 let mut start = 0usize;
711 for t in 0..itype {
712 start += sys.nmols[t] * sys.natoms[t];
713 }
714 start
715}
716
717fn accumulate_collective_f(sys: &PackContext) -> F {
721 if sys.collective.is_empty() {
722 return 0.0;
723 }
724 let (scale, scale2) = (sys.scale, sys.scale2);
725 let mut total = 0.0;
726 for (itype, r) in &sys.collective {
727 let itype = *itype;
728 if !sys.comptype[itype] {
729 continue;
730 }
731 let start = type_icart_start(sys, itype);
732 let len = sys.nmols[itype] * sys.natoms[itype];
733 if len == 0 {
734 continue;
735 }
736 total += r.f(&sys.xcart[start..start + len], scale, scale2);
737 }
738 total
739}
740
741fn accumulate_collective_fg(sys: &mut PackContext) -> F {
747 if sys.collective.is_empty() {
748 return 0.0;
749 }
750 let (scale, scale2) = (sys.scale, sys.scale2);
751 let collective = std::mem::take(&mut sys.collective);
754 let mut total = 0.0;
755 for (itype, r) in &collective {
756 let itype = *itype;
757 if !sys.comptype[itype] {
758 continue;
759 }
760 let start = type_icart_start(sys, itype);
761 let len = sys.nmols[itype] * sys.natoms[itype];
762 if len == 0 {
763 continue;
764 }
765 let coords: Vec<[F; 3]> = sys.xcart[start..start + len].to_vec();
766 let mut grads = vec![[0.0 as F; 3]; len];
767 total += r.fg(&coords, scale, scale2, &mut grads);
768 for (k, g) in grads.iter().enumerate() {
769 let gc = &mut sys.work.gxcar[start + k];
770 gc[0] += g[0];
771 gc[1] += g[1];
772 gc[2] += g[2];
773 }
774 }
775 sys.collective = collective;
776 total
777}
778
779#[inline(always)]
780fn insert_atom_in_cell(icart: usize, pos: &[F; 3], sys: &mut PackContext) {
781 let cell = setcell(
782 pos,
783 &sys.pbc_min,
784 &sys.pbc_length,
785 &sys.cell_length,
786 &sys.ncells,
787 &sys.pbc_periodic,
788 );
789 let icell = index_cell(&cell, &sys.ncells);
790 sys.latomnext[icart] = sys.latomfirst[icell];
791 sys.latomfirst[icell] = icart as u32;
792
793 if sys.empty_cell[icell] {
794 sys.empty_cell[icell] = false;
795 sys.active_cells.push(icell);
796 sys.lcellnext[icell] = sys.lcellfirst;
797 sys.lcellfirst = icell as u32;
798 }
799
800 }
803
804#[inline(always)]
805fn accumulate_pair_f(sys: &mut PackContext) -> F {
806 #[cfg(feature = "rayon")]
807 if sys.parallel_pair_eval && !sys.move_flag {
808 let (f, fdist_max) = accumulate_pair_f_parallel(sys);
809 sys.fdist = sys.fdist.max(fdist_max);
810 return f;
811 }
812
813 let pbc = pbc_constants(sys);
814 let mut f = 0.0;
815 let mut fdist_local: F = 0.0;
816 let mut icell_id = sys.lcellfirst;
817 while icell_id != NONE_IDX {
818 let icell = icell_id as usize;
819 let neighbors = sys.neighbor_cells_f[icell];
820
821 let mut icart_id = sys.latomfirst[icell];
822 while icart_id != NONE_IDX {
823 let icart = icart_id as usize;
824 let (df, dfd) = fparc(icart, sys.latomnext[icart], sys, &pbc);
825 f += df;
826 if dfd > fdist_local {
827 fdist_local = dfd;
828 }
829 for &ncell in &neighbors {
830 let (df, dfd) = fparc(icart, sys.latomfirst[ncell], sys, &pbc);
831 f += df;
832 if dfd > fdist_local {
833 fdist_local = dfd;
834 }
835 }
836
837 icart_id = sys.latomnext[icart];
838 }
839
840 icell_id = sys.lcellnext[icell];
841 }
842
843 if fdist_local > sys.fdist {
844 sys.fdist = fdist_local;
845 }
846 f
847}
848
849#[cfg(feature = "rayon")]
850fn accumulate_pair_f_parallel(sys: &PackContext) -> (F, F) {
851 let pbc = pbc_constants(sys);
852 sys.active_cells
853 .par_iter()
854 .map(|&icell| {
855 let mut f: F = 0.0;
856 let mut fdist_max: F = 0.0;
857 let neighbors = sys.neighbor_cells_f[icell];
858
859 let mut icart_id = sys.latomfirst[icell];
860 while icart_id != NONE_IDX {
861 let icart = icart_id as usize;
862 let (f_same, fdist_same) = fparc_stats(icart, sys.latomnext[icart], sys, &pbc);
863 f += f_same;
864 fdist_max = fdist_max.max(fdist_same);
865
866 for &ncell in &neighbors {
867 let (f_neigh, fdist_neigh) =
868 fparc_stats(icart, sys.latomfirst[ncell], sys, &pbc);
869 f += f_neigh;
870 fdist_max = fdist_max.max(fdist_neigh);
871 }
872
873 icart_id = sys.latomnext[icart];
874 }
875
876 (f, fdist_max)
877 })
878 .reduce(
879 || (0.0 as F, 0.0 as F),
880 |(f1, d1), (f2, d2)| (f1 + f2, d1.max(d2)),
881 )
882}
883
884#[inline(always)]
885fn accumulate_pair_g(sys: &mut PackContext) {
886 let pbc = pbc_constants(sys);
887 let mut icell_id = sys.lcellfirst;
888 while icell_id != NONE_IDX {
889 let icell = icell_id as usize;
890 let neighbors = sys.neighbor_cells_g[icell];
891
892 let mut icart_id = sys.latomfirst[icell];
893 while icart_id != NONE_IDX {
894 let icart = icart_id as usize;
895 gparc(icart, sys.latomnext[icart], sys, &pbc);
896 for &ncell in &neighbors {
897 gparc(icart, sys.latomfirst[ncell], sys, &pbc);
898 }
899
900 icart_id = sys.latomnext[icart];
901 }
902
903 icell_id = sys.lcellnext[icell];
904 }
905}
906
907#[inline(always)]
908fn accumulate_pair_fg(sys: &mut PackContext) -> F {
909 #[cfg(feature = "rayon")]
910 if sys.parallel_pair_eval && !sys.move_flag {
911 let (f, fdist_max) = accumulate_pair_fg_parallel(sys);
912 if fdist_max > sys.fdist {
913 sys.fdist = fdist_max;
914 }
915 return f;
916 }
917
918 let pbc = pbc_constants(sys);
919 let mut f = 0.0;
920 let mut fdist_local: F = 0.0;
921 let mut icell_id = sys.lcellfirst;
922 while icell_id != NONE_IDX {
923 let icell = icell_id as usize;
924 let neighbors = sys.neighbor_cells_g[icell];
925
926 let mut icart_id = sys.latomfirst[icell];
927 while icart_id != NONE_IDX {
928 let icart = icart_id as usize;
929 let (df, dfd) = fgparc(icart, sys.latomnext[icart], sys, &pbc);
930 f += df;
931 if dfd > fdist_local {
932 fdist_local = dfd;
933 }
934 for &ncell in &neighbors {
935 let (df, dfd) = fgparc(icart, sys.latomfirst[ncell], sys, &pbc);
936 f += df;
937 if dfd > fdist_local {
938 fdist_local = dfd;
939 }
940 }
941
942 icart_id = sys.latomnext[icart];
943 }
944
945 icell_id = sys.lcellnext[icell];
946 }
947
948 if fdist_local > sys.fdist {
949 sys.fdist = fdist_local;
950 }
951 f
952}
953
954#[cfg(feature = "rayon")]
960#[derive(Clone, Copy)]
961struct PartialPtr {
962 base: *mut [F; 3],
963 ntotat: usize,
964}
965#[cfg(feature = "rayon")]
968unsafe impl Send for PartialPtr {}
969#[cfg(feature = "rayon")]
970unsafe impl Sync for PartialPtr {}
971#[cfg(feature = "rayon")]
972impl PartialPtr {
973 #[inline(always)]
976 unsafe fn slot<'a>(self, t: usize, i: usize) -> &'a mut [F; 3] {
977 unsafe { &mut *self.base.add(t * self.ntotat + i) }
978 }
979}
980
981#[cfg(feature = "rayon")]
1002fn accumulate_pair_fg_parallel(sys: &mut PackContext) -> (F, F) {
1003 let ntotat = sys.ntotat;
1004 let nthreads = rayon::current_num_threads().max(1);
1005
1006 let mut partials = std::mem::take(&mut sys.work.grad_partials);
1011 if partials.len() != nthreads * ntotat {
1012 partials.resize(nthreads * ntotat, [0.0; 3]);
1013 }
1014 partials
1015 .par_chunks_mut(ntotat.max(1))
1016 .for_each(|region| region.fill([0.0; 3]));
1017
1018 let pbc = pbc_constants(sys);
1019 let sys_ro: &PackContext = sys;
1020 let pptr = PartialPtr {
1021 base: partials.as_mut_ptr(),
1022 ntotat,
1023 };
1024
1025 let (f_total, fdist_max) = sys_ro
1026 .active_cells
1027 .par_iter()
1028 .map(|&icell| {
1029 let t = rayon::current_thread_index().unwrap_or(0);
1032 let neighbors = &sys_ro.neighbor_cells_g[icell];
1033 let mut f_local: F = 0.0;
1034 let mut fdist_local: F = 0.0;
1035 let mut icart_id = sys_ro.latomfirst[icell];
1036 while icart_id != NONE_IDX {
1037 let icart = icart_id as usize;
1038 let (df, dfd) = fgparc_into(icart, sys_ro.latomnext[icart], sys_ro, pptr, t, &pbc);
1040 f_local += df;
1041 if dfd > fdist_local {
1042 fdist_local = dfd;
1043 }
1044 for &ncell in neighbors {
1046 let (df, dfd) =
1047 fgparc_into(icart, sys_ro.latomfirst[ncell], sys_ro, pptr, t, &pbc);
1048 f_local += df;
1049 if dfd > fdist_local {
1050 fdist_local = dfd;
1051 }
1052 }
1053 icart_id = sys_ro.latomnext[icart];
1054 }
1055 (f_local, fdist_local)
1056 })
1057 .reduce(|| (0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)));
1058
1059 let mut grad = std::mem::take(&mut sys.work.gxcar);
1063 let chunk = ntotat.div_ceil(nthreads).max(1);
1064 {
1065 let partials_ref: &[[F; 3]] = &partials;
1066 grad.par_chunks_mut(chunk)
1067 .enumerate()
1068 .for_each(|(ci, gchunk)| {
1069 let start = ci * chunk;
1070 let len = gchunk.len();
1071 for t in 0..nthreads {
1072 let base = t * ntotat + start;
1073 let region = &partials_ref[base..base + len];
1074 for (g, p) in gchunk.iter_mut().zip(region) {
1075 g[0] += p[0];
1076 g[1] += p[1];
1077 g[2] += p[2];
1078 }
1079 }
1080 });
1081 }
1082
1083 sys.work.gxcar = grad;
1084 sys.work.grad_partials = partials;
1085 (f_total, fdist_max)
1086}
1087
1088#[cfg(feature = "rayon")]
1099#[inline(always)]
1100fn fgparc_into(
1101 icart: usize,
1102 first_jcart: u32,
1103 sys: &PackContext,
1104 pptr: PartialPtr,
1105 t: usize,
1106 pbc: &PbcConstants,
1107) -> (F, F) {
1108 let mut result: F = 0.0;
1109 let mut local_fdist: F = 0.0;
1110 let mut jcart_id = first_jcart;
1111 let xi = sys.xcart[icart];
1112 let hot = AtomHotState::load(icart, sys);
1113
1114 while jcart_id != NONE_IDX {
1115 let jcart = jcart_id as usize;
1116 let next = sys.latomnext[jcart];
1117 if let Some(c) = pair_term::<true, true>(&hot, xi, jcart, sys, pbc) {
1118 result += c.energy;
1119 if c.overlap {
1120 {
1124 let gi = unsafe { pptr.slot(t, icart) };
1125 gi[0] += c.grad[0];
1126 gi[1] += c.grad[1];
1127 gi[2] += c.grad[2];
1128 }
1129 {
1130 let gj = unsafe { pptr.slot(t, jcart) };
1131 gj[0] -= c.grad[0];
1132 gj[1] -= c.grad[1];
1133 gj[2] -= c.grad[2];
1134 }
1135 }
1136 if c.violation > local_fdist {
1137 local_fdist = c.violation;
1138 }
1139 }
1140 jcart_id = next;
1141 }
1142
1143 (result, local_fdist)
1144}
1145
1146#[inline(always)]
1149fn gparc(icart: usize, first_jcart: u32, sys: &mut PackContext, pbc: &PbcConstants) {
1150 let mut jcart_id = first_jcart;
1151 let xi = sys.xcart[icart];
1152 let hot = AtomHotState::load(icart, sys);
1153
1154 while jcart_id != NONE_IDX {
1155 let jcart = jcart_id as usize;
1156 let next = sys.latomnext[jcart];
1157 if let Some(c) = pair_term::<true, false>(&hot, xi, jcart, sys, pbc)
1158 && c.overlap
1159 {
1160 let gi = &mut sys.work.gxcar[icart];
1161 gi[0] += c.grad[0];
1162 gi[1] += c.grad[1];
1163 gi[2] += c.grad[2];
1164 let gj = &mut sys.work.gxcar[jcart];
1165 gj[0] -= c.grad[0];
1166 gj[1] -= c.grad[1];
1167 gj[2] -= c.grad[2];
1168 }
1169 jcart_id = next;
1170 }
1171}
1172
1173#[inline(always)]
1178fn fgparc(icart: usize, first_jcart: u32, sys: &mut PackContext, pbc: &PbcConstants) -> (F, F) {
1179 let mut result = 0.0;
1180 let mut local_fdist: F = 0.0;
1181 let mut jcart_id = first_jcart;
1182 let xi = sys.xcart[icart];
1183 let hot = AtomHotState::load(icart, sys);
1184 let move_flag = sys.move_flag;
1185
1186 while jcart_id != NONE_IDX {
1187 let jcart = jcart_id as usize;
1188 let next = sys.latomnext[jcart];
1189 if let Some(c) = pair_term::<true, true>(&hot, xi, jcart, sys, pbc) {
1190 result += c.energy;
1191 if c.overlap {
1192 let gi = &mut sys.work.gxcar[icart];
1193 gi[0] += c.grad[0];
1194 gi[1] += c.grad[1];
1195 gi[2] += c.grad[2];
1196 let gj = &mut sys.work.gxcar[jcart];
1197 gj[0] -= c.grad[0];
1198 gj[1] -= c.grad[1];
1199 gj[2] -= c.grad[2];
1200 }
1201 if c.violation > local_fdist {
1202 local_fdist = c.violation;
1203 }
1204 if move_flag {
1205 if c.violation > sys.fdist_atom[icart] {
1206 sys.fdist_atom[icart] = c.violation;
1207 }
1208 if c.violation > sys.fdist_atom[jcart] {
1209 sys.fdist_atom[jcart] = c.violation;
1210 }
1211 }
1212 }
1213 jcart_id = next;
1214 }
1215
1216 (result, local_fdist)
1217}
1218
1219#[cfg(feature = "rayon")]
1220#[inline(always)]
1221fn fparc_stats(icart: usize, first_jcart: u32, sys: &PackContext, pbc: &PbcConstants) -> (F, F) {
1222 let mut result: F = 0.0;
1223 let mut fdist_max: F = 0.0;
1224 let mut jcart_id = first_jcart;
1225 let xi = sys.xcart[icart];
1226 let hot = AtomHotState::load(icart, sys);
1227
1228 while jcart_id != NONE_IDX {
1229 let jcart = jcart_id as usize;
1230 let next = sys.latomnext[jcart];
1231 if let Some(c) = pair_term::<false, true>(&hot, xi, jcart, sys, pbc) {
1232 result += c.energy;
1233 if c.violation > fdist_max {
1234 fdist_max = c.violation;
1235 }
1236 }
1237 jcart_id = next;
1238 }
1239
1240 (result, fdist_max)
1241}
1242
1243fn project_cartesian_gradient(x: &[F], sys: &mut PackContext, g: &mut [F]) {
1252 g.iter_mut().for_each(|v| *v = 0.0);
1253
1254 let mut descs = std::mem::take(&mut sys.work.mol_descs);
1255 fill_active_mol_descs(&mut descs, sys);
1256 let move_flag = sys.move_flag;
1257 let parallel = sys.parallel_pair_eval && !move_flag;
1258
1259 #[derive(Clone, Copy)]
1260 struct GPtr(*mut F);
1261 unsafe impl Send for GPtr {}
1264 unsafe impl Sync for GPtr {}
1265 impl GPtr {
1266 #[inline(always)]
1271 unsafe fn write(self, i: usize, v: F) {
1272 unsafe {
1273 *self.0.add(i) = v;
1274 }
1275 }
1276 }
1277 let gptr = GPtr(g.as_mut_ptr());
1278
1279 {
1281 let sys_ro: &PackContext = sys;
1282 let body = |&(itype, icart0, ilubar, ilugan): &(usize, usize, usize, usize)| {
1283 let beta = x[ilugan];
1284 let gama = x[ilugan + 1];
1285 let teta = x[ilugan + 2];
1286 let (dv1beta, dv1gama, dv1teta, dv2beta, dv2gama, dv2teta, dv3beta, dv3gama, dv3teta) =
1287 eulerrmat_derivatives(beta, gama, teta);
1288
1289 let idbase = sys_ro.idfirst[itype];
1290 let na = sys_ro.natoms[itype];
1291 let mut gcom = [0.0 as F; 3];
1292 let mut gang = [0.0 as F; 3];
1293 for iatom in 0..na {
1294 let gx = sys_ro.work.gxcar[icart0 + iatom];
1295 let cr = sys_ro.coor[idbase + iatom];
1296 for k in 0..3 {
1297 gcom[k] += gx[k];
1298 }
1299 for k in 0..3 {
1300 gang[0] +=
1301 (cr[0] * dv1beta[k] + cr[1] * dv2beta[k] + cr[2] * dv3beta[k]) * gx[k];
1302 gang[1] +=
1303 (cr[0] * dv1gama[k] + cr[1] * dv2gama[k] + cr[2] * dv3gama[k]) * gx[k];
1304 gang[2] +=
1305 (cr[0] * dv1teta[k] + cr[1] * dv2teta[k] + cr[2] * dv3teta[k]) * gx[k];
1306 }
1307 }
1308 unsafe {
1310 for k in 0..3 {
1311 gptr.write(ilubar + k, gcom[k]);
1312 gptr.write(ilugan + k, gang[k]);
1313 }
1314 }
1315 };
1316 project_for_each(&descs, &body, parallel);
1317 }
1318
1319 sys.work.mol_descs = descs;
1320}
1321
1322#[cfg(feature = "rayon")]
1327fn project_for_each<B>(descs: &[(usize, usize, usize, usize)], body: &B, parallel: bool)
1328where
1329 B: Fn(&(usize, usize, usize, usize)) + Sync,
1330{
1331 use rayon::prelude::*;
1332 if parallel {
1333 descs.par_iter().for_each(body);
1334 } else {
1335 descs.iter().for_each(body);
1336 }
1337}
1338
1339#[cfg(not(feature = "rayon"))]
1341fn project_for_each<B>(descs: &[(usize, usize, usize, usize)], body: &B, _parallel: bool)
1342where
1343 B: Fn(&(usize, usize, usize, usize)),
1344{
1345 descs.iter().for_each(body);
1346}
1347
1348#[inline]
1349fn matches_cached_geometry(x: &[F], sys: &PackContext) -> bool {
1350 sys.work.matches_cached_geometry(
1351 x,
1352 &sys.comptype,
1353 sys.init1,
1354 sys.ncells,
1355 sys.cell_length,
1356 sys.pbc_min,
1357 sys.pbc_length,
1358 sys.pbc_periodic,
1359 )
1360}
1361
1362#[inline]
1363fn update_cached_geometry(x: &[F], sys: &mut PackContext) {
1364 sys.work.update_cached_geometry(
1365 x,
1366 &sys.comptype,
1367 sys.init1,
1368 sys.ncells,
1369 sys.cell_length,
1370 sys.pbc_min,
1371 sys.pbc_length,
1372 sys.pbc_periodic,
1373 );
1374}
1375
1376pub trait Objective {
1407 fn evaluate(&mut self, x: &[F], mode: EvalMode, gradient: Option<&mut [F]>) -> EvalOutput;
1417
1418 fn fdist(&self) -> F;
1420
1421 fn frest(&self) -> F;
1424
1425 fn ncf(&self) -> usize;
1428
1429 fn ncg(&self) -> usize;
1432
1433 fn reset_eval_counters(&mut self);
1436
1437 fn bounds(&self, l: &mut [F], u: &mut [F]) {
1447 debug_assert_eq!(l.len(), u.len(), "bounds: l/u length mismatch");
1448 l.fill(-1.0e20);
1449 u.fill(1.0e20);
1450 }
1451}
1452
1453impl Objective for PackContext {
1454 #[inline]
1455 fn evaluate(&mut self, x: &[F], mode: EvalMode, gradient: Option<&mut [F]>) -> EvalOutput {
1456 PackContext::evaluate(self, x, mode, gradient)
1457 }
1458
1459 #[inline]
1460 fn fdist(&self) -> F {
1461 self.fdist
1462 }
1463
1464 #[inline]
1465 fn frest(&self) -> F {
1466 self.frest
1467 }
1468
1469 #[inline]
1470 fn ncf(&self) -> usize {
1471 PackContext::ncf(self)
1472 }
1473
1474 #[inline]
1475 fn ncg(&self) -> usize {
1476 PackContext::ncg(self)
1477 }
1478
1479 #[inline]
1480 fn reset_eval_counters(&mut self) {
1481 PackContext::reset_eval_counters(self);
1482 }
1483
1484 fn bounds(&self, l: &mut [F], u: &mut [F]) {
1489 debug_assert_eq!(l.len(), u.len(), "bounds: l/u length mismatch");
1490 let n = l.len();
1491 l.fill(-1.0e20);
1492 u.fill(1.0e20);
1493 let mut i = n / 2;
1494 for itype in 0..self.ntype {
1495 if !self.comptype[itype] {
1496 continue;
1497 }
1498 for _imol in 0..self.nmols[itype] {
1499 for axis in 0..3 {
1500 if self.constrain_rot[itype][axis] {
1501 let center = self.rot_bound[itype][axis][0];
1502 let half_width = self.rot_bound[itype][axis][1].abs();
1503 l[i] = center - half_width;
1504 u[i] = center + half_width;
1505 }
1506 i += 1;
1507 }
1508 }
1509 }
1510 debug_assert_eq!(i, n);
1511 }
1512}
1513
1514#[cfg(test)]
1515mod objective_trait_tests {
1516 use super::*;
1517 use crate::PackContext;
1518
1519 #[test]
1530 fn dyn_objective_matches_inherent_evaluate() {
1531 fn build(ntotat: usize) -> PackContext {
1532 let mut sys = PackContext::new(ntotat, 0, 0);
1533 sys.radius.fill(0.75);
1534 sys.radius_ini.fill(1.5);
1535 sys.work.radiuswork.resize(ntotat, 0.0);
1536 sys.sync_atom_props();
1537 sys
1538 }
1539 let x: Vec<F> = Vec::new();
1540
1541 let mut via_inherent = build(4);
1542 let out_inherent = PackContext::evaluate(&mut via_inherent, &x, EvalMode::FOnly, None);
1543
1544 let mut via_trait_owner = build(4);
1545 let out_trait = {
1546 let obj: &mut dyn Objective = &mut via_trait_owner;
1547 obj.evaluate(&x, EvalMode::FOnly, None)
1548 };
1549
1550 assert_eq!(out_inherent.f_total, out_trait.f_total, "f_total mismatch");
1551 assert_eq!(
1552 out_inherent.fdist_max, out_trait.fdist_max,
1553 "fdist_max mismatch"
1554 );
1555 assert_eq!(
1556 out_inherent.frest_max, out_trait.frest_max,
1557 "frest_max mismatch"
1558 );
1559 assert_eq!(
1560 via_inherent.fdist, via_trait_owner.fdist,
1561 "post-call fdist drift"
1562 );
1563 assert_eq!(
1564 via_inherent.frest, via_trait_owner.frest,
1565 "post-call frest drift"
1566 );
1567
1568 let obj: &mut dyn Objective = &mut via_trait_owner;
1570 assert_eq!(obj.ncf(), 1, "ncf should be 1 after one FOnly evaluate");
1571 assert_eq!(obj.ncg(), 0, "ncg should stay 0 under FOnly");
1572 obj.reset_eval_counters();
1573 assert_eq!(obj.ncf(), 0, "ncf should be 0 after reset");
1574 assert_eq!(obj.ncg(), 0, "ncg should be 0 after reset");
1575 }
1576}