Skip to main content

sim_lib_interference_runtime/
sampling.rs

1//! Fail-closed reconstruction for sampling certificate Citizens.
2
3use sim_kernel::{Cx, Result, Value};
4use sim_lib_interference_core::{SamplingCertificate, SamplingThresholds, SamplingVerdict};
5
6use crate::{
7    SamplingCertificateDescriptor,
8    citizen::{RecordCitizenSpec, encode_field, invalid, next_field},
9    evidence::{sample_sampling, verdict_symbol},
10};
11
12impl SamplingCertificateDescriptor {
13    /// Reconstructs and revalidates all sampling measurements.
14    pub fn to_certificate(&self) -> Result<SamplingCertificate> {
15        let thresholds = SamplingThresholds::new(
16            self.resolved_min_samples_per_wavelength,
17            self.marginal_min_samples_per_wavelength,
18            self.resolved_max_envelope_fraction_per_cell,
19            self.marginal_max_envelope_fraction_per_cell,
20        )
21        .map_err(|error| invalid("SamplingCertificate", format!("{error:?}")))?;
22        for (name, value, positive) in [
23            ("wavelength-m", self.wavelength_m, true),
24            (
25                "samples-per-wavelength-u",
26                self.samples_per_wavelength_u,
27                true,
28            ),
29            (
30                "samples-per-wavelength-v",
31                self.samples_per_wavelength_v,
32                true,
33            ),
34            (
35                "samples-per-power-fringe-u",
36                self.samples_per_power_fringe_u,
37                true,
38            ),
39            (
40                "samples-per-power-fringe-v",
41                self.samples_per_power_fringe_v,
42                true,
43            ),
44            (
45                "max-envelope-fraction-per-cell",
46                self.max_envelope_fraction_per_cell,
47                false,
48            ),
49        ] {
50            if !value.is_finite() || (positive && value <= 0.0) || (!positive && value < 0.0) {
51                return Err(invalid(
52                    "SamplingCertificate",
53                    format!("{name} is outside its finite physical range"),
54                ));
55            }
56        }
57        if self
58            .nearest_point_source_distance_m
59            .is_some_and(|distance| !distance.is_finite() || distance <= 0.0)
60        {
61            return Err(invalid(
62                "SamplingCertificate",
63                "nearest point-source distance must be finite and positive",
64            ));
65        }
66        if self.samples_per_power_fringe_u.to_bits()
67            != (self.samples_per_wavelength_u / 2.0).to_bits()
68            || self.samples_per_power_fringe_v.to_bits()
69                != (self.samples_per_wavelength_v / 2.0).to_bits()
70        {
71            return Err(invalid(
72                "SamplingCertificate",
73                "power-fringe samples must be exactly half the carrier samples",
74            ));
75        }
76        if self.nearest_point_source_distance_m.is_none()
77            && self.max_envelope_fraction_per_cell != 0.0
78        {
79            return Err(invalid(
80                "SamplingCertificate",
81                "plane-only evidence must have zero envelope change",
82            ));
83        }
84        let minimum = self
85            .samples_per_wavelength_u
86            .min(self.samples_per_wavelength_v);
87        let expected = if minimum >= thresholds.resolved_min_samples_per_wavelength
88            && self.max_envelope_fraction_per_cell
89                <= thresholds.resolved_max_envelope_fraction_per_cell
90        {
91            SamplingVerdict::Resolved
92        } else if minimum >= thresholds.marginal_min_samples_per_wavelength
93            && self.max_envelope_fraction_per_cell
94                <= thresholds.marginal_max_envelope_fraction_per_cell
95        {
96            SamplingVerdict::Marginal
97        } else {
98            SamplingVerdict::Aliased
99        };
100        if self.verdict != verdict_symbol(expected) {
101            return Err(invalid(
102                "SamplingCertificate",
103                "verdict does not follow the recorded thresholds and measurements",
104            ));
105        }
106        Ok(SamplingCertificate {
107            thresholds,
108            wavelength_m: self.wavelength_m,
109            samples_per_wavelength_u: self.samples_per_wavelength_u,
110            samples_per_wavelength_v: self.samples_per_wavelength_v,
111            samples_per_power_fringe_u: self.samples_per_power_fringe_u,
112            samples_per_power_fringe_v: self.samples_per_power_fringe_v,
113            nearest_point_source_distance_m: self.nearest_point_source_distance_m,
114            max_envelope_fraction_per_cell: self.max_envelope_fraction_per_cell,
115            verdict: expected,
116        })
117    }
118}
119
120impl RecordCitizenSpec for SamplingCertificateDescriptor {
121    const FIELDS: &'static [&'static str] = &[
122        "resolved-min-samples-per-wavelength",
123        "marginal-min-samples-per-wavelength",
124        "resolved-max-envelope-fraction-per-cell",
125        "marginal-max-envelope-fraction-per-cell",
126        "wavelength-m",
127        "samples-per-wavelength-u",
128        "samples-per-wavelength-v",
129        "samples-per-power-fringe-u",
130        "samples-per-power-fringe-v",
131        "nearest-point-source-distance-m",
132        "max-envelope-fraction-per-cell",
133        "verdict",
134    ];
135
136    fn encode_fields(&self, _cx: &mut Cx) -> Result<Vec<sim_kernel::Expr>> {
137        Ok(vec![
138            encode_field(&self.resolved_min_samples_per_wavelength),
139            encode_field(&self.marginal_min_samples_per_wavelength),
140            encode_field(&self.resolved_max_envelope_fraction_per_cell),
141            encode_field(&self.marginal_max_envelope_fraction_per_cell),
142            encode_field(&self.wavelength_m),
143            encode_field(&self.samples_per_wavelength_u),
144            encode_field(&self.samples_per_wavelength_v),
145            encode_field(&self.samples_per_power_fringe_u),
146            encode_field(&self.samples_per_power_fringe_v),
147            encode_field(&self.nearest_point_source_distance_m),
148            encode_field(&self.max_envelope_fraction_per_cell),
149            encode_field(&self.verdict),
150        ])
151    }
152
153    fn decode_fields(cx: &mut Cx, fields: Vec<Value>) -> Result<Self> {
154        let mut fields = fields.into_iter();
155        let value = Self {
156            resolved_min_samples_per_wavelength: next_field(
157                cx,
158                &mut fields,
159                "resolved-min-samples-per-wavelength",
160            )?,
161            marginal_min_samples_per_wavelength: next_field(
162                cx,
163                &mut fields,
164                "marginal-min-samples-per-wavelength",
165            )?,
166            resolved_max_envelope_fraction_per_cell: next_field(
167                cx,
168                &mut fields,
169                "resolved-max-envelope-fraction-per-cell",
170            )?,
171            marginal_max_envelope_fraction_per_cell: next_field(
172                cx,
173                &mut fields,
174                "marginal-max-envelope-fraction-per-cell",
175            )?,
176            wavelength_m: next_field(cx, &mut fields, "wavelength-m")?,
177            samples_per_wavelength_u: next_field(cx, &mut fields, "samples-per-wavelength-u")?,
178            samples_per_wavelength_v: next_field(cx, &mut fields, "samples-per-wavelength-v")?,
179            samples_per_power_fringe_u: next_field(cx, &mut fields, "samples-per-power-fringe-u")?,
180            samples_per_power_fringe_v: next_field(cx, &mut fields, "samples-per-power-fringe-v")?,
181            nearest_point_source_distance_m: next_field(
182                cx,
183                &mut fields,
184                "nearest-point-source-distance-m",
185            )?,
186            max_envelope_fraction_per_cell: next_field(
187                cx,
188                &mut fields,
189                "max-envelope-fraction-per-cell",
190            )?,
191            verdict: next_field(cx, &mut fields, "verdict")?,
192        };
193        value.validate()?;
194        Ok(value)
195    }
196
197    fn example() -> Self {
198        sample_sampling()
199    }
200
201    fn validate(&self) -> Result<()> {
202        self.to_certificate().map(|_| ())
203    }
204}
205
206impl_record_citizen!(
207    SamplingCertificateDescriptor,
208    "interference/SamplingCertificate",
209    12
210);