phasesmith_workflows/
tof_structural_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 StructuralTofMultiBankCheckpoint, StructuralTofMultiBankError, StructuralTofMultiBankInput,
11 StructuralTofMultiBankRefinementError, StructuralTofMultiBankRefinementOptions,
12};
13
14#[derive(Clone, Debug, PartialEq)]
16pub struct StructuralTofMultiBankAnalysis {
17 pub analysis_id: RecordId,
19 pub input: StructuralTofMultiBankInput,
21 pub options: StructuralTofMultiBankRefinementOptions,
23 pub checkpoint: Option<StructuralTofMultiBankCheckpoint>,
25}
26
27impl StructuralTofMultiBankAnalysis {
28 pub fn validate(&self) -> Result<(), StructuralTofProjectError> {
34 self.input.validate()?;
35 self.options.validate()?;
36 if let Some(checkpoint) = &self.checkpoint {
37 checkpoint.validate_for(&self.input)?;
38 }
39 Ok(())
40 }
41}
42
43#[derive(Clone, Debug, PartialEq)]
45pub struct StructuralTofMultiBankProjectState {
46 pub project: ProjectRecord,
48 pub analyses: Vec<StructuralTofMultiBankAnalysis>,
50}
51
52impl StructuralTofMultiBankProjectState {
53 pub fn validate(&self) -> Result<(), StructuralTofProjectError> {
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(StructuralTofProjectError::DuplicateAnalysis {
70 analysis_id: analysis.analysis_id.clone(),
71 });
72 }
73 let phase_id = analysis.input.phase.phase_id();
74 let stored_phase = self
75 .project
76 .phases
77 .iter()
78 .find(|phase| &phase.phase_id == phase_id)
79 .ok_or_else(|| StructuralTofProjectError::PhaseStateMismatch {
80 phase_id: phase_id.clone(),
81 })?;
82 if stored_phase.name != analysis.input.phase.name()
83 || stored_phase.definition != *analysis.input.phase.definition()
84 || !stored_phase.required_providers.is_empty()
85 {
86 return Err(StructuralTofProjectError::PhaseStateMismatch {
87 phase_id: phase_id.clone(),
88 });
89 }
90 for bank in &analysis.input.banks {
91 if !histogram_ids.insert(bank.bank_id.clone()) {
92 return Err(StructuralTofProjectError::DuplicateHistogramOwnership {
93 histogram_id: bank.bank_id.clone(),
94 });
95 }
96 let histogram = self
97 .project
98 .tof_histograms
99 .iter()
100 .find(|item| item.histogram_id == bank.bank_id)
101 .ok_or_else(|| StructuralTofProjectError::UnknownHistogram {
102 histogram_id: bank.bank_id.clone(),
103 })?;
104 if histogram.pattern != bank.pattern
105 || histogram.experiment.instrument != bank.instrument
106 {
107 return Err(StructuralTofProjectError::HistogramStateMismatch {
108 histogram_id: bank.bank_id.clone(),
109 });
110 }
111 if histogram.phase_ids.as_slice() != std::slice::from_ref(phase_id) {
112 return Err(StructuralTofProjectError::PhaseOrderMismatch {
113 histogram_id: bank.bank_id.clone(),
114 });
115 }
116 }
117 }
118 Ok(())
119 }
120}
121
122#[derive(Debug)]
124pub enum StructuralTofProjectError {
125 Domain(DomainError),
127 Workflow(StructuralTofMultiBankError),
129 Refinement(StructuralTofMultiBankRefinementError),
131 DuplicateAnalysis {
133 analysis_id: RecordId,
135 },
136 DuplicateHistogramOwnership {
138 histogram_id: RecordId,
140 },
141 UnknownHistogram {
143 histogram_id: RecordId,
145 },
146 HistogramStateMismatch {
148 histogram_id: RecordId,
150 },
151 PhaseOrderMismatch {
153 histogram_id: RecordId,
155 },
156 PhaseStateMismatch {
158 phase_id: RecordId,
160 },
161}
162
163impl Display for StructuralTofProjectError {
164 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
165 match self {
166 Self::Domain(error) => Display::fmt(error, formatter),
167 Self::Workflow(error) => Display::fmt(error, formatter),
168 Self::Refinement(error) => Display::fmt(error, formatter),
169 Self::DuplicateAnalysis { analysis_id } => {
170 write!(formatter, "duplicate structural TOF analysis {analysis_id}")
171 }
172 Self::DuplicateHistogramOwnership { histogram_id } => write!(
173 formatter,
174 "TOF histogram {histogram_id} belongs to multiple structural analyses"
175 ),
176 Self::UnknownHistogram { histogram_id } => {
177 write!(formatter, "unknown TOF histogram {histogram_id}")
178 }
179 Self::HistogramStateMismatch { histogram_id } => write!(
180 formatter,
181 "structural TOF bank state differs from histogram {histogram_id}"
182 ),
183 Self::PhaseOrderMismatch { histogram_id } => write!(
184 formatter,
185 "structural TOF phase order differs from histogram {histogram_id}"
186 ),
187 Self::PhaseStateMismatch { phase_id } => {
188 write!(
189 formatter,
190 "structural TOF phase state differs for {phase_id}"
191 )
192 }
193 }
194 }
195}
196
197impl Error for StructuralTofProjectError {
198 fn source(&self) -> Option<&(dyn Error + 'static)> {
199 match self {
200 Self::Domain(error) => Some(error),
201 Self::Workflow(error) => Some(error),
202 Self::Refinement(error) => Some(error),
203 _ => None,
204 }
205 }
206}
207
208impl From<DomainError> for StructuralTofProjectError {
209 fn from(value: DomainError) -> Self {
210 Self::Domain(value)
211 }
212}
213
214impl From<StructuralTofMultiBankError> for StructuralTofProjectError {
215 fn from(value: StructuralTofMultiBankError) -> Self {
216 Self::Workflow(value)
217 }
218}
219
220impl From<StructuralTofMultiBankRefinementError> for StructuralTofProjectError {
221 fn from(value: StructuralTofMultiBankRefinementError) -> Self {
222 Self::Refinement(value)
223 }
224}