Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement the <-> subscript alias operator #80

Merged
merged 9 commits into from
Jul 23, 2021
21 changes: 21 additions & 0 deletions models/subalias/subalias.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
e[F1]
0 10
e[F2]
0 20
e[F3]
0 30
f[F1]
0 1
f[F2]
0 2
f[F3]
0 3
FINAL TIME
0 1
INITIAL TIME
0 0
SAVEPER
0 1
1 1
TIME STEP
0 1
11 changes: 11 additions & 0 deletions models/subalias/subalias.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{UTF-8}
DimE <-> DimF ~~|
DimF: F1, F2, F3 ~~|

e[DimE] = 10, 20, 30 ~~~:SUPPLEMENTARY|
f[DimF] = 1, 2, 3 ~~~:SUPPLEMENTARY|

INITIAL TIME = 0 ~~|
FINAL TIME = 1 ~~|
TIME STEP = 1 ~~|
SAVEPER = TIME STEP ~~|
33 changes: 33 additions & 0 deletions models/subalias/subalias_subs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
_dime:
{
modelName: 'DimE',
modelValue: [ 'F1', 'F2', 'F3' ],
modelMappings: [],
name: '_dime',
value: [ '_f1', '_f2', '_f3' ],
size: 3,
family: '_dime',
mappings: {}
}

_dimf:
{
modelName: 'DimF',
modelValue: [ 'F1', 'F2', 'F3' ],
modelMappings: [],
name: '_dimf',
value: [ '_f1', '_f2', '_f3' ],
size: 3,
family: '_dime',
mappings: {}
}

_f1:
{ name: '_f1', value: 0, size: 1, family: '_dime', mappings: {} }

_f2:
{ name: '_f2', value: 1, size: 1, family: '_dime', mappings: {} }

_f3:
{ name: '_f3', value: 2, size: 1, family: '_dime', mappings: {} }

74 changes: 74 additions & 0 deletions models/subalias/subalias_vars.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
e[DimE]: const (non-apply-to-all)
= 10,20,30
refId(_e[_f1])
families(_dime)
subscripts(_f1)
separationDims(_dime)
hasInitValue(false)

e[DimE]: const (non-apply-to-all)
= 10,20,30
refId(_e[_f2])
families(_dime)
subscripts(_f2)
separationDims(_dime)
hasInitValue(false)

e[DimE]: const (non-apply-to-all)
= 10,20,30
refId(_e[_f3])
families(_dime)
subscripts(_f3)
separationDims(_dime)
hasInitValue(false)

f[DimF]: const (non-apply-to-all)
= 1,2,3
refId(_f[_f1])
families(_dime)
subscripts(_f1)
separationDims(_dimf)
hasInitValue(false)

f[DimF]: const (non-apply-to-all)
= 1,2,3
refId(_f[_f2])
families(_dime)
subscripts(_f2)
separationDims(_dimf)
hasInitValue(false)

f[DimF]: const (non-apply-to-all)
= 1,2,3
refId(_f[_f3])
families(_dime)
subscripts(_f3)
separationDims(_dimf)
hasInitValue(false)

FINAL TIME: const
= 1
refId(_final_time)
hasInitValue(false)

