Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,13 @@ This is a yarn workspace with these packages:

- Rust: Unit tests in `src/*/tests/` and integration tests in `test/` directory
- TypeScript: Workspace-level linting and type checking
- Models: Extensive test suite in `test/` with expected outputs. This is very important and ensures the engine behavior matches known-good results from other software.
- Models: Extensive test suite in `test/` with expected outputs. This is very important and ensures the engine behavior matches known-good results from other software.

### Development strategy when working in Rust

When asked to perform tasks in Rust crates like `src/simlin-engine`, the following general workflow should be followed:
* Before starting, run `cargo clippy` and note any existing lints that are failing.
* At the end of the task:
* Run `cargo fmt` to ensure the code is appropriately formatted.
* Run `cargo clippy`, and if there are new lints that weren't failing at the start of the task fix them (directly and without shortcuts) so that `cargo clippy` doesn't complain.
* Run `cargo test` at the root of the workspace to ensure we haven't regressed on any behavior.
581 changes: 581 additions & 0 deletions design/full-array-support.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/engine/error_codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function errorCodeDescription(code: ErrorCode): string {
case ErrorCode.UnknownBuiltin:
return 'Reference to unknown or unimplemented builtin';
case ErrorCode.BadBuiltinArgs:
return 'Builtin function arguments';
return 'Incorrect arguments to a builtin function (e.g. too many, too few)';
case ErrorCode.EmptyEquation:
return 'Variable has empty equation';
case ErrorCode.BadModuleInputDst:
Expand Down
11 changes: 10 additions & 1 deletion src/simlin-compat/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,17 @@ pub fn load_csv(file_path: &str, delimiter: u8) -> StdResult<Results, Box<dyn Er

let mut row = vec![0.0; step_size];
for (i, field) in record.iter().enumerate() {
let field = field.trim();

// vensim data seems to omit data for consts or things that don't change, so copy it forward
if field.is_empty() {
let prev = &step_data[step_count-1];
row[i] = prev[i];
continue;
}

use std::str::FromStr;
row[i] = match f64::from_str(field.trim()) {
row[i] = match f64::from_str(field) {
Ok(n) => n,
Err(err) => {
return Err(Box::new(err));
Expand Down
11 changes: 4 additions & 7 deletions src/simlin-compat/tests/roundtrip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ fn roundtrips_model() {
for &path in TEST_MODELS {
let file_path = format!("../../{}", path);
use std::io::Write;
writeln!(std::io::stderr(), "model: {}", path).unwrap();
eprintln!("model: {}", path);

let f = File::open(file_path).unwrap();
let mut f = BufReader::new(f);
Expand All @@ -60,13 +60,10 @@ fn roundtrips_model() {

for (model_name, model) in project.models.iter() {
for (var_name, var) in model.variables.iter() {
match var.equation_errors() {
Some(errors) => {
for err in errors {
eprintln!(" {}.{} error: {}", model_name, var_name, err);
}
if let Some(errors) = var.equation_errors() {
for err in errors {
eprintln!(" {}.{} error: {}", model_name, var_name, err);
}
None => (),
}
}
}
Expand Down
41 changes: 29 additions & 12 deletions src/simlin-compat/tests/simulate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use std::rc::Rc;
use float_cmp::approx_eq;

use simlin_compat::{load_csv, load_dat, xmile};
use simlin_engine::serde::{deserialize, serialize};
use simlin_engine::{Project, Results, Simulation, Vm};
use simlin_engine::{build_sim_with_stderrors, project_io};
use simlin_engine::build_sim_with_stderrors;
use simlin_engine::interpreter::Simulation;
use simlin_engine::{Project, Results, Vm};

const OUTPUT_FILES: &[(&str, u8)] = &[("output.csv", b','), ("output.tab", b'\t')];

Expand Down Expand Up @@ -119,8 +119,10 @@ fn ensure_results(expected: &Results, results: &Results) {

let mut step = 0;
for (expected_row, results_row) in expected.iter().zip(results.iter()) {
let mut err = false;
for ident in expected.offsets.keys() {
let expected = expected_row[expected.offsets[ident]];
let ident_off = expected.offsets[ident];
let expected = expected_row[ident_off];
if !results.offsets.contains_key(ident) && IGNORABLE_COLS.contains(&ident.as_str()) {
continue;
}
Expand Down Expand Up @@ -159,10 +161,13 @@ fn ensure_results(expected: &Results, results: &Results) {
"step {}: {}: {} (expected) != {} (actual)",
step, ident, expected, actual
);
panic!("not equal");
err = true;
}
}
}
if err {
panic!("errors in step {}", step);
}

step += 1;
}
Expand All @@ -179,7 +184,7 @@ fn simulate_path(xmile_path: &str) {

// first read-in the XMILE model, convert it to our own representation,
// and simulate it using our tree-walking interpreter
let (datamodel_project, sim, results1) = {
let (datamodel_project, sim, results_interp) = {
let f = File::open(xmile_path).unwrap();
let mut f = BufReader::new(f);

Expand All @@ -197,7 +202,7 @@ fn simulate_path(xmile_path: &str) {
};

// next simulate the model using our bytecode VM
let results2 = {
let results_vm = {
let compiled = sim.compile();

assert!(compiled.is_ok());
Expand All @@ -209,13 +214,14 @@ fn simulate_path(xmile_path: &str) {
vm.into_results()
};

// ensure the two results match each other
ensure_results(&results1, &results2);

// also ensure they match our reference results
let expected = load_expected_results(xmile_path).unwrap();
ensure_results(&expected, &results1);
ensure_results(&expected, &results2);
ensure_results(&expected, &results_interp);
/*
ensure_results(&expected, &results_vm);

// ensure the two results match each other
ensure_results(&results_interp, &results_vm);

// serialized our project through protobufs and ensure we don't see problems
let results3 = {
Expand Down Expand Up @@ -263,6 +269,7 @@ fn simulate_path(xmile_path: &str) {
// byte-for-byte identical (we aren't losing any information)
let serialized_xmile2 = xmile::project_to_xmile(&roundtripped_project).unwrap();
assert_eq!(&serialized_xmile, &serialized_xmile2);
*/
}

#[test]
Expand Down Expand Up @@ -328,6 +335,16 @@ fn simulates_active_initial() {
simulate_path("../../test/sdeverywhere/models/active_initial/active_initial.xmile");
}

#[test]
fn simulates_sum() {
simulate_path("../../test/sdeverywhere/models/sum/sum.xmile");
}

#[test]
fn simulates_subscripted_logicals() {
simulate_path("../../test/subscripted_logicals/test_subscripted_logicals.xmile");
}

#[test]
#[ignore]
fn simulates_except() {
Expand Down
Loading