use super::*;
use crate::SPDCError;
use dim::f64prefixes::MICRO;
use dim::ucum::{Kelvin, K, M};
use std::fmt;
use std::str::FromStr;
use utils::from_celsius_to_kelvin;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CrystalExpr {
Uniaxial {
#[serde(skip_serializing)]
no: meval::Expr,
#[serde(skip_serializing)]
ne: meval::Expr,
},
Biaxial {
#[serde(skip_serializing)]
nx: meval::Expr,
#[serde(skip_serializing)]
ny: meval::Expr,
#[serde(skip_serializing)]
nz: meval::Expr,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[allow(non_camel_case_types)]
pub enum CrystalType {
BBO_1,
KTP,
BiBO_1,
LiNbO3_1,
LiNb_MgO,
KDP_1,
AgGaSe2_1,
AgGaSe2_2,
LiIO3_2,
LiIO3_1,
AgGaS2_1,
#[serde(untagged)]
Expr(CrystalExpr),
}
impl fmt::Display for CrystalType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_meta().id)
}
}
impl FromStr for CrystalType {
type Err = SPDCError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
CrystalType::from_string(s)
}
}
impl CrystalType {
pub fn from_string(id: &str) -> Result<Self, SPDCError> {
match id {
"BBO_1" => Ok(CrystalType::BBO_1),
"KTP" => Ok(CrystalType::KTP),
"BiBO_1" => Ok(CrystalType::BiBO_1),
"LiIO3_1" => Ok(CrystalType::LiIO3_1),
"LiIO3_2" => Ok(CrystalType::LiIO3_2),
"LiNbO3_1" => Ok(CrystalType::LiNbO3_1),
"LiNb_MgO" => Ok(CrystalType::LiNb_MgO),
"KDP_1" => Ok(CrystalType::KDP_1),
"AgGaS2_1" => Ok(CrystalType::AgGaS2_1),
"AgGaSe2_1" => Ok(CrystalType::AgGaSe2_1),
"AgGaSe2_2" => Ok(CrystalType::AgGaSe2_2),
_ => {
let mut s = id.replace('=', ":");
if !s.trim().starts_with('{') {
s = format!("{{{}}}", s);
}
Ok(CrystalType::Expr(
deser_hjson::from_str(&s).map_err(|e| SPDCError(e.to_string()))?,
))
}
}
}
pub fn get_all_meta() -> Vec<CrystalMeta> {
vec![
bbo_1::META,
ktp::META,
bibo_1::META,
linbo3_1::META,
linb_mgo::META,
kdp_1::META,
aggase2_1::META,
aggase2_2::META,
liio3_2::META,
liio3_1::LiIO3_1.get_meta(),
aggas2_1::AgGaS2_1.get_meta(),
]
}
pub fn get_indices(&self, vacuum_wavelength: Wavelength, temperature: Kelvin<f64>) -> Indices {
match &self {
CrystalType::BBO_1 => bbo_1::get_indices(vacuum_wavelength, temperature),
CrystalType::KTP => ktp::get_indices(vacuum_wavelength, temperature),
CrystalType::LiNbO3_1 => linbo3_1::get_indices(vacuum_wavelength, temperature),
CrystalType::LiNb_MgO => linb_mgo::get_indices(vacuum_wavelength, temperature),
CrystalType::BiBO_1 => bibo_1::get_indices(vacuum_wavelength, temperature),
CrystalType::KDP_1 => kdp_1::get_indices(vacuum_wavelength, temperature),
CrystalType::AgGaSe2_1 => aggase2_1::get_indices(vacuum_wavelength, temperature),
CrystalType::AgGaSe2_2 => aggase2_2::get_indices(vacuum_wavelength, temperature),
CrystalType::LiIO3_2 => liio3_2::get_indices(vacuum_wavelength, temperature),
CrystalType::LiIO3_1 => liio3_1::LiIO3_1.get_indices(vacuum_wavelength, temperature),
CrystalType::AgGaS2_1 => aggas2_1::AgGaS2_1.get_indices(vacuum_wavelength, temperature),
CrystalType::Expr(expr) => {
let mut ctx = meval::Context::new();
ctx.var("T", *((temperature - from_celsius_to_kelvin(20.0)) / K));
match expr {
CrystalExpr::Uniaxial { no, ne } => {
let no = no.clone().bind_with_context(ctx.clone(), "l").unwrap()(
*(vacuum_wavelength / (MICRO * M)),
);
let ne =
ne.clone().bind_with_context(ctx, "l").unwrap()(*(vacuum_wavelength / (MICRO * M)));
Indices::new(na::Vector3::new(no, no, ne))
}
CrystalExpr::Biaxial { nx, ny, nz } => {
let nx = nx.clone().bind_with_context(ctx.clone(), "l").unwrap()(
*(vacuum_wavelength / (MICRO * M)),
);
let ny = ny.clone().bind_with_context(ctx.clone(), "l").unwrap()(
*(vacuum_wavelength / (MICRO * M)),
);
let nz =
nz.clone().bind_with_context(ctx, "l").unwrap()(*(vacuum_wavelength / (MICRO * M)));
Indices::new(na::Vector3::new(nx, ny, nz))
}
}
}
}
}
pub fn get_meta(&self) -> CrystalMeta {
match &self {
CrystalType::BBO_1 => bbo_1::META,
CrystalType::KTP => ktp::META,
CrystalType::BiBO_1 => bibo_1::META,
CrystalType::LiNbO3_1 => linbo3_1::META,
CrystalType::LiNb_MgO => linb_mgo::META,
CrystalType::KDP_1 => kdp_1::META,
CrystalType::AgGaSe2_1 => aggase2_1::META,
CrystalType::AgGaSe2_2 => aggase2_2::META,
CrystalType::LiIO3_2 => liio3_2::META,
CrystalType::LiIO3_1 => liio3_1::LiIO3_1.get_meta(),
CrystalType::AgGaS2_1 => aggas2_1::AgGaS2_1.get_meta(),
CrystalType::Expr(_) => CrystalMeta {
id: "Expr",
name: "Expr",
reference_url: "Expr",
axis_type: OpticAxisType::PositiveUniaxial,
point_group: PointGroup::HM_mm2,
transmission_range: None,
temperature_dependence_known: false,
},
}
}
}
#[cfg(feature = "pyo3")]
mod pyo3_impls {
use super::*;
use pyo3::{exceptions::PyValueError, prelude::*};
impl FromPyObject<'_> for CrystalType {
fn extract_bound(ob: &Bound<'_, pyo3::PyAny>) -> PyResult<Self> {
let s: &str = ob.extract()?;
CrystalType::from_str(s).map_err(|e| PyErr::new::<PyValueError, _>(e.to_string()))
}
}
impl ToPyObject for CrystalType {
fn to_object(&self, py: Python<'_>) -> PyObject {
self.to_string().to_object(py)
}
}
impl IntoPy<PyObject> for CrystalType {
fn into_py(self, py: Python<'_>) -> PyObject {
self.to_string().into_py(py)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use dim::f64prefixes::NANO;
use dim::ucum::M;
#[test]
fn test_crystal_ids() {
for meta in CrystalType::get_all_meta() {
let crystal = CrystalType::from_string(meta.id).unwrap();
assert_eq!(meta, crystal.get_meta());
}
}
#[test]
fn test_crystal_expr() {
let expr = r#"{
"no": "sqrt(2.7359+0.01878/(l^2-0.01822)-0.01354*l^2) - 9.3e-6 * T",
"ne": "sqrt(2.3753+0.01224/(l^2-0.01667)-0.01516*l^2) - 16.6e-6 * T"
}"#;
let crystal: CrystalType = serde_json::from_str(expr).unwrap();
let other = CrystalType::from_string(
r#"
no = sqrt(2.7359+0.01878/(l^2-0.01822)-0.01354*l^2) - 9.3e-6 * T
ne = sqrt(2.3753+0.01224/(l^2-0.01667)-0.01516*l^2) - 16.6e-6 * T
"#,
)
.unwrap();
assert_eq!(crystal, other);
let indices = crystal.get_indices(1064.0 * NANO * M, from_celsius_to_kelvin(45.0));
let expected = CrystalType::BBO_1.get_indices(1064.0 * NANO * M, from_celsius_to_kelvin(45.0));
assert_eq!(indices, expected);
}
}