use crate::cell::{index_cell, setcell};
use crate::constraints::{EvalMode, EvalOutput};
use crate::context::{ATOM_FLAG_FIXED, ATOM_FLAG_SHORT, NONE_IDX, PackContext};
use crate::euler::{compcart, eulerrmat, eulerrmat_derivatives};
use molrs::types::F;
#[cfg(feature = "rayon")]
use rayon::prelude::*;
#[derive(Clone, Copy)]
enum ExpandMode {
F,
G,
FG,
}
#[derive(Clone, Copy)]
struct PbcConstants {
length: [F; 3],
inv_length: [F; 3],
any_active: bool,
}
#[inline(always)]
fn pbc_constants(sys: &PackContext) -> PbcConstants {
let length = sys.pbc_length;
let mut inv_length = [0.0 as F; 3];
let mut any_active = false;
for k in 0..3 {
if sys.pbc_periodic[k] && length[k] > 0.0 {
inv_length[k] = 1.0 / length[k];
any_active = true;
}
}
PbcConstants {
length,
inv_length,
any_active,
}
}
#[inline(always)]
fn pbc_wrap_delta(dx: F, dy: F, dz: F, pbc: &PbcConstants) -> (F, F, F) {
if !pbc.any_active {
return (dx, dy, dz);
}
(
dx - (dx * pbc.inv_length[0]).round() * pbc.length[0],
dy - (dy * pbc.inv_length[1]).round() * pbc.length[1],
dz - (dz * pbc.inv_length[2]).round() * pbc.length[2],
)
}
#[derive(Clone, Copy)]
struct AtomHotState {
props: crate::context::AtomProps,
has_short: bool,
fixed_i: bool,
use_short_i: bool,
shrad_i: F,
shscl_i: F,
}
impl AtomHotState {
#[inline(always)]
fn load(icart: usize, sys: &PackContext) -> Self {
let props = sys.atom_props[icart];
let has_short = sys.any_short_radius;
let has_fixed = sys.any_fixed_atoms;
let fixed_i = has_fixed && (props.flags & ATOM_FLAG_FIXED != 0);
let use_short_i = has_short && (props.flags & ATOM_FLAG_SHORT != 0);
let shrad_i = if has_short {
sys.short_radius[icart]
} else {
0.0
};
let shscl_i = if has_short {
sys.short_radius_scale[icart]
} else {
0.0
};
Self {
props,
has_short,
fixed_i,
use_short_i,
shrad_i,
shscl_i,
}
}
}
pub fn compute_f(x: &[F], sys: &mut PackContext) -> F {
sys.debug_assert_atom_props_sync();
sys.increment_ncf();
sys.fdist = 0.0;
sys.frest = 0.0;
if matches_cached_geometry(x, sys) {
let mut f = accumulate_constraint_values_from_xcart(sys);
if !sys.init1 {
f += accumulate_pair_f(sys);
f += accumulate_collective_f(sys);
}
return f;
}
if !sys.init1 {
sys.resetcells();
}
let mut f = expand_molecules(x, sys, ExpandMode::F);
if sys.init1 {
update_cached_geometry(x, sys);
return f;
}
f += accumulate_pair_f(sys);
f += accumulate_collective_f(sys);
update_cached_geometry(x, sys);
f
}
pub fn compute_fg(x: &[F], sys: &mut PackContext, g: &mut [F]) -> F {
sys.debug_assert_atom_props_sync();
sys.increment_ncf();
sys.increment_ncg();
sys.fdist = 0.0;
sys.frest = 0.0;
sys.work.gxcar.fill([0.0; 3]);
if matches_cached_geometry(x, sys) {
let mut f = accumulate_constraint_values_and_gradients_from_xcart(sys);
if !sys.init1 {
f += accumulate_pair_fg(sys);
f += accumulate_collective_fg(sys);
}
project_cartesian_gradient(x, sys, g);
return f;
}
if !sys.init1 {
sys.resetcells();
}
let mut f = expand_molecules(x, sys, ExpandMode::FG);
if !sys.init1 {
f += accumulate_pair_fg(sys);
f += accumulate_collective_fg(sys);
}
update_cached_geometry(x, sys);
project_cartesian_gradient(x, sys, g);
f
}
#[derive(Clone, Copy)]
struct PairContribution {
energy: F,
grad: [F; 3],
violation: F,
overlap: bool,
}
#[inline(always)]
fn pair_term<const GRAD: bool, const VIOLATION: bool>(
hot: &AtomHotState,
xi: [F; 3],
jcart: usize,
sys: &PackContext,
pbc: &PbcConstants,
) -> Option<PairContribution> {
let props_j = sys.atom_props[jcart];
if hot.props.ibmol == props_j.ibmol && hot.props.ibtype == props_j.ibtype {
return None;
}
if hot.fixed_i && (props_j.flags & ATOM_FLAG_FIXED != 0) {
return None;
}
let xj = sys.xcart[jcart];
let (dx, dy, dz) = pbc_wrap_delta(xi[0] - xj[0], xi[1] - xj[1], xi[2] - xj[2], pbc);
let datom = dx * dx + dy * dy + dz * dz;
let rsum = hot.props.radius + props_j.radius;
let tol = rsum * rsum;
let mut energy: F = 0.0;
let mut grad = [0.0; 3];
let overlap = datom < tol;
if overlap {
let penalty = datom - tol;
let scale = hot.props.fscale * props_j.fscale;
energy += scale * penalty * penalty;
if GRAD {
let dtemp = scale * 4.0 * penalty;
grad[0] += dtemp * dx;
grad[1] += dtemp * dy;
grad[2] += dtemp * dz;
}
if hot.has_short && (hot.use_short_i || (props_j.flags & ATOM_FLAG_SHORT != 0)) {
let short_rsum = hot.shrad_i + sys.short_radius[jcart];
let short_tol = short_rsum * short_rsum;
if datom < short_tol {
let short_penalty = datom - short_tol;
let mut sr_scale = (hot.shscl_i * sys.short_radius_scale[jcart]).sqrt();
sr_scale *= (tol * tol) / (short_tol * short_tol);
let sr_pair_scale = scale * sr_scale;
energy += sr_pair_scale * short_penalty * short_penalty;
if GRAD {
let dtemp2 = sr_pair_scale * 4.0 * short_penalty;
grad[0] += dtemp2 * dx;
grad[1] += dtemp2 * dy;
grad[2] += dtemp2 * dz;
}
}
}
}
let violation = if VIOLATION {
let rsum_ini = hot.props.radius_ini + props_j.radius_ini;
let tol_ini = rsum_ini * rsum_ini;
tol_ini - datom
} else {
0.0
};
Some(PairContribution {
energy,
grad,
violation,
overlap,
})
}
#[inline(always)]
fn fparc(icart: usize, first_jcart: u32, sys: &mut PackContext, pbc: &PbcConstants) -> (F, F) {
let mut result = 0.0;
let mut local_fdist: F = 0.0;
let mut jcart_id = first_jcart;
let xi = sys.xcart[icart];
let hot = AtomHotState::load(icart, sys);
let move_flag = sys.move_flag;
while jcart_id != NONE_IDX {
let jcart = jcart_id as usize;
let next = sys.latomnext[jcart];
if let Some(c) = pair_term::<false, true>(&hot, xi, jcart, sys, pbc) {
result += c.energy;
if c.violation > local_fdist {
local_fdist = c.violation;
}
if move_flag {
if c.violation > sys.fdist_atom[icart] {
sys.fdist_atom[icart] = c.violation;
}
if c.violation > sys.fdist_atom[jcart] {
sys.fdist_atom[jcart] = c.violation;
}
}
}
jcart_id = next;
}
(result, local_fdist)
}
pub fn compute_g(x: &[F], sys: &mut PackContext, g: &mut [F]) {
sys.debug_assert_atom_props_sync();
sys.increment_ncg();
sys.work.gxcar.fill([0.0; 3]);
if matches_cached_geometry(x, sys) {
accumulate_constraint_gradient_from_xcart(sys);
if !sys.init1 {
accumulate_pair_g(sys);
let _ = accumulate_collective_fg(sys);
}
project_cartesian_gradient(x, sys, g);
return;
}
if !sys.init1 {
sys.resetcells();
}
expand_molecules(x, sys, ExpandMode::G);
if !sys.init1 {
accumulate_pair_g(sys);
let _ = accumulate_collective_fg(sys);
}
update_cached_geometry(x, sys);
project_cartesian_gradient(x, sys, g);
}
fn expand_molecules(x: &[F], sys: &mut PackContext, mode: ExpandMode) -> F {
let mut descs = std::mem::take(&mut sys.work.mol_descs);
fill_active_mol_descs(&mut descs, sys);
let mut xcart = std::mem::take(&mut sys.xcart);
let mut gxcar = std::mem::take(&mut sys.work.gxcar);
let mut frest_atom = std::mem::take(&mut sys.frest_atom);
let scale = sys.scale;
let scale2 = sys.scale2;
let move_flag = sys.move_flag;
let parallel = sys.parallel_pair_eval && !move_flag;
#[derive(Clone, Copy)]
struct Slots {
xcart: *mut [F; 3],
gxcar: *mut [F; 3],
frest_atom: *mut F,
}
unsafe impl Send for Slots {}
unsafe impl Sync for Slots {}
impl Slots {
#[inline(always)]
unsafe fn xcart_at(self, i: usize) -> *mut [F; 3] {
unsafe { self.xcart.add(i) }
}
#[inline(always)]
unsafe fn gxcar_at<'a>(self, i: usize) -> &'a mut [F; 3] {
unsafe { &mut *self.gxcar.add(i) }
}
#[inline(always)]
unsafe fn frest_atom_at(self, i: usize) -> *mut F {
unsafe { self.frest_atom.add(i) }
}
}
let slots = Slots {
xcart: xcart.as_mut_ptr(),
gxcar: gxcar.as_mut_ptr(),
frest_atom: frest_atom.as_mut_ptr(),
};
let (f_total, frest_max) = {
let sys_ro: &PackContext = sys;
let body = |&(itype, icart0, ilubar, ilugan): &(usize, usize, usize, usize)| -> (F, F) {
let (v1, v2, v3) = eulerrmat(x[ilugan], x[ilugan + 1], x[ilugan + 2]);
let xcm = [x[ilubar], x[ilubar + 1], x[ilubar + 2]];
let idbase = sys_ro.idfirst[itype];
let na = sys_ro.natoms[itype];
let mut f_local: F = 0.0;
let mut frest_local: F = 0.0;
for iatom in 0..na {
let icart = icart0 + iatom;
let pos = compcart(&xcm, &sys_ro.coor[idbase + iatom], &v1, &v2, &v3);
unsafe {
*slots.xcart_at(icart) = pos;
}
let start = sys_ro.iratom_offsets[icart];
let end = sys_ro.iratom_offsets[icart + 1];
if start == end {
continue;
}
if matches!(mode, ExpandMode::F | ExpandMode::FG) {
let mut fplus = 0.0;
for &irest in &sys_ro.iratom_data[start..end] {
fplus += sys_ro.restraints[irest].f(&pos, scale, scale2);
}
f_local += fplus;
if fplus > frest_local {
frest_local = fplus;
}
if move_flag {
unsafe {
*slots.frest_atom_at(icart) += fplus;
}
}
}
if matches!(mode, ExpandMode::G | ExpandMode::FG) {
let gc = unsafe { slots.gxcar_at(icart) };
for &irest in &sys_ro.iratom_data[start..end] {
let _ = sys_ro.restraints[irest].fg(&pos, scale, scale2, gc);
}
}
}
(f_local, frest_local)
};
expand_reduce(&descs, &body, parallel)
};
sys.xcart = xcart;
sys.work.gxcar = gxcar;
sys.frest_atom = frest_atom;
if frest_max > sys.frest {
sys.frest = frest_max;
}
if !sys.init1 {
for &(itype, icart0, _, _) in &descs {
let na = sys.natoms[itype];
for iatom in 0..na {
let icart = icart0 + iatom;
let pos = sys.xcart[icart];
insert_atom_in_cell(icart, &pos, sys);
}
}
}
sys.work.mol_descs = descs;
f_total
}
#[cfg(feature = "rayon")]
fn expand_reduce<B>(descs: &[(usize, usize, usize, usize)], body: &B, parallel: bool) -> (F, F)
where
B: Fn(&(usize, usize, usize, usize)) -> (F, F) + Sync,
{
use rayon::prelude::*;
if parallel {
descs
.par_iter()
.map(body)
.reduce(|| (0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)))
} else {
descs
.iter()
.map(body)
.fold((0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)))
}
}
#[cfg(not(feature = "rayon"))]
fn expand_reduce<B>(descs: &[(usize, usize, usize, usize)], body: &B, _parallel: bool) -> (F, F)
where
B: Fn(&(usize, usize, usize, usize)) -> (F, F),
{
descs
.iter()
.map(body)
.fold((0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)))
}
fn fill_active_mol_descs(descs: &mut Vec<(usize, usize, usize, usize)>, sys: &PackContext) {
descs.clear();
let mut ilubar = 0usize;
let mut ilugan = sys.ntotmol * 3;
let mut icart = 0usize;
for itype in 0..sys.ntype {
if !sys.comptype[itype] {
icart += sys.nmols[itype] * sys.natoms[itype];
continue;
}
for _ in 0..sys.nmols[itype] {
descs.push((itype, icart, ilubar, ilugan));
icart += sys.natoms[itype];
ilubar += 3;
ilugan += 3;
}
}
}
#[inline(always)]
fn accumulate_constraint_value(icart: usize, pos: &[F; 3], sys: &mut PackContext) -> F {
let mut fplus = 0.0;
let start = sys.iratom_offsets[icart];
let end = sys.iratom_offsets[icart + 1];
for &irest in &sys.iratom_data[start..end] {
fplus += sys.restraints[irest].f(pos, sys.scale, sys.scale2);
}
if fplus > sys.frest {
sys.frest = fplus;
}
if sys.move_flag {
sys.frest_atom[icart] += fplus;
}
fplus
}
#[inline(always)]
fn accumulate_constraint_gradient(icart: usize, pos: &[F; 3], sys: &mut PackContext) {
let start = sys.iratom_offsets[icart];
let end = sys.iratom_offsets[icart + 1];
let scale = sys.scale;
let scale2 = sys.scale2;
let gc = &mut sys.work.gxcar[icart];
for &irest in &sys.iratom_data[start..end] {
let _ = sys.restraints[irest].fg(pos, scale, scale2, gc);
}
}
#[inline]
fn accumulate_constraint_gradient_from_xcart(sys: &mut PackContext) {
let mut icart = 0usize;
for itype in 0..sys.ntype {
if !sys.comptype[itype] {
icart += sys.nmols[itype] * sys.natoms[itype];
continue;
}
for _imol in 0..sys.nmols[itype] {
for _iatom in 0..sys.natoms[itype] {
let pos = sys.xcart[icart];
accumulate_constraint_gradient(icart, &pos, sys);
icart += 1;
}
}
}
}
#[inline]
fn accumulate_constraint_values_from_xcart(sys: &mut PackContext) -> F {
let mut f = 0.0;
let mut icart = 0usize;
for itype in 0..sys.ntype {
if !sys.comptype[itype] {
icart += sys.nmols[itype] * sys.natoms[itype];
continue;
}
for _imol in 0..sys.nmols[itype] {
for _iatom in 0..sys.natoms[itype] {
let pos = sys.xcart[icart];
f += accumulate_constraint_value(icart, &pos, sys);
icart += 1;
}
}
}
f
}
#[inline]
fn accumulate_constraint_values_and_gradients_from_xcart(sys: &mut PackContext) -> F {
let mut f = 0.0;
let mut icart = 0usize;
for itype in 0..sys.ntype {
if !sys.comptype[itype] {
icart += sys.nmols[itype] * sys.natoms[itype];
continue;
}
for _imol in 0..sys.nmols[itype] {
for _iatom in 0..sys.natoms[itype] {
let pos = sys.xcart[icart];
f += accumulate_constraint_value(icart, &pos, sys);
accumulate_constraint_gradient(icart, &pos, sys);
icart += 1;
}
}
}
f
}
#[inline]
fn type_icart_start(sys: &PackContext, itype: usize) -> usize {
let mut start = 0usize;
for t in 0..itype {
start += sys.nmols[t] * sys.natoms[t];
}
start
}
fn accumulate_collective_f(sys: &PackContext) -> F {
if sys.collective.is_empty() {
return 0.0;
}
let (scale, scale2) = (sys.scale, sys.scale2);
let mut total = 0.0;
for (itype, r) in &sys.collective {
let itype = *itype;
if !sys.comptype[itype] {
continue;
}
let start = type_icart_start(sys, itype);
let len = sys.nmols[itype] * sys.natoms[itype];
if len == 0 {
continue;
}
total += r.f(&sys.xcart[start..start + len], scale, scale2);
}
total
}
fn accumulate_collective_fg(sys: &mut PackContext) -> F {
if sys.collective.is_empty() {
return 0.0;
}
let (scale, scale2) = (sys.scale, sys.scale2);
let collective = std::mem::take(&mut sys.collective);
let mut total = 0.0;
for (itype, r) in &collective {
let itype = *itype;
if !sys.comptype[itype] {
continue;
}
let start = type_icart_start(sys, itype);
let len = sys.nmols[itype] * sys.natoms[itype];
if len == 0 {
continue;
}
let coords: Vec<[F; 3]> = sys.xcart[start..start + len].to_vec();
let mut grads = vec![[0.0 as F; 3]; len];
total += r.fg(&coords, scale, scale2, &mut grads);
for (k, g) in grads.iter().enumerate() {
let gc = &mut sys.work.gxcar[start + k];
gc[0] += g[0];
gc[1] += g[1];
gc[2] += g[2];
}
}
sys.collective = collective;
total
}
#[inline(always)]
fn insert_atom_in_cell(icart: usize, pos: &[F; 3], sys: &mut PackContext) {
let cell = setcell(
pos,
&sys.pbc_min,
&sys.pbc_length,
&sys.cell_length,
&sys.ncells,
&sys.pbc_periodic,
);
let icell = index_cell(&cell, &sys.ncells);
sys.latomnext[icart] = sys.latomfirst[icell];
sys.latomfirst[icell] = icart as u32;
if sys.empty_cell[icell] {
sys.empty_cell[icell] = false;
sys.active_cells.push(icell);
sys.lcellnext[icell] = sys.lcellfirst;
sys.lcellfirst = icell as u32;
}
}
#[inline(always)]
fn accumulate_pair_f(sys: &mut PackContext) -> F {
#[cfg(feature = "rayon")]
if sys.parallel_pair_eval && !sys.move_flag {
let (f, fdist_max) = accumulate_pair_f_parallel(sys);
sys.fdist = sys.fdist.max(fdist_max);
return f;
}
let pbc = pbc_constants(sys);
let mut f = 0.0;
let mut fdist_local: F = 0.0;
let mut icell_id = sys.lcellfirst;
while icell_id != NONE_IDX {
let icell = icell_id as usize;
let neighbors = sys.neighbor_cells_f[icell];
let mut icart_id = sys.latomfirst[icell];
while icart_id != NONE_IDX {
let icart = icart_id as usize;
let (df, dfd) = fparc(icart, sys.latomnext[icart], sys, &pbc);
f += df;
if dfd > fdist_local {
fdist_local = dfd;
}
for &ncell in &neighbors {
let (df, dfd) = fparc(icart, sys.latomfirst[ncell], sys, &pbc);
f += df;
if dfd > fdist_local {
fdist_local = dfd;
}
}
icart_id = sys.latomnext[icart];
}
icell_id = sys.lcellnext[icell];
}
if fdist_local > sys.fdist {
sys.fdist = fdist_local;
}
f
}
#[cfg(feature = "rayon")]
fn accumulate_pair_f_parallel(sys: &PackContext) -> (F, F) {
let pbc = pbc_constants(sys);
sys.active_cells
.par_iter()
.map(|&icell| {
let mut f: F = 0.0;
let mut fdist_max: F = 0.0;
let neighbors = sys.neighbor_cells_f[icell];
let mut icart_id = sys.latomfirst[icell];
while icart_id != NONE_IDX {
let icart = icart_id as usize;
let (f_same, fdist_same) = fparc_stats(icart, sys.latomnext[icart], sys, &pbc);
f += f_same;
fdist_max = fdist_max.max(fdist_same);
for &ncell in &neighbors {
let (f_neigh, fdist_neigh) =
fparc_stats(icart, sys.latomfirst[ncell], sys, &pbc);
f += f_neigh;
fdist_max = fdist_max.max(fdist_neigh);
}
icart_id = sys.latomnext[icart];
}
(f, fdist_max)
})
.reduce(
|| (0.0 as F, 0.0 as F),
|(f1, d1), (f2, d2)| (f1 + f2, d1.max(d2)),
)
}
#[inline(always)]
fn accumulate_pair_g(sys: &mut PackContext) {
let pbc = pbc_constants(sys);
let mut icell_id = sys.lcellfirst;
while icell_id != NONE_IDX {
let icell = icell_id as usize;
let neighbors = sys.neighbor_cells_g[icell];
let mut icart_id = sys.latomfirst[icell];
while icart_id != NONE_IDX {
let icart = icart_id as usize;
gparc(icart, sys.latomnext[icart], sys, &pbc);
for &ncell in &neighbors {
gparc(icart, sys.latomfirst[ncell], sys, &pbc);
}
icart_id = sys.latomnext[icart];
}
icell_id = sys.lcellnext[icell];
}
}
#[inline(always)]
fn accumulate_pair_fg(sys: &mut PackContext) -> F {
#[cfg(feature = "rayon")]
if sys.parallel_pair_eval && !sys.move_flag {
let (f, fdist_max) = accumulate_pair_fg_parallel(sys);
if fdist_max > sys.fdist {
sys.fdist = fdist_max;
}
return f;
}
let pbc = pbc_constants(sys);
let mut f = 0.0;
let mut fdist_local: F = 0.0;
let mut icell_id = sys.lcellfirst;
while icell_id != NONE_IDX {
let icell = icell_id as usize;
let neighbors = sys.neighbor_cells_g[icell];
let mut icart_id = sys.latomfirst[icell];
while icart_id != NONE_IDX {
let icart = icart_id as usize;
let (df, dfd) = fgparc(icart, sys.latomnext[icart], sys, &pbc);
f += df;
if dfd > fdist_local {
fdist_local = dfd;
}
for &ncell in &neighbors {
let (df, dfd) = fgparc(icart, sys.latomfirst[ncell], sys, &pbc);
f += df;
if dfd > fdist_local {
fdist_local = dfd;
}
}
icart_id = sys.latomnext[icart];
}
icell_id = sys.lcellnext[icell];
}
if fdist_local > sys.fdist {
sys.fdist = fdist_local;
}
f
}
#[cfg(feature = "rayon")]
#[derive(Clone, Copy)]
struct PartialPtr {
base: *mut [F; 3],
ntotat: usize,
}
#[cfg(feature = "rayon")]
unsafe impl Send for PartialPtr {}
#[cfg(feature = "rayon")]
unsafe impl Sync for PartialPtr {}
#[cfg(feature = "rayon")]
impl PartialPtr {
#[inline(always)]
unsafe fn slot<'a>(self, t: usize, i: usize) -> &'a mut [F; 3] {
unsafe { &mut *self.base.add(t * self.ntotat + i) }
}
}
#[cfg(feature = "rayon")]
fn accumulate_pair_fg_parallel(sys: &mut PackContext) -> (F, F) {
let ntotat = sys.ntotat;
let nthreads = rayon::current_num_threads().max(1);
let mut partials = std::mem::take(&mut sys.work.grad_partials);
if partials.len() != nthreads * ntotat {
partials.resize(nthreads * ntotat, [0.0; 3]);
}
partials
.par_chunks_mut(ntotat.max(1))
.for_each(|region| region.fill([0.0; 3]));
let pbc = pbc_constants(sys);
let sys_ro: &PackContext = sys;
let pptr = PartialPtr {
base: partials.as_mut_ptr(),
ntotat,
};
let (f_total, fdist_max) = sys_ro
.active_cells
.par_iter()
.map(|&icell| {
let t = rayon::current_thread_index().unwrap_or(0);
let neighbors = &sys_ro.neighbor_cells_g[icell];
let mut f_local: F = 0.0;
let mut fdist_local: F = 0.0;
let mut icart_id = sys_ro.latomfirst[icell];
while icart_id != NONE_IDX {
let icart = icart_id as usize;
let (df, dfd) = fgparc_into(icart, sys_ro.latomnext[icart], sys_ro, pptr, t, &pbc);
f_local += df;
if dfd > fdist_local {
fdist_local = dfd;
}
for &ncell in neighbors {
let (df, dfd) =
fgparc_into(icart, sys_ro.latomfirst[ncell], sys_ro, pptr, t, &pbc);
f_local += df;
if dfd > fdist_local {
fdist_local = dfd;
}
}
icart_id = sys_ro.latomnext[icart];
}
(f_local, fdist_local)
})
.reduce(|| (0.0 as F, 0.0 as F), |a, b| (a.0 + b.0, a.1.max(b.1)));
let mut grad = std::mem::take(&mut sys.work.gxcar);
let chunk = ntotat.div_ceil(nthreads).max(1);
{
let partials_ref: &[[F; 3]] = &partials;
grad.par_chunks_mut(chunk)
.enumerate()
.for_each(|(ci, gchunk)| {
let start = ci * chunk;
let len = gchunk.len();
for t in 0..nthreads {
let base = t * ntotat + start;
let region = &partials_ref[base..base + len];
for (g, p) in gchunk.iter_mut().zip(region) {
g[0] += p[0];
g[1] += p[1];
g[2] += p[2];
}
}
});
}
sys.work.gxcar = grad;
sys.work.grad_partials = partials;
(f_total, fdist_max)
}
#[cfg(feature = "rayon")]
#[inline(always)]
fn fgparc_into(
icart: usize,
first_jcart: u32,
sys: &PackContext,
pptr: PartialPtr,
t: usize,
pbc: &PbcConstants,
) -> (F, F) {
let mut result: F = 0.0;
let mut local_fdist: F = 0.0;
let mut jcart_id = first_jcart;
let xi = sys.xcart[icart];
let hot = AtomHotState::load(icart, sys);
while jcart_id != NONE_IDX {
let jcart = jcart_id as usize;
let next = sys.latomnext[jcart];
if let Some(c) = pair_term::<true, true>(&hot, xi, jcart, sys, pbc) {
result += c.energy;
if c.overlap {
{
let gi = unsafe { pptr.slot(t, icart) };
gi[0] += c.grad[0];
gi[1] += c.grad[1];
gi[2] += c.grad[2];
}
{
let gj = unsafe { pptr.slot(t, jcart) };
gj[0] -= c.grad[0];
gj[1] -= c.grad[1];
gj[2] -= c.grad[2];
}
}
if c.violation > local_fdist {
local_fdist = c.violation;
}
}
jcart_id = next;
}
(result, local_fdist)
}
#[inline(always)]
fn gparc(icart: usize, first_jcart: u32, sys: &mut PackContext, pbc: &PbcConstants) {
let mut jcart_id = first_jcart;
let xi = sys.xcart[icart];
let hot = AtomHotState::load(icart, sys);
while jcart_id != NONE_IDX {
let jcart = jcart_id as usize;
let next = sys.latomnext[jcart];
if let Some(c) = pair_term::<true, false>(&hot, xi, jcart, sys, pbc)
&& c.overlap
{
let gi = &mut sys.work.gxcar[icart];
gi[0] += c.grad[0];
gi[1] += c.grad[1];
gi[2] += c.grad[2];
let gj = &mut sys.work.gxcar[jcart];
gj[0] -= c.grad[0];
gj[1] -= c.grad[1];
gj[2] -= c.grad[2];
}
jcart_id = next;
}
}
#[inline(always)]
fn fgparc(icart: usize, first_jcart: u32, sys: &mut PackContext, pbc: &PbcConstants) -> (F, F) {
let mut result = 0.0;
let mut local_fdist: F = 0.0;
let mut jcart_id = first_jcart;
let xi = sys.xcart[icart];
let hot = AtomHotState::load(icart, sys);
let move_flag = sys.move_flag;
while jcart_id != NONE_IDX {
let jcart = jcart_id as usize;
let next = sys.latomnext[jcart];
if let Some(c) = pair_term::<true, true>(&hot, xi, jcart, sys, pbc) {
result += c.energy;
if c.overlap {
let gi = &mut sys.work.gxcar[icart];
gi[0] += c.grad[0];
gi[1] += c.grad[1];
gi[2] += c.grad[2];
let gj = &mut sys.work.gxcar[jcart];
gj[0] -= c.grad[0];
gj[1] -= c.grad[1];
gj[2] -= c.grad[2];
}
if c.violation > local_fdist {
local_fdist = c.violation;
}
if move_flag {
if c.violation > sys.fdist_atom[icart] {
sys.fdist_atom[icart] = c.violation;
}
if c.violation > sys.fdist_atom[jcart] {
sys.fdist_atom[jcart] = c.violation;
}
}
}
jcart_id = next;
}
(result, local_fdist)
}
#[cfg(feature = "rayon")]
#[inline(always)]
fn fparc_stats(icart: usize, first_jcart: u32, sys: &PackContext, pbc: &PbcConstants) -> (F, F) {
let mut result: F = 0.0;
let mut fdist_max: F = 0.0;
let mut jcart_id = first_jcart;
let xi = sys.xcart[icart];
let hot = AtomHotState::load(icart, sys);
while jcart_id != NONE_IDX {
let jcart = jcart_id as usize;
let next = sys.latomnext[jcart];
if let Some(c) = pair_term::<false, true>(&hot, xi, jcart, sys, pbc) {
result += c.energy;
if c.violation > fdist_max {
fdist_max = c.violation;
}
}
jcart_id = next;
}
(result, fdist_max)
}
fn project_cartesian_gradient(x: &[F], sys: &mut PackContext, g: &mut [F]) {
g.iter_mut().for_each(|v| *v = 0.0);
let mut descs = std::mem::take(&mut sys.work.mol_descs);
fill_active_mol_descs(&mut descs, sys);
let move_flag = sys.move_flag;
let parallel = sys.parallel_pair_eval && !move_flag;
#[derive(Clone, Copy)]
struct GPtr(*mut F);
unsafe impl Send for GPtr {}
unsafe impl Sync for GPtr {}
impl GPtr {
#[inline(always)]
unsafe fn write(self, i: usize, v: F) {
unsafe {
*self.0.add(i) = v;
}
}
}
let gptr = GPtr(g.as_mut_ptr());
{
let sys_ro: &PackContext = sys;
let body = |&(itype, icart0, ilubar, ilugan): &(usize, usize, usize, usize)| {
let beta = x[ilugan];
let gama = x[ilugan + 1];
let teta = x[ilugan + 2];
let (dv1beta, dv1gama, dv1teta, dv2beta, dv2gama, dv2teta, dv3beta, dv3gama, dv3teta) =
eulerrmat_derivatives(beta, gama, teta);
let idbase = sys_ro.idfirst[itype];
let na = sys_ro.natoms[itype];
let mut gcom = [0.0 as F; 3];
let mut gang = [0.0 as F; 3];
for iatom in 0..na {
let gx = sys_ro.work.gxcar[icart0 + iatom];
let cr = sys_ro.coor[idbase + iatom];
for k in 0..3 {
gcom[k] += gx[k];
}
for k in 0..3 {
gang[0] +=
(cr[0] * dv1beta[k] + cr[1] * dv2beta[k] + cr[2] * dv3beta[k]) * gx[k];
gang[1] +=
(cr[0] * dv1gama[k] + cr[1] * dv2gama[k] + cr[2] * dv3gama[k]) * gx[k];
gang[2] +=
(cr[0] * dv1teta[k] + cr[1] * dv2teta[k] + cr[2] * dv3teta[k]) * gx[k];
}
}
unsafe {
for k in 0..3 {
gptr.write(ilubar + k, gcom[k]);
gptr.write(ilugan + k, gang[k]);
}
}
};
project_for_each(&descs, &body, parallel);
}
sys.work.mol_descs = descs;
}
#[cfg(feature = "rayon")]
fn project_for_each<B>(descs: &[(usize, usize, usize, usize)], body: &B, parallel: bool)
where
B: Fn(&(usize, usize, usize, usize)) + Sync,
{
use rayon::prelude::*;
if parallel {
descs.par_iter().for_each(body);
} else {
descs.iter().for_each(body);
}
}
#[cfg(not(feature = "rayon"))]
fn project_for_each<B>(descs: &[(usize, usize, usize, usize)], body: &B, _parallel: bool)
where
B: Fn(&(usize, usize, usize, usize)),
{
descs.iter().for_each(body);
}
#[inline]
fn matches_cached_geometry(x: &[F], sys: &PackContext) -> bool {
sys.work.matches_cached_geometry(
x,
&sys.comptype,
sys.init1,
sys.ncells,
sys.cell_length,
sys.pbc_min,
sys.pbc_length,
sys.pbc_periodic,
)
}
#[inline]
fn update_cached_geometry(x: &[F], sys: &mut PackContext) {
sys.work.update_cached_geometry(
x,
&sys.comptype,
sys.init1,
sys.ncells,
sys.cell_length,
sys.pbc_min,
sys.pbc_length,
sys.pbc_periodic,
);
}
pub trait Objective {
fn evaluate(&mut self, x: &[F], mode: EvalMode, gradient: Option<&mut [F]>) -> EvalOutput;
fn fdist(&self) -> F;
fn frest(&self) -> F;
fn ncf(&self) -> usize;
fn ncg(&self) -> usize;
fn reset_eval_counters(&mut self);
fn bounds(&self, l: &mut [F], u: &mut [F]) {
debug_assert_eq!(l.len(), u.len(), "bounds: l/u length mismatch");
l.fill(-1.0e20);
u.fill(1.0e20);
}
}
impl Objective for PackContext {
#[inline]
fn evaluate(&mut self, x: &[F], mode: EvalMode, gradient: Option<&mut [F]>) -> EvalOutput {
PackContext::evaluate(self, x, mode, gradient)
}
#[inline]
fn fdist(&self) -> F {
self.fdist
}
#[inline]
fn frest(&self) -> F {
self.frest
}
#[inline]
fn ncf(&self) -> usize {
PackContext::ncf(self)
}
#[inline]
fn ncg(&self) -> usize {
PackContext::ncg(self)
}
#[inline]
fn reset_eval_counters(&mut self) {
PackContext::reset_eval_counters(self);
}
fn bounds(&self, l: &mut [F], u: &mut [F]) {
debug_assert_eq!(l.len(), u.len(), "bounds: l/u length mismatch");
let n = l.len();
l.fill(-1.0e20);
u.fill(1.0e20);
let mut i = n / 2;
for itype in 0..self.ntype {
if !self.comptype[itype] {
continue;
}
for _imol in 0..self.nmols[itype] {
for axis in 0..3 {
if self.constrain_rot[itype][axis] {
let center = self.rot_bound[itype][axis][0];
let half_width = self.rot_bound[itype][axis][1].abs();
l[i] = center - half_width;
u[i] = center + half_width;
}
i += 1;
}
}
}
debug_assert_eq!(i, n);
}
}
#[cfg(test)]
mod objective_trait_tests {
use super::*;
use crate::PackContext;
#[test]
fn dyn_objective_matches_inherent_evaluate() {
fn build(ntotat: usize) -> PackContext {
let mut sys = PackContext::new(ntotat, 0, 0);
sys.radius.fill(0.75);
sys.radius_ini.fill(1.5);
sys.work.radiuswork.resize(ntotat, 0.0);
sys.sync_atom_props();
sys
}
let x: Vec<F> = Vec::new();
let mut via_inherent = build(4);
let out_inherent = PackContext::evaluate(&mut via_inherent, &x, EvalMode::FOnly, None);
let mut via_trait_owner = build(4);
let out_trait = {
let obj: &mut dyn Objective = &mut via_trait_owner;
obj.evaluate(&x, EvalMode::FOnly, None)
};
assert_eq!(out_inherent.f_total, out_trait.f_total, "f_total mismatch");
assert_eq!(
out_inherent.fdist_max, out_trait.fdist_max,
"fdist_max mismatch"
);
assert_eq!(
out_inherent.frest_max, out_trait.frest_max,
"frest_max mismatch"
);
assert_eq!(
via_inherent.fdist, via_trait_owner.fdist,
"post-call fdist drift"
);
assert_eq!(
via_inherent.frest, via_trait_owner.frest,
"post-call frest drift"
);
let obj: &mut dyn Objective = &mut via_trait_owner;
assert_eq!(obj.ncf(), 1, "ncf should be 1 after one FOnly evaluate");
assert_eq!(obj.ncg(), 0, "ncg should stay 0 under FOnly");
obj.reset_eval_counters();
assert_eq!(obj.ncf(), 0, "ncf should be 0 after reset");
assert_eq!(obj.ncg(), 0, "ncg should be 0 after reset");
}
}