Skip to main content

use_molecule/
molecule.rs

1use std::fmt;
2
3use use_chemical_formula::ChemicalFormula;
4
5use crate::{
6    AtomConnection, AtomCount, MolecularAtom, MolecularFormula, MoleculeBuilder, MoleculeCharge,
7    MoleculeKind, MoleculeName, MoleculeValidationError,
8};
9
10/// A named molecular entity with formula and optional structural primitives.
11#[derive(Clone, Debug, Eq, PartialEq)]
12pub struct Molecule {
13    name: MoleculeName,
14    formula: MolecularFormula,
15    atoms: Vec<MolecularAtom>,
16    connections: Vec<AtomConnection>,
17    charge: MoleculeCharge,
18    kinds: Vec<MoleculeKind>,
19}
20
21impl Molecule {
22    /// Creates a molecule from a name and formula.
23    ///
24    /// # Errors
25    ///
26    /// Returns [`MoleculeValidationError::EmptyName`] when `name` is empty after trimming.
27    pub fn new(name: &str, formula: ChemicalFormula) -> Result<Self, MoleculeValidationError> {
28        Ok(Self {
29            name: MoleculeName::new(name)?,
30            formula: MolecularFormula::new(formula),
31            atoms: Vec::new(),
32            connections: Vec::new(),
33            charge: MoleculeCharge::NEUTRAL,
34            kinds: Vec::new(),
35        })
36    }
37
38    /// Starts a molecule builder.
39    #[must_use]
40    pub fn builder(name: &str) -> MoleculeBuilder {
41        MoleculeBuilder::new(name)
42    }
43
44    /// Returns the molecule name.
45    #[must_use]
46    pub const fn name(&self) -> &MoleculeName {
47        &self.name
48    }
49
50    /// Returns the molecule formula.
51    #[must_use]
52    pub fn formula(&self) -> &ChemicalFormula {
53        self.formula.as_formula()
54    }
55
56    /// Returns the molecule formula wrapper.
57    #[must_use]
58    pub const fn molecular_formula(&self) -> &MolecularFormula {
59        &self.formula
60    }
61
62    /// Returns explicit atoms in insertion order.
63    #[must_use]
64    pub fn atoms(&self) -> &[MolecularAtom] {
65        &self.atoms
66    }
67
68    /// Returns the number of explicit atoms.
69    #[must_use]
70    pub fn atom_count(&self) -> usize {
71        self.atoms.len()
72    }
73
74    /// Returns the explicit atom count wrapper.
75    #[must_use]
76    pub fn atom_count_value(&self) -> AtomCount {
77        AtomCount::new(self.atom_count())
78    }
79
80    /// Returns explicit atom connections in insertion order.
81    #[must_use]
82    pub fn connections(&self) -> &[AtomConnection] {
83        &self.connections
84    }
85
86    /// Returns the formal molecule charge.
87    #[must_use]
88    pub const fn charge(&self) -> MoleculeCharge {
89        self.charge
90    }
91
92    /// Returns molecule kind labels in insertion order.
93    #[must_use]
94    pub fn kinds(&self) -> &[MoleculeKind] {
95        &self.kinds
96    }
97
98    /// Adds a kind label if it is not already present.
99    #[must_use]
100    pub fn with_kind(mut self, kind: MoleculeKind) -> Self {
101        if !self.kinds.contains(&kind) {
102            self.kinds.push(kind);
103        }
104        self
105    }
106
107    /// Sets the formal molecule charge.
108    #[must_use]
109    pub const fn with_charge(mut self, charge: MoleculeCharge) -> Self {
110        self.charge = charge;
111        self
112    }
113
114    /// Adds an explicit atom.
115    #[must_use]
116    pub fn with_atom(mut self, atom: MolecularAtom) -> Self {
117        self.atoms.push(atom);
118        self
119    }
120
121    /// Adds an atom connection after validating its indices against the explicit atom list.
122    ///
123    /// # Errors
124    ///
125    /// Returns [`MoleculeValidationError::InvalidConnectionIndex`] when the connection references an
126    /// atom index outside the explicit atom list.
127    pub fn try_with_connection(
128        mut self,
129        connection: AtomConnection,
130    ) -> Result<Self, MoleculeValidationError> {
131        let connection = connection.validate_indices(self.atoms.len())?;
132        if !self.connections.contains(&connection) {
133            self.connections.push(connection);
134        }
135        Ok(self)
136    }
137}
138
139impl fmt::Display for Molecule {
140    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
141        write!(formatter, "{} ({})", self.name, self.formula)
142    }
143}