1mod analysis;
21mod forcing;
22mod lanes;
23mod model;
24mod select;
25mod tile;
26mod walk;
27
28pub use forcing::{ForcingSet, ForcingSlot, ParamSlot, RhsError, SlotKind, StateBlock};
29pub use lanes::{LaneInputs, LaneProgram};
30use model::ProgramModel;
31pub use tile::TileProgram;
32
33use super::tape::{CallInputs, ForcingView, TapeCtx, TapeProgram, run_tape_inputs};
34use super::{ArrayCompiled, RhsCall, RhsScratch, RhsStats, evaluate_rhs_with_scratch};
35use crate::flatten::{FlattenedSystem, TermOrigin};
36use crate::problem::{Compiler, EsmProblem, ProgramSource};
37use crate::simulate::SimulateError;
38use ndarray::{ArrayD, IxDyn, ShapeBuilder};
39use std::cell::{Cell, RefCell};
40use std::collections::{BTreeMap, HashMap, HashSet};
41use std::marker::PhantomData;
42use std::ops::RangeInclusive;
43use std::sync::Arc;
44use std::sync::atomic::{AtomicU64, Ordering};
45use walk::{AxisSpec, VarInfo};
46
47#[derive(Debug, Clone, Default, PartialEq)]
50pub struct RhsSelection {
51 pub writes: Option<Vec<String>>,
53 pub terms: Option<Vec<TermOrigin>>,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct AxisCut {
62 pub index_set: String,
63 pub period: Option<i64>,
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Eq)]
70pub struct Decomposition {
71 pub axes: Vec<AxisCut>,
72 pub faces: Vec<(String, String)>,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct TileBox {
80 pub owned: Vec<(String, RangeInclusive<i64>)>,
81 pub storage: Vec<(String, RangeInclusive<i64>)>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
87pub enum LaneKind {
88 Cells { sets: Vec<String> },
90 Columns { sets: Vec<String> },
93}
94
95impl LaneKind {
96 pub fn sets(&self) -> &[String] {
98 match self {
99 LaneKind::Cells { sets } | LaneKind::Columns { sets } => sets,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Default, PartialEq, Eq)]
109pub struct HaloReport {
110 pub reach: BTreeMap<(String, String), (i64, i64)>,
111}
112
113impl HaloReport {
114 pub fn get(&self, variable: &str, index_set: &str) -> Option<(i64, i64)> {
116 self.reach
117 .get(&(variable.to_string(), index_set.to_string()))
118 .copied()
119 }
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub(crate) enum TileInput {
125 U,
127 Aux,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
134pub(crate) struct Block {
135 pub(crate) name: String,
136 pub(crate) input: TileInput,
137 pub(crate) base: usize,
139 pub(crate) index_sets: Vec<String>,
142 pub(crate) storage: Vec<(i64, i64)>,
144 pub(crate) strides: Vec<usize>,
146 pub(crate) owned: Vec<(i64, i64)>,
149}
150
151#[derive(Debug, Clone, Default, PartialEq, Eq)]
154pub(crate) struct Blocks {
155 pub(crate) vars: Vec<Block>,
156 pub(crate) u_len: usize,
157 pub(crate) aux_len: usize,
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct TileBlock {
165 pub name: String,
166 pub sets: Vec<String>,
169 pub storage: Vec<(i64, i64)>,
171 pub owned: Vec<(i64, i64)>,
174 pub base: usize,
176 pub strides: Vec<usize>,
178}
179
180impl TileBlock {
181 pub fn slot(&self, g: &[i64]) -> Option<usize> {
184 let mut off = self.base;
185 for (d, &x) in g.iter().enumerate() {
186 let (lo, hi) = *self.storage.get(d)?;
187 if x < lo || x > hi {
188 return None;
189 }
190 off += (x - lo) as usize * self.strides[d];
191 }
192 Some(off)
193 }
194
195 pub fn len(&self) -> usize {
197 self.storage
198 .iter()
199 .map(|(lo, hi)| (hi - lo + 1).max(0) as usize)
200 .product()
201 }
202
203 pub fn is_empty(&self) -> bool {
205 self.len() == 0
206 }
207}
208
209#[derive(Debug, Clone, Default, PartialEq, Eq)]
212pub struct TileLayout {
213 pub u: Vec<TileBlock>,
214 pub aux: Vec<TileBlock>,
215 pub u_len: usize,
216 pub aux_len: usize,
217}
218
219impl TileLayout {
220 pub fn block(&self, name: &str) -> Option<&TileBlock> {
222 self.u.iter().chain(&self.aux).find(|b| b.name == name)
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct LaneBlock {
232 pub name: String,
233 pub sets: Vec<String>,
234 pub extents: Vec<usize>,
235 pub offset: usize,
236}
237
238#[derive(Debug, Clone, Default, PartialEq, Eq)]
241pub struct LaneLayout {
242 pub u: Vec<LaneBlock>,
243 pub aux: Vec<LaneBlock>,
244 pub sets: Vec<String>,
245 pub capacity: usize,
246 pub u_len: usize,
247 pub aux_len: usize,
248}
249
250impl LaneLayout {
251 pub fn block(&self, name: &str) -> Option<&LaneBlock> {
253 self.u.iter().chain(&self.aux).find(|b| b.name == name)
254 }
255}
256
257impl Blocks {
258 pub(crate) fn var(&self, name: &str) -> Option<&Block> {
260 self.vars.iter().find(|v| v.name == name)
261 }
262
263 pub(crate) fn public(&self) -> TileLayout {
265 let block = |v: &Block| TileBlock {
266 name: v.name.clone(),
267 sets: v.index_sets.clone(),
268 storage: v.storage.clone(),
269 owned: if v.input == TileInput::U {
270 v.owned.clone()
271 } else {
272 Vec::new()
273 },
274 base: v.base,
275 strides: v.strides.clone(),
276 };
277 TileLayout {
278 u: self
279 .vars
280 .iter()
281 .filter(|v| v.input == TileInput::U)
282 .map(block)
283 .collect(),
284 aux: self
285 .vars
286 .iter()
287 .filter(|v| v.input == TileInput::Aux)
288 .map(block)
289 .collect(),
290 u_len: self.u_len,
291 aux_len: self.aux_len,
292 }
293 }
294
295 pub(crate) fn lane_public(&self, sets: &[String], capacity: usize) -> LaneLayout {
298 let block = |v: &Block| LaneBlock {
299 name: v.name.clone(),
300 sets: v.index_sets[1..].to_vec(),
301 extents: v.storage[1..]
302 .iter()
303 .map(|(lo, hi)| (hi - lo + 1).max(0) as usize)
304 .collect(),
305 offset: v.base / capacity.max(1),
306 };
307 LaneLayout {
308 u: self
309 .vars
310 .iter()
311 .filter(|v| v.input == TileInput::U)
312 .map(block)
313 .collect(),
314 aux: self
315 .vars
316 .iter()
317 .filter(|v| v.input == TileInput::Aux)
318 .map(block)
319 .collect(),
320 sets: sets.to_vec(),
321 capacity,
322 u_len: self.u_len,
323 aux_len: self.aux_len,
324 }
325 }
326
327 fn build(
329 entries: Vec<(
330 String,
331 TileInput,
332 Vec<String>,
333 Vec<(i64, i64)>,
334 Vec<(i64, i64)>,
335 )>,
336 ) -> Blocks {
337 let mut vars = Vec::with_capacity(entries.len());
338 let mut u_len = 0usize;
339 let mut aux_len = 0usize;
340 for (name, input, index_sets, storage, owned) in entries {
341 let mut strides = Vec::with_capacity(storage.len());
342 let mut acc = 1usize;
343 for &(lo, hi) in &storage {
344 strides.push(acc);
345 acc *= (hi - lo + 1).max(0) as usize;
346 }
347 let len = if storage.is_empty() { 1 } else { acc };
348 let base = match input {
349 TileInput::U => {
350 u_len += len;
351 u_len - len
352 }
353 TileInput::Aux => {
354 aux_len += len;
355 aux_len - len
356 }
357 };
358 vars.push(Block {
359 name,
360 input,
361 base,
362 index_sets,
363 storage,
364 strides,
365 owned,
366 });
367 }
368 Blocks {
369 vars,
370 u_len,
371 aux_len,
372 }
373 }
374}
375
376pub struct RhsWorkspace {
381 owner: u64,
382 state: Vec<f64>,
383 dy: Vec<f64>,
384 exec: WsExec,
385 _not_sync: PhantomData<Cell<()>>,
386}
387
388enum WsExec {
389 Tape(Box<TapeCtx>),
390 Interp(Box<InterpWs>),
391 Lanes {
392 main: Box<TapeCtx>,
393 pre: Box<TapeCtx>,
394 },
395}
396
397struct InterpWs {
400 scratch: RhsScratch,
401 forcing: RefCell<HashMap<String, ArrayD<f64>>>,
402 epoch: u64,
403}
404
405impl std::fmt::Debug for RhsWorkspace {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 f.debug_struct("RhsWorkspace")
408 .field("state_len", &self.state.len())
409 .finish()
410 }
411}
412
413fn next_program_id() -> u64 {
416 static NEXT: AtomicU64 = AtomicU64::new(1);
417 NEXT.fetch_add(1, Ordering::Relaxed)
418}
419
420fn range_error(code: &'static str, message: String) -> SimulateError {
421 SimulateError::RangeRestriction { code, message }
422}
423
424fn build_error(message: String) -> SimulateError {
425 SimulateError::Compile(crate::compile_error::CompileError::build_err(message))
426}
427
428fn native_only(compiler: Compiler, call: &str) -> SimulateError {
430 SimulateError::CompilerUnavailable {
431 compiler: compiler.as_str(),
432 details: format!(
433 "`{call}` restricts the native tape, and this program was built with `{}`; build it \
434 with `compiler = native`",
435 compiler.as_str()
436 ),
437 }
438}
439
440#[derive(Clone)]
447pub struct RhsProgram {
448 inner: Arc<ProgramInner>,
449}
450
451struct ProgramInner {
452 id: u64,
453 flat: Option<Arc<FlattenedSystem>>,
456 model: Arc<ProgramModel>,
457 compiler: Compiler,
458 runtime_array_parameters: bool,
459 tape: Option<Arc<TapeProgram>>,
461 slot_of: Vec<u32>,
463 writes: Vec<String>,
464 aux: Vec<String>,
465 blocks: Blocks,
466 layout: TileLayout,
467 state_layout: Vec<StateBlock>,
468 params: Vec<ParamSlot>,
469 slots: Arc<[ForcingSlot]>,
470 default_set: ForcingSet,
472 scatter: Vec<(TileInput, usize, usize, usize)>,
475 identity: bool,
478 declared: Arc<HashSet<String>>,
479}
480
481impl ProgramInner {
482 fn flat(&self) -> &FlattenedSystem {
483 self.flat
484 .as_deref()
485 .expect("a program built by `rhs_program` keeps its flattened system")
486 }
487}
488
489impl std::fmt::Debug for RhsProgram {
490 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491 f.debug_struct("RhsProgram")
492 .field("compiler", &self.inner.compiler)
493 .field("writes", &self.inner.writes)
494 .field("aux", &self.inner.aux)
495 .finish()
496 }
497}
498
499pub fn rhs_program(prob: &EsmProblem) -> Result<RhsProgram, SimulateError> {
508 let compiler = prob.compiler;
509 if !matches!(compiler, Compiler::Native | Compiler::Interpreter) {
510 return Err(SimulateError::CompilerUnavailable {
511 compiler: compiler.as_str(),
512 details: "`rhs_program` serves the `native` and `interpreter` compilers".to_string(),
513 });
514 }
515 let Some(source) = prob.program_source.as_ref() else {
516 return Err(build_error(
517 "`rhs_program`: this problem has no right-hand side to restrict".to_string(),
518 ));
519 };
520 let _precision = prob.precision.enter();
521 let flat = match &**source {
522 ProgramSource::Flattened(f) => (**f).clone(),
523 ProgramSource::File(file) => {
524 let annotated =
525 crate::precision_infer::annotated(file).map_err(SimulateError::Compile)?;
526 let file = annotated.as_ref().unwrap_or(file);
527 crate::flatten::flatten(file)
528 .map_err(|e| SimulateError::Compile(crate::compile_error::CompileError::from(e)))?
529 }
530 };
531 let forcing = match &*prob.backend {
532 crate::problem::Backend::Array(c) => problem_forcing(c, &flat),
533 crate::problem::Backend::Static(_) => HashMap::new(),
534 };
535 RhsProgram::compile(
536 Arc::new(flat),
537 compiler,
538 None,
539 ForcingSource::Problem(&forcing),
540 prob.runtime_array_parameters,
541 )
542}
543
544fn problem_forcing(c: &ArrayCompiled, flat: &FlattenedSystem) -> HashMap<String, ArrayD<f64>> {
548 c.forcing
549 .borrow()
550 .iter()
551 .filter_map(|(k, v)| {
552 if flat.parameters.contains_key(k) {
553 return Some((k.clone(), v.clone()));
554 }
555 let suffix = format!(".{k}");
556 let mut m = flat.parameters.keys().filter(|n| n.ends_with(&suffix));
557 match (m.next(), m.next()) {
558 (Some(n), None) => Some((n.clone(), v.clone())),
559 _ => None,
560 }
561 })
562 .collect()
563}
564
565enum ForcingSource<'a> {
567 Problem(&'a HashMap<String, ArrayD<f64>>),
569 Parent(&'a ProgramInner),
571}
572
573fn flat_layout_order(names: &mut [String], model: &ProgramModel) {
576 names.sort_by(|a, b| {
577 let rank = |n: &String| !model.var_shapes[n].shape.is_empty();
578 (rank(a), a).cmp(&(rank(b), b))
579 });
580}
581
582fn column_major(a: &ArrayD<f64>) -> Arc<[f64]> {
584 a.view().reversed_axes().iter().copied().collect()
585}
586
587fn forcing_slots(
592 model: &ProgramModel,
593 problem: &HashMap<String, ArrayD<f64>>,
594 array_params: &HashSet<String>,
595) -> (Vec<ForcingSlot>, Vec<Arc<[f64]>>) {
596 let mut names: Vec<&String> = model.forcing_decls.keys().collect();
597 names.sort();
598 let mut slots = Vec::new();
599 let mut defaults = Vec::new();
600 for name in names {
601 let Some(shape) = model.forcing_decls[name].clone() else {
602 continue;
603 };
604 let len = shape.iter().product::<usize>();
605 let default = match problem.get(name) {
606 Some(a) => Some(column_major(a)),
607 None => model
608 .forcing_defaults
609 .get(name)
610 .cloned()
611 .flatten()
612 .and_then(|(s, v)| ArrayD::from_shape_vec(IxDyn(&s), v).ok())
613 .map(|a| column_major(&a)),
614 }
615 .filter(|b| b.len() == len)
616 .unwrap_or_else(|| vec![f64::NAN; len].into());
617 slots.push(ForcingSlot {
618 name: name.clone(),
619 shape,
620 kind: if array_params.contains(name) {
621 SlotKind::ArrayParameter
622 } else {
623 SlotKind::Forcing
624 },
625 });
626 defaults.push(default);
627 }
628 (slots, defaults)
629}
630
631fn slot_map(tape: &TapeProgram, slots: &[ForcingSlot]) -> Result<Vec<u32>, SimulateError> {
634 tape.forcings
635 .iter()
636 .map(|fr| {
637 let k = slots
638 .iter()
639 .position(|s| s.name == fr.name)
640 .ok_or_else(|| {
641 build_error(format!(
642 "`rhs_program`: the program reads forcing `{}`, which no forcing slot serves",
643 fr.name
644 ))
645 })?;
646 if slots[k].shape[..] != fr.shape[..] {
647 return Err(build_error(format!(
648 "`rhs_program`: the program reads forcing `{}` as {:?}, its slot is {:?}",
649 fr.name, &fr.shape[..], slots[k].shape
650 )));
651 }
652 Ok(k as u32)
653 })
654 .collect()
655}
656
657impl RhsProgram {
658 fn compile(
661 flat: Arc<FlattenedSystem>,
662 compiler: Compiler,
663 writes: Option<&[String]>,
664 forcing: ForcingSource,
665 runtime_array_parameters: bool,
666 ) -> Result<RhsProgram, SimulateError> {
667 let mut compiled = ArrayCompiled::from_flattened_with(&flat, runtime_array_parameters)?;
668 compiled.runtime_mode = match compiler {
669 Compiler::Interpreter => super::RuntimeMode::Interpreter,
670 _ => super::RuntimeMode::Native,
671 };
672 let shapes: Vec<(String, Vec<usize>)> = match &forcing {
674 ForcingSource::Problem(m) => m
675 .iter()
676 .map(|(k, a)| (k.clone(), a.shape().to_vec()))
677 .collect(),
678 ForcingSource::Parent(p) => p
679 .slots
680 .iter()
681 .map(|s| (s.name.clone(), s.shape.clone()))
682 .collect(),
683 };
684 for (name, shape) in shapes {
685 if let Some(decl) = compiled.forcing_decls.get_mut(&name) {
686 *decl = Some(shape);
687 }
688 }
689 let tape = match compiler {
690 Compiler::Native => {
691 let discrete: HashSet<String> = compiled.forcing_decls.keys().cloned().collect();
692 Some(compiled.strict_tape(&discrete)?)
693 }
694 _ => None,
695 };
696 let model = ProgramModel::from_compiled(compiled);
697 let array_params: HashSet<String> = if runtime_array_parameters {
698 model
699 .forcing_decls
700 .keys()
701 .filter(|n| {
702 flat.parameters.get(*n).is_some_and(|v| {
703 v.update.is_none() && v.shape.as_ref().is_some_and(|s| !s.is_empty())
704 })
705 })
706 .cloned()
707 .collect()
708 } else {
709 HashSet::new()
710 };
711 let default_set = match forcing {
712 ForcingSource::Problem(m) => {
713 let (s, d) = forcing_slots(&model, m, &array_params);
714 ForcingSet::for_slots(s.into(), d).expect("each default holds its slot's shape")
715 }
716 ForcingSource::Parent(p) => p.default_set.clone(),
717 };
718 let slots = default_set.slots_arc();
719 let slot_of = match &tape {
720 Some(t) => slot_map(t, &slots)?,
721 None => Vec::new(),
722 };
723 let states: Vec<String> = model.var_shapes.keys().cloned().collect();
724 let mut writes: Vec<String> = match writes {
725 None => states.clone(),
726 Some(w) => states.iter().filter(|s| w.contains(s)).cloned().collect(),
727 };
728 let mut aux: Vec<String> = states
729 .iter()
730 .filter(|s| !writes.contains(s))
731 .cloned()
732 .collect();
733 flat_layout_order(&mut writes, &model);
734 flat_layout_order(&mut aux, &model);
735 let set_names = state_set_names(&flat, &model);
736 let mut entries = Vec::new();
737 for (input, names) in [(TileInput::U, &writes), (TileInput::Aux, &aux)] {
738 for name in names {
739 let vs = &model.var_shapes[name];
740 let bx: Vec<(i64, i64)> = vs
741 .shape
742 .iter()
743 .zip(&vs.origin)
744 .map(|(&n, &o)| (o, o + n as i64 - 1))
745 .collect();
746 entries.push((name.clone(), input, set_names[name].clone(), bx.clone(), bx));
747 }
748 }
749 let blocks = Blocks::build(entries);
750 let layout = blocks.public();
751 let scatter: Vec<(TileInput, usize, usize, usize)> = model
752 .var_shapes
753 .iter()
754 .map(|(name, vs)| {
755 let v = blocks.var(name).expect("every state is in the layout");
756 let len = vs.shape.iter().product::<usize>().max(1);
757 (v.input, v.base, vs.flat_offset, len)
758 })
759 .collect();
760 let identity = scatter
761 .iter()
762 .all(|&(input, base, off, _)| input == TileInput::U && base == off);
763 let state_layout = layout
764 .u
765 .iter()
766 .map(|b| StateBlock {
767 name: b.name.clone(),
768 flat_offset: b.base,
769 shape: b
770 .storage
771 .iter()
772 .map(|(lo, hi)| (hi - lo + 1).max(0) as usize)
773 .collect(),
774 })
775 .collect();
776 let params = model
777 .param_names
778 .iter()
779 .zip(&model.param_defaults)
780 .map(|(name, default)| ParamSlot {
781 name: name.clone(),
782 default: *default,
783 })
784 .collect();
785 let declared = Arc::new(model.declared_names.clone());
786 Ok(RhsProgram {
787 inner: Arc::new(ProgramInner {
788 id: next_program_id(),
789 flat: Some(flat),
790 model: Arc::new(model),
791 compiler,
792 runtime_array_parameters,
793 tape,
794 slot_of,
795 writes,
796 aux,
797 blocks,
798 layout,
799 state_layout,
800 params,
801 slots,
802 default_set,
803 scatter,
804 identity,
805 declared,
806 }),
807 })
808 }
809
810 pub(super) fn slots_arc(&self) -> Arc<[ForcingSlot]> {
811 Arc::clone(&self.inner.slots)
812 }
813
814 pub fn compiler(&self) -> Compiler {
816 self.inner.compiler
817 }
818
819 pub fn writes(&self) -> &[String] {
821 &self.inner.writes
822 }
823
824 pub fn aux(&self) -> &[String] {
827 &self.inner.aux
828 }
829
830 pub fn layout(&self) -> &TileLayout {
832 &self.inner.layout
833 }
834
835 pub fn n_states(&self) -> usize {
837 self.inner.blocks.u_len
838 }
839
840 pub fn state_layout(&self) -> &[StateBlock] {
843 &self.inner.state_layout
844 }
845
846 pub fn parameters(&self) -> &[ParamSlot] {
849 &self.inner.params
850 }
851
852 pub fn parameter_vector(
856 &self,
857 overrides: &HashMap<String, f64>,
858 ) -> Result<Vec<f64>, SimulateError> {
859 self.inner.model.parameter_vector(overrides)
860 }
861
862 pub fn forcing_slots(&self) -> &[ForcingSlot] {
864 &self.inner.slots
865 }
866
867 pub fn default_forcing(&self) -> ForcingSet {
872 self.inner.default_set.clone()
873 }
874
875 pub fn default_state(&self) -> (Vec<f64>, Vec<f64>) {
878 let inner = &*self.inner;
879 let mut u = vec![f64::NAN; inner.blocks.u_len];
880 let mut aux = vec![f64::NAN; inner.blocks.aux_len];
881 for ((name, vs), default) in inner
882 .model
883 .var_shapes
884 .iter()
885 .zip(&inner.model.state_defaults)
886 {
887 let v = inner
888 .blocks
889 .var(name)
890 .expect("every state is in the layout");
891 let dst = match v.input {
892 TileInput::U => &mut u,
893 TileInput::Aux => &mut aux,
894 };
895 let len = vs.shape.iter().product::<usize>().max(1);
896 match default {
897 super::StateDefault::Scalar(Some(x)) => dst[v.base..v.base + len].fill(*x),
898 super::StateDefault::Scalar(None) => {}
899 super::StateDefault::Field(rm) => {
900 let shape = &vs.shape;
902 for (k, &x) in rm.iter().enumerate().take(len) {
903 let mut rem = k;
904 let mut off = 0usize;
905 let mut stride_cm = vec![1usize; shape.len()];
906 for d in 1..shape.len() {
907 stride_cm[d] = stride_cm[d - 1] * shape[d - 1];
908 }
909 for d in (0..shape.len()).rev() {
910 let i = rem % shape[d];
911 rem /= shape[d];
912 off += i * stride_cm[d];
913 }
914 dst[v.base + off] = x;
915 }
916 }
917 }
918 }
919 (u, aux)
920 }
921
922 pub fn flattened(&self) -> &FlattenedSystem {
924 self.inner.flat()
925 }
926
927 pub fn select(&self, sel: &RhsSelection) -> Result<RhsProgram, SimulateError> {
932 let (flat, writes) = select::select(self.inner.flat(), sel)?;
933 let _precision = self.inner.model.precision.enter();
934 RhsProgram::compile(
935 Arc::new(flat),
936 self.inner.compiler,
937 Some(&writes),
938 ForcingSource::Parent(&self.inner),
939 self.inner.runtime_array_parameters,
940 )
941 }
942
943 pub fn halo(&self, dec: &Decomposition) -> Result<HaloReport, SimulateError> {
946 if self.inner.compiler != Compiler::Native {
947 return Err(native_only(self.inner.compiler, "halo"));
948 }
949 analysis::halo(self, dec)
950 }
951
952 pub fn tile(&self, dec: &Decomposition, bx: &TileBox) -> Result<TileProgram, SimulateError> {
954 if self.inner.compiler != Compiler::Native {
955 return Err(native_only(self.inner.compiler, "tile"));
956 }
957 tile::build(self, dec, bx)
958 }
959
960 pub fn lanes(&self, kind: LaneKind, capacity: usize) -> Result<LaneProgram, SimulateError> {
963 if self.inner.compiler != Compiler::Native {
964 return Err(native_only(self.inner.compiler, "lanes"));
965 }
966 lanes::build(self, &kind, capacity)
967 }
968
969 pub fn new_workspace(&self) -> RhsWorkspace {
971 let inner = &*self.inner;
972 let exec = match &inner.tape {
973 Some(t) => {
974 let mut ctx = TapeCtx::new(Arc::clone(t));
975 ctx.set_serial();
976 WsExec::Tape(Box::new(ctx))
977 }
978 None => {
979 let mut s = RhsScratch::new(&inner.model.var_shapes);
980 s.set_const_arrays(Arc::clone(&inner.model.const_scope));
981 WsExec::Interp(Box::new(InterpWs {
982 scratch: s,
983 forcing: RefCell::new(HashMap::new()),
984 epoch: 0,
985 }))
986 }
987 };
988 let n = if inner.identity {
989 0
990 } else {
991 inner.model.n_states
992 };
993 RhsWorkspace {
994 owner: inner.id,
995 state: vec![0.0; n],
996 dy: vec![0.0; n],
997 exec,
998 _not_sync: PhantomData,
999 }
1000 }
1001
1002 #[allow(clippy::too_many_arguments)]
1010 pub fn eval_into(
1011 &self,
1012 u: &[f64],
1013 aux: &[f64],
1014 p: &[f64],
1015 t: f64,
1016 forcing: &ForcingSet,
1017 du: &mut [f64],
1018 ws: &mut RhsWorkspace,
1019 ) -> Result<(), RhsError> {
1020 let inner = &*self.inner;
1021 check_lengths(&inner.blocks, u, aux, du, p, inner.params.len())?;
1022 check_owner(ws, inner.id)?;
1023 check_forcing(forcing, &inner.slots)?;
1024 let _precision = inner.model.precision.enter();
1025 let scope = super::FaultScope::enter();
1026 self.run(u, aux, p, t, Forcing::Set(forcing), du, ws);
1027 match scope.finish() {
1028 Some(msg) => {
1029 if let WsExec::Tape(ctx) = &mut ws.exec {
1030 ctx.invalidate_sections();
1031 }
1032 Err(forcing::fault(msg))
1033 }
1034 None => Ok(()),
1035 }
1036 }
1037
1038 #[cfg(feature = "solve")]
1042 pub(in crate::simulate_array) fn for_solve(
1043 c: &ArrayCompiled,
1044 tape: Arc<TapeProgram>,
1045 ) -> RhsProgram {
1046 let entries = c
1047 .var_shapes
1048 .iter()
1049 .map(|(name, vs)| {
1050 let bx: Vec<(i64, i64)> = vs
1051 .shape
1052 .iter()
1053 .zip(&vs.origin)
1054 .map(|(&n, &o)| (o, o + n as i64 - 1))
1055 .collect();
1056 (name.clone(), TileInput::U, Vec::new(), bx.clone(), bx)
1057 })
1058 .collect();
1059 let blocks = Blocks::build(entries);
1060 let layout = blocks.public();
1061 let model = ProgramModel {
1062 var_shapes: c.var_shapes.clone(),
1063 param_names: c.param_names.clone(),
1064 n_states: c.n_states,
1065 precision: c.precision.clone(),
1066 ..ProgramModel::default()
1067 };
1068 RhsProgram {
1069 inner: Arc::new(ProgramInner {
1070 id: next_program_id(),
1071 flat: None,
1072 compiler: Compiler::Native,
1073 runtime_array_parameters: false,
1074 tape: Some(tape),
1075 slot_of: Vec::new(),
1076 writes: c.var_shapes.keys().cloned().collect(),
1077 aux: Vec::new(),
1078 state_layout: Vec::new(),
1079 params: Vec::new(),
1080 slots: Arc::from(Vec::new()),
1081 default_set: ForcingSet::for_slots(Arc::from(Vec::new()), Vec::new())
1082 .expect("an empty set"),
1083 scatter: Vec::new(),
1084 identity: true,
1085 declared: Arc::new(c.declared_names.clone()),
1086 blocks,
1087 layout,
1088 model: Arc::new(model),
1089 }),
1090 }
1091 }
1092
1093 #[cfg(feature = "solve")]
1096 pub(in crate::simulate_array) fn solve_workspace(&self) -> RhsWorkspace {
1097 let tape = self
1098 .inner
1099 .tape
1100 .as_ref()
1101 .expect("the solve's program is taped");
1102 RhsWorkspace {
1103 owner: self.inner.id,
1104 state: Vec::new(),
1105 dy: Vec::new(),
1106 exec: WsExec::Tape(Box::new(TapeCtx::new(Arc::clone(tape)))),
1107 _not_sync: PhantomData,
1108 }
1109 }
1110
1111 #[cfg(feature = "solve")]
1117 pub(in crate::simulate_array) fn eval_solve(
1118 &self,
1119 u: &[f64],
1120 p: &[f64],
1121 t: f64,
1122 forcing: &HashMap<String, ArrayD<f64>>,
1123 du: &mut [f64],
1124 ws: &mut RhsWorkspace,
1125 ) {
1126 let _precision = self.inner.model.precision.enter();
1127 self.run(u, &[], p, t, Forcing::Buffer(forcing), du, ws);
1128 }
1129
1130 #[allow(clippy::too_many_arguments)]
1133 fn run(
1134 &self,
1135 u: &[f64],
1136 aux: &[f64],
1137 p: &[f64],
1138 t: f64,
1139 forcing: Forcing,
1140 du: &mut [f64],
1141 ws: &mut RhsWorkspace,
1142 ) {
1143 let inner = &*self.inner;
1144 let RhsWorkspace {
1145 state, dy, exec, ..
1146 } = ws;
1147 let (state, dy): (&[f64], &mut [f64]) = if inner.identity {
1148 (u, &mut *du)
1149 } else {
1150 for &(input, base, off, len) in &inner.scatter {
1151 let src = match input {
1152 TileInput::U => u,
1153 TileInput::Aux => aux,
1154 };
1155 state[off..off + len].copy_from_slice(&src[base..base + len]);
1156 }
1157 (state.as_slice(), dy.as_mut_slice())
1158 };
1159 match exec {
1160 WsExec::Tape(ctx) => {
1161 let (view, epoch) = match forcing {
1162 Forcing::Set(set) => (
1163 ForcingView::Slots {
1164 bufs: set.bufs(),
1165 slot_of: &inner.slot_of,
1166 },
1167 Some(set.epoch()),
1168 ),
1169 Forcing::Buffer(map) => (ForcingView::Map(map), None),
1170 };
1171 let inputs = CallInputs {
1172 state,
1173 params: p,
1174 forcing: view,
1175 forcing_epoch: epoch,
1176 t,
1177 declared: &inner.declared,
1178 };
1179 run_tape_inputs(ctx, &inputs, dy, true);
1180 }
1181 WsExec::Lanes { .. } => unreachable!("a whole-domain workspace runs one program"),
1182 WsExec::Interp(iw) => {
1183 let Forcing::Set(set) = forcing else {
1184 unreachable!("the solve's program is taped");
1185 };
1186 if iw.epoch != set.epoch() {
1187 fill_forcing_map(&mut iw.forcing.borrow_mut(), set);
1188 iw.epoch = set.epoch();
1189 }
1190 let m = &*inner.model;
1191 evaluate_rhs_with_scratch(
1192 &RhsCall {
1193 rhs_rules: &m.rhs_rules,
1194 observed_rules: &m.observed_rules,
1195 rule_set: m.rule_set,
1196 var_shapes: &m.var_shapes,
1197 param_names: &m.param_names,
1198 state,
1199 params: p,
1200 forcing: &iw.forcing,
1201 t,
1202 declared: &m.declared_names,
1203 },
1204 dy,
1205 true,
1206 &mut RhsStats::default(),
1207 &mut iw.scratch,
1208 );
1209 }
1210 }
1211 if !inner.identity {
1212 for &(input, base, off, len) in &inner.scatter {
1213 if input == TileInput::U {
1214 du[base..base + len].copy_from_slice(&ws.dy[off..off + len]);
1215 }
1216 }
1217 }
1218 }
1219}
1220
1221#[derive(Clone, Copy)]
1223enum Forcing<'a> {
1224 Set(&'a ForcingSet),
1226 #[cfg_attr(not(feature = "solve"), allow(dead_code))]
1228 Buffer(&'a HashMap<String, ArrayD<f64>>),
1229}
1230
1231impl RhsWorkspace {
1232 #[cfg(feature = "solve")]
1235 pub(in crate::simulate_array) fn forcing_refreshed(&mut self) {
1236 if let WsExec::Tape(ctx) = &mut self.exec {
1237 ctx.bump_forcing_epoch();
1238 }
1239 }
1240}
1241
1242fn fill_forcing_map(map: &mut HashMap<String, ArrayD<f64>>, set: &ForcingSet) {
1245 for (k, slot) in set.slots().iter().enumerate() {
1246 let src = ndarray::ArrayViewD::from_shape(IxDyn(&slot.shape).f(), set.buffer(k))
1247 .expect("a forcing set's buffer holds its slot's shape");
1248 match map.get_mut(&slot.name) {
1249 Some(a) if a.shape() == &slot.shape[..] => a.assign(&src),
1250 _ => {
1251 map.insert(slot.name.clone(), src.as_standard_layout().into_owned());
1252 }
1253 }
1254 }
1255}
1256
1257fn check_forcing(set: &ForcingSet, slots: &Arc<[ForcingSlot]>) -> Result<(), RhsError> {
1258 if !set.fits(slots) {
1259 return Err(set.mismatch(slots));
1260 }
1261 Ok(())
1262}
1263
1264fn check_owner(ws: &RhsWorkspace, id: u64) -> Result<(), RhsError> {
1265 if ws.owner != id {
1266 return Err(RhsError::ForeignProgram { what: "workspace" });
1267 }
1268 Ok(())
1269}
1270
1271fn tape_fault(scope: super::FaultScope, ctx: &mut TapeCtx) -> Result<(), RhsError> {
1274 match scope.finish() {
1275 Some(msg) => {
1276 ctx.invalidate_sections();
1277 Err(forcing::fault(msg))
1278 }
1279 None => Ok(()),
1280 }
1281}
1282
1283fn check_lengths(
1284 layout: &Blocks,
1285 u: &[f64],
1286 aux: &[f64],
1287 du: &[f64],
1288 p: &[f64],
1289 n_params: usize,
1290) -> Result<(), RhsError> {
1291 if u.len() != layout.u_len {
1292 return Err(forcing::shape("u", layout.u_len, u.len()));
1293 }
1294 if du.len() != layout.u_len {
1295 return Err(forcing::shape("du", layout.u_len, du.len()));
1296 }
1297 if aux.len() != layout.aux_len {
1298 return Err(forcing::shape("aux", layout.aux_len, aux.len()));
1299 }
1300 if p.len() != n_params {
1301 return Err(forcing::shape("p", n_params, p.len()));
1302 }
1303 Ok(())
1304}
1305
1306fn state_set_names(
1309 flat: &FlattenedSystem,
1310 compiled: &ProgramModel,
1311) -> HashMap<String, Vec<String>> {
1312 let sizes = index_set_sizes(flat);
1313 compiled
1314 .var_shapes
1315 .iter()
1316 .map(|(name, vs)| {
1317 let declared = flat.state_variables.get(name).and_then(|v| v.shape.clone());
1318 let extents: Vec<i64> = vs.shape.iter().map(|&n| n as i64).collect();
1319 (name.clone(), axis_set_names(declared, &extents, &sizes))
1320 })
1321 .collect()
1322}
1323
1324fn axis_set_names(
1329 declared: Option<Vec<String>>,
1330 extents: &[i64],
1331 sizes: &HashMap<String, i64>,
1332) -> Vec<String> {
1333 if let Some(d) = declared.filter(|d| d.len() == extents.len()) {
1334 return d;
1335 }
1336 let names: Vec<Option<String>> = extents
1337 .iter()
1338 .map(|&n| {
1339 let mut c = sizes
1340 .iter()
1341 .filter(|(_, m)| **m == n)
1342 .map(|(k, _)| k.clone());
1343 match (c.next(), c.next()) {
1344 (Some(k), None) => Some(k),
1345 _ => None,
1346 }
1347 })
1348 .collect();
1349 if names.iter().all(Option::is_some) {
1350 names.into_iter().flatten().collect()
1351 } else {
1352 Vec::new()
1353 }
1354}
1355
1356fn index_set_sizes(flat: &FlattenedSystem) -> HashMap<String, i64> {
1359 flat.index_sets
1360 .iter()
1361 .filter_map(|(k, v)| v.size.map(|n| (k.clone(), n)))
1362 .collect()
1363}
1364
1365fn axis_spec(dec: &Decomposition, set: Option<&str>, sizes: &HashMap<String, i64>) -> AxisSpec {
1367 let Some(set) = set else {
1368 return AxisSpec::Whole { set: None };
1369 };
1370 if let Some(cut) = dec.axes.iter().find(|a| a.index_set == set) {
1371 let n = sizes.get(set).copied().unwrap_or(0);
1372 return AxisSpec::Cut {
1373 set: set.to_string(),
1374 cell: set.to_string(),
1375 hi: n,
1376 period: cut.period,
1377 face: false,
1378 };
1379 }
1380 if let Some((_, cell)) = dec.faces.iter().find(|(f, _)| f == set)
1381 && let Some(cut) = dec.axes.iter().find(|a| &a.index_set == cell)
1382 {
1383 let n = sizes.get(cell.as_str()).copied().unwrap_or(0);
1384 return AxisSpec::Cut {
1385 set: set.to_string(),
1386 cell: cell.clone(),
1387 hi: n + 1,
1388 period: cut.period,
1389 face: true,
1390 };
1391 }
1392 AxisSpec::Whole {
1393 set: Some(set.to_string()),
1394 }
1395}
1396
1397fn var_infos(prog: &RhsProgram, dec: &Decomposition) -> HashMap<String, VarInfo> {
1400 let prog = &*prog.inner;
1401 let flat = prog.flat();
1402 let sizes = index_set_sizes(flat);
1403 let set_names = state_set_names(flat, &prog.model);
1404 let mut out = HashMap::new();
1405 for (name, vs) in &prog.model.var_shapes {
1406 let sets = &set_names[name];
1407 let axes = (0..vs.shape.len())
1408 .map(|d| axis_spec(dec, sets.get(d).map(String::as_str), &sizes))
1409 .collect();
1410 let extent = vs
1411 .shape
1412 .iter()
1413 .zip(&vs.origin)
1414 .map(|(&n, &o)| (o, o + n as i64 - 1))
1415 .collect();
1416 out.insert(name.clone(), VarInfo { axes, extent });
1417 }
1418 for rule in &prog.model.observed_rules {
1419 let (var, ranges) = match rule {
1420 super::AlgebraicRule::ArrayLoop {
1421 var, output_ranges, ..
1422 }
1423 | super::AlgebraicRule::Recurrence {
1424 var, output_ranges, ..
1425 } => (var, output_ranges.clone()),
1426 super::AlgebraicRule::Scalar {
1427 var,
1428 declared_shape,
1429 ..
1430 } => (
1431 var,
1432 declared_shape
1433 .as_ref()
1434 .map(|s| s.iter().map(|&n| (1, n as i64)).collect())
1435 .unwrap_or_default(),
1436 ),
1437 };
1438 let declared = flat
1439 .observed_variables
1440 .get(var)
1441 .or_else(|| flat.state_variables.get(var))
1442 .and_then(|v| v.shape.clone());
1443 let extents: Vec<i64> = ranges.iter().map(|r| r.1).collect();
1444 let sets = axis_set_names(declared, &extents, &sizes);
1445 let axes = (0..ranges.len())
1446 .map(|d| axis_spec(dec, sets.get(d).map(String::as_str), &sizes))
1447 .collect();
1448 let extent = ranges.iter().map(|&(_, hi)| (1, hi)).collect();
1449 out.insert(var.clone(), VarInfo { axes, extent });
1450 }
1451 out
1452}