fdtd-coremem/crates/coremem/src/stim/vector_field.rs

137 lines
3.7 KiB
Rust

use crate::geom::{Coord as _, HasCrossSection, Index, Region};
use crate::real::Real;
use crate::stim::Fields;
use coremem_cross::dim::DimSlice;
use coremem_cross::vec::Vec3u;
/// a static vector field. different value at each location, but constant in time.
/// often used as a building block by wrapping it in something which modulates the fields over
/// time.
pub trait VectorField<R> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R>;
}
// a vec of VectorFields is the sum of those fields
impl<R: Real, V: VectorField<R>> VectorField<R> for Vec<V> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
let mut acc = Fields::default();
for v in self {
acc += v.at(feat_size, loc);
}
acc
}
}
// uniform vector field
impl<R: Real> VectorField<R> for Fields<R> {
fn at(&self, _feat_size: R, _loc: Index) -> Fields<R> {
*self
}
}
// could broaden this and implement directly on T, but blanket impls
// are unwieldy
impl<R: Real, T> VectorField<R> for DimSlice<T>
where
DimSlice<T>: core::ops::Index<Vec3u, Output=Fields<R>>
{
fn at(&self, _feat_size: R, loc: Index) -> Fields<R> {
self[loc.into()]
}
}
/// restrict the VectorField to just the specified region, letting it be zero everywhere else
#[derive(Clone)]
pub struct RegionGated<G, V> {
region: G,
field: V,
}
impl<G, V> RegionGated<G, V> {
pub fn new(region: G, field: V) -> Self {
Self {
region, field
}
}
}
impl<R: Real, G: Region + Sync, V: VectorField<R>> VectorField<R> for RegionGated<G, V> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
if self.region.contains(loc.to_meters(feat_size.cast())) {
self.field.at(feat_size, loc)
} else {
Fields::default()
}
}
}
/// VectorField whose field at each point is based on its angle about the specified ray.
/// the field has equal E and H vectors. if you want just one, filter it out with `Scaled`.
#[derive(Clone)]
pub struct CurlVectorField<G> {
region: G,
}
impl<G> CurlVectorField<G> {
pub fn new(region: G) -> Self {
Self { region }
}
}
impl<R: Real, G: Region + HasCrossSection> VectorField<R> for CurlVectorField<G> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
let pos = loc.to_meters(feat_size.cast());
if self.region.contains(pos) {
// TODO: do we *want* this to be normalized?
let rotational = self.region.cross_section_normal(pos).norm().cast();
Fields::new_eh(rotational, rotational)
} else {
Fields::default()
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::cross::vec::Vec3;
use crate::geom::Meters;
struct MockRegion {
normal: Vec3<f32>,
}
impl HasCrossSection for MockRegion {
fn cross_section_normal(&self, _p: Meters) -> Vec3<f32> {
self.normal
}
}
impl Region for MockRegion {
fn contains(&self, _p: Meters) -> bool {
true
}
}
#[test]
fn curl_stimulus_trivial() {
let region = MockRegion {
normal: Vec3::new(1.0, 0.0, 0.0)
};
let stim = CurlVectorField::new(region);
assert_eq!(stim.at(1.0, Index::new(0, 0, 0)), Fields {
e: Vec3::new(1.0, 0.0, 0.0),
h: Vec3::new(1.0, 0.0, 0.0),
});
}
#[test]
fn curl_stimulus_multi_axis() {
let region = MockRegion {
normal: Vec3::new(0.0, -1.0, 1.0)
};
let stim = CurlVectorField::new(region);
let Fields { e, h } = stim.at(1.0, Index::new(0, 0, 0));
assert_eq!(e, h);
assert!(e.distance(Vec3::new(0.0, -1.0, 1.0).norm()) < 1e-6);
}
}