laddu_core/reaction/
graph.rs1use std::collections::HashSet;
2
3use serde::{Deserialize, Serialize};
4
5use super::Particle;
6use crate::{LadduError, LadduResult};
7
8#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
10pub struct ParticleGraph {
11 roots: Vec<Particle>,
12}
13
14impl ParticleGraph {
15 pub fn new(roots: impl IntoIterator<Item = Particle>) -> LadduResult<Self> {
17 let roots = roots.into_iter().collect::<Vec<_>>();
18 validate_unique_particles(roots.iter())?;
19 Ok(Self { roots })
20 }
21
22 pub fn roots(&self) -> &[Particle] {
24 &self.roots
25 }
26
27 pub fn particle(&self, particle: &str) -> LadduResult<&Particle> {
29 self.find_particle(particle)
30 .ok_or_else(|| LadduError::Custom(format!("unknown reaction particle '{particle}'")))
31 }
32
33 pub fn contains(&self, particle: &str) -> bool {
35 self.find_particle(particle).is_some()
36 }
37
38 pub fn particles(&self) -> Vec<&Particle> {
40 let mut particles = Vec::new();
41 for root in &self.roots {
42 collect_particles(root, &mut particles);
43 }
44 particles
45 }
46
47 pub fn root_containing(&self, particle: &str) -> Option<&Particle> {
49 self.roots.iter().find(|root| root.contains_id(particle))
50 }
51
52 pub fn parent_of(&self, child: &str) -> Option<&Particle> {
54 self.roots.iter().find_map(|root| root.parent_of_id(child))
55 }
56
57 fn find_particle(&self, particle: &str) -> Option<&Particle> {
58 self.roots
59 .iter()
60 .find_map(|root| find_particle(root, particle))
61 }
62}
63
64fn validate_unique_particles<'a>(roots: impl IntoIterator<Item = &'a Particle>) -> LadduResult<()> {
65 let mut seen = HashSet::new();
66 for root in roots {
67 validate_unique_particle(root, &mut seen)?;
68 }
69 Ok(())
70}
71
72fn validate_unique_particle<'a>(
73 particle: &'a Particle,
74 seen: &mut HashSet<&'a str>,
75) -> LadduResult<()> {
76 if !seen.insert(particle.label()) {
77 return Err(LadduError::Custom(format!(
78 "duplicate reaction particle identifier '{}'",
79 particle.label()
80 )));
81 }
82 for daughter in particle.daughters() {
83 validate_unique_particle(daughter, seen)?;
84 }
85 Ok(())
86}
87
88fn collect_particles<'a>(particle: &'a Particle, particles: &mut Vec<&'a Particle>) {
89 particles.push(particle);
90 for daughter in particle.daughters() {
91 collect_particles(daughter, particles);
92 }
93}
94
95fn find_particle<'a>(particle: &'a Particle, id: &str) -> Option<&'a Particle> {
96 if particle.label() == id {
97 return Some(particle);
98 }
99 particle
100 .daughters()
101 .iter()
102 .find_map(|daughter| find_particle(daughter, id))
103}