use crate::Thermodynamics::User_substances::SubsData;
use std::collections::HashMap;
use std::ops::Range;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PhaseId(pub Option<String>);
impl PhaseId {
pub fn new(phase: Option<String>) -> Self {
Self(phase)
}
pub fn as_option(&self) -> &Option<String> {
&self.0
}
pub fn into_option(self) -> Option<String> {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PhaseComponentId {
pub phase: PhaseId,
pub substance: String,
}
impl PhaseComponentId {
pub fn new(phase: PhaseId, substance: impl Into<String>) -> Self {
Self {
phase,
substance: substance.into(),
}
}
pub fn label(&self) -> String {
match self.phase.as_option() {
Some(phase) => format!("{}::{}", phase, self.substance),
None => self.substance.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SystemLayout {
pub phases: Vec<PhaseId>,
pub components: Vec<PhaseComponentId>,
phase_ranges: HashMap<PhaseId, Range<usize>>,
}
impl SystemLayout {
pub fn from_ordered_phase_components(phase_components: Vec<(PhaseId, Vec<String>)>) -> Self {
let mut phases = Vec::with_capacity(phase_components.len());
let mut components = Vec::new();
let mut phase_ranges = HashMap::with_capacity(phase_components.len());
for (phase, substances) in phase_components {
let start = components.len();
components.extend(
substances
.into_iter()
.map(|substance| PhaseComponentId::new(phase.clone(), substance)),
);
phase_ranges.insert(phase.clone(), start..components.len());
phases.push(phase);
}
Self {
phases,
components,
phase_ranges,
}
}
pub fn from_single_phase(substances: &[String]) -> Self {
let phase = PhaseId::new(None);
let components = substances
.iter()
.cloned()
.map(|substance| PhaseComponentId::new(phase.clone(), substance))
.collect::<Vec<_>>();
let mut phase_ranges = HashMap::new();
phase_ranges.insert(phase.clone(), 0..components.len());
Self {
phases: vec![phase],
components,
phase_ranges,
}
}
pub fn from_phase_map(phases: &HashMap<Option<String>, SubsData>) -> Self {
let mut ordered_phases: Vec<PhaseId> = phases.keys().cloned().map(PhaseId::new).collect();
ordered_phases.sort();
let mut components = Vec::new();
let mut phase_ranges = HashMap::with_capacity(ordered_phases.len());
for phase in &ordered_phases {
let start = components.len();
if let Some(subsdata) = phases.get(&phase.0) {
for substance in subsdata.substances.iter().cloned() {
components.push(PhaseComponentId::new(phase.clone(), substance));
}
}
phase_ranges.insert(phase.clone(), start..components.len());
}
Self {
phases: ordered_phases,
components,
phase_ranges,
}
}
pub fn component_count(&self) -> usize {
self.components.len()
}
pub fn phase_component_range(&self, phase: &PhaseId) -> Option<&Range<usize>> {
self.phase_ranges.get(phase)
}
pub fn phase_index(&self, phase: &PhaseId) -> Option<usize> {
self.phases.iter().position(|candidate| candidate == phase)
}
pub fn component_index(&self, component: &PhaseComponentId) -> Option<usize> {
self.components
.iter()
.position(|candidate| candidate == component)
}
pub fn component_index_by_parts(&self, phase: &PhaseId, substance: &str) -> Option<usize> {
self.components
.iter()
.position(|candidate| &candidate.phase == phase && candidate.substance == substance)
}
pub fn phases(&self) -> &[PhaseId] {
&self.phases
}
pub fn components(&self) -> &[PhaseComponentId] {
&self.components
}
pub fn components_for_phase(&self, phase: &PhaseId) -> Option<&[PhaseComponentId]> {
self.phase_component_range(phase)
.map(|range| &self.components[range.clone()])
}
pub fn component_label(&self, index: usize) -> Option<String> {
self.components
.get(index)
.map(|component| component.label())
}
pub fn component_labels(&self) -> Vec<String> {
(0..self.components.len())
.filter_map(|index| self.component_label(index))
.collect()
}
}
pub fn ordered_phase_entries<'a>(
phases: &'a HashMap<Option<String>, SubsData>,
) -> Vec<(PhaseId, &'a SubsData)> {
let mut ordered: Vec<(PhaseId, &'a SubsData)> = phases
.iter()
.map(|(phase, subsdata)| (PhaseId(phase.clone()), subsdata))
.collect();
ordered.sort_by(|left, right| left.0.cmp(&right.0));
ordered
}
pub fn ordered_phase_keys(phases: &HashMap<Option<String>, SubsData>) -> Vec<Option<String>> {
ordered_phase_entries(phases)
.into_iter()
.map(|(phase, _)| phase.0)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phase_id_and_component_id_helpers() {
let phase = PhaseId::new(Some("gas".to_string()));
assert_eq!(phase.as_option(), &Some("gas".to_string()));
let component = PhaseComponentId::new(phase.clone(), "O2");
assert_eq!(component.label(), "gas::O2".to_string());
assert_eq!(component.phase, phase);
assert_eq!(component.substance, "O2".to_string());
}
#[test]
fn test_system_layout_uses_typed_order_and_phase_ranges() {
let mut phases = HashMap::new();
let mut gas = SubsData::new();
gas.substances = vec!["O2".to_string(), "H2".to_string()];
let mut liquid = SubsData::new();
liquid.substances = vec!["H2O".to_string()];
phases.insert(Some("liquid".to_string()), liquid);
phases.insert(Some("gas".to_string()), gas);
let layout = SystemLayout::from_phase_map(&phases);
assert_eq!(
layout.phases(),
&[
PhaseId::new(Some("gas".to_string())),
PhaseId::new(Some("liquid".to_string()))
]
);
assert_eq!(layout.component_count(), 3);
assert_eq!(
layout.component_labels(),
vec![
"gas::O2".to_string(),
"gas::H2".to_string(),
"liquid::H2O".to_string()
]
);
let gas_phase = PhaseId::new(Some("gas".to_string()));
let gas_components = layout.components_for_phase(&gas_phase).unwrap();
assert_eq!(gas_components.len(), 2);
assert_eq!(gas_components[0].label(), "gas::O2".to_string());
assert_eq!(gas_components[1].label(), "gas::H2".to_string());
}
#[test]
fn test_single_phase_layout_is_canonical_and_ordered() {
let layout = SystemLayout::from_single_phase(&vec!["CO2".to_string(), "H2O".to_string()]);
assert_eq!(layout.phases(), &[PhaseId::new(None)]);
assert_eq!(
layout.component_labels(),
vec!["CO2".to_string(), "H2O".to_string()]
);
let phase = PhaseId::new(None);
let range = layout.phase_component_range(&phase).unwrap();
assert_eq!(range.start, 0);
assert_eq!(range.end, 2);
let component = PhaseComponentId::new(phase.clone(), "H2O");
assert_eq!(layout.component_index(&component), Some(1));
assert_eq!(layout.component_index_by_parts(&phase, "CO2"), Some(0));
}
}