Skip to main content

sim_lib_interference_runtime/
solver.rs

1//! Late-bound interference-study solver contract and CPU reference provider.
2
3use std::{any::Any, sync::Arc};
4
5use sim_kernel::{Cx, DefaultFactory, Error, Factory, Object, Result, Symbol, Value};
6use sim_lib_interference_core::{
7    InterferenceProblem, SamplingPlane, SamplingPolicy, SamplingThresholds, WorkBudget,
8};
9use sim_lib_interference_solve::ReferencePhasorSolver;
10use sim_lib_numbers_tensor::active_tensor_executor;
11
12use crate::StudyDescriptor;
13
14/// Complete runtime study returned by a [`StudySolver`].
15pub type InterferenceStudy = StudyDescriptor;
16
17/// Immutable, fully checked request passed to a study-solver provider.
18#[derive(Clone, Copy, Debug)]
19pub struct SolveRequest<'a> {
20    problem: &'a InterferenceProblem,
21    plane: &'a SamplingPlane,
22    sampling_policy: SamplingPolicy,
23    sampling_thresholds: SamplingThresholds,
24    work_budget: WorkBudget,
25}
26
27impl<'a> SolveRequest<'a> {
28    /// Builds a request from checked domain values and explicit admission policy.
29    pub fn new(
30        problem: &'a InterferenceProblem,
31        plane: &'a SamplingPlane,
32        sampling_policy: SamplingPolicy,
33        sampling_thresholds: SamplingThresholds,
34        work_budget: WorkBudget,
35    ) -> Self {
36        Self {
37            problem,
38            plane,
39            sampling_policy,
40            sampling_thresholds,
41            work_budget,
42        }
43    }
44
45    /// Returns the coherent problem to solve.
46    pub fn problem(self) -> &'a InterferenceProblem {
47        self.problem
48    }
49
50    /// Returns the exact physical sampling plane.
51    pub fn plane(self) -> &'a SamplingPlane {
52        self.plane
53    }
54
55    /// Returns the sampling-admission policy.
56    pub fn sampling_policy(self) -> SamplingPolicy {
57        self.sampling_policy
58    }
59
60    /// Returns the sampling-classification thresholds.
61    pub fn sampling_thresholds(self) -> SamplingThresholds {
62        self.sampling_thresholds
63    }
64
65    /// Returns the complete pre-allocation work budget.
66    pub fn work_budget(self) -> WorkBudget {
67        self.work_budget
68    }
69}
70
71/// Narrow provider seam for producing one complete interference study.
72pub trait StudySolver: Send + Sync + 'static {
73    /// Solves one admitted request or returns no partial study.
74    fn solve(&self, cx: &mut Cx, request: &SolveRequest<'_>) -> Result<InterferenceStudy>;
75}
76
77/// Runtime value carrying one loadable [`StudySolver`].
78#[derive(Clone)]
79pub struct SolverProvider {
80    solver: Arc<dyn StudySolver>,
81}
82
83impl SolverProvider {
84    /// Wraps a loadable study solver.
85    pub fn new(solver: Arc<dyn StudySolver>) -> Self {
86        Self { solver }
87    }
88
89    /// Returns the wrapped solver.
90    pub fn solver(&self) -> Arc<dyn StudySolver> {
91        self.solver.clone()
92    }
93
94    /// Boxes this provider as a kernel value.
95    pub fn into_value(self) -> Result<Value> {
96        DefaultFactory.opaque(Arc::new(self))
97    }
98}
99
100impl Object for SolverProvider {
101    fn display(&self, _cx: &mut Cx) -> Result<String> {
102        Ok("#<interference-study-solver>".to_owned())
103    }
104
105    fn as_any(&self) -> &dyn Any {
106        self
107    }
108}
109
110impl sim_kernel::ObjectCompat for SolverProvider {}
111
112/// Stable binding and registry symbol for the active study solver.
113pub fn study_solver_symbol() -> Symbol {
114    Symbol::qualified("interference", "study-solver")
115}
116
117/// Stable registry symbol for the solver selected by an active Tensor executor.
118///
119/// Provider libraries export this value without replacing the deterministic
120/// registry default at [`study_solver_symbol`].
121pub fn tensor_study_solver_symbol() -> Symbol {
122    Symbol::qualified("interference", "tensor-study-solver")
123}
124
125/// Resolves an explicit child solver, an active Tensor solver, then the default.
126///
127/// A present but malformed child binding is rejected rather than silently
128/// bypassed. EvalFabric sites can therefore override the registry default
129/// without changing the solve expression. A TensorSite child selects the
130/// registered Tensor solver only while its executor binding is active.
131pub fn resolve_study_solver(cx: &Cx) -> Result<Arc<dyn StudySolver>> {
132    if let Some(value) = cx.env().get(&study_solver_symbol()) {
133        return solver_from_value(&value, "active environment");
134    }
135    if active_tensor_executor(cx).is_some()
136        && let Some(value) = cx.registry().value_by_symbol(&tensor_study_solver_symbol())
137    {
138        return solver_from_value(value, "Tensor solver registry");
139    }
140    let value = cx
141        .registry()
142        .value_by_symbol(&study_solver_symbol())
143        .ok_or_else(|| Error::Eval(format!("no {} is installed", study_solver_symbol())))?;
144    solver_from_value(value, "runtime registry")
145}
146
147fn solver_from_value(value: &Value, source: &str) -> Result<Arc<dyn StudySolver>> {
148    value
149        .object()
150        .downcast_ref::<SolverProvider>()
151        .map(SolverProvider::solver)
152        .ok_or_else(|| {
153            Error::Eval(format!(
154                "{} in the {source} is not a SolverProvider",
155                study_solver_symbol()
156            ))
157        })
158}
159
160/// Deterministic CPU `f64` implementation of [`StudySolver`].
161#[derive(Clone, Copy, Debug, Default)]
162pub struct ReferenceStudySolver;
163
164impl StudySolver for ReferenceStudySolver {
165    fn solve(&self, _cx: &mut Cx, request: &SolveRequest<'_>) -> Result<InterferenceStudy> {
166        let solver = ReferencePhasorSolver::new(
167            request.sampling_policy(),
168            request.sampling_thresholds(),
169            request.work_budget(),
170        );
171        let (field, evidence) =
172            solver
173                .solve(request.problem(), request.plane())
174                .map_err(|error| {
175                    Error::Eval(format!("interference reference solve failed: {error}"))
176                })?;
177        StudyDescriptor::from_reference(request.problem(), *request.plane(), field, &evidence)
178    }
179}