rill_core_model/plate/
mod.rs1mod params;
9
10pub use params::PlateParams;
11
12use rill_core::traits::algorithm::{
13 Algorithm, AlgorithmCategory, AlgorithmMetadata, ParameterizedAlgorithm,
14};
15use rill_core::traits::ParamValue;
16use rill_core::Transcendental;
17
18#[derive(Debug, Clone)]
23pub struct PlateModel<T: Transcendental> {
24 params: PlateParams<T>,
25 grid_prev: Vec<T>,
26 grid_curr: Vec<T>,
27 grid_next: Vec<T>,
28 exc_x: usize,
29 exc_y: usize,
30 input_energy: T,
31}
32
33impl<T: Transcendental> PlateModel<T> {
34 pub fn new(params: PlateParams<T>) -> Self {
38 let size = params.grid_width * params.grid_height;
39 let exc_x = ((params.excitation_x.to_f64() * (params.grid_width - 1) as f64).round()
40 as usize)
41 .min(params.grid_width - 1);
42 let exc_y = ((params.excitation_y.to_f64() * (params.grid_height - 1) as f64).round()
43 as usize)
44 .min(params.grid_height - 1);
45 Self {
46 params,
47 grid_prev: vec![T::ZERO; size],
48 grid_curr: vec![T::ZERO; size],
49 grid_next: vec![T::ZERO; size],
50 exc_x,
51 exc_y,
52 input_energy: T::ZERO,
53 }
54 }
55
56 pub fn strike(&mut self, strength: T) {
58 let idx = self.exc_y * self.params.grid_width + self.exc_x;
59 self.grid_curr[idx] = strength;
60 }
61
62 fn idx(&self, x: usize, y: usize) -> usize {
63 y * self.params.grid_width + x
64 }
65
66 fn process_sample(&mut self, input: T) -> T {
67 let w = self.params.grid_width;
68 let h = self.params.grid_height;
69 let c2 = self.params.wave_speed * self.params.wave_speed;
70 let two = T::from_f32(2.0);
71
72 for y in 1..(h - 1) {
74 for x in 1..(w - 1) {
75 let i = self.idx(x, y);
76 let lap = self.grid_curr[self.idx(x, y - 1)]
77 + self.grid_curr[self.idx(x, y + 1)]
78 + self.grid_curr[self.idx(x - 1, y)]
79 + self.grid_curr[self.idx(x + 1, y)]
80 - self.grid_curr[i] * T::from_f32(4.0);
81 self.grid_next[i] = c2 * lap + two * self.grid_curr[i] - self.grid_prev[i];
82 self.grid_next[i] *= self.params.decay;
83 }
84 }
85
86 let bound = self.params.boundary;
88 for x in 0..w {
89 let top = self.idx(x, 0);
90 let bot = self.idx(x, h - 1);
91 self.grid_next[top] *= bound;
92 self.grid_next[bot] *= bound;
93 }
94 for y in 1..(h - 1) {
95 let left = self.idx(0, y);
96 let right = self.idx(w - 1, y);
97 self.grid_next[left] *= bound;
98 self.grid_next[right] *= bound;
99 }
100
101 let exc_idx = self.idx(self.exc_x, self.exc_y);
103 self.grid_next[exc_idx] += input;
104
105 let output = self.grid_next[exc_idx];
106
107 std::mem::swap(&mut self.grid_prev, &mut self.grid_curr);
109 std::mem::swap(&mut self.grid_curr, &mut self.grid_next);
110
111 output
112 }
113}
114
115impl<T: Transcendental> Algorithm<T> for PlateModel<T> {
116 fn process(
117 &mut self,
118 input: Option<&[T]>,
119 output: &mut [T],
120 ) -> rill_core::traits::ProcessResult<()> {
121 for (i, out) in output.iter_mut().enumerate() {
122 let inp = input
123 .map(|s| s.get(i).copied().unwrap_or(T::ZERO))
124 .unwrap_or(T::ZERO);
125 *out = self.process_sample(inp);
126 }
127 Ok(())
128 }
129
130 fn reset(&mut self) {
131 self.grid_prev.fill(T::ZERO);
132 self.grid_curr.fill(T::ZERO);
133 self.grid_next.fill(T::ZERO);
134 self.input_energy = T::ZERO;
135 }
136
137 fn metadata(&self) -> AlgorithmMetadata {
138 AlgorithmMetadata {
139 name: "Plate Model",
140 category: AlgorithmCategory::Generator,
141 description: "2D FDTD waveguide mesh for plate and membrane simulation",
142 author: "Rill",
143 version: "0.5",
144 }
145 }
146}
147
148impl<T: Transcendental> ParameterizedAlgorithm<T> for PlateModel<T> {
149 type Params = PlateParams<T>;
150
151 fn params(&self) -> &Self::Params {
152 &self.params
153 }
154
155 fn set_params(&mut self, params: Self::Params) {
156 let size_changed = params.grid_width != self.params.grid_width
157 || params.grid_height != self.params.grid_height;
158 self.params = params;
159 if size_changed {
160 let size = self.params.grid_width * self.params.grid_height;
161 self.grid_prev = vec![T::ZERO; size];
162 self.grid_curr = vec![T::ZERO; size];
163 self.grid_next = vec![T::ZERO; size];
164 }
165 self.exc_x = ((self.params.excitation_x.to_f64() * (self.params.grid_width - 1) as f64)
166 .round() as usize)
167 .min(self.params.grid_width - 1);
168 self.exc_y = ((self.params.excitation_y.to_f64() * (self.params.grid_height - 1) as f64)
169 .round() as usize)
170 .min(self.params.grid_height - 1);
171 }
172
173 fn set_parameter(&mut self, name: &str, value: ParamValue) -> Result<(), &'static str> {
174 match name {
175 "wave_speed" => {
176 let mut p = self.params.clone();
177 p.wave_speed = T::from_f64(value.as_f32().map(|v| v as f64).unwrap_or(0.25));
178 self.set_params(p);
179 Ok(())
180 }
181 "decay" => {
182 let mut p = self.params.clone();
183 p.decay = T::from_f64(value.as_f32().map(|v| v as f64).unwrap_or(0.999));
184 self.set_params(p);
185 Ok(())
186 }
187 "boundary" => {
188 let mut p = self.params.clone();
189 p.boundary = T::from_f64(value.as_f32().map(|v| v as f64).unwrap_or(0.5));
190 self.set_params(p);
191 Ok(())
192 }
193 _ => Err("Unknown parameter"),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_plate_creation() {
204 let params = PlateParams::<f64>::default();
205 let model = PlateModel::<f64>::new(params);
206 assert_eq!(model.grid_prev.len(), 16 * 16);
207 }
208
209 #[test]
210 fn test_plate_algorithm_process() {
211 let params = PlateParams::<f64>::default();
212 let mut model = PlateModel::<f64>::new(params);
213 model.strike(1.0.into());
214 let mut output = [0.0f64; 64];
215 model.process(None, &mut output).unwrap();
216 let max_abs = output.iter().map(|x| x.abs()).fold(0.0, f64::max);
217 assert!(max_abs > 0.0);
218 }
219
220 #[test]
221 fn test_plate_boundary() {
222 let mut params = PlateParams::<f64>::default();
223 params.boundary = 0.0.into();
224 params.decay = 0.95.into();
225 params.grid_width = 8;
226 params.grid_height = 8;
227 params.excitation_x = 0.5.into();
228 params.excitation_y = 0.5.into();
229 let mut model = PlateModel::<f64>::new(params);
230 model.strike(1.0.into());
231 for _ in 0..200 {
232 let mut out = [0.0f64; 1];
233 model.process(None, &mut out).unwrap();
234 }
235 let mut out = [0.0f64; 1];
236 model.process(None, &mut out).unwrap();
237 assert!(out[0].abs() < 0.01, "expected near-zero, got {}", out[0]);
238 }
239
240 #[test]
241 fn test_plate_params() {
242 let params = PlateParams::<f64>::default();
243 let mut model = PlateModel::<f64>::new(params);
244 let new_params = PlateParams {
245 wave_speed: 0.125.into(),
246 ..PlateParams::default()
247 };
248 model.set_params(new_params);
249 let ws = model.params.wave_speed.to_f64();
250 assert!((ws - 0.125).abs() < 1e-10, "expected 0.125, got {}", ws);
251 }
252
253 #[test]
254 fn test_plate_reset() {
255 let params = PlateParams::<f64>::default();
256 let mut model = PlateModel::<f64>::new(params);
257 model.strike(1.0.into());
258 model.reset();
259 let mut out = [0.0f64; 1];
260 model.process(None, &mut out).unwrap();
261 assert!((out[0].abs() - 0.0).abs() < 1e-15);
262 }
263}