Skip to content

Commit

Permalink
feat: implement the <-> subscript alias operator (#80)
Browse files Browse the repository at this point in the history
Fixes #78
  • Loading branch information
ToddFincannon authored Jul 23, 2021
1 parent 51691e7 commit a43917f
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 29 deletions.
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

0 comments on commit a43917f

Please sign in to comment.