@sdeverywhere/compile 0.7.20 → 0.7.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -4
- package/src/_shared/helpers.js +8 -39
- package/src/_shared/subscript.js +1 -23
- package/src/generate/gen-code-c.js +10 -15
- package/src/generate/gen-code-js.js +8 -3
- package/src/generate/gen-const-list.js +9 -19
- package/src/generate/gen-equation.js +114 -79
- package/src/generate/gen-expr.js +67 -24
- package/src/generate/gen-lookup-from-points.js +17 -4
- package/src/generate/loop-index-vars.js +12 -0
- package/src/index.js +5 -1
- package/src/model/model.js +2 -6
- package/src/model/read-equation-fn-delay.js +55 -29
- package/src/model/read-equation-fn-game.js +2 -2
- package/src/model/read-equation-fn-npv.js +10 -3
- package/src/model/read-equation-fn-smooth.js +15 -8
- package/src/model/read-equation-fn-trend.js +6 -2
- package/src/model/read-equation-fn-with-lookup.js +1 -1
- package/src/model/read-equations.js +273 -119
- package/src/model/read-variables.js +13 -21
- package/src/model/toposort.js +1 -1
- package/src/parse-and-generate.js +10 -15
- package/src/preprocess/preprocessor.js +0 -247
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/compile",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.22",
|
|
4
4
|
"description": "The core Vensim to C compiler for the SDEverywhere tool suite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@sdeverywhere/parse": "^0.1.
|
|
8
|
+
"@sdeverywhere/parse": "^0.1.2",
|
|
9
9
|
"bufx": "^1.0.5",
|
|
10
10
|
"byline": "^5.0.0",
|
|
11
11
|
"csv-parse": "^5.3.3",
|
|
12
12
|
"js-yaml": "^3.13.1",
|
|
13
13
|
"ramda": "^0.27.0",
|
|
14
|
-
"split-string": "^6.0.0",
|
|
15
14
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
|
|
16
15
|
},
|
|
17
16
|
"author": "Climate Interactive",
|
|
@@ -32,7 +31,7 @@
|
|
|
32
31
|
"precommit": "../../scripts/precommit",
|
|
33
32
|
"type-check": "tsc --noEmit -p tsconfig-test.json",
|
|
34
33
|
"test": "vitest run",
|
|
35
|
-
"test:watch": "vitest",
|
|
34
|
+
"test:watch": "vitest --hideSkippedTests",
|
|
36
35
|
"test:ci": "vitest run",
|
|
37
36
|
"ci:build": "run-s lint prettier:check type-check test:ci"
|
|
38
37
|
}
|
package/src/_shared/helpers.js
CHANGED
|
@@ -3,9 +3,10 @@ import util from 'util'
|
|
|
3
3
|
import B from 'bufx'
|
|
4
4
|
import { parse as parseCsv } from 'csv-parse/sync'
|
|
5
5
|
import * as R from 'ramda'
|
|
6
|
-
import split from 'split-string'
|
|
7
6
|
import XLSX from 'xlsx'
|
|
8
7
|
|
|
8
|
+
import { canonicalId, canonicalVarId } from '@sdeverywhere/parse'
|
|
9
|
+
|
|
9
10
|
// Set true to print a stack trace in vlog
|
|
10
11
|
export const PRINT_VLOG_TRACE = false
|
|
11
12
|
|
|
@@ -40,25 +41,11 @@ export function resetHelperState() {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export let canonicalName = name => {
|
|
43
|
-
// Format a model variable name into a valid C identifier.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.replace(/"/g, '_')
|
|
49
|
-
.replace(/\s+!$/g, '!')
|
|
50
|
-
.replace(/\s/g, '_')
|
|
51
|
-
.replace(/,/g, '_')
|
|
52
|
-
.replace(/-/g, '_')
|
|
53
|
-
.replace(/\./g, '_')
|
|
54
|
-
.replace(/\$/g, '_')
|
|
55
|
-
.replace(/'/g, '_')
|
|
56
|
-
.replace(/&/g, '_')
|
|
57
|
-
.replace(/%/g, '_')
|
|
58
|
-
.replace(/\//g, '_')
|
|
59
|
-
.replace(/\|/g, '_')
|
|
60
|
-
.toLowerCase()
|
|
61
|
-
)
|
|
44
|
+
// Format a model variable or subscript/dimension name into a valid C identifier.
|
|
45
|
+
// In the case where you have a full variable name that includes subscripts/dimensions
|
|
46
|
+
// (e.g., 'Variable name[DimA,B2]'), use `canonicalVensimName` to convert the
|
|
47
|
+
// base variable name and subscript/dimension parts to canonical form indepdendently.
|
|
48
|
+
return canonicalId(name)
|
|
62
49
|
}
|
|
63
50
|
export let decanonicalize = name => {
|
|
64
51
|
// Decanonicalize the var name.
|
|
@@ -71,9 +58,6 @@ export let decanonicalize = name => {
|
|
|
71
58
|
}
|
|
72
59
|
return name
|
|
73
60
|
}
|
|
74
|
-
export let cFunctionName = name => {
|
|
75
|
-
return canonicalName(name).toUpperCase()
|
|
76
|
-
}
|
|
77
61
|
export let isSeparatedVar = v => {
|
|
78
62
|
return v.separationDims.length > 0
|
|
79
63
|
}
|
|
@@ -244,22 +228,7 @@ export let readCsv = (pathname, delimiter = ',') => {
|
|
|
244
228
|
}
|
|
245
229
|
// Convert the var name and subscript names to canonical form separately.
|
|
246
230
|
export let canonicalVensimName = vname => {
|
|
247
|
-
|
|
248
|
-
let m = vname.match(/([^[]+)(?:\[([^\]]+)\])?/)
|
|
249
|
-
if (m) {
|
|
250
|
-
result = canonicalName(m[1])
|
|
251
|
-
if (m[2]) {
|
|
252
|
-
let subscripts = m[2].split(',').map(x => canonicalName(x))
|
|
253
|
-
result += `[${subscripts.join(',')}]`
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return result
|
|
257
|
-
}
|
|
258
|
-
// Split a model string into an array of equations without the "|" terminator.
|
|
259
|
-
// Allow "|" to occur in quoted variable names across line breaks.
|
|
260
|
-
// Retain the backslash character.
|
|
261
|
-
export let splitEquations = mdl => {
|
|
262
|
-
return split(mdl, { separator: '|', quotes: ['"'], keep: () => true })
|
|
231
|
+
return canonicalVarId(vname)
|
|
263
232
|
}
|
|
264
233
|
// Function to map over lists's value and index
|
|
265
234
|
export let mapIndexed = R.addIndex(R.map)
|
package/src/_shared/subscript.js
CHANGED
|
@@ -2,7 +2,7 @@ import util from 'util'
|
|
|
2
2
|
import B from 'bufx'
|
|
3
3
|
import yaml from 'js-yaml'
|
|
4
4
|
import * as R from 'ramda'
|
|
5
|
-
import { canonicalName,
|
|
5
|
+
import { canonicalName, vlog } from './helpers.js'
|
|
6
6
|
|
|
7
7
|
// A subscript is a dimension or an index.
|
|
8
8
|
// Both have the same properties: model name, canonical name, family, values.
|
|
@@ -233,18 +233,6 @@ export function loadSubscriptsFromYaml(yamlSubs) {
|
|
|
233
233
|
subscripts.set(k, subs[k])
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
export function normalizeSubscripts(subscripts) {
|
|
237
|
-
// Sort a list of subscript names already in canonical form according to the subscript family.
|
|
238
|
-
let subs = R.map(name => sub(name), subscripts)
|
|
239
|
-
subs = R.sortBy(R.prop('family'), subs)
|
|
240
|
-
let normalizedSubs
|
|
241
|
-
try {
|
|
242
|
-
normalizedSubs = R.map(R.prop('name'), subs)
|
|
243
|
-
} catch (e) {
|
|
244
|
-
console.error(`normalizeSubscripts fails for ${subscripts}`)
|
|
245
|
-
}
|
|
246
|
-
return normalizedSubs
|
|
247
|
-
}
|
|
248
236
|
export function extractMarkedDims(subscripts) {
|
|
249
237
|
// Extract all marked dimensions and update subscripts.
|
|
250
238
|
let dims = []
|
|
@@ -366,13 +354,3 @@ export function separatedVariableIndex(rhsSub, variable, rhsSubscripts) {
|
|
|
366
354
|
}
|
|
367
355
|
return null
|
|
368
356
|
}
|
|
369
|
-
// Function to filter canonical dimension names from a list of names
|
|
370
|
-
export let dimensionNames = R.pipe(
|
|
371
|
-
R.filter(subscript => isDimension(subscript)),
|
|
372
|
-
asort
|
|
373
|
-
)
|
|
374
|
-
// Function to filter canonical index names from a list of names
|
|
375
|
-
export let indexNames = R.pipe(
|
|
376
|
-
R.filter(subscript => isIndex(subscript)),
|
|
377
|
-
asort
|
|
378
|
-
)
|
|
@@ -153,11 +153,19 @@ ${chunkedFunctions('evalLevels', Model.levelVars(), ' // Evaluate levels.')}`
|
|
|
153
153
|
let setLookupBody
|
|
154
154
|
if (spec.customLookups === true || Array.isArray(spec.customLookups)) {
|
|
155
155
|
setLookupBody = `\
|
|
156
|
+
Lookup** pLookup = NULL;
|
|
156
157
|
switch (varIndex) {
|
|
157
158
|
${setLookupImpl(Model.varIndexInfo(), spec.customLookups)}
|
|
158
159
|
default:
|
|
159
160
|
fprintf(stderr, "No lookup found for var index %zu in setLookup\\n", varIndex);
|
|
160
161
|
break;
|
|
162
|
+
}
|
|
163
|
+
if (pLookup != NULL) {
|
|
164
|
+
if (*pLookup == NULL) {
|
|
165
|
+
*pLookup = __new_lookup(numPoints, /*copy=*/true, points);
|
|
166
|
+
} else {
|
|
167
|
+
__set_lookup(*pLookup, numPoints, points);
|
|
168
|
+
}
|
|
161
169
|
}`
|
|
162
170
|
} else {
|
|
163
171
|
let msg = 'The setLookup function was not enabled for the generated model. '
|
|
@@ -200,19 +208,6 @@ void setInputsFromBuffer(double* inputData) {
|
|
|
200
208
|
${inputsFromBufferImpl()}
|
|
201
209
|
}
|
|
202
210
|
|
|
203
|
-
void replaceLookup(Lookup** lookup, double* points, size_t numPoints) {
|
|
204
|
-
if (lookup == NULL) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (*lookup != NULL) {
|
|
208
|
-
__delete_lookup(*lookup);
|
|
209
|
-
*lookup = NULL;
|
|
210
|
-
}
|
|
211
|
-
if (points != NULL) {
|
|
212
|
-
*lookup = __new_lookup(numPoints, /*copy=*/true, points);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
211
|
void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints) {
|
|
217
212
|
${setLookupBody}
|
|
218
213
|
}
|
|
@@ -441,7 +436,7 @@ ${section(chunk)}
|
|
|
441
436
|
return inputVars.join('\n')
|
|
442
437
|
}
|
|
443
438
|
function setLookupImpl(varIndexInfo, customLookups) {
|
|
444
|
-
// Emit
|
|
439
|
+
// Emit case statements for all lookups and data variables that can be overridden
|
|
445
440
|
// at runtime
|
|
446
441
|
let includeCase
|
|
447
442
|
if (Array.isArray(customLookups)) {
|
|
@@ -467,7 +462,7 @@ ${section(chunk)}
|
|
|
467
462
|
}
|
|
468
463
|
let c = ''
|
|
469
464
|
c += ` case ${info.varIndex}:\n`
|
|
470
|
-
c += `
|
|
465
|
+
c += ` pLookup = &${lookupVar};\n`
|
|
471
466
|
c += ` break;`
|
|
472
467
|
return c
|
|
473
468
|
})
|
|
@@ -251,10 +251,15 @@ ${chunkedFunctions('evalLevels', true, Model.levelVars(), ' // Evaluate levels'
|
|
|
251
251
|
}
|
|
252
252
|
const varIndex = varSpec.varIndex;
|
|
253
253
|
const subs = varSpec.subscriptIndices;
|
|
254
|
+
let lookup;
|
|
254
255
|
switch (varIndex) {
|
|
255
256
|
${setLookupImpl(Model.varIndexInfo(), spec.customLookups)}
|
|
256
257
|
default:
|
|
257
258
|
throw new Error(\`No lookup found for var index \${varIndex} in setLookup\`);
|
|
259
|
+
}
|
|
260
|
+
if (lookup) {
|
|
261
|
+
const size = points ? points.length / 2 : 0;
|
|
262
|
+
lookup.setData(size, points);
|
|
258
263
|
}`
|
|
259
264
|
} else {
|
|
260
265
|
let msg = 'The setLookup function was not enabled for the generated model. '
|
|
@@ -307,7 +312,7 @@ ${customOutputSection(Model.varIndexInfo(), spec.customOutputs)}
|
|
|
307
312
|
return `\
|
|
308
313
|
/*export*/ function setInputs(valueAtIndex /*: (index: number) => number*/) {${inputsFromBufferImpl()}}
|
|
309
314
|
|
|
310
|
-
/*export*/ function setLookup(varSpec /*: VarSpec*/, points /*: Float64Array*/) {
|
|
315
|
+
/*export*/ function setLookup(varSpec /*: VarSpec*/, points /*: Float64Array | undefined*/) {
|
|
311
316
|
${setLookupBody}
|
|
312
317
|
}
|
|
313
318
|
|
|
@@ -517,7 +522,7 @@ ${section(chunk)}
|
|
|
517
522
|
return inputVars
|
|
518
523
|
}
|
|
519
524
|
function setLookupImpl(varIndexInfo, customLookups) {
|
|
520
|
-
// Emit
|
|
525
|
+
// Emit case statements for all lookups and data variables that can be overridden
|
|
521
526
|
// at runtime
|
|
522
527
|
let overrideAllowed
|
|
523
528
|
if (Array.isArray(customLookups)) {
|
|
@@ -543,7 +548,7 @@ ${section(chunk)}
|
|
|
543
548
|
}
|
|
544
549
|
let c = ''
|
|
545
550
|
c += ` case ${info.varIndex}:\n`
|
|
546
|
-
c += ` ${lookupVar}
|
|
551
|
+
c += ` lookup = ${lookupVar};\n`
|
|
547
552
|
c += ` break;`
|
|
548
553
|
return c
|
|
549
554
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cartesianProductOf, cdbl } from '../_shared/helpers.js'
|
|
2
|
-
import { isDimension,
|
|
2
|
+
import { isDimension, sub } from '../_shared/subscript.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Generate code for a single element in a const list definition.
|
|
@@ -12,12 +12,10 @@ import { isDimension, normalizeSubscripts, sub } from '../_shared/subscript.js'
|
|
|
12
12
|
export function generateConstListElement(variable, parsedEqn) {
|
|
13
13
|
// In the "read variables" phase, const lists are expanded into separated variable
|
|
14
14
|
// definitions, so `variable` here will have `subscripts` that represent specific
|
|
15
|
-
// subscript indices
|
|
16
|
-
//
|
|
17
|
-
// be in the original order from the model equation.
|
|
15
|
+
// subscript indices that are ordered according to the dimension positions from the
|
|
16
|
+
// variable definition.
|
|
18
17
|
//
|
|
19
|
-
// In the following example,
|
|
20
|
-
// we have a 2D variable whose original dimensions are not in normal order:
|
|
18
|
+
// In the following example, we have a 2D variable whose original dimensions are:
|
|
21
19
|
// DimA: A1, A2 ~~|
|
|
22
20
|
// DimB: B1, B2, B3 ~~|
|
|
23
21
|
// x[DimB, DimA] = 1, 2; 3, 4; 5, 6; ~~|
|
|
@@ -53,25 +51,17 @@ export function generateConstListElement(variable, parsedEqn) {
|
|
|
53
51
|
// order of the dimensions from the equation LHS.
|
|
54
52
|
const origCombos = cartesianProductOf(subIdArrays)
|
|
55
53
|
|
|
56
|
-
// Now we have the combinations in
|
|
54
|
+
// Convert to strings to make matching easier. Now we have the combinations in
|
|
55
|
+
// original order.
|
|
57
56
|
// [_b1,_a1]
|
|
58
57
|
// [_b1,_a2]
|
|
59
58
|
// [_b2,_a1]
|
|
60
59
|
// ...
|
|
61
|
-
|
|
62
|
-
// `variable.subscripts` (which is already in normalized order).
|
|
63
|
-
const normalizedCombos = origCombos.map(normalizeSubscripts)
|
|
64
|
-
|
|
65
|
-
// Convert to strings to make matching easier. Now we have the strings in normalized order:
|
|
66
|
-
// [_a1,_b1]
|
|
67
|
-
// [_a2,_b1]
|
|
68
|
-
// [_a1,_b2]
|
|
69
|
-
// ...
|
|
70
|
-
const comboStrings = normalizedCombos.map(combo => combo.map(subId => `[${subId}]`).join(''))
|
|
60
|
+
const comboStrings = origCombos.map(combo => combo.map(subId => `[${subId}]`).join(''))
|
|
71
61
|
|
|
72
62
|
// Convert `variable.subscripts` into the same format so that we can do an array lookup,
|
|
73
|
-
// for example if this separated variable instance is x[_a2
|
|
74
|
-
// [_a2
|
|
63
|
+
// for example if this separated variable instance is x[_b1,_a2], this will be:
|
|
64
|
+
// [_b1,_a2]
|
|
75
65
|
const lhsComboString = variable.subscripts.map(subId => `[${subId}]`).join('')
|
|
76
66
|
|
|
77
67
|
// Find the index of the combination that matches `variable.subscripts`
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
dimensionNames,
|
|
3
|
-
hasMapping,
|
|
4
|
-
isDimension,
|
|
5
|
-
isIndex,
|
|
6
|
-
isTrivialDimension,
|
|
7
|
-
normalizeSubscripts,
|
|
8
|
-
separatedVariableIndex,
|
|
9
|
-
sub
|
|
10
|
-
} from '../_shared/subscript.js'
|
|
1
|
+
import { hasMapping, isDimension, isIndex, isTrivialDimension, sub } from '../_shared/subscript.js'
|
|
11
2
|
import { generateConstListElement } from './gen-const-list.js'
|
|
12
3
|
|
|
13
4
|
import { generateDirectConstInit } from './gen-direct-const.js'
|
|
@@ -18,6 +9,9 @@ import { generateLookupFromPoints } from './gen-lookup-from-points.js'
|
|
|
18
9
|
|
|
19
10
|
import LoopIndexVars from './loop-index-vars.js'
|
|
20
11
|
|
|
12
|
+
const loopIndexVarNames = ['i', 'j', 'k', 'l', 'm']
|
|
13
|
+
const arrayIndexVarNames = ['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r']
|
|
14
|
+
|
|
21
15
|
/**
|
|
22
16
|
* Generate C code for the given model equation.
|
|
23
17
|
*
|
|
@@ -36,8 +30,22 @@ import LoopIndexVars from './loop-index-vars.js'
|
|
|
36
30
|
*/
|
|
37
31
|
export function generateEquation(variable, mode, extData, directData, modelDir, outFormat) {
|
|
38
32
|
// Maps of LHS subscript families to loop index vars for lookup on the RHS
|
|
39
|
-
const loopIndexVars = new LoopIndexVars(
|
|
40
|
-
const arrayIndexVars = new LoopIndexVars(
|
|
33
|
+
const loopIndexVars = new LoopIndexVars(loopIndexVarNames)
|
|
34
|
+
const arrayIndexVars = new LoopIndexVars(arrayIndexVarNames)
|
|
35
|
+
|
|
36
|
+
// Make the generated loops easier to follow by eagerly determining the order of index
|
|
37
|
+
// variables based on the order of the LHS dimension names. For example, if we have:
|
|
38
|
+
// x[DimA, DimC, DimB] = y[DimB, DimC, DimA] ~~|
|
|
39
|
+
// This will generate loop index variable mappings in the following order:
|
|
40
|
+
// DimA -> i
|
|
41
|
+
// DimC -> j
|
|
42
|
+
// DimB -> k
|
|
43
|
+
const lhsDimIds = variable.subscripts.filter(isDimension)
|
|
44
|
+
for (const lhsDimId of lhsDimIds) {
|
|
45
|
+
// We ignore the return value here. Calling `index` will make `LoopIndexVars` eagerly
|
|
46
|
+
// set up a mapping from the dimension ID to the loop index variable name.
|
|
47
|
+
loopIndexVars.index(lhsDimId)
|
|
48
|
+
}
|
|
41
49
|
|
|
42
50
|
// Generate the LHS variable reference code
|
|
43
51
|
const parsedEqn = variable.parsedEqn
|
|
@@ -64,17 +72,14 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
|
|
|
64
72
|
return [comment, ...initCode]
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
// Get the dimension IDs for the LHS variable
|
|
68
|
-
const dimIds = dimensionNames(variable.subscripts)
|
|
69
|
-
|
|
70
75
|
// Turn each dimension ID into a loop with a loop index variable.
|
|
71
76
|
// If the variable has no subscripts, nothing will be emitted here.
|
|
72
77
|
const indexDecl = outFormat === 'js' ? 'let' : 'size_t'
|
|
73
78
|
const openLoops = []
|
|
74
79
|
const closeLoops = []
|
|
75
|
-
for (const
|
|
76
|
-
const indexName = loopIndexVars.index(
|
|
77
|
-
const dimLength = sub(
|
|
80
|
+
for (const lhsDimId of lhsDimIds) {
|
|
81
|
+
const indexName = loopIndexVars.index(lhsDimId)
|
|
82
|
+
const dimLength = sub(lhsDimId).size
|
|
78
83
|
openLoops.push(` for (${indexDecl} ${indexName} = 0; ${indexName} < ${dimLength}; ${indexName}++) {`)
|
|
79
84
|
closeLoops.push(' }')
|
|
80
85
|
}
|
|
@@ -108,13 +113,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
|
|
|
108
113
|
// Apply special handling for lookup variables. The data for lookup variables is already
|
|
109
114
|
// defined as a set of explicit data points (stored in the `Variable` instance).
|
|
110
115
|
if (variable.isLookup()) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return []
|
|
116
|
+
// Emit decl/init code for the lookup
|
|
117
|
+
const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
|
|
118
|
+
if (lookupDef.length > 0) {
|
|
119
|
+
return [...openLoops, ...lookupDef, ...closeLoops]
|
|
115
120
|
} else {
|
|
116
|
-
|
|
117
|
-
return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars, outFormat)
|
|
121
|
+
return []
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -127,9 +131,6 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
|
|
|
127
131
|
// Keep a buffer of code that will be included after the generated primary formula
|
|
128
132
|
const postFormulaLines = []
|
|
129
133
|
|
|
130
|
-
// Keep track of marked dimensions
|
|
131
|
-
const markedDimIds = new Set()
|
|
132
|
-
|
|
133
134
|
// Generate code for an equation with an expression on the RHS
|
|
134
135
|
const genExprCtx = {
|
|
135
136
|
variable,
|
|
@@ -138,14 +139,12 @@ export function generateEquation(variable, mode, extData, directData, modelDir,
|
|
|
138
139
|
cLhs,
|
|
139
140
|
loopIndexVars,
|
|
140
141
|
arrayIndexVars,
|
|
141
|
-
resetMarkedDims: () => markedDimIds.clear(),
|
|
142
|
-
addMarkedDim: dimId => markedDimIds.add(dimId),
|
|
143
142
|
emitPreInnerLoop: s => preInnerLoopLines.push(s),
|
|
144
143
|
emitPreFormula: s => preFormulaLines.push(s),
|
|
145
144
|
emitPostFormula: s => postFormulaLines.push(s),
|
|
146
|
-
cVarRef: varRef => cVarRef(variable, varRef,
|
|
145
|
+
cVarRef: varRef => cVarRef(variable, varRef, loopIndexVars, arrayIndexVars),
|
|
147
146
|
cVarRefWithLhsSubscripts: baseVarId => cVarRefWithLhsSubscripts(variable, baseVarId, loopIndexVars),
|
|
148
|
-
cVarIndex: subOrDimId => cVarIndex(variable,
|
|
147
|
+
cVarIndex: subOrDimId => cVarIndex(variable, subOrDimId, loopIndexVars, arrayIndexVars)
|
|
149
148
|
}
|
|
150
149
|
const cRhs = generateExpr(parsedEqn.rhs.expr, genExprCtx)
|
|
151
150
|
const formula = ` ${cLhs} = ${cRhs};`
|
|
@@ -177,9 +176,12 @@ function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
|
|
|
177
176
|
// When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
|
|
178
177
|
return `[${i}]`
|
|
179
178
|
} else {
|
|
180
|
-
|
|
179
|
+
// Otherwise, emit e.g. `[_dim[i]]`
|
|
180
|
+
return `[${subId}[${i}]]`
|
|
181
181
|
}
|
|
182
182
|
} else {
|
|
183
|
+
// This is a specific subscript (i.e., an index); dereference the array using the index
|
|
184
|
+
// number of the subscript
|
|
183
185
|
return `[${sub(subId).value}]`
|
|
184
186
|
}
|
|
185
187
|
})
|
|
@@ -191,33 +193,26 @@ function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
|
|
|
191
193
|
*
|
|
192
194
|
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
193
195
|
* @param {*} rhsVarRef The `VariableRef` used in a RHS expression.
|
|
194
|
-
* @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
|
|
195
|
-
* in an array function, for example `SUM(x[DimA!])`.
|
|
196
196
|
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
197
197
|
* @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
|
|
198
198
|
* (that use marked dimensions).
|
|
199
199
|
* @returns {string} The C variable reference.
|
|
200
200
|
*/
|
|
201
|
-
function cVarRef(lhsVariable, rhsVarRef,
|
|
201
|
+
function cVarRef(lhsVariable, rhsVarRef, loopIndexVars, arrayIndexVars) {
|
|
202
202
|
if (rhsVarRef.subscriptRefs === undefined) {
|
|
203
203
|
// No subscripts, so return the base variable ID
|
|
204
204
|
return rhsVarRef.varId
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// XXX: For now, strip the mark here (need to revisit this)
|
|
211
|
-
rhsSubIds = normalizeSubscripts(rhsVarRef.subscriptRefs.map(subRef => subRef.subId.replace('!', '')))
|
|
212
|
-
} catch (e) {
|
|
213
|
-
throw new Error(`normalizeSubscripts failed in rhsVarRef: refId=${lhsVariable.refId} error=${e}`)
|
|
214
|
-
}
|
|
207
|
+
// Get the RHS subscript IDs. Note that we leave the "!" in place in the case of
|
|
208
|
+
// marked dimensions; they will be handled specially in `cVarIndex`.
|
|
209
|
+
const rhsSubIds = rhsVarRef.subscriptRefs.map(subRef => subRef.subId)
|
|
215
210
|
|
|
216
211
|
// Determine the subscript code (array lookup) for each dimension. For example, if
|
|
217
212
|
// the RHS variable reference in the model looks like `x[DimA]`, this will convert the
|
|
218
213
|
// `[DimA]` part to `[_dima[i]]` (or simply `[i]` if it is a "trivial" dimension).
|
|
219
214
|
const cSubParts = rhsSubIds.map(rhsSubId => {
|
|
220
|
-
return cVarIndex(lhsVariable,
|
|
215
|
+
return cVarIndex(lhsVariable, rhsSubId, loopIndexVars, arrayIndexVars)
|
|
221
216
|
})
|
|
222
217
|
|
|
223
218
|
return `${rhsVarRef.varId}${cSubParts.map(part => `[${part}]`).join('')}`
|
|
@@ -227,57 +222,97 @@ function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndex
|
|
|
227
222
|
* Return the C code for indexing into a subscripted variable.
|
|
228
223
|
*
|
|
229
224
|
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
230
|
-
* @param {string[]} rhsSubIds The set of all subscript or dimension IDs used on the RHS.
|
|
231
225
|
* @param {string} rhsSubId The specific subscript or dimension ID being evaluated.
|
|
232
|
-
* @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
|
|
233
|
-
* in an array function, for example `SUM(x[DimA!])`.
|
|
234
226
|
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
235
227
|
* @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
|
|
236
228
|
* (that use marked dimensions).
|
|
237
229
|
* @returns {string} The C variable reference.
|
|
238
230
|
*/
|
|
239
|
-
function cVarIndex(lhsVariable,
|
|
231
|
+
function cVarIndex(lhsVariable, rhsSubId, loopIndexVars, arrayIndexVars) {
|
|
232
|
+
// NOTE: The code in this function follows basically the same steps that are used in
|
|
233
|
+
// the `resolveRhsSubOrDim` function in the `readEquation` phase.
|
|
234
|
+
// TODO: Since these two functions are so similar, it would be better to write a
|
|
235
|
+
// single function that has the common logic, and then write a wrapper function
|
|
236
|
+
// that converts the resulting subscript or dimension ID to an index
|
|
237
|
+
|
|
238
|
+
// Helper function that returns either `indexVarName` or `dimId[indexVarName]` depending
|
|
239
|
+
// on whether `dimId` is considered "trivial".
|
|
240
|
+
function optimalIndex(indexVars, dimId) {
|
|
241
|
+
const indexVarName = indexVars.index(dimId)
|
|
242
|
+
if (isTrivialDimension(dimId)) {
|
|
243
|
+
// When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
|
|
244
|
+
return `${indexVarName}`
|
|
245
|
+
} else {
|
|
246
|
+
// Otherwise, emit e.g. `[_dim[i]]`
|
|
247
|
+
return `${dimId}[${indexVarName}]`
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (rhsSubId.endsWith('!')) {
|
|
252
|
+
// The dimension ID at this position is "marked", indicating that the vector function
|
|
253
|
+
// (e.g., `SUM`) should operate over the elements in this dimension. Strip the "!"
|
|
254
|
+
// to get the actual dimension ID, then get the associated array loop index variable.
|
|
255
|
+
const rhsDimId = rhsSubId.replace('!', '')
|
|
256
|
+
return optimalIndex(arrayIndexVars, rhsDimId)
|
|
257
|
+
}
|
|
258
|
+
|
|
240
259
|
if (isIndex(rhsSubId)) {
|
|
241
260
|
// This is a specific subscript (i.e., an index); dereference the array using the index
|
|
242
261
|
// number of the subscript
|
|
243
262
|
return `${sub(rhsSubId).value}`
|
|
244
263
|
}
|
|
245
264
|
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
//
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
257
|
-
|
|
265
|
+
// At this point we know that it is a dimension ID. Figure out which LHS subscript or
|
|
266
|
+
// dimension is a match. First see if there is an exact match.
|
|
267
|
+
const lhsSubRefs = lhsVariable.parsedEqn.lhs.varDef.subscriptRefs
|
|
268
|
+
const lhsSubIds = lhsSubRefs?.map(subRef => subRef.subId) || []
|
|
269
|
+
const lhsDimIndex = lhsSubIds.findIndex(lhsSubId => lhsSubId === rhsSubId)
|
|
270
|
+
if (lhsDimIndex >= 0) {
|
|
271
|
+
// There is a match. If the LHS variable is separated, use the separated subscript
|
|
272
|
+
// ID at this position (i.e., the value from the `subscripts` array), otherwise we
|
|
273
|
+
// use the dimension ID at this position.
|
|
274
|
+
const lhsSubOrDimId = lhsVariable.subscripts[lhsDimIndex]
|
|
275
|
+
if (isIndex(lhsSubOrDimId)) {
|
|
276
|
+
// This is a specific subscript (i.e., an index); dereference the array using the index
|
|
277
|
+
// number of the subscript
|
|
278
|
+
return `${sub(lhsSubOrDimId).value}`
|
|
279
|
+
} else {
|
|
280
|
+
// This is a dimension; use the associated loop index variable
|
|
281
|
+
return optimalIndex(loopIndexVars, lhsSubOrDimId)
|
|
258
282
|
}
|
|
283
|
+
}
|
|
259
284
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
285
|
+
// There wasn't an exact match by dimension ID. Find the position of the LHS dimension
|
|
286
|
+
// that has a mapping to the RHS dimension.
|
|
287
|
+
const mappedLhsDimIndex = lhsSubIds.findIndex(lhsSubId => hasMapping(rhsSubId, lhsSubId))
|
|
288
|
+
if (mappedLhsDimIndex >= 0) {
|
|
289
|
+
// There is a match. If the LHS variable is separated, use the _mapped_ separated
|
|
290
|
+
// subscript ID at this position (i.e., the value from the `subscripts` array),
|
|
291
|
+
// otherwise we use the _mapped_ dimension ID at this position.
|
|
292
|
+
const mappedLhsSubOrDimId = lhsVariable.subscripts[mappedLhsDimIndex]
|
|
293
|
+
if (isIndex(mappedLhsSubOrDimId)) {
|
|
294
|
+
// This is a specific subscript (i.e., an index); dereference the array using the index
|
|
295
|
+
// number of the _mapped_ subscript
|
|
296
|
+
const mappedLhsSubId = mappedLhsSubOrDimId
|
|
297
|
+
const mappedLhsDimId = lhsSubIds[mappedLhsDimIndex]
|
|
298
|
+
const lhsDim = sub(mappedLhsDimId)
|
|
299
|
+
const rhsDim = sub(rhsSubId)
|
|
300
|
+
const lhsSubIndex = lhsDim.value.indexOf(mappedLhsSubId)
|
|
301
|
+
if (lhsSubIndex >= 0) {
|
|
302
|
+
const mappedSubId = rhsDim.mappings[mappedLhsDimId][lhsSubIndex]
|
|
303
|
+
return `${sub(mappedSubId).value}`
|
|
304
|
+
} else {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`Failed to find mapped LHS subscript ${mappedLhsSubId} for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`
|
|
307
|
+
)
|
|
269
308
|
}
|
|
309
|
+
} else {
|
|
310
|
+
// Determine the dimension mapping
|
|
311
|
+
const mappedLhsDimId = lhsSubIds[mappedLhsDimIndex]
|
|
312
|
+
const indexVarName = loopIndexVars.index(mappedLhsDimId)
|
|
313
|
+
return `__map${rhsSubId}${mappedLhsDimId}[${indexVarName}]`
|
|
270
314
|
}
|
|
271
|
-
|
|
272
|
-
// There is no mapping, so use the loop index for this dim family on the LHS
|
|
273
|
-
indexName = loopIndexVars.index(rhsSubId)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Dereference the array using the corresponding loop index variable
|
|
277
|
-
if (isTrivialDimension(rhsSubId)) {
|
|
278
|
-
// When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
|
|
279
|
-
return `${indexName}`
|
|
280
315
|
} else {
|
|
281
|
-
|
|
316
|
+
throw new Error(`Failed to find LHS dimension for RHS dimension ${rhsSubId} in lhs=${lhsVariable.refId}`)
|
|
282
317
|
}
|
|
283
318
|
}
|