Skip to content

Commit 92f261e

Browse files
committed
feat: add fmi 3.0 terminals and icons xml generation and extraction, fix fmi config typings
1 parent b6cbd97 commit 92f261e

6 files changed

Lines changed: 291 additions & 14 deletions

File tree

packages/core/src/compiler/modelica/flattener.ts

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ interface FlattenerContext {
149149
streamConnections?: { side1: string; side2: string }[];
150150
/** Deferred flow variable connection pairs for connection-set-based flow balance generation. */
151151
flowConnectPairs?: { name1: string; name2: string }[];
152+
/** Monomorphization bindings for higher order functions */
153+
functionBindings?: Map<string, ModelicaPartialFunctionExpression | string>;
152154
}
153155

154156
/** Extract an integer shape array from a list of expressions (all must be ModelicaIntegerLiteral). */
@@ -2926,6 +2928,22 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
29262928
let functionName =
29272929
node.functionReference?.parts?.map((p) => p.identifier?.text ?? "").join(".") ||
29282930
(node.functionReferenceName ?? "");
2931+
2932+
// Monomorphization: Resolve bound function parameters dynamically
2933+
let boundArgsMap: Map<string, ModelicaExpression> | null = null;
2934+
if (ctx.functionBindings?.has(functionName)) {
2935+
const boundItem = ctx.functionBindings.get(functionName);
2936+
if (typeof boundItem === "string") {
2937+
functionName = boundItem;
2938+
} else if (boundItem instanceof ModelicaPartialFunctionExpression) {
2939+
functionName = boundItem.functionName;
2940+
boundArgsMap = new Map();
2941+
for (const namedArg of boundItem.namedArgs) {
2942+
boundArgsMap.set(namedArg.name, namedArg.value);
2943+
}
2944+
}
2945+
}
2946+
29292947
let flatArgs: ModelicaExpression[] = [];
29302948
for (const arg of node.functionCallArguments?.arguments ?? []) {
29312949
let flatArg: ModelicaExpression | null;
@@ -2937,6 +2955,39 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
29372955
if (flatArg) flatArgs.push(flatArg);
29382956
}
29392957

2958+
// Merge bound arguments into the flattened arguments array
2959+
if (boundArgsMap && boundArgsMap.size > 0) {
2960+
// Resolve the target function to correctly align named bound arguments with positions
2961+
const resolved = ctx.classInstance.resolveName(functionName.split("."));
2962+
if (
2963+
resolved instanceof ModelicaClassInstance &&
2964+
(resolved.classKind === ModelicaClassKind.FUNCTION ||
2965+
resolved.classKind === ModelicaClassKind.OPERATOR_FUNCTION)
2966+
) {
2967+
if (!resolved.instantiated) resolved.instantiate();
2968+
const inputs = Array.from(resolved.components).filter((c) => c.causality?.toString() === "input");
2969+
2970+
const mergedArgs: ModelicaExpression[] = [];
2971+
let positionalIndex = 0;
2972+
for (const input of inputs) {
2973+
const inputName = input.name ?? "";
2974+
const boundValue = boundArgsMap.get(inputName);
2975+
if (boundValue) {
2976+
mergedArgs.push(boundValue);
2977+
} else if (positionalIndex < flatArgs.length) {
2978+
mergedArgs.push(flatArgs[positionalIndex] as ModelicaExpression);
2979+
positionalIndex++;
2980+
}
2981+
}
2982+
// Append any remaining arguments
2983+
while (positionalIndex < flatArgs.length) {
2984+
mergedArgs.push(flatArgs[positionalIndex] as ModelicaExpression);
2985+
positionalIndex++;
2986+
}
2987+
flatArgs = mergedArgs;
2988+
}
2989+
}
2990+
29402991
// Handle record constructor calls: Complex(re=2.0, im=3.0) or Rec(r = 1.0)
29412992
// Record constructors use the record type name as the function name and may have named arguments.
29422993
{
@@ -3862,8 +3913,73 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
38623913
}
38633914
}
38643915
}
3916+
// Specialized Higher-Order Function Execution (Monomorphization)
3917+
let finalFunctionName = isExternalBuiltinAlias ? functionName : originalName;
3918+
let finalArgs = flatArgs;
38653919

