use serde::{Deserialize, Serialize};
pub type Vec3 = [f64; 3];
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DipoleSource {
pub position: Vec3,
pub moment: Vec3,
}
impl DipoleSource {
pub const fn new(position: Vec3, moment: Vec3) -> Self {
Self { position, moment }
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CurrentLoop {
pub centre: Vec3,
pub normal: Vec3,
pub radius: f64,
pub current: f64,
#[serde(default = "default_segments")]
pub n_segments: u32,
}
const fn default_segments() -> u32 {
64
}
impl CurrentLoop {
pub fn new(centre: Vec3, normal: Vec3, radius: f64, current: f64) -> Self {
Self {
centre,
normal,
radius,
current,
n_segments: default_segments(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FerrousObject {
pub position: Vec3,
pub volume: f64,
pub susceptibility: f64,
}
impl FerrousObject {
pub fn steel(position: Vec3, volume: f64) -> Self {
Self {
position,
volume,
susceptibility: 5000.0,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EddyCurrent {
pub position: Vec3,
pub area: f64,
pub conductivity: f64,
pub inductance: f64,
pub normal: Vec3,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Scene {
pub dipoles: Vec<DipoleSource>,
pub loops: Vec<CurrentLoop>,
pub ferrous: Vec<FerrousObject>,
pub eddy: Vec<EddyCurrent>,
pub sensors: Vec<Vec3>,
#[serde(default)]
pub ambient_field: Vec3,
}
impl Scene {
pub fn new() -> Self {
Self::default()
}
pub fn add_dipole(&mut self, dipole: DipoleSource) -> &mut Self {
self.dipoles.push(dipole);
self
}
pub fn add_loop(&mut self, l: CurrentLoop) -> &mut Self {
self.loops.push(l);
self
}
pub fn add_ferrous(&mut self, ferrous: FerrousObject) -> &mut Self {
self.ferrous.push(ferrous);
self
}
pub fn add_sensor(&mut self, position: Vec3) -> &mut Self {
self.sensors.push(position);
self
}
pub fn n_sources(&self) -> usize {
self.dipoles.len() + self.loops.len() + self.ferrous.len() + self.eddy.len()
}
pub fn to_canonical_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dipole_construction_round_trip_via_json() {
let d = DipoleSource::new([1.0, 2.0, 3.0], [0.1, 0.2, 0.3]);
let s = serde_json::to_string(&d).unwrap();
let d2: DipoleSource = serde_json::from_str(&s).unwrap();
assert_eq!(d, d2);
}
#[test]
fn current_loop_default_n_segments_is_64() {
let l = CurrentLoop::new([0.0; 3], [0.0, 0.0, 1.0], 0.05, 1.5);
assert_eq!(l.n_segments, 64);
}
#[test]
fn empty_scene_is_default_and_serialises() {
let s = Scene::new();
assert_eq!(s.n_sources(), 0);
assert_eq!(s.sensors.len(), 0);
let _ = s.to_canonical_json().unwrap();
}
#[test]
fn scene_round_trip_via_json_preserves_all_primitives() {
let mut s = Scene::new();
s.add_dipole(DipoleSource::new([0.0; 3], [1e-6, 0.0, 0.0]));
s.add_loop(CurrentLoop::new([0.0; 3], [0.0, 0.0, 1.0], 0.1, 0.5));
s.add_ferrous(FerrousObject::steel([0.5; 3], 1e-3));
s.add_sensor([1.0, 0.0, 0.0]);
let json = s.to_canonical_json().unwrap();
let s2: Scene = serde_json::from_str(&json).unwrap();
assert_eq!(s, s2);
}
}