#![allow(
clippy::type_complexity,
clippy::collapsible_if,
clippy::needless_range_loop
)]
mod compile;
mod cse;
mod driver;
mod eval;
mod layout;
pub(crate) mod range;
mod rhs;
pub mod tape;
mod vectorized;
pub(crate) use compile::eval_buildtime_field;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use compile::eval_buildtime_field_in_scope;
pub use compile::{
file_has_array_ops, file_has_spatial_model, run_value_invention,
run_value_invention_with_params,
};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use compile::check_free_variables;
pub(crate) use compile::data_feed_source;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use compile::inline_parameter_arrays;
pub(crate) use compile::{model_tree_any, parse_subsystem_model};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use compile::{apply_flatten_rewrites, mount_subsystems, resolve_model_self_references};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use eval::eval_observed_taped;
#[cfg(test)]
pub(crate) use eval::per_cell_cells;
pub(crate) use eval::{
ONE_CELL_NOTE, StopAtFirstCell, eval_observed_recurrence, eval_scalar_expression,
per_cell_walk_refused, per_cell_walks,
};
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use tape::StandaloneError;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use vectorized::take_overlay_decline;
pub(crate) use eval::GEOMETRY_CLIP_CODE;
#[cfg(test)]
pub(crate) use eval::check_scalar_evaluable;
pub use eval::{
eval_expression, eval_expression_with_extents, eval_expression_with_extents_and_consts,
take_const_array_oob,
};
pub(crate) use eval::{apply_binary, eval_expression_with_extents_and_consts_shared};
pub use rhs::RhsScratch;
use compile::*;
use cse::*;
use eval::*;
use layout::*;
use rhs::*;
use vectorized::*;
use crate::faq::{ReduceKind, empty_derived_extents};
use crate::types::{Expr, ExpressionNode, IndexSet, RangeSpec};
use crate::value_invention::BoundaryKind;
use indexmap::IndexMap;
use ndarray::{ArrayD, IxDyn};
use rustc_hash::FxBuildHasher;
use smallvec::SmallVec;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
type DimI = SmallVec<[i64; 4]>;
type DimU = SmallVec<[usize; 4]>;
type IdxMap = HashMap<String, i64, FxBuildHasher>;
pub(crate) type ArrMap = HashMap<String, ArrayD<f64>, FxBuildHasher>;
type ValVec = SmallVec<[Value; 4]>;
#[derive(Debug, Clone)]
pub enum Value {
Scalar(f64),
Array(Box<ArrayD<f64>>),
}
impl Value {
fn as_scalar(&self) -> Option<f64> {
match self {
Value::Scalar(v) => Some(*v),
Value::Array(a) if a.ndim() == 0 => Some(a[IxDyn(&[])]),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct VarShape {
pub shape: Vec<usize>,
pub origin: Vec<i64>,
pub flat_offset: usize,
}
#[derive(Debug, Clone)]
pub(crate) enum StateDefault {
Scalar(Option<f64>),
Field(Vec<f64>),
}
#[derive(Debug, Clone)]
enum ContractDim {
Static(i64, i64),
Ragged { offsets: String, of: Vec<String> },
Derived { from_faq: String },
}
impl ContractDim {
fn from_range(spec: &RangeSpec) -> Self {
if let Some((offsets, of)) = spec.ragged() {
return ContractDim::Ragged {
offsets: offsets.to_string(),
of: of.to_vec(),
};
}
if let Some(from_faq) = spec.derived() {
return ContractDim::Derived {
from_faq: from_faq.to_string(),
};
}
let r = spec.bounds().unwrap_or([0, 0]);
ContractDim::Static(r[0], r[1])
}
fn concrete(&self, ctx: &EvalCtx) -> (i64, i64) {
match self {
ContractDim::Static(lo, hi) => (*lo, *hi),
ContractDim::Ragged { offsets, of } => (1, ragged_upper_bound(offsets, of, ctx)),
ContractDim::Derived { from_faq } => (1, derived_extent(from_faq, ctx)),
}
}
fn static_bound(&self) -> Option<(i64, i64)> {
match self {
ContractDim::Static(lo, hi) => Some((*lo, *hi)),
ContractDim::Ragged { .. } | ContractDim::Derived { .. } => None,
}
}
}
#[derive(Debug, Clone)]
enum RhsRule {
Scalar { slot: usize, body: Box<Expr> },
IndexedScalar { slot: usize, body: Box<Expr> },
ArrayLoop {
var_name: String,
output_idx_names: Vec<String>,
output_ranges: Vec<(i64, i64)>,
lhs_idx_exprs: Vec<Expr>,
body: Box<Expr>,
contract_names: Vec<String>,
contract_dims: Vec<ContractDim>,
reduce: ReduceKind,
filter: Option<Box<Expr>>,
},
}
#[derive(Debug, Clone, Default)]
pub struct RhsStats {
pub kernel_ops: usize,
pub vectorized_rules: usize,
pub scalar_rules: usize,
pub obs_vectorized_rules: usize,
pub obs_scalar_rules: usize,
pub taped_rules: usize,
}
#[derive(Debug, Clone)]
enum AlgebraicRule {
Scalar {
var: String,
body: std::sync::Arc<Expr>,
declared_shape: Option<Vec<usize>>,
},
ArrayLoop {
var: String,
output_idx_names: Vec<String>,
output_ranges: Vec<(i64, i64)>,
body: std::sync::Arc<Expr>,
},
Recurrence {
var: String,
output_idx_names: Vec<String>,
output_ranges: Vec<(i64, i64)>,
body: std::sync::Arc<Expr>,
axis: usize,
max_lag: i64,
lag_proven: bool,
},
}
pub(super) struct RecurScope<'a> {
pub(super) name: &'a str,
pub(super) shape: DimU,
pub(super) origin: DimI,
cells: RefCell<Vec<f64>>,
written: RefCell<Vec<bool>>,
}
impl<'a> RecurScope<'a> {
pub(super) fn new(name: &'a str, shape: DimU, origin: DimI) -> Self {
let total = shape.iter().copied().product::<usize>().max(1);
Self {
name,
shape,
origin,
cells: RefCell::new(vec![0.0; total]),
written: RefCell::new(vec![false; total]),
}
}
pub(super) fn publish(&self, tuple: &[i64], v: f64) {
let flat = layout::multi_to_flat_col_major(tuple, &self.shape, &self.origin);
let mut cells = self.cells.borrow_mut();
if flat < cells.len() {
cells[flat] = crate::precision::active().round(v);
self.written.borrow_mut()[flat] = true;
}
}
pub(super) fn read(&self, raw: &[i64]) -> Option<f64> {
if raw.len() != self.shape.len() {
return None;
}
for (d, &one_based) in raw.iter().enumerate() {
let lo = self.origin[d];
let hi = lo + self.shape[d] as i64 - 1;
if one_based < lo || one_based > hi {
return None;
}
}
let flat = layout::multi_to_flat_col_major(raw, &self.shape, &self.origin);
if !*self.written.borrow().get(flat)? {
return None;
}
self.cells.borrow().get(flat).copied()
}
pub(super) fn finish(&self) -> ArrayD<f64> {
layout::col_major_to_arrayd(&self.cells.borrow(), &self.shape)
}
}
#[derive(Debug, Clone, Default)]
pub struct BuildInspection {
pub setup_arrays: HashMap<String, ArrayD<f64>>,
pub observed_exprs: HashMap<String, Expr>,
pub params: HashMap<String, f64>,
pub recurrences: Vec<RecurrenceInfo>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecurrenceInfo {
pub var: String,
pub axis: String,
pub max_lag: i64,
pub lag_proven: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(crate) enum RuntimeMode {
#[default]
Legacy,
Native,
Interpreter,
Xla,
}
pub struct ArrayCompiled {
pub(crate) runtime_mode: RuntimeMode,
#[cfg(feature = "xla")]
pub(crate) xla_rhs: std::cell::OnceCell<Rc<crate::xla_runtime::CompiledRhs>>,
merged_renames: HashMap<String, String>,
var_shapes: IndexMap<String, VarShape>,
state_names: std::cell::OnceCell<Vec<String>>,
qualified_state_names: std::cell::OnceCell<Vec<String>>,
state_defaults: Vec<StateDefault>,
param_names: Vec<String>,
param_index: HashMap<String, usize>,
param_defaults: Vec<Option<f64>>,
observed_rules: Vec<AlgebraicRule>,
rhs_rules: Vec<RhsRule>,
rule_set: u64,
n_states: usize,
forcing: Rc<RefCell<HashMap<String, ArrayD<f64>>>>,
data_fed: Vec<(String, String)>,
forcing_generation: std::cell::Cell<u64>,
field_ics: Vec<(String, Expr)>,
init_faqs: Vec<(String, Expr)>,
ic_scope: compile::IcScope,
index_sets: HashMap<String, IndexSet>,
const_scope: std::sync::Arc<ConstArrayScope>,
namespace: Option<String>,
precision: crate::precision::Env,
declared_names: HashSet<String>,
#[cfg(feature = "solve")]
field_ic_memo: RefCell<Option<driver::FieldIcMemo>>,
inline_param_arrays: HashMap<String, (Vec<usize>, Vec<f64>)>,
forcing_decls: IndexMap<String, Option<Vec<usize>>>,
#[allow(clippy::type_complexity)]
forcing_defaults: HashMap<String, Option<(Vec<usize>, Vec<f64>)>>,
unvalued_shaped_params: Vec<String>,
held_at_ic: HashSet<String>,
tape_cache: tape::TapeCache,
}
#[derive(Default)]
struct Pool {
shaped: HashMap<DimU, Vec<ArrayD<f64>>, FxBuildHasher>,
free: Vec<Vec<f64>>,
}
impl Pool {
fn take(&mut self, len: usize) -> Vec<f64> {
if let Some(pos) = self.free.iter().position(|b| b.capacity() >= len) {
let mut b = self.free.swap_remove(pos);
b.clear();
b.resize(len, 0.0);
b
} else if let Some(mut b) = self.free.pop() {
b.clear();
b.resize(len, 0.0);
b
} else {
vec![0.0; len]
}
}
fn take_array_uninit(&mut self, shape: &[usize]) -> ArrayD<f64> {
if let Some(bucket) = self.shaped.get_mut(shape) {
if let Some(arr) = bucket.pop() {
return arr;
}
}
let len = shape.iter().copied().product::<usize>().max(1);
let buf = self.take(len);
ArrayD::from_shape_vec(IxDyn(shape), buf).expect("pool buffer length matches shape")
}
fn take_array(&mut self, shape: &[usize]) -> ArrayD<f64> {
let mut arr = self.take_array_uninit(shape);
arr.fill(0.0);
arr
}
fn give_array(&mut self, arr: ArrayD<f64>) {
if arr.is_standard_layout() {
if let Some(bucket) = self.shaped.get_mut(arr.shape()) {
bucket.push(arr);
return;
}
let shape: DimU = arr.shape().iter().copied().collect();
self.shaped.insert(shape, vec![arr]);
return;
}
let (buf, _offset) = arr.into_raw_vec_and_offset();
self.free.push(buf);
}
}
#[derive(Clone, Copy)]
struct EvalEnv<'a> {
state_arrays: &'a ArrMap,
params: &'a [f64],
param_names: &'a [String],
t: f64,
derived_rings: &'a RefCell<HashMap<String, ArrayD<f64>>>,
derived_extents: &'a HashMap<String, i64>,
forcing: &'a RefCell<HashMap<String, ArrayD<f64>>>,
cse: Option<&'a CseRt>,
const_lits: Option<&'a ConstLitMemo>,
const_arrays: &'a ConstArrayScope,
declared: &'a HashSet<String>,
}
impl<'a> EvalEnv<'a> {
fn ctx(&self, observed_arrays: &'a ArrMap) -> EvalCtx<'a> {
EvalCtx {
state_arrays: self.state_arrays,
observed_arrays,
params: self.params,
param_names: self.param_names,
loop_binds: IdxMap::default(),
t: self.t,
derived_rings: self.derived_rings,
derived_extents: self.derived_extents,
forcing: self.forcing,
cse: self.cse,
const_lits: self.const_lits,
const_arrays: self.const_arrays,
recur: None,
declared: self.declared,
}
}
fn oracle_ctx(&self, observed_arrays: &'a ArrMap) -> EvalCtx<'a> {
EvalCtx {
cse: None,
..self.ctx(observed_arrays)
}
}
}
struct EvalCtx<'a> {
state_arrays: &'a ArrMap,
observed_arrays: &'a ArrMap,
params: &'a [f64],
param_names: &'a [String],
loop_binds: IdxMap,
t: f64,
derived_rings: &'a RefCell<HashMap<String, ArrayD<f64>>>,
derived_extents: &'a HashMap<String, i64>,
forcing: &'a RefCell<HashMap<String, ArrayD<f64>>>,
cse: Option<&'a CseRt>,
const_lits: Option<&'a ConstLitMemo>,
const_arrays: &'a ConstArrayScope,
recur: Option<&'a RecurScope<'a>>,
declared: &'a HashSet<String>,
}
fn empty_declared_names() -> &'static HashSet<String> {
static EMPTY: std::sync::OnceLock<HashSet<String>> = std::sync::OnceLock::new();
EMPTY.get_or_init(HashSet::new)
}
pub(crate) const INLINE_CONST_NAME: &str = "inline const";
#[derive(Default)]
pub(super) struct ConstLitMemo {
inner: RefCell<ConstLitInner>,
}
#[derive(Default)]
struct ConstLitInner {
key: u64,
map: HashMap<(usize, bool), Option<std::sync::Arc<ArrayD<f64>>>>,
}
fn walk_const_lit(node: &ExpressionNode) -> Option<std::sync::Arc<ArrayD<f64>>> {
match node.value.as_ref().map(eval::json_to_value) {
Some(Some(Value::Array(a))) => Some(std::sync::Arc::new(*a)),
_ => None,
}
}
impl ConstLitMemo {
pub(super) fn retarget(&self, key: u64) {
let mut inner = self.inner.borrow_mut();
if inner.key != key {
inner.key = key;
inner.map.clear();
}
}
pub(super) fn get(&self, node: &ExpressionNode) -> Option<std::sync::Arc<ArrayD<f64>>> {
let k = (
node as *const ExpressionNode as usize,
crate::precision::active().is_f32(),
);
if let Some(hit) = self.inner.borrow().map.get(&k) {
return hit.clone();
}
let built = walk_const_lit(node);
self.inner.borrow_mut().map.insert(k, built.clone());
built
}
}
pub(super) fn const_lit_array(
node: &ExpressionNode,
memo: Option<&ConstLitMemo>,
) -> Option<std::sync::Arc<ArrayD<f64>>> {
match memo {
Some(m) => m.get(node),
None => walk_const_lit(node),
}
}
#[derive(Debug, Clone, Default)]
pub struct ConstArrayScope {
names: HashSet<String>,
boundaries: HashMap<String, Vec<BoundaryKind>>,
}
impl ConstArrayScope {
pub fn empty() -> &'static ConstArrayScope {
static EMPTY: std::sync::OnceLock<ConstArrayScope> = std::sync::OnceLock::new();
EMPTY.get_or_init(ConstArrayScope::default)
}
pub fn from_names<I: IntoIterator<Item = String>>(names: I) -> ConstArrayScope {
ConstArrayScope {
names: names.into_iter().collect(),
boundaries: HashMap::new(),
}
}
pub fn with_boundary(mut self, name: impl Into<String>, dims: Vec<BoundaryKind>) -> Self {
let name = name.into();
self.names.insert(name.clone());
self.boundaries.insert(name, dims);
self
}
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
pub fn is_const(&self, name: &str) -> bool {
self.names.contains(name)
}
pub fn is_inline_const(base: &Expr) -> bool {
matches!(base, Expr::Operator(n) if n.op == "const")
}
pub fn is_const_base(&self, base: &Expr) -> bool {
match base {
Expr::Variable(v) => self.is_const(v),
other => Self::is_inline_const(other),
}
}
pub fn boundary(&self, name: &str, d: usize) -> BoundaryKind {
self.boundaries
.get(name)
.and_then(|dims| dims.get(d))
.copied()
.unwrap_or(BoundaryKind::Error)
}
}