3866-
const result = new ModelicaFunctionCallExpression(isExternalBuiltinAlias ? functionName : originalName, flatArgs);
3920+
if (!builtinDef) {
3921+
const fDef =
3922+
ctx.dae.functions.find((f) => f.name === functionName) ||
3923+
ctx.rootDae?.functions.find((f) => f.name === functionName);
3924+
if (fDef) {
3925+
const inputVars = fDef.variables.filter((v) => v.causality === "input");
3926+
let hasHigherOrderArg = false;
3927+
const newBindings = new Map<string, ModelicaPartialFunctionExpression | string>();
3928+
const filteredArgs: ModelicaExpression[] = [];
3929+
3930+
for (let i = 0; i < flatArgs.length && i < inputVars.length; i++) {
3931+
const inVar = inputVars[i];
3932+
const argVal = flatArgs[i];
3933+
if (inVar?.functionType) {
3934+
hasHigherOrderArg = true;
3935+
if (argVal instanceof ModelicaPartialFunctionExpression) {
3936+
newBindings.set(inVar.name, argVal);
3937+
} else if (argVal instanceof ModelicaNameExpression) {
3938+
newBindings.set(inVar.name, argVal.name);
3939+
}
3940+
} else if (argVal) {
3941+
filteredArgs.push(argVal);
3942+
}
3943+
}
3944+
3945+
// Push any remaining defaults that were expanded
3946+
for (let i = inputVars.length; i < flatArgs.length; i++) {
3947+
const argVal = flatArgs[i];
3948+
if (argVal) filteredArgs.push(argVal);
3949+
}
3950+
3951+
if (hasHigherOrderArg) {
3952+
// Generate deterministic specialized name based on bound function names
3953+
const hashPairs = Array.from(newBindings.entries()).map(([k, v]) => {
3954+
const vName = v instanceof ModelicaPartialFunctionExpression ? v.functionName : v;
3955+
return `${k}_${vName.replace(/\\./g, "_")}`;
3956+
});
3957+
const specializedName = `${functionName}$${hashPairs.join("$")}`;
3958+
3959+
// Resolve the base function class instance to use as an override
3960+
let baseResolved: ModelicaClassInstance | undefined = resolvedOverride;
3961+
if (!baseResolved && ctx.classInstance) {
3962+
const parts = originalName.split(".");
3963+
const r = ctx.classInstance.resolveName(parts);
3964+
if (r instanceof ModelicaClassInstance) baseResolved = r;
3965+
}
3966+
3967+
if (baseResolved) {
3968+
const baseBindings = ctx.functionBindings ? Array.from(ctx.functionBindings.entries()) : [];
3969+
const specializedCtx: FlattenerContext = {
3970+
...ctx,
3971+
functionBindings: new Map([...baseBindings, ...Array.from(newBindings.entries())]),
3972+
};
3973+
this.#collectFunctionDefinition(specializedName, specializedCtx, baseResolved, componentPrefix);
3974+
3975+
finalFunctionName = specializedName;
3976+
finalArgs = filteredArgs;
3977+
}
3978+
}
3979+
}
3980+
}
3981+
3982+
const result = new ModelicaFunctionCallExpression(finalFunctionName, finalArgs);
38673983

38683984
// Only inline user-defined function calls when ALL arguments are compile-time constants.
38693985
// Parameters are NOT constants — they can change between simulations.
@@ -4468,6 +4584,11 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
44684584
variable.customTypeName = typeInstance.name ?? null;
44694585
}
44704586
}
4587+
4588+
if (ctx.functionBindings?.has(compName)) {
4589+
// Skip emitting this variable as an argument! It is statically bound.
4590+
continue;
4591+
}
44714592
fnDae.variables.push(variable);
44724593
}
44734594

