use std::collections::BTreeSet;
use std::error::Error;
use std::fmt::{Display, Formatter};
use phasesmith_model::{DomainError, ProjectRecord, RecordId};
use crate::{
LatticeParameterization, TofMultiBankGeometryCheckpoint, TofMultiBankGeometryError,
TofMultiBankGeometryInput, TofMultiBankGeometryOptions,
};
#[derive(Clone, Debug, PartialEq)]
pub struct TofMultiBankGeometryAnalysis {
pub analysis_id: RecordId,
pub input: TofMultiBankGeometryInput,
pub options: TofMultiBankGeometryOptions,
pub checkpoint: Option<TofMultiBankGeometryCheckpoint>,
}
impl TofMultiBankGeometryAnalysis {
pub fn validate(&self) -> Result<(), TofMultiBankProjectError> {
self.input.validate()?;
self.options.validate()?;
if let Some(checkpoint) = &self.checkpoint {
checkpoint.validate_for(&self.input, &self.options)?;
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TofMultiBankGeometryProjectState {
pub project: ProjectRecord,
pub analyses: Vec<TofMultiBankGeometryAnalysis>,
}
impl TofMultiBankGeometryProjectState {
pub fn validate(&self) -> Result<(), TofMultiBankProjectError> {
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(TofMultiBankProjectError::DuplicateAnalysis {
analysis_id: analysis.analysis_id.clone(),
});
}
for bank in &analysis.input.lattice.multibank.banks {
if !histogram_ids.insert(bank.bank_id.clone()) {
return Err(TofMultiBankProjectError::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(|| TofMultiBankProjectError::UnknownHistogram {
histogram_id: bank.bank_id.clone(),
})?;
if bank.input.pattern != histogram.pattern
|| bank.input.instrument != histogram.experiment.instrument
{
return Err(TofMultiBankProjectError::HistogramStateMismatch {
histogram_id: bank.bank_id.clone(),
});
}
let phase_ids = bank
.input
.phases
.iter()
.map(crate::TofLeBailPhase::phase_id)
.collect::<Vec<_>>();
if phase_ids != histogram.phase_ids.iter().collect::<Vec<_>>() {
return Err(TofMultiBankProjectError::PhaseOrderMismatch {
histogram_id: bank.bank_id.clone(),
});
}
for phase in &bank.input.phases {
let stored = self
.project
.phases
.iter()
.find(|item| &item.phase_id == phase.phase_id())
.ok_or_else(|| TofMultiBankProjectError::PhaseOrderMismatch {
histogram_id: bank.bank_id.clone(),
})?;
if stored.name != phase.name() {
return Err(TofMultiBankProjectError::PhaseStateMismatch {
phase_id: stored.phase_id.clone(),
});
}
}
}
for lattice in &analysis.input.lattice.lattice_phases {
let stored = self
.project
.phases
.iter()
.find(|phase| &phase.phase_id == lattice.phase_id())
.ok_or_else(|| TofMultiBankProjectError::PhaseStateMismatch {
phase_id: lattice.phase_id().clone(),
})?;
let expected = LatticeParameterization::new(
stored.definition.space_group.clone(),
stored.definition.cell,
)?;
if lattice.initial_cell() != stored.definition.cell
|| lattice.parameterization() != &expected
{
return Err(TofMultiBankProjectError::LatticeStateMismatch {
phase_id: lattice.phase_id().clone(),
});
}
}
}
Ok(())
}
}
#[derive(Debug)]
pub enum TofMultiBankProjectError {
Domain(DomainError),
Workflow(TofMultiBankGeometryError),
DuplicateAnalysis {
analysis_id: RecordId,
},
DuplicateHistogramOwnership {
histogram_id: RecordId,
},
UnknownHistogram {
histogram_id: RecordId,
},
HistogramStateMismatch {
histogram_id: RecordId,
},
PhaseOrderMismatch {
histogram_id: RecordId,
},
PhaseStateMismatch {
phase_id: RecordId,
},
LatticeStateMismatch {
phase_id: RecordId,
},
}
impl Display for TofMultiBankProjectError {
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::DuplicateAnalysis { analysis_id } => {
write!(formatter, "duplicate joint TOF analysis {analysis_id}")
}
Self::DuplicateHistogramOwnership { histogram_id } => write!(
formatter,
"TOF histogram {histogram_id} belongs to multiple joint analyses"
),
Self::UnknownHistogram { histogram_id } => {
write!(formatter, "unknown TOF histogram {histogram_id}")
}
Self::HistogramStateMismatch { histogram_id } => write!(
formatter,
"joint TOF bank state differs from histogram {histogram_id}"
),
Self::PhaseOrderMismatch { histogram_id } => write!(
formatter,
"joint TOF phase order differs from histogram {histogram_id}"
),
Self::PhaseStateMismatch { phase_id } => {
write!(formatter, "joint TOF phase state differs for {phase_id}")
}
Self::LatticeStateMismatch { phase_id } => write!(
formatter,
"joint TOF lattice state differs from project phase {phase_id}"
),
}
}
}
impl Error for TofMultiBankProjectError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Domain(error) => Some(error),
Self::Workflow(error) => Some(error),
_ => None,
}
}
}
impl From<DomainError> for TofMultiBankProjectError {
fn from(value: DomainError) -> Self {
Self::Domain(value)
}
}
impl From<TofMultiBankGeometryError> for TofMultiBankProjectError {
fn from(value: TofMultiBankGeometryError) -> Self {
Self::Workflow(value)
}
}
impl From<crate::LatticeError> for TofMultiBankProjectError {
fn from(value: crate::LatticeError) -> Self {
Self::Workflow(value.into())
}
}