137 lines
3.7 KiB
Rust
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);
|
|
}
|
|
}
|