sim_lib_interference_runtime/
sampling.rs1use 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 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);