use std::borrow::BorrowMut;
use std::fs::File;
use std::path::Path;
use std::io::{self, BufRead, Write};
use log::{info, warn};
use crate::atoms::AtomicNumber;
use crate::coordinates::Point;
use crate::molecule::Molecule;
#[derive(Default)]
pub struct XYZFile{
pub filename: String,
pub coordinates: Vec<Point>,
pub atomic_numbers: Vec<AtomicNumber>
}
impl XYZFile {
pub fn read(filename: &str) -> Result<Self, &'static str>{
if !filename.ends_with(".xyz") {
panic!("Cannot read {}. File must end with .xyz", filename)
}
let mut xyz_file: XYZFile = Default::default();
for (i, line) in read_lines(filename).enumerate() {
if let Ok(line) = line {
if i <= 1 || line.is_empty() { continue; }
match xyz_file.append_atom_on_line(line){
Ok(..) => info!("Added line"),
Err(..) => warn!("Failed to add line")
}
}
}
if xyz_file.coordinates.len() == 0{
return Err("Failed to find any coordinates")
}
Ok(xyz_file)
}
pub fn write(filename: &str, molecule: &Molecule){
let mut file = match File::create(&Path::new(filename)) {
Err(why) => panic!("Couldn't create {}", why),
Ok(file) => file,
};
Self::write_line(format!("{}", molecule.num_atoms()), file.borrow_mut());
Self::write_line(String::from(""), file.borrow_mut());
for atom in molecule.atoms().iter(){
Self::write_line(format!("{an:<3}{x:11.6}{y:11.6}{z:11.6}",
an=atom.atomic_number.to_atomic_symbol(),
x=atom.coordinate.x,
y=atom.coordinate.y,
z=atom.coordinate.z),
file.borrow_mut())
}
}
fn write_line(string: String, file: &mut File){
file.write((string+"\n").as_bytes()).expect("Failed to write line");
}
fn append_atom_on_line(&mut self, line: String) -> Result<(), &str>{
let mut items = line.split_whitespace();
let atomic_number = AtomicNumber::from_option_string(items.next());
match atomic_number {
Ok(a) => self.atomic_numbers.push(a),
Err(..) => return Err("Failed to parse the atomic symbol"),
}
let coord = Point::from_option_strings(
items.next(), items.next(), items.next()
);
match coord {
Ok(c) => self.coordinates.push(c),
Err(..) => return Err("Failed to create a coordinate"),
}
Ok(())
}
}
fn read_lines<P>(filename: P) -> io::Lines<io::BufReader<File>>
where P: AsRef<Path>, {
let file = File::open(filename).expect("Failed to open file");
io::BufReader::new(file).lines()
}
#[cfg(test)]
mod tests{
use super::*;
use crate::utils::*;
#[test]
fn test_ok_file_read(){
print_methane_xyz_file("tmp_methane_2.xyz");
let file = XYZFile::read("tmp_methane_2.xyz").unwrap();
assert_eq!(file.coordinates.len(), 5);
assert_eq!(file.atomic_numbers.len(), 5);
let z_carbon = AtomicNumber::from_string("C").unwrap();
let z_hydrogen = AtomicNumber::from_string("H").unwrap();
assert!(file.atomic_numbers.contains(&z_carbon));
assert!(file.atomic_numbers.contains(&z_hydrogen));
std::fs::remove_file("tmp_methane_2.xyz").expect("Failed to remove file");
}
#[test]
#[should_panic]
fn test_no_coords_file_read(){
std::fs::write(format!("tmp2.xyz"),
"5\n\n")
.expect("Failed to write tmp2.xyz!");
let file = XYZFile::read("tmp2.xyz");
std::fs::remove_file("tmp2.xyz").expect("Failed to remove file");
file.unwrap();
}
#[test]
#[should_panic]
fn test_wrong_format_file_read(){
std::fs::write(format!("tmp3.xyz"),
"1\n\n\
H 0.00000 0.00000\n")
.expect("Failed to write tmp3.xyz!");
let file = XYZFile::read("tmp3.xyz");
std::fs::remove_file("tmp3.xyz").expect("Failed to remove file");
file.unwrap();
}
#[test]
fn test_read_then_write(){
print_methane_xyz_file("tmp_trtw1.xyz");
let mol1 = Molecule::from_xyz_file("tmp_trtw1.xyz");
XYZFile::write("tmp_trtw2.xyz", &mol1);
let mol2 = Molecule::from_xyz_file("tmp_trtw2.xyz");
assert_eq!(mol1.num_atoms(), mol2.num_atoms());
std::fs::remove_file("tmp_trtw1.xyz").expect("Failed to remove file");
std::fs::remove_file("tmp_trtw2.xyz").expect("Failed to remove file");
}
}