ak_core/io/
xyz.rs

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}