1use sim_kernel::{Cx, Result, Symbol, Value};
4use sim_lib_interference_core::{
5 Emitter, FieldAmplitude, Hertz, InterferenceProblem, MetresPerSecond, NepersPerMetre, Point3M,
6 PositiveMetres, Radians, SamplingPlane, ScalarMedium, SourceSet, UnitVector3,
7};
8
9use crate::citizen::{
10 RecordCitizenSpec, decode_record, decode_records, encode_field, encode_record, encode_records,
11 invalid, next_field,
12};
13
14#[derive(Clone, Copy, Debug, PartialEq)]
16pub struct MediumDescriptor {
17 pub speed_m_s: f64,
19 pub attenuation_np_m: f64,
21}
22
23impl MediumDescriptor {
24 pub fn new(speed_m_s: f64, attenuation_np_m: f64) -> Result<Self> {
26 let value = Self {
27 speed_m_s,
28 attenuation_np_m,
29 };
30 value.validate()?;
31 Ok(value)
32 }
33
34 pub fn from_medium(medium: ScalarMedium) -> Self {
36 Self {
37 speed_m_s: medium.speed().get(),
38 attenuation_np_m: medium.attenuation().get(),
39 }
40 }
41
42 pub fn to_medium(self) -> Result<ScalarMedium> {
44 Ok(ScalarMedium::new(
45 MetresPerSecond::new(self.speed_m_s)
46 .map_err(|error| invalid("Medium", format!("{error:?}")))?,
47 NepersPerMetre::new(self.attenuation_np_m)
48 .map_err(|error| invalid("Medium", format!("{error:?}")))?,
49 ))
50 }
51}
52
53impl RecordCitizenSpec for MediumDescriptor {
54 const FIELDS: &'static [&'static str] = &["speed-m-s", "attenuation-np-m"];
55
56 fn encode_fields(&self, _cx: &mut Cx) -> Result<Vec<sim_kernel::Expr>> {
57 Ok(vec![
58 encode_field(&self.speed_m_s),
59 encode_field(&self.attenuation_np_m),
60 ])
61 }
62
63 fn decode_fields(cx: &mut Cx, fields: Vec<Value>) -> Result<Self> {
64 let mut fields = fields.into_iter();
65 Self::new(
66 next_field(cx, &mut fields, "speed-m-s")?,
67 next_field(cx, &mut fields, "attenuation-np-m")?,
68 )
69 }
70
71 fn example() -> Self {
72 Self::new(343.0, 0.0).expect("example medium")
73 }
74
75 fn validate(&self) -> Result<()> {
76 self.to_medium().map(|_| ())
77 }
78}
79
80impl_record_citizen!(MediumDescriptor, "interference/Medium", 2);
81
82#[derive(Clone, Debug, PartialEq)]
84pub struct EmitterDescriptor {
85 pub kind: Symbol,
87 pub id: String,
89 pub anchor_m: Vec<f64>,
91 pub direction: Option<Vec<f64>>,
93 pub amplitude: f64,
95 pub phase_rad: f64,
97}
98
99impl EmitterDescriptor {
100 pub fn from_emitter(emitter: &Emitter) -> Self {
102 match emitter {
103 Emitter::Point {
104 id,
105 position,
106 amplitude_at_reference,
107 phase,
108 } => Self {
109 kind: point_kind(),
110 id: id.clone(),
111 anchor_m: position.coordinates_metres().to_vec(),
112 direction: None,
113 amplitude: amplitude_at_reference.get(),
114 phase_rad: phase.get(),
115 },
116 Emitter::ForwardPlane {
117 id,
118 through,
119 direction,
120 amplitude,
121 phase,
122 } => Self {
123 kind: forward_plane_kind(),
124 id: id.clone(),
125 anchor_m: through.coordinates_metres().to_vec(),
126 direction: Some(direction.components().to_vec()),
127 amplitude: amplitude.get(),
128 phase_rad: phase.get(),
129 },
130 }
131 }
132
133 pub fn to_emitter(&self) -> Result<Emitter> {
135 if self.id.is_empty() {
136 return Err(invalid("Emitter", "source id cannot be empty"));
137 }
138 let [x, y, z] = vector3(&self.anchor_m, "emitter anchor")?;
139 let anchor = Point3M::from_metres(x, y, z)
140 .map_err(|error| invalid("Emitter", format!("{error:?}")))?;
141 let amplitude = FieldAmplitude::new(self.amplitude)
142 .map_err(|error| invalid("Emitter", format!("{error:?}")))?;
143 let phase = Radians::new(self.phase_rad)
144 .map_err(|error| invalid("Emitter", format!("{error:?}")))?;
145 if phase.get().to_bits() != self.phase_rad.to_bits() {
146 return Err(invalid(
147 "Emitter",
148 "phase-rad must already be canonical in [-pi, pi)",
149 ));
150 }
151 if self.kind == point_kind() {
152 if self.direction.is_some() {
153 return Err(invalid(
154 "Emitter",
155 "point emitters cannot carry a direction",
156 ));
157 }
158 Ok(Emitter::Point {
159 id: self.id.clone(),
160 position: anchor,
161 amplitude_at_reference: amplitude,
162 phase,
163 })
164 } else if self.kind == forward_plane_kind() {
165 let [dx, dy, dz] = vector3(
166 self.direction
167 .as_deref()
168 .ok_or_else(|| invalid("Emitter", "forward-plane direction is required"))?,
169 "emitter direction",
170 )?;
171 let direction = UnitVector3::new(dx, dy, dz)
172 .map_err(|error| invalid("Emitter", format!("{error:?}")))?;
173 let normalized = direction.components();
174 if normalized
175 .iter()
176 .zip([dx, dy, dz])
177 .any(|(actual, supplied)| actual.to_bits() != supplied.to_bits())
178 {
179 return Err(invalid(
180 "Emitter",
181 "forward-plane direction must already be normalized",
182 ));
183 }
184 Ok(Emitter::ForwardPlane {
185 id: self.id.clone(),
186 through: anchor,
187 direction,
188 amplitude,
189 phase,
190 })
191 } else {
192 Err(invalid(
193 "Emitter",
194 format!("unknown emitter kind {}", self.kind),
195 ))
196 }
197 }
198}
199
200impl RecordCitizenSpec for EmitterDescriptor {
201 const FIELDS: &'static [&'static str] = &[
202 "kind",
203 "id",
204 "anchor-m",
205 "direction",
206 "amplitude",
207 "phase-rad",
208 ];
209
210 fn encode_fields(&self, _cx: &mut Cx) -> Result<Vec<sim_kernel::Expr>> {
211 Ok(vec![
212 encode_field(&self.kind),
213 encode_field(&self.id),
214 encode_field(&self.anchor_m),
215 encode_field(&self.direction),
216 encode_field(&self.amplitude),
217 encode_field(&self.phase_rad),
218 ])
219 }
220
221 fn decode_fields(cx: &mut Cx, fields: Vec<Value>) -> Result<Self> {
222 let mut fields = fields.into_iter();
223 let value = Self {
224 kind: next_field(cx, &mut fields, "kind")?,
225 id: next_field(cx, &mut fields, "id")?,
226 anchor_m: next_field(cx, &mut fields, "anchor-m")?,
227 direction: next_field(cx, &mut fields, "direction")?,
228 amplitude: next_field(cx, &mut fields, "amplitude")?,
229 phase_rad: next_field(cx, &mut fields, "phase-rad")?,
230 };
231 value.validate()?;
232 Ok(value)
233 }
234
235 fn example() -> Self {
236 Self::from_emitter(&Emitter::Point {
237 id: "source".to_owned(),
238 position: Point3M::from_metres(0.0, 0.0, 0.0).expect("example point"),
239 amplitude_at_reference: FieldAmplitude::new(1.0).expect("example amplitude"),
240 phase: Radians::new(0.0).expect("example phase"),
241 })
242 }
243
244 fn validate(&self) -> Result<()> {
245 self.to_emitter().map(|_| ())
246 }
247}
248
249impl_record_citizen!(EmitterDescriptor, "interference/Emitter", 6);
250
251#[derive(Clone, Debug, PartialEq)]
253pub struct ProblemDescriptor {
254 pub frequency_hz: f64,
256 pub medium: MediumDescriptor,
258 pub emitters: Vec<EmitterDescriptor>,
260 pub singularity_radius_m: f64,
262}
263
264impl ProblemDescriptor {
265 pub fn from_problem(problem: &InterferenceProblem) -> Self {
267 Self {
268 frequency_hz: problem.frequency.get(),
269 medium: MediumDescriptor::from_medium(problem.medium),
270 emitters: problem
271 .sources
272 .iter()
273 .map(EmitterDescriptor::from_emitter)
274 .collect(),
275 singularity_radius_m: problem.singularity_radius.get(),
276 }
277 }
278
279 pub fn to_problem(&self) -> Result<InterferenceProblem> {
281 let frequency = Hertz::new(self.frequency_hz)
282 .map_err(|error| invalid("Problem", format!("{error:?}")))?;
283 let sources = self
284 .emitters
285 .iter()
286 .map(EmitterDescriptor::to_emitter)
287 .collect::<Result<Vec<_>>>()?;
288 let source_set =
289 SourceSet::new(sources).map_err(|error| invalid("Problem", format!("{error:?}")))?;
290 let canonical_ids = source_set.iter().map(Emitter::id);
291 if !canonical_ids.eq(self.emitters.iter().map(|emitter| emitter.id.as_str())) {
292 return Err(invalid(
293 "Problem",
294 "emitters must be in canonical stable-id order",
295 ));
296 }
297 let radius = PositiveMetres::new(self.singularity_radius_m)
298 .map_err(|error| invalid("Problem", format!("{error:?}")))?;
299 Ok(InterferenceProblem::new(
300 frequency,
301 self.medium.to_medium()?,
302 source_set,
303 radius,
304 ))
305 }
306}
307
308impl RecordCitizenSpec for ProblemDescriptor {
309 const FIELDS: &'static [&'static str] =
310 &["frequency-hz", "medium", "emitters", "singularity-radius-m"];
311
312 fn encode_fields(&self, cx: &mut Cx) -> Result<Vec<sim_kernel::Expr>> {
313 Ok(vec![
314 encode_field(&self.frequency_hz),
315 encode_record(cx, &self.medium)?,
316 encode_records(cx, &self.emitters)?,
317 encode_field(&self.singularity_radius_m),
318 ])
319 }
320
321 fn decode_fields(cx: &mut Cx, fields: Vec<Value>) -> Result<Self> {
322 let mut fields = fields.into_iter();
323 let frequency_hz = next_field(cx, &mut fields, "frequency-hz")?;
324 let medium = decode_record(
325 cx,
326 fields
327 .next()
328 .ok_or_else(|| invalid("Problem", "missing medium"))?,
329 "medium",
330 )?;
331 let emitters = decode_records(
332 cx,
333 fields
334 .next()
335 .ok_or_else(|| invalid("Problem", "missing emitters"))?,
336 "emitters",
337 )?;
338 let singularity_radius_m = next_field(cx, &mut fields, "singularity-radius-m")?;
339 let value = Self {
340 frequency_hz,
341 medium,
342 emitters,
343 singularity_radius_m,
344 };
345 value.validate()?;
346 Ok(value)
347 }
348
349 fn example() -> Self {
350 sample_problem()
351 }
352
353 fn validate(&self) -> Result<()> {
354 self.to_problem().map(|_| ())
355 }
356}
357
358impl_record_citizen!(ProblemDescriptor, "interference/Problem", 4);
359
360#[derive(Clone, Debug, PartialEq)]
362pub struct PlaneDescriptor {
363 pub origin_m: Vec<f64>,
365 pub u_axis: Vec<f64>,
367 pub v_axis: Vec<f64>,
369 pub extent_u_m: f64,
371 pub extent_v_m: f64,
373 pub rows: usize,
375 pub columns: usize,
377}
378
379impl PlaneDescriptor {
380 pub fn from_plane(plane: SamplingPlane) -> Self {
382 Self {
383 origin_m: plane.origin().coordinates_metres().to_vec(),
384 u_axis: plane.u_axis().components().to_vec(),
385 v_axis: plane.v_axis().components().to_vec(),
386 extent_u_m: plane.extent_u().get(),
387 extent_v_m: plane.extent_v().get(),
388 rows: plane.rows(),
389 columns: plane.columns(),
390 }
391 }
392
393 pub fn to_plane(&self) -> Result<SamplingPlane> {
395 let [x, y, z] = vector3(&self.origin_m, "plane origin")?;
396 let [ux, uy, uz] = vector3(&self.u_axis, "plane u-axis")?;
397 let [vx, vy, vz] = vector3(&self.v_axis, "plane v-axis")?;
398 let u_axis = canonical_unit_vector(ux, uy, uz, "u-axis")?;
399 let v_axis = canonical_unit_vector(vx, vy, vz, "v-axis")?;
400 SamplingPlane::new(
401 Point3M::from_metres(x, y, z)
402 .map_err(|error| invalid("Plane", format!("{error:?}")))?,
403 u_axis,
404 v_axis,
405 PositiveMetres::new(self.extent_u_m)
406 .map_err(|error| invalid("Plane", format!("{error:?}")))?,
407 PositiveMetres::new(self.extent_v_m)
408 .map_err(|error| invalid("Plane", format!("{error:?}")))?,
409 self.rows,
410 self.columns,
411 )
412 .map_err(|error| invalid("Plane", format!("{error:?}")))
413 }
414}
415
416impl RecordCitizenSpec for PlaneDescriptor {
417 const FIELDS: &'static [&'static str] = &[
418 "origin-m",
419 "u-axis",
420 "v-axis",
421 "extent-u-m",
422 "extent-v-m",
423 "rows",
424 "columns",
425 ];
426
427 fn encode_fields(&self, _cx: &mut Cx) -> Result<Vec<sim_kernel::Expr>> {
428 Ok(vec![
429 encode_field(&self.origin_m),
430 encode_field(&self.u_axis),
431 encode_field(&self.v_axis),
432 encode_field(&self.extent_u_m),
433 encode_field(&self.extent_v_m),
434 encode_field(&self.rows),
435 encode_field(&self.columns),
436 ])
437 }
438
439 fn decode_fields(cx: &mut Cx, fields: Vec<Value>) -> Result<Self> {
440 let mut fields = fields.into_iter();
441 let value = Self {
442 origin_m: next_field(cx, &mut fields, "origin-m")?,
443 u_axis: next_field(cx, &mut fields, "u-axis")?,
444 v_axis: next_field(cx, &mut fields, "v-axis")?,
445 extent_u_m: next_field(cx, &mut fields, "extent-u-m")?,
446 extent_v_m: next_field(cx, &mut fields, "extent-v-m")?,
447 rows: next_field(cx, &mut fields, "rows")?,
448 columns: next_field(cx, &mut fields, "columns")?,
449 };
450 value.validate()?;
451 Ok(value)
452 }
453
454 fn example() -> Self {
455 sample_plane()
456 }
457
458 fn validate(&self) -> Result<()> {
459 self.to_plane().map(|_| ())
460 }
461}
462
463impl_record_citizen!(PlaneDescriptor, "interference/Plane", 7);
464
465pub(crate) fn sample_problem() -> ProblemDescriptor {
466 let medium = MediumDescriptor::new(343.0, 0.0).expect("example medium");
467 let emitter = <EmitterDescriptor as RecordCitizenSpec>::example();
468 let value = ProblemDescriptor {
469 frequency_hz: 1_000.0,
470 medium,
471 emitters: vec![emitter],
472 singularity_radius_m: 0.01,
473 };
474 value.validate().expect("example problem");
475 value
476}
477
478pub(crate) fn sample_plane() -> PlaneDescriptor {
479 let value = PlaneDescriptor {
480 origin_m: vec![0.0, 0.0, 1.0],
481 u_axis: vec![1.0, 0.0, 0.0],
482 v_axis: vec![0.0, 1.0, 0.0],
483 extent_u_m: 0.1,
484 extent_v_m: 0.1,
485 rows: 2,
486 columns: 2,
487 };
488 value.validate().expect("example plane");
489 value
490}
491
492fn vector3(values: &[f64], field: &str) -> Result<[f64; 3]> {
493 let [x, y, z] = values else {
494 return Err(invalid(field, "expected exactly three components"));
495 };
496 if [*x, *y, *z].iter().any(|value| !value.is_finite()) {
497 return Err(invalid(field, "components must be finite"));
498 }
499 Ok([*x, *y, *z])
500}
501
502fn canonical_unit_vector(x: f64, y: f64, z: f64, field: &str) -> Result<UnitVector3> {
503 let vector = UnitVector3::new(x, y, z)
504 .map_err(|error| invalid("Plane", format!("{field}: {error:?}")))?;
505 if vector
506 .components()
507 .iter()
508 .zip([x, y, z])
509 .any(|(actual, supplied)| actual.to_bits() != supplied.to_bits())
510 {
511 return Err(invalid(
512 "Plane",
513 format!("{field} must already be normalized"),
514 ));
515 }
516 Ok(vector)
517}
518
519fn point_kind() -> Symbol {
520 Symbol::qualified("interference", "point")
521}
522
523fn forward_plane_kind() -> Symbol {
524 Symbol::qualified("interference", "forward-plane")
525}