From 92b7105c356668825f28edf5b69ed6491705a6ad Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Wed, 8 Sep 2021 22:46:47 -0700 Subject: [PATCH 01/13] add DELAY FIXED function with test model --- models/delayfixed/delayfixed.dat | 418 +++++++++++++++++++++++++++++++ models/delayfixed/delayfixed.mdl | 72 ++++++ src/c/vensim.c | 31 ++- src/c/vensim.h | 13 + 4 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 models/delayfixed/delayfixed.dat create mode 100644 models/delayfixed/delayfixed.mdl diff --git a/models/delayfixed/delayfixed.dat b/models/delayfixed/delayfixed.dat new file mode 100644 index 00000000..3ea617a7 --- /dev/null +++ b/models/delayfixed/delayfixed.dat @@ -0,0 +1,418 @@ +FINAL TIME +0 100 +INITIAL TIME +0 0 +receiving +0 0 +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +9 0 +10 0 +11 0 +12 0 +13 0 +14 0 +15 0 +16 0 +17 0 +18 0 +19 0 +20 0 +21 0 +22 0 +23 0 +24 0 +25 0 +26 0 +27 0 +28 0 +29 0 +30 1 +31 1 +32 1 +33 1 +34 1 +35 1 +36 1 +37 1 +38 1 +39 1 +40 0 +41 0 +42 0 +43 0 +44 0 +45 0 +46 0 +47 0 +48 0 +49 0 +50 0 +51 0 +52 0 +53 0 +54 0 +55 0 +56 0 +57 0 +58 0 +59 0 +60 0 +61 0 +62 0 +63 0 +64 0 +65 0 +66 0 +67 0 +68 0 +69 0 +70 0 +71 0 +72 0 +73 0 +74 0 +75 0 +76 0 +77 0 +78 0 +79 0 +80 0 +81 0 +82 0 +83 0 +84 0 +85 0 +86 0 +87 0 +88 0 +89 0 +90 0 +91 0 +92 0 +93 0 +94 0 +95 0 +96 0 +97 0 +98 0 +99 0 +100 0 +reference shipping rate +0 1 +SAVEPER +0 1 +1 1 +2 1 +3 1 +4 1 +5 1 +6 1 +7 1 +8 1 +9 1 +10 1 +11 1 +12 1 +13 1 +14 1 +15 1 +16 1 +17 1 +18 1 +19 1 +20 1 +21 1 +22 1 +23 1 +24 1 +25 1 +26 1 +27 1 +28 1 +29 1 +30 1 +31 1 +32 1 +33 1 +34 1 +35 1 +36 1 +37 1 +38 1 +39 1 +40 1 +41 1 +42 1 +43 1 +44 1 +45 1 +46 1 +47 1 +48 1 +49 1 +50 1 +51 1 +52 1 +53 1 +54 1 +55 1 +56 1 +57 1 +58 1 +59 1 +60 1 +61 1 +62 1 +63 1 +64 1 +65 1 +66 1 +67 1 +68 1 +69 1 +70 1 +71 1 +72 1 +73 1 +74 1 +75 1 +76 1 +77 1 +78 1 +79 1 +80 1 +81 1 +82 1 +83 1 +84 1 +85 1 +86 1 +87 1 +88 1 +89 1 +90 1 +91 1 +92 1 +93 1 +94 1 +95 1 +96 1 +97 1 +98 1 +99 1 +100 1 +shipments in transit +0 0 +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +9 0 +10 0 +11 1 +12 2 +13 3 +14 4 +15 5 +16 6 +17 7 +18 8 +19 9 +20 10 +21 10 +22 10 +23 10 +24 10 +25 10 +26 10 +27 10 +28 10 +29 10 +30 10 +31 9 +32 8 +33 7 +34 6 +35 5 +36 4 +37 3 +38 2 +39 1 +40 0 +41 0 +42 0 +43 0 +44 0 +45 0 +46 0 +47 0 +48 0 +49 0 +50 0 +51 0 +52 0 +53 0 +54 0 +55 0 +56 0 +57 0 +58 0 +59 0 +60 0 +61 0 +62 0 +63 0 +64 0 +65 0 +66 0 +67 0 +68 0 +69 0 +70 0 +71 0 +72 0 +73 0 +74 0 +75 0 +76 0 +77 0 +78 0 +79 0 +80 0 +81 0 +82 0 +83 0 +84 0 +85 0 +86 0 +87 0 +88 0 +89 0 +90 0 +91 0 +92 0 +93 0 +94 0 +95 0 +96 0 +97 0 +98 0 +99 0 +100 0 +shipping +0 0 +1 0 +2 0 +3 0 +4 0 +5 0 +6 0 +7 0 +8 0 +9 0 +10 1 +11 1 +12 1 +13 1 +14 1 +15 1 +16 1 +17 1 +18 1 +19 1 +20 0 +21 0 +22 0 +23 0 +24 0 +25 0 +26 0 +27 0 +28 0 +29 0 +30 0 +31 0 +32 0 +33 0 +34 0 +35 0 +36 0 +37 0 +38 0 +39 0 +40 0 +41 0 +42 0 +43 0 +44 0 +45 0 +46 0 +47 0 +48 0 +49 0 +50 0 +51 0 +52 0 +53 0 +54 0 +55 0 +56 0 +57 0 +58 0 +59 0 +60 0 +61 0 +62 0 +63 0 +64 0 +65 0 +66 0 +67 0 +68 0 +69 0 +70 0 +71 0 +72 0 +73 0 +74 0 +75 0 +76 0 +77 0 +78 0 +79 0 +80 0 +81 0 +82 0 +83 0 +84 0 +85 0 +86 0 +87 0 +88 0 +89 0 +90 0 +91 0 +92 0 +93 0 +94 0 +95 0 +96 0 +97 0 +98 0 +99 0 +100 0 +shipping time +0 20 +TIME STEP +0 1 diff --git a/models/delayfixed/delayfixed.mdl b/models/delayfixed/delayfixed.mdl new file mode 100644 index 00000000..ee8c86e2 --- /dev/null +++ b/models/delayfixed/delayfixed.mdl @@ -0,0 +1,72 @@ +{UTF-8} +receiving = DELAY FIXED(shipping, shipping time, shipping) ~~| +shipments in transit = INTEG(shipping - receiving, shipping * shipping time) ~~| +reference shipping rate = 1 ~~| +shipping = STEP(reference shipping rate, 10) - STEP(reference shipping rate, 20) ~~| +shipping time = 20 ~~| + +INITIAL TIME = 0 ~~| +FINAL TIME = 100 ~~| +TIME STEP = 1 ~~| +SAVEPER = TIME STEP ~~| + +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 1 +$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|255-255-255|96,96,100,0 +///---\\\ +:L<%^E!@ +1:delayfixed.vdfx +4:Time +5:FINAL TIME +9:delayfixed +19:100,0 +24:0 +25:100 +26:100 +57:1 +54:0 +55:0 +82:0 +86:0 +59:0 +56:0 +58:0 +71:0 +44:65001 +46:0 +45:0 +49:0 +50:0 +51: +52: +53: +43:delayfixed +47:delayfixed +48: +15:0,0,0,0,0,0 +27:0, +34:0, +42:1 +72:0 +73:0 +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:2 +41:0 +95:0 +96:0 +97:0 +77:0 +78:0 +93:0 +94:0 +92:0 +91:0 +90:0 +87:0 +75: +43:delayfixed diff --git a/src/c/vensim.c b/src/c/vensim.c index a05f0d5b..2748f33b 100644 --- a/src/c/vensim.c +++ b/src/c/vensim.c @@ -10,7 +10,6 @@ double _epsilon = 1e-6; // See the Vensim Reference Manual for descriptions of the functions. // http://www.vensim.com/documentation/index.html?22300.htm // - double _PULSE(double start, double width) { double time_plus = _time + _time_step / 2.0; if (width == 0.0) { @@ -404,3 +403,33 @@ double* _ALLOCATE_AVAILABLE( // Return a pointer to the allocations array the caller passed with the results filled in. return allocations; } + +// +// DELAY FIXED +// +FixedDelay* __new_fixed_delay(double delay_time, double initial_value) { + // Make new fixed delay data with a ring buffer for the delay line. + // We don't know the size until runtime, so it must be dynamically allocated. + // Oniy initialize once, when the pointer to the structure is still null. + FixedDelay* fixed_delay = malloc(sizeof(FixedDelay)); + fixed_delay->n = (size_t)ceil(delay_time / _time_step); + fixed_delay->data = malloc(sizeof(double) * fixed_delay->n); + fixed_delay->data_index = 0; + fixed_delay->initial_value = initial_value; + return fixed_delay; +} +double _DELAY_FIXED(double input, FixedDelay* fixed_delay) { + // Cache input values in a ring buffer for the number of time steps equal to the delay time. + // Return the init value until the time reaches the delay time. + double result; + if (_time < fixed_delay->n * _time_step - 1e-6) { + result = fixed_delay->initial_value; + } else { + result = fixed_delay->data[fixed_delay->data_index + 1]; + } + fixed_delay->data[fixed_delay->data_index++] = input; + if (fixed_delay->data_index >= fixed_delay->n) { + fixed_delay->data_index = 0; + } + return result; +} diff --git a/src/c/vensim.h b/src/c/vensim.h index 2ded646e..a4616b13 100644 --- a/src/c/vensim.h +++ b/src/c/vensim.h @@ -71,6 +71,19 @@ double __get_data_between_times(double *data, size_t n, double input, LookupMode #define _GET_DATA_MODE_TO_LOOKUP_MODE(mode) ((mode) >= 1) ? Forward : (((mode) <= -1) ? Backward : Interpolate) #define _GET_DATA_BETWEEN_TIMES(lookup, x, mode) __get_data_between_times((lookup)->data, (lookup)->n, x, _GET_DATA_MODE_TO_LOOKUP_MODE(mode)) +// +// DELAY FIXED +// +typedef struct { + double* data; + size_t n; + size_t data_index; + double initial_value; +} FixedDelay; + +double _DELAY_FIXED(double input, FixedDelay* fixed_delay); +FixedDelay* __new_fixed_delay(double delay_time, double initial_value); + #ifdef __cplusplus } #endif From 994b172e35612c9edef0ef14fc2b27d9218cbf5f Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Thu, 9 Sep 2021 21:20:42 -0700 Subject: [PATCH 02/13] set initial value in data buffer before delay time reached --- src/c/vensim.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/c/vensim.c b/src/c/vensim.c index 2748f33b..70d7825f 100644 --- a/src/c/vensim.c +++ b/src/c/vensim.c @@ -423,13 +423,26 @@ double _DELAY_FIXED(double input, FixedDelay* fixed_delay) { // Return the init value until the time reaches the delay time. double result; if (_time < fixed_delay->n * _time_step - 1e-6) { - result = fixed_delay->initial_value; + result = fixed_delay->initial_value; + fixed_delay->data[fixed_delay->data_index] = fixed_delay->initial_value; } else { - result = fixed_delay->data[fixed_delay->data_index + 1]; + result = fixed_delay->data[fixed_delay->data_index]; + fixed_delay->data[fixed_delay->data_index] = input; } - fixed_delay->data[fixed_delay->data_index++] = input; - if (fixed_delay->data_index >= fixed_delay->n) { + if (++fixed_delay->data_index >= fixed_delay->n) { fixed_delay->data_index = 0; } return result; } +// double _DELAY_FIXED(double input, FixedDelay* fixed_delay) { +// // Cache input values in a ring buffer for the number of time steps equal to the delay time. +// // Return the init value until the time reaches the delay time. +// double result; +// bool delayTimeReached = _time > fixed_delay->n * _time_step + 1e-6; +// result = delayTimeReached ? fixed_delay->data[fixed_delay->data_index] : fixed_delay->initial_value; +// fixed_delay->data[fixed_delay->data_index++] = delayTimeReached ? input : fixed_delay->initial_value; +// if (fixed_delay->data_index >= fixed_delay->n) { +// fixed_delay->data_index = 0; +// } +// return result; +// } From 1b6c44309e07e62d879aa80c5a05955a958e799a Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 10 Sep 2021 17:16:53 -0700 Subject: [PATCH 03/13] emit FIXED DELAY as a level with special support data --- src/CodeGen.js | 7 ++++++- src/EquationGen.js | 22 +++++++++++++++++++--- src/EquationReader.js | 9 +++++++-- src/Helpers.js | 6 ++++++ src/ModelReader.js | 6 +++++- src/Variable.js | 7 +++++++ src/c/vensim.c | 36 ++++++++++++------------------------ 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/CodeGen.js b/src/CodeGen.js index d63c86be..33ee3efd 100644 --- a/src/CodeGen.js +++ b/src/CodeGen.js @@ -197,12 +197,17 @@ ${postStep} // function declSection() { // Emit a declaration for each variable in the model. + let fixedDelayDecls = '' let decl = v => { // Build a C array declaration for the variable v. // This uses the subscript family for each dimension, which may overallocate // if the subscript is a subdimension. let varType = v.isLookup() || v.isData() ? 'Lookup* ' : 'double ' let families = subscriptFamilies(v.subscripts) + if (v.isFixedDelay()) { + // Add the associated FixedDelay var decl. + fixedDelayDecls += `\nFixedDelay* ${v.fixedDelayVarName};` + } return varType + v.varName + R.map(family => `[${sub(family).size}]`, families).join('') } // Non-apply-to-all variables are declared multiple times, but coalesce using uniq. @@ -212,7 +217,7 @@ ${postStep} asort, lines ) - return decls(Model.allVars()) + return decls(Model.allVars()) + fixedDelayDecls } function internalVarsSection() { // Declare internal variables to run the model. diff --git a/src/EquationGen.js b/src/EquationGen.js index 455fd51c..64d1359f 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -754,23 +754,39 @@ export default class EquationGen extends ModelReader { let exprs = ctx.expr() let fn = this.currentFunctionName() // Split level functions into init and eval expressions. - if (fn === '_INTEG' || fn === '_SAMPLE_IF_TRUE' || fn === '_ACTIVE_INITIAL') { + if (fn === '_INTEG' || fn === '_SAMPLE_IF_TRUE' || fn === '_ACTIVE_INITIAL' || fn === '_DELAY_FIXED') { if (this.mode.startsWith('init')) { // Get the index of the argument holding the initial value. let i = 0 if (fn === '_INTEG' || fn === '_ACTIVE_INITIAL') { i = 1 - } else if (fn === '_SAMPLE_IF_TRUE') { + } else if (fn === '_SAMPLE_IF_TRUE' || fn === '_DELAY_FIXED') { i = 2 } this.setArgIndex(i) exprs[i].accept(this) + // For DELAY FIXED, also initialize the support struct out of band, as it is not a Vensim var. + if (fn === '_DELAY_FIXED') { + this.emit(`;\n ${this.var.fixedDelayVarName} = __new_fixed_delay(`) + this.setArgIndex(1) + exprs[1].accept(this) + this.emit(', ') + this.setArgIndex(2) + exprs[2].accept(this) + this.emit(')') + } } else { // We are in eval mode, not init mode. - // For ACTIVE INITIAL, emit the first arg without a function call. if (fn === '_ACTIVE_INITIAL') { + // For ACTIVE INITIAL, emit the first arg without a function call. this.setArgIndex(0) exprs[0].accept(this) + } else if (fn === '_DELAY_FIXED') { + // For DELAY FIXED, emit the first arg followed by the FixedDelay support var. + this.setArgIndex(0) + exprs[0].accept(this) + this.emit(', ') + this.emit(this.var.fixedDelayVarName) } else { // Emit the variable LHS as the first arg at eval time, giving the current value for the level. this.emit(this.lhs) diff --git a/src/EquationReader.js b/src/EquationReader.js index 9f37863d..d6220cd6 100644 --- a/src/EquationReader.js +++ b/src/EquationReader.js @@ -25,7 +25,8 @@ import { matchRegex, newAuxVarName, newLevelVarName, - newLookupVarName + newLookupVarName, + newFixedDelayVarName } from './Helpers.js' // Set this true to get a list of functions used in the model. This may include lookups. @@ -88,9 +89,13 @@ export default class EquationReader extends ModelReader { if (PRINT_FUNCTION_NAMES) { console.error(fn) } - if (fn === '_INTEG') { + if (fn === '_INTEG' || fn === '_DELAY_FIXED') { this.var.varType = 'level' this.var.hasInitValue = true + if (fn === '_DELAY_FIXED') { + this.var.varSubtype = 'fixedDelay' + this.var.fixedDelayVarName = canonicalName(newFixedDelayVarName()) + } } else if (fn === '_INITIAL') { this.var.varType = 'initial' this.var.hasInitValue = true diff --git a/src/Helpers.js b/src/Helpers.js index 12a6c826..5e586ee5 100644 --- a/src/Helpers.js +++ b/src/Helpers.js @@ -16,6 +16,8 @@ export const PRINT_VLOG_TRACE = false let nextTmpVarSeq = 1 // next sequence number for generated lookup variable names let nextLookupVarSeq = 1 +// next sequence number for generated fixed delay variable names +let nextFixedDelayVarSeq = 1 // next sequence number for generated level variable names let nextLevelVarSeq = 1 // next sequence number for generated aux variable names @@ -74,6 +76,10 @@ export let newLookupVarName = () => { // Return a unique lookup arg variable name return `_lookup${nextLookupVarSeq++}` } +export let newFixedDelayVarName = () => { + // Return a unique fixed delay variable name + return `_fixed_delay${nextFixedDelayVarSeq++}` +} export let newLevelVarName = (basename = null, levelNumber = 0) => { // Return a unique level variable name. let levelName = basename || nextLevelVarSeq++ diff --git a/src/ModelReader.js b/src/ModelReader.js index cc12f516..7b8970e6 100644 --- a/src/ModelReader.js +++ b/src/ModelReader.js @@ -3,20 +3,24 @@ import { ModelVisitor } from 'antlr4-vensim' export default class ModelReader extends ModelVisitor { constructor() { super() - // stack of function names and argument indices + // stack of function names and argument indices encountered on the RHS this.callStack = [] } currentFunctionName() { + // Return the name of the current function on top of the call stack. let n = this.callStack.length return n > 0 ? this.callStack[n - 1].fn : '' } setArgIndex(argIndex) { + // Set the argument index in the current function call on top of the call stack. + // This may be set in the exprList visitor and picked up in the var visitor to facilitate special argument handling. let n = this.callStack.length if (n > 0) { this.callStack[n - 1].argIndex = argIndex } } argIndexForFunctionName(name) { + // Search the call stack for the function name. Return the current argument index or undefined if not found. let argIndex for (let i = this.callStack.length - 1; i >= 0; i--) { if (this.callStack[i].fn === name) { diff --git a/src/Variable.js b/src/Variable.js index db7b35d2..879a719d 100644 --- a/src/Variable.js +++ b/src/Variable.js @@ -24,6 +24,8 @@ export default class Variable { this.refId = '' // The default varType is aux, but may be overridden later. this.varType = 'aux' + // The variable subtype accommodates special handling needed by some Vensim functions. + this.varSubtype = '' // A variable may reference other variable names at eval time. this.references = [] // Levels and certain other variables have an initial value that may reference other variable names. @@ -41,6 +43,8 @@ export default class Variable { // DELAY3* calls are expanded into new level vars and substituted during code generation. this.delayVarRefId = '' this.delayTimeVarName = '' + // DELAY FIXED calls generate a FixedDelay support var. + this.fixedDelayVarName = '' // Variables generated by special expansions are not included in output. this.includeInOutput = true } @@ -92,6 +96,9 @@ export default class Variable { isLevel() { return this.varType === 'level' } + isFixedDelay() { + return this.varSubtype === 'fixedDelay' + } isInitial() { return this.varType === 'initial' } diff --git a/src/c/vensim.c b/src/c/vensim.c index 70d7825f..daaa81e0 100644 --- a/src/c/vensim.c +++ b/src/c/vensim.c @@ -408,9 +408,10 @@ double* _ALLOCATE_AVAILABLE( // DELAY FIXED // FixedDelay* __new_fixed_delay(double delay_time, double initial_value) { - // Make new fixed delay data with a ring buffer for the delay line. + // Construct a FixedDelay struct with a ring buffer for the delay line. // We don't know the size until runtime, so it must be dynamically allocated. - // Oniy initialize once, when the pointer to the structure is still null. + // The delay time is quantized to an integral number of time steps. + // The FixedDelay should be constructed at init time to latch the delay time and initial value. FixedDelay* fixed_delay = malloc(sizeof(FixedDelay)); fixed_delay->n = (size_t)ceil(delay_time / _time_step); fixed_delay->data = malloc(sizeof(double) * fixed_delay->n); @@ -421,28 +422,15 @@ FixedDelay* __new_fixed_delay(double delay_time, double initial_value) { double _DELAY_FIXED(double input, FixedDelay* fixed_delay) { // Cache input values in a ring buffer for the number of time steps equal to the delay time. // Return the init value until the time reaches the delay time. - double result; - if (_time < fixed_delay->n * _time_step - 1e-6) { - result = fixed_delay->initial_value; - fixed_delay->data[fixed_delay->data_index] = fixed_delay->initial_value; - } else { - result = fixed_delay->data[fixed_delay->data_index]; - fixed_delay->data[fixed_delay->data_index] = input; - } - if (++fixed_delay->data_index >= fixed_delay->n) { - fixed_delay->data_index = 0; + double result = 0.0; + // Require the buffer size to be positive to protect from buffer overflows. + if (fixed_delay->n > 0) { + // Compare the current time to the delay time up to an epsilon difference. + bool delayTimeReached = _time > fixed_delay->n * _time_step - 1e-6; + fixed_delay->data[fixed_delay->data_index] = delayTimeReached ? input : fixed_delay->initial_value; + // Because DELAY FIXED is a level, get the value one time step ahead in the buffer. + fixed_delay->data_index = (fixed_delay->data_index + 1) % fixed_delay->n; + result = delayTimeReached ? fixed_delay->data[fixed_delay->data_index] : fixed_delay->initial_value; } return result; } -// double _DELAY_FIXED(double input, FixedDelay* fixed_delay) { -// // Cache input values in a ring buffer for the number of time steps equal to the delay time. -// // Return the init value until the time reaches the delay time. -// double result; -// bool delayTimeReached = _time > fixed_delay->n * _time_step + 1e-6; -// result = delayTimeReached ? fixed_delay->data[fixed_delay->data_index] : fixed_delay->initial_value; -// fixed_delay->data[fixed_delay->data_index++] = delayTimeReached ? input : fixed_delay->initial_value; -// if (fixed_delay->data_index >= fixed_delay->n) { -// fixed_delay->data_index = 0; -// } -// return result; -// } From 00011fc5ba613ce0ba4ef4ff4e83a5af2a951e29 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 10 Sep 2021 19:57:31 -0700 Subject: [PATCH 04/13] set init and eval references for the DELAY FIXED var --- src/EquationReader.js | 4 ++++ src/c/vensim.c | 11 +++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/EquationReader.js b/src/EquationReader.js index d6220cd6..d334c998 100644 --- a/src/EquationReader.js +++ b/src/EquationReader.js @@ -239,6 +239,10 @@ export default class EquationReader extends ModelReader { // with the generated level var. } else if (this.argIndexForFunctionName('_INTEG') === 1) { this.addReferencesToList(this.var.initReferences) + } else if (this.argIndexForFunctionName('_DELAY_FIXED') === 1) { + this.addReferencesToList(this.var.initReferences) + } else if (this.argIndexForFunctionName('_DELAY_FIXED') === 2) { + this.addReferencesToList(this.var.initReferences) } else if (this.argIndexForFunctionName('_ACTIVE_INITIAL') === 1) { this.addReferencesToList(this.var.initReferences) } else if (this.argIndexForFunctionName('_SAMPLE_IF_TRUE') === 2) { diff --git a/src/c/vensim.c b/src/c/vensim.c index daaa81e0..7672e2b6 100644 --- a/src/c/vensim.c +++ b/src/c/vensim.c @@ -425,12 +425,15 @@ double _DELAY_FIXED(double input, FixedDelay* fixed_delay) { double result = 0.0; // Require the buffer size to be positive to protect from buffer overflows. if (fixed_delay->n > 0) { - // Compare the current time to the delay time up to an epsilon difference. - bool delayTimeReached = _time > fixed_delay->n * _time_step - 1e-6; - fixed_delay->data[fixed_delay->data_index] = delayTimeReached ? input : fixed_delay->initial_value; + fixed_delay->data[fixed_delay->data_index] = input; // Because DELAY FIXED is a level, get the value one time step ahead in the buffer. fixed_delay->data_index = (fixed_delay->data_index + 1) % fixed_delay->n; - result = delayTimeReached ? fixed_delay->data[fixed_delay->data_index] : fixed_delay->initial_value; + // Compare the current time to the delay time up to an epsilon difference. + if (_time < fixed_delay->n * _time_step - 1e-6) { + result = fixed_delay->initial_value; + } else { + result = fixed_delay->data[fixed_delay->data_index]; + } } return result; } From 4eec5c017238a93220c834a9b686697b44a1f2d3 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 10 Sep 2021 21:07:27 -0700 Subject: [PATCH 05/13] add subscripts to FixedDelay support vars when LHS subscripts used --- models/delayfixed/delayfixed.dat | 514 +++++++++++++++++++------------ models/delayfixed/delayfixed.mdl | 65 +--- src/CodeGen.js | 5 +- src/EquationGen.js | 4 +- 4 files changed, 336 insertions(+), 252 deletions(-) diff --git a/models/delayfixed/delayfixed.dat b/models/delayfixed/delayfixed.dat index 3ea617a7..b96279af 100644 --- a/models/delayfixed/delayfixed.dat +++ b/models/delayfixed/delayfixed.dat @@ -1,7 +1,319 @@ FINAL TIME -0 100 +0 50 INITIAL TIME 0 0 +input[A1] +0 0 +1 10 +2 20 +3 30 +4 40 +5 50 +6 60 +7 70 +8 80 +9 90 +10 100 +11 110 +12 120 +13 130 +14 140 +15 150 +16 160 +17 170 +18 180 +19 190 +20 200 +21 210 +22 220 +23 230 +24 240 +25 250 +26 260 +27 270 +28 280 +29 290 +30 300 +31 310 +32 320 +33 330 +34 340 +35 350 +36 360 +37 370 +38 380 +39 390 +40 400 +41 410 +42 420 +43 430 +44 440 +45 450 +46 460 +47 470 +48 480 +49 490 +50 500 +input[A2] +0 0 +1 20 +2 40 +3 60 +4 80 +5 100 +6 120 +7 140 +8 160 +9 180 +10 200 +11 220 +12 240 +13 260 +14 280 +15 300 +16 320 +17 340 +18 360 +19 380 +20 400 +21 420 +22 440 +23 460 +24 480 +25 500 +26 520 +27 540 +28 560 +29 580 +30 600 +31 620 +32 640 +33 660 +34 680 +35 700 +36 720 +37 740 +38 760 +39 780 +40 800 +41 820 +42 840 +43 860 +44 880 +45 900 +46 920 +47 940 +48 960 +49 980 +50 1000 +input[A3] +0 0 +1 30 +2 60 +3 90 +4 120 +5 150 +6 180 +7 210 +8 240 +9 270 +10 300 +11 330 +12 360 +13 390 +14 420 +15 450 +16 480 +17 510 +18 540 +19 570 +20 600 +21 630 +22 660 +23 690 +24 720 +25 750 +26 780 +27 810 +28 840 +29 870 +30 900 +31 930 +32 960 +33 990 +34 1020 +35 1050 +36 1080 +37 1110 +38 1140 +39 1170 +40 1200 +41 1230 +42 1260 +43 1290 +44 1320 +45 1350 +46 1380 +47 1410 +48 1440 +49 1470 +50 1500 +output[A1] +0 0 +1 0 +2 10 +3 20 +4 30 +5 40 +6 50 +7 60 +8 70 +9 80 +10 90 +11 100 +12 110 +13 120 +14 130 +15 140 +16 150 +17 160 +18 170 +19 180 +20 190 +21 200 +22 210 +23 220 +24 230 +25 240 +26 250 +27 260 +28 270 +29 280 +30 290 +31 300 +32 310 +33 320 +34 330 +35 340 +36 350 +37 360 +38 370 +39 380 +40 390 +41 400 +42 410 +43 420 +44 430 +45 440 +46 450 +47 460 +48 470 +49 480 +50 490 +output[A2] +0 0 +1 0 +2 20 +3 40 +4 60 +5 80 +6 100 +7 120 +8 140 +9 160 +10 180 +11 200 +12 220 +13 240 +14 260 +15 280 +16 300 +17 320 +18 340 +19 360 +20 380 +21 400 +22 420 +23 440 +24 460 +25 480 +26 500 +27 520 +28 540 +29 560 +30 580 +31 600 +32 620 +33 640 +34 660 +35 680 +36 700 +37 720 +38 740 +39 760 +40 780 +41 800 +42 820 +43 840 +44 860 +45 880 +46 900 +47 920 +48 940 +49 960 +50 980 +output[A3] +0 0 +1 0 +2 30 +3 60 +4 90 +5 120 +6 150 +7 180 +8 210 +9 240 +10 270 +11 300 +12 330 +13 360 +14 390 +15 420 +16 450 +17 480 +18 510 +19 540 +20 570 +21 600 +22 630 +23 660 +24 690 +25 720 +26 750 +27 780 +28 810 +29 840 +30 870 +31 900 +32 930 +33 960 +34 990 +35 1020 +36 1050 +37 1080 +38 1110 +39 1140 +40 1170 +41 1200 +42 1230 +43 1260 +44 1290 +45 1320 +46 1350 +47 1380 +48 1410 +49 1440 +50 1470 receiving 0 0 1 0 @@ -54,56 +366,6 @@ receiving 48 0 49 0 50 0 -51 0 -52 0 -53 0 -54 0 -55 0 -56 0 -57 0 -58 0 -59 0 -60 0 -61 0 -62 0 -63 0 -64 0 -65 0 -66 0 -67 0 -68 0 -69 0 -70 0 -71 0 -72 0 -73 0 -74 0 -75 0 -76 0 -77 0 -78 0 -79 0 -80 0 -81 0 -82 0 -83 0 -84 0 -85 0 -86 0 -87 0 -88 0 -89 0 -90 0 -91 0 -92 0 -93 0 -94 0 -95 0 -96 0 -97 0 -98 0 -99 0 -100 0 reference shipping rate 0 1 SAVEPER @@ -158,56 +420,6 @@ SAVEPER 48 1 49 1 50 1 -51 1 -52 1 -53 1 -54 1 -55 1 -56 1 -57 1 -58 1 -59 1 -60 1 -61 1 -62 1 -63 1 -64 1 -65 1 -66 1 -67 1 -68 1 -69 1 -70 1 -71 1 -72 1 -73 1 -74 1 -75 1 -76 1 -77 1 -78 1 -79 1 -80 1 -81 1 -82 1 -83 1 -84 1 -85 1 -86 1 -87 1 -88 1 -89 1 -90 1 -91 1 -92 1 -93 1 -94 1 -95 1 -96 1 -97 1 -98 1 -99 1 -100 1 shipments in transit 0 0 1 0 @@ -260,56 +472,6 @@ shipments in transit 48 0 49 0 50 0 -51 0 -52 0 -53 0 -54 0 -55 0 -56 0 -57 0 -58 0 -59 0 -60 0 -61 0 -62 0 -63 0 -64 0 -65 0 -66 0 -67 0 -68 0 -69 0 -70 0 -71 0 -72 0 -73 0 -74 0 -75 0 -76 0 -77 0 -78 0 -79 0 -80 0 -81 0 -82 0 -83 0 -84 0 -85 0 -86 0 -87 0 -88 0 -89 0 -90 0 -91 0 -92 0 -93 0 -94 0 -95 0 -96 0 -97 0 -98 0 -99 0 -100 0 shipping 0 0 1 0 @@ -362,56 +524,6 @@ shipping 48 0 49 0 50 0 -51 0 -52 0 -53 0 -54 0 -55 0 -56 0 -57 0 -58 0 -59 0 -60 0 -61 0 -62 0 -63 0 -64 0 -65 0 -66 0 -67 0 -68 0 -69 0 -70 0 -71 0 -72 0 -73 0 -74 0 -75 0 -76 0 -77 0 -78 0 -79 0 -80 0 -81 0 -82 0 -83 0 -84 0 -85 0 -86 0 -87 0 -88 0 -89 0 -90 0 -91 0 -92 0 -93 0 -94 0 -95 0 -96 0 -97 0 -98 0 -99 0 -100 0 shipping time 0 20 TIME STEP diff --git a/models/delayfixed/delayfixed.mdl b/models/delayfixed/delayfixed.mdl index ee8c86e2..45d0702a 100644 --- a/models/delayfixed/delayfixed.mdl +++ b/models/delayfixed/delayfixed.mdl @@ -1,55 +1,33 @@ {UTF-8} receiving = DELAY FIXED(shipping, shipping time, shipping) ~~| -shipments in transit = INTEG(shipping - receiving, shipping * shipping time) ~~| -reference shipping rate = 1 ~~| shipping = STEP(reference shipping rate, 10) - STEP(reference shipping rate, 20) ~~| shipping time = 20 ~~| +reference shipping rate = 1 ~~| +shipments in transit = INTEG(shipping - receiving, shipping * shipping time) ~~| + +DimA: A1, A2, A3 ~~| +input[A1] = 10 * TIME ~~| +input[A2] = 20 * TIME ~~| +input[A3] = 30 * TIME ~~| +output[DimA] = DELAY FIXED(input[DimA], 1, 0) ~~| INITIAL TIME = 0 ~~| -FINAL TIME = 100 ~~| +FINAL TIME = 50 ~~| TIME STEP = 1 ~~| SAVEPER = TIME STEP ~~| \\\---/// Sketch information - do not modify anything except names V300 Do not put anything below this section - it will be ignored *View 1 -$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|255-255-255|96,96,100,0 +$0-0-0,0,|0||0-0-0|0-0-0|0-0-0|0-0-0|0-0-0|0,0,100,0 ///---\\\ :L<%^E!@ -1:delayfixed.vdfx -4:Time -5:FINAL TIME 9:delayfixed -19:100,0 -24:0 -25:100 -26:100 -57:1 -54:0 -55:0 -82:0 -86:0 -59:0 -56:0 -58:0 -71:0 -44:65001 -46:0 -45:0 -49:0 -50:0 -51: -52: -53: -43:delayfixed -47:delayfixed -48: 15:0,0,0,0,0,0 -27:0, +19:100,0 +27:2, 34:0, -42:1 -72:0 -73:0 +5:FINAL TIME 35:Date 36:YYYY-MM-DD 37:2000 @@ -57,16 +35,7 @@ $192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|255-255-255|96,9 39:1 40:2 41:0 -95:0 -96:0 -97:0 -77:0 -78:0 -93:0 -94:0 -92:0 -91:0 -90:0 -87:0 -75: -43:delayfixed +42:1 +24:0 +25:0 +26:0 diff --git a/src/CodeGen.js b/src/CodeGen.js index 33ee3efd..0c0a6a08 100644 --- a/src/CodeGen.js +++ b/src/CodeGen.js @@ -206,7 +206,10 @@ ${postStep} let families = subscriptFamilies(v.subscripts) if (v.isFixedDelay()) { // Add the associated FixedDelay var decl. - fixedDelayDecls += `\nFixedDelay* ${v.fixedDelayVarName};` + fixedDelayDecls += `\nFixedDelay* ${v.fixedDelayVarName}${R.map( + family => `[${sub(family).size}]`, + families + ).join('')};` } return varType + v.varName + R.map(family => `[${sub(family).size}]`, families).join('') } diff --git a/src/EquationGen.js b/src/EquationGen.js index 64d1359f..be58afdb 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -767,7 +767,7 @@ export default class EquationGen extends ModelReader { exprs[i].accept(this) // For DELAY FIXED, also initialize the support struct out of band, as it is not a Vensim var. if (fn === '_DELAY_FIXED') { - this.emit(`;\n ${this.var.fixedDelayVarName} = __new_fixed_delay(`) + this.emit(`;\n ${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)} = __new_fixed_delay(`) this.setArgIndex(1) exprs[1].accept(this) this.emit(', ') @@ -786,7 +786,7 @@ export default class EquationGen extends ModelReader { this.setArgIndex(0) exprs[0].accept(this) this.emit(', ') - this.emit(this.var.fixedDelayVarName) + this.emit(`${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)}`) } else { // Emit the variable LHS as the first arg at eval time, giving the current value for the level. this.emit(this.lhs) From 1ea5ed1e68793c6f29615f8275207e339ac743b2 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 10 Sep 2021 21:10:10 -0700 Subject: [PATCH 06/13] run prettier on source files --- .prettierignore | 1 + src/EquationGen.js | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index e90d48e4..32d13ceb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ src/web +.vscode diff --git a/src/EquationGen.js b/src/EquationGen.js index be58afdb..9f0453ab 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -767,7 +767,9 @@ export default class EquationGen extends ModelReader { exprs[i].accept(this) // For DELAY FIXED, also initialize the support struct out of band, as it is not a Vensim var. if (fn === '_DELAY_FIXED') { - this.emit(`;\n ${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)} = __new_fixed_delay(`) + this.emit( + `;\n ${this.var.fixedDelayVarName}${this.lhsSubscriptGen(this.var.subscripts)} = __new_fixed_delay(` + ) this.setArgIndex(1) exprs[1].accept(this) this.emit(', ') From 179fa4aab38ea74143debb54091737bb91916b52 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Tue, 14 Sep 2021 18:02:53 -0700 Subject: [PATCH 07/13] fix: allow extra index subscripts in 2D const lists --- models/arrays_cname/arrays_cname.dat | 36 +++++++++++++++++ models/arrays_cname/arrays_cname.mdl | 40 +++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- src/EquationGen.js | 58 +++++++--------------------- src/VariableReader.js | 45 +++++++++++---------- 6 files changed, 115 insertions(+), 70 deletions(-) diff --git a/models/arrays_cname/arrays_cname.dat b/models/arrays_cname/arrays_cname.dat index 8aa91ace..4ae53bc3 100644 --- a/models/arrays_cname/arrays_cname.dat +++ b/models/arrays_cname/arrays_cname.dat @@ -411,3 +411,39 @@ y[D4,A2] 0 42 y[D4,A3] 0 43 +z[C1,A1,B1] +0 110 +z[C1,A1,B2] +0 111 +z[C1,A1,B3] +0 112 +z[C1,A2,B1] +0 120 +z[C1,A2,B2] +0 121 +z[C1,A2,B3] +0 122 +z[C1,A3,B1] +0 130 +z[C1,A3,B2] +0 131 +z[C1,A3,B3] +0 132 +z[C2,A1,B1] +0 210 +z[C2,A1,B2] +0 211 +z[C2,A1,B3] +0 212 +z[C2,A2,B1] +0 220 +z[C2,A2,B2] +0 221 +z[C2,A2,B3] +0 222 +z[C2,A3,B1] +0 230 +z[C2,A3,B2] +0 231 +z[C2,A3,B3] +0 232 diff --git a/models/arrays_cname/arrays_cname.mdl b/models/arrays_cname/arrays_cname.mdl index 5427444e..cfe32687 100755 --- a/models/arrays_cname/arrays_cname.mdl +++ b/models/arrays_cname/arrays_cname.mdl @@ -134,7 +134,47 @@ y[DimD,DimA]= ~ 2D constant array with dimensions not in normal order | +z[C1, DimA, DimB]= + 110, 111, 112; + 120, 121, 122; + 130, 131, 132; + ~ + ~ 2D constant array with additional index subscript (1/2) + | + +z[C2, DimA, DimB]= + 210, 211, 212; + 220, 221, 222; + 230, 231, 232; + ~ + ~ 2D constant array with additional index subscript (2/2) + | + INITIAL TIME = 0 ~~| FINAL TIME = 1 ~~| TIME STEP = 1 ~~| SAVEPER = TIME STEP ~~| + +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 1 +$0-0-0,0,|0||0-0-0|0-0-0|0-0-0|0-0-0|0-0-0|0,0,100,0 +///---\\\ +:L<%^E!@ +9:arrays_cname +15:0,0,0,0,0,0 +19:100,0 +27:2, +34:0, +5:FINAL TIME +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:2 +41:0 +42:1 +24:0 +25:0 +26:0 diff --git a/package-lock.json b/package-lock.json index 5df7aee9..8f629fb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,8 +42,8 @@ "integrity": "sha512-UjMSlenUORL+a+6g4RNZxRh5LcFWybRi2g0ASDBpgXBY6nlavg0BRVAVEQF0dz8jH6SyX3lV7uP5y/krJzc+Hw==" }, "antlr4-vensim": { - "version": "git+https://github.com/climateinteractive/antlr4-vensim.git#5baa4cf6477221ebcbd0efc4b4e9ea98ec721fd7", - "from": "git+https://github.com/climateinteractive/antlr4-vensim.git#5baa4cf", + "version": "git+https://github.com/climateinteractive/antlr4-vensim.git#efee9937e2261c42e73d4685ba60fb97a1b16c41", + "from": "git+https://github.com/climateinteractive/antlr4-vensim.git#efee993", "requires": { "antlr4": "4.9.2" } diff --git a/package.json b/package.json index 1c3c8c81..27dd4803 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "antlr4": "4.9.2", - "antlr4-vensim": "https://github.com/climateinteractive/antlr4-vensim#5baa4cf", + "antlr4-vensim": "https://github.com/climateinteractive/antlr4-vensim#efee993", "bufx": "^1.0.5", "byline": "^5.0.0", "chart.js": "^2.9.4", diff --git a/src/EquationGen.js b/src/EquationGen.js index 9f0453ab..9ebb5daf 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -1029,51 +1029,21 @@ export default class EquationGen extends ModelReader { // Emit a single constant into the expression code. emitConstAtPos(0) } else { - // Extract a single value from the const list by its index number. // All const lists with > 1 value are separated on dimensions in the LHS. - // The LHS of a separated variable here will contain only index subscripts. - let numDims = this.var.separationDims.length - if (numDims === 1) { - // Find the index that is in the separation dimension. - let sepDim = sub(this.var.separationDims[0]) - for (let ind of this.var.subscripts) { - let i = sepDim.value.indexOf(ind) - if (i >= 0) { - // Emit the constant at this position in the constant list. - emitConstAtPos(i) - break - } - } - } else if (numDims === 2) { - // Calculate an index into a flattened array by converting the indices to numeric form and looking them up - // in a C name array listed in the same Vensim order as the constant array in the model. - let cVarName - let modelLHSReader = new ModelLHSReader() - modelLHSReader.read(this.var.modelLHS) - let cNames = modelLHSReader.names().map(Model.cName) - // Visit dims in normal order. Find the ind in the dim. Compose the C array expression with numeric indices. - for (let i = 0; i < this.var.separationDims.length; i++) { - const dim = this.var.separationDims[i] - const sepDim = sub(dim) - const ind = this.var.subscripts[i] - const j = sepDim.value.indexOf(ind) - if (j >= 0) { - const indexNum = sub(ind).value - if (!cVarName) { - cVarName = `${this.var.varName}[${indexNum}]` - } else { - cVarName += `[${indexNum}]` - } - } - } - // Find the position of the constant in Vensim order from the expanded LHS var list. - let constPos = R.indexOf(cVarName, cNames) - if (constPos >= 0) { - emitConstAtPos(constPos) - // console.error(`${this.var.refId} position = ${constPos}`) - } else { - console.error(`${this.var.refId} → ${cVarName} not found in C names`) - } + // The LHS of a separated variable here will contain only index subscripts in normal order. + // Calculate an index into a flattened array by converting the indices to numeric form and looking them up + // in a C name array listed in the same Vensim order as the constant array in the model. + let modelLHSReader = new ModelLHSReader() + modelLHSReader.read(this.var.modelLHS) + let cNames = modelLHSReader.names().map(Model.cName) + let cVarName = this.var.varName + R.map(indName => `[${sub(indName).value}]`, this.var.subscripts).join('') + // Find the position of the constant in Vensim order from the expanded LHS var list. + let constPos = R.indexOf(cVarName, cNames) + if (constPos >= 0) { + emitConstAtPos(constPos) + // console.error(`${this.var.refId} position = ${constPos}`) + } else { + console.error(`ERROR: const list element ${this.var.refId} → ${cVarName} not found in C names`) } } } diff --git a/src/VariableReader.js b/src/VariableReader.js index cda9bf90..72412491 100644 --- a/src/VariableReader.js +++ b/src/VariableReader.js @@ -4,7 +4,7 @@ import ModelReader from './ModelReader.js' import Model from './Model.js' import Variable from './Variable.js' import { sub, isDimension, isIndex, normalizeSubscripts } from './Subscript.js' -import { canonicalName, vlog, replaceInArray, strlist } from './Helpers.js' +import { canonicalName, vlog, replaceInArray, strlist, cartesianProductOf } from './Helpers.js' // Set true to print extra debugging information to stderr. const DEBUG_LOG = false @@ -116,12 +116,10 @@ export default class VariableReader extends ModelReader { } return skip } - let skipExpansion2 = (indName0, indName1) => { + let skipExpansion2 = indNames => { let skip = false - for (const exceptSubs of this.var.exceptSubscripts) { - let exceptSub0 = exceptSubs[0] - let exceptSub1 = exceptSubs[1] - if (isException(indName0, exceptSub0) && isException(indName1, exceptSub1)) { + for (let exceptSubs of this.var.exceptSubscripts) { + if (isException(indNames[0], exceptSubs[0]) && isException(indNames[1], exceptSubs[1])) { skip = true break } @@ -150,24 +148,25 @@ export default class VariableReader extends ModelReader { `expanding ${this.var.varName}[${strlist(this.var.subscripts)}] subscripts`, strlist(this.var.subscripts) ) - let expansionSubscript0 = this.var.subscripts[0] - let expansionSubscript1 = this.var.subscripts[1] - let expansionSubs0 = isIndex(expansionSubscript0) - ? [sub(expansionSubscript0).name] - : sub(expansionSubscript0).value - let expansionSubs1 = isIndex(expansionSubscript1) - ? [sub(expansionSubscript1).name] - : sub(expansionSubscript1).value - for (let indName0 of expansionSubs0) { - for (let indName1 of expansionSubs1) { - if (!skipExpansion2(indName0, indName1)) { - let v = new Variable(this.var.eqnCtx) - v.varName = this.var.varName - v.subscripts = [indName0, indName1] - v.separationDims = [expansionSubscript0, expansionSubscript1] - debugLog(` ${strlist(v.subscripts)}`) - this.expandedVars.push(v) + // Find the subscripts we need to expand. + let separationDims = [] + for (let i = 0; i < expanding.length; i++) { + if (expanding[i]) { + separationDims.push(this.var.subscripts[i]) + } + } + let expansionSubs = separationDims.map(s => (isIndex(s) ? [sub(s).name] : sub(s).value)) + for (let indNames of cartesianProductOf(expansionSubs)) { + if (!skipExpansion2(indNames)) { + let v = new Variable(this.var.eqnCtx) + v.varName = this.var.varName + v.subscripts = [] + for (let i = 0; i < expanding.length; i++) { + v.subscripts.push(expanding[i] ? indNames.shift() : this.var.subscripts[i]) } + v.separationDims = separationDims + debugLog(` ${strlist(v.subscripts)}`) + this.expandedVars.push(v) } } } From b6f0236e42f253759a580c076f7379c98c92b756 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Wed, 15 Sep 2021 17:22:05 -0700 Subject: [PATCH 08/13] remove some obsolete Ramda usage --- src/EquationReader.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/EquationReader.js b/src/EquationReader.js index d334c998..3ab14716 100644 --- a/src/EquationReader.js +++ b/src/EquationReader.js @@ -320,21 +320,21 @@ export default class EquationReader extends ModelReader { indexNamesAtPos = indexNamesForSubscript(subscripts[pos]) } // vlog('indexNamesAtPos', indexNamesAtPos); - R.forEach(indexName => { + for (let indexName of indexNamesAtPos) { // Consider each var with the same name as the reference in the equation. - R.forEach(refVar => { + for (let refVar of varsWithRefName) { let refVarIndexNames = indexNamesForSubscript(refVar.subscripts[pos]) if (refVarIndexNames.length === 0) { console.error( `no subscript at pos ${pos} for var ${refVar.refId} with subscripts ${refVar.subscripts}` ) } - if (R.contains(indexName, refVarIndexNames)) { + if (refVarIndexNames.includes(indexName)) { expandedRefIds.push(refVar.refId) // console.error(`adding reference ${refVar.refId}`); } - }, varsWithRefName) - }, indexNamesAtPos) + } + } } else if (numLoops === 2) { // Expand the dimension in both positions. let indexNamesAtPos0 @@ -351,9 +351,9 @@ export default class EquationReader extends ModelReader { } else { indexNamesAtPos1 = indexNamesForSubscript(subscripts[1]) } - R.forEach(indexName0 => { - R.forEach(indexName1 => { - R.forEach(refVar => { + for (let indexName0 of indexNamesAtPos0) { + for (let indexName1 of indexNamesAtPos1) { + for (let refVar of varsWithRefName) { let refVarIndexNames0 = indexNamesForSubscript(refVar.subscripts[0]) if (refVarIndexNames0.length === 0) { console.error( @@ -366,12 +366,12 @@ export default class EquationReader extends ModelReader { `ERROR: no subscript at pos 1 for var ${refVar.refId} with subscripts ${refVar.subscripts}` ) } - if (R.contains(indexName0, refVarIndexNames0) && R.contains(indexName1, refVarIndexNames1)) { + if (refVarIndexNames0.includes(indexName0) && refVarIndexNames1.includes(indexName1)) { expandedRefIds.push(refVar.refId) } - }, varsWithRefName) - }, indexNamesAtPos1) - }, indexNamesAtPos0) + } + } + } } // Sort the expandedRefIds and eliminate duplicates. this.expandedRefIds = R.uniq(expandedRefIds.sort()) From dc64817b702126dd0dd7582832a67c2a68828a62 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Wed, 15 Sep 2021 22:48:32 -0700 Subject: [PATCH 09/13] generalize visitSubscriptList to any number of dimensions --- models/sum/sum.dat | 70 ++++++++++ models/sum/sum.mdl | 62 +++++++++ models/sum/sum_vars.txt | 276 +++++++++++++++++++++++++++++----------- src/EquationGen.js | 1 - src/EquationReader.js | 101 ++++++--------- 5 files changed, 373 insertions(+), 137 deletions(-) diff --git a/models/sum/sum.dat b/models/sum/sum.dat index 241313e6..1b6185cf 100644 --- a/models/sum/sum.dat +++ b/models/sum/sum.dat @@ -106,6 +106,76 @@ l[A2] l[A3] 0 0.222222 1 0.222222 +m[D1,E1] +0 11 +m[D1,E2] +0 12 +m[D2,E1] +0 21 +m[D2,E2] +0 22 +msum[D1] +0 23 +1 23 +msum[D2] +0 43 +1 43 +n[D1,E1,F1] +0 111 +n[D1,E1,F2] +0 112 +n[D1,E2,F1] +0 121 +n[D1,E2,F2] +0 122 +n[D2,E1,F1] +0 211 +n[D2,E1,F2] +0 212 +n[D2,E2,F1] +0 221 +n[D2,E2,F2] +0 222 +nsum[D1,E1] +0 223 +1 223 +nsum[D1,E2] +0 243 +1 243 +nsum[D2,E1] +0 423 +1 423 +nsum[D2,E2] +0 443 +1 443 +o[D1,E1,F1] +0 111 +o[D1,E1,F2] +0 112 +o[D1,E2,F1] +0 111 +o[D1,E2,F2] +0 112 +o[D2,E1,F1] +0 211 +o[D2,E1,F2] +0 212 +o[D2,E2,F1] +0 211 +o[D2,E2,F2] +0 212 +osum[D1,E1] +0 223 +1 223 +osum[D1,E2] +0 223 +1 223 +osum[D2,E1] +0 423 +1 423 +osum[D2,E2] +0 423 +1 423 SAVEPER 0 1 1 1 diff --git a/models/sum/sum.mdl b/models/sum/sum.mdl index 0554f351..5b92700b 100755 --- a/models/sum/sum.mdl +++ b/models/sum/sum.mdl @@ -18,7 +18,69 @@ j[DimA] = a[DimA] / SUM(b[DimA!]) ~~| k[SubA] = SUM(b 2[SubA!]) ~~| l[SubA] = a 2[SubA] / SUM(b 2[SubA!]) ~~| +DimD: D1, D2 ~~| +DimE: E1, E2 ~~| +DimF: F1, F2 ~~| + +m[D1, E1] = 11 ~~| +m[D1, E2] = 12 ~~| +m[D2, E1] = 21 ~~| +m[D2, E2] = 22 ~~| +msum[DimD] = SUM(m[DimD, DimE!]) +~ +~ +2 separated dimensions +| + +n[D1, E1, F1] = 111 ~~| +n[D1, E1, F2] = 112 ~~| +n[D1, E2, F1] = 121 ~~| +n[D1, E2, F2] = 122 ~~| +n[D2, E1, F1] = 211 ~~| +n[D2, E1, F2] = 212 ~~| +n[D2, E2, F1] = 221 ~~| +n[D2, E2, F2] = 222 ~~| +nsum[DimD, DimE] = SUM(n[DimD, DimE, DimF!]) +~ +~ +3 separated dimensions +| + +o[D1, DimE, F1] = 111 ~~| +o[D1, DimE, F2] = 112 ~~| +o[D2, DimE, F1] = 211 ~~| +o[D2, DimE, F2] = 212 ~~| +osum[DimD, DimE] = SUM(o[DimD, DimE, DimF!]) +~ +~ +2 separated dimensions with 1 unseparated dimension +| + INITIAL TIME = 0 ~~| FINAL TIME = 1 ~~| TIME STEP = 1 ~~| SAVEPER = TIME STEP ~~| + +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 1 +$0-0-0,0,|0||0-0-0|0-0-0|0-0-0|0-0-0|0-0-0|0,0,100,0 +///---\\\ +:L<%^E!@ +9:sum +15:0,0,0,0,0,0 +19:100,0 +27:2, +34:0, +5:FINAL TIME +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:2 +41:0 +42:1 +24:0 +25:0 +26:0 diff --git a/models/sum/sum_vars.txt b/models/sum/sum_vars.txt index 3625e07d..945745c3 100644 --- a/models/sum/sum_vars.txt +++ b/models/sum/sum_vars.txt @@ -1,57 +1,10 @@ -_dima: -{ modelName: 'DimA', - modelValue: [ 'A1', 'A2', 'A3' ], - modelMappings: [], - name: '_dima', - value: [ '_a1', '_a2', '_a3' ], - size: 3, - family: '_dima', - mappings: {} } - -_dimc: -{ modelName: 'DimC', - modelValue: [ 'C1', 'C2', 'C3' ], - modelMappings: [], - name: '_dimc', - value: [ '_c1', '_c2', '_c3' ], - size: 3, - family: '_dimc', - mappings: {} } - -_suba: -{ modelName: 'SubA', - modelValue: [ 'A2', 'A3' ], - modelMappings: [], - name: '_suba', - value: [ '_a2', '_a3' ], - size: 2, - family: '_dima', - mappings: {} } - -_a1: -{ name: '_a1', value: 0, size: 1, family: '_dima', mappings: {} } - -_a2: -{ name: '_a2', value: 1, size: 1, family: '_dima', mappings: {} } - -_a3: -{ name: '_a3', value: 2, size: 1, family: '_dima', mappings: {} } - -_c1: -{ name: '_c1', value: 0, size: 1, family: '_dimc', mappings: {} } - -_c2: -{ name: '_c2', value: 1, size: 1, family: '_dimc', mappings: {} } - -_c3: -{ name: '_c3', value: 2, size: 1, family: '_dimc', mappings: {} } - a[DimA]: const (non-apply-to-all) = 1,2,3 refId(_a[_a1]) families(_dima) subscripts(_a1) separationDims(_dima) +hasInitValue(false) a[DimA]: const (non-apply-to-all) = 1,2,3 @@ -59,6 +12,7 @@ refId(_a[_a2]) families(_dima) subscripts(_a2) separationDims(_dima) +hasInitValue(false) a[DimA]: const (non-apply-to-all) = 1,2,3 @@ -66,6 +20,23 @@ refId(_a[_a3]) families(_dima) subscripts(_a3) separationDims(_dima) +hasInitValue(false) + +a 2[SubA]: const (non-apply-to-all) += 1,2 +refId(_a_2[_a2]) +families(_dima) +subscripts(_a2) +separationDims(_suba) +hasInitValue(false) + +a 2[SubA]: const (non-apply-to-all) += 1,2 +refId(_a_2[_a3]) +families(_dima) +subscripts(_a3) +separationDims(_suba) +hasInitValue(false) b[DimA]: const (non-apply-to-all) = 4,5,6 @@ -73,6 +44,7 @@ refId(_b[_a1]) families(_dima) subscripts(_a1) separationDims(_dima) +hasInitValue(false) b[DimA]: const (non-apply-to-all) = 4,5,6 @@ -80,6 +52,7 @@ refId(_b[_a2]) families(_dima) subscripts(_a2) separationDims(_dima) +hasInitValue(false) b[DimA]: const (non-apply-to-all) = 4,5,6 @@ -87,20 +60,7 @@ refId(_b[_a3]) families(_dima) subscripts(_a3) separationDims(_dima) - -a 2[SubA]: const (non-apply-to-all) -= 1,2 -refId(_a_2[_a2]) -families(_dima) -subscripts(_a2) -separationDims(_suba) - -a 2[SubA]: const (non-apply-to-all) -= 1,2 -refId(_a_2[_a3]) -families(_dima) -subscripts(_a3) -separationDims(_suba) +hasInitValue(false) b 2[SubA]: const (non-apply-to-all) = 4,5 @@ -108,6 +68,7 @@ refId(_b_2[_a2]) families(_dima) subscripts(_a2) separationDims(_suba) +hasInitValue(false) b 2[SubA]: const (non-apply-to-all) = 4,5 @@ -115,30 +76,45 @@ refId(_b_2[_a3]) families(_dima) subscripts(_a3) separationDims(_suba) +hasInitValue(false) c: aux = SUM(a[DimA!])+1 refId(_c) +hasInitValue(false) +refs(_a[_a1], _a[_a2], _a[_a3]) d: aux = SUM(a[DimA!])+SUM(b[DimA!]) refId(_d) +hasInitValue(false) +refs(_a[_a1], _a[_a2], _a[_a3], _b[_a1], _b[_a2], _b[_a3]) e: aux = SUM(a[DimA!]*b[DimA!]/TIME STEP) refId(_e) +hasInitValue(false) +refs(_a[_a1], _a[_a2], _a[_a3], _b[_a1], _b[_a2], _b[_a3], _time_step) f[DimA,DimC]: const = 1 refId(_f) families(_dima, _dimc) subscripts(_dima, _dimc) +hasInitValue(false) + +FINAL TIME: const += 1 +refId(_final_time) +hasInitValue(false) g[DimA,DimC]: aux = SUM(f[DimA!,DimC!]) refId(_g) families(_dima, _dimc) subscripts(_dima, _dimc) +hasInitValue(false) +refs(_f) h[DimC]: const (non-apply-to-all) = 10,20,30 @@ -146,6 +122,7 @@ refId(_h[_c1]) families(_dimc) subscripts(_c1) separationDims(_dimc) +hasInitValue(false) h[DimC]: const (non-apply-to-all) = 10,20,30 @@ -153,6 +130,7 @@ refId(_h[_c2]) families(_dimc) subscripts(_c2) separationDims(_dimc) +hasInitValue(false) h[DimC]: const (non-apply-to-all) = 10,20,30 @@ -160,16 +138,26 @@ refId(_h[_c3]) families(_dimc) subscripts(_c3) separationDims(_dimc) +hasInitValue(false) i: aux = SUM(a[DimA!]+h[DimC!]) refId(_i) +hasInitValue(false) +refs(_a[_a1], _a[_a2], _a[_a3], _h[_c1], _h[_c2], _h[_c3]) + +INITIAL TIME: const += 0 +refId(_initial_time) +hasInitValue(false) j[DimA]: aux = a[DimA]/SUM(b[DimA!]) refId(_j) families(_dima) subscripts(_dima) +hasInitValue(false) +refs(_a[_a1], _a[_a2], _a[_a3], _b[_a1], _b[_a2], _b[_a3]) k[SubA]: aux (non-apply-to-all) = SUM(b 2[SubA!]) @@ -177,6 +165,8 @@ refId(_k[_a2]) families(_dima) subscripts(_a2) separationDims(_suba) +hasInitValue(false) +refs(_b_2[_a2], _b_2[_a3]) k[SubA]: aux (non-apply-to-all) = SUM(b 2[SubA!]) @@ -184,6 +174,8 @@ refId(_k[_a3]) families(_dima) subscripts(_a3) separationDims(_suba) +hasInitValue(false) +refs(_b_2[_a2], _b_2[_a3]) l[SubA]: aux (non-apply-to-all) = a 2[SubA]/SUM(b 2[SubA!]) @@ -191,6 +183,8 @@ refId(_l[_a2]) families(_dima) subscripts(_a2) separationDims(_suba) +hasInitValue(false) +refs(_a_2[_a2], _b_2[_a2], _b_2[_a3]) l[SubA]: aux (non-apply-to-all) = a 2[SubA]/SUM(b 2[SubA!]) @@ -198,24 +192,158 @@ refId(_l[_a3]) families(_dima) subscripts(_a3) separationDims(_suba) - -INITIAL TIME: const -= 0 -refId(_initial_time) - -FINAL TIME: const -= 1 -refId(_final_time) - -TIME STEP: const -= 1 -refId(_time_step) +hasInitValue(false) +refs(_a_2[_a3], _b_2[_a2], _b_2[_a3]) + +m[D1,E1]: const (non-apply-to-all) += 11 +refId(_m[_d1,_e1]) +families(_dimd, _dime) +subscripts(_d1, _e1) +hasInitValue(false) + +m[D1,E2]: const (non-apply-to-all) += 12 +refId(_m[_d1,_e2]) +families(_dimd, _dime) +subscripts(_d1, _e2) +hasInitValue(false) + +m[D2,E1]: const (non-apply-to-all) += 21 +refId(_m[_d2,_e1]) +families(_dimd, _dime) +subscripts(_d2, _e1) +hasInitValue(false) + +m[D2,E2]: const (non-apply-to-all) += 22 +refId(_m[_d2,_e2]) +families(_dimd, _dime) +subscripts(_d2, _e2) +hasInitValue(false) + +msum[DimD]: aux += SUM(m[DimD,DimE!]) +refId(_msum) +families(_dimd) +subscripts(_dimd) +hasInitValue(false) +refs(_m[_d1,_e1], _m[_d1,_e2], _m[_d2,_e1], _m[_d2,_e2]) + +n[D1,E1,F1]: const (non-apply-to-all) += 111 +refId(_n[_d1,_e1,_f1]) +families(_dimd, _dime, _dimf) +subscripts(_d1, _e1, _f1) +hasInitValue(false) + +n[D1,E1,F2]: const (non-apply-to-all) += 112 +refId(_n[_d1,_e1,_f2]) +families(_dimd, _dime, _dimf) +subscripts(_d1, _e1, _f2) +hasInitValue(false) + +n[D1,E2,F1]: const (non-apply-to-all) += 121 +refId(_n[_d1,_e2,_f1]) +families(_dimd, _dime, _dimf) +subscripts(_d1, _e2, _f1) +hasInitValue(false) + +n[D1,E2,F2]: const (non-apply-to-all) += 122 +refId(_n[_d1,_e2,_f2]) +families(_dimd, _dime, _dimf) +subscripts(_d1, _e2, _f2) +hasInitValue(false) + +n[D2,E1,F1]: const (non-apply-to-all) += 211 +refId(_n[_d2,_e1,_f1]) +families(_dimd, _dime, _dimf) +subscripts(_d2, _e1, _f1) +hasInitValue(false) + +n[D2,E1,F2]: const (non-apply-to-all) += 212 +refId(_n[_d2,_e1,_f2]) +families(_dimd, _dime, _dimf) +subscripts(_d2, _e1, _f2) +hasInitValue(false) + +n[D2,E2,F1]: const (non-apply-to-all) += 221 +refId(_n[_d2,_e2,_f1]) +families(_dimd, _dime, _dimf) +subscripts(_d2, _e2, _f1) +hasInitValue(false) + +n[D2,E2,F2]: const (non-apply-to-all) += 222 +refId(_n[_d2,_e2,_f2]) +families(_dimd, _dime, _dimf) +subscripts(_d2, _e2, _f2) +hasInitValue(false) + +nsum[DimD,DimE]: aux += SUM(n[DimD,DimE,DimF!]) +refId(_nsum) +families(_dimd, _dime) +subscripts(_dimd, _dime) +hasInitValue(false) +refs(_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]) + +o[D1,DimE,F1]: const (non-apply-to-all) += 111 +refId(_o[_d1,_dime,_f1]) +families(_dimd, _dime, _dimf) +subscripts(_d1, _dime, _f1) +hasInitValue(false) + +o[D1,DimE,F2]: const (non-apply-to-all) += 112 +refId(_o[_d1,_dime,_f2]) +families(_dimd, _dime, _dimf) +subscripts(_d1, _dime, _f2) +hasInitValue(false) + +o[D2,DimE,F1]: const (non-apply-to-all) += 211 +refId(_o[_d2,_dime,_f1]) +families(_dimd, _dime, _dimf) +subscripts(_d2, _dime, _f1) +hasInitValue(false) + +o[D2,DimE,F2]: const (non-apply-to-all) += 212 +refId(_o[_d2,_dime,_f2]) +families(_dimd, _dime, _dimf) +subscripts(_d2, _dime, _f2) +hasInitValue(false) + +osum[DimD,DimE]: aux += SUM(o[DimD,DimE,DimF!]) +refId(_osum) +families(_dimd, _dime) +subscripts(_dimd, _dime) +hasInitValue(false) +refs(_o[_d1,_dime,_f1], _o[_d1,_dime,_f2], _o[_d2,_dime,_f1], _o[_d2,_dime,_f2]) SAVEPER: aux = TIME STEP refId(_saveper) +hasInitValue(false) +refs(_time_step) Time: const = refId(_time) +hasInitValue(false) + +TIME STEP: const += 1 +refId(_time_step) +hasInitValue(false) diff --git a/src/EquationGen.js b/src/EquationGen.js index 9ebb5daf..7acb2d39 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -2,7 +2,6 @@ import path from 'path' import R from 'ramda' import XLSX from 'xlsx' import { ModelLexer, ModelParser } from 'antlr4-vensim' -import ExprReader from './ExprReader.js' import ModelReader from './ModelReader.js' import ModelLHSReader from './ModelLHSReader.js' import LoopIndexVars from './LoopIndexVars.js' diff --git a/src/EquationReader.js b/src/EquationReader.js index 3ab14716..89b5ed66 100644 --- a/src/EquationReader.js +++ b/src/EquationReader.js @@ -26,7 +26,8 @@ import { newAuxVarName, newLevelVarName, newLookupVarName, - newFixedDelayVarName + newFixedDelayVarName, + cartesianProductOf } from './Helpers.js' // Set this true to get a list of functions used in the model. This may include lookups. @@ -300,77 +301,53 @@ export default class EquationReader extends ModelReader { // Find the refIds of the vars that include the indices in the reference. // Get the vars with the var name of the reference. We will choose from these vars. let varsWithRefName = Model.varsWithName(this.refId) - // Support one or two dimensions that vary in non-apply-to-all variable definitions. - let numLoops = R.reduce((n, f) => n + (f ? 1 : 0), 0, expansionFlags) // The refIds of actual vars containing the indices will accumulate with possible duplicates. let expandedRefIds = [] - if (numLoops === 1) { - // Find refIds for a subscript in either the first or the second position. - let pos = expansionFlags[0] ? 0 : 1 - // For each index name at the subscript position, find refIds for vars that include the index. - // This process ensures that we generate references to vars that are in the var table. - let indexNamesAtPos - // Use the single index name for a separated variable if it exists. - // But don't do this if the subscript is a marked dimension in a vector function. - let separatedIndexName = separatedVariableIndex(subscripts[pos], this.var, subscripts) - if (!markedDims.includes(subscripts[pos]) && separatedIndexName) { - indexNamesAtPos = [separatedIndexName] - } else { - // Generate references to all the indices for the subscript. - indexNamesAtPos = indexNamesForSubscript(subscripts[pos]) - } - // vlog('indexNamesAtPos', indexNamesAtPos); - for (let indexName of indexNamesAtPos) { - // Consider each var with the same name as the reference in the equation. - for (let refVar of varsWithRefName) { - let refVarIndexNames = indexNamesForSubscript(refVar.subscripts[pos]) - if (refVarIndexNames.length === 0) { - console.error( - `no subscript at pos ${pos} for var ${refVar.refId} with subscripts ${refVar.subscripts}` - ) - } - if (refVarIndexNames.includes(indexName)) { - expandedRefIds.push(refVar.refId) - // console.error(`adding reference ${refVar.refId}`); - } + let iSub + // Accumulate an array of lists of the separated index names at each position. + let indexNames = [] + for (iSub = 0; iSub < expansionFlags.length; iSub++) { + if (expansionFlags[iSub]) { + // For each index name at the subscript position, find refIds for vars that include the index. + // This process ensures that we generate references to vars that are in the var table. + let indexNamesAtPos + // Use the single index name for a separated variable if it exists. + // But don't do this if the subscript is a marked dimension in a vector function. + let separatedIndexName = separatedVariableIndex(subscripts[iSub], this.var, subscripts) + if (!markedDims.includes(subscripts[iSub]) && separatedIndexName) { + indexNamesAtPos = [separatedIndexName] + } else { + // Generate references to all the indices for the subscript. + indexNamesAtPos = indexNamesForSubscript(subscripts[iSub]) } + indexNames.push(indexNamesAtPos) } - } else if (numLoops === 2) { - // Expand the dimension in both positions. - let indexNamesAtPos0 - let separatedIndexName0 = separatedVariableIndex(subscripts[0], this.var, subscripts) - if (!markedDims.includes(subscripts[0]) && separatedIndexName0) { - indexNamesAtPos0 = [separatedIndexName0] - } else { - indexNamesAtPos0 = indexNamesForSubscript(subscripts[0]) - } - let indexNamesAtPos1 - let separatedIndexName1 = separatedVariableIndex(subscripts[1], this.var, subscripts) - if (!markedDims.includes(subscripts[1]) && separatedIndexName1) { - indexNamesAtPos1 = [separatedIndexName1] - } else { - indexNamesAtPos1 = indexNamesForSubscript(subscripts[1]) - } - for (let indexName0 of indexNamesAtPos0) { - for (let indexName1 of indexNamesAtPos1) { - for (let refVar of varsWithRefName) { - let refVarIndexNames0 = indexNamesForSubscript(refVar.subscripts[0]) - if (refVarIndexNames0.length === 0) { - console.error( - `ERROR: no subscript at pos 0 for var ${refVar.refId} with subscripts ${refVar.subscripts}` - ) - } - let refVarIndexNames1 = indexNamesForSubscript(refVar.subscripts[1]) - if (refVarIndexNames1.length === 0) { + } + // Flatten the arrays of index names at each position into an array of index name combinations. + let separatedIndices = cartesianProductOf(indexNames) + // Find a separated variable for each combination of indices. + for (let separatedIndex of separatedIndices) { + // Consider each var with the same name as the reference in the equation. + for (let refVar of varsWithRefName) { + let iSeparatedIndex = 0 + for (iSub = 0; iSub < expansionFlags.length; iSub++) { + if (expansionFlags[iSub]) { + let refVarIndexNames = indexNamesForSubscript(refVar.subscripts[iSub]) + if (refVarIndexNames.length === 0) { console.error( - `ERROR: no subscript at pos 1 for var ${refVar.refId} with subscripts ${refVar.subscripts}` + `ERROR: no subscript at subscript position ${iSub} for var ${refVar.refId} with subscripts ${refVar.subscripts}` ) } - if (refVarIndexNames0.includes(indexName0) && refVarIndexNames1.includes(indexName1)) { - expandedRefIds.push(refVar.refId) + if (!refVarIndexNames.includes(separatedIndex[iSeparatedIndex++])) { + break } } } + if (iSub >= expansionFlags.length) { + // All separated index names matched index names in the var, so add it as a reference. + expandedRefIds.push(refVar.refId) + break + } } } // Sort the expandedRefIds and eliminate duplicates. From 85251138852ca640f3ccdbde7883e6e501792500 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Thu, 16 Sep 2021 16:47:48 -0700 Subject: [PATCH 10/13] get direct data offset from the separated dimension --- models/directdata/directdata.dat | 136 +++++++++++++++++++++++++++++++ models/directdata/directdata.mdl | 59 ++++++++++---- src/EquationGen.js | 16 +++- 3 files changed, 190 insertions(+), 21 deletions(-) diff --git a/models/directdata/directdata.dat b/models/directdata/directdata.dat index 41d78ef5..46fb5c44 100644 --- a/models/directdata/directdata.dat +++ b/models/directdata/directdata.dat @@ -374,6 +374,130 @@ h 2050 470 INITIAL TIME 1990 1990 +j[A1,B1] +1990 610 +1991 609.333 +1992 608.667 +1993 608 +1994 607.333 +1995 606.667 +1996 606 +1997 605.333 +1998 604.667 +1999 604 +2000 603.333 +2001 602.667 +2002 602 +2003 601.333 +2004 600.667 +2005 600 +2006 599 +2007 598 +2008 597 +2009 596 +2010 595 +2011 594 +2012 593 +2013 592 +2014 591 +2015 590 +2016 590.2 +2017 590.4 +2018 590.6 +2019 590.8 +2020 591 +2021 591.2 +2022 591.4 +2023 591.6 +2024 591.8 +2025 592 +2026 592.2 +2027 592.4 +2028 592.6 +2029 592.8 +2030 593 +2031 592.5 +2032 592 +2033 591.5 +2034 591 +2035 590.5 +2036 590 +2037 589.5 +2038 589 +2039 588.5 +2040 588 +2041 587.5 +2042 587 +2043 586.5 +2044 586 +2045 585.5 +2046 585 +2047 584.5 +2048 584 +2049 583.5 +2050 583 +j[A1,B2] +1990 210 +1991 209.333 +1992 208.667 +1993 208 +1994 207.333 +1995 206.667 +1996 206 +1997 205.333 +1998 204.667 +1999 204 +2000 203.333 +2001 202.667 +2002 202 +2003 201.333 +2004 200.667 +2005 200 +2006 199 +2007 198 +2008 197 +2009 196 +2010 195 +2011 194 +2012 193 +2013 192 +2014 191 +2015 190 +2016 189.667 +2017 189.333 +2018 189 +2019 188.667 +2020 188.333 +2021 188 +2022 187.667 +2023 187.333 +2024 187 +2025 186.667 +2026 186.333 +2027 186 +2028 185.667 +2029 185.333 +2030 185 +2031 184.75 +2032 184.5 +2033 184.25 +2034 184 +2035 183.75 +2036 183.5 +2037 183.25 +2038 183 +2039 182.75 +2040 182.5 +2041 182.25 +2042 182 +2043 181.75 +2044 181.5 +2045 181.25 +2046 181 +2047 180.75 +2048 180.5 +2049 180.25 +2050 180 SAVEPER 1990 1 1991 1 @@ -490,3 +614,15 @@ g 2040 17 2045 35 2050 47 +i[A1,B1] +1990 610 +2005 600 +2015 590 +2030 593 +2050 583 +i[A1,B2] +1990 210 +2005 200 +2015 190 +2030 185 +2050 180 diff --git a/models/directdata/directdata.mdl b/models/directdata/directdata.mdl index 10afb071..25e303eb 100644 --- a/models/directdata/directdata.mdl +++ b/models/directdata/directdata.mdl @@ -1,5 +1,6 @@ {UTF-8} DimA: A1, A2 ~~| +DimB: B1, B2 ~~| a[DimA] := GET DIRECT DATA('?data', 'A Data', 'A', 'B2') ~~| b[DimA] = a[DimA] * 10 ~~~:SUPPLEMENTARY| @@ -13,6 +14,12 @@ f[DimA] = e[DimA] * 10 ~~~:SUPPLEMENTARY| g:= GET DIRECT DATA('g_data.csv', ',', 'A', 'B2') ~~| h = g * 10 ~~~:SUPPLEMENTARY| +i[A1, DimB] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B2') +~ +~ multiple LHS subscripts +| +j[A1, DimB] = i[A1, DimB] ~~~:SUPPLEMENTARY| + ******************************************************** .Control ********************************************************~ @@ -27,32 +34,24 @@ TIME STEP = 1 ~~| \\\---/// Sketch information - do not modify anything except names V300 Do not put anything below this section - it will be ignored *View 1 -$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|0,0,100,0 +$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,100,0 ///---\\\ :L<%^E!@ -1:directdata.vdf -9:directdata -30:?data=data.xlsx -15:0,0,0,0,0,0 -19:100,0 -27:0, -34:0, +1:directdata.vdfx 4:Time 5:FINAL TIME -35:Date -36:YYYY-MM-DD -37:2000 -38:1 -39:1 -40:2 -41:0 -42:0 +6:A1 +6:B1 +9:directdata +19:100,0 24:1990 25:2050 26:2050 57:1 54:0 55:0 +82:0 +86:0 59:0 56:0 58:0 @@ -68,4 +67,30 @@ $192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|0,0,100 43:directdata 47:directdata 48: -6:A1 +30:?data=data.xlsx +15:0,0,0,0,0,0 +27:0, +34:0, +42:0 +72:0 +73:0 +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:2 +41:0 +95:0 +96:0 +97:0 +77:0 +78:0 +93:0 +94:0 +92:0 +91:0 +90:0 +87:0 +75: +43:directdata diff --git a/src/EquationGen.js b/src/EquationGen.js index 7acb2d39..79447243 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -388,11 +388,19 @@ export default class EquationGen extends ModelReader { // If the data was found, convert it to a lookup. if (getCellValue) { let indexNum = 0 - if (!R.isEmpty(this.var.subscripts)) { + if (!R.isEmpty(this.var.separationDims)) { // Generate a lookup for a separated index in the variable's dimension. - // TODO allow the index to be in either position of a 2D subscript - let ind = sub(this.var.subscripts[0]) - indexNum = ind.value + if (this.var.separationDims.length > 1) { + console.error(`WARNING: direct data variable ${this.var.varName} separated on more than one dimension`) + } + let dimName = this.var.separationDims[0] + for (let subscript of this.var.subscripts) { + if (sub(subscript).family === dimName) { + let ind = sub(subscript) + indexNum = ind.value + break + } + } } result.push(this.generateDirectDataLookup(getCellValue, timeRowOrCol, startCell, indexNum)) } From fb5c1491135484b7aaf9e4b7f46787ff06cb648b Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Fri, 17 Sep 2021 21:19:08 -0700 Subject: [PATCH 11/13] convert consts split from data equations into data variables --- models/directdata/directdata.dat | 260 +++++++++++++++++++++++++++++++ models/directdata/directdata.mdl | 8 +- src/EquationGen.js | 26 +++- src/Model.js | 30 +++- 4 files changed, 314 insertions(+), 10 deletions(-) diff --git a/models/directdata/directdata.dat b/models/directdata/directdata.dat index 46fb5c44..ead24075 100644 --- a/models/directdata/directdata.dat +++ b/models/directdata/directdata.dat @@ -498,6 +498,254 @@ j[A1,B2] 2048 180.5 2049 180.25 2050 180 +l[A1,B1] +1990 610 +1991 609.333 +1992 608.667 +1993 608 +1994 607.333 +1995 606.667 +1996 606 +1997 605.333 +1998 604.667 +1999 604 +2000 603.333 +2001 602.667 +2002 602 +2003 601.333 +2004 600.667 +2005 600 +2006 599 +2007 598 +2008 597 +2009 596 +2010 595 +2011 594 +2012 593 +2013 592 +2014 591 +2015 590 +2016 590.2 +2017 590.4 +2018 590.6 +2019 590.8 +2020 591 +2021 591.2 +2022 591.4 +2023 591.6 +2024 591.8 +2025 592 +2026 592.2 +2027 592.4 +2028 592.6 +2029 592.8 +2030 593 +2031 592.5 +2032 592 +2033 591.5 +2034 591 +2035 590.5 +2036 590 +2037 589.5 +2038 589 +2039 588.5 +2040 588 +2041 587.5 +2042 587 +2043 586.5 +2044 586 +2045 585.5 +2046 585 +2047 584.5 +2048 584 +2049 583.5 +2050 583 +l[A1,B2] +1990 210 +1991 209.333 +1992 208.667 +1993 208 +1994 207.333 +1995 206.667 +1996 206 +1997 205.333 +1998 204.667 +1999 204 +2000 203.333 +2001 202.667 +2002 202 +2003 201.333 +2004 200.667 +2005 200 +2006 199 +2007 198 +2008 197 +2009 196 +2010 195 +2011 194 +2012 193 +2013 192 +2014 191 +2015 190 +2016 189.667 +2017 189.333 +2018 189 +2019 188.667 +2020 188.333 +2021 188 +2022 187.667 +2023 187.333 +2024 187 +2025 186.667 +2026 186.333 +2027 186 +2028 185.667 +2029 185.333 +2030 185 +2031 184.75 +2032 184.5 +2033 184.25 +2034 184 +2035 183.75 +2036 183.5 +2037 183.25 +2038 183 +2039 182.75 +2040 182.5 +2041 182.25 +2042 182 +2043 181.75 +2044 181.5 +2045 181.25 +2046 181 +2047 180.75 +2048 180.5 +2049 180.25 +2050 180 +l[A2,B1] +1990 0 +1991 0 +1992 0 +1993 0 +1994 0 +1995 0 +1996 0 +1997 0 +1998 0 +1999 0 +2000 0 +2001 0 +2002 0 +2003 0 +2004 0 +2005 0 +2006 0 +2007 0 +2008 0 +2009 0 +2010 0 +2011 0 +2012 0 +2013 0 +2014 0 +2015 0 +2016 0 +2017 0 +2018 0 +2019 0 +2020 0 +2021 0 +2022 0 +2023 0 +2024 0 +2025 0 +2026 0 +2027 0 +2028 0 +2029 0 +2030 0 +2031 0 +2032 0 +2033 0 +2034 0 +2035 0 +2036 0 +2037 0 +2038 0 +2039 0 +2040 0 +2041 0 +2042 0 +2043 0 +2044 0 +2045 0 +2046 0 +2047 0 +2048 0 +2049 0 +2050 0 +l[A2,B2] +1990 0 +1991 0 +1992 0 +1993 0 +1994 0 +1995 0 +1996 0 +1997 0 +1998 0 +1999 0 +2000 0 +2001 0 +2002 0 +2003 0 +2004 0 +2005 0 +2006 0 +2007 0 +2008 0 +2009 0 +2010 0 +2011 0 +2012 0 +2013 0 +2014 0 +2015 0 +2016 0 +2017 0 +2018 0 +2019 0 +2020 0 +2021 0 +2022 0 +2023 0 +2024 0 +2025 0 +2026 0 +2027 0 +2028 0 +2029 0 +2030 0 +2031 0 +2032 0 +2033 0 +2034 0 +2035 0 +2036 0 +2037 0 +2038 0 +2039 0 +2040 0 +2041 0 +2042 0 +2043 0 +2044 0 +2045 0 +2046 0 +2047 0 +2048 0 +2049 0 +2050 0 SAVEPER 1990 1 1991 1 @@ -626,3 +874,15 @@ i[A1,B2] 2015 190 2030 185 2050 180 +k[A1,B1] +1990 610 +2005 600 +2015 590 +2030 593 +2050 583 +k[A1,B2] +1990 210 +2005 200 +2015 190 +2030 185 +2050 180 diff --git a/models/directdata/directdata.mdl b/models/directdata/directdata.mdl index 25e303eb..4f1b8467 100644 --- a/models/directdata/directdata.mdl +++ b/models/directdata/directdata.mdl @@ -20,16 +20,20 @@ i[A1, DimB] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B2') | j[A1, DimB] = i[A1, DimB] ~~~:SUPPLEMENTARY| +k[A1, DimB] = GET DIRECT DATA('e_data.csv', ',', 'A', 'B2') ~~| +k[A2, DimB] = 0 ~~| +l[DimA, DimB] = k[DimA, DimB] ~~| + ******************************************************** .Control ********************************************************~ Simulation Control Parameters | -FINAL TIME = 2050 ~~| INITIAL TIME = 1990 ~~| -SAVEPER = TIME STEP ~~| +FINAL TIME = 2050 ~~| TIME STEP = 1 ~~| +SAVEPER = TIME STEP ~~| \\\---/// Sketch information - do not modify anything except names V300 Do not put anything below this section - it will be ignored diff --git a/src/EquationGen.js b/src/EquationGen.js index 79447243..61bf8950 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -98,10 +98,14 @@ export default class EquationGen extends ModelReader { generate() { // Generate code for the variable in either init or eval mode. if (this.var.isData()) { - if (this.var.directDataArgs) { - return this.generateDirectDataInit() - } else { - return this.generateExternalDataInit() + if (R.isEmpty(this.var.points)) { + if (this.var.directDataArgs) { + return this.generateDirectDataInit() + } else { + return this.generateExternalDataInit() + } + } else if (this.mode === 'decl') { + return } } if (this.var.isLookup()) { @@ -196,7 +200,6 @@ export default class EquationGen extends ModelReader { } return value } - lookupDataNameGen(subscripts) { // Construct a name for the static data array associated with a lookup variable. return R.map(subscript => { @@ -354,7 +357,6 @@ export default class EquationGen extends ModelReader { return [] } } - generateDirectDataInit() { // If direct data exists for this variable, copy it from the workbook into one or more lookups. let result = [] @@ -588,10 +590,20 @@ export default class EquationGen extends ModelReader { } return result } - // // Visitor callbacks // + visitEquation(ctx) { + if (this.var.isData() && !R.isEmpty(this.var.points)) { + if (this.mode === 'init-lookups') { + // If the var already has lookup data points, use those instead of reading them from a file. + let lookupData = R.reduce((a, p) => listConcat(a, `${cdbl(p[0])}, ${cdbl(p[1])}`, true), '', this.var.points) + this.emit(`__new_lookup(${this.var.points.length}, /*copy=*/true, (double[]){ ${lookupData} });`) + } + } else { + super.visitEquation(ctx) + } + } visitCall(ctx) { // Convert the function name from Vensim to C format and push it onto the function name stack. // This maintains the name of the current function as its arguments are visited. diff --git a/src/Model.js b/src/Model.js index f61038c8..d66abdac 100644 --- a/src/Model.js +++ b/src/Model.js @@ -66,6 +66,8 @@ function read(parseTree, spec, extData, directData, modelDirname) { checkSpecVars(spec, extData) // Remove variables that are not referenced by an input or output variable. removeUnusedVariables(spec) + // Resolve duplicate declarations by converting to one variable type. + resolveDuplicateDeclarations() } function readSubscriptRanges(tree, dimensionFamilies, indexFamilies, modelDirname) { // Read subscript ranges from the model. @@ -372,7 +374,33 @@ function removeUnusedVariables(spec) { varsForName.push(v) } } - +function resolveDuplicateDeclarations() { + // Find subscripted const vars where some subscripts are data vars. + // TODO consider doing the same for lookup vars + let constValue = v => { + let value = parseFloat(v.modelFormula) + if (Number.isNaN(value)) { + value = 0 + } + return value + } + let data = dataVars() + for (let constVar of constVars()) { + let dataVar = data.find(d => d.varName === constVar.varName) + if (dataVar) { + // console.log(`dup decl const(${constVar.refId}), data(${dataVar.refId})`) + // Change the var type from const to data and add lookup data points. + constVar.varType = 'data' + let initialTimeVar = varWithName('_initial_time') + let finalTimeVar = varWithName('_final_time') + constVar.points = [ + [constValue(initialTimeVar), constValue(constVar)], + [constValue(finalTimeVar), constValue(constVar)] + ] + break + } + } +} // // Analysis helpers // From 3263bdbdcf06182e80308b455a52f9272ea042c7 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Mon, 20 Sep 2021 12:07:19 -0700 Subject: [PATCH 12/13] add some comments --- src/EquationGen.js | 2 ++ src/Model.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/EquationGen.js b/src/EquationGen.js index 61bf8950..43de4c9b 100644 --- a/src/EquationGen.js +++ b/src/EquationGen.js @@ -98,6 +98,8 @@ export default class EquationGen extends ModelReader { generate() { // Generate code for the variable in either init or eval mode. if (this.var.isData()) { + // If the data var was converted from a const, it will have lookup points. + // Otherwise, read a data file to get lookup data. if (R.isEmpty(this.var.points)) { if (this.var.directDataArgs) { return this.generateDirectDataInit() diff --git a/src/Model.js b/src/Model.js index d66abdac..07d5ff56 100644 --- a/src/Model.js +++ b/src/Model.js @@ -386,10 +386,10 @@ function resolveDuplicateDeclarations() { } let data = dataVars() for (let constVar of constVars()) { - let dataVar = data.find(d => d.varName === constVar.varName) - if (dataVar) { - // console.log(`dup decl const(${constVar.refId}), data(${dataVar.refId})`) + if (data.find(d => d.varName === constVar.varName)) { // Change the var type from const to data and add lookup data points. + // We are guaranteed to have values for the well-known initial and final time variables. + // For a constant, the equivalent lookup has the same value from initial through final times. constVar.varType = 'data' let initialTimeVar = varWithName('_initial_time') let finalTimeVar = varWithName('_final_time') From 59b17eab4e13acc08818702ebeed4e74dbd1d0d9 Mon Sep 17 00:00:00 2001 From: Todd Fincannon Date: Mon, 20 Sep 2021 17:57:07 -0700 Subject: [PATCH 13/13] change generated lookup to cover the entire x axis --- src/Model.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Model.js b/src/Model.js index 07d5ff56..8e67f820 100644 --- a/src/Model.js +++ b/src/Model.js @@ -377,27 +377,23 @@ function removeUnusedVariables(spec) { function resolveDuplicateDeclarations() { // Find subscripted const vars where some subscripts are data vars. // TODO consider doing the same for lookup vars - let constValue = v => { - let value = parseFloat(v.modelFormula) - if (Number.isNaN(value)) { - value = 0 - } - return value - } + // Least and greatest safe double values in C rounded to convenient consts + const MIN_SAFE_DBL = -1e308 + const MAX_SAFE_DBL = 1e308 let data = dataVars() for (let constVar of constVars()) { if (data.find(d => d.varName === constVar.varName)) { // Change the var type from const to data and add lookup data points. - // We are guaranteed to have values for the well-known initial and final time variables. - // For a constant, the equivalent lookup has the same value from initial through final times. + // For a constant, the equivalent lookup has the same value over the entire x axis. + let value = parseFloat(constVar.modelFormula) + if (isNaN(value)) { + console.error(`The value for const var ${constVar.refId} converted to a lookup is NaN.`) + } constVar.varType = 'data' - let initialTimeVar = varWithName('_initial_time') - let finalTimeVar = varWithName('_final_time') constVar.points = [ - [constValue(initialTimeVar), constValue(constVar)], - [constValue(finalTimeVar), constValue(constVar)] + [MIN_SAFE_DBL, value], + [MAX_SAFE_DBL, value] ] - break } } }