From 5e14e964e86af55cbc105cc4a564b5a539f70622 Mon Sep 17 00:00:00 2001 From: Chris Campbell Date: Wed, 28 Aug 2024 14:12:26 -0700 Subject: [PATCH] fix: include 'initial' variables in JSON model listing --- packages/compile/src/model/model.js | 64 ++++++++++++----- packages/compile/src/model/model.spec.ts | 92 ++++++++++++++---------- 2 files changed, 100 insertions(+), 56 deletions(-) diff --git a/packages/compile/src/model/model.js b/packages/compile/src/model/model.js index c6909d79..9c9f15ba 100644 --- a/packages/compile/src/model/model.js +++ b/packages/compile/src/model/model.js @@ -525,24 +525,28 @@ function allVars() { return R.filter(isNotPlaceholderVar, variables) } function constVars() { + // Return an array of vars of type `const`, sorted by LHS variable name. return vsort(varsOfType('const')) } function lookupVars() { + // Return an array of vars of type `lookup`, sorted by LHS variable name. return vsort(varsOfType('lookup')) } function dataVars() { + // Return an array of vars of type `data`, sorted by LHS variable name. return vsort(varsOfType('data')) } function auxVars() { - // console.error('AUX VARS'); + // Return an array of vars of type `aux`, sorted according to the dependency graph. return sortVarsOfType('aux') } function levelVars() { - // console.error('LEVEL VARS'); + // Return an array of vars of type `level`, sorted according to the dependency graph. return sortVarsOfType('level') } function initVars() { - // console.error('INIT VARS'); + // Return an array of all vars that have the `hasInitValue` flag set to true, + // sorted according to the dependency graph. return sortInitVars() } function varWithRefId(refId) { @@ -1055,26 +1059,52 @@ function printDepsGraph(graph, varType) { function allListedVars() { // Put variables into the order that they are evaluated by SDE in the generated model - let vars = [] - vars.push(...constVars()) - vars.push(...lookupVars()) - vars.push(...dataVars()) + const listedVars = [] + const visitedRefIds = new Set() + function addUnique(vars) { + for (const v of vars) { + // Skip variables that have already been visited + if (visitedRefIds.has(v.refId)) { + continue + } + visitedRefIds.add(v.refId) + + // Filter out variables that are generated/used internally + if (v.includeInOutput === false) { + continue + } + + // Include the variable + listedVars.push(v) + } + } + + // The order of execution/evaluation in the generated model is: + // initConstants (vars of type `const` only) + // initLookups (vars of type `lookup` only) + // initData (vars of type `data` only) + // initLevels (vars returned by `initVars`, a mix of initial, aux, and level vars) + // evalAux (vars of type `aux` only) + // evalLevels (vars of type `level` only) + // So to make the ordering in the listing better match the order of evaluation, + // we emit variables in the above order, but filter to avoid having duplicates. + addUnique(constVars()) + addUnique(lookupVars()) + addUnique(dataVars()) // The special exogenous `Time` variable may have already been removed by // `removeUnusedVariables` if it is not referenced explicitly in the model, - // so we will only include it in the listing if it is defined here + // so we will only include it in the listing if it is defined here. Note + // that `_time` is set to `_initial_time` as the first step in the + // `initLevels` function, which is why it is included here. const timeVar = varWithName('_time') if (timeVar) { - vars.push(timeVar) - } - vars.push(...auxVars()) - vars.push(...levelVars()) - - // Filter out variables that are generated/used internally - const isInternal = v => { - return v.includeInOutput === false + addUnique([timeVar]) } + addUnique(initVars()) + addUnique(auxVars()) + addUnique(levelVars()) - return R.filter(v => !isInternal(v), vars) + return listedVars } function filteredListedVars() { diff --git a/packages/compile/src/model/model.spec.ts b/packages/compile/src/model/model.spec.ts index 366d0e46..3c26a2fa 100644 --- a/packages/compile/src/model/model.spec.ts +++ b/packages/compile/src/model/model.spec.ts @@ -69,7 +69,7 @@ function readInlineModel( describe('Model', () => { describe('jsonList', () => { - it('should expose accessible variables', () => { + it('should expose accessible variables sorted in order of execution', () => { const listing = readInlineModel(` DimA: A1, A2 ~~| DimB: B1, B2 ~~| @@ -84,6 +84,7 @@ describe('Model', () => { c data ~~| c = c data ~~| d[DimA] = 10, 11 ~~| + e = INITIAL(c) ~~| level init = 5 ~~| level = INTEG(x, level init) ~~| w = WITH LOOKUP(x, ( [(0,0)-(2,2)], (0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3) )) ~~| @@ -92,7 +93,6 @@ describe('Model', () => { TIME STEP = 1 ~~| SAVEPER = 1 ~~| `) - expect(listing.full).toEqual({ dimensions: [ { @@ -235,6 +235,37 @@ describe('Model', () => { modelFormula: '', varIndex: 11 }, + { + refId: '_c', + varName: '_c', + references: ['_c_data'], + hasInitValue: false, + varType: 'aux', + modelLHS: 'c', + modelFormula: 'c data', + varIndex: 12 + }, + { + refId: '_e', + varName: '_e', + hasInitValue: true, + initReferences: ['_c'], + varType: 'initial', + modelLHS: 'e', + modelFormula: 'INITIAL(c)', + varIndex: 13 + }, + { + refId: '_level', + varName: '_level', + references: ['_x'], + hasInitValue: true, + initReferences: ['_level_init'], + varType: 'level', + modelLHS: 'level', + modelFormula: 'INTEG(x,level init)', + varIndex: 14 + }, { refId: '_a', varName: '_a', @@ -245,7 +276,7 @@ describe('Model', () => { varType: 'aux', modelLHS: 'a[DimA]', modelFormula: 'a data[DimA]', - varIndex: 12 + varIndex: 15 }, { refId: '_b', @@ -257,17 +288,7 @@ describe('Model', () => { varType: 'aux', modelLHS: 'b[DimA,DimB]', modelFormula: 'b data[DimA,DimB]', - varIndex: 13 - }, - { - refId: '_c', - varName: '_c', - references: ['_c_data'], - hasInitValue: false, - varType: 'aux', - modelLHS: 'c', - modelFormula: 'c data', - varIndex: 14 + varIndex: 16 }, { refId: '_x', @@ -277,7 +298,7 @@ describe('Model', () => { varType: 'aux', modelLHS: 'x', modelFormula: 'input', - varIndex: 15 + varIndex: 17 }, { refId: '_w', @@ -287,7 +308,7 @@ describe('Model', () => { varType: 'aux', modelLHS: 'w', modelFormula: 'WITH LOOKUP(x,([(0,0)-(2,2)],(0,0),(0.1,0.01),(0.5,0.7),(1,1),(1.5,1.2),(2,1.3)))', - varIndex: 16 + varIndex: 18 }, { refId: '_y', @@ -297,7 +318,7 @@ describe('Model', () => { varType: 'aux', modelLHS: 'y', modelFormula: ':NOT: x', - varIndex: 17 + varIndex: 19 }, { refId: '_z', @@ -307,18 +328,7 @@ describe('Model', () => { varType: 'aux', modelLHS: 'z', modelFormula: 'ABS(y)', - varIndex: 18 - }, - { - refId: '_level', - varName: '_level', - references: ['_x'], - hasInitValue: true, - initReferences: ['_level_init'], - varType: 'level', - modelLHS: 'level', - modelFormula: 'INTEG(x,level init)', - varIndex: 19 + varIndex: 20 } ] }) @@ -383,38 +393,42 @@ describe('Model', () => { index: 11 }, { - id: '_a', - dimIds: ['_dima'], + id: '_c', index: 12 }, { - id: '_b', - dimIds: ['_dima', '_dimb'], + id: '_e', index: 13 }, { - id: '_c', + id: '_level', index: 14 }, { - id: '_x', + id: '_a', + dimIds: ['_dima'], index: 15 }, { - id: '_w', + id: '_b', + dimIds: ['_dima', '_dimb'], index: 16 }, { - id: '_y', + id: '_x', index: 17 }, { - id: '_z', + id: '_w', index: 18 }, { - id: '_level', + id: '_y', index: 19 + }, + { + id: '_z', + index: 20 } ] })