sim_lib_interference_compute/
dense.rs1use std::sync::Arc;
4
5use sim_kernel::{Cx, Symbol};
6use sim_lib_interference_core::{InterferenceProblem, SamplingPlane};
7use sim_lib_numbers_tensor::{
8 CpuTensorExecutor, Tensor, TypedTensorStorage, domains, parse_f32_literal_cell,
9};
10
11use crate::{LoweredTile, LoweringError, LoweringPlan, PhaseBudget, PreflightCheck, TileProfile};
12
13#[derive(Clone, Debug, PartialEq)]
15pub struct DenseExecutionEvidence {
16 executor: Symbol,
17 tiles: usize,
18 max_segments_per_tensor: usize,
19 submissions: usize,
20 phase_limit_rad: f64,
21 predicted_max_abs_psi_rad: f64,
22 observed_max_abs_psi_rad: f64,
23 predicted_geometry_error_rad: f64,
24 predicted_roundoff_error_rad: f64,
25}
26
27impl DenseExecutionEvidence {
28 pub fn executor(&self) -> &Symbol {
30 &self.executor
31 }
32
33 pub fn tiles(&self) -> usize {
35 self.tiles
36 }
37
38 pub fn max_segments_per_tensor(&self) -> usize {
40 self.max_segments_per_tensor
41 }
42
43 pub fn submissions(&self) -> usize {
45 self.submissions
46 }
47
48 pub fn phase_limit_rad(&self) -> f64 {
50 self.phase_limit_rad
51 }
52
53 pub fn predicted_max_abs_psi_rad(&self) -> f64 {
55 self.predicted_max_abs_psi_rad
56 }
57
58 pub fn observed_max_abs_psi_rad(&self) -> f64 {
60 self.observed_max_abs_psi_rad
61 }
62
63 pub fn predicted_geometry_error_rad(&self) -> f64 {
65 self.predicted_geometry_error_rad
66 }
67
68 pub fn predicted_roundoff_error_rad(&self) -> f64 {
70 self.predicted_roundoff_error_rad
71 }
72}
73
74#[derive(Clone, Debug, PartialEq)]
76pub struct DenseF32Field {
77 rows: usize,
78 columns: usize,
79 real: Vec<f32>,
80 imaginary: Vec<f32>,
81 evidence: DenseExecutionEvidence,
82}
83
84impl DenseF32Field {
85 pub fn rows(&self) -> usize {
87 self.rows
88 }
89
90 pub fn columns(&self) -> usize {
92 self.columns
93 }
94
95 pub fn real(&self) -> &[f32] {
97 &self.real
98 }
99
100 pub fn imaginary(&self) -> &[f32] {
102 &self.imaginary
103 }
104
105 pub fn cell(&self, row: usize, column: usize) -> Option<(f32, f32)> {
107 let index = row.checked_mul(self.columns)?.checked_add(column)?;
108 (row < self.rows && column < self.columns)
109 .then(|| (self.real[index], self.imaginary[index]))
110 }
111
112 pub fn evidence(&self) -> &DenseExecutionEvidence {
114 &self.evidence
115 }
116}
117
118pub fn solve_dense_f32_cpu(
123 cx: &mut Cx,
124 problem: &InterferenceProblem,
125 plane: SamplingPlane,
126 phase_budget: PhaseBudget,
127 tile_profile: TileProfile,
128) -> Result<DenseF32Field, LoweringError> {
129 let plan = LoweringPlan::preflight_with_executor(
130 cx,
131 problem,
132 plane,
133 phase_budget,
134 tile_profile,
135 Arc::new(CpuTensorExecutor::new()),
136 )?;
137 let tiles = plan.execute(cx)?;
138 assemble_dense_field(plane, &plan, tiles)
139}
140
141fn assemble_dense_field(
142 plane: SamplingPlane,
143 plan: &LoweringPlan,
144 tiles: Vec<LoweredTile>,
145) -> Result<DenseF32Field, LoweringError> {
146 let cells = plane.cell_count();
147 let mut real = zeroed_component("real", cells)?;
148 let mut imaginary = zeroed_component("imaginary", cells)?;
149 let mut submissions = 0_usize;
150 let mut max_segments_per_tensor = 0_usize;
151
152 for lowered in &tiles {
153 let tile = lowered.tile();
154 max_segments_per_tensor = max_segments_per_tensor.max(tile.segments_per_tensor());
155 submissions = submissions
156 .checked_add(lowered.submissions().len())
157 .ok_or_else(|| {
158 LoweringError::new(
159 PreflightCheck::Execution,
160 "dense submission count overflowed usize",
161 )
162 })?;
163 place_component(
164 "real",
165 plane,
166 tile.row_start(),
167 tile.column_start(),
168 tile.rows(),
169 tile.columns(),
170 tensor_f32_cells(lowered.real())?.as_ref(),
171 &mut real,
172 )?;
173 place_component(
174 "imaginary",
175 plane,
176 tile.row_start(),
177 tile.column_start(),
178 tile.rows(),
179 tile.columns(),
180 tensor_f32_cells(lowered.imaginary())?.as_ref(),
181 &mut imaginary,
182 )?;
183 }
184
185 let estimate = plan.max_phase_estimate();
186 let phase_budget = plan.phase_budget();
187 let evidence = DenseExecutionEvidence {
188 executor: plan.executor_card().symbol.clone(),
189 tiles: tiles.len(),
190 max_segments_per_tensor,
191 submissions,
192 phase_limit_rad: phase_budget.max_abs_residual_phase_rad,
193 predicted_max_abs_psi_rad: plan.tile_plan().conservative_max_abs_phase_rad(),
194 observed_max_abs_psi_rad: estimate.max_abs_residual_phase_rad,
195 predicted_geometry_error_rad: estimate.max_predicted_geometry_error_rad,
196 predicted_roundoff_error_rad: estimate.max_predicted_roundoff_error_rad,
197 };
198 Ok(DenseF32Field {
199 rows: plane.rows(),
200 columns: plane.columns(),
201 real,
202 imaginary,
203 evidence,
204 })
205}
206
207fn zeroed_component(name: &'static str, cells: usize) -> Result<Vec<f32>, LoweringError> {
208 let mut values = Vec::new();
209 values.try_reserve_exact(cells).map_err(|_| {
210 LoweringError::new(
211 PreflightCheck::ResultAllocation,
212 format!("cannot reserve {cells} dense {name} cells"),
213 )
214 })?;
215 values.resize(cells, 0.0);
216 Ok(values)
217}
218
219#[allow(clippy::too_many_arguments)]
220fn place_component(
221 name: &'static str,
222 plane: SamplingPlane,
223 row_start: usize,
224 column_start: usize,
225 rows: usize,
226 columns: usize,
227 source: &[f32],
228 destination: &mut [f32],
229) -> Result<(), LoweringError> {
230 let expected = rows.checked_mul(columns).ok_or_else(|| {
231 LoweringError::new(
232 PreflightCheck::Shape,
233 format!("{name} tile shape overflowed usize"),
234 )
235 })?;
236 if source.len() != expected {
237 return Err(LoweringError::new(
238 PreflightCheck::Execution,
239 format!(
240 "{name} tile has {} cells for shape [{rows}, {columns}]",
241 source.len()
242 ),
243 ));
244 }
245 for (local_row, row_cells) in source.chunks_exact(columns).enumerate() {
246 let global_row = row_start + local_row;
247 let start = global_row
248 .checked_mul(plane.columns())
249 .and_then(|offset| offset.checked_add(column_start))
250 .ok_or_else(|| {
251 LoweringError::new(
252 PreflightCheck::Shape,
253 format!("{name} tile destination offset overflowed usize"),
254 )
255 })?;
256 let end = start.checked_add(columns).ok_or_else(|| {
257 LoweringError::new(
258 PreflightCheck::Shape,
259 format!("{name} tile destination end overflowed usize"),
260 )
261 })?;
262 let destination_row = destination.get_mut(start..end).ok_or_else(|| {
263 LoweringError::new(
264 PreflightCheck::Shape,
265 format!("{name} tile lies outside the admitted plane"),
266 )
267 })?;
268 destination_row.copy_from_slice(row_cells);
269 }
270 Ok(())
271}
272
273fn tensor_f32_cells(tensor: &Tensor) -> Result<Arc<[f32]>, LoweringError> {
274 if tensor.dtype() != &domains::f32() {
275 return Err(LoweringError::new(
276 PreflightCheck::Execution,
277 format!(
278 "dense component dtype is {}, expected numbers/f32",
279 tensor.dtype()
280 ),
281 ));
282 }
283 let storage = tensor.materialize().map_err(|error| {
284 LoweringError::new(
285 PreflightCheck::Execution,
286 format!("cannot materialize dense component: {error}"),
287 )
288 })?;
289 let cells = if let Some(typed) = storage.as_any().downcast_ref::<TypedTensorStorage<f32>>() {
290 typed.cells()
291 } else {
292 (0..storage.len())
293 .map(|index| {
294 let value = storage.cell(index).map_err(|error| {
295 LoweringError::new(
296 PreflightCheck::Execution,
297 format!("cannot observe dense component cell {index}: {error}"),
298 )
299 })?;
300 parse_f32_literal_cell(&value).ok_or_else(|| {
301 LoweringError::new(
302 PreflightCheck::Execution,
303 format!("dense component cell {index} is not a canonical f32"),
304 )
305 })
306 })
307 .collect::<Result<Vec<_>, _>>()?
308 .into()
309 };
310 if let Some((index, value)) = cells
311 .iter()
312 .copied()
313 .enumerate()
314 .find(|(_, value)| !value.is_finite())
315 {
316 return Err(LoweringError::new(
317 PreflightCheck::Execution,
318 format!("dense component cell {index} is non-finite: {value}"),
319 ));
320 }
321 Ok(cells)
322}