INITIAL TIME: const
= 0
refId(_initial_time)
hasInitValue(false)

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)

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
},
"dependencies": {
"antlr4": "4.9.2",
"antlr4-vensim": "https://github.com/climateinteractive/antlr4-vensim#d008777",
"antlr4-vensim": "https://github.com/climateinteractive/antlr4-vensim#5baa4cf",
"bufx": "^1.0.5",
"byline": "^5.0.0",
"chart.js": "^2.9.4",
Expand Down
50 changes: 36 additions & 14 deletions src/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import EquationReader from './EquationReader.js'
import Variable from './Variable.js'
import {
addIndex,
allAliases,
allDimensions,
indexNamesForSubscript,
isDimension,
Expand Down Expand Up @@ -54,22 +55,34 @@ function readSubscriptRanges(tree, dimensionFamilies, indexFamilies, modelDirnam
let subscriptRangeReader = new SubscriptRangeReader(modelDirname)
subscriptRangeReader.visitModel(tree)
let allDims = allDimensions()

// Expand dimensions that appeared in subscript range definitions into indices.
// Repeat until there are only indices in dimension values.
let dimFoundInValue
do {
dimFoundInValue = false
for (let dim of allDims) {
let value = R.flatten(R.map(subscript => (isDimension(subscript) ? sub(subscript).value : subscript), dim.value))
if (!R.equals(value, dim.value)) {
dimFoundInValue = true
dim.value = value
dim.size = value.length
if (dim.value !== '') {
let value = R.flatten(R.map(subscript => (isDimension(subscript) ? sub(subscript).value : subscript), dim.value))
if (!R.equals(value, dim.value)) {
dimFoundInValue = true
dim.value = value
dim.size = value.length
}
}
}
} while (dimFoundInValue)

// Fill in subscript aliases from their model families.
for (let dim of allAliases()) {
if (dim.value === '') {
let refDim = sub(dim.family)
dim.value = refDim.value
dim.size = refDim.size
dim.modelValue = refDim.modelValue
allDims.push(dim)
}
}

// Update the families of dimensions. At this point, all dimensions have their family
// provisionally set to their own dimension name.
let dimComparator = (dim1, dim2) => {
Expand Down Expand Up @@ -97,7 +110,10 @@ function readSubscriptRanges(tree, dimensionFamilies, indexFamilies, modelDirnam
// first in alpha sort order, by convention.
// Take the first index in the dimension.
let index = dim.value[0]
let familyDims = R.sort(dimComparator, R.filter(thisDim => R.contains(index, thisDim.value), allDims))
let familyDims = R.sort(
dimComparator,
R.filter(thisDim => R.contains(index, thisDim.value), allDims)
)
if (familyDims.length > 0) {
dim.family = R.last(familyDims).name
} else {
Expand Down Expand Up @@ -140,9 +156,7 @@ function readSubscriptRanges(tree, dimensionFamilies, indexFamilies, modelDirnam
invertedMappingValue[toIndNumber] = fromIndName
} else {
console.error(
`ERROR: map-to index "${toSubName}" not found when mapping from dimension "${
fromDim.name
}" index "${fromIndName}"`
`ERROR: map-to index "${toSubName}" not found when mapping from dimension "${fromDim.name}" index "${fromIndName}"`
)
}
}
Expand Down Expand Up @@ -467,7 +481,6 @@ function initVars() {
return sortInitVars()
}
function varWithRefId(refId) {

const findVarWithRefId = rid => {
// First see if we have a map key where ref id matches the var name
let varsForName = variablesByName.get(rid)
Expand Down Expand Up @@ -687,7 +700,10 @@ function sortVarsOfType(varType) {
}

// Turn the dependency-sorted var name list into a var list.
let sortedVars = varsOfType(varType, R.map(refId => varWithRefId(refId), deps))
let sortedVars = varsOfType(
varType,
R.map(refId => varWithRefId(refId), deps)
)

// Add the ref ids to a set for faster lookup in the next step
const sortedVarRefIds = new Set()
Expand Down Expand Up @@ -788,7 +804,10 @@ function sortInitVars() {
let sortedVars = R.map(refId => varWithRefId(refId), deps)

// Filter out vars with constant values.
sortedVars = R.reject(R.propSatisfies(varType => varType === 'const' || varType === 'lookup', 'varType'), sortedVars)
sortedVars = R.reject(
R.propSatisfies(varType => varType === 'const' || varType === 'lookup', 'varType'),
sortedVars
)

// Add the ref ids to a set for faster lookup in the next step
const sortedVarRefIds = new Set()
Expand Down Expand Up @@ -834,7 +853,10 @@ function printVarList() {
}
function yamlVarList() {
// Print selected properties of all variable objects to a YAML string.
let vars = R.sortBy(R.prop('refId'), R.map(v => filterVar(v), variables))
let vars = R.sortBy(
R.prop('refId'),
R.map(v => filterVar(v), variables)
)
return yaml.safeDump(vars)
}
function printVar(v) {
Expand Down
13 changes: 12 additions & 1 deletion src/Subscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import { canonicalName, asort, vlog } from './Helpers.js'
// sets a subscript dimension and its indices
// Subscript(modelName, modelValue, modelFamily) with modelValue as number
// sets a subscript element and its index value in the subscript family
// Subscript(modelName, '', modelFamily, [])
// sets modelName as an alias for the dimension named as modelFamily
//
// Call Subscript with an array of subscript indices to establish a dimension.
// If there is a mapping to another dimension, give modelMappings in the call.
Expand All @@ -53,14 +55,19 @@ export function Subscript(modelName, modelValue = null, modelFamily = null, mode
// Look up a subscript by its model name.
return sub(name)
}
// Map the subscript value array into canonical form.
let value, size
if (Array.isArray(modelValue)) {
// Map the subscript value array into canonical form.
value = R.map(x => canonicalName(x), modelValue)
size = value.length
} else if (typeof modelValue === 'number') {
// The value is an index.
value = modelValue
size = 1
} else if (modelValue === '') {
// An empty value string indicates a subscript alias given by the modelFamily.
value = ''
size = 0
}
// Convert the model family into canonical form.
if (modelFamily === null) {
Expand Down Expand Up @@ -234,6 +241,10 @@ export function allDimensions() {
// Return an array of all dimension subscript objects.
return R.filter(subscript => Array.isArray(subscript.value), allSubscripts())
}
export function allAliases() {
// Return an array of all subscript aliases.
return R.filter(subscript => subscript.value === '', allSubscripts())
}
export function allMappings() {
// Return an array of all subscript mappings as objects.
let mappings = []
Expand Down
29 changes: 18 additions & 11 deletions src/SubscriptRangeReader.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path';
import path from 'path'
import { ModelParser } from 'antlr4-vensim'
import R from 'ramda'
import XLSX from 'xlsx'
Expand Down Expand Up @@ -28,16 +28,23 @@ export default class SubscriptRangeReader extends ModelReader {
// When entering a new subscript range definition, reset the properties that will be filled in.
this.indNames = []
this.modelMappings = []
// All subscript ranges have a dimension name.
let modelName = ctx.Id().getText()
// Visit children to fill in the subscript range definition.
super.visitSubscriptRange(ctx)
// Create a new subscript range definition from Vensim-format names.
// The family is provisionally set to the dimension name.
// It will be updated to the maximal dimension if this is a subdimension.
// The mapping value contains dimensions and indices in the toDim.
// It will be expanded and inverted to fromDim indices later.
Subscript(modelName, this.indNames, modelName, this.modelMappings)
// A subscript alias has two Ids, while a regular subscript range definition has just one.
if (ctx.Id().length === 1) {
// Subscript range definitions have a dimension name.
let modelName = ctx.Id()[0].getText()
// Visit children to fill in the subscript range definition.
super.visitSubscriptRange(ctx)
// Create a new subscript range definition from Vensim-format names.
// The family is provisionally set to the dimension name.
// It will be updated to the maximal dimension if this is a subdimension.
// The mapping value contains dimensions and indices in the toDim.
// It will be expanded and inverted to fromDim indices later.
Subscript(modelName, this.indNames, modelName, this.modelMappings)
} else {
let modelName = ctx.Id()[0].getText()
let modelFamily = ctx.Id()[1].getText()
Subscript(modelName, '', modelFamily, [])
}
}
visitSubscriptList(ctx) {
// A subscript list can appear in either a subscript range or mapping.
Expand Down