use std::collections::BTreeSet;
use std::error::Error;
use std::fmt::{Display, Formatter};
use phasesmith_model::{DomainError, ProjectRecord, RecordId};
use crate::{
StructuralTofMultiBankCheckpoint, StructuralTofMultiBankError, StructuralTofMultiBankInput,
StructuralTofMultiBankRefinementError, StructuralTofMultiBankRefinementOptions,
};
#[derive(Clone, Debug, PartialEq)]
pub struct StructuralTofMultiBankAnalysis {
pub analysis_id: RecordId,
pub input: StructuralTofMultiBankInput,
pub options: StructuralTofMultiBankRefinementOptions,
pub checkpoint: Option<StructuralTofMultiBankCheckpoint>,
}
impl StructuralTofMultiBankAnalysis {
pub fn validate(&self) -> Result<(), StructuralTofProjectError> {
self.input.validate()?;
self.options.validate()?;
if let Some(checkpoint) = &self.checkpoint {
checkpoint.validate_for(&self.input)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StructuralTofMultiBankProjectState {
pub project: ProjectRecord,
pub analyses: Vec<StructuralTofMultiBankAnalysis>,
}
impl StructuralTofMultiBankProjectState {
pub fn validate(&self) -> Result<(), StructuralTofProjectError> {
self.project.validate()?;
let mut analysis_ids = BTreeSet::new();
let mut histogram_ids = BTreeSet::new();
for analysis in &self.analyses {
analysis.validate()?;
if !analysis_ids.insert(analysis.analysis_id.clone()) {
return Err(StructuralTofProjectError::DuplicateAnalysis {
analysis_id: analysis.analysis_id.clone(),
});
}
let phase_id = analysis.input.phase.phase_id();
let stored_phase = self
.project
.phases
.iter()
.find(|phase| &phase.phase_id == phase_id)
.ok_or_else(|| StructuralTofProjectError::PhaseStateMismatch {
phase_id: phase_id.clone(),
})?;
if stored_phase.name != analysis.input.phase.name()
|| stored_phase.definition != *analysis.input.phase.definition()
|| !stored_phase.required_providers.is_empty()
{
return Err(StructuralTofProjectError::PhaseStateMismatch {
phase_id: phase_id.clone(),
});
}
for bank in &analysis.input.banks {
if !histogram_ids.insert(bank.bank_id.clone()) {
return Err(StructuralTofProjectError::DuplicateHistogramOwnership {
histogram_id: bank.bank_id.clone(),
});
}
let histogram = self
.project
.tof_histograms
.iter()
.find(|item| item.histogram_id == bank.bank_id)
.ok_or_else(|| StructuralTofProjectError::UnknownHistogram {
histogram_id: bank.bank_id.clone(),
})?;
if histogram.pattern != bank.pattern
|| histogram.experiment.instrument != bank.instrument
{
return Err(StructuralTofProjectError::HistogramStateMismatch {
histogram_id: bank.bank_id.clone(),
});
}
if histogram.phase_ids.as_slice() != std::slice::from_ref(phase_id) {
return Err(StructuralTofProjectError::PhaseOrderMismatch {
histogram_id: bank.bank_id.clone(),
});
}
}
}
Ok(())
}
}
#[derive(Debug)]
pub enum StructuralTofProjectError {
Domain(DomainError),
Workflow(StructuralTofMultiBankError),
Refinement(StructuralTofMultiBankRefinementError),
DuplicateAnalysis {
analysis_id: RecordId,
},
DuplicateHistogramOwnership {
histogram_id: RecordId,
},
UnknownHistogram {
histogram_id: RecordId,
},
HistogramStateMismatch {
histogram_id: RecordId,
},
PhaseOrderMismatch {
histogram_id: RecordId,
},
PhaseStateMismatch {
phase_id: RecordId,
},
}
impl Display for StructuralTofProjectError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Domain(error) => Display::fmt(error, formatter),
Self::Workflow(error) => Display::fmt(error, formatter),
Self::Refinement(error) => Display::fmt(error, formatter),
Self::DuplicateAnalysis { analysis_id } => {
write!(formatter, "duplicate structural TOF analysis {analysis_id}")
}
Self::DuplicateHistogramOwnership { histogram_id } => write!(
formatter,
"TOF histogram {histogram_id} belongs to multiple structural analyses"
),
Self::UnknownHistogram { histogram_id } => {
write!(formatter, "unknown TOF histogram {histogram_id}")
}
Self::HistogramStateMismatch { histogram_id } => write!(
formatter,
"structural TOF bank state differs from histogram {histogram_id}"
),
Self::PhaseOrderMismatch { histogram_id } => write!(
formatter,
"structural TOF phase order differs from histogram {histogram_id}"
),
Self::PhaseStateMismatch { phase_id } => {
write!(
formatter,
"structural TOF phase state differs for {phase_id}"
)
}
}
}
}
impl Error for StructuralTofProjectError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Domain(error) => Some(error),
Self::Workflow(error) => Some(error),
Self::Refinement(error) => Some(error),
_ => None,
}
}
}
impl From<DomainError> for StructuralTofProjectError {
fn from(value: DomainError) -> Self {
Self::Domain(value)
}
}
impl From<StructuralTofMultiBankError> for StructuralTofProjectError {
fn from(value: StructuralTofMultiBankError) -> Self {
Self::Workflow(value)
}
}
impl From<StructuralTofMultiBankRefinementError> for StructuralTofProjectError {
fn from(value: StructuralTofMultiBankRefinementError) -> Self {
Self::Refinement(value)
}
}