Skip to content

Commit

Permalink
feat: implement GET DIRECT SUBSCRIPT for CSV (#79)
Browse files Browse the repository at this point in the history
Fixes #77 

Co-authored-by: Chris Campbell <[email protected]>
  • Loading branch information
ToddFincannon and chrispcampbell authored Jul 23, 2021
1 parent 4910ae3 commit 51691e7
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 27 deletions.
4 changes: 4 additions & 0 deletions models/directsubs/b_subs.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DimB
B1
B2
B3
2 changes: 2 additions & 0 deletions models/directsubs/c_subs.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DimC,,
C1,C2,C3
30 changes: 30 additions & 0 deletions models/directsubs/directsubs.dat
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
a[A1]
0 10
a[A2]
0 20
a[A3]
0 30
b[B1]
0 1
b[B2]
0 2
b[B3]
0 3
c[C1]
0 11
1 11
c[C2]
0 21
1 21
c[C3]
0 31
1 31
FINAL TIME
0 1
INITIAL TIME
0 0
SAVEPER
0 1
1 1
TIME STEP
0 1
61 changes: 61 additions & 0 deletions models/directsubs/directsubs.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{UTF-8}
DimA: A1, A2, A3 -> DimB, DimC ~~|
DimB:
GET DIRECT SUBSCRIPT(
'b_subs.csv',
',',
'A2',
'A',
''
)
~~|
DimC:
GET DIRECT SUBSCRIPT(
'c_subs.csv',
',',
'A2',
'2',
''
)
~~|
a[DimA] = 10, 20, 30
~~|
b[DimB] = 1, 2, 3
~~~:SUPPLEMENTARY|
c[DimC] = a[DimA] + 1
~~~:SUPPLEMENTARY|

********************************************************
.Control
********************************************************~
Simulation Control Parameters
|

FINAL TIME = 1 ~~|
INITIAL TIME = 0 ~~|
SAVEPER = TIME STEP ~~|
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
$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:Current
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
63 changes: 63 additions & 0 deletions models/directsubs/directsubs_subs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
_dima:
{
modelName: 'DimA',
modelValue: [ 'A1', 'A2', 'A3' ],
modelMappings: [ { toDim: 'DimB', value: [] }, { toDim: 'DimC', value: [] } ],
name: '_dima',
value: [ '_a1', '_a2', '_a3' ],
size: 3,
family: '_dima',
mappings: { _dimb: [ '_a1', '_a2', '_a3' ], _dimc: [ '_a1', '_a2', '_a3' ] }
}

_dimb:
{
modelName: 'DimB',
modelValue: [ 'B1', 'B2', 'B3' ],
modelMappings: [],
name: '_dimb',
value: [ '_b1', '_b2', '_b3' ],
size: 3,
family: '_dimb',
mappings: {}
}

_dimc:
{
modelName: 'DimC',
modelValue: [ 'C1', 'C2', 'C3' ],
modelMappings: [],
name: '_dimc',
value: [ '_c1', '_c2', '_c3' ],
size: 3,
family: '_dimc',
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: {} }

_b1:
{ name: '_b1', value: 0, size: 1, family: '_dimb', mappings: {} }

_b2:
{ name: '_b2', value: 1, size: 1, family: '_dimb', mappings: {} }

_b3:
{ name: '_b3', value: 2, size: 1, family: '_dimb', 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: {} }

82 changes: 82 additions & 0 deletions models/directsubs/directsubs_vars.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
a[DimA]: const (non-apply-to-all)
= 10,20,30
refId(_a[_a1])
families(_dima)
subscripts(_a1)
separationDims(_dima)
hasInitValue(false)

a[DimA]: const (non-apply-to-all)
= 10,20,30
refId(_a[_a2])
families(_dima)
subscripts(_a2)
separationDims(_dima)
hasInitValue(false)

a[DimA]: const (non-apply-to-all)
= 10,20,30
refId(_a[_a3])
families(_dima)
subscripts(_a3)
separationDims(_dima)
hasInitValue(false)

b[DimB]: const (non-apply-to-all)
= 1,2,3
refId(_b[_b1])
families(_dimb)
subscripts(_b1)
separationDims(_dimb)
hasInitValue(false)

b[DimB]: const (non-apply-to-all)
= 1,2,3
refId(_b[_b2])
families(_dimb)
subscripts(_b2)
separationDims(_dimb)
hasInitValue(false)

b[DimB]: const (non-apply-to-all)
= 1,2,3
refId(_b[_b3])
families(_dimb)
subscripts(_b3)
separationDims(_dimb)
hasInitValue(false)

c[DimC]: aux
= a[DimA]+1
refId(_c)
families(_dimc)
subscripts(_dimc)
hasInitValue(false)
refs(_a[_a1], _a[_a2], _a[_a3])

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#a1d8e0b",
"antlr4-vensim": "https://github.com/climateinteractive/antlr4-vensim#d008777",
"bufx": "^1.0.5",
"byline": "^5.0.0",
"chart.js": "^2.9.4",
Expand Down
4 changes: 2 additions & 2 deletions src/CodeGen.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { sub, allDimensions, allMappings, subscriptFamilies } from './Subscript.
import { asort, lines, strlist, abend, mapIndexed } from './Helpers.js'

export let codeGenerator = (parseTree, opts) => {
const { spec, operation, extData, directData } = opts
const { spec, operation, extData, directData, modelDirname } = opts
// Set to 'decl', 'init-lookups', 'eval', etc depending on the section being generated.
let mode = ''
// Set to true to output all variables when there is no model run spec.
Expand All @@ -25,7 +25,7 @@ export let codeGenerator = (parseTree, opts) => {
// Read variables and subscript ranges from the model parse tree.
// This is the main entry point for code generation and is called just once.
try {
Model.read(parseTree, spec, extData, directData)
Model.read(parseTree, spec, extData, directData, modelDirname)
// In list mode, print variables to the console instead of generating code.
if (operation === 'printRefIdTest') {
Model.printRefIdTest()
Expand Down
58 changes: 42 additions & 16 deletions src/Helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import sh from 'shelljs'
import split from 'split-string'
import byline from 'byline'
import XLSX from 'xlsx'
import parseCsv from 'csv-parse/lib/sync.js'
import B from 'bufx'

// Set true to print a stack trace in vlog
Expand Down Expand Up @@ -237,7 +238,7 @@ export let modelPathProps = model => {
return {
modelDirname: p.dir,
modelName: p.name,
modelPathname: path.format(p),
modelPathname: path.format(p)
}
}
export let execCmd = cmd => {
Expand Down Expand Up @@ -293,7 +294,9 @@ export let readDat = async (pathname, prefix = '') => {
if (Number.isNaN(t)) {
console.error(`DAT file ${pathname}:${lineNum} time value is NaN`)
} else if (Number.isNaN(value)) {
console.error(`DAT file ${pathname}:${lineNum} var "${varName}" value is NaN at time=${t}`)
console.error(
`DAT file ${pathname}:${lineNum} var "${varName}" value is NaN at time=${t}`
)
} else {
varValues.set(t, value)
}
Expand All @@ -310,6 +313,26 @@ export let readDat = async (pathname, prefix = '') => {
export let readXlsx = pathname => {
return XLSX.readFile(pathname, { cellDates: true })
}
export let readCsv = (pathname, delimiter = ',') => {
// Read the CSV file at the pathname and parse it with the given delimiter.
// Return an array of rows that are each an array of columns.
// If there is a header row, it is returned as the first row.
let result = null
const CSV_PARSE_OPTS = {
delimiter,
columns: false,
trim: true,
skip_empty_lines: true,
skip_lines_with_empty_values: true
}
try {
let data = B.read(pathname)
result = parseCsv(data, CSV_PARSE_OPTS)
} catch (error) {
console.error(`ERROR: CSV file ${pathname} not found`)
}
return result
}
// Convert the var name and subscript names to canonical form separately.
export let canonicalVensimName = vname => {
let result = vname
Expand All @@ -334,7 +357,9 @@ export let mapIndexed = R.addIndex(R.map)
// Function to sort an array of strings
export let asort = R.sort((a, b) => (a > b ? 1 : a < b ? -1 : 0))
// Function to alpha sort an array of variables on the model LHS
export let vsort = R.sort((a, b) => (a.modelLHS > b.modelLHS ? 1 : a.modelLHS < b.modelLHS ? -1 : 0))
export let vsort = R.sort((a, b) =>
a.modelLHS > b.modelLHS ? 1 : a.modelLHS < b.modelLHS ? -1 : 0
)
// Function to list an array to stderr
export let printArray = R.forEach(x => console.error(x))
// Function to expand an array of strings into a comma-delimited list of strings
Expand Down Expand Up @@ -407,13 +432,14 @@ export let replaceDelimitedStrings = (str, open, close, newStr) => {
* This can be used in place of nested for loops and has the benefit of working
* for multi-dimensional inputs.
*/
export const cartesianProductOf = arr => {
export const cartesianProductOf = arr => {
// Implementation based on: https://stackoverflow.com/a/36234242
return arr.reduce((a, b) => {
return a
.map(x => b.map(y => x.concat([y])))
.reduce((v, w) => v.concat(w), [])
}, [[]])
return arr.reduce(
(a, b) => {
return a.map(x => b.map(y => x.concat([y]))).reduce((v, w) => v.concat(w), [])
},
[[]]
)
}

/**
Expand All @@ -424,14 +450,14 @@ export let replaceDelimitedStrings = (str, open, close, newStr) => {
* this function will return all the permutations, e.g.:
* [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
*/
export const permutationsOf = (elems, subperms = [[]]) => {
export const permutationsOf = (elems, subperms = [[]]) => {
// Implementation based on: https://gist.github.com/CrossEye/f7c2f77f7db7a94af209
return R.isEmpty(elems) ?
subperms :
R.addIndex(R.chain)((elem, idx) => permutationsOf(
R.remove(idx, 1, elems),
R.map(R.append(elem), subperms)
), elems)
return R.isEmpty(elems)
? subperms
: R.addIndex(R.chain)(
(elem, idx) => permutationsOf(R.remove(idx, 1, elems), R.map(R.append(elem), subperms)),
elems
)
}

//
Expand Down
Loading

0 comments on commit 51691e7

Please sign in to comment.