1use std::sync::Arc;
4
5use sim_kernel::{
6 Consistency, Cx, Error, EvalFabric, EvalMode, EvalReply, EvalRequest, Expr, Result, Symbol,
7};
8use sim_lib_numbers_tensor::{TensorExecutor, TensorSite, tensor_value_ref};
9
10use crate::model::{ModeledComputeProfile, ModeledComputeSnapshot, ModeledTensorExecutor};
11use crate::site::compute_model_site_symbol;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum ResidentOdeKind {
16 Fixed,
18 Adaptive,
20}
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum ResidentRhsLowering {
25 TensorExpression,
27 NonLowerable,
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub struct ResidentOdePlan {
34 kind: ResidentOdeKind,
35 state_shape: Vec<usize>,
36 dtype: Symbol,
37}
38
39impl ResidentOdePlan {
40 pub fn fixed(state_shape: Vec<usize>, dtype: Symbol, rhs: ResidentRhsLowering) -> Result<Self> {
42 Self::new(ResidentOdeKind::Fixed, state_shape, dtype, rhs)
43 }
44
45 pub fn adaptive(
47 state_shape: Vec<usize>,
48 dtype: Symbol,
49 rhs: ResidentRhsLowering,
50 ) -> Result<Self> {
51 Self::new(ResidentOdeKind::Adaptive, state_shape, dtype, rhs)
52 }
53
54 fn new(
55 kind: ResidentOdeKind,
56 state_shape: Vec<usize>,
57 dtype: Symbol,
58 rhs: ResidentRhsLowering,
59 ) -> Result<Self> {
60 if state_shape.iter().product::<usize>() < 2 {
61 return Err(Error::Eval(
62 "resident ODE declines scalar or too-small tensor state".to_owned(),
63 ));
64 }
65 if rhs != ResidentRhsLowering::TensorExpression {
66 return Err(Error::Eval(
67 "resident ODE requires a lowerable tensor RHS expression".to_owned(),
68 ));
69 }
70 Ok(Self {
71 kind,
72 state_shape,
73 dtype,
74 })
75 }
76
77 pub fn kind(&self) -> ResidentOdeKind {
79 self.kind
80 }
81
82 pub fn state_shape(&self) -> &[usize] {
84 &self.state_shape
85 }
86
87 pub fn dtype(&self) -> &Symbol {
89 &self.dtype
90 }
91}
92
93#[derive(Clone)]
95pub struct ResidentOdeExecution {
96 pub reply: EvalReply,
98 pub snapshot: ModeledComputeSnapshot,
100 pub readbacks: usize,
102 pub final_flush_accepted: usize,
104}
105
106#[derive(Clone)]
108pub struct ResidentOdeExecutor {
109 executor: ModeledTensorExecutor,
110}
111
112impl ResidentOdeExecutor {
113 pub fn new(mut profile: ModeledComputeProfile) -> Self {
115 profile.auto_flush_batches = true;
116 Self {
117 executor: ModeledTensorExecutor::new(profile),
118 }
119 }
120
121 pub fn tensor_executor(&self) -> &ModeledTensorExecutor {
123 &self.executor
124 }
125
126 pub fn execute(
128 &self,
129 cx: &mut Cx,
130 plan: &ResidentOdePlan,
131 expr: Expr,
132 ) -> Result<ResidentOdeExecution> {
133 let before = self.executor.snapshot();
134 let site = TensorSite::new(
135 compute_model_site_symbol(),
136 Arc::new(self.executor.clone()) as Arc<dyn TensorExecutor>,
137 Vec::new(),
138 );
139 let request = eval_request(expr);
140 let reply = if plan.kind() == ResidentOdeKind::Fixed {
141 self.executor.begin_internal_materialization();
142 let reply = site.realize(cx, request);
143 self.executor.end_internal_materialization();
144 reply?
145 } else {
146 site.realize(cx, request)?
147 };
148 let value = if let Some(table) = reply.value.object().as_table_impl() {
149 table.get(cx, Symbol::new("value"))?
150 } else {
151 reply.value.clone()
152 };
153 let tensor = tensor_value_ref(&value).ok_or_else(|| {
154 Error::Eval("resident ODE result did not produce tensor state".to_owned())
155 })?;
156 if tensor.shape() != plan.state_shape() || tensor.dtype() != plan.dtype() {
157 return Err(Error::Eval(format!(
158 "resident ODE result shape/dtype {:?}/{} did not match plan {:?}/{}",
159 tensor.shape(),
160 tensor.dtype(),
161 plan.state_shape(),
162 plan.dtype()
163 )));
164 }
165 let final_flush_accepted = self.executor.flush().map_err(Error::from)?.accepted;
166 let snapshot = self.executor.snapshot();
167 Ok(ResidentOdeExecution {
168 reply,
169 readbacks: snapshot.readbacks.saturating_sub(before.readbacks),
170 snapshot,
171 final_flush_accepted,
172 })
173 }
174}
175
176impl Default for ResidentOdeExecutor {
177 fn default() -> Self {
178 Self::new(ModeledComputeProfile::default())
179 }
180}
181
182fn eval_request(expr: Expr) -> EvalRequest {
183 EvalRequest {
184 expr,
185 result_shape: None,
186 required_capabilities: Vec::new(),
187 deadline: None,
188 consistency: Consistency::LocalFirst,
189 mode: EvalMode::Eval,
190 answer_limit: None,
191 stream_buffer: None,
192 stream: false,
193 trace: false,
194 }
195}