use crate::StrError;
use crate::{Tensor2, Tensor3, Tensor4};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
const VACUUM_ELECTRIC_PERMITTIVITY: f64 = 8.854_187_818_8e-12;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct PiezoDatabase {
#[serde(rename = "_metadata")]
pub metadata: Metadata,
#[serde(flatten)]
pub materials: HashMap<String, Material>,
}
impl PiezoDatabase {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let db: PiezoDatabase = serde_json::from_str(&content)?;
Ok(db)
}
pub fn get(&self, material_id: &str) -> Result<&Material, StrError> {
self.materials
.get(material_id)
.ok_or("material not found in the database")
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Units {
pub e_tensor: String,
pub e_voigt: String,
pub cc_tensor: String,
pub cc_voigt: String,
pub relative_permittivity_tensor: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Metadata {
pub voigt_mapping: String,
pub voigt_mapping_notes: String,
pub units: Units,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Material {
pub material_id: String,
pub formula: String,
pub crystal_system: String,
pub point_group: String,
pub space_group_symbol: String,
pub space_group_number: i32,
pub e_tensor: Vec<Vec<Vec<f64>>>,
pub e_voigt: Vec<Vec<f64>>,
pub cc_tensor: Vec<Vec<Vec<Vec<f64>>>>,
pub cc_voigt: Vec<Vec<f64>>,
pub relative_permittivity_tensor: Vec<Vec<f64>>,
}
impl Material {
pub fn info(&self) -> String {
format!(
"{} ({}) : Crystal System {} : Space Group {}",
self.formula, self.material_id, self.crystal_system, self.space_group_symbol
)
}
pub fn moduli(&self) -> Result<(Tensor2<6>, Tensor3<3, 6>, Tensor4<6>), StrError> {
let mut p = Tensor2::<6>::from_std_matrix(&symmetrize3(&self.relative_permittivity_tensor))?;
let e = Tensor3::<3, 6>::from_std_array(&vec_to_std_array_3(&self.e_tensor))?;
let mut cc = Tensor4::<6>::from_std_array(&vec_to_std_array_4(&self.cc_tensor))?;
p.scale(VACUUM_ELECTRIC_PERMITTIVITY);
cc.scale(1e9); Ok((p, e, cc))
}
}
fn vec_to_std_array_3(v: &[Vec<Vec<f64>>]) -> [[[f64; 3]; 3]; 3] {
let mut a = [[[0.0; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
a[i][j][k] = v[i][j][k];
}
}
}
a
}
fn vec_to_std_array_4(v: &[Vec<Vec<Vec<f64>>>]) -> [[[[f64; 3]; 3]; 3]; 3] {
let mut a = [[[[0.0; 3]; 3]; 3]; 3];
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
for l in 0..3 {
a[i][j][k][l] = v[i][j][k][l];
}
}
}
}
a
}
fn symmetrize3(v: &[Vec<f64>]) -> [[f64; 3]; 3] {
let mut a = [[0.0; 3]; 3];
for i in 0..3 {
for j in 0..3 {
a[i][j] = 0.5 * (v[i][j] + v[j][i]);
}
}
a
}
#[cfg(test)]
mod tests {
use super::*;
use russell_lab::approx_eq;
use std::env;
use std::path::PathBuf;
#[test]
fn test_parse_piezo_data_json() {
let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let path = root.join("data/piezo_data.json");
let db = PiezoDatabase::from_file(path).expect("Failed to parse JSON database");
assert_eq!(db.metadata.voigt_mapping, "11, 22, 33, 23, 13, 12");
assert!(db.materials.contains_key("mp-3731"), "Missing mp-3731");
let linbo3 = &db.materials["mp-3731"];
assert_eq!(linbo3.material_id, "mp-3731");
assert_eq!(linbo3.formula, "LiNbO3");
assert_eq!(linbo3.crystal_system, "Trigonal");
assert_eq!(linbo3.space_group_number, 161);
let e_voigt = &linbo3.e_voigt;
assert_eq!(e_voigt.len(), 3);
assert_eq!(e_voigt[0].len(), 6);
let e_tensor = &linbo3.e_tensor;
assert_eq!(e_tensor.len(), 3);
assert_eq!(e_tensor[0].len(), 3);
assert_eq!(e_tensor[0][0].len(), 3);
let cc_voigt = &linbo3.cc_voigt;
assert_eq!(cc_voigt.len(), 6);
assert_eq!(cc_voigt[0].len(), 6);
}
#[test]
fn material_moduli_works() {
let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let path = root.join("data/piezo_data.json");
let db = PiezoDatabase::from_file(path).expect("Failed to parse JSON database");
let mat = db.get("mp-3731").unwrap();
let (p, e, cc) = mat.moduli().unwrap();
let linbo3 = &db.materials["mp-3731"];
for i in 0..3 {
for j in 0..3 {
approx_eq(
p.get_std(i, j),
VACUUM_ELECTRIC_PERMITTIVITY * linbo3.relative_permittivity_tensor[i][j],
1e-13,
);
}
}
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
approx_eq(e.get_std(i, j, k), linbo3.e_tensor[i][j][k], 1e-13);
}
}
}
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
for l in 0..3 {
approx_eq(cc.get_std(i, j, k, l), 1e9 * linbo3.cc_tensor[i][j][k][l], 1e-4);
}
}
}
}
}
}