use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldSlice2D {
pub values: Vec<f64>,
pub dimensions: [usize; 2],
pub origin: [f64; 2],
pub spacing: f64,
pub component: String,
pub step: usize,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldSlice3D {
pub values: Vec<f64>,
pub dimensions: [usize; 3],
pub origin: [f64; 3],
pub spacing: f64,
pub component: String,
pub step: usize,
}
#[cfg(feature = "fdtd")]
impl FieldSlice2D {
#[must_use]
pub fn from_fdtd_2d(sim: &crate::fdtd::Fdtd2d, component: &str) -> Self {
let data = match component {
"fx" => &sim.fx,
"fy" => &sim.fy,
_ => &sim.fz,
};
let label = match component {
"fx" => format!("{:?}_x", sim.mode),
"fy" => format!("{:?}_y", sim.mode),
_ => format!("{:?}_z", sim.mode),
};
Self {
values: data.clone(),
dimensions: [sim.nx, sim.ny],
origin: [0.0, 0.0],
spacing: sim.dx,
component: label,
step: sim.step,
}
}
}
#[cfg(feature = "fdtd")]
impl FieldSlice3D {
#[must_use]
pub fn from_fdtd_3d(sim: &crate::fdtd::Fdtd3d, component: &str) -> Self {
let data = match component {
"ex" => &sim.ex,
"ey" => &sim.ey,
"ez" => &sim.ez,
"hx" => &sim.hx,
"hy" => &sim.hy,
"hz" => &sim.hz,
_ => &sim.ez,
};
Self {
values: data.clone(),
dimensions: [sim.nx, sim.ny, sim.nz],
origin: [0.0, 0.0, 0.0],
spacing: sim.dx,
component: component.to_string(),
step: sim.step,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldLineVisualization {
pub lines: Vec<FieldLine>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldLine {
pub points: Vec<[f64; 3]>,
pub magnitudes: Vec<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChargeVisualization {
pub charges: Vec<ChargePoint>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct ChargePoint {
pub position: [f32; 3],
pub charge: f32,
pub magnitude: f32,
}
#[cfg(feature = "charge")]
impl ChargeVisualization {
#[must_use]
pub fn from_point_charges(charges: &[crate::charge::PointCharge]) -> Self {
Self {
charges: charges
.iter()
.map(|pc| ChargePoint {
position: [
pc.position[0] as f32,
pc.position[1] as f32,
pc.position[2] as f32,
],
charge: pc.charge as f32,
magnitude: pc.charge.abs() as f32,
})
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RadiationPattern {
pub angles: Vec<f64>,
pub pattern: Vec<f64>,
pub max_gain: f64,
}
#[cfg(feature = "fdtd")]
impl RadiationPattern {
#[must_use]
pub fn from_nf2ff(result: &crate::fdtd::Nf2ffResult) -> Self {
let max_gain = result.pattern.iter().cloned().fold(0.0_f64, f64::max);
Self {
angles: result.angles.clone(),
pattern: result.pattern.clone(),
max_gain,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VectorFieldSample {
pub vectors: Vec<[f64; 3]>,
pub dimensions: [usize; 3],
pub origin: [f64; 3],
pub spacing: f64,
pub max_magnitude: f64,
}
impl VectorFieldSample {
#[must_use]
pub fn from_components(
vx: &[f64],
vy: &[f64],
vz: &[f64],
dimensions: [usize; 3],
origin: [f64; 3],
spacing: f64,
) -> Self {
let count = vx.len().min(vy.len()).min(vz.len());
let mut max_mag = 0.0_f64;
let vectors: Vec<[f64; 3]> = (0..count)
.map(|i| {
let v = [vx[i], vy[i], vz[i]];
let mag = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
if mag > max_mag {
max_mag = mag;
}
v
})
.collect();
Self {
vectors,
dimensions,
origin,
spacing,
max_magnitude: max_mag,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn field_slice_2d_serializes() {
let slice = FieldSlice2D {
values: vec![0.0; 4],
dimensions: [2, 2],
origin: [0.0, 0.0],
spacing: 1.0,
component: "Ez".to_string(),
step: 0,
};
let json = serde_json::to_string(&slice);
assert!(json.is_ok());
}
#[test]
fn field_slice_3d_serializes() {
let slice = FieldSlice3D {
values: vec![0.0; 8],
dimensions: [2, 2, 2],
origin: [0.0; 3],
spacing: 0.5,
component: "ex".to_string(),
step: 10,
};
let json = serde_json::to_string(&slice);
assert!(json.is_ok());
}
#[test]
fn field_line_visualization() {
let viz = FieldLineVisualization {
lines: vec![FieldLine {
points: vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.5, 0.0]],
magnitudes: vec![1.0, 0.8, 0.5],
}],
};
assert_eq!(viz.lines.len(), 1);
assert_eq!(viz.lines[0].points.len(), 3);
}
#[test]
fn charge_visualization_manual() {
let viz = ChargeVisualization {
charges: vec![
ChargePoint {
position: [0.0, 0.0, 0.0],
charge: 1.6e-19,
magnitude: 1.6e-19,
},
ChargePoint {
position: [1.0, 0.0, 0.0],
charge: -1.6e-19,
magnitude: 1.6e-19,
},
],
};
assert_eq!(viz.charges.len(), 2);
assert!(viz.charges[0].charge > 0.0);
assert!(viz.charges[1].charge < 0.0);
}
#[cfg(feature = "charge")]
#[test]
fn charge_visualization_from_point_charges() {
let charges = vec![
crate::charge::PointCharge::proton([0.0, 0.0, 0.0]),
crate::charge::PointCharge::electron([1e-10, 0.0, 0.0]),
];
let viz = ChargeVisualization::from_point_charges(&charges);
assert_eq!(viz.charges.len(), 2);
assert!(viz.charges[0].charge > 0.0); assert!(viz.charges[1].charge < 0.0); }
#[test]
fn radiation_pattern_manual() {
let pat = RadiationPattern {
angles: vec![0.0, std::f64::consts::FRAC_PI_2, std::f64::consts::PI],
pattern: vec![1.0, 0.5, 0.1],
max_gain: 1.0,
};
assert_eq!(pat.angles.len(), 3);
assert_eq!(pat.max_gain, 1.0);
}
#[test]
fn vector_field_from_components() {
let vx = vec![1.0, 0.0, 0.0, 1.0];
let vy = vec![0.0, 1.0, 0.0, 0.0];
let vz = vec![0.0, 0.0, 1.0, 0.0];
let field = VectorFieldSample::from_components(&vx, &vy, &vz, [2, 2, 1], [0.0; 3], 1.0);
assert_eq!(field.vectors.len(), 4);
assert!((field.max_magnitude - 1.0).abs() < 0.001);
}
#[test]
fn vector_field_empty() {
let field = VectorFieldSample::from_components(&[], &[], &[], [0, 0, 0], [0.0; 3], 1.0);
assert!(field.vectors.is_empty());
assert_eq!(field.max_magnitude, 0.0);
}
#[cfg(feature = "fdtd")]
#[test]
fn field_slice_from_fdtd_2d() {
let sim = crate::fdtd::Fdtd2d::new(10, 10, 0.01, crate::fdtd::Mode2d::Tm).unwrap();
let slice = FieldSlice2D::from_fdtd_2d(&sim, "fz");
assert_eq!(slice.dimensions, [10, 10]);
assert_eq!(slice.values.len(), 100);
assert!((slice.spacing - 0.01).abs() < 1e-10);
}
#[cfg(feature = "fdtd")]
#[test]
fn field_slice_from_fdtd_3d() {
let sim = crate::fdtd::Fdtd3d::new(4, 4, 4, 0.01).unwrap();
let slice = FieldSlice3D::from_fdtd_3d(&sim, "ez");
assert_eq!(slice.dimensions, [4, 4, 4]);
assert_eq!(slice.values.len(), 64);
}
}