use super::*;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum InlineValue {
Scalar(f64),
Array(Vec<InlineValue>),
}
pub type DenseInlineArray = (Vec<usize>, Vec<f64>);
impl From<f64> for InlineValue {
fn from(v: f64) -> Self {
InlineValue::Scalar(v)
}
}
impl InlineValue {
pub fn as_scalar(&self) -> Option<f64> {
match self {
InlineValue::Scalar(v) => Some(*v),
InlineValue::Array(_) => None,
}
}
pub fn is_array(&self) -> bool {
matches!(self, InlineValue::Array(_))
}
pub fn to_dense(&self) -> Result<DenseInlineArray, String> {
let mut shape = Vec::new();
let mut probe = self;
while let InlineValue::Array(items) = probe {
shape.push(items.len());
match items.first() {
Some(first) => probe = first,
None => break,
}
}
let mut out = Vec::new();
flatten_inline(self, 0, &shape, &mut out)?;
Ok((shape, out))
}
}
fn flatten_inline(
value: &InlineValue,
depth: usize,
shape: &[usize],
out: &mut Vec<f64>,
) -> Result<(), String> {
match value {
InlineValue::Scalar(v) => {
if depth != shape.len() {
return Err(format!(
"inline array data is ragged: a number appears at nesting depth {depth} where an array of {} elements is expected",
shape[depth]
));
}
out.push(*v);
Ok(())
}
InlineValue::Array(items) => {
if depth >= shape.len() {
return Err(
"inline array data is ragged: an array appears where a number is expected"
.to_string(),
);
}
if items.len() != shape[depth] {
return Err(format!(
"inline array data is ragged: axis {depth} has both {} and {} elements",
shape[depth],
items.len()
));
}
for item in items {
flatten_inline(item, depth + 1, shape, out)?;
}
Ok(())
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IndexSet {
pub kind: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub members: Option<Vec<serde_json::Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from_faq: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub member_factor: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub of: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offsets: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub values: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Solver {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stiffness: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abstol: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reltol: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub splitting: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time_span: Option<SolverTimeSpan>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SolverTimeSpan {
pub start: TimePoint,
pub end: TimePoint,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TimePoint {
Seconds(serde_json::Number),
Timestamp(String),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Coordinate {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub values: Option<Vec<f64>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub standard_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub units: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub axis: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Model {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reference: Option<Reference>,
pub variables: IndexMap<String, ModelVariable>,
pub equations: Vec<Equation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub discrete_events: Option<Vec<DiscreteEvent>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub continuous_events: Option<Vec<ContinuousEvent>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subsystems: Option<IndexMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tolerance: Option<Tolerance>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tests: Option<Vec<ModelTest>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub analyses: Option<Vec<ModelAnalysis>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub initialization_equations: Option<Vec<Equation>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub guesses: Option<HashMap<String, serde_json::Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub system_kind: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelVariable {
#[serde(rename = "type")]
pub var_type: VariableType,
#[serde(skip_serializing_if = "Option::is_none")]
pub units: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<InlineValue>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default_units: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub element_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shape: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub distribution: Option<Distribution>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub update: Option<ParameterUpdateSpec>,
}
impl Default for ModelVariable {
fn default() -> Self {
ModelVariable {
var_type: VariableType::Parameter,
units: None,
default: None,
default_units: None,
element_type: None,
description: None,
shape: None,
location: None,
distribution: None,
update: None,
}
}
}
impl ModelVariable {
pub fn default_scalar(&self) -> Option<f64> {
self.default.as_ref().and_then(InlineValue::as_scalar)
}
pub fn default_array(&self) -> Option<Result<DenseInlineArray, String>> {
match self.default.as_ref() {
Some(v @ InlineValue::Array(_)) => Some(v.to_dense()),
_ => None,
}
}
pub fn for_each_expression(&self, f: &mut impl FnMut(&Expr)) {
let Some(spec) = &self.update else { return };
for rule in spec.rules() {
if let Some(when) = rule.when() {
f(when);
}
let Some(value) = rule.value() else { continue };
if let Some(expr) = &value.expression {
f(expr);
}
if let Some(UnitConversion::Expression(expr)) =
value.from.as_ref().and_then(|b| b.unit_conversion.as_ref())
{
f(expr);
}
}
}
pub fn for_each_expression_at(&self, f: &mut impl FnMut(&Expr, &str)) {
let Some(spec) = &self.update else { return };
let indexed = matches!(spec, ParameterUpdateSpec::Several(_));
for (i, rule) in spec.rules().iter().enumerate() {
let rule_path = if indexed {
format!("/{i}")
} else {
String::new()
};
if let Some(when) = rule.when() {
f(when, &format!("{rule_path}/when"));
}
let Some(value) = rule.value() else { continue };
if let Some(expr) = &value.expression {
f(expr, &format!("{rule_path}/expression"));
}
if let Some(UnitConversion::Expression(expr)) =
value.from.as_ref().and_then(|b| b.unit_conversion.as_ref())
{
f(expr, &format!("{rule_path}/from/unit_conversion"));
}
}
}
pub fn for_each_expression_mut(&mut self, f: &mut impl FnMut(&mut Expr)) {
let Some(spec) = &mut self.update else { return };
let rules: &mut [ParameterUpdate] = match spec {
ParameterUpdateSpec::Single(rule) => std::slice::from_mut(rule),
ParameterUpdateSpec::Several(rules) => rules,
};
for rule in rules {
if let Some(when) = rule.when_mut() {
f(when);
}
let Some(value) = rule.value_mut() else {
continue;
};
if let Some(expr) = &mut value.expression {
f(expr);
}
if let Some(UnitConversion::Expression(expr)) =
value.from.as_mut().and_then(|b| b.unit_conversion.as_mut())
{
f(expr);
}
}
}
pub fn try_for_each_expression_mut<E>(
&mut self,
f: &mut impl FnMut(&mut Expr) -> Result<(), E>,
) -> Result<(), E> {
let mut first_err: Option<E> = None;
self.for_each_expression_mut(&mut |expr| {
if first_err.is_none()
&& let Err(e) = f(expr)
{
first_err = Some(e);
}
});
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
pub fn update_sources(&self) -> Vec<&str> {
self.update
.iter()
.flat_map(|spec| spec.rules())
.filter_map(|rule| rule.data_source())
.collect()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Distribution {
Normal {
mean: DistributionParam,
#[serde(default, skip_serializing_if = "Option::is_none")]
std: Option<DistributionParam>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cov: Option<CovarianceMatrix>,
},
Lognormal {
mu: DistributionParam,
#[serde(default, skip_serializing_if = "Option::is_none")]
sigma: Option<DistributionParam>,
#[serde(default, skip_serializing_if = "Option::is_none")]
cov: Option<CovarianceMatrix>,
},
Uniform {
low: DistributionParam,
high: DistributionParam,
},
}
impl Distribution {
pub fn kind(&self) -> &'static str {
match self {
Distribution::Normal { .. } => "normal",
Distribution::Lognormal { .. } => "lognormal",
Distribution::Uniform { .. } => "uniform",
}
}
pub fn location(&self) -> &DistributionParam {
match self {
Distribution::Normal { mean, .. } => mean,
Distribution::Lognormal { mu, .. } => mu,
Distribution::Uniform { low, .. } => low,
}
}
pub fn is_multivariate(&self) -> bool {
self.location().is_vector()
}
pub fn cov(&self) -> Option<&CovarianceMatrix> {
match self {
Distribution::Normal { cov, .. } | Distribution::Lognormal { cov, .. } => cov.as_ref(),
Distribution::Uniform { .. } => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DistributionParam {
Scalar(f64),
Vector(Vec<f64>),
}
impl DistributionParam {
pub fn is_vector(&self) -> bool {
matches!(self, DistributionParam::Vector(_))
}
}
pub type CovarianceMatrix = Vec<Vec<f64>>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum ParameterUpdateSpec {
Single(ParameterUpdate),
Several(Vec<ParameterUpdate>),
}
impl ParameterUpdateSpec {
pub fn rules(&self) -> &[ParameterUpdate] {
match self {
ParameterUpdateSpec::Single(rule) => std::slice::from_ref(rule),
ParameterUpdateSpec::Several(rules) => rules,
}
}
pub fn is_wiener(&self) -> bool {
self.rules()
.iter()
.any(|r| matches!(r, ParameterUpdate::Wiener))
}
pub fn requires_shape(&self) -> bool {
self.rules().iter().any(|r| {
matches!(
r,
ParameterUpdate::Schedule { .. }
| ParameterUpdate::Data { .. }
| ParameterUpdate::Remesh { .. }
)
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ParameterUpdate {
Wiener,
Schedule {
#[serde(default, skip_serializing_if = "Option::is_none")]
times: Option<Vec<f64>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
interval: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
initial_offset: Option<f64>,
#[serde(flatten)]
value: UpdateValue,
},
Condition {
when: Expr,
#[serde(flatten)]
value: UpdateValue,
},
Crossing {
when: Expr,
#[serde(default, skip_serializing_if = "Option::is_none")]
direction: Option<String>,
#[serde(flatten)]
value: UpdateValue,
},
Data {
source: String,
#[serde(flatten)]
value: UpdateValue,
},
Remesh {
#[serde(default, skip_serializing_if = "Option::is_none")]
hook: Option<String>,
#[serde(flatten)]
value: UpdateValue,
},
}
impl ParameterUpdate {
pub fn kind(&self) -> &'static str {
match self {
ParameterUpdate::Wiener => "wiener",
ParameterUpdate::Schedule { .. } => "schedule",
ParameterUpdate::Condition { .. } => "condition",
ParameterUpdate::Crossing { .. } => "crossing",
ParameterUpdate::Data { .. } => "data",
ParameterUpdate::Remesh { .. } => "remesh",
}
}
pub fn value(&self) -> Option<&UpdateValue> {
match self {
ParameterUpdate::Wiener => None,
ParameterUpdate::Schedule { value, .. }
| ParameterUpdate::Condition { value, .. }
| ParameterUpdate::Crossing { value, .. }
| ParameterUpdate::Data { value, .. }
| ParameterUpdate::Remesh { value, .. } => Some(value),
}
}
pub fn value_mut(&mut self) -> Option<&mut UpdateValue> {
match self {
ParameterUpdate::Wiener => None,
ParameterUpdate::Schedule { value, .. }
| ParameterUpdate::Condition { value, .. }
| ParameterUpdate::Crossing { value, .. }
| ParameterUpdate::Data { value, .. }
| ParameterUpdate::Remesh { value, .. } => Some(value),
}
}
pub fn when(&self) -> Option<&Expr> {
match self {
ParameterUpdate::Condition { when, .. } | ParameterUpdate::Crossing { when, .. } => {
Some(when)
}
_ => None,
}
}
pub fn when_mut(&mut self) -> Option<&mut Expr> {
match self {
ParameterUpdate::Condition { when, .. } | ParameterUpdate::Crossing { when, .. } => {
Some(when)
}
_ => None,
}
}
pub fn data_source(&self) -> Option<&str> {
match self {
ParameterUpdate::Data { source, .. } => Some(source.as_str()),
_ => None,
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct UpdateValue {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expression: Option<Expr>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub from: Option<DataSourceBinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub handler: Option<FunctionalUpdate>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FunctionalUpdate {
pub handler_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub read_vars: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub read_params: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config: Option<serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DataSourceBinding {
pub file_variable: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unit_conversion: Option<UnitConversion>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub codes: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub select: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reference: Option<Reference>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VariableType {
Unknown,
Parameter,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Equation {
pub lhs: Expr,
pub rhs: Expr,
#[serde(rename = "_comment", default, skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
}
impl Default for Equation {
fn default() -> Self {
Equation {
lhs: Expr::Integer(0),
rhs: Expr::Integer(0),
comment: None,
}
}
}