phasesmith_workflows/
tof_multibank_project.rs1use std::collections::BTreeSet;
4use std::error::Error;
5use std::fmt::{Display, Formatter};
6
7use phasesmith_model::{DomainError, ProjectRecord, RecordId};
8
9use crate::{
10 LatticeParameterization, TofMultiBankGeometryCheckpoint, TofMultiBankGeometryError,
11 TofMultiBankGeometryInput, TofMultiBankGeometryOptions,
12};
13
14#[derive(Clone, Debug, PartialEq)]
16pub struct TofMultiBankGeometryAnalysis {
17 pub analysis_id: RecordId,
19 pub input: TofMultiBankGeometryInput,
21 pub options: TofMultiBankGeometryOptions,
23 pub checkpoint: Option<TofMultiBankGeometryCheckpoint>,
25}
26
27impl TofMultiBankGeometryAnalysis {
28 pub fn validate(&self) -> Result<(), TofMultiBankProjectError> {
34 self.input.validate()?;
35 self.options.validate()?;
36 if let Some(checkpoint) = &self.checkpoint {
37 checkpoint.validate_for(&self.input, &self.options)?;
38 }
39 Ok(())
40 }
41}
42
43#[derive(Clone, Debug, PartialEq)]
45pub struct TofMultiBankGeometryProjectState {
46 pub project: ProjectRecord,
48 pub analyses: Vec<TofMultiBankGeometryAnalysis>,
50}
51
52impl TofMultiBankGeometryProjectState {
53 pub fn validate(&self) -> Result<(), TofMultiBankProjectError> {
63 self.project.validate()?;
64 let mut analysis_ids = BTreeSet::new();
65 let mut histogram_ids = BTreeSet::new();
66 for analysis in &self.analyses {
67 analysis.validate()?;
68 if !analysis_ids.insert(analysis.analysis_id.clone()) {
69 return Err(TofMultiBankProjectError::DuplicateAnalysis {
70 analysis_id: analysis.analysis_id.clone(),
71 });
72 }
73 for bank in &analysis.input.lattice.multibank.banks {
74 if !histogram_ids.insert(bank.bank_id.clone()) {
75 return Err(TofMultiBankProjectError::DuplicateHistogramOwnership {
76 histogram_id: bank.bank_id.clone(),
77 });
78 }
79 let histogram = self
80 .project
81 .tof_histograms
82 .iter()
83 .find(|item| item.histogram_id == bank.bank_id)
84 .ok_or_else(|| TofMultiBankProjectError::UnknownHistogram {
85 histogram_id: bank.bank_id.clone(),
86 })?;
87 if bank.input.pattern != histogram.pattern
88 || bank.input.instrument != histogram.experiment.instrument
89 {
90 return Err(TofMultiBankProjectError::HistogramStateMismatch {
91 histogram_id: bank.bank_id.clone(),
92 });
93 }
94 let phase_ids = bank
95 .input
96 .phases
97 .iter()
98 .map(crate::TofLeBailPhase::phase_id)
99 .collect::<Vec<_>>();
100 if phase_ids != histogram.phase_ids.iter().collect::<Vec<_>>() {
101 return Err(TofMultiBankProjectError::PhaseOrderMismatch {
102 histogram_id: bank.bank_id.clone(),
103 });
104 }
105 for phase in &bank.input.phases {
106 let stored = self
107 .project
108 .phases
109 .iter()
110 .find(|item| &item.phase_id == phase.phase_id())
111 .ok_or_else(|| TofMultiBankProjectError::PhaseOrderMismatch {
112 histogram_id: bank.bank_id.clone(),
113 })?;
114 if stored.name != phase.name() {
115 return Err(TofMultiBankProjectError::PhaseStateMismatch {
116 phase_id: stored.phase_id.clone(),
117 });
118 }
119 }
120 }
121 for lattice in &analysis.input.lattice.lattice_phases {
122 let stored = self
123 .project
124 .phases
125 .iter()
126 .find(|phase| &phase.phase_id == lattice.phase_id())
127 .ok_or_else(|| TofMultiBankProjectError::PhaseStateMismatch {
128 phase_id: lattice.phase_id().clone(),
129 })?;
130 let expected = LatticeParameterization::new(
131 stored.definition.space_group.clone(),
132 stored.definition.cell,
133 )?;
134 if lattice.initial_cell() != stored.definition.cell
135 || lattice.parameterization() != &expected
136 {
137 return Err(TofMultiBankProjectError::LatticeStateMismatch {
138 phase_id: lattice.phase_id().clone(),
139 });
140 }
141 }
142 }
143 Ok(())
144 }
145}
146
147#[derive(Debug)]
149pub enum TofMultiBankProjectError {
150 Domain(DomainError),
152 Workflow(TofMultiBankGeometryError),
154 DuplicateAnalysis {
156 analysis_id: RecordId,
158 },
159 DuplicateHistogramOwnership {
161 histogram_id: RecordId,
163 },
164 UnknownHistogram {
166 histogram_id: RecordId,
168 },
169 HistogramStateMismatch {
171 histogram_id: RecordId,
173 },
174 PhaseOrderMismatch {
176 histogram_id: RecordId,
178 },
179 PhaseStateMismatch {
181 phase_id: RecordId,
183 },
184 LatticeStateMismatch {
186 phase_id: RecordId,
188 },
189}
190
191impl Display for TofMultiBankProjectError {
192 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
193 match self {
194 Self::Domain(error) => Display::fmt(error, formatter),
195 Self::Workflow(error) => Display::fmt(error, formatter),
196 Self::DuplicateAnalysis { analysis_id } => {
197 write!(formatter, "duplicate joint TOF analysis {analysis_id}")
198 }
199 Self::DuplicateHistogramOwnership { histogram_id } => write!(
200 formatter,
201 "TOF histogram {histogram_id} belongs to multiple joint analyses"
202 ),
203 Self::UnknownHistogram { histogram_id } => {
204 write!(formatter, "unknown TOF histogram {histogram_id}")
205 }
206 Self::HistogramStateMismatch { histogram_id } => write!(
207 formatter,
208 "joint TOF bank state differs from histogram {histogram_id}"
209 ),
210 Self::PhaseOrderMismatch { histogram_id } => write!(
211 formatter,
212 "joint TOF phase order differs from histogram {histogram_id}"
213 ),
214 Self::PhaseStateMismatch { phase_id } => {
215 write!(formatter, "joint TOF phase state differs for {phase_id}")
216 }
217 Self::LatticeStateMismatch { phase_id } => write!(
218 formatter,
219 "joint TOF lattice state differs from project phase {phase_id}"
220 ),
221 }
222 }
223}
224
225impl Error for TofMultiBankProjectError {
226 fn source(&self) -> Option<&(dyn Error + 'static)> {
227 match self {
228 Self::Domain(error) => Some(error),
229 Self::Workflow(error) => Some(error),
230 _ => None,
231 }
232 }
233}
234
235impl From<DomainError> for TofMultiBankProjectError {
236 fn from(value: DomainError) -> Self {
237 Self::Domain(value)
238 }
239}
240
241impl From<TofMultiBankGeometryError> for TofMultiBankProjectError {
242 fn from(value: TofMultiBankGeometryError) -> Self {
243 Self::Workflow(value)
244 }
245}
246
247impl From<crate::LatticeError> for TofMultiBankProjectError {
248 fn from(value: crate::LatticeError) -> Self {
249 Self::Workflow(value.into())
250 }
251}