@sdeverywhere/compile 0.7.10 → 0.7.12
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 +2 -1
- package/src/_shared/helpers.js +0 -4
- package/src/_shared/read-dat.js +11 -2
- package/src/generate/code-gen.js +51 -59
- package/src/generate/direct-data-helpers.js +86 -0
- package/src/generate/equation-gen.js +1 -1
- package/src/generate/expand-var-names.js +115 -0
- package/src/generate/gen-const-list.js +89 -0
- package/src/generate/gen-direct-const.js +91 -0
- package/src/generate/gen-equation.js +273 -0
- package/src/generate/gen-expr.js +851 -0
- package/src/generate/gen-lookup-from-direct.js +105 -0
- package/src/generate/gen-lookup-from-external.js +115 -0
- package/src/generate/gen-lookup-from-points.js +85 -0
- package/src/model/equation-reader.js +27 -22
- package/src/model/model.js +87 -36
- package/src/model/read-equation-fn-delay.js +312 -0
- package/src/model/read-equation-fn-npv.js +67 -0
- package/src/model/read-equation-fn-smooth.js +133 -0
- package/src/model/read-equation-fn-trend.js +44 -0
- package/src/model/read-equation-fn-with-lookup.js +37 -0
- package/src/model/read-equations.js +795 -0
- package/src/model/read-subscripts.js +77 -0
- package/src/model/read-variables.js +264 -0
- package/src/model/reduce-variables.js +166 -0
- package/src/model/toposort.js +1 -1
- package/src/model/variable.js +7 -2
- package/src/parse-and-generate.js +65 -12
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dimensionNames,
|
|
3
|
+
hasMapping,
|
|
4
|
+
isDimension,
|
|
5
|
+
isIndex,
|
|
6
|
+
isTrivialDimension,
|
|
7
|
+
normalizeSubscripts,
|
|
8
|
+
separatedVariableIndex,
|
|
9
|
+
sub
|
|
10
|
+
} from '../_shared/subscript.js'
|
|
11
|
+
import { generateConstListElement } from './gen-const-list.js'
|
|
12
|
+
|
|
13
|
+
import { generateDirectConstInit } from './gen-direct-const.js'
|
|
14
|
+
import { generateExpr } from './gen-expr.js'
|
|
15
|
+
import { generateLookupsFromDirectData } from './gen-lookup-from-direct.js'
|
|
16
|
+
import { generateLookupsFromExternalData } from './gen-lookup-from-external.js'
|
|
17
|
+
import { generateLookupFromPoints } from './gen-lookup-from-points.js'
|
|
18
|
+
|
|
19
|
+
import LoopIndexVars from './loop-index-vars.js'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Generate C code for the given model equation.
|
|
23
|
+
*
|
|
24
|
+
* TODO: Variable type
|
|
25
|
+
* TODO: Define type for mode values
|
|
26
|
+
*
|
|
27
|
+
* @param {*} variable The `Variable` instance to process.
|
|
28
|
+
* @param {'decl' | 'init-constants' | 'init-lookups' | 'init-levels' | 'eval'} mode The code generation mode.
|
|
29
|
+
* @param {Map<string, any>} extData The map of datasets from external `.dat` files.
|
|
30
|
+
* @param {Map<string, any>} directData The mapping of dataset name used in a `GET DIRECT DATA` call (e.g.,
|
|
31
|
+
* `?data`) to the tabular data contained in the loaded data file.
|
|
32
|
+
* @param {string} modelDir The path to the directory containing the model (used for resolving data files).
|
|
33
|
+
* @return {string[]} An array of strings containing the generated C code for the variable,
|
|
34
|
+
* one string per line of code.
|
|
35
|
+
*/
|
|
36
|
+
export function generateEquation(variable, mode, extData, directData, modelDir) {
|
|
37
|
+
// Maps of LHS subscript families to loop index vars for lookup on the RHS
|
|
38
|
+
const loopIndexVars = new LoopIndexVars(['i', 'j', 'k', 'l', 'm'])
|
|
39
|
+
const arrayIndexVars = new LoopIndexVars(['u', 'v', 'w', 's', 't', 'f', 'g', 'h', 'o', 'p', 'q', 'r'])
|
|
40
|
+
|
|
41
|
+
// Generate the LHS variable reference code
|
|
42
|
+
const parsedEqn = variable.parsedEqn
|
|
43
|
+
const cLhs = cVarRefWithLhsSubscripts(variable, parsedEqn.lhs.varDef.varId, loopIndexVars)
|
|
44
|
+
|
|
45
|
+
// Include the original model equation in a comment that comes before the generated code
|
|
46
|
+
// for that equation
|
|
47
|
+
const modelFormula = variable.origModelFormula || variable.modelFormula
|
|
48
|
+
const comment = ` // ${variable.modelLHS} = ${modelFormula.replace(/\n/g, '')}`
|
|
49
|
+
|
|
50
|
+
// Apply special handling for const lists
|
|
51
|
+
if (parsedEqn.rhs.kind === 'const-list') {
|
|
52
|
+
if (mode !== 'init-constants' && mode !== 'eval') {
|
|
53
|
+
throw new Error(`Invalid code gen mode '${mode}' for const list variable ${variable.modelLHS}`)
|
|
54
|
+
}
|
|
55
|
+
// XXX: The legacy code gen emitted a comment before each init statement, so we will
|
|
56
|
+
// do the same for now to maintain compatibility
|
|
57
|
+
return [comment, generateConstListElement(variable, parsedEqn)]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Emit direct constants individually without separating them first
|
|
61
|
+
if (variable.directConstArgs) {
|
|
62
|
+
const initCode = generateDirectConstInit(variable, directData, modelDir)
|
|
63
|
+
return [comment, ...initCode]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get the dimension IDs for the LHS variable
|
|
67
|
+
const dimIds = dimensionNames(variable.subscripts)
|
|
68
|
+
|
|
69
|
+
// Turn each dimension ID into a loop with a loop index variable.
|
|
70
|
+
// If the variable has no subscripts, nothing will be emitted here.
|
|
71
|
+
const openLoops = []
|
|
72
|
+
const closeLoops = []
|
|
73
|
+
for (const dimId of dimIds) {
|
|
74
|
+
const indexName = loopIndexVars.index(dimId)
|
|
75
|
+
const dimLength = sub(dimId).size
|
|
76
|
+
openLoops.push(` for (size_t ${indexName} = 0; ${indexName} < ${dimLength}; ${indexName}++) {`)
|
|
77
|
+
closeLoops.push(' }')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Apply special handling for data variables. The data can be defined in one of three ways:
|
|
81
|
+
// - as a set of explicit data points (stored in the `Variable` instance), or
|
|
82
|
+
// - from an external file via a `GET DIRECT DATA` call, or
|
|
83
|
+
// - from an external data file (i.e., a "normal" data variable)
|
|
84
|
+
if (variable.isData()) {
|
|
85
|
+
if (variable.points.length > 0) {
|
|
86
|
+
// The variable already has data points defined, so generate a new lookup using that data.
|
|
87
|
+
// Note that unlike the other lookup cases, this one needs to include loop open/close code
|
|
88
|
+
// if the variable is subscripted.
|
|
89
|
+
const lookupDef = generateLookupFromPoints(variable, mode, /*copy=*/ true, cLhs, loopIndexVars)
|
|
90
|
+
if (lookupDef.length > 0) {
|
|
91
|
+
return [comment, ...openLoops, ...lookupDef, ...closeLoops]
|
|
92
|
+
} else {
|
|
93
|
+
return []
|
|
94
|
+
}
|
|
95
|
+
} else if (variable.directDataArgs) {
|
|
96
|
+
// The data is referenced using a `GET DIRECT DATA` call; generate one or more lookups
|
|
97
|
+
// using the data defined in external files
|
|
98
|
+
return generateLookupsFromDirectData(variable, mode, directData, modelDir, cLhs)
|
|
99
|
+
} else {
|
|
100
|
+
// This is a "normal" data variable; generate one or more lookups using the data defined
|
|
101
|
+
// in external files
|
|
102
|
+
return generateLookupsFromExternalData(variable, mode, extData, cLhs)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Apply special handling for lookup variables. The data for lookup variables is already
|
|
107
|
+
// defined as a set of explicit data points (stored in the `Variable` instance).
|
|
108
|
+
if (variable.isLookup()) {
|
|
109
|
+
return generateLookupFromPoints(variable, mode, /*copy=*/ false, cLhs, loopIndexVars)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Keep a buffer of code that will be included before the innermost loop
|
|
113
|
+
const preInnerLoopLines = []
|
|
114
|
+
|
|
115
|
+
// Keep a buffer of code that will be included before the generated primary formula
|
|
116
|
+
const preFormulaLines = []
|
|
117
|
+
|
|
118
|
+
// Keep a buffer of code that will be included after the generated primary formula
|
|
119
|
+
const postFormulaLines = []
|
|
120
|
+
|
|
121
|
+
// Keep track of marked dimensions
|
|
122
|
+
const markedDimIds = new Set()
|
|
123
|
+
|
|
124
|
+
// Generate code for an equation with an expression on the RHS
|
|
125
|
+
const genExprCtx = {
|
|
126
|
+
variable,
|
|
127
|
+
mode,
|
|
128
|
+
cLhs,
|
|
129
|
+
loopIndexVars,
|
|
130
|
+
arrayIndexVars,
|
|
131
|
+
resetMarkedDims: () => markedDimIds.clear(),
|
|
132
|
+
addMarkedDim: dimId => markedDimIds.add(dimId),
|
|
133
|
+
emitPreInnerLoop: s => preInnerLoopLines.push(s),
|
|
134
|
+
emitPreFormula: s => preFormulaLines.push(s),
|
|
135
|
+
emitPostFormula: s => postFormulaLines.push(s),
|
|
136
|
+
cVarRef: varRef => cVarRef(variable, varRef, markedDimIds, loopIndexVars, arrayIndexVars),
|
|
137
|
+
cVarRefWithLhsSubscripts: baseVarId => cVarRefWithLhsSubscripts(variable, baseVarId, loopIndexVars),
|
|
138
|
+
cVarIndex: subOrDimId => cVarIndex(variable, [subOrDimId], subOrDimId, markedDimIds, loopIndexVars, arrayIndexVars)
|
|
139
|
+
}
|
|
140
|
+
const cRhs = generateExpr(parsedEqn.rhs.expr, genExprCtx)
|
|
141
|
+
const formula = ` ${cLhs} = ${cRhs};`
|
|
142
|
+
|
|
143
|
+
// Insert the pre-inner loop code, if needed
|
|
144
|
+
if (preInnerLoopLines.length > 0) {
|
|
145
|
+
openLoops.splice(variable.subscripts.length - 1, 0, ...preInnerLoopLines)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Combine all lines of comments and code into a single array
|
|
149
|
+
return [comment, ...openLoops, ...preFormulaLines, formula, ...postFormulaLines, ...closeLoops]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Return the C code for a subscripted reference to the given variable using the LHS subscripts
|
|
154
|
+
* of the given equation.
|
|
155
|
+
*
|
|
156
|
+
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
157
|
+
* @param {*} baseVarId The base variable ID to which the subscript parts will be appended.
|
|
158
|
+
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
159
|
+
* @return {string} The C variable reference.
|
|
160
|
+
*/
|
|
161
|
+
function cVarRefWithLhsSubscripts(lhsVariable, baseVarId, loopIndexVars) {
|
|
162
|
+
const lhsSubIds = lhsVariable.subscripts
|
|
163
|
+
const cSubParts = lhsSubIds.map(subId => {
|
|
164
|
+
if (isDimension(subId)) {
|
|
165
|
+
const i = loopIndexVars.index(subId)
|
|
166
|
+
if (isTrivialDimension(subId)) {
|
|
167
|
+
// When the dimension is trivial, we can simply emit e.g. `[i]` instead of `[_dim[i]]`
|
|
168
|
+
return `[${i}]`
|
|
169
|
+
} else {
|
|
170
|
+
return `[${subId}][${i}]`
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
return `[${sub(subId).value}]`
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
return `${baseVarId}${cSubParts.join('')}`
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Return the C code for a RHS (possibly subscripted) variable reference.
|
|
181
|
+
*
|
|
182
|
+
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
183
|
+
* @param {*} rhsVarRef The `VariableRef` used in a RHS expression.
|
|
184
|
+
* @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
|
|
185
|
+
* in an array function, for example `SUM(x[DimA!])`.
|
|
186
|
+
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
187
|
+
* @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
|
|
188
|
+
* (that use marked dimensions).
|
|
189
|
+
* @returns {string} The C variable reference.
|
|
190
|
+
*/
|
|
191
|
+
function cVarRef(lhsVariable, rhsVarRef, markedDimIds, loopIndexVars, arrayIndexVars) {
|
|
192
|
+
if (rhsVarRef.subscriptRefs === undefined) {
|
|
193
|
+
// No subscripts, so return the base variable ID
|
|
194
|
+
return rhsVarRef.varId
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Normalize the RHS subscripts
|
|
198
|
+
let rhsSubIds
|
|
199
|
+
try {
|
|
200
|
+
// XXX: For now, strip the mark here (need to revisit this)
|
|
201
|
+
rhsSubIds = normalizeSubscripts(rhsVarRef.subscriptRefs.map(subRef => subRef.subId.replace('!', '')))
|
|
202
|
+
} catch (e) {
|
|
203
|
+
throw new Error(`normalizeSubscripts failed in rhsVarRef: refId=${lhsVariable.refId} error=${e}`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Determine the subscript code (array lookup) for each dimension. For example, if
|
|
207
|
+
// the RHS variable reference in the model looks like `x[DimA]`, this will convert the
|
|
208
|
+
// `[DimA]` part to `[_dima[i]]` (or simply `[i]` if it is a "trivial" dimension).
|
|
209
|
+
const cSubParts = rhsSubIds.map(rhsSubId => {
|
|
210
|
+
return cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return `${rhsVarRef.varId}${cSubParts.map(part => `[${part}]`).join('')}`
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Return the C code for indexing into a subscripted variable.
|
|
218
|
+
*
|
|
219
|
+
* @param {*} lhsVariable The LHS `Variable` instance.
|
|
220
|
+
* @param {string[]} rhsSubIds The set of all subscript or dimension IDs used on the RHS.
|
|
221
|
+
* @param {string} rhsSubId The specific subscript or dimension ID being evaluated.
|
|
222
|
+
* @param {Set<string>} markedDimIds The set of dimension IDs that are marked for use
|
|
223
|
+
* in an array function, for example `SUM(x[DimA!])`.
|
|
224
|
+
* @param {LoopIndexVars} loopIndexVars The loop index state.
|
|
225
|
+
* @param {LoopIndexVars} arrayIndexVars The loop index state used for array functions
|
|
226
|
+
* (that use marked dimensions).
|
|
227
|
+
* @returns {string} The C variable reference.
|
|
228
|
+
*/
|
|
229
|
+
function cVarIndex(lhsVariable, rhsSubIds, rhsSubId, markedDimIds, loopIndexVars, arrayIndexVars) {
|
|
230
|
+
if (isIndex(rhsSubId)) {
|
|
231
|
+
// This is a specific subscript (i.e., an index); dereference the array using the index
|
|
232
|
+
// number of the subscript
|
|
233
|
+
return `${sub(rhsSubId).value}`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Otherwise, this is a dimension. Get the corresponding loop index variable used
|
|
237
|
+
// in the "for" loop.
|
|
238
|
+
let indexName
|
|
239
|
+
if (markedDimIds.has(rhsSubId)) {
|
|
240
|
+
// This is a marked dimension as used in an array function (e.g., `SUM`), so use
|
|
241
|
+
// the name of the array loop index variable
|
|
242
|
+
indexName = arrayIndexVars.index(rhsSubId)
|
|
243
|
+
} else {
|
|
244
|
+
// Use the single index name for a separated variable if it exists
|
|
245
|
+
const separatedIndexName = separatedVariableIndex(rhsSubId, lhsVariable, rhsSubIds)
|
|
246
|
+
if (separatedIndexName) {
|
|
247
|
+
return `${sub(separatedIndexName).value}`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// See if we need to apply a mapping because the RHS dim is not found on the LHS
|
|
251
|
+
const found = lhsVariable.subscripts.findIndex(lhsSubId => sub(lhsSubId).family === sub(rhsSubId).family)
|
|
252
|
+
if (found < 0) {
|
|
253
|
+
// Find the mapping from the RHS subscript to a LHS subscript
|
|
254
|
+
for (const lhsSubId of lhsVariable.subscripts) {
|
|
255
|
+
if (hasMapping(rhsSubId, lhsSubId)) {
|
|
256
|
+
indexName = loopIndexVars.index(lhsSubId)
|
|
257
|
+
return `__map${rhsSubId}${lhsSubId}[${indexName}]`
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// There is no mapping, so use the loop index for this dim family on the LHS
|
|
263
|
+
indexName = loopIndexVars.index(rhsSubId)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Dereference the array using the corresponding loop index variable
|
|
267
|
+
if (isTrivialDimension(rhsSubId)) {
|
|
268
|
+
// When the dimension is trivial, we can emit e.g. `[i]` instead of `[_dim[i]]`
|
|
269
|
+
return `${indexName}`
|
|
270
|
+
} else {
|
|
271
|
+
return `${rhsSubId}[${indexName}]`
|
|
272
|
+
}
|
|
273
|
+
}
|