1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! Excitation operators.
use super::fields::Fields;
use super::operator::{Hooks, Operator, StepContext};
use crate::axis::Axis;
/// Time function of a source, `t` \[s\] → amplitude \[V\].
pub type Amplitude = Box<dyn Fn(f64) -> f64 + Send>;
/// What an [`EdgeSource`] drives and how.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SourceKind {
/// Additive ("soft") source on E edges (default): after the E update
/// of step `n`, `u += w·a(t)` with `t = (n + ½)·dt`, the centre of
/// that E update (design §5.7).
#[default]
SoftE,
/// Hard source on E edges: after the E update of step `n`,
/// `u = w·a(t)` with `t = (n + 1)·dt`, the time of the updated `u`.
HardE,
/// Additive source on H dual edges: after the H update of step `n`,
/// `i += w·a(t)` with `t = n·dt`, the centre of that H update.
SoftH,
/// Hard source on H dual edges: after the H update of step `n`,
/// `i = w·a(t)` with `t = (n + ½)·dt`, the time of the updated `i`.
HardH,
}
impl SourceKind {
/// Whether the source acts on the H currents.
pub fn is_magnetic(self) -> bool {
matches!(self, SourceKind::SoftH | SourceKind::HardH)
}
/// Whether the source overwrites the field instead of adding to it.
pub fn is_hard(self) -> bool {
matches!(self, SourceKind::HardE | SourceKind::HardH)
}
}
/// Voltage (or current) source on a set of E edges (or H dual edges).
///
/// By default ([`SourceKind::SoftE`]) it adds `weight · amplitude(t)` to the
/// voltage `u` of each listed edge after every E update ("soft" or additive
/// source, Taflove & Hagness, *Computational Electrodynamics*, 3rd ed.,
/// ch. 5 on source conditions). The amplitude is sampled at
/// `t = (n + ½)·dt`, the centre of the E update it is applied with
/// (design §5.7). See [`SourceKind`] for hard sources and H sources and
/// their sample times.
///
/// A soft source on a PEC edge (`ca = cb = 0`) behaves like a hard source:
/// the kernel clears the edge and the source writes the amplitude.
pub struct EdgeSource {
kind: SourceKind,
edges: Vec<(Axis, [usize; 3], f32)>,
amplitude: Amplitude,
}
impl core::fmt::Debug for EdgeSource {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("EdgeSource")
.field("kind", &self.kind)
.field("edges", &self.edges)
.finish_non_exhaustive()
}
}
impl EdgeSource {
/// Soft E source with time function `amplitude` and no edges yet.
pub fn new(amplitude: impl Fn(f64) -> f64 + Send + 'static) -> Self {
Self {
kind: SourceKind::SoftE,
edges: Vec::new(),
amplitude: Box::new(amplitude),
}
}
/// Sets the kind of source (default [`SourceKind::SoftE`]).
pub fn kind(mut self, kind: SourceKind) -> Self {
self.kind = kind;
self
}
/// The kind of source.
pub fn get_kind(&self) -> SourceKind {
self.kind
}
/// Adds the primary edge (or, for H kinds, the dual edge) along `axis`
/// at `node`, with `weight` (use a negative weight to excite against
/// the axis direction).
pub fn edge(mut self, axis: Axis, node: [usize; 3], weight: f64) -> Self {
self.edges.push((axis, node, weight as f32));
self
}
/// The excited edges `(axis, node, weight)`.
pub fn edges(&self) -> impl Iterator<Item = (Axis, [usize; 3], f64)> + '_ {
self.edges.iter().map(|&(a, n, w)| (a, n, w as f64))
}
fn apply(&self, arr: &mut super::fields::FieldArrays<'_>, t: f64) {
let a = (self.amplitude)(t) as f32;
if self.kind.is_hard() {
for &(axis, node, w) in &self.edges {
arr.set(axis, node, w * a);
}
} else {
for &(axis, node, w) in &self.edges {
*arr.get_mut(axis, node) += w * a;
}
}
}
}
impl Operator for EdgeSource {
fn hooks(&self) -> Hooks {
if self.kind.is_magnetic() {
Hooks::POST_H
} else {
Hooks::POST_E
}
}
fn post_h(&mut self, fields: &mut Fields<'_>, ctx: &StepContext<'_>) {
let t = match self.kind {
SourceKind::SoftH => ctx.time(),
SourceKind::HardH => ctx.h_time(),
_ => return,
};
self.apply(&mut fields.i, t);
}
fn post_e(&mut self, fields: &mut Fields<'_>, ctx: &StepContext<'_>) {
let t = match self.kind {
SourceKind::SoftE => ctx.h_time(),
SourceKind::HardE => ctx.e_time(),
_ => return,
};
self.apply(&mut fields.u, t);
}
}