Skip to content

Commit f52621b

Browse files
committed
feat(core,cosim): string variable support in fmu codegen, typed cosim coupling
1 parent acd0f22 commit f52621b

5 files changed

Lines changed: 93 additions & 23 deletions

File tree

packages/core/src/compiler/modelica/fmu-codegen.ts

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,16 @@ export function generateFmuCSources(dae: ModelicaDAE, fmuResult: FmuResult, opti
4949
const vars = fmuResult.scalarVariables;
5050
const nStates = fmuResult.modelStructure.derivatives.length;
5151
const nVars = vars.length;
52+
const nStringVars = vars.filter((v) => v.type === "String").length;
5253

5354
// ── model.h ──
54-
const modelH = generateModelH(id, nVars, nStates, fmuResult);
55+
const modelH = generateModelH(id, nVars, nStates, nStringVars, fmuResult);
5556

5657
// ── model.c ──
5758
const modelC = generateModelC(id, dae, fmuResult);
5859

5960
// ── fmi2Functions.c ──
60-
const fmi2FunctionsC = generateFmi2FunctionsC(id, nVars, nStates, fmuResult);
61+
const fmi2FunctionsC = generateFmi2FunctionsC(id, nVars, nStates, nStringVars, fmuResult);
6162

6263
// ── CMakeLists.txt ──
6364
const cmakeLists = generateCMakeLists(id);
@@ -237,7 +238,7 @@ function escapeCString(s: string): string {
237238

238239
// ── File generators ──
239240

240-
function generateModelH(id: string, nVars: number, nStates: number, result: FmuResult): string {
241+
function generateModelH(id: string, nVars: number, nStates: number, nStringVars: number, result: FmuResult): string {
241242
const lines: string[] = [];
242243
lines.push("/* Auto-generated by ModelScript — do not edit */");
243244
lines.push(`#ifndef ${id.toUpperCase()}_MODEL_H`);
@@ -250,6 +251,7 @@ function generateModelH(id: string, nVars: number, nStates: number, result: FmuR
250251
lines.push(`#define MODEL_GUID "${result.guid}"`);
251252
lines.push(`#define N_VARS ${nVars}`);
252253
lines.push(`#define N_STATES ${nStates}`);
254+
lines.push(`#define N_STRING_VARS ${nStringVars}`);
253255
lines.push(`#define N_EVENT_INDICATORS ${result.numberOfEventIndicators}`);
254256
lines.push("");
255257

@@ -265,6 +267,7 @@ function generateModelH(id: string, nVars: number, nStates: number, result: FmuR
265267
lines.push(" double vars[N_VARS + 1]; /* +1 for safety */");
266268
lines.push(" double states[N_STATES + 1];");
267269
lines.push(" double derivatives[N_STATES + 1];");
270+
lines.push(" char* stringVars[N_STRING_VARS + 1]; /* string variable storage */");
268271
lines.push(" double time;");
269272
lines.push(" int isDirtyValues;");
270273
lines.push(`} ${id}_Instance;`);
@@ -376,7 +379,13 @@ function generateModelC(id: string, dae: ModelicaDAE, result: FmuResult): string
376379
return lines.join("\n");
377380
}
378381

379-
function generateFmi2FunctionsC(id: string, nVars: number, nStates: number, result: FmuResult): string {
382+
function generateFmi2FunctionsC(
383+
id: string,
384+
nVars: number,
385+
nStates: number,
386+
nStringVars: number,
387+
result: FmuResult,
388+
): string {
380389
const lines: string[] = [];
381390
lines.push("/* Auto-generated by ModelScript — FMI 2.0 API implementation */");
382391
lines.push(`#include "${id}_model.h"`);
@@ -623,18 +632,74 @@ function generateFmi2FunctionsC(id: string, nVars: number, nStates: number, resu
623632

624633
// ── Terminate / FreeInstance ──
625634
lines.push("fmi2Status fmi2Terminate(fmi2Component c) { (void)c; return fmi2OK; }");
626-
lines.push("void fmi2FreeInstance(fmi2Component c) { free(c); }");
635+
lines.push("");
636+
lines.push("void fmi2FreeInstance(fmi2Component c) {");
637+
lines.push(" if (!c) return;");
638+
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
639+
lines.push(" /* Free all allocated string variables */");
640+
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
641+
lines.push(" if (inst->model.stringVars[i]) free(inst->model.stringVars[i]);");
642+
lines.push(" }");
643+
lines.push(" free(inst);");
644+
lines.push("}");
627645
lines.push("");
628646

629647
// ── Stubs for remaining FMI 2.0 functions ──
630648
lines.push("/* --- Stubs --- */");
631649
lines.push("fmi2Status fmi2Reset(fmi2Component c) { (void)c; return fmi2OK; }");
632-
lines.push(
633-
"fmi2Status fmi2SetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2String value[]) { (void)c; (void)vr; (void)nvr; (void)value; return fmi2OK; }",
634-
);
635-
lines.push(
636-
"fmi2Status fmi2GetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2String value[]) { (void)c; (void)vr; (void)nvr; (void)value; return fmi2OK; }",
637-
);
650+
651+
// ── fmi2SetString / fmi2GetString ──
652+
// Build a value-reference → string-index mapping for string variables
653+
const stringVarIndices = result.scalarVariables
654+
.filter((sv) => sv.type === "String")
655+
.map((sv, idx) => ({ vr: sv.valueReference, idx }));
656+
657+
if (nStringVars > 0) {
658+
// Emit a lookup table: VR → string index (-1 if not a string var)
659+
lines.push("");
660+
lines.push("/* VR-to-string-index mapping */");
661+
lines.push(`static int stringVarIndex(fmi2ValueReference vr) {`);
662+
lines.push(" switch (vr) {");
663+
for (const { vr, idx } of stringVarIndices) {
664+
lines.push(` case ${vr}: return ${idx};`);
665+
}
666+
lines.push(" default: return -1;");
667+
lines.push(" }");
668+
lines.push("}");
669+
lines.push("");
670+
671+
lines.push(
672+
"fmi2Status fmi2SetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2String value[]) {",
673+
);
674+
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
675+
lines.push(" for (size_t i = 0; i < nvr; i++) {");
676+
lines.push(" int idx = stringVarIndex(vr[i]);");
677+
lines.push(" if (idx >= 0) {");
678+
lines.push(" if (inst->model.stringVars[idx]) free(inst->model.stringVars[idx]);");
679+
lines.push(' inst->model.stringVars[idx] = value[i] ? strdup(value[i]) : strdup("");');
680+
lines.push(" }");
681+
lines.push(" }");
682+
lines.push(" return fmi2OK;");
683+
lines.push("}");
684+
lines.push("");
685+
lines.push(
686+
"fmi2Status fmi2GetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2String value[]) {",
687+
);
688+
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
689+
lines.push(" for (size_t i = 0; i < nvr; i++) {");
690+
lines.push(" int idx = stringVarIndex(vr[i]);");
691+
lines.push(' value[i] = (idx >= 0 && inst->model.stringVars[idx]) ? inst->model.stringVars[idx] : "";');
692+
lines.push(" }");
693+
lines.push(" return fmi2OK;");
694+
lines.push("}");
695+
} else {
696+
lines.push(
697+
"fmi2Status fmi2SetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2String value[]) { (void)c; (void)vr; (void)nvr; (void)value; return fmi2OK; }",
698+
);
699+
lines.push(
700+
"fmi2Status fmi2GetString(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, fmi2String value[]) { (void)c; (void)vr; (void)nvr; (void)value; return fmi2OK; }",
701+
);
702+
}
638703
lines.push(
639704
"fmi2Status fmi2GetNominalsOfContinuousStates(fmi2Component c, fmi2Real nominals[], size_t nx) { for (size_t i = 0; i < nx; i++) nominals[i] = 1.0; (void)c; return fmi2OK; }",
640705
);

packages/cosim/src/coupling.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
*
66
* Defines which output variables from one participant feed into
77
* which input variables of another participant.
8+
/**
9+
* Value type for co-simulation variable exchange.
10+
* Supports all FMI 2.0 scalar types: Real, Integer, Boolean, String.
811
*/
12+
export type CosimValue = number | string | boolean;
913

1014
/** A single variable coupling: one output feeds one input. */
1115
export interface VariableCoupling {
@@ -66,8 +70,8 @@ export class CouplingGraph {
6670
* Apply coupling values: given a map of all participant outputs,
6771
* produce a map of participant ID → input values to inject.
6872
*/
69-
applyCouplings(allOutputs: Map<string, Map<string, number>>): Map<string, Map<string, number>> {
70-
const inputs = new Map<string, Map<string, number>>();
73+
applyCouplings(allOutputs: Map<string, Map<string, CosimValue>>): Map<string, Map<string, CosimValue>> {
74+
const inputs = new Map<string, Map<string, CosimValue>>();
7175

7276
for (const coupling of this.couplings) {
7377
const sourceOutputs = allOutputs.get(coupling.from.participantId);
@@ -78,7 +82,7 @@ export class CouplingGraph {
7882

7983
let targetInputs = inputs.get(coupling.to.participantId);
8084
if (!targetInputs) {
81-
targetInputs = new Map<string, number>();
85+
targetInputs = new Map<string, CosimValue>();
8286
inputs.set(coupling.to.participantId, targetInputs);
8387
}
8488
targetInputs.set(coupling.to.variableName, value);

packages/cosim/src/mqtt/protocol.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export interface SetInputsCommand {
7878
/** Participant ID. */
7979
participantId: string;
8080
/** Variable name → value map. */
81-
values: Record<string, number>;
81+
values: Record<string, number | string | boolean>;
8282
}
8383

8484
/** Terminate command. */
@@ -118,7 +118,7 @@ export interface StatusMessage {
118118
/** Error message (when state is 'error'). */
119119
error?: string | undefined;
120120
/** Output variable values (after step). */
121-
outputs?: Record<string, number> | undefined;
121+
outputs?: Record<string, number | string | boolean> | undefined;
122122
}
123123

124124
// ── Result messages ──
@@ -128,7 +128,7 @@ export interface StepResult {
128128
/** Simulation time. */
129129
time: number;
130130
/** Participant ID → variable values map. */
131-
participants: Record<string, Record<string, number>>;
131+
participants: Record<string, Record<string, number | string | boolean>>;
132132
}
133133

134134
// ── Batched variable data ──
@@ -138,7 +138,7 @@ export interface VariableBatch {
138138
/** Simulation time. */
139139
time: number;
140140
/** Variable name → value map. */
141-
values: Record<string, number>;
141+
values: Record<string, number | string | boolean>;
142142
}
143143

144144
// ── Serialization helpers ──

packages/cosim/src/orchestrator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class Orchestrator {
9898
const effectiveH = Math.min(h, experiment.stopTime - t);
9999

100100
// ── Step 2a: Collect all current outputs ──
101-
const allOutputs = new Map<string, Map<string, number>>();
101+
const allOutputs = new Map<string, Map<string, number | string | boolean>>();
102102
for (const p of participants) {
103103
const outputs = await p.getOutputs();
104104
allOutputs.set(p.id, outputs);
@@ -121,10 +121,10 @@ export class Orchestrator {
121121
}
122122

123123
// ── Step 2e: Collect outputs and publish results ──
124-
const stepOutputs: Record<string, Record<string, number>> = {};
124+
const stepOutputs: Record<string, Record<string, number | string | boolean>> = {};
125125
for (const p of participants) {
126126
const outputs = await p.getOutputs();
127-
const map: Record<string, number> = {};
127+
const map: Record<string, number | string | boolean> = {};
128128
outputs.forEach((value, key) => {
129129
map[key] = value;
130130
});

packages/cosim/src/participant.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* must implement to participate in a co-simulation session.
88
*/
99

10+
import type { CosimValue } from "./coupling.js";
1011
import type { ParticipantMetadata } from "./mqtt/protocol.js";
1112

1213
/**
@@ -46,13 +47,13 @@ export interface CoSimParticipant {
4647
* Get current output variable values.
4748
* Called after doStep() to read results.
4849
*/
49-
getOutputs(): Promise<Map<string, number>>;
50+
getOutputs(): Promise<Map<string, CosimValue>>;
5051

5152
/**
5253
* Set input variable values before the next step.
5354
* Called before doStep() to provide coupled values.
5455
*/
55-
setInputs(values: Map<string, number>): Promise<void>;
56+
setInputs(values: Map<string, CosimValue>): Promise<void>;
5657

5758
/**
5859
* Terminate the participant and release resources.

0 commit comments

Comments
 (0)