Skip to main content

sim_lib_interference_core/
emitter.rs

1//! Coherent scalar emitters and their canonical source collection.
2
3use crate::{FieldAmplitude, InterferenceError, Point3M, Radians, UnitVector3};
4
5/// A coherent scalar-wave emitter.
6///
7/// Emitters deliberately carry no frequency. One frequency is owned by the
8/// complete interference problem so unlike phasors cannot be mixed.
9#[derive(Clone, Debug, PartialEq)]
10pub enum Emitter {
11    /// An outgoing spherical point source.
12    Point {
13        /// Stable source identity.
14        id: String,
15        /// Source position.
16        position: Point3M,
17        /// Field amplitude stated at
18        /// [`crate::POINT_SOURCE_REFERENCE_DISTANCE_METRES`].
19        amplitude_at_reference: FieldAmplitude,
20        /// Source phase offset.
21        phase: Radians,
22    },
23    /// A plane wave admitted only in its forward half-space.
24    ForwardPlane {
25        /// Stable source identity.
26        id: String,
27        /// A point on the zero-phase plane.
28        through: Point3M,
29        /// Forward propagation direction.
30        direction: UnitVector3,
31        /// Field amplitude on the zero-phase plane.
32        amplitude: FieldAmplitude,
33        /// Source phase offset.
34        phase: Radians,
35    },
36}
37
38impl Emitter {
39    /// Returns the source's stable identity.
40    pub fn id(&self) -> &str {
41        match self {
42            Self::Point { id, .. } | Self::ForwardPlane { id, .. } => id,
43        }
44    }
45}
46
47/// A non-empty collection of emitters in canonical stable-id order.
48#[derive(Clone, Debug, PartialEq)]
49pub struct SourceSet {
50    sources: Vec<Emitter>,
51}
52
53impl SourceSet {
54    /// Validates source identities and sorts the sources by their id bytes.
55    pub fn new(mut sources: Vec<Emitter>) -> Result<Self, InterferenceError> {
56        if sources.is_empty() {
57            return Err(InterferenceError::EmptySourceSet);
58        }
59        if sources.iter().any(|source| source.id().is_empty()) {
60            return Err(InterferenceError::EmptySourceId);
61        }
62
63        sources.sort_unstable_by(|left, right| left.id().cmp(right.id()));
64        if let Some(pair) = sources.windows(2).find(|pair| pair[0].id() == pair[1].id()) {
65            return Err(InterferenceError::DuplicateSourceId {
66                id: pair[0].id().to_owned(),
67            });
68        }
69
70        Ok(Self { sources })
71    }
72
73    /// Returns the number of sources.
74    pub fn len(&self) -> usize {
75        self.sources.len()
76    }
77
78    /// Returns whether the set is empty.
79    ///
80    /// This is always false for a successfully constructed `SourceSet`.
81    pub fn is_empty(&self) -> bool {
82        self.sources.is_empty()
83    }
84
85    /// Returns the canonically ordered sources.
86    pub fn as_slice(&self) -> &[Emitter] {
87        &self.sources
88    }
89
90    /// Iterates over sources in canonical stable-id order.
91    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Emitter> {
92        self.sources.iter()
93    }
94}
95
96impl<'a> IntoIterator for &'a SourceSet {
97    type Item = &'a Emitter;
98    type IntoIter = std::slice::Iter<'a, Emitter>;
99
100    fn into_iter(self) -> Self::IntoIter {
101        self.sources.iter()
102    }
103}