From fed74ce2310e08a8fc1bbe143cceeb81fbbd3a56 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 24 Nov 2023 15:42:58 -0800 Subject: [PATCH 01/77] feat: add initial XMILE parsing support --- packages/parse/package.json | 1 + .../xmile/parse-xmile-dimension-def.spec.ts | 64 +++ .../src/xmile/parse-xmile-dimension-def.ts | 59 +++ .../parse/src/xmile/parse-xmile-model.spec.ts | 146 +++++ packages/parse/src/xmile/parse-xmile-model.ts | 77 +++ .../xmile/parse-xmile-variable-def.spec.ts | 499 ++++++++++++++++++ .../src/xmile/parse-xmile-variable-def.ts | 212 ++++++++ packages/parse/src/xmile/xml.ts | 42 ++ pnpm-lock.yaml | 8 + 9 files changed, 1108 insertions(+) create mode 100644 packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts create mode 100644 packages/parse/src/xmile/parse-xmile-dimension-def.ts create mode 100644 packages/parse/src/xmile/parse-xmile-model.spec.ts create mode 100644 packages/parse/src/xmile/parse-xmile-model.ts create mode 100644 packages/parse/src/xmile/parse-xmile-variable-def.spec.ts create mode 100644 packages/parse/src/xmile/parse-xmile-variable-def.ts create mode 100644 packages/parse/src/xmile/xml.ts diff --git a/packages/parse/package.json b/packages/parse/package.json index c2fe5edc..0055551e 100644 --- a/packages/parse/package.json +++ b/packages/parse/package.json @@ -31,6 +31,7 @@ "ci:build": "run-s clean lint prettier:check type-check test:ci build docs" }, "dependencies": { + "@rgrove/parse-xml": "^4.1.0", "antlr4": "4.12.0", "antlr4-vensim": "0.6.2", "assert-never": "^1.2.1", diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts new file mode 100644 index 00000000..4d1e064d --- /dev/null +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts @@ -0,0 +1,64 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import { describe, expect, it } from 'vitest' + +import { type XmlElement, parseXml } from '@rgrove/parse-xml' + +import { subRange } from '../ast/ast-builders' + +import { parseXmileDimensionDef } from './parse-xmile-dimension-def' + +function xml(input: string): XmlElement { + let xml + try { + xml = parseXml(input) + } catch (e) { + throw new Error(`Invalid XML:\n${input}\n\n${e}`) + } + return xml.root +} + +describe('parseXmileDimensionDef', () => { + it('should parse a dimension def with explicit subscripts', () => { + const dim = xml(` + + + + + + `) + expect(parseXmileDimensionDef(dim)).toEqual(subRange('DimA', 'DimA', ['A1', 'A2', 'A3'])) + }) + + it('should throw an error if dimension name is not defined', () => { + const dim = xml(` + + + + + + `) + expect(() => parseXmileDimensionDef(dim)).toThrow(' name attribute is required for dimension definition') + }) + + it('should throw an error if dimension element name is not defined', () => { + const dim = xml(` + + + + + + `) + expect(() => parseXmileDimensionDef(dim)).toThrow( + ' name attribute is required for dimension element definition' + ) + }) + + it('should throw an error if no dimension elements are defined', () => { + const dim = xml(` + + + `) + expect(() => parseXmileDimensionDef(dim)).toThrow(' must contain one or more elements') + }) +}) diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.ts new file mode 100644 index 00000000..31834182 --- /dev/null +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.ts @@ -0,0 +1,59 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import type { XmlElement } from '@rgrove/parse-xml' + +import { canonicalName } from '../_shared/names' + +import type { SubscriptRange, SubscriptRef } from '../ast/ast-types' + +import { elemsOf, firstElemOf, xmlError } from './xml' + +/** + * Parse the given XMILE dimension (``) definition and return a `SubscriptRange` AST node. + * + * @param input A string containing the XMILE `` definition. + * @returns A `SubscriptRange` AST node. + */ +export function parseXmileDimensionDef(dimElem: XmlElement): SubscriptRange { + // Extract + const dimName = dimElem.attributes?.name + if (dimName === undefined) { + throw new Error(xmlError(dimElem, ' name attribute is required for dimension definition')) + } + + // Extract child elements + const elemElems = elemsOf(dimElem, ['elem']) + if (elemElems.length === 0) { + throw new Error(xmlError(dimElem, ' must contain one or more elements')) + } + const subscriptRefs: SubscriptRef[] = [] + for (const elem of elemElems) { + // Extract + const subName = elem.attributes?.name + if (subName === undefined) { + throw new Error(xmlError(dimElem, ' name attribute is required for dimension element definition')) + } + + const subId = canonicalName(subName) + subscriptRefs.push({ + subId, + subName + }) + } + + // Extract -> comment string + const comment = firstElemOf(dimElem, 'doc')?.text || '' + + const dimId = canonicalName(dimName) + return { + dimName, + dimId, + // TODO: For Vensim `DimA <-> DimB` aliases, the family name would be `DimB` + familyName: dimName, + familyId: dimId, + subscriptRefs, + // TODO: Does XMILE support mappings? + subscriptMappings: [], + comment + } +} diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts new file mode 100644 index 00000000..48549622 --- /dev/null +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -0,0 +1,146 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import { describe, expect, it } from 'vitest' + +import { exprEqn, model, num, subRange, varDef } from '../ast/ast-builders' + +import { parseXmileModel } from './parse-xmile-model' + +function xmile(dimensions: string, variables: string): string { + let dims: string + if (dimensions.length > 0) { + dims = `\ + + ${dimensions} + ` + } else { + dims = '' + } + + let vars: string + if (variables.length > 0) { + vars = `\ + + ${variables} + ` + } else { + vars = '' + } + + return `\ + +
+ + Ventana Systems, xmutil + Vensim, xmutil +
+ + 0 + 100 +
1
+
+${dims} + + ${vars} + +
` +} + +describe('parseXmileModel', () => { + it('should throw an error if model cannot be parsed', () => { + const mdl = 'NOT XMILE' + + let msg = 'Failed to parse XMILE model definition:\n\n' + msg += 'Root element is missing or invalid (line 1, column 1)\n' + msg += ' NOT XMILE\n' + msg += ' ^' + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + + it('should parse a model with dimension definition only (no equations)', () => { + const dims = `\ + + + + +` + const mdl = xmile(dims, '') + expect(parseXmileModel(mdl)).toEqual(model([subRange('DimA', 'DimA', ['A1', 'A2', 'A3'])], [])) + }) + + it('should parse a model with dimension definition with comment', () => { + const dims = `\ + + comment is here + + + +` + const mdl = xmile(dims, '') + expect(parseXmileModel(mdl)).toEqual( + model([subRange('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], []) + ) + }) + + it('should parse a model with equation only (no dimension definitions)', () => { + const vars = `\ + + 1 +` + const mdl = xmile('', vars) + expect(parseXmileModel(mdl)).toEqual(model([], [exprEqn(varDef('x'), num(1))])) + }) + + it('should parse a model with equation with units and single-line comment', () => { + const dims = `\ + + comment is here + + + +` + const vars = `\ + + + + + comment is here + meters + 1 +` + + const mdl = xmile(dims, vars) + expect(parseXmileModel(mdl)).toEqual( + model( + // dimension definitions + [subRange('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], + + // variable definitions + [exprEqn(varDef('x', ['DimA']), num(1), 'meters', 'comment is here')] + ) + ) + }) + + // it('should parse a model with equation with units and multi-line comment', () => { + // const mdl = ` + // x = 1 + // ~ watt/(meter*meter) + // ~ Something, Chapter 6. More things. p.358. More words \\ + // continued on next line. + // | + // ` + // expect(parseVensimModel(mdl)).toEqual( + // model( + // [], + // [ + // exprEqn( + // varDef('x'), + // num(1), + // 'watt/(meter*meter)', + // 'Something, Chapter 6. More things. p.358. More words continued on next line.' + // ) + // ] + // ) + // ) + // }) +}) diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts new file mode 100644 index 00000000..0951d05d --- /dev/null +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -0,0 +1,77 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import type { XmlElement } from '@rgrove/parse-xml' +import { parseXml } from '@rgrove/parse-xml' + +import type { Equation, Model, SubscriptRange } from '../ast/ast-types' + +import { parseXmileDimensionDef } from './parse-xmile-dimension-def' +import { parseXmileVariableDef } from './parse-xmile-variable-def' +import { firstElemOf, elemsOf } from './xml' + +/** + * Parse the given XMILE model definition and return a `Model` AST node. + * + * @param input A string containing the XMILE model. + * @param context An object that provides access to file system resources (such as + * external data files) that are referenced during the parse phase. + * @returns A `Model` AST node. + */ +export function parseXmileModel(input: string): Model { + let xml + try { + xml = parseXml(input) + } catch (e) { + // Include context such as line/column numbers in the error message if available + const msg = `Failed to parse XMILE model definition:\n\n${e.message}` + throw new Error(msg) + } + + // Extract -> SubscriptRange[] + const subscriptRanges: SubscriptRange[] = parseDimensionDefs(xml.root) + + // Extract -> Equation[] + const equations: Equation[] = parseVariableDefs(xml.root) + + return { + subscriptRanges, + equations + } +} + +function parseDimensionDefs(rootElem: XmlElement | undefined): SubscriptRange[] { + const subscriptRanges: SubscriptRange[] = [] + + const dimensionsElem = firstElemOf(rootElem, 'dimensions') + if (dimensionsElem) { + // Extract -> SubscriptRange + const dimElems = elemsOf(dimensionsElem, ['dim']) + for (const dimElem of dimElems) { + subscriptRanges.push(parseXmileDimensionDef(dimElem)) + } + } + + return subscriptRanges +} + +function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { + const modelElem = firstElemOf(rootElem, 'model') + if (modelElem === undefined) { + return [] + } + + const equations: Equation[] = [] + const variablesElem = firstElemOf(modelElem, 'variables') + if (variablesElem) { + // Extract variable definition (e.g., or or ) -> Equation[] + const varElems = elemsOf(variablesElem, ['aux', 'stock', 'flow']) + for (const varElem of varElems) { + const eqns = parseXmileVariableDef(varElem) + if (eqns) { + equations.push(...eqns) + } + } + } + + return equations +} diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts new file mode 100644 index 00000000..a013a9e1 --- /dev/null +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -0,0 +1,499 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import { describe, expect, it } from 'vitest' + +import type { XmlElement } from '@rgrove/parse-xml' +import { parseXml } from '@rgrove/parse-xml' + +import { binaryOp, call, exprEqn, num, varDef, varRef } from '../ast/ast-builders' + +import { parseXmileVariableDef } from './parse-xmile-variable-def' + +function xml(input: string): XmlElement { + let xml + try { + xml = parseXml(input) + } catch (e) { + throw new Error(`Invalid XML:\n${input}\n\n${e}`) + } + return xml.root +} + +describe('parseXmileVariableDef with ', () => { + it('should parse a stock variable definition (without subscripts, single inflow)', () => { + const v = xml(` + + y + 10 + z * 2 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x'), call('INTEG', binaryOp(varRef('z'), '*', num(2)), binaryOp(varRef('y'), '+', num(10)))) + ]) + }) + + it('should parse a stock variable definition (with one dimension, apply-to-all, single inflow)', () => { + const v = xml(` + + + + + y[DimA] + 10 + z * 2 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x', ['DimA']), + call('INTEG', binaryOp(varRef('z'), '*', num(2)), binaryOp(varRef('y', ['DimA']), '+', num(10))) + ) + ]) + }) + + it('should parse a stock variable definition (with one dimension, non-apply-to-all, single inflow)', () => { + const v = xml(` + + + + + + y[A1] + 10 + z * 2 + + + y[A2] + 20 + z * 3 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x', ['A1']), + call('INTEG', binaryOp(varRef('z'), '*', num(2)), binaryOp(varRef('y', ['A1']), '+', num(10))) + ), + exprEqn( + varDef('x', ['A2']), + call('INTEG', binaryOp(varRef('z'), '*', num(3)), binaryOp(varRef('y', ['A2']), '+', num(20))) + ) + ]) + }) + + it('should throw an error if stock variable definition has no ', () => { + const v = xml(` + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is required for a variable') + }) + + it('should throw an error if stock variable definition has no ', () => { + const v = xml(` + + y + 10 + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently only one is supported for a variable') + }) + + // TODO: Support multiple inflows + it('should throw an error if stock variable definition has multiple ', () => { + const v = xml(` + + y + 10 + z * 2 + q + 4 + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently only one is supported for a variable') + }) + + // TODO: Support + it('should throw an error if stock variable definition has ', () => { + const v = xml(` + + 1000 + matriculating + graduating + + 4 + 1200 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) + + // TODO: Support + it('should throw an error if stock variable definition has ', () => { + const v = xml(` + + 1000 + matriculating + graduating + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) + + // TODO: Support + it('should throw an error if stock variable definition has ', () => { + const v = xml(` + + 1000 + matriculating + graduating + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) +}) + +describe('parseXmileVariableDef with ', () => { + it('should parse a flow variable definition (without subscripts)', () => { + const v = xml(` + + y + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), binaryOp(varRef('y'), '+', num(10)))]) + }) + + it('should parse a flow variable definition (with one dimension, apply to all)', () => { + const v = xml(` + + + + + y[DimA] + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['DimA']), binaryOp(varRef('y', ['DimA']), '+', num(10))) + ]) + }) + + it('should parse a flow variable definition (with one dimension, non-apply-to-all, named subscripts)', () => { + const v = xml(` + + + + + + y[A1] + 10 + + + 20 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['A1']), binaryOp(varRef('y', ['A1']), '+', num(10))), + exprEqn(varDef('x', ['A2']), num(20)) + ]) + }) + + it('should throw an error if flow variable definition has no ', () => { + const v = xml(` + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is required for a variable') + }) + + // TODO: Support + it('should throw an error if flow variable definition has ', () => { + const v = xml(` + + y + 10 + 3 + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) + + // TODO: Support + it('should throw an error if flow variable definition has ', () => { + const v = xml(` + + 1000 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) + + // TODO: Support + it('should throw an error if flow variable definition has ', () => { + const v = xml(` + + 1000 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) + + // TODO: Support + it('should throw an error if flow variable definition has ', () => { + const v = xml(` + + 1000 + 0.1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') + }) +}) + +describe('parseXmileVariableDef with ', () => { + it('should parse an aux variable definition (without subscripts)', () => { + const v = xml(` + + y + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), binaryOp(varRef('y'), '+', num(10)))]) + }) + + it('should parse an aux variable definition (with one dimension, apply to all)', () => { + const v = xml(` + + + + + y[DimA] + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['DimA']), binaryOp(varRef('y', ['DimA']), '+', num(10))) + ]) + }) + + it('should parse an aux variable definition (with one dimension, non-apply-to-all, named subscripts)', () => { + const v = xml(` + + + + + + y[A1] + 10 + + + 20 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['A1']), binaryOp(varRef('y', ['A1']), '+', num(10))), + exprEqn(varDef('x', ['A2']), num(20)) + ]) + }) + + it('should parse an aux variable definition with XMILE conditional expression', () => { + const v = xml(` + + IF c > 10 THEN y + 3 ELSE z * 5 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + binaryOp(varRef('c'), '>', num(10)), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ) + ]) + }) + + it('should throw an error if aux variable equation cannot be parsed', () => { + const v = xml(` + + y ? 10 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(`token recognition error at: '?'`) + }) + + it('should parse a constant definition (without subscripts)', () => { + const v = xml(` + + 1 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), num(1))]) + }) + + it('should parse a constant definition (with one dimension, apply-to-all)', () => { + const v = xml(` + + + + + 1 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x', ['DimA']), num(1))]) + }) + + it('should parse a constant definition (with one dimension, non-apply-to-all, named subscripts)', () => { + const v = xml(` + + + + + + 1 + + + 2 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['A1']), num(1)), + exprEqn(varDef('x', ['A2']), num(2)) + ]) + }) + + // TODO: Support numeric subscript indices + it('should parse a constant definition (with one dimension, non-apply-to-all, numeric subscripts)', () => { + const v = xml(` + + + + + + 1 + + + 1 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Numeric subscript indices are not currently supported') + }) + + it('should parse a constant definition (with two dimensions, apply-to-all)', () => { + const v = xml(` + + + + + + 1 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x', ['DimA', 'DimB']), num(1))]) + }) + + it('should parse a constant definition (with two dimensions, non-apply-to-all, named subscripts)', () => { + const v = xml(` + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['A1', 'B1']), num(1)), + exprEqn(varDef('x', ['A1', 'B2']), num(2)), + exprEqn(varDef('x', ['A2', 'B1']), num(3)), + exprEqn(varDef('x', ['A2', 'B2']), num(4)) + ]) + }) + + // TODO: Support numeric subscript indices + it('should parse a constant definition (with two dimension, non-apply-to-all, numeric subscripts)', () => { + const v = xml(` + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Numeric subscript indices are not currently supported') + }) + + // it('should parse a data variable definition (without subscripts)', () => { + // const eqn = `x ~~|` + // expect(parseXmileVariableDef(v)).toEqual(dataVarEqn(varDef('x'))) + // }) + + // it('should parse a data variable definition (with one dimension)', () => { + // const eqn = `x[a] ~~|` + // expect(parseXmileVariableDef(v)).toEqual(dataVarEqn(varDef('x', ['a']))) + // }) + + // it('should parse a data variable definition (with two dimensions)', () => { + // const eqn = `x[a, b] ~~|` + // expect(parseXmileVariableDef(v)).toEqual(dataVarEqn(varDef('x', ['a', 'b']))) + // }) + + // it('should parse a lookup definition (without lookup range)', () => { + // const eqn = `x( (0,0), (1,2), (2, 5) ) ~~|` + // expect(parseXmileVariableDef(v)).toEqual( + // lookupVarEqn( + // varDef('x'), + // lookupDef([ + // [0, 0], + // [1, 2], + // [2, 5] + // ]) + // ) + // ) + // }) + + // it('should parse a lookup definition (with lookup range)', () => { + // const eqn = `x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~|` + // expect(parseXmileVariableDef(v)).toEqual( + // lookupVarEqn( + // varDef('x'), + // lookupDef( + // [ + // [0, 0], + // [0.1, 0.01], + // [0.5, 0.7], + // [1, 1], + // [1.5, 1.2], + // [2, 1.3] + // ], + // { + // min: [0, 0], + // max: [2, 2] + // } + // ) + // ) + // ) + // }) +}) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts new file mode 100644 index 00000000..669929c1 --- /dev/null +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -0,0 +1,212 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import type { XmlElement } from '@rgrove/parse-xml' + +import { canonicalName } from '../_shared/names' + +import { call, subRef } from '../ast/ast-builders' +import type { Equation, Expr, SubscriptRef } from '../ast/ast-types' + +import { parseVensimExpr } from '../vensim/parse-vensim-expr' + +import { elemsOf, firstElemOf, firstTextOf, xmlError } from './xml' + +// Regular expression for XMILE conditional expressions +const conditionalRegExp = /IF\s+(.*)\s+THEN\s+(.*)\s+ELSE\s+(.*)\s*/gi + +/** + * Parse the given XMILE variable definition and return an array of `Equation` AST nodes + * corresponding to the variable definition (or definitions, in the case of a + * non-apply-to-all variable that is defined with an `` for each subscript). + * + * @param input A string containing the XMILE equation definition. + * @returns An `Equation` AST node. + */ +export function parseXmileVariableDef(varElem: XmlElement): Equation[] { + // Extract required variable name + const varName = varElem.attributes?.name + if (varName === undefined) { + throw new Error(xmlError(varElem, `<${varElem.name}> name attribute is required`)) + } + const varId = canonicalName(varName) + + // Extract optional -> units string + const units = firstElemOf(varElem, 'units')?.text || '' + + // Extract optional -> comment string + const comment = firstElemOf(varElem, 'doc')?.text || '' + + // Helper function that creates a single `Equation` instance for this variable definition + function equation(subscriptRefs: SubscriptRef[] | undefined, expr: Expr): Equation { + return { + lhs: { + varDef: { + kind: 'variable-def', + varName, + varId, + subscriptRefs + } + }, + rhs: { + kind: 'expr', + expr + }, + units, + comment + } + } + + // Check for + const dimensionsElem = firstElemOf(varElem, 'dimensions') + const equationDefs: Equation[] = [] + if (dimensionsElem === undefined) { + // This is a non-subscripted variable + const expr = parseEqnElem(varElem, varElem) + if (expr) { + equationDefs.push(equation(undefined, expr)) + } + } else { + // This is an array (subscripted) variable. An array variable definition will include + // a element that declares which dimensions are used. + const dimElems = elemsOf(dimensionsElem, ['dim']) + const dimNames: string[] = [] + for (const dimElem of dimElems) { + const dimName = dimElem.attributes?.name + if (dimName === undefined) { + throw new Error(xmlError(varElem, ' name attribute is required in for variable definition')) + } + dimNames.push(dimName) + } + + // If it is an apply-to-all variable, there will be a single . If it is a + // non-apply-to-all variable, there will be one or more elements that define + // the separate equation for each "instance". + const elementElems = elemsOf(varElem, ['element']) + if (elementElems.length === 0) { + // This is an apply-to-all variable + const dimRefs = dimNames.map(subRef) + const expr = parseEqnElem(varElem, varElem) + if (expr) { + equationDefs.push(equation(dimRefs, expr)) + } + } else { + // This is a non-apply-to-all variable + // TODO: We should change SubscriptRef so that it can include an explicit dimension + // name/ID (which we can pull from the section of the variable definition). + // Until then, we will include the subscript name only (and we do not yet support + // numeric subscript indices). + for (const elementElem of elementElems) { + const subscriptAttr = elementElem.attributes?.subscript + if (subscriptAttr === undefined) { + throw new Error(xmlError(varElem, ' subscript attribute is required in variable definition')) + } + const subscriptNames = subscriptAttr.split(',').map(s => s.trim()) + const subRefs: SubscriptRef[] = [] + for (const subscriptName of subscriptNames) { + if (parseInt(subscriptAttr)) { + throw new Error(xmlError(varElem, 'Numeric subscript indices are not currently supported')) + } + subRefs.push(subRef(subscriptName)) + } + const expr = parseEqnElem(varElem, elementElem) + if (expr) { + equationDefs.push(equation(subRefs, expr)) + } + } + } + } + + return equationDefs +} + +function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { + const varTagName = varElem.name + const eqnElem = firstElemOf(parentElem, 'eqn') + + // Interpret the element + // TODO: Handle the case where is defined using CDATA + const eqnText = eqnElem ? firstTextOf(eqnElem) : undefined + switch (varTagName) { + case 'aux': + if (eqnText === undefined) { + // Technically the is optional for an ; if not defined, we will skip it + return undefined + } + return parseExpr(eqnText.text) + + case 'stock': { + // elements are currently translated to a Vensim-style aux: + // INTEG({inflow}, {eqn}) + if (eqnText === undefined) { + // An is currently required for a + throw new Error(xmlError(varElem, 'Currently is required for a variable')) + } + const inflowElems = elemsOf(parentElem, ['inflow']) + if (inflowElems.length !== 1) { + // TODO: XMILE allows for multiple elements, but we don't support that yet + throw new Error(xmlError(varElem, 'Currently only one is supported for a variable')) + } + // TODO: Handle the case where is defined using CDATA + const inflowText = firstTextOf(inflowElems[0]) + if (inflowText === undefined) { + throw new Error(xmlError(varElem, 'Currently is required for a variable')) + } + + // TODO: We currently do not support certain options, so for now we + // fail fast if we encounter these + if (firstElemOf(parentElem, 'conveyor')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + if (firstElemOf(parentElem, 'queue')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + if (firstElemOf(parentElem, 'non_negative')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + + const initExpr = parseExpr(eqnText.text) + const inflowExpr = parseExpr(inflowText.text) + return call('INTEG', inflowExpr, initExpr) + } + + case 'flow': + // elements are currently translated to a Vensim-style aux + // TODO: The XMILE spec says some variants must not have an equation (in the case + // of conveyors or queues). For now, we don't support those, and we require an . + if (eqnText === undefined) { + // An is currently required for a + throw new Error(xmlError(varElem, 'Currently is required for a variable')) + } + // TODO: We currently do not support certain options, so for now we + // fail fast if we encounter these + if (firstElemOf(parentElem, 'multiplier')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + if (firstElemOf(parentElem, 'non_negative')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + if (firstElemOf(parentElem, 'overflow')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + if (firstElemOf(parentElem, 'leak')) { + throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + } + return parseExpr(eqnText.text) + + default: + throw new Error(xmlError(varElem, `Unhandled variable type '${varTagName}'`)) + } +} + +function parseExpr(exprText: string): Expr { + // Except for a few slight differences (e.g., `IF ... THEN ... ELSE ...`), the expression + // syntax in XMILE is the same as in Vensim, so we will use the existing Vensim expression + // parser here. The idiomatic way to do conditional statements in XMILE is: + // IF {condition} THEN {trueExpr} ELSE {falseExpr} + // But the spec allows for the Vensim form: + // IF_THEN_ELSE({condition}, {trueExpr}, {falseExpr}) + // Since we only support the latter in the compile package, it's better if we transform + // the XMILE form to Vensim form, and then we can use the Vensim expression parser. + exprText = exprText.replace(conditionalRegExp, 'IF THEN ELSE($1, $2, $3)') + return parseVensimExpr(exprText) +} diff --git a/packages/parse/src/xmile/xml.ts b/packages/parse/src/xmile/xml.ts new file mode 100644 index 00000000..362ac456 --- /dev/null +++ b/packages/parse/src/xmile/xml.ts @@ -0,0 +1,42 @@ +// Copyright (c) 2023 Climate Interactive / New Venture Fund + +import type { XmlElement, XmlText } from '@rgrove/parse-xml' +import { XmlNode } from '@rgrove/parse-xml' + +export function firstElemOf(parent: XmlElement | undefined, tagName: string): XmlElement | undefined { + return parent?.children.find(n => { + if (n.type === XmlNode.TYPE_ELEMENT) { + const e = n as XmlElement + return e.name === tagName + } else { + return undefined + } + }) as XmlElement +} + +export function firstTextOf(parent: XmlElement | undefined): XmlText | undefined { + return parent?.children.find(n => { + return n.type === XmlNode.TYPE_TEXT + }) as XmlText +} + +export function elemsOf(parent: XmlElement | undefined, tagNames: string[]): XmlElement[] { + if (parent === undefined) { + return [] + } + + const elems: XmlElement[] = [] + for (const n of parent.children) { + if (n.type === XmlNode.TYPE_ELEMENT) { + const e = n as XmlElement + if (tagNames.includes(e.name)) { + elems.push(e) + } + } + } + return elems +} + +export function xmlError(elem: XmlElement, msg: string): string { + return `${msg}: ${JSON.stringify(elem.toJSON(), null, 2)}` +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57c0984e..41eb11c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -350,6 +350,9 @@ importers: packages/parse: dependencies: + '@rgrove/parse-xml': + specifier: ^4.1.0 + version: 4.1.0 antlr4: specifier: 4.12.0 version: 4.12.0 @@ -947,6 +950,11 @@ packages: fastq: 1.13.0 dev: true + /@rgrove/parse-xml@4.1.0: + resolution: {integrity: sha512-pBiltENdy8SfI0AeR1e5TRpS9/9Gl0eiOEt6ful2jQfzsgvZYWqsKiBWaOCLdocQuk0wS7KOHI37n0C1pnKqTw==} + engines: {node: '>=14.0.0'} + dev: false + /@rollup/plugin-node-resolve@13.3.0(rollup@2.76.0): resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==} engines: {node: '>= 10.0.0'} From 0ab29b457c0078fa3025e2a33dded19b88a76471 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 24 Nov 2023 16:22:02 -0800 Subject: [PATCH 02/77] fix: update XMILE parser to account for SubscriptRange -> DimensionDef change --- .../src/xmile/parse-xmile-dimension-def.spec.ts | 4 ++-- .../parse/src/xmile/parse-xmile-dimension-def.ts | 8 ++++---- .../parse/src/xmile/parse-xmile-model.spec.ts | 8 ++++---- packages/parse/src/xmile/parse-xmile-model.ts | 16 ++++++++-------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts index 4d1e064d..0e8b1c6c 100644 --- a/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts @@ -4,7 +4,7 @@ import { describe, expect, it } from 'vitest' import { type XmlElement, parseXml } from '@rgrove/parse-xml' -import { subRange } from '../ast/ast-builders' +import { dimDef } from '../ast/ast-builders' import { parseXmileDimensionDef } from './parse-xmile-dimension-def' @@ -27,7 +27,7 @@ describe('parseXmileDimensionDef', () => { `) - expect(parseXmileDimensionDef(dim)).toEqual(subRange('DimA', 'DimA', ['A1', 'A2', 'A3'])) + expect(parseXmileDimensionDef(dim)).toEqual(dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'])) }) it('should throw an error if dimension name is not defined', () => { diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.ts index 31834182..78db4572 100644 --- a/packages/parse/src/xmile/parse-xmile-dimension-def.ts +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.ts @@ -4,17 +4,17 @@ import type { XmlElement } from '@rgrove/parse-xml' import { canonicalName } from '../_shared/names' -import type { SubscriptRange, SubscriptRef } from '../ast/ast-types' +import type { DimensionDef, SubscriptRef } from '../ast/ast-types' import { elemsOf, firstElemOf, xmlError } from './xml' /** - * Parse the given XMILE dimension (``) definition and return a `SubscriptRange` AST node. + * Parse the given XMILE dimension (``) definition and return a `DimensionDef` AST node. * * @param input A string containing the XMILE `` definition. - * @returns A `SubscriptRange` AST node. + * @returns A `DimensionDef` AST node. */ -export function parseXmileDimensionDef(dimElem: XmlElement): SubscriptRange { +export function parseXmileDimensionDef(dimElem: XmlElement): DimensionDef { // Extract const dimName = dimElem.attributes?.name if (dimName === undefined) { diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index 48549622..d9420a8f 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' -import { exprEqn, model, num, subRange, varDef } from '../ast/ast-builders' +import { dimDef, exprEqn, model, num, varDef } from '../ast/ast-builders' import { parseXmileModel } from './parse-xmile-model' @@ -65,7 +65,7 @@ describe('parseXmileModel', () => { ` const mdl = xmile(dims, '') - expect(parseXmileModel(mdl)).toEqual(model([subRange('DimA', 'DimA', ['A1', 'A2', 'A3'])], [])) + expect(parseXmileModel(mdl)).toEqual(model([dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'])], [])) }) it('should parse a model with dimension definition with comment', () => { @@ -78,7 +78,7 @@ describe('parseXmileModel', () => { ` const mdl = xmile(dims, '') expect(parseXmileModel(mdl)).toEqual( - model([subRange('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], []) + model([dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], []) ) }) @@ -113,7 +113,7 @@ describe('parseXmileModel', () => { expect(parseXmileModel(mdl)).toEqual( model( // dimension definitions - [subRange('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], + [dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], // variable definitions [exprEqn(varDef('x', ['DimA']), num(1), 'meters', 'comment is here')] diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index 0951d05d..4efaa431 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -3,7 +3,7 @@ import type { XmlElement } from '@rgrove/parse-xml' import { parseXml } from '@rgrove/parse-xml' -import type { Equation, Model, SubscriptRange } from '../ast/ast-types' +import type { DimensionDef, Equation, Model } from '../ast/ast-types' import { parseXmileDimensionDef } from './parse-xmile-dimension-def' import { parseXmileVariableDef } from './parse-xmile-variable-def' @@ -27,31 +27,31 @@ export function parseXmileModel(input: string): Model { throw new Error(msg) } - // Extract -> SubscriptRange[] - const subscriptRanges: SubscriptRange[] = parseDimensionDefs(xml.root) + // Extract -> DimensionDef[] + const dimensions: DimensionDef[] = parseDimensionDefs(xml.root) // Extract -> Equation[] const equations: Equation[] = parseVariableDefs(xml.root) return { - subscriptRanges, + dimensions, equations } } -function parseDimensionDefs(rootElem: XmlElement | undefined): SubscriptRange[] { - const subscriptRanges: SubscriptRange[] = [] +function parseDimensionDefs(rootElem: XmlElement | undefined): DimensionDef[] { + const dimensionDefs: DimensionDef[] = [] const dimensionsElem = firstElemOf(rootElem, 'dimensions') if (dimensionsElem) { // Extract -> SubscriptRange const dimElems = elemsOf(dimensionsElem, ['dim']) for (const dimElem of dimElems) { - subscriptRanges.push(parseXmileDimensionDef(dimElem)) + dimensionDefs.push(parseXmileDimensionDef(dimElem)) } } - return subscriptRanges + return dimensionDefs } function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { From 180e87a6a34c7ef21818ef73cf953a1593945a01 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 27 Nov 2023 14:20:40 -0800 Subject: [PATCH 03/77] fix: checkpoint work on parsing gf elements --- packages/parse/src/xmile/parse-xmile-model.ts | 4 +- .../xmile/parse-xmile-variable-def.spec.ts | 262 ++++++++++++++---- .../src/xmile/parse-xmile-variable-def.ts | 129 ++++++++- 3 files changed, 327 insertions(+), 68 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index 4efaa431..23a559ac 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -63,8 +63,8 @@ function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { const equations: Equation[] = [] const variablesElem = firstElemOf(modelElem, 'variables') if (variablesElem) { - // Extract variable definition (e.g., or or ) -> Equation[] - const varElems = elemsOf(variablesElem, ['aux', 'stock', 'flow']) + // Extract variable definition (e.g., , , , ) -> Equation[] + const varElems = elemsOf(variablesElem, ['aux', 'stock', 'flow', 'gf']) for (const varElem of varElems) { const eqns = parseXmileVariableDef(varElem) if (eqns) { diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index a013a9e1..1ac46f01 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest' import type { XmlElement } from '@rgrove/parse-xml' import { parseXml } from '@rgrove/parse-xml' -import { binaryOp, call, exprEqn, num, varDef, varRef } from '../ast/ast-builders' +import { binaryOp, call, exprEqn, lookupDef, lookupVarEqn, num, varDef, varRef } from '../ast/ast-builders' import { parseXmileVariableDef } from './parse-xmile-variable-def' @@ -86,6 +86,18 @@ describe('parseXmileVariableDef with ', () => { expect(() => parseXmileVariableDef(v)).toThrow('Currently is required for a variable') }) + it('should throw an error if stock variable definition has ', () => { + const v = xml(` + + + + 0,0.1,0.5,0.9,1 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' is not valid within a variable') + }) + it('should throw an error if stock variable definition has no ', () => { const v = xml(` @@ -151,7 +163,7 @@ describe('parseXmileVariableDef with ', () => { }) describe('parseXmileVariableDef with ', () => { - it('should parse a flow variable definition (without subscripts)', () => { + it('should parse a flow variable definition (without subscripts, defined with )', () => { const v = xml(` y + 10 @@ -160,6 +172,52 @@ describe('parseXmileVariableDef with ', () => { expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), binaryOp(varRef('y'), '+', num(10)))]) }) + it('should parse a flow variable definition (without subscripts, defined with and )', () => { + const v = xml(` + + + + 0,0.1,0.5,0.9,1 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + lookupVarEqn( + varDef('x'), + lookupDef([ + [0, 0], + [0.25, 0.1], + [0.5, 0.5], + [0.75, 0.9], + [1, 1] + ]) + ) + ]) + }) + + it('should parse a flow variable definition (without subscripts, defined with and )', () => { + const v = xml(` + + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 + + + `) + expect(parseXmileVariableDef(v)).toEqual([ + lookupVarEqn( + varDef('x'), + lookupDef([ + [0, 0], + [0.4, 0.1], + [0.5, 0.5], + [0.8, 0.9], + [1, 1] + ]) + ) + ]) + }) + it('should parse a flow variable definition (with one dimension, apply to all)', () => { const v = xml(` @@ -194,12 +252,12 @@ describe('parseXmileVariableDef with ', () => { ]) }) - it('should throw an error if flow variable definition has no ', () => { + it('should throw an error if flow variable definition has no or ', () => { const v = xml(` `) - expect(() => parseXmileVariableDef(v)).toThrow('Currently is required for a variable') + expect(() => parseXmileVariableDef(v)).toThrow('Currently or is required for a variable') }) // TODO: Support @@ -444,56 +502,150 @@ describe('parseXmileVariableDef with ', () => { `) expect(() => parseXmileVariableDef(v)).toThrow('Numeric subscript indices are not currently supported') }) +}) + +describe('parseXmileVariableDef with ', () => { + it('should parse a graphical function (with and ', () => { + const v = xml(` + + + 0,0.1,0.5,0.9,1 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + lookupVarEqn( + varDef('x'), + lookupDef([ + [0, 0], + [0.25, 0.1], + [0.5, 0.5], + [0.75, 0.9], + [1, 1] + ]) + ) + ]) + }) + + it('should parse a graphical function (with and ', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 + + `) + expect(parseXmileVariableDef(v)).toEqual( + lookupVarEqn( + varDef('x'), + lookupDef([ + [0, 0], + [0.4, 0.1], + [0.5, 0.5], + [0.8, 0.9], + [1, 1] + ]) + ) + ) + }) + + it('should parse a graphical function (with custom separator)', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1 + 0;0.1;0.5;0.9;1 + + `) + expect(parseXmileVariableDef(v)).toEqual( + lookupVarEqn( + varDef('x'), + lookupDef([ + [0, 0], + [0.4, 0.1], + [0.5, 0.5], + [0.8, 0.9], + [1, 1] + ]) + ) + ) + }) + + it('should throw an error if name is undefined', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' name attribute is required') + }) + + it('should throw an error if name is empty', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' name attribute is required') + }) + + // TODO: Support other types (extrapolate and discrete) + it('should throw an error if type is defined and is not "continuous"', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow('Currently "continuous" is the only type supported for ') + }) - // it('should parse a data variable definition (without subscripts)', () => { - // const eqn = `x ~~|` - // expect(parseXmileVariableDef(v)).toEqual(dataVarEqn(varDef('x'))) - // }) - - // it('should parse a data variable definition (with one dimension)', () => { - // const eqn = `x[a] ~~|` - // expect(parseXmileVariableDef(v)).toEqual(dataVarEqn(varDef('x', ['a']))) - // }) - - // it('should parse a data variable definition (with two dimensions)', () => { - // const eqn = `x[a, b] ~~|` - // expect(parseXmileVariableDef(v)).toEqual(dataVarEqn(varDef('x', ['a', 'b']))) - // }) - - // it('should parse a lookup definition (without lookup range)', () => { - // const eqn = `x( (0,0), (1,2), (2, 5) ) ~~|` - // expect(parseXmileVariableDef(v)).toEqual( - // lookupVarEqn( - // varDef('x'), - // lookupDef([ - // [0, 0], - // [1, 2], - // [2, 5] - // ]) - // ) - // ) - // }) - - // it('should parse a lookup definition (with lookup range)', () => { - // const eqn = `x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~|` - // expect(parseXmileVariableDef(v)).toEqual( - // lookupVarEqn( - // varDef('x'), - // lookupDef( - // [ - // [0, 0], - // [0.1, 0.01], - // [0.5, 0.7], - // [1, 1], - // [1.5, 1.2], - // [2, 1.3] - // ], - // { - // min: [0, 0], - // max: [2, 2] - // } - // ) - // ) - // ) - // }) + it('should throw an error if and are both defined', () => { + const v = xml(` + + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' must contain or but not both') + }) + + it('should throw an error if is empty', () => { + const v = xml(` + + + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' must have at least one element') + }) + + it('should throw an error if is undefined', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1,666 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' must be defined') + }) + + it('should throw an error if is empty', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1,666 + + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' must have at least one element') + }) + + it('should throw an error if and have different number of elements', () => { + const v = xml(` + + 0,0.4,0.5,0.8,1,666 + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' and must have the same number of elements') + }) }) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 669929c1..169b0b44 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -4,8 +4,8 @@ import type { XmlElement } from '@rgrove/parse-xml' import { canonicalName } from '../_shared/names' -import { call, subRef } from '../ast/ast-builders' -import type { Equation, Expr, SubscriptRef } from '../ast/ast-types' +import { call, lookupDef, subRef } from '../ast/ast-builders' +import type { Equation, Expr, LookupDef, LookupPoint, SubscriptRef } from '../ast/ast-types' import { parseVensimExpr } from '../vensim/parse-vensim-expr' @@ -25,7 +25,7 @@ const conditionalRegExp = /IF\s+(.*)\s+THEN\s+(.*)\s+ELSE\s+(.*)\s*/gi export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // Extract required variable name const varName = varElem.attributes?.name - if (varName === undefined) { + if (varName === undefined || varName.length === 0) { throw new Error(xmlError(varElem, `<${varElem.name}> name attribute is required`)) } const varId = canonicalName(varName) @@ -36,8 +36,9 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // Extract optional -> comment string const comment = firstElemOf(varElem, 'doc')?.text || '' - // Helper function that creates a single `Equation` instance for this variable definition - function equation(subscriptRefs: SubscriptRef[] | undefined, expr: Expr): Equation { + // Helper function that creates a single `Equation` instance with an expression for + // this variable definition + function exprEquation(subscriptRefs: SubscriptRef[] | undefined, expr: Expr): Equation { return { lhs: { varDef: { @@ -56,14 +57,52 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { } } + // Helper function that creates a single `Equation` instance with a lookup for + // this variable definition + function lookupEquation(subscriptRefs: SubscriptRef[] | undefined, lookup: LookupDef): Equation { + return { + lhs: { + varDef: { + kind: 'variable-def', + varName, + varId, + subscriptRefs + } + }, + rhs: { + kind: 'lookup', + lookupDef: lookup + }, + units, + comment + } + } + + if (varElem.name === 'gf') { + // This is a top-level + // TODO: Are subscripted elements allowed? If so, handle them here. + const lookup = parseGfElem(varElem) + return [lookupEquation(undefined, lookup)] + } + // Check for const dimensionsElem = firstElemOf(varElem, 'dimensions') const equationDefs: Equation[] = [] if (dimensionsElem === undefined) { // This is a non-subscripted variable - const expr = parseEqnElem(varElem, varElem) - if (expr) { - equationDefs.push(equation(undefined, expr)) + const gfElem = firstElemOf(varElem, 'gf') + if (gfElem) { + // The variable is defined with a graphical function + // TODO: Throw error if this is a variable ( is only allowed for + // and variables) + const lookup = parseGfPoints(varElem) + equationDefs.push(lookupEquation(undefined, lookup)) + } else { + // The variable is defined with an equation + const expr = parseEqnElem(varElem, varElem) + if (expr) { + equationDefs.push(exprEquation(undefined, expr)) + } } } else { // This is an array (subscripted) variable. An array variable definition will include @@ -81,13 +120,14 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // If it is an apply-to-all variable, there will be a single . If it is a // non-apply-to-all variable, there will be one or more elements that define // the separate equation for each "instance". + // TODO: Handle apply-to-all variable defs that contain ? const elementElems = elemsOf(varElem, ['element']) if (elementElems.length === 0) { // This is an apply-to-all variable const dimRefs = dimNames.map(subRef) const expr = parseEqnElem(varElem, varElem) if (expr) { - equationDefs.push(equation(dimRefs, expr)) + equationDefs.push(exprEquation(dimRefs, expr)) } } else { // This is a non-apply-to-all variable @@ -95,6 +135,7 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // name/ID (which we can pull from the section of the variable definition). // Until then, we will include the subscript name only (and we do not yet support // numeric subscript indices). + // TODO: Handle non-apply-to-all variable defs that contain ? for (const elementElem of elementElems) { const subscriptAttr = elementElem.attributes?.subscript if (subscriptAttr === undefined) { @@ -103,14 +144,14 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { const subscriptNames = subscriptAttr.split(',').map(s => s.trim()) const subRefs: SubscriptRef[] = [] for (const subscriptName of subscriptNames) { - if (parseInt(subscriptAttr)) { + if (!isNaN(parseInt(subscriptAttr))) { throw new Error(xmlError(varElem, 'Numeric subscript indices are not currently supported')) } subRefs.push(subRef(subscriptName)) } const expr = parseEqnElem(varElem, elementElem) if (expr) { - equationDefs.push(equation(subRefs, expr)) + equationDefs.push(exprEquation(subRefs, expr)) } } } @@ -210,3 +251,69 @@ function parseExpr(exprText: string): Expr { exprText = exprText.replace(conditionalRegExp, 'IF THEN ELSE($1, $2, $3)') return parseVensimExpr(exprText) } + +function parseGfElem(varElem: XmlElement, gfElem: XmlElement): LookupDef { + // Parse the required + const yptsElem = firstElemOf(gfElem, 'ypts') + if (yptsElem === undefined) { + throw new Error(xmlError(varElem, 'TODO')) + } + const ypts = parseGfPts(varElem, yptsElem) + if (ypts.length === 0) { + throw new Error(xmlError(varElem, 'TODO')) + } + + // Check for or (must be one or the other) + const xptsElem = firstElemOf(gfElem, 'xpts') + const xscaleElem = firstElemOf(gfElem, 'xscale') + if (xptsElem && xscaleElem) { + throw new Error(xmlError(varElem, 'TODO')) + } else if (xptsElem === undefined && xscaleElem === undefined) { + throw new Error(xmlError(varElem, 'TODO')) + } + + let xpts: number[] + if (xPtsElem) { + // Parse the + const ypts = parseGfPts(varElem, yptsElem) + if (ypts.length === 0) { + throw new Error(xmlError(varElem, 'TODO')) + } + } else { + // Parse the + // TODO: min and max + // } + } + + // TODO: Check for same length as + if (xpts.length !== ypts.length) { + throw new Error(xmlError(varElem, 'TODO')) + } + + // Zip the arrays + const points: LookupPoint[] = [] + for (let i = 0; i < xpts.length; i++) { + points.push([xpts[i], ypts[i]]) + } + return lookupDef(points) +} + +function parseGfPts(varElem: XmlElement, ptsElem: XmlElement): number[] { + const ptsText = firstTextOf(ptsElem)?.text + if (ptsText === undefined) { + return [] + } + + const sep = ptsElem.attributes?.sep || ',' + const elems = ptsText.split(sep) + const nums: number[] = [] + for (const elem of elems) { + const numText = elem.trim() + const num = parseFloat(numText) + if (isNaN(num)) { + throw new Error(xmlError(varElem, `Invalid number value '${numText}' in <${ptsElem.name}>'`)) + } + nums.push(num) + } + return nums +} From 5182e02dc11e1b3e13a68fa9f258001d50cdf5a9 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Wed, 24 Apr 2024 17:12:00 -0700 Subject: [PATCH 04/77] fix: flesh out parsing of gf elements --- .../xmile/parse-xmile-variable-def.spec.ts | 57 ++++++++++-- .../src/xmile/parse-xmile-variable-def.ts | 86 ++++++++++++++----- 2 files changed, 112 insertions(+), 31 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 1ac46f01..65ee4a14 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -95,7 +95,7 @@ describe('parseXmileVariableDef with ', () => { `) - expect(() => parseXmileVariableDef(v)).toThrow(' is not valid within a variable') + expect(() => parseXmileVariableDef(v)).toThrow(' is only allowed for and variables') }) it('should throw an error if stock variable definition has no ', () => { @@ -199,7 +199,7 @@ describe('parseXmileVariableDef with ', () => { const v = xml(` - 0,0.4,0.5,0.8,1 + 0,0.4,0.5,0.8,1 0,0.1,0.5,0.9,1 @@ -526,14 +526,14 @@ describe('parseXmileVariableDef with ', () => { ]) }) - it('should parse a graphical function (with and ', () => { + it('should parse a graphical function (with and and explicit type)', () => { const v = xml(` - + 0,0.4,0.5,0.8,1 0,0.1,0.5,0.9,1 `) - expect(parseXmileVariableDef(v)).toEqual( + expect(parseXmileVariableDef(v)).toEqual([ lookupVarEqn( varDef('x'), lookupDef([ @@ -544,7 +544,7 @@ describe('parseXmileVariableDef with ', () => { [1, 1] ]) ) - ) + ]) }) it('should parse a graphical function (with custom separator)', () => { @@ -554,7 +554,7 @@ describe('parseXmileVariableDef with ', () => { 0;0.1;0.5;0.9;1 `) - expect(parseXmileVariableDef(v)).toEqual( + expect(parseXmileVariableDef(v)).toEqual([ lookupVarEqn( varDef('x'), lookupDef([ @@ -565,7 +565,7 @@ describe('parseXmileVariableDef with ', () => { [1, 1] ]) ) - ) + ]) }) it('should throw an error if name is undefined', () => { @@ -599,6 +599,15 @@ describe('parseXmileVariableDef with ', () => { expect(() => parseXmileVariableDef(v)).toThrow('Currently "continuous" is the only type supported for ') }) + it('should throw an error if neither nor is defined', () => { + const v = xml(` + + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' must contain either or ') + }) + it('should throw an error if and are both defined', () => { const v = xml(` @@ -610,6 +619,36 @@ describe('parseXmileVariableDef with ', () => { expect(() => parseXmileVariableDef(v)).toThrow(' must contain or but not both') }) + it('should throw an error if min is undefined', () => { + const v = xml(` + + + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' min attribute is required') + }) + + it('should throw an error if max is undefined', () => { + const v = xml(` + + + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' max attribute is required') + }) + + it('should throw an error if min >= max', () => { + const v = xml(` + + + 0,0.1,0.5,0.9,1 + + `) + expect(() => parseXmileVariableDef(v)).toThrow(' max attribute must be > min attribute') + }) + it('should throw an error if is empty', () => { const v = xml(` @@ -626,7 +665,7 @@ describe('parseXmileVariableDef with ', () => { 0,0.4,0.5,0.8,1,666 `) - expect(() => parseXmileVariableDef(v)).toThrow(' must be defined') + expect(() => parseXmileVariableDef(v)).toThrow(' must be defined for a ') }) it('should throw an error if is empty', () => { diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 169b0b44..e4f287a9 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -24,10 +24,7 @@ const conditionalRegExp = /IF\s+(.*)\s+THEN\s+(.*)\s+ELSE\s+(.*)\s*/gi */ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // Extract required variable name - const varName = varElem.attributes?.name - if (varName === undefined || varName.length === 0) { - throw new Error(xmlError(varElem, `<${varElem.name}> name attribute is required`)) - } + const varName = parseRequiredAttr(varElem, varElem, 'name') const varId = canonicalName(varName) // Extract optional -> units string @@ -81,7 +78,7 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { if (varElem.name === 'gf') { // This is a top-level // TODO: Are subscripted elements allowed? If so, handle them here. - const lookup = parseGfElem(varElem) + const lookup = parseGfElem(varElem, varElem) return [lookupEquation(undefined, lookup)] } @@ -93,9 +90,10 @@ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { const gfElem = firstElemOf(varElem, 'gf') if (gfElem) { // The variable is defined with a graphical function - // TODO: Throw error if this is a variable ( is only allowed for - // and variables) - const lookup = parseGfPoints(varElem) + if (varElem.name !== 'flow' && varElem.name !== 'aux') { + throw new Error(xmlError(varElem, ' is only allowed for and variables')) + } + const lookup = parseGfElem(varElem, gfElem) equationDefs.push(lookupEquation(undefined, lookup)) } else { // The variable is defined with an equation @@ -211,12 +209,12 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { } case 'flow': - // elements are currently translated to a Vensim-style aux + // elements with an are currently translated to a Vensim-style aux // TODO: The XMILE spec says some variants must not have an equation (in the case // of conveyors or queues). For now, we don't support those, and we require an . if (eqnText === undefined) { // An is currently required for a - throw new Error(xmlError(varElem, 'Currently is required for a variable')) + throw new Error(xmlError(varElem, 'Currently or is required for a variable')) } // TODO: We currently do not support certain options, so for now we // fail fast if we encounter these @@ -253,41 +251,61 @@ function parseExpr(exprText: string): Expr { } function parseGfElem(varElem: XmlElement, gfElem: XmlElement): LookupDef { + // Parse the optional `type` attribute + const typeAttr = parseOptionalAttr(gfElem, 'type') + if (typeAttr && typeAttr !== 'continuous') { + throw new Error(xmlError(varElem, 'Currently "continuous" is the only type supported for ')) + } + // Parse the required const yptsElem = firstElemOf(gfElem, 'ypts') if (yptsElem === undefined) { - throw new Error(xmlError(varElem, 'TODO')) + throw new Error(xmlError(varElem, ' must be defined for a ')) } const ypts = parseGfPts(varElem, yptsElem) if (ypts.length === 0) { - throw new Error(xmlError(varElem, 'TODO')) + throw new Error(xmlError(varElem, ' must have at least one element')) } // Check for or (must be one or the other) const xptsElem = firstElemOf(gfElem, 'xpts') const xscaleElem = firstElemOf(gfElem, 'xscale') if (xptsElem && xscaleElem) { - throw new Error(xmlError(varElem, 'TODO')) + throw new Error(xmlError(varElem, ' must contain or but not both')) } else if (xptsElem === undefined && xscaleElem === undefined) { - throw new Error(xmlError(varElem, 'TODO')) + throw new Error(xmlError(varElem, ' must contain either or ')) } let xpts: number[] - if (xPtsElem) { + if (xptsElem) { // Parse the - const ypts = parseGfPts(varElem, yptsElem) - if (ypts.length === 0) { - throw new Error(xmlError(varElem, 'TODO')) + xpts = parseGfPts(varElem, xptsElem) + if (xpts.length === 0) { + throw new Error(xmlError(varElem, ' must have at least one element')) } } else { // Parse the - // TODO: min and max - // } + const xMin = parseFloatAttr(varElem, xscaleElem, 'min') + const xMax = parseFloatAttr(varElem, xscaleElem, 'max') + if (xMin > xMax) { + throw new Error(xmlError(varElem, ' max attribute must be > min attribute')) + } + xpts = Array(ypts.length) + const xRange = xMax - xMin + if (ypts.length === 1) { + // TODO: Error? + xpts[0] = 0 + } else { + for (let i = 0; i < ypts.length; i++) { + const frac = i / (ypts.length - 1) + xpts[i] = xMin + xRange * frac + } + } } - // TODO: Check for same length as + // Check for same length as if (xpts.length !== ypts.length) { - throw new Error(xmlError(varElem, 'TODO')) + throw new Error(xmlError(varElem, ' and must have the same number of elements')) } // Zip the arrays @@ -311,9 +329,33 @@ function parseGfPts(varElem: XmlElement, ptsElem: XmlElement): number[] { const numText = elem.trim() const num = parseFloat(numText) if (isNaN(num)) { + console.log(JSON.stringify(ptsElem)) throw new Error(xmlError(varElem, `Invalid number value '${numText}' in <${ptsElem.name}>'`)) } nums.push(num) } return nums } + +function parseRequiredAttr(varElem: XmlElement, elem: XmlElement, attrName: string): string { + let s = elem.attributes && elem.attributes[attrName] + s = s?.trim() + if (s === undefined || s.length === 0) { + throw new Error(xmlError(varElem, `<${elem.name}> ${attrName} attribute is required`)) + } + return s +} + +function parseOptionalAttr(elem: XmlElement, attrName: string): string { + const s = elem.attributes && elem.attributes[attrName] + return s?.trim() +} + +function parseFloatAttr(varElem: XmlElement, elem: XmlElement, attrName: string): number { + const s = parseRequiredAttr(varElem, elem, attrName) + const num = parseFloat(s) + if (isNaN(num)) { + throw new Error(xmlError(varElem, `Invalid number value '${s}' for <${elem.name}> ${attrName} attribute'`)) + } + return num +} From 2c21f7520e9e2d4891301d68a53af0605013fa11 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Thu, 25 Apr 2024 11:21:49 -0700 Subject: [PATCH 05/77] fix: expose xmile parsing functions in index --- packages/parse/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/parse/src/index.ts b/packages/parse/src/index.ts index 7459e0fd..f0c3d7ac 100644 --- a/packages/parse/src/index.ts +++ b/packages/parse/src/index.ts @@ -9,3 +9,7 @@ export * from './vensim/parse-vensim-expr' export * from './vensim/parse-vensim-equation' export * from './vensim/parse-vensim-model' export * from './vensim/vensim-parse-context' + +export * from './xmile/parse-xmile-dimension-def' +export * from './xmile/parse-xmile-model' +export * from './xmile/parse-xmile-variable-def' From b0343a1c5d524744beab4d6ee0b23cc79b460a52 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 16 Aug 2025 12:31:10 -0700 Subject: [PATCH 06/77] fix: update XMILE code in parse package to use new canonicalId function --- packages/parse/src/xmile/parse-xmile-dimension-def.ts | 6 +++--- packages/parse/src/xmile/parse-xmile-variable-def.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.ts index 78db4572..0e4d111a 100644 --- a/packages/parse/src/xmile/parse-xmile-dimension-def.ts +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.ts @@ -2,7 +2,7 @@ import type { XmlElement } from '@rgrove/parse-xml' -import { canonicalName } from '../_shared/names' +import { canonicalId } from '../_shared/canonical-id' import type { DimensionDef, SubscriptRef } from '../ast/ast-types' @@ -34,7 +34,7 @@ export function parseXmileDimensionDef(dimElem: XmlElement): DimensionDef { throw new Error(xmlError(dimElem, ' name attribute is required for dimension element definition')) } - const subId = canonicalName(subName) + const subId = canonicalId(subName) subscriptRefs.push({ subId, subName @@ -44,7 +44,7 @@ export function parseXmileDimensionDef(dimElem: XmlElement): DimensionDef { // Extract -> comment string const comment = firstElemOf(dimElem, 'doc')?.text || '' - const dimId = canonicalName(dimName) + const dimId = canonicalId(dimName) return { dimName, dimId, diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index e4f287a9..452d2b40 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -2,7 +2,7 @@ import type { XmlElement } from '@rgrove/parse-xml' -import { canonicalName } from '../_shared/names' +import { canonicalId } from '../_shared/canonical-id' import { call, lookupDef, subRef } from '../ast/ast-builders' import type { Equation, Expr, LookupDef, LookupPoint, SubscriptRef } from '../ast/ast-types' @@ -25,7 +25,7 @@ const conditionalRegExp = /IF\s+(.*)\s+THEN\s+(.*)\s+ELSE\s+(.*)\s*/gi export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // Extract required variable name const varName = parseRequiredAttr(varElem, varElem, 'name') - const varId = canonicalName(varName) + const varId = canonicalId(varName) // Extract optional -> units string const units = firstElemOf(varElem, 'units')?.text || '' From 89b98d251b4957683f3e568bb648cc6b09a9fc6e Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 16 Aug 2025 12:38:29 -0700 Subject: [PATCH 07/77] fix: add initial support for parsing XMILE models in compile package --- packages/compile/src/_tests/test-support.ts | 15 ++++- packages/compile/src/index.js | 2 +- ....spec.ts => read-equations-vensim.spec.ts} | 2 +- packages/compile/src/parse-and-generate.js | 58 +++++++++++-------- 4 files changed, 50 insertions(+), 27 deletions(-) rename packages/compile/src/model/{read-equations.spec.ts => read-equations-vensim.spec.ts} (99%) diff --git a/packages/compile/src/_tests/test-support.ts b/packages/compile/src/_tests/test-support.ts index e44247da..80ccd779 100644 --- a/packages/compile/src/_tests/test-support.ts +++ b/packages/compile/src/_tests/test-support.ts @@ -154,11 +154,22 @@ export function parseVensimModel(modelName: string): ParsedModel { const modelDir = sampleModelDir(modelName) const modelFile = resolve(modelDir, `${modelName}.mdl`) const mdlContent = readFileSync(modelFile, 'utf8') - return parseModel(mdlContent, modelDir) + return parseModel(mdlContent, 'vensim', modelDir) } export function parseInlineVensimModel(mdlContent: string, modelDir?: string): ParsedModel { - return parseModel(mdlContent, modelDir) + return parseModel(mdlContent, 'vensim', modelDir) +} + +export function parseXmileModel(modelName: string): ParsedModel { + const modelDir = sampleModelDir(modelName) + const modelFile = resolve(modelDir, `${modelName}.stmx`) + const mdlContent = readFileSync(modelFile, 'utf8') + return parseModel(mdlContent, 'xmile', modelDir) +} + +export function parseInlineXmileModel(mdlContent: string, modelDir?: string): ParsedModel { + return parseModel(mdlContent, 'xmile', modelDir) } function prettyVar(variable: Variable): string { diff --git a/packages/compile/src/index.js b/packages/compile/src/index.js index 084f89d4..b682204d 100644 --- a/packages/compile/src/index.js +++ b/packages/compile/src/index.js @@ -35,7 +35,7 @@ export function parseInlineVensimModel(mdlContent /*: string*/, modelDir /*?: st // the preprocess step, and in the case of the new parser (which implicitly runs the // preprocess step), don't sort the definitions. This makes it easier to do apples // to apples comparisons on the outputs from the two parser implementations. - return parseModel(mdlContent, modelDir, { sort: false }) + return parseModel(mdlContent, 'vensim', modelDir, { sort: false }) } /** diff --git a/packages/compile/src/model/read-equations.spec.ts b/packages/compile/src/model/read-equations-vensim.spec.ts similarity index 99% rename from packages/compile/src/model/read-equations.spec.ts rename to packages/compile/src/model/read-equations-vensim.spec.ts index 4d21842f..040a5afc 100644 --- a/packages/compile/src/model/read-equations.spec.ts +++ b/packages/compile/src/model/read-equations-vensim.spec.ts @@ -99,7 +99,7 @@ function v(lhs: string, formula: string, overrides?: Partial): Variabl return variable as Variable } -describe('readEquations', () => { +describe('readEquations (from Vensim model)', () => { it('should work for simple equation with explicit parentheses', () => { const vars = readInlineModel(` x = 1 ~~| diff --git a/packages/compile/src/parse-and-generate.js b/packages/compile/src/parse-and-generate.js index b08a62b8..4713650d 100644 --- a/packages/compile/src/parse-and-generate.js +++ b/packages/compile/src/parse-and-generate.js @@ -3,7 +3,7 @@ import path from 'path' import B from 'bufx' -import { parseVensimModel } from '@sdeverywhere/parse' +import { parseVensimModel, parseXmileModel } from '@sdeverywhere/parse' import { readXlsx } from './_shared/helpers.js' import { readDat } from './_shared/read-dat.js' @@ -13,7 +13,7 @@ import { getDirectSubscripts } from './model/read-subscripts.js' import { generateCode } from './generate/gen-code.js' /** - * Parse a Vensim model and generate C code. + * Parse a Vensim or XMILE model and generate C code. * * This is the primary entrypoint for the `sde generate` command. * @@ -25,7 +25,8 @@ import { generateCode } from './generate/gen-code.js' * - If `operations` has 'convertNames', no output will be generated, but the results of model * analysis will be available. * - * @param {string} input The preprocessed Vensim model text. + * @param {string} input The preprocessed Vensim or XMILE model text. + * @param {string} modelKind The kind of model to parse, either 'vensim' or 'xmile'. * @param {*} spec The model spec (from the JSON file). * @param {string[]} operations The set of operations to perform; can include 'generateC', 'generateJS', * 'printVarList', 'printRefIdTest', 'convertNames'. If the array is empty, the model will be @@ -37,7 +38,7 @@ import { generateCode } from './generate/gen-code.js' * @param {string} [varname] The variable name passed to the 'sde causes' command. * @return A string containing the generated C code. */ -export async function parseAndGenerate(input, spec, operations, modelDirname, modelName, buildDir, varname) { +export async function parseAndGenerate(input, modelKind, spec, operations, modelDirname, modelName, buildDir, varname) { // Read time series from external DAT files into a single object. // externalDatfiles is an array of either filenames or objects // giving a variable name prefix as the key and a filename as the value. @@ -68,7 +69,7 @@ export async function parseAndGenerate(input, spec, operations, modelDirname, mo } // Parse the model and generate code - let parsedModel = parseModel(input, modelDirname) + let parsedModel = parseModel(input, modelKind, modelDirname) let code = generateCode(parsedModel, { spec, operations, extData, directData, modelDirname, varname }) function writeOutput(filename, text) { @@ -135,33 +136,44 @@ export function printNames(namesPathname, operation) { * TODO: Fix return type * * @param {string} input The string containing the model text. + * @param {string} modelKind The kind of model to parse, either 'vensim' or 'xmile'. * @param {string} modelDir The absolute path to the directory containing the mdl file. * The dat, xlsx, and csv files referenced by the model will be relative to this directory. * @param {Object} [options] The options that control parsing. * @param {boolean} options.sort Whether to sort definitions alphabetically in the preprocess step. * @return {*} A parsed tree representation of the model. */ -export function parseModel(input, modelDir, options) { - // Prepare the parse context that provides access to external data files - let parseContext /*: VensimParseContext*/ - if (modelDir) { - parseContext = { - getDirectSubscripts(fileName, tabOrDelimiter, firstCell, lastCell /*, prefix*/) { - // Resolve the CSV file relative the model directory - const csvPath = path.resolve(modelDir, fileName) - - // Read the subscripts from the CSV file - return getDirectSubscripts(csvPath, tabOrDelimiter, firstCell, lastCell) +export function parseModel(input, modelKind, modelDir, options) { + if (modelKind === 'vensim') { + // Prepare the parse context that provides access to external data files + let parseContext /*: VensimParseContext*/ + if (modelDir) { + parseContext = { + getDirectSubscripts(fileName, tabOrDelimiter, firstCell, lastCell /*, prefix*/) { + // Resolve the CSV file relative the model directory + const csvPath = path.resolve(modelDir, fileName) + + // Read the subscripts from the CSV file + return getDirectSubscripts(csvPath, tabOrDelimiter, firstCell, lastCell) + } } } - } - // Parse the model - const sort = options?.sort === true - const root = parseVensimModel(input, parseContext, sort) + // Parse the Vensim model + const sort = options?.sort === true + const root = parseVensimModel(input, parseContext, sort) + + return { + kind: 'vensim', + root + } + } else { + // Parse the XMILE model + const root = parseXmileModel(input) - return { - kind: 'vensim', - root + return { + kind: 'xmile', + root + } } } From d7c15c0ed4e992a40fea517b8e7e2fc6a52d07f8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 15:31:03 -0700 Subject: [PATCH 08/77] test: add initial support for testing stmx files --- tests/modeltests | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/modeltests b/tests/modeltests index f7294ea3..1f145818 100755 --- a/tests/modeltests +++ b/tests/modeltests @@ -4,6 +4,11 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" PROJ_DIR=$SCRIPT_DIR/.. MODELS_DIR=$PROJ_DIR/models +# If INPUT_FORMAT is not set, default to "mdl" +if [[ -z $INPUT_FORMAT ]]; then + export INPUT_FORMAT=mdl +fi + # If GEN_FORMAT is not set, default to "c" if [[ -z $GEN_FORMAT ]]; then export GEN_FORMAT=c @@ -41,7 +46,7 @@ function test { if [[ $MODEL == "allocate" ]]; then PRECISION="1e-2" fi - node "$SDE_MAIN" test $TEST_ARGS --genformat=$GEN_FORMAT -p $PRECISION "$MODEL_DIR/$MODEL" + node "$SDE_MAIN" test $TEST_ARGS --genformat=$GEN_FORMAT -p $PRECISION "$MODEL_DIR/$MODEL.$INPUT_FORMAT" fi # Run additional script to validate output From 22cb090ac929435df67febc230fdc1befc2e5474 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 15:37:21 -0700 Subject: [PATCH 09/77] fix: update cli package to accept stmx and xmile files + infer model kind from file extension --- packages/cli/src/sde-causes.js | 4 ++-- packages/cli/src/sde-generate.js | 5 +++-- packages/cli/src/sde-names.js | 4 ++-- packages/cli/src/utils.js | 26 +++++++++++++++++++++++--- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/sde-causes.js b/packages/cli/src/sde-causes.js index 8942fce3..5f5662e8 100644 --- a/packages/cli/src/sde-causes.js +++ b/packages/cli/src/sde-causes.js @@ -18,11 +18,11 @@ let handler = argv => { } let causes = async (model, varname, opts) => { // Get the model name and directory from the model argument. - let { modelDirname, modelPathname, modelName } = modelPathProps(model) + let { modelDirname, modelPathname, modelName, modelKind } = modelPathProps(model) let spec = parseSpec(opts.spec) // Parse the model to get variable and subscript information. let input = readFileSync(modelPathname, 'utf8') - await parseAndGenerate(input, spec, ['printRefGraph'], modelDirname, modelName, '', varname) + await parseAndGenerate(input, modelKind, spec, ['printRefGraph'], modelDirname, modelName, '', varname) } export default { command, diff --git a/packages/cli/src/sde-generate.js b/packages/cli/src/sde-generate.js index da7abcee..39d571e6 100644 --- a/packages/cli/src/sde-generate.js +++ b/packages/cli/src/sde-generate.js @@ -57,7 +57,8 @@ export let handler = async argv => { export let generate = async (model, opts) => { // Get the model name and directory from the model argument. - let { modelDirname, modelName, modelPathname } = modelPathProps(model) + let { modelDirname, modelName, modelPathname, modelKind } = modelPathProps(model) + console.log(modelPathname, modelKind) // Ensure the build directory exists. let buildDirname = buildDir(opts.builddir, modelDirname) let spec = parseSpec(opts.spec) @@ -87,7 +88,7 @@ export let generate = async (model, opts) => { if (opts.refidtest) { operations.push('printRefIdTest') } - await parseAndGenerate(mdlContent, spec, operations, modelDirname, modelName, buildDirname) + await parseAndGenerate(mdlContent, modelKind, spec, operations, modelDirname, modelName, buildDirname) } export default { diff --git a/packages/cli/src/sde-names.js b/packages/cli/src/sde-names.js index 6db8ff72..6432dfa0 100644 --- a/packages/cli/src/sde-names.js +++ b/packages/cli/src/sde-names.js @@ -26,11 +26,11 @@ let handler = argv => { } let names = async (model, namesPathname, opts) => { // Get the model name and directory from the model argument. - let { modelDirname, modelPathname, modelName } = modelPathProps(model) + let { modelDirname, modelPathname, modelName, modelKind } = modelPathProps(model) let spec = parseSpec(opts.spec) // Parse the model to get variable and subscript information. let input = readFileSync(modelPathname, 'utf8') - await parseAndGenerate(input, spec, ['convertNames'], modelDirname, modelName, '') + await parseAndGenerate(input, modelKind, spec, ['convertNames'], modelDirname, modelName, '') // Read each variable name from the names file and convert it. printNames(namesPathname, opts.toc ? 'to-c' : 'to-vensim') } diff --git a/packages/cli/src/utils.js b/packages/cli/src/utils.js index 6eccd4e3..0c4f7606 100644 --- a/packages/cli/src/utils.js +++ b/packages/cli/src/utils.js @@ -24,25 +24,45 @@ export function execCmd(cmd) { } /** - * Normalize a model pathname that may or may not include the .mdl extension. + * Normalize a model pathname that may or may not include the .mdl or .xmile/.stmx extension. + * If the pathname does not end with .mdl, .xmile, or .stmx, this will attempt to find a + * file with one of those extensions. * If there is not a path in the model argument, default to the current working directory. + * * Return an object with properties that look like this: * modelDirname: '/Users/todd/src/models/arrays' * modelName: 'arrays' * modelPathname: '/Users/todd/src/models/arrays/arrays.mdl' + * modelKind: 'vensim' * * @param model A path to a Vensim model file. * @return An object with the properties specified above. */ export function modelPathProps(model) { - let p = R.merge({ ext: '.mdl' }, R.pick(['dir', 'name'], path.parse(model))) + const parsedPath = path.parse(model) + if (parsedPath.ext === '') { + const exts = ['.mdl', '.xmile', '.stmx'] + const paths = exts.map(ext => path.join(parsedPath.dir, parsedPath.name + ext)) + const existingPaths = paths.filter(path => fs.existsSync(path)) + if (existingPaths.length > 1) { + throw new Error( + `Found multiple files that match '${model}'; please specify a file with a .mdl, .xmile, or .stmx extension` + ) + } + if (existingPaths.length === 0) { + throw new Error(`No {mdl,xmile,stmx} file found for ${model}`) + } + parsedPath.ext = path.extname(existingPaths[0]) + } + let p = R.merge({ ext: parsedPath.ext }, R.pick(['dir', 'name'], parsedPath)) if (R.isEmpty(p.dir)) { p.dir = process.cwd() } return { modelDirname: p.dir, modelName: p.name, - modelPathname: path.format(p) + modelPathname: path.format(p), + modelKind: p.ext === '.mdl' ? 'vensim' : 'xmile' } } From a56e83991f9ecfb0cfbecdbe0b25fa3a90a0e29c Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 15:37:51 -0700 Subject: [PATCH 10/77] test: add helper function for generating XMILE content --- packages/compile/src/_tests/test-support.ts | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/compile/src/_tests/test-support.ts b/packages/compile/src/_tests/test-support.ts index 80ccd779..7e76faee 100644 --- a/packages/compile/src/_tests/test-support.ts +++ b/packages/compile/src/_tests/test-support.ts @@ -172,6 +172,46 @@ export function parseInlineXmileModel(mdlContent: string, modelDir?: string): Pa return parseModel(mdlContent, 'xmile', modelDir) } +export function xmile(dimensions: string, variables: string): string { + let dims: string + if (dimensions.length > 0) { + dims = `\ + + ${dimensions} + ` + } else { + dims = '' + } + + let vars: string + if (variables.length > 0) { + vars = `\ + + ${variables} + ` + } else { + vars = '' + } + + return `\ + +
+ + Ventana Systems, xmutil + Vensim, xmutil +
+ + 0 + 100 +
1
+
+${dims} + + ${vars} + +
` +} + function prettyVar(variable: Variable): string { const stringify = (x: any) => { return JSON.stringify(x) From bff38378acc02be3fc09dec39643417e5fca1e63 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 15:43:18 -0700 Subject: [PATCH 11/77] test: create separate files for vensim and xmile paths for read-equations and read-subscripts tests --- .../src/model/read-equations-xmile.spec.ts | 11151 ++++++++++++++++ ...spec.ts => read-subscripts-vensim.spec.ts} | 2 +- .../src/model/read-subscripts-xmile.spec.ts | 687 + 3 files changed, 11839 insertions(+), 1 deletion(-) create mode 100644 packages/compile/src/model/read-equations-xmile.spec.ts rename packages/compile/src/model/{read-subscripts.spec.ts => read-subscripts-vensim.spec.ts} (99%) create mode 100644 packages/compile/src/model/read-subscripts-xmile.spec.ts diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts new file mode 100644 index 00000000..effacd4c --- /dev/null +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -0,0 +1,11151 @@ +import { describe, expect, it } from 'vitest' + +import { canonicalName, resetHelperState } from '../_shared/helpers' +import { resetSubscriptsAndDimensions } from '../_shared/subscript' + +import Model from './model' +import { default as VariableImpl } from './variable' + +import type { ParsedModel, Variable } from '../_tests/test-support' +import { parseInlineXmileModel, parseXmileModel, sampleModelDir, xmile } from '../_tests/test-support' + +/** + * This is a shorthand for the following steps to read equations: + * - parseXmileModel + * - readSubscriptRanges + * - resolveSubscriptRanges + * - readVariables + * - analyze (this includes readEquations) + */ +function readSubscriptsAndEquationsFromSource( + source: { + modelText?: string + modelName?: string + modelDir?: string + }, + opts?: { + specialSeparationDims?: { [key: string]: string } + separateAllVarsWithDims?: string[][] + } +): Variable[] { + // XXX: These steps are needed due to subs/dims and variables being in module-level storage + resetHelperState() + resetSubscriptsAndDimensions() + Model.resetModelState() + + let parsedModel: ParsedModel + if (source.modelText) { + parsedModel = parseInlineXmileModel(source.modelText) + } else { + parsedModel = parseXmileModel(source.modelName) + } + + const spec = { + specialSeparationDims: opts?.specialSeparationDims, + separateAllVarsWithDims: opts?.separateAllVarsWithDims + } + + let modelDir = source.modelDir + if (modelDir === undefined) { + if (source.modelName) { + modelDir = sampleModelDir(source.modelName) + } + } + + Model.read(parsedModel, spec, /*extData=*/ undefined, /*directData=*/ undefined, modelDir, { + reduceVariables: false, + stopAfterAnalyze: true + }) + + return Model.variables.map(v => { + // XXX: Strip out the new parsedEqn field, since we don't need it for comparing + delete v.parsedEqn + return v + }) +} + +function readInlineModel( + modelText: string, + modelDir?: string, + opts?: { + specialSeparationDims?: { [key: string]: string } + separateAllVarsWithDims?: string[][] + } +): Variable[] { + const vars = readSubscriptsAndEquationsFromSource({ modelText, modelDir }, opts) + + // Exclude the `Time` variable so that we have one less thing to check + return vars.filter(v => v.varName !== '_time') +} + +function readSubscriptsAndEquations(modelName: string): Variable[] { + return readSubscriptsAndEquationsFromSource({ modelName }) +} + +function v(lhs: string, formula: string, overrides?: Partial): Variable { + const variable = new VariableImpl() + variable.modelLHS = lhs + variable.modelFormula = formula + variable.varName = canonicalName(lhs.split('[')[0]) + variable.varType = 'aux' + variable.hasInitValue = false + variable.includeInOutput = true + if (overrides) { + for (const [key, value] of Object.entries(overrides)) { + const r = variable as Record + r[key] = value + } + } + return variable as Variable +} + +describe('readEquations (from XMILE model)', () => { + it.only('should work for simple equation with explicit parentheses', () => { + const xmileVars = `\ + + 1 + + + (x + 2) * 3 + +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', '(x+2)*3', { + refId: '_y', + references: ['_x'] + }) + ]) + }) + + it('should work for conditional expression with = op', () => { + const xmileVars = `\ + + 1 + + + IF x = time THEN 1 ELSE 0 + +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', 'IF THEN ELSE(x=time,1,0)', { + refId: '_y', + references: ['_x', '_time'] + }) + ]) + }) + + it('should work for conditional expression with reference to dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x = 1 ~~| + y[DimA] = IF THEN ELSE(DimA = x, 1, 0) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y[DimA]', 'IF THEN ELSE(DimA=x,1,0)', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work for conditional expression with reference to dimension and subscript/index', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + y[DimA] = IF THEN ELSE(DimA = A2, 1, 0) ~~| + `) + expect(vars).toEqual([ + v('y[DimA]', 'IF THEN ELSE(DimA=A2,1,0)', { + refId: '_y', + subscripts: ['_dima'] + }) + ]) + }) + + it('should work for equation that uses specialSeparationDims', () => { + const vars = readInlineModel( + ` + DimA: A1, A2 ~~| + y[DimA] = 0 ~~| + `, + undefined, + { + specialSeparationDims: { + _y: '_dima' + } + } + ) + expect(vars).toEqual([ + v('y[DimA]', '0', { + refId: '_y[_a1]', + varType: 'const', + separationDims: ['_dima'], + subscripts: ['_a1'] + }), + v('y[DimA]', '0', { + refId: '_y[_a2]', + varType: 'const', + separationDims: ['_dima'], + subscripts: ['_a2'] + }) + ]) + }) + + it('should work for equations that are affected by separateAllVarsWithDims', () => { + const vars = readInlineModel( + ` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA] = 0 ~~| + y[DimB] = 0 ~~| + z[DimA, DimB, DimC] = 0 ~~| + `, + undefined, + { + separateAllVarsWithDims: [['_dima', '_dimc'], ['_dimb']] + } + ) + expect(vars).toEqual([ + // x should not be separated ('_dima' is not listed in `separateAllVarsWithDims`) + v('x[DimA]', '0', { + refId: '_x', + varType: 'const', + subscripts: ['_dima'] + }), + // y should be separated ('_dimb' is listed in `separateAllVarsWithDims`) + v('y[DimB]', '0', { + refId: '_y[_b1]', + varType: 'const', + separationDims: ['_dimb'], + subscripts: ['_b1'] + }), + v('y[DimB]', '0', { + refId: '_y[_b2]', + varType: 'const', + separationDims: ['_dimb'], + subscripts: ['_b2'] + }), + // z should be separated only on DimA and DimC + v('z[DimA,DimB,DimC]', '0', { + refId: '_z[_a1,_dimb,_c1]', + varType: 'const', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_dimb', '_c1'] + }), + v('z[DimA,DimB,DimC]', '0', { + refId: '_z[_a1,_dimb,_c2]', + varType: 'const', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_dimb', '_c2'] + }), + v('z[DimA,DimB,DimC]', '0', { + refId: '_z[_a2,_dimb,_c1]', + varType: 'const', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_dimb', '_c1'] + }), + v('z[DimA,DimB,DimC]', '0', { + refId: '_z[_a2,_dimb,_c2]', + varType: 'const', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_dimb', '_c2'] + }) + ]) + }) + + it('should work for equations when specialSeparationDims and separateAllVarsWithDims are used together', () => { + const vars = readInlineModel( + ` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x1[DimA] = 0 ~~| + x2[DimA] = 0 ~~| + y[DimB] = 0 ~~| + `, + undefined, + { + specialSeparationDims: { _x1: '_dima' }, + separateAllVarsWithDims: [['_dimb']] + } + ) + expect(vars).toEqual([ + // x1 should be separated ('_x1[_dima]' is listed in `specialSeparationDims`) + v('x1[DimA]', '0', { + refId: '_x1[_a1]', + varType: 'const', + separationDims: ['_dima'], + subscripts: ['_a1'] + }), + v('x1[DimA]', '0', { + refId: '_x1[_a2]', + varType: 'const', + separationDims: ['_dima'], + subscripts: ['_a2'] + }), + // x2 should not be separated ('_x2[_dima]' is listed in `specialSeparationDims`) + v('x2[DimA]', '0', { + refId: '_x2', + varType: 'const', + subscripts: ['_dima'] + }), + // y should be separated ('_dimb' is listed in `separateAllVarsWithDims`) + v('y[DimB]', '0', { + refId: '_y[_b1]', + varType: 'const', + separationDims: ['_dimb'], + subscripts: ['_b1'] + }), + v('y[DimB]', '0', { + refId: '_y[_b2]', + varType: 'const', + separationDims: ['_dimb'], + subscripts: ['_b2'] + }) + ]) + }) + + it('should work for data variable definition', () => { + const vars = readInlineModel( + ` + x ~~| + ` + ) + expect(vars).toEqual([ + v('x', '', { + refId: '_x', + varType: 'data' + }) + ]) + }) + + it.only('should work for lookup definition', () => { + const xmileVars = `\ + + 0,0.4,0.5,0.8,1 + 0,0.1,0.5,0.9,1 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars).toEqual([ + v('x', '', { + refId: '_x', + varType: 'lookup', + range: [], + points: [ + [0, 0], + [0.4, 0.1], + [0.5, 0.5], + [0.8, 0.9], + [1, 1] + ] + }) + ]) + }) + + it('should work for lookup call', () => { + const vars = readInlineModel(` + x( (0,0),(2,1.3) ) ~~| + y = x(2) ~~| + `) + expect(vars).toEqual([ + v('x', '', { + refId: '_x', + varType: 'lookup', + range: [], + points: [ + [0, 0], + [2, 1.3] + ] + }), + v('y', 'x(2)', { + refId: '_y', + referencedFunctionNames: ['__x'] + }) + ]) + }) + + it('should work for lookup call (with apply-to-all lookup variable)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA]( (0,0),(2,1.3) ) ~~| + y = x[A1](2) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '', { + points: [ + [0, 0], + [2, 1.3] + ], + refId: '_x', + subscripts: ['_dima'], + varType: 'lookup' + }), + v('y', 'x[A1](2)', { + refId: '_y', + referencedLookupVarNames: ['_x'] + }) + ]) + }) + + it('should work for lookup call (with separated lookup variable)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[A1]( (0,0),(2,1.3) ) ~~| + x[A2]( (1,1),(4,3) ) ~~| + y = x[A1](2) ~~| + `) + expect(vars).toEqual([ + v('x[A1]', '', { + points: [ + [0, 0], + [2, 1.3] + ], + refId: '_x[_a1]', + subscripts: ['_a1'], + varType: 'lookup' + }), + v('x[A2]', '', { + points: [ + [1, 1], + [4, 3] + ], + refId: '_x[_a2]', + subscripts: ['_a2'], + varType: 'lookup' + }), + v('y', 'x[A1](2)', { + refId: '_y', + referencedLookupVarNames: ['_x'] + }) + ]) + }) + + // + // NOTE: The following "should work for {0,1,2,3}D variable" tests are aligned with the ones + // in `gen-equation-{c,js}.spec.ts` (they exercise the same test models/equations). Having both + // sets of tests makes it easier to see whether a bug is in the "read equations" phase or + // in the "code gen" phase or both. + // + + describe('when LHS has no subscripts', () => { + it('should work when RHS variable has no subscripts', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', []) + // -> ['_x'] + v('y', 'x', { + refId: '_y', + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1 ~~| + y = x[A1] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1']) + // -> ['_x'] + v('y', 'x[A1]', { + refId: '_y', + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y = x[A1] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1']) + // -> ['_x[_a1]'] + v('y', 'x[A1]', { + refId: '_y', + references: ['_x[_a1]'] + }) + ]) + }) + + it.only('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension', () => { + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1 ~~| + // y = SUM(x[DimA!]) ~~| + // `) + + const xmileVars = `\ + + + + + + + + + 1 + + + SUM(x[*]) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x'] + v('y', 'SUM(x[DimA!])', { + refId: '_y', + referencedFunctionNames: ['__sum'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y = SUM(x[DimA!]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x[_a1]', '_x[_a2]'] + v('y', 'SUM(x[DimA!])', { + refId: '_y', + referencedFunctionNames: ['__sum'], + references: ['_x[_a1]', '_x[_a2]'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1 ~~| + y = x[A1, B2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1', '_b2']) + // -> ['_x'] + v('y', 'x[A1,B2]', { + refId: '_y', + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1, 2; 3, 4; ~~| + y = x[A1, B2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a1,_b1]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a1', '_b1'], + varType: 'const' + }), + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a1,_b2]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a1', '_b2'], + varType: 'const' + }), + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a2,_b1]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a2', '_b1'], + varType: 'const' + }), + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a2,_b2]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a2', '_b2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1', '_b2']) + // -> ['_x[_a1,_b2]'] + v('y', 'x[A1,B2]', { + refId: '_y', + references: ['_x[_a1,_b2]'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, DimC, DimB] = 1 ~~| + y = x[A1, C2, B2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimC,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimc', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1', '_c2', '_b2']) + // -> ['_x'] + v('y', 'x[A1,C2,B2]', { + refId: '_y', + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, DimC, DimB] :EXCEPT: [DimA, DimC, B1] = 1 ~~| + x[DimA, DimC, B1] = 2 ~~| + y = x[A1, C2, B2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimC,DimB]:EXCEPT:[DimA,DimC,B1]', '1', { + refId: '_x[_dima,_dimc,_b2]', + separationDims: ['_dimb'], + subscripts: ['_dima', '_dimc', '_b2'], + varType: 'const' + }), + v('x[DimA,DimC,B1]', '2', { + refId: '_x[_dima,_dimc,_b1]', + subscripts: ['_dima', '_dimc', '_b1'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1', '_c2', '_b2']) + // -> ['_x[_dima,_dimc,_b2]'] + v('y', 'x[A1,C2,B2]', { + refId: '_y', + references: ['_x[_dima,_dimc,_b2]'] + }) + ]) + }) + }) + + describe('when LHS is apply-to-all (1D)', () => { + it('should work when RHS variable has no subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x = 1 ~~| + y[DimA] = x ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', []) + // -> ['_x'] + v('y[DimA]', 'x', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] = x[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a2']) + // -> ['_x'] + v('y[DimA]', 'x[A2]', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] = x[DimA] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima']) + // -> ['_x'] + v('y[DimA]', 'x[DimA]', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] = x[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a2']) + // -> ['_x[_a2]'] + v('y[DimA]', 'x[A2]', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x[_a2]'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] = x[DimA] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima']) + // -> ['_x[_a1]', '_x[_a2]', '_x[_a3]'] + v('y[DimA]', 'x[DimA]', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) with separated definitions and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[A1] = 1 ~~| + x[A2] = 2 ~~| + y[DimA] = x[DimA] ~~| + `) + expect(vars).toEqual([ + v('x[A1]', '1', { + refId: '_x[_a1]', + subscripts: ['_a1'], + varType: 'const' + }), + v('x[A2]', '2', { + refId: '_x[_a2]', + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima']) + // -> ['_x[_a1]', '_x[_a2]'] + v('y[DimA]', 'x[DimA]', { + refId: '_y', + subscripts: ['_dima'], + references: ['_x[_a1]', '_x[_a2]'] + }) + ]) + }) + + // This is adapted from the "except" sample model (see equation for `k`) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimB: B1, B2 -> (DimA: SubA, A1) ~~| + a[DimA] = 1, 2, 3 ~~| + b[DimB] = 4, 5 ~~| + y[DimA] = a[DimA] + b[DimB] ~~| + `) + expect(vars).toEqual([ + v('a[DimA]', '1,2,3', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b[DimB]', '4,5', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '4,5', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_a', ['_dima']) + // -> ['_a[_a1]', '_a[_a2]', '_a[_a3]'] + // expandedRefIdsForVar(_y, '_b', ['_dimb']) + // -> ['_b[_b1]', '_b[_b2]'] + v('y[DimA]', 'a[DimA]+b[DimB]', { + refId: '_y', + subscripts: ['_dima'], + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_b1]', '_b[_b2]'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA] = 1 ~~| + y[DimB] = SUM(x[DimA!]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x'] + v('y[DimB]', 'SUM(x[DimA!])', { + refId: '_y', + subscripts: ['_dimb'], + referencedFunctionNames: ['__sum'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1 ~~| + y[DimA] = SUM(x[DimA!]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x'] + v('y[DimA]', 'SUM(x[DimA!])', { + refId: '_y', + subscripts: ['_dima'], + referencedFunctionNames: ['__sum'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA] = 1, 2 ~~| + y[DimB] = SUM(x[DimA!]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x[_a1]', '_x[_a2]'] + v('y[DimB]', 'SUM(x[DimA!])', { + refId: '_y', + subscripts: ['_dimb'], + referencedFunctionNames: ['__sum'], + references: ['_x[_a1]', '_x[_a2]'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y[DimA] = SUM(x[DimA!]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x[_a1]', '_x[_a2]'] + v('y[DimA]', 'SUM(x[DimA!])', { + refId: '_y', + subscripts: ['_dima'], + referencedFunctionNames: ['__sum'], + references: ['_x[_a1]', '_x[_a2]'] + }) + ]) + }) + + // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with one normal dimension and one marked dimension that resolve to same family', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: DimA ~~| + x[DimA,DimB] = 1 ~~| + y[DimA] = SUM(x[DimA,DimA!]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima!']) + // -> ['_x'] + v('y[DimA]', 'SUM(x[DimA,DimA!])', { + refId: '_y', + subscripts: ['_dima'], + referencedFunctionNames: ['__sum'], + references: ['_x'] + }) + ]) + }) + + // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + }) + + describe('when LHS is NON-apply-to-all (1D)', () => { + it('should work when RHS variable has no subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x = 1 ~~| + y[DimA] :EXCEPT: [A1] = x ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a2], '_x', []) + // -> ['_x'] + v('y[DimA]:EXCEPT:[A1]', 'x', { + refId: '_y[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + references: ['_x'] + }), + // expandedRefIdsForVar(_y[_a3], '_x', []) + // -> ['_x'] + v('y[DimA]:EXCEPT:[A1]', 'x', { + refId: '_y[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] :EXCEPT: [A1] = x[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a2], '_x', ['_a2']) + // -> ['_x'] + v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + refId: '_y[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + references: ['_x'] + }), + // expandedRefIdsForVar(_y[_a3], '_x', ['_a2']) + // -> ['_x'] + v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + refId: '_y[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] :EXCEPT: [A1] = x[DimA] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a2], '_x', ['_dima']) + // -> ['_x'] + v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + refId: '_y[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + references: ['_x'] + }), + // expandedRefIdsForVar(_y[_a3], '_x', ['_dima']) + // -> ['_x'] + v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + refId: '_y[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] :EXCEPT: [A1] = x[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a2], '_x', ['_a2']) + // -> ['_x[_a2]'] + v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + refId: '_y[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + references: ['_x[_a2]'] + }), + // expandedRefIdsForVar(_y[_a3], '_x', ['_a2']) + // -> ['_x[_a2]'] + v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + refId: '_y[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + references: ['_x[_a2]'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] :EXCEPT: [A1] = x[DimA] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a2], '_x', ['_dima']) + // -> ['_x[_a2]'] + v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + refId: '_y[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + references: ['_x[_a2]'] + }), + // expandedRefIdsForVar(_y[_a3], '_x', ['_dima']) + // -> ['_x[_a3]'] + v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + refId: '_y[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + references: ['_x[_a3]'] + }) + ]) + }) + + // This is adapted from the "except" sample model (see equation for `k`) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimB: B1, B2 -> (DimA: SubA, A1) ~~| + a[DimA] = 1, 2, 3 ~~| + b[DimB] = 4, 5 ~~| + y[DimA] :EXCEPT: [A1] = a[DimA] + b[DimB] ~~| + `) + expect(vars).toEqual([ + v('a[DimA]', '1,2,3', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b[DimB]', '4,5', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '4,5', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a2], '_a', ['_dima']) + // -> ['_a[_a2]'] + // expandedRefIdsForVar(_y[_a2], '_b', ['_dimb']) + // -> ['_b[_b1]'] + v('y[DimA]:EXCEPT:[A1]', 'a[DimA]+b[DimB]', { + refId: '_y[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + references: ['_a[_a2]', '_b[_b1]'] + }), + // expandedRefIdsForVar(_y[_a3], '_a', ['_dima']) + // -> ['_a[_a3]'] + // expandedRefIdsForVar(_y[_a3], '_b', ['_dimb']) + // -> ['_b[_b1]'] + v('y[DimA]:EXCEPT:[A1]', 'a[DimA]+b[DimB]', { + refId: '_y[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + references: ['_a[_a3]', '_b[_b1]'] + }) + ]) + }) + + // This is adapted from the "ref" sample model (with updated naming for clarity) + it('should work for complex mapping example', () => { + const vars = readInlineModel(` + Target: (t1-t3) ~~| + tNext: (t2-t3) -> tPrev ~~| + tPrev: (t1-t2) -> tNext ~~| + x[t1] = y[t1] + 1 ~~| + x[tNext] = y[tNext] + 1 ~~| + y[t1] = 1 ~~| + y[tNext] = x[tPrev] + 1 ~~| + `) + expect(vars).toEqual([ + v('x[t1]', 'y[t1]+1', { + refId: '_x[_t1]', + subscripts: ['_t1'], + references: ['_y[_t1]'] + }), + v('x[tNext]', 'y[tNext]+1', { + refId: '_x[_t2]', + separationDims: ['_tnext'], + subscripts: ['_t2'], + references: ['_y[_t2]'] + }), + v('x[tNext]', 'y[tNext]+1', { + refId: '_x[_t3]', + separationDims: ['_tnext'], + subscripts: ['_t3'], + references: ['_y[_t3]'] + }), + v('y[t1]', '1', { + refId: '_y[_t1]', + subscripts: ['_t1'], + varType: 'const' + }), + v('y[tNext]', 'x[tPrev]+1', { + refId: '_y[_t2]', + references: ['_x[_t1]'], + separationDims: ['_tnext'], + subscripts: ['_t2'] + }), + v('y[tNext]', 'x[tPrev]+1', { + refId: '_y[_t3]', + references: ['_x[_t2]'], + separationDims: ['_tnext'], + subscripts: ['_t3'] + }) + ]) + }) + }) + + describe('when LHS is apply-to-all (2D)', () => { + // it('should work when RHS variable has no subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + // // TODO + // }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB <-> DimA ~~| + x[DimA] = 1, 2 ~~| + y[DimA, DimB] = x[DimA] + x[DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_dima,_dimb], '_x', ['_dima']) + // -> ['_x[_a1]', '_x[_a2]'] + // expandedRefIdsForVar(_y[_dima,_dimb], '_x', ['_dimb']) + // -> ['_x[_a1]', '_x[_a2]'] + v('y[DimA,DimB]', 'x[DimA]+x[DimB]', { + refId: '_y', + subscripts: ['_dima', '_dimb'], + references: ['_x[_a1]', '_x[_a2]'] + }) + ]) + }) + + // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1 ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_dimb,_dima], '_x', ['_dima', '_dimb']) + // -> ['_x'] + v('y[DimB,DimA]', 'x[DimA,DimB]', { + refId: '_y', + subscripts: ['_dimb', '_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB <-> DimA ~~| + x[DimA, DimB] = 1 ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_dimb,_dima], '_x', ['_dima', '_dimb']) + // -> ['_x'] + v('y[DimB,DimA]', 'x[DimA,DimB]', { + refId: '_y', + subscripts: ['_dimb', '_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1, 2; 3, 4; ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a1,_b1]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a1', '_b1'], + varType: 'const' + }), + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a1,_b2]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a1', '_b2'], + varType: 'const' + }), + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a2,_b1]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a2', '_b1'], + varType: 'const' + }), + v('x[DimA,DimB]', '1,2;3,4;', { + refId: '_x[_a2,_b2]', + separationDims: ['_dima', '_dimb'], + subscripts: ['_a2', '_b2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_dimb,_dima], '_x', ['_dima', '_dimb']) + // -> ['_x[_a1,_b1]', '_x[_a1,_b2]', '_x[_a2,_b1]', '_x[_a2,_b2]'] + v('y[DimB,DimA]', 'x[DimA,DimB]', { + refId: '_y', + subscripts: ['_dimb', '_dima'], + references: ['_x[_a1,_b1]', '_x[_a1,_b2]', '_x[_a2,_b1]', '_x[_a2,_b2]'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1, DimB] = 1 ~~| + x[A2, DimB] = 2 ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[A1,DimB]', '1', { + refId: '_x[_a1,_dimb]', + subscripts: ['_a1', '_dimb'], + varType: 'const' + }), + v('x[A2,DimB]', '2', { + refId: '_x[_a2,_dimb]', + subscripts: ['_a2', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_dimb,_dima], '_x', ['_dima', '_dimb']) + // -> ['_x[_a1,_dimb]', '_x[_a2,_dimb]'] + v('y[DimB,DimA]', 'x[DimA,DimB]', { + refId: '_y', + subscripts: ['_dimb', '_dima'], + references: ['_x[_a1,_dimb]', '_x[_a2,_dimb]'] + }) + ]) + }) + + // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + }) + + describe('when LHS is NON-apply-to-all (2D)', () => { + // The LHS in this test is partially separated (expanded only for first dimension position) + it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1 ~~| + y[SubA, DimB] = x[SubA, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a1,_dimb], '_x', ['_suba', '_dimb']) + // -> ['_x'] + v('y[SubA,DimB]', 'x[SubA,DimB]', { + refId: '_y[_a1,_dimb]', + separationDims: ['_suba'], + subscripts: ['_a1', '_dimb'], + references: ['_x'] + }), + // expandedRefIdsForVar(_y[_a2,_dimb], '_x', ['_suba', '_dimb']) + // -> ['_x'] + v('y[SubA,DimB]', 'x[SubA,DimB]', { + refId: '_y[_a2,_dimb]', + separationDims: ['_suba'], + subscripts: ['_a2', '_dimb'], + references: ['_x'] + }) + ]) + }) + + // This test is based on the example from #179 (simplified to use subdimensions to ensure separation) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + SubB <-> SubA ~~| + x[SubA] = 1, 2 ~~| + y[SubA, SubB] = x[SubA] + x[SubB] ~~| + `) + expect(vars).toEqual([ + v('x[SubA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_suba'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[SubA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_a1,_a1], '_x', ['_suba']) + // -> ['_x[_a1]'] + // expandedRefIdsForVar(_y[_a1,_a1], '_x', ['_subb']) + // -> ['_x[_a1]'] + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a1,_a1]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a1', '_a1'], + references: ['_x[_a1]'] + }), + // expandedRefIdsForVar(_y[_a1,_a2], '_x', ['_suba']) + // -> ['_x[_a1]'] + // expandedRefIdsForVar(_y[_a1,_a2], '_x', ['_subb']) + // -> ['_x[_a2]'] + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a1,_a2]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a1', '_a2'], + references: ['_x[_a1]', '_x[_a2]'] + }), + // expandedRefIdsForVar(_y[_a2,_a1], '_x', ['_suba']) + // -> ['_x[_a2]'] + // expandedRefIdsForVar(_y[_a2,_a1], '_x', ['_subb']) + // -> ['_x[_a1]'] + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a2,_a1]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a2', '_a1'], + references: ['_x[_a2]', '_x[_a1]'] + }), + // expandedRefIdsForVar(_y[_a2,_a2], '_x', ['_suba']) + // -> ['_x[_a2]'] + // expandedRefIdsForVar(_y[_a2,_a2], '_x', ['_subb']) + // -> ['_x[_a2]'] + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a2,_a2]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a2', '_a2'], + references: ['_x[_a2]'] + }) + ]) + }) + + // This test is based on the example from #179 (simplified to use subdimensions to ensure separation). + // It is similar to the previous one, except in this one, `x` is apply-to-all (and refers to the parent + // dimension). + it('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + SubB <-> SubA ~~| + x[DimA] = 1 ~~| + y[SubA, SubB] = x[SubA] + x[SubB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1', { + refId: '_x', + subscripts: ['_dima'], + varType: 'const' + }), + // For all 4 instances of `y`, the following should hold true: + // expandedRefIdsForVar(_y[_aN,_aN], '_x', ['_suba']) + // -> ['_x'] + // expandedRefIdsForVar(_y[_aN,_aN], '_x', ['_subb']) + // -> ['_x'] + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a1,_a1]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a1', '_a1'], + references: ['_x'] + }), + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a1,_a2]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a1', '_a2'], + references: ['_x'] + }), + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a2,_a1]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a2', '_a1'], + references: ['_x'] + }), + v('y[SubA,SubB]', 'x[SubA]+x[SubB]', { + refId: '_y[_a2,_a2]', + separationDims: ['_suba', '_subb'], + subscripts: ['_a2', '_a2'], + references: ['_x'] + }) + ]) + }) + }) + + describe('when LHS is apply-to-all (3D)', () => { + it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, DimC, DimB] = 1 ~~| + y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimC,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimc', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima', '_dimc', '_dimb']) + // -> ['_x'] + v('y[DimC,DimB,DimA]', 'x[DimA,DimC,DimB]', { + refId: '_y', + subscripts: ['_dimc', '_dimb', '_dima'], + references: ['_x'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, C1, DimB] = 1 ~~| + x[DimA, C2, DimB] = 2 ~~| + y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,C1,DimB]', '1', { + refId: '_x[_dima,_c1,_dimb]', + subscripts: ['_dima', '_c1', '_dimb'], + varType: 'const' + }), + v('x[DimA,C2,DimB]', '2', { + refId: '_x[_dima,_c2,_dimb]', + subscripts: ['_dima', '_c2', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_dima', '_dimc', '_dimb']) + // -> ['_x[_dima,_c1,_dimb]', '_x[_dima,_c2,_dimb]'] + v('y[DimC,DimB,DimA]', 'x[DimA,DimC,DimB]', { + refId: '_y', + subscripts: ['_dimc', '_dimb', '_dima'], + references: ['_x[_dima,_c1,_dimb]', '_x[_dima,_c2,_dimb]'] + }) + ]) + }) + }) + + describe('when LHS is NON-apply-to-all (3D)', () => { + it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2, C3 ~~| + SubC: C2, C3 ~~| + x[DimA, DimC, DimB] = 1 ~~| + y[SubC, DimB, DimA] = x[DimA, SubC, DimB] ~~| + `) + expect(vars).toEqual([ + v('x[DimA,DimC,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimc', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_c2,_dimb,_dima], '_x', ['_dima', '_dimc', '_dimb']) + // -> ['_x'] + v('y[SubC,DimB,DimA]', 'x[DimA,SubC,DimB]', { + refId: '_y[_c2,_dimb,_dima]', + separationDims: ['_subc'], + subscripts: ['_c2', '_dimb', '_dima'], + references: ['_x'] + }), + // expandedRefIdsForVar(_y[_c3,_dimb,_dima], '_x', ['_dima', '_dimc', '_dimb']) + // -> ['_x'] + v('y[SubC,DimB,DimA]', 'x[DimA,SubC,DimB]', { + refId: '_y[_c3,_dimb,_dima]', + separationDims: ['_subc'], + subscripts: ['_c3', '_dimb', '_dima'], + references: ['_x'] + }) + ]) + }) + + // This test is based on the example from #278 + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + const vars = readInlineModel(` + Scenario: S1, S2 ~~| + Sector: A1, A2, A3 ~~| + Supplying Sector: A1, A2 -> Producing Sector ~~| + Producing Sector: A1, A2 -> Supplying Sector ~~| + x[A1,A1] = 101 ~~| + x[A1,A2] = 102 ~~| + x[A1,A3] = 103 ~~| + x[A2,A1] = 201 ~~| + x[A2,A2] = 202 ~~| + x[A2,A3] = 203 ~~| + x[A3,A1] = 301 ~~| + x[A3,A2] = 302 ~~| + x[A3,A3] = 303 ~~| + y[S1] = 1000 ~~| + y[S2] = 2000 ~~| + z[Scenario, Supplying Sector, Producing Sector] = + y[Scenario] + x[Supplying Sector, Producing Sector] + ~~| + `) + expect(vars).toEqual([ + v('x[A1,A1]', '101', { + refId: '_x[_a1,_a1]', + subscripts: ['_a1', '_a1'], + varType: 'const' + }), + v('x[A1,A2]', '102', { + refId: '_x[_a1,_a2]', + subscripts: ['_a1', '_a2'], + varType: 'const' + }), + v('x[A1,A3]', '103', { + refId: '_x[_a1,_a3]', + subscripts: ['_a1', '_a3'], + varType: 'const' + }), + v('x[A2,A1]', '201', { + refId: '_x[_a2,_a1]', + subscripts: ['_a2', '_a1'], + varType: 'const' + }), + v('x[A2,A2]', '202', { + refId: '_x[_a2,_a2]', + subscripts: ['_a2', '_a2'], + varType: 'const' + }), + v('x[A2,A3]', '203', { + refId: '_x[_a2,_a3]', + subscripts: ['_a2', '_a3'], + varType: 'const' + }), + v('x[A3,A1]', '301', { + refId: '_x[_a3,_a1]', + subscripts: ['_a3', '_a1'], + varType: 'const' + }), + v('x[A3,A2]', '302', { + refId: '_x[_a3,_a2]', + subscripts: ['_a3', '_a2'], + varType: 'const' + }), + v('x[A3,A3]', '303', { + refId: '_x[_a3,_a3]', + subscripts: ['_a3', '_a3'], + varType: 'const' + }), + v('y[S1]', '1000', { + refId: '_y[_s1]', + subscripts: ['_s1'], + varType: 'const' + }), + v('y[S2]', '2000', { + refId: '_y[_s2]', + subscripts: ['_s2'], + varType: 'const' + }), + // expandedRefIdsForVar(_z[_scenario,_a1,_a1], '_y', ['_scenario']) + // -> ['_y[_s1]', '_y[_s2]'] + // expandedRefIdsForVar(_z[_scenario,_a1,_a1], '_x', ['_supplying_sector', '_producing_sector']) + // -> ['_x[_a1,_a1]'] + v('z[Scenario,Supplying Sector,Producing Sector]', 'y[Scenario]+x[Supplying Sector,Producing Sector]', { + refId: '_z[_scenario,_a1,_a1]', + subscripts: ['_scenario', '_a1', '_a1'], + separationDims: ['_supplying_sector', '_producing_sector'], + references: ['_y[_s1]', '_y[_s2]', '_x[_a1,_a1]'], + varType: 'aux' + }), + // expandedRefIdsForVar(_z[_scenario,_a1,_a2], '_x', ['_supplying_sector', '_producing_sector']) + // -> ['_x[_a1,_a2]'] + v('z[Scenario,Supplying Sector,Producing Sector]', 'y[Scenario]+x[Supplying Sector,Producing Sector]', { + refId: '_z[_scenario,_a1,_a2]', + subscripts: ['_scenario', '_a1', '_a2'], + separationDims: ['_supplying_sector', '_producing_sector'], + references: ['_y[_s1]', '_y[_s2]', '_x[_a1,_a2]'], + varType: 'aux' + }), + // expandedRefIdsForVar(_z[_scenario,_a2,_a1], '_x', ['_supplying_sector', '_producing_sector']) + // -> ['_x[_a2,_a1]'] + v('z[Scenario,Supplying Sector,Producing Sector]', 'y[Scenario]+x[Supplying Sector,Producing Sector]', { + refId: '_z[_scenario,_a2,_a1]', + subscripts: ['_scenario', '_a2', '_a1'], + separationDims: ['_supplying_sector', '_producing_sector'], + references: ['_y[_s1]', '_y[_s2]', '_x[_a2,_a1]'], + varType: 'aux' + }), + // expandedRefIdsForVar(_z[_scenario,_a2,_a2], '_x', ['_supplying_sector', '_producing_sector']) + // -> ['_x[_a2,_a2]'] + v('z[Scenario,Supplying Sector,Producing Sector]', 'y[Scenario]+x[Supplying Sector,Producing Sector]', { + refId: '_z[_scenario,_a2,_a2]', + subscripts: ['_scenario', '_a2', '_a2'], + separationDims: ['_supplying_sector', '_producing_sector'], + references: ['_y[_s1]', '_y[_s2]', '_x[_a2,_a2]'], + varType: 'aux' + }) + ]) + }) + }) + + // + // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. + // + + it('should work for ACTIVE INITIAL function', () => { + const vars = readInlineModel(` + Initial Target Capacity = 1 ~~| + Capacity = 2 ~~| + Target Capacity = ACTIVE INITIAL(Capacity, Initial Target Capacity) ~~| + `) + expect(vars).toEqual([ + v('Initial Target Capacity', '1', { + refId: '_initial_target_capacity', + varType: 'const' + }), + v('Capacity', '2', { + refId: '_capacity', + varType: 'const' + }), + v('Target Capacity', 'ACTIVE INITIAL(Capacity,Initial Target Capacity)', { + refId: '_target_capacity', + references: ['_capacity'], + hasInitValue: true, + initReferences: ['_initial_target_capacity'], + referencedFunctionNames: ['__active_initial'] + }) + ]) + }) + + it('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 2D pp, non-subscripted avail)', () => { + const vars = readInlineModel(` + branch: Boston, Dayton ~~| + pprofile: ptype, ppriority ~~| + supply available = 200 ~~| + demand[branch] = 500,300 ~~| + priority[Boston,pprofile] = 1,5 ~~| + priority[Dayton,pprofile] = 1,7 ~~| + shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) ~~| + `) + expect(vars).toEqual([ + v('supply available', '200', { + refId: '_supply_available', + varType: 'const' + }), + v('demand[branch]', '500,300', { + refId: '_demand[_boston]', + separationDims: ['_branch'], + subscripts: ['_boston'], + varType: 'const' + }), + v('demand[branch]', '500,300', { + refId: '_demand[_dayton]', + separationDims: ['_branch'], + subscripts: ['_dayton'], + varType: 'const' + }), + v('priority[Boston,pprofile]', '1,5', { + refId: '_priority[_boston,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_boston', '_ptype'], + varType: 'const' + }), + v('priority[Boston,pprofile]', '1,5', { + refId: '_priority[_boston,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_boston', '_ppriority'], + varType: 'const' + }), + v('priority[Dayton,pprofile]', '1,7', { + refId: '_priority[_dayton,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_dayton', '_ptype'], + varType: 'const' + }), + v('priority[Dayton,pprofile]', '1,7', { + refId: '_priority[_dayton,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_dayton', '_ppriority'], + varType: 'const' + }), + v('shipments[branch]', 'ALLOCATE AVAILABLE(demand[branch],priority[branch,ptype],supply available)', { + refId: '_shipments', + referencedFunctionNames: ['__allocate_available'], + references: [ + '_demand[_boston]', + '_demand[_dayton]', + '_priority[_boston,_ptype]', + '_priority[_dayton,_ptype]', + '_priority[_boston,_ppriority]', + '_priority[_dayton,_ppriority]', + '_supply_available' + ], + subscripts: ['_branch'] + }) + ]) + }) + + it('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 3D pp with specific first subscript, non-subscripted avail)', () => { + const vars = readInlineModel(` + branch: Boston, Dayton, Fresno ~~| + item: Item1, Item2 ~~| + pprofile: ptype, ppriority ~~| + supply available = 200 ~~| + demand[branch] = 500,300,750 ~~| + priority[Item1,Boston,pprofile] = 3,5 ~~| + priority[Item1,Dayton,pprofile] = 3,7 ~~| + priority[Item1,Fresno,pprofile] = 3,3 ~~| + priority[Item2,Boston,pprofile] = 3,6 ~~| + priority[Item2,Dayton,pprofile] = 3,8 ~~| + priority[Item2,Fresno,pprofile] = 3,4 ~~| + item 1 shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[Item1,branch,ptype], supply available) ~~| + item 2 shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[Item2,branch,ptype], supply available) ~~| + `) + expect(vars).toEqual([ + v('supply available', '200', { + refId: '_supply_available', + varType: 'const' + }), + v('demand[branch]', '500,300,750', { + refId: '_demand[_boston]', + separationDims: ['_branch'], + subscripts: ['_boston'], + varType: 'const' + }), + v('demand[branch]', '500,300,750', { + refId: '_demand[_dayton]', + separationDims: ['_branch'], + subscripts: ['_dayton'], + varType: 'const' + }), + v('demand[branch]', '500,300,750', { + refId: '_demand[_fresno]', + separationDims: ['_branch'], + subscripts: ['_fresno'], + varType: 'const' + }), + v('priority[Item1,Boston,pprofile]', '3,5', { + refId: '_priority[_item1,_boston,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_boston', '_ptype'], + varType: 'const' + }), + v('priority[Item1,Boston,pprofile]', '3,5', { + refId: '_priority[_item1,_boston,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_boston', '_ppriority'], + varType: 'const' + }), + v('priority[Item1,Dayton,pprofile]', '3,7', { + refId: '_priority[_item1,_dayton,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_dayton', '_ptype'], + varType: 'const' + }), + v('priority[Item1,Dayton,pprofile]', '3,7', { + refId: '_priority[_item1,_dayton,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_dayton', '_ppriority'], + varType: 'const' + }), + v('priority[Item1,Fresno,pprofile]', '3,3', { + refId: '_priority[_item1,_fresno,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_fresno', '_ptype'], + varType: 'const' + }), + v('priority[Item1,Fresno,pprofile]', '3,3', { + refId: '_priority[_item1,_fresno,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_fresno', '_ppriority'], + varType: 'const' + }), + v('priority[Item2,Boston,pprofile]', '3,6', { + refId: '_priority[_item2,_boston,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_boston', '_ptype'], + varType: 'const' + }), + v('priority[Item2,Boston,pprofile]', '3,6', { + refId: '_priority[_item2,_boston,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_boston', '_ppriority'], + varType: 'const' + }), + v('priority[Item2,Dayton,pprofile]', '3,8', { + refId: '_priority[_item2,_dayton,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_dayton', '_ptype'], + varType: 'const' + }), + v('priority[Item2,Dayton,pprofile]', '3,8', { + refId: '_priority[_item2,_dayton,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_dayton', '_ppriority'], + varType: 'const' + }), + v('priority[Item2,Fresno,pprofile]', '3,4', { + refId: '_priority[_item2,_fresno,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_fresno', '_ptype'], + varType: 'const' + }), + v('priority[Item2,Fresno,pprofile]', '3,4', { + refId: '_priority[_item2,_fresno,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_fresno', '_ppriority'], + varType: 'const' + }), + v( + 'item 1 shipments[branch]', + 'ALLOCATE AVAILABLE(demand[branch],priority[Item1,branch,ptype],supply available)', + { + refId: '_item_1_shipments', + referencedFunctionNames: ['__allocate_available'], + references: [ + '_demand[_boston]', + '_demand[_dayton]', + '_demand[_fresno]', + '_priority[_item1,_boston,_ptype]', + '_priority[_item1,_dayton,_ptype]', + '_priority[_item1,_fresno,_ptype]', + '_priority[_item1,_boston,_ppriority]', + '_priority[_item1,_dayton,_ppriority]', + '_priority[_item1,_fresno,_ppriority]', + '_supply_available' + ], + subscripts: ['_branch'] + } + ), + v( + 'item 2 shipments[branch]', + 'ALLOCATE AVAILABLE(demand[branch],priority[Item2,branch,ptype],supply available)', + { + refId: '_item_2_shipments', + referencedFunctionNames: ['__allocate_available'], + references: [ + '_demand[_boston]', + '_demand[_dayton]', + '_demand[_fresno]', + '_priority[_item2,_boston,_ptype]', + '_priority[_item2,_dayton,_ptype]', + '_priority[_item2,_fresno,_ptype]', + '_priority[_item2,_boston,_ppriority]', + '_priority[_item2,_dayton,_ppriority]', + '_priority[_item2,_fresno,_ppriority]', + '_supply_available' + ], + subscripts: ['_branch'] + } + ) + ]) + }) + + it('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 2D pp, non-subscripted avail)', () => { + const vars = readInlineModel(` + branch: Boston, Dayton, Fresno ~~| + item: Item1, Item2 ~~| + pprofile: ptype, ppriority ~~| + supply available = 200 ~~| + demand[item,branch] = 500,300,750;501,301,751; ~~| + priority[Boston,pprofile] = 3,5 ~~| + priority[Dayton,pprofile] = 3,7 ~~| + priority[Fresno,pprofile] = 3,3 ~~| + shipments[item,branch] = ALLOCATE AVAILABLE(demand[item,branch], priority[branch,ptype], supply available) ~~| + `) + expect(vars).toEqual([ + v('supply available', '200', { + refId: '_supply_available', + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item1,_boston]', + separationDims: ['_item', '_branch'], + subscripts: ['_item1', '_boston'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item1,_dayton]', + separationDims: ['_item', '_branch'], + subscripts: ['_item1', '_dayton'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item1,_fresno]', + separationDims: ['_item', '_branch'], + subscripts: ['_item1', '_fresno'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item2,_boston]', + separationDims: ['_item', '_branch'], + subscripts: ['_item2', '_boston'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item2,_dayton]', + separationDims: ['_item', '_branch'], + subscripts: ['_item2', '_dayton'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item2,_fresno]', + separationDims: ['_item', '_branch'], + subscripts: ['_item2', '_fresno'], + varType: 'const' + }), + v('priority[Boston,pprofile]', '3,5', { + refId: '_priority[_boston,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_boston', '_ptype'], + varType: 'const' + }), + v('priority[Boston,pprofile]', '3,5', { + refId: '_priority[_boston,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_boston', '_ppriority'], + varType: 'const' + }), + v('priority[Dayton,pprofile]', '3,7', { + refId: '_priority[_dayton,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_dayton', '_ptype'], + varType: 'const' + }), + v('priority[Dayton,pprofile]', '3,7', { + refId: '_priority[_dayton,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_dayton', '_ppriority'], + varType: 'const' + }), + v('priority[Fresno,pprofile]', '3,3', { + refId: '_priority[_fresno,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_fresno', '_ptype'], + varType: 'const' + }), + v('priority[Fresno,pprofile]', '3,3', { + refId: '_priority[_fresno,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_fresno', '_ppriority'], + varType: 'const' + }), + v('shipments[item,branch]', 'ALLOCATE AVAILABLE(demand[item,branch],priority[branch,ptype],supply available)', { + refId: '_shipments', + referencedFunctionNames: ['__allocate_available'], + references: [ + '_demand[_item1,_boston]', + '_demand[_item1,_dayton]', + '_demand[_item1,_fresno]', + '_demand[_item2,_boston]', + '_demand[_item2,_dayton]', + '_demand[_item2,_fresno]', + '_priority[_boston,_ptype]', + '_priority[_dayton,_ptype]', + '_priority[_fresno,_ptype]', + '_priority[_boston,_ppriority]', + '_priority[_dayton,_ppriority]', + '_priority[_fresno,_ppriority]', + '_supply_available' + ], + subscripts: ['_item', '_branch'] + }) + ]) + }) + + it('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 3D pp, 1D avail)', () => { + const vars = readInlineModel(` + branch: Boston, Dayton, Fresno ~~| + item: Item1, Item2 ~~| + pprofile: ptype, ppriority ~~| + supply available[item] = 200,400 ~~| + demand[item,branch] = 500,300,750;501,301,751; ~~| + priority[Item1,Boston,pprofile] = 3,5 ~~| + priority[Item1,Dayton,pprofile] = 3,7 ~~| + priority[Item1,Fresno,pprofile] = 3,3 ~~| + priority[Item2,Boston,pprofile] = 3,6 ~~| + priority[Item2,Dayton,pprofile] = 3,8 ~~| + priority[Item2,Fresno,pprofile] = 3,4 ~~| + shipments[item,branch] = ALLOCATE AVAILABLE(demand[item,branch], priority[item,branch,ptype], supply available[item]) ~~| + `) + expect(vars).toEqual([ + v('supply available[item]', '200,400', { + refId: '_supply_available[_item1]', + separationDims: ['_item'], + subscripts: ['_item1'], + varType: 'const' + }), + v('supply available[item]', '200,400', { + refId: '_supply_available[_item2]', + separationDims: ['_item'], + subscripts: ['_item2'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item1,_boston]', + separationDims: ['_item', '_branch'], + subscripts: ['_item1', '_boston'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item1,_dayton]', + separationDims: ['_item', '_branch'], + subscripts: ['_item1', '_dayton'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item1,_fresno]', + separationDims: ['_item', '_branch'], + subscripts: ['_item1', '_fresno'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item2,_boston]', + separationDims: ['_item', '_branch'], + subscripts: ['_item2', '_boston'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item2,_dayton]', + separationDims: ['_item', '_branch'], + subscripts: ['_item2', '_dayton'], + varType: 'const' + }), + v('demand[item,branch]', '500,300,750;501,301,751;', { + refId: '_demand[_item2,_fresno]', + separationDims: ['_item', '_branch'], + subscripts: ['_item2', '_fresno'], + varType: 'const' + }), + v('priority[Item1,Boston,pprofile]', '3,5', { + refId: '_priority[_item1,_boston,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_boston', '_ptype'], + varType: 'const' + }), + v('priority[Item1,Boston,pprofile]', '3,5', { + refId: '_priority[_item1,_boston,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_boston', '_ppriority'], + varType: 'const' + }), + v('priority[Item1,Dayton,pprofile]', '3,7', { + refId: '_priority[_item1,_dayton,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_dayton', '_ptype'], + varType: 'const' + }), + v('priority[Item1,Dayton,pprofile]', '3,7', { + refId: '_priority[_item1,_dayton,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_dayton', '_ppriority'], + varType: 'const' + }), + v('priority[Item1,Fresno,pprofile]', '3,3', { + refId: '_priority[_item1,_fresno,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_fresno', '_ptype'], + varType: 'const' + }), + v('priority[Item1,Fresno,pprofile]', '3,3', { + refId: '_priority[_item1,_fresno,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item1', '_fresno', '_ppriority'], + varType: 'const' + }), + v('priority[Item2,Boston,pprofile]', '3,6', { + refId: '_priority[_item2,_boston,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_boston', '_ptype'], + varType: 'const' + }), + v('priority[Item2,Boston,pprofile]', '3,6', { + refId: '_priority[_item2,_boston,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_boston', '_ppriority'], + varType: 'const' + }), + v('priority[Item2,Dayton,pprofile]', '3,8', { + refId: '_priority[_item2,_dayton,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_dayton', '_ptype'], + varType: 'const' + }), + v('priority[Item2,Dayton,pprofile]', '3,8', { + refId: '_priority[_item2,_dayton,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_dayton', '_ppriority'], + varType: 'const' + }), + v('priority[Item2,Fresno,pprofile]', '3,4', { + refId: '_priority[_item2,_fresno,_ptype]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_fresno', '_ptype'], + varType: 'const' + }), + v('priority[Item2,Fresno,pprofile]', '3,4', { + refId: '_priority[_item2,_fresno,_ppriority]', + separationDims: ['_pprofile'], + subscripts: ['_item2', '_fresno', '_ppriority'], + varType: 'const' + }), + v( + 'shipments[item,branch]', + 'ALLOCATE AVAILABLE(demand[item,branch],priority[item,branch,ptype],supply available[item])', + { + refId: '_shipments', + referencedFunctionNames: ['__allocate_available'], + references: [ + '_demand[_item1,_boston]', + '_demand[_item1,_dayton]', + '_demand[_item1,_fresno]', + '_demand[_item2,_boston]', + '_demand[_item2,_dayton]', + '_demand[_item2,_fresno]', + '_priority[_item1,_boston,_ptype]', + '_priority[_item1,_dayton,_ptype]', + '_priority[_item1,_fresno,_ptype]', + '_priority[_item2,_boston,_ptype]', + '_priority[_item2,_dayton,_ptype]', + '_priority[_item2,_fresno,_ptype]', + '_priority[_item1,_boston,_ppriority]', + '_priority[_item1,_dayton,_ppriority]', + '_priority[_item1,_fresno,_ppriority]', + '_priority[_item2,_boston,_ppriority]', + '_priority[_item2,_dayton,_ppriority]', + '_priority[_item2,_fresno,_ppriority]', + '_supply_available[_item1]', + '_supply_available[_item2]' + ], + subscripts: ['_item', '_branch'] + } + ) + ]) + }) + + it('should work for DELAY1 function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = DELAY1(x, 5) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', 'DELAY1(x,5)', { + refId: '_y', + references: ['__level1', '__aux1'], + delayVarRefId: '__level1', + delayTimeVarName: '__aux1' + }), + v('_level1', 'INTEG(x-y,x*5)', { + refId: '__level1', + varType: 'level', + includeInOutput: false, + references: ['_x', '_y'], + hasInitValue: true, + initReferences: ['_x'], + referencedFunctionNames: ['__integ'] + }), + v('_aux1', '5', { + refId: '__aux1', + varType: 'const', + includeInOutput: false + }) + ]) + }) + + it('should work for DELAY1I function', () => { + const vars = readInlineModel(` + x = 1 ~~| + init = 2 ~~| + y = DELAY1I(x, 5, init) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('init', '2', { + refId: '_init', + varType: 'const' + }), + v('y', 'DELAY1I(x,5,init)', { + refId: '_y', + references: ['__level1', '__aux1'], + delayVarRefId: '__level1', + delayTimeVarName: '__aux1' + }), + v('_level1', 'INTEG(x-y,init*5)', { + refId: '__level1', + varType: 'level', + includeInOutput: false, + references: ['_x', '_y'], + hasInitValue: true, + initReferences: ['_init'], + referencedFunctionNames: ['__integ'] + }), + v('_aux1', '5', { + refId: '__aux1', + varType: 'const', + includeInOutput: false + }) + ]) + }) + + it('should work for DELAY1I function (with subscripted variables)', () => { + // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) + // variables here to cover both cases + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + input[DimA] = 10, 20, 30 ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 0 ~~| + y[DimA] = DELAY1I(input[DimA], delay[DimA], init[DimA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '10,20,30', { + refId: '_input[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '0', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[DimA]', 'DELAY1I(input[DimA],delay[DimA],init[DimA])', { + delayTimeVarName: '__aux1', + delayVarRefId: '__level1', + refId: '_y', + references: ['__level1', '__aux1[_dima]'], + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG(input[DimA]-y[DimA],init[DimA]*delay[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input[_a1]', '_input[_a2]', '_input[_a3]', '_y'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux1[DimA]', 'delay[DimA]', { + includeInOutput: false, + refId: '__aux1', + references: ['_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'] + }) + ]) + }) + + it('should work for DELAY1I function (with separated variables using subdimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + input[DimA] = 10, 20, 30 ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 0 ~~| + y[A1] = 5 ~~| + y[SubA] = DELAY1I(input[SubA], delay[SubA], init[SubA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '10,20,30', { + refId: '_input[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '0', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[A1]', '5', { + refId: '_y[_a1]', + subscripts: ['_a1'], + varType: 'const' + }), + v('y[SubA]', 'DELAY1I(input[SubA],delay[SubA],init[SubA])', { + delayTimeVarName: '__aux1', + delayVarRefId: '__level_y_1[_a2]', + refId: '_y[_a2]', + references: ['__level_y_1[_a2]', '__aux1[_a2]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('y[SubA]', 'DELAY1I(input[SubA],delay[SubA],init[SubA])', { + delayTimeVarName: '__aux2', + delayVarRefId: '__level_y_1[_a3]', + refId: '_y[_a3]', + references: ['__level_y_1[_a3]', '__aux2[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('_level_y_1[a2]', 'INTEG(input[a2]-y[a2],init[a2]*delay[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a2]'], + refId: '__level_y_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input[_a2]', '_y[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_aux1[a2]', 'delay[a2]', { + includeInOutput: false, + refId: '__aux1[_a2]', + references: ['_delay[_a2]'], + subscripts: ['_a2'] + }), + v('_level_y_1[a3]', 'INTEG(input[a3]-y[a3],init[a3]*delay[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a3]'], + refId: '__level_y_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input[_a3]', '_y[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_aux2[a3]', 'delay[a3]', { + includeInOutput: false, + refId: '__aux2[_a3]', + references: ['_delay[_a3]'], + subscripts: ['_a3'] + }) + ]) + }) + + it('should work for DELAY3 function', () => { + const vars = readInlineModel(` + input = 1 ~~| + delay = 2 ~~| + y = DELAY3(input, delay) ~~| + `) + expect(vars).toEqual([ + v('input', '1', { + refId: '_input', + varType: 'const' + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y', 'DELAY3(input,delay)', { + delayTimeVarName: '__aux4', + delayVarRefId: '__level3', + refId: '_y', + references: ['__level3', '__level2', '__level1', '__aux4'] + }), + v('_level3', 'INTEG(_aux2-_aux3,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__aux2', '__aux3'], + varType: 'level' + }), + v('_level2', 'INTEG(_aux1-_aux2,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__aux1', '__aux2'], + varType: 'level' + }), + v('_level1', 'INTEG(input-_aux1,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '__aux1'], + varType: 'level' + }), + v('_aux1', '_level1/((delay)/3)', { + includeInOutput: false, + refId: '__aux1', + references: ['__level1', '_delay'] + }), + v('_aux2', '_level2/((delay)/3)', { + includeInOutput: false, + refId: '__aux2', + references: ['__level2', '_delay'] + }), + v('_aux3', '_level3/((delay)/3)', { + includeInOutput: false, + refId: '__aux3', + references: ['__level3', '_delay'] + }), + v('_aux4', '((delay)/3)', { + includeInOutput: false, + refId: '__aux4', + references: ['_delay'] + }) + ]) + }) + + it('should work for DELAY3I function', () => { + const vars = readInlineModel(` + input = 1 ~~| + delay = 2 ~~| + init = 3 ~~| + y = DELAY3I(input, delay, init) ~~| + `) + expect(vars).toEqual([ + v('input', '1', { + refId: '_input', + varType: 'const' + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('init', '3', { + refId: '_init', + varType: 'const' + }), + v('y', 'DELAY3I(input,delay,init)', { + delayTimeVarName: '__aux4', + delayVarRefId: '__level3', + refId: '_y', + references: ['__level3', '__level2', '__level1', '__aux4'] + }), + v('_level3', 'INTEG(_aux2-_aux3,init*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__aux2', '__aux3'], + varType: 'level' + }), + v('_level2', 'INTEG(_aux1-_aux2,init*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__aux1', '__aux2'], + varType: 'level' + }), + v('_level1', 'INTEG(input-_aux1,init*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '__aux1'], + varType: 'level' + }), + v('_aux1', '_level1/((delay)/3)', { + includeInOutput: false, + refId: '__aux1', + references: ['__level1', '_delay'] + }), + v('_aux2', '_level2/((delay)/3)', { + includeInOutput: false, + refId: '__aux2', + references: ['__level2', '_delay'] + }), + v('_aux3', '_level3/((delay)/3)', { + includeInOutput: false, + refId: '__aux3', + references: ['__level3', '_delay'] + }), + v('_aux4', '((delay)/3)', { + includeInOutput: false, + refId: '__aux4', + references: ['_delay'] + }) + ]) + }) + + it('should work for DELAY3I function (with nested function calls)', () => { + const vars = readInlineModel(` + input = 1 ~~| + delay = 2 ~~| + init = 3 ~~| + y = DELAY3I(MIN(0, input), MAX(0, delay), ABS(init)) ~~| + `) + expect(vars).toEqual([ + v('input', '1', { + refId: '_input', + varType: 'const' + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('init', '3', { + refId: '_init', + varType: 'const' + }), + v('y', 'DELAY3I(MIN(0,input),MAX(0,delay),ABS(init))', { + delayTimeVarName: '__aux4', + delayVarRefId: '__level3', + refId: '_y', + references: ['__level3', '__level2', '__level1', '__aux4'] + }), + v('_level3', 'INTEG(_aux2-_aux3,ABS(init)*((MAX(0,delay))/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay'], + refId: '__level3', + referencedFunctionNames: ['__integ', '__abs', '__max'], + references: ['__aux2', '__aux3'], + varType: 'level' + }), + v('_level2', 'INTEG(_aux1-_aux2,ABS(init)*((MAX(0,delay))/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay'], + refId: '__level2', + referencedFunctionNames: ['__integ', '__abs', '__max'], + references: ['__aux1', '__aux2'], + varType: 'level' + }), + v('_level1', 'INTEG(MIN(0,input)-_aux1,ABS(init)*((MAX(0,delay))/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay'], + refId: '__level1', + referencedFunctionNames: ['__integ', '__min', '__abs', '__max'], + references: ['_input', '__aux1'], + varType: 'level' + }), + v('_aux1', '_level1/((MAX(0,delay))/3)', { + includeInOutput: false, + refId: '__aux1', + referencedFunctionNames: ['__max'], + references: ['__level1', '_delay'] + }), + v('_aux2', '_level2/((MAX(0,delay))/3)', { + includeInOutput: false, + refId: '__aux2', + referencedFunctionNames: ['__max'], + references: ['__level2', '_delay'] + }), + v('_aux3', '_level3/((MAX(0,delay))/3)', { + includeInOutput: false, + refId: '__aux3', + referencedFunctionNames: ['__max'], + references: ['__level3', '_delay'] + }), + v('_aux4', '((MAX(0,delay))/3)', { + includeInOutput: false, + refId: '__aux4', + referencedFunctionNames: ['__max'], + references: ['_delay'] + }) + ]) + }) + + it('should work for DELAY3I function (with subscripted variables)', () => { + // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) + // variables here to cover both cases + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + input[DimA] = 10, 20, 30 ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 0 ~~| + y[DimA] = DELAY3I(input[DimA], delay[DimA], init[DimA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '10,20,30', { + refId: '_input[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '0', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[DimA]', 'DELAY3I(input[DimA],delay[DimA],init[DimA])', { + delayTimeVarName: '__aux4', + delayVarRefId: '__level3', + refId: '_y', + references: ['__level3', '__level2', '__level1', '__aux4[_dima]'], // TODO: The last one is suspicious + subscripts: ['_dima'] + }), + v('_level3[DimA]', 'INTEG(_aux2[DimA]-_aux3[DimA],init[DimA]*((delay[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__aux2', '__aux3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level2[DimA]', 'INTEG(_aux1[DimA]-_aux2[DimA],init[DimA]*((delay[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__aux1', '__aux2'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level1[DimA]', 'INTEG(input[DimA]-_aux1[DimA],init[DimA]*((delay[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input[_a1]', '_input[_a2]', '_input[_a3]', '__aux1'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux1[DimA]', '_level1[DimA]/((delay[DimA])/3)', { + includeInOutput: false, + refId: '__aux1', + references: ['__level1', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'] + }), + v('_aux2[DimA]', '_level2[DimA]/((delay[DimA])/3)', { + includeInOutput: false, + refId: '__aux2', + references: ['__level2', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'] + }), + v('_aux3[DimA]', '_level3[DimA]/((delay[DimA])/3)', { + includeInOutput: false, + refId: '__aux3', + references: ['__level3', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'] + }), + v('_aux4[DimA]', '((delay[DimA])/3)', { + includeInOutput: false, + refId: '__aux4', + references: ['_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'] + }) + ]) + }) + + it('should work for DELAY3I function (with separated variables using subdimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + input[DimA] = 10, 20, 30 ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 0 ~~| + y[A1] = 5 ~~| + y[SubA] = DELAY3I(input[SubA], delay[SubA], init[SubA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '10,20,30', { + refId: '_input[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('input[DimA]', '10,20,30', { + refId: '_input[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '0', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[A1]', '5', { + refId: '_y[_a1]', + subscripts: ['_a1'], + varType: 'const' + }), + v('y[SubA]', 'DELAY3I(input[SubA],delay[SubA],init[SubA])', { + delayTimeVarName: '__aux_y_4', + delayVarRefId: '__level_y_3[_a2]', + refId: '_y[_a2]', + references: ['__level_y_3[_a2]', '__level_y_2[_a2]', '__level_y_1[_a2]', '__aux_y_4[_a2]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('y[SubA]', 'DELAY3I(input[SubA],delay[SubA],init[SubA])', { + delayTimeVarName: '__aux_y_4', + delayVarRefId: '__level_y_3[_a3]', + refId: '_y[_a3]', + references: ['__level_y_3[_a3]', '__level_y_2[_a3]', '__level_y_1[_a3]', '__aux_y_4[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('_level_y_3[a2]', 'INTEG(_aux_y_2[a2]-_aux_y_3[a2],init[a2]*((delay[a2])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a2]'], + refId: '__level_y_3[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__aux_y_2[_a2]', '__aux_y_3[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_y_2[a2]', 'INTEG(_aux_y_1[a2]-_aux_y_2[a2],init[a2]*((delay[a2])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a2]'], + refId: '__level_y_2[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__aux_y_1[_a2]', '__aux_y_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_y_1[a2]', 'INTEG(input[a2]-_aux_y_1[a2],init[a2]*((delay[a2])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a2]'], + refId: '__level_y_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input[_a2]', '__aux_y_1[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_aux_y_1[a2]', '_level_y_1[a2]/((delay[a2])/3)', { + includeInOutput: false, + refId: '__aux_y_1[_a2]', + references: ['__level_y_1[_a2]', '_delay[_a2]'], + subscripts: ['_a2'] + }), + v('_aux_y_2[a2]', '_level_y_2[a2]/((delay[a2])/3)', { + includeInOutput: false, + refId: '__aux_y_2[_a2]', + references: ['__level_y_2[_a2]', '_delay[_a2]'], + subscripts: ['_a2'] + }), + v('_aux_y_3[a2]', '_level_y_3[a2]/((delay[a2])/3)', { + includeInOutput: false, + refId: '__aux_y_3[_a2]', + references: ['__level_y_3[_a2]', '_delay[_a2]'], + subscripts: ['_a2'] + }), + v('_aux_y_4[a2]', '((delay[a2])/3)', { + includeInOutput: false, + refId: '__aux_y_4[_a2]', + references: ['_delay[_a2]'], + subscripts: ['_a2'] + }), + v('_level_y_3[a3]', 'INTEG(_aux_y_2[a3]-_aux_y_3[a3],init[a3]*((delay[a3])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a3]'], + refId: '__level_y_3[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__aux_y_2[_a3]', '__aux_y_3[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_y_2[a3]', 'INTEG(_aux_y_1[a3]-_aux_y_2[a3],init[a3]*((delay[a3])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a3]'], + refId: '__level_y_2[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__aux_y_1[_a3]', '__aux_y_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_y_1[a3]', 'INTEG(input[a3]-_aux_y_1[a3],init[a3]*((delay[a3])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init', '_delay[_a3]'], + refId: '__level_y_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input[_a3]', '__aux_y_1[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_aux_y_1[a3]', '_level_y_1[a3]/((delay[a3])/3)', { + includeInOutput: false, + refId: '__aux_y_1[_a3]', + references: ['__level_y_1[_a3]', '_delay[_a3]'], + subscripts: ['_a3'] + }), + v('_aux_y_2[a3]', '_level_y_2[a3]/((delay[a3])/3)', { + includeInOutput: false, + refId: '__aux_y_2[_a3]', + references: ['__level_y_2[_a3]', '_delay[_a3]'], + subscripts: ['_a3'] + }), + v('_aux_y_3[a3]', '_level_y_3[a3]/((delay[a3])/3)', { + includeInOutput: false, + refId: '__aux_y_3[_a3]', + references: ['__level_y_3[_a3]', '_delay[_a3]'], + subscripts: ['_a3'] + }), + v('_aux_y_4[a3]', '((delay[a3])/3)', { + includeInOutput: false, + refId: '__aux_y_4[_a3]', + references: ['_delay[_a3]'], + subscripts: ['_a3'] + }) + ]) + }) + + it('should work for DELAY FIXED function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = 2 ~~| + delay = y + 5 ~~| + init = 3 ~~| + z = DELAY FIXED(x, delay, init) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', '2', { + refId: '_y', + varType: 'const' + }), + v('delay', 'y+5', { + refId: '_delay', + references: ['_y'] + }), + v('init', '3', { + refId: '_init', + varType: 'const' + }), + v('z', 'DELAY FIXED(x,delay,init)', { + refId: '_z', + varType: 'level', + varSubtype: 'fixedDelay', + fixedDelayVarName: '__fixed_delay1', + references: ['_x'], + hasInitValue: true, + initReferences: ['_delay', '_init'], + referencedFunctionNames: ['__delay_fixed'] + }) + ]) + }) + + it('should work for DEPRECIATE STRAIGHTLINE function', () => { + const vars = readInlineModel(` + dtime = 20 ~~| + fisc = 1 ~~| + init = 5 ~~| + Capacity Cost = 1000 ~~| + New Capacity = 2000 ~~| + stream = Capacity Cost * New Capacity ~~| + Depreciated Amount = DEPRECIATE STRAIGHTLINE(stream, dtime, fisc, init) ~~| + `) + expect(vars).toEqual([ + v('dtime', '20', { + refId: '_dtime', + varType: 'const' + }), + v('fisc', '1', { + refId: '_fisc', + varType: 'const' + }), + v('init', '5', { + refId: '_init', + varType: 'const' + }), + v('Capacity Cost', '1000', { + refId: '_capacity_cost', + varType: 'const' + }), + v('New Capacity', '2000', { + refId: '_new_capacity', + varType: 'const' + }), + v('stream', 'Capacity Cost*New Capacity', { + refId: '_stream', + references: ['_capacity_cost', '_new_capacity'] + }), + v('Depreciated Amount', 'DEPRECIATE STRAIGHTLINE(stream,dtime,fisc,init)', { + refId: '_depreciated_amount', + varSubtype: 'depreciation', + depreciationVarName: '__depreciation1', + references: ['_stream', '_init'], + hasInitValue: true, + initReferences: ['_dtime', '_fisc'], + referencedFunctionNames: ['__depreciate_straightline'] + }) + ]) + }) + + it('should work for GAME function (no dimensions)', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = GAME(x) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', 'GAME(x)', { + gameLookupVarName: '_y_game_inputs', + refId: '_y', + referencedFunctionNames: ['__game'], + referencedLookupVarNames: ['_y_game_inputs'], + references: ['_x'] + }), + v('y game inputs', '', { + refId: '_y_game_inputs', + varType: 'lookup', + varSubtype: 'gameInputs' + }) + ]) + }) + + it('should work for GAME function (1D)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y[DimA] = GAME(x[DimA]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('y[DimA]', 'GAME(x[DimA])', { + gameLookupVarName: '_y_game_inputs', + refId: '_y', + referencedFunctionNames: ['__game'], + referencedLookupVarNames: ['_y_game_inputs'], + references: ['_x[_a1]', '_x[_a2]'], + subscripts: ['_dima'] + }), + v('y game inputs[DimA]', '', { + refId: '_y_game_inputs', + subscripts: ['_dima'], + varType: 'lookup', + varSubtype: 'gameInputs' + }) + ]) + }) + + it('should work for GAME function (2D)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + a[DimA] = 1, 2 ~~| + b[DimB] = 1, 2 ~~| + y[DimA, DimB] = GAME(a[DimA] + b[DimB]) ~~| + `) + expect(vars).toEqual([ + v('a[DimA]', '1,2', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '1,2', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('b[DimB]', '1,2', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '1,2', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + v('y[DimA,DimB]', 'GAME(a[DimA]+b[DimB])', { + gameLookupVarName: '_y_game_inputs', + refId: '_y', + referencedFunctionNames: ['__game'], + referencedLookupVarNames: ['_y_game_inputs'], + references: ['_a[_a1]', '_a[_a2]', '_b[_b1]', '_b[_b2]'], + subscripts: ['_dima', '_dimb'] + }), + v('y game inputs[DimA,DimB]', '', { + refId: '_y_game_inputs', + subscripts: ['_dima', '_dimb'], + varType: 'lookup', + varSubtype: 'gameInputs' + }) + ]) + }) + + it('should work for GAMMA LN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = GAMMA LN(x) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', 'GAMMA LN(x)', { + refId: '_y', + referencedFunctionNames: ['__gamma_ln'], + references: ['_x'] + }) + ]) + }) + + it('should work for GET DIRECT CONSTANTS function (single value)', () => { + const vars = readInlineModel(` + x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| + `) + expect(vars).toEqual([ + v('x', "GET DIRECT CONSTANTS('data/a.csv',',','B2')", { + directConstArgs: { file: 'data/a.csv', tab: ',', startCell: 'B2' }, + refId: '_x', + varType: 'const' + }) + ]) + }) + + it('should work for GET DIRECT CONSTANTS function (1D)', () => { + const vars = readInlineModel(` + DimB: B1, B2, B3 ~~| + x[DimB] = GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') ~~| + `) + expect(vars).toEqual([ + v('x[DimB]', "GET DIRECT CONSTANTS('data/b.csv',',','B2*')", { + directConstArgs: { file: 'data/b.csv', tab: ',', startCell: 'B2*' }, + refId: '_x', + subscripts: ['_dimb'], + varType: 'const' + }) + ]) + }) + + it('should work for GET DIRECT CONSTANTS function (2D)', () => { + const vars = readInlineModel(` + DimB: B1, B2, B3 ~~| + DimC: C1, C2 ~~| + x[DimB, DimC] = GET DIRECT CONSTANTS('data/c.csv', ',', 'B2') ~~| + `) + expect(vars).toEqual([ + v('x[DimB,DimC]', "GET DIRECT CONSTANTS('data/c.csv',',','B2')", { + directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2' }, + refId: '_x', + subscripts: ['_dimb', '_dimc'], + varType: 'const' + }) + ]) + }) + + it('should work for GET DIRECT CONSTANTS function (separate definitions)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimC: C1, C2 ~~| + x[DimC, SubA] = GET DIRECT CONSTANTS('data/f.csv',',','B2') ~~| + x[DimC, DimA] :EXCEPT: [DimC, SubA] = 0 ~~| + `) + expect(vars).toEqual([ + v('x[DimC,SubA]', "GET DIRECT CONSTANTS('data/f.csv',',','B2')", { + directConstArgs: { file: 'data/f.csv', tab: ',', startCell: 'B2' }, + refId: '_x[_dimc,_a2]', + separationDims: ['_suba'], + subscripts: ['_dimc', '_a2'], + varType: 'const' + }), + v('x[DimC,SubA]', "GET DIRECT CONSTANTS('data/f.csv',',','B2')", { + directConstArgs: { file: 'data/f.csv', tab: ',', startCell: 'B2' }, + refId: '_x[_dimc,_a3]', + separationDims: ['_suba'], + subscripts: ['_dimc', '_a3'], + varType: 'const' + }), + v('x[DimC,DimA]:EXCEPT:[DimC,SubA]', '0', { + refId: '_x[_dimc,_a1]', + separationDims: ['_dima'], + subscripts: ['_dimc', '_a1'], + varType: 'const' + }) + ]) + }) + + it('should work for GET DIRECT DATA function (single value)', () => { + const vars = readInlineModel(` + x = GET DIRECT DATA('g_data.csv', ',', 'A', 'B13') ~~| + y = x * 10 ~~| + `) + expect(vars).toEqual([ + v('x', "GET DIRECT DATA('g_data.csv',',','A','B13')", { + directDataArgs: { file: 'g_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B13' }, + refId: '_x', + varType: 'data' + }), + v('y', 'x*10', { + refId: '_y', + references: ['_x'] + }) + ]) + }) + + it('should work for GET DIRECT DATA function (1D)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| + y = x[A2] * 10 ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', "GET DIRECT DATA('e_data.csv',',','A','B5')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B5' }, + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT DATA('e_data.csv',',','A','B5')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B5' }, + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('y', 'x[A2]*10', { + refId: '_y', + references: ['_x[_a2]'] + }) + ]) + }) + + it('should work for GET DIRECT DATA function (2D with separate definitions)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1, DimB] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| + x[A2, DimB] = 0 ~~| + y = x[A2, B1] * 10 ~~| + `) + expect(vars).toEqual([ + v('x[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B5')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B5' }, + refId: '_x[_a1,_b1]', + separationDims: ['_dimb'], + subscripts: ['_a1', '_b1'], + varType: 'data' + }), + v('x[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B5')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B5' }, + refId: '_x[_a1,_b2]', + separationDims: ['_dimb'], + subscripts: ['_a1', '_b2'], + varType: 'data' + }), + v('x[A2,DimB]', '0', { + refId: '_x[_a2,_dimb]', + subscripts: ['_a2', '_dimb'], + varType: 'const' + }), + v('y', 'x[A2,B1]*10', { + refId: '_y', + references: ['_x[_a2,_dimb]'] + }) + ]) + }) + + it('should work for GET DIRECT LOOKUPS function', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| + y[DimA] = x[DimA](Time) ~~| + z = y[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('y[DimA]', 'x[DimA](Time)', { + refId: '_y', + referencedLookupVarNames: ['_x'], + references: ['_time'], + subscripts: ['_dima'] + }), + v('z', 'y[A2]', { + refId: '_z', + references: ['_y'] + }) + ]) + }) + + it('should work for IF THEN ELSE function', () => { + const vars = readInlineModel(` + x = 100 ~~| + y = 2 ~~| + z = IF THEN ELSE(Time > x, 1, y) ~~| + `) + expect(vars).toEqual([ + v('x', '100', { + refId: '_x', + varType: 'const' + }), + v('y', '2', { + refId: '_y', + varType: 'const' + }), + v('z', 'IF THEN ELSE(Time>x,1,y)', { + refId: '_z', + references: ['_time', '_x', '_y'] + }) + ]) + }) + + it('should work for INITIAL function', () => { + const vars = readInlineModel(` + x = Time * 2 ~~| + y = INITIAL(x) ~~| + `) + expect(vars).toEqual([ + v('x', 'Time*2', { + refId: '_x', + references: ['_time'] + }), + v('y', 'INITIAL(x)', { + refId: '_y', + varType: 'initial', + hasInitValue: true, + initReferences: ['_x'], + referencedFunctionNames: ['__initial'] + }) + ]) + }) + + it('should work for INTEG function', () => { + const vars = readInlineModel(` + x = Time * 2 ~~| + init = 5 ~~| + y = INTEG(x, init) ~~| + `) + expect(vars).toEqual([ + v('x', 'Time*2', { + refId: '_x', + references: ['_time'] + }), + v('init', '5', { + refId: '_init', + varType: 'const' + }), + v('y', 'INTEG(x,init)', { + refId: '_y', + varType: 'level', + references: ['_x'], + hasInitValue: true, + initReferences: ['_init'], + referencedFunctionNames: ['__integ'] + }) + ]) + }) + + it('should work for INTEG function (with nested function calls)', () => { + const vars = readInlineModel(` + x = Time * 2 ~~| + init = 5 ~~| + y = INTEG(ABS(x), POW(init, 3)) ~~| + `) + expect(vars).toEqual([ + v('x', 'Time*2', { + refId: '_x', + references: ['_time'] + }), + v('init', '5', { + refId: '_init', + varType: 'const' + }), + v('y', 'INTEG(ABS(x),POW(init,3))', { + refId: '_y', + varType: 'level', + references: ['_x'], + hasInitValue: true, + initReferences: ['_init'], + referencedFunctionNames: ['__integ', '__abs', '__pow'] + }) + ]) + }) + + it('should work for LOOKUP BACKWARD function (with lookup defined explicitly)', () => { + const vars = readInlineModel(` + x( (0,0),(2,1.3) ) ~~| + y = LOOKUP BACKWARD(x, 1) ~~| + `) + expect(vars).toEqual([ + v('x', '', { + refId: '_x', + varType: 'lookup', + range: [], + points: [ + [0, 0], + [2, 1.3] + ] + }), + v('y', 'LOOKUP BACKWARD(x,1)', { + refId: '_y', + referencedFunctionNames: ['__lookup_backward'], + references: ['_x'] + }) + ]) + }) + + it('should work for LOOKUP BACKWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| + y[DimA] = LOOKUP BACKWARD(x[DimA], Time) ~~| + z = y[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('y[DimA]', 'LOOKUP BACKWARD(x[DimA],Time)', { + refId: '_y', + referencedFunctionNames: ['__lookup_backward'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]', '_time'], + subscripts: ['_dima'] + }), + v('z', 'y[A2]', { + refId: '_z', + references: ['_y'] + }) + ]) + }) + + it('should work for LOOKUP FORWARD function (with lookup defined explicitly)', () => { + const vars = readInlineModel(` + x( (0,0),(2,1.3) ) ~~| + y = LOOKUP FORWARD(x, 1) ~~| + `) + expect(vars).toEqual([ + v('x', '', { + refId: '_x', + varType: 'lookup', + range: [], + points: [ + [0, 0], + [2, 1.3] + ] + }), + v('y', 'LOOKUP FORWARD(x,1)', { + refId: '_y', + referencedFunctionNames: ['__lookup_forward'], + references: ['_x'] + }) + ]) + }) + + it('should work for LOOKUP FORWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| + y[DimA] = LOOKUP FORWARD(x[DimA], Time) ~~| + z = y[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('y[DimA]', 'LOOKUP FORWARD(x[DimA],Time)', { + refId: '_y', + referencedFunctionNames: ['__lookup_forward'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]', '_time'], + subscripts: ['_dima'] + }), + v('z', 'y[A2]', { + refId: '_z', + references: ['_y'] + }) + ]) + }) + + it('should work for LOOKUP INVERT function (with lookup defined explicitly)', () => { + const vars = readInlineModel(` + x( (0,0),(2,1.3) ) ~~| + y = LOOKUP INVERT(x, 1) ~~| + `) + expect(vars).toEqual([ + v('x', '', { + refId: '_x', + varType: 'lookup', + range: [], + points: [ + [0, 0], + [2, 1.3] + ] + }), + v('y', 'LOOKUP INVERT(x,1)', { + refId: '_y', + referencedFunctionNames: ['__lookup_invert'], + references: ['_x'] + }) + ]) + }) + + it('should work for LOOKUP INVERT function (with lookup defined using GET DIRECT LOOKUPS)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| + y[DimA] = LOOKUP INVERT(x[DimA], Time) ~~| + z = y[A2] ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('x[DimA]', "GET DIRECT LOOKUPS('lookups.csv',',','1','AH2')", { + directDataArgs: { file: 'lookups.csv', tab: ',', timeRowOrCol: '1', startCell: 'AH2' }, + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('y[DimA]', 'LOOKUP INVERT(x[DimA],Time)', { + refId: '_y', + referencedFunctionNames: ['__lookup_invert'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]', '_time'], + subscripts: ['_dima'] + }), + v('z', 'y[A2]', { + refId: '_z', + references: ['_y'] + }) + ]) + }) + + it('should work for MAX function', () => { + const vars = readInlineModel(` + a = 10 ~~| + b = 20 ~~| + y = MAX(a, b) ~~| + `) + expect(vars).toEqual([ + v('a', '10', { + refId: '_a', + varType: 'const' + }), + v('b', '20', { + refId: '_b', + varType: 'const' + }), + v('y', 'MAX(a,b)', { + refId: '_y', + referencedFunctionNames: ['__max'], + references: ['_a', '_b'] + }) + ]) + }) + + it('should work for MIN function', () => { + const vars = readInlineModel(` + a = 10 ~~| + b = 20 ~~| + y = MIN(a, b) ~~| + `) + expect(vars).toEqual([ + v('a', '10', { + refId: '_a', + varType: 'const' + }), + v('b', '20', { + refId: '_b', + varType: 'const' + }), + v('y', 'MIN(a,b)', { + refId: '_y', + referencedFunctionNames: ['__min'], + references: ['_a', '_b'] + }) + ]) + }) + + it('should work for MODULO function', () => { + const vars = readInlineModel(` + a = 20 ~~| + b = 10 ~~| + y = MODULO(a, b) ~~| + `) + expect(vars).toEqual([ + v('a', '20', { + refId: '_a', + varType: 'const' + }), + v('b', '10', { + refId: '_b', + varType: 'const' + }), + v('y', 'MODULO(a,b)', { + refId: '_y', + referencedFunctionNames: ['__modulo'], + references: ['_a', '_b'] + }) + ]) + }) + + // TODO: Add a variant where discount rate is defined as (x+1) (old reader did not include + // parens and might generate incorrect equation) + it('should work for NPV function', () => { + const vars = readInlineModel(` + stream = 100 ~~| + discount rate = 10 ~~| + init = 0 ~~| + factor = 2 ~~| + y = NPV(stream, discount rate, init, factor) ~~| + `) + expect(vars).toEqual([ + v('stream', '100', { + refId: '_stream', + varType: 'const' + }), + v('discount rate', '10', { + refId: '_discount_rate', + varType: 'const' + }), + v('init', '0', { + refId: '_init', + varType: 'const' + }), + v('factor', '2', { + refId: '_factor', + varType: 'const' + }), + v('y', 'NPV(stream,discount rate,init,factor)', { + refId: '_y', + references: ['__level2', '__level1', '__aux1'], + npvVarName: '__aux1' + }), + v('_level1', 'INTEG((-_level1*discount rate)/(1+discount rate*TIME STEP),1)', { + refId: '__level1', + varType: 'level', + includeInOutput: false, + references: ['_discount_rate', '_time_step'], + hasInitValue: true, + referencedFunctionNames: ['__integ'] + }), + v('_level2', 'INTEG(stream*_level1,init)', { + refId: '__level2', + varType: 'level', + includeInOutput: false, + references: ['_stream', '__level1'], + hasInitValue: true, + initReferences: ['_init'], + referencedFunctionNames: ['__integ'] + }), + v('_aux1', '(_level2+stream*TIME STEP*_level1)*factor', { + refId: '__aux1', + includeInOutput: false, + references: ['__level2', '_stream', '_time_step', '__level1', '_factor'] + }) + ]) + }) + + // TODO + it.skip('should work for NPV function (with subscripted variables)', () => {}) + + it('should work for PULSE function', () => { + const vars = readInlineModel(` + start = 10 ~~| + width = 20 ~~| + y = PULSE(start, width) ~~| + `) + expect(vars).toEqual([ + v('start', '10', { + refId: '_start', + varType: 'const' + }), + v('width', '20', { + refId: '_width', + varType: 'const' + }), + v('y', 'PULSE(start,width)', { + refId: '_y', + referencedFunctionNames: ['__pulse'], + references: ['_start', '_width'] + }) + ]) + }) + + it('should work for SAMPLE IF TRUE function', () => { + const vars = readInlineModel(` + initial = 10 ~~| + input = 5 ~~| + x = 1 ~~| + y = SAMPLE IF TRUE(Time > x, input, initial) ~~| + `) + expect(vars).toEqual([ + v('initial', '10', { + refId: '_initial', + varType: 'const' + }), + v('input', '5', { + refId: '_input', + varType: 'const' + }), + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('y', 'SAMPLE IF TRUE(Time>x,input,initial)', { + refId: '_y', + references: ['_time', '_x', '_input'], + hasInitValue: true, + initReferences: ['_initial'], + referencedFunctionNames: ['__sample_if_true'] + }) + ]) + }) + + it('should work for SMOOTH function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTH(input, delay) ~~| + `) + expect(vars).toEqual([ + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y', 'SMOOTH(input,delay)', { + refId: '_y', + references: ['__level1'], + smoothVarRefId: '__level1' + }), + v('_level1', 'INTEG((input-_level1)/delay,input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH function (with subscripted input and subscripted delay)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay[DimA] = 2, 3 ~~| + y[DimA] = SMOOTH(input[DimA], delay[DimA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima'] + }), + v('delay[DimA]', '2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('y[DimA]', 'SMOOTH(input[DimA],delay[DimA])', { + refId: '_y', + references: ['__level1'], + smoothVarRefId: '__level1', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/delay[DimA],input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a1]', '_delay[_a2]'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH function (with subscripted input and non-subscripted delay)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y[DimA] = SMOOTH(input[DimA], delay) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y[DimA]', 'SMOOTH(input[DimA],delay)', { + refId: '_y', + references: ['__level1'], + smoothVarRefId: '__level1', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/delay,input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTHI function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + init = 5 ~~| + y = SMOOTHI(input, delay, init) ~~| + `) + expect(vars).toEqual([ + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('init', '5', { + refId: '_init', + varType: 'const' + }), + v('y', 'SMOOTHI(input,delay,init)', { + refId: '_y', + references: ['__level1'], + smoothVarRefId: '__level1' + }), + v('_level1', 'INTEG((input-_level1)/delay,init)', { + includeInOutput: false, + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + hasInitValue: true, + initReferences: ['_init'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTHI function (with subscripted variables)', () => { + // Note that we have a mix of non-apply-to-all (delay, init) and apply-to-all (input) + // variables here to cover both cases + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + input[DimA] = x[DimA] + PULSE(10, 10) ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 4, 5, 6 ~~| + y[DimA] = SMOOTHI(input[DimA], delay[DimA], init[DimA]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], + subscripts: ['_dima'] + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '4,5,6', { + refId: '_init[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('init[DimA]', '4,5,6', { + refId: '_init[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('init[DimA]', '4,5,6', { + refId: '_init[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('y[DimA]', 'SMOOTHI(input[DimA],delay[DimA],init[DimA])', { + refId: '_y', + references: ['__level1'], + smoothVarRefId: '__level1', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/delay[DimA],init[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init[_a1]', '_init[_a2]', '_init[_a3]'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTHI function (with separated variables using subdimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + input[DimA] = x[DimA] + PULSE(10, 10) ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 0 ~~| + y[A1] = 5 ~~| + y[SubA] = SMOOTHI(input[SubA], delay[SubA], init[SubA]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], + subscripts: ['_dima'] + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '0', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[A1]', '5', { + refId: '_y[_a1]', + subscripts: ['_a1'], + varType: 'const' + }), + v('y[SubA]', 'SMOOTHI(input[SubA],delay[SubA],init[SubA])', { + refId: '_y[_a2]', + references: ['__level_y_1[_a2]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_y_1[_a2]', + subscripts: ['_a2'] + }), + v('y[SubA]', 'SMOOTHI(input[SubA],delay[SubA],init[SubA])', { + refId: '_y[_a3]', + references: ['__level_y_1[_a3]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_y_1[_a3]', + subscripts: ['_a3'] + }), + v('_level_y_1[a2]', 'INTEG((input[a2]-_level_y_1[a2])/delay[a2],init[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_y_1[a3]', 'INTEG((input[a3]-_level_y_1[a3])/delay[a3],init[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3 function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTH3(input, delay) ~~| + `) + expect(vars).toEqual([ + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y', 'SMOOTH3(input,delay)', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3' + }), + v('_level1', 'INTEG((input-_level1)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level2', 'INTEG((_level1-_level2)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay'], + varType: 'level' + }), + v('_level3', 'INTEG((_level2-_level3)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3 function (when nested in another function)', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = MAX(SMOOTH3(input, delay), 0) ~~| + `) + expect(vars).toEqual([ + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y', 'MAX(SMOOTH3(input,delay),0)', { + refId: '_y', + referencedFunctionNames: ['__max'], + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3' + }), + v('_level1', 'INTEG((input-_level1)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level2', 'INTEG((_level1-_level2)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay'], + varType: 'level' + }), + v('_level3', 'INTEG((_level2-_level3)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3 function (with subscripted input and subscripted delay)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay[DimA] = 2, 3 ~~| + y[DimA] = SMOOTH3(input[DimA], delay[DimA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima'] + }), + v('delay[DimA]', '2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('y[DimA]', 'SMOOTH3(input[DimA],delay[DimA])', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/(delay[DimA]/3),input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a1]', '_delay[_a2]'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level2[DimA]', 'INTEG((_level1[DimA]-_level2[DimA])/(delay[DimA]/3),input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay[_a1]', '_delay[_a2]'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level3[DimA]', 'INTEG((_level2[DimA]-_level3[DimA])/(delay[DimA]/3),input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay[_a1]', '_delay[_a2]'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3 function (with subscripted input and non-subscripted delay)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y[DimA] = SMOOTH3(input[DimA], delay) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y[DimA]', 'SMOOTH3(input[DimA],delay)', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/(delay/3),input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level2[DimA]', 'INTEG((_level1[DimA]-_level2[DimA])/(delay/3),input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level3[DimA]', 'INTEG((_level2[DimA]-_level3[DimA])/(delay/3),input[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3I function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTH3I(input, delay, 5) ~~| + `) + expect(vars).toEqual([ + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y', 'SMOOTH3I(input,delay,5)', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3' + }), + v('_level1', 'INTEG((input-_level1)/(delay/3),5)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level2', 'INTEG((_level1-_level2)/(delay/3),5)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay'], + varType: 'level' + }), + v('_level3', 'INTEG((_level2-_level3)/(delay/3),5)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3I function (with nested function calls)', () => { + const vars = readInlineModel(` + x = 1 ~~| + input = x + PULSE(10, 10) ~~| + delay = 3 ~~| + init = 0 ~~| + y = SMOOTH3I(MIN(0, input), MIN(0, delay), ABS(init)) ~~| + `) + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('input', 'x+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + references: ['_x'] + }), + v('delay', '3', { + refId: '_delay', + varType: 'const' + }), + v('init', '0', { + refId: '_init', + varType: 'const' + }), + v('y', 'SMOOTH3I(MIN(0,input),MIN(0,delay),ABS(init))', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3' + }), + v('_level1', 'INTEG((MIN(0,input)-_level1)/(MIN(0,delay)/3),ABS(init))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level1', + referencedFunctionNames: ['__integ', '__min', '__abs'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level2', 'INTEG((_level1-_level2)/(MIN(0,delay)/3),ABS(init))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level2', + referencedFunctionNames: ['__integ', '__min', '__abs'], + references: ['__level1', '_delay'], + varType: 'level' + }), + v('_level3', 'INTEG((_level2-_level3)/(MIN(0,delay)/3),ABS(init))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level3', + referencedFunctionNames: ['__integ', '__min', '__abs'], + references: ['__level2', '_delay'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3I function (with subscripted variables)', () => { + // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) + // variables here to cover both cases + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + input[DimA] = x[DimA] + PULSE(10, 10) ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 4, 5, 6 ~~| + y[DimA] = SMOOTH3I(input[DimA], delay[DimA], init[DimA]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], + subscripts: ['_dima'] + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '4,5,6', { + refId: '_init[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('init[DimA]', '4,5,6', { + refId: '_init[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('init[DimA]', '4,5,6', { + refId: '_init[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('y[DimA]', 'SMOOTH3I(input[DimA],delay[DimA],init[DimA])', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/(delay[DimA]/3),init[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init[_a1]', '_init[_a2]', '_init[_a3]'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level2[DimA]', 'INTEG((_level1[DimA]-_level2[DimA])/(delay[DimA]/3),init[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init[_a1]', '_init[_a2]', '_init[_a3]'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level3[DimA]', 'INTEG((_level2[DimA]-_level3[DimA])/(delay[DimA]/3),init[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init[_a1]', '_init[_a2]', '_init[_a3]'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay[_a1]', '_delay[_a2]', '_delay[_a3]'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3I function (with subscripted input and non-subscripted delay)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y[DimA] = SMOOTH3I(input[DimA], delay, 5) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('y[DimA]', 'SMOOTH3I(input[DimA],delay,5)', { + refId: '_y', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3', + subscripts: ['_dima'] + }), + v('_level1[DimA]', 'INTEG((input[DimA]-_level1[DimA])/(delay/3),5)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level2[DimA]', 'INTEG((_level1[DimA]-_level2[DimA])/(delay/3),5)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['__level1', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level3[DimA]', 'INTEG((_level2[DimA]-_level3[DimA])/(delay/3),5)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['__level2', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for SMOOTH3I function (with separated variables using subdimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + input[DimA] = x[DimA] + PULSE(10, 10) ~~| + delay[DimA] = 1, 2, 3 ~~| + init[DimA] = 0 ~~| + y[A1] = 5 ~~| + y[SubA] = SMOOTH3I(input[SubA], delay[SubA], init[SubA]) ~~| + `) + expect(vars).toEqual([ + v('x[DimA]', '1,2,3', { + refId: '_x[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('x[DimA]', '1,2,3', { + refId: '_x[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'], + references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], + subscripts: ['_dima'] + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay[DimA]', '1,2,3', { + refId: '_delay[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init[DimA]', '0', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[A1]', '5', { + refId: '_y[_a1]', + subscripts: ['_a1'], + varType: 'const' + }), + v('y[SubA]', 'SMOOTH3I(input[SubA],delay[SubA],init[SubA])', { + refId: '_y[_a2]', + references: ['__level_y_1[_a2]', '__level_y_2[_a2]', '__level_y_3[_a2]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_y_3[_a2]', + subscripts: ['_a2'] + }), + v('y[SubA]', 'SMOOTH3I(input[SubA],delay[SubA],init[SubA])', { + refId: '_y[_a3]', + references: ['__level_y_1[_a3]', '__level_y_2[_a3]', '__level_y_3[_a3]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_y_3[_a3]', + subscripts: ['_a3'] + }), + v('_level_y_1[a2]', 'INTEG((input[a2]-_level_y_1[a2])/(delay[a2]/3),init[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_y_2[a2]', 'INTEG((_level_y_1[a2]-_level_y_2[a2])/(delay[a2]/3),init[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_2[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__level_y_1[_a2]', '_delay[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_y_3[a2]', 'INTEG((_level_y_2[a2]-_level_y_3[a2])/(delay[a2]/3),init[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_3[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__level_y_2[_a2]', '_delay[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_y_1[a3]', 'INTEG((input[a3]-_level_y_1[a3])/(delay[a3]/3),init[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_y_2[a3]', 'INTEG((_level_y_1[a3]-_level_y_2[a3])/(delay[a3]/3),init[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_2[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__level_y_1[_a3]', '_delay[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_y_3[a3]', 'INTEG((_level_y_2[a3]-_level_y_3[a3])/(delay[a3]/3),init[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init'], + refId: '__level_y_3[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__level_y_2[_a3]', '_delay[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }) + ]) + }) + + it('should work for TREND function', () => { + const vars = readInlineModel(` + input = 1 ~~| + avg time = 2 ~~| + init = 3 ~~| + y = TREND(input, avg time, init) ~~| + `) + expect(vars).toEqual([ + v('input', '1', { + refId: '_input', + varType: 'const' + }), + v('avg time', '2', { + refId: '_avg_time', + varType: 'const' + }), + v('init', '3', { + refId: '_init', + varType: 'const' + }), + v('y', 'TREND(input,avg time,init)', { + refId: '_y', + references: ['__level1', '__aux1'], + trendVarName: '__aux1' + }), + v('_level1', 'INTEG((input-_level1)/avg time,input/(1+init*avg time))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_init', '_avg_time'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_avg_time'], + varType: 'level' + }), + v('_aux1', 'ZIDZ(input-_level1,avg time*ABS(_level1))', { + includeInOutput: false, + refId: '__aux1', + referencedFunctionNames: ['__zidz', '__abs'], + references: ['_input', '__level1', '_avg_time'] + }) + ]) + }) + + it('should work for TREND function (with subscripted variables)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 1, 2 ~~| + avg time[DimA] = 3, 4 ~~| + init[DimA] = 5 ~~| + y[DimA] = TREND(input[DimA], avg time[DimA], init[DimA]) ~~| + `) + expect(vars).toEqual([ + v('input[DimA]', '1,2', { + refId: '_input[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('input[DimA]', '1,2', { + refId: '_input[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('avg time[DimA]', '3,4', { + refId: '_avg_time[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('avg time[DimA]', '3,4', { + refId: '_avg_time[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('init[DimA]', '5', { + refId: '_init', + subscripts: ['_dima'], + varType: 'const' + }), + v('y[DimA]', 'TREND(input[DimA],avg time[DimA],init[DimA])', { + refId: '_y', + references: ['__level1', '__aux1'], + subscripts: ['_dima'], + trendVarName: '__aux1' + }), + v( + '_level1[DimA]', + 'INTEG((input[DimA]-_level1[DimA])/avg time[DimA],input[DimA]/(1+init[DimA]*avg time[DimA]))', + { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input[_a1]', '_input[_a2]', '_init', '_avg_time[_a1]', '_avg_time[_a2]'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input[_a1]', '_input[_a2]', '_avg_time[_a1]', '_avg_time[_a2]'], + subscripts: ['_dima'], + varType: 'level' + } + ), + v('_aux1[DimA]', 'ZIDZ(input[DimA]-_level1[DimA],avg time[DimA]*ABS(_level1[DimA]))', { + includeInOutput: false, + refId: '__aux1', + referencedFunctionNames: ['__zidz', '__abs'], + references: ['_input[_a1]', '_input[_a2]', '__level1', '_avg_time[_a1]', '_avg_time[_a2]'], + subscripts: ['_dima'] + }) + ]) + }) + + it('should work for WITH LOOKUP function', () => { + const vars = readInlineModel(` + y = WITH LOOKUP(Time, ( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) )) ~~| + `) + expect(vars).toEqual([ + v('y', 'WITH LOOKUP(Time,([(0,0)-(2,2)],(0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3)))', { + lookupArgVarName: '__lookup1', + refId: '_y', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup1'], + references: ['_time'] + }), + v('_lookup1', '', { + includeInOutput: false, + points: [ + [0, 0], + [0.1, 0.01], + [0.5, 0.7], + [1, 1], + [1.5, 1.2], + [2, 1.3] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup1', + varType: 'lookup' + }) + ]) + }) + + it('should work for XMILE "active_initial" model', () => { + const vars = readSubscriptsAndEquations('active_initial') + expect(vars).toEqual([ + v('Capacity', 'INTEG(Capacity Adjustment Rate,Target Capacity)', { + hasInitValue: true, + initReferences: ['_target_capacity'], + refId: '_capacity', + referencedFunctionNames: ['__integ'], + references: ['_capacity_adjustment_rate'], + varType: 'level' + }), + v('Capacity Adjustment Rate', '(Target Capacity-Capacity)/Capacity Adjustment Time', { + refId: '_capacity_adjustment_rate', + references: ['_target_capacity', '_capacity', '_capacity_adjustment_time'] + }), + v('Capacity Adjustment Time', '10', { + refId: '_capacity_adjustment_time', + varType: 'const' + }), + v('Capacity Utilization', 'Production/Capacity', { + refId: '_capacity_utilization', + references: ['_production', '_capacity'] + }), + v('Initial Target Capacity', '100', { + refId: '_initial_target_capacity', + varType: 'const' + }), + v('Production', '100+STEP(100,10)', { + refId: '_production', + referencedFunctionNames: ['__step'] + }), + v('Target Capacity', 'ACTIVE INITIAL(Capacity*Utilization Adjustment,Initial Target Capacity)', { + hasInitValue: true, + initReferences: ['_initial_target_capacity'], + refId: '_target_capacity', + referencedFunctionNames: ['__active_initial'], + references: ['_capacity', '_utilization_adjustment'] + }), + v('Utilization Adjustment', 'Capacity Utilization^Utilization Sensitivity', { + refId: '_utilization_adjustment', + references: ['_capacity_utilization', '_utilization_sensitivity'] + }), + v('Utilization Sensitivity', '1', { + refId: '_utilization_sensitivity', + varType: 'const' + }), + v('FINAL TIME', '100', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "allocate" model', () => { + const vars = readSubscriptsAndEquations('allocate') + expect(vars).toEqual([ + v( + 'shipments[region]', + 'ALLOCATE AVAILABLE(demand[region],priority vector[region,ptype],total supply available)', + { + refId: '_shipments', + referencedFunctionNames: ['__allocate_available'], + references: [ + '_demand[_boston]', + '_demand[_dayton]', + '_demand[_fresno]', + '_priority_vector[_region,_ptype]', + '_priority_vector[_region,_ppriority]', + '_priority_vector[_region,_pwidth]', + '_priority_vector[_region,_pextra]', + '_total_supply_available' + ], + subscripts: ['_region'] + } + ), + v( + 'total supply available', + 'IF THEN ELSE(integer supply,INTEGER(Initial Supply+(Final Supply-Initial Supply)*(Time-INITIAL TIME)/(FINAL TIME-INITIAL TIME)),Initial Supply+(Final Supply-Initial Supply)*(Time-INITIAL TIME)/(FINAL TIME-INITIAL TIME))', + { + refId: '_total_supply_available', + referencedFunctionNames: ['__integer'], + references: ['_integer_supply', '_initial_supply', '_final_supply', '_time', '_initial_time', '_final_time'] + } + ), + v('integer supply', '0', { + refId: '_integer_supply', + varType: 'const' + }), + v('total demand', 'SUM(demand[region!])', { + refId: '_total_demand', + referencedFunctionNames: ['__sum'], + references: ['_demand[_boston]', '_demand[_dayton]', '_demand[_fresno]'] + }), + v('total shipments', 'SUM(shipments[region!])', { + refId: '_total_shipments', + referencedFunctionNames: ['__sum'], + references: ['_shipments'] + }), + v('extra', '1', { + refId: '_extra', + varType: 'const' + }), + v('priority[region]', '1,2,3', { + refId: '_priority[_boston]', + separationDims: ['_region'], + subscripts: ['_boston'], + varType: 'const' + }), + v('priority[region]', '1,2,3', { + refId: '_priority[_dayton]', + separationDims: ['_region'], + subscripts: ['_dayton'], + varType: 'const' + }), + v('priority[region]', '1,2,3', { + refId: '_priority[_fresno]', + separationDims: ['_region'], + subscripts: ['_fresno'], + varType: 'const' + }), + v('Final Supply', '10', { + refId: '_final_supply', + varType: 'const' + }), + v('Initial Supply', '0', { + refId: '_initial_supply', + varType: 'const' + }), + v('integer type', '0', { + refId: '_integer_type', + varType: 'const' + }), + v('demand[region]', '3,2,4', { + refId: '_demand[_boston]', + separationDims: ['_region'], + subscripts: ['_boston'], + varType: 'const' + }), + v('demand[region]', '3,2,4', { + refId: '_demand[_dayton]', + separationDims: ['_region'], + subscripts: ['_dayton'], + varType: 'const' + }), + v('demand[region]', '3,2,4', { + refId: '_demand[_fresno]', + separationDims: ['_region'], + subscripts: ['_fresno'], + varType: 'const' + }), + v('priority vector[region,ptype]', 'priority type+integer type', { + refId: '_priority_vector[_region,_ptype]', + references: ['_priority_type', '_integer_type'], + subscripts: ['_region', '_ptype'] + }), + v('priority vector[region,ppriority]', 'priority[region]', { + refId: '_priority_vector[_region,_ppriority]', + references: ['_priority[_boston]', '_priority[_dayton]', '_priority[_fresno]'], + subscripts: ['_region', '_ppriority'] + }), + v('priority vector[region,pwidth]', 'priority width', { + refId: '_priority_vector[_region,_pwidth]', + references: ['_priority_width'], + subscripts: ['_region', '_pwidth'] + }), + v('priority vector[region,pextra]', 'extra', { + refId: '_priority_vector[_region,_pextra]', + references: ['_extra'], + subscripts: ['_region', '_pextra'] + }), + v('priority width', '1', { + refId: '_priority_width', + varType: 'const' + }), + v('priority type', '3', { + refId: '_priority_type', + varType: 'const' + }), + v('FINAL TIME', '12', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '0.125', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + // it('should work for XMILE "arrays" model', () => { + // const vars = readSubscriptsAndEquations('arrays') + // logPrettyVars(vars) + // expect(vars).toEqual([]) + // }) + + it('should work for XMILE "delay" model', () => { + const vars = readSubscriptsAndEquations('delay') + expect(vars).toEqual([ + v('input', 'STEP(10,0)-STEP(10,4)', { + refId: '_input', + referencedFunctionNames: ['__step'] + }), + v('delay', '5', { + refId: '_delay', + varType: 'const' + }), + v('init 1', '0', { + refId: '_init_1', + varType: 'const' + }), + v('input a[DimA]', '10,20,30', { + refId: '_input_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('input a[DimA]', '10,20,30', { + refId: '_input_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('input a[DimA]', '10,20,30', { + refId: '_input_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay a[DimA]', '1,2,3', { + refId: '_delay_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('delay a[DimA]', '1,2,3', { + refId: '_delay_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay a[DimA]', '1,2,3', { + refId: '_delay_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('init a[DimA]', '0', { + refId: '_init_a', + subscripts: ['_dima'], + varType: 'const' + }), + v('input 2[SubA]', '20,30', { + refId: '_input_2[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('input 2[SubA]', '20,30', { + refId: '_input_2[_a3]', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay 2', '5', { + refId: '_delay_2', + varType: 'const' + }), + v('init 2[SubA]', '0', { + refId: '_init_2[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('init 2[SubA]', '0', { + refId: '_init_2[_a3]', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('k', '42', { + refId: '_k', + varType: 'const' + }), + v('d1', 'DELAY1(input,delay)', { + delayTimeVarName: '__aux1', + delayVarRefId: '__level1', + refId: '_d1', + references: ['__level1', '__aux1'] + }), + v('d2[DimA]', 'DELAY1I(input a[DimA],delay,init 1)', { + delayTimeVarName: '__aux2', + delayVarRefId: '__level2', + refId: '_d2', + references: ['__level2', '__aux2[_dima]'], + subscripts: ['_dima'] + }), + v('d3[DimA]', 'DELAY1I(input,delay a[DimA],init 1)', { + delayTimeVarName: '__aux3', + delayVarRefId: '__level3', + refId: '_d3', + references: ['__level3', '__aux3[_dima]'], + subscripts: ['_dima'] + }), + v('d4[DimA]', 'DELAY1I(input,delay,init a[DimA])', { + delayTimeVarName: '__aux4', + delayVarRefId: '__level4', + refId: '_d4', + references: ['__level4', '__aux4[_dima]'], + subscripts: ['_dima'] + }), + v('d5[DimA]', 'DELAY1I(input a[DimA],delay a[DimA],init a[DimA])', { + delayTimeVarName: '__aux5', + delayVarRefId: '__level5', + refId: '_d5', + references: ['__level5', '__aux5[_dima]'], + subscripts: ['_dima'] + }), + v('d6[SubA]', 'DELAY1I(input 2[SubA],delay 2,init 2[SubA])', { + delayTimeVarName: '__aux6', + delayVarRefId: '__level_d6_1[_a2]', + refId: '_d6[_a2]', + references: ['__level_d6_1[_a2]', '__aux6[_a2]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('d6[SubA]', 'DELAY1I(input 2[SubA],delay 2,init 2[SubA])', { + delayTimeVarName: '__aux7', + delayVarRefId: '__level_d6_1[_a3]', + refId: '_d6[_a3]', + references: ['__level_d6_1[_a3]', '__aux7[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('d7', 'DELAY3(input,delay)', { + delayTimeVarName: '__aux11', + delayVarRefId: '__level8', + refId: '_d7', + references: ['__level8', '__level7', '__level6', '__aux11'] + }), + v('d8[DimA]', 'DELAY3(input,delay a[DimA])', { + delayTimeVarName: '__aux15', + delayVarRefId: '__level11', + refId: '_d8', + references: ['__level11', '__level10', '__level9', '__aux15[_dima]'], + subscripts: ['_dima'] + }), + v('d9[SubA]', 'DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { + delayTimeVarName: '__aux_d9_4', + delayVarRefId: '__level_d9_3[_a2]', + refId: '_d9[_a2]', + references: ['__level_d9_3[_a2]', '__level_d9_2[_a2]', '__level_d9_1[_a2]', '__aux_d9_4[_a2]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('d9[SubA]', 'DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { + delayTimeVarName: '__aux_d9_4', + delayVarRefId: '__level_d9_3[_a3]', + refId: '_d9[_a3]', + references: ['__level_d9_3[_a3]', '__level_d9_2[_a3]', '__level_d9_1[_a3]', '__aux_d9_4[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('d10', 'k*DELAY3(input,delay)', { + delayTimeVarName: '__aux19', + delayVarRefId: '__level14', + refId: '_d10', + references: ['_k', '__level14', '__level13', '__level12', '__aux19'] + }), + v('d11[DimA]', 'k*DELAY3(input,delay a[DimA])', { + delayTimeVarName: '__aux23', + delayVarRefId: '__level17', + refId: '_d11', + references: ['_k', '__level17', '__level16', '__level15', '__aux23[_dima]'], + subscripts: ['_dima'] + }), + v('d12[SubA]', 'k*DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { + delayTimeVarName: '__aux_d12_4', + delayVarRefId: '__level_d12_3[_a2]', + refId: '_d12[_a2]', + references: ['_k', '__level_d12_3[_a2]', '__level_d12_2[_a2]', '__level_d12_1[_a2]', '__aux_d12_4[_a2]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('d12[SubA]', 'k*DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { + delayTimeVarName: '__aux_d12_4', + delayVarRefId: '__level_d12_3[_a3]', + refId: '_d12[_a3]', + references: ['_k', '__level_d12_3[_a3]', '__level_d12_2[_a3]', '__level_d12_1[_a3]', '__aux_d12_4[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_level1', 'INTEG(input-d1,input*delay)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_d1'], + varType: 'level' + }), + v('_aux1', 'delay', { + includeInOutput: false, + refId: '__aux1', + references: ['_delay'] + }), + v('_level2[DimA]', 'INTEG(input a[DimA]-d2[DimA],init 1*delay)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_1', '_delay'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['_input_a[_a1]', '_input_a[_a2]', '_input_a[_a3]', '_d2'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux2[DimA]', 'delay', { + includeInOutput: false, + refId: '__aux2', + references: ['_delay'], + subscripts: ['_dima'] + }), + v('_level3[DimA]', 'INTEG(input-d3[DimA],init 1*delay a[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_1', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['_input', '_d3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux3[DimA]', 'delay a[DimA]', { + includeInOutput: false, + refId: '__aux3', + references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_level4[DimA]', 'INTEG(input-d4[DimA],init a[DimA]*delay)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_a', '_delay'], + refId: '__level4', + referencedFunctionNames: ['__integ'], + references: ['_input', '_d4'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux4[DimA]', 'delay', { + includeInOutput: false, + refId: '__aux4', + references: ['_delay'], + subscripts: ['_dima'] + }), + v('_level5[DimA]', 'INTEG(input a[DimA]-d5[DimA],init a[DimA]*delay a[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_a', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level5', + referencedFunctionNames: ['__integ'], + references: ['_input_a[_a1]', '_input_a[_a2]', '_input_a[_a3]', '_d5'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux5[DimA]', 'delay a[DimA]', { + includeInOutput: false, + refId: '__aux5', + references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_level_d6_1[a2]', 'INTEG(input 2[a2]-d6[a2],init 2[a2]*delay 2)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d6_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '_d6[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_aux6[a2]', 'delay 2', { + includeInOutput: false, + refId: '__aux6[_a2]', + references: ['_delay_2'], + subscripts: ['_a2'] + }), + v('_level_d6_1[a3]', 'INTEG(input 2[a3]-d6[a3],init 2[a3]*delay 2)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d6_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '_d6[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_aux7[a3]', 'delay 2', { + includeInOutput: false, + refId: '__aux7[_a3]', + references: ['_delay_2'], + subscripts: ['_a3'] + }), + v('_level8', 'INTEG(_aux9-_aux10,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level8', + referencedFunctionNames: ['__integ'], + references: ['__aux9', '__aux10'], + varType: 'level' + }), + v('_level7', 'INTEG(_aux8-_aux9,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level7', + referencedFunctionNames: ['__integ'], + references: ['__aux8', '__aux9'], + varType: 'level' + }), + v('_level6', 'INTEG(input-_aux8,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level6', + referencedFunctionNames: ['__integ'], + references: ['_input', '__aux8'], + varType: 'level' + }), + v('_aux8', '_level6/((delay)/3)', { + includeInOutput: false, + refId: '__aux8', + references: ['__level6', '_delay'] + }), + v('_aux9', '_level7/((delay)/3)', { + includeInOutput: false, + refId: '__aux9', + references: ['__level7', '_delay'] + }), + v('_aux10', '_level8/((delay)/3)', { + includeInOutput: false, + refId: '__aux10', + references: ['__level8', '_delay'] + }), + v('_aux11', '((delay)/3)', { + includeInOutput: false, + refId: '__aux11', + references: ['_delay'] + }), + v('_level11[DimA]', 'INTEG(_aux13[DimA]-_aux14[DimA],input*((delay a[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level11', + referencedFunctionNames: ['__integ'], + references: ['__aux13', '__aux14'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level10[DimA]', 'INTEG(_aux12[DimA]-_aux13[DimA],input*((delay a[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level10', + referencedFunctionNames: ['__integ'], + references: ['__aux12', '__aux13'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level9[DimA]', 'INTEG(input-_aux12[DimA],input*((delay a[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level9', + referencedFunctionNames: ['__integ'], + references: ['_input', '__aux12'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux12[DimA]', '_level9[DimA]/((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux12', + references: ['__level9', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_aux13[DimA]', '_level10[DimA]/((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux13', + references: ['__level10', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_aux14[DimA]', '_level11[DimA]/((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux14', + references: ['__level11', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_aux15[DimA]', '((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux15', + references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_level_d9_3[a2]', 'INTEG(_aux_d9_2[a2]-_aux_d9_3[a2],init 2[a2]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d9_3[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d9_2[_a2]', '__aux_d9_3[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_d9_2[a2]', 'INTEG(_aux_d9_1[a2]-_aux_d9_2[a2],init 2[a2]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d9_2[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d9_1[_a2]', '__aux_d9_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_d9_1[a2]', 'INTEG(input 2[a2]-_aux_d9_1[a2],init 2[a2]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d9_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '__aux_d9_1[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_aux_d9_1[a2]', '_level_d9_1[a2]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_1[_a2]', + references: ['__level_d9_1[_a2]', '_delay_2'], + subscripts: ['_a2'] + }), + v('_aux_d9_2[a2]', '_level_d9_2[a2]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_2[_a2]', + references: ['__level_d9_2[_a2]', '_delay_2'], + subscripts: ['_a2'] + }), + v('_aux_d9_3[a2]', '_level_d9_3[a2]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_3[_a2]', + references: ['__level_d9_3[_a2]', '_delay_2'], + subscripts: ['_a2'] + }), + v('_aux_d9_4[a2]', '((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_4[_a2]', + references: ['_delay_2'], + subscripts: ['_a2'] + }), + v('_level_d9_3[a3]', 'INTEG(_aux_d9_2[a3]-_aux_d9_3[a3],init 2[a3]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d9_3[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d9_2[_a3]', '__aux_d9_3[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_d9_2[a3]', 'INTEG(_aux_d9_1[a3]-_aux_d9_2[a3],init 2[a3]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d9_2[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d9_1[_a3]', '__aux_d9_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_d9_1[a3]', 'INTEG(input 2[a3]-_aux_d9_1[a3],init 2[a3]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d9_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '__aux_d9_1[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_aux_d9_1[a3]', '_level_d9_1[a3]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_1[_a3]', + references: ['__level_d9_1[_a3]', '_delay_2'], + subscripts: ['_a3'] + }), + v('_aux_d9_2[a3]', '_level_d9_2[a3]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_2[_a3]', + references: ['__level_d9_2[_a3]', '_delay_2'], + subscripts: ['_a3'] + }), + v('_aux_d9_3[a3]', '_level_d9_3[a3]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_3[_a3]', + references: ['__level_d9_3[_a3]', '_delay_2'], + subscripts: ['_a3'] + }), + v('_aux_d9_4[a3]', '((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d9_4[_a3]', + references: ['_delay_2'], + subscripts: ['_a3'] + }), + v('_level14', 'INTEG(_aux17-_aux18,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level14', + referencedFunctionNames: ['__integ'], + references: ['__aux17', '__aux18'], + varType: 'level' + }), + v('_level13', 'INTEG(_aux16-_aux17,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level13', + referencedFunctionNames: ['__integ'], + references: ['__aux16', '__aux17'], + varType: 'level' + }), + v('_level12', 'INTEG(input-_aux16,input*((delay)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay'], + refId: '__level12', + referencedFunctionNames: ['__integ'], + references: ['_input', '__aux16'], + varType: 'level' + }), + v('_aux16', '_level12/((delay)/3)', { + includeInOutput: false, + refId: '__aux16', + references: ['__level12', '_delay'] + }), + v('_aux17', '_level13/((delay)/3)', { + includeInOutput: false, + refId: '__aux17', + references: ['__level13', '_delay'] + }), + v('_aux18', '_level14/((delay)/3)', { + includeInOutput: false, + refId: '__aux18', + references: ['__level14', '_delay'] + }), + v('_aux19', '((delay)/3)', { + includeInOutput: false, + refId: '__aux19', + references: ['_delay'] + }), + v('_level17[DimA]', 'INTEG(_aux21[DimA]-_aux22[DimA],input*((delay a[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level17', + referencedFunctionNames: ['__integ'], + references: ['__aux21', '__aux22'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level16[DimA]', 'INTEG(_aux20[DimA]-_aux21[DimA],input*((delay a[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level16', + referencedFunctionNames: ['__integ'], + references: ['__aux20', '__aux21'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level15[DimA]', 'INTEG(input-_aux20[DimA],input*((delay a[DimA])/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + refId: '__level15', + referencedFunctionNames: ['__integ'], + references: ['_input', '__aux20'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_aux20[DimA]', '_level15[DimA]/((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux20', + references: ['__level15', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_aux21[DimA]', '_level16[DimA]/((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux21', + references: ['__level16', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_aux22[DimA]', '_level17[DimA]/((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux22', + references: ['__level17', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_aux23[DimA]', '((delay a[DimA])/3)', { + includeInOutput: false, + refId: '__aux23', + references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], + subscripts: ['_dima'] + }), + v('_level_d12_3[a2]', 'INTEG(_aux_d12_2[a2]-_aux_d12_3[a2],init 2[a2]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d12_3[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d12_2[_a2]', '__aux_d12_3[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_d12_2[a2]', 'INTEG(_aux_d12_1[a2]-_aux_d12_2[a2],init 2[a2]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d12_2[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d12_1[_a2]', '__aux_d12_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_d12_1[a2]', 'INTEG(input 2[a2]-_aux_d12_1[a2],init 2[a2]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a2]', '_delay_2'], + refId: '__level_d12_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '__aux_d12_1[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_aux_d12_1[a2]', '_level_d12_1[a2]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_1[_a2]', + references: ['__level_d12_1[_a2]', '_delay_2'], + subscripts: ['_a2'] + }), + v('_aux_d12_2[a2]', '_level_d12_2[a2]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_2[_a2]', + references: ['__level_d12_2[_a2]', '_delay_2'], + subscripts: ['_a2'] + }), + v('_aux_d12_3[a2]', '_level_d12_3[a2]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_3[_a2]', + references: ['__level_d12_3[_a2]', '_delay_2'], + subscripts: ['_a2'] + }), + v('_aux_d12_4[a2]', '((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_4[_a2]', + references: ['_delay_2'], + subscripts: ['_a2'] + }), + v('_level_d12_3[a3]', 'INTEG(_aux_d12_2[a3]-_aux_d12_3[a3],init 2[a3]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d12_3[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d12_2[_a3]', '__aux_d12_3[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_d12_2[a3]', 'INTEG(_aux_d12_1[a3]-_aux_d12_2[a3],init 2[a3]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d12_2[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__aux_d12_1[_a3]', '__aux_d12_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_d12_1[a3]', 'INTEG(input 2[a3]-_aux_d12_1[a3],init 2[a3]*((delay 2)/3))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_2[_a3]', '_delay_2'], + refId: '__level_d12_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '__aux_d12_1[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_aux_d12_1[a3]', '_level_d12_1[a3]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_1[_a3]', + references: ['__level_d12_1[_a3]', '_delay_2'], + subscripts: ['_a3'] + }), + v('_aux_d12_2[a3]', '_level_d12_2[a3]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_2[_a3]', + references: ['__level_d12_2[_a3]', '_delay_2'], + subscripts: ['_a3'] + }), + v('_aux_d12_3[a3]', '_level_d12_3[a3]/((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_3[_a3]', + references: ['__level_d12_3[_a3]', '_delay_2'], + subscripts: ['_a3'] + }), + v('_aux_d12_4[a3]', '((delay 2)/3)', { + includeInOutput: false, + refId: '__aux_d12_4[_a3]', + references: ['_delay_2'], + subscripts: ['_a3'] + }) + ]) + }) + + it('should work for XMILE "delayfixed" model', () => { + const vars = readSubscriptsAndEquations('delayfixed') + expect(vars).toEqual([ + v('receiving', 'DELAY FIXED(shipping,shipping time,shipping)', { + fixedDelayVarName: '__fixed_delay1', + hasInitValue: true, + initReferences: ['_shipping_time', '_shipping'], + refId: '_receiving', + referencedFunctionNames: ['__delay_fixed'], + references: ['_shipping'], + varSubtype: 'fixedDelay', + varType: 'level' + }), + v('shipping', 'STEP(reference shipping rate,10)-STEP(reference shipping rate,20)', { + refId: '_shipping', + referencedFunctionNames: ['__step'], + references: ['_reference_shipping_rate'] + }), + v('shipping time', '20', { + refId: '_shipping_time', + varType: 'const' + }), + v('reference shipping rate', '1', { + refId: '_reference_shipping_rate', + varType: 'const' + }), + v('shipments in transit', 'INTEG(shipping-receiving,shipping*shipping time)', { + hasInitValue: true, + initReferences: ['_shipping', '_shipping_time'], + refId: '_shipments_in_transit', + referencedFunctionNames: ['__integ'], + references: ['_shipping', '_receiving'], + varType: 'level' + }), + v('input[A1]', '10*TIME', { + refId: '_input[_a1]', + references: ['_time'], + subscripts: ['_a1'] + }), + v('input[A2]', '20*TIME', { + refId: '_input[_a2]', + references: ['_time'], + subscripts: ['_a2'] + }), + v('input[A3]', '30*TIME', { + refId: '_input[_a3]', + references: ['_time'], + subscripts: ['_a3'] + }), + v('output[DimA]', 'DELAY FIXED(input[DimA],1,0)', { + fixedDelayVarName: '__fixed_delay2', + hasInitValue: true, + refId: '_output', + referencedFunctionNames: ['__delay_fixed'], + references: ['_input[_a1]', '_input[_a2]', '_input[_a3]'], + subscripts: ['_dima'], + varSubtype: 'fixedDelay', + varType: 'level' + }), + v('a delay time', '0', { + refId: '_a_delay_time', + varType: 'const' + }), + v('a', 'DELAY FIXED(input[A1]+1,a delay time,0)', { + fixedDelayVarName: '__fixed_delay3', + hasInitValue: true, + initReferences: ['_a_delay_time'], + refId: '_a', + referencedFunctionNames: ['__delay_fixed'], + references: ['_input[_a1]'], + varSubtype: 'fixedDelay', + varType: 'level' + }), + v('b delay time', '1', { + refId: '_b_delay_time', + varType: 'const' + }), + v('b', 'DELAY FIXED(input[A1]+1,b delay time,0)', { + fixedDelayVarName: '__fixed_delay4', + hasInitValue: true, + initReferences: ['_b_delay_time'], + refId: '_b', + referencedFunctionNames: ['__delay_fixed'], + references: ['_input[_a1]'], + varSubtype: 'fixedDelay', + varType: 'level' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '50', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "delayfixed2" model', () => { + const vars = readSubscriptsAndEquations('delayfixed2') + expect(vars).toEqual([ + v('input1', '10*TIME+10', { + refId: '_input1', + references: ['_time'] + }), + v('output1', 'DELAY FIXED(input1,1,0)', { + fixedDelayVarName: '__fixed_delay1', + hasInitValue: true, + refId: '_output1', + referencedFunctionNames: ['__delay_fixed'], + references: ['_input1'], + varSubtype: 'fixedDelay', + varType: 'level' + }), + v('input2', '10*TIME+10', { + refId: '_input2', + references: ['_time'] + }), + v('output2', 'DELAY FIXED(input2,5,0)', { + fixedDelayVarName: '__fixed_delay2', + hasInitValue: true, + refId: '_output2', + referencedFunctionNames: ['__delay_fixed'], + references: ['_input2'], + varSubtype: 'fixedDelay', + varType: 'level' + }), + v('INITIAL TIME', '10', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '20', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "depreciate" model', () => { + const vars = readSubscriptsAndEquations('depreciate') + expect(vars).toEqual([ + v('dtime', '20', { + refId: '_dtime', + varType: 'const' + }), + v('Capacity Cost', '1e+06', { + refId: '_capacity_cost', + varType: 'const' + }), + v('New Capacity', 'IF THEN ELSE(Time=2022,1000,IF THEN ELSE(Time=2026,2500,0))', { + refId: '_new_capacity', + references: ['_time'] + }), + v('str', 'Capacity Cost*New Capacity', { + refId: '_str', + references: ['_capacity_cost', '_new_capacity'] + }), + v('Depreciated Amount', 'DEPRECIATE STRAIGHTLINE(str,dtime,1,0)', { + depreciationVarName: '__depreciation1', + hasInitValue: true, + initReferences: ['_dtime'], + refId: '_depreciated_amount', + referencedFunctionNames: ['__depreciate_straightline'], + references: ['_str'], + varSubtype: 'depreciation' + }), + v('FINAL TIME', '2050', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '2020', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "directconst" model', () => { + const vars = readSubscriptsAndEquations('directconst') + expect(vars).toEqual([ + v('a', "GET DIRECT CONSTANTS('data/a.csv',',','B2')", { + directConstArgs: { file: 'data/a.csv', tab: ',', startCell: 'B2' }, + refId: '_a', + varType: 'const' + }), + v('a from named xlsx', "GET DIRECT CONSTANTS('data/a.xlsx','a','B2')", { + directConstArgs: { file: 'data/a.xlsx', tab: 'a', startCell: 'B2' }, + refId: '_a_from_named_xlsx', + varType: 'const' + }), + v('a from tagged xlsx', "GET DIRECT CONSTANTS('?a','a','B2')", { + directConstArgs: { file: '?a', tab: 'a', startCell: 'B2' }, + refId: '_a_from_tagged_xlsx', + varType: 'const' + }), + v('b[DimB]', "GET DIRECT CONSTANTS('data/b.csv',',','b2*')", { + directConstArgs: { file: 'data/b.csv', tab: ',', startCell: 'b2*' }, + refId: '_b', + subscripts: ['_dimb'], + varType: 'const' + }), + v('c[DimB,DimC]', "GET DIRECT CONSTANTS('data/c.csv',',','B2')", { + directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2' }, + refId: '_c', + subscripts: ['_dimb', '_dimc'], + varType: 'const' + }), + v('d[D1,DimB,DimC]', "GET DIRECT CONSTANTS('data/c.csv',',','B2')", { + directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2' }, + refId: '_d', + subscripts: ['_d1', '_dimb', '_dimc'], + varType: 'const' + }), + v('e[DimC,DimB]', "GET DIRECT CONSTANTS('data/c.csv',',','B2*')", { + directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2*' }, + refId: '_e', + subscripts: ['_dimc', '_dimb'], + varType: 'const' + }), + v('f[DimC,SubA]', "GET DIRECT CONSTANTS('data/f.csv',',','B2')", { + directConstArgs: { file: 'data/f.csv', tab: ',', startCell: 'B2' }, + refId: '_f[_dimc,_a2]', + separationDims: ['_suba'], + subscripts: ['_dimc', '_a2'], + varType: 'const' + }), + v('f[DimC,SubA]', "GET DIRECT CONSTANTS('data/f.csv',',','B2')", { + directConstArgs: { file: 'data/f.csv', tab: ',', startCell: 'B2' }, + refId: '_f[_dimc,_a3]', + separationDims: ['_suba'], + subscripts: ['_dimc', '_a3'], + varType: 'const' + }), + v('f[DimC,DimA]:EXCEPT:[DimC,SubA]', '0', { + refId: '_f[_dimc,_a1]', + separationDims: ['_dima'], + subscripts: ['_dimc', '_a1'], + varType: 'const' + }), + v('g[From DimC,To DimC]', "GET DIRECT CONSTANTS('data/g.csv',',','B2')", { + directConstArgs: { file: 'data/g.csv', tab: ',', startCell: 'B2' }, + refId: '_g', + subscripts: ['_from_dimc', '_to_dimc'], + varType: 'const' + }), + v('h', "GET DIRECT CONSTANTS('data/h.csv',',','B5')", { + directConstArgs: { file: 'data/h.csv', tab: ',', startCell: 'B5' }, + refId: '_h', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "directdata" model', () => { + const vars = readSubscriptsAndEquations('directdata') + expect(vars).toEqual([ + v('a[DimA]', "GET DIRECT DATA('data.xlsx','A Data','A','B2')", { + directDataArgs: { file: 'data.xlsx', tab: 'A Data', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('a[DimA]', "GET DIRECT DATA('data.xlsx','A Data','A','B2')", { + directDataArgs: { file: 'data.xlsx', tab: 'A Data', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('b[DimA]', 'a[DimA]*10', { + refId: '_b', + references: ['_a[_a1]', '_a[_a2]'], + subscripts: ['_dima'] + }), + v('c', "GET DIRECT DATA('?data','C Data','a','b2')", { + directDataArgs: { file: '?data', tab: 'C Data', timeRowOrCol: 'a', startCell: 'b2' }, + refId: '_c', + varType: 'data' + }), + v('d', 'c*10', { + refId: '_d', + references: ['_c'] + }), + v('e[DimA]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_e[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('e[DimA]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_e[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('f[DimA]', 'e[DimA]*10', { + refId: '_f', + references: ['_e[_a1]', '_e[_a2]'], + subscripts: ['_dima'] + }), + v('g', "GET DIRECT DATA('g_data.csv',',','A','B2')", { + directDataArgs: { file: 'g_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_g', + varType: 'data' + }), + v('h', 'g*10', { + refId: '_h', + references: ['_g'] + }), + v('i[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_i[_a1,_b1]', + separationDims: ['_dimb'], + subscripts: ['_a1', '_b1'], + varType: 'data' + }), + v('i[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_i[_a1,_b2]', + separationDims: ['_dimb'], + subscripts: ['_a1', '_b2'], + varType: 'data' + }), + v('j[A1,DimB]', 'i[A1,DimB]', { + refId: '_j', + references: ['_i[_a1,_b1]', '_i[_a1,_b2]'], + subscripts: ['_a1', '_dimb'] + }), + v('k[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_k[_a1,_b1]', + separationDims: ['_dimb'], + subscripts: ['_a1', '_b1'], + varType: 'data' + }), + v('k[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_k[_a1,_b2]', + separationDims: ['_dimb'], + subscripts: ['_a1', '_b2'], + varType: 'data' + }), + v('k[A2,DimB]', '0', { + refId: '_k[_a2,_dimb]', + subscripts: ['_a2', '_dimb'], + varType: 'const' + }), + v('l[DimA,DimB]', 'k[DimA,DimB]', { + refId: '_l', + references: ['_k[_a1,_b1]', '_k[_a1,_b2]', '_k[_a2,_dimb]'], + subscripts: ['_dima', '_dimb'] + }), + v('m[DimM]', "GET DIRECT DATA('m.csv',',','1','B2')", { + directDataArgs: { file: 'm.csv', tab: ',', timeRowOrCol: '1', startCell: 'B2' }, + refId: '_m[_m1]', + separationDims: ['_dimm'], + subscripts: ['_m1'], + varType: 'data' + }), + v('m[DimM]', "GET DIRECT DATA('m.csv',',','1','B2')", { + directDataArgs: { file: 'm.csv', tab: ',', timeRowOrCol: '1', startCell: 'B2' }, + refId: '_m[_m2]', + separationDims: ['_dimm'], + subscripts: ['_m2'], + varType: 'data' + }), + v('m[DimM]', "GET DIRECT DATA('m.csv',',','1','B2')", { + directDataArgs: { file: 'm.csv', tab: ',', timeRowOrCol: '1', startCell: 'B2' }, + refId: '_m[_m3]', + separationDims: ['_dimm'], + subscripts: ['_m3'], + varType: 'data' + }), + v('n', 'm[M2]', { + refId: '_n', + references: ['_m[_m2]'] + }), + v('o[DimM]', "GET DIRECT DATA('mt.csv',',','A','B2')", { + directDataArgs: { file: 'mt.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_o[_m1]', + separationDims: ['_dimm'], + subscripts: ['_m1'], + varType: 'data' + }), + v('o[DimM]', "GET DIRECT DATA('mt.csv',',','A','B2')", { + directDataArgs: { file: 'mt.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_o[_m2]', + separationDims: ['_dimm'], + subscripts: ['_m2'], + varType: 'data' + }), + v('o[DimM]', "GET DIRECT DATA('mt.csv',',','A','B2')", { + directDataArgs: { file: 'mt.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_o[_m3]', + separationDims: ['_dimm'], + subscripts: ['_m3'], + varType: 'data' + }), + v('p', 'o[M2]', { + refId: '_p', + references: ['_o[_m2]'] + }), + v('q[SubM]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_q[_m2]', + separationDims: ['_subm'], + subscripts: ['_m2'], + varType: 'data' + }), + v('q[SubM]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { + directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, + refId: '_q[_m3]', + separationDims: ['_subm'], + subscripts: ['_m3'], + varType: 'data' + }), + v('r', 'q[M3]', { + refId: '_r', + references: ['_q[_m3]'] + }), + v('INITIAL TIME', '1990', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '2050', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "directlookups" model', () => { + const vars = readSubscriptsAndEquations('directlookups') + expect(vars).toEqual([ + v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { + directDataArgs: { file: 'lookups.CSV', tab: ',', timeRowOrCol: '1', startCell: 'e2' }, + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { + directDataArgs: { file: 'lookups.CSV', tab: ',', timeRowOrCol: '1', startCell: 'e2' }, + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { + directDataArgs: { file: 'lookups.CSV', tab: ',', timeRowOrCol: '1', startCell: 'e2' }, + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('a from named xlsx[DimA]', "GET DIRECT LOOKUPS('lookups.xlsx','a','1','E2')", { + directDataArgs: { file: 'lookups.xlsx', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, + refId: '_a_from_named_xlsx[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('a from named xlsx[DimA]', "GET DIRECT LOOKUPS('lookups.xlsx','a','1','E2')", { + directDataArgs: { file: 'lookups.xlsx', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, + refId: '_a_from_named_xlsx[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('a from named xlsx[DimA]', "GET DIRECT LOOKUPS('lookups.xlsx','a','1','E2')", { + directDataArgs: { file: 'lookups.xlsx', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, + refId: '_a_from_named_xlsx[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('a from tagged xlsx[DimA]', "GET DIRECT LOOKUPS('?lookups','a','1','E2')", { + directDataArgs: { file: '?lookups', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, + refId: '_a_from_tagged_xlsx[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'data' + }), + v('a from tagged xlsx[DimA]', "GET DIRECT LOOKUPS('?lookups','a','1','E2')", { + directDataArgs: { file: '?lookups', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, + refId: '_a_from_tagged_xlsx[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'data' + }), + v('a from tagged xlsx[DimA]', "GET DIRECT LOOKUPS('?lookups','a','1','E2')", { + directDataArgs: { file: '?lookups', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, + refId: '_a_from_tagged_xlsx[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'data' + }), + v('b', 'a[A1](Time)', { + refId: '_b', + referencedLookupVarNames: ['_a'], + references: ['_time'] + }), + v('b from named xlsx', 'a from named xlsx[A1](Time)', { + refId: '_b_from_named_xlsx', + referencedLookupVarNames: ['_a_from_named_xlsx'], + references: ['_time'] + }), + v('b from tagged xlsx', 'a from tagged xlsx[A1](Time)', { + refId: '_b_from_tagged_xlsx', + referencedLookupVarNames: ['_a_from_tagged_xlsx'], + references: ['_time'] + }), + v('c', 'LOOKUP INVERT(a[A1],0.5)', { + refId: '_c', + referencedFunctionNames: ['__lookup_invert'], + references: ['_a[_a1]'] + }), + v('d', 'LOOKUP FORWARD(a[A1],2028.1)', { + refId: '_d', + referencedFunctionNames: ['__lookup_forward'], + references: ['_a[_a1]'] + }), + v('e', 'LOOKUP FORWARD(a[A1],2028)', { + refId: '_e', + referencedFunctionNames: ['__lookup_forward'], + references: ['_a[_a1]'] + }), + v('f', 'a[A1](2028.1)', { + refId: '_f', + referencedLookupVarNames: ['_a'] + }), + v('g', '', { + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + refId: '_g', + varType: 'lookup' + }), + v('h', 'LOOKUP FORWARD(g,1)', { + refId: '_h', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('INITIAL TIME', '2020', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '2050', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "directsubs" model', () => { + const vars = readSubscriptsAndEquations('directsubs') + expect(vars).toEqual([ + v('a[DimA]', '10,20,30', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '10,20,30', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a[DimA]', '10,20,30', { + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b[DimB]', '1,2,3', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '1,2,3', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + v('b[DimB]', '1,2,3', { + refId: '_b[_b3]', + separationDims: ['_dimb'], + subscripts: ['_b3'], + varType: 'const' + }), + v('c[DimC]', 'a[DimA]+1', { + refId: '_c', + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'], + subscripts: ['_dimc'] + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "elmcount" model', () => { + const vars = readSubscriptsAndEquations('elmcount') + expect(vars).toEqual([ + v('a', 'ELMCOUNT(DimA)', { + refId: '_a', + referencedFunctionNames: ['__elmcount'] + }), + v('b[DimA]', '10*ELMCOUNT(DimA)+a', { + refId: '_b', + referencedFunctionNames: ['__elmcount'], + references: ['_a'], + subscripts: ['_dima'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "except" model', () => { + const vars = readSubscriptsAndEquations('except') + expect(vars).toEqual([ + v('a[DimA]', '1', { + refId: '_a', + subscripts: ['_dima'], + varType: 'const' + }), + v('b[SubA]', '2', { + refId: '_b[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('b[SubA]', '2', { + refId: '_b[_a3]', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('c[DimA,DimC]', '3', { + refId: '_c', + subscripts: ['_dima', '_dimc'], + varType: 'const' + }), + v('d[SubA,C1]', '4', { + refId: '_d[_a2,_c1]', + separationDims: ['_suba'], + subscripts: ['_a2', '_c1'], + varType: 'const' + }), + v('d[SubA,C1]', '4', { + refId: '_d[_a3,_c1]', + separationDims: ['_suba'], + subscripts: ['_a3', '_c1'], + varType: 'const' + }), + v('e[DimA,SubC]', '5', { + refId: '_e[_dima,_c2]', + separationDims: ['_subc'], + subscripts: ['_dima', '_c2'], + varType: 'const' + }), + v('e[DimA,SubC]', '5', { + refId: '_e[_dima,_c3]', + separationDims: ['_subc'], + subscripts: ['_dima', '_c3'], + varType: 'const' + }), + v('f[A1,C1]', '6', { + refId: '_f', + subscripts: ['_a1', '_c1'], + varType: 'const' + }), + v('g[DimA]:EXCEPT:[A1]', '7', { + refId: '_g[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('g[DimA]:EXCEPT:[A1]', '7', { + refId: '_g[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('h[DimA]:EXCEPT:[SubA]', '8', { + refId: '_h', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('j[DimD]', '10,20', { + refId: '_j[_d1]', + separationDims: ['_dimd'], + subscripts: ['_d1'], + varType: 'const' + }), + v('j[DimD]', '10,20', { + refId: '_j[_d2]', + separationDims: ['_dimd'], + subscripts: ['_d2'], + varType: 'const' + }), + v('k[DimA]:EXCEPT:[A1]', 'a[DimA]+j[DimD]', { + refId: '_k[_a2]', + references: ['_a', '_j[_d1]'], + separationDims: ['_dima'], + subscripts: ['_a2'] + }), + v('k[DimA]:EXCEPT:[A1]', 'a[DimA]+j[DimD]', { + refId: '_k[_a3]', + references: ['_a', '_j[_d1]'], + separationDims: ['_dima'], + subscripts: ['_a3'] + }), + v('o[SubA]:EXCEPT:[SubA2]', '9', { + refId: '_o', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a1,_c2]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_c2'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a1,_c3]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_c3'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a2,_c1]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_c1'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a2,_c2]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_c2'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a2,_c3]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_c3'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a3,_c1]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a3', '_c1'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a3,_c2]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a3', '_c2'], + varType: 'const' + }), + v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { + refId: '_p[_a3,_c3]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a3', '_c3'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a1,_c1]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_c1'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a1,_c2]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_c2'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a1,_c3]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a1', '_c3'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a2,_c1]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_c1'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a2,_c3]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a2', '_c3'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a3,_c1]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a3', '_c1'], + varType: 'const' + }), + v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { + refId: '_q[_a3,_c3]', + separationDims: ['_dima', '_dimc'], + subscripts: ['_a3', '_c3'], + varType: 'const' + }), + v('r[DimA,DimC]:EXCEPT:[DimA,C1]', '12', { + refId: '_r[_dima,_c2]', + separationDims: ['_dimc'], + subscripts: ['_dima', '_c2'], + varType: 'const' + }), + v('r[DimA,DimC]:EXCEPT:[DimA,C1]', '12', { + refId: '_r[_dima,_c3]', + separationDims: ['_dimc'], + subscripts: ['_dima', '_c3'], + varType: 'const' + }), + v('s[A3]', '13', { + refId: '_s[_a3]', + subscripts: ['_a3'], + varType: 'const' + }), + v('s[SubA]:EXCEPT:[A3]', '14', { + refId: '_s[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('t[SubA,SubC]', '15', { + refId: '_t[_a2,_c2]', + separationDims: ['_suba', '_subc'], + subscripts: ['_a2', '_c2'], + varType: 'const' + }), + v('t[SubA,SubC]', '15', { + refId: '_t[_a2,_c3]', + separationDims: ['_suba', '_subc'], + subscripts: ['_a2', '_c3'], + varType: 'const' + }), + v('t[SubA,SubC]', '15', { + refId: '_t[_a3,_c2]', + separationDims: ['_suba', '_subc'], + subscripts: ['_a3', '_c2'], + varType: 'const' + }), + v('t[SubA,SubC]', '15', { + refId: '_t[_a3,_c3]', + separationDims: ['_suba', '_subc'], + subscripts: ['_a3', '_c3'], + varType: 'const' + }), + v('u[DimA]:EXCEPT:[A1]', 'a[DimA]', { + refId: '_u[_a2]', + references: ['_a'], + separationDims: ['_dima'], + subscripts: ['_a2'] + }), + v('u[DimA]:EXCEPT:[A1]', 'a[DimA]', { + refId: '_u[_a3]', + references: ['_a'], + separationDims: ['_dima'], + subscripts: ['_a3'] + }), + v('v[SubA]:EXCEPT:[A1]', 'a[SubA]', { + refId: '_v[_a2]', + references: ['_a'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('v[SubA]:EXCEPT:[A1]', 'a[SubA]', { + refId: '_v[_a3]', + references: ['_a'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('w[DimA]:EXCEPT:[SubA]', 'a[DimA]', { + refId: '_w', + references: ['_a'], + separationDims: ['_dima'], + subscripts: ['_a1'] + }), + v('x[DimA]:EXCEPT:[SubA]', 'c[DimA,C1]', { + refId: '_x', + references: ['_c'], + separationDims: ['_dima'], + subscripts: ['_a1'] + }), + v('y[SubA,SubC]:EXCEPT:[A3,C3]', 'c[SubA,SubC]', { + refId: '_y[_a2,_c2]', + references: ['_c'], + separationDims: ['_suba', '_subc'], + subscripts: ['_a2', '_c2'] + }), + v('y[SubA,SubC]:EXCEPT:[A3,C3]', 'c[SubA,SubC]', { + refId: '_y[_a2,_c3]', + references: ['_c'], + separationDims: ['_suba', '_subc'], + subscripts: ['_a2', '_c3'] + }), + v('y[SubA,SubC]:EXCEPT:[A3,C3]', 'c[SubA,SubC]', { + refId: '_y[_a3,_c2]', + references: ['_c'], + separationDims: ['_suba', '_subc'], + subscripts: ['_a3', '_c2'] + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e1,_f1,_g1]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e1', '_f1', '_g1'], + varType: 'const' + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e1,_f1,_g2]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e1', '_f1', '_g2'], + varType: 'const' + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e1,_f2,_g1]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e1', '_f2', '_g1'], + varType: 'const' + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e1,_f2,_g2]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e1', '_f2', '_g2'], + varType: 'const' + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e2,_f1,_g1]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e2', '_f1', '_g1'], + varType: 'const' + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e2,_f1,_g2]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e2', '_f1', '_g2'], + varType: 'const' + }), + v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { + refId: '_except3[_e2,_f2,_g1]', + separationDims: ['_dime', '_dimf', '_dimg'], + subscripts: ['_e2', '_f2', '_g1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f1,_g1,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f1', '_g1', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f1,_g1,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f1', '_g1', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f1,_g2,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f1', '_g2', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f1,_g2,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f1', '_g2', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f2,_g1,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f2', '_g1', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f2,_g1,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f2', '_g1', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f2,_g2,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f2', '_g2', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e1,_f2,_g2,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e1', '_f2', '_g2', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f1,_g1,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f1', '_g1', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f1,_g1,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f1', '_g1', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f1,_g2,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f1', '_g2', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f1,_g2,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f1', '_g2', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f2,_g1,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f2', '_g1', '_h1'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f2,_g1,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f2', '_g1', '_h2'], + varType: 'const' + }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f2,_g2,_h1]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f2', '_g2', '_h1'], + varType: 'const' + }), + v('input', '0', { + refId: '_input', + varType: 'const' + }), + v('z ref a', '25', { + refId: '_z_ref_a', + varType: 'const' + }), + v('z ref b', '5', { + refId: '_z_ref_b', + varType: 'const' + }), + v('z[SubA]', 'z ref a*z ref b', { + refId: '_z[_a2]', + references: ['_z_ref_a', '_z_ref_b'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('z[SubA]', 'z ref a*z ref b', { + refId: '_z[_a3]', + references: ['_z_ref_a', '_z_ref_b'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('z[DimA]:EXCEPT:[SubA]', '10', { + refId: '_z[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('z total', 'SUM(z[SubA!])', { + refId: '_z_total', + referencedFunctionNames: ['__sum'], + references: ['_z[_a2]', '_z[_a3]'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('SAVEPER', '1', { + refId: '_saveper', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + // it('should work for XMILE "except2" model', () => { + // const vars = readSubscriptsAndEquations('except2') + // logPrettyVars(vars) + // expect(vars).toEqual([]) + // }) + + it('should work for XMILE "extdata" model', () => { + const vars = readSubscriptsAndEquations('extdata') + expect(vars).toEqual([ + v('Simple 1', '', { + refId: '_simple_1', + varType: 'data' + }), + v('Simple 2', '', { + refId: '_simple_2', + varType: 'data' + }), + v('A Values[DimA]', '', { + refId: '_a_values', + subscripts: ['_dima'], + varType: 'data' + }), + v('BC Values[DimB,DimC]', '', { + refId: '_bc_values', + subscripts: ['_dimb', '_dimc'], + varType: 'data' + }), + v('D Values[DimD]', '', { + refId: '_d_values', + subscripts: ['_dimd'], + varType: 'data' + }), + v('E Values[E1]', '', { + refId: '_e_values[_e1]', + subscripts: ['_e1'], + varType: 'data' + }), + v('E Values[E2]', '', { + refId: '_e_values[_e2]', + subscripts: ['_e2'], + varType: 'data' + }), + v('EBC Values[DimE,DimB,DimC]', '', { + refId: '_ebc_values', + subscripts: ['_dime', '_dimb', '_dimc'], + varType: 'data' + }), + v('Simple Totals', 'Simple 1+Simple 2', { + refId: '_simple_totals', + references: ['_simple_1', '_simple_2'] + }), + v('A Totals', 'SUM(A Values[DimA!])', { + refId: '_a_totals', + referencedFunctionNames: ['__sum'], + references: ['_a_values'] + }), + v('B1 Totals', 'SUM(BC Values[B1,DimC!])', { + refId: '_b1_totals', + referencedFunctionNames: ['__sum'], + references: ['_bc_values'] + }), + v('D Totals', 'SUM(D Values[DimD!])', { + refId: '_d_totals', + referencedFunctionNames: ['__sum'], + references: ['_d_values'] + }), + v('E1 Values', 'E Values[E1]', { + refId: '_e1_values', + references: ['_e_values[_e1]'] + }), + v('E2 Values', 'E Values[E2]', { + refId: '_e2_values', + references: ['_e_values[_e2]'] + }), + v('Chosen E', '2', { + refId: '_chosen_e', + varType: 'const' + }), + v('Chosen B', '3', { + refId: '_chosen_b', + varType: 'const' + }), + v('Chosen C', '1', { + refId: '_chosen_c', + varType: 'const' + }), + v('E Selection[DimE]', 'IF THEN ELSE(DimE=Chosen E,1,0)', { + refId: '_e_selection', + references: ['_chosen_e'], + subscripts: ['_dime'] + }), + v('B Selection[DimB]', 'IF THEN ELSE(DimB=Chosen B,1,0)', { + refId: '_b_selection', + references: ['_chosen_b'], + subscripts: ['_dimb'] + }), + v('C Selection[DimC]', 'IF THEN ELSE(DimC=Chosen C,1,0)', { + refId: '_c_selection', + references: ['_chosen_c'], + subscripts: ['_dimc'] + }), + v( + 'Total EBC for Selected C[DimE,DimB]', + 'VECTOR SELECT(C Selection[DimC!],EBC Values[DimE,DimB,DimC!],0,VSSUM,VSERRATLEASTONE)', + { + refId: '_total_ebc_for_selected_c', + referencedFunctionNames: ['__vector_select'], + references: ['_c_selection', '_ebc_values', '_vssum', '_vserratleastone'], + subscripts: ['_dime', '_dimb'] + } + ), + v( + 'Total EBC for Selected BC[DimE]', + 'VECTOR SELECT(B Selection[DimB!],Total EBC for Selected C[DimE,DimB!],0,VSSUM,VSERRATLEASTONE)', + { + refId: '_total_ebc_for_selected_bc', + referencedFunctionNames: ['__vector_select'], + references: ['_b_selection', '_total_ebc_for_selected_c', '_vssum', '_vserratleastone'], + subscripts: ['_dime'] + } + ), + v('Total EBC', 'VECTOR SELECT(E Selection[DimE!],Total EBC for Selected BC[DimE!],0,VSSUM,VSERRATLEASTONE)', { + refId: '_total_ebc', + referencedFunctionNames: ['__vector_select'], + references: ['_e_selection', '_total_ebc_for_selected_bc', '_vssum', '_vserratleastone'] + }), + v('VSERRATLEASTONE', '1', { + refId: '_vserratleastone', + varType: 'const' + }), + v('VSSUM', '0', { + refId: '_vssum', + varType: 'const' + }), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + // it('should work for XMILE "flatten" model', () => { + // const vars = readSubscriptsAndEquations('flatten') + // logPrettyVars(vars) + // expect(vars).toEqual([]) + // }) + + it('should work for XMILE "gamma_ln" model', () => { + const vars = readSubscriptsAndEquations('gamma_ln') + expect(vars).toEqual([ + v('a', 'GAMMA LN(10)', { + refId: '_a', + referencedFunctionNames: ['__gamma_ln'] + }), + v('b', 'GAMMA LN(0.5)', { + refId: '_b', + referencedFunctionNames: ['__gamma_ln'] + }), + v('c', 'GAMMA LN(1)', { + refId: '_c', + referencedFunctionNames: ['__gamma_ln'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "getdata" model', () => { + const vars = readSubscriptsAndEquations('getdata') + expect(vars).toEqual([ + v('Values[DimA]', '', { + refId: '_values', + subscripts: ['_dima'], + varType: 'data' + }), + v('One year', '1', { + refId: '_one_year', + varType: 'const' + }), + v('Half year', '0.5', { + refId: '_half_year', + varType: 'const' + }), + v('Interpolate', '0', { + refId: '_interpolate', + varType: 'const' + }), + v('Forward', '1', { + refId: '_forward', + varType: 'const' + }), + v('Backward', '-1', { + refId: '_backward', + varType: 'const' + }), + v( + 'Value for A1 at time minus one year interpolate', + 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-One year),Interpolate)', + { + refId: '_value_for_a1_at_time_minus_one_year_interpolate', + referencedFunctionNames: ['__get_data_between_times', '__max'], + references: ['_values', '_initial_time', '_time', '_one_year', '_interpolate'] + } + ), + v( + 'Value for A1 at time minus one year forward', + 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-One year),Forward)', + { + refId: '_value_for_a1_at_time_minus_one_year_forward', + referencedFunctionNames: ['__get_data_between_times', '__max'], + references: ['_values', '_initial_time', '_time', '_one_year', '_forward'] + } + ), + v( + 'Value for A1 at time minus one year backward', + 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-One year),Backward)', + { + refId: '_value_for_a1_at_time_minus_one_year_backward', + referencedFunctionNames: ['__get_data_between_times', '__max'], + references: ['_values', '_initial_time', '_time', '_one_year', '_backward'] + } + ), + v( + 'Value for A1 at time minus half year forward', + 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-Half year),Forward)', + { + refId: '_value_for_a1_at_time_minus_half_year_forward', + referencedFunctionNames: ['__get_data_between_times', '__max'], + references: ['_values', '_initial_time', '_time', '_half_year', '_forward'] + } + ), + v( + 'Value for A1 at time minus half year backward', + 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-Half year),Backward)', + { + refId: '_value_for_a1_at_time_minus_half_year_backward', + referencedFunctionNames: ['__get_data_between_times', '__max'], + references: ['_values', '_initial_time', '_time', '_half_year', '_backward'] + } + ), + v( + 'Value for A1 at time plus half year forward', + 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+Half year),Forward)', + { + refId: '_value_for_a1_at_time_plus_half_year_forward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_half_year', '_forward'] + } + ), + v( + 'Value for A1 at time plus half year backward', + 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+Half year),Backward)', + { + refId: '_value_for_a1_at_time_plus_half_year_backward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_half_year', '_backward'] + } + ), + v( + 'Value for A1 at time plus one year interpolate', + 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Interpolate)', + { + refId: '_value_for_a1_at_time_plus_one_year_interpolate', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_one_year', '_interpolate'] + } + ), + v( + 'Value for A1 at time plus one year forward', + 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Forward)', + { + refId: '_value_for_a1_at_time_plus_one_year_forward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_one_year', '_forward'] + } + ), + v( + 'Value for A1 at time plus one year backward', + 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Backward)', + { + refId: '_value_for_a1_at_time_plus_one_year_backward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_one_year', '_backward'] + } + ), + v( + 'Value at time plus half year forward[DimA]', + 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+Half year),Forward)', + { + refId: '_value_at_time_plus_half_year_forward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_half_year', '_forward'], + subscripts: ['_dima'] + } + ), + v( + 'Value at time plus half year backward[DimA]', + 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+Half year),Backward)', + { + refId: '_value_at_time_plus_half_year_backward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_half_year', '_backward'], + subscripts: ['_dima'] + } + ), + v( + 'Value at time plus one year interpolate[DimA]', + 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Interpolate)', + { + refId: '_value_at_time_plus_one_year_interpolate', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_one_year', '_interpolate'], + subscripts: ['_dima'] + } + ), + v( + 'Value at time plus one year forward[DimA]', + 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Forward)', + { + refId: '_value_at_time_plus_one_year_forward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_one_year', '_forward'], + subscripts: ['_dima'] + } + ), + v( + 'Value at time plus one year backward[DimA]', + 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Backward)', + { + refId: '_value_at_time_plus_one_year_backward', + referencedFunctionNames: ['__get_data_between_times', '__min'], + references: ['_values', '_final_time', '_time', '_one_year', '_backward'], + subscripts: ['_dima'] + } + ), + v( + 'Initial value at time plus one year interpolate[DimA]', + 'INITIAL(GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Interpolate))', + { + hasInitValue: true, + initReferences: ['_values', '_final_time', '_time', '_one_year', '_interpolate'], + refId: '_initial_value_at_time_plus_one_year_interpolate', + referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], + subscripts: ['_dima'], + varType: 'initial' + } + ), + v( + 'Initial value at time plus one year forward[DimA]', + 'INITIAL(GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Forward))', + { + hasInitValue: true, + initReferences: ['_values', '_final_time', '_time', '_one_year', '_forward'], + refId: '_initial_value_at_time_plus_one_year_forward', + referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], + subscripts: ['_dima'], + varType: 'initial' + } + ), + v( + 'Initial value at time plus one year backward[DimA]', + 'INITIAL(GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Backward))', + { + hasInitValue: true, + initReferences: ['_values', '_final_time', '_time', '_one_year', '_backward'], + refId: '_initial_value_at_time_plus_one_year_backward', + referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], + subscripts: ['_dima'], + varType: 'initial' + } + ), + v( + 'Initial value for A1 at time plus one year interpolate', + 'INITIAL(GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Interpolate))', + { + hasInitValue: true, + initReferences: ['_values', '_final_time', '_time', '_one_year', '_interpolate'], + refId: '_initial_value_for_a1_at_time_plus_one_year_interpolate', + referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], + varType: 'initial' + } + ), + v( + 'Initial value for A1 at time plus one year forward', + 'INITIAL(GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Forward))', + { + hasInitValue: true, + initReferences: ['_values', '_final_time', '_time', '_one_year', '_forward'], + refId: '_initial_value_for_a1_at_time_plus_one_year_forward', + referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], + varType: 'initial' + } + ), + v( + 'Initial value for A1 at time plus one year backward', + 'INITIAL(GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Backward))', + { + hasInitValue: true, + initReferences: ['_values', '_final_time', '_time', '_one_year', '_backward'], + refId: '_initial_value_for_a1_at_time_plus_one_year_backward', + referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], + varType: 'initial' + } + ), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "index" model', () => { + const vars = readSubscriptsAndEquations('index') + expect(vars).toEqual([ + v('a[DimA]', 'b[DimA]+10', { + refId: '_a', + references: ['_b[_a1]', '_b[_a2]', '_b[_a3]'], + subscripts: ['_dima'] + }), + v('b[A1]', '1', { + refId: '_b[_a1]', + subscripts: ['_a1'], + varType: 'const' + }), + v('b[A2]', '2', { + refId: '_b[_a2]', + subscripts: ['_a2'], + varType: 'const' + }), + v('b[A3]', '3', { + refId: '_b[_a3]', + subscripts: ['_a3'], + varType: 'const' + }), + v('c[DimA]', 'b[A1]+1', { + refId: '_c', + references: ['_b[_a1]'], + subscripts: ['_dima'] + }), + v('d[DimA]', 'b[A1]+b[DimA]', { + refId: '_d', + references: ['_b[_a1]', '_b[_a2]', '_b[_a3]'], + subscripts: ['_dima'] + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "initial" model', () => { + const vars = readSubscriptsAndEquations('initial') + expect(vars).toEqual([ + v('amplitude', '2', { + refId: '_amplitude', + varType: 'const' + }), + v('Period', '20', { + refId: '_period', + varType: 'const' + }), + v('x', 'amplitude*COS(6.28*Time/Period)', { + refId: '_x', + referencedFunctionNames: ['__cos'], + references: ['_amplitude', '_time', '_period'] + }), + v('relative x', 'x/INITIAL x', { + refId: '_relative_x', + references: ['_x', '_initial_x'] + }), + v('INITIAL x', 'INITIAL(x)', { + hasInitValue: true, + initReferences: ['_x'], + refId: '_initial_x', + referencedFunctionNames: ['__initial'], + varType: 'initial' + }), + v('FINAL TIME', '100', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "interleaved" model', () => { + const vars = readSubscriptsAndEquations('interleaved') + expect(vars).toEqual([ + v('x', '1', { + refId: '_x', + varType: 'const' + }), + v('a[A1]', 'x', { + refId: '_a[_a1]', + references: ['_x'], + subscripts: ['_a1'] + }), + v('a[A2]', 'y', { + refId: '_a[_a2]', + references: ['_y'], + subscripts: ['_a2'] + }), + v('y', 'a[A1]', { + refId: '_y', + references: ['_a[_a1]'] + }), + v('b[DimA]', 'a[DimA]', { + refId: '_b', + references: ['_a[_a1]', '_a[_a2]'], + subscripts: ['_dima'] + }), + v('FINAL TIME', '100', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "longeqns" model', () => { + const vars = readSubscriptsAndEquations('longeqns') + expect(vars).toEqual([ + v('EqnA[DimX,DimY]', '1', { + refId: '_eqna', + subscripts: ['_dimx', '_dimy'], + varType: 'const' + }), + v('EqnB[DimX,DimW]', '1', { + refId: '_eqnb', + subscripts: ['_dimx', '_dimw'], + varType: 'const' + }), + v( + 'EqnC[DimX,DimY,DimZ]', + 'EqnA[DimX,DimY]*(-SUM(EqnB[DimX,DimW\n!])-(SUM(EqnB[DimX,DimW!])-SUM(EqnB[DimX,DimW\n!]))*EqnA[DimX,DimY])', + { + refId: '_eqnc', + referencedFunctionNames: ['__sum'], + references: ['_eqna', '_eqnb'], + subscripts: ['_dimx', '_dimy', '_dimz'] + } + ), + v('Result', 'EqnC[X1,Y1,Z1]', { + refId: '_result', + references: ['_eqnc'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "lookup" model', () => { + const vars = readSubscriptsAndEquations('lookup') + expect(vars).toEqual([ + v('a', '', { + points: [ + [0, 0], + [0.7, 0], + [0.8, 0.1], + [0.9, 0.9], + [1, -1], + [2, 1] + ], + range: [ + [0, 0], + [2, 1.2] + ], + refId: '_a', + varType: 'lookup' + }), + v('b', 'a(i)', { + refId: '_b', + referencedFunctionNames: ['__a'], + references: ['_i'] + }), + v('i', 'Time/10', { + refId: '_i', + references: ['_time'] + }), + v('c[A1]', '', { + points: [ + [0, 10], + [1, 20] + ], + refId: '_c[_a1]', + subscripts: ['_a1'], + varType: 'lookup' + }), + v('c[A2]', '', { + points: [ + [0, 20], + [1, 30] + ], + refId: '_c[_a2]', + subscripts: ['_a2'], + varType: 'lookup' + }), + v('c[A3]', '', { + points: [ + [0, 30], + [1, 40] + ], + refId: '_c[_a3]', + subscripts: ['_a3'], + varType: 'lookup' + }), + v('d', 'WITH LOOKUP(i,([(0,0)-(2,2)],(0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3)))', { + lookupArgVarName: '__lookup1', + refId: '_d', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup1'], + references: ['_i'] + }), + v('e[DimA]', 'c[DimA](i)', { + refId: '_e', + referencedLookupVarNames: ['_c'], + references: ['_i'], + subscripts: ['_dima'] + }), + v('f', 'c[A1](i)', { + refId: '_f', + referencedLookupVarNames: ['_c'], + references: ['_i'] + }), + v('g', '', { + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + refId: '_g', + varType: 'lookup' + }), + v('g at minus 1 forward', 'LOOKUP FORWARD(g,-1)', { + refId: '_g_at_minus_1_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at 0 forward', 'LOOKUP FORWARD(g,0)', { + refId: '_g_at_0_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at 0pt5 forward', 'LOOKUP FORWARD(g,0.5)', { + refId: '_g_at_0pt5_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at 1pt0 forward', 'LOOKUP FORWARD(g,1.0)', { + refId: '_g_at_1pt0_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at 1pt5 forward', 'LOOKUP FORWARD(g,1.5)', { + refId: '_g_at_1pt5_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at 2pt0 forward', 'LOOKUP FORWARD(g,2.0)', { + refId: '_g_at_2pt0_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at 2pt5 forward', 'LOOKUP FORWARD(g,2.5)', { + refId: '_g_at_2pt5_forward', + referencedFunctionNames: ['__lookup_forward'], + references: ['_g'] + }), + v('g at minus 1 backward', 'LOOKUP BACKWARD(g,-1)', { + refId: '_g_at_minus_1_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('g at 0 backward', 'LOOKUP BACKWARD(g,0)', { + refId: '_g_at_0_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('g at 0pt5 backward', 'LOOKUP BACKWARD(g,0.5)', { + refId: '_g_at_0pt5_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('g at 1pt0 backward', 'LOOKUP BACKWARD(g,1.0)', { + refId: '_g_at_1pt0_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('g at 1pt5 backward', 'LOOKUP BACKWARD(g,1.5)', { + refId: '_g_at_1pt5_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('g at 2pt0 backward', 'LOOKUP BACKWARD(g,2.0)', { + refId: '_g_at_2pt0_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('g at 2pt5 backward', 'LOOKUP BACKWARD(g,2.5)', { + refId: '_g_at_2pt5_backward', + referencedFunctionNames: ['__lookup_backward'], + references: ['_g'] + }), + v('withlookup at minus 1', 'WITH LOOKUP(-1,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup2', + refId: '_withlookup_at_minus_1', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup2'] + }), + v('withlookup at 0', 'WITH LOOKUP(0,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup3', + refId: '_withlookup_at_0', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup3'] + }), + v('withlookup at 0pt5', 'WITH LOOKUP(0.5,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup4', + refId: '_withlookup_at_0pt5', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup4'] + }), + v('withlookup at 1pt0', 'WITH LOOKUP(1.0,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup5', + refId: '_withlookup_at_1pt0', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup5'] + }), + v('withlookup at 1pt5', 'WITH LOOKUP(1.5,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup6', + refId: '_withlookup_at_1pt5', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup6'] + }), + v('withlookup at 2pt0', 'WITH LOOKUP(2.0,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup7', + refId: '_withlookup_at_2pt0', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup7'] + }), + v('withlookup at 2pt5', 'WITH LOOKUP(2.5,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup8', + refId: '_withlookup_at_2pt5', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup8'] + }), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_lookup1', '', { + includeInOutput: false, + points: [ + [0, 0], + [0.1, 0.01], + [0.5, 0.7], + [1, 1], + [1.5, 1.2], + [2, 1.3] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup1', + varType: 'lookup' + }), + v('_lookup2', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup2', + varType: 'lookup' + }), + v('_lookup3', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup3', + varType: 'lookup' + }), + v('_lookup4', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup4', + varType: 'lookup' + }), + v('_lookup5', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup5', + varType: 'lookup' + }), + v('_lookup6', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup6', + varType: 'lookup' + }), + v('_lookup7', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup7', + varType: 'lookup' + }), + v('_lookup8', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup8', + varType: 'lookup' + }) + ]) + }) + + it('should work for XMILE "mapping" model', () => { + const vars = readSubscriptsAndEquations('mapping') + expect(vars).toEqual([ + v('b[DimB]', '1,2', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '1,2', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + v('a[DimA]', 'b[DimB]*10', { + refId: '_a', + references: ['_b[_b1]', '_b[_b2]'], + subscripts: ['_dima'] + }), + v('c[DimC]', '1,2,3', { + refId: '_c[_c1]', + separationDims: ['_dimc'], + subscripts: ['_c1'], + varType: 'const' + }), + v('c[DimC]', '1,2,3', { + refId: '_c[_c2]', + separationDims: ['_dimc'], + subscripts: ['_c2'], + varType: 'const' + }), + v('c[DimC]', '1,2,3', { + refId: '_c[_c3]', + separationDims: ['_dimc'], + subscripts: ['_c3'], + varType: 'const' + }), + v('d[DimD]', 'c[DimC]*10', { + refId: '_d', + references: ['_c[_c1]', '_c[_c2]', '_c[_c3]'], + subscripts: ['_dimd'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "multimap" model', () => { + const vars = readSubscriptsAndEquations('multimap') + expect(vars).toEqual([ + v('a[DimA]', '1,2,3', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b[DimB]', 'a[DimA]', { + refId: '_b', + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'], + subscripts: ['_dimb'] + }), + v('c[DimC]', 'a[DimA]', { + refId: '_c', + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'], + subscripts: ['_dimc'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('SAVEPER', '1', { + refId: '_saveper', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "npv" model', () => { + const vars = readSubscriptsAndEquations('npv') + expect(vars).toEqual([ + v('investment', '100', { + refId: '_investment', + varType: 'const' + }), + v('start time', '12', { + refId: '_start_time', + varType: 'const' + }), + v('revenue', '3', { + refId: '_revenue', + varType: 'const' + }), + v('interest rate', '10', { + refId: '_interest_rate', + varType: 'const' + }), + v('stream', '-investment/TIME STEP*PULSE(start time,TIME STEP)+STEP(revenue,start time)', { + refId: '_stream', + referencedFunctionNames: ['__pulse', '__step'], + references: ['_investment', '_time_step', '_start_time', '_revenue'] + }), + v('discount rate', 'interest rate/12/100', { + refId: '_discount_rate', + references: ['_interest_rate'] + }), + v('init val', '0', { + refId: '_init_val', + varType: 'const' + }), + v('factor', '1', { + refId: '_factor', + varType: 'const' + }), + v('NPV vs initial time', 'NPV(stream,discount rate,init val,factor)', { + npvVarName: '__aux1', + refId: '_npv_vs_initial_time', + references: ['__level2', '__level1', '__aux1'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '100', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_level1', 'INTEG((-_level1*discount rate)/(1+discount rate*TIME STEP),1)', { + hasInitValue: true, + includeInOutput: false, + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_discount_rate', '_time_step'], + varType: 'level' + }), + v('_level2', 'INTEG(stream*_level1,init val)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_init_val'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['_stream', '__level1'], + varType: 'level' + }), + v('_aux1', '(_level2+stream*TIME STEP*_level1)*factor', { + includeInOutput: false, + refId: '__aux1', + references: ['__level2', '_stream', '_time_step', '__level1', '_factor'] + }) + ]) + }) + + it('should work for XMILE "power" model', () => { + const vars = readSubscriptsAndEquations('power') + expect(vars).toEqual([ + v('base', '2', { + refId: '_base', + varType: 'const' + }), + v('a', 'POWER(base,2)', { + refId: '_a', + referencedFunctionNames: ['__power'], + references: ['_base'] + }), + v('b', 'POWER(base,0.5)', { + refId: '_b', + referencedFunctionNames: ['__power'], + references: ['_base'] + }), + v('c', 'POWER(base,1.5)', { + refId: '_c', + referencedFunctionNames: ['__power'], + references: ['_base'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + // it('should work for XMILE "preprocess" model', () => { + // const vars = readSubscriptsAndEquations('preprocess') + // logPrettyVars(vars) + // expect(vars).toEqual([]) + // }) + + it('should work for XMILE "prune" model', () => { + const vars = readSubscriptsAndEquations('prune') + expect(vars).toEqual([ + v('Simple 1', '', { + refId: '_simple_1', + varType: 'data' + }), + v('Simple 2', '', { + refId: '_simple_2', + varType: 'data' + }), + v('A Values[DimA]', '', { + refId: '_a_values', + subscripts: ['_dima'], + varType: 'data' + }), + v('BC Values[DimB,DimC]', '', { + refId: '_bc_values', + subscripts: ['_dimb', '_dimc'], + varType: 'data' + }), + v('D Values[DimD]', '', { + refId: '_d_values', + subscripts: ['_dimd'], + varType: 'data' + }), + v('E Values[E1]', '', { + refId: '_e_values[_e1]', + subscripts: ['_e1'], + varType: 'data' + }), + v('E Values[E2]', '', { + refId: '_e_values[_e2]', + subscripts: ['_e2'], + varType: 'data' + }), + v('Look1', '', { + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + refId: '_look1', + varType: 'lookup' + }), + v('Look2', '', { + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + refId: '_look2', + varType: 'lookup' + }), + v('Input 1', '10', { + refId: '_input_1', + varType: 'const' + }), + v('Input 2', '20', { + refId: '_input_2', + varType: 'const' + }), + v('Input 3', '30', { + refId: '_input_3', + varType: 'const' + }), + v('Simple Totals', 'Simple 1+Simple 2', { + refId: '_simple_totals', + references: ['_simple_1', '_simple_2'] + }), + v('A Totals', 'SUM(A Values[DimA!])', { + refId: '_a_totals', + referencedFunctionNames: ['__sum'], + references: ['_a_values'] + }), + v('B1 Totals', 'SUM(BC Values[B1,DimC!])', { + refId: '_b1_totals', + referencedFunctionNames: ['__sum'], + references: ['_bc_values'] + }), + v('D Totals', 'SUM(D Values[DimD!])', { + refId: '_d_totals', + referencedFunctionNames: ['__sum'], + references: ['_d_values'] + }), + v('E1 Values', 'E Values[E1]', { + refId: '_e1_values', + references: ['_e_values[_e1]'] + }), + v('E2 Values', 'E Values[E2]', { + refId: '_e2_values', + references: ['_e_values[_e2]'] + }), + v('Input 1 and 2 Total', 'Input 1+Input 2', { + refId: '_input_1_and_2_total', + references: ['_input_1', '_input_2'] + }), + v('Input 2 and 3 Total', 'Input 2+Input 3', { + refId: '_input_2_and_3_total', + references: ['_input_2', '_input_3'] + }), + v('Look1 Value at t1', 'Look1(1)', { + refId: '_look1_value_at_t1', + referencedFunctionNames: ['__look1'] + }), + v('Look2 Value at t1', 'Look2(1)', { + refId: '_look2_value_at_t1', + referencedFunctionNames: ['__look2'] + }), + v('With Look1 at t1', 'WITH LOOKUP(1,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup1', + refId: '_with_look1_at_t1', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup1'] + }), + v('With Look2 at t1', 'WITH LOOKUP(1,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { + lookupArgVarName: '__lookup2', + refId: '_with_look2_at_t1', + referencedFunctionNames: ['__with_lookup'], + referencedLookupVarNames: ['__lookup2'] + }), + v('Constant Partial 1', '1', { + refId: '_constant_partial_1', + varType: 'const' + }), + v('Constant Partial 2', '2', { + refId: '_constant_partial_2', + varType: 'const' + }), + v('Initial Partial[C1]', 'INITIAL(Constant Partial 1)', { + hasInitValue: true, + initReferences: ['_constant_partial_1'], + refId: '_initial_partial[_c1]', + referencedFunctionNames: ['__initial'], + subscripts: ['_c1'], + varType: 'initial' + }), + v('Initial Partial[C2]', 'INITIAL(Constant Partial 2)', { + hasInitValue: true, + initReferences: ['_constant_partial_2'], + refId: '_initial_partial[_c2]', + referencedFunctionNames: ['__initial'], + subscripts: ['_c2'], + varType: 'initial' + }), + v('Partial[C2]', 'Initial Partial[C2]', { + refId: '_partial', + references: ['_initial_partial[_c2]'], + subscripts: ['_c2'] + }), + v('Test 1 T', '1', { + refId: '_test_1_t', + varType: 'const' + }), + v('Test 1 F', '2', { + refId: '_test_1_f', + varType: 'const' + }), + v('Test 1 Result', 'IF THEN ELSE(Input 1=10,Test 1 T,Test 1 F)', { + refId: '_test_1_result', + references: ['_input_1', '_test_1_t', '_test_1_f'] + }), + v('Test 2 T', '1', { + refId: '_test_2_t', + varType: 'const' + }), + v('Test 2 F', '2', { + refId: '_test_2_f', + varType: 'const' + }), + v('Test 2 Result', 'IF THEN ELSE(0,Test 2 T,Test 2 F)', { + refId: '_test_2_result', + references: ['_test_2_t', '_test_2_f'] + }), + v('Test 3 T', '1', { + refId: '_test_3_t', + varType: 'const' + }), + v('Test 3 F', '2', { + refId: '_test_3_f', + varType: 'const' + }), + v('Test 3 Result', 'IF THEN ELSE(1,Test 3 T,Test 3 F)', { + refId: '_test_3_result', + references: ['_test_3_t', '_test_3_f'] + }), + v('Test 4 Cond', '0', { + refId: '_test_4_cond', + varType: 'const' + }), + v('Test 4 T', '1', { + refId: '_test_4_t', + varType: 'const' + }), + v('Test 4 F', '2', { + refId: '_test_4_f', + varType: 'const' + }), + v('Test 4 Result', 'IF THEN ELSE(Test 4 Cond,Test 4 T,Test 4 F)', { + refId: '_test_4_result', + references: ['_test_4_cond', '_test_4_t', '_test_4_f'] + }), + v('Test 5 Cond', '1', { + refId: '_test_5_cond', + varType: 'const' + }), + v('Test 5 T', '1', { + refId: '_test_5_t', + varType: 'const' + }), + v('Test 5 F', '2', { + refId: '_test_5_f', + varType: 'const' + }), + v('Test 5 Result', 'IF THEN ELSE(Test 5 Cond,Test 5 T,Test 5 F)', { + refId: '_test_5_result', + references: ['_test_5_cond', '_test_5_t', '_test_5_f'] + }), + v('Test 6 Cond', '0', { + refId: '_test_6_cond', + varType: 'const' + }), + v('Test 6 T', '1', { + refId: '_test_6_t', + varType: 'const' + }), + v('Test 6 F', '2', { + refId: '_test_6_f', + varType: 'const' + }), + v('Test 6 Result', 'IF THEN ELSE(Test 6 Cond=1,Test 6 T,Test 6 F)', { + refId: '_test_6_result', + references: ['_test_6_cond', '_test_6_t', '_test_6_f'] + }), + v('Test 7 Cond', '1', { + refId: '_test_7_cond', + varType: 'const' + }), + v('Test 7 T', '1', { + refId: '_test_7_t', + varType: 'const' + }), + v('Test 7 F', '2', { + refId: '_test_7_f', + varType: 'const' + }), + v('Test 7 Result', 'IF THEN ELSE(Test 7 Cond=1,Test 7 T,Test 7 F)', { + refId: '_test_7_result', + references: ['_test_7_cond', '_test_7_t', '_test_7_f'] + }), + v('Test 8 Cond', '0', { + refId: '_test_8_cond', + varType: 'const' + }), + v('Test 8 T', '1', { + refId: '_test_8_t', + varType: 'const' + }), + v('Test 8 F', '2', { + refId: '_test_8_f', + varType: 'const' + }), + v('Test 8 Result', 'IF THEN ELSE(Test 8 Cond>0,Test 8 T,Test 8 F)', { + refId: '_test_8_result', + references: ['_test_8_cond', '_test_8_t', '_test_8_f'] + }), + v('Test 9 Cond', '1', { + refId: '_test_9_cond', + varType: 'const' + }), + v('Test 9 T', '1', { + refId: '_test_9_t', + varType: 'const' + }), + v('Test 9 F', '2', { + refId: '_test_9_f', + varType: 'const' + }), + v('Test 9 Result', 'IF THEN ELSE(Test 9 Cond>0,Test 9 T,Test 9 F)', { + refId: '_test_9_result', + references: ['_test_9_cond', '_test_9_t', '_test_9_f'] + }), + v('Test 10 Cond', '1', { + refId: '_test_10_cond', + varType: 'const' + }), + v('Test 10 T', '1', { + refId: '_test_10_t', + varType: 'const' + }), + v('Test 10 F', '2', { + refId: '_test_10_f', + varType: 'const' + }), + v('Test 10 Result', 'IF THEN ELSE(ABS(Test 10 Cond),Test 10 T,Test 10 F)', { + refId: '_test_10_result', + referencedFunctionNames: ['__abs'], + references: ['_test_10_cond', '_test_10_t', '_test_10_f'] + }), + v('Test 11 Cond', '0', { + refId: '_test_11_cond', + varType: 'const' + }), + v('Test 11 T', '1', { + refId: '_test_11_t', + varType: 'const' + }), + v('Test 11 F', '2', { + refId: '_test_11_f', + varType: 'const' + }), + v('Test 11 Result', 'IF THEN ELSE(Test 11 Cond:AND:ABS(Test 11 Cond),Test 11 T,Test 11 F)', { + refId: '_test_11_result', + referencedFunctionNames: ['__abs'], + references: ['_test_11_cond', '_test_11_t', '_test_11_f'] + }), + v('Test 12 Cond', '1', { + refId: '_test_12_cond', + varType: 'const' + }), + v('Test 12 T', '1', { + refId: '_test_12_t', + varType: 'const' + }), + v('Test 12 F', '2', { + refId: '_test_12_f', + varType: 'const' + }), + v('Test 12 Result', 'IF THEN ELSE(Test 12 Cond:OR:ABS(Test 12 Cond),Test 12 T,Test 12 F)', { + refId: '_test_12_result', + referencedFunctionNames: ['__abs'], + references: ['_test_12_cond', '_test_12_t', '_test_12_f'] + }), + v('Test 13 Cond', '1', { + refId: '_test_13_cond', + varType: 'const' + }), + v('Test 13 T1', '1', { + refId: '_test_13_t1', + varType: 'const' + }), + v('Test 13 T2', '7', { + refId: '_test_13_t2', + varType: 'const' + }), + v('Test 13 F', '2', { + refId: '_test_13_f', + varType: 'const' + }), + v('Test 13 Result', 'IF THEN ELSE(Test 13 Cond,Test 13 T1+Test 13 T2,Test 13 F)*10.0', { + refId: '_test_13_result', + references: ['_test_13_cond', '_test_13_t1', '_test_13_t2', '_test_13_f'] + }), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_lookup1', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup1', + varType: 'lookup' + }), + v('_lookup2', '', { + includeInOutput: false, + points: [ + [0, 0], + [1, 1], + [2, 2] + ], + range: [ + [0, 0], + [2, 2] + ], + refId: '__lookup2', + varType: 'lookup' + }) + ]) + }) + + it('should work for XMILE "quantum" model', () => { + const vars = readSubscriptsAndEquations('quantum') + expect(vars).toEqual([ + v('a', 'QUANTUM(1.9,1.0)', { + refId: '_a', + referencedFunctionNames: ['__quantum'] + }), + v('b', 'QUANTUM(0.9,1.0)', { + refId: '_b', + referencedFunctionNames: ['__quantum'] + }), + v('c', 'QUANTUM(-0.9,1.0)', { + refId: '_c', + referencedFunctionNames: ['__quantum'] + }), + v('d', 'QUANTUM(-1.9,1.0)', { + refId: '_d', + referencedFunctionNames: ['__quantum'] + }), + v('e', 'QUANTUM(112.3,10.0)', { + refId: '_e', + referencedFunctionNames: ['__quantum'] + }), + v('f', 'QUANTUM(50,12)', { + refId: '_f', + referencedFunctionNames: ['__quantum'] + }), + v('g', 'QUANTUM(423,63)', { + refId: '_g', + referencedFunctionNames: ['__quantum'] + }), + v('h', 'QUANTUM(10,10)', { + refId: '_h', + referencedFunctionNames: ['__quantum'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "ref" model', () => { + const vars = readSubscriptsAndEquations('ref') + expect(vars).toEqual([ + v('ecc[t1]', 'ce[t1]+1', { + refId: '_ecc[_t1]', + references: ['_ce[_t1]'], + subscripts: ['_t1'] + }), + v('ecc[tNext]', 'ce[tNext]+1', { + refId: '_ecc[_t2]', + references: ['_ce[_t2]'], + separationDims: ['_tnext'], + subscripts: ['_t2'] + }), + v('ecc[tNext]', 'ce[tNext]+1', { + refId: '_ecc[_t3]', + references: ['_ce[_t3]'], + separationDims: ['_tnext'], + subscripts: ['_t3'] + }), + v('ce[t1]', '1', { + refId: '_ce[_t1]', + subscripts: ['_t1'], + varType: 'const' + }), + v('ce[tNext]', 'ecc[tPrev]+1', { + refId: '_ce[_t2]', + references: ['_ecc[_t1]'], + separationDims: ['_tnext'], + subscripts: ['_t2'] + }), + v('ce[tNext]', 'ecc[tPrev]+1', { + refId: '_ce[_t3]', + references: ['_ecc[_t2]'], + separationDims: ['_tnext'], + subscripts: ['_t3'] + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "sample" model', () => { + const vars = readSubscriptsAndEquations('sample') + expect(vars).toEqual([ + v('a', 'SAMPLE IF TRUE(MODULO(Time,5)=0,Time,0)', { + hasInitValue: true, + refId: '_a', + referencedFunctionNames: ['__sample_if_true', '__modulo'], + references: ['_time'] + }), + v('b', 'a', { + refId: '_b', + references: ['_a'] + }), + v('F', 'SAMPLE IF TRUE(Time=5,2,IF THEN ELSE(switch=1,1,0))', { + hasInitValue: true, + initReferences: ['_switch'], + refId: '_f', + referencedFunctionNames: ['__sample_if_true'], + references: ['_time'] + }), + v('G', 'INTEG(rate,2*COS(scale))', { + hasInitValue: true, + initReferences: ['_scale'], + refId: '_g', + referencedFunctionNames: ['__integ', '__cos'], + references: ['_rate'], + varType: 'level' + }), + v('rate', 'STEP(10,10)', { + refId: '_rate', + referencedFunctionNames: ['__step'] + }), + v('scale', '1', { + refId: '_scale', + varType: 'const' + }), + v('switch', '1', { + refId: '_switch', + varType: 'const' + }), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "sir" model', () => { + const vars = readSubscriptsAndEquations('sir') + expect(vars).toEqual([ + v('Infectious Population I', 'INTEG(Infection Rate-Recovery Rate,1)', { + hasInitValue: true, + refId: '_infectious_population_i', + referencedFunctionNames: ['__integ'], + references: ['_infection_rate', '_recovery_rate'], + varType: 'level' + }), + v('Initial Contact Rate', '2.5', { + refId: '_initial_contact_rate', + varType: 'const' + }), + v('Contact Rate c', 'Initial Contact Rate', { + refId: '_contact_rate_c', + references: ['_initial_contact_rate'] + }), + v( + 'Reproduction Rate', + 'Contact Rate c*Infectivity i*Average Duration of Illness d*Susceptible Population S/Total Population P', + { + refId: '_reproduction_rate', + references: [ + '_contact_rate_c', + '_infectivity_i', + '_average_duration_of_illness_d', + '_susceptible_population_s', + '_total_population_p' + ] + } + ), + v('Total Population P', '10000', { + refId: '_total_population_p', + varType: 'const' + }), + v( + 'Infection Rate', + 'Contact Rate c*Infectivity i*Susceptible Population S*Infectious Population I/Total Population P', + { + refId: '_infection_rate', + references: [ + '_contact_rate_c', + '_infectivity_i', + '_susceptible_population_s', + '_infectious_population_i', + '_total_population_p' + ] + } + ), + v('Average Duration of Illness d', '2', { + refId: '_average_duration_of_illness_d', + varType: 'const' + }), + v('Recovered Population R', 'INTEG(Recovery Rate,0)', { + hasInitValue: true, + refId: '_recovered_population_r', + referencedFunctionNames: ['__integ'], + references: ['_recovery_rate'], + varType: 'level' + }), + v('Recovery Rate', 'Infectious Population I/Average Duration of Illness d', { + refId: '_recovery_rate', + references: ['_infectious_population_i', '_average_duration_of_illness_d'] + }), + v('Infectivity i', '0.25', { + refId: '_infectivity_i', + varType: 'const' + }), + v( + 'Susceptible Population S', + 'INTEG(-Infection Rate,Total Population P-Infectious Population I-Recovered Population R)', + { + hasInitValue: true, + initReferences: ['_total_population_p', '_infectious_population_i', '_recovered_population_r'], + refId: '_susceptible_population_s', + referencedFunctionNames: ['__integ'], + references: ['_infection_rate'], + varType: 'level' + } + ), + v('FINAL TIME', '200', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', '2', { + refId: '_saveper', + varType: 'const' + }), + v('TIME STEP', '0.0625', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "smooth" model', () => { + const vars = readSubscriptsAndEquations('smooth') + expect(vars).toEqual([ + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('input 2[SubA]', '3+PULSE(10,10)', { + refId: '_input_2[_a2]', + referencedFunctionNames: ['__pulse'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('input 2[SubA]', '3+PULSE(10,10)', { + refId: '_input_2[_a3]', + referencedFunctionNames: ['__pulse'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('input 3[DimA]', '3+PULSE(10,10)', { + refId: '_input_3', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima'] + }), + v('input 3x3[DimA,DimB]', '3+PULSE(10,10)', { + refId: '_input_3x3', + referencedFunctionNames: ['__pulse'], + subscripts: ['_dima', '_dimb'] + }), + v('input 2x3[SubA,DimB]', '3+PULSE(10,10)', { + refId: '_input_2x3[_a2,_dimb]', + referencedFunctionNames: ['__pulse'], + separationDims: ['_suba'], + subscripts: ['_a2', '_dimb'] + }), + v('input 2x3[SubA,DimB]', '3+PULSE(10,10)', { + refId: '_input_2x3[_a3,_dimb]', + referencedFunctionNames: ['__pulse'], + separationDims: ['_suba'], + subscripts: ['_a3', '_dimb'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('delay 2[SubA]', '2', { + refId: '_delay_2[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('delay 2[SubA]', '2', { + refId: '_delay_2[_a3]', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('delay 3[DimA]', '2', { + refId: '_delay_3', + subscripts: ['_dima'], + varType: 'const' + }), + v('initial s', '50', { + refId: '_initial_s', + varType: 'const' + }), + v('initial s with subscripts[DimA]', '50', { + refId: '_initial_s_with_subscripts', + subscripts: ['_dima'], + varType: 'const' + }), + v('s1', 'SMOOTH(input,delay)', { + refId: '_s1', + references: ['__level1'], + smoothVarRefId: '__level1' + }), + v('s2[DimA]', 'SMOOTH(input,delay)', { + refId: '_s2', + references: ['__level2'], + smoothVarRefId: '__level2', + subscripts: ['_dima'] + }), + v('s3[DimA]', 'SMOOTH(input 3[DimA],delay 3[DimA])', { + refId: '_s3', + references: ['__level3'], + smoothVarRefId: '__level3', + subscripts: ['_dima'] + }), + v('s4[SubA]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { + refId: '_s4[_a2]', + references: ['__level_s4_1[_a2]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s4_1[_a2]', + subscripts: ['_a2'] + }), + v('s4[SubA]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { + refId: '_s4[_a3]', + references: ['__level_s4_1[_a3]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s4_1[_a3]', + subscripts: ['_a3'] + }), + v('s5[SubA]', 'SMOOTH3(input 2[SubA],delay 2[SubA])', { + refId: '_s5[_a2]', + references: ['__level_s5_1[_a2]', '__level_s5_2[_a2]', '__level_s5_3[_a2]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s5_3[_a2]', + subscripts: ['_a2'] + }), + v('s5[SubA]', 'SMOOTH3(input 2[SubA],delay 2[SubA])', { + refId: '_s5[_a3]', + references: ['__level_s5_1[_a3]', '__level_s5_2[_a3]', '__level_s5_3[_a3]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s5_3[_a3]', + subscripts: ['_a3'] + }), + v('s6[DimB]', 'SMOOTH(input 3[DimA],delay 3[DimA])', { + refId: '_s6', + references: ['__level4'], + smoothVarRefId: '__level4', + subscripts: ['_dimb'] + }), + v('s7[SubB]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { + refId: '_s7[_b2]', + references: ['__level_s7_1[_a2]'], + separationDims: ['_subb'], + smoothVarRefId: '__level_s7_1[_a2]', + subscripts: ['_b2'] + }), + v('s7[SubB]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { + refId: '_s7[_b3]', + references: ['__level_s7_1[_a3]'], + separationDims: ['_subb'], + smoothVarRefId: '__level_s7_1[_a3]', + subscripts: ['_b3'] + }), + v('s8[DimA,DimB]', 'SMOOTH(input 3x3[DimA,DimB],delay)', { + refId: '_s8', + references: ['__level5'], + smoothVarRefId: '__level5', + subscripts: ['_dima', '_dimb'] + }), + v('s9[SubA,DimB]', 'SMOOTH(input 2x3[SubA,DimB],delay)', { + refId: '_s9[_a2,_dimb]', + references: ['__level_s9_1[_a2,_dimb]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s9_1[_a2,_dimb]', + subscripts: ['_a2', '_dimb'] + }), + v('s9[SubA,DimB]', 'SMOOTH(input 2x3[SubA,DimB],delay)', { + refId: '_s9[_a3,_dimb]', + references: ['__level_s9_1[_a3,_dimb]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s9_1[_a3,_dimb]', + subscripts: ['_a3', '_dimb'] + }), + v('s10[SubA,B1]', 'SMOOTH(input 2[SubA],delay)', { + refId: '_s10[_a2,_b1]', + references: ['__level_s10_1[_a2]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s10_1[_a2]', + subscripts: ['_a2', '_b1'] + }), + v('s10[SubA,B1]', 'SMOOTH(input 2[SubA],delay)', { + refId: '_s10[_a3,_b1]', + references: ['__level_s10_1[_a3]'], + separationDims: ['_suba'], + smoothVarRefId: '__level_s10_1[_a3]', + subscripts: ['_a3', '_b1'] + }), + v('s11[DimA]', 'SMOOTH3(input 3[DimA],delay)', { + refId: '_s11', + references: ['__level6', '__level7', '__level8'], + smoothVarRefId: '__level8', + subscripts: ['_dima'] + }), + v('s12[DimA]', 'SMOOTH3I(input 3[DimA],delay 3[DimA],initial s)', { + refId: '_s12', + references: ['__level9', '__level10', '__level11'], + smoothVarRefId: '__level11', + subscripts: ['_dima'] + }), + v('s13[DimA]', 'SMOOTH3I(input 3[DimA],delay,initial s)', { + refId: '_s13', + references: ['__level12', '__level13', '__level14'], + smoothVarRefId: '__level14', + subscripts: ['_dima'] + }), + v('s14[DimA]', 'SMOOTH3I(input 3[DimA],delay,initial s with subscripts[DimA])', { + refId: '_s14', + references: ['__level15', '__level16', '__level17'], + smoothVarRefId: '__level17', + subscripts: ['_dima'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '40', { + refId: '_final_time', + varType: 'const' + }), + v('SAVEPER', '1', { + refId: '_saveper', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_level1', 'INTEG((input-_level1)/delay,input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level2', 'INTEG((input-_level2)/delay,input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level2', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level3[DimA]', 'INTEG((input 3[DimA]-_level3[DimA])/delay 3[DimA],input 3[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_3'], + refId: '__level3', + referencedFunctionNames: ['__integ'], + references: ['_input_3', '_delay_3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level_s4_1[a2]', 'INTEG((input 2[a2]-_level_s4_1[a2])/delay 2[a2],input 2[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a2]'], + refId: '__level_s4_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '_delay_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_s4_1[a3]', 'INTEG((input 2[a3]-_level_s4_1[a3])/delay 2[a3],input 2[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a3]'], + refId: '__level_s4_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '_delay_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_s5_1[a2]', 'INTEG((input 2[a2]-_level_s5_1[a2])/(delay 2[a2]/3),input 2[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a2]'], + refId: '__level_s5_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '_delay_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_s5_2[a2]', 'INTEG((_level_s5_1[a2]-_level_s5_2[a2])/(delay 2[a2]/3),input 2[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a2]'], + refId: '__level_s5_2[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__level_s5_1[_a2]', '_delay_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_s5_3[a2]', 'INTEG((_level_s5_2[a2]-_level_s5_3[a2])/(delay 2[a2]/3),input 2[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a2]'], + refId: '__level_s5_3[_a2]', + referencedFunctionNames: ['__integ'], + references: ['__level_s5_2[_a2]', '_delay_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_s5_1[a3]', 'INTEG((input 2[a3]-_level_s5_1[a3])/(delay 2[a3]/3),input 2[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a3]'], + refId: '__level_s5_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '_delay_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_s5_2[a3]', 'INTEG((_level_s5_1[a3]-_level_s5_2[a3])/(delay 2[a3]/3),input 2[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a3]'], + refId: '__level_s5_2[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__level_s5_1[_a3]', '_delay_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level_s5_3[a3]', 'INTEG((_level_s5_2[a3]-_level_s5_3[a3])/(delay 2[a3]/3),input 2[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a3]'], + refId: '__level_s5_3[_a3]', + referencedFunctionNames: ['__integ'], + references: ['__level_s5_2[_a3]', '_delay_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level4[DimA]', 'INTEG((input 3[DimA]-_level4[DimA])/delay 3[DimA],input 3[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_3'], + refId: '__level4', + referencedFunctionNames: ['__integ'], + references: ['_input_3', '_delay_3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level_s7_1[a2]', 'INTEG((input 2[a2]-_level_s7_1[a2])/delay 2[a2],input 2[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a2]'], + refId: '__level_s7_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '_delay_2[_a2]'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_s7_1[a3]', 'INTEG((input 2[a3]-_level_s7_1[a3])/delay 2[a3],input 2[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a3]'], + refId: '__level_s7_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '_delay_2[_a3]'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level5[DimA,DimB]', 'INTEG((input 3x3[DimA,DimB]-_level5[DimA,DimB])/delay,input 3x3[DimA,DimB])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_3x3'], + refId: '__level5', + referencedFunctionNames: ['__integ'], + references: ['_input_3x3', '_delay'], + subscripts: ['_dima', '_dimb'], + varType: 'level' + }), + v('_level_s9_1[a2,DimB]', 'INTEG((input 2x3[a2,DimB]-_level_s9_1[a2,DimB])/delay,input 2x3[a2,DimB])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2x3[_a2,_dimb]'], + refId: '__level_s9_1[_a2,_dimb]', + referencedFunctionNames: ['__integ'], + references: ['_input_2x3[_a2,_dimb]', '_delay'], + subscripts: ['_a2', '_dimb'], + varType: 'level' + }), + v('_level_s9_1[a3,DimB]', 'INTEG((input 2x3[a3,DimB]-_level_s9_1[a3,DimB])/delay,input 2x3[a3,DimB])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2x3[_a3,_dimb]'], + refId: '__level_s9_1[_a3,_dimb]', + referencedFunctionNames: ['__integ'], + references: ['_input_2x3[_a3,_dimb]', '_delay'], + subscripts: ['_a3', '_dimb'], + varType: 'level' + }), + v('_level_s10_1[a2]', 'INTEG((input 2[a2]-_level_s10_1[a2])/delay,input 2[a2])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a2]'], + refId: '__level_s10_1[_a2]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a2]', '_delay'], + subscripts: ['_a2'], + varType: 'level' + }), + v('_level_s10_1[a3]', 'INTEG((input 2[a3]-_level_s10_1[a3])/delay,input 2[a3])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_2[_a3]'], + refId: '__level_s10_1[_a3]', + referencedFunctionNames: ['__integ'], + references: ['_input_2[_a3]', '_delay'], + subscripts: ['_a3'], + varType: 'level' + }), + v('_level6[DimA]', 'INTEG((input 3[DimA]-_level6[DimA])/(delay/3),input 3[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_3'], + refId: '__level6', + referencedFunctionNames: ['__integ'], + references: ['_input_3', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level7[DimA]', 'INTEG((_level6[DimA]-_level7[DimA])/(delay/3),input 3[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_3'], + refId: '__level7', + referencedFunctionNames: ['__integ'], + references: ['__level6', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level8[DimA]', 'INTEG((_level7[DimA]-_level8[DimA])/(delay/3),input 3[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input_3'], + refId: '__level8', + referencedFunctionNames: ['__integ'], + references: ['__level7', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level9[DimA]', 'INTEG((input 3[DimA]-_level9[DimA])/(delay 3[DimA]/3),initial s)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s'], + refId: '__level9', + referencedFunctionNames: ['__integ'], + references: ['_input_3', '_delay_3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level10[DimA]', 'INTEG((_level9[DimA]-_level10[DimA])/(delay 3[DimA]/3),initial s)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s'], + refId: '__level10', + referencedFunctionNames: ['__integ'], + references: ['__level9', '_delay_3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level11[DimA]', 'INTEG((_level10[DimA]-_level11[DimA])/(delay 3[DimA]/3),initial s)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s'], + refId: '__level11', + referencedFunctionNames: ['__integ'], + references: ['__level10', '_delay_3'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level12[DimA]', 'INTEG((input 3[DimA]-_level12[DimA])/(delay/3),initial s)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s'], + refId: '__level12', + referencedFunctionNames: ['__integ'], + references: ['_input_3', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level13[DimA]', 'INTEG((_level12[DimA]-_level13[DimA])/(delay/3),initial s)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s'], + refId: '__level13', + referencedFunctionNames: ['__integ'], + references: ['__level12', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level14[DimA]', 'INTEG((_level13[DimA]-_level14[DimA])/(delay/3),initial s)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s'], + refId: '__level14', + referencedFunctionNames: ['__integ'], + references: ['__level13', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level15[DimA]', 'INTEG((input 3[DimA]-_level15[DimA])/(delay/3),initial s with subscripts[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s_with_subscripts'], + refId: '__level15', + referencedFunctionNames: ['__integ'], + references: ['_input_3', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level16[DimA]', 'INTEG((_level15[DimA]-_level16[DimA])/(delay/3),initial s with subscripts[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s_with_subscripts'], + refId: '__level16', + referencedFunctionNames: ['__integ'], + references: ['__level15', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }), + v('_level17[DimA]', 'INTEG((_level16[DimA]-_level17[DimA])/(delay/3),initial s with subscripts[DimA])', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_initial_s_with_subscripts'], + refId: '__level17', + referencedFunctionNames: ['__integ'], + references: ['__level16', '_delay'], + subscripts: ['_dima'], + varType: 'level' + }) + ]) + }) + + it('should work for XMILE "smooth3" model', () => { + const vars = readSubscriptsAndEquations('smooth3') + expect(vars).toEqual([ + v('a', '1', { + refId: '_a', + varType: 'const' + }), + v('S3', 'SMOOTH3(s3 input,MAX(a,b))', { + refId: '_s3', + references: ['__level1', '__level2', '__level3'], + smoothVarRefId: '__level3' + }), + v('b', '2', { + refId: '_b', + varType: 'const' + }), + v('s3 input', '3+PULSE(10,10)', { + refId: '_s3_input', + referencedFunctionNames: ['__pulse'] + }), + v('apt', '1', { + refId: '_apt', + varType: 'const' + }), + v('ca[A1]', '1000+RAMP(100,1,10)', { + refId: '_ca[_a1]', + referencedFunctionNames: ['__ramp'], + subscripts: ['_a1'] + }), + v('ca[A2]', '1000+RAMP(300,1,10)', { + refId: '_ca[_a2]', + referencedFunctionNames: ['__ramp'], + subscripts: ['_a2'] + }), + v('ca[A3]', '1000+RAMP(600,1,10)', { + refId: '_ca[_a3]', + referencedFunctionNames: ['__ramp'], + subscripts: ['_a3'] + }), + v('cs[DimA]', 'MIN(SMOOTH3(sr,apt),ca[DimA]/TIME STEP)', { + refId: '_cs', + referencedFunctionNames: ['__min'], + references: ['__level4', '__level5', '__level6', '_ca[_a1]', '_ca[_a2]', '_ca[_a3]', '_time_step'], + smoothVarRefId: '__level6', + subscripts: ['_dima'] + }), + v('sr', 'COS(Time/5)', { + refId: '_sr', + referencedFunctionNames: ['__cos'], + references: ['_time'] + }), + v('S2 Level 1', 'INTEG((input-S2 Level 1)/S2 Delay,input)', { + hasInitValue: true, + initReferences: ['_input'], + refId: '_s2_level_1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_s2_delay'], + varType: 'level' + }), + v('S2', 'scale*S2 Level 3', { + refId: '_s2', + references: ['_scale', '_s2_level_3'] + }), + v('S2 Level 3', 'INTEG((S2 Level 2-S2 Level 3)/S2 Delay,input)', { + hasInitValue: true, + initReferences: ['_input'], + refId: '_s2_level_3', + referencedFunctionNames: ['__integ'], + references: ['_s2_level_2', '_s2_delay'], + varType: 'level' + }), + v('S2 Level 2', 'INTEG((S2 Level 1-S2 Level 2)/S2 Delay,input)', { + hasInitValue: true, + initReferences: ['_input'], + refId: '_s2_level_2', + referencedFunctionNames: ['__integ'], + references: ['_s2_level_1', '_s2_delay'], + varType: 'level' + }), + v('S2 Delay', 'delay/3', { + refId: '_s2_delay', + references: ['_delay'] + }), + v('delay', '2', { + refId: '_delay', + varType: 'const' + }), + v('input', '3+PULSE(10,10)', { + refId: '_input', + referencedFunctionNames: ['__pulse'] + }), + v('S1', 'scale*SMOOTH3(input,delay)', { + refId: '_s1', + references: ['_scale', '__level7', '__level8', '__level9'], + smoothVarRefId: '__level9' + }), + v('scale', '6', { + refId: '_scale', + varType: 'const' + }), + v('FINAL TIME', '40', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_level1', 'INTEG((s3 input-_level1)/(MAX(a,b)/3),s3 input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_s3_input'], + refId: '__level1', + referencedFunctionNames: ['__integ', '__max'], + references: ['_s3_input', '_a', '_b'], + varType: 'level' + }), + v('_level2', 'INTEG((_level1-_level2)/(MAX(a,b)/3),s3 input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_s3_input'], + refId: '__level2', + referencedFunctionNames: ['__integ', '__max'], + references: ['__level1', '_a', '_b'], + varType: 'level' + }), + v('_level3', 'INTEG((_level2-_level3)/(MAX(a,b)/3),s3 input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_s3_input'], + refId: '__level3', + referencedFunctionNames: ['__integ', '__max'], + references: ['__level2', '_a', '_b'], + varType: 'level' + }), + v('_level4', 'INTEG((sr-_level4)/(apt/3),sr)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_sr'], + refId: '__level4', + referencedFunctionNames: ['__integ'], + references: ['_sr', '_apt'], + varType: 'level' + }), + v('_level5', 'INTEG((_level4-_level5)/(apt/3),sr)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_sr'], + refId: '__level5', + referencedFunctionNames: ['__integ'], + references: ['__level4', '_apt'], + varType: 'level' + }), + v('_level6', 'INTEG((_level5-_level6)/(apt/3),sr)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_sr'], + refId: '__level6', + referencedFunctionNames: ['__integ'], + references: ['__level5', '_apt'], + varType: 'level' + }), + v('_level7', 'INTEG((input-_level7)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level7', + referencedFunctionNames: ['__integ'], + references: ['_input', '_delay'], + varType: 'level' + }), + v('_level8', 'INTEG((_level7-_level8)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level8', + referencedFunctionNames: ['__integ'], + references: ['__level7', '_delay'], + varType: 'level' + }), + v('_level9', 'INTEG((_level8-_level9)/(delay/3),input)', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input'], + refId: '__level9', + referencedFunctionNames: ['__integ'], + references: ['__level8', '_delay'], + varType: 'level' + }) + ]) + }) + + it('should work for XMILE "specialchars" model', () => { + const vars = readSubscriptsAndEquations('specialchars') + expect(vars).toEqual([ + v('DOLLAR SIGN$', '1', { + refId: '_dollar_sign_', + varType: 'const' + }), + v("time's up", '2', { + refId: '_time_s_up', + varType: 'const' + }), + v('"M&Ms"', '3', { + refId: '__m_ms_', + varType: 'const' + }), + v('"100% true"', '4', { + refId: '__100__true_', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "subalias" model', () => { + const vars = readSubscriptsAndEquations('subalias') + expect(vars).toEqual([ + v('e[DimE]', '10,20,30', { + refId: '_e[_f1]', + separationDims: ['_dime'], + subscripts: ['_f1'], + varType: 'const' + }), + v('e[DimE]', '10,20,30', { + refId: '_e[_f2]', + separationDims: ['_dime'], + subscripts: ['_f2'], + varType: 'const' + }), + v('e[DimE]', '10,20,30', { + refId: '_e[_f3]', + separationDims: ['_dime'], + subscripts: ['_f3'], + varType: 'const' + }), + v('f[DimF]', '1,2,3', { + refId: '_f[_f1]', + separationDims: ['_dimf'], + subscripts: ['_f1'], + varType: 'const' + }), + v('f[DimF]', '1,2,3', { + refId: '_f[_f2]', + separationDims: ['_dimf'], + subscripts: ['_f2'], + varType: 'const' + }), + v('f[DimF]', '1,2,3', { + refId: '_f[_f3]', + separationDims: ['_dimf'], + subscripts: ['_f3'], + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "subscript" model', () => { + const vars = readSubscriptsAndEquations('subscript') + expect(vars).toEqual([ + v('b[DimB]', '1,2,3', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '1,2,3', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + v('b[DimB]', '1,2,3', { + refId: '_b[_b3]', + separationDims: ['_dimb'], + subscripts: ['_b3'], + varType: 'const' + }), + v('a[DimA]', 'b[DimB]', { + refId: '_a', + references: ['_b[_b1]', '_b[_b2]', '_b[_b3]'], + subscripts: ['_dima'] + }), + v('c[DimB]', 'b[DimB]', { + refId: '_c', + references: ['_b[_b1]', '_b[_b2]', '_b[_b3]'], + subscripts: ['_dimb'] + }), + v('d[A1]', 'b[B1]', { + refId: '_d', + references: ['_b[_b1]'], + subscripts: ['_a1'] + }), + v('e[B1]', 'b[B1]', { + refId: '_e', + references: ['_b[_b1]'], + subscripts: ['_b1'] + }), + v('f[DimA,B1]', '1', { + refId: '_f[_dima,_b1]', + subscripts: ['_dima', '_b1'], + varType: 'const' + }), + v('f[DimA,B2]', '2', { + refId: '_f[_dima,_b2]', + subscripts: ['_dima', '_b2'], + varType: 'const' + }), + v('f[DimA,B3]', '3', { + refId: '_f[_dima,_b3]', + subscripts: ['_dima', '_b3'], + varType: 'const' + }), + v('g[B1,DimA]', 'f[DimA,B1]', { + refId: '_g[_b1,_dima]', + references: ['_f[_dima,_b1]'], + subscripts: ['_b1', '_dima'] + }), + v('g[B2,DimA]', 'f[DimA,B2]', { + refId: '_g[_b2,_dima]', + references: ['_f[_dima,_b2]'], + subscripts: ['_b2', '_dima'] + }), + v('g[B3,DimA]', 'f[DimA,B3]', { + refId: '_g[_b3,_dima]', + references: ['_f[_dima,_b3]'], + subscripts: ['_b3', '_dima'] + }), + v('o[DimA,DimB]', 'f[DimA,DimB]', { + refId: '_o', + references: ['_f[_dima,_b1]', '_f[_dima,_b2]', '_f[_dima,_b3]'], + subscripts: ['_dima', '_dimb'] + }), + v('p[DimB,DimA]', 'f[DimA,DimB]', { + refId: '_p', + references: ['_f[_dima,_b1]', '_f[_dima,_b2]', '_f[_dima,_b3]'], + subscripts: ['_dimb', '_dima'] + }), + v('r[DimA]', 'IF THEN ELSE(DimA=Selected A,1,0)', { + refId: '_r', + references: ['_selected_a'], + subscripts: ['_dima'] + }), + v('Selected A', '2', { + refId: '_selected_a', + varType: 'const' + }), + v('s[DimA]', 'DimB', { + refId: '_s', + subscripts: ['_dima'] + }), + v('t[DimC]', '1', { + refId: '_t', + subscripts: ['_dimc'], + varType: 'const' + }), + v('u[C1]', '1', { + refId: '_u[_c1]', + subscripts: ['_c1'], + varType: 'const' + }), + v('u[C2]', '2', { + refId: '_u[_c2]', + subscripts: ['_c2'], + varType: 'const' + }), + v('u[C3]', '3', { + refId: '_u[_c3]', + subscripts: ['_c3'], + varType: 'const' + }), + v('u[C4]', '4', { + refId: '_u[_c4]', + subscripts: ['_c4'], + varType: 'const' + }), + v('u[C5]', '5', { + refId: '_u[_c5]', + subscripts: ['_c5'], + varType: 'const' + }), + v('v[DimA]', 'IF THEN ELSE(DimA=A2,1,0)', { + refId: '_v', + subscripts: ['_dima'] + }), + v('w[DimX,DimY]', 'DimX-DimY', { + refId: '_w', + subscripts: ['_dimx', '_dimy'] + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "sum" model', () => { + const vars = readSubscriptsAndEquations('sum') + expect(vars).toEqual([ + v('a[DimA]', '1,2,3', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a[DimA]', '1,2,3', { + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b[DimA]', '4,5,6', { + refId: '_b[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('b[DimA]', '4,5,6', { + refId: '_b[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('b[DimA]', '4,5,6', { + refId: '_b[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('a 2[SubA]', '1,2', { + refId: '_a_2[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a 2[SubA]', '1,2', { + refId: '_a_2[_a3]', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b 2[SubA]', '4,5', { + refId: '_b_2[_a2]', + separationDims: ['_suba'], + subscripts: ['_a2'], + varType: 'const' + }), + v('b 2[SubA]', '4,5', { + refId: '_b_2[_a3]', + separationDims: ['_suba'], + subscripts: ['_a3'], + varType: 'const' + }), + v('c', 'SUM(a[DimA!])+1', { + refId: '_c', + referencedFunctionNames: ['__sum'], + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'] + }), + v('d', 'SUM(a[DimA!])+SUM(b[DimA!])', { + refId: '_d', + referencedFunctionNames: ['__sum'], + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_a1]', '_b[_a2]', '_b[_a3]'] + }), + v('e', 'SUM(a[DimA!]*b[DimA!]/TIME STEP)', { + refId: '_e', + referencedFunctionNames: ['__sum'], + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_a1]', '_b[_a2]', '_b[_a3]', '_time_step'] + }), + v('f[DimA,DimC]', '1', { + refId: '_f', + subscripts: ['_dima', '_dimc'], + varType: 'const' + }), + v('g[DimA,DimC]', 'SUM(f[DimA!,DimC!])', { + refId: '_g', + referencedFunctionNames: ['__sum'], + references: ['_f'], + subscripts: ['_dima', '_dimc'] + }), + v('h[DimC]', '10,20,30', { + refId: '_h[_c1]', + separationDims: ['_dimc'], + subscripts: ['_c1'], + varType: 'const' + }), + v('h[DimC]', '10,20,30', { + refId: '_h[_c2]', + separationDims: ['_dimc'], + subscripts: ['_c2'], + varType: 'const' + }), + v('h[DimC]', '10,20,30', { + refId: '_h[_c3]', + separationDims: ['_dimc'], + subscripts: ['_c3'], + varType: 'const' + }), + v('i', 'SUM(a[DimA!]+h[DimC!])', { + refId: '_i', + referencedFunctionNames: ['__sum'], + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_h[_c1]', '_h[_c2]', '_h[_c3]'] + }), + v('j[DimA]', 'a[DimA]/SUM(b[DimA!])', { + refId: '_j', + referencedFunctionNames: ['__sum'], + references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_a1]', '_b[_a2]', '_b[_a3]'], + subscripts: ['_dima'] + }), + v('k[SubA]', 'SUM(b 2[SubA!])', { + refId: '_k[_a2]', + referencedFunctionNames: ['__sum'], + references: ['_b_2[_a2]', '_b_2[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('k[SubA]', 'SUM(b 2[SubA!])', { + refId: '_k[_a3]', + referencedFunctionNames: ['__sum'], + references: ['_b_2[_a2]', '_b_2[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('l[SubA]', 'a 2[SubA]/SUM(b 2[SubA!])', { + refId: '_l[_a2]', + referencedFunctionNames: ['__sum'], + references: ['_a_2[_a2]', '_b_2[_a2]', '_b_2[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a2'] + }), + v('l[SubA]', 'a 2[SubA]/SUM(b 2[SubA!])', { + refId: '_l[_a3]', + referencedFunctionNames: ['__sum'], + references: ['_a_2[_a3]', '_b_2[_a2]', '_b_2[_a3]'], + separationDims: ['_suba'], + subscripts: ['_a3'] + }), + v('m[D1,E1]', '11', { + refId: '_m[_d1,_e1]', + subscripts: ['_d1', '_e1'], + varType: 'const' + }), + v('m[D1,E2]', '12', { + refId: '_m[_d1,_e2]', + subscripts: ['_d1', '_e2'], + varType: 'const' + }), + v('m[D2,E1]', '21', { + refId: '_m[_d2,_e1]', + subscripts: ['_d2', '_e1'], + varType: 'const' + }), + v('m[D2,E2]', '22', { + refId: '_m[_d2,_e2]', + subscripts: ['_d2', '_e2'], + varType: 'const' + }), + v('msum[DimD]', 'SUM(m[DimD,DimE!])', { + refId: '_msum', + referencedFunctionNames: ['__sum'], + references: ['_m[_d1,_e1]', '_m[_d1,_e2]', '_m[_d2,_e1]', '_m[_d2,_e2]'], + subscripts: ['_dimd'] + }), + v('n[D1,E1,F1]', '111', { + refId: '_n[_d1,_e1,_f1]', + subscripts: ['_d1', '_e1', '_f1'], + varType: 'const' + }), + v('n[D1,E1,F2]', '112', { + refId: '_n[_d1,_e1,_f2]', + subscripts: ['_d1', '_e1', '_f2'], + varType: 'const' + }), + v('n[D1,E2,F1]', '121', { + refId: '_n[_d1,_e2,_f1]', + subscripts: ['_d1', '_e2', '_f1'], + varType: 'const' + }), + v('n[D1,E2,F2]', '122', { + refId: '_n[_d1,_e2,_f2]', + subscripts: ['_d1', '_e2', '_f2'], + varType: 'const' + }), + v('n[D2,E1,F1]', '211', { + refId: '_n[_d2,_e1,_f1]', + subscripts: ['_d2', '_e1', '_f1'], + varType: 'const' + }), + v('n[D2,E1,F2]', '212', { + refId: '_n[_d2,_e1,_f2]', + subscripts: ['_d2', '_e1', '_f2'], + varType: 'const' + }), + v('n[D2,E2,F1]', '221', { + refId: '_n[_d2,_e2,_f1]', + subscripts: ['_d2', '_e2', '_f1'], + varType: 'const' + }), + v('n[D2,E2,F2]', '222', { + refId: '_n[_d2,_e2,_f2]', + subscripts: ['_d2', '_e2', '_f2'], + varType: 'const' + }), + v('nsum[DimD,DimE]', 'SUM(n[DimD,DimE,DimF!])', { + refId: '_nsum', + referencedFunctionNames: ['__sum'], + references: [ + '_n[_d1,_e1,_f1]', + '_n[_d1,_e1,_f2]', + '_n[_d1,_e2,_f1]', + '_n[_d1,_e2,_f2]', + '_n[_d2,_e1,_f1]', + '_n[_d2,_e1,_f2]', + '_n[_d2,_e2,_f1]', + '_n[_d2,_e2,_f2]' + ], + subscripts: ['_dimd', '_dime'] + }), + v('o[D1,DimE,F1]', '111', { + refId: '_o[_d1,_dime,_f1]', + subscripts: ['_d1', '_dime', '_f1'], + varType: 'const' + }), + v('o[D1,DimE,F2]', '112', { + refId: '_o[_d1,_dime,_f2]', + subscripts: ['_d1', '_dime', '_f2'], + varType: 'const' + }), + v('o[D2,DimE,F1]', '211', { + refId: '_o[_d2,_dime,_f1]', + subscripts: ['_d2', '_dime', '_f1'], + varType: 'const' + }), + v('o[D2,DimE,F2]', '212', { + refId: '_o[_d2,_dime,_f2]', + subscripts: ['_d2', '_dime', '_f2'], + varType: 'const' + }), + v('osum[DimD,DimE]', 'SUM(o[DimD,DimE,DimF!])', { + refId: '_osum', + referencedFunctionNames: ['__sum'], + references: ['_o[_d1,_dime,_f1]', '_o[_d1,_dime,_f2]', '_o[_d2,_dime,_f1]', '_o[_d2,_dime,_f2]'], + subscripts: ['_dimd', '_dime'] + }), + v('t[DimT]', '1,2', { + refId: '_t[_t1]', + separationDims: ['_dimt'], + subscripts: ['_t1'], + varType: 'const' + }), + v('t[DimT]', '1,2', { + refId: '_t[_t2]', + separationDims: ['_dimt'], + subscripts: ['_t2'], + varType: 'const' + }), + v('u[DimU]', '10,20,30,40', { + refId: '_u[_u1]', + separationDims: ['_dimu'], + subscripts: ['_u1'], + varType: 'const' + }), + v('u[DimU]', '10,20,30,40', { + refId: '_u[_u2]', + separationDims: ['_dimu'], + subscripts: ['_u2'], + varType: 'const' + }), + v('u[DimU]', '10,20,30,40', { + refId: '_u[_u3]', + separationDims: ['_dimu'], + subscripts: ['_u3'], + varType: 'const' + }), + v('u[DimU]', '10,20,30,40', { + refId: '_u[_u4]', + separationDims: ['_dimu'], + subscripts: ['_u4'], + varType: 'const' + }), + v("t two dim[DimT,DimT']", "(10*t[DimT])+t[DimT']", { + refId: '_t_two_dim', + references: ['_t[_t1]', '_t[_t2]'], + subscripts: ['_dimt', '_dimt_'] + }), + v("t two dim with u[DimT,DimT',DimU]", "(10*u[DimU])+(10*t[DimT])+t[DimT']", { + refId: '_t_two_dim_with_u', + references: ['_u[_u1]', '_u[_u2]', '_u[_u3]', '_u[_u4]', '_t[_t1]', '_t[_t2]'], + subscripts: ['_dimt', '_dimt_', '_dimu'] + }), + v('v[DimT]', 'SUM(t two dim[DimT,DimT!])', { + refId: '_v', + referencedFunctionNames: ['__sum'], + references: ['_t_two_dim'], + subscripts: ['_dimt'] + }), + v('w[DimT,DimU]', 'u[DimU]*SUM(t two dim[DimT,DimT!])', { + refId: '_w', + referencedFunctionNames: ['__sum'], + references: ['_u[_u1]', '_u[_u2]', '_u[_u3]', '_u[_u4]', '_t_two_dim'], + subscripts: ['_dimt', '_dimu'] + }), + v('x[DimT,DimU]', 'SUM(t two dim with u[DimT,DimT!,DimU])', { + refId: '_x', + referencedFunctionNames: ['__sum'], + references: ['_t_two_dim_with_u'], + subscripts: ['_dimt', '_dimu'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "sumif" model', () => { + const vars = readSubscriptsAndEquations('sumif') + expect(vars).toEqual([ + v('A Values[DimA]', '', { + refId: '_a_values', + subscripts: ['_dima'], + varType: 'data' + }), + v('A Values Total', 'SUM(A Values[DimA!])', { + refId: '_a_values_total', + referencedFunctionNames: ['__sum'], + references: ['_a_values'] + }), + v( + 'A Values Avg', + 'ZIDZ(SUM(IF THEN ELSE(A Values[DimA!]=:NA:,0,A Values[DimA!])),SUM(IF THEN ELSE(A Values[DimA!]=:NA:,0,1)))', + { + refId: '_a_values_avg', + referencedFunctionNames: ['__zidz', '__sum'], + references: ['_a_values'] + } + ), + v('FINAL TIME', '10', { + refId: '_final_time', + varType: 'const' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) + + it('should work for XMILE "trend" model', () => { + const vars = readSubscriptsAndEquations('trend') + expect(vars).toEqual([ + v('description', '0', { + refId: '_description', + varType: 'const' + }), + v('input', '1+0.5*SIN(2*3.14159*Time/period)', { + refId: '_input', + referencedFunctionNames: ['__sin'], + references: ['_time', '_period'] + }), + v('average time', '6', { + refId: '_average_time', + varType: 'const' + }), + v('initial trend', '10', { + refId: '_initial_trend', + varType: 'const' + }), + v('period', '20', { + refId: '_period', + varType: 'const' + }), + v('TREND of input', 'TREND(input,average time,initial trend)', { + refId: '_trend_of_input', + references: ['__level1', '__aux1'], + trendVarName: '__aux1' + }), + v('trend1', 'ZIDZ(input-average value,average time*ABS(average value))', { + refId: '_trend1', + referencedFunctionNames: ['__zidz', '__abs'], + references: ['_input', '_average_value', '_average_time'] + }), + v('average value', 'INTEG((input-average value)/average time,input/(1+initial trend*average time))', { + hasInitValue: true, + initReferences: ['_input', '_initial_trend', '_average_time'], + refId: '_average_value', + referencedFunctionNames: ['__integ'], + references: ['_input', '_average_time'], + varType: 'level' + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '100', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }), + v('_level1', 'INTEG((input-_level1)/average time,input/(1+initial trend*average time))', { + hasInitValue: true, + includeInOutput: false, + initReferences: ['_input', '_initial_trend', '_average_time'], + refId: '__level1', + referencedFunctionNames: ['__integ'], + references: ['_input', '_average_time'], + varType: 'level' + }), + v('_aux1', 'ZIDZ(input-_level1,average time*ABS(_level1))', { + includeInOutput: false, + refId: '__aux1', + referencedFunctionNames: ['__zidz', '__abs'], + references: ['_input', '__level1', '_average_time'] + }) + ]) + }) + + it('should work for XMILE "vector" model', () => { + const vars = readSubscriptsAndEquations('vector') + expect(vars).toEqual([ + v('ASCENDING', '1', { + refId: '_ascending', + varType: 'const' + }), + v('DESCENDING', '0', { + refId: '_descending', + varType: 'const' + }), + v('VSSUM', '0', { + refId: '_vssum', + varType: 'const' + }), + v('VSMAX', '3', { + refId: '_vsmax', + varType: 'const' + }), + v('VSERRNONE', '0', { + refId: '_vserrnone', + varType: 'const' + }), + v('VSERRATLEASTONE', '1', { + refId: '_vserratleastone', + varType: 'const' + }), + v('a[DimA]', '0,1,1', { + refId: '_a[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('a[DimA]', '0,1,1', { + refId: '_a[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('a[DimA]', '0,1,1', { + refId: '_a[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('b[DimB]', '1,2', { + refId: '_b[_b1]', + separationDims: ['_dimb'], + subscripts: ['_b1'], + varType: 'const' + }), + v('b[DimB]', '1,2', { + refId: '_b[_b2]', + separationDims: ['_dimb'], + subscripts: ['_b2'], + varType: 'const' + }), + v('c[DimA]', '10+VECTOR ELM MAP(b[B1],a[DimA])', { + refId: '_c', + referencedFunctionNames: ['__vector_elm_map'], + references: ['_b[_b1]', '_a[_a1]', '_a[_a2]', '_a[_a3]'], + subscripts: ['_dima'] + }), + v('d[A1,B1]', '1', { + refId: '_d[_a1,_b1]', + subscripts: ['_a1', '_b1'], + varType: 'const' + }), + v('d[A2,B1]', '2', { + refId: '_d[_a2,_b1]', + subscripts: ['_a2', '_b1'], + varType: 'const' + }), + v('d[A3,B1]', '3', { + refId: '_d[_a3,_b1]', + subscripts: ['_a3', '_b1'], + varType: 'const' + }), + v('d[A1,B2]', '4', { + refId: '_d[_a1,_b2]', + subscripts: ['_a1', '_b2'], + varType: 'const' + }), + v('d[A2,B2]', '5', { + refId: '_d[_a2,_b2]', + subscripts: ['_a2', '_b2'], + varType: 'const' + }), + v('d[A3,B2]', '6', { + refId: '_d[_a3,_b2]', + subscripts: ['_a3', '_b2'], + varType: 'const' + }), + v('e[A1,B1]', '0', { + refId: '_e[_a1,_b1]', + subscripts: ['_a1', '_b1'], + varType: 'const' + }), + v('e[A2,B1]', '1', { + refId: '_e[_a2,_b1]', + subscripts: ['_a2', '_b1'], + varType: 'const' + }), + v('e[A3,B1]', '0', { + refId: '_e[_a3,_b1]', + subscripts: ['_a3', '_b1'], + varType: 'const' + }), + v('e[A1,B2]', '1', { + refId: '_e[_a1,_b2]', + subscripts: ['_a1', '_b2'], + varType: 'const' + }), + v('e[A2,B2]', '0', { + refId: '_e[_a2,_b2]', + subscripts: ['_a2', '_b2'], + varType: 'const' + }), + v('e[A3,B2]', '1', { + refId: '_e[_a3,_b2]', + subscripts: ['_a3', '_b2'], + varType: 'const' + }), + v('f[DimA,DimB]', 'VECTOR ELM MAP(d[DimA,B1],a[DimA])', { + refId: '_f', + referencedFunctionNames: ['__vector_elm_map'], + references: ['_d[_a1,_b1]', '_d[_a2,_b1]', '_d[_a3,_b1]', '_a[_a1]', '_a[_a2]', '_a[_a3]'], + subscripts: ['_dima', '_dimb'] + }), + v('g[DimA,DimB]', 'VECTOR ELM MAP(d[DimA,B1],e[DimA,DimB])', { + refId: '_g', + referencedFunctionNames: ['__vector_elm_map'], + references: [ + '_d[_a1,_b1]', + '_d[_a2,_b1]', + '_d[_a3,_b1]', + '_e[_a1,_b1]', + '_e[_a1,_b2]', + '_e[_a2,_b1]', + '_e[_a2,_b2]', + '_e[_a3,_b1]', + '_e[_a3,_b2]' + ], + subscripts: ['_dima', '_dimb'] + }), + v('h[DimA]', '2100,2010,2020', { + refId: '_h[_a1]', + separationDims: ['_dima'], + subscripts: ['_a1'], + varType: 'const' + }), + v('h[DimA]', '2100,2010,2020', { + refId: '_h[_a2]', + separationDims: ['_dima'], + subscripts: ['_a2'], + varType: 'const' + }), + v('h[DimA]', '2100,2010,2020', { + refId: '_h[_a3]', + separationDims: ['_dima'], + subscripts: ['_a3'], + varType: 'const' + }), + v('l[DimA]', 'VECTOR SORT ORDER(h[DimA],ASCENDING)', { + refId: '_l', + referencedFunctionNames: ['__vector_sort_order'], + references: ['_h[_a1]', '_h[_a2]', '_h[_a3]', '_ascending'], + subscripts: ['_dima'] + }), + v('m[DimA]', 'VECTOR SORT ORDER(h[DimA],0)', { + refId: '_m', + referencedFunctionNames: ['__vector_sort_order'], + references: ['_h[_a1]', '_h[_a2]', '_h[_a3]'], + subscripts: ['_dima'] + }), + v('o[A1,B1]', '1', { + refId: '_o[_a1,_b1]', + subscripts: ['_a1', '_b1'], + varType: 'const' + }), + v('o[A1,B2]', '2', { + refId: '_o[_a1,_b2]', + subscripts: ['_a1', '_b2'], + varType: 'const' + }), + v('o[A2,B1]', '4', { + refId: '_o[_a2,_b1]', + subscripts: ['_a2', '_b1'], + varType: 'const' + }), + v('o[A2,B2]', '3', { + refId: '_o[_a2,_b2]', + subscripts: ['_a2', '_b2'], + varType: 'const' + }), + v('o[A3,B1]', '5', { + refId: '_o[_a3,_b1]', + subscripts: ['_a3', '_b1'], + varType: 'const' + }), + v('o[A3,B2]', '5', { + refId: '_o[_a3,_b2]', + subscripts: ['_a3', '_b2'], + varType: 'const' + }), + v('p[DimA,DimB]', 'VECTOR SORT ORDER(o[DimA,DimB],ASCENDING)', { + refId: '_p', + referencedFunctionNames: ['__vector_sort_order'], + references: [ + '_o[_a1,_b1]', + '_o[_a1,_b2]', + '_o[_a2,_b1]', + '_o[_a2,_b2]', + '_o[_a3,_b1]', + '_o[_a3,_b2]', + '_ascending' + ], + subscripts: ['_dima', '_dimb'] + }), + v('q[DimB]', 'VECTOR SELECT(e[DimA!,DimB],c[DimA!],0,VSSUM,VSERRNONE)', { + refId: '_q', + referencedFunctionNames: ['__vector_select'], + references: [ + '_e[_a1,_b1]', + '_e[_a1,_b2]', + '_e[_a2,_b1]', + '_e[_a2,_b2]', + '_e[_a3,_b1]', + '_e[_a3,_b2]', + '_c', + '_vssum', + '_vserrnone' + ], + subscripts: ['_dimb'] + }), + v('r[DimA]', 'VECTOR SELECT(e[DimA,DimB!],d[DimA,DimB!],:NA:,VSMAX,VSERRNONE)', { + refId: '_r', + referencedFunctionNames: ['__vector_select'], + references: [ + '_e[_a1,_b1]', + '_e[_a1,_b2]', + '_e[_a2,_b1]', + '_e[_a2,_b2]', + '_e[_a3,_b1]', + '_e[_a3,_b2]', + '_d[_a1,_b1]', + '_d[_a1,_b2]', + '_d[_a2,_b1]', + '_d[_a2,_b2]', + '_d[_a3,_b1]', + '_d[_a3,_b2]', + '_vsmax', + '_vserrnone' + ], + subscripts: ['_dima'] + }), + v('s[DimB]', 'SUM(c[DimA!]*e[DimA!,DimB])', { + refId: '_s', + referencedFunctionNames: ['__sum'], + references: ['_c', '_e[_a1,_b1]', '_e[_a1,_b2]', '_e[_a2,_b1]', '_e[_a2,_b2]', '_e[_a3,_b1]', '_e[_a3,_b2]'], + subscripts: ['_dimb'] + }), + v('u', 'VMAX(x[DimX!])', { + refId: '_u', + referencedFunctionNames: ['__vmax'], + references: ['_x[_five]', '_x[_four]', '_x[_one]', '_x[_three]', '_x[_two]'] + }), + v('v', 'VMAX(x[SubX!])', { + refId: '_v', + referencedFunctionNames: ['__vmax'], + references: ['_x[_four]', '_x[_three]', '_x[_two]'] + }), + v('w', 'VMIN(x[DimX!])', { + refId: '_w', + referencedFunctionNames: ['__vmin'], + references: ['_x[_five]', '_x[_four]', '_x[_one]', '_x[_three]', '_x[_two]'] + }), + v('x[DimX]', '1,2,3,4,5', { + refId: '_x[_one]', + separationDims: ['_dimx'], + subscripts: ['_one'], + varType: 'const' + }), + v('x[DimX]', '1,2,3,4,5', { + refId: '_x[_two]', + separationDims: ['_dimx'], + subscripts: ['_two'], + varType: 'const' + }), + v('x[DimX]', '1,2,3,4,5', { + refId: '_x[_three]', + separationDims: ['_dimx'], + subscripts: ['_three'], + varType: 'const' + }), + v('x[DimX]', '1,2,3,4,5', { + refId: '_x[_four]', + separationDims: ['_dimx'], + subscripts: ['_four'], + varType: 'const' + }), + v('x[DimX]', '1,2,3,4,5', { + refId: '_x[_five]', + separationDims: ['_dimx'], + subscripts: ['_five'], + varType: 'const' + }), + v('y[DimA]', 'VECTOR ELM MAP(x[three],(DimA-1))', { + refId: '_y', + referencedFunctionNames: ['__vector_elm_map'], + references: ['_x[_three]'], + subscripts: ['_dima'] + }), + v('INITIAL TIME', '0', { + refId: '_initial_time', + varType: 'const' + }), + v('FINAL TIME', '1', { + refId: '_final_time', + varType: 'const' + }), + v('TIME STEP', '1', { + refId: '_time_step', + varType: 'const' + }), + v('SAVEPER', 'TIME STEP', { + refId: '_saveper', + references: ['_time_step'] + }), + v('Time', '', { + refId: '_time', + varType: 'const' + }) + ]) + }) +}) diff --git a/packages/compile/src/model/read-subscripts.spec.ts b/packages/compile/src/model/read-subscripts-vensim.spec.ts similarity index 99% rename from packages/compile/src/model/read-subscripts.spec.ts rename to packages/compile/src/model/read-subscripts-vensim.spec.ts index 034c82bf..405deb40 100644 --- a/packages/compile/src/model/read-subscripts.spec.ts +++ b/packages/compile/src/model/read-subscripts-vensim.spec.ts @@ -65,7 +65,7 @@ function readAndResolveSubscripts(modelName: string): any[] { return readSubscriptsFromSource({ modelName }, /*resolve=*/ true) } -describe('readSubscriptRanges + resolveSubscriptRanges', () => { +describe('readSubscriptRanges + resolveSubscriptRanges (from Vensim model)', () => { it('should work for a subscript range with explicit subscripts', () => { const ranges = `DimA: A1, A2, A3 ~~|` diff --git a/packages/compile/src/model/read-subscripts-xmile.spec.ts b/packages/compile/src/model/read-subscripts-xmile.spec.ts new file mode 100644 index 00000000..decddb75 --- /dev/null +++ b/packages/compile/src/model/read-subscripts-xmile.spec.ts @@ -0,0 +1,687 @@ +import { describe, expect, it } from 'vitest' + +import { allSubscripts, resetSubscriptsAndDimensions } from '../_shared/subscript' + +import Model from './model' + +import type { ParsedModel } from '../_tests/test-support' +import { + dim, + dimMapping, + parseInlineXmileModel, + parseXmileModel, + sampleModelDir, + sub, + xmile +} from '../_tests/test-support' + +/** + * This is a shorthand for the following steps to read (and optionally resolve) subscript ranges: + * - parseXmileModel + * - readSubscriptRanges + * - resolveSubscriptRanges (if `resolve` is true) + * - allSubscripts + * + * TODO: Update the return type once type info is added for `allSubscripts` + */ +function readSubscriptsFromSource( + source: { + modelText?: string + modelName?: string + modelDir?: string + }, + resolve: boolean +): any[] { + // XXX: This is needed due to subs/dims being in module-level storage + resetSubscriptsAndDimensions() + + let parsedModel: ParsedModel + if (source.modelText) { + parsedModel = parseInlineXmileModel(source.modelText) + } else { + parsedModel = parseXmileModel(source.modelName) + } + + let modelDir = source.modelDir + if (modelDir === undefined) { + if (source.modelName) { + modelDir = sampleModelDir(source.modelName) + } + } + + Model.read(parsedModel, /*spec=*/ {}, /*extData=*/ undefined, /*directData=*/ undefined, modelDir, { + stopAfterReadSubscripts: !resolve, + stopAfterResolveSubscripts: true + }) + + return allSubscripts() +} + +function readInlineSubscripts(modelText: string, modelDir?: string): any[] { + return readSubscriptsFromSource({ modelText, modelDir }, /*resolve=*/ false) +} + +function readAndResolveInlineSubscripts(modelText: string, modelDir?: string): any[] { + return readSubscriptsFromSource({ modelText, modelDir }, /*resolve=*/ true) +} + +function readSubscripts(modelName: string): any[] { + return readSubscriptsFromSource({ modelName }, /*resolve=*/ false) +} + +function readAndResolveSubscripts(modelName: string): any[] { + return readSubscriptsFromSource({ modelName }, /*resolve=*/ true) +} + +describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () => { + it.only('should work for a subscript range with explicit subscripts', () => { + const xmileDims = `\ + + + + +` + + const mdl = xmile(xmileDims, '') + const rawSubs = readInlineSubscripts(mdl) + expect(rawSubs).toEqual([dim('DimA', ['A1', 'A2', 'A3'])]) + + const resolvedSubs = readAndResolveInlineSubscripts(mdl) + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2) + ]) + }) + + it('should work for a subscript range with a single numeric range', () => { + const range = `DimA: (A1-A3) ~~|` + + const rawSubs = readInlineSubscripts(range) + expect(rawSubs).toEqual([dim('DimA', ['A1', 'A2', 'A3'])]) + + const resolvedSubs = readAndResolveInlineSubscripts(range) + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2) + ]) + }) + + it('should work for a subscript range with multiple numeric ranges', () => { + const ranges = `DimA: (A1-A3),A5,(A7-A8) ~~|` + + const rawSubs = readInlineSubscripts(ranges) + expect(rawSubs).toEqual([dim('DimA', ['A1', 'A2', 'A3', 'A5', 'A7', 'A8'])]) + + const resolvedSubs = readAndResolveInlineSubscripts(ranges) + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3', 'A5', 'A7', 'A8']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('A5', 'DimA', 3), + sub('A7', 'DimA', 4), + sub('A8', 'DimA', 5) + ]) + }) + + it('should work for a subscript range with one mapping (to dimension with explicit individual subscripts)', () => { + const ranges = ` + DimA: A1, A2, A3 -> DimB ~~| + DimB: B1, B2, B3 ~~| + ` + + const rawSubs = readInlineSubscripts(ranges) + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimb: [] + }), + dim('DimB', ['B1', 'B2', 'B3']) + ]) + + const resolvedSubs = readAndResolveInlineSubscripts(ranges) + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + _dimb: ['_a1', '_a2', '_a3'] + }), + dim('DimB', ['B1', 'B2', 'B3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2) + ]) + }) + + it('should work for a subscript range with one mapping (to dimension with explicit mix of dimensions and subscripts)', () => { + const ranges = ` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + DimB: B1, B2 -> (DimA: SubA, A3) ~~| + ` + + const rawSubs = readInlineSubscripts(ranges) + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + dim('SubA', ['A1', 'A2']), + dim('DimB', ['B1', 'B2'], 'DimB', undefined, [dimMapping('DimA', ['SubA', 'A3'])], { + // After resolve phase, this will be filled in with _b1,_b2,_b2 + _dima: ['_suba', '_a3'] + }) + ]) + + const resolvedSubs = readAndResolveInlineSubscripts(ranges) + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + dim('SubA', ['A1', 'A2'], 'DimA'), + dim('DimB', ['B1', 'B2'], 'DimB', undefined, [dimMapping('DimA', ['SubA', 'A3'])], { + _dima: ['_b1', '_b1', '_b2'] + }), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1) + ]) + }) + + it('should work for a subscript range with one mapping (to dimension without explicit subscripts)', () => { + const ranges = ` + DimA: SubA, A3 -> DimB ~~| + SubA: A1, A2 ~~| + DimB: B1, B2, B3 ~~| + ` + + const rawSubs = readInlineSubscripts(ranges) + expect(rawSubs).toEqual([ + dim('DimA', ['SubA', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimb: [] + }), + dim('SubA', ['A1', 'A2']), + dim('DimB', ['B1', 'B2', 'B3']) + ]) + + const resolvedSubs = readAndResolveInlineSubscripts(ranges) + expect(resolvedSubs).toEqual([ + dim('DimA', ['SubA', 'A3'], 'DimA', ['A1', 'A2', 'A3'], [dimMapping('DimB', [])], { + _dimb: ['_a1', '_a2', '_a3'] + }), + dim('SubA', ['A1', 'A2'], 'DimA'), + dim('DimB', ['B1', 'B2', 'B3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2) + ]) + }) + + it('should work for a subscript range with two mappings', () => { + const ranges = ` + DimA: A1, A2, A3 -> (DimB: B3, B2, B1), DimC ~~| + DimB: B1, B2, B3 ~~| + DimC: C1, C2, C3 ~~| + ` + + const rawSubs = readInlineSubscripts(ranges) + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB', ['B3', 'B2', 'B1']), dimMapping('DimC')], { + // After resolve phase, this will be changed to _a3,_a2,_a1 + _dimb: ['_b3', '_b2', '_b1'], + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimc: [] + }), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']) + ]) + + const resolvedSubs = readAndResolveInlineSubscripts(ranges) + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB', ['B3', 'B2', 'B1']), dimMapping('DimC')], { + _dimb: ['_a3', '_a2', '_a1'], + _dimc: ['_a1', '_a2', '_a3'] + }), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('C3', 'DimC', 2) + ]) + }) + + it('should work for a subscript range alias (<-> operator)', () => { + const ranges = ` + DimA <-> DimB ~~| + DimB: B1, B2, B3 ~~| + ` + + const rawSubs = readInlineSubscripts(ranges) + // TODO: In "unresolved" dimensions, `value` is an empty string instead of an array + // (in the case of aliases). It would be good to fix it so that we don't need to mix types. + expect(rawSubs).toEqual([dim('DimA', '', 'DimB'), dim('DimB', ['B1', 'B2', 'B3'])]) + + const resolvedSubs = readAndResolveInlineSubscripts(ranges) + expect(resolvedSubs).toEqual([ + dim('DimA', ['B1', 'B2', 'B3']), + dim('DimB', ['B1', 'B2', 'B3'], 'DimA'), + sub('B1', 'DimA', 0), + sub('B2', 'DimA', 1), + sub('B3', 'DimA', 2) + ]) + }) + + TODO: `GET DIRECT SUBSCRIPTS` is already covered by the "directsubs" test below. + It would be nice if we had a simpler inline test here, but since it depends on + reading files, it would end up doing the same as the "directsubs" test. Once + we make it easier to provide in-memory (or mock) data sources, we can consider + implementing this inline test. + it('should work for a subscript range defined with GET DIRECT SUBSCRIPTS', () => { + }) + + it('should work for XMILE "active_initial" model', () => { + const rawSubs = readSubscripts('active_initial') + expect(rawSubs).toEqual([]) + + const resolvedSubs = readAndResolveSubscripts('active_initial') + expect(resolvedSubs).toEqual([]) + }) + + it('should work for XMILE "allocate" model', () => { + const rawSubs = readSubscripts('allocate') + expect(rawSubs).toEqual([ + dim('region', ['Boston', 'Dayton', 'Fresno']), + dim('XPriority', ['ptype', 'ppriority', 'pwidth', 'pextra']) + ]) + + const resolvedSubs = readAndResolveSubscripts('allocate') + expect(resolvedSubs).toEqual([ + dim('region', ['Boston', 'Dayton', 'Fresno']), + dim('XPriority', ['ptype', 'ppriority', 'pwidth', 'pextra']), + sub('Boston', 'Region', 0), + sub('Dayton', 'Region', 1), + sub('Fresno', 'Region', 2), + sub('ptype', 'XPriority', 0), + sub('ppriority', 'XPriority', 1), + sub('pwidth', 'XPriority', 2), + sub('pextra', 'XPriority', 3) + ]) + }) + + it.only('should work for XMILE "arrays" model', () => { + const rawSubs = readSubscripts('arrays') + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimb: [] + }), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']), + // After resolve phase, DimC' will be expanded to individual subscripts, + // and family will be changed from DimC' to DimC + dim("DimC'", ['DimC'], "DimC'", ['DimC']), + dim('DimD', ['D1', 'D2', 'D3', 'D4']), + // After resolve phase, family will be changed from SubA to DimA + dim('SubA', ['A2', 'A3'], 'SubA'), + // After resolve phase, DimX will be expanded to individual subscripts, + // and family will be changed from DimX to DimA + dim('DimX', ['SubA', 'A1'], 'DimX', ['SubA', 'A1']) + ]) + + const resolvedSubs = readAndResolveSubscripts('arrays') + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + _dimb: ['_a1', '_a2', '_a3'] + }), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']), + dim("DimC'", ['DimC'], 'DimC', ['C1', 'C2', 'C3']), + dim('DimD', ['D1', 'D2', 'D3', 'D4']), + dim('SubA', ['A2', 'A3'], 'DimA'), + dim('DimX', ['SubA', 'A1'], 'DimA', ['A2', 'A3', 'A1']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('C3', 'DimC', 2), + sub('D1', 'DimD', 0), + sub('D2', 'DimD', 1), + sub('D3', 'DimD', 2), + sub('D4', 'DimD', 3) + ]) + }) + + it('should work for XMILE "directconst" model', () => { + const rawSubs = readSubscripts('directconst') + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + // After resolve phase, family will be changed from SubA to DimA + dim('SubA', ['A2', 'A3'], 'SubA'), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2']), + // After resolve phase, "From DimC" will be expanded to individual subscripts, + // and family will be changed from "From DimC" to DimC + dim('From DimC', ['DimC'], 'From DimC', ['DimC']), + // After resolve phase, "To DimC" will be expanded to individual subscripts, + // and family will be changed from "To DimC" to DimC + dim('To DimC', ['DimC'], 'To DimC', ['DimC']), + dim('DimD', ['D1', 'D2']) + ]) + + const resolvedSubs = readAndResolveSubscripts('directconst') + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + dim('SubA', ['A2', 'A3'], 'DimA'), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2']), + dim('From DimC', ['DimC'], 'DimC', ['C1', 'C2']), + dim('To DimC', ['DimC'], 'DimC', ['C1', 'C2']), + dim('DimD', ['D1', 'D2']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('D1', 'DimD', 0), + sub('D2', 'DimD', 1) + ]) + }) + + it('should work for XMILE "directdata" model', () => { + const rawSubs = readSubscripts('directdata') + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2']), + dim('DimB', ['B1', 'B2']), + // After resolve phase, DimC will be expanded to individual subscripts, + // and family will be changed from DimM to DimC + dim('DimC', '', 'DimM'), + // After resolve phase, family will be changed from DimM to DimC + dim('DimM', ['M1', 'M2', 'M3']), + // After resolve phase, family will be changed from SubM to DimC + dim('SubM', ['M2', 'M3'], 'SubM') + ]) + + const resolvedSubs = readAndResolveSubscripts('directdata') + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2']), + dim('DimB', ['B1', 'B2']), + dim('DimC', ['M1', 'M2', 'M3']), + dim('DimM', ['M1', 'M2', 'M3'], 'DimC'), + dim('SubM', ['M2', 'M3'], 'DimC'), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('M1', 'DimC', 0), + sub('M2', 'DimC', 1), + sub('M3', 'DimC', 2) + ]) + }) + + it('should work for XMILE "directsubs" model', () => { + const rawSubs = readSubscripts('directsubs') + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], undefined, undefined, [dimMapping('DimB'), dimMapping('DimC')], { + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimb: [], + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimc: [] + }), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']) + ]) + + const resolvedSubs = readAndResolveSubscripts('directsubs') + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3'], undefined, undefined, [dimMapping('DimB'), dimMapping('DimC')], { + _dimb: ['_a1', '_a2', '_a3'], + _dimc: ['_a1', '_a2', '_a3'] + }), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('C3', 'DimC', 2) + ]) + }) + + it('should work for XMILE "mapping" model', () => { + const rawSubs = readSubscripts('mapping') + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + dim('DimB', ['B1', 'B2'], undefined, undefined, [dimMapping('DimA', ['SubA', 'A3'])], { + // After resolve phase, this will be filled in with _b1,_b2,_b3 + _dima: ['_suba', '_a3'] + }), + // After resolve phase, DimC will be expanded to individual subscripts + dim('DimC', ['SubC', 'C3'], 'DimC', ['SubC', 'C3'], [dimMapping('DimD', [])], { + // After resolve phase, this will be filled in with _c1,_c2,_c3 + _dimd: [] + }), + dim('DimD', ['D1', 'D2', 'D3']), + // After resolve phase, family will be changed from SubA to DimA + dim('SubA', ['A1', 'A2'], 'SubA'), + // After resolve phase, family will be changed from SubC to DimC + dim('SubC', ['C1', 'C2'], 'SubC') + ]) + + const resolvedSubs = readAndResolveSubscripts('mapping') + expect(resolvedSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + dim('DimB', ['B1', 'B2'], undefined, undefined, [dimMapping('DimA', ['SubA', 'A3'])], { + _dima: ['_b1', '_b1', '_b2'] + }), + dim('DimC', ['SubC', 'C3'], undefined, ['C1', 'C2', 'C3'], [dimMapping('DimD', [])], { + _dimd: ['_c1', '_c2', '_c3'] + }), + dim('DimD', ['D1', 'D2', 'D3']), + dim('SubA', ['A1', 'A2'], 'DimA'), + dim('SubC', ['C1', 'C2'], 'DimC'), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('C3', 'DimC', 2), + sub('D1', 'DimD', 0), + sub('D2', 'DimD', 1), + sub('D3', 'DimD', 2) + ]) + }) + + it('should work for XMILE "multimap" model', () => { + const rawSubs = readSubscripts('multimap') + expect(rawSubs).toEqual([ + dim( + 'DimA', + ['A1', 'A2', 'A3'], + undefined, + undefined, + [dimMapping('DimB', ['B3', 'B2', 'B1']), dimMapping('DimC')], + { + // After resolve phase, these will be changed to _a3,_a2,_a1 + _dimb: ['_b3', '_b2', '_b1'], + // After resolve phase, this will be filled in with _a1,_a2,_a3 + _dimc: [] + } + ), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']) + ]) + + const resolvedSubs = readAndResolveSubscripts('multimap') + expect(resolvedSubs).toEqual([ + dim( + 'DimA', + ['A1', 'A2', 'A3'], + undefined, + undefined, + [dimMapping('DimB', ['B3', 'B2', 'B1']), dimMapping('DimC')], + { + _dimb: ['_a3', '_a2', '_a1'], + _dimc: ['_a1', '_a2', '_a3'] + } + ), + dim('DimB', ['B1', 'B2', 'B3']), + dim('DimC', ['C1', 'C2', 'C3']), + sub('A1', 'DimA', 0), + sub('A2', 'DimA', 1), + sub('A3', 'DimA', 2), + sub('B1', 'DimB', 0), + sub('B2', 'DimB', 1), + sub('B3', 'DimB', 2), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('C3', 'DimC', 2) + ]) + }) + + it('should work for XMILE "ref" model', () => { + const rawSubs = readSubscripts('ref') + expect(rawSubs).toEqual([ + dim('Target', ['t1', 't2', 't3']), + // After resolve phase, family will be changed from tNext to Target + dim('tNext', ['t2', 't3'], 'tNext', undefined, [dimMapping('tPrev')], { + // After resolve phase, this will be filled in with _t2,_t3 + _tprev: [] + }), + // After resolve phase, family will be changed from tPrev to Target + dim('tPrev', ['t1', 't2'], 'tPrev', undefined, [dimMapping('tNext')], { + // After resolve phase, this will be filled in with _t2,_t3 + _tnext: [] + }) + ]) + + const resolvedSubs = readAndResolveSubscripts('ref') + expect(resolvedSubs).toEqual([ + dim('Target', ['t1', 't2', 't3']), + dim('tNext', ['t2', 't3'], 'Target', undefined, [dimMapping('tPrev')], { + _tprev: ['_t2', '_t3'] + }), + dim('tPrev', ['t1', 't2'], 'Target', undefined, [dimMapping('tNext')], { + _tnext: ['_t1', '_t2'] + }), + sub('t1', 'Target', 0), + sub('t2', 'Target', 1), + sub('t3', 'Target', 2) + ]) + }) + + it('should work for XMILE "subalias" model', () => { + const rawSubs = readSubscripts('subalias') + expect(rawSubs).toEqual([ + // After resolve phase, DimE will be expanded to individual subscripts, + // and family will be changed from DimF to DimE + dim('DimE', '', 'DimF'), + // After resolve phase, family will be changed from DimF to DimE + dim('DimF', ['F1', 'F2', 'F3'], 'DimF') + ]) + + const resolvedSubs = readAndResolveSubscripts('subalias') + expect(resolvedSubs).toEqual([ + dim('DimE', ['F1', 'F2', 'F3']), + dim('DimF', ['F1', 'F2', 'F3'], 'DimE'), + sub('F1', 'DimE', 0), + sub('F2', 'DimE', 1), + sub('F3', 'DimE', 2) + ]) + }) + + it('should work for XMILE "subscript" model', () => { + const rawSubs = readSubscripts('subscript') + expect(rawSubs).toEqual([ + dim('DimA', ['A1', 'A2', 'A3']), + dim('DimB', ['B1', 'B2', 'B3'], undefined, undefined, [dimMapping('DimA')], { + _dima: [] + }), + dim('DimC', ['C1', 'C2', 'C3', 'C4', 'C5']), + dim('DimX', ['X1', 'X2', 'X3']), + dim('DimY', ['Y1', 'Y2', 'Y3']) + ]) + + const resolvedSubs = readAndResolveSubscripts('subscript') + // Note: The full pretty-printed objects are included as comments below to help + // show how they are expanded by each shorthand version (for reference purposes) + expect(resolvedSubs).toEqual([ + // { + // modelName: 'DimA', + // modelValue: ['A1', 'A2', 'A3'], + // modelMappings: [], + // name: '_dima', + // value: ['_a1', '_a2', '_a3'], + // size: 3, + // family: '_dima', + // mappings: {} + // }, + dim('DimA', ['A1', 'A2', 'A3']), + // { + // modelName: 'DimB', + // modelValue: ['B1', 'B2', 'B3'], + // modelMappings: [{ toDim: 'DimA', value: [] }], + // name: '_dimb', + // value: ['_b1', '_b2', '_b3'], + // size: 3, + // family: '_dimb', + // mappings: { + // _dima: ['_b1', '_b2', '_b3'] + // } + // }, + dim('DimB', ['B1', 'B2', 'B3'], undefined, undefined, [dimMapping('DimA')], { + _dima: ['_b1', '_b2', '_b3'] + }), + dim('DimC', ['C1', 'C2', 'C3', 'C4', 'C5']), + dim('DimX', ['X1', 'X2', 'X3']), + dim('DimY', ['Y1', 'Y2', 'Y3']), + // { name: '_a1', value: 0, size: 1, family: '_dima', mappings: {} }, + sub('A1', 'DimA', 0), + // { name: '_a2', value: 1, size: 1, family: '_dima', mappings: {} }, + sub('A2', 'DimA', 1), + // { name: '_a3', value: 2, size: 1, family: '_dima', mappings: {} }, + sub('A3', 'DimA', 2), + // { name: '_b1', value: 0, size: 1, family: '_dimb', mappings: {} }, + sub('B1', 'DimB', 0), + // { name: '_b2', value: 1, size: 1, family: '_dimb', mappings: {} }, + sub('B2', 'DimB', 1), + // { name: '_b3', value: 2, size: 1, family: '_dimb', mappings: {} } + sub('B3', 'DimB', 2), + sub('C1', 'DimC', 0), + sub('C2', 'DimC', 1), + sub('C3', 'DimC', 2), + sub('C4', 'DimC', 3), + sub('C5', 'DimC', 4), + sub('X1', 'DimX', 0), + sub('X2', 'DimX', 1), + sub('X3', 'DimX', 2), + sub('Y1', 'DimY', 0), + sub('Y2', 'DimY', 1), + sub('Y3', 'DimY', 2) + ]) + }) +}) From a56cb1ab45aed31e541de3b1d3cfd3905ca8d38c Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 15:44:23 -0700 Subject: [PATCH 12/77] fix: add temporary workaround for XMILE dimension wildcard in parse package --- .../xmile/parse-xmile-variable-def.spec.ts | 50 +++++++++++++++++++ .../src/xmile/parse-xmile-variable-def.ts | 8 +++ 2 files changed, 58 insertions(+) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 65ee4a14..32e68b45 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -349,6 +349,56 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse an aux variable definition with array function using wildcard (one dimension)', () => { + const v = xml(` + + + + + SUM(y[*]) + + `) + // TODO: For now the parser will replace the wildcard with a placeholder dimension name; + // we should have a better way to express this in the AST + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['DimA']), call('SUM', varRef('y', ['_SDE_WILDCARD_!']))) + ]) + }) + + it('should parse an aux variable definition with array function using wildcard (two dimensions, first is wildcard)', () => { + const v = xml(` + + + + + + SUM(y[*, DimB]) + + `) + // TODO: For now the parser will replace the wildcard with a placeholder dimension name; + // we should have a better way to express this in the AST + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['DimA', 'DimB']), call('SUM', varRef('y', ['_SDE_WILDCARD_!', 'DimB']))) + ]) + }) + + it('should parse an aux variable definition with array function using wildcard (two dimensions, second is wildcard)', () => { + const v = xml(` + + + + + + SUM(y[DimA, *]) + + `) + // TODO: For now the parser will replace the wildcard with a placeholder dimension name; + // we should have a better way to express this in the AST + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x', ['DimA', 'DimB']), call('SUM', varRef('y', ['DimA', '_SDE_WILDCARD_!']))) + ]) + }) + it('should parse an aux variable definition with XMILE conditional expression', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 452d2b40..8e8d4576 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -247,6 +247,14 @@ function parseExpr(exprText: string): Expr { // Since we only support the latter in the compile package, it's better if we transform // the XMILE form to Vensim form, and then we can use the Vensim expression parser. exprText = exprText.replace(conditionalRegExp, 'IF THEN ELSE($1, $2, $3)') + + // XXX: XMILE uses different syntax for array functions than Vensim. XMILE uses an + // asterisk (wildcard), e.g., `SUM(x[*])`, while Vensim uses e.g., `SUM(x[DimA!])`. + // To allow for reusing the Vensim expression parser, we will replace the XMILE wildcard + // with the Vensim syntax (using a placeholder dimension name; the real one will be + // resolved later). + exprText = exprText.replace(/\[([^\]]*)\*([^\]]*)\]/g, '[$1_SDE_WILDCARD_!$2]') + return parseVensimExpr(exprText) } From feefb1f679d2d805c7b279ba59d896e2cec958d2 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 17:15:12 -0700 Subject: [PATCH 13/77] test: restore comment in test --- .../compile/src/model/read-subscripts-xmile.spec.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/compile/src/model/read-subscripts-xmile.spec.ts b/packages/compile/src/model/read-subscripts-xmile.spec.ts index decddb75..6e17502f 100644 --- a/packages/compile/src/model/read-subscripts-xmile.spec.ts +++ b/packages/compile/src/model/read-subscripts-xmile.spec.ts @@ -283,13 +283,12 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - TODO: `GET DIRECT SUBSCRIPTS` is already covered by the "directsubs" test below. - It would be nice if we had a simpler inline test here, but since it depends on - reading files, it would end up doing the same as the "directsubs" test. Once - we make it easier to provide in-memory (or mock) data sources, we can consider - implementing this inline test. - it('should work for a subscript range defined with GET DIRECT SUBSCRIPTS', () => { - }) + // TODO: `GET DIRECT SUBSCRIPTS` is already covered by the "directsubs" test below. + // It would be nice if we had a simpler inline test here, but since it depends on + // reading files, it would end up doing the same as the "directsubs" test. Once + // we make it easier to provide in-memory (or mock) data sources, we can consider + // implementing this inline test. + it('should work for a subscript range defined with GET DIRECT SUBSCRIPTS', () => {}) it('should work for XMILE "active_initial" model', () => { const rawSubs = readSubscripts('active_initial') From 7ffe1f6fd4381fb3fce7feb0207b9264b4a822d7 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 17:15:57 -0700 Subject: [PATCH 14/77] fix: update compile package to handle XMILE wildcards --- packages/compile/src/model/model.js | 22 +++- .../src/model/read-equations-xmile.spec.ts | 6 +- packages/compile/src/model/read-equations.js | 107 ++++++++++++++++++ 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/packages/compile/src/model/model.js b/packages/compile/src/model/model.js index 6ea8c6f2..5ffe3116 100644 --- a/packages/compile/src/model/model.js +++ b/packages/compile/src/model/model.js @@ -2,6 +2,8 @@ import B from 'bufx' import yaml from 'js-yaml' import * as R from 'ramda' +import { toPrettyString } from '@sdeverywhere/parse' + import { canonicalName, decanonicalize, isIterable, /*listConcat,*/ strlist, vlog, vsort } from '../_shared/helpers.js' import { addIndex, @@ -14,7 +16,7 @@ import { subscriptFamilies } from '../_shared/subscript.js' -import { readEquation } from './read-equations.js' +import { readEquation, resolveXmileDimensionWildcards } from './read-equations.js' import { readDimensionDefs } from './read-subscripts.js' import { readVariables } from './read-variables.js' import { reduceVariables } from './reduce-variables.js' @@ -95,6 +97,20 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) { vars.forEach(addVariable) if (opts?.stopAfterReadVariables) return + if (parsedModel.kind === 'xmile') { + // XXX: In the case of XMILE, we need to resolve any wildcards used in dimension + // position in the RHS of the equation + for (const variable of vars) { + if (variable.parsedEqn?.rhs?.kind === 'expr') { + const updatedEqn = resolveXmileDimensionWildcards(variable) + if (updatedEqn) { + variable.parsedEqn = updatedEqn + variable.modelFormula = toPrettyString(updatedEqn.rhs.expr, { compact: true }) + } + } + } + } + if (spec) { // If the spec file contains `input/outputVarNames`, convert the full Vensim variable // names to C names first so that later phases only need to work with canonical names @@ -111,7 +127,7 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) { } // Analyze model equations to fill in more details about variables. - analyze(parsedModel.kind, spec?.inputVars, opts) + analyze(spec?.inputVars, opts) if (opts?.stopAfterAnalyze) return // Check that all input and output vars in the spec actually exist in the model. @@ -267,7 +283,7 @@ function resolveDimensions(dimensionFamilies) { } } -function analyze(parsedModelKind, inputVars, opts) { +function analyze(inputVars, opts) { // Analyze the RHS of each equation in stages after all the variables are read. // Find non-apply-to-all vars that are defined with more than one equation. findNonAtoAVars() diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index effacd4c..356c309f 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -520,11 +520,13 @@ describe('readEquations (from XMILE model)', () => { // y = SUM(x[DimA!]) ~~| // `) - const xmileVars = `\ + const xmileDims = `\ +` + const xmileVars = `\ @@ -534,7 +536,7 @@ describe('readEquations (from XMILE model)', () => { SUM(x[*]) ` - const mdl = xmile('', xmileVars) + const mdl = xmile(xmileDims, xmileVars) const vars = readInlineModel(mdl) expect(vars).toEqual([ diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 0c630830..19cf7fcd 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -949,3 +949,110 @@ function resolveRhsSubOrDim(lhsVariable, lhsSubIds, rhsSubId) { throw new Error(`Failed to find LHS dimension for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`) } } + +/** + * Resolve any XMILE dimension wildcards in the given equation and return a new equation + * that has the `_SDE_WILDCARD_` placeholder replaced with the actual dimension name. + * + * @param {*} variable The `Variable` instance to process. + * @returns {*} The parsed equation with the `_SDE_WILDCARD_` placeholder replaced with the + * actual dimension name, or `undefined` if the equation does not contain any wildcards. + */ +export function resolveXmileDimensionWildcards(variable) { + const eqn = variable.parsedEqn + if (!eqn.rhs || eqn.rhs.kind !== 'expr') { + return undefined + } + + // Create a deep copy of the equation and resolve wildcards + let hasWildcards = false + function resolveWildcardsInExpr(expr) { + switch (expr.kind) { + case 'variable-ref': { + if (!expr.subscriptRefs) { + return expr + } + + // Check if this variable reference has wildcards + let varRefHasWildcard = false + const newSubscriptRefs = expr.subscriptRefs.map((subRef, subIndex) => { + if (subRef.subId.startsWith('__sde_wildcard_')) { + varRefHasWildcard = true + hasWildcards = true + + // Look up the referenced variable to get its dimensions + const referencedVars = Model.varsWithName(expr.varId) + if (referencedVars && referencedVars.length > 0) { + // Get the dimension ID at this index from the referenced variable + const referencedDimId = referencedVars[0].subscripts[subIndex] + + // Get the dimension name for the ID + const referencedDim = sub(referencedDimId) + const referencedDimName = referencedDim.modelName + + // Preserve any trailing characters (like '!') from the wildcard + const trailingChars = subRef.subId.substring('__sde_wildcard_'.length) + return { + subName: referencedDimName + trailingChars, + subId: referencedDimId + trailingChars + } + } else { + // If we can't find the referenced variable or it has no dimensions, keep the wildcard + return subRef + } + } + return subRef + }) + + if (varRefHasWildcard) { + return { ...expr, subscriptRefs: newSubscriptRefs } + } + return expr + } + + case 'binary-op': + return { + ...expr, + lhs: resolveWildcardsInExpr(expr.lhs), + rhs: resolveWildcardsInExpr(expr.rhs) + } + + case 'parens': + case 'unary-op': + return { ...expr, expr: resolveWildcardsInExpr(expr.expr) } + + case 'function-call': { + const newArgs = expr.args.map(arg => resolveWildcardsInExpr(arg)) + return { ...expr, args: newArgs } + } + + case 'lookup-call': + return { ...expr, arg: resolveWildcardsInExpr(expr.arg) } + + case 'number': + case 'string': + case 'keyword': + case 'lookup-def': + return expr + + default: + throw new Error(`Unhandled expression kind '${expr.kind}' when reading '${variable.modelLHS}'`) + } + } + + const resolvedRhs = resolveWildcardsInExpr(eqn.rhs.expr) + if (!hasWildcards) { + // No wildcards were found, so return the original equation + return undefined + } + + // Wildcards were found, so return a new equation with the wildcards replaced with the + // actual dimension names + return { + ...eqn, + rhs: { + ...eqn.rhs, + expr: resolvedRhs + } + } +} From 90c20511b5f3bace57fcb002724e4abb7e966ed9 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 18 Aug 2025 17:39:11 -0700 Subject: [PATCH 15/77] test: update test expectations to account for differences in arrays.stmx model --- .../src/model/read-subscripts-xmile.spec.ts | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/compile/src/model/read-subscripts-xmile.spec.ts b/packages/compile/src/model/read-subscripts-xmile.spec.ts index 6e17502f..a82528db 100644 --- a/packages/compile/src/model/read-subscripts-xmile.spec.ts +++ b/packages/compile/src/model/read-subscripts-xmile.spec.ts @@ -322,34 +322,51 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = it.only('should work for XMILE "arrays" model', () => { const rawSubs = readSubscripts('arrays') expect(rawSubs).toEqual([ - dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { - // After resolve phase, this will be filled in with _a1,_a2,_a3 - _dimb: [] - }), + // NOTE: XMILE does not have the concept of aliases/mappings, so the stmx file + // just uses {A1,A2,A3} + // dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + // // After resolve phase, this will be filled in with _a1,_a2,_a3 + // _dimb: [] + // }), + dim('DimA', ['A1', 'A2', 'A3']), dim('DimB', ['B1', 'B2', 'B3']), dim('DimC', ['C1', 'C2', 'C3']), - // After resolve phase, DimC' will be expanded to individual subscripts, - // and family will be changed from DimC' to DimC - dim("DimC'", ['DimC'], "DimC'", ['DimC']), + // NOTE: XMILE does not have the concept of aliases/mappings, so the stmx file + // just uses {C1,C2,C3} + // // After resolve phase, DimC' will be expanded to individual subscripts, + // // and family will be changed from DimC' to DimC + // dim("DimC'", ['DimC'], "DimC'", ['DimC']), + dim("DimC'", ['C1', 'C2', 'C3']), dim('DimD', ['D1', 'D2', 'D3', 'D4']), + // NOTE: XMILE does not seem to have the concept of putting a dimension name + // in another dimension definition, so the stmx file just uses {A2,A3,A1} + // // After resolve phase, DimX will be expanded to individual subscripts, + // // and family will be changed from DimX to DimA + // dim('DimX', ['SubA', 'A1'], 'DimX', ['SubA', 'A1']), + dim('DimX', ['A2', 'A3', 'A1']), // After resolve phase, family will be changed from SubA to DimA - dim('SubA', ['A2', 'A3'], 'SubA'), - // After resolve phase, DimX will be expanded to individual subscripts, - // and family will be changed from DimX to DimA - dim('DimX', ['SubA', 'A1'], 'DimX', ['SubA', 'A1']) + dim('SubA', ['A2', 'A3'], 'SubA') ]) const resolvedSubs = readAndResolveSubscripts('arrays') expect(resolvedSubs).toEqual([ - dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { - _dimb: ['_a1', '_a2', '_a3'] - }), + // NOTE: XMILE does not have the concept of aliases/mappings, so the stmx file + // just uses {A1,A2,A3} + // dim('DimA', ['A1', 'A2', 'A3'], 'DimA', undefined, [dimMapping('DimB')], { + // _dimb: ['_a1', '_a2', '_a3'] + // }), + dim('DimA', ['A1', 'A2', 'A3']), dim('DimB', ['B1', 'B2', 'B3']), dim('DimC', ['C1', 'C2', 'C3']), - dim("DimC'", ['DimC'], 'DimC', ['C1', 'C2', 'C3']), + // NOTE: XMILE does not have the concept of aliases/mappings, so the stmx file + // just uses {C1,C2,C3} + dim("DimC'", ['C1', 'C2', 'C3'], 'DimC', ['C1', 'C2', 'C3']), dim('DimD', ['D1', 'D2', 'D3', 'D4']), + // NOTE: XMILE does not seem to have the concept of putting a dimension name + // in another dimension definition, so the stmx file just uses {A2,A3,A1} + // dim('DimX', ['SubA', 'A1'], 'DimA', ['A2', 'A3', 'A1']), + dim('DimX', ['A2', 'A3', 'A1'], 'DimA', ['A2', 'A3', 'A1']), dim('SubA', ['A2', 'A3'], 'DimA'), - dim('DimX', ['SubA', 'A1'], 'DimA', ['A2', 'A3', 'A1']), sub('A1', 'DimA', 0), sub('A2', 'DimA', 1), sub('A3', 'DimA', 2), From b42ab637f51d3ff2350bb3e7ed00394c177d976a Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 19 Aug 2025 16:02:05 -0700 Subject: [PATCH 16/77] test: add separate tests for non-apply-to-all with separate defs and with shorthand --- .../src/model/read-equations-vensim.spec.ts | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/compile/src/model/read-equations-vensim.spec.ts b/packages/compile/src/model/read-equations-vensim.spec.ts index 040a5afc..72492fe6 100644 --- a/packages/compile/src/model/read-equations-vensim.spec.ts +++ b/packages/compile/src/model/read-equations-vensim.spec.ts @@ -574,7 +574,47 @@ describe('readEquations (from Vensim model)', () => { ]) }) - it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + it('should work when RHS variable is NON-apply-to-all (2D with separated definitions) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1, B1] = 1 ~~| + x[A1, B2] = 2 ~~| + x[A2, B1] = 3 ~~| + x[A2, B2] = 4 ~~| + y = x[A1, B2] ~~| + `) + expect(vars).toEqual([ + v('x[A1,B1]', '1', { + refId: '_x[_a1,_b1]', + subscripts: ['_a1', '_b1'], + varType: 'const' + }), + v('x[A1,B2]', '2', { + refId: '_x[_a1,_b2]', + subscripts: ['_a1', '_b2'], + varType: 'const' + }), + v('x[A2,B1]', '3', { + refId: '_x[_a2,_b1]', + subscripts: ['_a2', '_b1'], + varType: 'const' + }), + v('x[A2,B2]', '4', { + refId: '_x[_a2,_b2]', + subscripts: ['_a2', '_b2'], + varType: 'const' + }), + // expandedRefIdsForVar(_y, '_x', ['_a1', '_b2']) + // -> ['_x[_a1,_b2]'] + v('y', 'x[A1,B2]', { + refId: '_y', + references: ['_x[_a1,_b2]'] + }) + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (2D with shorthand definition) and is accessed with specific subscripts', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| From 48532e36d142d5c1aa75913182c49d5bcbbe5821 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 19 Aug 2025 16:03:12 -0700 Subject: [PATCH 17/77] fix: correct handling of wildcards to work in the case where referenced variable is non-apply-to-all --- packages/compile/src/model/read-equations.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 19cf7fcd..163c7827 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -984,11 +984,22 @@ export function resolveXmileDimensionWildcards(variable) { const referencedVars = Model.varsWithName(expr.varId) if (referencedVars && referencedVars.length > 0) { // Get the dimension ID at this index from the referenced variable - const referencedDimId = referencedVars[0].subscripts[subIndex] + const referencedDimOrSubId = referencedVars[0].subscripts[subIndex] // Get the dimension name for the ID - const referencedDim = sub(referencedDimId) - const referencedDimName = referencedDim.modelName + const referencedDimOrSub = sub(referencedDimOrSubId) + let referencedDimName + let referencedDimId + if (isIndex(referencedDimOrSubId)) { + // This is a subscript, so get the parent dimension name and ID + const parentDim = sub(referencedDimOrSub.family) + referencedDimName = parentDim.modelName + referencedDimId = parentDim.name + } else { + // This is a dimension, so take its name and ID directly + referencedDimName = referencedDimOrSub.modelName + referencedDimId = referencedDimOrSub.name + } // Preserve any trailing characters (like '!') from the wildcard const trailingChars = subRef.subId.substring('__sde_wildcard_'.length) From 8ef46057dc3b8a135e1d4954b907b1aec77e2c23 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 19 Aug 2025 16:04:07 -0700 Subject: [PATCH 18/77] test: convert a batch of readEquations tests to verify XMILE --- .../src/model/read-equations-xmile.spec.ts | 1747 +++++++++++++---- 1 file changed, 1318 insertions(+), 429 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 356c309f..774984a3 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' -import { canonicalName, resetHelperState } from '../_shared/helpers' +import { canonicalName, cartesianProductOf, resetHelperState } from '../_shared/helpers' import { resetSubscriptsAndDimensions } from '../_shared/subscript' import Model from './model' @@ -100,7 +100,7 @@ function v(lhs: string, formula: string, overrides?: Partial): Variabl } describe('readEquations (from XMILE model)', () => { - it.only('should work for simple equation with explicit parentheses', () => { + it('should work for simple equation with explicit parentheses', () => { const xmileVars = `\ 1 @@ -124,6 +124,12 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for conditional expression with = op', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x = time, 1, 0) ~~| + // `) + const xmileVars = `\ 1 @@ -147,11 +153,31 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for conditional expression with reference to dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x = 1 ~~| - y[DimA] = IF THEN ELSE(DimA = x, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x = 1 ~~| + // y[DimA] = IF THEN ELSE(DimA = x, 1, 0) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + 1 + + + + + + IF DimA = x THEN 1 ELSE 0 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -166,10 +192,27 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for conditional expression with reference to dimension and subscript/index', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - y[DimA] = IF THEN ELSE(DimA = A2, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // y[DimA] = IF THEN ELSE(DimA = A2, 1, 0) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + IF THEN ELSE(DimA = A2, 1, 0) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('y[DimA]', 'IF THEN ELSE(DimA=A2,1,0)', { refId: '_y', @@ -179,18 +222,39 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for equation that uses specialSeparationDims', () => { - const vars = readInlineModel( - ` - DimA: A1, A2 ~~| - y[DimA] = 0 ~~| - `, - undefined, - { - specialSeparationDims: { - _y: '_dima' - } + // Equivalent Vensim model for reference: + // const vars = readInlineModel( + // ` + // DimA: A1, A2 ~~| + // y[DimA] = 0 ~~| + // `, + // undefined, + // { + // specialSeparationDims: { + // _y: '_dima' + // } + // } + // ) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + 0 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl, undefined, { + specialSeparationDims: { + _y: '_dima' } - ) + }) expect(vars).toEqual([ v('y[DimA]', '0', { refId: '_y[_a1]', @@ -208,20 +272,61 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for equations that are affected by separateAllVarsWithDims', () => { - const vars = readInlineModel( - ` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA] = 0 ~~| - y[DimB] = 0 ~~| - z[DimA, DimB, DimC] = 0 ~~| - `, - undefined, - { - separateAllVarsWithDims: [['_dima', '_dimc'], ['_dimb']] - } - ) + // Equivalent Vensim model for reference: + // const vars = readInlineModel( + // ` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // DimC: C1, C2 ~~| + // x[DimA] = 0 ~~| + // y[DimB] = 0 ~~| + // z[DimA, DimB, DimC] = 0 ~~| + // `, + // undefined, + // { + // separateAllVarsWithDims: [['_dima', '_dimc'], ['_dimb']] + // } + // ) + + const xmileDims = `\ + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + 0 + + + + + + 0 + + + + + + + + 0 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl, undefined, { + separateAllVarsWithDims: [['_dima', '_dimc'], ['_dimb']] + }) expect(vars).toEqual([ // x should not be separated ('_dima' is not listed in `separateAllVarsWithDims`) v('x[DimA]', '0', { @@ -271,20 +376,56 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for equations when specialSeparationDims and separateAllVarsWithDims are used together', () => { - const vars = readInlineModel( - ` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x1[DimA] = 0 ~~| - x2[DimA] = 0 ~~| - y[DimB] = 0 ~~| - `, - undefined, - { - specialSeparationDims: { _x1: '_dima' }, - separateAllVarsWithDims: [['_dimb']] - } - ) + // Equivalent Vensim model for reference: + // const vars = readInlineModel( + // ` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x1[DimA] = 0 ~~| + // x2[DimA] = 0 ~~| + // y[DimB] = 0 ~~| + // `, + // undefined, + // { + // specialSeparationDims: { _x1: '_dima' }, + // separateAllVarsWithDims: [['_dimb']] + // } + // ) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + 0 + + + + + + 0 + + + + + + 0 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl, undefined, { + specialSeparationDims: { _x1: '_dima' }, + separateAllVarsWithDims: [['_dimb']] + }) expect(vars).toEqual([ // x1 should be separated ('_x1[_dima]' is listed in `specialSeparationDims`) v('x1[DimA]', '0', { @@ -321,12 +462,21 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for data variable definition', () => { - const vars = readInlineModel( - ` - x ~~| - ` - ) + // TODO: Figure out equivalent XMILE model for this + it.skip('should work for data variable definition', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel( + // ` + // x ~~| + // ` + // ) + + const xmileVars = `\ + + +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '', { refId: '_x', @@ -335,7 +485,12 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it.only('should work for lookup definition', () => { + it('should work for lookup definition', () => { + // Somewhat equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| + // `) + const xmileVars = `\ 0,0.4,0.5,0.8,1 @@ -360,10 +515,22 @@ describe('readEquations (from XMILE model)', () => { }) it('should work for lookup call', () => { - const vars = readInlineModel(` - x( (0,0),(2,1.3) ) ~~| - y = x(2) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x( (0,0),(2,1.3) ) ~~| + // y = x(2) ~~| + // `) + + const xmileVars = `\ + + 0,2 + 0,1.3 + + + x(2) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '', { refId: '_x', @@ -381,12 +548,35 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for lookup call (with apply-to-all lookup variable)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA]( (0,0),(2,1.3) ) ~~| - y = x[A1](2) ~~| - `) + // TODO: This test is skipped because apply-to-all lookups are not fully supported in SDE yet + it.skip('should work for lookup call (with apply-to-all lookup variable)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA]( (0,0),(2,1.3) ) ~~| + // y = x[A1](2) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + 0,2 + 0,1.3 + + + x[A1](2) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) + console.log(vars) expect(vars).toEqual([ v('x[DimA]', '', { points: [ @@ -404,13 +594,46 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for lookup call (with separated lookup variable)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[A1]( (0,0),(2,1.3) ) ~~| - x[A2]( (1,1),(4,3) ) ~~| - y = x[A1](2) ~~| - `) + // TODO: This test is skipped until we support XMILE spec 4.5.3: + // 4.5.3 Apply-to-All Arrays with Non-Apply-to-All Graphical Functions + it.skip('should work for lookup call (with separated lookup variable)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[A1]( (0,0),(2,1.3) ) ~~| + // x[A2]( (1,1),(4,3) ) ~~| + // y = x[A1](2) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + + 0,2 + 0,1.3 + + + + + 1,4 + 1,3 + + + + + x[A1](2) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[A1]', '', { points: [ @@ -446,10 +669,21 @@ describe('readEquations (from XMILE model)', () => { describe('when LHS has no subscripts', () => { it('should work when RHS variable has no subscripts', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = x ~~| + // `) + + const xmileVars = `\ + + 1 + + + x +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -465,11 +699,31 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1 ~~| - y = x[A1] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1 ~~| + // y = x[A1] ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + 1 + + + x[A1] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -486,21 +740,44 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y = x[A1] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1, 2 ~~| + // y = x[A1] ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + x[A1] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -513,7 +790,8 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it.only('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension', () => { + it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension', () => { + // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2 ~~| // x[DimA] = 1 ~~| @@ -556,21 +834,44 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y = SUM(x[DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1, 2 ~~| + // y = SUM(x[DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + SUM(x[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -585,13 +886,38 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1 ~~| - y = x[A1, B2] ~~| - `) - expect(vars).toEqual([ + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA, DimB] = 1 ~~| + // y = x[A1, B2] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + x[A1, B2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) + expect(vars).toEqual([ v('x[DimA,DimB]', '1', { refId: '_x', subscripts: ['_dima', '_dimb'], @@ -607,34 +933,66 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1, 2; 3, 4; ~~| - y = x[A1, B2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA, DimB] = 1, 2; 3, 4; ~~| + // y = x[A1, B2] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + + x[A1, B2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A1,B1]', '1', { refId: '_x[_a1,_b1]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a1', '_b1'], varType: 'const' }), - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A1,B2]', '2', { refId: '_x[_a1,_b2]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a1', '_b2'], varType: 'const' }), - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A2,B1]', '3', { refId: '_x[_a2,_b1]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a2', '_b1'], varType: 'const' }), - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A2,B2]', '4', { refId: '_x[_a2,_b2]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a2', '_b2'], varType: 'const' }), @@ -648,13 +1006,43 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, DimC, DimB] = 1 ~~| - y = x[A1, C2, B2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // DimC: C1, C2 ~~| + // x[DimA, DimC, DimB] = 1 ~~| + // y = x[A1, C2, B2] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + x[A1, C2, B2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,DimC,DimB]', '1', { refId: '_x', @@ -671,43 +1059,99 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, DimC, DimB] :EXCEPT: [DimA, DimC, B1] = 1 ~~| - x[DimA, DimC, B1] = 2 ~~| - y = x[A1, C2, B2] ~~| - `) - expect(vars).toEqual([ - v('x[DimA,DimC,DimB]:EXCEPT:[DimA,DimC,B1]', '1', { - refId: '_x[_dima,_dimc,_b2]', - separationDims: ['_dimb'], - subscripts: ['_dima', '_dimc', '_b2'], - varType: 'const' - }), - v('x[DimA,DimC,B1]', '2', { - refId: '_x[_dima,_dimc,_b1]', - subscripts: ['_dima', '_dimc', '_b1'], - varType: 'const' - }), - // expandedRefIdsForVar(_y, '_x', ['_a1', '_c2', '_b2']) - // -> ['_x[_dima,_dimc,_b2]'] + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // DimC: C1, C2 ~~| + // x[DimA, DimC, DimB] :EXCEPT: [DimA, DimC, B1] = 1 ~~| + // x[DimA, DimC, B1] = 2 ~~| + // y = x[A1, C2, B2] ~~| + // `) + + // XXX: XMILE doesn't seem to have a shorthand like `:EXCEPT:` so we will + // build the combinations manually here + const dimA = ['A1', 'A2'] + const dimB = ['B1', 'B2'] + const dimC = ['C1', 'C2'] + const dims = [dimA, dimC, dimB] + const combos = cartesianProductOf(dims) + const elements = combos.map((combo: string[]) => { + const subscripts = combo.join(',') + const value = subscripts.endsWith('B1') ? '2' : '1' + return `\ + + ${value} + ` + }) + console.log() + + const xmileDims = `\ + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + +${elements.join('\n')} + + + x[A1, C2, B2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) + // TODO: Verify the `x` variable instances + expect(vars.find(v => v.varName === '_y')).toEqual( v('y', 'x[A1,C2,B2]', { refId: '_y', - references: ['_x[_dima,_dimc,_b2]'] + references: ['_x[_a1,_c2,_b2]'] }) - ]) + ) }) }) describe('when LHS is apply-to-all (1D)', () => { it('should work when RHS variable has no subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x = 1 ~~| - y[DimA] = x ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x = 1 ~~| + // y[DimA] = x ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + 1 + + + + + + x +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -724,11 +1168,35 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1 ~~| + // y[DimA] = x[A2] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + x[A2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -746,11 +1214,35 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] = x[DimA] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1 ~~| + // y[DimA] = x[DimA] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + x[DimA] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -768,27 +1260,56 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // y[DimA] = x[A2] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + x[A2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), @@ -803,27 +1324,56 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] = x[DimA] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // y[DimA] = x[DimA] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + x[DimA] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), @@ -838,12 +1388,40 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) with separated definitions and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[A1] = 1 ~~| - x[A2] = 2 ~~| - y[DimA] = x[DimA] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[A1] = 1 ~~| + // x[A2] = 2 ~~| + // y[DimA] = x[DimA] ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + x[DimA] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[A1]', '1', { refId: '_x[_a1]', @@ -866,41 +1444,93 @@ describe('readEquations (from XMILE model)', () => { }) // This is adapted from the "except" sample model (see equation for `k`) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - DimB: B1, B2 -> (DimA: SubA, A1) ~~| - a[DimA] = 1, 2, 3 ~~| - b[DimB] = 4, 5 ~~| - y[DimA] = a[DimA] + b[DimB] ~~| - `) + // TODO: This test is skipped because it's not clear if XMILE supports mapped subdimensions + it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A2, A3 ~~| + // DimB: B1, B2 -> (DimA: SubA, A1) ~~| + // a[DimA] = 1, 2, 3 ~~| + // b[DimB] = 4, 5 ~~| + // y[DimA] = a[DimA] + b[DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + + 4 + + + 5 + + + + + + + a[DimA] + b[DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('a[DimA]', '1,2,3', { + v('a[A1]', '1', { refId: '_a[_a1]', separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('a[DimA]', '1,2,3', { + v('a[A2]', '2', { refId: '_a[_a2]', separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('a[DimA]', '1,2,3', { + v('a[A3]', '3', { refId: '_a[_a3]', separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('b[DimB]', '4,5', { + v('b[B1]', '4', { refId: '_b[_b1]', separationDims: ['_dimb'], subscripts: ['_b1'], varType: 'const' }), - v('b[DimB]', '4,5', { + v('b[B2]', '5', { refId: '_b[_b2]', separationDims: ['_dimb'], subscripts: ['_b2'], @@ -919,12 +1549,39 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA] = 1 ~~| - y[DimB] = SUM(x[DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA] = 1 ~~| + // y[DimB] = SUM(x[DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + SUM(x[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -943,11 +1600,34 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1 ~~| - y[DimA] = SUM(x[DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1 ~~| + // y[DimA] = SUM(x[DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + SUM(x[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -966,22 +1646,52 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA] = 1, 2 ~~| - y[DimB] = SUM(x[DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA] = 1, 2 ~~| + // y[DimB] = SUM(x[DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + SUM(x[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -997,21 +1707,47 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y[DimA] = SUM(x[DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1, 2 ~~| + // y[DimA] = SUM(x[DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + SUM(x[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -1026,21 +1762,49 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { - // // TODO - // }) + it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // TODO + }) - // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { - // // TODO - // }) + it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // TODO + }) it('should work when RHS variable is apply-to-all (2D) and is accessed with one normal dimension and one marked dimension that resolve to same family', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: DimA ~~| - x[DimA,DimB] = 1 ~~| - y[DimA] = SUM(x[DimA,DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: DimA ~~| + // x[DimA,DimB] = 1 ~~| + // y[DimA] = SUM(x[DimA,DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + + + + SUM(x[DimA,*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,DimB]', '1', { refId: '_x', @@ -1049,7 +1813,7 @@ describe('readEquations (from XMILE model)', () => { }), // expandedRefIdsForVar(_y, '_x', ['_dima!']) // -> ['_x'] - v('y[DimA]', 'SUM(x[DimA,DimA!])', { + v('y[DimA]', 'SUM(x[DimA,DimB!])', { refId: '_y', subscripts: ['_dima'], referencedFunctionNames: ['__sum'], @@ -1058,22 +1822,48 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { - // // TODO - // }) + it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // TODO + }) - // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { - // // TODO - // }) + it.skip('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // TODO + }) }) describe('when LHS is NON-apply-to-all (1D)', () => { it('should work when RHS variable has no subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x = 1 ~~| - y[DimA] :EXCEPT: [A1] = x ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x = 1 ~~| + // y[DimA] :EXCEPT: [A1] = x ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + 1 + + + + + + + x + + + x + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -1081,17 +1871,15 @@ describe('readEquations (from XMILE model)', () => { }), // expandedRefIdsForVar(_y[_a2], '_x', []) // -> ['_x'] - v('y[DimA]:EXCEPT:[A1]', 'x', { + v('y[A2]', 'x', { refId: '_y[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], references: ['_x'] }), // expandedRefIdsForVar(_y[_a3], '_x', []) // -> ['_x'] - v('y[DimA]:EXCEPT:[A1]', 'x', { + v('y[A3]', 'x', { refId: '_y[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], references: ['_x'] }) @@ -1099,11 +1887,40 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] :EXCEPT: [A1] = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1 ~~| + // y[DimA] :EXCEPT: [A1] = x[A2] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + + x[A2] + + + x[A2] + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -1112,48 +1929,82 @@ describe('readEquations (from XMILE model)', () => { }), // expandedRefIdsForVar(_y[_a2], '_x', ['_a2']) // -> ['_x'] - v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + v('y[A2]', 'x[A2]', { refId: '_y[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], references: ['_x'] }), // expandedRefIdsForVar(_y[_a3], '_x', ['_a2']) // -> ['_x'] - v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + v('y[A3]', 'x[A2]', { refId: '_y[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], references: ['_x'] }) ]) }) - it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] :EXCEPT: [A1] = x[DimA] ~~| - `) + // TODO: This test is skipped because it's failing; not sure if this is a valid construct in XMILE + it.skip('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1 ~~| + // y[DimA] :EXCEPT: [A1] = x[DimA] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + + 1 + + + x[DimA] + + + x[DimA] + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', subscripts: ['_dima'], varType: 'const' }), + v('y[A1]', '1', { + refId: '_y[_a1]', + subscripts: ['_a1'], + references: ['_x'] + }), // expandedRefIdsForVar(_y[_a2], '_x', ['_dima']) // -> ['_x'] - v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + v('y[A2]', 'x[DimA]', { refId: '_y[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], references: ['_x'] }), // expandedRefIdsForVar(_y[_a3], '_x', ['_dima']) // -> ['_x'] - v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + v('y[A3]', 'x[DimA]', { refId: '_y[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], references: ['_x'] }) @@ -1161,50 +2012,82 @@ describe('readEquations (from XMILE model)', () => { }) it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] :EXCEPT: [A1] = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // y[DimA] :EXCEPT: [A1] = x[A2] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + + x[A2] + + + x[A2] + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), // expandedRefIdsForVar(_y[_a2], '_x', ['_a2']) // -> ['_x[_a2]'] - v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + v('y[A2]', 'x[A2]', { refId: '_y[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], references: ['_x[_a2]'] }), // expandedRefIdsForVar(_y[_a3], '_x', ['_a2']) // -> ['_x[_a2]'] - v('y[DimA]:EXCEPT:[A1]', 'x[A2]', { + v('y[A3]', 'x[A2]', { refId: '_y[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], references: ['_x[_a2]'] }) ]) }) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| x[DimA] = 1, 2, 3 ~~| @@ -1249,7 +2132,7 @@ describe('readEquations (from XMILE model)', () => { }) // This is adapted from the "except" sample model (see equation for `k`) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { + it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -1313,7 +2196,7 @@ describe('readEquations (from XMILE model)', () => { }) // This is adapted from the "ref" sample model (with updated naming for clarity) - it('should work for complex mapping example', () => { + it.skip('should work for complex mapping example', () => { const vars = readInlineModel(` Target: (t1-t3) ~~| tNext: (t2-t3) -> tPrev ~~| @@ -1363,19 +2246,19 @@ describe('readEquations (from XMILE model)', () => { }) describe('when LHS is apply-to-all (2D)', () => { - // it('should work when RHS variable has no subscripts', () => { + // it.skip('should work when RHS variable has no subscripts', () => { // // TODO // }) - // it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + // it.skip('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { // // TODO // }) - // it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + // it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { // // TODO // }) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { + it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB <-> DimA ~~| @@ -1407,15 +2290,15 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { // // TODO // }) - // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { // // TODO // }) - it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -1438,7 +2321,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { + it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB <-> DimA ~~| @@ -1461,7 +2344,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -1503,7 +2386,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { + it.skip('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -1532,18 +2415,18 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { // // TODO // }) - // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // it.skip('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { // // TODO // }) }) describe('when LHS is NON-apply-to-all (2D)', () => { // The LHS in this test is partially separated (expanded only for first dimension position) - it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A1, A2 ~~| @@ -1577,7 +2460,7 @@ describe('readEquations (from XMILE model)', () => { }) // This test is based on the example from #179 (simplified to use subdimensions to ensure separation) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A1, A2 ~~| @@ -1644,7 +2527,7 @@ describe('readEquations (from XMILE model)', () => { // This test is based on the example from #179 (simplified to use subdimensions to ensure separation). // It is similar to the previous one, except in this one, `x` is apply-to-all (and refers to the parent // dimension). - it('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + it.skip('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A1, A2 ~~| @@ -1692,7 +2575,7 @@ describe('readEquations (from XMILE model)', () => { }) describe('when LHS is apply-to-all (3D)', () => { - it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -1716,7 +2599,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + it.skip('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -1748,7 +2631,7 @@ describe('readEquations (from XMILE model)', () => { }) describe('when LHS is NON-apply-to-all (3D)', () => { - it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -1783,7 +2666,7 @@ describe('readEquations (from XMILE model)', () => { }) // This test is based on the example from #278 - it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { const vars = readInlineModel(` Scenario: S1, S2 ~~| Sector: A1, A2, A3 ~~| @@ -1906,7 +2789,7 @@ describe('readEquations (from XMILE model)', () => { // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. // - it('should work for ACTIVE INITIAL function', () => { + it.skip('should work for ACTIVE INITIAL function', () => { const vars = readInlineModel(` Initial Target Capacity = 1 ~~| Capacity = 2 ~~| @@ -1931,7 +2814,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 2D pp, non-subscripted avail)', () => { + it.skip('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 2D pp, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton ~~| pprofile: ptype, ppriority ~~| @@ -1999,7 +2882,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 3D pp with specific first subscript, non-subscripted avail)', () => { + it.skip('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 3D pp with specific first subscript, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| item: Item1, Item2 ~~| @@ -2155,7 +3038,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 2D pp, non-subscripted avail)', () => { + it.skip('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 2D pp, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| item: Item1, Item2 ~~| @@ -2267,7 +3150,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 3D pp, 1D avail)', () => { + it.skip('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 3D pp, 1D avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| item: Item1, Item2 ~~| @@ -2437,7 +3320,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY1 function', () => { + it.skip('should work for DELAY1 function', () => { const vars = readInlineModel(` x = 1 ~~| y = DELAY1(x, 5) ~~| @@ -2470,7 +3353,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY1I function', () => { + it.skip('should work for DELAY1I function', () => { const vars = readInlineModel(` x = 1 ~~| init = 2 ~~| @@ -2508,7 +3391,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY1I function (with subscripted variables)', () => { + it.skip('should work for DELAY1I function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases const vars = readInlineModel(` @@ -2586,7 +3469,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY1I function (with separated variables using subdimension)', () => { + it.skip('should work for DELAY1I function (with separated variables using subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -2694,7 +3577,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY3 function', () => { + it.skip('should work for DELAY3 function', () => { const vars = readInlineModel(` input = 1 ~~| delay = 2 ~~| @@ -2765,7 +3648,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY3I function', () => { + it.skip('should work for DELAY3I function', () => { const vars = readInlineModel(` input = 1 ~~| delay = 2 ~~| @@ -2841,7 +3724,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY3I function (with nested function calls)', () => { + it.skip('should work for DELAY3I function (with nested function calls)', () => { const vars = readInlineModel(` input = 1 ~~| delay = 2 ~~| @@ -2921,7 +3804,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY3I function (with subscripted variables)', () => { + it.skip('should work for DELAY3I function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases const vars = readInlineModel(` @@ -3037,7 +3920,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY3I function (with separated variables using subdimension)', () => { + it.skip('should work for DELAY3I function (with separated variables using subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -3221,7 +4104,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DELAY FIXED function', () => { + it.skip('should work for DELAY FIXED function', () => { const vars = readInlineModel(` x = 1 ~~| y = 2 ~~| @@ -3259,7 +4142,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for DEPRECIATE STRAIGHTLINE function', () => { + it.skip('should work for DEPRECIATE STRAIGHTLINE function', () => { const vars = readInlineModel(` dtime = 20 ~~| fisc = 1 ~~| @@ -3306,7 +4189,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GAME function (no dimensions)', () => { + it.skip('should work for GAME function (no dimensions)', () => { const vars = readInlineModel(` x = 1 ~~| y = GAME(x) ~~| @@ -3331,7 +4214,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GAME function (1D)', () => { + it.skip('should work for GAME function (1D)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| x[DimA] = 1, 2 ~~| @@ -3367,7 +4250,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GAME function (2D)', () => { + it.skip('should work for GAME function (2D)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -3417,7 +4300,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GAMMA LN function', () => { + it.skip('should work for GAMMA LN function', () => { const vars = readInlineModel(` x = 1 ~~| y = GAMMA LN(x) ~~| @@ -3435,7 +4318,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT CONSTANTS function (single value)', () => { + it.skip('should work for GET DIRECT CONSTANTS function (single value)', () => { const vars = readInlineModel(` x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| `) @@ -3448,7 +4331,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT CONSTANTS function (1D)', () => { + it.skip('should work for GET DIRECT CONSTANTS function (1D)', () => { const vars = readInlineModel(` DimB: B1, B2, B3 ~~| x[DimB] = GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') ~~| @@ -3463,7 +4346,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT CONSTANTS function (2D)', () => { + it.skip('should work for GET DIRECT CONSTANTS function (2D)', () => { const vars = readInlineModel(` DimB: B1, B2, B3 ~~| DimC: C1, C2 ~~| @@ -3479,7 +4362,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT CONSTANTS function (separate definitions)', () => { + it.skip('should work for GET DIRECT CONSTANTS function (separate definitions)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -3511,7 +4394,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT DATA function (single value)', () => { + it.skip('should work for GET DIRECT DATA function (single value)', () => { const vars = readInlineModel(` x = GET DIRECT DATA('g_data.csv', ',', 'A', 'B13') ~~| y = x * 10 ~~| @@ -3529,7 +4412,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT DATA function (1D)', () => { + it.skip('should work for GET DIRECT DATA function (1D)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| x[DimA] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| @@ -3557,7 +4440,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT DATA function (2D with separate definitions)', () => { + it.skip('should work for GET DIRECT DATA function (2D with separate definitions)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -3592,7 +4475,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for GET DIRECT LOOKUPS function', () => { + it.skip('should work for GET DIRECT LOOKUPS function', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| @@ -3634,7 +4517,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for IF THEN ELSE function', () => { + it.skip('should work for IF THEN ELSE function', () => { const vars = readInlineModel(` x = 100 ~~| y = 2 ~~| @@ -3656,7 +4539,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for INITIAL function', () => { + it.skip('should work for INITIAL function', () => { const vars = readInlineModel(` x = Time * 2 ~~| y = INITIAL(x) ~~| @@ -3676,7 +4559,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for INTEG function', () => { + it.skip('should work for INTEG function', () => { const vars = readInlineModel(` x = Time * 2 ~~| init = 5 ~~| @@ -3702,7 +4585,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for INTEG function (with nested function calls)', () => { + it.skip('should work for INTEG function (with nested function calls)', () => { const vars = readInlineModel(` x = Time * 2 ~~| init = 5 ~~| @@ -3728,7 +4611,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for LOOKUP BACKWARD function (with lookup defined explicitly)', () => { + it.skip('should work for LOOKUP BACKWARD function (with lookup defined explicitly)', () => { const vars = readInlineModel(` x( (0,0),(2,1.3) ) ~~| y = LOOKUP BACKWARD(x, 1) ~~| @@ -3751,7 +4634,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for LOOKUP BACKWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { + it.skip('should work for LOOKUP BACKWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| @@ -3793,7 +4676,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for LOOKUP FORWARD function (with lookup defined explicitly)', () => { + it.skip('should work for LOOKUP FORWARD function (with lookup defined explicitly)', () => { const vars = readInlineModel(` x( (0,0),(2,1.3) ) ~~| y = LOOKUP FORWARD(x, 1) ~~| @@ -3816,7 +4699,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for LOOKUP FORWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { + it.skip('should work for LOOKUP FORWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| @@ -3858,7 +4741,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for LOOKUP INVERT function (with lookup defined explicitly)', () => { + it.skip('should work for LOOKUP INVERT function (with lookup defined explicitly)', () => { const vars = readInlineModel(` x( (0,0),(2,1.3) ) ~~| y = LOOKUP INVERT(x, 1) ~~| @@ -3881,7 +4764,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for LOOKUP INVERT function (with lookup defined using GET DIRECT LOOKUPS)', () => { + it.skip('should work for LOOKUP INVERT function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| @@ -3923,7 +4806,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for MAX function', () => { + it.skip('should work for MAX function', () => { const vars = readInlineModel(` a = 10 ~~| b = 20 ~~| @@ -3946,7 +4829,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for MIN function', () => { + it.skip('should work for MIN function', () => { const vars = readInlineModel(` a = 10 ~~| b = 20 ~~| @@ -3969,7 +4852,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for MODULO function', () => { + it.skip('should work for MODULO function', () => { const vars = readInlineModel(` a = 20 ~~| b = 10 ~~| @@ -3994,7 +4877,7 @@ describe('readEquations (from XMILE model)', () => { // TODO: Add a variant where discount rate is defined as (x+1) (old reader did not include // parens and might generate incorrect equation) - it('should work for NPV function', () => { + it.skip('should work for NPV function', () => { const vars = readInlineModel(` stream = 100 ~~| discount rate = 10 ~~| @@ -4052,7 +4935,7 @@ describe('readEquations (from XMILE model)', () => { // TODO it.skip('should work for NPV function (with subscripted variables)', () => {}) - it('should work for PULSE function', () => { + it.skip('should work for PULSE function', () => { const vars = readInlineModel(` start = 10 ~~| width = 20 ~~| @@ -4075,7 +4958,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SAMPLE IF TRUE function', () => { + it.skip('should work for SAMPLE IF TRUE function', () => { const vars = readInlineModel(` initial = 10 ~~| input = 5 ~~| @@ -4105,7 +4988,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH function', () => { + it.skip('should work for SMOOTH function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -4137,7 +5020,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH function (with subscripted input and subscripted delay)', () => { + it.skip('should work for SMOOTH function (with subscripted input and subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -4181,7 +5064,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH function (with subscripted input and non-subscripted delay)', () => { + it.skip('should work for SMOOTH function (with subscripted input and non-subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -4217,7 +5100,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTHI function', () => { + it.skip('should work for SMOOTHI function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -4254,7 +5137,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTHI function (with subscripted variables)', () => { + it.skip('should work for SMOOTHI function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (delay, init) and apply-to-all (input) // variables here to cover both cases const vars = readInlineModel(` @@ -4345,7 +5228,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTHI function (with separated variables using subdimension)', () => { + it.skip('should work for SMOOTHI function (with separated variables using subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -4446,7 +5329,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3 function', () => { + it.skip('should work for SMOOTH3 function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -4496,7 +5379,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3 function (when nested in another function)', () => { + it.skip('should work for SMOOTH3 function (when nested in another function)', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -4547,7 +5430,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3 function (with subscripted input and subscripted delay)', () => { + it.skip('should work for SMOOTH3 function (with subscripted input and subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -4611,7 +5494,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3 function (with subscripted input and non-subscripted delay)', () => { + it.skip('should work for SMOOTH3 function (with subscripted input and non-subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -4667,7 +5550,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3I function', () => { + it.skip('should work for SMOOTH3I function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -4714,7 +5597,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3I function (with nested function calls)', () => { + it.skip('should work for SMOOTH3I function (with nested function calls)', () => { const vars = readInlineModel(` x = 1 ~~| input = x + PULSE(10, 10) ~~| @@ -4775,7 +5658,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3I function (with subscripted variables)', () => { + it.skip('should work for SMOOTH3I function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases const vars = readInlineModel(` @@ -4886,7 +5769,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3I function (with subscripted input and non-subscripted delay)', () => { + it.skip('should work for SMOOTH3I function (with subscripted input and non-subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -4939,7 +5822,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for SMOOTH3I function (with separated variables using subdimension)', () => { + it.skip('should work for SMOOTH3I function (with separated variables using subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -5080,7 +5963,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for TREND function', () => { + it.skip('should work for TREND function', () => { const vars = readInlineModel(` input = 1 ~~| avg time = 2 ~~| @@ -5123,7 +6006,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for TREND function (with subscripted variables)', () => { + it.skip('should work for TREND function (with subscripted variables)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 1, 2 ~~| @@ -5191,7 +6074,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for WITH LOOKUP function', () => { + it.skip('should work for WITH LOOKUP function', () => { const vars = readInlineModel(` y = WITH LOOKUP(Time, ( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) )) ~~| `) @@ -5223,7 +6106,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "active_initial" model', () => { + it.skip('should work for XMILE "active_initial" model', () => { const vars = readSubscriptsAndEquations('active_initial') expect(vars).toEqual([ v('Capacity', 'INTEG(Capacity Adjustment Rate,Target Capacity)', { @@ -5292,7 +6175,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "allocate" model', () => { + it.skip('should work for XMILE "allocate" model', () => { const vars = readSubscriptsAndEquations('allocate') expect(vars).toEqual([ v( @@ -5440,13 +6323,13 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work for XMILE "arrays" model', () => { + // it.skip('should work for XMILE "arrays" model', () => { // const vars = readSubscriptsAndEquations('arrays') // logPrettyVars(vars) // expect(vars).toEqual([]) // }) - it('should work for XMILE "delay" model', () => { + it.skip('should work for XMILE "delay" model', () => { const vars = readSubscriptsAndEquations('delay') expect(vars).toEqual([ v('input', 'STEP(10,0)-STEP(10,4)', { @@ -6193,7 +7076,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "delayfixed" model', () => { + it.skip('should work for XMILE "delayfixed" model', () => { const vars = readSubscriptsAndEquations('delayfixed') expect(vars).toEqual([ v('receiving', 'DELAY FIXED(shipping,shipping time,shipping)', { @@ -6303,7 +7186,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "delayfixed2" model', () => { + it.skip('should work for XMILE "delayfixed2" model', () => { const vars = readSubscriptsAndEquations('delayfixed2') expect(vars).toEqual([ v('input1', '10*TIME+10', { @@ -6355,7 +7238,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "depreciate" model', () => { + it.skip('should work for XMILE "depreciate" model', () => { const vars = readSubscriptsAndEquations('depreciate') expect(vars).toEqual([ v('dtime', '20', { @@ -6406,7 +7289,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "directconst" model', () => { + it.skip('should work for XMILE "directconst" model', () => { const vars = readSubscriptsAndEquations('directconst') expect(vars).toEqual([ v('a', "GET DIRECT CONSTANTS('data/a.csv',',','B2')", { @@ -6502,7 +7385,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "directdata" model', () => { + it.skip('should work for XMILE "directdata" model', () => { const vars = readSubscriptsAndEquations('directdata') expect(vars).toEqual([ v('a[DimA]', "GET DIRECT DATA('data.xlsx','A Data','A','B2')", { @@ -6695,7 +7578,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "directlookups" model', () => { + it.skip('should work for XMILE "directlookups" model', () => { const vars = readSubscriptsAndEquations('directlookups') expect(vars).toEqual([ v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { @@ -6832,7 +7715,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "directsubs" model', () => { + it.skip('should work for XMILE "directsubs" model', () => { const vars = readSubscriptsAndEquations('directsubs') expect(vars).toEqual([ v('a[DimA]', '10,20,30', { @@ -6899,7 +7782,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "elmcount" model', () => { + it.skip('should work for XMILE "elmcount" model', () => { const vars = readSubscriptsAndEquations('elmcount') expect(vars).toEqual([ v('a', 'ELMCOUNT(DimA)', { @@ -6935,7 +7818,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "except" model', () => { + it.skip('should work for XMILE "except" model', () => { const vars = readSubscriptsAndEquations('except') expect(vars).toEqual([ v('a[DimA]', '1', { @@ -7360,6 +8243,12 @@ describe('readEquations (from XMILE model)', () => { subscripts: ['_e2', '_f2', '_g2', '_h1'], varType: 'const' }), + v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { + refId: '_except4[_e2,_f2,_g2,_h2]', + separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], + subscripts: ['_e2', '_f2', '_g2', '_h2'], + varType: 'const' + }), v('input', '0', { refId: '_input', varType: 'const' @@ -7418,13 +8307,13 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work for XMILE "except2" model', () => { + // it.skip('should work for XMILE "except2" model', () => { // const vars = readSubscriptsAndEquations('except2') // logPrettyVars(vars) // expect(vars).toEqual([]) // }) - it('should work for XMILE "extdata" model', () => { + it.skip('should work for XMILE "extdata" model', () => { const vars = readSubscriptsAndEquations('extdata') expect(vars).toEqual([ v('Simple 1', '', { @@ -7575,13 +8464,13 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work for XMILE "flatten" model', () => { + // it.skip('should work for XMILE "flatten" model', () => { // const vars = readSubscriptsAndEquations('flatten') // logPrettyVars(vars) // expect(vars).toEqual([]) // }) - it('should work for XMILE "gamma_ln" model', () => { + it.skip('should work for XMILE "gamma_ln" model', () => { const vars = readSubscriptsAndEquations('gamma_ln') expect(vars).toEqual([ v('a', 'GAMMA LN(10)', { @@ -7619,7 +8508,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "getdata" model', () => { + it.skip('should work for XMILE "getdata" model', () => { const vars = readSubscriptsAndEquations('getdata') expect(vars).toEqual([ v('Values[DimA]', '', { @@ -7879,7 +8768,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "index" model', () => { + it.skip('should work for XMILE "index" model', () => { const vars = readSubscriptsAndEquations('index') expect(vars).toEqual([ v('a[DimA]', 'b[DimA]+10', { @@ -7935,7 +8824,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "initial" model', () => { + it.skip('should work for XMILE "initial" model', () => { const vars = readSubscriptsAndEquations('initial') expect(vars).toEqual([ v('amplitude', '2', { @@ -7985,7 +8874,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "interleaved" model', () => { + it.skip('should work for XMILE "interleaved" model', () => { const vars = readSubscriptsAndEquations('interleaved') expect(vars).toEqual([ v('x', '1', { @@ -8034,7 +8923,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "longeqns" model', () => { + it.skip('should work for XMILE "longeqns" model', () => { const vars = readSubscriptsAndEquations('longeqns') expect(vars).toEqual([ v('EqnA[DimX,DimY]', '1', { @@ -8084,7 +8973,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "lookup" model', () => { + it.skip('should work for XMILE "lookup" model', () => { const vars = readSubscriptsAndEquations('lookup') expect(vars).toEqual([ v('a', '', { @@ -8416,7 +9305,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "mapping" model', () => { + it.skip('should work for XMILE "mapping" model', () => { const vars = readSubscriptsAndEquations('mapping') expect(vars).toEqual([ v('b[DimB]', '1,2', { @@ -8482,7 +9371,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "multimap" model', () => { + it.skip('should work for XMILE "multimap" model', () => { const vars = readSubscriptsAndEquations('multimap') expect(vars).toEqual([ v('a[DimA]', '1,2,3', { @@ -8536,7 +9425,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "npv" model', () => { + it.skip('should work for XMILE "npv" model', () => { const vars = readSubscriptsAndEquations('npv') expect(vars).toEqual([ v('investment', '100', { @@ -8622,7 +9511,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "power" model', () => { + it.skip('should work for XMILE "power" model', () => { const vars = readSubscriptsAndEquations('power') expect(vars).toEqual([ v('base', '2', { @@ -8667,13 +9556,13 @@ describe('readEquations (from XMILE model)', () => { ]) }) - // it('should work for XMILE "preprocess" model', () => { + // it.skip('should work for XMILE "preprocess" model', () => { // const vars = readSubscriptsAndEquations('preprocess') // logPrettyVars(vars) // expect(vars).toEqual([]) // }) - it('should work for XMILE "prune" model', () => { + it.skip('should work for XMILE "prune" model', () => { const vars = readSubscriptsAndEquations('prune') expect(vars).toEqual([ v('Simple 1', '', { @@ -9077,7 +9966,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "quantum" model', () => { + it.skip('should work for XMILE "quantum" model', () => { const vars = readSubscriptsAndEquations('quantum') expect(vars).toEqual([ v('a', 'QUANTUM(1.9,1.0)', { @@ -9135,7 +10024,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "ref" model', () => { + it.skip('should work for XMILE "ref" model', () => { const vars = readSubscriptsAndEquations('ref') expect(vars).toEqual([ v('ecc[t1]', 'ce[t1]+1', { @@ -9195,7 +10084,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "sample" model', () => { + it.skip('should work for XMILE "sample" model', () => { const vars = readSubscriptsAndEquations('sample') expect(vars).toEqual([ v('a', 'SAMPLE IF TRUE(MODULO(Time,5)=0,Time,0)', { @@ -9258,7 +10147,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "sir" model', () => { + it.skip('should work for XMILE "sir" model', () => { const vars = readSubscriptsAndEquations('sir') expect(vars).toEqual([ v('Infectious Population I', 'INTEG(Infection Rate-Recovery Rate,1)', { @@ -9362,7 +10251,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "smooth" model', () => { + it.skip('should work for XMILE "smooth" model', () => { const vars = readSubscriptsAndEquations('smooth') expect(vars).toEqual([ v('input', '3+PULSE(10,10)', { @@ -9887,7 +10776,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "smooth3" model', () => { + it.skip('should work for XMILE "smooth3" model', () => { const vars = readSubscriptsAndEquations('smooth3') expect(vars).toEqual([ v('a', '1', { @@ -10091,7 +10980,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "specialchars" model', () => { + it.skip('should work for XMILE "specialchars" model', () => { const vars = readSubscriptsAndEquations('specialchars') expect(vars).toEqual([ v('DOLLAR SIGN$', '1', { @@ -10133,7 +11022,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "subalias" model', () => { + it.skip('should work for XMILE "subalias" model', () => { const vars = readSubscriptsAndEquations('subalias') expect(vars).toEqual([ v('e[DimE]', '10,20,30', { @@ -10195,7 +11084,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "subscript" model', () => { + it.skip('should work for XMILE "subscript" model', () => { const vars = readSubscriptsAndEquations('subscript') expect(vars).toEqual([ v('b[DimB]', '1,2,3', { @@ -10350,7 +11239,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "sum" model', () => { + it.skip('should work for XMILE "sum" model', () => { const vars = readSubscriptsAndEquations('sum') expect(vars).toEqual([ v('a[DimA]', '1,2,3', { @@ -10690,7 +11579,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "sumif" model', () => { + it.skip('should work for XMILE "sumif" model', () => { const vars = readSubscriptsAndEquations('sumif') expect(vars).toEqual([ v('A Values[DimA]', '', { @@ -10735,7 +11624,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "trend" model', () => { + it.skip('should work for XMILE "trend" model', () => { const vars = readSubscriptsAndEquations('trend') expect(vars).toEqual([ v('description', '0', { @@ -10815,7 +11704,7 @@ describe('readEquations (from XMILE model)', () => { ]) }) - it('should work for XMILE "vector" model', () => { + it.skip('should work for XMILE "vector" model', () => { const vars = readSubscriptsAndEquations('vector') expect(vars).toEqual([ v('ASCENDING', '1', { From 2032ae5bff813bc21456b55a96b876b7a7610cce Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 19 Aug 2025 18:02:34 -0700 Subject: [PATCH 19/77] test: convert a batch of readEquations tests for XMILE --- .../src/model/read-equations-xmile.spec.ts | 1949 +++++++++++++---- 1 file changed, 1557 insertions(+), 392 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 774984a3..2f44fe28 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -1762,13 +1762,13 @@ ${elements.join('\n')} ]) }) - it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { - // TODO - }) + // it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) - it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { - // TODO - }) + // it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) it('should work when RHS variable is apply-to-all (2D) and is accessed with one normal dimension and one marked dimension that resolve to same family', () => { // Equivalent Vensim model for reference: @@ -1822,13 +1822,13 @@ ${elements.join('\n')} ]) }) - it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { - // TODO - }) + // it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) - it.skip('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { - // TODO - }) + // it.skip('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) }) describe('when LHS is NON-apply-to-all (1D)', () => { @@ -2087,44 +2087,77 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because it's failing; not sure if this is a valid construct in XMILE it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] :EXCEPT: [A1] = x[DimA] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // y[DimA] :EXCEPT: [A1] = x[DimA] ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + + x[DimA] + + + x[DimA] + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), // expandedRefIdsForVar(_y[_a2], '_x', ['_dima']) // -> ['_x[_a2]'] - v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + v('y[A2]', 'x[DimA]', { refId: '_y[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], references: ['_x[_a2]'] }), // expandedRefIdsForVar(_y[_a3], '_x', ['_dima']) // -> ['_x[_a3]'] - v('y[DimA]:EXCEPT:[A1]', 'x[DimA]', { + v('y[A3]', 'x[DimA]', { refId: '_y[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], references: ['_x[_a3]'] }) @@ -2132,43 +2165,95 @@ ${elements.join('\n')} }) // This is adapted from the "except" sample model (see equation for `k`) + // TODO: This test is skipped because it's not clear if XMILE supports mapped subdimensions it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - DimB: B1, B2 -> (DimA: SubA, A1) ~~| - a[DimA] = 1, 2, 3 ~~| - b[DimB] = 4, 5 ~~| - y[DimA] :EXCEPT: [A1] = a[DimA] + b[DimB] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A2, A3 ~~| + // DimB: B1, B2 -> (DimA: SubA, A1) ~~| + // a[DimA] = 1, 2, 3 ~~| + // b[DimB] = 4, 5 ~~| + // y[DimA] :EXCEPT: [A1] = a[DimA] + b[DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + + 4 + + + 5 + + + + + + + + a[DimA] + b[DimB] + + + a[DimA] + b[DimB] + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('a[DimA]', '1,2,3', { + v('a[A1]', '1', { refId: '_a[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('a[DimA]', '1,2,3', { + v('a[A2]', '2', { refId: '_a[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('a[DimA]', '1,2,3', { + v('a[A3]', '3', { refId: '_a[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('b[DimB]', '4,5', { + v('b[B1]', '4', { refId: '_b[_b1]', - separationDims: ['_dimb'], subscripts: ['_b1'], varType: 'const' }), - v('b[DimB]', '4,5', { + v('b[B2]', '5', { refId: '_b[_b2]', - separationDims: ['_dimb'], subscripts: ['_b2'], varType: 'const' }), @@ -2176,9 +2261,8 @@ ${elements.join('\n')} // -> ['_a[_a2]'] // expandedRefIdsForVar(_y[_a2], '_b', ['_dimb']) // -> ['_b[_b1]'] - v('y[DimA]:EXCEPT:[A1]', 'a[DimA]+b[DimB]', { + v('y[A2]', 'a[DimA]+b[DimB]', { refId: '_y[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], references: ['_a[_a2]', '_b[_b1]'] }), @@ -2186,9 +2270,8 @@ ${elements.join('\n')} // -> ['_a[_a3]'] // expandedRefIdsForVar(_y[_a3], '_b', ['_dimb']) // -> ['_b[_b1]'] - v('y[DimA]:EXCEPT:[A1]', 'a[DimA]+b[DimB]', { + v('y[A3]', 'a[DimA]+b[DimB]', { refId: '_y[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], references: ['_a[_a3]', '_b[_b1]'] }) @@ -2196,16 +2279,65 @@ ${elements.join('\n')} }) // This is adapted from the "ref" sample model (with updated naming for clarity) + // TODO: This test is skipped because it's not clear if XMILE supports this kind of mapping it.skip('should work for complex mapping example', () => { - const vars = readInlineModel(` - Target: (t1-t3) ~~| - tNext: (t2-t3) -> tPrev ~~| - tPrev: (t1-t2) -> tNext ~~| - x[t1] = y[t1] + 1 ~~| - x[tNext] = y[tNext] + 1 ~~| - y[t1] = 1 ~~| - y[tNext] = x[tPrev] + 1 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // Target: (t1-t3) ~~| + // tNext: (t2-t3) -> tPrev ~~| + // tPrev: (t1-t2) -> tNext ~~| + // x[t1] = y[t1] + 1 ~~| + // x[tNext] = y[tNext] + 1 ~~| + // y[t1] = 1 ~~| + // y[tNext] = x[tPrev] + 1 ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + y[t1] + 1 + + + y[t2] + 1 + + + y[t3] + 1 + + + + + + + + 1 + + + x[t1] + 1 + + + x[t2] + 1 + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[t1]', 'y[t1]+1', { refId: '_x[_t1]', @@ -2258,23 +2390,54 @@ ${elements.join('\n')} // // TODO // }) - it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB <-> DimA ~~| - x[DimA] = 1, 2 ~~| - y[DimA, DimB] = x[DimA] + x[DimB] ~~| - `) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB <-> DimA ~~| + // x[DimA] = 1, 2 ~~| + // y[DimA, DimB] = x[DimA] + x[DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + + x[DimA] + x[DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -2298,13 +2461,42 @@ ${elements.join('\n')} // // TODO // }) - it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1 ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) + it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA, DimB] = 1 ~~| + // y[DimB, DimA] = x[DimA, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + + + + + x[DimA, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,DimB]', '1', { refId: '_x', @@ -2321,58 +2513,123 @@ ${elements.join('\n')} ]) }) - it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB <-> DimA ~~| - x[DimA, DimB] = 1 ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) - expect(vars).toEqual([ - v('x[DimA,DimB]', '1', { - refId: '_x', - subscripts: ['_dima', '_dimb'], - varType: 'const' - }), - // expandedRefIdsForVar(_y[_dimb,_dima], '_x', ['_dima', '_dimb']) - // -> ['_x'] - v('y[DimB,DimA]', 'x[DimA,DimB]', { - refId: '_y', - subscripts: ['_dimb', '_dima'], - references: ['_x'] - }) + it('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB <-> DimA ~~| + // x[DimA, DimB] = 1 ~~| + // y[DimB, DimA] = x[DimA, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + + + + + x[DimA, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) + expect(vars).toEqual([ + v('x[DimA,DimB]', '1', { + refId: '_x', + subscripts: ['_dima', '_dimb'], + varType: 'const' + }), + // expandedRefIdsForVar(_y[_dimb,_dima], '_x', ['_dima', '_dimb']) + // -> ['_x'] + v('y[DimB,DimA]', 'x[DimA,DimB]', { + refId: '_y', + subscripts: ['_dimb', '_dima'], + references: ['_x'] + }) ]) }) - it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1, 2; 3, 4; ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA, DimB] = 1, 2; 3, 4; ~~| + // y[DimB, DimA] = x[DimA, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + x[DimA, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A1,B1]', '1', { refId: '_x[_a1,_b1]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a1', '_b1'], varType: 'const' }), - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A1,B2]', '2', { refId: '_x[_a1,_b2]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a1', '_b2'], varType: 'const' }), - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A2,B1]', '3', { refId: '_x[_a2,_b1]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a2', '_b1'], varType: 'const' }), - v('x[DimA,DimB]', '1,2;3,4;', { + v('x[A2,B2]', '4', { refId: '_x[_a2,_b2]', - separationDims: ['_dima', '_dimb'], subscripts: ['_a2', '_b2'], varType: 'const' }), @@ -2386,14 +2643,48 @@ ${elements.join('\n')} ]) }) - it.skip('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[A1, DimB] = 1 ~~| - x[A2, DimB] = 2 ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) + it('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[A1, DimB] = 1 ~~| + // x[A2, DimB] = 2 ~~| + // y[DimB, DimA] = x[DimA, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + + + + + + x[DimA, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[A1,DimB]', '1', { refId: '_x[_a1,_dimb]', @@ -2426,14 +2717,48 @@ ${elements.join('\n')} describe('when LHS is NON-apply-to-all (2D)', () => { // The LHS in this test is partially separated (expanded only for first dimension position) - it.skip('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1 ~~| - y[SubA, DimB] = x[SubA, DimB] ~~| - `) + it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA, DimB] = 1 ~~| + // y[SubA, DimB] = x[SubA, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + + + + + x[SubA, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,DimB]', '1', { refId: '_x', @@ -2460,24 +2785,60 @@ ${elements.join('\n')} }) // This test is based on the example from #179 (simplified to use subdimensions to ensure separation) - it.skip('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A2 ~~| - SubB <-> SubA ~~| - x[SubA] = 1, 2 ~~| - y[SubA, SubB] = x[SubA] + x[SubB] ~~| - `) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A1, A2 ~~| + // SubB <-> SubA ~~| + // x[SubA] = 1, 2 ~~| + // y[SubA, SubB] = x[SubA] + x[SubB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + + x[SubA] + x[SubB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[SubA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_suba'], subscripts: ['_a1'], varType: 'const' }), - v('x[SubA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_suba'], subscripts: ['_a2'], varType: 'const' }), @@ -2527,14 +2888,47 @@ ${elements.join('\n')} // This test is based on the example from #179 (simplified to use subdimensions to ensure separation). // It is similar to the previous one, except in this one, `x` is apply-to-all (and refers to the parent // dimension). - it.skip('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A2 ~~| - SubB <-> SubA ~~| - x[DimA] = 1 ~~| - y[SubA, SubB] = x[SubA] + x[SubB] ~~| - `) + it('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A1, A2 ~~| + // SubB <-> SubA ~~| + // x[DimA] = 1 ~~| + // y[SubA, SubB] = x[SubA] + x[SubB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + + x[SubA] + x[SubB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1', { refId: '_x', @@ -2575,14 +2969,49 @@ ${elements.join('\n')} }) describe('when LHS is apply-to-all (3D)', () => { - it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, DimC, DimB] = 1 ~~| - y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| - `) + it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // DimC: C1, C2 ~~| + // x[DimA, DimC, DimB] = 1 ~~| + // y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + + + + + + x[DimA, DimC, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,DimC,DimB]', '1', { refId: '_x', @@ -2599,15 +3028,55 @@ ${elements.join('\n')} ]) }) - it.skip('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, C1, DimB] = 1 ~~| - x[DimA, C2, DimB] = 2 ~~| - y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| - `) + it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // DimC: C1, C2 ~~| + // x[DimA, C1, DimB] = 1 ~~| + // x[DimA, C2, DimB] = 2 ~~| + // y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + + + 1 + + + 2 + + + + + + + + + x[DimA, DimC, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,C1,DimB]', '1', { refId: '_x[_dima,_c1,_dimb]', @@ -2631,15 +3100,55 @@ ${elements.join('\n')} }) describe('when LHS is NON-apply-to-all (3D)', () => { - it.skip('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2, C3 ~~| - SubC: C2, C3 ~~| - x[DimA, DimC, DimB] = 1 ~~| - y[SubC, DimB, DimA] = x[DimA, SubC, DimB] ~~| - `) + it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // DimC: C1, C2, C3 ~~| + // SubC: C2, C3 ~~| + // x[DimA, DimC, DimB] = 1 ~~| + // y[SubC, DimB, DimA] = x[DimA, SubC, DimB] ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + + + + + + x[DimA, SubC, DimB] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA,DimC,DimB]', '1', { refId: '_x', @@ -2666,27 +3175,103 @@ ${elements.join('\n')} }) // This test is based on the example from #278 - it.skip('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { - const vars = readInlineModel(` - Scenario: S1, S2 ~~| - Sector: A1, A2, A3 ~~| - Supplying Sector: A1, A2 -> Producing Sector ~~| - Producing Sector: A1, A2 -> Supplying Sector ~~| - x[A1,A1] = 101 ~~| - x[A1,A2] = 102 ~~| - x[A1,A3] = 103 ~~| - x[A2,A1] = 201 ~~| - x[A2,A2] = 202 ~~| - x[A2,A3] = 203 ~~| - x[A3,A1] = 301 ~~| - x[A3,A2] = 302 ~~| - x[A3,A3] = 303 ~~| - y[S1] = 1000 ~~| - y[S2] = 2000 ~~| - z[Scenario, Supplying Sector, Producing Sector] = - y[Scenario] + x[Supplying Sector, Producing Sector] - ~~| - `) + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // Scenario: S1, S2 ~~| + // Sector: A1, A2, A3 ~~| + // Supplying Sector: A1, A2 -> Producing Sector ~~| + // Producing Sector: A1, A2 -> Supplying Sector ~~| + // x[A1,A1] = 101 ~~| + // x[A1,A2] = 102 ~~| + // x[A1,A3] = 103 ~~| + // x[A2,A1] = 201 ~~| + // x[A2,A2] = 202 ~~| + // x[A2,A3] = 203 ~~| + // x[A3,A1] = 301 ~~| + // x[A3,A2] = 302 ~~| + // x[A3,A3] = 303 ~~| + // y[S1] = 1000 ~~| + // y[S2] = 2000 ~~| + // z[Scenario, Supplying Sector, Producing Sector] = + // y[Scenario] + x[Supplying Sector, Producing Sector] + // ~~| + // `) + + const xmileDims = `\ + + + + + + + + + + + + + + + + + +` + const xmileVars = `\ + + + + + + + 101 + + + 102 + + + 103 + + + 201 + + + 202 + + + 203 + + + 301 + + + 302 + + + 303 + + + + + + + + 1000 + + + 2000 + + + + + + + + + y[Scenario] + x[Supplying Sector, Producing Sector] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[A1,A1]', '101', { refId: '_x[_a1,_a1]', @@ -2789,12 +3374,26 @@ ${elements.join('\n')} // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. // - it.skip('should work for ACTIVE INITIAL function', () => { - const vars = readInlineModel(` - Initial Target Capacity = 1 ~~| - Capacity = 2 ~~| - Target Capacity = ACTIVE INITIAL(Capacity, Initial Target Capacity) ~~| - `) + it('should work for ACTIVE INITIAL function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // Initial Target Capacity = 1 ~~| + // Capacity = 2 ~~| + // Target Capacity = ACTIVE INITIAL(Capacity, Initial Target Capacity) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + ACTIVE INITIAL(Capacity, Initial Target Capacity) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('Initial Target Capacity', '1', { refId: '_initial_target_capacity', @@ -2814,16 +3413,71 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see + // if the Vensim `ALLOCATE AVAILABLE` is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 2D pp, non-subscripted avail)', () => { - const vars = readInlineModel(` - branch: Boston, Dayton ~~| - pprofile: ptype, ppriority ~~| - supply available = 200 ~~| - demand[branch] = 500,300 ~~| - priority[Boston,pprofile] = 1,5 ~~| - priority[Dayton,pprofile] = 1,7 ~~| - shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // branch: Boston, Dayton ~~| + // pprofile: ptype, ppriority ~~| + // supply available = 200 ~~| + // demand[branch] = 500,300 ~~| + // priority[Boston,pprofile] = 1,5 ~~| + // priority[Dayton,pprofile] = 1,7 ~~| + // shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + 200 + + + + + + + 500 + + + 300 + + + + + + + + + 1 + + + 5 + + + 1 + + + 7 + + + + + + + ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('supply available', '200', { refId: '_supply_available', @@ -2882,6 +3536,8 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see + // if the Vensim `ALLOCATE AVAILABLE` is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 3D pp with specific first subscript, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| @@ -3038,6 +3694,8 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see + // if the Vensim `ALLOCATE AVAILABLE` is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 2D pp, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| @@ -3150,6 +3808,8 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see + // if the Vensim `ALLOCATE AVAILABLE` is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 3D pp, 1D avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| @@ -3320,11 +3980,22 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY1 function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = DELAY1(x, 5) ~~| - `) + it('should work for DELAY1 function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = DELAY1(x, 5) ~~| + // `) + + const xmileVars = `\ + + 1 + + + DELAY1(x, 5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -3353,12 +4024,26 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY1I function', () => { - const vars = readInlineModel(` - x = 1 ~~| - init = 2 ~~| - y = DELAY1I(x, 5, init) ~~| - `) + it('should work for DELAY1I function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // init = 2 ~~| + // y = DELAY1I(x, 5, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + DELAY1I(x, 5, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -3391,50 +4076,96 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY1I function (with subscripted variables)', () => { + it('should work for DELAY1I function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - input[DimA] = 10, 20, 30 ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 0 ~~| - y[DimA] = DELAY1I(input[DimA], delay[DimA], init[DimA]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // input[DimA] = 10, 20, 30 ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 0 ~~| + // y[DimA] = DELAY1I(input[DimA], delay[DimA], init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + 30 + + + + + + + + 1 + + + 2 + + + 3 + + + + + + + 0 + + + + + + DELAY1I(input[DimA], delay[DimA], init[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '10,20,30', { + v('input[A1]', '10', { refId: '_input[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('input[DimA]', '10,20,30', { + v('input[A2]', '20', { refId: '_input[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('input[DimA]', '10,20,30', { + v('input[A3]', '30', { refId: '_input[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A1]', '1', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A2]', '2', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A3]', '3', { refId: '_delay[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), @@ -3469,50 +4200,110 @@ ${elements.join('\n')} ]) }) + // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions + // for y[A1] and y[A2] instead of a single definition for y[SubA] it.skip('should work for DELAY1I function (with separated variables using subdimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - input[DimA] = 10, 20, 30 ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 0 ~~| - y[A1] = 5 ~~| - y[SubA] = DELAY1I(input[SubA], delay[SubA], init[SubA]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A2, A3 ~~| + // input[DimA] = 10, 20, 30 ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 0 ~~| + // y[A1] = 5 ~~| + // y[SubA] = DELAY1I(input[SubA], delay[SubA], init[SubA]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + 30 + + + + + + + + 1 + + + 2 + + + 3 + + + + + + + 0 + + + + + + + 5 + + + DELAY1I(input[A2], delay[A2], init[A2]) + + + DELAY1I(input[A3], delay[A3], init[A3]) + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '10,20,30', { + v('input[A1]', '10', { refId: '_input[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('input[DimA]', '10,20,30', { + v('input[A2]', '20', { refId: '_input[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('input[DimA]', '10,20,30', { + v('input[A3]', '30', { refId: '_input[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A1]', '1', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A2]', '2', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A3]', '3', { refId: '_delay[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), @@ -3526,7 +4317,7 @@ ${elements.join('\n')} subscripts: ['_a1'], varType: 'const' }), - v('y[SubA]', 'DELAY1I(input[SubA],delay[SubA],init[SubA])', { + v('y[A2]', 'DELAY1I(input[A2],delay[A2],init[A2])', { delayTimeVarName: '__aux1', delayVarRefId: '__level_y_1[_a2]', refId: '_y[_a2]', @@ -3534,7 +4325,7 @@ ${elements.join('\n')} separationDims: ['_suba'], subscripts: ['_a2'] }), - v('y[SubA]', 'DELAY1I(input[SubA],delay[SubA],init[SubA])', { + v('y[A3]', 'DELAY1I(input[A3],delay[A3],init[A3])', { delayTimeVarName: '__aux2', delayVarRefId: '__level_y_1[_a3]', refId: '_y[_a3]', @@ -3577,12 +4368,26 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY3 function', () => { - const vars = readInlineModel(` - input = 1 ~~| - delay = 2 ~~| - y = DELAY3(input, delay) ~~| - `) + it('should work for DELAY3 function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = DELAY3(input, delay) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + DELAY3(input, delay) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('input', '1', { refId: '_input', @@ -3648,13 +4453,30 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY3I function', () => { - const vars = readInlineModel(` - input = 1 ~~| - delay = 2 ~~| - init = 3 ~~| - y = DELAY3I(input, delay, init) ~~| - `) + it('should work for DELAY3I function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // init = 3 ~~| + // y = DELAY3I(input, delay, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + 3 + + + DELAY3I(input, delay, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('input', '1', { refId: '_input', @@ -3724,13 +4546,30 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY3I function (with nested function calls)', () => { - const vars = readInlineModel(` - input = 1 ~~| - delay = 2 ~~| - init = 3 ~~| - y = DELAY3I(MIN(0, input), MAX(0, delay), ABS(init)) ~~| - `) + it('should work for DELAY3I function (with nested function calls)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // init = 3 ~~| + // y = DELAY3I(MIN(0, input), MAX(0, delay), ABS(init)) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + 3 + + + DELAY3I(MIN(0, input), MAX(0, delay), ABS(init)) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('input', '1', { refId: '_input', @@ -3804,50 +4643,96 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY3I function (with subscripted variables)', () => { + it('should work for DELAY3I function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - input[DimA] = 10, 20, 30 ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 0 ~~| - y[DimA] = DELAY3I(input[DimA], delay[DimA], init[DimA]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // input[DimA] = 10, 20, 30 ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 0 ~~| + // y[DimA] = DELAY3I(input[DimA], delay[DimA], init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + 30 + + + + + + + + 1 + + + 2 + + + 3 + + + + + + + 0 + + + + + + DELAY3I(input[DimA], delay[DimA], init[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '10,20,30', { + v('input[A1]', '10', { refId: '_input[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('input[DimA]', '10,20,30', { + v('input[A2]', '20', { refId: '_input[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('input[DimA]', '10,20,30', { + v('input[A3]', '30', { refId: '_input[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A1]', '1', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A2]', '2', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A3]', '3', { refId: '_delay[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), @@ -3920,16 +4805,82 @@ ${elements.join('\n')} ]) }) + // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions + // for y[A1] and y[A2] instead of a single definition for y[SubA] it.skip('should work for DELAY3I function (with separated variables using subdimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - input[DimA] = 10, 20, 30 ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 0 ~~| - y[A1] = 5 ~~| - y[SubA] = DELAY3I(input[SubA], delay[SubA], init[SubA]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A2, A3 ~~| + // input[DimA] = 10, 20, 30 ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 0 ~~| + // y[A1] = 5 ~~| + // y[SubA] = DELAY3I(input[SubA], delay[SubA], init[SubA]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + 30 + + + + + + + + 1 + + + 2 + + + 3 + + + + + + + 0 + + + + + + + 5 + + + DELAY3I(input[A2], delay[A2], init[A2]) + + + DELAY3I(input[A3], delay[A3], init[A3]) + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('input[DimA]', '10,20,30', { refId: '_input[_a1]', @@ -4104,14 +5055,34 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DELAY FIXED function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = 2 ~~| - delay = y + 5 ~~| - init = 3 ~~| - z = DELAY FIXED(x, delay, init) ~~| - `) + it('should work for DELAY FIXED function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = 2 ~~| + // delay = y + 5 ~~| + // init = 3 ~~| + // z = DELAY FIXED(x, delay, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + y + 5 + + + 3 + + + DELAY FIXED(x, delay, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -4142,16 +5113,42 @@ ${elements.join('\n')} ]) }) - it.skip('should work for DEPRECIATE STRAIGHTLINE function', () => { - const vars = readInlineModel(` - dtime = 20 ~~| - fisc = 1 ~~| - init = 5 ~~| - Capacity Cost = 1000 ~~| - New Capacity = 2000 ~~| - stream = Capacity Cost * New Capacity ~~| - Depreciated Amount = DEPRECIATE STRAIGHTLINE(stream, dtime, fisc, init) ~~| - `) + it('should work for DEPRECIATE STRAIGHTLINE function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // dtime = 20 ~~| + // fisc = 1 ~~| + // init = 5 ~~| + // Capacity Cost = 1000 ~~| + // New Capacity = 2000 ~~| + // stream = Capacity Cost * New Capacity ~~| + // Depreciated Amount = DEPRECIATE STRAIGHTLINE(stream, dtime, fisc, init) ~~| + // `) + + const xmileVars = `\ + + 20 + + + 1 + + + 5 + + + 1000 + + + 2000 + + + Capacity Cost * New Capacity + + + DEPRECIATE STRAIGHTLINE(stream, dtime, fisc, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('dtime', '20', { refId: '_dtime', @@ -4189,11 +5186,22 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GAME function (no dimensions)', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = GAME(x) ~~| - `) + it('should work for GAME function (no dimensions)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = GAME(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + GAME(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -4214,22 +5222,48 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GAME function (1D)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y[DimA] = GAME(x[DimA]) ~~| - `) + it('should work for GAME function (1D)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1, 2 ~~| + // y[DimA] = GAME(x[DimA]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + GAME(x[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -4250,36 +5284,76 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GAME function (2D)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - a[DimA] = 1, 2 ~~| - b[DimB] = 1, 2 ~~| - y[DimA, DimB] = GAME(a[DimA] + b[DimB]) ~~| - `) + it('should work for GAME function (2D)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // a[DimA] = 1, 2 ~~| + // b[DimB] = 1, 2 ~~| + // y[DimA, DimB] = GAME(a[DimA] + b[DimB]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + + 1 + + + 2 + + + + + + + + GAME(a[DimA] + b[DimB]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('a[DimA]', '1,2', { + v('a[A1]', '1', { refId: '_a[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('a[DimA]', '1,2', { + v('a[A2]', '2', { refId: '_a[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('b[DimB]', '1,2', { + v('b[B1]', '1', { refId: '_b[_b1]', - separationDims: ['_dimb'], subscripts: ['_b1'], varType: 'const' }), - v('b[DimB]', '1,2', { + v('b[B2]', '2', { refId: '_b[_b2]', - separationDims: ['_dimb'], subscripts: ['_b2'], varType: 'const' }), @@ -4300,11 +5374,22 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GAMMA LN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = GAMMA LN(x) ~~| - `) + it('should work for GAMMA LN function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = GAMMA LN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + GAMMA LN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', @@ -4318,10 +5403,18 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GET DIRECT CONSTANTS function (single value)', () => { - const vars = readInlineModel(` - x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| - `) + it('should work for GET DIRECT CONSTANTS function (single value)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| + // `) + + const xmileVars = `\ + + GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', "GET DIRECT CONSTANTS('data/a.csv',',','B2')", { directConstArgs: { file: 'data/a.csv', tab: ',', startCell: 'B2' }, @@ -4331,11 +5424,29 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GET DIRECT CONSTANTS function (1D)', () => { - const vars = readInlineModel(` - DimB: B1, B2, B3 ~~| - x[DimB] = GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') ~~| - `) + it('should work for GET DIRECT CONSTANTS function (1D)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimB: B1, B2, B3 ~~| + // x[DimB] = GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') ~~| + // `) + + const xmileDims = `\ + + + + + +` + const xmileVars = `\ + + + + + GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimB]', "GET DIRECT CONSTANTS('data/b.csv',',','B2*')", { directConstArgs: { file: 'data/b.csv', tab: ',', startCell: 'B2*' }, @@ -4346,12 +5457,35 @@ ${elements.join('\n')} ]) }) - it.skip('should work for GET DIRECT CONSTANTS function (2D)', () => { - const vars = readInlineModel(` - DimB: B1, B2, B3 ~~| - DimC: C1, C2 ~~| - x[DimB, DimC] = GET DIRECT CONSTANTS('data/c.csv', ',', 'B2') ~~| - `) + it('should work for GET DIRECT CONSTANTS function (2D)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimB: B1, B2, B3 ~~| + // DimC: C1, C2 ~~| + // x[DimB, DimC] = GET DIRECT CONSTANTS('data/c.csv', ',', 'B2') ~~| + // `) + + const xmileDims = `\ + + + + + + + + + +` + const xmileVars = `\ + + + + + + GET DIRECT CONSTANTS('data/c.csv', ',', 'B2') +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimB,DimC]', "GET DIRECT CONSTANTS('data/c.csv',',','B2')", { directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2' }, @@ -4362,6 +5496,8 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the GET DIRECT CONSTANTS function + // and it would need to be updated to not use :EXCEPT: it.skip('should work for GET DIRECT CONSTANTS function (separate definitions)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -4394,6 +5530,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the GET DIRECT DATA function it.skip('should work for GET DIRECT DATA function (single value)', () => { const vars = readInlineModel(` x = GET DIRECT DATA('g_data.csv', ',', 'A', 'B13') ~~| @@ -4440,6 +5577,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the GET DIRECT DATA function it.skip('should work for GET DIRECT DATA function (2D with separate definitions)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| @@ -4475,6 +5613,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the GET DIRECT DATA function it.skip('should work for GET DIRECT LOOKUPS function', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -4517,12 +5656,26 @@ ${elements.join('\n')} ]) }) - it.skip('should work for IF THEN ELSE function', () => { - const vars = readInlineModel(` - x = 100 ~~| - y = 2 ~~| - z = IF THEN ELSE(Time > x, 1, y) ~~| - `) + it('should work for IF THEN ELSE function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 100 ~~| + // y = 2 ~~| + // z = IF THEN ELSE(Time > x, 1, y) ~~| + // `) + + const xmileVars = `\ + + 100 + + + 2 + + + IF Time>x THEN 1 ELSE y +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '100', { refId: '_x', @@ -4539,11 +5692,23 @@ ${elements.join('\n')} ]) }) - it.skip('should work for INITIAL function', () => { - const vars = readInlineModel(` - x = Time * 2 ~~| - y = INITIAL(x) ~~| - `) + // TODO: Stella calls this function INIT, not INITIAL + it('should work for INITIAL function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = Time * 2 ~~| + // y = INITIAL(x) ~~| + // `) + + const xmileVars = `\ + + Time*2 + + + INITIAL(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', 'Time*2', { refId: '_x', From 8227deda2616c4f150c48fab4095605be40a983e Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 19 Aug 2025 18:29:54 -0700 Subject: [PATCH 20/77] test: convert the last batch and remove all remaining tests that read from sample models --- .../src/model/read-equations-xmile.spec.ts | 6319 +---------------- 1 file changed, 302 insertions(+), 6017 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 2f44fe28..7c7b9bfb 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -78,9 +78,9 @@ function readInlineModel( return vars.filter(v => v.varName !== '_time') } -function readSubscriptsAndEquations(modelName: string): Variable[] { - return readSubscriptsAndEquationsFromSource({ modelName }) -} +// function readSubscriptsAndEquations(modelName: string): Variable[] { +// return readSubscriptsAndEquationsFromSource({ modelName }) +// } function v(lhs: string, formula: string, overrides?: Partial): Variable { const variable = new VariableImpl() @@ -5613,7 +5613,7 @@ ${elements.join('\n')} ]) }) - // TODO: This test is skipped because Stella doesn't include the GET DIRECT DATA function + // TODO: This test is skipped because Stella doesn't include the GET DIRECT LOOKUPS function it.skip('should work for GET DIRECT LOOKUPS function', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -5692,7 +5692,7 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function INIT, not INITIAL + // TODO: Stella calls this function INIT instead of INITIAL it('should work for INITIAL function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` @@ -5724,12 +5724,27 @@ ${elements.join('\n')} ]) }) - it.skip('should work for INTEG function', () => { - const vars = readInlineModel(` - x = Time * 2 ~~| - init = 5 ~~| - y = INTEG(x, init) ~~| - `) + // TODO: Not sure what the equivalent function is called in Stella + it('should work for INTEG function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = Time * 2 ~~| + // init = 5 ~~| + // y = INTEG(x, init) ~~| + // `) + + const xmileVars = `\ + + Time*2 + + + 5 + + + INTEG(x,init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', 'Time*2', { refId: '_x', @@ -5750,12 +5765,27 @@ ${elements.join('\n')} ]) }) - it.skip('should work for INTEG function (with nested function calls)', () => { - const vars = readInlineModel(` - x = Time * 2 ~~| - init = 5 ~~| - y = INTEG(ABS(x), POW(init, 3)) ~~| - `) + // TODO: Not sure what the equivalent function is called in Stella + it('should work for INTEG function (with nested function calls)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = Time * 2 ~~| + // init = 5 ~~| + // y = INTEG(ABS(x), POW(init, 3)) ~~| + // `) + + const xmileVars = `\ + + Time*2 + + + 5 + + + INTEG(ABS(x),POW(init,3)) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', 'Time*2', { refId: '_x', @@ -5776,6 +5806,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't appear to include the LOOKUP BACKWARD function it.skip('should work for LOOKUP BACKWARD function (with lookup defined explicitly)', () => { const vars = readInlineModel(` x( (0,0),(2,1.3) ) ~~| @@ -5799,6 +5830,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't appear to include the LOOKUP BACKWARD function it.skip('should work for LOOKUP BACKWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -5841,6 +5873,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't appear to include the LOOKUP FORWARD function it.skip('should work for LOOKUP FORWARD function (with lookup defined explicitly)', () => { const vars = readInlineModel(` x( (0,0),(2,1.3) ) ~~| @@ -5864,6 +5897,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't appear to include the LOOKUP FORWARD function it.skip('should work for LOOKUP FORWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -5906,11 +5940,24 @@ ${elements.join('\n')} ]) }) - it.skip('should work for LOOKUP INVERT function (with lookup defined explicitly)', () => { - const vars = readInlineModel(` - x( (0,0),(2,1.3) ) ~~| - y = LOOKUP INVERT(x, 1) ~~| - `) + // TODO: Stella calls this function LOOKUPINV instead of LOOKUP INVERT + it('should work for LOOKUP INVERT function (with lookup defined explicitly)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x( (0,0),(2,1.3) ) ~~| + // y = LOOKUP INVERT(x, 1) ~~| + // `) + + const xmileVars = `\ + + 0,2 + 0,1.3 + + + LOOKUP INVERT(x,1) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '', { refId: '_x', @@ -5929,6 +5976,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the GET DIRECT LOOKUPS function it.skip('should work for LOOKUP INVERT function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -5971,12 +6019,26 @@ ${elements.join('\n')} ]) }) - it.skip('should work for MAX function', () => { - const vars = readInlineModel(` - a = 10 ~~| - b = 20 ~~| - y = MAX(a, b) ~~| - `) + it('should work for MAX function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // a = 10 ~~| + // b = 20 ~~| + // y = MAX(a, b) ~~| + // `) + + const xmileVars = `\ + + 10 + + + 20 + + + MAX(a,b) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('a', '10', { refId: '_a', @@ -5994,12 +6056,26 @@ ${elements.join('\n')} ]) }) - it.skip('should work for MIN function', () => { - const vars = readInlineModel(` - a = 10 ~~| - b = 20 ~~| - y = MIN(a, b) ~~| - `) + it('should work for MIN function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // a = 10 ~~| + // b = 20 ~~| + // y = MIN(a, b) ~~| + // `) + + const xmileVars = `\ + + 10 + + + 20 + + + MIN(a,b) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('a', '10', { refId: '_a', @@ -6017,12 +6093,27 @@ ${elements.join('\n')} ]) }) - it.skip('should work for MODULO function', () => { - const vars = readInlineModel(` - a = 20 ~~| - b = 10 ~~| - y = MODULO(a, b) ~~| - `) + // TODO: Stella calls this function MOD instead of MODULO + it('should work for MODULO function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // a = 20 ~~| + // b = 10 ~~| + // y = MODULO(a, b) ~~| + // `) + + const xmileVars = `\ + + 20 + + + 10 + + + MODULO(a,b) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('a', '20', { refId: '_a', @@ -6042,14 +6133,35 @@ ${elements.join('\n')} // TODO: Add a variant where discount rate is defined as (x+1) (old reader did not include // parens and might generate incorrect equation) - it.skip('should work for NPV function', () => { - const vars = readInlineModel(` - stream = 100 ~~| - discount rate = 10 ~~| - init = 0 ~~| - factor = 2 ~~| - y = NPV(stream, discount rate, init, factor) ~~| - `) + // TODO: Stella's NPV function takes 2 or 3 arguments, but Vensim's takes 4 arguments + it('should work for NPV function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // stream = 100 ~~| + // discount rate = 10 ~~| + // init = 0 ~~| + // factor = 2 ~~| + // y = NPV(stream, discount rate, init, factor) ~~| + // `) + + const xmileVars = `\ + + 100 + + + 10 + + + 0 + + + 2 + + + NPV(stream,discount rate,init,factor) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('stream', '100', { refId: '_stream', @@ -6100,12 +6212,27 @@ ${elements.join('\n')} // TODO it.skip('should work for NPV function (with subscripted variables)', () => {}) - it.skip('should work for PULSE function', () => { - const vars = readInlineModel(` - start = 10 ~~| - width = 20 ~~| - y = PULSE(start, width) ~~| - `) + // TODO: Stella's PULSE function takes 1 or 3 arguments, but Vensim's takes 2 arguments + it('should work for PULSE function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // start = 10 ~~| + // width = 20 ~~| + // y = PULSE(start, width) ~~| + // `) + + const xmileVars = `\ + + 10 + + + 20 + + + PULSE(start,width) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('start', '10', { refId: '_start', @@ -6123,6 +6250,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the SAMPLE IF TRUE function it.skip('should work for SAMPLE IF TRUE function', () => { const vars = readInlineModel(` initial = 10 ~~| @@ -6229,13 +6357,33 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH1 instead of SMOOTH, skipping this test for now it.skip('should work for SMOOTH function (with subscripted input and non-subscripted delay)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y[DimA] = SMOOTH(input[DimA], delay) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 3 + PULSE(10, 10) ~~| + // delay = 2 ~~| + // y[DimA] = SMOOTH(input[DimA], delay) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + 3+PULSE(10,10) + + + 2 + + + SMOOTH(input[DimA],delay) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('input[DimA]', '3+PULSE(10,10)', { refId: '_input', @@ -6265,6 +6413,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH1 instead of SMOOTH, skipping this test for now it.skip('should work for SMOOTHI function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| @@ -6302,6 +6451,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH1 instead of SMOOTHI, skipping this test for now it.skip('should work for SMOOTHI function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (delay, init) and apply-to-all (input) // variables here to cover both cases @@ -6393,6 +6543,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH1 instead of SMOOTHI, skipping this test for now it.skip('should work for SMOOTHI function (with separated variables using subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -6494,6 +6645,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now it.skip('should work for SMOOTH3 function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| @@ -6544,6 +6696,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now it.skip('should work for SMOOTH3 function (when nested in another function)', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| @@ -6595,6 +6748,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now it.skip('should work for SMOOTH3 function (with subscripted input and subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| @@ -6659,6 +6813,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now it.skip('should work for SMOOTH3 function (with subscripted input and non-subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| @@ -6715,6 +6870,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now it.skip('should work for SMOOTH3I function', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| @@ -6762,6 +6918,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now it.skip('should work for SMOOTH3I function (with nested function calls)', () => { const vars = readInlineModel(` x = 1 ~~| @@ -6823,6 +6980,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now it.skip('should work for SMOOTH3I function (with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases @@ -6934,6 +7092,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now it.skip('should work for SMOOTH3I function (with subscripted input and non-subscripted delay)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| @@ -6987,6 +7146,7 @@ ${elements.join('\n')} ]) }) + // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now it.skip('should work for SMOOTH3I function (with separated variables using subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| @@ -7128,13 +7288,30 @@ ${elements.join('\n')} ]) }) - it.skip('should work for TREND function', () => { - const vars = readInlineModel(` - input = 1 ~~| - avg time = 2 ~~| - init = 3 ~~| - y = TREND(input, avg time, init) ~~| - `) + it('should work for TREND function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // avg time = 2 ~~| + // init = 3 ~~| + // y = TREND(input, avg time, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + 3 + + + TREND(input, avg time, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('input', '1', { refId: '_input', @@ -7171,36 +7348,77 @@ ${elements.join('\n')} ]) }) - it.skip('should work for TREND function (with subscripted variables)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 1, 2 ~~| - avg time[DimA] = 3, 4 ~~| - init[DimA] = 5 ~~| - y[DimA] = TREND(input[DimA], avg time[DimA], init[DimA]) ~~| - `) + it('should work for TREND function (with subscripted variables)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1, 2 ~~| + // avg time[DimA] = 3, 4 ~~| + // init[DimA] = 5 ~~| + // y[DimA] = TREND(input[DimA], avg time[DimA], init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + + 3 + + + 4 + + + + + + + 5 + + + + + + TREND(input[DimA], avg time[DimA], init[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '1,2', { + v('input[A1]', '1', { refId: '_input[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('input[DimA]', '1,2', { + v('input[A2]', '2', { refId: '_input[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('avg time[DimA]', '3,4', { + v('avg time[A1]', '3', { refId: '_avg_time[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('avg time[DimA]', '3,4', { + v('avg time[A2]', '4', { refId: '_avg_time[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), @@ -7239,6 +7457,7 @@ ${elements.join('\n')} ]) }) + // TODO: This test is skipped because Stella doesn't include the WITH LOOKUP function it.skip('should work for WITH LOOKUP function', () => { const vars = readInlineModel(` y = WITH LOOKUP(Time, ( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) )) ~~| @@ -7270,5938 +7489,4 @@ ${elements.join('\n')} }) ]) }) - - it.skip('should work for XMILE "active_initial" model', () => { - const vars = readSubscriptsAndEquations('active_initial') - expect(vars).toEqual([ - v('Capacity', 'INTEG(Capacity Adjustment Rate,Target Capacity)', { - hasInitValue: true, - initReferences: ['_target_capacity'], - refId: '_capacity', - referencedFunctionNames: ['__integ'], - references: ['_capacity_adjustment_rate'], - varType: 'level' - }), - v('Capacity Adjustment Rate', '(Target Capacity-Capacity)/Capacity Adjustment Time', { - refId: '_capacity_adjustment_rate', - references: ['_target_capacity', '_capacity', '_capacity_adjustment_time'] - }), - v('Capacity Adjustment Time', '10', { - refId: '_capacity_adjustment_time', - varType: 'const' - }), - v('Capacity Utilization', 'Production/Capacity', { - refId: '_capacity_utilization', - references: ['_production', '_capacity'] - }), - v('Initial Target Capacity', '100', { - refId: '_initial_target_capacity', - varType: 'const' - }), - v('Production', '100+STEP(100,10)', { - refId: '_production', - referencedFunctionNames: ['__step'] - }), - v('Target Capacity', 'ACTIVE INITIAL(Capacity*Utilization Adjustment,Initial Target Capacity)', { - hasInitValue: true, - initReferences: ['_initial_target_capacity'], - refId: '_target_capacity', - referencedFunctionNames: ['__active_initial'], - references: ['_capacity', '_utilization_adjustment'] - }), - v('Utilization Adjustment', 'Capacity Utilization^Utilization Sensitivity', { - refId: '_utilization_adjustment', - references: ['_capacity_utilization', '_utilization_sensitivity'] - }), - v('Utilization Sensitivity', '1', { - refId: '_utilization_sensitivity', - varType: 'const' - }), - v('FINAL TIME', '100', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "allocate" model', () => { - const vars = readSubscriptsAndEquations('allocate') - expect(vars).toEqual([ - v( - 'shipments[region]', - 'ALLOCATE AVAILABLE(demand[region],priority vector[region,ptype],total supply available)', - { - refId: '_shipments', - referencedFunctionNames: ['__allocate_available'], - references: [ - '_demand[_boston]', - '_demand[_dayton]', - '_demand[_fresno]', - '_priority_vector[_region,_ptype]', - '_priority_vector[_region,_ppriority]', - '_priority_vector[_region,_pwidth]', - '_priority_vector[_region,_pextra]', - '_total_supply_available' - ], - subscripts: ['_region'] - } - ), - v( - 'total supply available', - 'IF THEN ELSE(integer supply,INTEGER(Initial Supply+(Final Supply-Initial Supply)*(Time-INITIAL TIME)/(FINAL TIME-INITIAL TIME)),Initial Supply+(Final Supply-Initial Supply)*(Time-INITIAL TIME)/(FINAL TIME-INITIAL TIME))', - { - refId: '_total_supply_available', - referencedFunctionNames: ['__integer'], - references: ['_integer_supply', '_initial_supply', '_final_supply', '_time', '_initial_time', '_final_time'] - } - ), - v('integer supply', '0', { - refId: '_integer_supply', - varType: 'const' - }), - v('total demand', 'SUM(demand[region!])', { - refId: '_total_demand', - referencedFunctionNames: ['__sum'], - references: ['_demand[_boston]', '_demand[_dayton]', '_demand[_fresno]'] - }), - v('total shipments', 'SUM(shipments[region!])', { - refId: '_total_shipments', - referencedFunctionNames: ['__sum'], - references: ['_shipments'] - }), - v('extra', '1', { - refId: '_extra', - varType: 'const' - }), - v('priority[region]', '1,2,3', { - refId: '_priority[_boston]', - separationDims: ['_region'], - subscripts: ['_boston'], - varType: 'const' - }), - v('priority[region]', '1,2,3', { - refId: '_priority[_dayton]', - separationDims: ['_region'], - subscripts: ['_dayton'], - varType: 'const' - }), - v('priority[region]', '1,2,3', { - refId: '_priority[_fresno]', - separationDims: ['_region'], - subscripts: ['_fresno'], - varType: 'const' - }), - v('Final Supply', '10', { - refId: '_final_supply', - varType: 'const' - }), - v('Initial Supply', '0', { - refId: '_initial_supply', - varType: 'const' - }), - v('integer type', '0', { - refId: '_integer_type', - varType: 'const' - }), - v('demand[region]', '3,2,4', { - refId: '_demand[_boston]', - separationDims: ['_region'], - subscripts: ['_boston'], - varType: 'const' - }), - v('demand[region]', '3,2,4', { - refId: '_demand[_dayton]', - separationDims: ['_region'], - subscripts: ['_dayton'], - varType: 'const' - }), - v('demand[region]', '3,2,4', { - refId: '_demand[_fresno]', - separationDims: ['_region'], - subscripts: ['_fresno'], - varType: 'const' - }), - v('priority vector[region,ptype]', 'priority type+integer type', { - refId: '_priority_vector[_region,_ptype]', - references: ['_priority_type', '_integer_type'], - subscripts: ['_region', '_ptype'] - }), - v('priority vector[region,ppriority]', 'priority[region]', { - refId: '_priority_vector[_region,_ppriority]', - references: ['_priority[_boston]', '_priority[_dayton]', '_priority[_fresno]'], - subscripts: ['_region', '_ppriority'] - }), - v('priority vector[region,pwidth]', 'priority width', { - refId: '_priority_vector[_region,_pwidth]', - references: ['_priority_width'], - subscripts: ['_region', '_pwidth'] - }), - v('priority vector[region,pextra]', 'extra', { - refId: '_priority_vector[_region,_pextra]', - references: ['_extra'], - subscripts: ['_region', '_pextra'] - }), - v('priority width', '1', { - refId: '_priority_width', - varType: 'const' - }), - v('priority type', '3', { - refId: '_priority_type', - varType: 'const' - }), - v('FINAL TIME', '12', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '0.125', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - // it.skip('should work for XMILE "arrays" model', () => { - // const vars = readSubscriptsAndEquations('arrays') - // logPrettyVars(vars) - // expect(vars).toEqual([]) - // }) - - it.skip('should work for XMILE "delay" model', () => { - const vars = readSubscriptsAndEquations('delay') - expect(vars).toEqual([ - v('input', 'STEP(10,0)-STEP(10,4)', { - refId: '_input', - referencedFunctionNames: ['__step'] - }), - v('delay', '5', { - refId: '_delay', - varType: 'const' - }), - v('init 1', '0', { - refId: '_init_1', - varType: 'const' - }), - v('input a[DimA]', '10,20,30', { - refId: '_input_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('input a[DimA]', '10,20,30', { - refId: '_input_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('input a[DimA]', '10,20,30', { - refId: '_input_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('delay a[DimA]', '1,2,3', { - refId: '_delay_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('delay a[DimA]', '1,2,3', { - refId: '_delay_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('delay a[DimA]', '1,2,3', { - refId: '_delay_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('init a[DimA]', '0', { - refId: '_init_a', - subscripts: ['_dima'], - varType: 'const' - }), - v('input 2[SubA]', '20,30', { - refId: '_input_2[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('input 2[SubA]', '20,30', { - refId: '_input_2[_a3]', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('delay 2', '5', { - refId: '_delay_2', - varType: 'const' - }), - v('init 2[SubA]', '0', { - refId: '_init_2[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('init 2[SubA]', '0', { - refId: '_init_2[_a3]', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('k', '42', { - refId: '_k', - varType: 'const' - }), - v('d1', 'DELAY1(input,delay)', { - delayTimeVarName: '__aux1', - delayVarRefId: '__level1', - refId: '_d1', - references: ['__level1', '__aux1'] - }), - v('d2[DimA]', 'DELAY1I(input a[DimA],delay,init 1)', { - delayTimeVarName: '__aux2', - delayVarRefId: '__level2', - refId: '_d2', - references: ['__level2', '__aux2[_dima]'], - subscripts: ['_dima'] - }), - v('d3[DimA]', 'DELAY1I(input,delay a[DimA],init 1)', { - delayTimeVarName: '__aux3', - delayVarRefId: '__level3', - refId: '_d3', - references: ['__level3', '__aux3[_dima]'], - subscripts: ['_dima'] - }), - v('d4[DimA]', 'DELAY1I(input,delay,init a[DimA])', { - delayTimeVarName: '__aux4', - delayVarRefId: '__level4', - refId: '_d4', - references: ['__level4', '__aux4[_dima]'], - subscripts: ['_dima'] - }), - v('d5[DimA]', 'DELAY1I(input a[DimA],delay a[DimA],init a[DimA])', { - delayTimeVarName: '__aux5', - delayVarRefId: '__level5', - refId: '_d5', - references: ['__level5', '__aux5[_dima]'], - subscripts: ['_dima'] - }), - v('d6[SubA]', 'DELAY1I(input 2[SubA],delay 2,init 2[SubA])', { - delayTimeVarName: '__aux6', - delayVarRefId: '__level_d6_1[_a2]', - refId: '_d6[_a2]', - references: ['__level_d6_1[_a2]', '__aux6[_a2]'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('d6[SubA]', 'DELAY1I(input 2[SubA],delay 2,init 2[SubA])', { - delayTimeVarName: '__aux7', - delayVarRefId: '__level_d6_1[_a3]', - refId: '_d6[_a3]', - references: ['__level_d6_1[_a3]', '__aux7[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('d7', 'DELAY3(input,delay)', { - delayTimeVarName: '__aux11', - delayVarRefId: '__level8', - refId: '_d7', - references: ['__level8', '__level7', '__level6', '__aux11'] - }), - v('d8[DimA]', 'DELAY3(input,delay a[DimA])', { - delayTimeVarName: '__aux15', - delayVarRefId: '__level11', - refId: '_d8', - references: ['__level11', '__level10', '__level9', '__aux15[_dima]'], - subscripts: ['_dima'] - }), - v('d9[SubA]', 'DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { - delayTimeVarName: '__aux_d9_4', - delayVarRefId: '__level_d9_3[_a2]', - refId: '_d9[_a2]', - references: ['__level_d9_3[_a2]', '__level_d9_2[_a2]', '__level_d9_1[_a2]', '__aux_d9_4[_a2]'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('d9[SubA]', 'DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { - delayTimeVarName: '__aux_d9_4', - delayVarRefId: '__level_d9_3[_a3]', - refId: '_d9[_a3]', - references: ['__level_d9_3[_a3]', '__level_d9_2[_a3]', '__level_d9_1[_a3]', '__aux_d9_4[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('d10', 'k*DELAY3(input,delay)', { - delayTimeVarName: '__aux19', - delayVarRefId: '__level14', - refId: '_d10', - references: ['_k', '__level14', '__level13', '__level12', '__aux19'] - }), - v('d11[DimA]', 'k*DELAY3(input,delay a[DimA])', { - delayTimeVarName: '__aux23', - delayVarRefId: '__level17', - refId: '_d11', - references: ['_k', '__level17', '__level16', '__level15', '__aux23[_dima]'], - subscripts: ['_dima'] - }), - v('d12[SubA]', 'k*DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { - delayTimeVarName: '__aux_d12_4', - delayVarRefId: '__level_d12_3[_a2]', - refId: '_d12[_a2]', - references: ['_k', '__level_d12_3[_a2]', '__level_d12_2[_a2]', '__level_d12_1[_a2]', '__aux_d12_4[_a2]'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('d12[SubA]', 'k*DELAY3I(input 2[SubA],delay 2,init 2[SubA])', { - delayTimeVarName: '__aux_d12_4', - delayVarRefId: '__level_d12_3[_a3]', - refId: '_d12[_a3]', - references: ['_k', '__level_d12_3[_a3]', '__level_d12_2[_a3]', '__level_d12_1[_a3]', '__aux_d12_4[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_level1', 'INTEG(input-d1,input*delay)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level1', - referencedFunctionNames: ['__integ'], - references: ['_input', '_d1'], - varType: 'level' - }), - v('_aux1', 'delay', { - includeInOutput: false, - refId: '__aux1', - references: ['_delay'] - }), - v('_level2[DimA]', 'INTEG(input a[DimA]-d2[DimA],init 1*delay)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_1', '_delay'], - refId: '__level2', - referencedFunctionNames: ['__integ'], - references: ['_input_a[_a1]', '_input_a[_a2]', '_input_a[_a3]', '_d2'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_aux2[DimA]', 'delay', { - includeInOutput: false, - refId: '__aux2', - references: ['_delay'], - subscripts: ['_dima'] - }), - v('_level3[DimA]', 'INTEG(input-d3[DimA],init 1*delay a[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_1', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level3', - referencedFunctionNames: ['__integ'], - references: ['_input', '_d3'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_aux3[DimA]', 'delay a[DimA]', { - includeInOutput: false, - refId: '__aux3', - references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_level4[DimA]', 'INTEG(input-d4[DimA],init a[DimA]*delay)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_a', '_delay'], - refId: '__level4', - referencedFunctionNames: ['__integ'], - references: ['_input', '_d4'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_aux4[DimA]', 'delay', { - includeInOutput: false, - refId: '__aux4', - references: ['_delay'], - subscripts: ['_dima'] - }), - v('_level5[DimA]', 'INTEG(input a[DimA]-d5[DimA],init a[DimA]*delay a[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_a', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level5', - referencedFunctionNames: ['__integ'], - references: ['_input_a[_a1]', '_input_a[_a2]', '_input_a[_a3]', '_d5'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_aux5[DimA]', 'delay a[DimA]', { - includeInOutput: false, - refId: '__aux5', - references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_level_d6_1[a2]', 'INTEG(input 2[a2]-d6[a2],init 2[a2]*delay 2)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d6_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '_d6[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_aux6[a2]', 'delay 2', { - includeInOutput: false, - refId: '__aux6[_a2]', - references: ['_delay_2'], - subscripts: ['_a2'] - }), - v('_level_d6_1[a3]', 'INTEG(input 2[a3]-d6[a3],init 2[a3]*delay 2)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d6_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '_d6[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_aux7[a3]', 'delay 2', { - includeInOutput: false, - refId: '__aux7[_a3]', - references: ['_delay_2'], - subscripts: ['_a3'] - }), - v('_level8', 'INTEG(_aux9-_aux10,input*((delay)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level8', - referencedFunctionNames: ['__integ'], - references: ['__aux9', '__aux10'], - varType: 'level' - }), - v('_level7', 'INTEG(_aux8-_aux9,input*((delay)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level7', - referencedFunctionNames: ['__integ'], - references: ['__aux8', '__aux9'], - varType: 'level' - }), - v('_level6', 'INTEG(input-_aux8,input*((delay)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level6', - referencedFunctionNames: ['__integ'], - references: ['_input', '__aux8'], - varType: 'level' - }), - v('_aux8', '_level6/((delay)/3)', { - includeInOutput: false, - refId: '__aux8', - references: ['__level6', '_delay'] - }), - v('_aux9', '_level7/((delay)/3)', { - includeInOutput: false, - refId: '__aux9', - references: ['__level7', '_delay'] - }), - v('_aux10', '_level8/((delay)/3)', { - includeInOutput: false, - refId: '__aux10', - references: ['__level8', '_delay'] - }), - v('_aux11', '((delay)/3)', { - includeInOutput: false, - refId: '__aux11', - references: ['_delay'] - }), - v('_level11[DimA]', 'INTEG(_aux13[DimA]-_aux14[DimA],input*((delay a[DimA])/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level11', - referencedFunctionNames: ['__integ'], - references: ['__aux13', '__aux14'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level10[DimA]', 'INTEG(_aux12[DimA]-_aux13[DimA],input*((delay a[DimA])/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level10', - referencedFunctionNames: ['__integ'], - references: ['__aux12', '__aux13'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level9[DimA]', 'INTEG(input-_aux12[DimA],input*((delay a[DimA])/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level9', - referencedFunctionNames: ['__integ'], - references: ['_input', '__aux12'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_aux12[DimA]', '_level9[DimA]/((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux12', - references: ['__level9', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_aux13[DimA]', '_level10[DimA]/((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux13', - references: ['__level10', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_aux14[DimA]', '_level11[DimA]/((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux14', - references: ['__level11', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_aux15[DimA]', '((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux15', - references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_level_d9_3[a2]', 'INTEG(_aux_d9_2[a2]-_aux_d9_3[a2],init 2[a2]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d9_3[_a2]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d9_2[_a2]', '__aux_d9_3[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_d9_2[a2]', 'INTEG(_aux_d9_1[a2]-_aux_d9_2[a2],init 2[a2]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d9_2[_a2]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d9_1[_a2]', '__aux_d9_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_d9_1[a2]', 'INTEG(input 2[a2]-_aux_d9_1[a2],init 2[a2]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d9_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '__aux_d9_1[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_aux_d9_1[a2]', '_level_d9_1[a2]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_1[_a2]', - references: ['__level_d9_1[_a2]', '_delay_2'], - subscripts: ['_a2'] - }), - v('_aux_d9_2[a2]', '_level_d9_2[a2]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_2[_a2]', - references: ['__level_d9_2[_a2]', '_delay_2'], - subscripts: ['_a2'] - }), - v('_aux_d9_3[a2]', '_level_d9_3[a2]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_3[_a2]', - references: ['__level_d9_3[_a2]', '_delay_2'], - subscripts: ['_a2'] - }), - v('_aux_d9_4[a2]', '((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_4[_a2]', - references: ['_delay_2'], - subscripts: ['_a2'] - }), - v('_level_d9_3[a3]', 'INTEG(_aux_d9_2[a3]-_aux_d9_3[a3],init 2[a3]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d9_3[_a3]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d9_2[_a3]', '__aux_d9_3[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_d9_2[a3]', 'INTEG(_aux_d9_1[a3]-_aux_d9_2[a3],init 2[a3]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d9_2[_a3]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d9_1[_a3]', '__aux_d9_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_d9_1[a3]', 'INTEG(input 2[a3]-_aux_d9_1[a3],init 2[a3]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d9_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '__aux_d9_1[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_aux_d9_1[a3]', '_level_d9_1[a3]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_1[_a3]', - references: ['__level_d9_1[_a3]', '_delay_2'], - subscripts: ['_a3'] - }), - v('_aux_d9_2[a3]', '_level_d9_2[a3]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_2[_a3]', - references: ['__level_d9_2[_a3]', '_delay_2'], - subscripts: ['_a3'] - }), - v('_aux_d9_3[a3]', '_level_d9_3[a3]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_3[_a3]', - references: ['__level_d9_3[_a3]', '_delay_2'], - subscripts: ['_a3'] - }), - v('_aux_d9_4[a3]', '((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d9_4[_a3]', - references: ['_delay_2'], - subscripts: ['_a3'] - }), - v('_level14', 'INTEG(_aux17-_aux18,input*((delay)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level14', - referencedFunctionNames: ['__integ'], - references: ['__aux17', '__aux18'], - varType: 'level' - }), - v('_level13', 'INTEG(_aux16-_aux17,input*((delay)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level13', - referencedFunctionNames: ['__integ'], - references: ['__aux16', '__aux17'], - varType: 'level' - }), - v('_level12', 'INTEG(input-_aux16,input*((delay)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay'], - refId: '__level12', - referencedFunctionNames: ['__integ'], - references: ['_input', '__aux16'], - varType: 'level' - }), - v('_aux16', '_level12/((delay)/3)', { - includeInOutput: false, - refId: '__aux16', - references: ['__level12', '_delay'] - }), - v('_aux17', '_level13/((delay)/3)', { - includeInOutput: false, - refId: '__aux17', - references: ['__level13', '_delay'] - }), - v('_aux18', '_level14/((delay)/3)', { - includeInOutput: false, - refId: '__aux18', - references: ['__level14', '_delay'] - }), - v('_aux19', '((delay)/3)', { - includeInOutput: false, - refId: '__aux19', - references: ['_delay'] - }), - v('_level17[DimA]', 'INTEG(_aux21[DimA]-_aux22[DimA],input*((delay a[DimA])/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level17', - referencedFunctionNames: ['__integ'], - references: ['__aux21', '__aux22'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level16[DimA]', 'INTEG(_aux20[DimA]-_aux21[DimA],input*((delay a[DimA])/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level16', - referencedFunctionNames: ['__integ'], - references: ['__aux20', '__aux21'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level15[DimA]', 'INTEG(input-_aux20[DimA],input*((delay a[DimA])/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - refId: '__level15', - referencedFunctionNames: ['__integ'], - references: ['_input', '__aux20'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_aux20[DimA]', '_level15[DimA]/((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux20', - references: ['__level15', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_aux21[DimA]', '_level16[DimA]/((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux21', - references: ['__level16', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_aux22[DimA]', '_level17[DimA]/((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux22', - references: ['__level17', '_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_aux23[DimA]', '((delay a[DimA])/3)', { - includeInOutput: false, - refId: '__aux23', - references: ['_delay_a[_a1]', '_delay_a[_a2]', '_delay_a[_a3]'], - subscripts: ['_dima'] - }), - v('_level_d12_3[a2]', 'INTEG(_aux_d12_2[a2]-_aux_d12_3[a2],init 2[a2]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d12_3[_a2]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d12_2[_a2]', '__aux_d12_3[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_d12_2[a2]', 'INTEG(_aux_d12_1[a2]-_aux_d12_2[a2],init 2[a2]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d12_2[_a2]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d12_1[_a2]', '__aux_d12_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_d12_1[a2]', 'INTEG(input 2[a2]-_aux_d12_1[a2],init 2[a2]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a2]', '_delay_2'], - refId: '__level_d12_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '__aux_d12_1[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_aux_d12_1[a2]', '_level_d12_1[a2]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_1[_a2]', - references: ['__level_d12_1[_a2]', '_delay_2'], - subscripts: ['_a2'] - }), - v('_aux_d12_2[a2]', '_level_d12_2[a2]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_2[_a2]', - references: ['__level_d12_2[_a2]', '_delay_2'], - subscripts: ['_a2'] - }), - v('_aux_d12_3[a2]', '_level_d12_3[a2]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_3[_a2]', - references: ['__level_d12_3[_a2]', '_delay_2'], - subscripts: ['_a2'] - }), - v('_aux_d12_4[a2]', '((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_4[_a2]', - references: ['_delay_2'], - subscripts: ['_a2'] - }), - v('_level_d12_3[a3]', 'INTEG(_aux_d12_2[a3]-_aux_d12_3[a3],init 2[a3]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d12_3[_a3]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d12_2[_a3]', '__aux_d12_3[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_d12_2[a3]', 'INTEG(_aux_d12_1[a3]-_aux_d12_2[a3],init 2[a3]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d12_2[_a3]', - referencedFunctionNames: ['__integ'], - references: ['__aux_d12_1[_a3]', '__aux_d12_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_d12_1[a3]', 'INTEG(input 2[a3]-_aux_d12_1[a3],init 2[a3]*((delay 2)/3))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_2[_a3]', '_delay_2'], - refId: '__level_d12_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '__aux_d12_1[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_aux_d12_1[a3]', '_level_d12_1[a3]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_1[_a3]', - references: ['__level_d12_1[_a3]', '_delay_2'], - subscripts: ['_a3'] - }), - v('_aux_d12_2[a3]', '_level_d12_2[a3]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_2[_a3]', - references: ['__level_d12_2[_a3]', '_delay_2'], - subscripts: ['_a3'] - }), - v('_aux_d12_3[a3]', '_level_d12_3[a3]/((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_3[_a3]', - references: ['__level_d12_3[_a3]', '_delay_2'], - subscripts: ['_a3'] - }), - v('_aux_d12_4[a3]', '((delay 2)/3)', { - includeInOutput: false, - refId: '__aux_d12_4[_a3]', - references: ['_delay_2'], - subscripts: ['_a3'] - }) - ]) - }) - - it.skip('should work for XMILE "delayfixed" model', () => { - const vars = readSubscriptsAndEquations('delayfixed') - expect(vars).toEqual([ - v('receiving', 'DELAY FIXED(shipping,shipping time,shipping)', { - fixedDelayVarName: '__fixed_delay1', - hasInitValue: true, - initReferences: ['_shipping_time', '_shipping'], - refId: '_receiving', - referencedFunctionNames: ['__delay_fixed'], - references: ['_shipping'], - varSubtype: 'fixedDelay', - varType: 'level' - }), - v('shipping', 'STEP(reference shipping rate,10)-STEP(reference shipping rate,20)', { - refId: '_shipping', - referencedFunctionNames: ['__step'], - references: ['_reference_shipping_rate'] - }), - v('shipping time', '20', { - refId: '_shipping_time', - varType: 'const' - }), - v('reference shipping rate', '1', { - refId: '_reference_shipping_rate', - varType: 'const' - }), - v('shipments in transit', 'INTEG(shipping-receiving,shipping*shipping time)', { - hasInitValue: true, - initReferences: ['_shipping', '_shipping_time'], - refId: '_shipments_in_transit', - referencedFunctionNames: ['__integ'], - references: ['_shipping', '_receiving'], - varType: 'level' - }), - v('input[A1]', '10*TIME', { - refId: '_input[_a1]', - references: ['_time'], - subscripts: ['_a1'] - }), - v('input[A2]', '20*TIME', { - refId: '_input[_a2]', - references: ['_time'], - subscripts: ['_a2'] - }), - v('input[A3]', '30*TIME', { - refId: '_input[_a3]', - references: ['_time'], - subscripts: ['_a3'] - }), - v('output[DimA]', 'DELAY FIXED(input[DimA],1,0)', { - fixedDelayVarName: '__fixed_delay2', - hasInitValue: true, - refId: '_output', - referencedFunctionNames: ['__delay_fixed'], - references: ['_input[_a1]', '_input[_a2]', '_input[_a3]'], - subscripts: ['_dima'], - varSubtype: 'fixedDelay', - varType: 'level' - }), - v('a delay time', '0', { - refId: '_a_delay_time', - varType: 'const' - }), - v('a', 'DELAY FIXED(input[A1]+1,a delay time,0)', { - fixedDelayVarName: '__fixed_delay3', - hasInitValue: true, - initReferences: ['_a_delay_time'], - refId: '_a', - referencedFunctionNames: ['__delay_fixed'], - references: ['_input[_a1]'], - varSubtype: 'fixedDelay', - varType: 'level' - }), - v('b delay time', '1', { - refId: '_b_delay_time', - varType: 'const' - }), - v('b', 'DELAY FIXED(input[A1]+1,b delay time,0)', { - fixedDelayVarName: '__fixed_delay4', - hasInitValue: true, - initReferences: ['_b_delay_time'], - refId: '_b', - referencedFunctionNames: ['__delay_fixed'], - references: ['_input[_a1]'], - varSubtype: 'fixedDelay', - varType: 'level' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '50', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "delayfixed2" model', () => { - const vars = readSubscriptsAndEquations('delayfixed2') - expect(vars).toEqual([ - v('input1', '10*TIME+10', { - refId: '_input1', - references: ['_time'] - }), - v('output1', 'DELAY FIXED(input1,1,0)', { - fixedDelayVarName: '__fixed_delay1', - hasInitValue: true, - refId: '_output1', - referencedFunctionNames: ['__delay_fixed'], - references: ['_input1'], - varSubtype: 'fixedDelay', - varType: 'level' - }), - v('input2', '10*TIME+10', { - refId: '_input2', - references: ['_time'] - }), - v('output2', 'DELAY FIXED(input2,5,0)', { - fixedDelayVarName: '__fixed_delay2', - hasInitValue: true, - refId: '_output2', - referencedFunctionNames: ['__delay_fixed'], - references: ['_input2'], - varSubtype: 'fixedDelay', - varType: 'level' - }), - v('INITIAL TIME', '10', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '20', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "depreciate" model', () => { - const vars = readSubscriptsAndEquations('depreciate') - expect(vars).toEqual([ - v('dtime', '20', { - refId: '_dtime', - varType: 'const' - }), - v('Capacity Cost', '1e+06', { - refId: '_capacity_cost', - varType: 'const' - }), - v('New Capacity', 'IF THEN ELSE(Time=2022,1000,IF THEN ELSE(Time=2026,2500,0))', { - refId: '_new_capacity', - references: ['_time'] - }), - v('str', 'Capacity Cost*New Capacity', { - refId: '_str', - references: ['_capacity_cost', '_new_capacity'] - }), - v('Depreciated Amount', 'DEPRECIATE STRAIGHTLINE(str,dtime,1,0)', { - depreciationVarName: '__depreciation1', - hasInitValue: true, - initReferences: ['_dtime'], - refId: '_depreciated_amount', - referencedFunctionNames: ['__depreciate_straightline'], - references: ['_str'], - varSubtype: 'depreciation' - }), - v('FINAL TIME', '2050', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '2020', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "directconst" model', () => { - const vars = readSubscriptsAndEquations('directconst') - expect(vars).toEqual([ - v('a', "GET DIRECT CONSTANTS('data/a.csv',',','B2')", { - directConstArgs: { file: 'data/a.csv', tab: ',', startCell: 'B2' }, - refId: '_a', - varType: 'const' - }), - v('a from named xlsx', "GET DIRECT CONSTANTS('data/a.xlsx','a','B2')", { - directConstArgs: { file: 'data/a.xlsx', tab: 'a', startCell: 'B2' }, - refId: '_a_from_named_xlsx', - varType: 'const' - }), - v('a from tagged xlsx', "GET DIRECT CONSTANTS('?a','a','B2')", { - directConstArgs: { file: '?a', tab: 'a', startCell: 'B2' }, - refId: '_a_from_tagged_xlsx', - varType: 'const' - }), - v('b[DimB]', "GET DIRECT CONSTANTS('data/b.csv',',','b2*')", { - directConstArgs: { file: 'data/b.csv', tab: ',', startCell: 'b2*' }, - refId: '_b', - subscripts: ['_dimb'], - varType: 'const' - }), - v('c[DimB,DimC]', "GET DIRECT CONSTANTS('data/c.csv',',','B2')", { - directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2' }, - refId: '_c', - subscripts: ['_dimb', '_dimc'], - varType: 'const' - }), - v('d[D1,DimB,DimC]', "GET DIRECT CONSTANTS('data/c.csv',',','B2')", { - directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2' }, - refId: '_d', - subscripts: ['_d1', '_dimb', '_dimc'], - varType: 'const' - }), - v('e[DimC,DimB]', "GET DIRECT CONSTANTS('data/c.csv',',','B2*')", { - directConstArgs: { file: 'data/c.csv', tab: ',', startCell: 'B2*' }, - refId: '_e', - subscripts: ['_dimc', '_dimb'], - varType: 'const' - }), - v('f[DimC,SubA]', "GET DIRECT CONSTANTS('data/f.csv',',','B2')", { - directConstArgs: { file: 'data/f.csv', tab: ',', startCell: 'B2' }, - refId: '_f[_dimc,_a2]', - separationDims: ['_suba'], - subscripts: ['_dimc', '_a2'], - varType: 'const' - }), - v('f[DimC,SubA]', "GET DIRECT CONSTANTS('data/f.csv',',','B2')", { - directConstArgs: { file: 'data/f.csv', tab: ',', startCell: 'B2' }, - refId: '_f[_dimc,_a3]', - separationDims: ['_suba'], - subscripts: ['_dimc', '_a3'], - varType: 'const' - }), - v('f[DimC,DimA]:EXCEPT:[DimC,SubA]', '0', { - refId: '_f[_dimc,_a1]', - separationDims: ['_dima'], - subscripts: ['_dimc', '_a1'], - varType: 'const' - }), - v('g[From DimC,To DimC]', "GET DIRECT CONSTANTS('data/g.csv',',','B2')", { - directConstArgs: { file: 'data/g.csv', tab: ',', startCell: 'B2' }, - refId: '_g', - subscripts: ['_from_dimc', '_to_dimc'], - varType: 'const' - }), - v('h', "GET DIRECT CONSTANTS('data/h.csv',',','B5')", { - directConstArgs: { file: 'data/h.csv', tab: ',', startCell: 'B5' }, - refId: '_h', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "directdata" model', () => { - const vars = readSubscriptsAndEquations('directdata') - expect(vars).toEqual([ - v('a[DimA]', "GET DIRECT DATA('data.xlsx','A Data','A','B2')", { - directDataArgs: { file: 'data.xlsx', tab: 'A Data', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'data' - }), - v('a[DimA]', "GET DIRECT DATA('data.xlsx','A Data','A','B2')", { - directDataArgs: { file: 'data.xlsx', tab: 'A Data', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'data' - }), - v('b[DimA]', 'a[DimA]*10', { - refId: '_b', - references: ['_a[_a1]', '_a[_a2]'], - subscripts: ['_dima'] - }), - v('c', "GET DIRECT DATA('?data','C Data','a','b2')", { - directDataArgs: { file: '?data', tab: 'C Data', timeRowOrCol: 'a', startCell: 'b2' }, - refId: '_c', - varType: 'data' - }), - v('d', 'c*10', { - refId: '_d', - references: ['_c'] - }), - v('e[DimA]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_e[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'data' - }), - v('e[DimA]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_e[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'data' - }), - v('f[DimA]', 'e[DimA]*10', { - refId: '_f', - references: ['_e[_a1]', '_e[_a2]'], - subscripts: ['_dima'] - }), - v('g', "GET DIRECT DATA('g_data.csv',',','A','B2')", { - directDataArgs: { file: 'g_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_g', - varType: 'data' - }), - v('h', 'g*10', { - refId: '_h', - references: ['_g'] - }), - v('i[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_i[_a1,_b1]', - separationDims: ['_dimb'], - subscripts: ['_a1', '_b1'], - varType: 'data' - }), - v('i[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_i[_a1,_b2]', - separationDims: ['_dimb'], - subscripts: ['_a1', '_b2'], - varType: 'data' - }), - v('j[A1,DimB]', 'i[A1,DimB]', { - refId: '_j', - references: ['_i[_a1,_b1]', '_i[_a1,_b2]'], - subscripts: ['_a1', '_dimb'] - }), - v('k[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_k[_a1,_b1]', - separationDims: ['_dimb'], - subscripts: ['_a1', '_b1'], - varType: 'data' - }), - v('k[A1,DimB]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_k[_a1,_b2]', - separationDims: ['_dimb'], - subscripts: ['_a1', '_b2'], - varType: 'data' - }), - v('k[A2,DimB]', '0', { - refId: '_k[_a2,_dimb]', - subscripts: ['_a2', '_dimb'], - varType: 'const' - }), - v('l[DimA,DimB]', 'k[DimA,DimB]', { - refId: '_l', - references: ['_k[_a1,_b1]', '_k[_a1,_b2]', '_k[_a2,_dimb]'], - subscripts: ['_dima', '_dimb'] - }), - v('m[DimM]', "GET DIRECT DATA('m.csv',',','1','B2')", { - directDataArgs: { file: 'm.csv', tab: ',', timeRowOrCol: '1', startCell: 'B2' }, - refId: '_m[_m1]', - separationDims: ['_dimm'], - subscripts: ['_m1'], - varType: 'data' - }), - v('m[DimM]', "GET DIRECT DATA('m.csv',',','1','B2')", { - directDataArgs: { file: 'm.csv', tab: ',', timeRowOrCol: '1', startCell: 'B2' }, - refId: '_m[_m2]', - separationDims: ['_dimm'], - subscripts: ['_m2'], - varType: 'data' - }), - v('m[DimM]', "GET DIRECT DATA('m.csv',',','1','B2')", { - directDataArgs: { file: 'm.csv', tab: ',', timeRowOrCol: '1', startCell: 'B2' }, - refId: '_m[_m3]', - separationDims: ['_dimm'], - subscripts: ['_m3'], - varType: 'data' - }), - v('n', 'm[M2]', { - refId: '_n', - references: ['_m[_m2]'] - }), - v('o[DimM]', "GET DIRECT DATA('mt.csv',',','A','B2')", { - directDataArgs: { file: 'mt.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_o[_m1]', - separationDims: ['_dimm'], - subscripts: ['_m1'], - varType: 'data' - }), - v('o[DimM]', "GET DIRECT DATA('mt.csv',',','A','B2')", { - directDataArgs: { file: 'mt.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_o[_m2]', - separationDims: ['_dimm'], - subscripts: ['_m2'], - varType: 'data' - }), - v('o[DimM]', "GET DIRECT DATA('mt.csv',',','A','B2')", { - directDataArgs: { file: 'mt.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_o[_m3]', - separationDims: ['_dimm'], - subscripts: ['_m3'], - varType: 'data' - }), - v('p', 'o[M2]', { - refId: '_p', - references: ['_o[_m2]'] - }), - v('q[SubM]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_q[_m2]', - separationDims: ['_subm'], - subscripts: ['_m2'], - varType: 'data' - }), - v('q[SubM]', "GET DIRECT DATA('e_data.csv',',','A','B2')", { - directDataArgs: { file: 'e_data.csv', tab: ',', timeRowOrCol: 'A', startCell: 'B2' }, - refId: '_q[_m3]', - separationDims: ['_subm'], - subscripts: ['_m3'], - varType: 'data' - }), - v('r', 'q[M3]', { - refId: '_r', - references: ['_q[_m3]'] - }), - v('INITIAL TIME', '1990', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '2050', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "directlookups" model', () => { - const vars = readSubscriptsAndEquations('directlookups') - expect(vars).toEqual([ - v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { - directDataArgs: { file: 'lookups.CSV', tab: ',', timeRowOrCol: '1', startCell: 'e2' }, - refId: '_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'data' - }), - v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { - directDataArgs: { file: 'lookups.CSV', tab: ',', timeRowOrCol: '1', startCell: 'e2' }, - refId: '_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'data' - }), - v('a[DimA]', "GET DIRECT LOOKUPS('lookups.CSV',',','1','e2')", { - directDataArgs: { file: 'lookups.CSV', tab: ',', timeRowOrCol: '1', startCell: 'e2' }, - refId: '_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'data' - }), - v('a from named xlsx[DimA]', "GET DIRECT LOOKUPS('lookups.xlsx','a','1','E2')", { - directDataArgs: { file: 'lookups.xlsx', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, - refId: '_a_from_named_xlsx[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'data' - }), - v('a from named xlsx[DimA]', "GET DIRECT LOOKUPS('lookups.xlsx','a','1','E2')", { - directDataArgs: { file: 'lookups.xlsx', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, - refId: '_a_from_named_xlsx[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'data' - }), - v('a from named xlsx[DimA]', "GET DIRECT LOOKUPS('lookups.xlsx','a','1','E2')", { - directDataArgs: { file: 'lookups.xlsx', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, - refId: '_a_from_named_xlsx[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'data' - }), - v('a from tagged xlsx[DimA]', "GET DIRECT LOOKUPS('?lookups','a','1','E2')", { - directDataArgs: { file: '?lookups', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, - refId: '_a_from_tagged_xlsx[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'data' - }), - v('a from tagged xlsx[DimA]', "GET DIRECT LOOKUPS('?lookups','a','1','E2')", { - directDataArgs: { file: '?lookups', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, - refId: '_a_from_tagged_xlsx[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'data' - }), - v('a from tagged xlsx[DimA]', "GET DIRECT LOOKUPS('?lookups','a','1','E2')", { - directDataArgs: { file: '?lookups', tab: 'a', timeRowOrCol: '1', startCell: 'E2' }, - refId: '_a_from_tagged_xlsx[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'data' - }), - v('b', 'a[A1](Time)', { - refId: '_b', - referencedLookupVarNames: ['_a'], - references: ['_time'] - }), - v('b from named xlsx', 'a from named xlsx[A1](Time)', { - refId: '_b_from_named_xlsx', - referencedLookupVarNames: ['_a_from_named_xlsx'], - references: ['_time'] - }), - v('b from tagged xlsx', 'a from tagged xlsx[A1](Time)', { - refId: '_b_from_tagged_xlsx', - referencedLookupVarNames: ['_a_from_tagged_xlsx'], - references: ['_time'] - }), - v('c', 'LOOKUP INVERT(a[A1],0.5)', { - refId: '_c', - referencedFunctionNames: ['__lookup_invert'], - references: ['_a[_a1]'] - }), - v('d', 'LOOKUP FORWARD(a[A1],2028.1)', { - refId: '_d', - referencedFunctionNames: ['__lookup_forward'], - references: ['_a[_a1]'] - }), - v('e', 'LOOKUP FORWARD(a[A1],2028)', { - refId: '_e', - referencedFunctionNames: ['__lookup_forward'], - references: ['_a[_a1]'] - }), - v('f', 'a[A1](2028.1)', { - refId: '_f', - referencedLookupVarNames: ['_a'] - }), - v('g', '', { - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - refId: '_g', - varType: 'lookup' - }), - v('h', 'LOOKUP FORWARD(g,1)', { - refId: '_h', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('INITIAL TIME', '2020', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '2050', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "directsubs" model', () => { - const vars = readSubscriptsAndEquations('directsubs') - expect(vars).toEqual([ - v('a[DimA]', '10,20,30', { - refId: '_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('a[DimA]', '10,20,30', { - refId: '_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('a[DimA]', '10,20,30', { - refId: '_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('b[DimB]', '1,2,3', { - refId: '_b[_b1]', - separationDims: ['_dimb'], - subscripts: ['_b1'], - varType: 'const' - }), - v('b[DimB]', '1,2,3', { - refId: '_b[_b2]', - separationDims: ['_dimb'], - subscripts: ['_b2'], - varType: 'const' - }), - v('b[DimB]', '1,2,3', { - refId: '_b[_b3]', - separationDims: ['_dimb'], - subscripts: ['_b3'], - varType: 'const' - }), - v('c[DimC]', 'a[DimA]+1', { - refId: '_c', - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'], - subscripts: ['_dimc'] - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "elmcount" model', () => { - const vars = readSubscriptsAndEquations('elmcount') - expect(vars).toEqual([ - v('a', 'ELMCOUNT(DimA)', { - refId: '_a', - referencedFunctionNames: ['__elmcount'] - }), - v('b[DimA]', '10*ELMCOUNT(DimA)+a', { - refId: '_b', - referencedFunctionNames: ['__elmcount'], - references: ['_a'], - subscripts: ['_dima'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "except" model', () => { - const vars = readSubscriptsAndEquations('except') - expect(vars).toEqual([ - v('a[DimA]', '1', { - refId: '_a', - subscripts: ['_dima'], - varType: 'const' - }), - v('b[SubA]', '2', { - refId: '_b[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('b[SubA]', '2', { - refId: '_b[_a3]', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('c[DimA,DimC]', '3', { - refId: '_c', - subscripts: ['_dima', '_dimc'], - varType: 'const' - }), - v('d[SubA,C1]', '4', { - refId: '_d[_a2,_c1]', - separationDims: ['_suba'], - subscripts: ['_a2', '_c1'], - varType: 'const' - }), - v('d[SubA,C1]', '4', { - refId: '_d[_a3,_c1]', - separationDims: ['_suba'], - subscripts: ['_a3', '_c1'], - varType: 'const' - }), - v('e[DimA,SubC]', '5', { - refId: '_e[_dima,_c2]', - separationDims: ['_subc'], - subscripts: ['_dima', '_c2'], - varType: 'const' - }), - v('e[DimA,SubC]', '5', { - refId: '_e[_dima,_c3]', - separationDims: ['_subc'], - subscripts: ['_dima', '_c3'], - varType: 'const' - }), - v('f[A1,C1]', '6', { - refId: '_f', - subscripts: ['_a1', '_c1'], - varType: 'const' - }), - v('g[DimA]:EXCEPT:[A1]', '7', { - refId: '_g[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('g[DimA]:EXCEPT:[A1]', '7', { - refId: '_g[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('h[DimA]:EXCEPT:[SubA]', '8', { - refId: '_h', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('j[DimD]', '10,20', { - refId: '_j[_d1]', - separationDims: ['_dimd'], - subscripts: ['_d1'], - varType: 'const' - }), - v('j[DimD]', '10,20', { - refId: '_j[_d2]', - separationDims: ['_dimd'], - subscripts: ['_d2'], - varType: 'const' - }), - v('k[DimA]:EXCEPT:[A1]', 'a[DimA]+j[DimD]', { - refId: '_k[_a2]', - references: ['_a', '_j[_d1]'], - separationDims: ['_dima'], - subscripts: ['_a2'] - }), - v('k[DimA]:EXCEPT:[A1]', 'a[DimA]+j[DimD]', { - refId: '_k[_a3]', - references: ['_a', '_j[_d1]'], - separationDims: ['_dima'], - subscripts: ['_a3'] - }), - v('o[SubA]:EXCEPT:[SubA2]', '9', { - refId: '_o', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a1,_c2]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a1', '_c2'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a1,_c3]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a1', '_c3'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a2,_c1]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a2', '_c1'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a2,_c2]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a2', '_c2'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a2,_c3]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a2', '_c3'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a3,_c1]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a3', '_c1'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a3,_c2]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a3', '_c2'], - varType: 'const' - }), - v('p[DimA,DimC]:EXCEPT:[A1,C1]', '10', { - refId: '_p[_a3,_c3]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a3', '_c3'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a1,_c1]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a1', '_c1'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a1,_c2]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a1', '_c2'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a1,_c3]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a1', '_c3'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a2,_c1]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a2', '_c1'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a2,_c3]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a2', '_c3'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a3,_c1]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a3', '_c1'], - varType: 'const' - }), - v('q[DimA,DimC]:EXCEPT:[SubA,C2]', '11', { - refId: '_q[_a3,_c3]', - separationDims: ['_dima', '_dimc'], - subscripts: ['_a3', '_c3'], - varType: 'const' - }), - v('r[DimA,DimC]:EXCEPT:[DimA,C1]', '12', { - refId: '_r[_dima,_c2]', - separationDims: ['_dimc'], - subscripts: ['_dima', '_c2'], - varType: 'const' - }), - v('r[DimA,DimC]:EXCEPT:[DimA,C1]', '12', { - refId: '_r[_dima,_c3]', - separationDims: ['_dimc'], - subscripts: ['_dima', '_c3'], - varType: 'const' - }), - v('s[A3]', '13', { - refId: '_s[_a3]', - subscripts: ['_a3'], - varType: 'const' - }), - v('s[SubA]:EXCEPT:[A3]', '14', { - refId: '_s[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('t[SubA,SubC]', '15', { - refId: '_t[_a2,_c2]', - separationDims: ['_suba', '_subc'], - subscripts: ['_a2', '_c2'], - varType: 'const' - }), - v('t[SubA,SubC]', '15', { - refId: '_t[_a2,_c3]', - separationDims: ['_suba', '_subc'], - subscripts: ['_a2', '_c3'], - varType: 'const' - }), - v('t[SubA,SubC]', '15', { - refId: '_t[_a3,_c2]', - separationDims: ['_suba', '_subc'], - subscripts: ['_a3', '_c2'], - varType: 'const' - }), - v('t[SubA,SubC]', '15', { - refId: '_t[_a3,_c3]', - separationDims: ['_suba', '_subc'], - subscripts: ['_a3', '_c3'], - varType: 'const' - }), - v('u[DimA]:EXCEPT:[A1]', 'a[DimA]', { - refId: '_u[_a2]', - references: ['_a'], - separationDims: ['_dima'], - subscripts: ['_a2'] - }), - v('u[DimA]:EXCEPT:[A1]', 'a[DimA]', { - refId: '_u[_a3]', - references: ['_a'], - separationDims: ['_dima'], - subscripts: ['_a3'] - }), - v('v[SubA]:EXCEPT:[A1]', 'a[SubA]', { - refId: '_v[_a2]', - references: ['_a'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('v[SubA]:EXCEPT:[A1]', 'a[SubA]', { - refId: '_v[_a3]', - references: ['_a'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('w[DimA]:EXCEPT:[SubA]', 'a[DimA]', { - refId: '_w', - references: ['_a'], - separationDims: ['_dima'], - subscripts: ['_a1'] - }), - v('x[DimA]:EXCEPT:[SubA]', 'c[DimA,C1]', { - refId: '_x', - references: ['_c'], - separationDims: ['_dima'], - subscripts: ['_a1'] - }), - v('y[SubA,SubC]:EXCEPT:[A3,C3]', 'c[SubA,SubC]', { - refId: '_y[_a2,_c2]', - references: ['_c'], - separationDims: ['_suba', '_subc'], - subscripts: ['_a2', '_c2'] - }), - v('y[SubA,SubC]:EXCEPT:[A3,C3]', 'c[SubA,SubC]', { - refId: '_y[_a2,_c3]', - references: ['_c'], - separationDims: ['_suba', '_subc'], - subscripts: ['_a2', '_c3'] - }), - v('y[SubA,SubC]:EXCEPT:[A3,C3]', 'c[SubA,SubC]', { - refId: '_y[_a3,_c2]', - references: ['_c'], - separationDims: ['_suba', '_subc'], - subscripts: ['_a3', '_c2'] - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e1,_f1,_g1]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e1', '_f1', '_g1'], - varType: 'const' - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e1,_f1,_g2]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e1', '_f1', '_g2'], - varType: 'const' - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e1,_f2,_g1]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e1', '_f2', '_g1'], - varType: 'const' - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e1,_f2,_g2]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e1', '_f2', '_g2'], - varType: 'const' - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e2,_f1,_g1]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e2', '_f1', '_g1'], - varType: 'const' - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e2,_f1,_g2]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e2', '_f1', '_g2'], - varType: 'const' - }), - v('except3[DimE,DimF,DimG]:EXCEPT:[E2,F2,G2]', '3', { - refId: '_except3[_e2,_f2,_g1]', - separationDims: ['_dime', '_dimf', '_dimg'], - subscripts: ['_e2', '_f2', '_g1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f1,_g1,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f1', '_g1', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f1,_g1,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f1', '_g1', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f1,_g2,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f1', '_g2', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f1,_g2,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f1', '_g2', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f2,_g1,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f2', '_g1', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f2,_g1,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f2', '_g1', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f2,_g2,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f2', '_g2', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e1,_f2,_g2,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e1', '_f2', '_g2', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f1,_g1,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f1', '_g1', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f1,_g1,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f1', '_g1', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f1,_g2,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f1', '_g2', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f1,_g2,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f1', '_g2', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f2,_g1,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f2', '_g1', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f2,_g1,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f2', '_g1', '_h2'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f2,_g2,_h1]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f2', '_g2', '_h1'], - varType: 'const' - }), - v('except4[DimE,DimF,DimG,DimH]:EXCEPT:[E2,F2,G2,H2]', '4', { - refId: '_except4[_e2,_f2,_g2,_h2]', - separationDims: ['_dime', '_dimf', '_dimg', '_dimh'], - subscripts: ['_e2', '_f2', '_g2', '_h2'], - varType: 'const' - }), - v('input', '0', { - refId: '_input', - varType: 'const' - }), - v('z ref a', '25', { - refId: '_z_ref_a', - varType: 'const' - }), - v('z ref b', '5', { - refId: '_z_ref_b', - varType: 'const' - }), - v('z[SubA]', 'z ref a*z ref b', { - refId: '_z[_a2]', - references: ['_z_ref_a', '_z_ref_b'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('z[SubA]', 'z ref a*z ref b', { - refId: '_z[_a3]', - references: ['_z_ref_a', '_z_ref_b'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('z[DimA]:EXCEPT:[SubA]', '10', { - refId: '_z[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('z total', 'SUM(z[SubA!])', { - refId: '_z_total', - referencedFunctionNames: ['__sum'], - references: ['_z[_a2]', '_z[_a3]'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('SAVEPER', '1', { - refId: '_saveper', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - // it.skip('should work for XMILE "except2" model', () => { - // const vars = readSubscriptsAndEquations('except2') - // logPrettyVars(vars) - // expect(vars).toEqual([]) - // }) - - it.skip('should work for XMILE "extdata" model', () => { - const vars = readSubscriptsAndEquations('extdata') - expect(vars).toEqual([ - v('Simple 1', '', { - refId: '_simple_1', - varType: 'data' - }), - v('Simple 2', '', { - refId: '_simple_2', - varType: 'data' - }), - v('A Values[DimA]', '', { - refId: '_a_values', - subscripts: ['_dima'], - varType: 'data' - }), - v('BC Values[DimB,DimC]', '', { - refId: '_bc_values', - subscripts: ['_dimb', '_dimc'], - varType: 'data' - }), - v('D Values[DimD]', '', { - refId: '_d_values', - subscripts: ['_dimd'], - varType: 'data' - }), - v('E Values[E1]', '', { - refId: '_e_values[_e1]', - subscripts: ['_e1'], - varType: 'data' - }), - v('E Values[E2]', '', { - refId: '_e_values[_e2]', - subscripts: ['_e2'], - varType: 'data' - }), - v('EBC Values[DimE,DimB,DimC]', '', { - refId: '_ebc_values', - subscripts: ['_dime', '_dimb', '_dimc'], - varType: 'data' - }), - v('Simple Totals', 'Simple 1+Simple 2', { - refId: '_simple_totals', - references: ['_simple_1', '_simple_2'] - }), - v('A Totals', 'SUM(A Values[DimA!])', { - refId: '_a_totals', - referencedFunctionNames: ['__sum'], - references: ['_a_values'] - }), - v('B1 Totals', 'SUM(BC Values[B1,DimC!])', { - refId: '_b1_totals', - referencedFunctionNames: ['__sum'], - references: ['_bc_values'] - }), - v('D Totals', 'SUM(D Values[DimD!])', { - refId: '_d_totals', - referencedFunctionNames: ['__sum'], - references: ['_d_values'] - }), - v('E1 Values', 'E Values[E1]', { - refId: '_e1_values', - references: ['_e_values[_e1]'] - }), - v('E2 Values', 'E Values[E2]', { - refId: '_e2_values', - references: ['_e_values[_e2]'] - }), - v('Chosen E', '2', { - refId: '_chosen_e', - varType: 'const' - }), - v('Chosen B', '3', { - refId: '_chosen_b', - varType: 'const' - }), - v('Chosen C', '1', { - refId: '_chosen_c', - varType: 'const' - }), - v('E Selection[DimE]', 'IF THEN ELSE(DimE=Chosen E,1,0)', { - refId: '_e_selection', - references: ['_chosen_e'], - subscripts: ['_dime'] - }), - v('B Selection[DimB]', 'IF THEN ELSE(DimB=Chosen B,1,0)', { - refId: '_b_selection', - references: ['_chosen_b'], - subscripts: ['_dimb'] - }), - v('C Selection[DimC]', 'IF THEN ELSE(DimC=Chosen C,1,0)', { - refId: '_c_selection', - references: ['_chosen_c'], - subscripts: ['_dimc'] - }), - v( - 'Total EBC for Selected C[DimE,DimB]', - 'VECTOR SELECT(C Selection[DimC!],EBC Values[DimE,DimB,DimC!],0,VSSUM,VSERRATLEASTONE)', - { - refId: '_total_ebc_for_selected_c', - referencedFunctionNames: ['__vector_select'], - references: ['_c_selection', '_ebc_values', '_vssum', '_vserratleastone'], - subscripts: ['_dime', '_dimb'] - } - ), - v( - 'Total EBC for Selected BC[DimE]', - 'VECTOR SELECT(B Selection[DimB!],Total EBC for Selected C[DimE,DimB!],0,VSSUM,VSERRATLEASTONE)', - { - refId: '_total_ebc_for_selected_bc', - referencedFunctionNames: ['__vector_select'], - references: ['_b_selection', '_total_ebc_for_selected_c', '_vssum', '_vserratleastone'], - subscripts: ['_dime'] - } - ), - v('Total EBC', 'VECTOR SELECT(E Selection[DimE!],Total EBC for Selected BC[DimE!],0,VSSUM,VSERRATLEASTONE)', { - refId: '_total_ebc', - referencedFunctionNames: ['__vector_select'], - references: ['_e_selection', '_total_ebc_for_selected_bc', '_vssum', '_vserratleastone'] - }), - v('VSERRATLEASTONE', '1', { - refId: '_vserratleastone', - varType: 'const' - }), - v('VSSUM', '0', { - refId: '_vssum', - varType: 'const' - }), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - // it.skip('should work for XMILE "flatten" model', () => { - // const vars = readSubscriptsAndEquations('flatten') - // logPrettyVars(vars) - // expect(vars).toEqual([]) - // }) - - it.skip('should work for XMILE "gamma_ln" model', () => { - const vars = readSubscriptsAndEquations('gamma_ln') - expect(vars).toEqual([ - v('a', 'GAMMA LN(10)', { - refId: '_a', - referencedFunctionNames: ['__gamma_ln'] - }), - v('b', 'GAMMA LN(0.5)', { - refId: '_b', - referencedFunctionNames: ['__gamma_ln'] - }), - v('c', 'GAMMA LN(1)', { - refId: '_c', - referencedFunctionNames: ['__gamma_ln'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "getdata" model', () => { - const vars = readSubscriptsAndEquations('getdata') - expect(vars).toEqual([ - v('Values[DimA]', '', { - refId: '_values', - subscripts: ['_dima'], - varType: 'data' - }), - v('One year', '1', { - refId: '_one_year', - varType: 'const' - }), - v('Half year', '0.5', { - refId: '_half_year', - varType: 'const' - }), - v('Interpolate', '0', { - refId: '_interpolate', - varType: 'const' - }), - v('Forward', '1', { - refId: '_forward', - varType: 'const' - }), - v('Backward', '-1', { - refId: '_backward', - varType: 'const' - }), - v( - 'Value for A1 at time minus one year interpolate', - 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-One year),Interpolate)', - { - refId: '_value_for_a1_at_time_minus_one_year_interpolate', - referencedFunctionNames: ['__get_data_between_times', '__max'], - references: ['_values', '_initial_time', '_time', '_one_year', '_interpolate'] - } - ), - v( - 'Value for A1 at time minus one year forward', - 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-One year),Forward)', - { - refId: '_value_for_a1_at_time_minus_one_year_forward', - referencedFunctionNames: ['__get_data_between_times', '__max'], - references: ['_values', '_initial_time', '_time', '_one_year', '_forward'] - } - ), - v( - 'Value for A1 at time minus one year backward', - 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-One year),Backward)', - { - refId: '_value_for_a1_at_time_minus_one_year_backward', - referencedFunctionNames: ['__get_data_between_times', '__max'], - references: ['_values', '_initial_time', '_time', '_one_year', '_backward'] - } - ), - v( - 'Value for A1 at time minus half year forward', - 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-Half year),Forward)', - { - refId: '_value_for_a1_at_time_minus_half_year_forward', - referencedFunctionNames: ['__get_data_between_times', '__max'], - references: ['_values', '_initial_time', '_time', '_half_year', '_forward'] - } - ), - v( - 'Value for A1 at time minus half year backward', - 'GET DATA BETWEEN TIMES(Values[A1],MAX(INITIAL TIME,Time-Half year),Backward)', - { - refId: '_value_for_a1_at_time_minus_half_year_backward', - referencedFunctionNames: ['__get_data_between_times', '__max'], - references: ['_values', '_initial_time', '_time', '_half_year', '_backward'] - } - ), - v( - 'Value for A1 at time plus half year forward', - 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+Half year),Forward)', - { - refId: '_value_for_a1_at_time_plus_half_year_forward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_half_year', '_forward'] - } - ), - v( - 'Value for A1 at time plus half year backward', - 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+Half year),Backward)', - { - refId: '_value_for_a1_at_time_plus_half_year_backward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_half_year', '_backward'] - } - ), - v( - 'Value for A1 at time plus one year interpolate', - 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Interpolate)', - { - refId: '_value_for_a1_at_time_plus_one_year_interpolate', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_one_year', '_interpolate'] - } - ), - v( - 'Value for A1 at time plus one year forward', - 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Forward)', - { - refId: '_value_for_a1_at_time_plus_one_year_forward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_one_year', '_forward'] - } - ), - v( - 'Value for A1 at time plus one year backward', - 'GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Backward)', - { - refId: '_value_for_a1_at_time_plus_one_year_backward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_one_year', '_backward'] - } - ), - v( - 'Value at time plus half year forward[DimA]', - 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+Half year),Forward)', - { - refId: '_value_at_time_plus_half_year_forward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_half_year', '_forward'], - subscripts: ['_dima'] - } - ), - v( - 'Value at time plus half year backward[DimA]', - 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+Half year),Backward)', - { - refId: '_value_at_time_plus_half_year_backward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_half_year', '_backward'], - subscripts: ['_dima'] - } - ), - v( - 'Value at time plus one year interpolate[DimA]', - 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Interpolate)', - { - refId: '_value_at_time_plus_one_year_interpolate', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_one_year', '_interpolate'], - subscripts: ['_dima'] - } - ), - v( - 'Value at time plus one year forward[DimA]', - 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Forward)', - { - refId: '_value_at_time_plus_one_year_forward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_one_year', '_forward'], - subscripts: ['_dima'] - } - ), - v( - 'Value at time plus one year backward[DimA]', - 'GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Backward)', - { - refId: '_value_at_time_plus_one_year_backward', - referencedFunctionNames: ['__get_data_between_times', '__min'], - references: ['_values', '_final_time', '_time', '_one_year', '_backward'], - subscripts: ['_dima'] - } - ), - v( - 'Initial value at time plus one year interpolate[DimA]', - 'INITIAL(GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Interpolate))', - { - hasInitValue: true, - initReferences: ['_values', '_final_time', '_time', '_one_year', '_interpolate'], - refId: '_initial_value_at_time_plus_one_year_interpolate', - referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], - subscripts: ['_dima'], - varType: 'initial' - } - ), - v( - 'Initial value at time plus one year forward[DimA]', - 'INITIAL(GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Forward))', - { - hasInitValue: true, - initReferences: ['_values', '_final_time', '_time', '_one_year', '_forward'], - refId: '_initial_value_at_time_plus_one_year_forward', - referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], - subscripts: ['_dima'], - varType: 'initial' - } - ), - v( - 'Initial value at time plus one year backward[DimA]', - 'INITIAL(GET DATA BETWEEN TIMES(Values[DimA],MIN(FINAL TIME,Time+One year),Backward))', - { - hasInitValue: true, - initReferences: ['_values', '_final_time', '_time', '_one_year', '_backward'], - refId: '_initial_value_at_time_plus_one_year_backward', - referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], - subscripts: ['_dima'], - varType: 'initial' - } - ), - v( - 'Initial value for A1 at time plus one year interpolate', - 'INITIAL(GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Interpolate))', - { - hasInitValue: true, - initReferences: ['_values', '_final_time', '_time', '_one_year', '_interpolate'], - refId: '_initial_value_for_a1_at_time_plus_one_year_interpolate', - referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], - varType: 'initial' - } - ), - v( - 'Initial value for A1 at time plus one year forward', - 'INITIAL(GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Forward))', - { - hasInitValue: true, - initReferences: ['_values', '_final_time', '_time', '_one_year', '_forward'], - refId: '_initial_value_for_a1_at_time_plus_one_year_forward', - referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], - varType: 'initial' - } - ), - v( - 'Initial value for A1 at time plus one year backward', - 'INITIAL(GET DATA BETWEEN TIMES(Values[A1],MIN(FINAL TIME,Time+One year),Backward))', - { - hasInitValue: true, - initReferences: ['_values', '_final_time', '_time', '_one_year', '_backward'], - refId: '_initial_value_for_a1_at_time_plus_one_year_backward', - referencedFunctionNames: ['__initial', '__get_data_between_times', '__min'], - varType: 'initial' - } - ), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "index" model', () => { - const vars = readSubscriptsAndEquations('index') - expect(vars).toEqual([ - v('a[DimA]', 'b[DimA]+10', { - refId: '_a', - references: ['_b[_a1]', '_b[_a2]', '_b[_a3]'], - subscripts: ['_dima'] - }), - v('b[A1]', '1', { - refId: '_b[_a1]', - subscripts: ['_a1'], - varType: 'const' - }), - v('b[A2]', '2', { - refId: '_b[_a2]', - subscripts: ['_a2'], - varType: 'const' - }), - v('b[A3]', '3', { - refId: '_b[_a3]', - subscripts: ['_a3'], - varType: 'const' - }), - v('c[DimA]', 'b[A1]+1', { - refId: '_c', - references: ['_b[_a1]'], - subscripts: ['_dima'] - }), - v('d[DimA]', 'b[A1]+b[DimA]', { - refId: '_d', - references: ['_b[_a1]', '_b[_a2]', '_b[_a3]'], - subscripts: ['_dima'] - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "initial" model', () => { - const vars = readSubscriptsAndEquations('initial') - expect(vars).toEqual([ - v('amplitude', '2', { - refId: '_amplitude', - varType: 'const' - }), - v('Period', '20', { - refId: '_period', - varType: 'const' - }), - v('x', 'amplitude*COS(6.28*Time/Period)', { - refId: '_x', - referencedFunctionNames: ['__cos'], - references: ['_amplitude', '_time', '_period'] - }), - v('relative x', 'x/INITIAL x', { - refId: '_relative_x', - references: ['_x', '_initial_x'] - }), - v('INITIAL x', 'INITIAL(x)', { - hasInitValue: true, - initReferences: ['_x'], - refId: '_initial_x', - referencedFunctionNames: ['__initial'], - varType: 'initial' - }), - v('FINAL TIME', '100', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "interleaved" model', () => { - const vars = readSubscriptsAndEquations('interleaved') - expect(vars).toEqual([ - v('x', '1', { - refId: '_x', - varType: 'const' - }), - v('a[A1]', 'x', { - refId: '_a[_a1]', - references: ['_x'], - subscripts: ['_a1'] - }), - v('a[A2]', 'y', { - refId: '_a[_a2]', - references: ['_y'], - subscripts: ['_a2'] - }), - v('y', 'a[A1]', { - refId: '_y', - references: ['_a[_a1]'] - }), - v('b[DimA]', 'a[DimA]', { - refId: '_b', - references: ['_a[_a1]', '_a[_a2]'], - subscripts: ['_dima'] - }), - v('FINAL TIME', '100', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "longeqns" model', () => { - const vars = readSubscriptsAndEquations('longeqns') - expect(vars).toEqual([ - v('EqnA[DimX,DimY]', '1', { - refId: '_eqna', - subscripts: ['_dimx', '_dimy'], - varType: 'const' - }), - v('EqnB[DimX,DimW]', '1', { - refId: '_eqnb', - subscripts: ['_dimx', '_dimw'], - varType: 'const' - }), - v( - 'EqnC[DimX,DimY,DimZ]', - 'EqnA[DimX,DimY]*(-SUM(EqnB[DimX,DimW\n!])-(SUM(EqnB[DimX,DimW!])-SUM(EqnB[DimX,DimW\n!]))*EqnA[DimX,DimY])', - { - refId: '_eqnc', - referencedFunctionNames: ['__sum'], - references: ['_eqna', '_eqnb'], - subscripts: ['_dimx', '_dimy', '_dimz'] - } - ), - v('Result', 'EqnC[X1,Y1,Z1]', { - refId: '_result', - references: ['_eqnc'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "lookup" model', () => { - const vars = readSubscriptsAndEquations('lookup') - expect(vars).toEqual([ - v('a', '', { - points: [ - [0, 0], - [0.7, 0], - [0.8, 0.1], - [0.9, 0.9], - [1, -1], - [2, 1] - ], - range: [ - [0, 0], - [2, 1.2] - ], - refId: '_a', - varType: 'lookup' - }), - v('b', 'a(i)', { - refId: '_b', - referencedFunctionNames: ['__a'], - references: ['_i'] - }), - v('i', 'Time/10', { - refId: '_i', - references: ['_time'] - }), - v('c[A1]', '', { - points: [ - [0, 10], - [1, 20] - ], - refId: '_c[_a1]', - subscripts: ['_a1'], - varType: 'lookup' - }), - v('c[A2]', '', { - points: [ - [0, 20], - [1, 30] - ], - refId: '_c[_a2]', - subscripts: ['_a2'], - varType: 'lookup' - }), - v('c[A3]', '', { - points: [ - [0, 30], - [1, 40] - ], - refId: '_c[_a3]', - subscripts: ['_a3'], - varType: 'lookup' - }), - v('d', 'WITH LOOKUP(i,([(0,0)-(2,2)],(0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3)))', { - lookupArgVarName: '__lookup1', - refId: '_d', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup1'], - references: ['_i'] - }), - v('e[DimA]', 'c[DimA](i)', { - refId: '_e', - referencedLookupVarNames: ['_c'], - references: ['_i'], - subscripts: ['_dima'] - }), - v('f', 'c[A1](i)', { - refId: '_f', - referencedLookupVarNames: ['_c'], - references: ['_i'] - }), - v('g', '', { - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - refId: '_g', - varType: 'lookup' - }), - v('g at minus 1 forward', 'LOOKUP FORWARD(g,-1)', { - refId: '_g_at_minus_1_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at 0 forward', 'LOOKUP FORWARD(g,0)', { - refId: '_g_at_0_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at 0pt5 forward', 'LOOKUP FORWARD(g,0.5)', { - refId: '_g_at_0pt5_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at 1pt0 forward', 'LOOKUP FORWARD(g,1.0)', { - refId: '_g_at_1pt0_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at 1pt5 forward', 'LOOKUP FORWARD(g,1.5)', { - refId: '_g_at_1pt5_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at 2pt0 forward', 'LOOKUP FORWARD(g,2.0)', { - refId: '_g_at_2pt0_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at 2pt5 forward', 'LOOKUP FORWARD(g,2.5)', { - refId: '_g_at_2pt5_forward', - referencedFunctionNames: ['__lookup_forward'], - references: ['_g'] - }), - v('g at minus 1 backward', 'LOOKUP BACKWARD(g,-1)', { - refId: '_g_at_minus_1_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('g at 0 backward', 'LOOKUP BACKWARD(g,0)', { - refId: '_g_at_0_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('g at 0pt5 backward', 'LOOKUP BACKWARD(g,0.5)', { - refId: '_g_at_0pt5_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('g at 1pt0 backward', 'LOOKUP BACKWARD(g,1.0)', { - refId: '_g_at_1pt0_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('g at 1pt5 backward', 'LOOKUP BACKWARD(g,1.5)', { - refId: '_g_at_1pt5_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('g at 2pt0 backward', 'LOOKUP BACKWARD(g,2.0)', { - refId: '_g_at_2pt0_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('g at 2pt5 backward', 'LOOKUP BACKWARD(g,2.5)', { - refId: '_g_at_2pt5_backward', - referencedFunctionNames: ['__lookup_backward'], - references: ['_g'] - }), - v('withlookup at minus 1', 'WITH LOOKUP(-1,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup2', - refId: '_withlookup_at_minus_1', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup2'] - }), - v('withlookup at 0', 'WITH LOOKUP(0,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup3', - refId: '_withlookup_at_0', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup3'] - }), - v('withlookup at 0pt5', 'WITH LOOKUP(0.5,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup4', - refId: '_withlookup_at_0pt5', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup4'] - }), - v('withlookup at 1pt0', 'WITH LOOKUP(1.0,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup5', - refId: '_withlookup_at_1pt0', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup5'] - }), - v('withlookup at 1pt5', 'WITH LOOKUP(1.5,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup6', - refId: '_withlookup_at_1pt5', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup6'] - }), - v('withlookup at 2pt0', 'WITH LOOKUP(2.0,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup7', - refId: '_withlookup_at_2pt0', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup7'] - }), - v('withlookup at 2pt5', 'WITH LOOKUP(2.5,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup8', - refId: '_withlookup_at_2pt5', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup8'] - }), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_lookup1', '', { - includeInOutput: false, - points: [ - [0, 0], - [0.1, 0.01], - [0.5, 0.7], - [1, 1], - [1.5, 1.2], - [2, 1.3] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup1', - varType: 'lookup' - }), - v('_lookup2', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup2', - varType: 'lookup' - }), - v('_lookup3', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup3', - varType: 'lookup' - }), - v('_lookup4', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup4', - varType: 'lookup' - }), - v('_lookup5', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup5', - varType: 'lookup' - }), - v('_lookup6', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup6', - varType: 'lookup' - }), - v('_lookup7', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup7', - varType: 'lookup' - }), - v('_lookup8', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup8', - varType: 'lookup' - }) - ]) - }) - - it.skip('should work for XMILE "mapping" model', () => { - const vars = readSubscriptsAndEquations('mapping') - expect(vars).toEqual([ - v('b[DimB]', '1,2', { - refId: '_b[_b1]', - separationDims: ['_dimb'], - subscripts: ['_b1'], - varType: 'const' - }), - v('b[DimB]', '1,2', { - refId: '_b[_b2]', - separationDims: ['_dimb'], - subscripts: ['_b2'], - varType: 'const' - }), - v('a[DimA]', 'b[DimB]*10', { - refId: '_a', - references: ['_b[_b1]', '_b[_b2]'], - subscripts: ['_dima'] - }), - v('c[DimC]', '1,2,3', { - refId: '_c[_c1]', - separationDims: ['_dimc'], - subscripts: ['_c1'], - varType: 'const' - }), - v('c[DimC]', '1,2,3', { - refId: '_c[_c2]', - separationDims: ['_dimc'], - subscripts: ['_c2'], - varType: 'const' - }), - v('c[DimC]', '1,2,3', { - refId: '_c[_c3]', - separationDims: ['_dimc'], - subscripts: ['_c3'], - varType: 'const' - }), - v('d[DimD]', 'c[DimC]*10', { - refId: '_d', - references: ['_c[_c1]', '_c[_c2]', '_c[_c3]'], - subscripts: ['_dimd'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "multimap" model', () => { - const vars = readSubscriptsAndEquations('multimap') - expect(vars).toEqual([ - v('a[DimA]', '1,2,3', { - refId: '_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('a[DimA]', '1,2,3', { - refId: '_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('a[DimA]', '1,2,3', { - refId: '_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('b[DimB]', 'a[DimA]', { - refId: '_b', - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'], - subscripts: ['_dimb'] - }), - v('c[DimC]', 'a[DimA]', { - refId: '_c', - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'], - subscripts: ['_dimc'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('SAVEPER', '1', { - refId: '_saveper', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "npv" model', () => { - const vars = readSubscriptsAndEquations('npv') - expect(vars).toEqual([ - v('investment', '100', { - refId: '_investment', - varType: 'const' - }), - v('start time', '12', { - refId: '_start_time', - varType: 'const' - }), - v('revenue', '3', { - refId: '_revenue', - varType: 'const' - }), - v('interest rate', '10', { - refId: '_interest_rate', - varType: 'const' - }), - v('stream', '-investment/TIME STEP*PULSE(start time,TIME STEP)+STEP(revenue,start time)', { - refId: '_stream', - referencedFunctionNames: ['__pulse', '__step'], - references: ['_investment', '_time_step', '_start_time', '_revenue'] - }), - v('discount rate', 'interest rate/12/100', { - refId: '_discount_rate', - references: ['_interest_rate'] - }), - v('init val', '0', { - refId: '_init_val', - varType: 'const' - }), - v('factor', '1', { - refId: '_factor', - varType: 'const' - }), - v('NPV vs initial time', 'NPV(stream,discount rate,init val,factor)', { - npvVarName: '__aux1', - refId: '_npv_vs_initial_time', - references: ['__level2', '__level1', '__aux1'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '100', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_level1', 'INTEG((-_level1*discount rate)/(1+discount rate*TIME STEP),1)', { - hasInitValue: true, - includeInOutput: false, - refId: '__level1', - referencedFunctionNames: ['__integ'], - references: ['_discount_rate', '_time_step'], - varType: 'level' - }), - v('_level2', 'INTEG(stream*_level1,init val)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_init_val'], - refId: '__level2', - referencedFunctionNames: ['__integ'], - references: ['_stream', '__level1'], - varType: 'level' - }), - v('_aux1', '(_level2+stream*TIME STEP*_level1)*factor', { - includeInOutput: false, - refId: '__aux1', - references: ['__level2', '_stream', '_time_step', '__level1', '_factor'] - }) - ]) - }) - - it.skip('should work for XMILE "power" model', () => { - const vars = readSubscriptsAndEquations('power') - expect(vars).toEqual([ - v('base', '2', { - refId: '_base', - varType: 'const' - }), - v('a', 'POWER(base,2)', { - refId: '_a', - referencedFunctionNames: ['__power'], - references: ['_base'] - }), - v('b', 'POWER(base,0.5)', { - refId: '_b', - referencedFunctionNames: ['__power'], - references: ['_base'] - }), - v('c', 'POWER(base,1.5)', { - refId: '_c', - referencedFunctionNames: ['__power'], - references: ['_base'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - // it.skip('should work for XMILE "preprocess" model', () => { - // const vars = readSubscriptsAndEquations('preprocess') - // logPrettyVars(vars) - // expect(vars).toEqual([]) - // }) - - it.skip('should work for XMILE "prune" model', () => { - const vars = readSubscriptsAndEquations('prune') - expect(vars).toEqual([ - v('Simple 1', '', { - refId: '_simple_1', - varType: 'data' - }), - v('Simple 2', '', { - refId: '_simple_2', - varType: 'data' - }), - v('A Values[DimA]', '', { - refId: '_a_values', - subscripts: ['_dima'], - varType: 'data' - }), - v('BC Values[DimB,DimC]', '', { - refId: '_bc_values', - subscripts: ['_dimb', '_dimc'], - varType: 'data' - }), - v('D Values[DimD]', '', { - refId: '_d_values', - subscripts: ['_dimd'], - varType: 'data' - }), - v('E Values[E1]', '', { - refId: '_e_values[_e1]', - subscripts: ['_e1'], - varType: 'data' - }), - v('E Values[E2]', '', { - refId: '_e_values[_e2]', - subscripts: ['_e2'], - varType: 'data' - }), - v('Look1', '', { - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - refId: '_look1', - varType: 'lookup' - }), - v('Look2', '', { - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - refId: '_look2', - varType: 'lookup' - }), - v('Input 1', '10', { - refId: '_input_1', - varType: 'const' - }), - v('Input 2', '20', { - refId: '_input_2', - varType: 'const' - }), - v('Input 3', '30', { - refId: '_input_3', - varType: 'const' - }), - v('Simple Totals', 'Simple 1+Simple 2', { - refId: '_simple_totals', - references: ['_simple_1', '_simple_2'] - }), - v('A Totals', 'SUM(A Values[DimA!])', { - refId: '_a_totals', - referencedFunctionNames: ['__sum'], - references: ['_a_values'] - }), - v('B1 Totals', 'SUM(BC Values[B1,DimC!])', { - refId: '_b1_totals', - referencedFunctionNames: ['__sum'], - references: ['_bc_values'] - }), - v('D Totals', 'SUM(D Values[DimD!])', { - refId: '_d_totals', - referencedFunctionNames: ['__sum'], - references: ['_d_values'] - }), - v('E1 Values', 'E Values[E1]', { - refId: '_e1_values', - references: ['_e_values[_e1]'] - }), - v('E2 Values', 'E Values[E2]', { - refId: '_e2_values', - references: ['_e_values[_e2]'] - }), - v('Input 1 and 2 Total', 'Input 1+Input 2', { - refId: '_input_1_and_2_total', - references: ['_input_1', '_input_2'] - }), - v('Input 2 and 3 Total', 'Input 2+Input 3', { - refId: '_input_2_and_3_total', - references: ['_input_2', '_input_3'] - }), - v('Look1 Value at t1', 'Look1(1)', { - refId: '_look1_value_at_t1', - referencedFunctionNames: ['__look1'] - }), - v('Look2 Value at t1', 'Look2(1)', { - refId: '_look2_value_at_t1', - referencedFunctionNames: ['__look2'] - }), - v('With Look1 at t1', 'WITH LOOKUP(1,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup1', - refId: '_with_look1_at_t1', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup1'] - }), - v('With Look2 at t1', 'WITH LOOKUP(1,([(0,0)-(2,2)],(0,0),(1,1),(2,2)))', { - lookupArgVarName: '__lookup2', - refId: '_with_look2_at_t1', - referencedFunctionNames: ['__with_lookup'], - referencedLookupVarNames: ['__lookup2'] - }), - v('Constant Partial 1', '1', { - refId: '_constant_partial_1', - varType: 'const' - }), - v('Constant Partial 2', '2', { - refId: '_constant_partial_2', - varType: 'const' - }), - v('Initial Partial[C1]', 'INITIAL(Constant Partial 1)', { - hasInitValue: true, - initReferences: ['_constant_partial_1'], - refId: '_initial_partial[_c1]', - referencedFunctionNames: ['__initial'], - subscripts: ['_c1'], - varType: 'initial' - }), - v('Initial Partial[C2]', 'INITIAL(Constant Partial 2)', { - hasInitValue: true, - initReferences: ['_constant_partial_2'], - refId: '_initial_partial[_c2]', - referencedFunctionNames: ['__initial'], - subscripts: ['_c2'], - varType: 'initial' - }), - v('Partial[C2]', 'Initial Partial[C2]', { - refId: '_partial', - references: ['_initial_partial[_c2]'], - subscripts: ['_c2'] - }), - v('Test 1 T', '1', { - refId: '_test_1_t', - varType: 'const' - }), - v('Test 1 F', '2', { - refId: '_test_1_f', - varType: 'const' - }), - v('Test 1 Result', 'IF THEN ELSE(Input 1=10,Test 1 T,Test 1 F)', { - refId: '_test_1_result', - references: ['_input_1', '_test_1_t', '_test_1_f'] - }), - v('Test 2 T', '1', { - refId: '_test_2_t', - varType: 'const' - }), - v('Test 2 F', '2', { - refId: '_test_2_f', - varType: 'const' - }), - v('Test 2 Result', 'IF THEN ELSE(0,Test 2 T,Test 2 F)', { - refId: '_test_2_result', - references: ['_test_2_t', '_test_2_f'] - }), - v('Test 3 T', '1', { - refId: '_test_3_t', - varType: 'const' - }), - v('Test 3 F', '2', { - refId: '_test_3_f', - varType: 'const' - }), - v('Test 3 Result', 'IF THEN ELSE(1,Test 3 T,Test 3 F)', { - refId: '_test_3_result', - references: ['_test_3_t', '_test_3_f'] - }), - v('Test 4 Cond', '0', { - refId: '_test_4_cond', - varType: 'const' - }), - v('Test 4 T', '1', { - refId: '_test_4_t', - varType: 'const' - }), - v('Test 4 F', '2', { - refId: '_test_4_f', - varType: 'const' - }), - v('Test 4 Result', 'IF THEN ELSE(Test 4 Cond,Test 4 T,Test 4 F)', { - refId: '_test_4_result', - references: ['_test_4_cond', '_test_4_t', '_test_4_f'] - }), - v('Test 5 Cond', '1', { - refId: '_test_5_cond', - varType: 'const' - }), - v('Test 5 T', '1', { - refId: '_test_5_t', - varType: 'const' - }), - v('Test 5 F', '2', { - refId: '_test_5_f', - varType: 'const' - }), - v('Test 5 Result', 'IF THEN ELSE(Test 5 Cond,Test 5 T,Test 5 F)', { - refId: '_test_5_result', - references: ['_test_5_cond', '_test_5_t', '_test_5_f'] - }), - v('Test 6 Cond', '0', { - refId: '_test_6_cond', - varType: 'const' - }), - v('Test 6 T', '1', { - refId: '_test_6_t', - varType: 'const' - }), - v('Test 6 F', '2', { - refId: '_test_6_f', - varType: 'const' - }), - v('Test 6 Result', 'IF THEN ELSE(Test 6 Cond=1,Test 6 T,Test 6 F)', { - refId: '_test_6_result', - references: ['_test_6_cond', '_test_6_t', '_test_6_f'] - }), - v('Test 7 Cond', '1', { - refId: '_test_7_cond', - varType: 'const' - }), - v('Test 7 T', '1', { - refId: '_test_7_t', - varType: 'const' - }), - v('Test 7 F', '2', { - refId: '_test_7_f', - varType: 'const' - }), - v('Test 7 Result', 'IF THEN ELSE(Test 7 Cond=1,Test 7 T,Test 7 F)', { - refId: '_test_7_result', - references: ['_test_7_cond', '_test_7_t', '_test_7_f'] - }), - v('Test 8 Cond', '0', { - refId: '_test_8_cond', - varType: 'const' - }), - v('Test 8 T', '1', { - refId: '_test_8_t', - varType: 'const' - }), - v('Test 8 F', '2', { - refId: '_test_8_f', - varType: 'const' - }), - v('Test 8 Result', 'IF THEN ELSE(Test 8 Cond>0,Test 8 T,Test 8 F)', { - refId: '_test_8_result', - references: ['_test_8_cond', '_test_8_t', '_test_8_f'] - }), - v('Test 9 Cond', '1', { - refId: '_test_9_cond', - varType: 'const' - }), - v('Test 9 T', '1', { - refId: '_test_9_t', - varType: 'const' - }), - v('Test 9 F', '2', { - refId: '_test_9_f', - varType: 'const' - }), - v('Test 9 Result', 'IF THEN ELSE(Test 9 Cond>0,Test 9 T,Test 9 F)', { - refId: '_test_9_result', - references: ['_test_9_cond', '_test_9_t', '_test_9_f'] - }), - v('Test 10 Cond', '1', { - refId: '_test_10_cond', - varType: 'const' - }), - v('Test 10 T', '1', { - refId: '_test_10_t', - varType: 'const' - }), - v('Test 10 F', '2', { - refId: '_test_10_f', - varType: 'const' - }), - v('Test 10 Result', 'IF THEN ELSE(ABS(Test 10 Cond),Test 10 T,Test 10 F)', { - refId: '_test_10_result', - referencedFunctionNames: ['__abs'], - references: ['_test_10_cond', '_test_10_t', '_test_10_f'] - }), - v('Test 11 Cond', '0', { - refId: '_test_11_cond', - varType: 'const' - }), - v('Test 11 T', '1', { - refId: '_test_11_t', - varType: 'const' - }), - v('Test 11 F', '2', { - refId: '_test_11_f', - varType: 'const' - }), - v('Test 11 Result', 'IF THEN ELSE(Test 11 Cond:AND:ABS(Test 11 Cond),Test 11 T,Test 11 F)', { - refId: '_test_11_result', - referencedFunctionNames: ['__abs'], - references: ['_test_11_cond', '_test_11_t', '_test_11_f'] - }), - v('Test 12 Cond', '1', { - refId: '_test_12_cond', - varType: 'const' - }), - v('Test 12 T', '1', { - refId: '_test_12_t', - varType: 'const' - }), - v('Test 12 F', '2', { - refId: '_test_12_f', - varType: 'const' - }), - v('Test 12 Result', 'IF THEN ELSE(Test 12 Cond:OR:ABS(Test 12 Cond),Test 12 T,Test 12 F)', { - refId: '_test_12_result', - referencedFunctionNames: ['__abs'], - references: ['_test_12_cond', '_test_12_t', '_test_12_f'] - }), - v('Test 13 Cond', '1', { - refId: '_test_13_cond', - varType: 'const' - }), - v('Test 13 T1', '1', { - refId: '_test_13_t1', - varType: 'const' - }), - v('Test 13 T2', '7', { - refId: '_test_13_t2', - varType: 'const' - }), - v('Test 13 F', '2', { - refId: '_test_13_f', - varType: 'const' - }), - v('Test 13 Result', 'IF THEN ELSE(Test 13 Cond,Test 13 T1+Test 13 T2,Test 13 F)*10.0', { - refId: '_test_13_result', - references: ['_test_13_cond', '_test_13_t1', '_test_13_t2', '_test_13_f'] - }), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_lookup1', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup1', - varType: 'lookup' - }), - v('_lookup2', '', { - includeInOutput: false, - points: [ - [0, 0], - [1, 1], - [2, 2] - ], - range: [ - [0, 0], - [2, 2] - ], - refId: '__lookup2', - varType: 'lookup' - }) - ]) - }) - - it.skip('should work for XMILE "quantum" model', () => { - const vars = readSubscriptsAndEquations('quantum') - expect(vars).toEqual([ - v('a', 'QUANTUM(1.9,1.0)', { - refId: '_a', - referencedFunctionNames: ['__quantum'] - }), - v('b', 'QUANTUM(0.9,1.0)', { - refId: '_b', - referencedFunctionNames: ['__quantum'] - }), - v('c', 'QUANTUM(-0.9,1.0)', { - refId: '_c', - referencedFunctionNames: ['__quantum'] - }), - v('d', 'QUANTUM(-1.9,1.0)', { - refId: '_d', - referencedFunctionNames: ['__quantum'] - }), - v('e', 'QUANTUM(112.3,10.0)', { - refId: '_e', - referencedFunctionNames: ['__quantum'] - }), - v('f', 'QUANTUM(50,12)', { - refId: '_f', - referencedFunctionNames: ['__quantum'] - }), - v('g', 'QUANTUM(423,63)', { - refId: '_g', - referencedFunctionNames: ['__quantum'] - }), - v('h', 'QUANTUM(10,10)', { - refId: '_h', - referencedFunctionNames: ['__quantum'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "ref" model', () => { - const vars = readSubscriptsAndEquations('ref') - expect(vars).toEqual([ - v('ecc[t1]', 'ce[t1]+1', { - refId: '_ecc[_t1]', - references: ['_ce[_t1]'], - subscripts: ['_t1'] - }), - v('ecc[tNext]', 'ce[tNext]+1', { - refId: '_ecc[_t2]', - references: ['_ce[_t2]'], - separationDims: ['_tnext'], - subscripts: ['_t2'] - }), - v('ecc[tNext]', 'ce[tNext]+1', { - refId: '_ecc[_t3]', - references: ['_ce[_t3]'], - separationDims: ['_tnext'], - subscripts: ['_t3'] - }), - v('ce[t1]', '1', { - refId: '_ce[_t1]', - subscripts: ['_t1'], - varType: 'const' - }), - v('ce[tNext]', 'ecc[tPrev]+1', { - refId: '_ce[_t2]', - references: ['_ecc[_t1]'], - separationDims: ['_tnext'], - subscripts: ['_t2'] - }), - v('ce[tNext]', 'ecc[tPrev]+1', { - refId: '_ce[_t3]', - references: ['_ecc[_t2]'], - separationDims: ['_tnext'], - subscripts: ['_t3'] - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "sample" model', () => { - const vars = readSubscriptsAndEquations('sample') - expect(vars).toEqual([ - v('a', 'SAMPLE IF TRUE(MODULO(Time,5)=0,Time,0)', { - hasInitValue: true, - refId: '_a', - referencedFunctionNames: ['__sample_if_true', '__modulo'], - references: ['_time'] - }), - v('b', 'a', { - refId: '_b', - references: ['_a'] - }), - v('F', 'SAMPLE IF TRUE(Time=5,2,IF THEN ELSE(switch=1,1,0))', { - hasInitValue: true, - initReferences: ['_switch'], - refId: '_f', - referencedFunctionNames: ['__sample_if_true'], - references: ['_time'] - }), - v('G', 'INTEG(rate,2*COS(scale))', { - hasInitValue: true, - initReferences: ['_scale'], - refId: '_g', - referencedFunctionNames: ['__integ', '__cos'], - references: ['_rate'], - varType: 'level' - }), - v('rate', 'STEP(10,10)', { - refId: '_rate', - referencedFunctionNames: ['__step'] - }), - v('scale', '1', { - refId: '_scale', - varType: 'const' - }), - v('switch', '1', { - refId: '_switch', - varType: 'const' - }), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "sir" model', () => { - const vars = readSubscriptsAndEquations('sir') - expect(vars).toEqual([ - v('Infectious Population I', 'INTEG(Infection Rate-Recovery Rate,1)', { - hasInitValue: true, - refId: '_infectious_population_i', - referencedFunctionNames: ['__integ'], - references: ['_infection_rate', '_recovery_rate'], - varType: 'level' - }), - v('Initial Contact Rate', '2.5', { - refId: '_initial_contact_rate', - varType: 'const' - }), - v('Contact Rate c', 'Initial Contact Rate', { - refId: '_contact_rate_c', - references: ['_initial_contact_rate'] - }), - v( - 'Reproduction Rate', - 'Contact Rate c*Infectivity i*Average Duration of Illness d*Susceptible Population S/Total Population P', - { - refId: '_reproduction_rate', - references: [ - '_contact_rate_c', - '_infectivity_i', - '_average_duration_of_illness_d', - '_susceptible_population_s', - '_total_population_p' - ] - } - ), - v('Total Population P', '10000', { - refId: '_total_population_p', - varType: 'const' - }), - v( - 'Infection Rate', - 'Contact Rate c*Infectivity i*Susceptible Population S*Infectious Population I/Total Population P', - { - refId: '_infection_rate', - references: [ - '_contact_rate_c', - '_infectivity_i', - '_susceptible_population_s', - '_infectious_population_i', - '_total_population_p' - ] - } - ), - v('Average Duration of Illness d', '2', { - refId: '_average_duration_of_illness_d', - varType: 'const' - }), - v('Recovered Population R', 'INTEG(Recovery Rate,0)', { - hasInitValue: true, - refId: '_recovered_population_r', - referencedFunctionNames: ['__integ'], - references: ['_recovery_rate'], - varType: 'level' - }), - v('Recovery Rate', 'Infectious Population I/Average Duration of Illness d', { - refId: '_recovery_rate', - references: ['_infectious_population_i', '_average_duration_of_illness_d'] - }), - v('Infectivity i', '0.25', { - refId: '_infectivity_i', - varType: 'const' - }), - v( - 'Susceptible Population S', - 'INTEG(-Infection Rate,Total Population P-Infectious Population I-Recovered Population R)', - { - hasInitValue: true, - initReferences: ['_total_population_p', '_infectious_population_i', '_recovered_population_r'], - refId: '_susceptible_population_s', - referencedFunctionNames: ['__integ'], - references: ['_infection_rate'], - varType: 'level' - } - ), - v('FINAL TIME', '200', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', '2', { - refId: '_saveper', - varType: 'const' - }), - v('TIME STEP', '0.0625', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "smooth" model', () => { - const vars = readSubscriptsAndEquations('smooth') - expect(vars).toEqual([ - v('input', '3+PULSE(10,10)', { - refId: '_input', - referencedFunctionNames: ['__pulse'] - }), - v('input 2[SubA]', '3+PULSE(10,10)', { - refId: '_input_2[_a2]', - referencedFunctionNames: ['__pulse'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('input 2[SubA]', '3+PULSE(10,10)', { - refId: '_input_2[_a3]', - referencedFunctionNames: ['__pulse'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('input 3[DimA]', '3+PULSE(10,10)', { - refId: '_input_3', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima'] - }), - v('input 3x3[DimA,DimB]', '3+PULSE(10,10)', { - refId: '_input_3x3', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima', '_dimb'] - }), - v('input 2x3[SubA,DimB]', '3+PULSE(10,10)', { - refId: '_input_2x3[_a2,_dimb]', - referencedFunctionNames: ['__pulse'], - separationDims: ['_suba'], - subscripts: ['_a2', '_dimb'] - }), - v('input 2x3[SubA,DimB]', '3+PULSE(10,10)', { - refId: '_input_2x3[_a3,_dimb]', - referencedFunctionNames: ['__pulse'], - separationDims: ['_suba'], - subscripts: ['_a3', '_dimb'] - }), - v('delay', '2', { - refId: '_delay', - varType: 'const' - }), - v('delay 2[SubA]', '2', { - refId: '_delay_2[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('delay 2[SubA]', '2', { - refId: '_delay_2[_a3]', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('delay 3[DimA]', '2', { - refId: '_delay_3', - subscripts: ['_dima'], - varType: 'const' - }), - v('initial s', '50', { - refId: '_initial_s', - varType: 'const' - }), - v('initial s with subscripts[DimA]', '50', { - refId: '_initial_s_with_subscripts', - subscripts: ['_dima'], - varType: 'const' - }), - v('s1', 'SMOOTH(input,delay)', { - refId: '_s1', - references: ['__level1'], - smoothVarRefId: '__level1' - }), - v('s2[DimA]', 'SMOOTH(input,delay)', { - refId: '_s2', - references: ['__level2'], - smoothVarRefId: '__level2', - subscripts: ['_dima'] - }), - v('s3[DimA]', 'SMOOTH(input 3[DimA],delay 3[DimA])', { - refId: '_s3', - references: ['__level3'], - smoothVarRefId: '__level3', - subscripts: ['_dima'] - }), - v('s4[SubA]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { - refId: '_s4[_a2]', - references: ['__level_s4_1[_a2]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s4_1[_a2]', - subscripts: ['_a2'] - }), - v('s4[SubA]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { - refId: '_s4[_a3]', - references: ['__level_s4_1[_a3]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s4_1[_a3]', - subscripts: ['_a3'] - }), - v('s5[SubA]', 'SMOOTH3(input 2[SubA],delay 2[SubA])', { - refId: '_s5[_a2]', - references: ['__level_s5_1[_a2]', '__level_s5_2[_a2]', '__level_s5_3[_a2]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s5_3[_a2]', - subscripts: ['_a2'] - }), - v('s5[SubA]', 'SMOOTH3(input 2[SubA],delay 2[SubA])', { - refId: '_s5[_a3]', - references: ['__level_s5_1[_a3]', '__level_s5_2[_a3]', '__level_s5_3[_a3]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s5_3[_a3]', - subscripts: ['_a3'] - }), - v('s6[DimB]', 'SMOOTH(input 3[DimA],delay 3[DimA])', { - refId: '_s6', - references: ['__level4'], - smoothVarRefId: '__level4', - subscripts: ['_dimb'] - }), - v('s7[SubB]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { - refId: '_s7[_b2]', - references: ['__level_s7_1[_a2]'], - separationDims: ['_subb'], - smoothVarRefId: '__level_s7_1[_a2]', - subscripts: ['_b2'] - }), - v('s7[SubB]', 'SMOOTH(input 2[SubA],delay 2[SubA])', { - refId: '_s7[_b3]', - references: ['__level_s7_1[_a3]'], - separationDims: ['_subb'], - smoothVarRefId: '__level_s7_1[_a3]', - subscripts: ['_b3'] - }), - v('s8[DimA,DimB]', 'SMOOTH(input 3x3[DimA,DimB],delay)', { - refId: '_s8', - references: ['__level5'], - smoothVarRefId: '__level5', - subscripts: ['_dima', '_dimb'] - }), - v('s9[SubA,DimB]', 'SMOOTH(input 2x3[SubA,DimB],delay)', { - refId: '_s9[_a2,_dimb]', - references: ['__level_s9_1[_a2,_dimb]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s9_1[_a2,_dimb]', - subscripts: ['_a2', '_dimb'] - }), - v('s9[SubA,DimB]', 'SMOOTH(input 2x3[SubA,DimB],delay)', { - refId: '_s9[_a3,_dimb]', - references: ['__level_s9_1[_a3,_dimb]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s9_1[_a3,_dimb]', - subscripts: ['_a3', '_dimb'] - }), - v('s10[SubA,B1]', 'SMOOTH(input 2[SubA],delay)', { - refId: '_s10[_a2,_b1]', - references: ['__level_s10_1[_a2]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s10_1[_a2]', - subscripts: ['_a2', '_b1'] - }), - v('s10[SubA,B1]', 'SMOOTH(input 2[SubA],delay)', { - refId: '_s10[_a3,_b1]', - references: ['__level_s10_1[_a3]'], - separationDims: ['_suba'], - smoothVarRefId: '__level_s10_1[_a3]', - subscripts: ['_a3', '_b1'] - }), - v('s11[DimA]', 'SMOOTH3(input 3[DimA],delay)', { - refId: '_s11', - references: ['__level6', '__level7', '__level8'], - smoothVarRefId: '__level8', - subscripts: ['_dima'] - }), - v('s12[DimA]', 'SMOOTH3I(input 3[DimA],delay 3[DimA],initial s)', { - refId: '_s12', - references: ['__level9', '__level10', '__level11'], - smoothVarRefId: '__level11', - subscripts: ['_dima'] - }), - v('s13[DimA]', 'SMOOTH3I(input 3[DimA],delay,initial s)', { - refId: '_s13', - references: ['__level12', '__level13', '__level14'], - smoothVarRefId: '__level14', - subscripts: ['_dima'] - }), - v('s14[DimA]', 'SMOOTH3I(input 3[DimA],delay,initial s with subscripts[DimA])', { - refId: '_s14', - references: ['__level15', '__level16', '__level17'], - smoothVarRefId: '__level17', - subscripts: ['_dima'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '40', { - refId: '_final_time', - varType: 'const' - }), - v('SAVEPER', '1', { - refId: '_saveper', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_level1', 'INTEG((input-_level1)/delay,input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input'], - refId: '__level1', - referencedFunctionNames: ['__integ'], - references: ['_input', '_delay'], - varType: 'level' - }), - v('_level2', 'INTEG((input-_level2)/delay,input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input'], - refId: '__level2', - referencedFunctionNames: ['__integ'], - references: ['_input', '_delay'], - varType: 'level' - }), - v('_level3[DimA]', 'INTEG((input 3[DimA]-_level3[DimA])/delay 3[DimA],input 3[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_3'], - refId: '__level3', - referencedFunctionNames: ['__integ'], - references: ['_input_3', '_delay_3'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level_s4_1[a2]', 'INTEG((input 2[a2]-_level_s4_1[a2])/delay 2[a2],input 2[a2])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a2]'], - refId: '__level_s4_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '_delay_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_s4_1[a3]', 'INTEG((input 2[a3]-_level_s4_1[a3])/delay 2[a3],input 2[a3])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a3]'], - refId: '__level_s4_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '_delay_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_s5_1[a2]', 'INTEG((input 2[a2]-_level_s5_1[a2])/(delay 2[a2]/3),input 2[a2])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a2]'], - refId: '__level_s5_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '_delay_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_s5_2[a2]', 'INTEG((_level_s5_1[a2]-_level_s5_2[a2])/(delay 2[a2]/3),input 2[a2])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a2]'], - refId: '__level_s5_2[_a2]', - referencedFunctionNames: ['__integ'], - references: ['__level_s5_1[_a2]', '_delay_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_s5_3[a2]', 'INTEG((_level_s5_2[a2]-_level_s5_3[a2])/(delay 2[a2]/3),input 2[a2])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a2]'], - refId: '__level_s5_3[_a2]', - referencedFunctionNames: ['__integ'], - references: ['__level_s5_2[_a2]', '_delay_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_s5_1[a3]', 'INTEG((input 2[a3]-_level_s5_1[a3])/(delay 2[a3]/3),input 2[a3])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a3]'], - refId: '__level_s5_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '_delay_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_s5_2[a3]', 'INTEG((_level_s5_1[a3]-_level_s5_2[a3])/(delay 2[a3]/3),input 2[a3])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a3]'], - refId: '__level_s5_2[_a3]', - referencedFunctionNames: ['__integ'], - references: ['__level_s5_1[_a3]', '_delay_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level_s5_3[a3]', 'INTEG((_level_s5_2[a3]-_level_s5_3[a3])/(delay 2[a3]/3),input 2[a3])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a3]'], - refId: '__level_s5_3[_a3]', - referencedFunctionNames: ['__integ'], - references: ['__level_s5_2[_a3]', '_delay_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level4[DimA]', 'INTEG((input 3[DimA]-_level4[DimA])/delay 3[DimA],input 3[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_3'], - refId: '__level4', - referencedFunctionNames: ['__integ'], - references: ['_input_3', '_delay_3'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level_s7_1[a2]', 'INTEG((input 2[a2]-_level_s7_1[a2])/delay 2[a2],input 2[a2])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a2]'], - refId: '__level_s7_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '_delay_2[_a2]'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_s7_1[a3]', 'INTEG((input 2[a3]-_level_s7_1[a3])/delay 2[a3],input 2[a3])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a3]'], - refId: '__level_s7_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '_delay_2[_a3]'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level5[DimA,DimB]', 'INTEG((input 3x3[DimA,DimB]-_level5[DimA,DimB])/delay,input 3x3[DimA,DimB])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_3x3'], - refId: '__level5', - referencedFunctionNames: ['__integ'], - references: ['_input_3x3', '_delay'], - subscripts: ['_dima', '_dimb'], - varType: 'level' - }), - v('_level_s9_1[a2,DimB]', 'INTEG((input 2x3[a2,DimB]-_level_s9_1[a2,DimB])/delay,input 2x3[a2,DimB])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2x3[_a2,_dimb]'], - refId: '__level_s9_1[_a2,_dimb]', - referencedFunctionNames: ['__integ'], - references: ['_input_2x3[_a2,_dimb]', '_delay'], - subscripts: ['_a2', '_dimb'], - varType: 'level' - }), - v('_level_s9_1[a3,DimB]', 'INTEG((input 2x3[a3,DimB]-_level_s9_1[a3,DimB])/delay,input 2x3[a3,DimB])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2x3[_a3,_dimb]'], - refId: '__level_s9_1[_a3,_dimb]', - referencedFunctionNames: ['__integ'], - references: ['_input_2x3[_a3,_dimb]', '_delay'], - subscripts: ['_a3', '_dimb'], - varType: 'level' - }), - v('_level_s10_1[a2]', 'INTEG((input 2[a2]-_level_s10_1[a2])/delay,input 2[a2])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a2]'], - refId: '__level_s10_1[_a2]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a2]', '_delay'], - subscripts: ['_a2'], - varType: 'level' - }), - v('_level_s10_1[a3]', 'INTEG((input 2[a3]-_level_s10_1[a3])/delay,input 2[a3])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_2[_a3]'], - refId: '__level_s10_1[_a3]', - referencedFunctionNames: ['__integ'], - references: ['_input_2[_a3]', '_delay'], - subscripts: ['_a3'], - varType: 'level' - }), - v('_level6[DimA]', 'INTEG((input 3[DimA]-_level6[DimA])/(delay/3),input 3[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_3'], - refId: '__level6', - referencedFunctionNames: ['__integ'], - references: ['_input_3', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level7[DimA]', 'INTEG((_level6[DimA]-_level7[DimA])/(delay/3),input 3[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_3'], - refId: '__level7', - referencedFunctionNames: ['__integ'], - references: ['__level6', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level8[DimA]', 'INTEG((_level7[DimA]-_level8[DimA])/(delay/3),input 3[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input_3'], - refId: '__level8', - referencedFunctionNames: ['__integ'], - references: ['__level7', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level9[DimA]', 'INTEG((input 3[DimA]-_level9[DimA])/(delay 3[DimA]/3),initial s)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s'], - refId: '__level9', - referencedFunctionNames: ['__integ'], - references: ['_input_3', '_delay_3'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level10[DimA]', 'INTEG((_level9[DimA]-_level10[DimA])/(delay 3[DimA]/3),initial s)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s'], - refId: '__level10', - referencedFunctionNames: ['__integ'], - references: ['__level9', '_delay_3'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level11[DimA]', 'INTEG((_level10[DimA]-_level11[DimA])/(delay 3[DimA]/3),initial s)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s'], - refId: '__level11', - referencedFunctionNames: ['__integ'], - references: ['__level10', '_delay_3'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level12[DimA]', 'INTEG((input 3[DimA]-_level12[DimA])/(delay/3),initial s)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s'], - refId: '__level12', - referencedFunctionNames: ['__integ'], - references: ['_input_3', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level13[DimA]', 'INTEG((_level12[DimA]-_level13[DimA])/(delay/3),initial s)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s'], - refId: '__level13', - referencedFunctionNames: ['__integ'], - references: ['__level12', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level14[DimA]', 'INTEG((_level13[DimA]-_level14[DimA])/(delay/3),initial s)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s'], - refId: '__level14', - referencedFunctionNames: ['__integ'], - references: ['__level13', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level15[DimA]', 'INTEG((input 3[DimA]-_level15[DimA])/(delay/3),initial s with subscripts[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s_with_subscripts'], - refId: '__level15', - referencedFunctionNames: ['__integ'], - references: ['_input_3', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level16[DimA]', 'INTEG((_level15[DimA]-_level16[DimA])/(delay/3),initial s with subscripts[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s_with_subscripts'], - refId: '__level16', - referencedFunctionNames: ['__integ'], - references: ['__level15', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }), - v('_level17[DimA]', 'INTEG((_level16[DimA]-_level17[DimA])/(delay/3),initial s with subscripts[DimA])', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_initial_s_with_subscripts'], - refId: '__level17', - referencedFunctionNames: ['__integ'], - references: ['__level16', '_delay'], - subscripts: ['_dima'], - varType: 'level' - }) - ]) - }) - - it.skip('should work for XMILE "smooth3" model', () => { - const vars = readSubscriptsAndEquations('smooth3') - expect(vars).toEqual([ - v('a', '1', { - refId: '_a', - varType: 'const' - }), - v('S3', 'SMOOTH3(s3 input,MAX(a,b))', { - refId: '_s3', - references: ['__level1', '__level2', '__level3'], - smoothVarRefId: '__level3' - }), - v('b', '2', { - refId: '_b', - varType: 'const' - }), - v('s3 input', '3+PULSE(10,10)', { - refId: '_s3_input', - referencedFunctionNames: ['__pulse'] - }), - v('apt', '1', { - refId: '_apt', - varType: 'const' - }), - v('ca[A1]', '1000+RAMP(100,1,10)', { - refId: '_ca[_a1]', - referencedFunctionNames: ['__ramp'], - subscripts: ['_a1'] - }), - v('ca[A2]', '1000+RAMP(300,1,10)', { - refId: '_ca[_a2]', - referencedFunctionNames: ['__ramp'], - subscripts: ['_a2'] - }), - v('ca[A3]', '1000+RAMP(600,1,10)', { - refId: '_ca[_a3]', - referencedFunctionNames: ['__ramp'], - subscripts: ['_a3'] - }), - v('cs[DimA]', 'MIN(SMOOTH3(sr,apt),ca[DimA]/TIME STEP)', { - refId: '_cs', - referencedFunctionNames: ['__min'], - references: ['__level4', '__level5', '__level6', '_ca[_a1]', '_ca[_a2]', '_ca[_a3]', '_time_step'], - smoothVarRefId: '__level6', - subscripts: ['_dima'] - }), - v('sr', 'COS(Time/5)', { - refId: '_sr', - referencedFunctionNames: ['__cos'], - references: ['_time'] - }), - v('S2 Level 1', 'INTEG((input-S2 Level 1)/S2 Delay,input)', { - hasInitValue: true, - initReferences: ['_input'], - refId: '_s2_level_1', - referencedFunctionNames: ['__integ'], - references: ['_input', '_s2_delay'], - varType: 'level' - }), - v('S2', 'scale*S2 Level 3', { - refId: '_s2', - references: ['_scale', '_s2_level_3'] - }), - v('S2 Level 3', 'INTEG((S2 Level 2-S2 Level 3)/S2 Delay,input)', { - hasInitValue: true, - initReferences: ['_input'], - refId: '_s2_level_3', - referencedFunctionNames: ['__integ'], - references: ['_s2_level_2', '_s2_delay'], - varType: 'level' - }), - v('S2 Level 2', 'INTEG((S2 Level 1-S2 Level 2)/S2 Delay,input)', { - hasInitValue: true, - initReferences: ['_input'], - refId: '_s2_level_2', - referencedFunctionNames: ['__integ'], - references: ['_s2_level_1', '_s2_delay'], - varType: 'level' - }), - v('S2 Delay', 'delay/3', { - refId: '_s2_delay', - references: ['_delay'] - }), - v('delay', '2', { - refId: '_delay', - varType: 'const' - }), - v('input', '3+PULSE(10,10)', { - refId: '_input', - referencedFunctionNames: ['__pulse'] - }), - v('S1', 'scale*SMOOTH3(input,delay)', { - refId: '_s1', - references: ['_scale', '__level7', '__level8', '__level9'], - smoothVarRefId: '__level9' - }), - v('scale', '6', { - refId: '_scale', - varType: 'const' - }), - v('FINAL TIME', '40', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_level1', 'INTEG((s3 input-_level1)/(MAX(a,b)/3),s3 input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_s3_input'], - refId: '__level1', - referencedFunctionNames: ['__integ', '__max'], - references: ['_s3_input', '_a', '_b'], - varType: 'level' - }), - v('_level2', 'INTEG((_level1-_level2)/(MAX(a,b)/3),s3 input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_s3_input'], - refId: '__level2', - referencedFunctionNames: ['__integ', '__max'], - references: ['__level1', '_a', '_b'], - varType: 'level' - }), - v('_level3', 'INTEG((_level2-_level3)/(MAX(a,b)/3),s3 input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_s3_input'], - refId: '__level3', - referencedFunctionNames: ['__integ', '__max'], - references: ['__level2', '_a', '_b'], - varType: 'level' - }), - v('_level4', 'INTEG((sr-_level4)/(apt/3),sr)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_sr'], - refId: '__level4', - referencedFunctionNames: ['__integ'], - references: ['_sr', '_apt'], - varType: 'level' - }), - v('_level5', 'INTEG((_level4-_level5)/(apt/3),sr)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_sr'], - refId: '__level5', - referencedFunctionNames: ['__integ'], - references: ['__level4', '_apt'], - varType: 'level' - }), - v('_level6', 'INTEG((_level5-_level6)/(apt/3),sr)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_sr'], - refId: '__level6', - referencedFunctionNames: ['__integ'], - references: ['__level5', '_apt'], - varType: 'level' - }), - v('_level7', 'INTEG((input-_level7)/(delay/3),input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input'], - refId: '__level7', - referencedFunctionNames: ['__integ'], - references: ['_input', '_delay'], - varType: 'level' - }), - v('_level8', 'INTEG((_level7-_level8)/(delay/3),input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input'], - refId: '__level8', - referencedFunctionNames: ['__integ'], - references: ['__level7', '_delay'], - varType: 'level' - }), - v('_level9', 'INTEG((_level8-_level9)/(delay/3),input)', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input'], - refId: '__level9', - referencedFunctionNames: ['__integ'], - references: ['__level8', '_delay'], - varType: 'level' - }) - ]) - }) - - it.skip('should work for XMILE "specialchars" model', () => { - const vars = readSubscriptsAndEquations('specialchars') - expect(vars).toEqual([ - v('DOLLAR SIGN$', '1', { - refId: '_dollar_sign_', - varType: 'const' - }), - v("time's up", '2', { - refId: '_time_s_up', - varType: 'const' - }), - v('"M&Ms"', '3', { - refId: '__m_ms_', - varType: 'const' - }), - v('"100% true"', '4', { - refId: '__100__true_', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "subalias" model', () => { - const vars = readSubscriptsAndEquations('subalias') - expect(vars).toEqual([ - v('e[DimE]', '10,20,30', { - refId: '_e[_f1]', - separationDims: ['_dime'], - subscripts: ['_f1'], - varType: 'const' - }), - v('e[DimE]', '10,20,30', { - refId: '_e[_f2]', - separationDims: ['_dime'], - subscripts: ['_f2'], - varType: 'const' - }), - v('e[DimE]', '10,20,30', { - refId: '_e[_f3]', - separationDims: ['_dime'], - subscripts: ['_f3'], - varType: 'const' - }), - v('f[DimF]', '1,2,3', { - refId: '_f[_f1]', - separationDims: ['_dimf'], - subscripts: ['_f1'], - varType: 'const' - }), - v('f[DimF]', '1,2,3', { - refId: '_f[_f2]', - separationDims: ['_dimf'], - subscripts: ['_f2'], - varType: 'const' - }), - v('f[DimF]', '1,2,3', { - refId: '_f[_f3]', - separationDims: ['_dimf'], - subscripts: ['_f3'], - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "subscript" model', () => { - const vars = readSubscriptsAndEquations('subscript') - expect(vars).toEqual([ - v('b[DimB]', '1,2,3', { - refId: '_b[_b1]', - separationDims: ['_dimb'], - subscripts: ['_b1'], - varType: 'const' - }), - v('b[DimB]', '1,2,3', { - refId: '_b[_b2]', - separationDims: ['_dimb'], - subscripts: ['_b2'], - varType: 'const' - }), - v('b[DimB]', '1,2,3', { - refId: '_b[_b3]', - separationDims: ['_dimb'], - subscripts: ['_b3'], - varType: 'const' - }), - v('a[DimA]', 'b[DimB]', { - refId: '_a', - references: ['_b[_b1]', '_b[_b2]', '_b[_b3]'], - subscripts: ['_dima'] - }), - v('c[DimB]', 'b[DimB]', { - refId: '_c', - references: ['_b[_b1]', '_b[_b2]', '_b[_b3]'], - subscripts: ['_dimb'] - }), - v('d[A1]', 'b[B1]', { - refId: '_d', - references: ['_b[_b1]'], - subscripts: ['_a1'] - }), - v('e[B1]', 'b[B1]', { - refId: '_e', - references: ['_b[_b1]'], - subscripts: ['_b1'] - }), - v('f[DimA,B1]', '1', { - refId: '_f[_dima,_b1]', - subscripts: ['_dima', '_b1'], - varType: 'const' - }), - v('f[DimA,B2]', '2', { - refId: '_f[_dima,_b2]', - subscripts: ['_dima', '_b2'], - varType: 'const' - }), - v('f[DimA,B3]', '3', { - refId: '_f[_dima,_b3]', - subscripts: ['_dima', '_b3'], - varType: 'const' - }), - v('g[B1,DimA]', 'f[DimA,B1]', { - refId: '_g[_b1,_dima]', - references: ['_f[_dima,_b1]'], - subscripts: ['_b1', '_dima'] - }), - v('g[B2,DimA]', 'f[DimA,B2]', { - refId: '_g[_b2,_dima]', - references: ['_f[_dima,_b2]'], - subscripts: ['_b2', '_dima'] - }), - v('g[B3,DimA]', 'f[DimA,B3]', { - refId: '_g[_b3,_dima]', - references: ['_f[_dima,_b3]'], - subscripts: ['_b3', '_dima'] - }), - v('o[DimA,DimB]', 'f[DimA,DimB]', { - refId: '_o', - references: ['_f[_dima,_b1]', '_f[_dima,_b2]', '_f[_dima,_b3]'], - subscripts: ['_dima', '_dimb'] - }), - v('p[DimB,DimA]', 'f[DimA,DimB]', { - refId: '_p', - references: ['_f[_dima,_b1]', '_f[_dima,_b2]', '_f[_dima,_b3]'], - subscripts: ['_dimb', '_dima'] - }), - v('r[DimA]', 'IF THEN ELSE(DimA=Selected A,1,0)', { - refId: '_r', - references: ['_selected_a'], - subscripts: ['_dima'] - }), - v('Selected A', '2', { - refId: '_selected_a', - varType: 'const' - }), - v('s[DimA]', 'DimB', { - refId: '_s', - subscripts: ['_dima'] - }), - v('t[DimC]', '1', { - refId: '_t', - subscripts: ['_dimc'], - varType: 'const' - }), - v('u[C1]', '1', { - refId: '_u[_c1]', - subscripts: ['_c1'], - varType: 'const' - }), - v('u[C2]', '2', { - refId: '_u[_c2]', - subscripts: ['_c2'], - varType: 'const' - }), - v('u[C3]', '3', { - refId: '_u[_c3]', - subscripts: ['_c3'], - varType: 'const' - }), - v('u[C4]', '4', { - refId: '_u[_c4]', - subscripts: ['_c4'], - varType: 'const' - }), - v('u[C5]', '5', { - refId: '_u[_c5]', - subscripts: ['_c5'], - varType: 'const' - }), - v('v[DimA]', 'IF THEN ELSE(DimA=A2,1,0)', { - refId: '_v', - subscripts: ['_dima'] - }), - v('w[DimX,DimY]', 'DimX-DimY', { - refId: '_w', - subscripts: ['_dimx', '_dimy'] - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "sum" model', () => { - const vars = readSubscriptsAndEquations('sum') - expect(vars).toEqual([ - v('a[DimA]', '1,2,3', { - refId: '_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('a[DimA]', '1,2,3', { - refId: '_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('a[DimA]', '1,2,3', { - refId: '_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('b[DimA]', '4,5,6', { - refId: '_b[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('b[DimA]', '4,5,6', { - refId: '_b[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('b[DimA]', '4,5,6', { - refId: '_b[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('a 2[SubA]', '1,2', { - refId: '_a_2[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('a 2[SubA]', '1,2', { - refId: '_a_2[_a3]', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('b 2[SubA]', '4,5', { - refId: '_b_2[_a2]', - separationDims: ['_suba'], - subscripts: ['_a2'], - varType: 'const' - }), - v('b 2[SubA]', '4,5', { - refId: '_b_2[_a3]', - separationDims: ['_suba'], - subscripts: ['_a3'], - varType: 'const' - }), - v('c', 'SUM(a[DimA!])+1', { - refId: '_c', - referencedFunctionNames: ['__sum'], - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]'] - }), - v('d', 'SUM(a[DimA!])+SUM(b[DimA!])', { - refId: '_d', - referencedFunctionNames: ['__sum'], - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_a1]', '_b[_a2]', '_b[_a3]'] - }), - v('e', 'SUM(a[DimA!]*b[DimA!]/TIME STEP)', { - refId: '_e', - referencedFunctionNames: ['__sum'], - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_a1]', '_b[_a2]', '_b[_a3]', '_time_step'] - }), - v('f[DimA,DimC]', '1', { - refId: '_f', - subscripts: ['_dima', '_dimc'], - varType: 'const' - }), - v('g[DimA,DimC]', 'SUM(f[DimA!,DimC!])', { - refId: '_g', - referencedFunctionNames: ['__sum'], - references: ['_f'], - subscripts: ['_dima', '_dimc'] - }), - v('h[DimC]', '10,20,30', { - refId: '_h[_c1]', - separationDims: ['_dimc'], - subscripts: ['_c1'], - varType: 'const' - }), - v('h[DimC]', '10,20,30', { - refId: '_h[_c2]', - separationDims: ['_dimc'], - subscripts: ['_c2'], - varType: 'const' - }), - v('h[DimC]', '10,20,30', { - refId: '_h[_c3]', - separationDims: ['_dimc'], - subscripts: ['_c3'], - varType: 'const' - }), - v('i', 'SUM(a[DimA!]+h[DimC!])', { - refId: '_i', - referencedFunctionNames: ['__sum'], - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_h[_c1]', '_h[_c2]', '_h[_c3]'] - }), - v('j[DimA]', 'a[DimA]/SUM(b[DimA!])', { - refId: '_j', - referencedFunctionNames: ['__sum'], - references: ['_a[_a1]', '_a[_a2]', '_a[_a3]', '_b[_a1]', '_b[_a2]', '_b[_a3]'], - subscripts: ['_dima'] - }), - v('k[SubA]', 'SUM(b 2[SubA!])', { - refId: '_k[_a2]', - referencedFunctionNames: ['__sum'], - references: ['_b_2[_a2]', '_b_2[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('k[SubA]', 'SUM(b 2[SubA!])', { - refId: '_k[_a3]', - referencedFunctionNames: ['__sum'], - references: ['_b_2[_a2]', '_b_2[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('l[SubA]', 'a 2[SubA]/SUM(b 2[SubA!])', { - refId: '_l[_a2]', - referencedFunctionNames: ['__sum'], - references: ['_a_2[_a2]', '_b_2[_a2]', '_b_2[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a2'] - }), - v('l[SubA]', 'a 2[SubA]/SUM(b 2[SubA!])', { - refId: '_l[_a3]', - referencedFunctionNames: ['__sum'], - references: ['_a_2[_a3]', '_b_2[_a2]', '_b_2[_a3]'], - separationDims: ['_suba'], - subscripts: ['_a3'] - }), - v('m[D1,E1]', '11', { - refId: '_m[_d1,_e1]', - subscripts: ['_d1', '_e1'], - varType: 'const' - }), - v('m[D1,E2]', '12', { - refId: '_m[_d1,_e2]', - subscripts: ['_d1', '_e2'], - varType: 'const' - }), - v('m[D2,E1]', '21', { - refId: '_m[_d2,_e1]', - subscripts: ['_d2', '_e1'], - varType: 'const' - }), - v('m[D2,E2]', '22', { - refId: '_m[_d2,_e2]', - subscripts: ['_d2', '_e2'], - varType: 'const' - }), - v('msum[DimD]', 'SUM(m[DimD,DimE!])', { - refId: '_msum', - referencedFunctionNames: ['__sum'], - references: ['_m[_d1,_e1]', '_m[_d1,_e2]', '_m[_d2,_e1]', '_m[_d2,_e2]'], - subscripts: ['_dimd'] - }), - v('n[D1,E1,F1]', '111', { - refId: '_n[_d1,_e1,_f1]', - subscripts: ['_d1', '_e1', '_f1'], - varType: 'const' - }), - v('n[D1,E1,F2]', '112', { - refId: '_n[_d1,_e1,_f2]', - subscripts: ['_d1', '_e1', '_f2'], - varType: 'const' - }), - v('n[D1,E2,F1]', '121', { - refId: '_n[_d1,_e2,_f1]', - subscripts: ['_d1', '_e2', '_f1'], - varType: 'const' - }), - v('n[D1,E2,F2]', '122', { - refId: '_n[_d1,_e2,_f2]', - subscripts: ['_d1', '_e2', '_f2'], - varType: 'const' - }), - v('n[D2,E1,F1]', '211', { - refId: '_n[_d2,_e1,_f1]', - subscripts: ['_d2', '_e1', '_f1'], - varType: 'const' - }), - v('n[D2,E1,F2]', '212', { - refId: '_n[_d2,_e1,_f2]', - subscripts: ['_d2', '_e1', '_f2'], - varType: 'const' - }), - v('n[D2,E2,F1]', '221', { - refId: '_n[_d2,_e2,_f1]', - subscripts: ['_d2', '_e2', '_f1'], - varType: 'const' - }), - v('n[D2,E2,F2]', '222', { - refId: '_n[_d2,_e2,_f2]', - subscripts: ['_d2', '_e2', '_f2'], - varType: 'const' - }), - v('nsum[DimD,DimE]', 'SUM(n[DimD,DimE,DimF!])', { - refId: '_nsum', - referencedFunctionNames: ['__sum'], - references: [ - '_n[_d1,_e1,_f1]', - '_n[_d1,_e1,_f2]', - '_n[_d1,_e2,_f1]', - '_n[_d1,_e2,_f2]', - '_n[_d2,_e1,_f1]', - '_n[_d2,_e1,_f2]', - '_n[_d2,_e2,_f1]', - '_n[_d2,_e2,_f2]' - ], - subscripts: ['_dimd', '_dime'] - }), - v('o[D1,DimE,F1]', '111', { - refId: '_o[_d1,_dime,_f1]', - subscripts: ['_d1', '_dime', '_f1'], - varType: 'const' - }), - v('o[D1,DimE,F2]', '112', { - refId: '_o[_d1,_dime,_f2]', - subscripts: ['_d1', '_dime', '_f2'], - varType: 'const' - }), - v('o[D2,DimE,F1]', '211', { - refId: '_o[_d2,_dime,_f1]', - subscripts: ['_d2', '_dime', '_f1'], - varType: 'const' - }), - v('o[D2,DimE,F2]', '212', { - refId: '_o[_d2,_dime,_f2]', - subscripts: ['_d2', '_dime', '_f2'], - varType: 'const' - }), - v('osum[DimD,DimE]', 'SUM(o[DimD,DimE,DimF!])', { - refId: '_osum', - referencedFunctionNames: ['__sum'], - references: ['_o[_d1,_dime,_f1]', '_o[_d1,_dime,_f2]', '_o[_d2,_dime,_f1]', '_o[_d2,_dime,_f2]'], - subscripts: ['_dimd', '_dime'] - }), - v('t[DimT]', '1,2', { - refId: '_t[_t1]', - separationDims: ['_dimt'], - subscripts: ['_t1'], - varType: 'const' - }), - v('t[DimT]', '1,2', { - refId: '_t[_t2]', - separationDims: ['_dimt'], - subscripts: ['_t2'], - varType: 'const' - }), - v('u[DimU]', '10,20,30,40', { - refId: '_u[_u1]', - separationDims: ['_dimu'], - subscripts: ['_u1'], - varType: 'const' - }), - v('u[DimU]', '10,20,30,40', { - refId: '_u[_u2]', - separationDims: ['_dimu'], - subscripts: ['_u2'], - varType: 'const' - }), - v('u[DimU]', '10,20,30,40', { - refId: '_u[_u3]', - separationDims: ['_dimu'], - subscripts: ['_u3'], - varType: 'const' - }), - v('u[DimU]', '10,20,30,40', { - refId: '_u[_u4]', - separationDims: ['_dimu'], - subscripts: ['_u4'], - varType: 'const' - }), - v("t two dim[DimT,DimT']", "(10*t[DimT])+t[DimT']", { - refId: '_t_two_dim', - references: ['_t[_t1]', '_t[_t2]'], - subscripts: ['_dimt', '_dimt_'] - }), - v("t two dim with u[DimT,DimT',DimU]", "(10*u[DimU])+(10*t[DimT])+t[DimT']", { - refId: '_t_two_dim_with_u', - references: ['_u[_u1]', '_u[_u2]', '_u[_u3]', '_u[_u4]', '_t[_t1]', '_t[_t2]'], - subscripts: ['_dimt', '_dimt_', '_dimu'] - }), - v('v[DimT]', 'SUM(t two dim[DimT,DimT!])', { - refId: '_v', - referencedFunctionNames: ['__sum'], - references: ['_t_two_dim'], - subscripts: ['_dimt'] - }), - v('w[DimT,DimU]', 'u[DimU]*SUM(t two dim[DimT,DimT!])', { - refId: '_w', - referencedFunctionNames: ['__sum'], - references: ['_u[_u1]', '_u[_u2]', '_u[_u3]', '_u[_u4]', '_t_two_dim'], - subscripts: ['_dimt', '_dimu'] - }), - v('x[DimT,DimU]', 'SUM(t two dim with u[DimT,DimT!,DimU])', { - refId: '_x', - referencedFunctionNames: ['__sum'], - references: ['_t_two_dim_with_u'], - subscripts: ['_dimt', '_dimu'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "sumif" model', () => { - const vars = readSubscriptsAndEquations('sumif') - expect(vars).toEqual([ - v('A Values[DimA]', '', { - refId: '_a_values', - subscripts: ['_dima'], - varType: 'data' - }), - v('A Values Total', 'SUM(A Values[DimA!])', { - refId: '_a_values_total', - referencedFunctionNames: ['__sum'], - references: ['_a_values'] - }), - v( - 'A Values Avg', - 'ZIDZ(SUM(IF THEN ELSE(A Values[DimA!]=:NA:,0,A Values[DimA!])),SUM(IF THEN ELSE(A Values[DimA!]=:NA:,0,1)))', - { - refId: '_a_values_avg', - referencedFunctionNames: ['__zidz', '__sum'], - references: ['_a_values'] - } - ), - v('FINAL TIME', '10', { - refId: '_final_time', - varType: 'const' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) - - it.skip('should work for XMILE "trend" model', () => { - const vars = readSubscriptsAndEquations('trend') - expect(vars).toEqual([ - v('description', '0', { - refId: '_description', - varType: 'const' - }), - v('input', '1+0.5*SIN(2*3.14159*Time/period)', { - refId: '_input', - referencedFunctionNames: ['__sin'], - references: ['_time', '_period'] - }), - v('average time', '6', { - refId: '_average_time', - varType: 'const' - }), - v('initial trend', '10', { - refId: '_initial_trend', - varType: 'const' - }), - v('period', '20', { - refId: '_period', - varType: 'const' - }), - v('TREND of input', 'TREND(input,average time,initial trend)', { - refId: '_trend_of_input', - references: ['__level1', '__aux1'], - trendVarName: '__aux1' - }), - v('trend1', 'ZIDZ(input-average value,average time*ABS(average value))', { - refId: '_trend1', - referencedFunctionNames: ['__zidz', '__abs'], - references: ['_input', '_average_value', '_average_time'] - }), - v('average value', 'INTEG((input-average value)/average time,input/(1+initial trend*average time))', { - hasInitValue: true, - initReferences: ['_input', '_initial_trend', '_average_time'], - refId: '_average_value', - referencedFunctionNames: ['__integ'], - references: ['_input', '_average_time'], - varType: 'level' - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '100', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }), - v('_level1', 'INTEG((input-_level1)/average time,input/(1+initial trend*average time))', { - hasInitValue: true, - includeInOutput: false, - initReferences: ['_input', '_initial_trend', '_average_time'], - refId: '__level1', - referencedFunctionNames: ['__integ'], - references: ['_input', '_average_time'], - varType: 'level' - }), - v('_aux1', 'ZIDZ(input-_level1,average time*ABS(_level1))', { - includeInOutput: false, - refId: '__aux1', - referencedFunctionNames: ['__zidz', '__abs'], - references: ['_input', '__level1', '_average_time'] - }) - ]) - }) - - it.skip('should work for XMILE "vector" model', () => { - const vars = readSubscriptsAndEquations('vector') - expect(vars).toEqual([ - v('ASCENDING', '1', { - refId: '_ascending', - varType: 'const' - }), - v('DESCENDING', '0', { - refId: '_descending', - varType: 'const' - }), - v('VSSUM', '0', { - refId: '_vssum', - varType: 'const' - }), - v('VSMAX', '3', { - refId: '_vsmax', - varType: 'const' - }), - v('VSERRNONE', '0', { - refId: '_vserrnone', - varType: 'const' - }), - v('VSERRATLEASTONE', '1', { - refId: '_vserratleastone', - varType: 'const' - }), - v('a[DimA]', '0,1,1', { - refId: '_a[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('a[DimA]', '0,1,1', { - refId: '_a[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('a[DimA]', '0,1,1', { - refId: '_a[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('b[DimB]', '1,2', { - refId: '_b[_b1]', - separationDims: ['_dimb'], - subscripts: ['_b1'], - varType: 'const' - }), - v('b[DimB]', '1,2', { - refId: '_b[_b2]', - separationDims: ['_dimb'], - subscripts: ['_b2'], - varType: 'const' - }), - v('c[DimA]', '10+VECTOR ELM MAP(b[B1],a[DimA])', { - refId: '_c', - referencedFunctionNames: ['__vector_elm_map'], - references: ['_b[_b1]', '_a[_a1]', '_a[_a2]', '_a[_a3]'], - subscripts: ['_dima'] - }), - v('d[A1,B1]', '1', { - refId: '_d[_a1,_b1]', - subscripts: ['_a1', '_b1'], - varType: 'const' - }), - v('d[A2,B1]', '2', { - refId: '_d[_a2,_b1]', - subscripts: ['_a2', '_b1'], - varType: 'const' - }), - v('d[A3,B1]', '3', { - refId: '_d[_a3,_b1]', - subscripts: ['_a3', '_b1'], - varType: 'const' - }), - v('d[A1,B2]', '4', { - refId: '_d[_a1,_b2]', - subscripts: ['_a1', '_b2'], - varType: 'const' - }), - v('d[A2,B2]', '5', { - refId: '_d[_a2,_b2]', - subscripts: ['_a2', '_b2'], - varType: 'const' - }), - v('d[A3,B2]', '6', { - refId: '_d[_a3,_b2]', - subscripts: ['_a3', '_b2'], - varType: 'const' - }), - v('e[A1,B1]', '0', { - refId: '_e[_a1,_b1]', - subscripts: ['_a1', '_b1'], - varType: 'const' - }), - v('e[A2,B1]', '1', { - refId: '_e[_a2,_b1]', - subscripts: ['_a2', '_b1'], - varType: 'const' - }), - v('e[A3,B1]', '0', { - refId: '_e[_a3,_b1]', - subscripts: ['_a3', '_b1'], - varType: 'const' - }), - v('e[A1,B2]', '1', { - refId: '_e[_a1,_b2]', - subscripts: ['_a1', '_b2'], - varType: 'const' - }), - v('e[A2,B2]', '0', { - refId: '_e[_a2,_b2]', - subscripts: ['_a2', '_b2'], - varType: 'const' - }), - v('e[A3,B2]', '1', { - refId: '_e[_a3,_b2]', - subscripts: ['_a3', '_b2'], - varType: 'const' - }), - v('f[DimA,DimB]', 'VECTOR ELM MAP(d[DimA,B1],a[DimA])', { - refId: '_f', - referencedFunctionNames: ['__vector_elm_map'], - references: ['_d[_a1,_b1]', '_d[_a2,_b1]', '_d[_a3,_b1]', '_a[_a1]', '_a[_a2]', '_a[_a3]'], - subscripts: ['_dima', '_dimb'] - }), - v('g[DimA,DimB]', 'VECTOR ELM MAP(d[DimA,B1],e[DimA,DimB])', { - refId: '_g', - referencedFunctionNames: ['__vector_elm_map'], - references: [ - '_d[_a1,_b1]', - '_d[_a2,_b1]', - '_d[_a3,_b1]', - '_e[_a1,_b1]', - '_e[_a1,_b2]', - '_e[_a2,_b1]', - '_e[_a2,_b2]', - '_e[_a3,_b1]', - '_e[_a3,_b2]' - ], - subscripts: ['_dima', '_dimb'] - }), - v('h[DimA]', '2100,2010,2020', { - refId: '_h[_a1]', - separationDims: ['_dima'], - subscripts: ['_a1'], - varType: 'const' - }), - v('h[DimA]', '2100,2010,2020', { - refId: '_h[_a2]', - separationDims: ['_dima'], - subscripts: ['_a2'], - varType: 'const' - }), - v('h[DimA]', '2100,2010,2020', { - refId: '_h[_a3]', - separationDims: ['_dima'], - subscripts: ['_a3'], - varType: 'const' - }), - v('l[DimA]', 'VECTOR SORT ORDER(h[DimA],ASCENDING)', { - refId: '_l', - referencedFunctionNames: ['__vector_sort_order'], - references: ['_h[_a1]', '_h[_a2]', '_h[_a3]', '_ascending'], - subscripts: ['_dima'] - }), - v('m[DimA]', 'VECTOR SORT ORDER(h[DimA],0)', { - refId: '_m', - referencedFunctionNames: ['__vector_sort_order'], - references: ['_h[_a1]', '_h[_a2]', '_h[_a3]'], - subscripts: ['_dima'] - }), - v('o[A1,B1]', '1', { - refId: '_o[_a1,_b1]', - subscripts: ['_a1', '_b1'], - varType: 'const' - }), - v('o[A1,B2]', '2', { - refId: '_o[_a1,_b2]', - subscripts: ['_a1', '_b2'], - varType: 'const' - }), - v('o[A2,B1]', '4', { - refId: '_o[_a2,_b1]', - subscripts: ['_a2', '_b1'], - varType: 'const' - }), - v('o[A2,B2]', '3', { - refId: '_o[_a2,_b2]', - subscripts: ['_a2', '_b2'], - varType: 'const' - }), - v('o[A3,B1]', '5', { - refId: '_o[_a3,_b1]', - subscripts: ['_a3', '_b1'], - varType: 'const' - }), - v('o[A3,B2]', '5', { - refId: '_o[_a3,_b2]', - subscripts: ['_a3', '_b2'], - varType: 'const' - }), - v('p[DimA,DimB]', 'VECTOR SORT ORDER(o[DimA,DimB],ASCENDING)', { - refId: '_p', - referencedFunctionNames: ['__vector_sort_order'], - references: [ - '_o[_a1,_b1]', - '_o[_a1,_b2]', - '_o[_a2,_b1]', - '_o[_a2,_b2]', - '_o[_a3,_b1]', - '_o[_a3,_b2]', - '_ascending' - ], - subscripts: ['_dima', '_dimb'] - }), - v('q[DimB]', 'VECTOR SELECT(e[DimA!,DimB],c[DimA!],0,VSSUM,VSERRNONE)', { - refId: '_q', - referencedFunctionNames: ['__vector_select'], - references: [ - '_e[_a1,_b1]', - '_e[_a1,_b2]', - '_e[_a2,_b1]', - '_e[_a2,_b2]', - '_e[_a3,_b1]', - '_e[_a3,_b2]', - '_c', - '_vssum', - '_vserrnone' - ], - subscripts: ['_dimb'] - }), - v('r[DimA]', 'VECTOR SELECT(e[DimA,DimB!],d[DimA,DimB!],:NA:,VSMAX,VSERRNONE)', { - refId: '_r', - referencedFunctionNames: ['__vector_select'], - references: [ - '_e[_a1,_b1]', - '_e[_a1,_b2]', - '_e[_a2,_b1]', - '_e[_a2,_b2]', - '_e[_a3,_b1]', - '_e[_a3,_b2]', - '_d[_a1,_b1]', - '_d[_a1,_b2]', - '_d[_a2,_b1]', - '_d[_a2,_b2]', - '_d[_a3,_b1]', - '_d[_a3,_b2]', - '_vsmax', - '_vserrnone' - ], - subscripts: ['_dima'] - }), - v('s[DimB]', 'SUM(c[DimA!]*e[DimA!,DimB])', { - refId: '_s', - referencedFunctionNames: ['__sum'], - references: ['_c', '_e[_a1,_b1]', '_e[_a1,_b2]', '_e[_a2,_b1]', '_e[_a2,_b2]', '_e[_a3,_b1]', '_e[_a3,_b2]'], - subscripts: ['_dimb'] - }), - v('u', 'VMAX(x[DimX!])', { - refId: '_u', - referencedFunctionNames: ['__vmax'], - references: ['_x[_five]', '_x[_four]', '_x[_one]', '_x[_three]', '_x[_two]'] - }), - v('v', 'VMAX(x[SubX!])', { - refId: '_v', - referencedFunctionNames: ['__vmax'], - references: ['_x[_four]', '_x[_three]', '_x[_two]'] - }), - v('w', 'VMIN(x[DimX!])', { - refId: '_w', - referencedFunctionNames: ['__vmin'], - references: ['_x[_five]', '_x[_four]', '_x[_one]', '_x[_three]', '_x[_two]'] - }), - v('x[DimX]', '1,2,3,4,5', { - refId: '_x[_one]', - separationDims: ['_dimx'], - subscripts: ['_one'], - varType: 'const' - }), - v('x[DimX]', '1,2,3,4,5', { - refId: '_x[_two]', - separationDims: ['_dimx'], - subscripts: ['_two'], - varType: 'const' - }), - v('x[DimX]', '1,2,3,4,5', { - refId: '_x[_three]', - separationDims: ['_dimx'], - subscripts: ['_three'], - varType: 'const' - }), - v('x[DimX]', '1,2,3,4,5', { - refId: '_x[_four]', - separationDims: ['_dimx'], - subscripts: ['_four'], - varType: 'const' - }), - v('x[DimX]', '1,2,3,4,5', { - refId: '_x[_five]', - separationDims: ['_dimx'], - subscripts: ['_five'], - varType: 'const' - }), - v('y[DimA]', 'VECTOR ELM MAP(x[three],(DimA-1))', { - refId: '_y', - referencedFunctionNames: ['__vector_elm_map'], - references: ['_x[_three]'], - subscripts: ['_dima'] - }), - v('INITIAL TIME', '0', { - refId: '_initial_time', - varType: 'const' - }), - v('FINAL TIME', '1', { - refId: '_final_time', - varType: 'const' - }), - v('TIME STEP', '1', { - refId: '_time_step', - varType: 'const' - }), - v('SAVEPER', 'TIME STEP', { - refId: '_saveper', - references: ['_time_step'] - }), - v('Time', '', { - refId: '_time', - varType: 'const' - }) - ]) - }) }) From 3a2650956acd7d0b17175bb1fe8c7092331a99fc Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 16:17:36 -0700 Subject: [PATCH 21/77] fix: update readEquations so that it handles Vensim and Stella functions differently --- packages/compile/src/model/model.js | 8 +- .../src/model/read-equation-fn-delay.js | 10 +- .../src/model/read-equation-fn-smooth.js | 12 +- .../src/model/read-equations-xmile.spec.ts | 139 ++-- packages/compile/src/model/read-equations.js | 605 +++++++++++------- 5 files changed, 463 insertions(+), 311 deletions(-) diff --git a/packages/compile/src/model/model.js b/packages/compile/src/model/model.js index 5ffe3116..8db55657 100644 --- a/packages/compile/src/model/model.js +++ b/packages/compile/src/model/model.js @@ -127,7 +127,7 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) { } // Analyze model equations to fill in more details about variables. - analyze(spec?.inputVars, opts) + analyze(parsedModel.kind, spec?.inputVars, opts) if (opts?.stopAfterAnalyze) return // Check that all input and output vars in the spec actually exist in the model. @@ -283,7 +283,7 @@ function resolveDimensions(dimensionFamilies) { } } -function analyze(inputVars, opts) { +function analyze(modelKind, inputVars, opts) { // Analyze the RHS of each equation in stages after all the variables are read. // Find non-apply-to-all vars that are defined with more than one equation. findNonAtoAVars() @@ -299,7 +299,9 @@ function analyze(inputVars, opts) { if (opts?.stopAfterReduceVariables === true) return // Read the RHS to list the refIds of vars that are referenced and set the var type. - variables.forEach(readEquation) + variables.forEach(v => { + readEquation(v, modelKind) + }) } function checkSpecVars(spec) { diff --git a/packages/compile/src/model/read-equation-fn-delay.js b/packages/compile/src/model/read-equation-fn-delay.js index eea10c83..aae4e49a 100644 --- a/packages/compile/src/model/read-equation-fn-delay.js +++ b/packages/compile/src/model/read-equation-fn-delay.js @@ -16,10 +16,12 @@ import Model from './model.js' /** * Generate level and aux variables that implement one of the following `DELAY` function * call variants: - * - DELAY1 - * - DELAY1I - * - DELAY3 - * - DELAY3I + * - DELAY1 (Vensim) + * - DELAY1I (Vensim) + * - DELAY3 (Vensim) + * - DELAY3I (Vensim) + * - DELAY1 (Stella) + * - DELAY3 (Stella) * * TODO: Docs * diff --git a/packages/compile/src/model/read-equation-fn-smooth.js b/packages/compile/src/model/read-equation-fn-smooth.js index 28c77e39..8c44b368 100644 --- a/packages/compile/src/model/read-equation-fn-smooth.js +++ b/packages/compile/src/model/read-equation-fn-smooth.js @@ -15,10 +15,12 @@ import Model from './model.js' /** * Generate level and aux variables that implement one of the following `SMOOTH` function * call variants: - * - SMOOTH - * - SMOOTHI - * - SMOOTH3 - * - SMOOTH3I + * - SMOOTH (Vensim) + * - SMOOTHI (Vensim) + * - SMOOTH3 (Vensim) + * - SMOOTH3I (Vensim) + * - SMTH1 (Stella) + * - SMTH3 (Stella) * * TODO: Docs * @@ -43,7 +45,7 @@ export function generateSmoothVariables(v, callExpr, context) { } const fnId = callExpr.fnId - if (fnId === '_SMOOTH' || fnId === '_SMOOTHI') { + if (fnId === '_SMOOTH' || fnId === '_SMOOTHI' || fnId === '_SMTH1') { // Generate 1 level variable that will replace the `SMOOTH[I]` function call const level = generateSmoothLevel(v, context, argInput, argDelay, argInit, 1) // For `SMOOTH[I]`, the smoothVarRefId is the level var's refId diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 7c7b9bfb..7a78229e 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -3374,7 +3374,8 @@ ${elements.join('\n')} // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. // - it('should work for ACTIVE INITIAL function', () => { + // TODO: This test is skipped because Stella doesn't appear to include the ACTIVE INITIAL function + it.skip('should work for ACTIVE INITIAL function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // Initial Target Capacity = 1 ~~| @@ -3414,7 +3415,7 @@ ${elements.join('\n')} }) // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see - // if the Vensim `ALLOCATE AVAILABLE` is compatible enough + // if the Vensim `ALLOCATE AVAILABLE` function is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 2D pp, non-subscripted avail)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` @@ -3537,7 +3538,7 @@ ${elements.join('\n')} }) // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see - // if the Vensim `ALLOCATE AVAILABLE` is compatible enough + // if the Vensim `ALLOCATE AVAILABLE` function is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (1D LHS, 1D demand, 3D pp with specific first subscript, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| @@ -3695,7 +3696,7 @@ ${elements.join('\n')} }) // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see - // if the Vensim `ALLOCATE AVAILABLE` is compatible enough + // if the Vensim `ALLOCATE AVAILABLE` function is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 2D pp, non-subscripted avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| @@ -3809,7 +3810,7 @@ ${elements.join('\n')} }) // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see - // if the Vensim `ALLOCATE AVAILABLE` is compatible enough + // if the Vensim `ALLOCATE AVAILABLE` function is compatible enough it.skip('should work for ALLOCATE AVAILABLE function (2D LHS, 2D demand, 3D pp, 1D avail)', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| @@ -4024,7 +4025,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY1I function', () => { + it('should work for DELAY1 function (with initial value argument)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| @@ -4040,7 +4041,7 @@ ${elements.join('\n')} 2 - DELAY1I(x, 5, init) + DELAY1(x, 5, init) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -4053,7 +4054,7 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('y', 'DELAY1I(x,5,init)', { + v('y', 'DELAY1(x,5,init)', { refId: '_y', references: ['__level1', '__aux1'], delayVarRefId: '__level1', @@ -4076,7 +4077,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY1I function (with subscripted variables)', () => { + it('should work for DELAY1 function (with initial value argument and subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases // Equivalent Vensim model for reference: @@ -4134,7 +4135,7 @@ ${elements.join('\n')} - DELAY1I(input[DimA], delay[DimA], init[DimA]) + DELAY1(input[DimA], delay[DimA], init[DimA]) ` const mdl = xmile(xmileDims, xmileVars) const vars = readInlineModel(mdl) @@ -4174,7 +4175,7 @@ ${elements.join('\n')} subscripts: ['_dima'], varType: 'const' }), - v('y[DimA]', 'DELAY1I(input[DimA],delay[DimA],init[DimA])', { + v('y[DimA]', 'DELAY1(input[DimA],delay[DimA],init[DimA])', { delayTimeVarName: '__aux1', delayVarRefId: '__level1', refId: '_y', @@ -4202,7 +4203,7 @@ ${elements.join('\n')} // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions // for y[A1] and y[A2] instead of a single definition for y[SubA] - it.skip('should work for DELAY1I function (with separated variables using subdimension)', () => { + it.skip('should work for DELAY1 function (with separated variables using subdimension)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2, A3 ~~| @@ -4268,10 +4269,10 @@ ${elements.join('\n')} 5
- DELAY1I(input[A2], delay[A2], init[A2]) + DELAY1(input[A2], delay[A2], init[A2]) - DELAY1I(input[A3], delay[A3], init[A3]) + DELAY1(input[A3], delay[A3], init[A3])
` const mdl = xmile(xmileDims, xmileVars) @@ -4317,7 +4318,7 @@ ${elements.join('\n')} subscripts: ['_a1'], varType: 'const' }), - v('y[A2]', 'DELAY1I(input[A2],delay[A2],init[A2])', { + v('y[A2]', 'DELAY1(input[A2],delay[A2],init[A2])', { delayTimeVarName: '__aux1', delayVarRefId: '__level_y_1[_a2]', refId: '_y[_a2]', @@ -4325,7 +4326,7 @@ ${elements.join('\n')} separationDims: ['_suba'], subscripts: ['_a2'] }), - v('y[A3]', 'DELAY1I(input[A3],delay[A3],init[A3])', { + v('y[A3]', 'DELAY1(input[A3],delay[A3],init[A3])', { delayTimeVarName: '__aux2', delayVarRefId: '__level_y_1[_a3]', refId: '_y[_a3]', @@ -4453,7 +4454,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY3I function', () => { + it('should work for DELAY3 function (with initial value argument)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // input = 1 ~~| @@ -4473,7 +4474,7 @@ ${elements.join('\n')} 3
- DELAY3I(input, delay, init) + DELAY3(input, delay, init) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -4490,7 +4491,7 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('y', 'DELAY3I(input,delay,init)', { + v('y', 'DELAY3(input,delay,init)', { delayTimeVarName: '__aux4', delayVarRefId: '__level3', refId: '_y', @@ -4546,7 +4547,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY3I function (with nested function calls)', () => { + it('should work for DELAY3I function (with initial value argument and nested function calls)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // input = 1 ~~| @@ -4566,7 +4567,7 @@ ${elements.join('\n')} 3 - DELAY3I(MIN(0, input), MAX(0, delay), ABS(init)) + DELAY3(MIN(0, input), MAX(0, delay), ABS(init)) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -4583,7 +4584,7 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('y', 'DELAY3I(MIN(0,input),MAX(0,delay),ABS(init))', { + v('y', 'DELAY3(MIN(0,input),MAX(0,delay),ABS(init))', { delayTimeVarName: '__aux4', delayVarRefId: '__level3', refId: '_y', @@ -4643,7 +4644,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY3I function (with subscripted variables)', () => { + it('should work for DELAY3 function (with initial value argument and subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases // Equivalent Vensim model for reference: @@ -4701,7 +4702,7 @@ ${elements.join('\n')} - DELAY3I(input[DimA], delay[DimA], init[DimA]) + DELAY3(input[DimA], delay[DimA], init[DimA]) ` const mdl = xmile(xmileDims, xmileVars) const vars = readInlineModel(mdl) @@ -4741,7 +4742,7 @@ ${elements.join('\n')} subscripts: ['_dima'], varType: 'const' }), - v('y[DimA]', 'DELAY3I(input[DimA],delay[DimA],init[DimA])', { + v('y[DimA]', 'DELAY3(input[DimA],delay[DimA],init[DimA])', { delayTimeVarName: '__aux4', delayVarRefId: '__level3', refId: '_y', @@ -5055,7 +5056,9 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY FIXED function', () => { + // TODO: This test is skipped for now; in Stella, the function is called `DELAY` and we will need to see + // if the Vensim `DELAY FIXED` function is compatible enough + it.skip('should work for DELAY FIXED function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| @@ -5079,7 +5082,7 @@ ${elements.join('\n')} 3 - DELAY FIXED(x, delay, init) + DELAY(x, delay, init) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -5100,7 +5103,7 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('z', 'DELAY FIXED(x,delay,init)', { + v('z', 'DELAY(x,delay,init)', { refId: '_z', varType: 'level', varSubtype: 'fixedDelay', @@ -5113,7 +5116,8 @@ ${elements.join('\n')} ]) }) - it('should work for DEPRECIATE STRAIGHTLINE function', () => { + // TODO: This test is skipped because Stella doesn't appear to include the DEPRECIATE STRAIGHTLINE function + it.skip('should work for DEPRECIATE STRAIGHTLINE function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // dtime = 20 ~~| @@ -5186,7 +5190,8 @@ ${elements.join('\n')} ]) }) - it('should work for GAME function (no dimensions)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the GAME function + it.skip('should work for GAME function (no dimensions)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| @@ -5222,7 +5227,8 @@ ${elements.join('\n')} ]) }) - it('should work for GAME function (1D)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the GAME function + it.skip('should work for GAME function (1D)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2 ~~| @@ -5284,7 +5290,8 @@ ${elements.join('\n')} ]) }) - it('should work for GAME function (2D)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the GAME function + it.skip('should work for GAME function (2D)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2 ~~| @@ -5374,7 +5381,7 @@ ${elements.join('\n')} ]) }) - it('should work for GAMMA LN function', () => { + it('should work for GAMMALN function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| @@ -5386,7 +5393,7 @@ ${elements.join('\n')} 1 - GAMMA LN(x) + GAMMALN(x) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -5395,15 +5402,16 @@ ${elements.join('\n')} refId: '_x', varType: 'const' }), - v('y', 'GAMMA LN(x)', { + v('y', 'GAMMALN(x)', { refId: '_y', - referencedFunctionNames: ['__gamma_ln'], + referencedFunctionNames: ['__gammaln'], references: ['_x'] }) ]) }) - it('should work for GET DIRECT CONSTANTS function (single value)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the GET DIRECT CONSTANTS function + it.skip('should work for GET DIRECT CONSTANTS function (single value)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| @@ -5424,7 +5432,8 @@ ${elements.join('\n')} ]) }) - it('should work for GET DIRECT CONSTANTS function (1D)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the GET DIRECT CONSTANTS function + it.skip('should work for GET DIRECT CONSTANTS function (1D)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimB: B1, B2, B3 ~~| @@ -5457,7 +5466,8 @@ ${elements.join('\n')} ]) }) - it('should work for GET DIRECT CONSTANTS function (2D)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the GET DIRECT CONSTANTS function + it.skip('should work for GET DIRECT CONSTANTS function (2D)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimB: B1, B2, B3 ~~| @@ -5692,8 +5702,7 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function INIT instead of INITIAL - it('should work for INITIAL function', () => { + it('should work for INIT function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = Time * 2 ~~| @@ -5705,7 +5714,7 @@ ${elements.join('\n')} Time*2 - INITIAL(x) + INIT(x) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -5714,18 +5723,18 @@ ${elements.join('\n')} refId: '_x', references: ['_time'] }), - v('y', 'INITIAL(x)', { + v('y', 'INIT(x)', { refId: '_y', varType: 'initial', hasInitValue: true, initReferences: ['_x'], - referencedFunctionNames: ['__initial'] + referencedFunctionNames: ['__init'] }) ]) }) - // TODO: Not sure what the equivalent function is called in Stella - it('should work for INTEG function', () => { + // TODO: This test is skipped because Stella doesn't appear to include the INTEG function + it.skip('should work for INTEG function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = Time * 2 ~~| @@ -5765,8 +5774,8 @@ ${elements.join('\n')} ]) }) - // TODO: Not sure what the equivalent function is called in Stella - it('should work for INTEG function (with nested function calls)', () => { + // TODO: This test is skipped because Stella doesn't appear to include the INTEG function + it.skip('should work for INTEG function (with nested function calls)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = Time * 2 ~~| @@ -5940,8 +5949,7 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function LOOKUPINV instead of LOOKUP INVERT - it('should work for LOOKUP INVERT function (with lookup defined explicitly)', () => { + it('should work for LOOKUPINV function (with lookup defined explicitly)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x( (0,0),(2,1.3) ) ~~| @@ -5954,7 +5962,7 @@ ${elements.join('\n')} 0,1.3 - LOOKUP INVERT(x,1) + LOOKUPINV(x,1) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -5968,16 +5976,16 @@ ${elements.join('\n')} [2, 1.3] ] }), - v('y', 'LOOKUP INVERT(x,1)', { + v('y', 'LOOKUPINV(x,1)', { refId: '_y', - referencedFunctionNames: ['__lookup_invert'], + referencedFunctionNames: ['__lookupinv'], references: ['_x'] }) ]) }) // TODO: This test is skipped because Stella doesn't include the GET DIRECT LOOKUPS function - it.skip('should work for LOOKUP INVERT function (with lookup defined using GET DIRECT LOOKUPS)', () => { + it.skip('should work for LOOKUPINV function (with lookup defined using GET DIRECT LOOKUPS)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| x[DimA] = GET DIRECT LOOKUPS('lookups.csv', ',', '1', 'AH2') ~~| @@ -6006,9 +6014,9 @@ ${elements.join('\n')} subscripts: ['_a3'], varType: 'data' }), - v('y[DimA]', 'LOOKUP INVERT(x[DimA],Time)', { + v('y[DimA]', 'LOOKUPINV(x[DimA],Time)', { refId: '_y', - referencedFunctionNames: ['__lookup_invert'], + referencedFunctionNames: ['__lookupinv'], references: ['_x[_a1]', '_x[_a2]', '_x[_a3]', '_time'], subscripts: ['_dima'] }), @@ -6093,8 +6101,7 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function MOD instead of MODULO - it('should work for MODULO function', () => { + it('should work for MOD function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // a = 20 ~~| @@ -6110,7 +6117,7 @@ ${elements.join('\n')} 10 - MODULO(a,b) + MOD(a,b) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) @@ -6123,9 +6130,9 @@ ${elements.join('\n')} refId: '_b', varType: 'const' }), - v('y', 'MODULO(a,b)', { + v('y', 'MOD(a,b)', { refId: '_y', - referencedFunctionNames: ['__modulo'], + referencedFunctionNames: ['__mod'], references: ['_a', '_b'] }) ]) @@ -6133,8 +6140,9 @@ ${elements.join('\n')} // TODO: Add a variant where discount rate is defined as (x+1) (old reader did not include // parens and might generate incorrect equation) - // TODO: Stella's NPV function takes 2 or 3 arguments, but Vensim's takes 4 arguments - it('should work for NPV function', () => { + // TODO: This test is skipped because Stella's NPV function takes 2 or 3 arguments, but Vensim's + // takes 4 arguments, so it is not implemented yet in SDE + it.skip('should work for NPV function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // stream = 100 ~~| @@ -6212,8 +6220,9 @@ ${elements.join('\n')} // TODO it.skip('should work for NPV function (with subscripted variables)', () => {}) - // TODO: Stella's PULSE function takes 1 or 3 arguments, but Vensim's takes 2 arguments - it('should work for PULSE function', () => { + // TODO: This test is skipped because Stella's PULSE function takes 1 or 3 arguments, but Vensim's + // takes 2 arguments, so it is not implemented yet in SDE + it.skip('should work for PULSE function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // start = 10 ~~| diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 163c7827..a538e85f 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -14,7 +14,10 @@ import { generateLookup } from './read-equation-fn-with-lookup.js' import { readVariables } from './read-variables.js' class Context { - constructor(eqnLhs, refId) { + constructor(modelKind, eqnLhs, refId) { + // The kind of model being read, either 'vensim' or 'xmile' + this.modelKind = modelKind + // The LHS of the equation being processed this.eqnLhs = eqnLhs @@ -109,7 +112,7 @@ class Context { * @param {string[]} eqnStrings An array of individual equation strings in Vensim format. */ defineVariables(eqnStrings) { - // Parse the equation text + // Parse the equation text, which is assumed to be in Vensim format const eqnText = eqnStrings.join('\n') const parsedModel = { kind: 'vensim', root: parseVensimModel(eqnText) } @@ -131,7 +134,7 @@ class Context { vars.forEach(v => { // Process each variable using the same process as above - readEquation(v) + readEquation(v, 'vensim') // Inhibit output for generated variables v.includeInOutput = false @@ -168,10 +171,11 @@ class Context { * TODO: Docs and types * * @param v {*} The `Variable` instance to process. + * @param {string} modelKind The kind of model being read, either 'vensim' or 'xmile'. */ -export function readEquation(v) { +export function readEquation(v, modelKind) { const eqn = v.parsedEqn - const context = new Context(eqn?.lhs, v.refId) + const context = new Context(modelKind, eqn?.lhs, v.refId) // Visit the RHS of the equation. If the equation is undefined, it is a synthesized // variable (e.g., `Time`), in which case we skip this step. @@ -383,257 +387,380 @@ function visitFunctionCall(v, callExpr, context) { // (they will be visited when processing the replacement equations) let visitArgs = true - switch (callExpr.fnId) { - // - // - // 1-argument functions... - // - // - - case '_ABS': - case '_ARCCOS': - case '_ARCSIN': - case '_ARCTAN': - case '_COS': - case '_ELMCOUNT': - case '_EXP': - case '_GAMMA_LN': - case '_INTEGER': - case '_LN': - case '_SIN': - case '_SQRT': - case '_SUM': - case '_TAN': - case '_VMAX': - case '_VMIN': - validateCallArgs(callExpr, 1) - break + // If the `unhandled` flag is set, it means we did not match a known function + let unhandled = false + + // Helper function that validates a function call for a Vensim model + function validateVensimFunctionCall() { + switch (callExpr.fnId) { + // + // + // 1-argument functions... + // + // + + case '_ABS': + case '_ARCCOS': + case '_ARCSIN': + case '_ARCTAN': + case '_COS': + case '_ELMCOUNT': + case '_EXP': + case '_GAMMA_LN': + case '_INTEGER': + case '_LN': + case '_SIN': + case '_SQRT': + case '_SUM': + case '_TAN': + case '_VMAX': + case '_VMIN': + validateCallArgs(callExpr, 1) + break - // - // - // 2-argument functions... - // - // - - case '_LOOKUP_BACKWARD': - case '_LOOKUP_FORWARD': - case '_LOOKUP_INVERT': - case '_MAX': - case '_MIN': - case '_MODULO': - case '_POW': - case '_POWER': - case '_PULSE': - case '_QUANTUM': - case '_STEP': - case '_VECTOR_ELM_MAP': - case '_VECTOR_SORT_ORDER': - case '_ZIDZ': - validateCallArgs(callExpr, 2) - break + // + // + // 2-argument functions... + // + // + + case '_LOOKUP_BACKWARD': + case '_LOOKUP_FORWARD': + case '_LOOKUP_INVERT': + case '_MAX': + case '_MIN': + case '_MODULO': + case '_POW': + case '_POWER': + case '_PULSE': + case '_QUANTUM': + case '_STEP': + case '_VECTOR_ELM_MAP': + case '_VECTOR_SORT_ORDER': + case '_ZIDZ': + validateCallArgs(callExpr, 2) + break - // - // - // 3-plus-argument functions... - // - // + // + // + // 3-plus-argument functions... + // + // - case '_GET_DATA_BETWEEN_TIMES': - case '_RAMP': - case '_XIDZ': - validateCallArgs(callExpr, 3) - break + case '_GET_DATA_BETWEEN_TIMES': + case '_RAMP': + case '_XIDZ': + validateCallArgs(callExpr, 3) + break - case '_PULSE_TRAIN': - validateCallArgs(callExpr, 4) - break + case '_PULSE_TRAIN': + validateCallArgs(callExpr, 4) + break - case '_VECTOR_SELECT': - validateCallArgs(callExpr, 5) - break + case '_VECTOR_SELECT': + validateCallArgs(callExpr, 5) + break - // - // - // Complex functions... - // - // - - case '_ACTIVE_INITIAL': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 2) - v.hasInitValue = true - // The 2nd argument is used at init time - argModes[1] = 'init' - break + // + // + // Complex functions... + // + // + + case '_ACTIVE_INITIAL': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + v.hasInitValue = true + // The 2nd argument is used at init time + argModes[1] = 'init' + break - case '_ALLOCATE_AVAILABLE': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 3) - break + case '_ALLOCATE_AVAILABLE': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 3) + break - case '_DELAY1': - case '_DELAY1I': - case '_DELAY3': - case '_DELAY3I': - validateCallArgs(callExpr, callExpr.fnId.endsWith('I') ? 3 : 2) - addFnReference = false - visitArgs = false - generateDelayVariables(v, callExpr, context) - break + case '_DELAY1': + case '_DELAY1I': + case '_DELAY3': + case '_DELAY3I': + validateCallArgs(callExpr, callExpr.fnId.endsWith('I') ? 3 : 2) + addFnReference = false + visitArgs = false + generateDelayVariables(v, callExpr, context) + break - case '_DELAY_FIXED': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 3) - v.varType = 'level' - v.varSubtype = 'fixedDelay' - v.hasInitValue = true - v.fixedDelayVarName = canonicalName(newFixedDelayVarName()) - // The 2nd and 3rd arguments are used at init time - argModes[1] = 'init' - argModes[2] = 'init' - break + case '_DELAY_FIXED': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 3) + v.varType = 'level' + v.varSubtype = 'fixedDelay' + v.hasInitValue = true + v.fixedDelayVarName = canonicalName(newFixedDelayVarName()) + // The 2nd and 3rd arguments are used at init time + argModes[1] = 'init' + argModes[2] = 'init' + break - case '_DEPRECIATE_STRAIGHTLINE': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 4) - v.varSubtype = 'depreciation' - v.hasInitValue = true - v.depreciationVarName = canonicalName(newDepreciationVarName()) - // The 2nd and 3rd arguments are used at init time - // TODO: The 3rd (fisc) argument is not currently supported - // TODO: Shouldn't the last (init) argument be marked as 'init' here? (It's - // not treated as 'init' in the legacy reader.) - argModes[1] = 'init' - argModes[2] = 'init' - break + case '_DEPRECIATE_STRAIGHTLINE': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 4) + v.varSubtype = 'depreciation' + v.hasInitValue = true + v.depreciationVarName = canonicalName(newDepreciationVarName()) + // The 2nd and 3rd arguments are used at init time + // TODO: The 3rd (fisc) argument is not currently supported + // TODO: Shouldn't the last (init) argument be marked as 'init' here? (It's + // not treated as 'init' in the legacy reader.) + argModes[1] = 'init' + argModes[2] = 'init' + break - case '_GAME': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 1) - generateGameVariables(v, callExpr, context) - break + case '_GAME': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 1) + generateGameVariables(v, callExpr, context) + break - case '_GET_DIRECT_CONSTANTS': { - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 3) - validateCallArgType(callExpr, 0, 'string') - validateCallArgType(callExpr, 1, 'string') - validateCallArgType(callExpr, 2, 'string') - addFnReference = false - v.varType = 'const' - v.directConstArgs = { - file: callExpr.args[0].text, - tab: callExpr.args[1].text, - startCell: callExpr.args[2].text + case '_GET_DIRECT_CONSTANTS': { + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 3) + validateCallArgType(callExpr, 0, 'string') + validateCallArgType(callExpr, 1, 'string') + validateCallArgType(callExpr, 2, 'string') + addFnReference = false + v.varType = 'const' + v.directConstArgs = { + file: callExpr.args[0].text, + tab: callExpr.args[1].text, + startCell: callExpr.args[2].text + } + break } - break + + case '_GET_DIRECT_DATA': + case '_GET_DIRECT_LOOKUPS': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 4) + validateCallArgType(callExpr, 0, 'string') + validateCallArgType(callExpr, 1, 'string') + validateCallArgType(callExpr, 2, 'string') + validateCallArgType(callExpr, 3, 'string') + addFnReference = false + v.varType = 'data' + v.directDataArgs = { + file: callExpr.args[0].text, + tab: callExpr.args[1].text, + timeRowOrCol: callExpr.args[2].text, + startCell: callExpr.args[3].text + } + break + + case '_IF_THEN_ELSE': + validateCallArgs(callExpr, 3) + addFnReference = false + break + + case '_INITIAL': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 1) + v.varType = 'initial' + v.hasInitValue = true + // The single argument is used at init time + argModes[0] = 'init' + break + + case '_INTEG': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + v.varType = 'level' + v.hasInitValue = true + // The 2nd argument is used at init time + argModes[1] = 'init' + break + + case '_NPV': + validateCallArgs(callExpr, 4) + addFnReference = false + visitArgs = false + generateNpvVariables(v, callExpr, context) + break + + case '_SAMPLE_IF_TRUE': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 3) + v.hasInitValue = true + // The 3rd argument is used at init time + argModes[2] = 'init' + break + + case '_SMOOTH': + case '_SMOOTHI': + case '_SMOOTH3': + case '_SMOOTH3I': + validateCallArgs(callExpr, callExpr.fnId.endsWith('I') ? 3 : 2) + addFnReference = false + visitArgs = false + generateSmoothVariables(v, callExpr, context) + break + + case '_TREND': + validateCallArgs(callExpr, 3) + addFnReference = false + visitArgs = false + generateTrendVariables(v, callExpr, context) + break + + case '_WITH_LOOKUP': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + generateLookup(v, callExpr, context) + break + + default: + unhandled = true + break } + } - case '_GET_DIRECT_DATA': - case '_GET_DIRECT_LOOKUPS': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 4) - validateCallArgType(callExpr, 0, 'string') - validateCallArgType(callExpr, 1, 'string') - validateCallArgType(callExpr, 2, 'string') - validateCallArgType(callExpr, 3, 'string') - addFnReference = false - v.varType = 'data' - v.directDataArgs = { - file: callExpr.args[0].text, - tab: callExpr.args[1].text, - timeRowOrCol: callExpr.args[2].text, - startCell: callExpr.args[3].text - } - break + // Helper function that validates a function call for a Stella model + // XXX: Currently we conflate "XMILE model" with "XMILE model as generated by Stella", + // so this function only handles the subset of Stella functions that are supported in + // SDEverywhere's runtime library + function validateStellaFunctionCall() { + switch (callExpr.fnId) { + // + // + // 1-argument functions... + // + // + + case '_ABS': + case '_ARCCOS': + case '_ARCSIN': + case '_ARCTAN': + case '_COS': + case '_EXP': + case '_GAMMALN': + case '_INT': + case '_LN': + case '_SIN': + case '_SQRT': + case '_SUM': + case '_TAN': + break - case '_IF_THEN_ELSE': - validateCallArgs(callExpr, 3) - addFnReference = false - break + // + // + // 2-argument functions... + // + // + + case '_LOOKUP': + case '_LOOKUPINV': + case '_MAX': + case '_MIN': + case '_MOD': + validateCallArgs(callExpr, 2) + break - case '_INITIAL': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 1) - v.varType = 'initial' - v.hasInitValue = true - // The single argument is used at init time - argModes[0] = 'init' - break + // + // + // 3-plus-argument functions... + // + // - case '_INTEG': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 2) - v.varType = 'level' - v.hasInitValue = true - // The 2nd argument is used at init time - argModes[1] = 'init' - break + case '_RAMP': + validateCallArgs(callExpr, 3) + break - case '_NPV': - validateCallArgs(callExpr, 4) - addFnReference = false - visitArgs = false - generateNpvVariables(v, callExpr, context) - break + // + // + // Complex functions... + // + // + + case '_DELAY1': + case '_DELAY3': + // Stella's DELAY1 and DELAY3 functions can take a third "initial" argument (in which case + // they behave like Vensim's DELAY1I and DELAY3I functions) + validateCallArgs(callExpr, [2, 3]) + addFnReference = false + visitArgs = false + generateDelayVariables(v, callExpr, context) + break - case '_SAMPLE_IF_TRUE': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 3) - v.hasInitValue = true - // The 3rd argument is used at init time - argModes[2] = 'init' - break + case '_IF_THEN_ELSE': + validateCallArgs(callExpr, 3) + addFnReference = false + break - case '_SMOOTH': - case '_SMOOTHI': - case '_SMOOTH3': - case '_SMOOTH3I': - validateCallArgs(callExpr, callExpr.fnId.endsWith('I') ? 3 : 2) - addFnReference = false - visitArgs = false - generateSmoothVariables(v, callExpr, context) - break + case '_INIT': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 1) + v.varType = 'initial' + v.hasInitValue = true + // The single argument is used at init time + argModes[0] = 'init' + break - case '_TREND': - validateCallArgs(callExpr, 3) - addFnReference = false - visitArgs = false - generateTrendVariables(v, callExpr, context) - break + case '_SMTH1': + case '_SMTH3': + // Stella's SMTH1 and SMTH3 functions can take a third "initial" argument (in which case + // they behave like Vensim's SMOOTHI and SMOOTH3I functions) + validateCallArgs(callExpr, [2, 3]) + addFnReference = false + visitArgs = false + generateSmoothVariables(v, callExpr, context) + break - case '_WITH_LOOKUP': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 2) - generateLookup(v, callExpr, context) - break + case '_TREND': + validateCallArgs(callExpr, 3) + addFnReference = false + visitArgs = false + generateTrendVariables(v, callExpr, context) + break - default: { - // See if the function name is actually the name of a lookup variable. For Vensim - // models, the antlr4-vensim grammar has separate definitions for lookup calls and - // function calls, but in practice they can only be differentiated in the case - // where the lookup has subscripts; when there are no subscripts, they get treated - // like normal function calls, and in that case we will end up here. If we find - // a variable with the given name, then we will assume it's a lookup call, otherwise - // we treat it as a call of an unimplemented function. - const varId = callExpr.fnId.toLowerCase() - const referencedVar = Model.varWithName(varId) - if (referencedVar === undefined || referencedVar.parsedEqn.rhs.kind !== 'lookup') { - // Throw an error if the function is not yet implemented in SDE - // TODO: This will report false positives in the case of user-defined macros. For now - // we provide the ability to turn off this check via an environment variable, but we - // should consider providing a way for the user to declare the names of any user-defined - // macros so that we can skip this check when those macros are detected. - if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS !== '0') { - const msg = `Unhandled function '${callExpr.fnId}' in readEquations for '${v.modelLHS}'` - if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS === 'warn') { - console.warn(`WARNING: ${msg}`) - } else { - throw new Error(msg) - } + default: + unhandled = true + break + } + } + + // Validate the function call based on the model kind + if (context.modelKind === 'vensim') { + validateVensimFunctionCall() + } else if (context.modelKind === 'xmile') { + validateStellaFunctionCall() + } else { + throw new Error(`Unknown model kind: ${context.modelKind}`) + } + + if (unhandled) { + // We did not match a known function, so we need to check if this is a lookup call. + // See if the function name is actually the name of a lookup variable. For Vensim + // models, the antlr4-vensim grammar has separate definitions for lookup calls and + // function calls, but in practice they can only be differentiated in the case + // where the lookup has subscripts; when there are no subscripts, they get treated + // like normal function calls, and in that case we will end up here. If we find + // a variable with the given name, then we will assume it's a lookup call, otherwise + // we treat it as a call of an unimplemented function. + const varId = callExpr.fnId.toLowerCase() + const referencedVar = Model.varWithName(varId) + if (referencedVar === undefined || referencedVar.parsedEqn.rhs.kind !== 'lookup') { + // Throw an error if the function is not yet implemented in SDE + // TODO: This will report false positives in the case of user-defined macros. For now + // we provide the ability to turn off this check via an environment variable, but we + // should consider providing a way for the user to declare the names of any user-defined + // macros so that we can skip this check when those macros are detected. + if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS !== '0') { + const msg = `Unhandled function '${callExpr.fnId}' in readEquations for '${v.modelLHS}'` + if (process.env.SDE_REPORT_UNSUPPORTED_FUNCTIONS === 'warn') { + console.warn(`WARNING: ${msg}`) + } else { + throw new Error(msg) } } - break } } @@ -725,10 +852,20 @@ function validateCallDepth(callExpr, context) { * Throw an error if the given function call does not have the expected number of arguments. */ function validateCallArgs(callExpr, expectedArgCount) { - if (callExpr.args.length !== expectedArgCount) { - throw new Error( - `Expected '${callExpr.fnName}' function call to have ${expectedArgCount} arguments but got ${callExpr.args.length} ` - ) + if (Array.isArray(expectedArgCount)) { + if (!expectedArgCount.includes(callExpr.args.length)) { + throw new Error( + `Expected '${callExpr.fnName}' function call to have ${expectedArgCount.join('|')} arguments but got ${ + callExpr.args.length + }` + ) + } + } else { + if (callExpr.args.length !== expectedArgCount) { + throw new Error( + `Expected '${callExpr.fnName}' function call to have ${expectedArgCount} arguments but got ${callExpr.args.length}` + ) + } } } From 7748cb97834ff0e2a3a794bf3881a2f96cc29a3f Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 17:01:21 -0700 Subject: [PATCH 22/77] fix: convert XMILE logical operators to Vensim syntax before parsing --- .../xmile/parse-xmile-variable-def.spec.ts | 102 +++++++++++++++++- .../src/xmile/parse-xmile-variable-def.ts | 10 +- 2 files changed, 109 insertions(+), 3 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 32e68b45..04e18e8a 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest' import type { XmlElement } from '@rgrove/parse-xml' import { parseXml } from '@rgrove/parse-xml' -import { binaryOp, call, exprEqn, lookupDef, lookupVarEqn, num, varDef, varRef } from '../ast/ast-builders' +import { binaryOp, call, exprEqn, lookupDef, lookupVarEqn, num, unaryOp, varDef, varRef } from '../ast/ast-builders' import { parseXmileVariableDef } from './parse-xmile-variable-def' @@ -315,6 +315,22 @@ describe('parseXmileVariableDef with ', () => { expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), binaryOp(varRef('y'), '+', num(10)))]) }) + // TODO: According to the XMILE spec, "this is a long variable name" should be equivalent to the same name + // without quotes, but SDE doesn't support this yet (it keeps the quotes), so this test is skipped for now + it.skip('should parse an aux variable definition (with quoted variable name)', () => { + const v = xml(` + + "this is a long variable name y" + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('this is a long variable name x'), + binaryOp(varRef('this is a long variable name y'), '+', num(10)) + ) + ]) + }) + it('should parse an aux variable definition (with one dimension, apply to all)', () => { const v = xml(` @@ -399,7 +415,7 @@ describe('parseXmileVariableDef with ', () => { ]) }) - it('should parse an aux variable definition with XMILE conditional expression', () => { + it('should parse an aux variable definition with XMILE-style "IF ... THEN ... ELSE ..." conditional expression', () => { const v = xml(` IF c > 10 THEN y + 3 ELSE z * 5 @@ -418,6 +434,88 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse an aux variable definition with XMILE conditional expression with binary AND op', () => { + const v = xml(` + + IF c > 10 AND d < 20 THEN y + 3 ELSE z * 5 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + binaryOp(binaryOp(varRef('c'), '>', num(10)), ':AND:', binaryOp(varRef('d'), '<', num(20))), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ) + ]) + }) + + it('should parse an aux variable definition with XMILE conditional expression with binary OR op', () => { + const v = xml(` + + IF c > 10 OR d < 20 THEN y + 3 ELSE z * 5 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + binaryOp(binaryOp(varRef('c'), '>', num(10)), ':OR:', binaryOp(varRef('d'), '<', num(20))), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ) + ]) + }) + + it('should parse an aux variable definition with XMILE conditional expression with binary NOT op', () => { + const v = xml(` + + IF NOT c THEN y + 3 ELSE z * 5 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + unaryOp(':NOT:', varRef('c')), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ) + ]) + }) + + // TODO: According to the XMILE spec, "this is a long variable name" should be equivalent to the same name + // without quotes, but SDE doesn't support this yet (it keeps the quotes), so this test is skipped for now + it.skip('should parse an aux variable definition with XMILE conditional expression (and should not replace boolean operators when inside quoted variable names)', () => { + const v = xml(` + + IF "This variable contains AND OR NOT keywords" > 10 AND d < 20 THEN y + 3 ELSE z * 5 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + binaryOp( + binaryOp(varRef('This variable contains AND OR NOT keywords'), '>', num(10)), + ':AND:', + binaryOp(varRef('d'), '<', num(20)) + ), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ) + ]) + }) + it('should throw an error if aux variable equation cannot be parsed', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 8e8d4576..081592c5 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -246,7 +246,15 @@ function parseExpr(exprText: string): Expr { // IF_THEN_ELSE({condition}, {trueExpr}, {falseExpr}) // Since we only support the latter in the compile package, it's better if we transform // the XMILE form to Vensim form, and then we can use the Vensim expression parser. - exprText = exprText.replace(conditionalRegExp, 'IF THEN ELSE($1, $2, $3)') + exprText = exprText.replace(conditionalRegExp, (_, p1, p2, p3) => { + // XXX: Vensim uses {:AND:,:OR:,:NOT:} but XMILE uses {AND,OR,NOT}, so we replace the + // XMILE syntax with the Vensim syntax so that the Vensim parser can handle them + // Replace XMILE logical operators with Vensim syntax, but avoid replacing when inside quoted strings + p1 = p1.replace(/(? Date: Fri, 22 Aug 2025 17:09:25 -0700 Subject: [PATCH 23/77] docs: fix comment --- packages/compile/src/model/read-equation-fn-trend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/compile/src/model/read-equation-fn-trend.js b/packages/compile/src/model/read-equation-fn-trend.js index e1078375..63660aa6 100644 --- a/packages/compile/src/model/read-equation-fn-trend.js +++ b/packages/compile/src/model/read-equation-fn-trend.js @@ -3,7 +3,7 @@ import { toPrettyString } from '@sdeverywhere/parse' import { canonicalName, newAuxVarName, newLevelVarName } from '../_shared/helpers.js' /** - * Generate two level variables and one aux that implement an `NPV` function call. + * Generate two level variables and one aux that implement an `TREND` function call. * * TODO: Docs * From 2c8ca56dfd58618d8ad01d64607eaeef7dd19e4d Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 17:17:31 -0700 Subject: [PATCH 24/77] fix: restore handling of INTEG function calls and update tests --- .../src/model/read-equations-xmile.spec.ts | 26 +++++++++---------- packages/compile/src/model/read-equations.js | 12 +++++++++ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 7a78229e..4516beb4 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -5733,8 +5733,7 @@ ${elements.join('\n')} ]) }) - // TODO: This test is skipped because Stella doesn't appear to include the INTEG function - it.skip('should work for INTEG function', () => { + it('should work for INTEG function (synthesized from variable definition)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = Time * 2 ~~| @@ -5749,9 +5748,10 @@ ${elements.join('\n')} 5 - - INTEG(x,init) -` + + init + x +` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) expect(vars).toEqual([ @@ -5774,13 +5774,12 @@ ${elements.join('\n')} ]) }) - // TODO: This test is skipped because Stella doesn't appear to include the INTEG function - it.skip('should work for INTEG function (with nested function calls)', () => { + it('should work for INTEG function (synthesized from variable definition, with nested function calls)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = Time * 2 ~~| // init = 5 ~~| - // y = INTEG(ABS(x), POW(init, 3)) ~~| + // y = INTEG(ABS(x), SQRT(init)) ~~| // `) const xmileVars = `\ @@ -5790,9 +5789,10 @@ ${elements.join('\n')} 5 - - INTEG(ABS(x),POW(init,3)) -` + + SQRT(init) + ABS(x) +` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) expect(vars).toEqual([ @@ -5804,13 +5804,13 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('y', 'INTEG(ABS(x),POW(init,3))', { + v('y', 'INTEG(ABS(x),SQRT(init))', { refId: '_y', varType: 'level', references: ['_x'], hasInitValue: true, initReferences: ['_init'], - referencedFunctionNames: ['__integ', '__abs', '__pow'] + referencedFunctionNames: ['__integ', '__abs', '__sqrt'] }) ]) }) diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index a538e85f..eff07aa3 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -704,6 +704,18 @@ function visitFunctionCall(v, callExpr, context) { argModes[0] = 'init' break + case '_INTEG': + // NOTE: Stella doesn't have a built-in `INTEG` function, but our XMILE parser synthesizes + // an `INTEG` function call for `` variable definitions using the `` element + // as the `rate` argument for the Vensim-style `INTEG` function call + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + v.varType = 'level' + v.hasInitValue = true + // The 2nd argument is used at init time + argModes[1] = 'init' + break + case '_SMTH1': case '_SMTH3': // Stella's SMTH1 and SMTH3 functions can take a third "initial" argument (in which case From c7d97441136337dd7f9ff6deb3a89083ec1c1fa8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 17:20:46 -0700 Subject: [PATCH 25/77] fix: include itmx as one of the supported xmile file extensions --- packages/cli/src/utils.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/utils.js b/packages/cli/src/utils.js index 0c4f7606..1d576e45 100644 --- a/packages/cli/src/utils.js +++ b/packages/cli/src/utils.js @@ -24,8 +24,8 @@ export function execCmd(cmd) { } /** - * Normalize a model pathname that may or may not include the .mdl or .xmile/.stmx extension. - * If the pathname does not end with .mdl, .xmile, or .stmx, this will attempt to find a + * Normalize a model pathname that may or may not include the .mdl or .xmile/.stmx/.itmx extension. + * If the pathname does not end with .mdl, .xmile, .stmx, or .itmx, this will attempt to find a * file with one of those extensions. * If there is not a path in the model argument, default to the current working directory. * @@ -41,16 +41,16 @@ export function execCmd(cmd) { export function modelPathProps(model) { const parsedPath = path.parse(model) if (parsedPath.ext === '') { - const exts = ['.mdl', '.xmile', '.stmx'] + const exts = ['.mdl', '.xmile', '.stmx', '.itmx'] const paths = exts.map(ext => path.join(parsedPath.dir, parsedPath.name + ext)) const existingPaths = paths.filter(path => fs.existsSync(path)) if (existingPaths.length > 1) { throw new Error( - `Found multiple files that match '${model}'; please specify a file with a .mdl, .xmile, or .stmx extension` + `Found multiple files that match '${model}'; please specify a file with a .mdl, .xmile, .stmx, or .itmx extension` ) } if (existingPaths.length === 0) { - throw new Error(`No {mdl,xmile,stmx} file found for ${model}`) + throw new Error(`No {mdl,xmile,stmx,itmx} file found for ${model}`) } parsedPath.ext = path.extname(existingPaths[0]) } From 4f384bd0ee7e33934efdde529f5284054cff1956 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 17:38:52 -0700 Subject: [PATCH 26/77] fix: update XMILE parser to ignore non_negative elements in stocks --- .../xmile/parse-xmile-variable-def.spec.ts | 27 ++++++++++--------- .../src/xmile/parse-xmile-variable-def.ts | 9 ++++--- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 04e18e8a..a0c14192 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -78,6 +78,20 @@ describe('parseXmileVariableDef with ', () => { ]) }) + // TODO: We currently ignore `` elements during parsing; more work will be needed to + // match the behavior described in the XMILE spec for stocks + it('should parse a stock variable definition that has ', () => { + const v = xml(` + + 1000 + matriculating + graduating + + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), call('INTEG', varRef('matriculating'), num(1000)))]) + }) + it('should throw an error if stock variable definition has no ', () => { const v = xml(` @@ -147,19 +161,6 @@ describe('parseXmileVariableDef with ', () => { `) expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') }) - - // TODO: Support - it('should throw an error if stock variable definition has ', () => { - const v = xml(` - - 1000 - matriculating - graduating - - - `) - expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') - }) }) describe('parseXmileVariableDef with ', () => { diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 081592c5..59dfa809 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -199,9 +199,12 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { if (firstElemOf(parentElem, 'queue')) { throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) } - if (firstElemOf(parentElem, 'non_negative')) { - throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) - } + // TODO: We currently ignore `` elements during parsing since it is noted in the XMILE + // spec that it is not directly supported by XMILE and an optional vendor-specific feature. More + // work will be needed to make this work according to the spec, but for now it's OK to ignore it. + // if (firstElemOf(parentElem, 'non_negative')) { + // throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + // } const initExpr = parseExpr(eqnText.text) const inflowExpr = parseExpr(inflowText.text) From 0033fca5503555bd347677c99b2b8021280cc25f Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 17:58:49 -0700 Subject: [PATCH 27/77] fix: improve error message when parsing fails for XMILE variable definition --- .../parse/src/xmile/parse-xmile-model.spec.ts | 18 +++++++++++- packages/parse/src/xmile/parse-xmile-model.ts | 28 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index d9420a8f..13f71803 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -47,7 +47,7 @@ ${dims} } describe('parseXmileModel', () => { - it('should throw an error if model cannot be parsed', () => { + it('should throw an error if model XML cannot be parsed', () => { const mdl = 'NOT XMILE' let msg = 'Failed to parse XMILE model definition:\n\n' @@ -57,6 +57,22 @@ describe('parseXmileModel', () => { expect(() => parseXmileModel(mdl)).toThrow(msg) }) + it('should throw an error if model equation cannot be parsed', () => { + const vars = `\ + + IF THEN 1 ELSE 2 +` + const mdl = xmile('', vars) + + const msg = `\ +Failed to parse XMILE variable definition at line unknown, col 13: +IF THEN 1 ELSE 2 + +Detail: + no viable alternative at input 'IF THEN ELSE(,'` + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + it('should parse a model with dimension definition only (no equations)', () => { const dims = `\ diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index 23a559ac..3cf3ddf2 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -66,9 +66,31 @@ function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { // Extract variable definition (e.g., , , , ) -> Equation[] const varElems = elemsOf(variablesElem, ['aux', 'stock', 'flow', 'gf']) for (const varElem of varElems) { - const eqns = parseXmileVariableDef(varElem) - if (eqns) { - equations.push(...eqns) + try { + const eqns = parseXmileVariableDef(varElem) + if (eqns) { + equations.push(...eqns) + } + } catch (e) { + // Include context such as line/column numbers in the error message if available + let linePart = '' + if (e.cause?.code === 'VensimParseError') { + if (e.cause.line) { + // The line number reported by ANTLR is relative to the beginning of the + // preprocessed definition (since we parse each definition individually), + // so we need to add it to the line of the definition in the original source + // TODO: Get the actual line number + // const lineNum = e.cause.line - 1 + def.line + const lineNum = 'unknown' + linePart += ` at line ${lineNum}` + if (e.cause.column) { + linePart += `, col ${e.cause.column}` + } + } + } + const varElemString = varElem.toString() + const msg = `Failed to parse XMILE variable definition${linePart}:\n${varElemString}\n\nDetail:\n ${e.message}` + throw new Error(msg) } } } From fe73b21ea47b4ecdd98b3288a81130e7f163c4ae Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 18:10:48 -0700 Subject: [PATCH 28/77] fix: include line number from original XML input in XMILE parsing errors --- .../parse/src/xmile/parse-xmile-model.spec.ts | 6 +- packages/parse/src/xmile/parse-xmile-model.ts | 88 +++++++++++++++---- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index 13f71803..b7f227ca 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -65,8 +65,10 @@ describe('parseXmileModel', () => { const mdl = xmile('', vars) const msg = `\ -Failed to parse XMILE variable definition at line unknown, col 13: -IF THEN 1 ELSE 2 +Failed to parse XMILE variable definition at line 15, col 13: + + IF THEN 1 ELSE 2 + Detail: no viable alternative at input 'IF THEN ELSE(,'` diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index 3cf3ddf2..3703d981 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -20,7 +20,8 @@ import { firstElemOf, elemsOf } from './xml' export function parseXmileModel(input: string): Model { let xml try { - xml = parseXml(input) + // Enable position tracking to get byte offsets for line number calculation + xml = parseXml(input, { includeOffsets: true }) } catch (e) { // Include context such as line/column numbers in the error message if available const msg = `Failed to parse XMILE model definition:\n\n${e.message}` @@ -31,7 +32,7 @@ export function parseXmileModel(input: string): Model { const dimensions: DimensionDef[] = parseDimensionDefs(xml.root) // Extract -> Equation[] - const equations: Equation[] = parseVariableDefs(xml.root) + const equations: Equation[] = parseVariableDefs(xml.root, input) return { dimensions, @@ -54,7 +55,7 @@ function parseDimensionDefs(rootElem: XmlElement | undefined): DimensionDef[] { return dimensionDefs } -function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { +function parseVariableDefs(rootElem: XmlElement | undefined, originalXml: string): Equation[] { const modelElem = firstElemOf(rootElem, 'model') if (modelElem === undefined) { return [] @@ -74,21 +75,26 @@ function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { } catch (e) { // Include context such as line/column numbers in the error message if available let linePart = '' - if (e.cause?.code === 'VensimParseError') { - if (e.cause.line) { - // The line number reported by ANTLR is relative to the beginning of the - // preprocessed definition (since we parse each definition individually), - // so we need to add it to the line of the definition in the original source - // TODO: Get the actual line number - // const lineNum = e.cause.line - 1 + def.line - const lineNum = 'unknown' - linePart += ` at line ${lineNum}` - if (e.cause.column) { - linePart += `, col ${e.cause.column}` + // Try to get line number from the XML element's position + const lineNumInOriginalXml = getLineNumber(originalXml, varElem.start) + if (lineNumInOriginalXml !== -1) { + if (e.cause?.code === 'VensimParseError') { + if (e.cause.line) { + // The line number reported by ANTLR is relative to the beginning of the + // preprocessed definition (since we parse each definition individually), + // so we need to add it to the line of the definition in the original source + const lineNum = e.cause.line - 1 + lineNumInOriginalXml + linePart += ` at line ${lineNum}` + if (e.cause.column) { + linePart += `, col ${e.cause.column}` + } } + } else { + // Include the line number from the original XML + linePart += ` at line ${lineNumInOriginalXml}` } } - const varElemString = varElem.toString() + const varElemString = elementToXmlString(varElem) const msg = `Failed to parse XMILE variable definition${linePart}:\n${varElemString}\n\nDetail:\n ${e.message}` throw new Error(msg) } @@ -97,3 +103,55 @@ function parseVariableDefs(rootElem: XmlElement | undefined): Equation[] { return equations } + +/** + * Calculate the line number from a byte offset in the original XML string. + * + * @param xmlString The original XML string + * @param byteOffset The byte offset from the XmlElement + * @returns The line number (1-indexed) or -1 if offset is invalid + */ +function getLineNumber(xmlString: string, byteOffset: number): number { + if (byteOffset === -1 || byteOffset >= xmlString.length) { + return -1 + } + + // Count newlines up to the byte offset + const substring = xmlString.substring(0, byteOffset) + return substring.split('\n').length +} + +/** + * Reconstruct XML string from an XmlElement. + * + * @param elem The XmlElement to convert back to XML + * @returns A string representation of the XML element + */ +function elementToXmlString(elem: XmlElement): string { + const attrs = Object.entries(elem.attributes || {}) + .map(([key, value]) => `${key}="${value}"`) + .join(' ') + + const attrStr = attrs ? ` ${attrs}` : '' + + if (elem.isEmpty) { + return `<${elem.name}${attrStr} />` + } + + const children = elem.children + .map(child => { + if (child.type === 'text') { + return (child as { text: string }).text + } else if (child.type === 'element') { + return elementToXmlString(child as XmlElement) + } else if (child.type === 'cdata') { + return `` + } else if (child.type === 'comment') { + return `` + } + return '' + }) + .join('') + + return `<${elem.name}${attrStr}>${children}` +} From 1be73defe72b8b841fa6b83aab0f9a2c21bb64e7 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 18:15:59 -0700 Subject: [PATCH 29/77] fix: include the element from the original XML in the error message --- .../parse/src/xmile/parse-xmile-model.spec.ts | 2 +- packages/parse/src/xmile/parse-xmile-model.ts | 50 +++++++++---------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index b7f227ca..56ad5bc7 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -66,7 +66,7 @@ describe('parseXmileModel', () => { const msg = `\ Failed to parse XMILE variable definition at line 15, col 13: - + IF THEN 1 ELSE 2 diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index 3703d981..a11d84e2 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -94,7 +94,7 @@ function parseVariableDefs(rootElem: XmlElement | undefined, originalXml: string linePart += ` at line ${lineNumInOriginalXml}` } } - const varElemString = elementToXmlString(varElem) + const varElemString = extractXmlLines(originalXml, varElem.start, varElem.end) const msg = `Failed to parse XMILE variable definition${linePart}:\n${varElemString}\n\nDetail:\n ${e.message}` throw new Error(msg) } @@ -122,36 +122,34 @@ function getLineNumber(xmlString: string, byteOffset: number): number { } /** - * Reconstruct XML string from an XmlElement. + * Extract relevant lines from the original XML string using start/end byte offsets. + * Includes full lines for context, even if start/end are not at line boundaries. * - * @param elem The XmlElement to convert back to XML - * @returns A string representation of the XML element + * @param originalXml The original XML string + * @param startOffset The starting byte offset + * @param endOffset The ending byte offset + * @returns A string containing the relevant lines with line numbers */ -function elementToXmlString(elem: XmlElement): string { - const attrs = Object.entries(elem.attributes || {}) - .map(([key, value]) => `${key}="${value}"`) - .join(' ') +function extractXmlLines(originalXml: string, startOffset: number, endOffset: number): string { + if (startOffset === -1 || endOffset === -1 || startOffset >= originalXml.length || endOffset > originalXml.length) { + return '[Unable to extract XML lines - invalid offsets]' + } - const attrStr = attrs ? ` ${attrs}` : '' + // Find the start of the line containing the start offset + let lineStart = startOffset + while (lineStart > 0 && originalXml[lineStart - 1] !== '\n') { + lineStart-- + } - if (elem.isEmpty) { - return `<${elem.name}${attrStr} />` + // Find the end of the line containing the end offset + let lineEnd = endOffset + while (lineEnd < originalXml.length && originalXml[lineEnd] !== '\n') { + lineEnd++ } - const children = elem.children - .map(child => { - if (child.type === 'text') { - return (child as { text: string }).text - } else if (child.type === 'element') { - return elementToXmlString(child as XmlElement) - } else if (child.type === 'cdata') { - return `` - } else if (child.type === 'comment') { - return `` - } - return '' - }) - .join('') + // Extract the lines + const relevantXml = originalXml.substring(lineStart, lineEnd) - return `<${elem.name}${attrStr}>${children}` + // Return just the XML content without line number prefix + return relevantXml } From 9576ce672ebff7389f10a33228eeaefb6dce31a0 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 20:47:15 -0700 Subject: [PATCH 30/77] test: update test for parse error to not rely on IF THEN ELSE --- packages/parse/src/xmile/parse-xmile-model.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index 56ad5bc7..b048a8e5 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -60,18 +60,18 @@ describe('parseXmileModel', () => { it('should throw an error if model equation cannot be parsed', () => { const vars = `\ - IF THEN 1 ELSE 2 + x + ?! + y ` const mdl = xmile('', vars) const msg = `\ -Failed to parse XMILE variable definition at line 15, col 13: +Failed to parse XMILE variable definition at line 15, col 4: - IF THEN 1 ELSE 2 + x + ?! + y Detail: - no viable alternative at input 'IF THEN ELSE(,'` + token recognition error at: '?'` expect(() => parseXmileModel(mdl)).toThrow(msg) }) From 44547b90248dc88722d2fb9843d8170836f4d657 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 22 Aug 2025 20:47:31 -0700 Subject: [PATCH 31/77] fix: handle nested IF THEN ELSE --- .../xmile/parse-xmile-variable-def.spec.ts | 55 ++++++++ .../src/xmile/parse-xmile-variable-def.ts | 126 ++++++++++++++++-- 2 files changed, 167 insertions(+), 14 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index a0c14192..d88fb71c 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -435,6 +435,61 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse an aux variable definition with XMILE-style "IF ... THEN ... ELSE ..." conditional expression (over multiple lines)', () => { + const v = xml(` + + IF c > 10 + THEN y + 3 ELSE z * 5 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + binaryOp(varRef('c'), '>', num(10)), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ) + ]) + }) + + it('should parse an aux variable definition with XMILE-style "IF ... THEN ... ELSE ..." conditional expression (with nested IF THEN ELSE)', () => { + const v = xml(` + + IF (vacation_switch = 1 AND total_weeks_vacation_taken < 4) + THEN (IF TIME - Last_Vacation_Start >= time_working_before_vacation THEN 1 + ELSE 0) + ELSE 0 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + call( + 'IF THEN ELSE', + binaryOp( + binaryOp(varRef('vacation_switch'), '=', num(1)), + ':AND:', + binaryOp(varRef('total_weeks_vacation_taken'), '<', num(4)) + ), + call( + 'IF THEN ELSE', + binaryOp( + binaryOp(varRef('TIME'), '-', varRef('Last_Vacation_Start')), + '>=', + varRef('time_working_before_vacation') + ), + num(1), + num(0) + ), + num(0) + ) + ) + ]) + }) + it('should parse an aux variable definition with XMILE conditional expression with binary AND op', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 59dfa809..73564c24 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -11,9 +11,6 @@ import { parseVensimExpr } from '../vensim/parse-vensim-expr' import { elemsOf, firstElemOf, firstTextOf, xmlError } from './xml' -// Regular expression for XMILE conditional expressions -const conditionalRegExp = /IF\s+(.*)\s+THEN\s+(.*)\s+ELSE\s+(.*)\s*/gi - /** * Parse the given XMILE variable definition and return an array of `Equation` AST nodes * corresponding to the variable definition (or definitions, in the case of a @@ -249,17 +246,9 @@ function parseExpr(exprText: string): Expr { // IF_THEN_ELSE({condition}, {trueExpr}, {falseExpr}) // Since we only support the latter in the compile package, it's better if we transform // the XMILE form to Vensim form, and then we can use the Vensim expression parser. - exprText = exprText.replace(conditionalRegExp, (_, p1, p2, p3) => { - // XXX: Vensim uses {:AND:,:OR:,:NOT:} but XMILE uses {AND,OR,NOT}, so we replace the - // XMILE syntax with the Vensim syntax so that the Vensim parser can handle them - // Replace XMILE logical operators with Vensim syntax, but avoid replacing when inside quoted strings - p1 = p1.replace(/(? 0 THEN 1 ELSE 0 + * - Nested: IF x > 0 THEN IF y > 0 THEN 2 ELSE 1 ELSE 0 + * - Complex: IF a > 0 THEN IF b > 0 THEN IF c > 0 THEN 3 ELSE 2 ELSE 1 ELSE 0 + */ +function convertConditionalExpressions(exprText: string): string { + // XXX: This function implementation was generated by an LLM and is rather complex. + // We probably could remove it if we had a new ANTLR grammar/parser for XMILE-style + // expressions, including nested conditional expressions. + + // Trim whitespace and normalize newlines + const normalizedText = exprText.trim().replace(/\s+/g, ' ') + + // Handle nested conditionals wrapped in parentheses + let textToParse = normalizedText + if (textToParse.startsWith('(') && textToParse.endsWith(')')) { + textToParse = textToParse.slice(1, -1).trim() + } + + // Find the outermost IF-THEN-ELSE pattern + const ifMatch = textToParse.match(/^IF\s+(.+)$/i) + if (!ifMatch) { + return exprText + } + + // Parse the condition part (everything after IF until THEN) + const remainingText = ifMatch[1] + const thenMatch = remainingText.match(/^(.+?)\s+THEN\s+(.+)$/i) + if (!thenMatch) { + return exprText + } + + const condition = thenMatch[1].trim() + const afterThen = thenMatch[2] + + // Find the ELSE part by looking for the outermost ELSE that's not inside parentheses + // We need to parse the THEN expression to understand its structure + let elseIndex = -1 + let parenCount = 0 + let inQuotes = false + let quoteChar = '' + + for (let i = 0; i < afterThen.length; i++) { + const char = afterThen[i] + + // Handle quoted strings + if ((char === '"' || char === "'") && (i === 0 || afterThen[i - 1] !== '\\')) { + if (!inQuotes) { + inQuotes = true + quoteChar = char + } else if (char === quoteChar) { + inQuotes = false + quoteChar = '' + } + continue + } + + if (inQuotes) { + continue + } + + // Handle parentheses for nested expressions + if (char === '(') { + parenCount++ + } else if (char === ')') { + parenCount-- + } + + // Look for ELSE, but only when not inside parentheses or quoted strings + if (parenCount === 0 && !inQuotes) { + const elseMatch = afterThen.substring(i).match(/^ELSE\s+(.+)$/i) + if (elseMatch) { + elseIndex = i + break + } + } + } + + if (elseIndex === -1) { + return exprText + } + + // Skip "ELSE " + const trueExpr = afterThen.substring(0, elseIndex).trim() + const falseExpr = afterThen.substring(elseIndex + 5).trim() + + // Recursively parse nested conditionals in the true and false expressions + const convertedTrueExpr = convertConditionalExpressions(trueExpr) + const convertedFalseExpr = convertConditionalExpressions(falseExpr) + + const convertedCondition = condition + // Replace logical operators for Vensim compatibility + .replace(/(? Date: Fri, 22 Aug 2025 20:51:44 -0700 Subject: [PATCH 32/77] fix: update XMILE parser to ignore non_negative elements in flows --- .../xmile/parse-xmile-variable-def.spec.ts | 23 ++++++++++--------- .../src/xmile/parse-xmile-variable-def.ts | 9 +++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index d88fb71c..eda9d7ce 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -253,6 +253,18 @@ describe('parseXmileVariableDef with ', () => { ]) }) + // TODO: We currently ignore `` elements during parsing; more work will be needed to + // match the behavior described in the XMILE spec for flows + it('should parse a flow variable definition that has ', () => { + const v = xml(` + + 1000 + + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), num(1000))]) + }) + it('should throw an error if flow variable definition has no or ', () => { const v = xml(` @@ -272,17 +284,6 @@ describe('parseXmileVariableDef with ', () => { expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') }) - // TODO: Support - it('should throw an error if flow variable definition has ', () => { - const v = xml(` - - 1000 - - - `) - expect(() => parseXmileVariableDef(v)).toThrow('Currently is not supported for a variable') - }) - // TODO: Support it('should throw an error if flow variable definition has ', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 73564c24..36af9ac2 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -221,9 +221,12 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { if (firstElemOf(parentElem, 'multiplier')) { throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) } - if (firstElemOf(parentElem, 'non_negative')) { - throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) - } + // TODO: We currently ignore `` elements during parsing since it is noted in the XMILE + // spec that it is not directly supported by XMILE and an optional vendor-specific feature. More + // work will be needed to make this work according to the spec, but for now it's OK to ignore it. + // if (firstElemOf(parentElem, 'non_negative')) { + // throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) + // } if (firstElemOf(parentElem, 'overflow')) { throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) } From 6c925102f451b5f9a574a1912381ddc501f52d20 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 08:55:56 -0700 Subject: [PATCH 33/77] test: update test comments --- .../compile/src/model/read-equations-xmile.spec.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 4516beb4..d38f8a75 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -5056,9 +5056,15 @@ ${elements.join('\n')} ]) }) - // TODO: This test is skipped for now; in Stella, the function is called `DELAY` and we will need to see - // if the Vensim `DELAY FIXED` function is compatible enough - it.skip('should work for DELAY FIXED function', () => { + // TODO: This test is skipped for now; in Stella, the DELAY function can be called with or + // without an initial value argument, but the code that handles the Vensim DELAY FIXED function + // currently assumes the initial value argument + it.skip('should work for DELAY function (without initial value argument)', () => {}) + + // TODO: This test is skipped for now because the code that handles Stella's DELAY function + // will need to be updated to generate an internal level variable, since Stella's DELAY + // does not necessarily have to follow the "=" like Vensim's DELAY FIXED function does + it.skip('should work for DELAY function (with initial value argument)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| From 0bc1444812b4d3637d7f17bb8f2b4f8862a382e8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 08:57:13 -0700 Subject: [PATCH 34/77] fix: update XMILE stock parser to handle multiple inflows and/or outflows --- .../xmile/parse-xmile-variable-def.spec.ts | 88 ++++++++++++++----- .../src/xmile/parse-xmile-variable-def.ts | 46 +++++++--- 2 files changed, 98 insertions(+), 36 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index eda9d7ce..e03a74d8 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -78,6 +78,63 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse a stock variable definition (without subscripts, multiple inflows, no outflows)', () => { + const v = xml(` + + y + 10 + a + b + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + // INTEG(a + b, y + 10) + call('INTEG', binaryOp(varRef('a'), '+', varRef('b')), binaryOp(varRef('y'), '+', num(10))) + ) + ]) + }) + + it('should parse a stock variable definition (without subscripts, no inflows, multiple outflows)', () => { + const v = xml(` + + y + 10 + a + b + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + // INTEG(-a - b, y + 10) + call('INTEG', binaryOp(unaryOp('-', varRef('a')), '-', varRef('b')), binaryOp(varRef('y'), '+', num(10))) + ) + ]) + }) + + it.only('should parse a stock variable definition (without subscripts, multiple inflows, multiple outflows)', () => { + const v = xml(` + + y + 10 + a + b + c + d + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + // INTEG(a + b - c - d, y + 10) + call( + 'INTEG', + binaryOp(binaryOp(binaryOp(varRef('a'), '+', varRef('b')), '-', varRef('c')), '-', varRef('d')), + binaryOp(varRef('y'), '+', num(10)) + ) + ) + ]) + }) + // TODO: We currently ignore `` elements during parsing; more work will be needed to // match the behavior described in the XMILE spec for stocks it('should parse a stock variable definition that has ', () => { @@ -89,7 +146,13 @@ describe('parseXmileVariableDef with ', () => { `) - expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x'), call('INTEG', varRef('matriculating'), num(1000)))]) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + // INTEG(matriculating - graduating, 1000) + call('INTEG', binaryOp(varRef('matriculating'), '-', varRef('graduating')), num(1000)) + ) + ]) }) it('should throw an error if stock variable definition has no ', () => { @@ -97,7 +160,7 @@ describe('parseXmileVariableDef with ', () => { `) - expect(() => parseXmileVariableDef(v)).toThrow('Currently is required for a variable') + expect(() => parseXmileVariableDef(v)).toThrow('An is required for a variable') }) it('should throw an error if stock variable definition has ', () => { @@ -112,27 +175,6 @@ describe('parseXmileVariableDef with ', () => { expect(() => parseXmileVariableDef(v)).toThrow(' is only allowed for and variables') }) - it('should throw an error if stock variable definition has no ', () => { - const v = xml(` - - y + 10 - - `) - expect(() => parseXmileVariableDef(v)).toThrow('Currently only one is supported for a variable') - }) - - // TODO: Support multiple inflows - it('should throw an error if stock variable definition has multiple ', () => { - const v = xml(` - - y + 10 - z * 2 - q + 4 - - `) - expect(() => parseXmileVariableDef(v)).toThrow('Currently only one is supported for a variable') - }) - // TODO: Support it('should throw an error if stock variable definition has ', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 36af9ac2..a4b7a6a8 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -172,21 +172,28 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { case 'stock': { // elements are currently translated to a Vensim-style aux: - // INTEG({inflow}, {eqn}) + // INTEG({inflow1} + {inflow2} + ... - {outflow1} - {outflow2} - ..., {eqn}) if (eqnText === undefined) { // An is currently required for a - throw new Error(xmlError(varElem, 'Currently is required for a variable')) + throw new Error(xmlError(varElem, 'An is required for a variable')) } const inflowElems = elemsOf(parentElem, ['inflow']) - if (inflowElems.length !== 1) { - // TODO: XMILE allows for multiple elements, but we don't support that yet - throw new Error(xmlError(varElem, 'Currently only one is supported for a variable')) - } - // TODO: Handle the case where is defined using CDATA - const inflowText = firstTextOf(inflowElems[0]) - if (inflowText === undefined) { - throw new Error(xmlError(varElem, 'Currently is required for a variable')) - } + const outflowElems = elemsOf(parentElem, ['outflow']) + // TODO: Handle the case where or is defined using CDATA + const inflowTexts = inflowElems.map(inflowElem => { + const inflowText = firstTextOf(inflowElem) + if (inflowText === undefined) { + throw new Error(xmlError(varElem, 'An must be non-empty for a variable')) + } + return inflowText.text + }) + const outflowTexts = outflowElems.map(outflowElem => { + const outflowText = firstTextOf(outflowElem) + if (outflowText === undefined) { + throw new Error(xmlError(varElem, 'An must be non-empty for a variable')) + } + return outflowText.text + }) // TODO: We currently do not support certain options, so for now we // fail fast if we encounter these @@ -203,9 +210,22 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { // throw new Error(xmlError(varElem, 'Currently is not supported for a variable')) // } + // Combine the inflow and outflow expressions into a single expression + // TODO: Do we need to worry about parentheses here? + const inflowParts = inflowTexts.join(' + ') + let outflowParts = outflowTexts.join(' - ') + if (outflowTexts.length > 0) { + if (inflowParts.length > 0) { + outflowParts = `- ${outflowParts}` + } else { + outflowParts = `-${outflowParts}` + } + } + const flowsExpr = parseExpr(`${inflowParts} ${outflowParts}`) + + // Synthesize a Vensim-style `INTEG` function call const initExpr = parseExpr(eqnText.text) - const inflowExpr = parseExpr(inflowText.text) - return call('INTEG', inflowExpr, initExpr) + return call('INTEG', flowsExpr, initExpr) } case 'flow': From 434366c31d8a254301aa3350508cc8bf7b5741c5 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 09:14:36 -0700 Subject: [PATCH 35/77] fix: include STEP as supported XMILE function --- packages/compile/src/model/read-equations.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index eff07aa3..8acea9b2 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -661,6 +661,7 @@ function visitFunctionCall(v, callExpr, context) { case '_MAX': case '_MIN': case '_MOD': + case '_STEP': validateCallArgs(callExpr, 2) break From 37704f342b0d27580ab0bbb6969c172551ce92cc Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 10:58:39 -0700 Subject: [PATCH 36/77] fix: replace newline sequences in variable names with spaces --- .../xmile/parse-xmile-variable-def.spec.ts | 36 ++++++++++++++++++- .../src/xmile/parse-xmile-variable-def.ts | 5 ++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index e03a74d8..5556da27 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -112,7 +112,7 @@ describe('parseXmileVariableDef with ', () => { ]) }) - it.only('should parse a stock variable definition (without subscripts, multiple inflows, multiple outflows)', () => { + it('should parse a stock variable definition (without subscripts, multiple inflows, multiple outflows)', () => { const v = xml(` y + 10 @@ -155,6 +155,22 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse a stock variable definition (with newline sequences in the name)', () => { + const v = xml(` + + 1000 + q + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x y z'), + // INTEG(q, 1000) + call('INTEG', varRef('q'), num(1000)) + ) + ]) + }) + it('should throw an error if stock variable definition has no ', () => { const v = xml(` @@ -295,6 +311,15 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse a flow variable definition (with newline sequences in the name)', () => { + const v = xml(` + + y + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x y z'), binaryOp(varRef('y'), '+', num(10)))]) + }) + // TODO: We currently ignore `` elements during parsing; more work will be needed to // match the behavior described in the XMILE spec for flows it('should parse a flow variable definition that has ', () => { @@ -615,6 +640,15 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse an aux variable definition (with newline sequences in the name)', () => { + const v = xml(` + + y + 10 + + `) + expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x y z'), binaryOp(varRef('y'), '+', num(10)))]) + }) + it('should throw an error if aux variable equation cannot be parsed', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index a4b7a6a8..29b23a83 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -21,7 +21,10 @@ import { elemsOf, firstElemOf, firstTextOf, xmlError } from './xml' */ export function parseXmileVariableDef(varElem: XmlElement): Equation[] { // Extract required variable name - const varName = parseRequiredAttr(varElem, varElem, 'name') + let varName = parseRequiredAttr(varElem, varElem, 'name') + // XXX: Variable names in XMILE can contain newline sequences (represented as '\n'). For now + // we will replace them with spaces since `canonicalId` does not currently handle this case. + varName = varName.replace(/\\n/g, ' ') const varId = canonicalId(varName) // Extract optional -> units string From d4c7f7d8c5ec38f1b2d0502ac3a6eb064fa2f0fd Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 12:04:53 -0700 Subject: [PATCH 37/77] fix: parse sim_specs parameters and include in Model AST type --- packages/parse/src/ast/ast-builders.ts | 4 +- packages/parse/src/ast/ast-types.ts | 21 ++ .../parse/src/xmile/parse-xmile-model.spec.ts | 183 +++++++++++++++++- packages/parse/src/xmile/parse-xmile-model.ts | 122 +++++++++--- 4 files changed, 290 insertions(+), 40 deletions(-) diff --git a/packages/parse/src/ast/ast-builders.ts b/packages/parse/src/ast/ast-builders.ts index c56f97ba..f45b85cd 100644 --- a/packages/parse/src/ast/ast-builders.ts +++ b/packages/parse/src/ast/ast-builders.ts @@ -20,6 +20,7 @@ import type { Model, NumberLiteral, ParensExpr, + SimulationSpec, StringLiteral, SubName, SubscriptMapping, @@ -244,8 +245,9 @@ export function lookupVarEqn(varDef: VariableDef, lookupDef: LookupDef, units = // MODEL // -export function model(dimensions: DimensionDef[], equations: Equation[]): Model { +export function model(dimensions: DimensionDef[], equations: Equation[], simulationSpec?: SimulationSpec): Model { return { + simulationSpec, dimensions, equations } diff --git a/packages/parse/src/ast/ast-types.ts b/packages/parse/src/ast/ast-types.ts index 87332d90..ef0de353 100644 --- a/packages/parse/src/ast/ast-types.ts +++ b/packages/parse/src/ast/ast-types.ts @@ -1,5 +1,19 @@ // Copyright (c) 2023 Climate Interactive / New Venture Fund +// +// SIMULATION SPEC +// + +/** The simulation parameters, such as start time, end time, and time step. */ +export interface SimulationSpec { + /** The start time of the simulation. */ + startTime: number + /** The end time of the simulation. */ + endTime: number + /** The time step of the simulation. */ + timeStep: number +} + // // DIMENSIONS + SUBSCRIPTS // @@ -391,6 +405,13 @@ export interface Equation { /** A complete model definition, including all defined dimensions and equations. */ export interface Model { + /** + * The simulation parameters, such as start time, end time, and time step. + * + * NOTE: This will be defined for XMILE models, but may be undefined for Vensim models + * for which the parameters are not compile-time constants. + */ + simulationSpec?: SimulationSpec /** The array of all dimension definitions in the model. */ dimensions: DimensionDef[] /** The array of all variable/equation definitions in the model. */ diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index b048a8e5..b41a1fe4 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -6,7 +6,7 @@ import { dimDef, exprEqn, model, num, varDef } from '../ast/ast-builders' import { parseXmileModel } from './parse-xmile-model' -function xmile(dimensions: string, variables: string): string { +function xmile(dimensions: string, variables: string, options?: { simSpecs?: string }): string { let dims: string if (dimensions.length > 0) { dims = `\ @@ -27,6 +27,18 @@ function xmile(dimensions: string, variables: string): string { vars = '' } + let simSpecs: string + if (options?.simSpecs !== undefined) { + simSpecs = options.simSpecs + } else { + simSpecs = `\ + + 0 + 100 +
1
+
` + } + return `\
@@ -34,11 +46,7 @@ function xmile(dimensions: string, variables: string): string { Ventana Systems, xmutil Vensim, xmutil
- - 0 - 100 -
1
-
+${simSpecs} ${dims} ${vars} @@ -57,6 +65,9 @@ describe('parseXmileModel', () => { expect(() => parseXmileModel(mdl)).toThrow(msg) }) + // TODO: Verify error message + it.skip('should throw an error if model dimension definition cannot be parsed') + it('should throw an error if model equation cannot be parsed', () => { const vars = `\ @@ -75,6 +86,114 @@ Detail: expect(() => parseXmileModel(mdl)).toThrow(msg) }) + it('should throw an error if sim specs element is missing', () => { + const simSpecs = '' + const mdl = xmile('', '', { simSpecs }) + const msg = ' element is required for XMILE model definition' + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + + it('should throw an error if element is missing', () => { + const simSpecs = `\ + + 100 +
1
+
` + const mdl = xmile('', '', { simSpecs }) + const msg = `\ +Failed to parse XMILE model definition at line 7: + + 100 +
1
+
+ +Detail: + element is required in XMILE sim specs` + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + + it('should throw an error if element is not a number', () => { + const simSpecs = `\ + + NOT A NUMBER + 100 +
1
+
` + const mdl = xmile('', '', { simSpecs }) + const msg = `\ +Failed to parse XMILE model definition at line 7: + + NOT A NUMBER + 100 +
1
+
+ +Detail: + Invalid numeric value for element: NOT A NUMBER` + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + + it('should throw an error if element is missing', () => { + const simSpecs = `\ + + 0 +
1
+
` + const mdl = xmile('', '', { simSpecs }) + const msg = `\ +Failed to parse XMILE model definition at line 7: + + 0 +
1
+
+ +Detail: + element is required in XMILE sim specs` + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + + it('should throw an error if element is not a number', () => { + const simSpecs = `\ + + 0 + NOT A NUMBER +
1
+
` + const mdl = xmile('', '', { simSpecs }) + const msg = `\ +Failed to parse XMILE model definition at line 7: + + 0 + NOT A NUMBER +
1
+
+ +Detail: + Invalid numeric value for element: NOT A NUMBER` + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + + it('should throw an error if
element is not a number', () => { + const simSpecs = `\ + + 0 + 100 +
NOT A NUMBER
+
` + const mdl = xmile('', '', { simSpecs }) + const msg = `\ +Failed to parse XMILE model definition at line 7: + + 0 + 100 +
NOT A NUMBER
+
+ +Detail: + Invalid numeric value for
element: NOT A NUMBER` + expect(() => parseXmileModel(mdl)).toThrow(msg) + }) + it('should parse a model with dimension definition only (no equations)', () => { const dims = `\ @@ -83,7 +202,13 @@ Detail: ` const mdl = xmile(dims, '') - expect(parseXmileModel(mdl)).toEqual(model([dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'])], [])) + expect(parseXmileModel(mdl)).toEqual( + model([dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'])], [], { + startTime: 0, + endTime: 100, + timeStep: 1 + }) + ) }) it('should parse a model with dimension definition with comment', () => { @@ -96,7 +221,11 @@ Detail: ` const mdl = xmile(dims, '') expect(parseXmileModel(mdl)).toEqual( - model([dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], []) + model([dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], [], { + startTime: 0, + endTime: 100, + timeStep: 1 + }) ) }) @@ -106,7 +235,34 @@ Detail: 1 ` const mdl = xmile('', vars) - expect(parseXmileModel(mdl)).toEqual(model([], [exprEqn(varDef('x'), num(1))])) + expect(parseXmileModel(mdl)).toEqual( + model([], [exprEqn(varDef('x'), num(1))], { + startTime: 0, + endTime: 100, + timeStep: 1 + }) + ) + }) + + it('should parse a model with no explicit dt value (defaults to 1)', () => { + const vars = `\ + + 1 +` + const mdl = xmile('', vars, { + simSpecs: `\ + + 2000 + 2100 +` + }) + expect(parseXmileModel(mdl)).toEqual( + model([], [exprEqn(varDef('x'), num(1))], { + startTime: 2000, + endTime: 2100, + timeStep: 1 + }) + ) }) it('should parse a model with equation with units and single-line comment', () => { @@ -134,7 +290,14 @@ Detail: [dimDef('DimA', 'DimA', ['A1', 'A2', 'A3'], undefined, 'comment is here')], // variable definitions - [exprEqn(varDef('x', ['DimA']), num(1), 'meters', 'comment is here')] + [exprEqn(varDef('x', ['DimA']), num(1), 'meters', 'comment is here')], + + // simulation spec + { + startTime: 0, + endTime: 100, + timeStep: 1 + } ) ) }) diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index a11d84e2..945225eb 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -3,11 +3,11 @@ import type { XmlElement } from '@rgrove/parse-xml' import { parseXml } from '@rgrove/parse-xml' -import type { DimensionDef, Equation, Model } from '../ast/ast-types' +import type { DimensionDef, Equation, Model, SimulationSpec } from '../ast/ast-types' import { parseXmileDimensionDef } from './parse-xmile-dimension-def' import { parseXmileVariableDef } from './parse-xmile-variable-def' -import { firstElemOf, elemsOf } from './xml' +import { firstElemOf, elemsOf, xmlError } from './xml' /** * Parse the given XMILE model definition and return a `Model` AST node. @@ -28,19 +28,68 @@ export function parseXmileModel(input: string): Model { throw new Error(msg) } + // Extract -> SimulationSpec + const simulationSpec: SimulationSpec = parseSimSpecs(xml.root, input) + // Extract -> DimensionDef[] - const dimensions: DimensionDef[] = parseDimensionDefs(xml.root) + const dimensions: DimensionDef[] = parseDimensionDefs(xml.root, input) // Extract -> Equation[] const equations: Equation[] = parseVariableDefs(xml.root, input) return { + simulationSpec, dimensions, equations } } -function parseDimensionDefs(rootElem: XmlElement | undefined): DimensionDef[] { +function parseSimSpecs(rootElem: XmlElement | undefined, originalXml: string): SimulationSpec { + // Extract element + const simSpecsElem = firstElemOf(rootElem, 'sim_specs') + if (simSpecsElem === undefined) { + throw new Error(xmlError(rootElem, ' element is required for XMILE model definition')) + } + + function getSimSpecValue(name: string, required: boolean): number | undefined { + const elem = firstElemOf(simSpecsElem, name) + if (required && elem === undefined) { + const error = new Error(xmlError(simSpecsElem, `<${name}> element is required in XMILE sim specs`)) + throwXmileParseError(error, originalXml, simSpecsElem, 'model') + } + if (elem === undefined) { + return undefined + } + const value = Number(elem.text) + if (!isNaN(value)) { + return value + } else { + const error = new Error(xmlError(elem, `Invalid numeric value for <${name}> element: ${elem.text}`)) + throwXmileParseError(error, originalXml, simSpecsElem, 'model') + } + } + + // Extract element + const startTime = getSimSpecValue('start', true) + + // Extract element + const endTime = getSimSpecValue('stop', true) + + // Extract
element + let timeStep = getSimSpecValue('dt', false) + if (timeStep === undefined) { + // The default `dt` value is 1 according to the XMILE spec + timeStep = 1 + } + + return { + startTime, + endTime, + timeStep + } +} + +function parseDimensionDefs(rootElem: XmlElement | undefined, originalXml: string): DimensionDef[] { const dimensionDefs: DimensionDef[] = [] const dimensionsElem = firstElemOf(rootElem, 'dimensions') @@ -48,7 +97,11 @@ function parseDimensionDefs(rootElem: XmlElement | undefined): DimensionDef[] { // Extract -> SubscriptRange const dimElems = elemsOf(dimensionsElem, ['dim']) for (const dimElem of dimElems) { - dimensionDefs.push(parseXmileDimensionDef(dimElem)) + try { + dimensionDefs.push(parseXmileDimensionDef(dimElem)) + } catch (e) { + throwXmileParseError(e, originalXml, dimElem, 'dimension') + } } } @@ -73,30 +126,7 @@ function parseVariableDefs(rootElem: XmlElement | undefined, originalXml: string equations.push(...eqns) } } catch (e) { - // Include context such as line/column numbers in the error message if available - let linePart = '' - // Try to get line number from the XML element's position - const lineNumInOriginalXml = getLineNumber(originalXml, varElem.start) - if (lineNumInOriginalXml !== -1) { - if (e.cause?.code === 'VensimParseError') { - if (e.cause.line) { - // The line number reported by ANTLR is relative to the beginning of the - // preprocessed definition (since we parse each definition individually), - // so we need to add it to the line of the definition in the original source - const lineNum = e.cause.line - 1 + lineNumInOriginalXml - linePart += ` at line ${lineNum}` - if (e.cause.column) { - linePart += `, col ${e.cause.column}` - } - } - } else { - // Include the line number from the original XML - linePart += ` at line ${lineNumInOriginalXml}` - } - } - const varElemString = extractXmlLines(originalXml, varElem.start, varElem.end) - const msg = `Failed to parse XMILE variable definition${linePart}:\n${varElemString}\n\nDetail:\n ${e.message}` - throw new Error(msg) + throwXmileParseError(e, originalXml, varElem, 'variable') } } } @@ -104,6 +134,40 @@ function parseVariableDefs(rootElem: XmlElement | undefined, originalXml: string return equations } +function throwXmileParseError( + originalError: Error, + originalXml: string, + elem: XmlElement, + elemKind: 'model' | 'dimension' | 'variable' +): void { + // Include context such as line/column numbers in the error message if available + let linePart = '' + // Try to get line number from the XML element's position + const lineNumInOriginalXml = getLineNumber(originalXml, elem.start) + if (lineNumInOriginalXml !== -1) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const cause = (originalError as any).cause as { code?: string; line?: number; column?: number } + if (cause?.code === 'VensimParseError') { + if (cause.line) { + // The line number reported by ANTLR is relative to the beginning of the + // preprocessed definition (since we parse each definition individually), + // so we need to add it to the line of the definition in the original source + const lineNum = cause.line - 1 + lineNumInOriginalXml + linePart += ` at line ${lineNum}` + if (cause.column) { + linePart += `, col ${cause.column}` + } + } + } else { + // Include the line number from the original XML + linePart += ` at line ${lineNumInOriginalXml}` + } + } + const elemString = extractXmlLines(originalXml, elem.start, elem.end) + const msg = `Failed to parse XMILE ${elemKind} definition${linePart}:\n${elemString}\n\nDetail:\n ${originalError.message}` + throw new Error(msg) +} + /** * Calculate the line number from a byte offset in the original XML string. * From 2097cccbf2debb68e578909ce25482c15c1aaa79 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 12:47:05 -0700 Subject: [PATCH 38/77] fix: remove debug logging --- packages/cli/src/sde-generate.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/sde-generate.js b/packages/cli/src/sde-generate.js index 39d571e6..7805740e 100644 --- a/packages/cli/src/sde-generate.js +++ b/packages/cli/src/sde-generate.js @@ -58,7 +58,6 @@ export let handler = async argv => { export let generate = async (model, opts) => { // Get the model name and directory from the model argument. let { modelDirname, modelName, modelPathname, modelKind } = modelPathProps(model) - console.log(modelPathname, modelKind) // Ensure the build directory exists. let buildDirname = buildDir(opts.builddir, modelDirname) let spec = parseSpec(opts.spec) From f252338a83f0fc5e63c90853c0a7beacfebafee8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 23 Aug 2025 12:47:51 -0700 Subject: [PATCH 39/77] fix: synthesize variables for XMILE control parameters --- packages/compile/src/model/model.js | 58 ++++++++++++++++++- .../src/model/read-equations-xmile.spec.ts | 24 +++++++- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/packages/compile/src/model/model.js b/packages/compile/src/model/model.js index 8db55657..3100dc81 100644 --- a/packages/compile/src/model/model.js +++ b/packages/compile/src/model/model.js @@ -2,7 +2,7 @@ import B from 'bufx' import yaml from 'js-yaml' import * as R from 'ramda' -import { toPrettyString } from '@sdeverywhere/parse' +import { canonicalId, toPrettyString } from '@sdeverywhere/parse' import { canonicalName, decanonicalize, isIterable, /*listConcat,*/ strlist, vlog, vsort } from '../_shared/helpers.js' import { @@ -93,6 +93,62 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) { timeVar.varName = '_time' vars.push(timeVar) + // Helper function to define a control variable for XMILE models + function defineXmileControlVar(varName, varId, rhsValue) { + let rhsExpr + if (typeof rhsValue === 'number') { + rhsExpr = { + kind: 'number', + value: rhsValue, + text: rhsValue.toString() + } + } else { + rhsExpr = { + kind: 'variable-ref', + varName: rhsValue, + varId: canonicalId(rhsValue) + } + } + const v = new Variable() + v.modelLHS = varName + v.varName = varId + v.parsedEqn = { + lhs: { + varDef: { + varName, + varId + } + }, + rhs: { + kind: 'expr', + expr: rhsExpr + } + } + v.modelFormula = toPrettyString(rhsExpr, { compact: true }) + v.includeInOutput = false + vars.push(v) + } + + if (parsedModel.kind === 'xmile') { + // XXX: Unlike Vensim models, XMILE models do not include the control parameters as + // normal model equations; instead, they are defined in the `` element. + // In addition, XMILE allows these values to be accessed in equations (e.g., `` + // can be accessed as `STARTTIME`, `` as `STOPTIME`, and `
` as `DT`). + // For compatibility with the existing runtime code (which expects these variables + // to be defined using the Vensim names), we will synthesize variables using the + // Vensim names (e.g., `INITIAL TIME`) and also synthesize variables that derive + // from these using the XMILE names (e.g., `STARTTIME`). + defineXmileControlVar('INITIAL TIME', '_initial_time', parsedModel.root.simulationSpec.startTime) + defineXmileControlVar('FINAL TIME', '_final_time', parsedModel.root.simulationSpec.endTime) + defineXmileControlVar('TIME STEP', '_time_step', parsedModel.root.simulationSpec.timeStep) + defineXmileControlVar('STARTTIME', '_starttime', 'INITIAL TIME') + defineXmileControlVar('STOPTIME', '_stoptime', 'FINAL TIME') + defineXmileControlVar('DT', '_dt', 'TIME STEP') + // XXX: For now, also include a `SAVEPER` variable that is the same as `TIME STEP` (is there + // an equivalent of this in XMILE?) + defineXmileControlVar('SAVEPER', '_saveper', 'TIME STEP') + } + // Add the variables to the `Model` vars.forEach(addVariable) if (opts?.stopAfterReadVariables) return diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index d38f8a75..feb004eb 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -70,12 +70,32 @@ function readInlineModel( opts?: { specialSeparationDims?: { [key: string]: string } separateAllVarsWithDims?: string[][] + filterControlVars?: boolean } ): Variable[] { const vars = readSubscriptsAndEquationsFromSource({ modelText, modelDir }, opts) - // Exclude the `Time` variable so that we have one less thing to check - return vars.filter(v => v.varName !== '_time') + if (opts?.filterControlVars !== false) { + // Exclude the `Time` variable and other synthesized control variables so that we have + // fewer things to check + return vars.filter(v => { + switch (v.varName) { + case '_time': + case '_initial_time': + case '_final_time': + case '_time_step': + case '_starttime': + case '_stoptime': + case '_dt': + return false + default: + return true + } + }) + } else { + // Include the control variables + return vars + } } // function readSubscriptsAndEquations(modelName: string): Variable[] { From c349a04d4fdeb09542174ce29c36aee794626187 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 24 Aug 2025 11:22:37 -0700 Subject: [PATCH 40/77] fix: expose parseInlineXmileModel in compile package [skip ci] --- packages/compile/src/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/compile/src/index.js b/packages/compile/src/index.js index b682204d..9692bc7e 100644 --- a/packages/compile/src/index.js +++ b/packages/compile/src/index.js @@ -38,6 +38,14 @@ export function parseInlineVensimModel(mdlContent /*: string*/, modelDir /*?: st return parseModel(mdlContent, 'vensim', modelDir, { sort: false }) } +/** + * @hidden This is not yet part of the public API; it is exposed only for use + * in the experimental playground app. + */ +export function parseInlineXmileModel(mdlContent /*: string*/, modelDir /*?: string*/) /*: ParsedModel*/ { + return parseModel(mdlContent, 'xmile', modelDir) +} + /** * @hidden This is not yet part of the public API; it is exposed only for use * in the experimental playground app. From c5d897c6c04a4a89a303ca97eb0193ae7c6a0cd8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 30 Aug 2025 20:23:56 -0700 Subject: [PATCH 41/77] build: update lock file --- pnpm-lock.yaml | 218 ++++++++++++++++++------------------------------- 1 file changed, 79 insertions(+), 139 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4faf9b3..6609c0ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ importers: devDependencies: '@typescript-eslint/eslint-plugin': specifier: ^8.2.0 - version: 8.2.0(@typescript-eslint/parser@8.2.0)(eslint@8.57.0)(typescript@5.2.2) + version: 8.2.0(@typescript-eslint/parser@8.2.0(eslint@8.57.0)(typescript@5.2.2))(eslint@8.57.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^8.2.0 version: 8.2.0(eslint@8.57.0)(typescript@5.2.2) @@ -25,7 +25,7 @@ importers: version: 3.2.0(eslint@8.57.0) eslint-plugin-svelte3: specifier: ^4.0.0 - version: 4.0.0(eslint@8.57.0) + version: 4.0.0(eslint@8.57.0)(svelte@4.2.19) glob: specifier: ^8.0.3 version: 8.0.3 @@ -37,19 +37,19 @@ importers: version: 2.6.2 tsup: specifier: ^8.2.4 - version: 8.2.4(typescript@5.2.2) + version: 8.2.4(postcss@8.4.41)(typescript@5.2.2) typedoc: specifier: 0.25.0 version: 0.25.0(typescript@5.2.2) typedoc-plugin-markdown: specifier: 3.16.0 - version: 3.16.0(typedoc@0.25.0) + version: 3.16.0(typedoc@0.25.0(typescript@5.2.2)) typescript: specifier: ^5.2.2 version: 5.2.2 vitest: specifier: ^2.0.5 - version: 2.0.5 + version: 2.0.5(@types/node@20.5.7)(sass@1.77.8) examples/hello-world: dependencies: @@ -67,7 +67,7 @@ importers: version: link:../../packages/plugin-worker vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/house-game: dependencies: @@ -98,7 +98,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@4.2.19)(vite@5.4.2) + version: 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) '@types/chart.js': specifier: ^2.9.34 version: 2.9.37 @@ -113,13 +113,13 @@ importers: version: 4.2.19 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss@8.4.41)(sass@1.77.8)(svelte@4.2.19) + version: 3.8.6(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19) svelte-preprocess: specifier: ^6.0.2 version: 6.0.2(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19)(typescript@5.2.2) vite: specifier: ^5.4.2 - version: 5.4.2(sass@1.77.8) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/sample-check-app: dependencies: @@ -135,7 +135,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@4.2.19)(vite@5.4.2) + version: 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) postcss: specifier: ^8.4.41 version: 8.4.41 @@ -156,7 +156,7 @@ importers: version: 6.0.2(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19)(typescript@5.2.2) vite: specifier: ^5.4.2 - version: 5.4.2(sass@1.77.8) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/sample-check-bundle: dependencies: @@ -169,7 +169,7 @@ importers: devDependencies: vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/sample-check-tests: dependencies: @@ -182,7 +182,7 @@ importers: devDependencies: vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/template-default: devDependencies: @@ -212,7 +212,7 @@ importers: version: link:../../packages/plugin-worker '@typescript-eslint/eslint-plugin': specifier: ^8.2.0 - version: 8.2.0(@typescript-eslint/parser@8.2.0)(eslint@8.57.0)(typescript@5.2.2) + version: 8.2.0(@typescript-eslint/parser@8.2.0(eslint@8.57.0)(typescript@5.2.2))(eslint@8.57.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^8.2.0 version: 8.2.0(eslint@8.57.0)(typescript@5.2.2) @@ -227,7 +227,7 @@ importers: version: 3.2.0(eslint@8.57.0) eslint-plugin-svelte3: specifier: ^4.0.0 - version: 4.0.0(eslint@8.57.0) + version: 4.0.0(eslint@8.57.0)(svelte@4.2.19) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -255,7 +255,7 @@ importers: version: 2.9.37 vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/template-default/packages/core: dependencies: @@ -271,7 +271,7 @@ importers: version: 5.2.2 vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/template-minimal: dependencies: @@ -322,7 +322,7 @@ importers: version: link:../../packages/plugin-worker '@typescript-eslint/eslint-plugin': specifier: ^8.2.0 - version: 8.2.0(@typescript-eslint/parser@8.2.0)(eslint@8.57.0)(typescript@5.2.2) + version: 8.2.0(@typescript-eslint/parser@8.2.0(eslint@8.57.0)(typescript@5.2.2))(eslint@8.57.0)(typescript@5.2.2) '@typescript-eslint/parser': specifier: ^8.2.0 version: 8.2.0(eslint@8.57.0)(typescript@5.2.2) @@ -337,7 +337,7 @@ importers: version: 3.2.0(eslint@8.57.0) eslint-plugin-svelte3: specifier: ^4.0.0 - version: 4.0.0(eslint@8.57.0) + version: 4.0.0(eslint@8.57.0)(svelte@4.2.19) npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -362,7 +362,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@4.2.19)(vite@5.4.2) + version: 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) '@types/chart.js': specifier: ^2.9.34 version: 2.9.37 @@ -383,13 +383,13 @@ importers: version: 4.2.19 svelte-check: specifier: ^3.8.6 - version: 3.8.6(postcss@8.4.41)(sass@1.77.8)(svelte@4.2.19) + version: 3.8.6(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19) svelte-preprocess: specifier: ^6.0.2 version: 6.0.2(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19)(typescript@5.2.2) vite: specifier: ^5.4.2 - version: 5.4.2(sass@1.77.8) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) examples/template-svelte/packages/core: dependencies: @@ -405,7 +405,7 @@ importers: version: 5.2.2 vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) models: dependencies: @@ -494,7 +494,7 @@ importers: devDependencies: '@sveltejs/vite-plugin-svelte': specifier: ^3.1.2 - version: 3.1.2(svelte@4.2.19)(vite@5.4.2) + version: 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) '@types/chart.js': specifier: ^2.9.34 version: 2.9.37 @@ -524,7 +524,7 @@ importers: version: 6.0.2(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19)(typescript@5.2.2) vite: specifier: ^5.4.2 - version: 5.4.2(sass@1.77.8) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) packages/cli: dependencies: @@ -625,6 +625,9 @@ importers: packages/parse: dependencies: + '@rgrove/parse-xml': + specifier: ^4.1.0 + version: 4.2.0 antlr4: specifier: 4.12.0 version: 4.12.0 @@ -676,7 +679,7 @@ importers: version: 2.76.0 vite: specifier: 5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) devDependencies: '@sdeverywhere/build': specifier: '*' @@ -732,7 +735,7 @@ importers: version: link:../build vite: specifier: ^5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) packages/plugin-wasm: dependencies: @@ -757,7 +760,7 @@ importers: version: link:../runtime-async vite: specifier: 5.4.2 - version: 5.4.2(@types/node@20.5.7) + version: 5.4.2(@types/node@20.5.7)(sass@1.77.8) devDependencies: '@sdeverywhere/build': specifier: '*' @@ -1403,9 +1406,6 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - peerDependenciesMeta: - eslint: - optional: true '@eslint-community/regexpp@4.11.0': resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} @@ -1506,14 +1506,15 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@rgrove/parse-xml@4.2.0': + resolution: {integrity: sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==} + engines: {node: '>=14.0.0'} + '@rollup/plugin-node-resolve@13.3.0': resolution: {integrity: sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw==} engines: {node: '>= 10.0.0'} peerDependencies: rollup: ^2.42.0 - peerDependenciesMeta: - rollup: - optional: true '@rollup/plugin-replace@5.0.2': resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} @@ -1529,9 +1530,6 @@ packages: engines: {node: '>= 8.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0 - peerDependenciesMeta: - rollup: - optional: true '@rollup/pluginutils@5.0.4': resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==} @@ -1629,9 +1627,6 @@ packages: '@sveltejs/vite-plugin-svelte': ^3.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 - peerDependenciesMeta: - svelte: - optional: true '@sveltejs/vite-plugin-svelte@3.1.2': resolution: {integrity: sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==} @@ -1639,9 +1634,6 @@ packages: peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 - peerDependenciesMeta: - svelte: - optional: true '@types/byline@4.2.33': resolution: {integrity: sha512-LJYez7wrWcJQQDknqZtrZuExMGP0IXmPl1rOOGDqLbu+H7UNNRfKNuSxCBcQMLH1EfjeWidLedC/hCc5dDfBog==} @@ -1708,8 +1700,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: - eslint: - optional: true typescript: optional: true @@ -1720,8 +1710,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '*' peerDependenciesMeta: - eslint: - optional: true typescript: optional: true @@ -1756,9 +1744,6 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - peerDependenciesMeta: - eslint: - optional: true '@typescript-eslint/visitor-keys@8.2.0': resolution: {integrity: sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==} @@ -2207,29 +2192,18 @@ packages: hasBin: true peerDependencies: eslint: '>=7.0.0' - peerDependenciesMeta: - eslint: - optional: true eslint-plugin-eslint-comments@3.2.0: resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} engines: {node: '>=6.5.0'} peerDependencies: eslint: '>=4.19.1' - peerDependenciesMeta: - eslint: - optional: true eslint-plugin-svelte3@4.0.0: resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} peerDependencies: eslint: '>=8.0.0' svelte: ^3.2.0 - peerDependenciesMeta: - eslint: - optional: true - svelte: - optional: true eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} @@ -3082,9 +3056,6 @@ packages: peerDependencies: prettier: ^3.0.0 svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - peerDependenciesMeta: - svelte: - optional: true prettier@2.6.2: resolution: {integrity: sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==} @@ -3420,35 +3391,23 @@ packages: resolution: {integrity: sha512-nC2KXPs/MQF79vnQEj7RZFkWKdocNfN64Zh/kAWTDS4gFNdCrp7q8dwN3PIxe/mqiaDIUc7x4iv2CtOnVPItcQ==} peerDependencies: svelte: ^3.43.1 || ^4.0.0 - peerDependenciesMeta: - svelte: - optional: true svelte-check@3.8.6: resolution: {integrity: sha512-ij0u4Lw/sOTREP13BdWZjiXD/BlHE6/e2e34XzmVmsp5IN4kVa3PWP65NM32JAgwjZlwBg/+JtiNV1MM8khu0Q==} hasBin: true peerDependencies: svelte: ^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 - peerDependenciesMeta: - svelte: - optional: true svelte-dnd-action@0.9.50: resolution: {integrity: sha512-EnUyBmiMjflGr+NKfPmPpyareKEbQkdVGRTv6YyBHIEXPOv92XHgPHubErQ1tOaXaZ2c7y4Gja85uA1/Desw/Q==} peerDependencies: svelte: '>=3.23.0 || ^5.0.0-next.0' - peerDependenciesMeta: - svelte: - optional: true svelte-hmr@0.16.0: resolution: {integrity: sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==} engines: {node: ^12.20 || ^14.13.1 || >= 16} peerDependencies: svelte: ^3.19.0 || ^4.0.0 - peerDependenciesMeta: - svelte: - optional: true svelte-i18n@4.0.1: resolution: {integrity: sha512-jaykGlGT5PUaaq04JWbJREvivlCnALtT+m87Kbm0fxyYHynkQaxQMnIKHLm2WeIuBRoljzwgyvz0Z6/CMwfdmQ==} @@ -3456,9 +3415,6 @@ packages: hasBin: true peerDependencies: svelte: ^3 || ^4 || ^5 - peerDependenciesMeta: - svelte: - optional: true svelte-preprocess@5.1.4: resolution: {integrity: sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==} @@ -3494,8 +3450,6 @@ packages: optional: true sugarss: optional: true - svelte: - optional: true typescript: optional: true @@ -3533,8 +3487,6 @@ packages: optional: true sugarss: optional: true - svelte: - optional: true typescript: optional: true @@ -3818,7 +3770,6 @@ packages: xlsx@https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz: resolution: {tarball: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz} - name: xlsx version: 0.20.2 engines: {node: '>=0.8'} hasBin: true @@ -4205,6 +4156,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@rgrove/parse-xml@4.2.0': {} + '@rollup/plugin-node-resolve@13.3.0(rollup@2.76.0)': dependencies: '@rollup/pluginutils': 3.1.0(rollup@2.76.0) @@ -4219,6 +4172,7 @@ snapshots: dependencies: '@rollup/pluginutils': 5.0.4(rollup@2.76.0) magic-string: 0.27.0 + optionalDependencies: rollup: 2.76.0 '@rollup/pluginutils@3.1.0(rollup@2.76.0)': @@ -4233,6 +4187,7 @@ snapshots: '@types/estree': 1.0.1 estree-walker: 2.0.2 picomatch: 2.3.1 + optionalDependencies: rollup: 2.76.0 '@rollup/rollup-android-arm-eabi@4.21.0': @@ -4283,26 +4238,26 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.21.0': optional: true - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.4.2)': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)))(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.2) + '@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) debug: 4.3.6 svelte: 4.2.19 - vite: 5.4.2(sass@1.77.8) + vite: 5.4.2(@types/node@20.5.7)(sass@1.77.8) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2)': + '@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@4.2.19)(vite@5.4.2) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)))(svelte@4.2.19)(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) debug: 4.3.6 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.11 svelte: 4.2.19 svelte-hmr: 0.16.0(svelte@4.2.19) - vite: 5.4.2(sass@1.77.8) - vitefu: 0.2.5(vite@5.4.2) + vite: 5.4.2(@types/node@20.5.7)(sass@1.77.8) + vitefu: 0.2.5(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)) transitivePeerDependencies: - supports-color @@ -4360,7 +4315,7 @@ snapshots: '@types/yargs-parser@21.0.0': {} - '@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0)(eslint@8.57.0)(typescript@5.2.2)': + '@typescript-eslint/eslint-plugin@8.2.0(@typescript-eslint/parser@8.2.0(eslint@8.57.0)(typescript@5.2.2))(eslint@8.57.0)(typescript@5.2.2)': dependencies: '@eslint-community/regexpp': 4.11.0 '@typescript-eslint/parser': 8.2.0(eslint@8.57.0)(typescript@5.2.2) @@ -4373,6 +4328,7 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.3.0(typescript@5.2.2) + optionalDependencies: typescript: 5.2.2 transitivePeerDependencies: - supports-color @@ -4385,6 +4341,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.2.0 debug: 4.3.6 eslint: 8.57.0 + optionalDependencies: typescript: 5.2.2 transitivePeerDependencies: - supports-color @@ -4400,6 +4357,7 @@ snapshots: '@typescript-eslint/utils': 8.2.0(eslint@8.57.0)(typescript@5.2.2) debug: 4.3.6 ts-api-utils: 1.3.0(typescript@5.2.2) + optionalDependencies: typescript: 5.2.2 transitivePeerDependencies: - eslint @@ -4417,6 +4375,7 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.3.0(typescript@5.2.2) + optionalDependencies: typescript: 5.2.2 transitivePeerDependencies: - supports-color @@ -4987,9 +4946,10 @@ snapshots: eslint: 8.57.0 ignore: 5.3.2 - eslint-plugin-svelte3@4.0.0(eslint@8.57.0): + eslint-plugin-svelte3@4.0.0(eslint@8.57.0)(svelte@4.2.19): dependencies: eslint: 8.57.0 + svelte: 4.2.19 eslint-scope@7.2.2: dependencies: @@ -5841,9 +5801,11 @@ snapshots: pirates@4.0.5: {} - postcss-load-config@6.0.1: + postcss-load-config@6.0.1(postcss@8.4.41): dependencies: lilconfig: 3.1.2 + optionalDependencies: + postcss: 8.4.41 postcss@8.4.41: dependencies: @@ -6234,26 +6196,6 @@ snapshots: svelte: 4.2.19 svelte-check@3.8.6(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - chokidar: 3.6.0 - picocolors: 1.0.1 - sade: 1.8.1 - svelte: 4.2.19 - svelte-preprocess: 5.1.4(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19)(typescript@5.2.2) - typescript: 5.2.2 - transitivePeerDependencies: - - '@babel/core' - - coffeescript - - less - - postcss - - postcss-load-config - - pug - - sass - - stylus - - sugarss - - svelte-check@3.8.6(postcss@8.4.41)(sass@1.77.8)(svelte@4.2.19): dependencies: '@jridgewell/trace-mapping': 0.3.19 chokidar: 3.5.3 @@ -6297,20 +6239,22 @@ snapshots: '@types/pug': 2.0.6 detect-indent: 6.1.0 magic-string: 0.30.11 - postcss: 8.4.41 - pug: 3.0.3 - sass: 1.77.8 sorcery: 0.11.0 strip-indent: 3.0.0 svelte: 4.2.19 + optionalDependencies: + postcss: 8.4.41 + pug: 3.0.3 + sass: 1.77.8 typescript: 5.2.2 svelte-preprocess@6.0.2(postcss@8.4.41)(pug@3.0.3)(sass@1.77.8)(svelte@4.2.19)(typescript@5.2.2): dependencies: + svelte: 4.2.19 + optionalDependencies: postcss: 8.4.41 pug: 3.0.3 sass: 1.77.8 - svelte: 4.2.19 typescript: 5.2.2 svelte@4.2.19: @@ -6410,7 +6354,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.2.4(typescript@5.2.2): + tsup@8.2.4(postcss@8.4.41)(typescript@5.2.2): dependencies: bundle-require: 5.0.0(esbuild@0.23.1) cac: 6.7.14 @@ -6422,12 +6366,14 @@ snapshots: globby: 11.1.0 joycon: 3.1.1 picocolors: 1.0.1 - postcss-load-config: 6.0.1 + postcss-load-config: 6.0.1(postcss@8.4.41) resolve-from: 5.0.0 rollup: 4.21.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.4.41 typescript: 5.2.2 transitivePeerDependencies: - jiti @@ -6443,7 +6389,7 @@ snapshots: type@2.7.3: {} - typedoc-plugin-markdown@3.16.0(typedoc@0.25.0): + typedoc-plugin-markdown@3.16.0(typedoc@0.25.0(typescript@5.2.2)): dependencies: handlebars: 4.7.7 typedoc: 0.25.0(typescript@5.2.2) @@ -6483,13 +6429,13 @@ snapshots: spdx-correct: 3.1.1 spdx-expression-parse: 3.0.1 - vite-node@2.0.5: + vite-node@2.0.5(@types/node@20.5.7)(sass@1.77.8): dependencies: cac: 6.7.14 debug: 4.3.6 pathe: 1.1.2 tinyrainbow: 1.2.0 - vite: 5.4.2(@types/node@20.5.7) + vite: 5.4.2(@types/node@20.5.7)(sass@1.77.8) transitivePeerDependencies: - '@types/node' - less @@ -6501,29 +6447,21 @@ snapshots: - supports-color - terser - vite@5.4.2(@types/node@20.5.7): + vite@5.4.2(@types/node@20.5.7)(sass@1.77.8): dependencies: - '@types/node': 20.5.7 esbuild: 0.21.5 postcss: 8.4.41 rollup: 4.21.0 optionalDependencies: + '@types/node': 20.5.7 fsevents: 2.3.3 - - vite@5.4.2(sass@1.77.8): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.41 - rollup: 4.21.0 sass: 1.77.8 + + vitefu@0.2.5(vite@5.4.2(@types/node@20.5.7)(sass@1.77.8)): optionalDependencies: - fsevents: 2.3.3 + vite: 5.4.2(@types/node@20.5.7)(sass@1.77.8) - vitefu@0.2.5(vite@5.4.2): - dependencies: - vite: 5.4.2(sass@1.77.8) - - vitest@2.0.5: + vitest@2.0.5(@types/node@20.5.7)(sass@1.77.8): dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.5 @@ -6541,9 +6479,11 @@ snapshots: tinybench: 2.9.0 tinypool: 1.0.1 tinyrainbow: 1.2.0 - vite: 5.4.2(@types/node@20.5.7) - vite-node: 2.0.5 + vite: 5.4.2(@types/node@20.5.7)(sass@1.77.8) + vite-node: 2.0.5(@types/node@20.5.7)(sass@1.77.8) why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.5.7 transitivePeerDependencies: - less - lightningcss From e103751b3db48c0e407cd136ab71a3db637981d7 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 30 Aug 2025 20:27:41 -0700 Subject: [PATCH 42/77] test: ignore SAVEPER in XMILE tests --- packages/compile/src/model/read-equations-xmile.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index feb004eb..4e7122f1 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -84,6 +84,7 @@ function readInlineModel( case '_initial_time': case '_final_time': case '_time_step': + case '_saveper': case '_starttime': case '_stoptime': case '_dt': From c8dd5fd1472944233fe8bb15e7c692fbe1b61431 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 31 Aug 2025 08:40:34 -0700 Subject: [PATCH 43/77] test: implement SMTH{1,3} tests --- .../src/model/read-equations-xmile.spec.ts | 431 +++++++++++++----- 1 file changed, 314 insertions(+), 117 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 4e7122f1..1ca6c0d1 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -4002,7 +4002,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY1 function', () => { + it('should work for DELAY1 function (without initial value argument)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| @@ -4390,7 +4390,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY3 function', () => { + it('should work for DELAY3 function (without initial value argument)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // input = 1 ~~| @@ -6317,22 +6317,36 @@ ${elements.join('\n')} ]) }) - it.skip('should work for SMOOTH function', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTH(input, delay) ~~| - `) + it('should work for SMTH1 function (without initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTH(input, delay) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH1(input, delay) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input', '3+PULSE(10,10)', { + v('input', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'] + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y', 'SMOOTH(input,delay)', { + v('y', 'SMTH1(input,delay)', { refId: '_y', references: ['__level1'], smoothVarRefId: '__level1' @@ -6349,32 +6363,63 @@ ${elements.join('\n')} ]) }) - it.skip('should work for SMOOTH function (with subscripted input and subscripted delay)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay[DimA] = 2, 3 ~~| - y[DimA] = SMOOTH(input[DimA], delay[DimA]) ~~| - `) + it('should work for SMTH1 function (with subscripted input and subscripted delay)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1 ~~| + // delay[DimA] = 2, 3 ~~| + // y[DimA] = SMOOTH(input[DimA], delay[DimA]) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + + 2 + + + 3 + + + + + + + SMTH1(input[DimA], delay[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '3+PULSE(10,10)', { + v('input[DimA]', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima'] + subscripts: ['_dima'], + varType: 'const' }), - v('delay[DimA]', '2,3', { + v('delay[A1]', '2', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '2,3', { + v('delay[A2]', '3', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('y[DimA]', 'SMOOTH(input[DimA],delay[DimA])', { + v('y[DimA]', 'SMTH1(input[DimA],delay[DimA])', { refId: '_y', references: ['__level1'], smoothVarRefId: '__level1', @@ -6393,12 +6438,11 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH1 instead of SMOOTH, skipping this test for now - it.skip('should work for SMOOTH function (with subscripted input and non-subscripted delay)', () => { + it('should work for SMTH1 function (with subscripted input and non-subscripted delay)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2 ~~| - // input[DimA] = 3 + PULSE(10, 10) ~~| + // input[DimA] = 1 ~~| // delay = 2 ~~| // y[DimA] = SMOOTH(input[DimA], delay) ~~| // `) @@ -6409,28 +6453,34 @@ ${elements.join('\n')} ` const xmileVars = `\ - - 3+PULSE(10,10) + + + + + 1 2 - - SMOOTH(input[DimA],delay) + + + + + SMTH1(input[DimA],delay) ` const mdl = xmile(xmileDims, xmileVars) const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '3+PULSE(10,10)', { + v('input[DimA]', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima'] + subscripts: ['_dima'], + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y[DimA]', 'SMOOTH(input[DimA],delay)', { + v('y[DimA]', 'SMTH1(input[DimA],delay)', { refId: '_y', references: ['__level1'], smoothVarRefId: '__level1', @@ -6449,18 +6499,34 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH1 instead of SMOOTH, skipping this test for now - it.skip('should work for SMOOTHI function', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - init = 5 ~~| - y = SMOOTHI(input, delay, init) ~~| - `) + it('should work for SMTH1 function (with initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // init = 5 ~~| + // y = SMOOTHI(input, delay, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + 5 + + + SMTH1(input, delay, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input', '3+PULSE(10,10)', { + v('input', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'] + varType: 'const' }), v('delay', '2', { refId: '_delay', @@ -6470,7 +6536,7 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('y', 'SMOOTHI(input,delay,init)', { + v('y', 'SMTH1(input,delay,init)', { refId: '_y', references: ['__level1'], smoothVarRefId: '__level1' @@ -6487,80 +6553,135 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH1 instead of SMOOTHI, skipping this test for now - it.skip('should work for SMOOTHI function (with subscripted variables)', () => { + it('should work for SMTH1 function (with initial value argument and subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (delay, init) and apply-to-all (input) // variables here to cover both cases - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - input[DimA] = x[DimA] + PULSE(10, 10) ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 4, 5, 6 ~~| - y[DimA] = SMOOTHI(input[DimA], delay[DimA], init[DimA]) ~~| - `) + + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // input[DimA] = x[DimA] ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 4, 5, 6 ~~| + // y[DimA] = SMOOTHI(input[DimA], delay[DimA], init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + x[DimA] + + + + + + + 1 + + + 2 + + + 3 + + + + + + + + 4 + + + 5 + + + 6 + + + + + + + SMTH1(input[DimA], delay[DimA], init[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + v('input[DimA]', 'x[DimA]', { refId: '_input', - referencedFunctionNames: ['__pulse'], references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], subscripts: ['_dima'] }), - v('delay[DimA]', '1,2,3', { + v('delay[A1]', '1', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A2]', '2', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A3]', '3', { refId: '_delay[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('init[DimA]', '4,5,6', { + v('init[A1]', '4', { refId: '_init[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('init[DimA]', '4,5,6', { + v('init[A2]', '5', { refId: '_init[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('init[DimA]', '4,5,6', { + v('init[A3]', '6', { refId: '_init[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('y[DimA]', 'SMOOTHI(input[DimA],delay[DimA],init[DimA])', { + v('y[DimA]', 'SMTH1(input[DimA],delay[DimA],init[DimA])', { refId: '_y', references: ['__level1'], smoothVarRefId: '__level1', @@ -6579,58 +6700,121 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH1 instead of SMOOTHI, skipping this test for now - it.skip('should work for SMOOTHI function (with separated variables using subdimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - input[DimA] = x[DimA] + PULSE(10, 10) ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 0 ~~| - y[A1] = 5 ~~| - y[SubA] = SMOOTHI(input[SubA], delay[SubA], init[SubA]) ~~| - `) + // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions + // for y[A1] and y[A2] instead of a single definition for y[SubA] + it.skip('should work for SMTH1 function (with initial value argument and separated variables using subdimension)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // input[DimA] = x[DimA] ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 0 ~~| + // y[A1] = 5 ~~| + // y[SubA] = SMOOTHI(input[SubA], delay[SubA], init[SubA]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + x[DimA] + + + + + + + 1 + + + 2 + + + 3 + + + + + + + 0 + + + + + + + 5 + + + SMTH1(input[A2], delay[A2], init[A2]) + + + SMTH1(input[A3], delay[A3], init[A3]) + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + v('input[DimA]', 'x[DimA]', { refId: '_input', - referencedFunctionNames: ['__pulse'], references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], subscripts: ['_dima'] }), - v('delay[DimA]', '1,2,3', { + v('delay[A1]', '1', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A2]', '2', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A3]', '3', { refId: '_delay[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), @@ -6644,14 +6828,14 @@ ${elements.join('\n')} subscripts: ['_a1'], varType: 'const' }), - v('y[SubA]', 'SMOOTHI(input[SubA],delay[SubA],init[SubA])', { + v('y[A2]', 'SMTH1(input[A2],delay[A2],init[A2])', { refId: '_y[_a2]', references: ['__level_y_1[_a2]'], separationDims: ['_suba'], smoothVarRefId: '__level_y_1[_a2]', subscripts: ['_a2'] }), - v('y[SubA]', 'SMOOTHI(input[SubA],delay[SubA],init[SubA])', { + v('y[A3]', 'SMTH1(input[A3],delay[A3],init[A3])', { refId: '_y[_a3]', references: ['__level_y_1[_a3]'], separationDims: ['_suba'], @@ -6681,23 +6865,36 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now - it.skip('should work for SMOOTH3 function', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTH3(input, delay) ~~| - `) + it('should work for SMTH3 function (without initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTH3(input, delay) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH3(input, delay) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input', '3+PULSE(10,10)', { + v('input', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'] + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y', 'SMOOTH3(input,delay)', { + v('y', 'SMTH3(input,delay)', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3' From 90d523e9cbdbfafb0522157ebae4b0daaae2fb35 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 31 Aug 2025 08:40:52 -0700 Subject: [PATCH 44/77] docs: update comment --- packages/compile/src/model/read-equations.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 8acea9b2..218ffa15 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -707,8 +707,8 @@ function visitFunctionCall(v, callExpr, context) { case '_INTEG': // NOTE: Stella doesn't have a built-in `INTEG` function, but our XMILE parser synthesizes - // an `INTEG` function call for `` variable definitions using the `` element - // as the `rate` argument for the Vensim-style `INTEG` function call + // an `INTEG` function call for `` variable definitions using the `` and + // `` elements as the `rate` argument for the Vensim-style `INTEG` function call validateCallDepth(callExpr, context) validateCallArgs(callExpr, 2) v.varType = 'level' From 5d4539270a22dac8a230e436111a50d3b614f80c Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 31 Aug 2025 08:52:41 -0700 Subject: [PATCH 45/77] fix: update create package to include model kind in parseAndGenerate call --- packages/create/src/step-config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create/src/step-config.ts b/packages/create/src/step-config.ts index 07d46a06..9debfe4a 100644 --- a/packages/create/src/step-config.ts +++ b/packages/create/src/step-config.ts @@ -483,7 +483,8 @@ async function readModelVars(projDir: string, mdlPath: string): Promise Date: Mon, 1 Sep 2025 08:57:20 -0700 Subject: [PATCH 46/77] test: convert the remaining SMOOTH3[I] tests --- .../src/model/read-equations-xmile.spec.ts | 499 +++++++++++++----- 1 file changed, 373 insertions(+), 126 deletions(-) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 1ca6c0d1..6b773997 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -4098,7 +4098,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY1 function (with initial value argument and subscripted variables)', () => { + it('should work for DELAY1 function (with initial value argument; with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases // Equivalent Vensim model for reference: @@ -4224,7 +4224,7 @@ ${elements.join('\n')} // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions // for y[A1] and y[A2] instead of a single definition for y[SubA] - it.skip('should work for DELAY1 function (with separated variables using subdimension)', () => { + it.skip('should work for DELAY1 function (with initial value argument; with separated variables using subdimension)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2, A3 ~~| @@ -4568,7 +4568,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY3I function (with initial value argument and nested function calls)', () => { + it('should work for DELAY3 function (with initial value argument; with nested function calls)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // input = 1 ~~| @@ -4665,7 +4665,7 @@ ${elements.join('\n')} ]) }) - it('should work for DELAY3 function (with initial value argument and subscripted variables)', () => { + it('should work for DELAY3 function (with initial value argument; with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases // Equivalent Vensim model for reference: @@ -4829,7 +4829,7 @@ ${elements.join('\n')} // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions // for y[A1] and y[A2] instead of a single definition for y[SubA] - it.skip('should work for DELAY3I function (with separated variables using subdimension)', () => { + it.skip('should work for DELAY3 function (with initial value argument; with separated variables using subdimension)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2, A3 ~~| @@ -6363,7 +6363,7 @@ ${elements.join('\n')} ]) }) - it('should work for SMTH1 function (with subscripted input and subscripted delay)', () => { + it('should work for SMTH1 function (without initial value argument; with subscripted input and subscripted delay)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2 ~~| @@ -6438,7 +6438,7 @@ ${elements.join('\n')} ]) }) - it('should work for SMTH1 function (with subscripted input and non-subscripted delay)', () => { + it('should work for SMTH1 function (without initial value argument; with subscripted input and non-subscripted delay)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2 ~~| @@ -6553,7 +6553,7 @@ ${elements.join('\n')} ]) }) - it('should work for SMTH1 function (with initial value argument and subscripted variables)', () => { + it('should work for SMTH1 function (with initial value argument; with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (delay, init) and apply-to-all (input) // variables here to cover both cases @@ -6702,7 +6702,7 @@ ${elements.join('\n')} // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions // for y[A1] and y[A2] instead of a single definition for y[SubA] - it.skip('should work for SMTH1 function (with initial value argument and separated variables using subdimension)', () => { + it.skip('should work for SMTH1 function (with initial value argument; with separated variables using subdimension)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // DimA: A1, A2, A3 ~~| @@ -6929,23 +6929,36 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now - it.skip('should work for SMOOTH3 function (when nested in another function)', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = MAX(SMOOTH3(input, delay), 0) ~~| - `) + it('should work for SMTH3 function (without initial value argument; when nested in another function)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = MAX(SMOOTH3(input, delay), 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + MAX(SMTH3(input, delay), 0) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input', '3+PULSE(10,10)', { + v('input', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'] + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y', 'MAX(SMOOTH3(input,delay),0)', { + v('y', 'MAX(SMTH3(input,delay),0)', { refId: '_y', referencedFunctionNames: ['__max'], references: ['__level1', '__level2', '__level3'], @@ -6981,33 +6994,63 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now - it.skip('should work for SMOOTH3 function (with subscripted input and subscripted delay)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay[DimA] = 2, 3 ~~| - y[DimA] = SMOOTH3(input[DimA], delay[DimA]) ~~| - `) + it('should work for SMTH3 function (without initial value argument; with subscripted input and subscripted delay)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1 ~~| + // delay[DimA] = 2, 3 ~~| + // y[DimA] = SMOOTH3(input[DimA], delay[DimA]) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + + 2 + + + 3 + + + + + + + SMTH3(input[DimA], delay[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '3+PULSE(10,10)', { + v('input[DimA]', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima'] + subscripts: ['_dima'], + varType: 'const' }), - v('delay[DimA]', '2,3', { + v('delay[A1]', '2', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '2,3', { + v('delay[A2]', '3', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('y[DimA]', 'SMOOTH3(input[DimA],delay[DimA])', { + v('y[DimA]', 'SMTH3(input[DimA],delay[DimA])', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3', @@ -7046,25 +7089,49 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3, skipping this test for now - it.skip('should work for SMOOTH3 function (with subscripted input and non-subscripted delay)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y[DimA] = SMOOTH3(input[DimA], delay) ~~| - `) + it('should work for SMTH3 function (without initial value argument; with subscripted input and non-subscripted delay)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1 ~~| + // delay = 2 ~~| + // y[DimA] = SMOOTH3(input[DimA], delay) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + 1 + + + 2 + + + + + + SMTH3(input[DimA], delay) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '3+PULSE(10,10)', { + v('input[DimA]', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima'] + subscripts: ['_dima'], + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y[DimA]', 'SMOOTH3(input[DimA],delay)', { + v('y[DimA]', 'SMTH3(input[DimA],delay)', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3', @@ -7103,23 +7170,36 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now - it.skip('should work for SMOOTH3I function', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTH3I(input, delay, 5) ~~| - `) + it('should work for SMTH3 function (with initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTH3I(input, delay, 5) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH3(input, delay, 5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input', '3+PULSE(10,10)', { + v('input', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'] + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y', 'SMOOTH3I(input,delay,5)', { + v('y', 'SMTH3(input,delay,5)', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3' @@ -7151,23 +7231,41 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now - it.skip('should work for SMOOTH3I function (with nested function calls)', () => { - const vars = readInlineModel(` - x = 1 ~~| - input = x + PULSE(10, 10) ~~| - delay = 3 ~~| - init = 0 ~~| - y = SMOOTH3I(MIN(0, input), MIN(0, delay), ABS(init)) ~~| - `) + it('should work for SMTH3 function (with initial value argument; with nested function calls)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // input = 1 ~~| + // delay = 3 ~~| + // init = 0 ~~| + // y = SMOOTH3I(MIN(0, input), MIN(0, delay), ABS(init)) ~~| + // `) + + const xmileVars = `\ + + 1 + + + x + + + 3 + + + 0 + + + SMTH3(MIN(0, input), MIN(0, delay), ABS(init)) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x', '1', { refId: '_x', varType: 'const' }), - v('input', 'x+PULSE(10,10)', { + v('input', 'x', { refId: '_input', - referencedFunctionNames: ['__pulse'], references: ['_x'] }), v('delay', '3', { @@ -7178,7 +7276,7 @@ ${elements.join('\n')} refId: '_init', varType: 'const' }), - v('y', 'SMOOTH3I(MIN(0,input),MIN(0,delay),ABS(init))', { + v('y', 'SMTH3(MIN(0,input),MIN(0,delay),ABS(init))', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3' @@ -7213,80 +7311,135 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now - it.skip('should work for SMOOTH3I function (with subscripted variables)', () => { + it('should work for SMTH3 function (with initial value argument; with subscripted variables)', () => { // Note that we have a mix of non-apply-to-all (input, delay) and apply-to-all (init) // variables here to cover both cases - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - input[DimA] = x[DimA] + PULSE(10, 10) ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 4, 5, 6 ~~| - y[DimA] = SMOOTH3I(input[DimA], delay[DimA], init[DimA]) ~~| - `) + + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // input[DimA] = x[DimA] ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 4, 5, 6 ~~| + // y[DimA] = SMOOTH3I(input[DimA], delay[DimA], init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + x[DimA] + + + + + + + 1 + + + 2 + + + 3 + + + + + + + + 4 + + + 5 + + + 6 + + + + + + + SMTH3(input[DimA], delay[DimA], init[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('x[DimA]', '1,2,3', { + v('x[A1]', '1', { refId: '_x[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A2]', '2', { refId: '_x[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('x[DimA]', '1,2,3', { + v('x[A3]', '3', { refId: '_x[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('input[DimA]', 'x[DimA]+PULSE(10,10)', { + v('input[DimA]', 'x[DimA]', { refId: '_input', - referencedFunctionNames: ['__pulse'], references: ['_x[_a1]', '_x[_a2]', '_x[_a3]'], subscripts: ['_dima'] }), - v('delay[DimA]', '1,2,3', { + v('delay[A1]', '1', { refId: '_delay[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A2]', '2', { refId: '_delay[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('delay[DimA]', '1,2,3', { + v('delay[A3]', '3', { refId: '_delay[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('init[DimA]', '4,5,6', { + v('init[A1]', '4', { refId: '_init[_a1]', - separationDims: ['_dima'], subscripts: ['_a1'], varType: 'const' }), - v('init[DimA]', '4,5,6', { + v('init[A2]', '5', { refId: '_init[_a2]', - separationDims: ['_dima'], subscripts: ['_a2'], varType: 'const' }), - v('init[DimA]', '4,5,6', { + v('init[A3]', '6', { refId: '_init[_a3]', - separationDims: ['_dima'], subscripts: ['_a3'], varType: 'const' }), - v('y[DimA]', 'SMOOTH3I(input[DimA],delay[DimA],init[DimA])', { + v('y[DimA]', 'SMTH3(input[DimA],delay[DimA],init[DimA])', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3', @@ -7325,25 +7478,49 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now - it.skip('should work for SMOOTH3I function (with subscripted input and non-subscripted delay)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y[DimA] = SMOOTH3I(input[DimA], delay, 5) ~~| - `) + it('should work for SMTH3 function (with initial value argument; with subscripted input and non-subscripted delay)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1 ~~| + // delay = 2 ~~| + // y[DimA] = SMOOTH3I(input[DimA], delay, 5) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + 1 + + + 2 + + + + + + SMTH3(input[DimA], delay, 5) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('input[DimA]', '3+PULSE(10,10)', { + v('input[DimA]', '1', { refId: '_input', - referencedFunctionNames: ['__pulse'], - subscripts: ['_dima'] + subscripts: ['_dima'], + varType: 'const' }), v('delay', '2', { refId: '_delay', varType: 'const' }), - v('y[DimA]', 'SMOOTH3I(input[DimA],delay,5)', { + v('y[DimA]', 'SMTH3(input[DimA],delay,5)', { refId: '_y', references: ['__level1', '__level2', '__level3'], smoothVarRefId: '__level3', @@ -7379,18 +7556,88 @@ ${elements.join('\n')} ]) }) - // TODO: Stella calls this function SMTH3 instead of SMOOTH3I, skipping this test for now - it.skip('should work for SMOOTH3I function (with separated variables using subdimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - input[DimA] = x[DimA] + PULSE(10, 10) ~~| - delay[DimA] = 1, 2, 3 ~~| - init[DimA] = 0 ~~| - y[A1] = 5 ~~| - y[SubA] = SMOOTH3I(input[SubA], delay[SubA], init[SubA]) ~~| - `) + // TODO: This test is not exactly equivalent to the Vensim one since it uses separated definitions + // for y[A1] and y[A2] instead of a single definition for y[SubA] + it.skip('should work for SMTH3 function (with initial value argument and separated variables using subdimension)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // SubA: A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // input[DimA] = x[DimA] ~~| + // delay[DimA] = 1, 2, 3 ~~| + // init[DimA] = 0 ~~| + // y[A1] = 5 ~~| + // y[SubA] = SMOOTH3I(input[SubA], delay[SubA], init[SubA]) ~~| + // `) + + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + + + + x[DimA] + + + + + + + 1 + + + 2 + + + 3 + + + + + + + 0 + + + + + + + 5 + + + SMTH3(input[A2], delay[A2], init[A2]) + + + SMTH3(input[A3], delay[A3], init[A3]) + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars).toEqual([ v('x[DimA]', '1,2,3', { refId: '_x[_a1]', @@ -7444,14 +7691,14 @@ ${elements.join('\n')} subscripts: ['_a1'], varType: 'const' }), - v('y[SubA]', 'SMOOTH3I(input[SubA],delay[SubA],init[SubA])', { + v('y[SubA]', 'SMTH3(input[SubA],delay[SubA],init[SubA])', { refId: '_y[_a2]', references: ['__level_y_1[_a2]', '__level_y_2[_a2]', '__level_y_3[_a2]'], separationDims: ['_suba'], smoothVarRefId: '__level_y_3[_a2]', subscripts: ['_a2'] }), - v('y[SubA]', 'SMOOTH3I(input[SubA],delay[SubA],init[SubA])', { + v('y[SubA]', 'SMTH3(input[SubA],delay[SubA],init[SubA])', { refId: '_y[_a3]', references: ['__level_y_1[_a3]', '__level_y_2[_a3]', '__level_y_3[_a3]'], separationDims: ['_suba'], From c3ba489d7a8e1a04e7636a990a0e7ccdaff2fd47 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 2 Sep 2025 09:20:24 -0700 Subject: [PATCH 47/77] test: skip more read-subscripts-xmile tests for now --- .../src/model/read-subscripts-xmile.spec.ts | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/packages/compile/src/model/read-subscripts-xmile.spec.ts b/packages/compile/src/model/read-subscripts-xmile.spec.ts index a82528db..7b663b59 100644 --- a/packages/compile/src/model/read-subscripts-xmile.spec.ts +++ b/packages/compile/src/model/read-subscripts-xmile.spec.ts @@ -74,7 +74,7 @@ function readAndResolveSubscripts(modelName: string): any[] { } describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () => { - it.only('should work for a subscript range with explicit subscripts', () => { + it('should work for a subscript range with explicit subscripts', () => { const xmileDims = `\ @@ -95,7 +95,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range with a single numeric range', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range with a single numeric range', () => { const range = `DimA: (A1-A3) ~~|` const rawSubs = readInlineSubscripts(range) @@ -110,7 +111,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range with multiple numeric ranges', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range with multiple numeric ranges', () => { const ranges = `DimA: (A1-A3),A5,(A7-A8) ~~|` const rawSubs = readInlineSubscripts(ranges) @@ -128,7 +130,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range with one mapping (to dimension with explicit individual subscripts)', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range with one mapping (to dimension with explicit individual subscripts)', () => { const ranges = ` DimA: A1, A2, A3 -> DimB ~~| DimB: B1, B2, B3 ~~| @@ -158,7 +161,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range with one mapping (to dimension with explicit mix of dimensions and subscripts)', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range with one mapping (to dimension with explicit mix of dimensions and subscripts)', () => { const ranges = ` DimA: A1, A2, A3 ~~| SubA: A1, A2 ~~| @@ -190,7 +194,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range with one mapping (to dimension without explicit subscripts)', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range with one mapping (to dimension without explicit subscripts)', () => { const ranges = ` DimA: SubA, A3 -> DimB ~~| SubA: A1, A2 ~~| @@ -223,7 +228,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range with two mappings', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range with two mappings', () => { const ranges = ` DimA: A1, A2, A3 -> (DimB: B3, B2, B1), DimC ~~| DimB: B1, B2, B3 ~~| @@ -262,7 +268,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for a subscript range alias (<-> operator)', () => { + // TODO: This test is skipped until we update it to use the equivalent XMILE syntax + it.skip('should work for a subscript range alias (<-> operator)', () => { const ranges = ` DimA <-> DimB ~~| DimB: B1, B2, B3 ~~| @@ -319,7 +326,7 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it.only('should work for XMILE "arrays" model', () => { + it('should work for XMILE "arrays" model', () => { const rawSubs = readSubscripts('arrays') expect(rawSubs).toEqual([ // NOTE: XMILE does not have the concept of aliases/mappings, so the stmx file @@ -383,7 +390,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "directconst" model', () => { + // TODO: This test is skipped because XMILE/Stella don't appear to have an equivalent function + it.skip('should work for XMILE "directconst" model', () => { const rawSubs = readSubscripts('directconst') expect(rawSubs).toEqual([ dim('DimA', ['A1', 'A2', 'A3']), @@ -422,7 +430,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "directdata" model', () => { + // TODO: This test is skipped because XMILE/Stella don't appear to have an equivalent function + it.skip('should work for XMILE "directdata" model', () => { const rawSubs = readSubscripts('directdata') expect(rawSubs).toEqual([ dim('DimA', ['A1', 'A2']), @@ -453,7 +462,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "directsubs" model', () => { + // TODO: This test is skipped because XMILE/Stella don't appear to have an equivalent function + it.skip('should work for XMILE "directsubs" model', () => { const rawSubs = readSubscripts('directsubs') expect(rawSubs).toEqual([ dim('DimA', ['A1', 'A2', 'A3'], undefined, undefined, [dimMapping('DimB'), dimMapping('DimC')], { @@ -486,7 +496,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "mapping" model', () => { + // TODO: This test is skipped because we don't yet have an equivalent XMILE model + it.skip('should work for XMILE "mapping" model', () => { const rawSubs = readSubscripts('mapping') expect(rawSubs).toEqual([ dim('DimA', ['A1', 'A2', 'A3']), @@ -532,7 +543,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "multimap" model', () => { + // TODO: This test is skipped because we don't yet have an equivalent XMILE model + it.skip('should work for XMILE "multimap" model', () => { const rawSubs = readSubscripts('multimap') expect(rawSubs).toEqual([ dim( @@ -579,7 +591,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "ref" model', () => { + // TODO: This test is skipped because we don't yet have an equivalent XMILE model + it.skip('should work for XMILE "ref" model', () => { const rawSubs = readSubscripts('ref') expect(rawSubs).toEqual([ dim('Target', ['t1', 't2', 't3']), @@ -610,7 +623,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "subalias" model', () => { + // TODO: This test is skipped because we don't yet have an equivalent XMILE model + it.skip('should work for XMILE "subalias" model', () => { const rawSubs = readSubscripts('subalias') expect(rawSubs).toEqual([ // After resolve phase, DimE will be expanded to individual subscripts, @@ -630,7 +644,8 @@ describe('readSubscriptRanges + resolveSubscriptRanges (from XMILE model)', () = ]) }) - it('should work for XMILE "subscript" model', () => { + // TODO: This test is skipped because we don't yet have an equivalent XMILE model + it.skip('should work for XMILE "subscript" model', () => { const rawSubs = readSubscripts('subscript') expect(rawSubs).toEqual([ dim('DimA', ['A1', 'A2', 'A3']), From 51d2b444d8f7ac664561260748aad001451157b7 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Thu, 4 Sep 2025 20:38:04 -0700 Subject: [PATCH 48/77] test: append "-from-vensim" to existing code gen test file names --- .../{gen-code-c.spec.ts => gen-code-c-from-vensim.spec.ts} | 0 .../{gen-code-js.spec.ts => gen-code-js-from-vensim.spec.ts} | 0 ...{gen-equation-c.spec.ts => gen-equation-c-from-vensim.spec.ts} | 0 ...en-equation-js.spec.ts => gen-equation-js-from-vensim.spec.ts} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename packages/compile/src/generate/{gen-code-c.spec.ts => gen-code-c-from-vensim.spec.ts} (100%) rename packages/compile/src/generate/{gen-code-js.spec.ts => gen-code-js-from-vensim.spec.ts} (100%) rename packages/compile/src/generate/{gen-equation-c.spec.ts => gen-equation-c-from-vensim.spec.ts} (100%) rename packages/compile/src/generate/{gen-equation-js.spec.ts => gen-equation-js-from-vensim.spec.ts} (100%) diff --git a/packages/compile/src/generate/gen-code-c.spec.ts b/packages/compile/src/generate/gen-code-c-from-vensim.spec.ts similarity index 100% rename from packages/compile/src/generate/gen-code-c.spec.ts rename to packages/compile/src/generate/gen-code-c-from-vensim.spec.ts diff --git a/packages/compile/src/generate/gen-code-js.spec.ts b/packages/compile/src/generate/gen-code-js-from-vensim.spec.ts similarity index 100% rename from packages/compile/src/generate/gen-code-js.spec.ts rename to packages/compile/src/generate/gen-code-js-from-vensim.spec.ts diff --git a/packages/compile/src/generate/gen-equation-c.spec.ts b/packages/compile/src/generate/gen-equation-c-from-vensim.spec.ts similarity index 100% rename from packages/compile/src/generate/gen-equation-c.spec.ts rename to packages/compile/src/generate/gen-equation-c-from-vensim.spec.ts diff --git a/packages/compile/src/generate/gen-equation-js.spec.ts b/packages/compile/src/generate/gen-equation-js-from-vensim.spec.ts similarity index 100% rename from packages/compile/src/generate/gen-equation-js.spec.ts rename to packages/compile/src/generate/gen-equation-js-from-vensim.spec.ts From 2cb691a38813b7f38c251b34af5b65525a1fafc1 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Thu, 4 Sep 2025 20:40:47 -0700 Subject: [PATCH 49/77] test: copy gen-equation-js-from-vensim in preparation for adding "from XMILE" tests --- .../gen-equation-js-from-xmile.spec.ts | 3312 +++++++++++++++++ 1 file changed, 3312 insertions(+) create mode 100644 packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts new file mode 100644 index 00000000..c98a4092 --- /dev/null +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -0,0 +1,3312 @@ +import path from 'node:path' + +import { describe, expect, it } from 'vitest' + +import { readXlsx, resetHelperState } from '../_shared/helpers' +import { resetSubscriptsAndDimensions } from '../_shared/subscript' + +import Model from '../model/model' +// import { default as VariableImpl } from '../model/variable' + +import { parseInlineXmileModel, sampleModelDir, type Variable } from '../_tests/test-support' +import { generateEquation } from './gen-equation' + +type ExtData = Map> +type DirectDataSpec = Map + +function readInlineModel( + mdlContent: string, + opts?: { + modelDir?: string + extData?: ExtData + inputVarNames?: string[] + outputVarNames?: string[] + } +): Map { + // XXX: These steps are needed due to subs/dims and variables being in module-level storage + resetHelperState() + resetSubscriptsAndDimensions() + Model.resetModelState() + + let spec + if (opts?.inputVarNames || opts?.outputVarNames) { + spec = { + inputVarNames: opts?.inputVarNames || [], + outputVarNames: opts?.outputVarNames || [] + } + } else { + spec = {} + } + + const parsedModel = parseInlineXmileModel(mdlContent, opts?.modelDir) + Model.read(parsedModel, spec, opts?.extData, /*directData=*/ undefined, opts?.modelDir, { + reduceVariables: false + }) + + // Get all variables (note that `allVars` already excludes the `Time` variable, and we want to + // exclude that so that we have one less thing to check) + const map = new Map() + Model.allVars().forEach((v: Variable) => { + map.set(v.refId, v) + }) + return map +} + +function genJS( + variable: Variable, + mode: 'decl' | 'init-constants' | 'init-lookups' | 'init-levels' | 'eval' = 'eval', + opts?: { + modelDir?: string + extData?: ExtData + directDataSpec?: DirectDataSpec + } +): string[] { + if (variable === undefined) { + throw new Error(`variable is undefined`) + } + + const directData = new Map() + if (opts?.modelDir && opts?.directDataSpec) { + for (const [file, xlsxFilename] of opts.directDataSpec.entries()) { + const xlsxPath = path.join(opts.modelDir, xlsxFilename) + directData.set(file, readXlsx(xlsxPath)) + } + } + + const lines = generateEquation(variable, mode, opts?.extData, directData, opts?.modelDir, 'js') + + // Strip the first comment line (containing the XMILE equation) + if (lines.length > 0 && lines[0].trim().startsWith('//')) { + lines.shift() + } + + // Trim the remaining lines to remove extra whitespace + return lines.map(line => line.trim()) +} + +describe('generateEquation (XMILE -> JS)', () => { + it('should work for simple equation with unary :NOT: op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = :NOT: x ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = !_x;']) + }) + + it('should work for simple equation with unary + op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = +x ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x;']) + }) + + it('should work for simple equation with unary - op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = -x ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = -_x;']) + }) + + it('should work for simple equation with binary + op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x + 2 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x + 2.0;']) + }) + + it('should work for simple equation with binary - op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x - 2 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x - 2.0;']) + }) + + it('should work for simple equation with binary * op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x * 2 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x * 2.0;']) + }) + + it('should work for simple equation with binary / op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x / 2 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x / 2.0;']) + }) + + it('should work for simple equation with binary ^ op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x ^ 2 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.POW(_x, 2.0);']) + }) + + it('should work for simple equation with explicit parentheses', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = (x + 2) * 3 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = (_x + 2.0) * 3.0;']) + }) + + it('should work for conditional expression with = op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x = time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x === _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with <> op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x <> time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x !== _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with < op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x < time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x < _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with <= op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x <= time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x <= _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with > op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x > time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x > _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with >= op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x >= time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x >= _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with :AND: op', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = IF THEN ELSE(x :AND: time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x && _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with :OR: op', () => { + // Note that we use `ABS(1)` here to circumvent the constant conditional optimization + // code (the legacy `ExprReader` doesn't currently optimize function calls). This + // allows us to verify the generated code without the risk of it being optimized away. + const vars = readInlineModel(` + x = ABS(1) ~~| + y = IF THEN ELSE(x :OR: time, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = fns.ABS(1.0);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x || _time) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with :NOT: op', () => { + // Note that we use `ABS(1)` here to circumvent the constant conditional optimization + // code (the legacy `ExprReader` doesn't currently optimize function calls). This + // allows us to verify the generated code without the risk of it being optimized away. + const vars = readInlineModel(` + x = ABS(1) ~~| + y = IF THEN ELSE(:NOT: x, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = fns.ABS(1.0);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((!_x) ? (1.0) : (0.0));']) + }) + + it('should work for expression using :NA: keyword', () => { + const vars = readInlineModel(` + x = Time ~~| + y = IF THEN ELSE(x <> :NA:, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = _time;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x !== _NA_) ? (1.0) : (0.0));']) + }) + + it('should work for conditional expression with reference to dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x = 1 ~~| + y[DimA] = IF THEN ELSE(DimA = x, 1, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_y[i] = (((i + 1) === _x) ? (1.0) : (0.0));', + '}' + ]) + }) + + it('should work for conditional expression with reference to dimension and subscript/index', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + y[DimA] = IF THEN ELSE(DimA = A2, 1, 0) ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_y[i] = (((i + 1) === 2) ? (1.0) : (0.0));', + '}' + ]) + }) + + it('should work for data variable definition', () => { + const extData: ExtData = new Map([ + [ + '_x', + new Map([ + [0, 0], + [1, 2], + [2, 5] + ]) + ] + ]) + const vars = readInlineModel( + ` + x ~~| + y = x * 10 ~~| + `, + { extData } + ) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl', { extData })).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 2.0, 2.0, 5.0];']) + expect(genJS(vars.get('_x'), 'init-lookups', { extData })).toEqual(['_x = fns.createLookup(3, _x_data_);']) + expect(genJS(vars.get('_y'), 'eval', { extData })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + it('should work for data variable definition (1D)', () => { + const extData: ExtData = new Map([ + [ + '_x[_a1]', + new Map([ + [0, 0], + [1, 2], + [2, 5] + ]) + ], + [ + '_x[_a2]', + new Map([ + [0, 10], + [1, 12], + [2, 15] + ]) + ] + ]) + const vars = readInlineModel( + ` + DimA: A1, A2 ~~| + x[DimA] ~~| + y[DimA] = x[DimA] * 10 ~~| + z = y[A2] ~~| + `, + { + extData + } + ) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x'), 'decl', { extData })).toEqual([ + 'const _x_data__0_ = [0.0, 0.0, 1.0, 2.0, 2.0, 5.0];', + 'const _x_data__1_ = [0.0, 10.0, 1.0, 12.0, 2.0, 15.0];' + ]) + expect(genJS(vars.get('_x'), 'init-lookups', { extData })).toEqual([ + '_x[0] = fns.createLookup(3, _x_data__0_);', + '_x[1] = fns.createLookup(3, _x_data__1_);' + ]) + expect(genJS(vars.get('_y'), 'eval', { extData })).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_y[i] = fns.LOOKUP(_x[i], _time) * 10.0;', + '}' + ]) + expect(genJS(vars.get('_z'), 'eval', { extData })).toEqual(['_z = _y[1];']) + }) + + it('should work for lookup definition', () => { + const vars = readInlineModel(` + x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'decl')).toEqual([ + 'const _x_data_ = [0.0, 0.0, 0.1, 0.01, 0.5, 0.7, 1.0, 1.0, 1.5, 1.2, 2.0, 1.3];' + ]) + expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(6, _x_data_);']) + }) + + it('should work for lookup definition (one dimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[A1]( (0,10), (1,20) ) ~~| + x[A2]( (0,30), (1,40) ) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x[_a1]'), 'decl')).toEqual(['const _x_data__0_ = [0.0, 10.0, 1.0, 20.0];']) + expect(genJS(vars.get('_x[_a2]'), 'decl')).toEqual(['const _x_data__1_ = [0.0, 30.0, 1.0, 40.0];']) + expect(genJS(vars.get('_x[_a1]'), 'init-lookups')).toEqual(['_x[0] = fns.createLookup(2, _x_data__0_);']) + expect(genJS(vars.get('_x[_a2]'), 'init-lookups')).toEqual(['_x[1] = fns.createLookup(2, _x_data__1_);']) + }) + + it('should work for lookup definition (two dimensions)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1,B1]( (0,10), (1,20) ) ~~| + x[A1,B2]( (0,30), (1,40) ) ~~| + x[A2,B1]( (0,50), (1,60) ) ~~| + x[A2,B2]( (0,70), (1,80) ) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1,_b1]'), 'decl')).toEqual(['const _x_data__0__0_ = [0.0, 10.0, 1.0, 20.0];']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'decl')).toEqual(['const _x_data__0__1_ = [0.0, 30.0, 1.0, 40.0];']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'decl')).toEqual(['const _x_data__1__0_ = [0.0, 50.0, 1.0, 60.0];']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'decl')).toEqual(['const _x_data__1__1_ = [0.0, 70.0, 1.0, 80.0];']) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-lookups')).toEqual(['_x[0][0] = fns.createLookup(2, _x_data__0__0_);']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-lookups')).toEqual(['_x[0][1] = fns.createLookup(2, _x_data__0__1_);']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'init-lookups')).toEqual(['_x[1][0] = fns.createLookup(2, _x_data__1__0_);']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'init-lookups')).toEqual(['_x[1][1] = fns.createLookup(2, _x_data__1__1_);']) + }) + + it('should work for lookup call', () => { + const vars = readInlineModel(` + x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| + y = x(2) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl')).toEqual([ + 'const _x_data_ = [0.0, 0.0, 0.1, 0.01, 0.5, 0.7, 1.0, 1.0, 1.5, 1.2, 2.0, 1.3];' + ]) + expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(6, _x_data_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP(_x, 2.0);']) + }) + + it('should work for lookup call (with one dimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[A1]( [(0,0)-(2,2)], (0,0),(2,1.3) ) ~~| + x[A2]( [(0,0)-(2,2)], (0,0.5),(2,1.5) ) ~~| + y = x[A1](2) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]'), 'decl')).toEqual(['const _x_data__0_ = [0.0, 0.0, 2.0, 1.3];']) + expect(genJS(vars.get('_x[_a2]'), 'decl')).toEqual(['const _x_data__1_ = [0.0, 0.5, 2.0, 1.5];']) + expect(genJS(vars.get('_x[_a1]'), 'init-lookups')).toEqual(['_x[0] = fns.createLookup(2, _x_data__0_);']) + expect(genJS(vars.get('_x[_a2]'), 'init-lookups')).toEqual(['_x[1] = fns.createLookup(2, _x_data__1_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP(_x[0], 2.0);']) + }) + + it('should work for constant definition (with one dimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y = x[A2] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[1];']) + }) + + it('should work for constant definition (with two dimensions + except + subdimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimC: C1, C2 ~~| + x[DimC, SubA] = 1 ~~| + x[DimC, DimA] :EXCEPT: [DimC, SubA] = 2 ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_dimc,_a1]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[i][0] = 2.0;', + '}' + ]) + expect(genJS(vars.get('_x[_dimc,_a2]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[i][1] = 1.0;', + '}' + ]) + expect(genJS(vars.get('_x[_dimc,_a3]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[i][2] = 1.0;', + '}' + ]) + }) + + it('should work for constant definition (with separate subscripts)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[A1] = 1 ~~| + x[A2] = 2 ~~| + x[A3] = 3 ~~| + y = x[A2] ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[1];']) + }) + + it('should work for const list definition (1D)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y = x[A2] ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[1];']) + }) + + it('should work for const list definition (2D, dimensions in normal/alphabetized order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2, B3 ~~| + x[DimA, DimB] = 1, 2, 3; 4, 5, 6; ~~| + y = x[A2, B3] ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_a1,_b3]'), 'init-constants')).toEqual(['_x[0][2] = 3.0;']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 4.0;']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 5.0;']) + expect(genJS(vars.get('_x[_a2,_b3]'), 'init-constants')).toEqual(['_x[1][2] = 6.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[1][2];']) + }) + + it('should work for const list definition (2D, dimensions not in normal/alphabetized order)', () => { + const vars = readInlineModel(` + DimB: B1, B2, B3 ~~| + DimA: A1, A2 ~~| + x[DimB, DimA] = 1, 2; 3, 4; 5, 6; ~~| + y = x[B3, A2] ~~| + z = x[B2, A1] ~~| + `) + expect(vars.size).toBe(8) + expect(genJS(vars.get('_x[_b1,_a1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_b1,_a2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_b2,_a1]'), 'init-constants')).toEqual(['_x[1][0] = 3.0;']) + expect(genJS(vars.get('_x[_b2,_a2]'), 'init-constants')).toEqual(['_x[1][1] = 4.0;']) + expect(genJS(vars.get('_x[_b3,_a1]'), 'init-constants')).toEqual(['_x[2][0] = 5.0;']) + expect(genJS(vars.get('_x[_b3,_a2]'), 'init-constants')).toEqual(['_x[2][1] = 6.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[2][1];']) + expect(genJS(vars.get('_z'))).toEqual(['_z = _x[1][0];']) + }) + + it('should work for const list definition (2D separated, dimensions in normal/alphabetized order)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + x[A1, DimB] = 1,2 ~~| + x[A2, DimB] = 3,4 ~~| + x[A3, DimB] = 5,6 ~~| + y = x[A3, B2] ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 3.0;']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 4.0;']) + expect(genJS(vars.get('_x[_a3,_b1]'), 'init-constants')).toEqual(['_x[2][0] = 5.0;']) + expect(genJS(vars.get('_x[_a3,_b2]'), 'init-constants')).toEqual(['_x[2][1] = 6.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[2][1];']) + }) + + it('should work for const list definition (2D separated, dimensions not in normal/alphabetized order)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + x[B1, DimA] = 1,2,3 ~~| + x[B2, DimA] = 4,5,6 ~~| + y = x[B2, A3] ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_x[_b1,_a1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_b1,_a2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_b1,_a3]'), 'init-constants')).toEqual(['_x[0][2] = 3.0;']) + expect(genJS(vars.get('_x[_b2,_a1]'), 'init-constants')).toEqual(['_x[1][0] = 4.0;']) + expect(genJS(vars.get('_x[_b2,_a2]'), 'init-constants')).toEqual(['_x[1][1] = 5.0;']) + expect(genJS(vars.get('_x[_b2,_a3]'), 'init-constants')).toEqual(['_x[1][2] = 6.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[1][2];']) + }) + + it('should work for equation with one dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y[DimA] = (x[DimA] + 2) * MIN(0, x[DimA]) ~~| + z = y[A2] ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_y[i] = (_x[i] + 2.0) * fns.MIN(0.0, _x[i]);', + '}' + ]) + expect(genJS(vars.get('_z'))).toEqual(['_z = _y[1];']) + }) + + it('should work for equation with two dimensions', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1, 2; 3, 4; ~~| + y[DimA, DimB] = (x[DimA, DimB] + 2) * MIN(0, x[DimA, DimB]) ~~| + z = y[A2, B1] ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 3.0;']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 4.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = (_x[i][j] + 2.0) * fns.MIN(0.0, _x[i][j]);', + '}', + '}' + ]) + expect(genJS(vars.get('_z'))).toEqual(['_z = _y[1][0];']) + }) + + it('should work for 1D equation with one mapped dimension name used in expression position', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 -> DimA ~~| + x[DimA] = DimB ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'))).toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = (__map_dimb_dima[i] + 1);', '}']) + }) + + it('should work for 1D equation with one mapped dimension name used in subscript position (separated/non-apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimD: D1, D2 -> (DimA: SubA, A1) ~~| + a[DimA] = 1 ~~| + j[DimD] = 10, 20 ~~| + k[DimA] :EXCEPT: [A1] = a[DimA] + j[DimD] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_a'), 'init-constants')).toEqual(['for (let i = 0; i < 3; i++) {', '_a[i] = 1.0;', '}']) + expect(genJS(vars.get('_j[_d1]'), 'init-constants')).toEqual(['_j[0] = 10.0;']) + expect(genJS(vars.get('_j[_d2]'), 'init-constants')).toEqual(['_j[1] = 20.0;']) + expect(genJS(vars.get('_k[_a2]'))).toEqual(['_k[1] = _a[1] + _j[0];']) + expect(genJS(vars.get('_k[_a3]'))).toEqual(['_k[2] = _a[2] + _j[0];']) + }) + + it('should work for 1D equation with one dimension used in expression position (apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + Selected A Index = 1 ~~| + x[DimA] = IF THEN ELSE ( DimA = Selected A Index, 1, 0 ) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_selected_a_index'), 'init-constants')).toEqual(['_selected_a_index = 1.0;']) + expect(genJS(vars.get('_x'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[i] = (((i + 1) === _selected_a_index) ? (1.0) : (0.0));', + '}' + ]) + }) + + it('should work for 1D equation with one subdimension used in expression position (separated/non-apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A3 ~~| + Selected A Index = 1 ~~| + x[SubA] = IF THEN ELSE ( SubA = Selected A Index, 1, 0 ) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_selected_a_index'), 'init-constants')).toEqual(['_selected_a_index = 1.0;']) + expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = (((0 + 1) === _selected_a_index) ? (1.0) : (0.0));']) + expect(genJS(vars.get('_x[_a3]'))).toEqual(['_x[2] = (((2 + 1) === _selected_a_index) ? (1.0) : (0.0));']) + }) + + it('should work for 2D equation with two distinct dimensions used in expression position (apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = (DimA * 10) + DimB ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = ((i + 1) * 10.0) + (j + 1);', + '}', + '}' + ]) + }) + + it('should work for 2D equation with two distinct dimensions used in expression position (separated/non-apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1, B1] = 0 ~~| + x[DimA, DimB] :EXCEPT: [A1, B1] = (DimA * 10) + DimB ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1,_b1]'))).toEqual(['_x[0][0] = 0.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'))).toEqual(['_x[0][1] = ((0 + 1) * 10.0) + (1 + 1);']) + expect(genJS(vars.get('_x[_a2,_b1]'))).toEqual(['_x[1][0] = ((1 + 1) * 10.0) + (0 + 1);']) + expect(genJS(vars.get('_x[_a2,_b2]'))).toEqual(['_x[1][1] = ((1 + 1) * 10.0) + (1 + 1);']) + }) + + it('should work for 2D equation with two dimensions that resolve to the same family used in expression position (apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB <-> DimA ~~| + x[DimA, DimB] = (DimA * 10) + DimB ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = ((i + 1) * 10.0) + (j + 1);', + '}', + '}' + ]) + }) + + it('should work for 2D equation with two dimensions that resolve to the same family used in expression position (separated/non-apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB <-> DimA ~~| + x[A1, A1] = 0 ~~| + x[DimA, DimB] :EXCEPT: [A1, A1] = (DimA * 10) + DimB ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1,_a1]'))).toEqual(['_x[0][0] = 0.0;']) + expect(genJS(vars.get('_x[_a1,_a2]'))).toEqual(['_x[0][1] = ((0 + 1) * 10.0) + (1 + 1);']) + expect(genJS(vars.get('_x[_a2,_a1]'))).toEqual(['_x[1][0] = ((1 + 1) * 10.0) + (0 + 1);']) + expect(genJS(vars.get('_x[_a2,_a2]'))).toEqual(['_x[1][1] = ((1 + 1) * 10.0) + (1 + 1);']) + }) + + it('should work for 2D equation with two dimensions (including one subdimension) that resolve to the same family used in expression position (separated/non-apply-to-all)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A3 ~~| + DimB <-> DimA ~~| + x[A1, A1] = 0 ~~| + x[SubA, DimB] :EXCEPT: [A1, A1] = (SubA * 10) + DimB ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_x[_a1,_a1]'))).toEqual(['_x[0][0] = 0.0;']) + expect(genJS(vars.get('_x[_a1,_a2]'))).toEqual(['_x[0][1] = ((0 + 1) * 10.0) + (1 + 1);']) + expect(genJS(vars.get('_x[_a1,_a3]'))).toEqual(['_x[0][2] = ((0 + 1) * 10.0) + (2 + 1);']) + expect(genJS(vars.get('_x[_a3,_a1]'))).toEqual(['_x[2][0] = ((2 + 1) * 10.0) + (0 + 1);']) + expect(genJS(vars.get('_x[_a3,_a2]'))).toEqual(['_x[2][1] = ((2 + 1) * 10.0) + (1 + 1);']) + expect(genJS(vars.get('_x[_a3,_a3]'))).toEqual(['_x[2][2] = ((2 + 1) * 10.0) + (2 + 1);']) + }) + + it('should work for 2D equation with mapped dimensions (separated/non-apply-to-all)', () => { + // This is taken from the `smooth` sample model. This test exercises the case where all dimensions + // resolve to the same family (DimA), and the variables are partially separated (the first dimension + // SubA is separated, but the second dimension DimB uses a loop). + const vars = readInlineModel(` + DimA: A1, A2, A3 -> DimB ~~| + SubA: A2, A3 -> SubB ~~| + DimB: B1, B2, B3 ~~| + SubB: B2, B3 ~~| + x[SubA,DimB] = 3 + PULSE(10, 10) ~~| + y[SubA,DimB] = x[SubA,DimB] ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a2,_dimb]'))).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_x[1][i] = 3.0 + fns.PULSE(10.0, 10.0);', + '}' + ]) + expect(genJS(vars.get('_x[_a3,_dimb]'))).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_x[2][i] = 3.0 + fns.PULSE(10.0, 10.0);', + '}' + ]) + expect(genJS(vars.get('_y[_a2,_dimb]'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[1][i] = _x[1][i];', '}']) + expect(genJS(vars.get('_y[_a3,_dimb]'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[2][i] = _x[2][i];', '}']) + }) + + it('should work for multiple equations that rely on subscript mappings', () => { + const vars = readInlineModel(` + DimA: A1, A2 -> DimB, DimC ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + a[DimA] = 10, 20 ~~| + b[DimB] = 1, 2 ~~| + c[DimC] = a[DimA] + 1 ~~| + d = b[B2] ~~| + e = c[C1] ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_b[_b1]'), 'eval')).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'eval')).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_c'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_c[i] = _a[__map_dima_dimc[i]] + 1.0;', + '}' + ]) + expect(genJS(vars.get('_d'), 'eval')).toEqual(['_d = _b[1];']) + expect(genJS(vars.get('_e'), 'eval')).toEqual(['_e = _c[0];']) + }) + + // + // NOTE: The following "should work for {0,1,2,3}D variable" tests are aligned with the ones + // from `read-equations.spec.ts` (they exercise the same test models/equations). Having both + // sets of tests makes it easier to see whether a bug is in the "read equations" phase or + // in the "code gen" phase or both. + // + + describe('when LHS has no subscripts', () => { + it('should work when RHS variable has no subscripts', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = x ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x;']) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1 ~~| + y = x[A1] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0];']) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y = x[A1] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0];']) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1 ~~| + y = SUM(x[DimA!]) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual([ + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[u];', + '}', + '_y = __t1;' + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y = SUM(x[DimA!]) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[u];', + '}', + '_y = __t1;' + ]) + }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1 ~~| + y = x[A1, B2] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1];']) + }) + + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1, 2; 3, 4; ~~| + y = x[A1, B2] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 3.0;']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 4.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1];']) + }) + + it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, DimC, DimB] = 1 ~~| + y = x[A1, C2, B2] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + 'for (let k = 0; k < 2; k++) {', + '_x[i][j][k] = 1.0;', + '}', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1][1];']) + }) + + it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, DimC, DimB] :EXCEPT: [DimA, DimC, B1] = 1 ~~| + x[DimA, DimC, B1] = 2 ~~| + y = x[A1, C2, B2] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_dima,_dimc,_b2]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j][1] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_x[_dima,_dimc,_b1]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j][0] = 2.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1][1];']) + }) + }) + + describe('when LHS is apply-to-all (1D)', () => { + it('should work when RHS variable has no subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x = 1 ~~| + y[DimA] = x ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x;', '}']) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] = x[A2] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[1];', '}']) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] = x[DimA] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[i];', '}']) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] = x[A2] ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]')), 'init-constants').toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[1];', '}']) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] = x[DimA] ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]')), 'init-constants').toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[i];', '}']) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) with separated definitions and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[A1] = 1 ~~| + x[A2] = 2 ~~| + y[DimA] = x[DimA] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = _x[i];', '}']) + }) + + // This is adapted from the "except" sample model (see equation for `k`) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimB: B1, B2 -> (DimA: SubA, A1) ~~| + a[DimA] = 1, 2, 3 ~~| + b[DimB] = 4, 5 ~~| + y[DimA] = a[DimA] + b[DimB] ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_a[_a1]'))).toEqual(['_a[0] = 1.0;']) + expect(genJS(vars.get('_a[_a2]'))).toEqual(['_a[1] = 2.0;']) + expect(genJS(vars.get('_a[_a3]'))).toEqual(['_a[2] = 3.0;']) + expect(genJS(vars.get('_b[_b1]'))).toEqual(['_b[0] = 4.0;']) + expect(genJS(vars.get('_b[_b2]'))).toEqual(['_b[1] = 5.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_y[i] = _a[i] + _b[__map_dimb_dima[i]];', + '}' + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA] = 1 ~~| + y[DimB] = SUM(x[DimA!]) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[u];', + '}', + '_y[i] = __t1;', + '}' + ]) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1 ~~| + y[DimA] = SUM(x[DimA!]) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[u];', + '}', + '_y[i] = __t1;', + '}' + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA] = 1, 2 ~~| + y[DimB] = SUM(x[DimA!]) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[u];', + '}', + '_y[i] = __t1;', + '}' + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y[DimA] = SUM(x[DimA!]) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[u];', + '}', + '_y[i] = __t1;', + '}' + ]) + }) + + // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with one normal dimension and one marked dimension that resolve to same family', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: DimA ~~| + x[DimA,DimB] = 1 ~~| + y[DimA] = SUM(x[DimA,DimA!]) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _x[i][u];', + '}', + '_y[i] = __t1;', + '}' + ]) + }) + + // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + }) + + describe('when LHS is NON-apply-to-all (1D)', () => { + it('should work when RHS variable has no subscripts', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x = 1 ~~| + y[DimA] :EXCEPT: [A1] = x ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x;']) + expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x;']) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] :EXCEPT: [A1] = x[A2] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) + expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[1];']) + }) + + it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1 ~~| + y[DimA] :EXCEPT: [A1] = x[DimA] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x'))).toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) + expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[2];']) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] :EXCEPT: [A1] = x[A2] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'))).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'))).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) + expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[1];']) + }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y[DimA] :EXCEPT: [A1] = x[DimA] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]')), 'init-constants').toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) + expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[2];']) + }) + + // This is adapted from the "except" sample model (see equation for `k`) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimB: B1, B2 -> (DimA: SubA, A1) ~~| + a[DimA] = 1, 2, 3 ~~| + b[DimB] = 4, 5 ~~| + y[DimA] :EXCEPT: [A1] = a[DimA] + b[DimB] ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_a[_a1]'))).toEqual(['_a[0] = 1.0;']) + expect(genJS(vars.get('_a[_a2]'))).toEqual(['_a[1] = 2.0;']) + expect(genJS(vars.get('_a[_a3]'))).toEqual(['_a[2] = 3.0;']) + expect(genJS(vars.get('_b[_b1]'))).toEqual(['_b[0] = 4.0;']) + expect(genJS(vars.get('_b[_b2]'))).toEqual(['_b[1] = 5.0;']) + expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _a[1] + _b[0];']) + expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _a[2] + _b[0];']) + }) + + // This is adapted from the "ref" sample model (with updated naming for clarity) + it('should work for complex mapping example', () => { + const vars = readInlineModel(` + Target: (t1-t3) ~~| + tNext: (t2-t3) -> tPrev ~~| + tPrev: (t1-t2) -> tNext ~~| + x[t1] = y[t1] + 1 ~~| + x[tNext] = y[tNext] + 1 ~~| + y[t1] = 1 ~~| + y[tNext] = x[tPrev] + 1 ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_y[_t1]'))).toEqual(['_y[0] = 1.0;']) + expect(genJS(vars.get('_x[_t1]'))).toEqual(['_x[0] = _y[0] + 1.0;']) + expect(genJS(vars.get('_y[_t2]'))).toEqual(['_y[1] = _x[0] + 1.0;']) + expect(genJS(vars.get('_x[_t2]'))).toEqual(['_x[1] = _y[1] + 1.0;']) + expect(genJS(vars.get('_y[_t3]'))).toEqual(['_y[2] = _x[1] + 1.0;']) + expect(genJS(vars.get('_x[_t3]'))).toEqual(['_x[2] = _y[2] + 1.0;']) + }) + }) + + describe('when LHS is apply-to-all (2D)', () => { + // it('should work when RHS variable has no subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { + // // TODO + // }) + + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB <-> DimA ~~| + x[DimA] = 1, 2 ~~| + y[DimA, DimB] = x[DimA] + x[DimB] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = _x[i] + _x[j];', + '}', + '}' + ]) + }) + + // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1 ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x')), 'init-constants').toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = _x[j][i];', + '}', + '}' + ]) + }) + + it('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB <-> DimA ~~| + x[DimA, DimB] = 1 ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = _x[j][i];', + '}', + '}' + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1, 2; 3, 4; ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x[_a1,_b1]'))).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'))).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_a2,_b1]'))).toEqual(['_x[1][0] = 3.0;']) + expect(genJS(vars.get('_x[_a2,_b2]'))).toEqual(['_x[1][1] = 4.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = _x[j][i];', + '}', + '}' + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1, DimB] = 1 ~~| + x[A2, DimB] = 2 ~~| + y[DimB, DimA] = x[DimA, DimB] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1,_dimb]')), 'init-constants').toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[0][i] = 1.0;', + '}' + ]) + expect(genJS(vars.get('_x[_a2,_dimb]')), 'init-constants').toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[1][i] = 2.0;', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = _x[j][i];', + '}', + '}' + ]) + }) + + // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + + // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { + // // TODO + // }) + }) + + describe('when LHS is NON-apply-to-all (2D)', () => { + // The LHS in this test is partially separated (expanded only for first dimension position) + it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[DimA, DimB] = 1 ~~| + y[SubA, DimB] = x[SubA, DimB] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x')), 'init-constants').toEqual([ + 'for (let i = 0; i < 3; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][j] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y[_a1,_dimb]'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[0][i] = _x[0][i];', '}']) + expect(genJS(vars.get('_y[_a2,_dimb]'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[1][i] = _x[1][i];', '}']) + }) + + // This test is based on the example from #179 (simplified to use subdimensions to ensure separation) + it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + SubB <-> SubA ~~| + x[SubA] = 1, 2 ~~| + y[SubA, SubB] = x[SubA] + x[SubB] ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y[_a1,_a1]'))).toEqual(['_y[0][0] = _x[0] + _x[0];']) + expect(genJS(vars.get('_y[_a1,_a2]'))).toEqual(['_y[0][1] = _x[0] + _x[1];']) + expect(genJS(vars.get('_y[_a2,_a1]'))).toEqual(['_y[1][0] = _x[1] + _x[0];']) + expect(genJS(vars.get('_y[_a2,_a2]'))).toEqual(['_y[1][1] = _x[1] + _x[1];']) + }) + + // This test is based on the example from #179 (simplified to use subdimensions to ensure separation). + // It is similar to the previous one, except in this one, `x` is apply-to-all (and refers to the parent + // dimension). + it('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A2 ~~| + SubB <-> SubA ~~| + x[DimA] = 1 ~~| + y[SubA, SubB] = x[SubA] + x[SubB] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x'))).toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) + expect(genJS(vars.get('_y[_a1,_a1]'))).toEqual(['_y[0][0] = _x[0] + _x[0];']) + expect(genJS(vars.get('_y[_a1,_a2]'))).toEqual(['_y[0][1] = _x[0] + _x[1];']) + expect(genJS(vars.get('_y[_a2,_a1]'))).toEqual(['_y[1][0] = _x[1] + _x[0];']) + expect(genJS(vars.get('_y[_a2,_a2]'))).toEqual(['_y[1][1] = _x[1] + _x[1];']) + }) + }) + + describe('when LHS is apply-to-all (3D)', () => { + it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, DimC, DimB] = 1 ~~| + y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + 'for (let k = 0; k < 2; k++) {', + '_x[i][j][k] = 1.0;', + '}', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + 'for (let k = 0; k < 2; k++) {', + '_y[i][j][k] = _x[k][i][j];', + '}', + '}', + '}' + ]) + }) + + it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2 ~~| + x[DimA, C1, DimB] = 1 ~~| + x[DimA, C2, DimB] = 2 ~~| + y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_dima,_c1,_dimb]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][0][j] = 1.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_x[_dima,_c2,_dimb]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_x[i][1][j] = 2.0;', + '}', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + 'for (let k = 0; k < 2; k++) {', + '_y[i][j][k] = _x[k][i][j];', + '}', + '}', + '}' + ]) + }) + }) + + describe('when LHS is NON-apply-to-all (3D)', () => { + it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + DimC: C1, C2, C3 ~~| + SubC: C2, C3 ~~| + x[DimA, DimC, DimB] = 1 ~~| + y[SubC, DimB, DimA] = x[DimA, SubC, DimB] ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 3; j++) {', + 'for (let k = 0; k < 2; k++) {', + '_x[i][j][k] = 1.0;', + '}', + '}', + '}' + ]) + expect(genJS(vars.get('_y[_c2,_dimb,_dima]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[1][i][j] = _x[j][1][i];', + '}', + '}' + ]) + expect(genJS(vars.get('_y[_c3,_dimb,_dima]'), 'init-constants')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[2][i][j] = _x[j][2][i];', + '}', + '}' + ]) + }) + + // This test is based on the example from #278 + it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { + const vars = readInlineModel(` + Scenario: S1, S2 ~~| + Sector: A1, A2, A3 ~~| + Supplying Sector: A1, A2 -> Producing Sector ~~| + Producing Sector: A1, A2 -> Supplying Sector ~~| + x[A1,A1] = 101 ~~| + x[A1,A2] = 102 ~~| + x[A1,A3] = 103 ~~| + x[A2,A1] = 201 ~~| + x[A2,A2] = 202 ~~| + x[A2,A3] = 203 ~~| + x[A3,A1] = 301 ~~| + x[A3,A2] = 302 ~~| + x[A3,A3] = 303 ~~| + y[S1] = 1000 ~~| + y[S2] = 2000 ~~| + z[Scenario, Supplying Sector, Producing Sector] = + y[Scenario] + x[Supplying Sector, Producing Sector] + ~~| + `) + expect(vars.size).toBe(15) + expect(genJS(vars.get('_x[_a1,_a1]'), 'init-constants')).toEqual(['_x[0][0] = 101.0;']) + expect(genJS(vars.get('_x[_a1,_a2]'), 'init-constants')).toEqual(['_x[0][1] = 102.0;']) + expect(genJS(vars.get('_x[_a1,_a3]'), 'init-constants')).toEqual(['_x[0][2] = 103.0;']) + expect(genJS(vars.get('_x[_a2,_a1]'), 'init-constants')).toEqual(['_x[1][0] = 201.0;']) + expect(genJS(vars.get('_x[_a2,_a2]'), 'init-constants')).toEqual(['_x[1][1] = 202.0;']) + expect(genJS(vars.get('_x[_a2,_a3]'), 'init-constants')).toEqual(['_x[1][2] = 203.0;']) + expect(genJS(vars.get('_x[_a3,_a1]'), 'init-constants')).toEqual(['_x[2][0] = 301.0;']) + expect(genJS(vars.get('_x[_a3,_a2]'), 'init-constants')).toEqual(['_x[2][1] = 302.0;']) + expect(genJS(vars.get('_x[_a3,_a3]'), 'init-constants')).toEqual(['_x[2][2] = 303.0;']) + expect(genJS(vars.get('_y[_s1]'), 'init-constants')).toEqual(['_y[0] = 1000.0;']) + expect(genJS(vars.get('_y[_s2]'), 'init-constants')).toEqual(['_y[1] = 2000.0;']) + expect(genJS(vars.get('_z[_scenario,_a1,_a1]'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_z[i][0][0] = _y[i] + _x[0][0];', + '}' + ]) + expect(genJS(vars.get('_z[_scenario,_a1,_a2]'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_z[i][0][1] = _y[i] + _x[0][1];', + '}' + ]) + expect(genJS(vars.get('_z[_scenario,_a2,_a1]'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_z[i][1][0] = _y[i] + _x[1][0];', + '}' + ]) + expect(genJS(vars.get('_z[_scenario,_a2,_a2]'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_z[i][1][1] = _y[i] + _x[1][1];', + '}' + ]) + }) + }) + + // + // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. + // + + it('should work when valid input and output variable names are provided in spec file', () => { + const vars = readInlineModel( + ` + DimA: A1, A2 ~~| + A[DimA] = 10, 20 ~~| + B = 30 ~~| + C = 40 ~~| + `, + { + inputVarNames: ['B'], + outputVarNames: ['A[A1]'] + } + ) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_b'), 'init-constants')).toEqual(['_b = 30.0;']) + }) + + it('should work when valid input variable name with subscript is provided in spec file', () => { + const vars = readInlineModel( + ` + DimA: A1, A2 ~~| + A[DimA] = 10, 20 ~~| + B[DimA] = A[DimA] + 1 ~~| + `, + { + inputVarNames: ['A[A1]'], + outputVarNames: ['B[A1]', 'B[A2]'] + } + ) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_b'), 'eval')).toEqual(['for (let i = 0; i < 2; i++) {', '_b[i] = _a[i] + 1.0;', '}']) + }) + + it('should throw error when unknown input variable name is provided in spec file', () => { + expect(() => + readInlineModel( + ` + DimA: A1, A2 ~~| + A[DimA] = 10, 20 ~~| + B = 30 ~~| + `, + { + // TODO: We should also check that an error is thrown if the input variable + // includes subscripts and is invalid, but currently the `checkSpecVars` + // function skips those + inputVarNames: ['C'], + outputVarNames: ['A[A1]'] + } + ) + ).toThrow( + 'The input variable _c was declared in spec.json, but no matching variable was found in the model or external data sources' + ) + }) + + it('should throw error when unknown output variable name is provided in spec file', () => { + expect(() => + readInlineModel( + ` + DimA: A1, A2 ~~| + A[DimA] = 10, 20 ~~| + B = 30 ~~| + `, + { + inputVarNames: ['B'], + // TODO: We should also check that an error is thrown if the output variable + // includes subscripts and is invalid, but currently the `checkSpecVars` + // function skips those + outputVarNames: ['C'] + } + ) + ).toThrow( + 'The output variable _c was declared in spec.json, but no matching variable was found in the model or external data sources' + ) + }) + + it('should work for ABS function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = ABS(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ABS(_x);']) + }) + + it('should work for ACTIVE INITIAL function', () => { + const vars = readInlineModel(` + Initial Target Capacity = 1 ~~| + Capacity = 2 ~~| + Target Capacity = ACTIVE INITIAL(Capacity, Initial Target Capacity) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_initial_target_capacity'))).toEqual(['_initial_target_capacity = 1.0;']) + expect(genJS(vars.get('_capacity'))).toEqual(['_capacity = 2.0;']) + expect(genJS(vars.get('_target_capacity'), 'init-levels')).toEqual(['_target_capacity = _initial_target_capacity;']) + expect(genJS(vars.get('_target_capacity'), 'eval')).toEqual(['_target_capacity = _capacity;']) + }) + + it('should work for ALLOCATE AVAILABLE function', () => { + const vars = readInlineModel(` + branch: Boston, Dayton, Fresno ~~| + pprofile: ptype, ppriority ~~| + supply available = 200 ~~| + demand[branch] = 500,300,750 ~~| + priority[Boston,pprofile] = 1,5 ~~| + priority[Dayton,pprofile] = 1,7 ~~| + priority[Fresno,pprofile] = 1,3 ~~| + shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) ~~| + `) + expect(vars.size).toBe(11) + expect(genJS(vars.get('_supply_available'))).toEqual(['_supply_available = 200.0;']) + expect(genJS(vars.get('_demand[_boston]'))).toEqual(['_demand[0] = 500.0;']) + expect(genJS(vars.get('_demand[_dayton]'))).toEqual(['_demand[1] = 300.0;']) + expect(genJS(vars.get('_demand[_fresno]'))).toEqual(['_demand[2] = 750.0;']) + expect(genJS(vars.get('_priority[_boston,_ptype]'))).toEqual(['_priority[0][0] = 1.0;']) + expect(genJS(vars.get('_priority[_boston,_ppriority]'))).toEqual(['_priority[0][1] = 5.0;']) + expect(genJS(vars.get('_priority[_dayton,_ptype]'))).toEqual(['_priority[1][0] = 1.0;']) + expect(genJS(vars.get('_priority[_dayton,_ppriority]'))).toEqual(['_priority[1][1] = 7.0;']) + expect(genJS(vars.get('_priority[_fresno,_ptype]'))).toEqual(['_priority[2][0] = 1.0;']) + expect(genJS(vars.get('_priority[_fresno,_ppriority]'))).toEqual(['_priority[2][1] = 3.0;']) + // expect(genJS(vars.get('_shipments'))).toEqual([ + // 'let __t1 = fns.ALLOCATE_AVAILABLE(_demand, _priority, _supply_available, 3);', + // 'for (let i = 0; i < 3; i++) {', + // '_shipments[i] = __t1[_branch[i]];', + // '}' + // ]) + expect(() => genJS(vars.get('_shipments'))).toThrow( + 'ALLOCATE AVAILABLE function not yet implemented for JS code gen' + ) + }) + + // TODO: Copy more tests from gen-equation-c.spec.ts once we implement ALLOCATE AVAILABLE + // for JS code gen + + it('should work for ARCCOS function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = ARCCOS(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ARCCOS(_x);']) + }) + + it('should work for ARCSIN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = ARCSIN(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ARCSIN(_x);']) + }) + + it('should work for ARCTAN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = ARCTAN(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ARCTAN(_x);']) + }) + + it('should work for COS function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = COS(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.COS(_x);']) + }) + + // TODO: Subscripted variants + it('should work for DELAY1 function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = DELAY1(x, 5) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _x * 5.0;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual(['__level1 = fns.INTEG(__level1, _x - _y);']) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual(['__aux1 = 5.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = (__level1 / __aux1);']) + }) + + it('should work for DELAY1I function', () => { + const vars = readInlineModel(` + x = 1 ~~| + init = 2 ~~| + y = DELAY1I(x, 5, init) ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_init'))).toEqual(['_init = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _init * 5.0;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual(['__level1 = fns.INTEG(__level1, _x - _y);']) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual(['__aux1 = 5.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = (__level1 / __aux1);']) + }) + + it('should work for DELAY3 function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = DELAY3(x, 5) ~~| + `) + expect(vars.size).toBe(9) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _x * ((5.0) / 3.0);']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual(['__level1 = fns.INTEG(__level1, _x - __aux1);']) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual(['__level2 = _x * ((5.0) / 3.0);']) + expect(genJS(vars.get('__level2'), 'eval')).toEqual(['__level2 = fns.INTEG(__level2, __aux1 - __aux2);']) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual(['__level3 = _x * ((5.0) / 3.0);']) + expect(genJS(vars.get('__level3'), 'eval')).toEqual(['__level3 = fns.INTEG(__level3, __aux2 - __aux3);']) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual(['__aux1 = __level1 / ((5.0) / 3.0);']) + expect(genJS(vars.get('__aux2'), 'eval')).toEqual(['__aux2 = __level2 / ((5.0) / 3.0);']) + expect(genJS(vars.get('__aux3'), 'eval')).toEqual(['__aux3 = __level3 / ((5.0) / 3.0);']) + expect(genJS(vars.get('__aux4'), 'eval')).toEqual(['__aux4 = ((5.0) / 3.0);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = (__level3 / __aux4);']) + }) + + it('should work for DELAY3I function', () => { + const vars = readInlineModel(` + x = 1 ~~| + init = 2 ~~| + y = DELAY3I(x, 5, init) ~~| + `) + expect(vars.size).toBe(10) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_init'))).toEqual(['_init = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _init * ((5.0) / 3.0);']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual(['__level1 = fns.INTEG(__level1, _x - __aux1);']) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual(['__level2 = _init * ((5.0) / 3.0);']) + expect(genJS(vars.get('__level2'), 'eval')).toEqual(['__level2 = fns.INTEG(__level2, __aux1 - __aux2);']) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual(['__level3 = _init * ((5.0) / 3.0);']) + expect(genJS(vars.get('__level3'), 'eval')).toEqual(['__level3 = fns.INTEG(__level3, __aux2 - __aux3);']) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual(['__aux1 = __level1 / ((5.0) / 3.0);']) + expect(genJS(vars.get('__aux2'), 'eval')).toEqual(['__aux2 = __level2 / ((5.0) / 3.0);']) + expect(genJS(vars.get('__aux3'), 'eval')).toEqual(['__aux3 = __level3 / ((5.0) / 3.0);']) + expect(genJS(vars.get('__aux4'), 'eval')).toEqual(['__aux4 = ((5.0) / 3.0);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = (__level3 / __aux4);']) + }) + + it('should work for DELAY3I function (one dimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + init[DimA] = 2, 3 ~~| + y[DimA] = DELAY3I(x[DimA], 5, init[DimA]) ~~| + `) + expect(vars.size).toBe(12) + expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'))).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_init[_a1]'))).toEqual(['_init[0] = 2.0;']) + expect(genJS(vars.get('_init[_a2]'))).toEqual(['_init[1] = 3.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level1[i] = _init[i] * ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level1[i] = fns.INTEG(__level1[i], _x[i] - __aux1[i]);', + '}' + ]) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level2[i] = _init[i] * ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('__level2'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level2[i] = fns.INTEG(__level2[i], __aux1[i] - __aux2[i]);', + '}' + ]) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level3[i] = _init[i] * ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('__level3'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level3[i] = fns.INTEG(__level3[i], __aux2[i] - __aux3[i]);', + '}' + ]) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__aux1[i] = __level1[i] / ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('__aux2'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__aux2[i] = __level2[i] / ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('__aux3'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__aux3[i] = __level3[i] / ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('__aux4'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__aux4[i] = ((5.0) / 3.0);', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = (__level3[i] / __aux4[i]);', '}']) + }) + + it('should work for DELAY FIXED function', () => { + const vars = readInlineModel(` + x = 1 ~~| + init = 2 ~~| + y = DELAY FIXED(x, 5, init) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_init'))).toEqual(['_init = 2.0;']) + // expect(genJS(vars.get('_y'), 'init-levels')).toEqual([ + // '_y = _init;', + // '__fixed_delay1 = __new_fixed_delay(__fixed_delay1, 5.0, _init);' + // ]) + // expect(genJS(vars.get('_y'), 'eval')).toEqual(['_y = fns.DELAY_FIXED(_x, __fixed_delay1);']) + expect(() => genJS(vars.get('_y'), 'init-levels')).toThrow( + 'DELAY FIXED function not yet implemented for JS code gen' + ) + expect(() => genJS(vars.get('_y'), 'eval')).toThrow('DELAY FIXED function not yet implemented for JS code gen') + }) + + it('should work for DEPRECIATE STRAIGHTLINE function', () => { + const vars = readInlineModel(` + dtime = 20 ~~| + Capacity Cost = 1000 ~~| + New Capacity = 2000 ~~| + stream = Capacity Cost * New Capacity ~~| + Depreciated Amount = DEPRECIATE STRAIGHTLINE(stream, dtime, 1, 0) ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_dtime'))).toEqual(['_dtime = 20.0;']) + expect(genJS(vars.get('_capacity_cost'))).toEqual(['_capacity_cost = 1000.0;']) + expect(genJS(vars.get('_new_capacity'))).toEqual(['_new_capacity = 2000.0;']) + expect(genJS(vars.get('_stream'))).toEqual(['_stream = _capacity_cost * _new_capacity;']) + // expect(genJS(vars.get('_depreciated_amount'), 'init-levels')).toEqual([ + // '_depreciated_amount = 0.0;', + // '__depreciation1 = __new_depreciation(__depreciation1, _dtime, 0.0);' + // ]) + // expect(genJS(vars.get('_depreciated_amount'), 'eval')).toEqual([ + // '_depreciated_amount = fns.DEPRECIATE_STRAIGHTLINE(_stream, __depreciation1);' + // ]) + expect(() => genJS(vars.get('_depreciated_amount'), 'init-levels')).toThrow( + 'DEPRECIATE STRAIGHTLINE function not yet implemented for JS code gen' + ) + expect(() => genJS(vars.get('_depreciated_amount'), 'eval')).toThrow( + 'DEPRECIATE STRAIGHTLINE function not yet implemented for JS code gen' + ) + }) + + it('should work for ELMCOUNT function', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x = ELMCOUNT(DimA) ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'))).toEqual(['_x = 3;']) + }) + + it('should work for EXP function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = EXP(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.EXP(_x);']) + }) + + it('should work for GAME function (no dimensions)', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = GAME(x) ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.GAME(_y_game_inputs, _x);']) + }) + + it('should work for GAME function (1D)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = 1, 2 ~~| + y[DimA] = GAME(x[DimA]) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'))).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_y[i] = fns.GAME(_y_game_inputs[i], _x[i]);', + '}' + ]) + }) + + it('should work for GAME function (2D)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + a[DimA] = 1, 2 ~~| + b[DimB] = 1, 2 ~~| + y[DimA, DimB] = GAME(a[DimA] + b[DimB]) ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_a[_a1]'))).toEqual(['_a[0] = 1.0;']) + expect(genJS(vars.get('_a[_a2]'))).toEqual(['_a[1] = 2.0;']) + expect(genJS(vars.get('_b[_b1]'))).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'))).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = fns.GAME(_y_game_inputs[i][j], _a[i] + _b[j]);', + '}', + '}' + ]) + }) + + it('should work for GAMMA LN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = GAMMA LN(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + // expect(genJS(vars.get('_y'))).toEqual(['_y = fns.GAMMA_LN(_x);']) + expect(() => genJS(vars.get('_y'))).toThrow('GAMMA LN function not yet implemented for JS code gen') + }) + + describe('should work for GET DATA BETWEEN TIMES function', () => { + const extData: ExtData = new Map([ + [ + '_x', + new Map([ + [0, 0], + [1, 2], + [2, 5] + ]) + ] + ]) + + function verify(mode: number): void { + const vars = readInlineModel( + ` + x ~~| + y = GET DATA BETWEEN TIMES(x, Time, ${mode}) ~~| + `, + { extData } + ) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl', { extData })).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 2.0, 2.0, 5.0];']) + expect(genJS(vars.get('_x'), 'init-lookups', { extData })).toEqual(['_x = fns.createLookup(3, _x_data_);']) + expect(genJS(vars.get('_y'))).toEqual([`_y = fns.GET_DATA_BETWEEN_TIMES(_x, _time, ${mode}.0);`]) + } + + it('with mode == Interpolate', () => { + verify(0) + }) + + it('with mode == Forward', () => { + verify(1) + }) + + it('with mode == Backward', () => { + verify(-1) + }) + + // TODO: Ideally we would validate the mode argument during the analyze phase + // it('with invalid mode', () => { + // verify(42) // should throw error + // }) + }) + + // TODO: Ideally we would validate the mode argument during the analyze phase + // it('should work for GET DATA BETWEEN TIMES function (with invalid mode)', () => { + // const vars = readInlineModel(` + // x = 1 ~~| + // y = GET DATA BETWEEN TIMES(x, Time, 42) ~~| + // `) + // expect(vars.size).toBe(2) + // expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + // expect(genJS(vars.get('_y'))).toEqual(['_y = fns.GET_DATA_BETWEEN_TIMES(_x, _time, 42);']) + // }) + + it('should work for GET DIRECT CONSTANTS function (single value from named csv file)', () => { + // TODO: Add new csv files for this test so that we don't have to rely on + // other test models + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual(['_x = 2050.0;']) + }) + + it('should work for GET DIRECT CONSTANTS function (single value from named xlsx file)', () => { + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + x = GET DIRECT CONSTANTS('data/a.xlsx', 'a', 'B2') ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual(['_x = 2050.0;']) + }) + + it('should work for GET DIRECT CONSTANTS function (single value from tagged xlsx file)', () => { + const modelDir = sampleModelDir('directconst') + const opts = { + modelDir, + directDataSpec: new Map([['?a', 'data/a.xlsx']]) + } + const vars = readInlineModel(` + x = GET DIRECT CONSTANTS('?a', 'a', 'B2') ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'init-constants', opts)).toEqual(['_x = 2050.0;']) + }) + + it('should work for GET DIRECT CONSTANTS function (single value with lowercase cell reference)', () => { + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + x = GET DIRECT CONSTANTS('data/a.csv', ',', 'b2') ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual(['_x = 2050.0;']) + }) + + it('should throw error for GET DIRECT CONSTANTS function (with invalid cell reference)', () => { + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + x = GET DIRECT CONSTANTS('data/a.csv', ',', '++') ~~| + `) + expect(vars.size).toBe(1) + expect(() => genJS(vars.get('_x'), 'init-constants', { modelDir })).toThrow( + `Failed to parse 'cell' argument for GET DIRECT CONSTANTS call for _x: ++` + ) + }) + + it('should work for GET DIRECT CONSTANTS function (1D)', () => { + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + DimB: B1, B2, B3 ~~| + x[DimB] = GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual([ + '_x[0] = 1.0;', + '_x[1] = 2.0;', + '_x[2] = 3.0;' + ]) + }) + + it('should work for GET DIRECT CONSTANTS function (2D)', () => { + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + DimB: B1, B2, B3 ~~| + DimC: C1, C2 ~~| + x[DimB, DimC] = GET DIRECT CONSTANTS('data/c.csv', ',', 'B2') ~~| + `) + expect(vars.size).toBe(1) + expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual([ + '_x[0][0] = 1.0;', + '_x[0][1] = 2.0;', + '_x[1][0] = 3.0;', + '_x[1][1] = 4.0;', + '_x[2][0] = 5.0;', + '_x[2][1] = 6.0;' + ]) + }) + + it('should work for GET DIRECT CONSTANTS function (separate definitions)', () => { + const modelDir = sampleModelDir('directconst') + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A2, A3 ~~| + DimC: C1, C2 ~~| + x[DimC, SubA] = GET DIRECT CONSTANTS('data/f.csv',',','B2') ~~| + x[DimC, DimA] :EXCEPT: [DimC, SubA] = 0 ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_dimc,_a1]'), 'init-constants', { modelDir })).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[i][0] = 0.0;', + '}' + ]) + expect(genJS(vars.get('_x[_dimc,_a2]'), 'init-constants', { modelDir })).toEqual([ + '_x[0][1] = 12.0;', + '_x[1][1] = 22.0;' + ]) + expect(genJS(vars.get('_x[_dimc,_a3]'), 'init-constants', { modelDir })).toEqual([ + '_x[0][2] = 13.0;', + '_x[1][2] = 23.0;' + ]) + }) + + it('should work for GET DIRECT DATA function (single value from named csv file)', () => { + const modelDir = sampleModelDir('directdata') + const opts = { + modelDir + } + const vars = readInlineModel(` + x = GET DIRECT DATA('g_data.csv', ',', 'A', 'B13') ~~| + y = x * 10 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-lookups', opts)).toEqual([ + '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' + ]) + expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + it('should work for GET DIRECT DATA function (single value from tagged xlsx file)', () => { + const modelDir = sampleModelDir('directdata') + const opts = { + modelDir + } + const vars = readInlineModel(` + x = GET DIRECT DATA('data.xlsx', 'C Data', 'A', 'B13') ~~| + y = x * 10 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-lookups', opts)).toEqual([ + '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' + ]) + expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + it('should work for GET DIRECT DATA function (single value from tagged xlsx file)', () => { + const modelDir = sampleModelDir('directdata') + const opts = { + modelDir, + directDataSpec: new Map([['?data', 'data.xlsx']]) + } + const vars = readInlineModel(` + x = GET DIRECT DATA('?data', 'C Data', 'A', 'B13') ~~| + y = x * 10 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-lookups', opts)).toEqual([ + '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' + ]) + expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + it('should work for GET DIRECT DATA function (single value with lowercase cell reference)', () => { + const modelDir = sampleModelDir('directdata') + const vars = readInlineModel(` + x = GET DIRECT DATA('g_data.csv', ',', 'a', 'b13') ~~| + y = x * 10 ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'init-lookups', { modelDir })).toEqual([ + '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' + ]) + expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + it('should throw error for GET DIRECT DATA function (with invalid cell reference)', () => { + const modelDir = sampleModelDir('directdata') + const vars = readInlineModel(` + x = GET DIRECT DATA('g_data.csv', ',', 'a', '++') ~~| + y = x * 10 ~~| + `) + expect(vars.size).toBe(2) + expect(() => genJS(vars.get('_x'), 'init-lookups', { modelDir })).toThrow( + `Failed to parse 'cell' argument for GET DIRECT {DATA,LOOKUPS} call for _x: ++` + ) + expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + it('should work for GET DIRECT DATA function (1D)', () => { + const modelDir = sampleModelDir('directdata') + const vars = readInlineModel(` + DimA: A1, A2 ~~| + x[DimA] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| + y = x[A2] * 10 ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_x[_a1]'), 'init-lookups', { modelDir })).toEqual([ + '_x[0] = fns.createLookup(2, [2030.0, 593.0, 2050.0, 583.0]);' + ]) + expect(genJS(vars.get('_x[_a2]'), 'init-lookups', { modelDir })).toEqual([ + '_x[1] = fns.createLookup(2, [2030.0, 185.0, 2050.0, 180.0]);' + ]) + expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual(['_y = fns.LOOKUP(_x[1], _time) * 10.0;']) + }) + + it('should work for GET DIRECT DATA function (2D with separate definitions)', () => { + const modelDir = sampleModelDir('directdata') + const opts = { + modelDir, + directDataSpec: new Map([['?data', 'data.xlsx']]) + } + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + x[A1, DimB] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| + x[A2, DimB] = 0 ~~| + y = x[A2, B1] * 10 ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-lookups', opts)).toEqual([ + '_x[0][0] = fns.createLookup(2, [2030.0, 593.0, 2050.0, 583.0]);' + ]) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-lookups', opts)).toEqual([ + '_x[0][1] = fns.createLookup(2, [2030.0, 185.0, 2050.0, 180.0]);' + ]) + expect(genJS(vars.get('_x[_a2,_dimb]'), 'init-lookups', opts)).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_x[1][i] = fns.createLookup(2, [-1e+308, 0.0, 1e+308, 0.0]);', + '}' + ]) + expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x[1][0], _time) * 10.0;']) + }) + + it('should work for GET DIRECT LOOKUPS function', () => { + const modelDir = sampleModelDir('directlookups') + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = GET DIRECT LOOKUPS('lookups.CSV', ',', '1', 'AH2') ~~| + y[DimA] = x[DimA](Time) ~~| + z = y[A2] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_x[_a1]'), 'init-lookups', { modelDir })).toEqual([ + '_x[0] = fns.createLookup(2, [2049.0, 0.966667, 2050.0, 1.0]);' + ]) + expect(genJS(vars.get('_x[_a2]'), 'init-lookups', { modelDir })).toEqual([ + '_x[1] = fns.createLookup(2, [2049.0, 0.965517, 2050.0, 1.0]);' + ]) + expect(genJS(vars.get('_x[_a3]'), 'init-lookups', { modelDir })).toEqual([ + '_x[2] = fns.createLookup(2, [2049.0, 0.98975, 2050.0, 0.998394]);' + ]) + expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_y[i] = fns.LOOKUP(_x[i], _time);', + '}' + ]) + expect(genJS(vars.get('_z'), 'eval', { modelDir })).toEqual(['_z = _y[1];']) + }) + + it('should work for GET DIRECT SUBSCRIPT function', () => { + const modelDir = sampleModelDir('directsubs') + // Note that we test both uppercase (typical) and lowercase (atypical) cell references below + const vars = readInlineModel( + ` + DimA: A1, A2, A3 -> DimB, DimC ~~| + DimB: GET DIRECT SUBSCRIPT('b_subs.csv', ',', 'a2', 'a', '') ~~| + DimC: GET DIRECT SUBSCRIPT('c_subs.csv', ',', 'A2', '2', '') ~~| + a[DimA] = 10, 20, 30 ~~| + b[DimB] = 1, 2, 3 ~~| + c[DimC] = a[DimA] + 1 ~~| + d = b[B2] ~~| + e = c[C3] ~~| + `, + { modelDir } + ) + expect(vars.size).toBe(9) + expect(genJS(vars.get('_a[_a1]'), 'init-constants', { modelDir })).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants', { modelDir })).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_a[_a3]'), 'init-constants', { modelDir })).toEqual(['_a[2] = 30.0;']) + expect(genJS(vars.get('_b[_b1]'), 'init-constants', { modelDir })).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'init-constants', { modelDir })).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_b[_b3]'), 'init-constants', { modelDir })).toEqual(['_b[2] = 3.0;']) + expect(genJS(vars.get('_b[_b1]'), 'eval', { modelDir })).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'eval', { modelDir })).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_b[_b3]'), 'eval', { modelDir })).toEqual(['_b[2] = 3.0;']) + expect(genJS(vars.get('_c'), 'eval', { modelDir })).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_c[i] = _a[__map_dima_dimc[i]] + 1.0;', + '}' + ]) + expect(genJS(vars.get('_d'), 'eval', { modelDir })).toEqual(['_d = _b[1];']) + expect(genJS(vars.get('_e'), 'eval', { modelDir })).toEqual(['_e = _c[2];']) + }) + + it('should work for IF THEN ELSE function', () => { + // Note that we use `ABS(1)` here to circumvent the constant conditional optimization + // code (the legacy `ExprReader` doesn't currently optimize function calls). This + // allows us to verify the generated code without the risk of it being optimized away. + const vars = readInlineModel(` + x = ABS(1) ~~| + y = IF THEN ELSE(x > 0, 1, x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = fns.ABS(1.0);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x > 0.0) ? (1.0) : (_x));']) + }) + + it('should work for INITIAL function', () => { + const vars = readInlineModel(` + x = Time * 2 ~~| + y = INITIAL(x) ~~| + `) + expect(vars.size).toBe(2) + // TODO: When `x` is only referenced in an `INITIAL`, we currently evaluate it in `evalAux` + // on every iteration even though it is unused. Maybe we can detect that case and avoid + // the redundant work. + expect(genJS(vars.get('_x'), 'init-levels')).toEqual(['_x = _time * 2.0;']) + expect(genJS(vars.get('_x'), 'eval')).toEqual(['_x = _time * 2.0;']) + expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = _x;']) + }) + + it('should work for INTEG function', () => { + const vars = readInlineModel(` + x = Time * 2 ~~| + y = INTEG(x, 10) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'eval')).toEqual(['_x = _time * 2.0;']) + expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = 10.0;']) + expect(genJS(vars.get('_y'), 'eval')).toEqual(['_y = fns.INTEG(_y, _x);']) + }) + + it('should work for INTEG function (with one dimension)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + rate[DimA] = 10, 20 ~~| + init[DimA] = 1, 2 ~~| + y[DimA] = INTEG(rate[DimA], init[DimA]) ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_rate[_a1]'), 'init-constants')).toEqual(['_rate[0] = 10.0;']) + expect(genJS(vars.get('_rate[_a2]'), 'init-constants')).toEqual(['_rate[1] = 20.0;']) + expect(genJS(vars.get('_init[_a1]'), 'init-constants')).toEqual(['_init[0] = 1.0;']) + expect(genJS(vars.get('_init[_a2]'), 'init-constants')).toEqual(['_init[1] = 2.0;']) + expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = _init[i];', '}']) + expect(genJS(vars.get('_y'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_y[i] = fns.INTEG(_y[i], _rate[i]);', + '}' + ]) + }) + + it('should work for INTEG function (with SUM used in arguments)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + rate[DimA] = 10, 20 ~~| + init[DimA] = 1, 2 ~~| + y = INTEG(SUM(rate[DimA!]), SUM(init[DimA!])) ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_rate[_a1]'), 'init-constants')).toEqual(['_rate[0] = 10.0;']) + expect(genJS(vars.get('_rate[_a2]'), 'init-constants')).toEqual(['_rate[1] = 20.0;']) + expect(genJS(vars.get('_init[_a1]'), 'init-constants')).toEqual(['_init[0] = 1.0;']) + expect(genJS(vars.get('_init[_a2]'), 'init-constants')).toEqual(['_init[1] = 2.0;']) + expect(genJS(vars.get('_y'), 'init-levels')).toEqual([ + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _init[u];', + '}', + '_y = __t1;' + ]) + expect(genJS(vars.get('_y'), 'eval')).toEqual([ + 'let __t2 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t2 += _rate[u];', + '}', + '_y = fns.INTEG(_y, __t2);' + ]) + }) + + it('should work for INTEGER function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = INTEGER(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.INTEGER(_x);']) + }) + + it('should work for LN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = LN(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LN(_x);']) + }) + + it('should work for LOOKUP BACKWARD function (with lookup defined explicitly)', () => { + const vars = readInlineModel(` + x((0,0),(1,1),(2,2)) ~~| + y = LOOKUP BACKWARD(x, 1.5) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) + expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP_BACKWARD(_x, 1.5);']) + }) + + it('should work for LOOKUP BACKWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { + const modelDir = sampleModelDir('directlookups') + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = GET DIRECT LOOKUPS('lookups.CSV', ',', '1', 'AH2') ~~| + y[DimA] = LOOKUP BACKWARD(x[DimA], Time) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-lookups', { modelDir })).toEqual([ + '_x[0] = fns.createLookup(2, [2049.0, 0.966667, 2050.0, 1.0]);' + ]) + expect(genJS(vars.get('_x[_a2]'), 'init-lookups', { modelDir })).toEqual([ + '_x[1] = fns.createLookup(2, [2049.0, 0.965517, 2050.0, 1.0]);' + ]) + expect(genJS(vars.get('_x[_a3]'), 'init-lookups', { modelDir })).toEqual([ + '_x[2] = fns.createLookup(2, [2049.0, 0.98975, 2050.0, 0.998394]);' + ]) + expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_y[i] = fns.LOOKUP_BACKWARD(_x[i], _time);', + '}' + ]) + }) + + it('should work for LOOKUP FORWARD function', () => { + const vars = readInlineModel(` + x((0,0),(1,1),(2,2)) ~~| + y = LOOKUP FORWARD(x, 1.5) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) + expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP_FORWARD(_x, 1.5);']) + }) + + it('should work for LOOKUP INVERT function', () => { + const vars = readInlineModel(` + x((0,0),(1,1),(2,2)) ~~| + y = LOOKUP INVERT(x, 1.5) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) + expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP_INVERT(_x, 1.5);']) + }) + + it('should work for MAX function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = MAX(x, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MAX(_x, 0.0);']) + }) + + it('should work for MIN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = MIN(x, 0) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MIN(_x, 0.0);']) + }) + + it('should work for MODULO function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = MODULO(x, 2) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MODULO(_x, 2.0);']) + }) + + it('should work for NPV function', () => { + const vars = readInlineModel(` + time step = 1 ~~| + stream = 100 ~~| + discount rate = 10 ~~| + init = 0 ~~| + factor = 2 ~~| + y = NPV(stream, discount rate, init, factor) ~~| + `) + expect(vars.size).toBe(9) + expect(genJS(vars.get('_stream'))).toEqual(['_stream = 100.0;']) + expect(genJS(vars.get('_discount_rate'))).toEqual(['_discount_rate = 10.0;']) + expect(genJS(vars.get('_init'))).toEqual(['_init = 0.0;']) + expect(genJS(vars.get('_factor'))).toEqual(['_factor = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = 1.0;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + '__level1 = fns.INTEG(__level1, (-__level1 * _discount_rate) / (1.0 + _discount_rate * _time_step));' + ]) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual(['__level2 = _init;']) + expect(genJS(vars.get('__level2'), 'eval')).toEqual(['__level2 = fns.INTEG(__level2, _stream * __level1);']) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual([ + '__aux1 = (__level2 + _stream * _time_step * __level1) * _factor;' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = __aux1;']) + }) + + it('should work for POWER function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = POWER(x, 2) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.POWER(_x, 2.0);']) + }) + + it('should work for PULSE function', () => { + const vars = readInlineModel(` + x = 10 ~~| + y = PULSE(x, 20) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 10.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.PULSE(_x, 20.0);']) + }) + + it('should work for PULSE TRAIN function', () => { + const vars = readInlineModel(` + first = 10 ~~| + duration = 1 ~~| + repeat = 5 ~~| + last = 30 ~~| + y = PULSE TRAIN(first, duration, repeat, last) ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_first'))).toEqual(['_first = 10.0;']) + expect(genJS(vars.get('_duration'))).toEqual(['_duration = 1.0;']) + expect(genJS(vars.get('_repeat'))).toEqual(['_repeat = 5.0;']) + expect(genJS(vars.get('_last'))).toEqual(['_last = 30.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.PULSE_TRAIN(_first, _duration, _repeat, _last);']) + }) + + it('should work for QUANTUM function', () => { + const vars = readInlineModel(` + x = 1.9 ~~| + y = QUANTUM(x, 10) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.9;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.QUANTUM(_x, 10.0);']) + }) + + it('should work for RAMP function', () => { + const vars = readInlineModel(` + slope = 100 ~~| + start = 1 ~~| + end = 10 ~~| + y = RAMP(slope, start, end) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_slope'))).toEqual(['_slope = 100.0;']) + expect(genJS(vars.get('_start'))).toEqual(['_start = 1.0;']) + expect(genJS(vars.get('_end'))).toEqual(['_end = 10.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.RAMP(_slope, _start, _end);']) + }) + + it('should work for SAMPLE IF TRUE function', () => { + const vars = readInlineModel(` + initial = 10 ~~| + input = 5 ~~| + x = 1 ~~| + y = SAMPLE IF TRUE(Time > x, input, initial) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_initial'))).toEqual(['_initial = 10.0;']) + expect(genJS(vars.get('_input'))).toEqual(['_input = 5.0;']) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = _initial;']) + expect(genJS(vars.get('_y'), 'eval')).toEqual(['_y = ((_time > _x) ? (_input) : (_y));']) + }) + + it('should work for SIN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = SIN(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.SIN(_x);']) + }) + + it('should work for SMOOTH function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTH(input, delay) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _input;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + '__level1 = fns.INTEG(__level1, (_input - __level1) / _delay);' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = __level1;']) + }) + + it('should work for SMOOTHI function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTHI(input, delay, 5) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = 5.0;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + '__level1 = fns.INTEG(__level1, (_input - __level1) / _delay);' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = __level1;']) + }) + + it('should work for SMOOTH3 function', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTH3(input, delay) ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _input;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + '__level1 = fns.INTEG(__level1, (_input - __level1) / (_delay / 3.0));' + ]) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual(['__level2 = _input;']) + expect(genJS(vars.get('__level2'), 'eval')).toEqual([ + '__level2 = fns.INTEG(__level2, (__level1 - __level2) / (_delay / 3.0));' + ]) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual(['__level3 = _input;']) + expect(genJS(vars.get('__level3'), 'eval')).toEqual([ + '__level3 = fns.INTEG(__level3, (__level2 - __level3) / (_delay / 3.0));' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = __level3;']) + }) + + it('should work for SMOOTH3I function (no dimensions)', () => { + const vars = readInlineModel(` + input = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y = SMOOTH3I(input, delay, 5) ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = 5.0;']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + '__level1 = fns.INTEG(__level1, (_input - __level1) / (_delay / 3.0));' + ]) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual(['__level2 = 5.0;']) + expect(genJS(vars.get('__level2'), 'eval')).toEqual([ + '__level2 = fns.INTEG(__level2, (__level1 - __level2) / (_delay / 3.0));' + ]) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual(['__level3 = 5.0;']) + expect(genJS(vars.get('__level3'), 'eval')).toEqual([ + '__level3 = fns.INTEG(__level3, (__level2 - __level3) / (_delay / 3.0));' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = __level3;']) + }) + + it('should work for SMOOTH3I function (1D with subscripted delay parameter)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay[DimA] = 2 ~~| + y[DimA] = SMOOTH3I(input[DimA], delay[DimA], 5) ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_input'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_input[i] = 3.0 + fns.PULSE(10.0, 10.0);', + '}' + ]) + expect(genJS(vars.get('_delay'))).toEqual(['for (let i = 0; i < 2; i++) {', '_delay[i] = 2.0;', '}']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level1[i] = 5.0;', + '}' + ]) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level1[i] = fns.INTEG(__level1[i], (_input[i] - __level1[i]) / (_delay[i] / 3.0));', + '}' + ]) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level2[i] = 5.0;', + '}' + ]) + expect(genJS(vars.get('__level2'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level2[i] = fns.INTEG(__level2[i], (__level1[i] - __level2[i]) / (_delay[i] / 3.0));', + '}' + ]) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level3[i] = 5.0;', + '}' + ]) + expect(genJS(vars.get('__level3'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level3[i] = fns.INTEG(__level3[i], (__level2[i] - __level3[i]) / (_delay[i] / 3.0));', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = __level3[i];', '}']) + }) + + it('should work for SMOOTH3I function (1D with non-subscripted delay parameter)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + input[DimA] = 3 + PULSE(10, 10) ~~| + delay = 2 ~~| + y[DimA] = SMOOTH3I(input[DimA], delay, 5) ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_input'))).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '_input[i] = 3.0 + fns.PULSE(10.0, 10.0);', + '}' + ]) + expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level1[i] = 5.0;', + '}' + ]) + expect(genJS(vars.get('__level1'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level1[i] = fns.INTEG(__level1[i], (_input[i] - __level1[i]) / (_delay / 3.0));', + '}' + ]) + expect(genJS(vars.get('__level2'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level2[i] = 5.0;', + '}' + ]) + expect(genJS(vars.get('__level2'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level2[i] = fns.INTEG(__level2[i], (__level1[i] - __level2[i]) / (_delay / 3.0));', + '}' + ]) + expect(genJS(vars.get('__level3'), 'init-levels')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level3[i] = 5.0;', + '}' + ]) + expect(genJS(vars.get('__level3'), 'eval')).toEqual([ + 'for (let i = 0; i < 2; i++) {', + '__level3[i] = fns.INTEG(__level3[i], (__level2[i] - __level3[i]) / (_delay / 3.0));', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = __level3[i];', '}']) + }) + + it('should work for SQRT function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = SQRT(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.SQRT(_x);']) + }) + + it('should work for STEP function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = STEP(x, 10) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.STEP(_x, 10.0);']) + }) + + it('should work for SUM function (single call)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + a[DimA] = 10, 20 ~~| + x = SUM(a[DimA!]) + 1 ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_x'))).toEqual([ + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _a[u];', + '}', + '_x = __t1 + 1.0;' + ]) + }) + + it('should work for SUM function (multiple calls)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + DimB: B1, B2 ~~| + a[DimA] = 10, 20 ~~| + b[DimB] = 50, 60 ~~| + c[DimA] = 1, 2 ~~| + x = SUM(a[DimA!]) + SUM(b[DimB!]) + SUM(c[DimA!]) ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 50.0;']) + expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 60.0;']) + expect(genJS(vars.get('_c[_a1]'), 'init-constants')).toEqual(['_c[0] = 1.0;']) + expect(genJS(vars.get('_c[_a2]'), 'init-constants')).toEqual(['_c[1] = 2.0;']) + expect(genJS(vars.get('_x'))).toEqual([ + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += _a[u];', + '}', + 'let __t2 = 0.0;', + 'for (let v = 0; v < 2; v++) {', + '__t2 += _b[v];', + '}', + 'let __t3 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t3 += _c[u];', + '}', + '_x = __t1 + __t2 + __t3;' + ]) + }) + + it('should work for SUM function (with nested function call)', () => { + const vars = readInlineModel(` + DimA: A1, A2 ~~| + a[DimA] = 10, 20 ~~| + x = SUM(IF THEN ELSE(a[DimA!] = 10, 0, a[DimA!])) + 1 ~~| + `) + expect(vars.size).toBe(3) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) + expect(genJS(vars.get('_x'))).toEqual([ + 'let __t1 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + '__t1 += ((_a[u] === 10.0) ? (0.0) : (_a[u]));', + '}', + '_x = __t1 + 1.0;' + ]) + }) + + it('should work for TAN function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = TAN(x) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.TAN(_x);']) + }) + + it('should work for TREND function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = TREND(x, 10, 2) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _x / (1.0 + 2.0 * 10.0);']) + expect(genJS(vars.get('__level1'), 'eval')).toEqual(['__level1 = fns.INTEG(__level1, (_x - __level1) / 10.0);']) + expect(genJS(vars.get('__aux1'), 'eval')).toEqual(['__aux1 = fns.ZIDZ(_x - __level1, 10.0 * fns.ABS(__level1));']) + expect(genJS(vars.get('_y'))).toEqual(['_y = __aux1;']) + }) + + it('should work for VECTOR ELM MAP function (with variable reference used for offset arg)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + a[DimA] = 0, 1, 1 ~~| + b[DimB] = 1, 2 ~~| + c[DimA] = VECTOR ELM MAP(b[B1], a[DimA]) ~~| + y = c[A1] ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 0.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) + expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 1.0;']) + expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_c'))).toEqual(['for (let i = 0; i < 3; i++) {', '_c[i] = _b[_dimb[0 + _a[i]]];', '}']) + expect(genJS(vars.get('_y'))).toEqual(['_y = _c[0];']) + }) + + it('should work for VECTOR ELM MAP function (with dimension index expression used for offset arg)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + DimX : one, two, three, four, five ~~| + x[DimX] = 1, 2, 3, 4, 5 ~~| + y[DimA] = VECTOR ELM MAP(x[three], (DimA - 1)) ~~| + `) + expect(vars.size).toBe(6) + expect(genJS(vars.get('_x[_one]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_two]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_three]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_x[_four]'), 'init-constants')).toEqual(['_x[3] = 4.0;']) + expect(genJS(vars.get('_x[_five]'), 'init-constants')).toEqual(['_x[4] = 5.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 3; i++) {', + '_y[i] = _x[_dimx[2 + ((i + 1) - 1.0)]];', + '}' + ]) + }) + + it('should work for VECTOR SELECT function (with sum action + zero for missing values)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + VSSUM == 0 ~~| + VSERRNONE == 0 ~~| + a[DimA] = 0, 1, 1 ~~| + b[DimB] = 1, 2 ~~| + c = VECTOR SELECT(b[DimB!], a[DimA!], 0, VSSUM, VSERRNONE) ~~| + `) + expect(vars.size).toBe(8) + expect(genJS(vars.get('_vssum'), 'init-constants')).toEqual(['_vssum = 0.0;']) + expect(genJS(vars.get('_vserrnone'), 'init-constants')).toEqual(['_vserrnone = 0.0;']) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 0.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) + expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 1.0;']) + expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_c'))).toEqual([ + 'let __t1 = false;', + 'let __t2 = 0.0;', + 'for (let u = 0; u < 2; u++) {', + 'for (let v = 0; v < 3; v++) {', + 'if (_b[u]) {', + '__t2 += _a[v];', + '__t1 = true;', + '}', + '}', + '}', + '_c = __t1 ? __t2 : 0.0;' + ]) + }) + + it('should work for VECTOR SELECT function (with max action + :NA: for missing values)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + VSMAX == 3 ~~| + VSERRNONE == 0 ~~| + a[DimA] = 0, 1, 1 ~~| + b[DimB] = 1, 2 ~~| + c = VECTOR SELECT(b[DimB!], a[DimA!], :NA:, VSMAX, VSERRNONE) ~~| + `) + expect(vars.size).toBe(8) + expect(genJS(vars.get('_vsmax'), 'init-constants')).toEqual(['_vsmax = 3.0;']) + expect(genJS(vars.get('_vserrnone'), 'init-constants')).toEqual(['_vserrnone = 0.0;']) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 0.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) + expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 1.0;']) + expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) + expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) + expect(genJS(vars.get('_c'))).toEqual([ + 'let __t1 = false;', + 'let __t2 = -Number.MAX_VALUE;', + 'for (let u = 0; u < 2; u++) {', + 'for (let v = 0; v < 3; v++) {', + 'if (_b[u]) {', + '__t2 = Math.max(__t2, _a[v]);', + '__t1 = true;', + '}', + '}', + '}', + '_c = __t1 ? __t2 : _NA_;' + ]) + }) + + it('should work for VECTOR SORT ORDER function (1D)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + a[DimA] = 3, 1, 2 ~~| + x[DimA] = VECTOR SORT ORDER(a[DimA], 1) ~~| + y = x[A1] ~~| + `) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 3.0;']) + expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) + expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 2.0;']) + expect(genJS(vars.get('_x'))).toEqual([ + 'let __t1 = fns.VECTOR_SORT_ORDER(_a, 3, 1.0);', + 'for (let i = 0; i < 3; i++) {', + '_x[i] = __t1[_dima[i]];', + '}' + ]) + expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0];']) + }) + + it('should work for VECTOR SORT ORDER function (2D)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + DimB: B1, B2 ~~| + x[A1,B1] = 1 ~~| + x[A1,B2] = 2 ~~| + x[A2,B1] = 4 ~~| + x[A2,B2] = 3 ~~| + x[A3,B1] = 5 ~~| + x[A3,B2] = 5 ~~| + y[DimA,DimB] = VECTOR SORT ORDER(x[DimA,DimB], 1) ~~| + `) + expect(vars.size).toBe(7) + expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) + expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) + expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 4.0;']) + expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 3.0;']) + expect(genJS(vars.get('_x[_a3,_b1]'), 'init-constants')).toEqual(['_x[2][0] = 5.0;']) + expect(genJS(vars.get('_x[_a3,_b2]'), 'init-constants')).toEqual(['_x[2][1] = 5.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'for (let i = 0; i < 3; i++) {', + 'let __t1 = fns.VECTOR_SORT_ORDER(_x[_dima[i]], 2, 1.0);', + 'for (let j = 0; j < 2; j++) {', + '_y[i][j] = __t1[_dimb[j]];', + '}', + '}' + ]) + }) + + it('should work for VMAX function (with full range)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y = VMAX(x[DimA!]) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'let __t1 = -Number.MAX_VALUE;', + 'for (let u = 0; u < 3; u++) {', + '__t1 = Math.max(__t1, _x[u]);', + '}', + '_y = __t1;' + ]) + }) + + it('should work for VMAX function (with partial range)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y = VMAX(x[SubA!]) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'let __t1 = -Number.MAX_VALUE;', + 'for (let u = 0; u < 2; u++) {', + '__t1 = Math.max(__t1, _x[_suba[u]]);', + '}', + '_y = __t1;' + ]) + }) + + it('should work for VMIN function (with full range)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y = VMIN(x[DimA!]) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'let __t1 = Number.MAX_VALUE;', + 'for (let u = 0; u < 3; u++) {', + '__t1 = Math.min(__t1, _x[u]);', + '}', + '_y = __t1;' + ]) + }) + + it('should work for VMIN function (with partial range)', () => { + const vars = readInlineModel(` + DimA: A1, A2, A3 ~~| + SubA: A1, A3 ~~| + x[DimA] = 1, 2, 3 ~~| + y = VMIN(x[SubA!]) ~~| + `) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) + expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) + expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) + expect(genJS(vars.get('_y'))).toEqual([ + 'let __t1 = Number.MAX_VALUE;', + 'for (let u = 0; u < 2; u++) {', + '__t1 = Math.min(__t1, _x[_suba[u]]);', + '}', + '_y = __t1;' + ]) + }) + + it('should work for WITH LOOKUP function', () => { + const vars = readInlineModel(` + y = WITH LOOKUP(Time, ( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) )) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('__lookup1'), 'decl')).toEqual([ + 'const __lookup1_data_ = [0.0, 0.0, 0.1, 0.01, 0.5, 0.7, 1.0, 1.0, 1.5, 1.2, 2.0, 1.3];' + ]) + expect(genJS(vars.get('__lookup1'), 'init-lookups')).toEqual(['__lookup1 = fns.createLookup(6, __lookup1_data_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.WITH_LOOKUP(_time, __lookup1);']) + }) + + it('should work for XIDZ function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = XIDZ(x, 2, 3) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.XIDZ(_x, 2.0, 3.0);']) + }) + + it('should work for ZIDZ function', () => { + const vars = readInlineModel(` + x = 1 ~~| + y = ZIDZ(x, 2) ~~| + `) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ZIDZ(_x, 2.0);']) + }) +}) From 5f574b064dbdc49cf64e5ef1753ac6ee1d8d4f55 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Thu, 4 Sep 2025 20:48:07 -0700 Subject: [PATCH 50/77] test: convert the first few code gen tests to XMILE syntax --- .../gen-equation-js-from-xmile.spec.ts | 97 +++++++++++++++---- 1 file changed, 78 insertions(+), 19 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index c98a4092..d33a9c21 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -8,7 +8,7 @@ import { resetSubscriptsAndDimensions } from '../_shared/subscript' import Model from '../model/model' // import { default as VariableImpl } from '../model/variable' -import { parseInlineXmileModel, sampleModelDir, type Variable } from '../_tests/test-support' +import { parseInlineXmileModel, sampleModelDir, type Variable, xmile } from '../_tests/test-support' import { generateEquation } from './gen-equation' type ExtData = Map> @@ -47,7 +47,20 @@ function readInlineModel( // exclude that so that we have one less thing to check) const map = new Map() Model.allVars().forEach((v: Variable) => { - map.set(v.refId, v) + // Exclude control variables so that we have fewer things to check + switch (v.varName) { + case '_initial_time': + case '_final_time': + case '_time_step': + case '_saveper': + case '_starttime': + case '_stoptime': + case '_dt': + return + default: + map.set(v.refId, v) + break + } }) return map } @@ -85,41 +98,87 @@ function genJS( } describe('generateEquation (XMILE -> JS)', () => { - it('should work for simple equation with unary :NOT: op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = :NOT: x ~~| - `) + // TODO: This test is skipped because we currently only handle boolean operators inside + // `IF THEN ELSE` expressions + it.skip('should work for simple equation with unary :NOT: op', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = :NOT: x ~~| + // `) + + const xmileVars = `\ + + 1 + + + NOT x +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = !_x;']) }) it('should work for simple equation with unary + op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = +x ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = +x ~~| + // `) + + const xmileVars = `\ + + 1 + + + +x +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = _x;']) }) it('should work for simple equation with unary - op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = -x ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = -x ~~| + // `) + + const xmileVars = `\ + + 1 + + + -x +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = -_x;']) }) it('should work for simple equation with binary + op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x + 2 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = x + 2 ~~| + // `) + + const xmileVars = `\ + + 1 + + + x + 2 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = _x + 2.0;']) From 4106b82e27a309d893060cb215ac84b54d0c5c45 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Thu, 4 Sep 2025 21:01:49 -0700 Subject: [PATCH 51/77] test: convert arithmetic tests --- .../gen-equation-js-from-xmile.spec.ts | 114 ++++++++++++++---- 1 file changed, 90 insertions(+), 24 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index d33a9c21..66f7bee3 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -185,60 +185,126 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for simple equation with binary - op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x - 2 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = x - 2 ~~| + // `) + + const xmileVars = `\ + + 1 + + + x - 2 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = _x - 2.0;']) }) it('should work for simple equation with binary * op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x * 2 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = x * 2 ~~| + // `) + + const xmileVars = `\ + + 1 + + + x * 2 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = _x * 2.0;']) }) it('should work for simple equation with binary / op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x / 2 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = x / 2 ~~| + // `) + + const xmileVars = `\ + + 1 + + + x / 2 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = _x / 2.0;']) }) it('should work for simple equation with binary ^ op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x ^ 2 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = x ^ 2 ~~| + // `) + + const xmileVars = `\ + + 1 + + + x ^ 2 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.POW(_x, 2.0);']) }) it('should work for simple equation with explicit parentheses', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = (x + 2) * 3 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = (x + 2) * 3 ~~| + // `) + + const xmileVars = `\ + + 1 + + + (x + 2) * 3 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = (_x + 2.0) * 3.0;']) }) it('should work for conditional expression with = op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x = time, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x = time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x = time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x === _time) ? (1.0) : (0.0));']) From ffbc7bedbe2d47ac4622e05b32815a0463c82fd1 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 11:25:16 -0700 Subject: [PATCH 52/77] test: convert conditional expression and constant definition tests --- .../gen-equation-js-from-xmile.spec.ts | 782 ++++++++++++++---- 1 file changed, 629 insertions(+), 153 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index 66f7bee3..08a2516a 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -98,13 +98,11 @@ function genJS( } describe('generateEquation (XMILE -> JS)', () => { - // TODO: This test is skipped because we currently only handle boolean operators inside - // `IF THEN ELSE` expressions - it.skip('should work for simple equation with unary :NOT: op', () => { + it('should work for simple equation with unary NOT op', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // x = 1 ~~| - // y = :NOT: x ~~| + // y = IF THEN ELSE(:NOT: x, 1, 0) ~~| // `) const xmileVars = `\ @@ -112,13 +110,13 @@ describe('generateEquation (XMILE -> JS)', () => { 1 - NOT x + IF NOT x THEN 1 ELSE 0 ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = !_x;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = ((!_x) ? (1.0) : (0.0));']) }) it('should work for simple equation with unary + op', () => { @@ -311,107 +309,221 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for conditional expression with <> op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x <> time, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x <> time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x <> time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x !== _time) ? (1.0) : (0.0));']) }) it('should work for conditional expression with < op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x < time, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x < time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x < time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x < _time) ? (1.0) : (0.0));']) }) it('should work for conditional expression with <= op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x <= time, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x <= time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x <= time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x <= _time) ? (1.0) : (0.0));']) }) it('should work for conditional expression with > op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x > time, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x > time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x > time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x > _time) ? (1.0) : (0.0));']) }) it('should work for conditional expression with >= op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x >= time, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x >= time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x >= time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x >= _time) ? (1.0) : (0.0));']) }) - it('should work for conditional expression with :AND: op', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = IF THEN ELSE(x :AND: time, 1, 0) ~~| - `) + it('should work for conditional expression with AND op', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = IF THEN ELSE(x :AND: time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + IF x AND time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x && _time) ? (1.0) : (0.0));']) }) - it('should work for conditional expression with :OR: op', () => { - // Note that we use `ABS(1)` here to circumvent the constant conditional optimization - // code (the legacy `ExprReader` doesn't currently optimize function calls). This - // allows us to verify the generated code without the risk of it being optimized away. - const vars = readInlineModel(` - x = ABS(1) ~~| - y = IF THEN ELSE(x :OR: time, 1, 0) ~~| - `) + it('should work for conditional expression with OR op', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = ABS(1) ~~| + // y = IF THEN ELSE(x :OR: time, 1, 0) ~~| + // `) + + const xmileVars = `\ + + ABS(1) + + + IF x OR time THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = fns.ABS(1.0);']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x || _time) ? (1.0) : (0.0));']) }) - it('should work for conditional expression with :NOT: op', () => { - // Note that we use `ABS(1)` here to circumvent the constant conditional optimization - // code (the legacy `ExprReader` doesn't currently optimize function calls). This - // allows us to verify the generated code without the risk of it being optimized away. - const vars = readInlineModel(` - x = ABS(1) ~~| - y = IF THEN ELSE(:NOT: x, 1, 0) ~~| - `) + it('should work for conditional expression with NOT op', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = ABS(1) ~~| + // y = IF THEN ELSE(:NOT: x, 1, 0) ~~| + // `) + + const xmileVars = `\ + + ABS(1) + + + IF NOT x THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = fns.ABS(1.0);']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((!_x) ? (1.0) : (0.0));']) }) - it('should work for expression using :NA: keyword', () => { - const vars = readInlineModel(` - x = Time ~~| - y = IF THEN ELSE(x <> :NA:, 1, 0) ~~| - `) + // TODO: This test is skipped because XMILE may not support :NA: keyword for missing values + it.skip('should work for expression using :NA: keyword', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = Time ~~| + // y = IF THEN ELSE(x <> :NA:, 1, 0) ~~| + // `) + + // TODO: Need to determine how XMILE handles missing values or NA values + const xmileVars = `\ + + Time + + + IF x <> NA THEN 1 ELSE 0 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = _time;']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x !== _NA_) ? (1.0) : (0.0));']) }) it('should work for conditional expression with reference to dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x = 1 ~~| - y[DimA] = IF THEN ELSE(DimA = x, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x = 1 ~~| + // y[DimA] = IF THEN ELSE(DimA = x, 1, 0) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + 1 + + + + + + IF DimA = x THEN 1 ELSE 0 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual([ @@ -422,10 +534,26 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for conditional expression with reference to dimension and subscript/index', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - y[DimA] = IF THEN ELSE(DimA = A2, 1, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // y[DimA] = IF THEN ELSE(DimA = A2, 1, 0) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + IF DimA = A2 THEN 1 ELSE 0 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(1) expect(genJS(vars.get('_y'))).toEqual([ 'for (let i = 0; i < 2; i++) {', @@ -434,31 +562,44 @@ describe('generateEquation (XMILE -> JS)', () => { ]) }) - it('should work for data variable definition', () => { - const extData: ExtData = new Map([ - [ - '_x', - new Map([ - [0, 0], - [1, 2], - [2, 5] - ]) - ] - ]) - const vars = readInlineModel( - ` - x ~~| - y = x * 10 ~~| - `, - { extData } - ) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'decl', { extData })).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 2.0, 2.0, 5.0];']) - expect(genJS(vars.get('_x'), 'init-lookups', { extData })).toEqual(['_x = fns.createLookup(3, _x_data_);']) - expect(genJS(vars.get('_y'), 'eval', { extData })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) - }) - - it('should work for data variable definition (1D)', () => { + // TODO: This test is skipped because XMILE handles external data variables differently + it.skip('should work for data variable definition', () => { + // Equivalent Vensim model for reference: + // const extData: ExtData = new Map([ + // [ + // '_x', + // new Map([ + // [0, 0], + // [1, 2], + // [2, 5] + // ]) + // ] + // ]) + // const vars = readInlineModel( + // ` + // x ~~| + // y = x * 10 ~~| + // `, + // { extData } + // ) + // TODO: Need to determine how XMILE handles external data variables + // const xmileVars = `\ + // + // data + // + // + // x * 10 + // ` + // const mdl = xmile('', xmileVars) + // const vars = readInlineModel(mdl, { extData }) + // expect(vars.size).toBe(2) + // expect(genJS(vars.get('_x'), 'decl', { extData })).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 2.0, 2.0, 5.0];']) + // expect(genJS(vars.get('_x'), 'init-lookups', { extData })).toEqual(['_x = fns.createLookup(3, _x_data_);']) + // expect(genJS(vars.get('_y'), 'eval', { extData })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) + }) + + // TODO: This test is skipped because XMILE handles external data variables differently + it.skip('should work for data variable definition (1D)', () => { const extData: ExtData = new Map([ [ '_x[_a1]', @@ -506,9 +647,18 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for lookup definition', () => { - const vars = readInlineModel(` - x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| + // `) + + const xmileVars = `\ + + 0,0.1,0.5,1,1.5,2 + 0,0.01,0.7,1,1.2,1.3 +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(1) expect(genJS(vars.get('_x'), 'decl')).toEqual([ 'const _x_data_ = [0.0, 0.0, 0.1, 0.01, 0.5, 0.7, 1.0, 1.0, 1.5, 1.2, 2.0, 1.3];' @@ -516,12 +666,41 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(6, _x_data_);']) }) - it('should work for lookup definition (one dimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[A1]( (0,10), (1,20) ) ~~| - x[A2]( (0,30), (1,40) ) ~~| - `) + // TODO: This test is skipped until we support XMILE spec 4.5.3: + // 4.5.3 Apply-to-All Arrays with Non-Apply-to-All Graphical Functions + it.skip('should work for lookup definition (one dimension)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[A1]( (0,10), (1,20) ) ~~| + // x[A2]( (0,30), (1,40) ) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + + 0,1 + 10,20 + + + + + 0,1 + 30,40 + + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x[_a1]'), 'decl')).toEqual(['const _x_data__0_ = [0.0, 10.0, 1.0, 20.0];']) expect(genJS(vars.get('_x[_a2]'), 'decl')).toEqual(['const _x_data__1_ = [0.0, 30.0, 1.0, 40.0];']) @@ -529,7 +708,9 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_x[_a2]'), 'init-lookups')).toEqual(['_x[1] = fns.createLookup(2, _x_data__1_);']) }) - it('should work for lookup definition (two dimensions)', () => { + // TODO: This test is skipped until we support XMILE spec 4.5.3: + // 4.5.3 Apply-to-All Arrays with Non-Apply-to-All Graphical Functions + it.skip('should work for lookup definition (two dimensions)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -550,10 +731,22 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for lookup call', () => { - const vars = readInlineModel(` - x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| - y = x(2) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) ) ~~| + // y = x(2) ~~| + // `) + + const xmileVars = `\ + + 0,0.1,0.5,1,1.5,2 + 0,0.01,0.7,1,1.2,1.3 + + + x(2) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'), 'decl')).toEqual([ 'const _x_data_ = [0.0, 0.0, 0.1, 0.01, 0.5, 0.7, 1.0, 1.0, 1.5, 1.2, 2.0, 1.3];' @@ -562,13 +755,42 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP(_x, 2.0);']) }) - it('should work for lookup call (with one dimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[A1]( [(0,0)-(2,2)], (0,0),(2,1.3) ) ~~| - x[A2]( [(0,0)-(2,2)], (0,0.5),(2,1.5) ) ~~| - y = x[A1](2) ~~| - `) + // TODO: This test is skipped until we support XMILE spec 4.5.3: + // 4.5.3 Apply-to-All Arrays with Non-Apply-to-All Graphical Functions + it.skip('should work for lookup call (with one dimension)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[A1]( [(0,0)-(2,2)], (0,0),(2,1.3) ) ~~| + // x[A2]( [(0,0)-(2,2)], (0,0.5),(2,1.5) ) ~~| + // y = x[A1](2) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + + 0,2 + 0,1.3 + + + + + 0,2 + 0.5,1.5 + + +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(3) expect(genJS(vars.get('_x[_a1]'), 'decl')).toEqual(['const _x_data__0_ = [0.0, 0.0, 2.0, 1.3];']) expect(genJS(vars.get('_x[_a2]'), 'decl')).toEqual(['const _x_data__1_ = [0.0, 0.5, 2.0, 1.5];']) @@ -578,17 +800,38 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for constant definition (with one dimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1 ~~| + // y = x[A2] ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + 1 + + + x[A2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) expect(genJS(vars.get('_y'))).toEqual(['_y = _x[1];']) }) - it('should work for constant definition (with two dimensions + except + subdimension)', () => { + // TODO: This test is skipped because XMILE doesn't support :EXCEPT: operator + it.skip('should work for constant definition (with two dimensions + except + subdimension)', () => { const vars = readInlineModel(` DimA: A1, A2, A3 ~~| SubA: A2, A3 ~~| @@ -615,13 +858,41 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for constant definition (with separate subscripts)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[A1] = 1 ~~| - x[A2] = 2 ~~| - x[A3] = 3 ~~| - y = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[A1] = 1 ~~| + // x[A2] = 2 ~~| + // x[A3] = 3 ~~| + // y = x[A2] ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + x[A2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) @@ -630,11 +901,41 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for const list definition (1D)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y = x[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // x[DimA] = 1, 2, 3 ~~| + // y = x[A2] ~~| + // `) + + // XMILE doesn't have a const list shorthand like Vensim, so this test is basically the + // same as the previous one + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + 3 + + + + x[A2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) @@ -643,12 +944,55 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for const list definition (2D, dimensions in normal/alphabetized order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2, B3 ~~| - x[DimA, DimB] = 1, 2, 3; 4, 5, 6; ~~| - y = x[A2, B3] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2, B3 ~~| + // x[DimA, DimB] = 1, 2, 3; 4, 5, 6; ~~| + // y = x[A2, B3] ~~| + // `) + + // XMILE doesn't have a const list shorthand like Vensim, so we use a non-apply-to-all definition + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + + + x[A2, B3] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(7) expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) @@ -660,13 +1004,59 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for const list definition (2D, dimensions not in normal/alphabetized order)', () => { - const vars = readInlineModel(` - DimB: B1, B2, B3 ~~| - DimA: A1, A2 ~~| - x[DimB, DimA] = 1, 2; 3, 4; 5, 6; ~~| - y = x[B3, A2] ~~| - z = x[B2, A1] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimB: B1, B2, B3 ~~| + // DimA: A1, A2 ~~| + // x[DimB, DimA] = 1, 2; 3, 4; 5, 6; ~~| + // y = x[B3, A2] ~~| + // z = x[B2, A1] ~~| + // `) + + // XMILE doesn't have a const list shorthand like Vensim, so we use a non-apply-to-all definition + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + + + x[B3, A2] + + + x[B2, A1] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(8) expect(genJS(vars.get('_x[_b1,_a1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) expect(genJS(vars.get('_x[_b1,_a2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) @@ -679,14 +1069,57 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for const list definition (2D separated, dimensions in normal/alphabetized order)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - x[A1, DimB] = 1,2 ~~| - x[A2, DimB] = 3,4 ~~| - x[A3, DimB] = 5,6 ~~| - y = x[A3, B2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // DimB: B1, B2 ~~| + // x[A1, DimB] = 1,2 ~~| + // x[A2, DimB] = 3,4 ~~| + // x[A3, DimB] = 5,6 ~~| + // y = x[A3, B2] ~~| + // `) + + // XMILE doesn't have a const list shorthand like Vensim, so we use a non-apply-to-all definition + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + + + x[A3, B2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(7) expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) @@ -698,13 +1131,56 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for const list definition (2D separated, dimensions not in normal/alphabetized order)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - x[B1, DimA] = 1,2,3 ~~| - x[B2, DimA] = 4,5,6 ~~| - y = x[B2, A3] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // DimB: B1, B2 ~~| + // x[B1, DimA] = 1,2,3 ~~| + // x[B2, DimA] = 4,5,6 ~~| + // y = x[B2, A3] ~~| + // `) + + // XMILE doesn't have a const list shorthand like Vensim, so we use a non-apply-to-all definition + const xmileDims = `\ + + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + 5 + + + 6 + + + + x[B2, A3] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(7) expect(genJS(vars.get('_x[_b1,_a1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) expect(genJS(vars.get('_x[_b1,_a2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) From 425552fdcbdb625ed96b06a72da92edde661c4e9 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 11:32:43 -0700 Subject: [PATCH 53/77] test: remove tests for subscripted variables that are covered in other files --- .../gen-equation-js-from-xmile.spec.ts | 1118 +---------------- 1 file changed, 4 insertions(+), 1114 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index 08a2516a..b94ca203 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -1232,1123 +1232,13 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_z'))).toEqual(['_z = _y[1][0];']) }) - it('should work for 1D equation with one mapped dimension name used in expression position', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 -> DimA ~~| - x[DimA] = DimB ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'))).toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = (__map_dimb_dima[i] + 1);', '}']) - }) - - it('should work for 1D equation with one mapped dimension name used in subscript position (separated/non-apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - DimD: D1, D2 -> (DimA: SubA, A1) ~~| - a[DimA] = 1 ~~| - j[DimD] = 10, 20 ~~| - k[DimA] :EXCEPT: [A1] = a[DimA] + j[DimD] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_a'), 'init-constants')).toEqual(['for (let i = 0; i < 3; i++) {', '_a[i] = 1.0;', '}']) - expect(genJS(vars.get('_j[_d1]'), 'init-constants')).toEqual(['_j[0] = 10.0;']) - expect(genJS(vars.get('_j[_d2]'), 'init-constants')).toEqual(['_j[1] = 20.0;']) - expect(genJS(vars.get('_k[_a2]'))).toEqual(['_k[1] = _a[1] + _j[0];']) - expect(genJS(vars.get('_k[_a3]'))).toEqual(['_k[2] = _a[2] + _j[0];']) - }) - - it('should work for 1D equation with one dimension used in expression position (apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - Selected A Index = 1 ~~| - x[DimA] = IF THEN ELSE ( DimA = Selected A Index, 1, 0 ) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_selected_a_index'), 'init-constants')).toEqual(['_selected_a_index = 1.0;']) - expect(genJS(vars.get('_x'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_x[i] = (((i + 1) === _selected_a_index) ? (1.0) : (0.0));', - '}' - ]) - }) - - it('should work for 1D equation with one subdimension used in expression position (separated/non-apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A3 ~~| - Selected A Index = 1 ~~| - x[SubA] = IF THEN ELSE ( SubA = Selected A Index, 1, 0 ) ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_selected_a_index'), 'init-constants')).toEqual(['_selected_a_index = 1.0;']) - expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = (((0 + 1) === _selected_a_index) ? (1.0) : (0.0));']) - expect(genJS(vars.get('_x[_a3]'))).toEqual(['_x[2] = (((2 + 1) === _selected_a_index) ? (1.0) : (0.0));']) - }) - - it('should work for 2D equation with two distinct dimensions used in expression position (apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = (DimA * 10) + DimB ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = ((i + 1) * 10.0) + (j + 1);', - '}', - '}' - ]) - }) - - it('should work for 2D equation with two distinct dimensions used in expression position (separated/non-apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[A1, B1] = 0 ~~| - x[DimA, DimB] :EXCEPT: [A1, B1] = (DimA * 10) + DimB ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1,_b1]'))).toEqual(['_x[0][0] = 0.0;']) - expect(genJS(vars.get('_x[_a1,_b2]'))).toEqual(['_x[0][1] = ((0 + 1) * 10.0) + (1 + 1);']) - expect(genJS(vars.get('_x[_a2,_b1]'))).toEqual(['_x[1][0] = ((1 + 1) * 10.0) + (0 + 1);']) - expect(genJS(vars.get('_x[_a2,_b2]'))).toEqual(['_x[1][1] = ((1 + 1) * 10.0) + (1 + 1);']) - }) - - it('should work for 2D equation with two dimensions that resolve to the same family used in expression position (apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB <-> DimA ~~| - x[DimA, DimB] = (DimA * 10) + DimB ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = ((i + 1) * 10.0) + (j + 1);', - '}', - '}' - ]) - }) - - it('should work for 2D equation with two dimensions that resolve to the same family used in expression position (separated/non-apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB <-> DimA ~~| - x[A1, A1] = 0 ~~| - x[DimA, DimB] :EXCEPT: [A1, A1] = (DimA * 10) + DimB ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1,_a1]'))).toEqual(['_x[0][0] = 0.0;']) - expect(genJS(vars.get('_x[_a1,_a2]'))).toEqual(['_x[0][1] = ((0 + 1) * 10.0) + (1 + 1);']) - expect(genJS(vars.get('_x[_a2,_a1]'))).toEqual(['_x[1][0] = ((1 + 1) * 10.0) + (0 + 1);']) - expect(genJS(vars.get('_x[_a2,_a2]'))).toEqual(['_x[1][1] = ((1 + 1) * 10.0) + (1 + 1);']) - }) - - it('should work for 2D equation with two dimensions (including one subdimension) that resolve to the same family used in expression position (separated/non-apply-to-all)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A3 ~~| - DimB <-> DimA ~~| - x[A1, A1] = 0 ~~| - x[SubA, DimB] :EXCEPT: [A1, A1] = (SubA * 10) + DimB ~~| - `) - expect(vars.size).toBe(6) - expect(genJS(vars.get('_x[_a1,_a1]'))).toEqual(['_x[0][0] = 0.0;']) - expect(genJS(vars.get('_x[_a1,_a2]'))).toEqual(['_x[0][1] = ((0 + 1) * 10.0) + (1 + 1);']) - expect(genJS(vars.get('_x[_a1,_a3]'))).toEqual(['_x[0][2] = ((0 + 1) * 10.0) + (2 + 1);']) - expect(genJS(vars.get('_x[_a3,_a1]'))).toEqual(['_x[2][0] = ((2 + 1) * 10.0) + (0 + 1);']) - expect(genJS(vars.get('_x[_a3,_a2]'))).toEqual(['_x[2][1] = ((2 + 1) * 10.0) + (1 + 1);']) - expect(genJS(vars.get('_x[_a3,_a3]'))).toEqual(['_x[2][2] = ((2 + 1) * 10.0) + (2 + 1);']) - }) - - it('should work for 2D equation with mapped dimensions (separated/non-apply-to-all)', () => { - // This is taken from the `smooth` sample model. This test exercises the case where all dimensions - // resolve to the same family (DimA), and the variables are partially separated (the first dimension - // SubA is separated, but the second dimension DimB uses a loop). - const vars = readInlineModel(` - DimA: A1, A2, A3 -> DimB ~~| - SubA: A2, A3 -> SubB ~~| - DimB: B1, B2, B3 ~~| - SubB: B2, B3 ~~| - x[SubA,DimB] = 3 + PULSE(10, 10) ~~| - y[SubA,DimB] = x[SubA,DimB] ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a2,_dimb]'))).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_x[1][i] = 3.0 + fns.PULSE(10.0, 10.0);', - '}' - ]) - expect(genJS(vars.get('_x[_a3,_dimb]'))).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_x[2][i] = 3.0 + fns.PULSE(10.0, 10.0);', - '}' - ]) - expect(genJS(vars.get('_y[_a2,_dimb]'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[1][i] = _x[1][i];', '}']) - expect(genJS(vars.get('_y[_a3,_dimb]'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[2][i] = _x[2][i];', '}']) - }) - - it('should work for multiple equations that rely on subscript mappings', () => { - const vars = readInlineModel(` - DimA: A1, A2 -> DimB, DimC ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - a[DimA] = 10, 20 ~~| - b[DimB] = 1, 2 ~~| - c[DimC] = a[DimA] + 1 ~~| - d = b[B2] ~~| - e = c[C1] ~~| - `) - expect(vars.size).toBe(7) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) - expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_b[_b1]'), 'eval')).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'eval')).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_c'), 'eval')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_c[i] = _a[__map_dima_dimc[i]] + 1.0;', - '}' - ]) - expect(genJS(vars.get('_d'), 'eval')).toEqual(['_d = _b[1];']) - expect(genJS(vars.get('_e'), 'eval')).toEqual(['_e = _c[0];']) - }) - - // - // NOTE: The following "should work for {0,1,2,3}D variable" tests are aligned with the ones - // from `read-equations.spec.ts` (they exercise the same test models/equations). Having both - // sets of tests makes it easier to see whether a bug is in the "read equations" phase or - // in the "code gen" phase or both. - // - - describe('when LHS has no subscripts', () => { - it('should work when RHS variable has no subscripts', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = x ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x;']) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1 ~~| - y = x[A1] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0];']) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y = x[A1] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0];']) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1 ~~| - y = SUM(x[DimA!]) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y'))).toEqual([ - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[u];', - '}', - '_y = __t1;' - ]) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y = SUM(x[DimA!]) ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[u];', - '}', - '_y = __t1;' - ]) - }) - - it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1 ~~| - y = x[A1, B2] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1];']) - }) - - it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1, 2; 3, 4; ~~| - y = x[A1, B2] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) - expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) - expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 3.0;']) - expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 4.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1];']) - }) - - it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, DimC, DimB] = 1 ~~| - y = x[A1, C2, B2] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - 'for (let k = 0; k < 2; k++) {', - '_x[i][j][k] = 1.0;', - '}', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1][1];']) - }) - - it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, DimC, DimB] :EXCEPT: [DimA, DimC, B1] = 1 ~~| - x[DimA, DimC, B1] = 2 ~~| - y = x[A1, C2, B2] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_dima,_dimc,_b2]'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j][1] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_x[_dima,_dimc,_b1]'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j][0] = 2.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0][1][1];']) - }) - }) - - describe('when LHS is apply-to-all (1D)', () => { - it('should work when RHS variable has no subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x = 1 ~~| - y[DimA] = x ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x;', '}']) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] = x[A2] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[1];', '}']) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] = x[DimA] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[i];', '}']) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] = x[A2] ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]')), 'init-constants').toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[1];', '}']) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] = x[DimA] ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]')), 'init-constants').toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 3; i++) {', '_y[i] = _x[i];', '}']) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) with separated definitions and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[A1] = 1 ~~| - x[A2] = 2 ~~| - y[DimA] = x[DimA] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = _x[i];', '}']) - }) - - // This is adapted from the "except" sample model (see equation for `k`) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - DimB: B1, B2 -> (DimA: SubA, A1) ~~| - a[DimA] = 1, 2, 3 ~~| - b[DimB] = 4, 5 ~~| - y[DimA] = a[DimA] + b[DimB] ~~| - `) - expect(vars.size).toBe(6) - expect(genJS(vars.get('_a[_a1]'))).toEqual(['_a[0] = 1.0;']) - expect(genJS(vars.get('_a[_a2]'))).toEqual(['_a[1] = 2.0;']) - expect(genJS(vars.get('_a[_a3]'))).toEqual(['_a[2] = 3.0;']) - expect(genJS(vars.get('_b[_b1]'))).toEqual(['_b[0] = 4.0;']) - expect(genJS(vars.get('_b[_b2]'))).toEqual(['_b[1] = 5.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_y[i] = _a[i] + _b[__map_dimb_dima[i]];', - '}' - ]) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA] = 1 ~~| - y[DimB] = SUM(x[DimA!]) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[u];', - '}', - '_y[i] = __t1;', - '}' - ]) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1 ~~| - y[DimA] = SUM(x[DimA!]) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 2; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[u];', - '}', - '_y[i] = __t1;', - '}' - ]) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is different from one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA] = 1, 2 ~~| - y[DimB] = SUM(x[DimA!]) ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[u];', - '}', - '_y[i] = __t1;', - '}' - ]) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with marked dimension that is same as one on LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y[DimA] = SUM(x[DimA!]) ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[u];', - '}', - '_y[i] = __t1;', - '}' - ]) - }) - - // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { - // // TODO - // }) - - // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { - // // TODO - // }) - - it('should work when RHS variable is apply-to-all (2D) and is accessed with one normal dimension and one marked dimension that resolve to same family', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: DimA ~~| - x[DimA,DimB] = 1 ~~| - y[DimA] = SUM(x[DimA,DimA!]) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'let __t1 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - '__t1 += _x[i][u];', - '}', - '_y[i] = __t1;', - '}' - ]) - }) - - // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { - // // TODO - // }) - - // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { - // // TODO - // }) - }) - - describe('when LHS is NON-apply-to-all (1D)', () => { - it('should work when RHS variable has no subscripts', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x = 1 ~~| - y[DimA] :EXCEPT: [A1] = x ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x;']) - expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x;']) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] :EXCEPT: [A1] = x[A2] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x')), 'init-constants').toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) - expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[1];']) - }) - - it('should work when RHS variable is apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1 ~~| - y[DimA] :EXCEPT: [A1] = x[DimA] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x'))).toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) - expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[2];']) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] :EXCEPT: [A1] = x[A2] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'))).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]'))).toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) - expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[1];']) - }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with same dimension that appears in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y[DimA] :EXCEPT: [A1] = x[DimA] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]')), 'init-constants').toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _x[1];']) - expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _x[2];']) - }) - - // This is adapted from the "except" sample model (see equation for `k`) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with mapped version of LHS dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - DimB: B1, B2 -> (DimA: SubA, A1) ~~| - a[DimA] = 1, 2, 3 ~~| - b[DimB] = 4, 5 ~~| - y[DimA] :EXCEPT: [A1] = a[DimA] + b[DimB] ~~| - `) - expect(vars.size).toBe(7) - expect(genJS(vars.get('_a[_a1]'))).toEqual(['_a[0] = 1.0;']) - expect(genJS(vars.get('_a[_a2]'))).toEqual(['_a[1] = 2.0;']) - expect(genJS(vars.get('_a[_a3]'))).toEqual(['_a[2] = 3.0;']) - expect(genJS(vars.get('_b[_b1]'))).toEqual(['_b[0] = 4.0;']) - expect(genJS(vars.get('_b[_b2]'))).toEqual(['_b[1] = 5.0;']) - expect(genJS(vars.get('_y[_a2]'))).toEqual(['_y[1] = _a[1] + _b[0];']) - expect(genJS(vars.get('_y[_a3]'))).toEqual(['_y[2] = _a[2] + _b[0];']) - }) - - // This is adapted from the "ref" sample model (with updated naming for clarity) - it('should work for complex mapping example', () => { - const vars = readInlineModel(` - Target: (t1-t3) ~~| - tNext: (t2-t3) -> tPrev ~~| - tPrev: (t1-t2) -> tNext ~~| - x[t1] = y[t1] + 1 ~~| - x[tNext] = y[tNext] + 1 ~~| - y[t1] = 1 ~~| - y[tNext] = x[tPrev] + 1 ~~| - `) - expect(vars.size).toBe(6) - expect(genJS(vars.get('_y[_t1]'))).toEqual(['_y[0] = 1.0;']) - expect(genJS(vars.get('_x[_t1]'))).toEqual(['_x[0] = _y[0] + 1.0;']) - expect(genJS(vars.get('_y[_t2]'))).toEqual(['_y[1] = _x[0] + 1.0;']) - expect(genJS(vars.get('_x[_t2]'))).toEqual(['_x[1] = _y[1] + 1.0;']) - expect(genJS(vars.get('_y[_t3]'))).toEqual(['_y[2] = _x[1] + 1.0;']) - expect(genJS(vars.get('_x[_t3]'))).toEqual(['_x[2] = _y[2] + 1.0;']) - }) - }) - - describe('when LHS is apply-to-all (2D)', () => { - // it('should work when RHS variable has no subscripts', () => { - // // TODO - // }) - - // it('should work when RHS variable is apply-to-all (1D) and is accessed with specific subscript', () => { - // // TODO - // }) - - // it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with specific subscript', () => { - // // TODO - // }) - - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with LHS dimensions that resolve to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB <-> DimA ~~| - x[DimA] = 1, 2 ~~| - y[DimA, DimB] = x[DimA] + x[DimB] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = _x[i] + _x[j];', - '}', - '}' - ]) - }) - - // it('should work when RHS variable is apply-to-all (2D) and is accessed with specific subscripts', () => { - // // TODO - // }) - - // it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with specific subscripts', () => { - // // TODO - // }) - - it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1 ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x')), 'init-constants').toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = _x[j][i];', - '}', - '}' - ]) - }) - - it('should work when RHS variable is apply-to-all (2D) and is accessed with LHS dimensions that resolve to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB <-> DimA ~~| - x[DimA, DimB] = 1 ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = _x[j][i];', - '}', - '}' - ]) - }) - - it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1, 2; 3, 4; ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_x[_a1,_b1]'))).toEqual(['_x[0][0] = 1.0;']) - expect(genJS(vars.get('_x[_a1,_b2]'))).toEqual(['_x[0][1] = 2.0;']) - expect(genJS(vars.get('_x[_a2,_b1]'))).toEqual(['_x[1][0] = 3.0;']) - expect(genJS(vars.get('_x[_a2,_b2]'))).toEqual(['_x[1][1] = 4.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = _x[j][i];', - '}', - '}' - ]) - }) - - it('should work when RHS variable is NON-apply-to-all (2D) with separated definitions (for subscript in first position) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[A1, DimB] = 1 ~~| - x[A2, DimB] = 2 ~~| - y[DimB, DimA] = x[DimA, DimB] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1,_dimb]')), 'init-constants').toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_x[0][i] = 1.0;', - '}' - ]) - expect(genJS(vars.get('_x[_a2,_dimb]')), 'init-constants').toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_x[1][i] = 2.0;', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = _x[j][i];', - '}', - '}' - ]) - }) - - // it('should work when RHS variable is apply-to-all (3D) and is accessed with specific subscripts', () => { - // // TODO - // }) - - // it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with specific subscripts', () => { - // // TODO - // }) - }) - - describe('when LHS is NON-apply-to-all (2D)', () => { - // The LHS in this test is partially separated (expanded only for first dimension position) - it('should work when RHS variable is apply-to-all (2D) and is accessed with same dimensions that appear in LHS', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1 ~~| - y[SubA, DimB] = x[SubA, DimB] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x')), 'init-constants').toEqual([ - 'for (let i = 0; i < 3; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][j] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y[_a1,_dimb]'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[0][i] = _x[0][i];', '}']) - expect(genJS(vars.get('_y[_a2,_dimb]'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[1][i] = _x[1][i];', '}']) - }) - - // This test is based on the example from #179 (simplified to use subdimensions to ensure separation) - it('should work when RHS variable is NON-apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A2 ~~| - SubB <-> SubA ~~| - x[SubA] = 1, 2 ~~| - y[SubA, SubB] = x[SubA] + x[SubB] ~~| - `) - expect(vars.size).toBe(6) - expect(genJS(vars.get('_x[_a1]')), 'init-constants').toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]')), 'init-constants').toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y[_a1,_a1]'))).toEqual(['_y[0][0] = _x[0] + _x[0];']) - expect(genJS(vars.get('_y[_a1,_a2]'))).toEqual(['_y[0][1] = _x[0] + _x[1];']) - expect(genJS(vars.get('_y[_a2,_a1]'))).toEqual(['_y[1][0] = _x[1] + _x[0];']) - expect(genJS(vars.get('_y[_a2,_a2]'))).toEqual(['_y[1][1] = _x[1] + _x[1];']) - }) - - // This test is based on the example from #179 (simplified to use subdimensions to ensure separation). - // It is similar to the previous one, except in this one, `x` is apply-to-all (and refers to the parent - // dimension). - it('should work when RHS variable is apply-to-all (1D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A2 ~~| - SubB <-> SubA ~~| - x[DimA] = 1 ~~| - y[SubA, SubB] = x[SubA] + x[SubB] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_x'))).toEqual(['for (let i = 0; i < 3; i++) {', '_x[i] = 1.0;', '}']) - expect(genJS(vars.get('_y[_a1,_a1]'))).toEqual(['_y[0][0] = _x[0] + _x[0];']) - expect(genJS(vars.get('_y[_a1,_a2]'))).toEqual(['_y[0][1] = _x[0] + _x[1];']) - expect(genJS(vars.get('_y[_a2,_a1]'))).toEqual(['_y[1][0] = _x[1] + _x[0];']) - expect(genJS(vars.get('_y[_a2,_a2]'))).toEqual(['_y[1][1] = _x[1] + _x[1];']) - }) - }) - - describe('when LHS is apply-to-all (3D)', () => { - it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, DimC, DimB] = 1 ~~| - y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - 'for (let k = 0; k < 2; k++) {', - '_x[i][j][k] = 1.0;', - '}', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - 'for (let k = 0; k < 2; k++) {', - '_y[i][j][k] = _x[k][i][j];', - '}', - '}', - '}' - ]) - }) - - it('should work when RHS variable is NON-apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2 ~~| - x[DimA, C1, DimB] = 1 ~~| - x[DimA, C2, DimB] = 2 ~~| - y[DimC, DimB, DimA] = x[DimA, DimC, DimB] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_dima,_c1,_dimb]'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][0][j] = 1.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_x[_dima,_c2,_dimb]'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_x[i][1][j] = 2.0;', - '}', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - 'for (let k = 0; k < 2; k++) {', - '_y[i][j][k] = _x[k][i][j];', - '}', - '}', - '}' - ]) - }) - }) - - describe('when LHS is NON-apply-to-all (3D)', () => { - it('should work when RHS variable is apply-to-all (3D) and is accessed with same dimensions that appear in LHS (but in a different order)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - DimC: C1, C2, C3 ~~| - SubC: C2, C3 ~~| - x[DimA, DimC, DimB] = 1 ~~| - y[SubC, DimB, DimA] = x[DimA, SubC, DimB] ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 3; j++) {', - 'for (let k = 0; k < 2; k++) {', - '_x[i][j][k] = 1.0;', - '}', - '}', - '}' - ]) - expect(genJS(vars.get('_y[_c2,_dimb,_dima]'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[1][i][j] = _x[j][1][i];', - '}', - '}' - ]) - expect(genJS(vars.get('_y[_c3,_dimb,_dima]'), 'init-constants')).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[2][i][j] = _x[j][2][i];', - '}', - '}' - ]) - }) - - // This test is based on the example from #278 - it('should work when RHS variable is NON-apply-to-all (2D) and is accessed with 2 different dimensions from LHS that map to the same family', () => { - const vars = readInlineModel(` - Scenario: S1, S2 ~~| - Sector: A1, A2, A3 ~~| - Supplying Sector: A1, A2 -> Producing Sector ~~| - Producing Sector: A1, A2 -> Supplying Sector ~~| - x[A1,A1] = 101 ~~| - x[A1,A2] = 102 ~~| - x[A1,A3] = 103 ~~| - x[A2,A1] = 201 ~~| - x[A2,A2] = 202 ~~| - x[A2,A3] = 203 ~~| - x[A3,A1] = 301 ~~| - x[A3,A2] = 302 ~~| - x[A3,A3] = 303 ~~| - y[S1] = 1000 ~~| - y[S2] = 2000 ~~| - z[Scenario, Supplying Sector, Producing Sector] = - y[Scenario] + x[Supplying Sector, Producing Sector] - ~~| - `) - expect(vars.size).toBe(15) - expect(genJS(vars.get('_x[_a1,_a1]'), 'init-constants')).toEqual(['_x[0][0] = 101.0;']) - expect(genJS(vars.get('_x[_a1,_a2]'), 'init-constants')).toEqual(['_x[0][1] = 102.0;']) - expect(genJS(vars.get('_x[_a1,_a3]'), 'init-constants')).toEqual(['_x[0][2] = 103.0;']) - expect(genJS(vars.get('_x[_a2,_a1]'), 'init-constants')).toEqual(['_x[1][0] = 201.0;']) - expect(genJS(vars.get('_x[_a2,_a2]'), 'init-constants')).toEqual(['_x[1][1] = 202.0;']) - expect(genJS(vars.get('_x[_a2,_a3]'), 'init-constants')).toEqual(['_x[1][2] = 203.0;']) - expect(genJS(vars.get('_x[_a3,_a1]'), 'init-constants')).toEqual(['_x[2][0] = 301.0;']) - expect(genJS(vars.get('_x[_a3,_a2]'), 'init-constants')).toEqual(['_x[2][1] = 302.0;']) - expect(genJS(vars.get('_x[_a3,_a3]'), 'init-constants')).toEqual(['_x[2][2] = 303.0;']) - expect(genJS(vars.get('_y[_s1]'), 'init-constants')).toEqual(['_y[0] = 1000.0;']) - expect(genJS(vars.get('_y[_s2]'), 'init-constants')).toEqual(['_y[1] = 2000.0;']) - expect(genJS(vars.get('_z[_scenario,_a1,_a1]'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_z[i][0][0] = _y[i] + _x[0][0];', - '}' - ]) - expect(genJS(vars.get('_z[_scenario,_a1,_a2]'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_z[i][0][1] = _y[i] + _x[0][1];', - '}' - ]) - expect(genJS(vars.get('_z[_scenario,_a2,_a1]'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_z[i][1][0] = _y[i] + _x[1][0];', - '}' - ]) - expect(genJS(vars.get('_z[_scenario,_a2,_a2]'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_z[i][1][1] = _y[i] + _x[1][1];', - '}' - ]) - }) - }) - // - // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. + // NOTE: We omit the tests for all the different variations of subscripted variables (like we have in + // `gen-equation-{c,js}-from-vensim.spec.ts`) because XMILE has a simpler subset of supported cases, + // and these are already well covered by the other tests. If there are any XMILE-specific cases, we + // can add tests for those here. // - it('should work when valid input and output variable names are provided in spec file', () => { - const vars = readInlineModel( - ` - DimA: A1, A2 ~~| - A[DimA] = 10, 20 ~~| - B = 30 ~~| - C = 40 ~~| - `, - { - inputVarNames: ['B'], - outputVarNames: ['A[A1]'] - } - ) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) - expect(genJS(vars.get('_b'), 'init-constants')).toEqual(['_b = 30.0;']) - }) - - it('should work when valid input variable name with subscript is provided in spec file', () => { - const vars = readInlineModel( - ` - DimA: A1, A2 ~~| - A[DimA] = 10, 20 ~~| - B[DimA] = A[DimA] + 1 ~~| - `, - { - inputVarNames: ['A[A1]'], - outputVarNames: ['B[A1]', 'B[A2]'] - } - ) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) - expect(genJS(vars.get('_b'), 'eval')).toEqual(['for (let i = 0; i < 2; i++) {', '_b[i] = _a[i] + 1.0;', '}']) - }) - - it('should throw error when unknown input variable name is provided in spec file', () => { - expect(() => - readInlineModel( - ` - DimA: A1, A2 ~~| - A[DimA] = 10, 20 ~~| - B = 30 ~~| - `, - { - // TODO: We should also check that an error is thrown if the input variable - // includes subscripts and is invalid, but currently the `checkSpecVars` - // function skips those - inputVarNames: ['C'], - outputVarNames: ['A[A1]'] - } - ) - ).toThrow( - 'The input variable _c was declared in spec.json, but no matching variable was found in the model or external data sources' - ) - }) - - it('should throw error when unknown output variable name is provided in spec file', () => { - expect(() => - readInlineModel( - ` - DimA: A1, A2 ~~| - A[DimA] = 10, 20 ~~| - B = 30 ~~| - `, - { - inputVarNames: ['B'], - // TODO: We should also check that an error is thrown if the output variable - // includes subscripts and is invalid, but currently the `checkSpecVars` - // function skips those - outputVarNames: ['C'] - } - ) - ).toThrow( - 'The output variable _c was declared in spec.json, but no matching variable was found in the model or external data sources' - ) - }) - it('should work for ABS function', () => { const vars = readInlineModel(` x = 1 ~~| From a0a0c04bc8a9dba33f1b6a5215ce20177e169099 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 11:48:44 -0700 Subject: [PATCH 54/77] test: remove tests for functions not supported in XMILE/Stella --- .../gen-equation-js-from-xmile.spec.ts | 856 +----------------- 1 file changed, 31 insertions(+), 825 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index b94ca203..a72e7ad6 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -1249,20 +1249,9 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ABS(_x);']) }) - it('should work for ACTIVE INITIAL function', () => { - const vars = readInlineModel(` - Initial Target Capacity = 1 ~~| - Capacity = 2 ~~| - Target Capacity = ACTIVE INITIAL(Capacity, Initial Target Capacity) ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_initial_target_capacity'))).toEqual(['_initial_target_capacity = 1.0;']) - expect(genJS(vars.get('_capacity'))).toEqual(['_capacity = 2.0;']) - expect(genJS(vars.get('_target_capacity'), 'init-levels')).toEqual(['_target_capacity = _initial_target_capacity;']) - expect(genJS(vars.get('_target_capacity'), 'eval')).toEqual(['_target_capacity = _capacity;']) - }) - - it('should work for ALLOCATE AVAILABLE function', () => { + // TODO: This test is skipped for now; in Stella, the function is called `ALLOCATE` and we will need to see + // if the Vensim `ALLOCATE AVAILABLE` function is compatible enough + it.skip('should work for ALLOCATE AVAILABLE function', () => { const vars = readInlineModel(` branch: Boston, Dayton, Fresno ~~| pprofile: ptype, ppriority ~~| @@ -1339,7 +1328,7 @@ describe('generateEquation (XMILE -> JS)', () => { }) // TODO: Subscripted variants - it('should work for DELAY1 function', () => { + it('should work for DELAY1 function (without initial value argument)', () => { const vars = readInlineModel(` x = 1 ~~| y = DELAY1(x, 5) ~~| @@ -1352,7 +1341,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = (__level1 / __aux1);']) }) - it('should work for DELAY1I function', () => { + it('should work for DELAY1 function (with initial value argument)', () => { const vars = readInlineModel(` x = 1 ~~| init = 2 ~~| @@ -1367,7 +1356,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = (__level1 / __aux1);']) }) - it('should work for DELAY3 function', () => { + it('should work for DELAY3 function (without initial value argument)', () => { const vars = readInlineModel(` x = 1 ~~| y = DELAY3(x, 5) ~~| @@ -1387,7 +1376,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = (__level3 / __aux4);']) }) - it('should work for DELAY3I function', () => { + it('should work for DELAY3 function (with initial value argument)', () => { const vars = readInlineModel(` x = 1 ~~| init = 2 ~~| @@ -1409,7 +1398,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = (__level3 / __aux4);']) }) - it('should work for DELAY3I function (one dimension)', () => { + it('should work for DELAY3 function (1D with initial value argument)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| x[DimA] = 1, 2 ~~| @@ -1474,7 +1463,10 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = (__level3[i] / __aux4[i]);', '}']) }) - it('should work for DELAY FIXED function', () => { + // TODO: This test is skipped for now; in Stella, the DELAY function can be called with or + // without an initial value argument, but the code that handles the Vensim DELAY FIXED function + // currently assumes the initial value argument + it.skip('should work for DELAY FIXED function', () => { const vars = readInlineModel(` x = 1 ~~| init = 2 ~~| @@ -1494,43 +1486,6 @@ describe('generateEquation (XMILE -> JS)', () => { expect(() => genJS(vars.get('_y'), 'eval')).toThrow('DELAY FIXED function not yet implemented for JS code gen') }) - it('should work for DEPRECIATE STRAIGHTLINE function', () => { - const vars = readInlineModel(` - dtime = 20 ~~| - Capacity Cost = 1000 ~~| - New Capacity = 2000 ~~| - stream = Capacity Cost * New Capacity ~~| - Depreciated Amount = DEPRECIATE STRAIGHTLINE(stream, dtime, 1, 0) ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_dtime'))).toEqual(['_dtime = 20.0;']) - expect(genJS(vars.get('_capacity_cost'))).toEqual(['_capacity_cost = 1000.0;']) - expect(genJS(vars.get('_new_capacity'))).toEqual(['_new_capacity = 2000.0;']) - expect(genJS(vars.get('_stream'))).toEqual(['_stream = _capacity_cost * _new_capacity;']) - // expect(genJS(vars.get('_depreciated_amount'), 'init-levels')).toEqual([ - // '_depreciated_amount = 0.0;', - // '__depreciation1 = __new_depreciation(__depreciation1, _dtime, 0.0);' - // ]) - // expect(genJS(vars.get('_depreciated_amount'), 'eval')).toEqual([ - // '_depreciated_amount = fns.DEPRECIATE_STRAIGHTLINE(_stream, __depreciation1);' - // ]) - expect(() => genJS(vars.get('_depreciated_amount'), 'init-levels')).toThrow( - 'DEPRECIATE STRAIGHTLINE function not yet implemented for JS code gen' - ) - expect(() => genJS(vars.get('_depreciated_amount'), 'eval')).toThrow( - 'DEPRECIATE STRAIGHTLINE function not yet implemented for JS code gen' - ) - }) - - it('should work for ELMCOUNT function', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x = ELMCOUNT(DimA) ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'))).toEqual(['_x = 3;']) - }) - it('should work for EXP function', () => { const vars = readInlineModel(` x = 1 ~~| @@ -1541,55 +1496,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.EXP(_x);']) }) - it('should work for GAME function (no dimensions)', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = GAME(x) ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.GAME(_y_game_inputs, _x);']) - }) - - it('should work for GAME function (1D)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y[DimA] = GAME(x[DimA]) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'))).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_y[i] = fns.GAME(_y_game_inputs[i], _x[i]);', - '}' - ]) - }) - - it('should work for GAME function (2D)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - a[DimA] = 1, 2 ~~| - b[DimB] = 1, 2 ~~| - y[DimA, DimB] = GAME(a[DimA] + b[DimB]) ~~| - `) - expect(vars.size).toBe(6) - expect(genJS(vars.get('_a[_a1]'))).toEqual(['_a[0] = 1.0;']) - expect(genJS(vars.get('_a[_a2]'))).toEqual(['_a[1] = 2.0;']) - expect(genJS(vars.get('_b[_b1]'))).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'))).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = fns.GAME(_y_game_inputs[i][j], _a[i] + _b[j]);', - '}', - '}' - ]) - }) - - it('should work for GAMMA LN function', () => { + it('should work for GAMMALN function', () => { const vars = readInlineModel(` x = 1 ~~| y = GAMMA LN(x) ~~| @@ -1600,352 +1507,6 @@ describe('generateEquation (XMILE -> JS)', () => { expect(() => genJS(vars.get('_y'))).toThrow('GAMMA LN function not yet implemented for JS code gen') }) - describe('should work for GET DATA BETWEEN TIMES function', () => { - const extData: ExtData = new Map([ - [ - '_x', - new Map([ - [0, 0], - [1, 2], - [2, 5] - ]) - ] - ]) - - function verify(mode: number): void { - const vars = readInlineModel( - ` - x ~~| - y = GET DATA BETWEEN TIMES(x, Time, ${mode}) ~~| - `, - { extData } - ) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'decl', { extData })).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 2.0, 2.0, 5.0];']) - expect(genJS(vars.get('_x'), 'init-lookups', { extData })).toEqual(['_x = fns.createLookup(3, _x_data_);']) - expect(genJS(vars.get('_y'))).toEqual([`_y = fns.GET_DATA_BETWEEN_TIMES(_x, _time, ${mode}.0);`]) - } - - it('with mode == Interpolate', () => { - verify(0) - }) - - it('with mode == Forward', () => { - verify(1) - }) - - it('with mode == Backward', () => { - verify(-1) - }) - - // TODO: Ideally we would validate the mode argument during the analyze phase - // it('with invalid mode', () => { - // verify(42) // should throw error - // }) - }) - - // TODO: Ideally we would validate the mode argument during the analyze phase - // it('should work for GET DATA BETWEEN TIMES function (with invalid mode)', () => { - // const vars = readInlineModel(` - // x = 1 ~~| - // y = GET DATA BETWEEN TIMES(x, Time, 42) ~~| - // `) - // expect(vars.size).toBe(2) - // expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - // expect(genJS(vars.get('_y'))).toEqual(['_y = fns.GET_DATA_BETWEEN_TIMES(_x, _time, 42);']) - // }) - - it('should work for GET DIRECT CONSTANTS function (single value from named csv file)', () => { - // TODO: Add new csv files for this test so that we don't have to rely on - // other test models - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - x = GET DIRECT CONSTANTS('data/a.csv', ',', 'B2') ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual(['_x = 2050.0;']) - }) - - it('should work for GET DIRECT CONSTANTS function (single value from named xlsx file)', () => { - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - x = GET DIRECT CONSTANTS('data/a.xlsx', 'a', 'B2') ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual(['_x = 2050.0;']) - }) - - it('should work for GET DIRECT CONSTANTS function (single value from tagged xlsx file)', () => { - const modelDir = sampleModelDir('directconst') - const opts = { - modelDir, - directDataSpec: new Map([['?a', 'data/a.xlsx']]) - } - const vars = readInlineModel(` - x = GET DIRECT CONSTANTS('?a', 'a', 'B2') ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'), 'init-constants', opts)).toEqual(['_x = 2050.0;']) - }) - - it('should work for GET DIRECT CONSTANTS function (single value with lowercase cell reference)', () => { - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - x = GET DIRECT CONSTANTS('data/a.csv', ',', 'b2') ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual(['_x = 2050.0;']) - }) - - it('should throw error for GET DIRECT CONSTANTS function (with invalid cell reference)', () => { - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - x = GET DIRECT CONSTANTS('data/a.csv', ',', '++') ~~| - `) - expect(vars.size).toBe(1) - expect(() => genJS(vars.get('_x'), 'init-constants', { modelDir })).toThrow( - `Failed to parse 'cell' argument for GET DIRECT CONSTANTS call for _x: ++` - ) - }) - - it('should work for GET DIRECT CONSTANTS function (1D)', () => { - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - DimB: B1, B2, B3 ~~| - x[DimB] = GET DIRECT CONSTANTS('data/b.csv', ',', 'B2*') ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual([ - '_x[0] = 1.0;', - '_x[1] = 2.0;', - '_x[2] = 3.0;' - ]) - }) - - it('should work for GET DIRECT CONSTANTS function (2D)', () => { - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - DimB: B1, B2, B3 ~~| - DimC: C1, C2 ~~| - x[DimB, DimC] = GET DIRECT CONSTANTS('data/c.csv', ',', 'B2') ~~| - `) - expect(vars.size).toBe(1) - expect(genJS(vars.get('_x'), 'init-constants', { modelDir })).toEqual([ - '_x[0][0] = 1.0;', - '_x[0][1] = 2.0;', - '_x[1][0] = 3.0;', - '_x[1][1] = 4.0;', - '_x[2][0] = 5.0;', - '_x[2][1] = 6.0;' - ]) - }) - - it('should work for GET DIRECT CONSTANTS function (separate definitions)', () => { - const modelDir = sampleModelDir('directconst') - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A2, A3 ~~| - DimC: C1, C2 ~~| - x[DimC, SubA] = GET DIRECT CONSTANTS('data/f.csv',',','B2') ~~| - x[DimC, DimA] :EXCEPT: [DimC, SubA] = 0 ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_dimc,_a1]'), 'init-constants', { modelDir })).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_x[i][0] = 0.0;', - '}' - ]) - expect(genJS(vars.get('_x[_dimc,_a2]'), 'init-constants', { modelDir })).toEqual([ - '_x[0][1] = 12.0;', - '_x[1][1] = 22.0;' - ]) - expect(genJS(vars.get('_x[_dimc,_a3]'), 'init-constants', { modelDir })).toEqual([ - '_x[0][2] = 13.0;', - '_x[1][2] = 23.0;' - ]) - }) - - it('should work for GET DIRECT DATA function (single value from named csv file)', () => { - const modelDir = sampleModelDir('directdata') - const opts = { - modelDir - } - const vars = readInlineModel(` - x = GET DIRECT DATA('g_data.csv', ',', 'A', 'B13') ~~| - y = x * 10 ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-lookups', opts)).toEqual([ - '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' - ]) - expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) - }) - - it('should work for GET DIRECT DATA function (single value from tagged xlsx file)', () => { - const modelDir = sampleModelDir('directdata') - const opts = { - modelDir - } - const vars = readInlineModel(` - x = GET DIRECT DATA('data.xlsx', 'C Data', 'A', 'B13') ~~| - y = x * 10 ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-lookups', opts)).toEqual([ - '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' - ]) - expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) - }) - - it('should work for GET DIRECT DATA function (single value from tagged xlsx file)', () => { - const modelDir = sampleModelDir('directdata') - const opts = { - modelDir, - directDataSpec: new Map([['?data', 'data.xlsx']]) - } - const vars = readInlineModel(` - x = GET DIRECT DATA('?data', 'C Data', 'A', 'B13') ~~| - y = x * 10 ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-lookups', opts)).toEqual([ - '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' - ]) - expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) - }) - - it('should work for GET DIRECT DATA function (single value with lowercase cell reference)', () => { - const modelDir = sampleModelDir('directdata') - const vars = readInlineModel(` - x = GET DIRECT DATA('g_data.csv', ',', 'a', 'b13') ~~| - y = x * 10 ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'init-lookups', { modelDir })).toEqual([ - '_x = fns.createLookup(2, [2045.0, 35.0, 2050.0, 47.0]);' - ]) - expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) - }) - - it('should throw error for GET DIRECT DATA function (with invalid cell reference)', () => { - const modelDir = sampleModelDir('directdata') - const vars = readInlineModel(` - x = GET DIRECT DATA('g_data.csv', ',', 'a', '++') ~~| - y = x * 10 ~~| - `) - expect(vars.size).toBe(2) - expect(() => genJS(vars.get('_x'), 'init-lookups', { modelDir })).toThrow( - `Failed to parse 'cell' argument for GET DIRECT {DATA,LOOKUPS} call for _x: ++` - ) - expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual(['_y = fns.LOOKUP(_x, _time) * 10.0;']) - }) - - it('should work for GET DIRECT DATA function (1D)', () => { - const modelDir = sampleModelDir('directdata') - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| - y = x[A2] * 10 ~~| - `) - expect(vars.size).toBe(3) - expect(genJS(vars.get('_x[_a1]'), 'init-lookups', { modelDir })).toEqual([ - '_x[0] = fns.createLookup(2, [2030.0, 593.0, 2050.0, 583.0]);' - ]) - expect(genJS(vars.get('_x[_a2]'), 'init-lookups', { modelDir })).toEqual([ - '_x[1] = fns.createLookup(2, [2030.0, 185.0, 2050.0, 180.0]);' - ]) - expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual(['_y = fns.LOOKUP(_x[1], _time) * 10.0;']) - }) - - it('should work for GET DIRECT DATA function (2D with separate definitions)', () => { - const modelDir = sampleModelDir('directdata') - const opts = { - modelDir, - directDataSpec: new Map([['?data', 'data.xlsx']]) - } - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[A1, DimB] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B5') ~~| - x[A2, DimB] = 0 ~~| - y = x[A2, B1] * 10 ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1,_b1]'), 'init-lookups', opts)).toEqual([ - '_x[0][0] = fns.createLookup(2, [2030.0, 593.0, 2050.0, 583.0]);' - ]) - expect(genJS(vars.get('_x[_a1,_b2]'), 'init-lookups', opts)).toEqual([ - '_x[0][1] = fns.createLookup(2, [2030.0, 185.0, 2050.0, 180.0]);' - ]) - expect(genJS(vars.get('_x[_a2,_dimb]'), 'init-lookups', opts)).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_x[1][i] = fns.createLookup(2, [-1e+308, 0.0, 1e+308, 0.0]);', - '}' - ]) - expect(genJS(vars.get('_y'), 'eval', opts)).toEqual(['_y = fns.LOOKUP(_x[1][0], _time) * 10.0;']) - }) - - it('should work for GET DIRECT LOOKUPS function', () => { - const modelDir = sampleModelDir('directlookups') - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = GET DIRECT LOOKUPS('lookups.CSV', ',', '1', 'AH2') ~~| - y[DimA] = x[DimA](Time) ~~| - z = y[A2] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_x[_a1]'), 'init-lookups', { modelDir })).toEqual([ - '_x[0] = fns.createLookup(2, [2049.0, 0.966667, 2050.0, 1.0]);' - ]) - expect(genJS(vars.get('_x[_a2]'), 'init-lookups', { modelDir })).toEqual([ - '_x[1] = fns.createLookup(2, [2049.0, 0.965517, 2050.0, 1.0]);' - ]) - expect(genJS(vars.get('_x[_a3]'), 'init-lookups', { modelDir })).toEqual([ - '_x[2] = fns.createLookup(2, [2049.0, 0.98975, 2050.0, 0.998394]);' - ]) - expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_y[i] = fns.LOOKUP(_x[i], _time);', - '}' - ]) - expect(genJS(vars.get('_z'), 'eval', { modelDir })).toEqual(['_z = _y[1];']) - }) - - it('should work for GET DIRECT SUBSCRIPT function', () => { - const modelDir = sampleModelDir('directsubs') - // Note that we test both uppercase (typical) and lowercase (atypical) cell references below - const vars = readInlineModel( - ` - DimA: A1, A2, A3 -> DimB, DimC ~~| - DimB: GET DIRECT SUBSCRIPT('b_subs.csv', ',', 'a2', 'a', '') ~~| - DimC: GET DIRECT SUBSCRIPT('c_subs.csv', ',', 'A2', '2', '') ~~| - a[DimA] = 10, 20, 30 ~~| - b[DimB] = 1, 2, 3 ~~| - c[DimC] = a[DimA] + 1 ~~| - d = b[B2] ~~| - e = c[C3] ~~| - `, - { modelDir } - ) - expect(vars.size).toBe(9) - expect(genJS(vars.get('_a[_a1]'), 'init-constants', { modelDir })).toEqual(['_a[0] = 10.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants', { modelDir })).toEqual(['_a[1] = 20.0;']) - expect(genJS(vars.get('_a[_a3]'), 'init-constants', { modelDir })).toEqual(['_a[2] = 30.0;']) - expect(genJS(vars.get('_b[_b1]'), 'init-constants', { modelDir })).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'init-constants', { modelDir })).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_b[_b3]'), 'init-constants', { modelDir })).toEqual(['_b[2] = 3.0;']) - expect(genJS(vars.get('_b[_b1]'), 'eval', { modelDir })).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'eval', { modelDir })).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_b[_b3]'), 'eval', { modelDir })).toEqual(['_b[2] = 3.0;']) - expect(genJS(vars.get('_c'), 'eval', { modelDir })).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_c[i] = _a[__map_dima_dimc[i]] + 1.0;', - '}' - ]) - expect(genJS(vars.get('_d'), 'eval', { modelDir })).toEqual(['_d = _b[1];']) - expect(genJS(vars.get('_e'), 'eval', { modelDir })).toEqual(['_e = _c[2];']) - }) - it('should work for IF THEN ELSE function', () => { // Note that we use `ABS(1)` here to circumvent the constant conditional optimization // code (the legacy `ExprReader` doesn't currently optimize function calls). This @@ -1959,7 +1520,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x > 0.0) ? (1.0) : (_x));']) }) - it('should work for INITIAL function', () => { + it('should work for INIT function', () => { const vars = readInlineModel(` x = Time * 2 ~~| y = INITIAL(x) ~~| @@ -2032,7 +1593,7 @@ describe('generateEquation (XMILE -> JS)', () => { ]) }) - it('should work for INTEGER function', () => { + it('should work for INT function', () => { const vars = readInlineModel(` x = 1 ~~| y = INTEGER(x) ~~| @@ -2052,53 +1613,10 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LN(_x);']) }) - it('should work for LOOKUP BACKWARD function (with lookup defined explicitly)', () => { - const vars = readInlineModel(` - x((0,0),(1,1),(2,2)) ~~| - y = LOOKUP BACKWARD(x, 1.5) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) - expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP_BACKWARD(_x, 1.5);']) - }) - - it('should work for LOOKUP BACKWARD function (with lookup defined using GET DIRECT LOOKUPS)', () => { - const modelDir = sampleModelDir('directlookups') - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = GET DIRECT LOOKUPS('lookups.CSV', ',', '1', 'AH2') ~~| - y[DimA] = LOOKUP BACKWARD(x[DimA], Time) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]'), 'init-lookups', { modelDir })).toEqual([ - '_x[0] = fns.createLookup(2, [2049.0, 0.966667, 2050.0, 1.0]);' - ]) - expect(genJS(vars.get('_x[_a2]'), 'init-lookups', { modelDir })).toEqual([ - '_x[1] = fns.createLookup(2, [2049.0, 0.965517, 2050.0, 1.0]);' - ]) - expect(genJS(vars.get('_x[_a3]'), 'init-lookups', { modelDir })).toEqual([ - '_x[2] = fns.createLookup(2, [2049.0, 0.98975, 2050.0, 0.998394]);' - ]) - expect(genJS(vars.get('_y'), 'eval', { modelDir })).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_y[i] = fns.LOOKUP_BACKWARD(_x[i], _time);', - '}' - ]) - }) - - it('should work for LOOKUP FORWARD function', () => { - const vars = readInlineModel(` - x((0,0),(1,1),(2,2)) ~~| - y = LOOKUP FORWARD(x, 1.5) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) - expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP_FORWARD(_x, 1.5);']) - }) + // TODO: Implement this test + it.skip('should work for LOOKUP function', () => {}) - it('should work for LOOKUP INVERT function', () => { + it('should work for LOOKUPINV function', () => { const vars = readInlineModel(` x((0,0),(1,1),(2,2)) ~~| y = LOOKUP INVERT(x, 1.5) ~~| @@ -2129,7 +1647,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MIN(_x, 0.0);']) }) - it('should work for MODULO function', () => { + it('should work for MOD function', () => { const vars = readInlineModel(` x = 1 ~~| y = MODULO(x, 2) ~~| @@ -2139,7 +1657,9 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MODULO(_x, 2.0);']) }) - it('should work for NPV function', () => { + // TODO: This test is skipped because Stella's NPV function takes 2 or 3 arguments, but Vensim's + // takes 4 arguments, so it is not implemented yet in SDE + it.skip('should work for NPV function', () => { const vars = readInlineModel(` time step = 1 ~~| stream = 100 ~~| @@ -2165,17 +1685,9 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __aux1;']) }) - it('should work for POWER function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = POWER(x, 2) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.POWER(_x, 2.0);']) - }) - - it('should work for PULSE function', () => { + // TODO: This test is skipped because Stella's PULSE function takes 1 or 3 arguments, but Vensim's + // takes 2 arguments, so it is not implemented yet in SDE + it.skip('should work for PULSE function', () => { const vars = readInlineModel(` x = 10 ~~| y = PULSE(x, 20) ~~| @@ -2185,32 +1697,6 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.PULSE(_x, 20.0);']) }) - it('should work for PULSE TRAIN function', () => { - const vars = readInlineModel(` - first = 10 ~~| - duration = 1 ~~| - repeat = 5 ~~| - last = 30 ~~| - y = PULSE TRAIN(first, duration, repeat, last) ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_first'))).toEqual(['_first = 10.0;']) - expect(genJS(vars.get('_duration'))).toEqual(['_duration = 1.0;']) - expect(genJS(vars.get('_repeat'))).toEqual(['_repeat = 5.0;']) - expect(genJS(vars.get('_last'))).toEqual(['_last = 30.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.PULSE_TRAIN(_first, _duration, _repeat, _last);']) - }) - - it('should work for QUANTUM function', () => { - const vars = readInlineModel(` - x = 1.9 ~~| - y = QUANTUM(x, 10) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'))).toEqual(['_x = 1.9;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.QUANTUM(_x, 10.0);']) - }) - it('should work for RAMP function', () => { const vars = readInlineModel(` slope = 100 ~~| @@ -2225,21 +1711,6 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.RAMP(_slope, _start, _end);']) }) - it('should work for SAMPLE IF TRUE function', () => { - const vars = readInlineModel(` - initial = 10 ~~| - input = 5 ~~| - x = 1 ~~| - y = SAMPLE IF TRUE(Time > x, input, initial) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_initial'))).toEqual(['_initial = 10.0;']) - expect(genJS(vars.get('_input'))).toEqual(['_input = 5.0;']) - expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = _initial;']) - expect(genJS(vars.get('_y'), 'eval')).toEqual(['_y = ((_time > _x) ? (_input) : (_y));']) - }) - it('should work for SIN function', () => { const vars = readInlineModel(` x = 1 ~~| @@ -2250,7 +1721,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.SIN(_x);']) }) - it('should work for SMOOTH function', () => { + it('should work for SMOOTH function (without initial value argument)', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -2266,7 +1737,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level1;']) }) - it('should work for SMOOTHI function', () => { + it('should work for SMOOTH function (with initial value argument)', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -2282,7 +1753,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level1;']) }) - it('should work for SMOOTH3 function', () => { + it('should work for SMOOTH3 function (without initial value argument)', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -2306,7 +1777,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level3;']) }) - it('should work for SMOOTH3I function (no dimensions)', () => { + it('should work for SMOOTH3 function (no dimensions with initial value argument)', () => { const vars = readInlineModel(` input = 3 + PULSE(10, 10) ~~| delay = 2 ~~| @@ -2330,7 +1801,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level3;']) }) - it('should work for SMOOTH3I function (1D with subscripted delay parameter)', () => { + it('should work for SMOOTH3 function (1D with subscripted delay parameter and initial value argument)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -2377,7 +1848,7 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = __level3[i];', '}']) }) - it('should work for SMOOTH3I function (1D with non-subscripted delay parameter)', () => { + it('should work for SMOOTH3 function (1D with non-subscripted delay parameter and initial value argument)', () => { const vars = readInlineModel(` DimA: A1, A2 ~~| input[DimA] = 3 + PULSE(10, 10) ~~| @@ -2535,269 +2006,4 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('__aux1'), 'eval')).toEqual(['__aux1 = fns.ZIDZ(_x - __level1, 10.0 * fns.ABS(__level1));']) expect(genJS(vars.get('_y'))).toEqual(['_y = __aux1;']) }) - - it('should work for VECTOR ELM MAP function (with variable reference used for offset arg)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - a[DimA] = 0, 1, 1 ~~| - b[DimB] = 1, 2 ~~| - c[DimA] = VECTOR ELM MAP(b[B1], a[DimA]) ~~| - y = c[A1] ~~| - `) - expect(vars.size).toBe(7) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 0.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) - expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 1.0;']) - expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_c'))).toEqual(['for (let i = 0; i < 3; i++) {', '_c[i] = _b[_dimb[0 + _a[i]]];', '}']) - expect(genJS(vars.get('_y'))).toEqual(['_y = _c[0];']) - }) - - it('should work for VECTOR ELM MAP function (with dimension index expression used for offset arg)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - DimX : one, two, three, four, five ~~| - x[DimX] = 1, 2, 3, 4, 5 ~~| - y[DimA] = VECTOR ELM MAP(x[three], (DimA - 1)) ~~| - `) - expect(vars.size).toBe(6) - expect(genJS(vars.get('_x[_one]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_two]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_three]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_x[_four]'), 'init-constants')).toEqual(['_x[3] = 4.0;']) - expect(genJS(vars.get('_x[_five]'), 'init-constants')).toEqual(['_x[4] = 5.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 3; i++) {', - '_y[i] = _x[_dimx[2 + ((i + 1) - 1.0)]];', - '}' - ]) - }) - - it('should work for VECTOR SELECT function (with sum action + zero for missing values)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - VSSUM == 0 ~~| - VSERRNONE == 0 ~~| - a[DimA] = 0, 1, 1 ~~| - b[DimB] = 1, 2 ~~| - c = VECTOR SELECT(b[DimB!], a[DimA!], 0, VSSUM, VSERRNONE) ~~| - `) - expect(vars.size).toBe(8) - expect(genJS(vars.get('_vssum'), 'init-constants')).toEqual(['_vssum = 0.0;']) - expect(genJS(vars.get('_vserrnone'), 'init-constants')).toEqual(['_vserrnone = 0.0;']) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 0.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) - expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 1.0;']) - expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_c'))).toEqual([ - 'let __t1 = false;', - 'let __t2 = 0.0;', - 'for (let u = 0; u < 2; u++) {', - 'for (let v = 0; v < 3; v++) {', - 'if (_b[u]) {', - '__t2 += _a[v];', - '__t1 = true;', - '}', - '}', - '}', - '_c = __t1 ? __t2 : 0.0;' - ]) - }) - - it('should work for VECTOR SELECT function (with max action + :NA: for missing values)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - VSMAX == 3 ~~| - VSERRNONE == 0 ~~| - a[DimA] = 0, 1, 1 ~~| - b[DimB] = 1, 2 ~~| - c = VECTOR SELECT(b[DimB!], a[DimA!], :NA:, VSMAX, VSERRNONE) ~~| - `) - expect(vars.size).toBe(8) - expect(genJS(vars.get('_vsmax'), 'init-constants')).toEqual(['_vsmax = 3.0;']) - expect(genJS(vars.get('_vserrnone'), 'init-constants')).toEqual(['_vserrnone = 0.0;']) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 0.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) - expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 1.0;']) - expect(genJS(vars.get('_b[_b1]'), 'init-constants')).toEqual(['_b[0] = 1.0;']) - expect(genJS(vars.get('_b[_b2]'), 'init-constants')).toEqual(['_b[1] = 2.0;']) - expect(genJS(vars.get('_c'))).toEqual([ - 'let __t1 = false;', - 'let __t2 = -Number.MAX_VALUE;', - 'for (let u = 0; u < 2; u++) {', - 'for (let v = 0; v < 3; v++) {', - 'if (_b[u]) {', - '__t2 = Math.max(__t2, _a[v]);', - '__t1 = true;', - '}', - '}', - '}', - '_c = __t1 ? __t2 : _NA_;' - ]) - }) - - it('should work for VECTOR SORT ORDER function (1D)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - a[DimA] = 3, 1, 2 ~~| - x[DimA] = VECTOR SORT ORDER(a[DimA], 1) ~~| - y = x[A1] ~~| - `) - expect(vars.size).toBe(5) - expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 3.0;']) - expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 1.0;']) - expect(genJS(vars.get('_a[_a3]'), 'init-constants')).toEqual(['_a[2] = 2.0;']) - expect(genJS(vars.get('_x'))).toEqual([ - 'let __t1 = fns.VECTOR_SORT_ORDER(_a, 3, 1.0);', - 'for (let i = 0; i < 3; i++) {', - '_x[i] = __t1[_dima[i]];', - '}' - ]) - expect(genJS(vars.get('_y'))).toEqual(['_y = _x[0];']) - }) - - it('should work for VECTOR SORT ORDER function (2D)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - DimB: B1, B2 ~~| - x[A1,B1] = 1 ~~| - x[A1,B2] = 2 ~~| - x[A2,B1] = 4 ~~| - x[A2,B2] = 3 ~~| - x[A3,B1] = 5 ~~| - x[A3,B2] = 5 ~~| - y[DimA,DimB] = VECTOR SORT ORDER(x[DimA,DimB], 1) ~~| - `) - expect(vars.size).toBe(7) - expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) - expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) - expect(genJS(vars.get('_x[_a2,_b1]'), 'init-constants')).toEqual(['_x[1][0] = 4.0;']) - expect(genJS(vars.get('_x[_a2,_b2]'), 'init-constants')).toEqual(['_x[1][1] = 3.0;']) - expect(genJS(vars.get('_x[_a3,_b1]'), 'init-constants')).toEqual(['_x[2][0] = 5.0;']) - expect(genJS(vars.get('_x[_a3,_b2]'), 'init-constants')).toEqual(['_x[2][1] = 5.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'for (let i = 0; i < 3; i++) {', - 'let __t1 = fns.VECTOR_SORT_ORDER(_x[_dima[i]], 2, 1.0);', - 'for (let j = 0; j < 2; j++) {', - '_y[i][j] = __t1[_dimb[j]];', - '}', - '}' - ]) - }) - - it('should work for VMAX function (with full range)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y = VMAX(x[DimA!]) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'let __t1 = -Number.MAX_VALUE;', - 'for (let u = 0; u < 3; u++) {', - '__t1 = Math.max(__t1, _x[u]);', - '}', - '_y = __t1;' - ]) - }) - - it('should work for VMAX function (with partial range)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y = VMAX(x[SubA!]) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'let __t1 = -Number.MAX_VALUE;', - 'for (let u = 0; u < 2; u++) {', - '__t1 = Math.max(__t1, _x[_suba[u]]);', - '}', - '_y = __t1;' - ]) - }) - - it('should work for VMIN function (with full range)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y = VMIN(x[DimA!]) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'let __t1 = Number.MAX_VALUE;', - 'for (let u = 0; u < 3; u++) {', - '__t1 = Math.min(__t1, _x[u]);', - '}', - '_y = __t1;' - ]) - }) - - it('should work for VMIN function (with partial range)', () => { - const vars = readInlineModel(` - DimA: A1, A2, A3 ~~| - SubA: A1, A3 ~~| - x[DimA] = 1, 2, 3 ~~| - y = VMIN(x[SubA!]) ~~| - `) - expect(vars.size).toBe(4) - expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) - expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) - expect(genJS(vars.get('_x[_a3]'), 'init-constants')).toEqual(['_x[2] = 3.0;']) - expect(genJS(vars.get('_y'))).toEqual([ - 'let __t1 = Number.MAX_VALUE;', - 'for (let u = 0; u < 2; u++) {', - '__t1 = Math.min(__t1, _x[_suba[u]]);', - '}', - '_y = __t1;' - ]) - }) - - it('should work for WITH LOOKUP function', () => { - const vars = readInlineModel(` - y = WITH LOOKUP(Time, ( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) )) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('__lookup1'), 'decl')).toEqual([ - 'const __lookup1_data_ = [0.0, 0.0, 0.1, 0.01, 0.5, 0.7, 1.0, 1.0, 1.5, 1.2, 2.0, 1.3];' - ]) - expect(genJS(vars.get('__lookup1'), 'init-lookups')).toEqual(['__lookup1 = fns.createLookup(6, __lookup1_data_);']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.WITH_LOOKUP(_time, __lookup1);']) - }) - - it('should work for XIDZ function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = XIDZ(x, 2, 3) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.XIDZ(_x, 2.0, 3.0);']) - }) - - it('should work for ZIDZ function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = ZIDZ(x, 2) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) - expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ZIDZ(_x, 2.0);']) - }) }) From 29688466cec511025b5f0bed88d34ec060f1bd6f Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 11:52:43 -0700 Subject: [PATCH 55/77] test: convert tests for subscripted equations --- .../gen-equation-js-from-xmile.spec.ts | 98 ++++++++++++++++--- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index a72e7ad6..07b93f1f 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -1192,12 +1192,42 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for equation with one dimension', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - y[DimA] = (x[DimA] + 2) * MIN(0, x[DimA]) ~~| - z = y[A2] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1, 2 ~~| + // y[DimA] = (x[DimA] + 2) * MIN(0, x[DimA]) ~~| + // z = y[A2] ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + (x[DimA] + 2) * MIN(0, x[DimA]) + + + y[A2] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) expect(genJS(vars.get('_x[_a1]'), 'init-constants')).toEqual(['_x[0] = 1.0;']) expect(genJS(vars.get('_x[_a2]'), 'init-constants')).toEqual(['_x[1] = 2.0;']) @@ -1210,13 +1240,55 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for equation with two dimensions', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - x[DimA, DimB] = 1, 2; 3, 4; ~~| - y[DimA, DimB] = (x[DimA, DimB] + 2) * MIN(0, x[DimA, DimB]) ~~| - z = y[A2, B1] ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // x[DimA, DimB] = 1, 2; 3, 4; ~~| + // y[DimA, DimB] = (x[DimA, DimB] + 2) * MIN(0, x[DimA, DimB]) ~~| + // z = y[A2, B1] ~~| + // `) + + const xmileDims = `\ + + + + + + + +` + const xmileVars = `\ + + + + + + + 1 + + + 2 + + + 3 + + + 4 + + + + + + + + (x[DimA, DimB] + 2) * MIN(0, x[DimA, DimB]) + + + y[A2, B1] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(6) expect(genJS(vars.get('_x[_a1,_b1]'), 'init-constants')).toEqual(['_x[0][0] = 1.0;']) expect(genJS(vars.get('_x[_a1,_b2]'), 'init-constants')).toEqual(['_x[0][1] = 2.0;']) From b65e52876d17ebec237d9e3eb6acde374c133693 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 12:41:31 -0700 Subject: [PATCH 56/77] test: convert tests for supported XMILE/Stella functions [skip ci] --- .../gen-equation-js-from-xmile.spec.ts | 1029 +++++++++++++---- 1 file changed, 818 insertions(+), 211 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index 07b93f1f..89a62289 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -8,7 +8,7 @@ import { resetSubscriptsAndDimensions } from '../_shared/subscript' import Model from '../model/model' // import { default as VariableImpl } from '../model/variable' -import { parseInlineXmileModel, sampleModelDir, type Variable, xmile } from '../_tests/test-support' +import { parseInlineXmileModel, type Variable, xmile } from '../_tests/test-support' import { generateEquation } from './gen-equation' type ExtData = Map> @@ -1312,10 +1312,21 @@ describe('generateEquation (XMILE -> JS)', () => { // it('should work for ABS function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = ABS(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = ABS(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + ABS(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ABS(_x);']) @@ -1360,40 +1371,84 @@ describe('generateEquation (XMILE -> JS)', () => { // for JS code gen it('should work for ARCCOS function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = ARCCOS(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = ARCCOS(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + ARCCOS(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ARCCOS(_x);']) }) it('should work for ARCSIN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = ARCSIN(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = ARCSIN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + ARCSIN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ARCSIN(_x);']) }) it('should work for ARCTAN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = ARCTAN(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = ARCTAN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + ARCTAN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ARCTAN(_x);']) }) it('should work for COS function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = COS(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = COS(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + COS(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.COS(_x);']) @@ -1401,10 +1456,21 @@ describe('generateEquation (XMILE -> JS)', () => { // TODO: Subscripted variants it('should work for DELAY1 function (without initial value argument)', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = DELAY1(x, 5) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = DELAY1(x, 5) ~~| + // `) + + const xmileVars = `\ + + 1 + + + DELAY1(x, 5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _x * 5.0;']) @@ -1414,11 +1480,25 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for DELAY1 function (with initial value argument)', () => { - const vars = readInlineModel(` - x = 1 ~~| - init = 2 ~~| - y = DELAY1I(x, 5, init) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // init = 2 ~~| + // y = DELAY1I(x, 5, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + DELAY1(x, 5, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(5) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_init'))).toEqual(['_init = 2.0;']) @@ -1429,10 +1509,21 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for DELAY3 function (without initial value argument)', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = DELAY3(x, 5) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = DELAY3(x, 5) ~~| + // `) + + const xmileVars = `\ + + 1 + + + DELAY3(x, 5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(9) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _x * ((5.0) / 3.0);']) @@ -1449,11 +1540,25 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for DELAY3 function (with initial value argument)', () => { - const vars = readInlineModel(` - x = 1 ~~| - init = 2 ~~| - y = DELAY3I(x, 5, init) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // init = 2 ~~| + // y = DELAY3I(x, 5, init) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + DELAY3(x, 5, init) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(10) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_init'))).toEqual(['_init = 2.0;']) @@ -1471,12 +1576,50 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for DELAY3 function (1D with initial value argument)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - x[DimA] = 1, 2 ~~| - init[DimA] = 2, 3 ~~| - y[DimA] = DELAY3I(x[DimA], 5, init[DimA]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // x[DimA] = 1, 2 ~~| + // init[DimA] = 2, 3 ~~| + // y[DimA] = DELAY3I(x[DimA], 5, init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + 1 + + + 2 + + + + + + + + 2 + + + 3 + + + + + + + DELAY3(x[DimA], 5, init[DimA]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(12) expect(genJS(vars.get('_x[_a1]'))).toEqual(['_x[0] = 1.0;']) expect(genJS(vars.get('_x[_a2]'))).toEqual(['_x[1] = 2.0;']) @@ -1559,71 +1702,161 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for EXP function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = EXP(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = EXP(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + EXP(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.EXP(_x);']) }) - it('should work for GAMMALN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = GAMMA LN(x) ~~| - `) + // TODO: Implement this test once we support the GAMMALN function for XMILE+JS + it.skip('should work for GAMMALN function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = GAMMA LN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + GAMMALN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) // expect(genJS(vars.get('_y'))).toEqual(['_y = fns.GAMMA_LN(_x);']) - expect(() => genJS(vars.get('_y'))).toThrow('GAMMA LN function not yet implemented for JS code gen') + expect(() => genJS(vars.get('_y'))).toThrow('GAMMA_LN function not yet implemented for JS code gen') }) it('should work for IF THEN ELSE function', () => { - // Note that we use `ABS(1)` here to circumvent the constant conditional optimization - // code (the legacy `ExprReader` doesn't currently optimize function calls). This - // allows us to verify the generated code without the risk of it being optimized away. - const vars = readInlineModel(` - x = ABS(1) ~~| - y = IF THEN ELSE(x > 0, 1, x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = ABS(1) ~~| + // y = IF THEN ELSE(x > 0, 1, x) ~~| + // `) + + const xmileVars = `\ + + ABS(1) + + + IF x > 0 THEN 1 ELSE x +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = fns.ABS(1.0);']) expect(genJS(vars.get('_y'))).toEqual(['_y = ((_x > 0.0) ? (1.0) : (_x));']) }) it('should work for INIT function', () => { - const vars = readInlineModel(` - x = Time * 2 ~~| - y = INITIAL(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = Time * 2 ~~| + // y = INITIAL(x) ~~| + // `) + + const xmileVars = `\ + + Time * 2 + + + INIT(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) - // TODO: When `x` is only referenced in an `INITIAL`, we currently evaluate it in `evalAux` - // on every iteration even though it is unused. Maybe we can detect that case and avoid - // the redundant work. expect(genJS(vars.get('_x'), 'init-levels')).toEqual(['_x = _time * 2.0;']) expect(genJS(vars.get('_x'), 'eval')).toEqual(['_x = _time * 2.0;']) expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = _x;']) }) - it('should work for INTEG function', () => { - const vars = readInlineModel(` - x = Time * 2 ~~| - y = INTEG(x, 10) ~~| - `) - expect(vars.size).toBe(2) - expect(genJS(vars.get('_x'), 'eval')).toEqual(['_x = _time * 2.0;']) - expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = 10.0;']) + it('should work for INTEG function (synthesized from variable definition)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = Time * 2 ~~| + // y = INTEG(x, 10) ~~| + // `) + + const xmileVars = `\ + + Time * 2 + + + 10 + x +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'eval')).toEqual(['_x = _time * 2.0;']) + expect(genJS(vars.get('_y'), 'init-levels')).toEqual(['_y = 10.0;']) expect(genJS(vars.get('_y'), 'eval')).toEqual(['_y = fns.INTEG(_y, _x);']) }) - it('should work for INTEG function (with one dimension)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - rate[DimA] = 10, 20 ~~| - init[DimA] = 1, 2 ~~| - y[DimA] = INTEG(rate[DimA], init[DimA]) ~~| - `) + it('should work for INTEG function (synthesized from variable definition with one dimension)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // rate[DimA] = 10, 20 ~~| + // init[DimA] = 1, 2 ~~| + // y[DimA] = INTEG(rate[DimA], init[DimA]) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + + + + + + 1 + + + 2 + + + + + + + init[DimA] + rate[DimA] +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(5) expect(genJS(vars.get('_rate[_a1]'), 'init-constants')).toEqual(['_rate[0] = 10.0;']) expect(genJS(vars.get('_rate[_a2]'), 'init-constants')).toEqual(['_rate[1] = 20.0;']) @@ -1637,13 +1870,49 @@ describe('generateEquation (XMILE -> JS)', () => { ]) }) - it('should work for INTEG function (with SUM used in arguments)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - rate[DimA] = 10, 20 ~~| - init[DimA] = 1, 2 ~~| - y = INTEG(SUM(rate[DimA!]), SUM(init[DimA!])) ~~| - `) + it('should work for INTEG function (synthesized from variable definition with SUM used in arguments)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // rate[DimA] = 10, 20 ~~| + // init[DimA] = 1, 2 ~~| + // y = INTEG(SUM(rate[DimA!]), SUM(init[DimA!])) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + + + + + + 1 + + + 2 + + + + SUM(init[*]) + SUM(rate[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(5) expect(genJS(vars.get('_rate[_a1]'), 'init-constants')).toEqual(['_rate[0] = 10.0;']) expect(genJS(vars.get('_rate[_a2]'), 'init-constants')).toEqual(['_rate[1] = 20.0;']) @@ -1666,20 +1935,42 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for INT function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = INTEGER(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = INTEGER(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + INT(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.INTEGER(_x);']) }) it('should work for LN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = LN(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = LN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + LN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LN(_x);']) @@ -1689,10 +1980,22 @@ describe('generateEquation (XMILE -> JS)', () => { it.skip('should work for LOOKUP function', () => {}) it('should work for LOOKUPINV function', () => { - const vars = readInlineModel(` - x((0,0),(1,1),(2,2)) ~~| - y = LOOKUP INVERT(x, 1.5) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x((0,0),(1,1),(2,2)) ~~| + // y = LOOKUP INVERT(x, 1.5) ~~| + // `) + + const xmileVars = `\ + + 0,1,2 + 0,1,2 + + + LOOKUP INVERT(x, 1.5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) @@ -1700,30 +2003,63 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for MAX function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = MAX(x, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = MAX(x, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + MAX(x, 0) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MAX(_x, 0.0);']) }) it('should work for MIN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = MIN(x, 0) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = MIN(x, 0) ~~| + // `) + + const xmileVars = `\ + + 1 + + + MIN(x, 0) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MIN(_x, 0.0);']) }) it('should work for MOD function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = MODULO(x, 2) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = MODULO(x, 2) ~~| + // `) + + const xmileVars = `\ + + 1 + + + MOD(x, 2) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.MODULO(_x, 2.0);']) @@ -1770,12 +2106,29 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for RAMP function', () => { - const vars = readInlineModel(` - slope = 100 ~~| - start = 1 ~~| - end = 10 ~~| - y = RAMP(slope, start, end) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // slope = 100 ~~| + // start = 1 ~~| + // end = 10 ~~| + // y = RAMP(slope, start, end) ~~| + // `) + + const xmileVars = `\ + + 100 + + + 1 + + + 10 + + + RAMP(slope, start, end) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) expect(genJS(vars.get('_slope'))).toEqual(['_slope = 100.0;']) expect(genJS(vars.get('_start'))).toEqual(['_start = 1.0;']) @@ -1784,23 +2137,48 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for SIN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = SIN(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = SIN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + SIN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.SIN(_x);']) }) - it('should work for SMOOTH function (without initial value argument)', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTH(input, delay) ~~| - `) + it('should work for SMTH1 function (without initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTH(input, delay) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH1(input, delay) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) - expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_input'))).toEqual(['_input = 1.0;']) expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _input;']) expect(genJS(vars.get('__level1'), 'eval')).toEqual([ @@ -1809,14 +2187,28 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level1;']) }) - it('should work for SMOOTH function (with initial value argument)', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTHI(input, delay, 5) ~~| - `) + it('should work for SMTH1 function (with initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTHI(input, delay, 5) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH1(input, delay, 5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) - expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_input'))).toEqual(['_input = 1.0;']) expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = 5.0;']) expect(genJS(vars.get('__level1'), 'eval')).toEqual([ @@ -1825,14 +2217,28 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level1;']) }) - it('should work for SMOOTH3 function (without initial value argument)', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTH3(input, delay) ~~| - `) + it('should work for SMTH3 function (without initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTH3(input, delay) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH3(input, delay) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(6) - expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_input'))).toEqual(['_input = 1.0;']) expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _input;']) expect(genJS(vars.get('__level1'), 'eval')).toEqual([ @@ -1849,14 +2255,28 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level3;']) }) - it('should work for SMOOTH3 function (no dimensions with initial value argument)', () => { - const vars = readInlineModel(` - input = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y = SMOOTH3I(input, delay, 5) ~~| - `) + it('should work for SMTH3 function (no dimensions with initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // input = 1 ~~| + // delay = 2 ~~| + // y = SMOOTH3I(input, delay, 5) ~~| + // `) + + const xmileVars = `\ + + 1 + + + 2 + + + SMTH3(input, delay, 5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(6) - expect(genJS(vars.get('_input'))).toEqual(['_input = 3.0 + fns.PULSE(10.0, 10.0);']) + expect(genJS(vars.get('_input'))).toEqual(['_input = 1.0;']) expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = 5.0;']) expect(genJS(vars.get('__level1'), 'eval')).toEqual([ @@ -1873,19 +2293,43 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = __level3;']) }) - it('should work for SMOOTH3 function (1D with subscripted delay parameter and initial value argument)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay[DimA] = 2 ~~| - y[DimA] = SMOOTH3I(input[DimA], delay[DimA], 5) ~~| - `) + it('should work for SMTH3 function (1D with subscripted delay parameter and initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1 ~~| + // delay[DimA] = 2 ~~| + // y[DimA] = SMOOTH3I(input[DimA], delay[DimA], 5) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + 1 + + + + + + 2 + + + + + + SMTH3(input[DimA], delay[DimA], 5) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(6) - expect(genJS(vars.get('_input'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_input[i] = 3.0 + fns.PULSE(10.0, 10.0);', - '}' - ]) + expect(genJS(vars.get('_input'))).toEqual(['for (let i = 0; i < 2; i++) {', '_input[i] = 1.0;', '}']) expect(genJS(vars.get('_delay'))).toEqual(['for (let i = 0; i < 2; i++) {', '_delay[i] = 2.0;', '}']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual([ 'for (let i = 0; i < 2; i++) {', @@ -1920,19 +2364,40 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = __level3[i];', '}']) }) - it('should work for SMOOTH3 function (1D with non-subscripted delay parameter and initial value argument)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - input[DimA] = 3 + PULSE(10, 10) ~~| - delay = 2 ~~| - y[DimA] = SMOOTH3I(input[DimA], delay, 5) ~~| - `) + it('should work for SMTH3 function (1D with non-subscripted delay parameter and initial value argument)', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // input[DimA] = 1 ~~| + // delay = 2 ~~| + // y[DimA] = SMOOTH3I(input[DimA], delay, 5) ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + 1 + + + 2 + + + + + + SMTH3(input[DimA], delay, 5) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(6) - expect(genJS(vars.get('_input'))).toEqual([ - 'for (let i = 0; i < 2; i++) {', - '_input[i] = 3.0 + fns.PULSE(10.0, 10.0);', - '}' - ]) + expect(genJS(vars.get('_input'))).toEqual(['for (let i = 0; i < 2; i++) {', '_input[i] = 1.0;', '}']) expect(genJS(vars.get('_delay'))).toEqual(['_delay = 2.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual([ 'for (let i = 0; i < 2; i++) {', @@ -1968,31 +2433,77 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for SQRT function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = SQRT(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = SQRT(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + SQRT(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.SQRT(_x);']) }) it('should work for STEP function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = STEP(x, 10) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = STEP(x, 10) ~~| + // `) + + const xmileVars = `\ + + 1 + + + STEP(x, 10) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.STEP(_x, 10.0);']) }) it('should work for SUM function (single call)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - a[DimA] = 10, 20 ~~| - x = SUM(a[DimA!]) + 1 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // a[DimA] = 10, 20 ~~| + // x = SUM(a[DimA!]) + 1 ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + + SUM(a[*]) + 1 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(3) expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) @@ -2006,14 +2517,64 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for SUM function (multiple calls)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - DimB: B1, B2 ~~| - a[DimA] = 10, 20 ~~| - b[DimB] = 50, 60 ~~| - c[DimA] = 1, 2 ~~| - x = SUM(a[DimA!]) + SUM(b[DimB!]) + SUM(c[DimA!]) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // DimB: B1, B2 ~~| + // a[DimA] = 10, 20 ~~| + // b[DimB] = 50, 60 ~~| + // c[DimA] = 1, 2 ~~| + // x = SUM(a[DimA!]) + SUM(b[DimB!]) + SUM(c[DimA!]) ~~| + // `) + + const xmileDims = `\ + + + + + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + + + + + + 50 + + + 60 + + + + + + + + 1 + + + 2 + + + + SUM(a[*]) + SUM(b[*]) + SUM(c[*]) +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(7) expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) @@ -2039,11 +2600,35 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for SUM function (with nested function call)', () => { - const vars = readInlineModel(` - DimA: A1, A2 ~~| - a[DimA] = 10, 20 ~~| - x = SUM(IF THEN ELSE(a[DimA!] = 10, 0, a[DimA!])) + 1 ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2 ~~| + // a[DimA] = 10, 20 ~~| + // x = SUM(IF THEN ELSE(a[DimA!] = 10, 0, a[DimA!])) + 1 ~~| + // `) + + const xmileDims = `\ + + + +` + const xmileVars = `\ + + + + + + 10 + + + 20 + + + + SUM(IF a[*] = 10 THEN 0 ELSE a[*])) + 1 +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(3) expect(genJS(vars.get('_a[_a1]'), 'init-constants')).toEqual(['_a[0] = 10.0;']) expect(genJS(vars.get('_a[_a2]'), 'init-constants')).toEqual(['_a[1] = 20.0;']) @@ -2057,20 +2642,42 @@ describe('generateEquation (XMILE -> JS)', () => { }) it('should work for TAN function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = TAN(x) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = TAN(x) ~~| + // `) + + const xmileVars = `\ + + 1 + + + TAN(x) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(2) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('_y'))).toEqual(['_y = fns.TAN(_x);']) }) it('should work for TREND function', () => { - const vars = readInlineModel(` - x = 1 ~~| - y = TREND(x, 10, 2) ~~| - `) + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = TREND(x, 10, 2) ~~| + // `) + + const xmileVars = `\ + + 1 + + + TREND(x, 10, 2) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) expect(vars.size).toBe(4) expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) expect(genJS(vars.get('__level1'), 'init-levels')).toEqual(['__level1 = _x / (1.0 + 2.0 * 10.0);']) From 7d00640a8f57045696ed9b5f4d50518b88ad9d6a Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 13:11:46 -0700 Subject: [PATCH 57/77] fix: update generateFunctionCall to handle differences between Vensim and XMILE/Stella function names --- .../gen-equation-js-from-xmile.spec.ts | 26 ++++- packages/compile/src/generate/gen-expr.js | 98 +++++++++++++------ 2 files changed, 90 insertions(+), 34 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index 89a62289..25df0976 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -1976,8 +1976,28 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LN(_x);']) }) - // TODO: Implement this test - it.skip('should work for LOOKUP function', () => {}) + it('should work for LOOKUP function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x((0,0),(1,1),(2,2)) ~~| + // y = x(1.5) ~~| + // `) + + const xmileVars = `\ + + 0,1,2 + 0,1,2 + + + LOOKUP(x, 1.5) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'), 'decl')).toEqual(['const _x_data_ = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];']) + expect(genJS(vars.get('_x'), 'init-lookups')).toEqual(['_x = fns.createLookup(3, _x_data_);']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.LOOKUP(_x, 1.5);']) + }) it('should work for LOOKUPINV function', () => { // Equivalent Vensim model for reference: @@ -1992,7 +2012,7 @@ describe('generateEquation (XMILE -> JS)', () => { 0,1,2 - LOOKUP INVERT(x, 1.5) + LOOKUPINV(x, 1.5) ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index d05f5b98..328b8434 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -162,8 +162,28 @@ export function generateExpr(expr, ctx) { * @return {string} The generated C/JS code. */ function generateFunctionCall(callExpr, ctx) { - const fnId = callExpr.fnId + function generateSimpleFunctionCall(fnId) { + const args = callExpr.args.map(argExpr => generateExpr(argExpr, ctx)) + if (ctx.outFormat === 'js' && fnId === '_IF_THEN_ELSE') { + // When generating conditional expressions for JS target, since we can't rely on macros like we do for C, + // it is better to translate it into a ternary instead of relying on a built-in function (since the latter + // would require always evaluating both branches, while the former can be more optimized by the interpreter) + return `((${args[0]}) ? (${args[1]}) : (${args[2]}))` + } else { + // For simple functions, emit a C/JS function call with a generated C/JS expression for each argument + return `${fnRef(fnId, ctx)}(${args.join(', ')})` + } + } + function generateLookupFunctionCall(fnId) { + // For LOOKUP* functions, the first argument must be a reference to the lookup variable. Emit + // a C/JS function call with a generated C/JS expression for each remaining argument. + const cVarRef = ctx.cVarRef(callExpr.args[0]) + const cArgs = callExpr.args.slice(1).map(arg => generateExpr(arg, ctx)) + return `${fnRef(fnId, ctx)}(${cVarRef}, ${cArgs.join(', ')})` + } + + const fnId = callExpr.fnId switch (fnId) { // // @@ -174,46 +194,50 @@ function generateFunctionCall(callExpr, ctx) { // // + // Simple functions that are common to Vensim and XMILE/Stella case '_ABS': case '_ARCCOS': case '_ARCSIN': case '_ARCTAN': case '_COS': case '_EXP': - case '_GAMMA_LN': case '_IF_THEN_ELSE': - case '_INTEGER': case '_LN': case '_MAX': case '_MIN': - case '_MODULO': - case '_POW': - case '_POWER': - case '_PULSE': - case '_PULSE_TRAIN': - case '_QUANTUM': case '_RAMP': case '_SIN': case '_SQRT': case '_STEP': case '_TAN': + return generateSimpleFunctionCall(fnId) + + // Simple functions supported by Vensim only + case '_GAMMA_LN': + case '_INTEGER': + case '_MODULO': + case '_POW': + case '_POWER': + case '_PULSE_TRAIN': + case '_PULSE': + case '_QUANTUM': case '_WITH_LOOKUP': case '_XIDZ': - case '_ZIDZ': { - const args = callExpr.args.map(argExpr => generateExpr(argExpr, ctx)) + case '_ZIDZ': if (ctx.outFormat === 'js' && fnId === '_GAMMA_LN') { throw new Error(`${callExpr.fnName} function not yet implemented for JS code gen`) } - if (ctx.outFormat === 'js' && fnId === '_IF_THEN_ELSE') { - // When generating conditional expressions for JS target, since we can't rely on macros like we do for C, - // it is better to translate it into a ternary instead of relying on a built-in function (since the latter - // would require always evaluating both branches, while the former can be more optimized by the interpreter) - return `((${args[0]}) ? (${args[1]}) : (${args[2]}))` - } else { - // For simple functions, emit a C/JS function call with a generated C/JS expression for each argument - return `${fnRef(fnId, ctx)}(${args.join(', ')})` - } - } + return generateSimpleFunctionCall(fnId) + + // Simple functions supported by XMILE/Stella only + case '_INT': + // XMILE/Stella uses `INT`, but it is the same as the Vensim `INTEGER` function, + // which is the name used in the runtime function implementation + return generateSimpleFunctionCall('_INTEGER') + case '_MOD': + // XMILE/Stella uses `MOD`, but it is the same as the Vensim `MODULO` function, + // which is the name used in the runtime function implementation + return generateSimpleFunctionCall('_MODULO') // // @@ -225,17 +249,12 @@ function generateFunctionCall(callExpr, ctx) { // // + // Lookup functions supported by Vensim only case '_GET_DATA_BETWEEN_TIMES': case '_LOOKUP_BACKWARD': case '_LOOKUP_FORWARD': - case '_LOOKUP_INVERT': { - // For LOOKUP* functions, the first argument must be a reference to the lookup variable. Emit - // a C/JS function call with a generated C/JS expression for each remaining argument. - const cVarRef = ctx.cVarRef(callExpr.args[0]) - const cArgs = callExpr.args.slice(1).map(arg => generateExpr(arg, ctx)) - return `${fnRef(fnId, ctx)}(${cVarRef}, ${cArgs.join(', ')})` - } - + case '_LOOKUP_INVERT': + return generateLookupFunctionCall(fnId) case '_GAME': { // For the GAME function, emit a C/JS function call that has the synthesized game inputs lookup // as the first argument, followed by the default value argument from the function call @@ -244,6 +263,16 @@ function generateFunctionCall(callExpr, ctx) { return `${fnRef(fnId, ctx)}(${cLookupArg}, ${cDefaultArg})` } + // Lookup functions supported by XMILE/Stella only + case '_LOOKUP': + // XMILE/Stella has an explicit `LOOKUP` function while Vensim uses `x(y)` syntax, but + // underneath both are implemented at runtime by the `LOOKUP` function + return generateLookupFunctionCall('_LOOKUP') + case '_LOOKUPINV': + // XMILE/Stella uses `LOOKUPINV`, but it is the same as the Vensim `LOOKUP INVERT` function, + // which is the name used in the runtime function implementation + return generateLookupFunctionCall('_LOOKUP_INVERT') + // // // Level functions @@ -311,7 +340,12 @@ function generateFunctionCall(callExpr, ctx) { case '_SMOOTH': case '_SMOOTHI': case '_SMOOTH3': - case '_SMOOTH3I': { + case '_SMOOTH3I': + case '_SMTH1': + case '_SMTH3': { + // Note that Vensim uses `SMOOTH[I]` and `SMOOTH3[I]` while XMILE uses `SMTH1` and + // `SMTH3`, but otherwise they have been translated the same way during the read + // equations phase const smoothVar = Model.varWithRefId(ctx.variable.smoothVarRefId) return ctx.cVarRef(smoothVar.parsedEqn.lhs.varDef) } @@ -362,7 +396,9 @@ function generateFunctionCall(callExpr, ctx) { throw new Error(`Unexpected function '${fnId}' in code gen for '${ctx.variable.modelLHS}'`) case '_INITIAL': - // In init mode, only emit the initial expression without the INITIAL function call + case '_INIT': + // Note that Vensim uses `INITIAL` while XMILE uses `INIT`, but otherwise they are the same. + // In init mode, only emit the initial expression without the INITIAL function call. if (ctx.mode.startsWith('init')) { return generateExpr(callExpr.args[0], ctx) } else { From f7bd6b59938b39153921d5e3eb07b427f8020012 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 14:27:21 -0700 Subject: [PATCH 58/77] fix: update IF THEN ELSE parsing to handle case where it is nested in a function call or more complex expression --- .../gen-equation-js-from-xmile.spec.ts | 2 +- .../xmile/parse-xmile-variable-def.spec.ts | 59 +++++++++++--- .../src/xmile/parse-xmile-variable-def.ts | 76 +++++++++++++++---- 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index 25df0976..ffd69f0b 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -2645,7 +2645,7 @@ describe('generateEquation (XMILE -> JS)', () => { - SUM(IF a[*] = 10 THEN 0 ELSE a[*])) + 1 + SUM(IF a[*] = 10 THEN 0 ELSE a[*]) + 1 ` const mdl = xmile(xmileDims, xmileVars) const vars = readInlineModel(mdl) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 5556da27..26730359 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -5,7 +5,18 @@ import { describe, expect, it } from 'vitest' import type { XmlElement } from '@rgrove/parse-xml' import { parseXml } from '@rgrove/parse-xml' -import { binaryOp, call, exprEqn, lookupDef, lookupVarEqn, num, unaryOp, varDef, varRef } from '../ast/ast-builders' +import { + binaryOp, + call, + exprEqn, + lookupDef, + lookupVarEqn, + num, + parens, + unaryOp, + varDef, + varRef +} from '../ast/ast-builders' import { parseXmileVariableDef } from './parse-xmile-variable-def' @@ -523,6 +534,32 @@ describe('parseXmileVariableDef with ', () => { ]) }) + it('should parse an aux variable definition with XMILE-style "IF ... THEN ... ELSE ..." conditional expression (inside a function call)', () => { + const v = xml(` + + ABS(IF c > 10 THEN y + 3 ELSE z * 5) + 1 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x'), + binaryOp( + call( + 'ABS', + call( + 'IF THEN ELSE', + binaryOp(varRef('c'), '>', num(10)), + binaryOp(varRef('y'), '+', num(3)), + binaryOp(varRef('z'), '*', num(5)) + ) + ), + '+', + num(1) + ) + ) + ]) + }) + it('should parse an aux variable definition with XMILE-style "IF ... THEN ... ELSE ..." conditional expression (with nested IF THEN ELSE)', () => { const v = xml(` @@ -542,15 +579,17 @@ describe('parseXmileVariableDef with ', () => { ':AND:', binaryOp(varRef('total_weeks_vacation_taken'), '<', num(4)) ), - call( - 'IF THEN ELSE', - binaryOp( - binaryOp(varRef('TIME'), '-', varRef('Last_Vacation_Start')), - '>=', - varRef('time_working_before_vacation') - ), - num(1), - num(0) + parens( + call( + 'IF THEN ELSE', + binaryOp( + binaryOp(varRef('TIME'), '-', varRef('Last_Vacation_Start')), + '>=', + varRef('time_working_before_vacation') + ), + num(1), + num(0) + ) ), num(0) ) diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 29b23a83..1d8f6a90 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -406,6 +406,7 @@ function parseFloatAttr(varElem: XmlElement, elem: XmlElement, attrName: string) * - Simple: IF x > 0 THEN 1 ELSE 0 * - Nested: IF x > 0 THEN IF y > 0 THEN 2 ELSE 1 ELSE 0 * - Complex: IF a > 0 THEN IF b > 0 THEN IF c > 0 THEN 3 ELSE 2 ELSE 1 ELSE 0 + * - In function call: ABS(IF x > 0 THEN 1 ELSE 0) + 1 */ function convertConditionalExpressions(exprText: string): string { // XXX: This function implementation was generated by an LLM and is rather complex. @@ -415,21 +416,19 @@ function convertConditionalExpressions(exprText: string): string { // Trim whitespace and normalize newlines const normalizedText = exprText.trim().replace(/\s+/g, ' ') - // Handle nested conditionals wrapped in parentheses - let textToParse = normalizedText - if (textToParse.startsWith('(') && textToParse.endsWith(')')) { - textToParse = textToParse.slice(1, -1).trim() - } - - // Find the outermost IF-THEN-ELSE pattern - const ifMatch = textToParse.match(/^IF\s+(.+)$/i) + // Find the first IF-THEN-ELSE pattern in the expression + const ifMatch = normalizedText.match(/\bIF\s+(.+)$/i) if (!ifMatch) { return exprText } + // Find the position of the IF keyword + const ifIndex = normalizedText.search(/\bIF\s+/i) + const beforeIf = normalizedText.substring(0, ifIndex) + const afterIf = normalizedText.substring(ifIndex + 3).trim() // Skip "IF " + // Parse the condition part (everything after IF until THEN) - const remainingText = ifMatch[1] - const thenMatch = remainingText.match(/^(.+?)\s+THEN\s+(.+)$/i) + const thenMatch = afterIf.match(/^(.+?)\s+THEN\s+(.+)$/i) if (!thenMatch) { return exprText } @@ -438,7 +437,6 @@ function convertConditionalExpressions(exprText: string): string { const afterThen = thenMatch[2] // Find the ELSE part by looking for the outermost ELSE that's not inside parentheses - // We need to parse the THEN expression to understand its structure let elseIndex = -1 let parenCount = 0 let inQuotes = false @@ -486,7 +484,50 @@ function convertConditionalExpressions(exprText: string): string { // Skip "ELSE " const trueExpr = afterThen.substring(0, elseIndex).trim() - const falseExpr = afterThen.substring(elseIndex + 5).trim() + let falseExpr = afterThen.substring(elseIndex + 5).trim() + + // Find the end of the false expression by looking for the end of the conditional. The conditional + // ends when we reach a closing parenthesis that brings us back to the original level. + let endIndex = -1 + parenCount = 0 + inQuotes = false + quoteChar = '' + + for (let i = 0; i < falseExpr.length; i++) { + const char = falseExpr[i] + + // Handle quoted strings + if ((char === '"' || char === "'") && (i === 0 || falseExpr[i - 1] !== '\\')) { + if (!inQuotes) { + inQuotes = true + quoteChar = char + } else if (char === quoteChar) { + inQuotes = false + quoteChar = '' + } + continue + } + + if (inQuotes) { + continue + } + + // Handle parentheses for nested expressions + if (char === '(') { + parenCount++ + } else if (char === ')') { + if (parenCount === 0) { + // This is the closing parenthesis that ends the conditional + endIndex = i + break + } + parenCount-- + } + } + + if (endIndex !== -1) { + falseExpr = falseExpr.substring(0, endIndex).trim() + } // Recursively parse nested conditionals in the true and false expressions const convertedTrueExpr = convertConditionalExpressions(trueExpr) @@ -500,5 +541,14 @@ function convertConditionalExpressions(exprText: string): string { // Remove outer parentheses from condition .replace(/^\((.+)\)$/, '$1') - return `IF THEN ELSE(${convertedCondition}, ${convertedTrueExpr}, ${convertedFalseExpr})` + // Find any text after the conditional expression and calculate where the original conditional + // expression ends in the normalized text + const elseStartInAfterIf = afterIf.indexOf(' ELSE ') + 6 + const falseExprStartInAfterIf = elseStartInAfterIf + const falseExprEndInAfterIf = falseExprStartInAfterIf + falseExpr.length + + const conditionalEndInNormalizedText = ifIndex + 3 + falseExprEndInAfterIf + const afterConditional = normalizedText.substring(conditionalEndInNormalizedText).trim() + + return `${beforeIf}IF THEN ELSE(${convertedCondition}, ${convertedTrueExpr}, ${convertedFalseExpr})${afterConditional}` } From bd8cb5621bfd2377ff203e968b78b46fa7b9a909 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 5 Sep 2025 14:42:50 -0700 Subject: [PATCH 59/77] fix: improve indentation in xmile function --- packages/compile/src/_tests/test-support.ts | 33 +++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/compile/src/_tests/test-support.ts b/packages/compile/src/_tests/test-support.ts index 7e76faee..b04b1047 100644 --- a/packages/compile/src/_tests/test-support.ts +++ b/packages/compile/src/_tests/test-support.ts @@ -173,12 +173,19 @@ export function parseInlineXmileModel(mdlContent: string, modelDir?: string): Pa } export function xmile(dimensions: string, variables: string): string { + function indent(text: string, indent: string): string { + return text + .split('\n') + .map(line => indent + line) + .join('\n') + } + let dims: string if (dimensions.length > 0) { dims = `\ - - ${dimensions} - ` + +${indent(dimensions, ' ')} + ` } else { dims = '' } @@ -186,29 +193,29 @@ export function xmile(dimensions: string, variables: string): string { let vars: string if (variables.length > 0) { vars = `\ - - ${variables} - ` + +${indent(variables, ' ')} + ` } else { vars = '' } return `\ -
+
Ventana Systems, xmutil Vensim, xmutil -
- +
+ 0 100
1
- + ${dims} - - ${vars} - + +${vars} +
` } From cf8f063730a7417effaeb5d5e728a759f802cb33 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 7 Sep 2025 08:58:09 -0700 Subject: [PATCH 60/77] fix: update create package to support XMILE file types + change instances of "mdl" to "model" --- packages/create/src/index.ts | 28 ++++----- packages/create/src/step-config.ts | 39 +++++++------ packages/create/src/step-mdl.ts | 72 ----------------------- packages/create/src/step-model-file.ts | 80 ++++++++++++++++++++++++++ packages/create/src/step-template.ts | 15 +++-- 5 files changed, 123 insertions(+), 111 deletions(-) delete mode 100644 packages/create/src/step-mdl.ts create mode 100644 packages/create/src/step-model-file.ts diff --git a/packages/create/src/index.ts b/packages/create/src/index.ts index b80e3255..37519427 100644 --- a/packages/create/src/index.ts +++ b/packages/create/src/index.ts @@ -15,7 +15,7 @@ import { chooseInstallDeps } from './step-deps' import { chooseProjectDir } from './step-directory' import { chooseInstallEmsdk } from './step-emsdk' import { chooseGitInit } from './step-git' -import { chooseMdlFile } from './step-mdl' +import { chooseModelFile } from './step-model-file' import { chooseTemplate, copyTemplate } from './step-template' export async function main(): Promise { @@ -46,32 +46,32 @@ export async function main(): Promise { const template = await chooseTemplate(args) console.log() - // Prompt the user to select an mdl file - let mdlPath = await chooseMdlFile(projDir) - const mdlExisted = mdlPath !== undefined + // Prompt the user to select a model file + let modelPath = await chooseModelFile(projDir) + const modelExisted = modelPath !== undefined console.log() if (!args.dryRun) { // Copy the template files to the project directory - await copyTemplate(template, projDir, pkgManager, configDirExisted, mdlExisted) + await copyTemplate(template, projDir, pkgManager, configDirExisted, modelExisted) console.log() } - if (mdlPath === undefined) { - // There wasn't already an mdl file in the project directory, so we will use + if (modelPath === undefined) { + // There wasn't already a model file in the project directory, so we will use // the one supplied by the template. The template is expected to have a // `model/MODEL_NAME.mdl` file, which gets renamed to `model/sample.mdl` - // in the `copyTemplate` step. Note that `chooseMdlFile` returns a + // in the `copyTemplate` step. Note that `chooseModelFile` returns a // POSIX-style relative path, so we will also use a relative path here. - mdlPath = `model${posix.sep}sample.mdl` + modelPath = `model${posix.sep}sample.mdl` } // Prompt the user to select a code generation format const genFormat = await chooseCodeFormat() if (!args.dryRun) { - // Update the `sde.config.js` file to use the chosen mdl file - await updateSdeConfig(projDir, mdlPath, genFormat) + // Update the `sde.config.js` file to use the chosen model file + await updateSdeConfig(projDir, modelPath, genFormat) // Generate sample `checks.yaml` and `comparisons.yaml` files if needed const modelCheckFilesExist = @@ -90,11 +90,11 @@ export async function main(): Promise { ) console.log() } else { - // There wasn't already a `config` directory, but there was already an mdl file, + // There wasn't already a `config` directory, but there was already a model file, // so offer to set up CSV files const configDirExistsNow = existsSync(resolvePath(projDir, 'config')) - if (configDirExistsNow && mdlExisted && !args.dryRun) { - await chooseGenConfig(projDir, mdlPath) + if (configDirExistsNow && modelExisted && !args.dryRun) { + await chooseGenConfig(projDir, modelPath) console.log() } } diff --git a/packages/create/src/step-config.ts b/packages/create/src/step-config.ts index 9debfe4a..73db9f24 100644 --- a/packages/create/src/step-config.ts +++ b/packages/create/src/step-config.ts @@ -74,7 +74,7 @@ const sampleComparisonsContent = `\ at: 20 ` -export async function updateSdeConfig(projDir: string, mdlPath: string, genFormat: string): Promise { +export async function updateSdeConfig(projDir: string, modelPath: string, genFormat: string): Promise { // Read the `sde.config.js` file from the template const configPath = joinPath(projDir, 'sde.config.js') let configText = await readFile(configPath, 'utf8') @@ -82,8 +82,8 @@ export async function updateSdeConfig(projDir: string, mdlPath: string, genForma // Set the code generation format to the chosen format configText = configText.replace(`const genFormat = 'js'`, `const genFormat = '${genFormat}'`) - // Replace instances of `model/MODEL_NAME.mdl` with the path to the chosen mdl file - configText = configText.replaceAll('model/MODEL_NAME.mdl', mdlPath) + // Replace instances of `model/MODEL_NAME.mdl` with the path to the chosen model file + configText = configText.replaceAll('model/MODEL_NAME.mdl', modelPath) // Write the updated file await writeFile(configPath, configText) @@ -126,17 +126,17 @@ export async function generateSampleYamlFiles(projDir: string): Promise { await generateYaml(projDir, 'comparisons', sampleComparisonsContent) } -export async function chooseGenConfig(projDir: string, mdlPath: string): Promise { - // TODO: For now we eagerly read the mdl file; maybe change this to only load it if +export async function chooseGenConfig(projDir: string, modelPath: string): Promise { + // TODO: For now we eagerly read the model file; maybe change this to only load it if // the user chooses to generate graph and/or slider config let mdlVars: MdlVariable[] try { // Get the list of variables available in the model - mdlVars = await readModelVars(projDir, mdlPath) + mdlVars = await readModelVars(projDir, modelPath) } catch (e) { console.log(e) ora( - yellow('The mdl file failed to load. We will continue setting things up, and you can diagnose the issue later.') + yellow('The model file failed to load. We will continue setting things up, and you can diagnose the issue later.') ).warn() return } @@ -459,35 +459,36 @@ function escapeCsvField(s: string): string { return s.includes(',') ? `"${s}"` : s } -async function readModelVars(projDir: string, mdlPath: string): Promise { +async function readModelVars(projDir: string, modelPath: string): Promise { // TODO: This function contains a subset of the logic from `sde-generate.js` in // the `cli` package; should revisit - // let { modelDirname, modelName, modelPathname } = modelPathProps(model) // Ensure the `build` directory exists (under the `sde-prep` directory) const buildDir = resolvePath(projDir, 'sde-prep', 'build') await mkdir(buildDir, { recursive: true }) - // Use an empty model spec; this will make SDE look at all variables in the mdl + // Use an empty model spec; this will make SDE look at all variables in the model file const spec = {} - // Try parsing the mdl file to generate the list of variables + // Try parsing the model file to generate the list of variables // TODO: This depends on some `compile` package APIs that are not yet considered stable. // Ideally we'd use an API that does not write files but instead returns an in-memory // object in a specified format. // Read the model file - const mdlFile = resolvePath(projDir, mdlPath) - const mdlContent = await readFile(mdlFile, 'utf8') + const modelFile = resolvePath(projDir, modelPath) + const modelContent = await readFile(modelFile, 'utf8') + + // Determine the model kind based on the presence of an `` tag + const modelKind = modelContent.includes(' { - // Find all `.mdl` files in the project directory - // From https://stackoverflow.com/a/45130990 - async function getFiles(dir: string): Promise { - const dirents = await readdir(dir, { withFileTypes: true }) - const files = await Promise.all( - dirents.map(dirent => { - const res = resolvePath(dir, dirent.name) - return dirent.isDirectory() ? getFiles(res) : res - }) - ) - return files.flat() - } - const allFiles = await getFiles(projDir) - const mdlFiles = allFiles - // Only include files ending with '.mdl' - .filter(f => f.endsWith('.mdl')) - // Convert to a relative path with POSIX path separators (we want - // paths in the 'sde.config.js' file to use POSIX path style only, - // since that works on any OS including Windows) - .map(f => relative(projDir, f).replaceAll(sep, posix.sep)) - const mdlChoices = mdlFiles.map(f => { - return { - title: f, - value: f - } as Choice - }) - - let mdlFile: string - if (mdlFiles.length === 0) { - // No mdl files found; return undefined so that the mdl from the template is copied over - ora(yellow(`No mdl files were found in "${projDir}". The mdl file from the template will be used instead.`)).warn() - mdlFile = undefined - } else if (mdlFiles.length === 1) { - // Only one mdl file - mdlFile = mdlFiles[0] - ora().succeed(`Found "${mdlFile}", will configure the project to use that mdl file.`) - } else { - // Multiple mdl files found; allow the user to choose one - // TODO: Eventually we should allow the user to choose to flatten if there are multiple submodels - const options = await prompts( - [ - { - type: 'select', - name: 'mdlFile', - message: 'It looks like there are multiple mdl files. Which one would you like to use?', - choices: mdlChoices - } - ], - { - onCancel: () => { - ora().info(dim('Operation cancelled.')) - process.exit(0) - } - } - ) - mdlFile = options.mdlFile - ora(green(`Using "${bold(mdlFile)}" as the model for the project.`)).succeed() - } - - return mdlFile -} diff --git a/packages/create/src/step-model-file.ts b/packages/create/src/step-model-file.ts new file mode 100644 index 00000000..4deb6d41 --- /dev/null +++ b/packages/create/src/step-model-file.ts @@ -0,0 +1,80 @@ +// Copyright (c) 2022 Climate Interactive / New Venture Fund + +import { readdir } from 'fs/promises' +import { relative, resolve as resolvePath, sep, posix, extname } from 'path' + +import { bold, dim, green, yellow } from 'kleur/colors' +import ora from 'ora' +import type { Choice } from 'prompts' +import prompts from 'prompts' + +// The set of supported model file extensions; this should match the set defined in the cli package +const supportedModelFileExtensions = new Set(['.mdl', '.xmile', '.stmx', '.itmx']) + +export async function chooseModelFile(projDir: string): Promise { + // Find all supported model files in the project directory + // From https://stackoverflow.com/a/45130990 + async function getFiles(dir: string): Promise { + const dirents = await readdir(dir, { withFileTypes: true }) + const files = await Promise.all( + dirents.map(dirent => { + const res = resolvePath(dir, dirent.name) + return dirent.isDirectory() ? getFiles(res) : res + }) + ) + return files.flat() + } + const allFiles = await getFiles(projDir) + const modelFiles = allFiles + // Only include files that have a supported extension + .filter(f => { + const ext = extname(f).toLowerCase() + return supportedModelFileExtensions.has(ext) + }) + // Convert to a relative path with POSIX path separators (we want + // paths in the 'sde.config.js' file to use POSIX path style only, + // since that works on any OS including Windows) + .map(f => relative(projDir, f).replaceAll(sep, posix.sep)) + const modelChoices = modelFiles.map(f => { + return { + title: f, + value: f + } as Choice + }) + + let modelFile: string + if (modelFiles.length === 0) { + // No model files found; return undefined so that the model from the template is copied over + ora( + yellow(`No model files were found in "${projDir}". The model file from the template will be used instead.`) + ).warn() + modelFile = undefined + } else if (modelFiles.length === 1) { + // Only one model file + modelFile = modelFiles[0] + ora().succeed(`Found "${modelFile}", will configure the project to use that model file.`) + } else { + // Multiple model files found; allow the user to choose one + // TODO: Eventually we should allow the user to choose to flatten if there are multiple submodels + const options = await prompts( + [ + { + type: 'select', + name: 'modelFile', + message: 'It looks like there are multiple model files. Which one would you like to use?', + choices: modelChoices + } + ], + { + onCancel: () => { + ora().info(dim('Operation cancelled.')) + process.exit(0) + } + } + ) + modelFile = options.modelFile + ora(green(`Using "${bold(modelFile)}" as the model for the project.`)).succeed() + } + + return modelFile +} diff --git a/packages/create/src/step-template.ts b/packages/create/src/step-template.ts index 83e956b5..923f36c7 100644 --- a/packages/create/src/step-template.ts +++ b/packages/create/src/step-template.ts @@ -101,7 +101,7 @@ export async function copyTemplate( projDir: string, pkgManager: string, configDirExisted: boolean, - mdlExisted: boolean + modelExisted: boolean ): Promise { // Show a spinner while copying the template files const templateSpinner = ora('Copying template files...').start() @@ -130,9 +130,9 @@ export async function copyTemplate( rmSync(joinPath(tmpDir, 'config'), { recursive: true, force: true }) } - if (mdlExisted) { - // There is already an mdl file in the project directory; remove the `model` - // directory (including the mdl file and the model-check yaml files) from the + if (modelExisted) { + // There is already a model file in the project directory; remove the `model` + // directory (including the model file and the model-check yaml files) from the // template so that we don't copy them into the project directory rmSync(joinPath(tmpDir, 'model'), { recursive: true, force: true }) } @@ -143,10 +143,13 @@ export async function copyTemplate( errorOnExist: false }) - if (!mdlExisted) { - // There wasn't already an mdl file in the project directory, so we will use + if (!modelExisted) { + // There wasn't already a model file in the project directory, so we will use // the one supplied by the template. Rename it from `MODEL_NAME.mdl` to // `sample.mdl`. + // TODO: For now we assume that all templates include a sample model in Vensim + // format. This will need to be updated if we add templates that include a + // sample model in a different format. renameSync(joinPath(projDir, 'model', 'MODEL_NAME.mdl'), joinPath(projDir, 'model', 'sample.mdl')) } From f24f43fc8de6ba2993449d37bb2979c3547199e4 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 7 Sep 2025 08:59:16 -0700 Subject: [PATCH 61/77] test: update step-config tests to verify handling of XMILE files in addition to Vensim files --- packages/create/tests/step-config.spec.ts | 184 +++++++++++++--------- 1 file changed, 109 insertions(+), 75 deletions(-) diff --git a/packages/create/tests/step-config.spec.ts b/packages/create/tests/step-config.spec.ts index 2d2451dd..954b2a32 100644 --- a/packages/create/tests/step-config.spec.ts +++ b/packages/create/tests/step-config.spec.ts @@ -60,77 +60,57 @@ async function respondAndWaitForPrompt( }) } -describe('step - read model variables and create config files', () => { - it('should read model variables and suggest input/output variables to include', async () => { - // Create a scratch directory - const scratchDir = resolvePath(testsDir, dirs.scratch) - if (existsSync(scratchDir)) { - rmSync(scratchDir, { recursive: true, force: true }) - } - mkdirSync(scratchDir) - - // Add a sample model file to the scratch directory - const sampleMdlContent = `\ -{UTF-8} - -X = TIME - ~~| - -Y = 0 - ~ [-10,10,0.1] - ~ - | +async function runAndVerify(sampleModelContent: string, sampleModelExt: string) { + // Create a scratch directory + const scratchDir = resolvePath(testsDir, dirs.scratch) + if (existsSync(scratchDir)) { + rmSync(scratchDir, { recursive: true, force: true }) + } + mkdirSync(scratchDir) -Z = X + Y - ~~| + // Add a sample model file to the scratch directory + writeFileSync(resolvePath(scratchDir, `sample.${sampleModelExt}`), sampleModelContent) -INITIAL TIME = 2000 ~~| -FINAL TIME = 2100 ~~| -TIME STEP = 1 ~~| -SAVEPER = TIME STEP ~~| -` - writeFileSync(resolvePath(scratchDir, 'sample.mdl'), sampleMdlContent) + // Run the create command + const { stdin, stdout } = runCreate([dirs.scratch]) - // Run the create command - const { stdin, stdout } = runCreate([dirs.scratch]) + // Wait for the template prompt + await respondAndWaitForPrompt(stdin!, stdout!, undefined, promptMessages.template) - // Wait for the template prompt - await respondAndWaitForPrompt(stdin!, stdout!, undefined, promptMessages.template) + // Press enter to accept the default template then wait for the wasm prompt + await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.wasm) - // Press enter to accept the default template then wait for the wasm prompt - await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.wasm) + // Press enter to accept the default project kind (JS) then wait for the configure graph prompt + await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.configGraph) - // Press enter to accept the default project kind (JS) then wait for the configure graph prompt - await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.configGraph) + // Press enter to accept the default choice (yes, configure a graph) + await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.chooseOutputs) - // Press enter to accept the default choice (yes, configure a graph) - await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.chooseOutputs) + // Select the two suggested variables, press enter to accept, then wait for the configure sliders prompt + await respondAndWaitForPrompt( + stdin!, + stdout!, + `${keyCodes.space}${keyCodes.down}${keyCodes.space}${keyCodes.enter}`, + promptMessages.configSliders + ) - // Select the two suggested variables, press enter to accept, then wait for the configure sliders prompt - await respondAndWaitForPrompt( - stdin!, - stdout!, - `${keyCodes.space}${keyCodes.down}${keyCodes.space}${keyCodes.enter}`, - promptMessages.configSliders - ) + // Press enter to accept the default choice (yes, configure sliders) + await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.chooseInputs) - // Press enter to accept the default choice (yes, configure sliders) - await respondAndWaitForPrompt(stdin!, stdout!, keyCodes.enter, promptMessages.chooseInputs) + // Select the one suggested variable, press enter to accept, then wait for the install dependencies prompt + await respondAndWaitForPrompt(stdin!, stdout!, `${keyCodes.space}${keyCodes.enter}`, promptMessages.deps) - // Select the one suggested variable, press enter to accept, then wait for the install dependencies prompt - await respondAndWaitForPrompt(stdin!, stdout!, `${keyCodes.space}${keyCodes.enter}`, promptMessages.deps) + // Enter "n" to skip installing dependencies + await respondAndWaitForPrompt(stdin!, stdout!, `n${keyCodes.enter}`, promptMessages.git) - // Enter "n" to skip installing dependencies - await respondAndWaitForPrompt(stdin!, stdout!, `n${keyCodes.enter}`, promptMessages.git) - - // Enter "n" to skip initializing a git repository - const msg = `\ + // Enter "n" to skip initializing a git repository + const msg = `\ You can now cd into the fixtures/scratch-dir project directory. Run pnpm dev to start the local dev server. CTRL-C to close.` - await respondAndWaitForPrompt(stdin!, stdout!, `n${keyCodes.enter}`, msg) + await respondAndWaitForPrompt(stdin!, stdout!, `n${keyCodes.enter}`, msg) - // Verify the generated `config/colors.csv` file - const expectedColors = `\ + // Verify the generated `config/colors.csv` file + const expectedColors = `\ id,hex code,name,comment blue,#0072b2,, red,#d33700,, @@ -138,38 +118,92 @@ green,#53bb37,, gray,#a7a9ac,, black,#000000,, ` - const actualColors = readFileSync(resolvePath(scratchDir, 'config', 'colors.csv'), 'utf8') - expect(actualColors).toEqual(expectedColors) + const actualColors = readFileSync(resolvePath(scratchDir, 'config', 'colors.csv'), 'utf8') + expect(actualColors).toEqual(expectedColors) - // Verify the generated `config/graphs.csv` file - const expectedGraphs = `\ + // Verify the generated `config/graphs.csv` file + const expectedGraphs = `\ id,side,parent menu,graph title,menu title,mini title,vensim graph,kind,modes,units,alternate,unused 1,unused 2,unused 3,x axis min,x axis max,x axis label,unused 4,unused 5,y axis min,y axis max,y axis soft max,y axis label,y axis format,unused 6,unused 7,plot 1 variable,plot 1 source,plot 1 style,plot 1 label,plot 1 color,plot 1 unused 1,plot 1 unused 2,plot 2 variable,plot 2 source,plot 2 style,plot 2 label,plot 2 color,plot 2 unused 1,plot 2 unused 2,plot 3 variable,plot 3 source,plot 3 style,plot 3 label,plot 3 color,plot 3 unused 1,plot 3 unused 2,plot 4 variable,plot 4 source,plot 4 style,plot 4 label,plot 4 color,plot 4 unused 1,plot 4 unused 2,plot 5 variable,plot 5 source,plot 5 style,plot 5 label,plot 5 color,plot 5 unused 1,plot 5 unused 2,plot 6 variable,plot 6 source,plot 6 style,plot 6 label,plot 6 color,plot 6 unused 1,plot 6 unused 2,plot 7 variable,plot 7 source,plot 7 style,plot 7 label,plot 7 color,plot 7 unused 1,plot 7 unused 2,plot 8 variable,plot 8 source,plot 8 style,plot 8 label,plot 8 color,plot 8 unused 1,plot 8 unused 2,plot 9 variable,plot 9 source,plot 9 style,plot 9 label,plot 9 color,plot 9 unused 1,plot 9 unused 2,plot 10 variable,plot 10 source,plot 10 style,plot 10 label,plot 10 color,plot 10 unused 1,plot 10 unused 2,plot 11 variable,plot 11 source,plot 11 style,plot 11 label,plot 11 color,plot 11 unused 1,plot 11 unused 2,plot 12 variable,plot 12 source,plot 12 style,plot 12 label,plot 12 color,plot 12 unused 1,plot 12 unused 2,plot 13 variable,plot 13 source,plot 13 style,plot 13 label,plot 13 color,plot 13 unused 1,plot 13 unused 2,plot 14 variable,plot 14 source,plot 14 style,plot 14 label,plot 14 color,plot 14 unused 1,plot 14 unused 2,plot 15 variable,plot 15 source,plot 15 style,plot 15 label,plot 15 color,plot 15 unused 1,plot 15 unused 2 1,,Graphs,Graph Title,,,,line,,,,,,,,,,,,,,,,,,,X,,line,X,blue,,,Z,,line,Z,red,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ` - const actualGraphs = readFileSync(resolvePath(scratchDir, 'config', 'graphs.csv'), 'utf8') - expect(actualGraphs).toEqual(expectedGraphs) + const actualGraphs = readFileSync(resolvePath(scratchDir, 'config', 'graphs.csv'), 'utf8') + expect(actualGraphs).toEqual(expectedGraphs) - // Verify the generated `config/inputs.csv` file - const expectedInputs = `\ + // Verify the generated `config/inputs.csv` file + const expectedInputs = `\ id,input type,viewid,varname,label,view level,group name,slider min,slider max,slider/switch default,slider step,units,format,reversed,range 2 start,range 3 start,range 4 start,range 5 start,range 1 label,range 2 label,range 3 label,range 4 label,range 5 label,enabled value,disabled value,controlled input ids,listing label,description 1,slider,view1,Y,Y,,Sliders,-1,1,0,0.1,(units),,,,,,,,,,,,,,,, ` - const actualInputs = readFileSync(resolvePath(scratchDir, 'config', 'inputs.csv'), 'utf8') - expect(actualInputs).toEqual(expectedInputs) + const actualInputs = readFileSync(resolvePath(scratchDir, 'config', 'inputs.csv'), 'utf8') + expect(actualInputs).toEqual(expectedInputs) - // Verify the generated `config/outputs.csv` file - const expectedOutputs = `\ + // Verify the generated `config/outputs.csv` file + const expectedOutputs = `\ variable name ` - const actualOutputs = readFileSync(resolvePath(scratchDir, 'config', 'outputs.csv'), 'utf8') - expect(actualOutputs).toEqual(expectedOutputs) + const actualOutputs = readFileSync(resolvePath(scratchDir, 'config', 'outputs.csv'), 'utf8') + expect(actualOutputs).toEqual(expectedOutputs) - // Verify the generated `config/strings.csv` file - const expectedStrings = `\ + // Verify the generated `config/strings.csv` file + const expectedStrings = `\ id,string __model_name,My Model ` - const actualStrings = readFileSync(resolvePath(scratchDir, 'config', 'strings.csv'), 'utf8') - expect(actualStrings).toEqual(expectedStrings) + const actualStrings = readFileSync(resolvePath(scratchDir, 'config', 'strings.csv'), 'utf8') + expect(actualStrings).toEqual(expectedStrings) +} + +describe('step - read model variables and create config files', () => { + it('should read model variables from a Vensim model and suggest input/output variables to include', async () => { + const sampleModelContent = `\ +{UTF-8} + +X = TIME + ~~| + +Y = 0 + ~ [-10,10,0.1] + ~ + | + +Z = X + Y + ~~| + +INITIAL TIME = 2000 ~~| +FINAL TIME = 2100 ~~| +TIME STEP = 1 ~~| +SAVEPER = TIME STEP ~~| +` + await runAndVerify(sampleModelContent, 'mdl') + }) + + it('should read model variables from a Stella/XMILE model and suggest input/output variables to include', async () => { + const sampleModelContent = `\ + +
+ + Ventana Systems, xmutil + Vensim, xmutil +
+ + 2000 + 2100 +
1
+
+ + + + TIME + + + 0 + + + X + Y + + + +
` + await runAndVerify(sampleModelContent, 'stmx') }) }) From 47be15a29dd13089dd333a7b7005119a0ea37e9a Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 16 Jan 2026 14:23:17 -0800 Subject: [PATCH 62/77] build: update lock file --- pnpm-lock.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8de36380..2faa9b26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -694,6 +694,9 @@ importers: packages/parse: dependencies: + '@rgrove/parse-xml': + specifier: ^4.1.0 + version: 4.2.0 antlr4: specifier: 4.12.0 version: 4.12.0 @@ -1450,6 +1453,10 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@rgrove/parse-xml@4.2.0': + resolution: {integrity: sha512-UuBOt7BOsKVOkFXRe4Ypd/lADuNIfqJXv8GvHqtXaTYXPPKkj2nS2zPllVsrtRjcomDhIJVBnZwfmlI222WH8g==} + engines: {node: '>=14.0.0'} + '@rollup/plugin-node-resolve@16.0.3': resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} engines: {node: '>=14.0.0'} @@ -3995,6 +4002,8 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@rgrove/parse-xml@4.2.0': {} + '@rollup/plugin-node-resolve@16.0.3(rollup@4.53.3)': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.53.3) From 0da1d4f54c4e1260037200affd3a2e5b48568eae Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 16 Jan 2026 15:39:16 -0800 Subject: [PATCH 63/77] fix: add tooldat option for `sde test` command --- packages/cli/src/sde-test.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/sde-test.js b/packages/cli/src/sde-test.js index 8e16a62a..0fa66896 100644 --- a/packages/cli/src/sde-test.js +++ b/packages/cli/src/sde-test.js @@ -18,6 +18,10 @@ export let builder = { choices: ['js', 'c'], default: 'js' }, + tooldat: { + describe: 'pathname of the tool DAT file to compare to SDE output', + type: 'string' + }, builddir: { describe: 'build directory', type: 'string', @@ -53,12 +57,19 @@ export let test = async (model, opts) => { // Convert the TSV log file to a DAT file in the same directory. opts.dat = true await log(logPathname, opts) - // Assume there is a Vensim-created DAT file named {modelName}.dat in the model directory. - // Compare it to the SDE DAT file. - let vensimPathname = path.join(modelDirname, `${modelName}.dat`) + let toolDatPathname + if (opts.tooldat) { + // Use the provided DAT file for comparison + toolDatPathname = opts.tooldat + } else { + // Assume there is a DAT file created by the modeling tool named {modelName}.dat + // in the model directory + toolDatPathname = path.join(modelDirname, `${modelName}.dat`) + } let p = path.parse(logPathname) - let sdePathname = path.format({ dir: p.dir, name: p.name, ext: '.dat' }) - let noDiffs = await compare(vensimPathname, sdePathname, opts) + let sdeDatPathname = path.format({ dir: p.dir, name: p.name, ext: '.dat' }) + // Compare SDE-generated DAT file to the tool-generated DAT file + let noDiffs = await compare(toolDatPathname, sdeDatPathname, opts) if (!noDiffs) { // Exit with a non-zero error code if differences were detected console.error() From af199ba9e13ed04bc8d6d6800479c0133706e59e Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 16 Jan 2026 15:39:50 -0800 Subject: [PATCH 64/77] test: update modeltests script to convert Stella-generated CSV to DAT before comparing --- package.json | 1 + pnpm-lock.yaml | 3 ++ tests/csv-to-dat.js | 91 +++++++++++++++++++++++++++++++++++++++++++++ tests/modeltests | 25 ++++++++++++- 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 tests/csv-to-dat.js diff --git a/package.json b/package.json index 8f4d950b..74fb00e4 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@typescript-eslint/parser": "^8.46.0", "@vitest/browser": "^4.0.16", "@vitest/coverage-v8": "^4.0.16", + "csv-parse": "^5.3.3", "eslint": "^9.37.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-eslint-comments": "^3.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2faa9b26..21cf369e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@vitest/coverage-v8': specifier: ^4.0.16 version: 4.0.16(@vitest/browser@4.0.16(vite@7.3.1(@types/node@20.19.19)(sass@1.97.2))(vitest@4.0.16(@types/node@20.19.19)(sass@1.97.2)))(vitest@4.0.16(@types/node@20.19.19)(sass@1.97.2)) + csv-parse: + specifier: ^5.3.3 + version: 5.3.3 eslint: specifier: ^9.37.0 version: 9.37.0 diff --git a/tests/csv-to-dat.js b/tests/csv-to-dat.js new file mode 100644 index 00000000..edcc3878 --- /dev/null +++ b/tests/csv-to-dat.js @@ -0,0 +1,91 @@ +#!/usr/bin/env node + +/** + * Convert a CSV file (exported from Stella/XMILE) to a DAT file (Vensim format). + * + * Usage: node csv-to-dat.js + * + * The CSV file should have: + * - A header row with variable names (first column is time, e.g., "Months") + * - Data rows with time values in the first column + * + * The DAT file will have: + * - Each variable on its own line + * - Followed by tab-separated time-value pairs + * - Empty values are skipped + * - Subscripts are converted from "var[A1, B1]" to "var[A1,B1]" (spaces removed) + */ + +import { readFileSync, writeFileSync } from 'fs' +import { parse as parseCsv } from 'csv-parse/sync' + +/** + * Convert a variable name from CSV format to DAT format. + * Remove spaces after commas inside subscript brackets. + * + * @param name The variable name from CSV (e.g., "f[A1, B1]"). + * @returns The variable name in DAT format (e.g., "f[A1,B1]"). + */ +function convertVarName(name) { + return name.replace(/\[([^\]]+)\]/g, (match, subscripts) => { + return '[' + subscripts.replace(/,\s+/g, ',') + ']' + }) +} + +/** + * Convert a CSV file to DAT format. + * + * @param inputPath Path to the input CSV file. + * @param outputPath Path to the output DAT file. + */ +function convertCsvToDat(inputPath, outputPath) { + const content = readFileSync(inputPath, 'utf-8') + const rows = parseCsv(content, { + columns: false, + skip_empty_lines: true, + relax_column_count: true + }) + + if (rows.length < 2) { + console.error('Error: CSV file must have at least a header row and one data row') + process.exit(1) + } + + const headers = rows[0] + const dataRows = rows.slice(1) + const output = [] + + // Process each variable (skip first column which is time) + for (let col = 1; col < headers.length; col++) { + const varName = convertVarName(headers[col]) + + // Collect time-value pairs where value is not empty + const pairs = [] + for (const row of dataRows) { + const time = row[0] + const value = row[col] + if (value !== undefined && value !== '') { + pairs.push([time, value]) + } + } + + // Only output variables that have at least one value + if (pairs.length > 0) { + output.push(varName) + for (const [time, value] of pairs) { + output.push(`${time}\t${value}`) + } + } + } + + writeFileSync(outputPath, output.join('\n') + '\n') +} + +// Main +const args = process.argv.slice(2) +if (args.length !== 2) { + console.error('Usage: node csv-to-dat.js ') + process.exit(1) +} + +convertCsvToDat(args[0], args[1]) diff --git a/tests/modeltests b/tests/modeltests index 1f145818..9cd43190 100755 --- a/tests/modeltests +++ b/tests/modeltests @@ -27,12 +27,33 @@ SDE_MAIN="$PROJ_DIR/packages/cli/src/main.js" function test { MODEL=$1 ARGS_MSG=$2 - echo "Testing the $MODEL model $ARGS_MSG" MODEL_DIR=$MODELS_DIR/$MODEL + if [[ $INPUT_FORMAT == "stmx" ]]; then + if [[ ! -f $MODEL_DIR/${MODEL}.stmx || ! -f $MODEL_DIR/${MODEL}.csv ]]; then + echo "Skipping test for $MODEL (no stmx or csv file found)" + echo + return + fi + fi + + echo "Testing the $MODEL model $ARGS_MSG" + # Clean up before node "$SDE_MAIN" clean --modeldir "$MODEL_DIR" + # If INPUT_FORMAT is stmx/xmile, convert the CSV file containing expected data from the + # modeling tool to DAT format for comparison with SDE output. This is a workaround for + # the fact that the `sde compare` and `sde test` commands do not yet support CSV input. + if [[ $INPUT_FORMAT == "mdl" ]]; then + TOOL_DAT=$MODEL_DIR/${MODEL}.dat + else + echo "Converting ${MODEL}.csv to DAT format" + mkdir -p $MODEL_DIR/output + TOOL_DAT=$MODEL_DIR/output/${MODEL}_${INPUT_FORMAT}.dat + node "$SCRIPT_DIR/csv-to-dat.js" "$MODEL_DIR/${MODEL}.csv" "$TOOL_DAT" + fi + # Test (only if there is a dat file to compare against) if [[ -f $MODEL_DIR/${MODEL}.dat ]]; then SPEC_FILE=$MODEL_DIR/${MODEL}_spec.json @@ -46,7 +67,7 @@ function test { if [[ $MODEL == "allocate" ]]; then PRECISION="1e-2" fi - node "$SDE_MAIN" test $TEST_ARGS --genformat=$GEN_FORMAT -p $PRECISION "$MODEL_DIR/$MODEL.$INPUT_FORMAT" + node "$SDE_MAIN" test $TEST_ARGS --genformat $GEN_FORMAT --tooldat $TOOL_DAT -p $PRECISION "$MODEL_DIR/$MODEL.$INPUT_FORMAT" fi # Run additional script to validate output From 7e4aa61b8f6eb839a296e574e3253d9b4dce2616 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 16 Jan 2026 15:46:54 -0800 Subject: [PATCH 65/77] test: disable lint warnings for use of any in tests --- packages/compile/src/model/read-equations-xmile.spec.ts | 1 + packages/compile/src/model/read-subscripts-xmile.spec.ts | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 6b773997..1a97d835 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -113,6 +113,7 @@ function v(lhs: string, formula: string, overrides?: Partial): Variabl variable.includeInOutput = true if (overrides) { for (const [key, value] of Object.entries(overrides)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const r = variable as Record r[key] = value } diff --git a/packages/compile/src/model/read-subscripts-xmile.spec.ts b/packages/compile/src/model/read-subscripts-xmile.spec.ts index 7b663b59..9cc7779e 100644 --- a/packages/compile/src/model/read-subscripts-xmile.spec.ts +++ b/packages/compile/src/model/read-subscripts-xmile.spec.ts @@ -31,6 +31,7 @@ function readSubscriptsFromSource( modelDir?: string }, resolve: boolean + // eslint-disable-next-line @typescript-eslint/no-explicit-any ): any[] { // XXX: This is needed due to subs/dims being in module-level storage resetSubscriptsAndDimensions() @@ -57,18 +58,22 @@ function readSubscriptsFromSource( return allSubscripts() } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function readInlineSubscripts(modelText: string, modelDir?: string): any[] { return readSubscriptsFromSource({ modelText, modelDir }, /*resolve=*/ false) } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function readAndResolveInlineSubscripts(modelText: string, modelDir?: string): any[] { return readSubscriptsFromSource({ modelText, modelDir }, /*resolve=*/ true) } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function readSubscripts(modelName: string): any[] { return readSubscriptsFromSource({ modelName }, /*resolve=*/ false) } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function readAndResolveSubscripts(modelName: string): any[] { return readSubscriptsFromSource({ modelName }, /*resolve=*/ true) } From f28013fabbbf4ef896df8f7c7a3d384c3dd91eb8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 16 Jan 2026 15:55:28 -0800 Subject: [PATCH 66/77] fix: use canonicalVarId instead of canonicalId (which doesn't handle subscripts) --- packages/compile/src/model/model.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/compile/src/model/model.js b/packages/compile/src/model/model.js index aee497d5..243faf76 100644 --- a/packages/compile/src/model/model.js +++ b/packages/compile/src/model/model.js @@ -1,6 +1,6 @@ import * as R from 'ramda' -import { canonicalId, toPrettyString } from '@sdeverywhere/parse' +import { canonicalVarId, toPrettyString } from '@sdeverywhere/parse' import B from '../_shared/bufx.js' import { decanonicalize, isIterable, strlist, vlog, vsort } from '../_shared/helpers.js' @@ -107,7 +107,7 @@ function read(parsedModel, spec, extData, directData, modelDirname, opts) { rhsExpr = { kind: 'variable-ref', varName: rhsValue, - varId: canonicalId(rhsValue) + varId: canonicalVarId(rhsValue) } } const v = new Variable() @@ -1295,7 +1295,7 @@ function jsonList() { const varInstances = expandVar(v) for (const { varName, subscriptIndices } of varInstances) { - const varId = canonicalId(varName) + const varId = canonicalVarId(varName) const varItem = { varId, varName, From 940d069f75127a84f83871858132e37ce6285695 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 17 Jan 2026 08:50:45 -0800 Subject: [PATCH 67/77] feat: add SAFEDIV function support for XMILE models Map XMILE/Stella's SAFEDIV function to the equivalent Vensim ZIDZ function. SAFEDIV performs safe division, returning 0 when the denominator is zero. Generated with Claude Code --- .../gen-equation-js-from-xmile.spec.ts | 21 +++++++++++++++++++ packages/compile/src/generate/gen-expr.js | 4 ++++ packages/compile/src/model/read-equations.js | 1 + 3 files changed, 26 insertions(+) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index ffd69f0b..ac0c7354 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -2156,6 +2156,27 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['_y = fns.RAMP(_slope, _start, _end);']) }) + it('should work for SAFEDIV function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // x = 1 ~~| + // y = ZIDZ(x, 2) ~~| + // `) + + const xmileVars = `\ + + 1 + + + SAFEDIV(x, 2) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_y'))).toEqual(['_y = fns.ZIDZ(_x, 2.0);']) + }) + it('should work for SIN function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index 328b8434..1e08c051 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -238,6 +238,10 @@ function generateFunctionCall(callExpr, ctx) { // XMILE/Stella uses `MOD`, but it is the same as the Vensim `MODULO` function, // which is the name used in the runtime function implementation return generateSimpleFunctionCall('_MODULO') + case '_SAFEDIV': + // XMILE/Stella uses `SAFEDIV`, but it is the same as the Vensim `ZIDZ` function, + // which is the name used in the runtime function implementation + return generateSimpleFunctionCall('_ZIDZ') // // diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 218ffa15..5cf5941b 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -661,6 +661,7 @@ function visitFunctionCall(v, callExpr, context) { case '_MAX': case '_MIN': case '_MOD': + case '_SAFEDIV': case '_STEP': validateCallArgs(callExpr, 2) break From 631cb38cd3fdbae1a44615956039a2ce12df9276 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 17 Jan 2026 08:52:43 -0800 Subject: [PATCH 68/77] feat: add DELAY function support for XMILE models Map XMILE/Stella's DELAY function to the equivalent Vensim DELAY FIXED function. DELAY is a fixed-delay function that outputs a past value of the input after a specified delay time. Generated with Claude Code --- .../gen-equation-js-from-xmile.spec.ts | 35 +++++++++++++++++-- packages/compile/src/generate/gen-expr.js | 10 ++++-- packages/compile/src/model/read-equations.js | 13 +++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index ac0c7354..6d790205 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -1678,9 +1678,38 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = (__level3[i] / __aux4[i]);', '}']) }) - // TODO: This test is skipped for now; in Stella, the DELAY function can be called with or - // without an initial value argument, but the code that handles the Vensim DELAY FIXED function - // currently assumes the initial value argument + it('should work for DELAY function (XMILE equivalent to Vensim DELAY FIXED)', () => { + // Stella's DELAY function is equivalent to Vensim's DELAY FIXED function. + // Note: JS code gen is not yet implemented for DELAY FIXED/DELAY, so this test + // just verifies that the parsing works correctly and throws the expected error. + + const xmileVars = `\ + + 1 + + + 5 + + + 2 + + + DELAY(x, delay_time, init) + +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars.size).toBe(4) + expect(genJS(vars.get('_x'))).toEqual(['_x = 1.0;']) + expect(genJS(vars.get('_delay_time'))).toEqual(['_delay_time = 5.0;']) + expect(genJS(vars.get('_init'))).toEqual(['_init = 2.0;']) + // JS code gen is not yet implemented for DELAY, so verify it throws the expected error + expect(() => genJS(vars.get('_y'), 'init-levels')).toThrow('DELAY function not yet implemented for JS code gen') + expect(() => genJS(vars.get('_y'), 'eval')).toThrow('DELAY function not yet implemented for JS code gen') + }) + + // TODO: This test is skipped for now; the Vensim DELAY FIXED function JS code gen + // is not yet implemented it.skip('should work for DELAY FIXED function', () => { const vars = readInlineModel(` x = 1 ~~| diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index 1e08c051..d5a9905d 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -284,12 +284,13 @@ function generateFunctionCall(callExpr, ctx) { // case '_ACTIVE_INITIAL': + case '_DELAY': case '_DELAY_FIXED': case '_DEPRECIATE_STRAIGHTLINE': case '_SAMPLE_IF_TRUE': case '_INTEG': // Split level functions into init and eval expressions - if (ctx.outFormat === 'js' && (fnId === '_DELAY_FIXED' || fnId === '_DEPRECIATE_STRAIGHTLINE')) { + if (ctx.outFormat === 'js' && (fnId === '_DELAY' || fnId === '_DELAY_FIXED' || fnId === '_DEPRECIATE_STRAIGHTLINE')) { throw new Error(`${callExpr.fnName} function not yet implemented for JS code gen`) } if (ctx.mode.startsWith('init')) { @@ -463,6 +464,7 @@ function generateLevelInit(callExpr, ctx) { case '_INTEG': initialArgIndex = 1 break + case '_DELAY': case '_DELAY_FIXED': { // Emit the code that initializes the `FixedDelay` support struct const fixedDelay = ctx.cVarRefWithLhsSubscripts(ctx.variable.fixedDelayVarName) @@ -514,12 +516,14 @@ function generateLevelEval(callExpr, ctx) { // For ACTIVE INITIAL, emit the first arg without a function call return generateExpr(callExpr.args[0], ctx) + case '_DELAY': case '_DELAY_FIXED': { - // For DELAY FIXED, emit the first arg followed by the FixedDelay support var + // For DELAY/DELAY FIXED, emit the first arg followed by the FixedDelay support var const args = [] args.push(generateExpr(callExpr.args[0], ctx)) args.push(ctx.cVarRefWithLhsSubscripts(ctx.variable.fixedDelayVarName)) - return generateCall(args) + // Both XMILE's DELAY and Vensim's DELAY FIXED use the same runtime function + return `${fnRef('_DELAY_FIXED', ctx)}(${args.join(', ')})` } case '_DEPRECIATE_STRAIGHTLINE': { diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 5cf5941b..7d0fd5d8 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -682,6 +682,19 @@ function visitFunctionCall(v, callExpr, context) { // // + case '_DELAY': + // Stella's DELAY function is equivalent to Vensim's DELAY FIXED function + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 3) + v.varType = 'level' + v.varSubtype = 'fixedDelay' + v.hasInitValue = true + v.fixedDelayVarName = canonicalName(newFixedDelayVarName()) + // The 2nd and 3rd arguments are used at init time + argModes[1] = 'init' + argModes[2] = 'init' + break + case '_DELAY1': case '_DELAY3': // Stella's DELAY1 and DELAY3 functions can take a third "initial" argument (in which case From 5099835c9472e1dd84ae3f0f842a588dcdcee292 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sat, 17 Jan 2026 08:54:07 -0800 Subject: [PATCH 69/77] feat: add SIZE function support for XMILE models Map XMILE/Stella's SIZE function to the equivalent Vensim ELMCOUNT function. SIZE returns the number of elements in a dimension at compile time. Generated with Claude Code --- .../gen-equation-js-from-xmile.spec.ts | 30 +++++++++++++++++++ packages/compile/src/generate/gen-expr.js | 8 +++-- packages/compile/src/model/read-equations.js | 1 + 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index 6d790205..ab828778 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -2502,6 +2502,36 @@ describe('generateEquation (XMILE -> JS)', () => { expect(genJS(vars.get('_y'))).toEqual(['for (let i = 0; i < 2; i++) {', '_y[i] = __level3[i];', '}']) }) + it('should work for SIZE function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // DimA: A1, A2, A3 ~~| + // a = ELMCOUNT(DimA) ~~| + // `) + + const xmileDims = `\ + + + + +` + const xmileVars = `\ + + SIZE(DimA) + + + + + + 10*SIZE(DimA)+a +` + const mdl = xmile(xmileDims, xmileVars) + const vars = readInlineModel(mdl) + expect(vars.size).toBe(2) + expect(genJS(vars.get('_a'))).toEqual(['_a = 3;']) + expect(genJS(vars.get('_b'))).toEqual(['for (let i = 0; i < 3; i++) {', '_b[i] = 10.0 * 3 + _a;', '}']) + }) + it('should work for SQRT function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index d5a9905d..11bc2cef 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -384,11 +384,13 @@ function generateFunctionCall(callExpr, ctx) { } return generateAllocateAvailableCall(callExpr, ctx) - case '_ELMCOUNT': { - // Emit the size of the dimension in place of the dimension name + case '_ELMCOUNT': + case '_SIZE': { + // Emit the size of the dimension in place of the dimension name. + // Note that Vensim uses `ELMCOUNT` while XMILE uses `SIZE`, but otherwise they are the same. const dimArg = callExpr.args[0] if (dimArg.kind !== 'variable-ref') { - throw new Error('Argument for ELMCOUNT must be a dimension name') + throw new Error(`Argument for ${callExpr.fnName} must be a dimension name`) } const dimId = dimArg.varId return `${sub(dimId).size}` diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 7d0fd5d8..7de2a1ad 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -645,6 +645,7 @@ function visitFunctionCall(v, callExpr, context) { case '_INT': case '_LN': case '_SIN': + case '_SIZE': case '_SQRT': case '_SUM': case '_TAN': From ef198993e220baba2c110d6a82489f3b8bc895fc Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Sun, 18 Jan 2026 08:53:16 -0800 Subject: [PATCH 70/77] feat: add DEPRECIATE_STRAIGHTLINE and ACTIVE_INITIAL support for XMILE models - Add _DEPRECIATE_STRAIGHTLINE case to validateStellaFunctionCall() - Add _ACTIVE_INITIAL case to validateStellaFunctionCall() - Update parse-xmile-variable-def.ts to synthesize ACTIVE_INITIAL calls - Add tests for both functions - Add PLAN.md documenting XMILE integration test analysis 7 of 11 XMILE integration tests now pass. Generated with Claude Code --- PLAN.md | 416 ++++++++++++++++++ .../gen-equation-js-from-xmile.spec.ts | 42 ++ .../src/model/read-equations-xmile.spec.ts | 18 +- packages/compile/src/model/read-equations.js | 24 + .../xmile/parse-xmile-variable-def.spec.ts | 49 +++ .../src/xmile/parse-xmile-variable-def.ts | 16 +- 6 files changed, 557 insertions(+), 8 deletions(-) create mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 00000000..6e194ccd --- /dev/null +++ b/PLAN.md @@ -0,0 +1,416 @@ +# XMILE Integration Test Fixes - Analysis and Plan + +This document analyzes the errors found when running XMILE/Stella integration tests (`INPUT_FORMAT=stmx ./tests/modeltests`) and proposes fixes for each issue. + +## Summary of Test Results + +| Model | Status | Error Type | +|-------|--------|------------| +| active_initial | ✅ **FIXED** | Was: Cyclic dependency (init_eqn handling added) | +| allocate | ❌ FAIL | Unhandled function `_ALLOCATE` (incompatible signatures, deferred) | +| arrays | ❌ FAIL | Undefined subscript mapping `_dim_ab_map` (HIGH complexity, deferred) | +| comments | ✅ PASS | - | +| delay | ❌ FAIL | Data differences (numerical errors) - requires investigation | +| delayfixed | ✅ **FIXED** | Was: Unhandled function `_DELAY` | +| delayfixed2 | ✅ **FIXED** | Was: Unhandled function `_DELAY` | +| depreciate | ✅ **FIXED** | Was: Unhandled function `_DEPRECIATE_STRAIGHTLINE` | +| elmcount | ✅ **FIXED** | Was: Unhandled function `_SIZE` | +| interleaved | ✅ PASS | - | +| trend | ⚠️ Runs | Was: Unhandled function `_SAFEDIV` (now fixed, but TREND has numerical issues) | + +**Current Score: 7 passing, 4 failing** (improved from 2 passing initially) + +--- + +## Error 1: active_initial - Cyclic Dependency (✅ FIXED) + +### Symptom (RESOLVED) +``` +Error: Found cyclic dependency during toposort: +_capacity_utilization → _capacity_1 → _target_capacity → _utilization_adjustment → _capacity_utilization +``` + +### Root Cause + +The XMILE parser was ignoring the `` element for auxiliary variables. In XMILE, a variable can have separate init-time and eval-time equations: + +```xml + + Capacity_1*Utilization_Adjustment + Initial_Target_Capacity + +``` + +In Vensim, this is expressed using the `ACTIVE INITIAL` function: +```vensim +Target Capacity = ACTIVE INITIAL(Capacity*Utilization Adjustment, Initial Target Capacity) +``` + +### Fix Applied + +The XMILE parser was updated to handle `` elements by synthesizing an `ACTIVE_INITIAL` function call when both `` and `` elements exist for aux variables. + +--- + +## Error 2: allocate - Unhandled _ALLOCATE Function + +### Symptom +``` +Error: Unhandled function '_ALLOCATE' in readEquations for 'shipments[region]' +``` + +### Root Cause + +**Critical Incompatibility:** Stella's `ALLOCATE` function has a fundamentally different signature than Vensim's `ALLOCATE AVAILABLE`: + +**Vensim (3 parameters):** +```vensim +shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) +``` + +**Stella (6 parameters):** +```xml +ALLOCATE(total_supply_available, region, demand, priority_vector[*,ppriority], priority_vector[region,pwidth], priority_vector[region,ptype]) +``` + +Key differences: +- Stella has 6 parameters vs Vensim's 3 +- Parameter order is different (Stella puts availability FIRST, Vensim puts it LAST) +- Stella explicitly names the dimension to iterate over +- Stella separates priority parameters (mean, width, type) instead of packing them in a 2D array + +### Current Implementation Status + +- **Vensim:** Fully implemented + - Parsing in `read-equations.js` (lines 478-481) + - C code generation in `gen-expr.js` (lines 855-928) + - C runtime in `vensim.c` (lines 408-504) +- **Stella:** NOT implemented (4 test cases explicitly skipped in `read-equations-xmile.spec.ts`) + +### Proposed Fix + +**Option A: Full Implementation (Recommended for Completeness)** +1. Add `case '_ALLOCATE':` to `validateStellaFunctionCall()` in `read-equations.js` +2. Create parameter adapter to map Stella's 6-param signature to internal representation +3. Either: + - Create new C runtime function for Stella's signature, OR + - Create wrapper that converts Stella params to Vensim format + +**Option B: Skip for Now** +- Document incompatibility +- Keep tests skipped +- Focus on higher-priority functions first + +### Complexity: HIGH +- Fundamentally incompatible parameter signatures +- Would require ~700-1200 lines of implementation + tests +- May require new C runtime function or adapter layer + +--- + +## Error 3: arrays - Undefined Subscript Mapping + +### Symptom +``` +ERROR: undefined hasMapping fromSubscript : '_dim_ab_map' +TypeError: Cannot read properties of undefined (reading 'mappings') +``` + +### Root Cause + +The XMILE parser **does not support subscript mappings**. In `packages/parse/src/xmile/parse-xmile-dimension-def.ts` (lines 55-56): + +```typescript +// TODO: Does XMILE support mappings? +subscriptMappings: [], +``` + +The arrays.stmx model uses Vensim-style subscript mapping patterns: +```xml + + + DimB + +``` + +And references it in expressions like: +```xml +inputA[dim_ab_map]*10 +``` + +The problem: +1. `dim ab map` is parsed as a regular variable, not a subscript dimension +2. When the compiler tries to resolve `dim_ab_map` as a subscript, it doesn't exist +3. `hasMapping()` receives `undefined` and crashes trying to access `.mappings` + +### Proposed Fix + +**Option A: Implement XMILE Subscript Mapping Support** + +Research how XMILE represents dimension mappings and implement parsing support. This may require: +1. Understanding XMILE's mapping syntax (if it exists) +2. Modifying `parse-xmile-dimension-def.ts` to populate `subscriptMappings` + +**Option B: Handle Variables as Subscript References** + +If XMILE uses variables to represent mappings (like the `dim ab map` aux variable): +1. Detect when a subscript reference is actually a variable name +2. Evaluate the variable to get the actual subscript value +3. This is more complex as it involves runtime subscript resolution + +**Option C: Skip Test with Documentation** + +If XMILE fundamentally doesn't support subscript mappings: +1. Document this limitation +2. Skip the arrays test for XMILE +3. Users would need to restructure models without mappings + +### Complexity: HIGH (Options A/B) or LOW (Option C) +- Requires understanding XMILE subscript semantics +- May involve fundamental parser changes +- Could require runtime subscript resolution + +--- + +## Error 4: delay - Numerical Data Differences + +### Symptom +``` +_d11[_a1] time=7.00 vensim=0 sde=-10920 diff=1092000.000000% +_d8[_a1] time=7.00 vensim=0 sde=-260 diff=26000.000000% +``` + +### Root Cause + +**UPDATE: After investigation, the initial analysis was incorrect.** The issue is NOT a simple canonicalization bug in `read-equation-fn-delay.js`. The generated C code structure appears correct when compared to the Vensim version. + +The failing variables are: +- `d8[DimA] = DELAY3(input, delay_a[DimA])` - subscripted apply-to-all DELAY3 +- `d11[DimA] = k*DELAY3(input, delay_a[DimA])` - DELAY3 nested in multiplication + +**Observations:** +1. The Vensim version (MDL) works correctly +2. The XMILE version (STMX) produces wildly incorrect negative values +3. The generated C code structure (integration chain, init values) looks correct +4. The variable numbering differs between versions but the mathematical structure appears equivalent + +**Possible causes requiring further investigation:** +1. **XMILE parsing issue**: Something in how the DELAY3 arguments are parsed from XMILE +2. **Variable ordering issue**: Different evaluation order in XMILE vs Vensim compilation +3. **Subscript handling difference**: How subscripted delay time arguments are handled in XMILE + +**Note:** The canonicalization fix (changing `canonicalName` to `canonicalVensimName`) does NOT resolve this issue. + +### Complexity: HIGH +- Requires deep debugging of XMILE compilation path +- May need to compare intermediate representations between Vensim and XMILE parsing + +--- + +## Error 5: delayfixed/delayfixed2 - Unhandled _DELAY Function (FIXED) + +### Symptom +``` +Error: Unhandled function '_DELAY' in readEquations for 'receiving' +``` + +### Root Cause + +XMILE uses `DELAY(input, delay_time, initial)` which gets canonicalized to `_DELAY`, but this function is not handled in `validateStellaFunctionCall()` in `read-equations.js`. + +Vensim has the equivalent `DELAY FIXED` which is already fully implemented (lines 493-503). + +### Proposed Fix + +**File:** `packages/compile/src/model/read-equations.js` + +Add case in `validateStellaFunctionCall()` (around line 630-735): + +```javascript +case '_DELAY': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 3) + v.varType = 'level' + v.varSubtype = 'fixedDelay' + v.hasInitValue = true + v.fixedDelayVarName = canonicalName(newFixedDelayVarName()) + argModes[1] = 'init' + argModes[2] = 'init' + break +``` + +### Complexity: LOW +- Copy existing `_DELAY_FIXED` case logic +- May need to verify parameter order matches Stella's + +--- + +## Error 6: depreciate - Unhandled _DEPRECIATE_STRAIGHTLINE Function + +### Symptom +``` +Error: Unhandled function '_DEPRECIATE_STRAIGHTLINE' in readEquations for 'Depreciated Amount' +``` + +### Root Cause + +`_DEPRECIATE_STRAIGHTLINE` is already handled for Vensim (lines 505-517 in `read-equations.js`) but not added to `validateStellaFunctionCall()`. + +### Proposed Fix + +**File:** `packages/compile/src/model/read-equations.js` + +Add case in `validateStellaFunctionCall()`: + +```javascript +case '_DEPRECIATE_STRAIGHTLINE': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 4) + v.varSubtype = 'depreciation' + v.hasInitValue = true + v.depreciationVarName = canonicalName(newDepreciationVarName()) + argModes[1] = 'init' + argModes[2] = 'init' + break +``` + +**Note:** The depreciate.stmx file includes a complete macro definition showing Stella's depreciation implementation. Need to verify the parameter semantics match Vensim's. + +### Complexity: LOW-MEDIUM +- Similar to existing Vensim handling +- May need parameter verification between Vensim/Stella implementations + +--- + +## Error 7: elmcount - Unhandled _SIZE Function (FIXED) + +### Symptom +``` +Error: Unhandled function '_SIZE' in readEquations for 'a' +``` + +### Root Cause + +`SIZE(DimA)` is an XMILE-specific function that returns the number of elements in a dimension. There is no direct Vensim equivalent (Vensim uses `ELMCOUNT`). + +Usage in elmcount model: +```xml +SIZE(DimA) +``` + +This should return 3 (since DimA has elements A1, A2, A3). + +### Proposed Fix + +**File:** `packages/compile/src/model/read-equations.js` + +This is a **compile-time constant** that can be resolved during parsing: + +1. Add case in `validateStellaFunctionCall()`: +```javascript +case '_SIZE': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 1) + // Mark as const since it can be resolved at compile time + v.varType = 'const' + break +``` + +2. In code generation (`gen-expr.js`), resolve the dimension size: +```javascript +case '_SIZE': { + const dimArg = callExpr.args[0] + const dimId = canonicalName(dimArg.name) + const dim = sub(dimId) + return String(dim.size) +} +``` + +### Complexity: LOW-MEDIUM +- Need to resolve dimension at compile time +- Simple once dimension lookup is implemented + +--- + +## Error 8: trend - Unhandled _SAFEDIV Function (FIXED) + +### Symptom +``` +Error: Unhandled function '_SAFEDIV' in readEquations for 'trend1' +``` + +### Root Cause + +`SAFEDIV(numerator, denominator)` is an XMILE-specific safe division function that returns 0 if the denominator is 0 (or near 0). + +This is equivalent to Vensim's `ZIDZ` (Zero If Divide by Zero), which is already implemented in `js-model-functions.ts`. + +Interestingly, the existing TREND function implementation already uses ZIDZ internally (in `read-equation-fn-trend.js`, line 41). + +### Proposed Fix + +**File:** `packages/compile/src/model/read-equations.js` + +Add case in `validateStellaFunctionCall()`: + +```javascript +case '_SAFEDIV': + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + // No special handling needed - treat as normal function + break +``` + +**File:** `packages/compile/src/generate/gen-expr.js` + +Either: +- Map `_SAFEDIV` to existing `_ZIDZ` implementation, OR +- Add inline code generation: +```javascript +case '_SAFEDIV': { + const num = visitExpr(callExpr.args[0]) + const denom = visitExpr(callExpr.args[1]) + return `_ZIDZ(${num}, ${denom})` +} +``` + +### Complexity: LOW +- Direct mapping to existing ZIDZ function +- Minimal code changes + +--- + +## Implementation Priority + +Based on complexity and impact, here's the recommended implementation order: + +### ✅ COMPLETED - Quick Wins +1. **_SAFEDIV** → Map to ZIDZ ✓ (commit 940d069f) +2. **_DELAY** → Copy DELAY_FIXED logic ✓ (commit 631cb38c) +3. **_SIZE** → Compile-time dimension resolution ✓ (commit 5099835c) +4. **active_initial** → Handle `` in XMILE parser ✓ +5. **_DEPRECIATE_STRAIGHTLINE** → Add to validateStellaFunctionCall ✓ + +### ⚠️ REQUIRES INVESTIGATION +6. **delay numerical fix** → NOT a simple canonicalization bug; requires deep debugging of XMILE compilation path +7. **trend numerical fix** → TREND function produces different numerical results than Vensim + +### Priority 3 - Deferred (HIGH complexity) +8. **arrays subscript mapping** → Research XMILE mapping support +9. **_ALLOCATE** → Significant signature differences, may skip + +--- + +## Files Modified + +| File | Changes | +|------|---------| +| `packages/compile/src/model/read-equations.js` | Added cases for _DELAY, _SIZE, _SAFEDIV, _DEPRECIATE_STRAIGHTLINE, _ACTIVE_INITIAL to validateStellaFunctionCall() | +| `packages/compile/src/generate/gen-expr.js` | Added code generation for _SIZE (maps to ELMCOUNT), _SAFEDIV (maps to ZIDZ), _DELAY (maps to DELAY_FIXED) | +| `packages/parse/src/xmile/parse-xmile-variable-def.ts` | Added support for `` element to synthesize ACTIVE_INITIAL function call | + +## Files Still Needing Changes + +| File | Changes | +|------|---------| +| `packages/parse/src/xmile/parse-xmile-dimension-def.ts` | (Future) Add subscript mapping support | diff --git a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts index ab828778..2eb8cf4c 100644 --- a/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts +++ b/packages/compile/src/generate/gen-equation-js-from-xmile.spec.ts @@ -1730,6 +1730,48 @@ describe('generateEquation (XMILE -> JS)', () => { expect(() => genJS(vars.get('_y'), 'eval')).toThrow('DELAY FIXED function not yet implemented for JS code gen') }) + it('should work for DEPRECIATE_STRAIGHTLINE function', () => { + // Equivalent Vensim model for reference: + // const vars = readInlineModel(` + // dtime = 20 ~~| + // Capacity Cost = 1000 ~~| + // New Capacity = 2000 ~~| + // stream = Capacity Cost * New Capacity ~~| + // Depreciated Amount = DEPRECIATE STRAIGHTLINE(stream, dtime, 1, 0) ~~| + // `) + + const xmileVars = `\ + + 20 + + + 1000 + + + 2000 + + + Capacity_Cost * New_Capacity + + + DEPRECIATE_STRAIGHTLINE(stream, dtime, 1, 0) +` + const mdl = xmile('', xmileVars) + const vars = readInlineModel(mdl) + expect(vars.size).toBe(5) + expect(genJS(vars.get('_dtime'))).toEqual(['_dtime = 20.0;']) + expect(genJS(vars.get('_capacity_cost'))).toEqual(['_capacity_cost = 1000.0;']) + expect(genJS(vars.get('_new_capacity'))).toEqual(['_new_capacity = 2000.0;']) + expect(genJS(vars.get('_stream'))).toEqual(['_stream = _capacity_cost * _new_capacity;']) + // JS code gen is not yet implemented for DEPRECIATE_STRAIGHTLINE, so verify it throws the expected error + expect(() => genJS(vars.get('_depreciated_amount'), 'init-levels')).toThrow( + 'DEPRECIATE_STRAIGHTLINE function not yet implemented for JS code gen' + ) + expect(() => genJS(vars.get('_depreciated_amount'), 'eval')).toThrow( + 'DEPRECIATE_STRAIGHTLINE function not yet implemented for JS code gen' + ) + }) + it('should work for EXP function', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` diff --git a/packages/compile/src/model/read-equations-xmile.spec.ts b/packages/compile/src/model/read-equations-xmile.spec.ts index 1a97d835..c5c6ba64 100644 --- a/packages/compile/src/model/read-equations-xmile.spec.ts +++ b/packages/compile/src/model/read-equations-xmile.spec.ts @@ -3396,8 +3396,9 @@ ${elements.join('\n')} // NOTE: This is the end of the "should work for {0,1,2,3}D variable" tests. // - // TODO: This test is skipped because Stella doesn't appear to include the ACTIVE INITIAL function - it.skip('should work for ACTIVE INITIAL function', () => { + // In XMILE/Stella, ACTIVE INITIAL is expressed using a separate element. + // The XMILE parser synthesizes an ACTIVE INITIAL call when both and are present. + it('should work for ACTIVE INITIAL function (synthesized from init_eqn)', () => { // Equivalent Vensim model for reference: // const vars = readInlineModel(` // Initial Target Capacity = 1 ~~| @@ -3405,20 +3406,23 @@ ${elements.join('\n')} // Target Capacity = ACTIVE INITIAL(Capacity, Initial Target Capacity) ~~| // `) + // Note: In XMILE, variable names with underscores are different from names with spaces. + // The references the variable using the exact name format (underscores here). const xmileVars = `\ - + 1 2 - - ACTIVE INITIAL(Capacity, Initial Target Capacity) + + Capacity + Initial_Target_Capacity ` const mdl = xmile('', xmileVars) const vars = readInlineModel(mdl) expect(vars).toEqual([ - v('Initial Target Capacity', '1', { + v('Initial_Target_Capacity', '1', { refId: '_initial_target_capacity', varType: 'const' }), @@ -3426,7 +3430,7 @@ ${elements.join('\n')} refId: '_capacity', varType: 'const' }), - v('Target Capacity', 'ACTIVE INITIAL(Capacity,Initial Target Capacity)', { + v('Target_Capacity', 'ACTIVE INITIAL(Capacity,Initial_Target_Capacity)', { refId: '_target_capacity', references: ['_capacity'], hasInitValue: true, diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 7de2a1ad..8a8cb1d9 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -696,6 +696,18 @@ function visitFunctionCall(v, callExpr, context) { argModes[2] = 'init' break + case '_DEPRECIATE_STRAIGHTLINE': + // Stella's DEPRECIATE_STRAIGHTLINE function has the same signature as Vensim's + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 4) + v.varSubtype = 'depreciation' + v.hasInitValue = true + v.depreciationVarName = canonicalName(newDepreciationVarName()) + // The 2nd and 3rd arguments are used at init time + argModes[1] = 'init' + argModes[2] = 'init' + break + case '_DELAY1': case '_DELAY3': // Stella's DELAY1 and DELAY3 functions can take a third "initial" argument (in which case @@ -706,6 +718,18 @@ function visitFunctionCall(v, callExpr, context) { generateDelayVariables(v, callExpr, context) break + case '_ACTIVE_INITIAL': + // NOTE: Stella doesn't have a built-in `ACTIVE INITIAL` function, but our XMILE parser + // synthesizes an `ACTIVE INITIAL` function call for `` variable definitions that + // have both `` and `` elements. This is equivalent to Vensim's + // `ACTIVE INITIAL` function. + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + v.hasInitValue = true + // The 2nd argument is used at init time + argModes[1] = 'init' + break + case '_IF_THEN_ELSE': validateCallArgs(callExpr, 3) addFnReference = false diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 26730359..4bc04cf1 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -688,6 +688,55 @@ describe('parseXmileVariableDef with ', () => { expect(parseXmileVariableDef(v)).toEqual([exprEqn(varDef('x y z'), binaryOp(varRef('y'), '+', num(10)))]) }) + it('should parse an aux variable definition with (synthesizes ACTIVE INITIAL call)', () => { + const v = xml(` + + Capacity_1*Utilization_Adjustment + Initial_Target_Capacity + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('Target Capacity'), + call( + 'ACTIVE INITIAL', + binaryOp(varRef('Capacity_1'), '*', varRef('Utilization_Adjustment')), + varRef('Initial_Target_Capacity') + ) + ) + ]) + }) + + it('should parse an aux variable definition with (with numeric init value)', () => { + const v = xml(` + + y + z + 100 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn(varDef('x'), call('ACTIVE INITIAL', binaryOp(varRef('y'), '+', varRef('z')), num(100))) + ]) + }) + + it('should parse an aux variable definition with (with one dimension, apply-to-all)', () => { + const v = xml(` + + + + + y[DimA] + z + 100 + + `) + expect(parseXmileVariableDef(v)).toEqual([ + exprEqn( + varDef('x', ['DimA']), + call('ACTIVE INITIAL', binaryOp(varRef('y', ['DimA']), '+', varRef('z')), num(100)) + ) + ]) + }) + it('should throw an error if aux variable equation cannot be parsed', () => { const v = xml(` diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 1d8f6a90..7868e6fa 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -166,12 +166,26 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { // TODO: Handle the case where is defined using CDATA const eqnText = eqnElem ? firstTextOf(eqnElem) : undefined switch (varTagName) { - case 'aux': + case 'aux': { if (eqnText === undefined) { // Technically the is optional for an ; if not defined, we will skip it return undefined } + + // Check for element. In XMILE, an aux variable can have a separate + // init-time equation using . This is equivalent to Vensim's ACTIVE_INITIAL + // function, so we synthesize an ACTIVE_INITIAL call when both elements are present. + const initEqnElem = firstElemOf(parentElem, 'init_eqn') + const initEqnText = initEqnElem ? firstTextOf(initEqnElem) : undefined + if (initEqnText !== undefined) { + // Synthesize: ACTIVE INITIAL(eqnExpr, initEqnExpr) + const eqnExpr = parseExpr(eqnText.text) + const initEqnExpr = parseExpr(initEqnText.text) + return call('ACTIVE INITIAL', eqnExpr, initEqnExpr) + } + return parseExpr(eqnText.text) + } case 'stock': { // elements are currently translated to a Vensim-style aux: From e325d6622bd62228bc48d46424b0013159d52391 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Mon, 9 Feb 2026 17:06:19 -0800 Subject: [PATCH 71/77] docs: update comments --- packages/compile/src/generate/gen-expr.js | 6 ++--- packages/compile/src/model/read-equations.js | 24 +++++++++---------- .../src/xmile/parse-xmile-variable-def.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index 11bc2cef..bdc125b8 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -386,8 +386,8 @@ function generateFunctionCall(callExpr, ctx) { case '_ELMCOUNT': case '_SIZE': { - // Emit the size of the dimension in place of the dimension name. - // Note that Vensim uses `ELMCOUNT` while XMILE uses `SIZE`, but otherwise they are the same. + // Emit the size of the dimension in place of the dimension name. Note that Vensim uses + // `ELMCOUNT` while XMILE uses `SIZE`, but otherwise they are the same. const dimArg = callExpr.args[0] if (dimArg.kind !== 'variable-ref') { throw new Error(`Argument for ${callExpr.fnName} must be a dimension name`) @@ -520,7 +520,7 @@ function generateLevelEval(callExpr, ctx) { case '_DELAY': case '_DELAY_FIXED': { - // For DELAY/DELAY FIXED, emit the first arg followed by the FixedDelay support var + // For DELAY and DELAY FIXED, emit the first arg followed by the FixedDelay support var const args = [] args.push(generateExpr(callExpr.args[0], ctx)) args.push(ctx.cVarRefWithLhsSubscripts(ctx.variable.fixedDelayVarName)) diff --git a/packages/compile/src/model/read-equations.js b/packages/compile/src/model/read-equations.js index 8a8cb1d9..5c581caf 100644 --- a/packages/compile/src/model/read-equations.js +++ b/packages/compile/src/model/read-equations.js @@ -683,6 +683,18 @@ function visitFunctionCall(v, callExpr, context) { // // + case '_ACTIVE_INITIAL': + // NOTE: Stella doesn't have a built-in `ACTIVE INITIAL` function, but our XMILE parser + // synthesizes an `ACTIVE INITIAL` function call for `` variable definitions that + // have both `` and `` elements. This is equivalent to Vensim's + // `ACTIVE INITIAL` function. + validateCallDepth(callExpr, context) + validateCallArgs(callExpr, 2) + v.hasInitValue = true + // The 2nd argument is used at init time + argModes[1] = 'init' + break + case '_DELAY': // Stella's DELAY function is equivalent to Vensim's DELAY FIXED function validateCallDepth(callExpr, context) @@ -718,18 +730,6 @@ function visitFunctionCall(v, callExpr, context) { generateDelayVariables(v, callExpr, context) break - case '_ACTIVE_INITIAL': - // NOTE: Stella doesn't have a built-in `ACTIVE INITIAL` function, but our XMILE parser - // synthesizes an `ACTIVE INITIAL` function call for `` variable definitions that - // have both `` and `` elements. This is equivalent to Vensim's - // `ACTIVE INITIAL` function. - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 2) - v.hasInitValue = true - // The 2nd argument is used at init time - argModes[1] = 'init' - break - case '_IF_THEN_ELSE': validateCallArgs(callExpr, 3) addFnReference = false diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index 7868e6fa..fea82565 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -178,7 +178,7 @@ function parseEqnElem(varElem: XmlElement, parentElem: XmlElement): Expr { const initEqnElem = firstElemOf(parentElem, 'init_eqn') const initEqnText = initEqnElem ? firstTextOf(initEqnElem) : undefined if (initEqnText !== undefined) { - // Synthesize: ACTIVE INITIAL(eqnExpr, initEqnExpr) + // Synthesize ACTIVE INITIAL(eqnExpr, initEqnExpr) const eqnExpr = parseExpr(eqnText.text) const initEqnExpr = parseExpr(initEqnText.text) return call('ACTIVE INITIAL', eqnExpr, initEqnExpr) From 34bf1d9cd59ab76905d5b4c3ed2a3994daa6d386 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 10 Feb 2026 13:41:06 -0800 Subject: [PATCH 72/77] docs: update comment --- packages/compile/src/generate/gen-expr.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index bdc125b8..ce3802cb 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -520,11 +520,12 @@ function generateLevelEval(callExpr, ctx) { case '_DELAY': case '_DELAY_FIXED': { - // For DELAY and DELAY FIXED, emit the first arg followed by the FixedDelay support var + // Stella's DELAY function is behaviorally equivalent to Vensim's DELAY FIXED function, so + // they use the same `_DELAY_FIXED` runtime function. For these, emit the first arg + // followed by the FixedDelay support var. const args = [] args.push(generateExpr(callExpr.args[0], ctx)) args.push(ctx.cVarRefWithLhsSubscripts(ctx.variable.fixedDelayVarName)) - // Both XMILE's DELAY and Vensim's DELAY FIXED use the same runtime function return `${fnRef('_DELAY_FIXED', ctx)}(${args.join(', ')})` } From 653bb3fd0a4445114954ee31323e75128c757933 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 10 Feb 2026 13:41:21 -0800 Subject: [PATCH 73/77] docs: update PLAN.md to account for latest test fixes --- PLAN.md | 85 ++++++++++++++++----------------------------------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/PLAN.md b/PLAN.md index 6e194ccd..e531fd9f 100644 --- a/PLAN.md +++ b/PLAN.md @@ -10,15 +10,15 @@ This document analyzes the errors found when running XMILE/Stella integration te | allocate | ❌ FAIL | Unhandled function `_ALLOCATE` (incompatible signatures, deferred) | | arrays | ❌ FAIL | Undefined subscript mapping `_dim_ab_map` (HIGH complexity, deferred) | | comments | ✅ PASS | - | -| delay | ❌ FAIL | Data differences (numerical errors) - requires investigation | +| delay | ✅ **FIXED** | Was: Data differences (test model TIME STEP updated to 0.25) | | delayfixed | ✅ **FIXED** | Was: Unhandled function `_DELAY` | | delayfixed2 | ✅ **FIXED** | Was: Unhandled function `_DELAY` | | depreciate | ✅ **FIXED** | Was: Unhandled function `_DEPRECIATE_STRAIGHTLINE` | | elmcount | ✅ **FIXED** | Was: Unhandled function `_SIZE` | | interleaved | ✅ PASS | - | -| trend | ⚠️ Runs | Was: Unhandled function `_SAFEDIV` (now fixed, but TREND has numerical issues) | +| trend | ✅ **FIXED** | Was: Unhandled function `_SAFEDIV` (Stella outputs regenerated) | -**Current Score: 7 passing, 4 failing** (improved from 2 passing initially) +**Current Score: 9 passing, 2 failing** (improved from 2 passing initially) --- @@ -172,9 +172,9 @@ If XMILE fundamentally doesn't support subscript mappings: --- -## Error 4: delay - Numerical Data Differences +## Error 4: delay - Numerical Data Differences (✅ FIXED) -### Symptom +### Symptom (RESOLVED) ``` _d11[_a1] time=7.00 vensim=0 sde=-10920 diff=1092000.000000% _d8[_a1] time=7.00 vensim=0 sde=-260 diff=26000.000000% @@ -182,28 +182,13 @@ _d8[_a1] time=7.00 vensim=0 sde=-260 diff=26000.000000% ### Root Cause -**UPDATE: After investigation, the initial analysis was incorrect.** The issue is NOT a simple canonicalization bug in `read-equation-fn-delay.js`. The generated C code structure appears correct when compared to the Vensim version. - -The failing variables are: -- `d8[DimA] = DELAY3(input, delay_a[DimA])` - subscripted apply-to-all DELAY3 -- `d11[DimA] = k*DELAY3(input, delay_a[DimA])` - DELAY3 nested in multiplication - -**Observations:** -1. The Vensim version (MDL) works correctly -2. The XMILE version (STMX) produces wildly incorrect negative values -3. The generated C code structure (integration chain, init values) looks correct -4. The variable numbering differs between versions but the mathematical structure appears equivalent +The test model was using a TIME STEP of 1, which was too coarse for accurate DELAY3 calculations. DELAY3 uses a third-order exponential delay approximation that requires smaller time steps for numerical stability. -**Possible causes requiring further investigation:** -1. **XMILE parsing issue**: Something in how the DELAY3 arguments are parsed from XMILE -2. **Variable ordering issue**: Different evaluation order in XMILE vs Vensim compilation -3. **Subscript handling difference**: How subscripted delay time arguments are handled in XMILE +### Fix Applied -**Note:** The canonicalization fix (changing `canonicalName` to `canonicalVensimName`) does NOT resolve this issue. +Updated the `delay` test models (both `.mdl` and `.stmx`) to use TIME STEP of 0.25 instead of 1, and regenerated the expected outputs. This provides sufficient numerical accuracy for the delay function approximations. -### Complexity: HIGH -- Requires deep debugging of XMILE compilation path -- May need to compare intermediate representations between Vensim and XMILE parsing +See commit 731ed4514a74de314e67ac2bc1037ee264322a69. --- @@ -332,51 +317,26 @@ case '_SIZE': { --- -## Error 8: trend - Unhandled _SAFEDIV Function (FIXED) +## Error 8: trend - Unhandled _SAFEDIV Function (✅ FIXED) -### Symptom +### Symptom (RESOLVED) ``` Error: Unhandled function '_SAFEDIV' in readEquations for 'trend1' ``` ### Root Cause -`SAFEDIV(numerator, denominator)` is an XMILE-specific safe division function that returns 0 if the denominator is 0 (or near 0). - -This is equivalent to Vensim's `ZIDZ` (Zero If Divide by Zero), which is already implemented in `js-model-functions.ts`. - -Interestingly, the existing TREND function implementation already uses ZIDZ internally (in `read-equation-fn-trend.js`, line 41). +Two issues were found: -### Proposed Fix +1. `SAFEDIV(numerator, denominator)` is an XMILE-specific safe division function that returns 0 if the denominator is 0 (or near 0). This is equivalent to Vensim's `ZIDZ` (Zero If Divide by Zero). -**File:** `packages/compile/src/model/read-equations.js` +2. The expected Stella outputs in `trend.csv` were outdated and didn't match the current model behavior. -Add case in `validateStellaFunctionCall()`: - -```javascript -case '_SAFEDIV': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 2) - // No special handling needed - treat as normal function - break -``` - -**File:** `packages/compile/src/generate/gen-expr.js` +### Fix Applied -Either: -- Map `_SAFEDIV` to existing `_ZIDZ` implementation, OR -- Add inline code generation: -```javascript -case '_SAFEDIV': { - const num = visitExpr(callExpr.args[0]) - const denom = visitExpr(callExpr.args[1]) - return `_ZIDZ(${num}, ${denom})` -} -``` +1. Added `_SAFEDIV` support in `validateStellaFunctionCall()` and mapped it to `_ZIDZ` in code generation (commit 940d069f). -### Complexity: LOW -- Direct mapping to existing ZIDZ function -- Minimal code changes +2. Regenerated the Stella outputs for `trend.stmx` test model (commit 6030a7a3). --- @@ -390,12 +350,10 @@ Based on complexity and impact, here's the recommended implementation order: 3. **_SIZE** → Compile-time dimension resolution ✓ (commit 5099835c) 4. **active_initial** → Handle `` in XMILE parser ✓ 5. **_DEPRECIATE_STRAIGHTLINE** → Add to validateStellaFunctionCall ✓ +6. **delay numerical fix** → Updated test model TIME STEP to 0.25 ✓ (commit 731ed451) +7. **trend numerical fix** → Regenerated Stella outputs ✓ (commit 6030a7a3) -### ⚠️ REQUIRES INVESTIGATION -6. **delay numerical fix** → NOT a simple canonicalization bug; requires deep debugging of XMILE compilation path -7. **trend numerical fix** → TREND function produces different numerical results than Vensim - -### Priority 3 - Deferred (HIGH complexity) +### Deferred (HIGH complexity) 8. **arrays subscript mapping** → Research XMILE mapping support 9. **_ALLOCATE** → Significant signature differences, may skip @@ -408,6 +366,9 @@ Based on complexity and impact, here's the recommended implementation order: | `packages/compile/src/model/read-equations.js` | Added cases for _DELAY, _SIZE, _SAFEDIV, _DEPRECIATE_STRAIGHTLINE, _ACTIVE_INITIAL to validateStellaFunctionCall() | | `packages/compile/src/generate/gen-expr.js` | Added code generation for _SIZE (maps to ELMCOUNT), _SAFEDIV (maps to ZIDZ), _DELAY (maps to DELAY_FIXED) | | `packages/parse/src/xmile/parse-xmile-variable-def.ts` | Added support for `` element to synthesize ACTIVE_INITIAL function call | +| `models/delay/delay.mdl`, `models/delay/delay.stmx` | Updated TIME STEP from 1 to 0.25 for numerical accuracy | +| `models/delay/delay.csv`, `models/delay/delay.dat` | Regenerated expected outputs with new TIME STEP | +| `models/trend/trend.csv` | Regenerated Stella expected outputs | ## Files Still Needing Changes From 546d07f1b6ea0a7ac087f83e005fb71bb083a685 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 10 Feb 2026 13:53:01 -0800 Subject: [PATCH 74/77] test: skip two test models that are not yet supported by XMILE backend --- tests/modeltests | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/modeltests b/tests/modeltests index 9cd43190..913be005 100755 --- a/tests/modeltests +++ b/tests/modeltests @@ -147,6 +147,15 @@ else continue fi + if [[ $INPUT_FORMAT == "stmx" ]]; then + # Skip tests for models that are not yet supported by the Stella/XMILE backend + if [[ $m == "allocate" || $m == "arrays" ]]; then + echo "Skipping test for $m (not yet supported by Stella/XMILE backend)" + echo + continue + fi + fi + if [[ $GEN_FORMAT == "js" ]]; then # Skip tests for models that are not yet supported for JS target if [[ $m == "allocate" || $m == delayfixed* || $m == "depreciate" || $m == "gamma_ln" ]]; then From 034ed4d88325daf6a632fac007d4879ed29088c8 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 10 Feb 2026 14:02:08 -0800 Subject: [PATCH 75/77] fix: prettier --- PLAN.md | 96 +++++++++++++++-------- packages/compile/src/generate/gen-expr.js | 5 +- 2 files changed, 67 insertions(+), 34 deletions(-) diff --git a/PLAN.md b/PLAN.md index e531fd9f..ae63b6fe 100644 --- a/PLAN.md +++ b/PLAN.md @@ -4,19 +4,19 @@ This document analyzes the errors found when running XMILE/Stella integration te ## Summary of Test Results -| Model | Status | Error Type | -|-------|--------|------------| -| active_initial | ✅ **FIXED** | Was: Cyclic dependency (init_eqn handling added) | -| allocate | ❌ FAIL | Unhandled function `_ALLOCATE` (incompatible signatures, deferred) | -| arrays | ❌ FAIL | Undefined subscript mapping `_dim_ab_map` (HIGH complexity, deferred) | -| comments | ✅ PASS | - | -| delay | ✅ **FIXED** | Was: Data differences (test model TIME STEP updated to 0.25) | -| delayfixed | ✅ **FIXED** | Was: Unhandled function `_DELAY` | -| delayfixed2 | ✅ **FIXED** | Was: Unhandled function `_DELAY` | -| depreciate | ✅ **FIXED** | Was: Unhandled function `_DEPRECIATE_STRAIGHTLINE` | -| elmcount | ✅ **FIXED** | Was: Unhandled function `_SIZE` | -| interleaved | ✅ PASS | - | -| trend | ✅ **FIXED** | Was: Unhandled function `_SAFEDIV` (Stella outputs regenerated) | +| Model | Status | Error Type | +| -------------- | ------------ | --------------------------------------------------------------------- | +| active_initial | ✅ **FIXED** | Was: Cyclic dependency (init_eqn handling added) | +| allocate | ❌ FAIL | Unhandled function `_ALLOCATE` (incompatible signatures, deferred) | +| arrays | ❌ FAIL | Undefined subscript mapping `_dim_ab_map` (HIGH complexity, deferred) | +| comments | ✅ PASS | - | +| delay | ✅ **FIXED** | Was: Data differences (test model TIME STEP updated to 0.25) | +| delayfixed | ✅ **FIXED** | Was: Unhandled function `_DELAY` | +| delayfixed2 | ✅ **FIXED** | Was: Unhandled function `_DELAY` | +| depreciate | ✅ **FIXED** | Was: Unhandled function `_DEPRECIATE_STRAIGHTLINE` | +| elmcount | ✅ **FIXED** | Was: Unhandled function `_SIZE` | +| interleaved | ✅ PASS | - | +| trend | ✅ **FIXED** | Was: Unhandled function `_SAFEDIV` (Stella outputs regenerated) | **Current Score: 9 passing, 2 failing** (improved from 2 passing initially) @@ -25,6 +25,7 @@ This document analyzes the errors found when running XMILE/Stella integration te ## Error 1: active_initial - Cyclic Dependency (✅ FIXED) ### Symptom (RESOLVED) + ``` Error: Found cyclic dependency during toposort: _capacity_utilization → _capacity_1 → _target_capacity → _utilization_adjustment → _capacity_utilization @@ -42,6 +43,7 @@ The XMILE parser was ignoring the `` element for auxiliary variables. ``` In Vensim, this is expressed using the `ACTIVE INITIAL` function: + ```vensim Target Capacity = ACTIVE INITIAL(Capacity*Utilization Adjustment, Initial Target Capacity) ``` @@ -52,9 +54,10 @@ The XMILE parser was updated to handle `` elements by synthesizing an --- -## Error 2: allocate - Unhandled _ALLOCATE Function +## Error 2: allocate - Unhandled \_ALLOCATE Function ### Symptom + ``` Error: Unhandled function '_ALLOCATE' in readEquations for 'shipments[region]' ``` @@ -64,16 +67,19 @@ Error: Unhandled function '_ALLOCATE' in readEquations for 'shipments[region]' **Critical Incompatibility:** Stella's `ALLOCATE` function has a fundamentally different signature than Vensim's `ALLOCATE AVAILABLE`: **Vensim (3 parameters):** + ```vensim shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) ``` **Stella (6 parameters):** + ```xml ALLOCATE(total_supply_available, region, demand, priority_vector[*,ppriority], priority_vector[region,pwidth], priority_vector[region,ptype]) ``` Key differences: + - Stella has 6 parameters vs Vensim's 3 - Parameter order is different (Stella puts availability FIRST, Vensim puts it LAST) - Stella explicitly names the dimension to iterate over @@ -90,6 +96,7 @@ Key differences: ### Proposed Fix **Option A: Full Implementation (Recommended for Completeness)** + 1. Add `case '_ALLOCATE':` to `validateStellaFunctionCall()` in `read-equations.js` 2. Create parameter adapter to map Stella's 6-param signature to internal representation 3. Either: @@ -97,11 +104,13 @@ Key differences: - Create wrapper that converts Stella params to Vensim format **Option B: Skip for Now** + - Document incompatibility - Keep tests skipped - Focus on higher-priority functions first ### Complexity: HIGH + - Fundamentally incompatible parameter signatures - Would require ~700-1200 lines of implementation + tests - May require new C runtime function or adapter layer @@ -111,6 +120,7 @@ Key differences: ## Error 3: arrays - Undefined Subscript Mapping ### Symptom + ``` ERROR: undefined hasMapping fromSubscript : '_dim_ab_map' TypeError: Cannot read properties of undefined (reading 'mappings') @@ -126,6 +136,7 @@ subscriptMappings: [], ``` The arrays.stmx model uses Vensim-style subscript mapping patterns: + ```xml @@ -134,11 +145,13 @@ The arrays.stmx model uses Vensim-style subscript mapping patterns: ``` And references it in expressions like: + ```xml inputA[dim_ab_map]*10 ``` The problem: + 1. `dim ab map` is parsed as a regular variable, not a subscript dimension 2. When the compiler tries to resolve `dim_ab_map` as a subscript, it doesn't exist 3. `hasMapping()` receives `undefined` and crashes trying to access `.mappings` @@ -148,12 +161,14 @@ The problem: **Option A: Implement XMILE Subscript Mapping Support** Research how XMILE represents dimension mappings and implement parsing support. This may require: + 1. Understanding XMILE's mapping syntax (if it exists) 2. Modifying `parse-xmile-dimension-def.ts` to populate `subscriptMappings` **Option B: Handle Variables as Subscript References** If XMILE uses variables to represent mappings (like the `dim ab map` aux variable): + 1. Detect when a subscript reference is actually a variable name 2. Evaluate the variable to get the actual subscript value 3. This is more complex as it involves runtime subscript resolution @@ -161,11 +176,13 @@ If XMILE uses variables to represent mappings (like the `dim ab map` aux variabl **Option C: Skip Test with Documentation** If XMILE fundamentally doesn't support subscript mappings: + 1. Document this limitation 2. Skip the arrays test for XMILE 3. Users would need to restructure models without mappings ### Complexity: HIGH (Options A/B) or LOW (Option C) + - Requires understanding XMILE subscript semantics - May involve fundamental parser changes - Could require runtime subscript resolution @@ -175,6 +192,7 @@ If XMILE fundamentally doesn't support subscript mappings: ## Error 4: delay - Numerical Data Differences (✅ FIXED) ### Symptom (RESOLVED) + ``` _d11[_a1] time=7.00 vensim=0 sde=-10920 diff=1092000.000000% _d8[_a1] time=7.00 vensim=0 sde=-260 diff=26000.000000% @@ -192,9 +210,10 @@ See commit 731ed4514a74de314e67ac2bc1037ee264322a69. --- -## Error 5: delayfixed/delayfixed2 - Unhandled _DELAY Function (FIXED) +## Error 5: delayfixed/delayfixed2 - Unhandled \_DELAY Function (FIXED) ### Symptom + ``` Error: Unhandled function '_DELAY' in readEquations for 'receiving' ``` @@ -225,14 +244,16 @@ case '_DELAY': ``` ### Complexity: LOW + - Copy existing `_DELAY_FIXED` case logic - May need to verify parameter order matches Stella's --- -## Error 6: depreciate - Unhandled _DEPRECIATE_STRAIGHTLINE Function +## Error 6: depreciate - Unhandled \_DEPRECIATE_STRAIGHTLINE Function ### Symptom + ``` Error: Unhandled function '_DEPRECIATE_STRAIGHTLINE' in readEquations for 'Depreciated Amount' ``` @@ -262,14 +283,16 @@ case '_DEPRECIATE_STRAIGHTLINE': **Note:** The depreciate.stmx file includes a complete macro definition showing Stella's depreciation implementation. Need to verify the parameter semantics match Vensim's. ### Complexity: LOW-MEDIUM + - Similar to existing Vensim handling - May need parameter verification between Vensim/Stella implementations --- -## Error 7: elmcount - Unhandled _SIZE Function (FIXED) +## Error 7: elmcount - Unhandled \_SIZE Function (FIXED) ### Symptom + ``` Error: Unhandled function '_SIZE' in readEquations for 'a' ``` @@ -279,6 +302,7 @@ Error: Unhandled function '_SIZE' in readEquations for 'a' `SIZE(DimA)` is an XMILE-specific function that returns the number of elements in a dimension. There is no direct Vensim equivalent (Vensim uses `ELMCOUNT`). Usage in elmcount model: + ```xml SIZE(DimA) ``` @@ -292,6 +316,7 @@ This should return 3 (since DimA has elements A1, A2, A3). This is a **compile-time constant** that can be resolved during parsing: 1. Add case in `validateStellaFunctionCall()`: + ```javascript case '_SIZE': validateCallDepth(callExpr, context) @@ -302,6 +327,7 @@ case '_SIZE': ``` 2. In code generation (`gen-expr.js`), resolve the dimension size: + ```javascript case '_SIZE': { const dimArg = callExpr.args[0] @@ -312,14 +338,16 @@ case '_SIZE': { ``` ### Complexity: LOW-MEDIUM + - Need to resolve dimension at compile time - Simple once dimension lookup is implemented --- -## Error 8: trend - Unhandled _SAFEDIV Function (✅ FIXED) +## Error 8: trend - Unhandled \_SAFEDIV Function (✅ FIXED) ### Symptom (RESOLVED) + ``` Error: Unhandled function '_SAFEDIV' in readEquations for 'trend1' ``` @@ -345,33 +373,35 @@ Two issues were found: Based on complexity and impact, here's the recommended implementation order: ### ✅ COMPLETED - Quick Wins -1. **_SAFEDIV** → Map to ZIDZ ✓ (commit 940d069f) -2. **_DELAY** → Copy DELAY_FIXED logic ✓ (commit 631cb38c) -3. **_SIZE** → Compile-time dimension resolution ✓ (commit 5099835c) + +1. **\_SAFEDIV** → Map to ZIDZ ✓ (commit 940d069f) +2. **\_DELAY** → Copy DELAY_FIXED logic ✓ (commit 631cb38c) +3. **\_SIZE** → Compile-time dimension resolution ✓ (commit 5099835c) 4. **active_initial** → Handle `` in XMILE parser ✓ -5. **_DEPRECIATE_STRAIGHTLINE** → Add to validateStellaFunctionCall ✓ +5. **\_DEPRECIATE_STRAIGHTLINE** → Add to validateStellaFunctionCall ✓ 6. **delay numerical fix** → Updated test model TIME STEP to 0.25 ✓ (commit 731ed451) 7. **trend numerical fix** → Regenerated Stella outputs ✓ (commit 6030a7a3) ### Deferred (HIGH complexity) + 8. **arrays subscript mapping** → Research XMILE mapping support -9. **_ALLOCATE** → Significant signature differences, may skip +9. **\_ALLOCATE** → Significant signature differences, may skip --- ## Files Modified -| File | Changes | -|------|---------| -| `packages/compile/src/model/read-equations.js` | Added cases for _DELAY, _SIZE, _SAFEDIV, _DEPRECIATE_STRAIGHTLINE, _ACTIVE_INITIAL to validateStellaFunctionCall() | -| `packages/compile/src/generate/gen-expr.js` | Added code generation for _SIZE (maps to ELMCOUNT), _SAFEDIV (maps to ZIDZ), _DELAY (maps to DELAY_FIXED) | -| `packages/parse/src/xmile/parse-xmile-variable-def.ts` | Added support for `` element to synthesize ACTIVE_INITIAL function call | -| `models/delay/delay.mdl`, `models/delay/delay.stmx` | Updated TIME STEP from 1 to 0.25 for numerical accuracy | -| `models/delay/delay.csv`, `models/delay/delay.dat` | Regenerated expected outputs with new TIME STEP | -| `models/trend/trend.csv` | Regenerated Stella expected outputs | +| File | Changes | +| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | +| `packages/compile/src/model/read-equations.js` | Added cases for \_DELAY, \_SIZE, \_SAFEDIV, \_DEPRECIATE_STRAIGHTLINE, \_ACTIVE_INITIAL to validateStellaFunctionCall() | +| `packages/compile/src/generate/gen-expr.js` | Added code generation for \_SIZE (maps to ELMCOUNT), \_SAFEDIV (maps to ZIDZ), \_DELAY (maps to DELAY_FIXED) | +| `packages/parse/src/xmile/parse-xmile-variable-def.ts` | Added support for `` element to synthesize ACTIVE_INITIAL function call | +| `models/delay/delay.mdl`, `models/delay/delay.stmx` | Updated TIME STEP from 1 to 0.25 for numerical accuracy | +| `models/delay/delay.csv`, `models/delay/delay.dat` | Regenerated expected outputs with new TIME STEP | +| `models/trend/trend.csv` | Regenerated Stella expected outputs | ## Files Still Needing Changes -| File | Changes | -|------|---------| +| File | Changes | +| ------------------------------------------------------- | -------------------------------------- | | `packages/parse/src/xmile/parse-xmile-dimension-def.ts` | (Future) Add subscript mapping support | diff --git a/packages/compile/src/generate/gen-expr.js b/packages/compile/src/generate/gen-expr.js index ce3802cb..d71b5a4e 100644 --- a/packages/compile/src/generate/gen-expr.js +++ b/packages/compile/src/generate/gen-expr.js @@ -290,7 +290,10 @@ function generateFunctionCall(callExpr, ctx) { case '_SAMPLE_IF_TRUE': case '_INTEG': // Split level functions into init and eval expressions - if (ctx.outFormat === 'js' && (fnId === '_DELAY' || fnId === '_DELAY_FIXED' || fnId === '_DEPRECIATE_STRAIGHTLINE')) { + if ( + ctx.outFormat === 'js' && + (fnId === '_DELAY' || fnId === '_DELAY_FIXED' || fnId === '_DEPRECIATE_STRAIGHTLINE') + ) { throw new Error(`${callExpr.fnName} function not yet implemented for JS code gen`) } if (ctx.mode.startsWith('init')) { From ff27a52152790c88722359f59eb1a129a9c530ed Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Tue, 10 Feb 2026 14:44:52 -0800 Subject: [PATCH 76/77] fix: update copyright years in parse package --- packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts | 2 +- packages/parse/src/xmile/parse-xmile-dimension-def.ts | 2 +- packages/parse/src/xmile/parse-xmile-model.spec.ts | 2 +- packages/parse/src/xmile/parse-xmile-model.ts | 2 +- packages/parse/src/xmile/parse-xmile-variable-def.spec.ts | 2 +- packages/parse/src/xmile/parse-xmile-variable-def.ts | 2 +- packages/parse/src/xmile/xml.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts index 0e8b1c6c..026b3629 100644 --- a/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.spec.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import { describe, expect, it } from 'vitest' diff --git a/packages/parse/src/xmile/parse-xmile-dimension-def.ts b/packages/parse/src/xmile/parse-xmile-dimension-def.ts index 0e4d111a..79728ef5 100644 --- a/packages/parse/src/xmile/parse-xmile-dimension-def.ts +++ b/packages/parse/src/xmile/parse-xmile-dimension-def.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import type { XmlElement } from '@rgrove/parse-xml' diff --git a/packages/parse/src/xmile/parse-xmile-model.spec.ts b/packages/parse/src/xmile/parse-xmile-model.spec.ts index b41a1fe4..7db6d2ac 100644 --- a/packages/parse/src/xmile/parse-xmile-model.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-model.spec.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import { describe, expect, it } from 'vitest' diff --git a/packages/parse/src/xmile/parse-xmile-model.ts b/packages/parse/src/xmile/parse-xmile-model.ts index 945225eb..32d22256 100644 --- a/packages/parse/src/xmile/parse-xmile-model.ts +++ b/packages/parse/src/xmile/parse-xmile-model.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import type { XmlElement } from '@rgrove/parse-xml' import { parseXml } from '@rgrove/parse-xml' diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts index 4bc04cf1..783c02c6 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.spec.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import { describe, expect, it } from 'vitest' diff --git a/packages/parse/src/xmile/parse-xmile-variable-def.ts b/packages/parse/src/xmile/parse-xmile-variable-def.ts index fea82565..ff578740 100644 --- a/packages/parse/src/xmile/parse-xmile-variable-def.ts +++ b/packages/parse/src/xmile/parse-xmile-variable-def.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import type { XmlElement } from '@rgrove/parse-xml' diff --git a/packages/parse/src/xmile/xml.ts b/packages/parse/src/xmile/xml.ts index 362ac456..9cf087a8 100644 --- a/packages/parse/src/xmile/xml.ts +++ b/packages/parse/src/xmile/xml.ts @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Climate Interactive / New Venture Fund +// Copyright (c) 2023-2026 Climate Interactive / New Venture Fund import type { XmlElement, XmlText } from '@rgrove/parse-xml' import { XmlNode } from '@rgrove/parse-xml' From dc97ccaca01296d8240284619344132c272f047c Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Fri, 13 Feb 2026 11:31:14 -0800 Subject: [PATCH 77/77] docs: remove PLAN.md (I added this to an issue comment for the record) --- PLAN.md | 407 -------------------------------------------------------- 1 file changed, 407 deletions(-) delete mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index ae63b6fe..00000000 --- a/PLAN.md +++ /dev/null @@ -1,407 +0,0 @@ -# XMILE Integration Test Fixes - Analysis and Plan - -This document analyzes the errors found when running XMILE/Stella integration tests (`INPUT_FORMAT=stmx ./tests/modeltests`) and proposes fixes for each issue. - -## Summary of Test Results - -| Model | Status | Error Type | -| -------------- | ------------ | --------------------------------------------------------------------- | -| active_initial | ✅ **FIXED** | Was: Cyclic dependency (init_eqn handling added) | -| allocate | ❌ FAIL | Unhandled function `_ALLOCATE` (incompatible signatures, deferred) | -| arrays | ❌ FAIL | Undefined subscript mapping `_dim_ab_map` (HIGH complexity, deferred) | -| comments | ✅ PASS | - | -| delay | ✅ **FIXED** | Was: Data differences (test model TIME STEP updated to 0.25) | -| delayfixed | ✅ **FIXED** | Was: Unhandled function `_DELAY` | -| delayfixed2 | ✅ **FIXED** | Was: Unhandled function `_DELAY` | -| depreciate | ✅ **FIXED** | Was: Unhandled function `_DEPRECIATE_STRAIGHTLINE` | -| elmcount | ✅ **FIXED** | Was: Unhandled function `_SIZE` | -| interleaved | ✅ PASS | - | -| trend | ✅ **FIXED** | Was: Unhandled function `_SAFEDIV` (Stella outputs regenerated) | - -**Current Score: 9 passing, 2 failing** (improved from 2 passing initially) - ---- - -## Error 1: active_initial - Cyclic Dependency (✅ FIXED) - -### Symptom (RESOLVED) - -``` -Error: Found cyclic dependency during toposort: -_capacity_utilization → _capacity_1 → _target_capacity → _utilization_adjustment → _capacity_utilization -``` - -### Root Cause - -The XMILE parser was ignoring the `` element for auxiliary variables. In XMILE, a variable can have separate init-time and eval-time equations: - -```xml - - Capacity_1*Utilization_Adjustment - Initial_Target_Capacity - -``` - -In Vensim, this is expressed using the `ACTIVE INITIAL` function: - -```vensim -Target Capacity = ACTIVE INITIAL(Capacity*Utilization Adjustment, Initial Target Capacity) -``` - -### Fix Applied - -The XMILE parser was updated to handle `` elements by synthesizing an `ACTIVE_INITIAL` function call when both `` and `` elements exist for aux variables. - ---- - -## Error 2: allocate - Unhandled \_ALLOCATE Function - -### Symptom - -``` -Error: Unhandled function '_ALLOCATE' in readEquations for 'shipments[region]' -``` - -### Root Cause - -**Critical Incompatibility:** Stella's `ALLOCATE` function has a fundamentally different signature than Vensim's `ALLOCATE AVAILABLE`: - -**Vensim (3 parameters):** - -```vensim -shipments[branch] = ALLOCATE AVAILABLE(demand[branch], priority[branch,ptype], supply available) -``` - -**Stella (6 parameters):** - -```xml -ALLOCATE(total_supply_available, region, demand, priority_vector[*,ppriority], priority_vector[region,pwidth], priority_vector[region,ptype]) -``` - -Key differences: - -- Stella has 6 parameters vs Vensim's 3 -- Parameter order is different (Stella puts availability FIRST, Vensim puts it LAST) -- Stella explicitly names the dimension to iterate over -- Stella separates priority parameters (mean, width, type) instead of packing them in a 2D array - -### Current Implementation Status - -- **Vensim:** Fully implemented - - Parsing in `read-equations.js` (lines 478-481) - - C code generation in `gen-expr.js` (lines 855-928) - - C runtime in `vensim.c` (lines 408-504) -- **Stella:** NOT implemented (4 test cases explicitly skipped in `read-equations-xmile.spec.ts`) - -### Proposed Fix - -**Option A: Full Implementation (Recommended for Completeness)** - -1. Add `case '_ALLOCATE':` to `validateStellaFunctionCall()` in `read-equations.js` -2. Create parameter adapter to map Stella's 6-param signature to internal representation -3. Either: - - Create new C runtime function for Stella's signature, OR - - Create wrapper that converts Stella params to Vensim format - -**Option B: Skip for Now** - -- Document incompatibility -- Keep tests skipped -- Focus on higher-priority functions first - -### Complexity: HIGH - -- Fundamentally incompatible parameter signatures -- Would require ~700-1200 lines of implementation + tests -- May require new C runtime function or adapter layer - ---- - -## Error 3: arrays - Undefined Subscript Mapping - -### Symptom - -``` -ERROR: undefined hasMapping fromSubscript : '_dim_ab_map' -TypeError: Cannot read properties of undefined (reading 'mappings') -``` - -### Root Cause - -The XMILE parser **does not support subscript mappings**. In `packages/parse/src/xmile/parse-xmile-dimension-def.ts` (lines 55-56): - -```typescript -// TODO: Does XMILE support mappings? -subscriptMappings: [], -``` - -The arrays.stmx model uses Vensim-style subscript mapping patterns: - -```xml - - - DimB - -``` - -And references it in expressions like: - -```xml -inputA[dim_ab_map]*10 -``` - -The problem: - -1. `dim ab map` is parsed as a regular variable, not a subscript dimension -2. When the compiler tries to resolve `dim_ab_map` as a subscript, it doesn't exist -3. `hasMapping()` receives `undefined` and crashes trying to access `.mappings` - -### Proposed Fix - -**Option A: Implement XMILE Subscript Mapping Support** - -Research how XMILE represents dimension mappings and implement parsing support. This may require: - -1. Understanding XMILE's mapping syntax (if it exists) -2. Modifying `parse-xmile-dimension-def.ts` to populate `subscriptMappings` - -**Option B: Handle Variables as Subscript References** - -If XMILE uses variables to represent mappings (like the `dim ab map` aux variable): - -1. Detect when a subscript reference is actually a variable name -2. Evaluate the variable to get the actual subscript value -3. This is more complex as it involves runtime subscript resolution - -**Option C: Skip Test with Documentation** - -If XMILE fundamentally doesn't support subscript mappings: - -1. Document this limitation -2. Skip the arrays test for XMILE -3. Users would need to restructure models without mappings - -### Complexity: HIGH (Options A/B) or LOW (Option C) - -- Requires understanding XMILE subscript semantics -- May involve fundamental parser changes -- Could require runtime subscript resolution - ---- - -## Error 4: delay - Numerical Data Differences (✅ FIXED) - -### Symptom (RESOLVED) - -``` -_d11[_a1] time=7.00 vensim=0 sde=-10920 diff=1092000.000000% -_d8[_a1] time=7.00 vensim=0 sde=-260 diff=26000.000000% -``` - -### Root Cause - -The test model was using a TIME STEP of 1, which was too coarse for accurate DELAY3 calculations. DELAY3 uses a third-order exponential delay approximation that requires smaller time steps for numerical stability. - -### Fix Applied - -Updated the `delay` test models (both `.mdl` and `.stmx`) to use TIME STEP of 0.25 instead of 1, and regenerated the expected outputs. This provides sufficient numerical accuracy for the delay function approximations. - -See commit 731ed4514a74de314e67ac2bc1037ee264322a69. - ---- - -## Error 5: delayfixed/delayfixed2 - Unhandled \_DELAY Function (FIXED) - -### Symptom - -``` -Error: Unhandled function '_DELAY' in readEquations for 'receiving' -``` - -### Root Cause - -XMILE uses `DELAY(input, delay_time, initial)` which gets canonicalized to `_DELAY`, but this function is not handled in `validateStellaFunctionCall()` in `read-equations.js`. - -Vensim has the equivalent `DELAY FIXED` which is already fully implemented (lines 493-503). - -### Proposed Fix - -**File:** `packages/compile/src/model/read-equations.js` - -Add case in `validateStellaFunctionCall()` (around line 630-735): - -```javascript -case '_DELAY': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 3) - v.varType = 'level' - v.varSubtype = 'fixedDelay' - v.hasInitValue = true - v.fixedDelayVarName = canonicalName(newFixedDelayVarName()) - argModes[1] = 'init' - argModes[2] = 'init' - break -``` - -### Complexity: LOW - -- Copy existing `_DELAY_FIXED` case logic -- May need to verify parameter order matches Stella's - ---- - -## Error 6: depreciate - Unhandled \_DEPRECIATE_STRAIGHTLINE Function - -### Symptom - -``` -Error: Unhandled function '_DEPRECIATE_STRAIGHTLINE' in readEquations for 'Depreciated Amount' -``` - -### Root Cause - -`_DEPRECIATE_STRAIGHTLINE` is already handled for Vensim (lines 505-517 in `read-equations.js`) but not added to `validateStellaFunctionCall()`. - -### Proposed Fix - -**File:** `packages/compile/src/model/read-equations.js` - -Add case in `validateStellaFunctionCall()`: - -```javascript -case '_DEPRECIATE_STRAIGHTLINE': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 4) - v.varSubtype = 'depreciation' - v.hasInitValue = true - v.depreciationVarName = canonicalName(newDepreciationVarName()) - argModes[1] = 'init' - argModes[2] = 'init' - break -``` - -**Note:** The depreciate.stmx file includes a complete macro definition showing Stella's depreciation implementation. Need to verify the parameter semantics match Vensim's. - -### Complexity: LOW-MEDIUM - -- Similar to existing Vensim handling -- May need parameter verification between Vensim/Stella implementations - ---- - -## Error 7: elmcount - Unhandled \_SIZE Function (FIXED) - -### Symptom - -``` -Error: Unhandled function '_SIZE' in readEquations for 'a' -``` - -### Root Cause - -`SIZE(DimA)` is an XMILE-specific function that returns the number of elements in a dimension. There is no direct Vensim equivalent (Vensim uses `ELMCOUNT`). - -Usage in elmcount model: - -```xml -SIZE(DimA) -``` - -This should return 3 (since DimA has elements A1, A2, A3). - -### Proposed Fix - -**File:** `packages/compile/src/model/read-equations.js` - -This is a **compile-time constant** that can be resolved during parsing: - -1. Add case in `validateStellaFunctionCall()`: - -```javascript -case '_SIZE': - validateCallDepth(callExpr, context) - validateCallArgs(callExpr, 1) - // Mark as const since it can be resolved at compile time - v.varType = 'const' - break -``` - -2. In code generation (`gen-expr.js`), resolve the dimension size: - -```javascript -case '_SIZE': { - const dimArg = callExpr.args[0] - const dimId = canonicalName(dimArg.name) - const dim = sub(dimId) - return String(dim.size) -} -``` - -### Complexity: LOW-MEDIUM - -- Need to resolve dimension at compile time -- Simple once dimension lookup is implemented - ---- - -## Error 8: trend - Unhandled \_SAFEDIV Function (✅ FIXED) - -### Symptom (RESOLVED) - -``` -Error: Unhandled function '_SAFEDIV' in readEquations for 'trend1' -``` - -### Root Cause - -Two issues were found: - -1. `SAFEDIV(numerator, denominator)` is an XMILE-specific safe division function that returns 0 if the denominator is 0 (or near 0). This is equivalent to Vensim's `ZIDZ` (Zero If Divide by Zero). - -2. The expected Stella outputs in `trend.csv` were outdated and didn't match the current model behavior. - -### Fix Applied - -1. Added `_SAFEDIV` support in `validateStellaFunctionCall()` and mapped it to `_ZIDZ` in code generation (commit 940d069f). - -2. Regenerated the Stella outputs for `trend.stmx` test model (commit 6030a7a3). - ---- - -## Implementation Priority - -Based on complexity and impact, here's the recommended implementation order: - -### ✅ COMPLETED - Quick Wins - -1. **\_SAFEDIV** → Map to ZIDZ ✓ (commit 940d069f) -2. **\_DELAY** → Copy DELAY_FIXED logic ✓ (commit 631cb38c) -3. **\_SIZE** → Compile-time dimension resolution ✓ (commit 5099835c) -4. **active_initial** → Handle `` in XMILE parser ✓ -5. **\_DEPRECIATE_STRAIGHTLINE** → Add to validateStellaFunctionCall ✓ -6. **delay numerical fix** → Updated test model TIME STEP to 0.25 ✓ (commit 731ed451) -7. **trend numerical fix** → Regenerated Stella outputs ✓ (commit 6030a7a3) - -### Deferred (HIGH complexity) - -8. **arrays subscript mapping** → Research XMILE mapping support -9. **\_ALLOCATE** → Significant signature differences, may skip - ---- - -## Files Modified - -| File | Changes | -| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | -| `packages/compile/src/model/read-equations.js` | Added cases for \_DELAY, \_SIZE, \_SAFEDIV, \_DEPRECIATE_STRAIGHTLINE, \_ACTIVE_INITIAL to validateStellaFunctionCall() | -| `packages/compile/src/generate/gen-expr.js` | Added code generation for \_SIZE (maps to ELMCOUNT), \_SAFEDIV (maps to ZIDZ), \_DELAY (maps to DELAY_FIXED) | -| `packages/parse/src/xmile/parse-xmile-variable-def.ts` | Added support for `` element to synthesize ACTIVE_INITIAL function call | -| `models/delay/delay.mdl`, `models/delay/delay.stmx` | Updated TIME STEP from 1 to 0.25 for numerical accuracy | -| `models/delay/delay.csv`, `models/delay/delay.dat` | Regenerated expected outputs with new TIME STEP | -| `models/trend/trend.csv` | Regenerated Stella expected outputs | - -## Files Still Needing Changes - -| File | Changes | -| ------------------------------------------------------- | -------------------------------------- | -| `packages/parse/src/xmile/parse-xmile-dimension-def.ts` | (Future) Add subscript mapping support |