@@ -4526,6 +4647,7 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
45264647
stmtCollector: [],
45274648
rootDae: targetDae,
45284649
...(componentPrefix ? { componentFunctionPrefix: componentPrefix } : {}),
4650+
...(ctx.functionBindings ? { functionBindings: ctx.functionBindings } : {}),
45294651
});
45304652
}
45314653
}
@@ -4539,6 +4661,7 @@ class ModelicaSyntaxFlattener extends ModelicaSyntaxVisitor<ModelicaExpression,
45394661
stmtCollector: collector,
45404662
rootDae: targetDae,
45414663
...(componentPrefix ? { componentFunctionPrefix: componentPrefix } : {}),
4664+
...(ctx.functionBindings ? { functionBindings: ctx.functionBindings } : {}),
45424665
});
45434666
}
45444667
if (collector.length > 0) {

packages/core/src/compiler/modelica/fmi3.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,21 +152,21 @@ export interface Fmi3Options {
152152
/** Model identifier. */
153153
modelIdentifier: string;
154154
/** Description of the model. */
155-
description?: string;
155+
description?: string | undefined;
156156
/** Author name. */
157-
author?: string;
157+
author?: string | undefined;
158158
/** Generation tool name. */
159-
generationTool?: string;
159+
generationTool?: string | undefined;
160160
/** GUID (auto-generated if not provided). */
161-
guid?: string;
161+
guid?: string | undefined;
162162
/** Default experiment start time. */
163-
startTime?: number;
163+
startTime?: number | undefined;
164164
/** Default experiment stop time. */
165-
stopTime?: number;
165+
stopTime?: number | undefined;
166166
/** Default experiment step size. */
167-
stepSize?: number;
167+
stepSize?: number | undefined;
168168
/** FMU type flags. */
169-
fmuType?: Fmi3TypeFlags;
169+
fmuType?: Fmi3TypeFlags | undefined;
170170
}
171171

172172
/** Result of FMI 3.0 FMU generation. */
@@ -189,6 +189,8 @@ export interface Fmi3Result {
189189
numberOfEventIndicators: number;
190190
/** Terminals (from Modelica connectors). */
191191
terminals: Fmi3Terminal[];
192+
/** Terminals and Icons XML payload. */
193+
terminalsAndIconsXml?: string | null;
192194
}
193195

194196
// ── Main generator ──
@@ -281,6 +283,7 @@ export function generateFmi3(dae: ModelicaDAE, options: Fmi3Options, stateVars?:
281283
// ── Generate modelDescription.xml ──
282284
const fmuType = options.fmuType ?? { modelExchange: true, coSimulation: true };
283285
const nEventIndicators = countEventIndicators3(dae);
286+
const terminals = detectTerminals3(groupedVariables);
284287
const xml = generateModelDescriptionXml3(groupedVariables, {
285288
...options,
286289
guid,
@@ -293,7 +296,7 @@ export function generateFmi3(dae: ModelicaDAE, options: Fmi3Options, stateVars?:
293296
aliasMap,
294297
deps,
295298
enumTypes,
296-
terminals: detectTerminals3(groupedVariables),
299+
terminals,
297300
});
298301

299302
return {
@@ -308,7 +311,8 @@ export function generateFmi3(dae: ModelicaDAE, options: Fmi3Options, stateVars?:
308311
},
309312
guid,
310313
numberOfEventIndicators: nEventIndicators,
311-
terminals: detectTerminals3(groupedVariables),
314+
terminals,
315+
terminalsAndIconsXml: generateTerminalsAndIconsXml(terminals),
312316
};
313317
}
314318

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { deflateRaw } from "pako";
2020
import type { ModelicaDAE } from "./dae.js";
2121
import type { FmuOptions, FmuResult } from "./fmi.js";
2222
import { generateFmu } from "./fmi.js";
23+
import { generateFmi3 } from "./fmi3.js";
2324
import { generateFmuCSources } from "./fmu-codegen.js";
2425
import type { ModelicaSimulator } from "./simulator.js";
2526

@@ -58,6 +59,7 @@ export function buildFmuArchive(
5859
): FmuArchiveResult {
5960
const stateVars = simulator?.stateVars ?? new Set<string>();
6061
const fmuResult = generateFmu(dae, options, stateVars);
62+
const fmi3Result = generateFmi3(dae, options, stateVars);
6163
const id = options.modelIdentifier;
6264

6365
const files = new Map<string, Uint8Array>();
@@ -66,6 +68,11 @@ export function buildFmuArchive(
6668
// ── modelDescription.xml ──
6769
files.set("modelDescription.xml", encoder.encode(fmuResult.modelDescriptionXml));
6870

71+
// ── terminalsAndIcons.xml (FMI 3.0) ──
72+
if (fmi3Result.terminalsAndIconsXml) {
73+
files.set("terminalsAndIcons/terminalsAndIcons.xml", encoder.encode(fmi3Result.terminalsAndIconsXml));
74+
}
75+
6976
// ── C source files ──
7077
if (options.includeSources !== false) {
7178
const sources = generateFmuCSources(dae, fmuResult, options);

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1571,6 +1571,7 @@ export function generateFmi3FunctionsC(id: string): string {
15711571
lines.push("/* Typedefs for FMI 3.0 */");
15721572
lines.push("typedef void* fmi3Instance;");
15731573
lines.push("typedef void* fmi3InstanceEnvironment;");
1574+
lines.push("typedef void* fmi3FMUState;");
15741575
lines.push("typedef const char* fmi3String;");
15751576
lines.push("typedef double fmi3Float64;");
15761577
lines.push("typedef float fmi3Float32;");
@@ -1617,7 +1618,69 @@ export function generateFmi3FunctionsC(id: string): string {
16171618
lines.push("}");
16181619
lines.push("");
16191620
lines.push("void fmi3FreeInstance(fmi3Instance instance) {");
1620-
lines.push(" if (instance) free(instance);");
1621+
lines.push(" if (!instance) return;");
1622+
lines.push(" FMI3InstanceData* inst = (FMI3InstanceData*)instance;");
1623+
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
1624+
lines.push(" if (inst->model.stringVars[i]) free(inst->model.stringVars[i]);");
1625+
lines.push(" }");
1626+
lines.push(" free(instance);");
1627+
lines.push("}");
1628+
lines.push("");
1629+
lines.push("fmi3Status fmi3GetFMUState(fmi3Instance instance, fmi3FMUState* state) {");
1630+
lines.push(" if (!instance || !state) return fmi3Error;");
1631+
lines.push(" FMI3InstanceData* inst = (FMI3InstanceData*)instance;");
1632+
lines.push(" FMI3InstanceData* copy = (FMI3InstanceData*)malloc(sizeof(FMI3InstanceData));");
1633+
lines.push(" if (!copy) return fmi3Error;");
1634+
lines.push(" memcpy(copy, inst, sizeof(FMI3InstanceData));");
1635+
lines.push(" /* Deep copy strings */");
1636+
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
1637+
lines.push(" if (inst->model.stringVars[i]) {");
1638+
lines.push(" copy->model.stringVars[i] = strdup(inst->model.stringVars[i]);");
1639+
lines.push(" } else {");
1640+
lines.push(" copy->model.stringVars[i] = NULL;");
1641+
lines.push(" }");
1642+
lines.push(" }");
1643+
lines.push(" *state = (fmi3FMUState)copy;");
1644+
lines.push(" return fmi3OK;");
1645+
lines.push("}");
1646+
lines.push("");
1647+
lines.push("fmi3Status fmi3SetFMUState(fmi3Instance instance, fmi3FMUState state) {");
1648+
lines.push(" if (!instance || !state) return fmi3Error;");
1649+
lines.push(" FMI3InstanceData* inst = (FMI3InstanceData*)instance;");
1650+
lines.push(" fmi3String savedName = inst->instanceName;");
1651+
lines.push(" fmi3InstanceEnvironment savedEnv = inst->env;");
1652+
lines.push(" fmi3LogMessageCallback savedLogMessage = inst->logMessage;");
1653+
lines.push(" fmi3Boolean savedLoggingOn = inst->loggingOn;");
1654+
lines.push(" /* Free current strings */");
1655+
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
1656+
lines.push(" if (inst->model.stringVars[i]) free(inst->model.stringVars[i]);");
1657+
lines.push(" }");
1658+
lines.push(" memcpy(inst, (FMI3InstanceData*)state, sizeof(FMI3InstanceData));");
1659+
lines.push(" inst->instanceName = savedName;");
1660+
lines.push(" inst->env = savedEnv;");
1661+
lines.push(" inst->logMessage = savedLogMessage;");
1662+
lines.push(" inst->loggingOn = savedLoggingOn;");
1663+
lines.push(" /* Deep copy strings */");
1664+
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
1665+
lines.push(" if (((FMI3InstanceData*)state)->model.stringVars[i]) {");
1666+
lines.push(" inst->model.stringVars[i] = strdup(((FMI3InstanceData*)state)->model.stringVars[i]);");
1667+
lines.push(" } else {");
1668+
lines.push(" inst->model.stringVars[i] = NULL;");
1669+
lines.push(" }");
1670+
lines.push(" }");
1671+
lines.push(" return fmi3OK;");
1672+
lines.push("}");
1673+
lines.push("");
1674+
lines.push("fmi3Status fmi3FreeFMUState(fmi3Instance instance, fmi3FMUState* state) {");
1675+
lines.push(" (void)instance;");
1676+
lines.push(" if (!state || !*state) return fmi3Error;");
1677+
lines.push(" FMI3InstanceData* copy = (FMI3InstanceData*)(*state);");
1678+
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
1679+
lines.push(" if (copy->model.stringVars[i]) free(copy->model.stringVars[i]);");
1680+
lines.push(" }");
1681+
lines.push(" free(copy);");
1682+
lines.push(" *state = NULL;");
1683+
lines.push(" return fmi3OK;");
16211684
lines.push("}");
16221685
lines.push("");
16231686
lines.push(

0 commit comments

Comments
 (0)