1use crate::molecule::{Molecule, MoleculeParseError};
2use std::{error::Error, fmt, str::FromStr};
3
4#[derive(Debug, Clone)]
6pub enum XyzParseError<'a> {
7 InvalidMolecule(usize, MoleculeParseError<'a>),
8}
9
10impl<'a> XyzParseError<'a> {
11 pub fn into_owned(self) -> XyzParseError<'static> {
12 match self {
13 Self::InvalidMolecule(num, err) => {
14 XyzParseError::InvalidMolecule(num, err.into_owned())
15 }
16 }
17 }
18}
19
20impl<'a> fmt::Display for XyzParseError<'a> {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 match self {
23 Self::InvalidMolecule(num, err) => write!(f, "Invalid molecule {num}: {err}"),
24 }
25 }
26}
27
28impl Error for XyzParseError<'static> {
29 fn source(&self) -> Option<&(dyn Error + 'static)> {
30 match self {
31 Self::InvalidMolecule(_, err) => Some(err),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Default, PartialEq, Eq)]
38pub struct Xyz<'a> {
39 pub molecules: Vec<Molecule<'a>>,
40}
41
42impl<'a> Xyz<'a> {
43 pub fn parse(string: &'a str) -> Result<Self, XyzParseError> {
44 let mut lines = string.lines().peekable();
45 let mut molecules = Vec::new();
46 while lines.peek().is_some() {
47 molecules.push(
48 Molecule::parse_lines(&mut lines)
49 .map_err(|err| XyzParseError::InvalidMolecule(molecules.len(), err))?,
50 );
51 }
52 Ok(Xyz { molecules })
53 }
54
55 pub fn into_owned(self) -> Xyz<'static> {
56 Xyz {
57 molecules: self
58 .molecules
59 .into_iter()
60 .map(|molecule| molecule.into_owned())
61 .collect(),
62 }
63 }
64}
65
66impl<'a> fmt::Display for Xyz<'a> {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 let mut iter = self.molecules.iter().peekable();
69 while let Some(molecule) = iter.next() {
70 write!(f, "{molecule}")?;
71 if iter.peek().is_some() {
72 writeln!(f)?;
73 }
74 }
75 Ok(())
76 }
77}
78
79impl FromStr for Xyz<'static> {
80 type Err = XyzParseError<'static>;
81 fn from_str(s: &str) -> Result<Self, Self::Err> {
82 Xyz::parse(s)
83 .map(|res| res.into_owned())
84 .map_err(|err| err.into_owned())
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::{atom::Atom, molecule::Molecule};
92 use rust_decimal::Decimal;
93 use std::borrow::Cow;
94
95 const PYRIDINE: &str = r#"11
96
97C -0.180226841 0.360945118 -1.120304970
98C -0.180226841 1.559292118 -0.407860970
99C -0.180226841 1.503191118 0.986935030
100N -0.180226841 0.360945118 1.29018350
101C -0.180226841 -0.781300882 0.986935030
102C -0.180226841 -0.837401882 -0.407860970
103H -0.180226841 0.360945118 -2.206546970
104H -0.180226841 2.517950118 -0.917077970
105H -0.180226841 2.421289118 1.572099030
106H -0.180226841 -1.699398882 1.572099030
107H -0.180226841 -1.796059882 -0.917077970"#;
108
109 fn pyridine() -> Molecule<'static> {
110 Molecule {
111 comment: Cow::Borrowed(""),
112 atoms: vec![
113 Atom {
114 symbol: Cow::Borrowed("C"),
115 x: Decimal::from_str_exact("-0.180226841").unwrap(),
116 y: Decimal::from_str_exact("0.360945118").unwrap(),
117 z: Decimal::from_str_exact("-1.120304970").unwrap(),
118 },
119 Atom {
120 symbol: Cow::Borrowed("C"),
121 x: Decimal::from_str_exact("-0.180226841").unwrap(),
122 y: Decimal::from_str_exact("1.559292118").unwrap(),
123 z: Decimal::from_str_exact("-0.407860970").unwrap(),
124 },
125 Atom {
126 symbol: Cow::Borrowed("C"),
127 x: Decimal::from_str_exact("-0.180226841").unwrap(),
128 y: Decimal::from_str_exact("1.503191118").unwrap(),
129 z: Decimal::from_str_exact("0.986935030").unwrap(),
130 },
131 Atom {
132 symbol: Cow::Borrowed("N"),
133 x: Decimal::from_str_exact("-0.180226841").unwrap(),
134 y: Decimal::from_str_exact("0.360945118").unwrap(),
135 z: Decimal::from_str_exact("1.29018350").unwrap(),
136 },
137 Atom {
138 symbol: Cow::Borrowed("C"),
139 x: Decimal::from_str_exact("-0.180226841").unwrap(),
140 y: Decimal::from_str_exact("-0.781300882").unwrap(),
141 z: Decimal::from_str_exact("0.986935030").unwrap(),
142 },
143 Atom {
144 symbol: Cow::Borrowed("C"),
145 x: Decimal::from_str_exact("-0.180226841").unwrap(),
146 y: Decimal::from_str_exact("-0.837401882").unwrap(),
147 z: Decimal::from_str_exact("-0.407860970").unwrap(),
148 },
149 Atom {
150 symbol: Cow::Borrowed("H"),
151 x: Decimal::from_str_exact("-0.180226841").unwrap(),
152 y: Decimal::from_str_exact("0.360945118").unwrap(),
153 z: Decimal::from_str_exact("-2.206546970").unwrap(),
154 },
155 Atom {
156 symbol: Cow::Borrowed("H"),
157 x: Decimal::from_str_exact("-0.180226841").unwrap(),
158 y: Decimal::from_str_exact("2.517950118").unwrap(),
159 z: Decimal::from_str_exact("-0.917077970").unwrap(),
160 },
161 Atom {
162 symbol: Cow::Borrowed("H"),
163 x: Decimal::from_str_exact("-0.180226841").unwrap(),
164 y: Decimal::from_str_exact("2.421289118").unwrap(),
165 z: Decimal::from_str_exact("1.572099030").unwrap(),
166 },
167 Atom {
168 symbol: Cow::Borrowed("H"),
169 x: Decimal::from_str_exact("-0.180226841").unwrap(),
170 y: Decimal::from_str_exact("-1.699398882").unwrap(),
171 z: Decimal::from_str_exact("1.572099030").unwrap(),
172 },
173 Atom {
174 symbol: Cow::Borrowed("H"),
175 x: Decimal::from_str_exact("-0.180226841").unwrap(),
176 y: Decimal::from_str_exact("-1.796059882").unwrap(),
177 z: Decimal::from_str_exact("-0.917077970").unwrap(),
178 },
179 ],
180 }
181 }
182
183 const H2: &str = r#"2
184H2
185H 0.3710 0.0 0.0
186H -0.3710 0.0 0.0"#;
187
188 fn h2() -> Molecule<'static> {
189 Molecule {
190 comment: Cow::Borrowed("H2"),
191 atoms: vec![
192 Atom {
193 symbol: Cow::Borrowed("H"),
194 x: Decimal::from_str_exact("0.3710").unwrap(),
195 y: Decimal::from_str_exact("0.0").unwrap(),
196 z: Decimal::from_str_exact("0.0").unwrap(),
197 },
198 Atom {
199 symbol: Cow::Borrowed("H"),
200 x: Decimal::from_str_exact("-0.3710").unwrap(),
201 y: Decimal::from_str_exact("0.0").unwrap(),
202 z: Decimal::from_str_exact("0.0").unwrap(),
203 },
204 ],
205 }
206 }
207
208 #[test]
209 fn parse_h2() {
210 assert_eq!(Molecule::parse(H2).unwrap(), h2())
211 }
212
213 #[test]
214 fn parse_pyridine() {
215 assert_eq!(Molecule::parse(PYRIDINE).unwrap(), pyridine())
216 }
217
218 #[test]
219 fn parse_together() {
220 let together = format!("{H2}\n{PYRIDINE}");
221 assert_eq!(
222 Xyz::parse(&together).unwrap(),
223 Xyz {
224 molecules: vec![h2(), pyridine()]
225 }
226 );
227 }
228
229 #[test]
230 fn print_h2() {
231 assert_eq!(h2().to_string(), H2);
232 }
233}