Skip to main content

castep_cell_io/param/
geometry_optimization_params.rs

1use bon::Builder;
2use castep_cell_fmt::{Cell, CResult, Error, FromCellFile, ToCellFile, ToCell, FromKeyValue};
3use super::geometry_optimization::*;
4
5/// Geometry optimization parameters for CASTEP calculations
6///
7/// This group contains settings that control geometry optimization runs,
8/// including convergence criteria, optimization method, and related parameters.
9#[derive(Debug, Clone, Default, Builder)]
10pub struct GeometryOptimizationParams {
11    pub geom_convergence_win: Option<GeomConvergenceWin>,
12    pub geom_disp_tol: Option<GeomDispTol>,
13    pub geom_energy_tol: Option<GeomEnergyTol>,
14    pub geom_force_tol: Option<GeomForceTol>,
15    pub geom_frequency_est: Option<GeomFrequencyEst>,
16    pub geom_max_iter: Option<GeomMaxIter>,
17    pub geom_method: Option<GeomMethod>,
18    pub geom_modulus_est: Option<GeomModulusEst>,
19    pub geom_preconditioner: Option<GeomPreconditioner>,
20    pub geom_spin_fix: Option<GeomSpinFix>,
21    pub geom_stress_tol: Option<GeomStressTol>,
22}
23
24impl GeometryOptimizationParams {
25    /// Validates intra-group constraints for geometry optimization parameters
26    pub fn validate(self) -> Result<Self, String> {
27        // Currently no mutual exclusivity constraints for geometry optimization
28        Ok(self)
29    }
30}
31
32impl FromCellFile for GeometryOptimizationParams {
33    fn from_cell_file(tokens: &[Cell<'_>]) -> CResult<Self> {
34        Self::builder()
35            .maybe_geom_convergence_win(GeomConvergenceWin::from_cells(tokens).ok().flatten())
36            .maybe_geom_disp_tol(GeomDispTol::from_cells(tokens).ok().flatten())
37            .maybe_geom_energy_tol(GeomEnergyTol::from_cells(tokens).ok().flatten())
38            .maybe_geom_force_tol(GeomForceTol::from_cells(tokens).ok().flatten())
39            .maybe_geom_frequency_est(GeomFrequencyEst::from_cells(tokens).ok().flatten())
40            .maybe_geom_max_iter(GeomMaxIter::from_cells(tokens).ok().flatten())
41            .maybe_geom_method(GeomMethod::from_cells(tokens).ok().flatten())
42            .maybe_geom_modulus_est(GeomModulusEst::from_cells(tokens).ok().flatten())
43            .maybe_geom_preconditioner(GeomPreconditioner::from_cells(tokens).ok().flatten())
44            .maybe_geom_spin_fix(GeomSpinFix::from_cells(tokens).ok().flatten())
45            .maybe_geom_stress_tol(GeomStressTol::from_cells(tokens).ok().flatten())
46            .build()
47            .validate()
48            .map_err(|e| Error::Message(e.to_string()))
49    }
50}
51
52impl ToCellFile for GeometryOptimizationParams {
53    fn to_cell_file(&self) -> Vec<Cell<'_>> {
54        let mut cells = Vec::new();
55        if let Some(v) = &self.geom_convergence_win { cells.push(v.to_cell()); }
56        if let Some(v) = &self.geom_disp_tol { cells.push(v.to_cell()); }
57        if let Some(v) = &self.geom_energy_tol { cells.push(v.to_cell()); }
58        if let Some(v) = &self.geom_force_tol { cells.push(v.to_cell()); }
59        if let Some(v) = &self.geom_frequency_est { cells.push(v.to_cell()); }
60        if let Some(v) = &self.geom_max_iter { cells.push(v.to_cell()); }
61        if let Some(v) = &self.geom_method { cells.push(v.to_cell()); }
62        if let Some(v) = &self.geom_modulus_est { cells.push(v.to_cell()); }
63        if let Some(v) = &self.geom_preconditioner { cells.push(v.to_cell()); }
64        if let Some(v) = &self.geom_spin_fix { cells.push(v.to_cell()); }
65        if let Some(v) = &self.geom_stress_tol { cells.push(v.to_cell()); }
66        cells
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_empty_builder() {
76        let params = GeometryOptimizationParams::builder().build();
77        assert!(params.validate().is_ok());
78    }
79
80    #[test]
81    fn test_builder_with_single_field() {
82        let params = GeometryOptimizationParams::builder()
83            .maybe_geom_max_iter(Some(GeomMaxIter(100)))
84            .build();
85        assert_eq!(params.geom_max_iter, Some(GeomMaxIter(100)));
86        assert!(params.validate().is_ok());
87    }
88
89    #[test]
90    fn test_builder_with_multiple_fields() {
91        let params = GeometryOptimizationParams::builder()
92            .maybe_geom_max_iter(Some(GeomMaxIter(100)))
93            .maybe_geom_method(Some(GeomMethod::Bfgs))
94            .build();
95        assert_eq!(params.geom_max_iter, Some(GeomMaxIter(100)));
96        assert_eq!(params.geom_method, Some(GeomMethod::Bfgs));
97        assert!(params.validate().is_ok());
98    }
99
100    #[test]
101    fn test_to_cell_file_empty() {
102        let params = GeometryOptimizationParams::builder().build();
103        let cells = params.to_cell_file();
104        assert_eq!(cells.len(), 0);
105    }
106
107    #[test]
108    fn test_to_cell_file_with_fields() {
109        let params = GeometryOptimizationParams::builder()
110            .maybe_geom_max_iter(Some(GeomMaxIter(100)))
111            .maybe_geom_method(Some(GeomMethod::Bfgs))
112            .build();
113        let cells = params.to_cell_file();
114        assert_eq!(cells.len(), 2);
115    }
116
117    #[test]
118    fn test_validate_always_succeeds() {
119        let params = GeometryOptimizationParams::builder()
120            .maybe_geom_max_iter(Some(GeomMaxIter(100)))
121            .maybe_geom_method(Some(GeomMethod::Bfgs))
122            .maybe_geom_force_tol(Some(GeomForceTol { value: 0.01, unit: None }))
123            .build();
124        assert!(params.validate().is_ok());
125    }
126}