Skip to content

Commit

Permalink
fix: change encoding of variable and lookup indices to allow for arbi…
Browse files Browse the repository at this point in the history
…trary number of subscripts (#507)

Fixes #506
  • Loading branch information
chrispcampbell authored Aug 17, 2024
1 parent 338e91e commit 697e943
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 263 deletions.
34 changes: 12 additions & 22 deletions packages/cli/src/c/model.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@
struct timespec startTime, finishTime;
#endif

// For each output variable specified in the indices buffer, there
// are 4 index values:
// varIndex
// subIndex0
// subIndex1
// subIndex2
#define INDICES_PER_OUTPUT 4

// The special _time variable is not included in .mdl files.
double _time;

Expand Down Expand Up @@ -149,22 +141,20 @@ void run() {
}
outputVarIndex = 0;
if (outputIndexBuffer != NULL) {
// Store the outputs as specified in the current output index buffer. This
// iterates over the output indices buffer until we reach the first zero index.
size_t i = 0;
while (true) {
size_t indexBufferOffset = i * INDICES_PER_OUTPUT;
size_t varIndex = (size_t)outputIndexBuffer[indexBufferOffset];
if (varIndex > 0) {
size_t subIndex0 = (size_t)outputIndexBuffer[indexBufferOffset + 1];
size_t subIndex1 = (size_t)outputIndexBuffer[indexBufferOffset + 2];
size_t subIndex2 = (size_t)outputIndexBuffer[indexBufferOffset + 3];
storeOutput(varIndex, subIndex0, subIndex1, subIndex2);
// Store the outputs as specified in the current output index buffer
size_t indexBufferOffset = 0;
size_t outputCount = (size_t)outputIndexBuffer[indexBufferOffset++];
for (size_t i = 0; i < outputCount; i++) {
size_t varIndex = (size_t)outputIndexBuffer[indexBufferOffset++];
size_t subCount = (size_t)outputIndexBuffer[indexBufferOffset++];
size_t* subIndices;
if (subCount > 0) {
subIndices = (size_t*)(outputIndexBuffer + indexBufferOffset);
} else {
// Stop when we reach the first zero index
break;
subIndices = NULL;
}
i++;
indexBufferOffset += subCount;
storeOutput(varIndex, subIndices);
}
} else {
// Store the normal outputs
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/c/sde.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ void initConstants(void);
void initLevels(void);
void setInputs(const char* inputData);
void setInputsFromBuffer(double *inputData);
void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints);
void evalAux(void);
void evalLevels(void);
void storeOutputData(void);
void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2);
void storeOutput(size_t varIndex, size_t* subIndices);
const char* getHeader(void);

#ifdef __cplusplus
Expand Down
12 changes: 3 additions & 9 deletions packages/compile/src/generate/gen-code-c.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void storeOutputData() {
${specOutputSection(outputVarIds)}
}
void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
void storeOutput(size_t varIndex, size_t* subIndices) {
${storeOutputBody}
}
`
Expand Down Expand Up @@ -396,14 +396,8 @@ ${section(chunk)}
})
const code = R.map(info => {
let varAccess = info.varName
if (info.subscriptCount > 0) {
varAccess += '[subIndex0]'
}
if (info.subscriptCount > 1) {
varAccess += '[subIndex1]'
}
if (info.subscriptCount > 2) {
varAccess += '[subIndex2]'
for (let i = 0; i < info.subscriptCount; i++) {
varAccess += `[subIndices[${i}]]`
}
return `\
case ${info.varIndex}:
Expand Down
14 changes: 7 additions & 7 deletions packages/compile/src/generate/gen-code-c.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ void storeOutputData() {
outputVar(_w);
}
void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
void storeOutput(size_t varIndex, size_t* subIndices) {
switch (varIndex) {
case 1:
outputVar(_final_time);
Expand All @@ -330,10 +330,10 @@ void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t sub
outputVar(_input);
break;
case 10:
outputVar(_a[subIndex0]);
outputVar(_a[subIndices[0]]);
break;
case 11:
outputVar(_b[subIndex0][subIndex1]);
outputVar(_b[subIndices[0]][subIndices[1]]);
break;
case 12:
outputVar(_c);
Expand All @@ -345,7 +345,7 @@ void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t sub
outputVar(_w);
break;
case 15:
outputVar(_d[subIndex0]);
outputVar(_d[subIndices[0]]);
break;
case 16:
outputVar(_y);
Expand Down Expand Up @@ -446,7 +446,7 @@ void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPo
outputVarNames: ['y']
})
expect(code).toMatch(`\
void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
void storeOutput(size_t varIndex, size_t* subIndices) {
fprintf(stderr, "The storeOutput function was not enabled for the generated model. Set the customOutputs property in the spec/config file to allow for capturing arbitrary variables at runtime.\\n");
}`)
})
Expand All @@ -469,10 +469,10 @@ void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t sub
customOutputs: ['u[A1]', 'x']
})
expect(code).toMatch(`\
void storeOutput(size_t varIndex, size_t subIndex0, size_t subIndex1, size_t subIndex2) {
void storeOutput(size_t varIndex, size_t* subIndices) {
switch (varIndex) {
case 5:
outputVar(_u[subIndex0]);
outputVar(_u[subIndices[0]]);
break;
case 6:
outputVar(_x);
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/src/_shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface VarSpec {
/** The variable index as used in the generated C/JS code. */
varIndex: number
/** The subscript index values as used in the generated C/JS code. */
subscriptIndices?: number[]
subscriptIndices?: number[] | Int32Array
}

/**
Expand Down
123 changes: 123 additions & 0 deletions packages/runtime/src/_shared/var-indices.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) 2024 Climate Interactive / New Venture Fund

import { describe, expect, it } from 'vitest'

import { createLookupDef, type LookupDef } from './lookup-def'
import type { VarSpec } from './types'
import {
decodeLookups,
encodeLookups,
encodeVarIndices,
getEncodedLookupBufferLengths,
getEncodedVarIndicesLength
} from './var-indices'

const varSpecs: VarSpec[] = [
{ varIndex: 1 },
{ varIndex: 2 },
{ varIndex: 3, subscriptIndices: [1, 2, 3, 4] },
{ varIndex: 4, subscriptIndices: [1, 2] }
]

describe('getEncodedVarIndicesLength', () => {
it('should return the correct length', () => {
expect(getEncodedVarIndicesLength(varSpecs)).toBe(15)
})
})

describe('encodeVarIndices', () => {
it('should encode the correct values', () => {
const array = new Int32Array(20)
encodeVarIndices(varSpecs, array)
expect(array).toEqual(
new Int32Array([
4, // variable count

1, // var0 index
0, // var0 subscript count

2, // var1 index
0, // var1 subscript count

3, // var2 index
4, // var2 subscript count
1, // var2 sub0 index
2, // var2 sub1 index
3, // var2 sub2 index
4, // var2 sub3 index

4, // var3 index
2, // var2 subscript count
1, // var3 sub0 index
2, // var3 sub1 index

// zero padding
0,
0,
0,
0,
0
])
)
})
})

const p = (x: number, y: number) => ({ x, y })
const lookupDefs: LookupDef[] = [
createLookupDef({ varSpec: { varIndex: 1 } }, [p(0, 0), p(1, 1)]),
createLookupDef({ varSpec: { varIndex: 2, subscriptIndices: [1, 2] } }, [p(0, 0), p(1, 1)])
]

describe('getEncodedLookupBufferLengths', () => {
it('should return the correct length', () => {
const { lookupIndicesLength, lookupsLength } = getEncodedLookupBufferLengths(lookupDefs)
expect(lookupIndicesLength).toBe(11)
expect(lookupsLength).toBe(8)
})
})

describe('encodeLookups and decodeLookups', () => {
it('should encode and decode the correct values', () => {
const lookupIndices = new Int32Array(13)
const lookupValues = new Float64Array(10)
encodeLookups(lookupDefs, lookupIndices, lookupValues)

expect(lookupIndices).toEqual(
new Int32Array([
2, // variable count

1, // var0 index
0, // var0 subscript count
0, // var0 data offset
4, // var0 data length

2, // var1 index
2, // var1 subscript count
1, // var1 sub0 index
2, // var1 sub1 index
4, // var1 data offset
4, // var1 data length

// zero padding
0,
0
])
)

expect(lookupValues).toEqual(
new Float64Array([
// var0 data
0, 0, 1, 1,

// var1 data
0, 0, 1, 1,

// zero padding
0, 0
])
)

const decodedLookupDefs = decodeLookups(lookupIndices, lookupValues)
expect(decodedLookupDefs).toEqual(lookupDefs)
})
})
Loading

0 comments on commit 697e943

Please sign in to comment.