gchemol_readwrite/formats/
vasp_input.rs

1// [[file:../../gchemol-readwrite.note::*header][header:1]]
2
3// header:1 ends here
4
5// [[file:../../gchemol-readwrite.note::*imports][imports:1]]
6use super::parser::*;
7use super::*;
8// imports:1 ends here
9
10// [[file:../../gchemol-readwrite.note::*cell][cell:1]]
11fn poscar_cell_vectors(s: &str) -> IResult<&str, [[f64; 3]; 3]> {
12    do_parse!(
13        s,
14        space0 >> va: xyz_array >> eol >> // vector a
15        space0 >> vb: xyz_array >> eol >> // vector b
16        space0 >> vc: xyz_array >> eol >> // vector c
17        ([va, vb, vc])
18    )
19}
20
21#[test]
22fn test_poscar_cell_vectors() {
23    let lines = " 21.23300000  0.00000000  0.00000000
24  0.00000000 26.60400000  0.00000000
25  0.00000000  0.00000000 12.67600000
26";
27
28    let (_, x) = poscar_cell_vectors(lines).expect("POSCAR cell vectors");
29    assert_eq!(21.233, x[0][0]);
30}
31// cell:1 ends here
32
33// [[file:../../gchemol-readwrite.note::*elements][elements:1]]
34fn poscar_ion_types(s: &str) -> IResult<&str, (Vec<&str>, Vec<usize>)> {
35    let mut elements = separated_list0(space1, alpha1);
36    let mut natoms = separated_list0(space1, unsigned_digit);
37    do_parse!(
38        s,
39        space0 >> e: elements       >> eol >> // element list
40        space0 >> n: natoms         >> eol >> // natoms list
41        ((e, n))
42    )
43}
44
45#[test]
46fn test_formats_vasp_poscar_ion_types() {
47    let lines = " O    Si   C    N    H
48 225  112   8    1    19 \n";
49    let (_, v) = poscar_ion_types(lines).expect("POSCAR ion types");
50    assert_eq!(5, v.0.len());
51}
52// elements:1 ends here
53
54// [[file:../../gchemol-readwrite.note::*coordinates][coordinates:1]]
55// Selective dynamics -- optional, can be omitted
56// only the first character is relevant
57fn selective_dynamics(s: &str) -> IResult<&str, &str> {
58    let selective = tag_no_case("S");
59    do_parse!(s, x: selective >> read_line >> (x))
60}
61
62/// Consume three chars in selective dynamics flag (T/F) separated by one or
63/// more spaces Return the frozen flag array
64fn selective_dynamics_flags(s: &str) -> IResult<&str, [bool; 3]> {
65    let tf_flag = one_of("TF");
66    do_parse!(
67        s,
68        x: tf_flag >> space1 >> y: tf_flag >> space1 >> z: tf_flag >> // T T F
69        ([x == 'T', y == 'T', z == 'T'])
70    )
71}
72
73#[test]
74fn test_poscar_select_dynamics() {
75    let (_, x) = selective_dynamics_flags("T T F").unwrap();
76    assert_eq!(x, [true, true, false]);
77}
78
79// Direct/Cartesian -- lattice coordinates type
80// only the first character is relevant
81fn direct_or_catersian(s: &str) -> IResult<&str, &str> {
82    let mut coords_type = alt((tag_no_case("D"), tag_no_case("C")));
83    do_parse!(s, d: coords_type >> read_line >> (d))
84}
85
86// combine two parsers
87fn poscar_select_direct(s: &str) -> IResult<&str, (bool, bool)> {
88    let mut selective_line = opt(selective_dynamics);
89    let direct_line = direct_or_catersian;
90    do_parse!(
91        s,
92        s: selective_line >>    // Selective dynamics
93        d: direct_line    >>    // Direct
94        ({
95            let d = d.to_lowercase();
96            (s.is_some(), d == "d")
97        })
98    )
99}
100
101#[test]
102fn test_poscar_select_direct() {
103    let lines = "Selective dynamics
104Direct\n";
105
106    let (_, (s, d)) = poscar_select_direct(lines).expect("poscar selective/direct");
107    assert_eq!(true, s);
108    assert_eq!(true, d);
109
110    let (_, (s, d)) = poscar_select_direct("Direct\n").expect("poscar direct");
111    assert_eq!(false, s);
112    assert_eq!(true, d);
113    let (_, (s, d)) = poscar_select_direct("Cartesian\n").expect("poscar catersian");
114    assert_eq!(false, s);
115    assert_eq!(false, d);
116}
117
118// 0.05185     0.39121     0.29921  T T T # O
119// 0.81339     0.57337     0.68777  T T T # O
120fn poscar_position(s: &str) -> IResult<&str, ([f64; 3], Option<[bool; 3]>)> {
121    let mut frozen_flags = opt(selective_dynamics_flags);
122    do_parse!(
123        s,
124        space0 >> p: xyz_array >> space0 >> // Coordinates
125        f: frozen_flags >> read_line     >> // T T T
126        ((p, f))
127    )
128}
129
130#[test]
131fn test_poscar_position() {
132    let line = "     0.05185     0.39121     0.29921  T T T # O \n";
133    let (_, (position, sflags)) = poscar_position(line).expect("POSCAR position style 1");
134    assert_eq!(0.05185, position[0]);
135    assert_eq!(0.39121, position[1]);
136    assert_eq!(0.29921, position[2]);
137    assert_eq!(Some([true, true, true]), sflags);
138
139    let line = "     0.05185     0.39121     0.29921\n";
140    let (_, (position, sflags)) = poscar_position(line).expect("POSCAR position style 1");
141    assert_eq!(None, sflags);
142}
143// coordinates:1 ends here
144
145// [[file:../../gchemol-readwrite.note::07fc76f7][07fc76f7]]
146/// Read Molecule from stream in VASP/POSCAR format
147pub(crate) fn parse_poscar_molecule(s: &str) -> IResult<&str, Molecule> {
148    let mut read_ion_positions = many1(poscar_position);
149    do_parse!(
150        s,
151        title            : read_until_eol        >> // system title
152        lattice_constant : read_double           >> // lattice constant
153        cell_vectors     : poscar_cell_vectors   >> // lattice vectors
154        ion_types        : poscar_ion_types      >> // ion types
155        select_direct    : poscar_select_direct  >> // selective line and direct line
156        ion_positions    : read_ion_positions    >> // ion positions
157    (
158        {
159            let selective_dynamics = select_direct.0;
160            let direct_coordinates = select_direct.1;
161
162            let mut mol = Molecule::new(title.trim());
163            let mut lat = Lattice::new(cell_vectors);
164            let x = lat.to_cart([0.0; 3]);
165            lat.scale_by(lattice_constant);
166
167            let mut symbols = vec![];
168            let (syms, nums) = ion_types;
169
170            for (&sym, &num) in syms.iter().zip(nums.iter()) {
171                for _ in 0..num {
172                    symbols.push(sym);
173                }
174            }
175
176            if symbols.len() != ion_positions.len() {
177                eprintln!("WARNING: some ions data not correctly parsed!");
178            }
179
180            for (i, (&sym, (pos, sflags))) in symbols.iter().zip(ion_positions).enumerate() {
181                let pos: Vector3f = if direct_coordinates {
182                    lat.to_cart(pos)
183                } else {
184                    pos.into()
185                };
186                let mut a = Atom::new(sym, pos);
187                // set atom freezing mask according to selective dynamics flags
188                if selective_dynamics {
189                    if let Some([xs, ys, zs]) = sflags {
190                        a.set_freezing([!xs, !ys, !zs]);
191                    }
192                }
193                mol.add_atom(i+1, a);
194            }
195            mol.set_lattice(lat);
196            mol
197        }
198    )
199    )
200}
201
202#[test]
203fn test_poscar_molecule() {
204    let lines = "title
2051.0
206 21.23300000  0.00000000  0.00000000
207  0.00000000 26.60400000  0.00000000
208  0.00000000  0.00000000 12.67600000
209 O    Si   C    N    H
210225  112   8    1    19
211Selective dynamics
212Direct
213     0.05185     0.39121     0.29921  T T T # O
214     0.81339     0.57337     0.68777  T T T # O
215     0.73422     0.23229     0.85313  T T T # O
216     0.02246     0.05156     0.49349  T T T # O
217     0.64451     0.66726     0.17130  T T T # O
218     0.05185     0.07337     0.29921  T T T # O
219     0.60095     0.57471     0.17096  T T T # O
220     0.64451     0.66726     0.81569  T T T # O
221     0.33416     0.64745     0.88951  T T T # O
222     0.33416     0.31713     0.09747  T T T # O
223     0.93262     0.92263     0.99349  T T T # O
224     0.43262     0.79195     0.99349  T T T # O
225     0.73422     0.73229     0.13386  T T T # O
226     0.22073     0.66726     0.81569  T T T # O
227\n";
228
229    // let (_, mol) = parse_poscar_molecule(lines).expect("poscar molecule");
230    // assert_eq!(14, mol.natoms());
231    // assert!(mol.lattice.is_some());
232
233    let txt = "Generated by cif2cell 1.1.5 from COD reference: 9008833. BAs :  Wyckoff, R. W. G., Crystal Structures 1, 85-237 (1963).
234   4.777000
235  1.000000000000000   0.000000000000000   0.000000000000000
236  0.000000000000000   1.000000000000000   0.000000000000000
237  0.000000000000000   0.000000000000000   1.000000000000000
238  As   B
239   4   4
240Direct
241  0.250000000000000   0.250000000000000   0.250000000000000
242  0.750000000000000   0.250000000000000   0.750000000000000
243  0.750000000000000   0.750000000000000   0.250000000000000
244  0.250000000000000   0.750000000000000   0.750000000000000
245  0.000000000000000   0.000000000000000   0.000000000000000
246  0.000000000000000   0.500000000000000   0.500000000000000
247  0.500000000000000   0.000000000000000   0.500000000000000
248  0.500000000000000   0.500000000000000   0.000000000000000
249";
250    let (_, mol) = parse_poscar_molecule(txt).expect("poscar molecule");
251    let lat = mol.get_lattice();
252    assert!(lat.is_some());
253    let [a, b, c] = lat.unwrap().lengths();
254    assert_eq!(a, 4.777);
255    assert_eq!(b, 4.777);
256    assert_eq!(c, 4.777);
257}
258// 07fc76f7 ends here
259
260// [[file:../../gchemol-readwrite.note::*format molecule][format molecule:1]]
261fn format_molecule(mol: &Molecule) -> String {
262    let mut lines = String::new();
263    let title = mol.title();
264
265    lines.push_str(&format!("{}\n", title));
266    lines.push_str("1.0\n");
267    let mut lattice = mol.lattice.expect("poscar lattice");
268    let va = lattice.vector_a();
269    let vb = lattice.vector_b();
270    let vc = lattice.vector_c();
271
272    for v in [va, vb, vc].iter() {
273        let line = format!("{:12.8}{:12.8}{:12.8}\n", v[0], v[1], v[2]);
274        lines.push_str(&line);
275    }
276
277    // atom symbols and counts
278    let mut line1 = String::new();
279    let mut line2 = String::new();
280    for (s, n) in count_symbols(mol.symbols().collect()) {
281        line1.push_str(&format!(" {:^4}", s));
282        line2.push_str(&format!(" {:^4}", n));
283    }
284    lines.push_str(&format!("{}\n", line1));
285    lines.push_str(&format!("{}\n", line2));
286
287    // write fractional coordinates for improving accuracy
288    lines.push_str("Selective dynamics\nDirect\n");
289    for (_, a) in mol.atoms() {
290        let p = lattice.to_frac(a.position());
291        let freezing = a.freezing();
292        let line = format!(
293            "{x:18.12} {y:18.12} {z:18.12} {fx} {fy} {fz}\n",
294            x = p.x,
295            y = p.y,
296            z = p.z,
297            fx = if freezing[0] { "F" } else { "T" },
298            fy = if freezing[1] { "F" } else { "T" },
299            fz = if freezing[2] { "F" } else { "T" },
300        );
301        lines.push_str(&line);
302    }
303
304    // write velocity data when they are not all zeros
305    let write_velocity = mol.atoms().flat_map(|(_, a)| a.velocity()).any(|x| x != 0.0);
306    if write_velocity {
307        lines.push_str("\n");
308        for (_, a) in mol.atoms() {
309            let [vx, vy, vz] = a.velocity();
310            let line = format!("{:18.12} {:18.12} {:18.12}\n", vx, vy, vz);
311            lines.push_str(&line);
312        }
313    }
314    // final blank line
315    lines.push_str("\n");
316
317    lines
318}
319
320// Panic if symbols is empty
321fn count_symbols(symbols: Vec<&str>) -> Vec<(&str, usize)> {
322    let mut lines = String::new();
323
324    let mut syms1 = symbols.iter();
325    let mut syms2 = symbols.iter().skip(1);
326    let mut counts = vec![];
327
328    let mut c = 1;
329    let mut s = symbols[0];
330    for (&sym1, &sym2) in syms1.zip(syms2) {
331        if sym2 == sym1 {
332            c += 1;
333        } else {
334            counts.push((sym1, c));
335            c = 1;
336        }
337        s = sym2;
338    }
339    // append the last piece
340    counts.push((s, c));
341
342    counts
343}
344
345#[test]
346fn test_poscar_symbols_counts() {
347    let symbols = ["C", "C", "C", "H", "O", "O", "C"];
348    let x = count_symbols(symbols.to_vec());
349    assert_eq!([("C", 3), ("H", 1), ("O", 2), ("C", 1)].to_vec(), x);
350
351    let symbols = ["C", "C"];
352    let x = count_symbols(symbols.to_vec());
353    assert_eq!([("C", 2)].to_vec(), x);
354
355    let symbols = ["C", "H"];
356    let x = count_symbols(symbols.to_vec());
357    assert_eq!([("C", 1), ("H", 1)].to_vec(), x);
358
359    let symbols = ["C"];
360    let x = count_symbols(symbols.to_vec());
361    assert_eq!([("C", 1)].to_vec(), x);
362}
363// format molecule:1 ends here
364
365// [[file:../../gchemol-readwrite.note::49ffb285][49ffb285]]
366#[derive(Clone, Copy, Debug)]
367pub struct PoscarFile();
368
369impl ChemicalFile for PoscarFile {
370    fn ftype(&self) -> &str {
371        "vasp/input"
372    }
373
374    fn possible_extensions(&self) -> Vec<&str> {
375        vec!["poscar", "vasp"]
376    }
377
378    /// Determine if file `filename` is parable according to its supported file
379    /// extensions
380    fn parsable(&self, path: &Path) -> bool {
381        let possible_filenames = vec!["CONTCAR", "POSCAR"];
382        if let Some(e) = path.extension() {
383            let e = e.to_string_lossy().to_lowercase();
384            self.possible_extensions().contains(&e.as_str())
385        } else
386        // no extension: check file name
387        {
388            if let Some(filename) = path.file_name() {
389                let f = filename.to_string_lossy().to_uppercase();
390                for x in possible_filenames {
391                    if f.starts_with(x) {
392                        return true;
393                    }
394                }
395            }
396            false
397        }
398    }
399
400    fn format_molecule(&self, mol: &Molecule) -> Result<String> {
401        Ok(format_molecule(mol))
402    }
403}
404
405impl ParseMolecule for PoscarFile {
406    fn parse_molecule(&self, input: &str) -> Result<Molecule> {
407        let (_, mol) = parse_poscar_molecule(input).map_err(|e| format_err!("parse POSCAR format failure: {:?}", e))?;
408        Ok(mol)
409    }
410}
411
412#[test]
413fn test_vasp_input_parsable() {
414    let cf = PoscarFile();
415    let parsable = |x: &str| cf.parsable(x.as_ref());
416
417    assert!(parsable("POSCAR"));
418    assert!(parsable("POSCAR1"));
419    assert!(parsable("POSCAR-1"));
420    assert!(!parsable("POSCAR.1"));
421    assert!(parsable("CONTCAR"));
422    assert!(parsable("CONTCAR1"));
423    assert!(parsable("POSCAR2"));
424    assert!(parsable("poscar2"));
425    assert!(parsable("x.poscar"));
426    assert!(parsable("x.vasp"));
427}
428// 49ffb285 ends here
429
430// [[file:../../gchemol-readwrite.note::*impl partition][impl partition:1]]
431crate::cf_impl_partitions!(PoscarFile);
432// impl partition:1 ends here