use std::io::{BufRead, Error, ErrorKind, Result};
use molrs::types::F;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoordMode {
Direct,
Cartesian,
}
#[derive(Debug, Clone)]
pub struct VaspHeader {
pub title: String,
pub scale: F,
pub cell: [[F; 3]; 3],
pub symbols: Vec<String>,
pub counts: Vec<usize>,
pub selective_dynamics: bool,
pub mode: CoordMode,
}
impl VaspHeader {
pub fn total_atoms(&self) -> usize {
self.counts.iter().sum()
}
}
fn parse_err(line_no: usize, msg: impl AsRef<str>) -> Error {
Error::new(
ErrorKind::InvalidData,
format!("line {}: {}", line_no, msg.as_ref()),
)
}
fn read_line_into<R: BufRead>(reader: &mut R, buf: &mut String) -> Result<bool> {
buf.clear();
let n = reader.read_line(buf)?;
Ok(n > 0)
}
pub fn parse_floats(line: &str, expected: usize, line_no: usize) -> Result<Vec<F>> {
let vals: Vec<F> = line
.split_whitespace()
.take(expected)
.map(|s| {
s.parse::<F>()
.map_err(|_| parse_err(line_no, format!("expected float, got '{}'", s)))
})
.collect::<Result<_>>()?;
if vals.len() < expected {
return Err(parse_err(
line_no,
format!("expected {} floats, got {}", expected, vals.len()),
));
}
Ok(vals)
}
pub fn parse_usize_vec(line: &str, line_no: usize) -> Result<Vec<usize>> {
line.split_whitespace()
.map(|s| {
s.parse::<usize>()
.map_err(|_| parse_err(line_no, format!("expected integer, got '{}'", s)))
})
.collect()
}
pub fn fractional_to_cartesian(
sx: &[F],
sy: &[F],
sz: &[F],
cell: &[[F; 3]; 3],
) -> (Vec<F>, Vec<F>, Vec<F>) {
let n = sx.len();
let mut cx = Vec::with_capacity(n);
let mut cy = Vec::with_capacity(n);
let mut cz = Vec::with_capacity(n);
for i in 0..n {
cx.push(sx[i] * cell[0][0] + sy[i] * cell[1][0] + sz[i] * cell[2][0]);
cy.push(sx[i] * cell[0][1] + sy[i] * cell[1][1] + sz[i] * cell[2][1]);
cz.push(sx[i] * cell[0][2] + sy[i] * cell[1][2] + sz[i] * cell[2][2]);
}
(cx, cy, cz)
}
pub fn read_header<R: BufRead>(reader: &mut R, line_no: &mut usize) -> Result<VaspHeader> {
let mut buf = String::new();
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF before title"));
}
*line_no += 1;
let title = buf.trim().to_string();
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF before scale"));
}
*line_no += 1;
let scale: F = buf
.split_whitespace()
.next()
.ok_or_else(|| parse_err(*line_no, "missing scale factor"))?
.parse()
.map_err(|_| parse_err(*line_no, "expected scale factor"))?;
let mut cell = [[0.0 as F; 3]; 3];
for row in &mut cell {
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF in lattice vectors"));
}
*line_no += 1;
let vals = parse_floats(&buf, 3, *line_no)?;
row[0] = vals[0] * scale;
row[1] = vals[1] * scale;
row[2] = vals[2] * scale;
}
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF before symbols"));
}
*line_no += 1;
let tokens: Vec<&str> = buf.split_whitespace().collect();
if tokens.is_empty() {
return Err(parse_err(*line_no, "empty symbols/counts line"));
}
let has_symbols = tokens[0].parse::<u32>().is_err();
let (symbols, counts) = if has_symbols {
let symbols: Vec<String> = tokens.iter().map(|s| s.to_string()).collect();
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF before counts"));
}
*line_no += 1;
let counts = parse_usize_vec(&buf, *line_no)?;
if counts.len() != symbols.len() {
return Err(parse_err(
*line_no,
format!(
"element count mismatch: {} symbols but {} counts",
symbols.len(),
counts.len()
),
));
}
(symbols, counts)
} else {
let counts = tokens
.iter()
.map(|s| {
s.parse::<usize>()
.map_err(|_| parse_err(*line_no, format!("expected count, got '{}'", s)))
})
.collect::<Result<Vec<_>>>()?;
(Vec::new(), counts)
};
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF before mode"));
}
*line_no += 1;
let mut selective_dynamics = false;
let mut mode_line = buf.trim().to_string();
if mode_line
.chars()
.next()
.map(|c| c.eq_ignore_ascii_case(&'s'))
.unwrap_or(false)
{
selective_dynamics = true;
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(*line_no + 1, "unexpected EOF after selective"));
}
*line_no += 1;
mode_line = buf.trim().to_string();
}
let mode = match mode_line.chars().next() {
Some(c) if c.eq_ignore_ascii_case(&'d') => CoordMode::Direct,
Some(c) if c.eq_ignore_ascii_case(&'c') || c.eq_ignore_ascii_case(&'k') => {
CoordMode::Cartesian
}
_ => {
return Err(parse_err(
*line_no,
format!(
"unrecognised coordinate mode {:?}; expected Direct or Cartesian",
mode_line
),
));
}
};
Ok(VaspHeader {
title,
scale,
cell,
symbols,
counts,
selective_dynamics,
mode,
})
}
#[derive(Debug, Clone)]
pub struct AtomRow {
pub x: F,
pub y: F,
pub z: F,
pub flags: Option<[bool; 3]>,
}
pub fn parse_atom_row(line: &str, line_no: usize, expect_flags: bool) -> Result<AtomRow> {
let tokens: Vec<&str> = line.split_whitespace().collect();
if tokens.len() < 3 {
return Err(parse_err(
line_no,
format!("expected 3 floats, got {} tokens", tokens.len()),
));
}
let x: F = tokens[0]
.parse()
.map_err(|_| parse_err(line_no, format!("bad x: '{}'", tokens[0])))?;
let y: F = tokens[1]
.parse()
.map_err(|_| parse_err(line_no, format!("bad y: '{}'", tokens[1])))?;
let z: F = tokens[2]
.parse()
.map_err(|_| parse_err(line_no, format!("bad z: '{}'", tokens[2])))?;
let flags = if expect_flags && tokens.len() >= 6 {
let parse_flag = |t: &str| -> Result<bool> {
match t {
"T" | "t" => Ok(true),
"F" | "f" => Ok(false),
other => Err(parse_err(
line_no,
format!("expected selective-dynamics T/F, got '{}'", other),
)),
}
};
Some([
parse_flag(tokens[3])?,
parse_flag(tokens[4])?,
parse_flag(tokens[5])?,
])
} else {
None
};
Ok(AtomRow { x, y, z, flags })
}
pub type CoordBlock = (Vec<F>, Vec<F>, Vec<F>, Option<Vec<[bool; 3]>>);
pub fn read_coords<R: BufRead>(
reader: &mut R,
n: usize,
line_no: &mut usize,
expect_flags: bool,
) -> Result<CoordBlock> {
let mut xs = Vec::with_capacity(n);
let mut ys = Vec::with_capacity(n);
let mut zs = Vec::with_capacity(n);
let mut flags = if expect_flags {
Some(Vec::with_capacity(n))
} else {
None
};
let mut buf = String::new();
for i in 0..n {
if !read_line_into(reader, &mut buf)? {
return Err(parse_err(
*line_no + 1,
format!("unexpected EOF at atom {}/{}", i, n),
));
}
*line_no += 1;
let row = parse_atom_row(&buf, *line_no, expect_flags)?;
xs.push(row.x);
ys.push(row.y);
zs.push(row.z);
if let (Some(out), Some(f)) = (flags.as_mut(), row.flags) {
out.push(f);
}
}
Ok((xs, ys, zs, flags))
}
pub fn expand_symbols(symbols: &[String], counts: &[usize]) -> Vec<String> {
let total: usize = counts.iter().sum();
let mut out = Vec::with_capacity(total);
for (sym, &cnt) in symbols.iter().zip(counts.iter()) {
for _ in 0..cnt {
out.push(sym.clone());
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
const POSCAR_BN: &str = "BN bulk\n\
1.0\n\
2.5 0.0 0.0\n\
0.0 2.5 0.0\n\
0.0 0.0 2.5\n\
B N\n\
1 1\n\
Direct\n\
0.0 0.0 0.0\n\
0.5 0.5 0.5\n";
#[test]
fn parses_basic_poscar_header() {
let mut reader = Cursor::new(POSCAR_BN.as_bytes());
let mut line_no = 0usize;
let h = read_header(&mut reader, &mut line_no).expect("header parses");
assert_eq!(h.title, "BN bulk");
assert_eq!(h.scale, 1.0);
assert_eq!(h.symbols, vec!["B".to_string(), "N".to_string()]);
assert_eq!(h.counts, vec![1, 1]);
assert!(!h.selective_dynamics);
assert_eq!(h.mode, CoordMode::Direct);
assert_eq!(h.total_atoms(), 2);
}
#[test]
fn fractional_to_cartesian_roundtrip() {
let cell = [[2.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 2.0]];
let (cx, cy, cz) = fractional_to_cartesian(&[0.5], &[0.5], &[0.5], &cell);
assert_eq!((cx[0], cy[0], cz[0]), (1.0, 1.0, 1.0));
}
#[test]
fn parses_atom_row_with_flags() {
let row = parse_atom_row("0.0 0.5 0.25 T F T", 1, true).unwrap();
assert_eq!(row.flags, Some([true, false, true]));
}
}