use crate::arguments::{Args, Reference};
use crate::atoms::Atoms;
use crate::density::Density;
use crate::utils;
use crate::voxel_map::VoxelMap;
use prettytable::{cell, format, row, Row, Table};
use rayon::prelude::*;
use std::fs::File;
use std::io::{self, Write};
pub mod cube;
pub mod reader;
pub mod vasp;
pub enum FileType {
Vasp,
Cube,
}
pub type ReadFunction =
io::Result<([f64; 3], [usize; 3], Atoms, Vec<Vec<f64>>)>;
type AddRow =
fn(&mut Table, String, (String, String, String), Vec<f64>, f64, f64);
type InitReturn = (Vec<Vec<f64>>, Vec<f64>, Atoms, [usize; 3], [f64; 3]);
pub trait FileFormat {
fn init(&self, args: &Args) -> InitReturn {
let (voxel_origin, grid, atoms, mut densities) =
match self.read(args.file.clone()) {
Ok(x) => x,
Err(e) => panic!("Error: Problem reading file.\n{}", e),
};
if let Some(x) = args.spin.clone() {
match densities.len() {
1 => {
let (_, g, _, d) = match self.read(x.clone()) {
Ok(r) => r,
Err(e) => panic!("{}", e),
};
if 1 != d.len() {
panic!(
"Number of densities in original file is not 1.
Ambiguous how to handle spin density when {} contains {} densities.",
x,
d.len()
);
}
assert_eq!(g, grid,
"Error: Spin density has different grid size.");
densities.push(d[0].clone());
}
x => panic!(
"Number of densities in original file is not 1.
Ambiguous how to handle new spin when {} already has {} spin densities.",
args.file,
x - 1
),
}
}
let rho = match args.reference.clone() {
Reference::None => Vec::with_capacity(0),
Reference::One(f) => {
let (_, g, _, densities) = match self.read(f) {
Ok(r) => r,
Err(e) => panic!("{}", e),
};
assert_eq!(g, grid,
"Error: Reference density has different grid size.");
densities[0].clone()
}
Reference::Two(f1, f2) => {
let (_, g, _, densities) = match self.read(f1) {
Ok(r) => r,
Err(e) => panic!("{}", e),
};
assert_eq!(g, grid,
"Error: Reference density has different grid size.");
let (_, g2, _, densities2) = match self.read(f2) {
Ok(r) => r,
Err(e) => panic!("{}", e),
};
assert_eq!(g2, grid,
"Error: Reference density has different grid size.");
densities[0].par_iter()
.zip(&densities2[0])
.map(|(a, b)| a + b)
.collect::<Vec<f64>>()
}
};
(densities, rho, atoms, grid, voxel_origin)
}
fn results(&self,
voxel_map: VoxelMap,
atoms: Atoms,
density: &Density)
-> (String, String) {
let mut bader_table = Table::new();
let mut atoms_table = Table::new();
bader_table.set_format(table_format());
atoms_table.set_format(table_format());
let add_row: AddRow = match voxel_map.bader_charge.len() {
1 => {
bader_table.set_titles(bader_no_spin());
atoms_table.set_titles(atom_no_spin());
add_row_no_spin
}
2 => {
bader_table.set_titles(bader_spin());
atoms_table.set_titles(atom_spin());
add_row_spin
}
4 => {
bader_table.set_titles(bader_ncl_spin());
atoms_table.set_titles(atom_ncl_spin());
add_row_ncl
}
_ => panic!(),
};
let mut count = 1;
let mut charge_t = vec![0f64; voxel_map.bader_charge.len()];
for (i, s_dist) in voxel_map.surface_distance.iter().enumerate() {
let mut charge_a = vec![0f64; 4];
let mut volume_a = 0f64;
for (ii, atom_num) in voxel_map.assigned_atom.iter().enumerate() {
if *atom_num == i {
let index = format!("{}: {}", atom_num + 1, count);
let maxima_cartesian =
{ density.to_cartesian(voxel_map.bader_maxima[ii]) };
let maxima_cartesian = utils::dot(maxima_cartesian,
density.voxel_lattice
.to_cartesian);
let (x, y, z) = self.coordinate_format(maxima_cartesian);
let volume = voxel_map.bader_volume[ii];
volume_a += volume;
let mut charge = vec![0f64; voxel_map.bader_charge.len()];
for j in 0..voxel_map.bader_charge.len() {
charge[j] = voxel_map.bader_charge[j][ii];
charge_a[j] += charge[j];
charge_t[j] += charge[j];
}
if charge[0] >= 1E-3 {
count += 1;
add_row(&mut bader_table,
index,
(x, y, z),
charge,
volume,
voxel_map.minimum_distance[ii]);
}
}
}
count = 1;
let (x, y, z) = self.coordinate_format(atoms.positions[i]);
let index = format!("{}", i + 1);
add_row(&mut atoms_table,
index,
(x, y, z),
charge_a,
volume_a,
*s_dist);
}
let footer = footer(voxel_map, charge_t);
let mut atoms_charge_file = atoms_table.to_string();
atoms_charge_file.push_str(&footer);
let bader_charge_file = bader_table.to_string();
(atoms_charge_file, bader_charge_file)
}
fn read(&self, filename: String) -> ReadFunction;
fn to_atoms(&self, atom_text: String) -> Atoms;
fn write(&self, atoms: &Atoms, data: Vec<Vec<f64>>);
fn coordinate_format(&self, coords: [f64; 3]) -> (String, String, String);
}
pub fn table_format() -> format::TableFormat {
let line_position =
&[format::LinePosition::Title, format::LinePosition::Bottom];
let line_separator = format::LineSeparator::new('-', '+', '+', '+');
format::FormatBuilder::new().column_separator('|')
.separators(line_position, line_separator)
.padding(1, 1)
.build()
}
pub fn bader_no_spin() -> Row {
row![c =>"#", "X", "Y", "Z", "Charge", "Volume", "Distance"]
}
pub fn atom_no_spin() -> Row {
row![c =>"#", "X", "Y", "Z", "Charge", "Volume", "Min. Dist."]
}
pub fn bader_spin() -> Row {
row![c =>"#", "X", "Y", "Z", "Charge", "Spin", "Volume", "Distance"]
}
pub fn atom_spin() -> Row {
row![c =>"#", "X", "Y", "Z", "Charge", "Spin", "Volume", "Min. Dist."]
}
pub fn bader_ncl_spin() -> Row {
row![c =>"#", "X", "Y", "Z", "Charge", "X Spin", "Y Spin", "Z Spin", "Volume", "Distance"]
}
pub fn atom_ncl_spin() -> Row {
row![c =>"#", "X", "Y", "Z", "Charge", "X Spin", "Y Spin", "Z Spin", "Volume", "Min. Dist."]
}
pub fn add_row_no_spin(t: &mut Table,
i_str: String,
pos_str: (String, String, String),
charge: Vec<f64>,
volume: f64,
distance: f64) {
let (x_str, y_str, z_str) = pos_str;
let c_str = format!("{:.6}", charge[0]);
let v_str = format!("{:.6}", volume);
let d_str = format!("{:.6}", distance);
t.add_row(row![r => i_str, x_str, y_str, z_str, c_str, v_str, d_str]);
}
pub fn add_row_spin(t: &mut Table,
i_str: String,
pos_str: (String, String, String),
charge: Vec<f64>,
volume: f64,
distance: f64) {
let (x_str, y_str, z_str) = pos_str;
let c_str = format!("{:.6}", charge[0]);
let s_str = format!("{:.6}", charge[1]);
let v_str = format!("{:.6}", volume);
let d_str = format!("{:.6}", distance);
t.add_row(
row![r => i_str, x_str, y_str, z_str, c_str, s_str, v_str, d_str],
);
}
pub fn add_row_ncl(t: &mut Table,
i_str: String,
pos_str: (String, String, String),
charge: Vec<f64>,
volume: f64,
distance: f64) {
let (x_str, y_str, z_str) = pos_str;
let c_str = format!("{:.6}", charge[0]);
let sx_str = format!("{:.6}", charge[1]);
let sy_str = format!("{:.6}", charge[2]);
let sz_str = format!("{:.6}", charge[3]);
let v_str = format!("{:.6}", volume);
let d_str = format!("{:.6}", distance);
t.add_row(row![r => i_str, x_str, y_str, z_str, c_str, sx_str, sy_str, sz_str, v_str, d_str]);
}
pub fn footer(voxel_map: VoxelMap, charge_total: Vec<f64>) -> String {
let mut vacuum_charge = Vec::<f64>::with_capacity(4);
for i in 0..voxel_map.bader_charge.len() {
if voxel_map.bader_maxima[0] < 0 {
vacuum_charge.push(*voxel_map.bader_charge[i].last().unwrap());
} else {
vacuum_charge.push(0.);
}
}
let vacuum_volume = if voxel_map.bader_maxima[0] < 0 {
*voxel_map.bader_volume.last().unwrap()
} else {
0.
};
match voxel_map.bader_charge.len() {
1 => format!(
" Vacuum Charge: {:>18.4}\n Vacuum Volume: {:>18.4}\n Partitioned Charge: {:>13.4}",
vacuum_charge[0],
vacuum_volume,
charge_total[0],
),
2 => format!(
" Vacuum Charge: {:>18.4}\n Vacuum Spin: {:>20.4}\n Vacuum Volume: {:>18.4}\n Partitioned Charge: {:>13.4}\n Partitioned Spin: {:>15.4}",
vacuum_charge[0],
vacuum_charge[1],
vacuum_volume,
charge_total[0], charge_total[1]
),
_ => format!(
" Vacuum Charge: {:>18.4}\n Vacuum Spin X: {:>18.4}\n Vacuum Spin Y: {:>18.4}\n Vacuum Spin Z: {:>18.4}\n Vacuum Volume: {:>18.4}\n Partitioned Charge: {:>13.4}\n Partitioned Spin X: {:>13.4}\n Partitioned Spin Y: {:>13.4}\n Partitioned Spin Z: {:>13.4}",
vacuum_charge[0],
vacuum_charge[1],
vacuum_charge[2],
vacuum_charge[3],
vacuum_volume,
charge_total[0], charge_total[1], charge_total[2], charge_total[3]
),
}
}
pub fn write(atoms_charge_file: String,
bader_charge_file: String)
-> io::Result<()> {
let mut bader_file = File::create("BCF.dat")?;
bader_file.write_all(bader_charge_file.as_bytes())?;
let mut atoms_file = File::create("ACF.dat")?;
atoms_file.write_all(atoms_charge_file.as_bytes())?;
Ok(())
}