Skip to main content

sim_lib_interference_core/
budget.rs

1//! Checked work accounting and allocation-free request admission.
2
3use std::fmt;
4
5use crate::{
6    InterferenceError, InterferenceProblem, SamplingCertificate, SamplingPlane, SamplingPolicy,
7    SamplingThresholds,
8};
9
10const PHASOR_RESULT_BYTES_PER_CELL: u64 = 2 * size_of::<f64>() as u64;
11const CERTIFICATE_STENCIL_POINTS_PER_CELL: u64 = 7;
12
13/// A separately limited work or storage dimension.
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum WorkMetric {
16    /// Number of output cells.
17    Cells,
18    /// Number of source Green-function evaluations.
19    EmitterEvaluations,
20    /// Peak bytes reserved for host phasor components.
21    HostBytes,
22    /// Bytes in the two-component result.
23    ResultBytes,
24    /// Seven-point certificate stencil evaluations.
25    CertificateStencilWork,
26}
27
28impl fmt::Display for WorkMetric {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        formatter.write_str(match self {
31            Self::Cells => "cells",
32            Self::EmitterEvaluations => "emitter-evaluations",
33            Self::HostBytes => "host-bytes",
34            Self::ResultBytes => "result-bytes",
35            Self::CertificateStencilWork => "certificate-stencil-work",
36        })
37    }
38}
39
40/// Allocation and evaluation counts known before a field solve starts.
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub struct WorkEstimate {
43    /// Number of sampling cells.
44    pub cells: u64,
45    /// Number of coherent emitters.
46    pub emitters: u64,
47    /// Product of cells and emitters.
48    pub emitter_evaluations: u64,
49    /// Peak bytes for the reference host phasor result.
50    pub host_bytes: u64,
51    /// Bytes in the two `f64` result components.
52    pub result_bytes: u64,
53    /// Work for a seven-point certificate stencil at every cell.
54    pub certificate_stencil_work: u64,
55}
56
57impl WorkEstimate {
58    /// Computes every work dimension with checked integer arithmetic.
59    ///
60    /// This function allocates nothing and is also useful for testing or
61    /// admitting decoded counts before a [`SamplingPlane`] is constructed.
62    pub fn new(cells: u64, emitters: u64) -> Result<Self, InterferenceError> {
63        let emitter_evaluations = checked_product(cells, emitters, WorkMetric::EmitterEvaluations)?;
64        let result_bytes =
65            checked_product(cells, PHASOR_RESULT_BYTES_PER_CELL, WorkMetric::ResultBytes)?;
66        let host_bytes = result_bytes;
67        let certificate_stencil_work = checked_product(
68            cells,
69            CERTIFICATE_STENCIL_POINTS_PER_CELL,
70            WorkMetric::CertificateStencilWork,
71        )?;
72        Ok(Self {
73            cells,
74            emitters,
75            emitter_evaluations,
76            host_bytes,
77            result_bytes,
78            certificate_stencil_work,
79        })
80    }
81
82    /// Computes a request estimate from checked domain records.
83    pub fn for_request(
84        problem: &InterferenceProblem,
85        plane: &SamplingPlane,
86    ) -> Result<Self, InterferenceError> {
87        let cells = u64::try_from(plane.cell_count()).map_err(|_| {
88            InterferenceError::WorkEstimateOverflow {
89                metric: WorkMetric::Cells,
90            }
91        })?;
92        let emitters = u64::try_from(problem.sources.len()).map_err(|_| {
93            InterferenceError::WorkEstimateOverflow {
94                metric: WorkMetric::EmitterEvaluations,
95            }
96        })?;
97        Self::new(cells, emitters)
98    }
99}
100
101/// Explicit upper bounds for every preflight work dimension.
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
103pub struct WorkBudget {
104    /// Maximum output cells.
105    pub max_cells: u64,
106    /// Maximum source Green-function evaluations.
107    pub max_emitter_evaluations: u64,
108    /// Maximum host bytes.
109    pub max_host_bytes: u64,
110    /// Maximum result bytes.
111    pub max_result_bytes: u64,
112    /// Maximum certificate stencil evaluations.
113    pub max_certificate_stencil_work: u64,
114}
115
116impl WorkBudget {
117    /// Admits an estimate or names the first estimate and limit exceeded.
118    ///
119    /// Checks follow the field order, making diagnostics deterministic.
120    pub fn admit(self, estimate: &WorkEstimate) -> Result<(), InterferenceError> {
121        for (metric, requested, limit) in [
122            (WorkMetric::Cells, estimate.cells, self.max_cells),
123            (
124                WorkMetric::EmitterEvaluations,
125                estimate.emitter_evaluations,
126                self.max_emitter_evaluations,
127            ),
128            (
129                WorkMetric::HostBytes,
130                estimate.host_bytes,
131                self.max_host_bytes,
132            ),
133            (
134                WorkMetric::ResultBytes,
135                estimate.result_bytes,
136                self.max_result_bytes,
137            ),
138            (
139                WorkMetric::CertificateStencilWork,
140                estimate.certificate_stencil_work,
141                self.max_certificate_stencil_work,
142            ),
143        ] {
144            if requested > limit {
145                return Err(InterferenceError::WorkBudgetExceeded {
146                    metric,
147                    estimate: requested,
148                    limit,
149                });
150            }
151        }
152        Ok(())
153    }
154}
155
156impl Default for WorkBudget {
157    fn default() -> Self {
158        const MAX_CELLS: u64 = 4_096 * 4_096;
159        Self {
160            max_cells: MAX_CELLS,
161            max_emitter_evaluations: 2_000_000_000,
162            max_host_bytes: 1 << 30,
163            max_result_bytes: 1 << 29,
164            max_certificate_stencil_work: MAX_CELLS * CERTIFICATE_STENCIL_POINTS_PER_CELL,
165        }
166    }
167}
168
169/// Successful sampling and work evidence obtained before field allocation.
170#[derive(Clone, Copy, Debug, PartialEq)]
171pub struct RequestPreflight {
172    /// Policy applied to the sampling certificate.
173    pub sampling_policy: SamplingPolicy,
174    /// Physical sampling measurements and thresholds.
175    pub sampling_certificate: SamplingCertificate,
176    /// Checked work and storage counts.
177    pub work_estimate: WorkEstimate,
178}
179
180impl RequestPreflight {
181    /// Classifies and budgets one request without allocating field storage.
182    pub fn admit(
183        problem: &InterferenceProblem,
184        plane: &SamplingPlane,
185        sampling_policy: SamplingPolicy,
186        sampling_thresholds: SamplingThresholds,
187        work_budget: WorkBudget,
188    ) -> Result<Self, InterferenceError> {
189        let work_estimate = WorkEstimate::for_request(problem, plane)?;
190        work_budget.admit(&work_estimate)?;
191        let sampling_certificate =
192            SamplingCertificate::measure_with_thresholds(problem, plane, sampling_thresholds)?;
193        sampling_policy.admit(&sampling_certificate)?;
194        Ok(Self {
195            sampling_policy,
196            sampling_certificate,
197            work_estimate,
198        })
199    }
200}
201
202fn checked_product(left: u64, right: u64, metric: WorkMetric) -> Result<u64, InterferenceError> {
203    left.checked_mul(right)
204        .ok_or(InterferenceError::WorkEstimateOverflow { metric })
205}