1use crate::{PERIODIC_TABLE, Structure};
2
3#[derive(Debug)]
4enum PositionParseError {
5 ParseF64Error,
6 MissingCoordinate,
7 SymbolError,
8}
9#[derive(Debug)]
10
11enum CellError {
12 NoCellSpecified,
13 Expected9Floats,
14 ParseError,
15}
16
17#[derive(Debug)]
18enum PBCError {
19 NoPBCSpecified,
20 Expected3Flags,
21}
22
23fn read_positions_and_numbers(
24 lines: &[String],
25 number_of_atoms: usize,
26) -> Result<(Vec<[f64; 3]>, Vec<i32>), PositionParseError> {
27 let mut positions: Vec<[f64; 3]> = Vec::with_capacity(number_of_atoms);
28 let mut numbers: Vec<i32> = Vec::with_capacity(number_of_atoms);
29 for line in lines.iter().take(number_of_atoms) {
30 let mut it = line.split_whitespace();
31 let symbol = it.next().ok_or(PositionParseError::SymbolError)?;
32 let x: f64 = it
33 .next()
34 .ok_or(PositionParseError::MissingCoordinate)?
35 .parse()
36 .map_err(|_| PositionParseError::ParseF64Error)?;
37 let y: f64 = it
38 .next()
39 .ok_or(PositionParseError::MissingCoordinate)?
40 .parse()
41 .map_err(|_| PositionParseError::ParseF64Error)?;
42 let z: f64 = it
43 .next()
44 .ok_or(PositionParseError::MissingCoordinate)?
45 .parse()
46 .map_err(|_| PositionParseError::ParseF64Error)?;
47 let position = [x, y, z];
48 positions.push(position);
49
50 let number = PERIODIC_TABLE.get_by_symbol(symbol).number.get();
51 numbers.push(number as i32);
52 }
53
54 Ok((positions, numbers))
55}
56
57fn read_cell(comment_line: &str) -> Result<[[f64; 3]; 3], CellError> {
58 let lattice_start: usize = comment_line
59 .match_indices("Lattice=")
60 .next()
61 .map(|(idx, _)| idx)
62 .ok_or(CellError::NoCellSpecified)?;
63
64 let tail = &comment_line[lattice_start..];
65 let quotes: Vec<usize> = tail.match_indices('"').map(|(i, _)| i).collect();
66 let part = &tail[(quotes[0] + 1)..quotes[1]];
67 let parts: Vec<f64> = part
68 .split_whitespace()
69 .map(|c| c.parse::<f64>().map_err(|_| CellError::ParseError))
70 .collect::<Result<Vec<f64>, CellError>>()?;
71 let arr: [f64; 9] = parts.try_into().map_err(|_| CellError::Expected9Floats)?;
72
73 let cell: [[f64; 3]; 3] = [
74 [arr[0], arr[1], arr[2]],
75 [arr[3], arr[4], arr[5]],
76 [arr[6], arr[7], arr[8]],
77 ];
78 Ok(cell)
79}
80
81fn read_pbc(comment_line: &str) -> Result<[bool; 3], PBCError> {
82 let pbc_start: usize = comment_line
83 .match_indices("pbc=")
84 .next()
85 .map(|(idx, _)| idx)
86 .ok_or(PBCError::NoPBCSpecified)?;
87
88 let tail = &comment_line[pbc_start..];
89 let quotes: Vec<usize> = tail.match_indices('"').map(|(i, _)| i).collect();
90 let pbc_slice = &tail[(quotes[0] + 1)..quotes[1]];
91 let pbc_vec: Vec<bool> = pbc_slice
92 .split_whitespace()
93 .map(|flag| !matches!(flag, "f" | "F" | "false" | "FALSE" | "False"))
94 .collect();
95 let pbc: [bool; 3] = pbc_vec.try_into().map_err(|_| PBCError::Expected3Flags)?;
96 Ok(pbc)
97}
98
99pub fn read_xyz<R: std::io::BufRead>(r: R) -> Structure {
100 let mut lines = Vec::new();
101 for line_result in r.lines() {
102 let line = line_result.expect("Read failure");
103 lines.push(line);
104 }
105
106 let number_of_atoms: usize = lines[0]
107 .parse()
108 .expect("Failed to convert atom number to int.");
109
110 let (positions, numbers) =
111 read_positions_and_numbers(&lines[2..], number_of_atoms).expect("Error");
112
113 let cell =
114 { read_cell(&lines[1]).unwrap_or([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]) };
115 let pbc = { read_pbc(&lines[1]).unwrap_or_default() };
116
117 Structure::new(positions, numbers, cell, pbc)
118}
119
120#[cfg(test)]
121mod tests {
122 use std::io::{BufReader, Cursor};
123
124 use crate::io::read_xyz;
125
126 #[test]
127 fn simple_test() {
128 let xyz = r#"3
129Lattice="10.0 0.0 0.0 0.0 10.0 0.0 0.0 0.0 10.0" Properties=species:S:1:pos:R:3 pbc="F F F"
130O 5.00000000 5.00000000 5.29815450
131H 5.00000000 5.76323900 4.70184550
132H 5.00000000 4.23676100 4.70184550
133"#;
134 let reader = BufReader::new(Cursor::new(xyz));
135 let _s = read_xyz(reader);
136 }
137}