1use std::path::{Path, PathBuf};
15
16use crate::{
17 AbovePlaneRestraint, Angle, AtomRestraint, BelowPlaneRestraint, CenteringMode,
18 InsideBoxRestraint, InsideCubeRestraint, InsideCylinderRestraint, InsideEllipsoidRestraint,
19 InsideSphereRestraint, Molpack, OutsideBoxRestraint, OutsideCubeRestraint,
20 OutsideCylinderRestraint, OutsideEllipsoidRestraint, OutsideSphereRestraint, Target,
21};
22
23use super::error::ScriptError;
24use super::parser::{AtomGroup, RestraintSpec, Script, Structure};
25
26pub struct ScriptPlan {
34 pub packer: Molpack,
36 pub structures: Vec<StructurePlan>,
38 pub output: PathBuf,
40 pub nloop: usize,
42 pub filetype: Option<String>,
45}
46
47pub struct StructurePlan {
50 pub filepath: PathBuf,
53 pub number: usize,
55 pub mol_restraints: Vec<RestraintSpec>,
57 pub atom_groups: Vec<AtomGroup>,
61 pub center: bool,
63 pub fixed: Option<([f64; 3], [f64; 3])>,
65}
66
67impl Script {
68 pub fn lower(&self, base_dir: &Path) -> Result<ScriptPlan, ScriptError> {
75 if self.structures.is_empty() {
76 return Err(ScriptError::NoStructures);
77 }
78
79 let mut packer = Molpack::new()
80 .with_tolerance(self.tolerance)
81 .with_avoid_overlap(self.avoid_overlap);
82 if let Some(seed) = self.seed {
83 packer = packer.with_seed(seed);
84 }
85 if let Some(pbc) = self.pbc {
86 packer = packer.with_periodic_box(pbc.min, pbc.max);
87 }
88
89 let structures: Vec<StructurePlan> = self
90 .structures
91 .iter()
92 .map(|s| StructurePlan::from_structure(s, base_dir))
93 .collect();
94
95 Ok(ScriptPlan {
96 packer,
97 structures,
98 output: resolve(base_dir, &self.output),
99 nloop: self.nloop,
100 filetype: self.filetype.clone(),
101 })
102 }
103}
104
105impl StructurePlan {
106 fn from_structure(s: &Structure, base_dir: &Path) -> Self {
107 Self {
108 filepath: resolve(base_dir, &s.filepath),
109 number: s.number,
110 mol_restraints: s.mol_restraints.clone(),
111 atom_groups: s.atom_groups.clone(),
112 center: s.center,
113 fixed: s.fixed,
114 }
115 }
116
117 pub fn apply(&self, mut target: Target) -> Target {
120 for r in &self.mol_restraints {
121 target = apply_mol_restraint(target, r);
122 }
123
124 for group in &self.atom_groups {
125 target = apply_atom_group(target, group);
126 }
127
128 if self.center {
129 target = target.with_centering(CenteringMode::Center);
130 }
131
132 if let Some((pos, euler)) = self.fixed {
133 target = target.fixed_at(pos).with_orientation([
134 Angle::from_radians(euler[0]),
135 Angle::from_radians(euler[1]),
136 Angle::from_radians(euler[2]),
137 ]);
138 }
139
140 target
141 }
142}
143
144fn resolve(base: &Path, path: &Path) -> PathBuf {
145 if path.is_absolute() {
146 path.to_path_buf()
147 } else {
148 base.join(path)
149 }
150}
151
152fn restraint_from_spec(r: &RestraintSpec) -> Box<dyn AtomRestraint> {
158 match *r {
159 RestraintSpec::InsideBox { min, max } => {
160 Box::new(InsideBoxRestraint::new(min, max, [false; 3]))
161 }
162 RestraintSpec::OutsideBox { min, max } => Box::new(OutsideBoxRestraint::new(min, max)),
163 RestraintSpec::InsideCube { origin, side } => {
164 Box::new(InsideCubeRestraint::new(origin, side))
165 }
166 RestraintSpec::OutsideCube { origin, side } => {
167 Box::new(OutsideCubeRestraint::new(origin, side))
168 }
169 RestraintSpec::InsideSphere { center, radius } => {
170 Box::new(InsideSphereRestraint::new(center, radius))
171 }
172 RestraintSpec::OutsideSphere { center, radius } => {
173 Box::new(OutsideSphereRestraint::new(center, radius))
174 }
175 RestraintSpec::InsideEllipsoid {
176 center,
177 axes,
178 exponent,
179 } => Box::new(InsideEllipsoidRestraint::new(center, axes, exponent)),
180 RestraintSpec::OutsideEllipsoid {
181 center,
182 axes,
183 exponent,
184 } => Box::new(OutsideEllipsoidRestraint::new(center, axes, exponent)),
185 RestraintSpec::InsideCylinder {
186 center,
187 axis,
188 radius,
189 length,
190 } => Box::new(InsideCylinderRestraint::new(center, axis, radius, length)),
191 RestraintSpec::OutsideCylinder {
192 center,
193 axis,
194 radius,
195 length,
196 } => Box::new(OutsideCylinderRestraint::new(center, axis, radius, length)),
197 RestraintSpec::AbovePlane { normal, distance } => {
198 Box::new(AbovePlaneRestraint::new(normal, distance))
199 }
200 RestraintSpec::BelowPlane { normal, distance } => {
201 Box::new(BelowPlaneRestraint::new(normal, distance))
202 }
203 }
204}
205
206fn apply_mol_restraint(target: Target, r: &RestraintSpec) -> Target {
207 target.with_restraint(restraint_from_spec(r))
208}
209
210fn apply_atom_group(mut target: Target, group: &AtomGroup) -> Target {
211 let zero_indexed: Vec<usize> = group
213 .atom_indices
214 .iter()
215 .map(|&i| i.saturating_sub(1))
216 .collect();
217 let indices = zero_indexed.as_slice();
218 for r in &group.restraints {
219 target = target.with_atom_restraint(indices, restraint_from_spec(r));
220 }
221 target
222}
223
224#[cfg(feature = "io")]
235pub struct BuildResult {
236 pub packer: Molpack,
237 pub targets: Vec<Target>,
238 pub output: PathBuf,
239 pub nloop: usize,
240}
241
242#[cfg(feature = "io")]
243impl Script {
244 pub fn build(&self, base_dir: &Path) -> Result<BuildResult, ScriptError> {
250 let plan = self.lower(base_dir)?;
251 let filetype = plan.filetype.as_deref();
252 let targets: Vec<Target> = plan
253 .structures
254 .iter()
255 .map(|sp| -> Result<Target, ScriptError> {
256 let frame = super::io::read_frame(&sp.filepath, filetype)?;
257 Ok(sp.apply(Target::new(frame, sp.number)))
258 })
259 .collect::<Result<_, _>>()?;
260 Ok(BuildResult {
261 packer: plan.packer,
262 targets,
263 output: plan.output,
264 nloop: plan.nloop,
265 })
266 }
267}