use std::sync::Arc;
use molrs::Element;
use molrs::spatial::region::simbox::SimBox;
use molrs::types::F;
use ndarray::Array1;
use rand::SeedableRng;
use rand::rngs::SmallRng;
use crate::constraints::EvalMode;
use crate::context::PackContext;
use crate::error::PackError;
use crate::euler::{compcart, eulerfixed};
use crate::gencan::{GencanParams, GencanWorkspace, pgencan};
use crate::handler::{
Handler, LammpsLogHandler, MolpackLogLevel, PhaseInfo, PhaseReport, StepInfo,
};
use crate::initial::{SwapState, init_xcart_from_x, initial};
use crate::movebad::{MoveBadConfig, movebad};
use crate::numerics::objective_small_floor;
use crate::relaxer::RelaxerRunner;
use crate::restraint::AtomRestraint;
use crate::target::{CenteringMode, Target};
#[derive(Debug, Clone)]
pub struct PackResult {
pub frame: molrs::Frame,
pub fdist: F,
pub frest: F,
pub converged: bool,
}
impl PackResult {
pub fn positions(&self) -> Vec<[F; 3]> {
let atoms = self.frame.get("atoms").expect("frame has no 'atoms' block");
let x = atoms.get_float("x").expect("no 'x' column");
let y = atoms.get_float("y").expect("no 'y' column");
let z = atoms.get_float("z").expect("no 'z' column");
x.iter()
.zip(y.iter())
.zip(z.iter())
.map(|((&xi, &yi), &zi)| [xi, yi, zi])
.collect()
}
#[inline]
pub fn natoms(&self) -> usize {
self.frame.get("atoms").and_then(|b| b.nrows()).unwrap_or(0)
}
}
const PRECISION: F = 0.01;
const DISCALE: F = 1.1;
const GENCAN_MAXIT: usize = 20;
const SIDEMAX: F = 1000.0;
const MOVEFRAC: F = 0.05;
const DEFAULT_TOLERANCE: F = 2.0;
const DEFAULT_SEED: u64 = 1_234_567;
pub struct Molpack {
handlers: Vec<Box<dyn Handler>>,
global_restraints: Vec<Arc<dyn AtomRestraint>>,
precision: F,
discale: F,
tolerance: F,
inner_iterations: usize,
init_passes: Option<usize>,
init_box_half_size: F,
perturb_fraction: F,
random_perturb: bool,
perturb: bool,
seed: u64,
parallel_eval: bool,
periodic_box: Option<PeriodicSpec>,
log_level: MolpackLogLevel,
log_frequency: usize,
avoid_overlap: bool,
}
impl Default for Molpack {
fn default() -> Self {
Self::new()
}
}
impl Molpack {
pub fn new() -> Self {
Self {
handlers: Vec::new(),
global_restraints: Vec::new(),
precision: PRECISION,
discale: DISCALE,
tolerance: DEFAULT_TOLERANCE,
inner_iterations: GENCAN_MAXIT,
init_passes: None,
init_box_half_size: SIDEMAX,
perturb_fraction: MOVEFRAC,
random_perturb: false,
perturb: true,
seed: DEFAULT_SEED,
parallel_eval: false,
periodic_box: None,
log_level: MolpackLogLevel::Quiet,
log_frequency: 1,
avoid_overlap: true,
}
}
pub fn with_handler(mut self, h: impl Handler + 'static) -> Self {
self.handlers.push(Box::new(h));
self
}
pub fn with_global_restraint(mut self, r: impl AtomRestraint + 'static) -> Self {
self.global_restraints.push(Arc::new(r));
self
}
pub fn with_precision(mut self, p: F) -> Self {
self.precision = p;
self
}
pub fn with_tolerance(mut self, t: F) -> Self {
self.tolerance = t;
self
}
pub fn with_inner_iterations(mut self, n: usize) -> Self {
self.inner_iterations = n;
self
}
pub fn with_avoid_overlap(mut self, enabled: bool) -> Self {
self.avoid_overlap = enabled;
self
}
pub fn with_init_passes(mut self, n: usize) -> Self {
self.init_passes = if n == 0 { None } else { Some(n) };
self
}
pub fn with_init_box_half_size(mut self, f: F) -> Self {
self.init_box_half_size = f;
self
}
pub fn with_periodic_box(mut self, min: [F; 3], max: [F; 3]) -> Self {
self.periodic_box = Some((min, max, [true; 3]));
self
}
pub fn with_perturb_fraction(mut self, f: F) -> Self {
self.perturb_fraction = f;
self
}
pub fn with_random_perturb(mut self, enabled: bool) -> Self {
self.random_perturb = enabled;
self
}
pub fn with_perturb(mut self, enabled: bool) -> Self {
self.perturb = enabled;
self
}
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
pub fn with_parallel_eval(mut self, enabled: bool) -> Self {
self.parallel_eval = enabled;
self
}
pub fn with_lammps_output(mut self, enabled: bool) -> Self {
self.log_level = if enabled {
MolpackLogLevel::Progress
} else {
MolpackLogLevel::Quiet
};
self
}
pub fn with_log_level(mut self, level: MolpackLogLevel) -> Self {
self.log_level = level;
self
}
pub fn with_log_frequency(mut self, n: usize) -> Self {
self.log_frequency = n.max(1);
self
}
pub fn pack(
&mut self,
targets: &[Target],
max_loops: usize,
) -> Result<molrs::Frame, PackError> {
Ok(self.pack_with_report(targets, max_loops)?.frame)
}
pub fn pack_with_report(
&mut self,
targets: &[Target],
max_loops: usize,
) -> Result<PackResult, PackError> {
if targets.is_empty() {
return Err(PackError::NoTargets);
}
for (i, t) in targets.iter().enumerate() {
if t.natoms() == 0 {
return Err(PackError::EmptyMolecule(i));
}
}
let broadcast_targets: Vec<Target>;
let targets: &[Target] = if self.global_restraints.is_empty() {
targets
} else {
broadcast_targets = targets
.iter()
.map(|t| {
let mut t = t.clone();
for r in &self.global_restraints {
t.molecule_restraints.push(Arc::clone(r));
}
t
})
.collect();
&broadcast_targets
};
if let Some((min, max, _)) = self.periodic_box {
let length = [max[0] - min[0], max[1] - min[1], max[2] - min[2]];
if length.iter().any(|&v| v <= 0.0) {
return Err(PackError::InvalidPBCBox { min, max });
}
}
let pbc = match (self.periodic_box, derive_periodic_box(targets)?) {
(None, derived) => derived,
(Some(global), None) => Some(global),
(Some(global), Some(derived)) if global == derived => Some(global),
(Some(global), Some(derived)) => {
return Err(PackError::ConflictingPeriodicBoxes {
first: global,
second: derived,
});
}
};
let mut rng = SmallRng::seed_from_u64(self.seed);
let free_targets: Vec<&Target> = targets.iter().filter(|t| t.fixed_at.is_none()).collect();
let fixed_targets: Vec<&Target> = targets.iter().filter(|t| t.fixed_at.is_some()).collect();
let ntype = free_targets.len();
let ntype_with_fixed = ntype + fixed_targets.len();
let mut handlers = std::mem::take(&mut self.handlers);
if self.log_level.is_enabled() {
handlers.push(Box::new(LammpsLogHandler::new(
self.log_level,
self.log_frequency,
self.tolerance,
self.precision,
self.seed,
max_loops,
ntype_with_fixed,
pbc,
)));
}
let ntotmol_free: usize = free_targets.iter().map(|t| t.count).sum();
let ntotat_free: usize = free_targets.iter().map(|t| t.count * t.natoms()).sum();
let ntotat_fixed: usize = fixed_targets.iter().map(|t| t.natoms()).sum();
let ntotat = ntotat_free + ntotat_fixed;
let n = 6 * ntotmol_free;
let mut sys = PackContext::new(ntotat, ntotmol_free, ntype);
sys.ntype_with_fixed = ntype_with_fixed;
sys.nfixedat = ntotat_fixed;
sys.comptype = vec![true; ntype_with_fixed];
let mut cum_atoms = 0usize;
let mut coor = Vec::new();
let mut maxmove_per_type = vec![0usize; ntype];
sys.nmols = vec![0; ntype_with_fixed];
sys.natoms = vec![0; ntype_with_fixed];
sys.idfirst = vec![0; ntype_with_fixed];
sys.constrain_rot = vec![[false; 3]; ntype];
sys.rot_bound = vec![[[0.0; 2]; 3]; ntype];
for (itype, target) in free_targets.iter().enumerate() {
sys.nmols[itype] = target.count;
sys.natoms[itype] = target.natoms();
sys.idfirst[itype] = cum_atoms;
coor.extend_from_slice(reference_coords(target));
cum_atoms += target.natoms();
maxmove_per_type[itype] = target.perturb_budget.unwrap_or(target.count);
for k in 0..3 {
if let Some((center, half_width)) = target.rotation_bound[k] {
sys.constrain_rot[itype][k] = true;
sys.rot_bound[itype][k][0] = center.radians();
sys.rot_bound[itype][k][1] = half_width.radians();
}
}
}
for (fi, target) in fixed_targets.iter().enumerate() {
let itype = ntype + fi;
sys.nmols[itype] = 1;
sys.natoms[itype] = target.natoms();
sys.idfirst[itype] = cum_atoms;
coor.extend_from_slice(reference_coords(target));
cum_atoms += target.natoms();
}
sys.coor = coor;
let atom_radius = self.tolerance / 2.0;
let mut icart = 0usize;
for (itype, target) in free_targets.iter().enumerate() {
for imol in 0..target.count {
for iatom in 0..target.natoms() {
sys.radius[icart] = atom_radius;
sys.radius_ini[icart] = atom_radius;
sys.ibtype[icart] = itype;
sys.ibmol[icart] = imol;
sys.elements[icart] = Element::by_symbol(&target.elements[iatom]);
icart += 1;
}
}
}
for (fi, target) in fixed_targets.iter().enumerate() {
let itype = ntype + fi;
for iatom in 0..target.natoms() {
sys.radius[icart] = atom_radius;
sys.radius_ini[icart] = atom_radius;
sys.ibtype[icart] = itype;
sys.ibmol[icart] = 0;
sys.elements[icart] = Element::by_symbol(&target.elements[iatom]);
icart += 1;
}
}
let mut irest_pool = Vec::new();
let mut iratom_lists: Vec<Vec<usize>> = vec![Vec::new(); ntotat];
let mut icart = 0usize;
for target in free_targets.iter() {
for _imol in 0..target.count {
for iatom in 0..target.natoms() {
for r in &target.molecule_restraints {
let irest = irest_pool.len();
irest_pool.push(std::sync::Arc::clone(r));
iratom_lists[icart].push(irest);
}
for (indices, restraint) in &target.atom_restraints {
if indices.contains(&iatom) {
let irest = irest_pool.len();
irest_pool.push(std::sync::Arc::clone(restraint));
iratom_lists[icart].push(irest);
}
}
icart += 1;
}
}
}
sys.restraints = irest_pool;
sys.iratom_offsets.clear();
sys.iratom_offsets.reserve(ntotat + 1);
sys.iratom_offsets.push(0);
for atom_restraints in &iratom_lists {
let next = sys.iratom_offsets.last().copied().unwrap_or(0) + atom_restraints.len();
sys.iratom_offsets.push(next);
}
sys.iratom_data.clear();
sys.iratom_data
.reserve(sys.iratom_offsets.last().copied().unwrap_or(0));
for atom_restraints in iratom_lists {
sys.iratom_data.extend(atom_restraints);
}
sys.collective.clear();
for (itype, target) in free_targets.iter().enumerate() {
for r in &target.collective_restraints {
sys.collective.push((itype, std::sync::Arc::clone(r)));
}
}
let free_atoms = ntotat_free;
let mut fixed_icart = free_atoms;
for target in fixed_targets.iter() {
let fp = target.fixed_at.as_ref().unwrap();
let (v1, v2, v3) = eulerfixed(
fp.orientation[0].radians(),
fp.orientation[1].radians(),
fp.orientation[2].radians(),
);
let ref_coords = reference_coords(target);
for ref_coord in ref_coords.iter().take(target.natoms()) {
let pos = compcart(&fp.position, ref_coord, &v1, &v2, &v3);
sys.xcart[fixed_icart] = pos;
sys.fixedatom[fixed_icart] = true;
fixed_icart += 1;
}
}
sys.sync_atom_props();
sys.parallel_pair_eval = self.parallel_eval;
crate::frame::init_frame_constants(&mut sys);
let mut x = vec![0.0 as F; n];
for h in handlers.iter_mut() {
h.on_start(ntotat, ntotmol_free);
}
sys.ntotmol = ntotmol_free;
let init_passes = self.init_passes.unwrap_or(20 * ntype);
let movebad_cfg = MoveBadConfig {
movefrac: self.perturb_fraction,
maxmove_per_type: &maxmove_per_type,
movebadrandom: self.random_perturb,
gencan_maxit: self.inner_iterations,
};
initial(
&mut x,
&mut sys,
self.precision,
self.discale,
self.init_box_half_size,
init_passes,
pbc,
self.avoid_overlap,
&movebad_cfg,
&mut rng,
);
for h in handlers.iter_mut() {
h.on_initialized(&sys);
}
let mut relaxer_runners: Vec<(usize, Vec<Box<dyn RelaxerRunner>>)> = free_targets
.iter()
.enumerate()
.filter(|(_, t)| !t.relaxers.is_empty())
.map(|(i, t)| {
let base = sys.idfirst[i];
let na = sys.natoms[i];
let ref_slice = &sys.coor[base..base + na];
let frame = t.template.as_ref();
let runners = t
.relaxers
.iter()
.map(|r| r.spawn(frame, ref_slice))
.collect();
(i, runners)
})
.collect();
let gencan_params = GencanParams {
maxit: self.inner_iterations,
maxfc: self.inner_iterations * 10,
iprint: 0,
..Default::default()
};
let mut converged = false;
let mut gencan_workspace = GencanWorkspace::new();
let mut swap = SwapState::init(&x, &sys);
let total_phases = ntype + 1;
for phase in 0..=(ntype) {
let outcome = run_phase(
phase,
ntype,
ntype_with_fixed,
total_phases,
max_loops,
self.discale,
self.precision,
!self.perturb,
&movebad_cfg,
&gencan_params,
&mut sys,
&mut x,
&mut swap,
&mut relaxer_runners,
&mut handlers,
&mut gencan_workspace,
&mut rng,
);
match outcome {
PhaseOutcome::Continue => {}
PhaseOutcome::Converged => {
converged = true;
break;
}
}
}
if !converged {
log::warn!(
" Pack did not fully converge (fdist={:.4e}, frest={:.4e})",
sys.fdist,
sys.frest
);
}
for itype in 0..ntype_with_fixed {
sys.comptype[itype] = true;
}
sys.ntotmol = ntotmol_free;
init_xcart_from_x(&x, &mut sys);
for h in handlers.iter_mut() {
h.on_finish(&sys);
}
let xcart = std::mem::take(&mut sys.xcart);
let positions = positions_in_target_order(targets, &xcart, ntotat_free);
let mut frame = crate::assemble::assemble_frame(targets, &positions);
if let Some((min, max, flags)) = pbc {
let lengths = Array1::from_vec(vec![max[0] - min[0], max[1] - min[1], max[2] - min[2]]);
let origin = Array1::from_vec(min.to_vec());
if let Ok(simbox) = SimBox::ortho(lengths, origin, flags) {
frame.simbox = Some(simbox);
}
}
if self.log_level.is_enabled() {
handlers.pop();
}
self.handlers = handlers;
Ok(PackResult {
frame,
fdist: sys.fdist,
frest: sys.frest,
converged,
})
}
}
type PeriodicSpec = ([F; 3], [F; 3], [bool; 3]);
fn derive_periodic_box(targets: &[Target]) -> Result<Option<PeriodicSpec>, PackError> {
let mut found: Option<PeriodicSpec> = None;
for target in targets {
let restraints = target
.molecule_restraints
.iter()
.chain(target.atom_restraints.iter().map(|(_, r)| r));
for r in restraints {
if let Some(candidate) = r.periodic_box() {
let (min, max, _periodic) = candidate;
let length = [max[0] - min[0], max[1] - min[1], max[2] - min[2]];
if length.iter().any(|&v| v <= 0.0) {
return Err(PackError::InvalidPBCBox { min, max });
}
match found {
None => found = Some(candidate),
Some(existing) if existing == candidate => {}
Some(existing) => {
return Err(PackError::ConflictingPeriodicBoxes {
first: existing,
second: candidate,
});
}
}
}
}
}
Ok(found)
}
fn reference_coords(target: &Target) -> &[[F; 3]] {
match target.centering {
CenteringMode::Center => &target.ref_coords,
CenteringMode::Off => &target.input_coords,
CenteringMode::Auto => {
if target.fixed_at.is_some() {
&target.input_coords
} else {
&target.ref_coords
}
}
}
}
fn positions_in_target_order(
targets: &[Target],
xcart: &[[F; 3]],
n_free_atoms: usize,
) -> Vec<[F; 3]> {
let mut out = Vec::with_capacity(xcart.len());
let mut free_cursor = 0usize;
let mut fixed_cursor = n_free_atoms;
for t in targets {
if t.fixed_at.is_some() {
let n = t.natoms();
out.extend_from_slice(&xcart[fixed_cursor..fixed_cursor + n]);
fixed_cursor += n;
} else {
let n = t.count * t.natoms();
out.extend_from_slice(&xcart[free_cursor..free_cursor + n]);
free_cursor += n;
}
}
out
}
pub fn evaluate_unscaled(sys: &mut PackContext, xwork: &[F]) -> (F, F, F) {
sys.work.radiuswork.copy_from_slice(&sys.radius);
for i in 0..sys.ntotat {
sys.set_radius(i, sys.radius_ini[i]);
}
let f_total = sys.evaluate(xwork, EvalMode::FOnly, None).f_total;
let fdist = sys.fdist;
let frest = sys.frest;
for i in 0..sys.ntotat {
sys.set_radius(i, sys.work.radiuswork[i]);
}
(f_total, fdist, frest)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IterOutcome {
Continue,
Converged,
EarlyStop,
}
#[allow(clippy::too_many_arguments)]
pub fn run_iteration(
loop_idx: usize,
max_loops: usize,
is_all: bool,
phase: usize,
phase_info: PhaseInfo,
precision: F,
disable_movebad: bool,
movebad_cfg: &MoveBadConfig,
gencan_params: &GencanParams,
sys: &mut PackContext,
xwork: &mut [F],
swap: &mut SwapState,
flast: &mut F,
fimp_prev: &mut F,
radscale: &mut F,
relaxer_runners: &mut Vec<(usize, Vec<Box<dyn RelaxerRunner>>)>,
handlers: &mut [Box<dyn Handler>],
gencan_workspace: &mut GencanWorkspace,
rng: &mut SmallRng,
) -> IterOutcome {
if !disable_movebad && *radscale == 1.0 && *fimp_prev <= 10.0 {
movebad(xwork, sys, precision, movebad_cfg, rng, gencan_workspace);
*flast = evaluate_unscaled(sys, xwork).0;
}
for (itype, runners) in relaxer_runners.iter_mut() {
if !is_all && *itype != phase {
continue;
}
let base = sys.idfirst[*itype];
let na = sys.natoms[*itype];
for runner in runners.iter_mut() {
let saved: Vec<[F; 3]> = sys.coor[base..base + na].to_vec();
let f_before = sys.evaluate(xwork, EvalMode::FOnly, None).f_total;
let result = runner.on_iter(
&saved,
f_before,
&mut |trial: &[[F; 3]]| {
sys.coor[base..base + na].copy_from_slice(trial);
let f = sys.evaluate(xwork, EvalMode::FOnly, None).f_total;
sys.coor[base..base + na].copy_from_slice(&saved);
f
},
rng,
);
if let Some(new_coords) = result {
sys.coor[base..base + na].copy_from_slice(&new_coords);
}
}
}
sys.reset_eval_counters();
let res = pgencan(xwork, sys, gencan_params, precision, gencan_workspace);
if !is_all {
swap.save_type(phase, xwork, sys);
}
let (fx_unscaled, fdist, frest) = evaluate_unscaled(sys, xwork);
let mut fimp = if *flast > 0.0 {
-100.0 * (fx_unscaled - *flast) / *flast
} else if fx_unscaled < objective_small_floor() {
100.0 } else {
F::INFINITY
};
fimp = fimp.clamp(-99.99, 99.99);
*flast = fx_unscaled;
*fimp_prev = fimp;
if !handlers.is_empty() {
let relaxer_acceptance: Vec<(usize, F)> = relaxer_runners
.iter()
.flat_map(|(itype, runners)| runners.iter().map(move |r| (*itype, r.acceptance_rate())))
.collect();
let step_info = StepInfo {
loop_idx,
max_loops,
phase: phase_info,
fdist,
frest,
improvement_pct: fimp,
radscale: *radscale,
precision,
relaxer_acceptance,
};
for h in handlers.iter_mut() {
h.on_step(&step_info, sys);
}
if handlers.iter().any(|h| h.should_stop()) {
log::debug!(" Early stop requested at loop {loop_idx}");
return IterOutcome::EarlyStop;
}
}
log::debug!(
" loop={loop_idx} f={:.4e} fdist={:.4e} frest={:.4e} radscale={:.4} fimp={:.2}% ncf={} ncg={} inform={}",
res.f,
fdist,
frest,
*radscale,
fimp,
sys.ncf(),
sys.ncg(),
res.inform
);
if fdist < precision && frest < precision {
log::debug!(" Converged at phase {phase} loop {loop_idx}");
return IterOutcome::Converged;
}
if *radscale > 1.0 && (fimp < 2.0 || (fdist < precision && fimp < 10.0)) {
*radscale = (0.9 * *radscale).max(1.0);
for i in 0..sys.ntotat {
let new_r = sys.radius_ini[i].max(0.9 * sys.radius[i]);
sys.set_radius(i, new_r);
}
}
IterOutcome::Continue
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PhaseOutcome {
Continue,
Converged,
}
#[allow(clippy::too_many_arguments)]
pub fn run_phase(
phase: usize,
ntype: usize,
ntype_with_fixed: usize,
total_phases: usize,
max_loops: usize,
discale: F,
precision: F,
disable_movebad: bool,
movebad_cfg: &MoveBadConfig,
gencan_params: &GencanParams,
sys: &mut PackContext,
x: &mut Vec<F>,
swap: &mut SwapState,
relaxer_runners: &mut Vec<(usize, Vec<Box<dyn RelaxerRunner>>)>,
handlers: &mut [Box<dyn Handler>],
gencan_workspace: &mut GencanWorkspace,
rng: &mut SmallRng,
) -> PhaseOutcome {
let is_all = phase == ntype;
let phase_info = PhaseInfo {
phase,
total_phases,
molecule_type: if is_all { None } else { Some(phase) },
};
for h in handlers.iter_mut() {
h.on_phase_start(&phase_info);
}
for itype in 0..ntype_with_fixed {
sys.comptype[itype] = if is_all {
true
} else {
itype >= ntype || itype == phase
};
}
log::debug!(
" Packing phase {phase} ({})",
if is_all {
"all".to_string()
} else {
format!("type {phase}")
}
);
let mut radscale = discale;
for icart in 0..sys.ntotat {
sys.set_radius(icart, discale * sys.radius_ini[icart]);
}
let mut xwork: Vec<F> = if !is_all {
*swap = SwapState::init(x, sys);
swap.set_type(phase, sys)
} else {
swap.restore(x, sys);
x.clone()
};
sys.evaluate(&xwork, EvalMode::FOnly, None);
if sys.fdist < precision && sys.frest < precision {
let report = PhaseReport {
iterations: 0,
fdist: sys.fdist,
frest: sys.frest,
converged: true,
};
for h in handlers.iter_mut() {
h.on_phase_end(&phase_info, &report);
}
if !is_all {
swap.save_type(phase, &xwork, sys);
swap.restore(x, sys);
return PhaseOutcome::Continue;
} else {
x.clone_from(&xwork);
return PhaseOutcome::Converged;
}
}
let mut flast = evaluate_unscaled(sys, &xwork).0;
let mut fimp_prev = F::INFINITY;
let mut converged_inner = false;
let mut iterations = 0usize;
for loop_idx in 0..max_loops {
let outcome = run_iteration(
loop_idx,
max_loops,
is_all,
phase,
phase_info,
precision,
disable_movebad,
movebad_cfg,
gencan_params,
sys,
&mut xwork,
swap,
&mut flast,
&mut fimp_prev,
&mut radscale,
relaxer_runners,
handlers,
gencan_workspace,
rng,
);
iterations += 1;
match outcome {
IterOutcome::Continue => {}
IterOutcome::Converged => {
converged_inner = true;
break;
}
IterOutcome::EarlyStop => break,
}
}
let report = PhaseReport {
iterations,
fdist: sys.fdist,
frest: sys.frest,
converged: converged_inner,
};
for h in handlers.iter_mut() {
h.on_phase_end(&phase_info, &report);
}
if !is_all {
swap.restore(x, sys);
PhaseOutcome::Continue
} else {
x.clone_from(&xwork);
if converged_inner {
PhaseOutcome::Converged
} else {
PhaseOutcome::Continue
}
}
}
#[cfg(test)]
mod reorder_tests {
use super::*;
#[test]
fn fixed_target_declared_first_maps_to_its_xcart_block() {
let a = Target::from_coords(&[[0.0; 3]; 2], &[1.0; 2], 1).fixed_at([0.0, 0.0, 0.0]);
let b = Target::from_coords(&[[0.0; 3]; 3], &[1.0; 3], 2);
let targets = vec![a, b];
let xcart: Vec<[F; 3]> = (0..8).map(|i| [i as F, 0.0, 0.0]).collect();
let out = positions_in_target_order(&targets, &xcart, 6);
let x: Vec<F> = out.iter().map(|p| p[0]).collect();
assert_eq!(x, vec![6.0, 7.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn no_fixed_targets_is_identity() {
let a = Target::from_coords(&[[0.0; 3]; 2], &[1.0; 2], 1);
let b = Target::from_coords(&[[0.0; 3]; 3], &[1.0; 3], 2);
let targets = vec![a, b];
let xcart: Vec<[F; 3]> = (0..8).map(|i| [i as F, 0.0, 0.0]).collect();
let out = positions_in_target_order(&targets, &xcart, 8);
let x: Vec<F> = out.iter().map(|p| p[0]).collect();
assert_eq!(x, (0..8).map(|i| i as F).collect::<Vec<_>>());
}
}