Skip to content

Commit 027c905

Browse files
committed
feat(core,cosim): externalobject lifecycle, fmi2reset, assert/terminate/initial/terminal c codegen, dopri5 integrator, spatialdistribution c helper, pantelides index reduction, fmi3 scheduled execution participant, fmi2setrealinputderivatives, msvc/mingw cmake cross-compilation
1 parent 70272af commit 027c905

4 files changed

Lines changed: 562 additions & 12 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ export interface FmuResult {
114114
guid: string;
115115
/** Number of event indicators. */
116116
numberOfEventIndicators: number;
117+
/** External C source files to include in CMake build (from `external "C"` annotations). */
118+
externalSources?: string[];
117119
}
118120

119121
/**

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

Lines changed: 180 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ export function generateFmuCSources(dae: ModelicaDAE, fmuResult: FmuResult, opti
7575
/** Convert a DAE expression to a C expression string. */
7676
/** Counter for deterministic delay buffer indexing across exprToC calls. */
7777
let delayBufferCounter = 0;
78+
/** Counter for deterministic spatialDistribution buffer indexing. */
79+
let spatialDistCounter = 0;
7880

7981
function exprToC(expr: ModelicaExpression): string {
8082
if (expr instanceof ModelicaRealLiteral) {
@@ -112,6 +114,30 @@ function exprToC(expr: ModelicaExpression): string {
112114
const bufIdx = delayBufferCounter++;
113115
return `delay_lookup(&m->delayBuf[${bufIdx}], m->time - (${delayTime}), ${delayExpr})`;
114116
}
117+
// initial() / terminal() predicates
118+
if (expr.functionName === "initial") return "inst->isInitPhase";
119+
if (expr.functionName === "terminal") return "0";
120+
// assert(condition, message) — runtime assertion
121+
if (expr.functionName === "assert" && expr.args.length >= 2) {
122+
const cond = exprToC(expr.args[0] as ModelicaExpression);
123+
// Message is a string literal — extract it or use a default
124+
const msgExpr = expr.args[1];
125+
const msg = msgExpr instanceof ModelicaStringLiteral ? msgExpr.value.replace(/"/g, '\\"') : "Assertion failed";
126+
return `((${cond}) ? 0.0 : (inst->callbacks.logger(inst, inst->instanceName, fmi2Error, "assert", "${msg}"), 0.0))`;
127+
}
128+
// terminate(message) — signal simulation termination
129+
if (expr.functionName === "terminate") {
130+
return "(inst->terminateRequested = 1, 0.0)";
131+
}
132+
// spatialDistribution(in0, in1, x, positiveVelocity) — 1D advection
133+
if (expr.functionName === "spatialDistribution" && expr.args.length >= 4) {
134+
const in0 = exprToC(expr.args[0] as ModelicaExpression);
135+
const in1 = exprToC(expr.args[1] as ModelicaExpression);
136+
const x = exprToC(expr.args[2] as ModelicaExpression);
137+
const posVel = exprToC(expr.args[3] as ModelicaExpression);
138+
const bufIdx = spatialDistCounter++;
139+
return `spatial_step(&m->spatialDist[${bufIdx}], ${in0}, ${in1}, ${x}, (int)(${posVel}))`;
140+
}
115141
const args = expr.args.map((a: ModelicaExpression) => exprToC(a)).join(", ");
116142
const fname = mapFunctionName(expr.functionName);
117143
return `${fname}(${args})`;
@@ -357,6 +383,15 @@ function generateModelH(
357383
);
358384
lines.push(" double time;");
359385
lines.push(" int isDirtyValues;");
386+
lines.push(" int isInitPhase; /* 1 during initialization, 0 otherwise */");
387+
388+
// ExternalObject opaque pointer fields
389+
if (dae.externalObjects.length > 0) {
390+
lines.push(` /* ExternalObject handles (${dae.externalObjects.length}) */`);
391+
for (let ei = 0; ei < dae.externalObjects.length; ei++) {
392+
lines.push(` void* extObj_${ei}; /* ${dae.externalObjects[ei]?.typeName ?? "unknown"} */`);
393+
}
394+
}
360395

361396
// Count delay() calls in DAE for delay buffer allocation
362397
let nDelayBuffers = 0;
@@ -520,8 +555,9 @@ function generateAlgebraicLoopSolvers(id: string, dae: ModelicaDAE, result: FmuR
520555
}
521556

522557
function generateModelC(id: string, dae: ModelicaDAE, result: FmuResult): string {
523-
// Reset delay buffer counter for this codegen invocation
558+
// Reset buffer counters for this codegen invocation
524559
delayBufferCounter = 0;
560+
spatialDistCounter = 0;
525561

526562
const lines: string[] = [];
527563
lines.push("/* Auto-generated by ModelScript — do not edit */");
@@ -594,6 +630,39 @@ function generateModelC(id: string, dae: ModelicaDAE, result: FmuResult): string
594630
lines.push("");
595631
}
596632

633+
// ── spatialDistribution() C helpers ──
634+
lines.push("/* --- spatialDistribution() 1D advection helpers --- */");
635+
lines.push("#define SPATIAL_BUF_SIZE 256");
636+
lines.push("typedef struct {");
637+
lines.push(" double positions[SPATIAL_BUF_SIZE];");
638+
lines.push(" double values[SPATIAL_BUF_SIZE];");
639+
lines.push(" int count;");
640+
lines.push(" double prevX; /* previous position for delta computation */");
641+
lines.push("} SpatialDist;");
642+
lines.push("");
643+
lines.push("static double spatial_step(SpatialDist* sd, double in0, double in1, double x, int posVel) {");
644+
lines.push(" double dx = x - sd->prevX;");
645+
lines.push(" sd->prevX = x;");
646+
lines.push(" /* Advect all sample points by dx */");
647+
lines.push(" for (int i = 0; i < sd->count; i++) sd->positions[i] += (posVel ? dx : -dx);");
648+
lines.push(" /* Insert boundary values */");
649+
lines.push(" if (posVel && sd->count < SPATIAL_BUF_SIZE) {");
650+
lines.push(" sd->positions[sd->count] = 0.0; sd->values[sd->count] = in0; sd->count++;");
651+
lines.push(" } else if (!posVel && sd->count < SPATIAL_BUF_SIZE) {");
652+
lines.push(" sd->positions[sd->count] = 1.0; sd->values[sd->count] = in1; sd->count++;");
653+
lines.push(" }");
654+
lines.push(" /* Look up output value at boundary 1 (posVel) or 0 (!posVel) */");
655+
lines.push(" double target = posVel ? 1.0 : 0.0;");
656+
lines.push(" double result = posVel ? in1 : in0; /* default */");
657+
lines.push(" double bestDist = 1e30;");
658+
lines.push(" for (int i = 0; i < sd->count; i++) {");
659+
lines.push(" double d = fabs(sd->positions[i] - target);");
660+
lines.push(" if (d < bestDist) { bestDist = d; result = sd->values[i]; }");
661+
lines.push(" }");
662+
lines.push(" return result;");
663+
lines.push("}");
664+
lines.push("");
665+
597666
// Build value-reference → C accessor mappings
598667
const vrMap = new Map<string, number>();
599668
for (const sv of result.scalarVariables) {
@@ -800,6 +869,10 @@ function generateFmi2FunctionsC(
800869
if (!eo) continue;
801870
lines.push(` void* extObj_${ei}; /* ${eo.typeName}: ${eo.variableName} */`);
802871
}
872+
lines.push(" /* Input derivative storage for fmi2SetRealInputDerivatives */");
873+
lines.push(" double inputDerivatives[N_VARS + 1];");
874+
lines.push(" /* Terminate flag for Modelica terminate() calls */");
875+
lines.push(" int terminateRequested;");
803876
lines.push("} FMUInstance;");
804877
lines.push("");
805878

@@ -821,11 +894,12 @@ function generateFmi2FunctionsC(
821894

822895
// Emit ExternalObject constructor calls right after fmi2Instantiate
823896
if (dae.externalObjects.length > 0) {
824-
lines.push("/* --- ExternalObject constructor stubs --- */");
897+
lines.push(" /* --- ExternalObject constructors --- */");
825898
for (let ei = 0; ei < dae.externalObjects.length; ei++) {
826899
const eo = dae.externalObjects[ei];
827900
if (!eo) continue;
828-
lines.push(`/* TODO: inst->extObj_${ei} = ${sanitizeIdentifier(eo.constructorName)}(...); */`);
901+
const ctorName = sanitizeIdentifier(eo.constructorName);
902+
lines.push(` inst->model.extObj_${ei} = (void*)${ctorName}(); /* ${eo.typeName} */`);
829903
}
830904
lines.push("");
831905
}
@@ -845,10 +919,15 @@ function generateFmi2FunctionsC(
845919
// ── fmi2EnterInitializationMode / fmi2ExitInitializationMode ──
846920
lines.push("fmi2Status fmi2EnterInitializationMode(fmi2Component c) {");
847921
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
922+
lines.push(" inst->model.isInitPhase = 1;");
848923
lines.push(` ${id}_solveAlgebraicLoops(&inst->model);`);
849924
lines.push(" return fmi2OK;");
850925
lines.push("}");
851-
lines.push("fmi2Status fmi2ExitInitializationMode(fmi2Component c) { (void)c; return fmi2OK; }");
926+
lines.push("fmi2Status fmi2ExitInitializationMode(fmi2Component c) {");
927+
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
928+
lines.push(" inst->model.isInitPhase = 0;");
929+
lines.push(" return fmi2OK;");
930+
lines.push("}");
852931
lines.push("");
853932

854933
// ── fmi2SetReal / fmi2GetReal ──
@@ -1013,6 +1092,83 @@ function generateFmi2FunctionsC(
10131092
lines.push("}");
10141093
lines.push("");
10151094

1095+
// ── DOPRI5 helper function (Dormand-Prince 4(5) with error estimate) ──
1096+
lines.push(
1097+
`static double take_dopri5_step(${id}_Instance* m, double t, double h, const double* s0, double* s4, double* s5) {`,
1098+
);
1099+
lines.push(" /* Dormand-Prince coefficients */");
1100+
lines.push(" double k1[N_STATES+1], k2[N_STATES+1], k3[N_STATES+1], k4[N_STATES+1];");
1101+
lines.push(" double k5[N_STATES+1], k6[N_STATES+1], k7[N_STATES+1];");
1102+
lines.push(` m->time = t; ${id}_getDerivatives(m);`);
1103+
lines.push(" for (int i = 0; i < N_STATES; i++) k1[i] = m->derivatives[i];");
1104+
// k2: t + 1/5 h
1105+
lines.push(" m->time = t + h/5.0;");
1106+
for (let i = 0; i < stateRefs2.length; i++) {
1107+
lines.push(` m->vars[${stateRefs2[i]}] = s0[${i}] + h*(1.0/5)*k1[${i}];`);
1108+
}
1109+
lines.push(` ${id}_getDerivatives(m);`);
1110+
lines.push(" for (int i = 0; i < N_STATES; i++) k2[i] = m->derivatives[i];");
1111+
// k3: t + 3/10 h
1112+
lines.push(" m->time = t + 3.0*h/10.0;");
1113+
for (let i = 0; i < stateRefs2.length; i++) {
1114+
lines.push(` m->vars[${stateRefs2[i]}] = s0[${i}] + h*(3.0/40*k1[${i}] + 9.0/40*k2[${i}]);`);
1115+
}
1116+
lines.push(` ${id}_getDerivatives(m);`);
1117+
lines.push(" for (int i = 0; i < N_STATES; i++) k3[i] = m->derivatives[i];");
1118+
// k4: t + 4/5 h
1119+
lines.push(" m->time = t + 4.0*h/5.0;");
1120+
for (let i = 0; i < stateRefs2.length; i++) {
1121+
lines.push(` m->vars[${stateRefs2[i]}] = s0[${i}] + h*(44.0/45*k1[${i}] - 56.0/15*k2[${i}] + 32.0/9*k3[${i}]);`);
1122+
}
1123+
lines.push(` ${id}_getDerivatives(m);`);
1124+
lines.push(" for (int i = 0; i < N_STATES; i++) k4[i] = m->derivatives[i];");
1125+
// k5: t + 8/9 h
1126+
lines.push(" m->time = t + 8.0*h/9.0;");
1127+
for (let i = 0; i < stateRefs2.length; i++) {
1128+
lines.push(
1129+
` m->vars[${stateRefs2[i]}] = s0[${i}] + h*(19372.0/6561*k1[${i}] - 25360.0/2187*k2[${i}] + 64448.0/6561*k3[${i}] - 212.0/729*k4[${i}]);`,
1130+
);
1131+
}
1132+
lines.push(` ${id}_getDerivatives(m);`);
1133+
lines.push(" for (int i = 0; i < N_STATES; i++) k5[i] = m->derivatives[i];");
1134+
// k6: t + h
1135+
lines.push(" m->time = t + h;");
1136+
for (let i = 0; i < stateRefs2.length; i++) {
1137+
lines.push(
1138+
` m->vars[${stateRefs2[i]}] = s0[${i}] + h*(9017.0/3168*k1[${i}] - 355.0/33*k2[${i}] + 46732.0/5247*k3[${i}] + 49.0/176*k4[${i}] - 5103.0/18656*k5[${i}]);`,
1139+
);
1140+
}
1141+
lines.push(` ${id}_getDerivatives(m);`);
1142+
lines.push(" for (int i = 0; i < N_STATES; i++) k6[i] = m->derivatives[i];");
1143+
// 5th-order solution
1144+
lines.push(" for (int i = 0; i < N_STATES; i++) {");
1145+
lines.push(
1146+
" s5[i] = s0[i] + h*(35.0/384*k1[i] + 500.0/1113*k3[i] + 125.0/192*k4[i] - 2187.0/6784*k5[i] + 11.0/84*k6[i]);",
1147+
);
1148+
lines.push(" }");
1149+
// k7: FSAL at new point
1150+
for (let i = 0; i < stateRefs2.length; i++) {
1151+
lines.push(` m->vars[${stateRefs2[i]}] = s5[${i}];`);
1152+
}
1153+
lines.push(` ${id}_getDerivatives(m);`);
1154+
lines.push(" for (int i = 0; i < N_STATES; i++) k7[i] = m->derivatives[i];");
1155+
// 4th-order solution (for error estimation)
1156+
lines.push(" for (int i = 0; i < N_STATES; i++) {");
1157+
lines.push(
1158+
" s4[i] = s0[i] + h*(5179.0/57600*k1[i] + 7571.0/16695*k3[i] + 393.0/640*k4[i] - 92097.0/339200*k5[i] + 187.0/2100*k6[i] + 1.0/40*k7[i]);",
1159+
);
1160+
lines.push(" }");
1161+
// Error estimate: max |s5 - s4| / (atol + rtol * |s5|)
1162+
lines.push(" double maxErr = 0.0;");
1163+
lines.push(" for (int i = 0; i < N_STATES; i++) {");
1164+
lines.push(" double sc = 1e-6 + 1e-3 * fabs(s5[i]);");
1165+
lines.push(" double ei = fabs(s5[i] - s4[i]) / sc;");
1166+
lines.push(" if (ei > maxErr) maxErr = ei;");
1167+
lines.push(" }");
1168+
lines.push(" return maxErr;");
1169+
lines.push("}");
1170+
lines.push("");
1171+
10161172
// ── Static worker function for async co-simulation ──
10171173
lines.push("/* --- Async Co-Simulation worker --- */");
10181174
lines.push("static void doStep_sync(FMUInstance* inst) {");
@@ -1220,13 +1376,14 @@ function generateFmi2FunctionsC(
12201376
lines.push(" for (int i = 0; i < N_STRING_VARS; i++) {");
12211377
lines.push(" if (inst->model.stringVars[i]) free(inst->model.stringVars[i]);");
12221378
lines.push(" }");
1223-
// ExternalObject destructor stubs
1379+
// ExternalObject destructors
12241380
if (dae.externalObjects.length > 0) {
12251381
lines.push(" /* ExternalObject destructors */");
12261382
for (let ei = 0; ei < dae.externalObjects.length; ei++) {
12271383
const eo = dae.externalObjects[ei];
12281384
if (!eo) continue;
1229-
lines.push(` /* TODO: ${sanitizeIdentifier(eo.destructorName)}(inst->extObj_${ei}); */`);
1385+
const dtorName = sanitizeIdentifier(eo.destructorName);
1386+
lines.push(` ${dtorName}(inst->model.extObj_${ei});`);
12301387
}
12311388
}
12321389
lines.push(" free(inst);");
@@ -1235,7 +1392,11 @@ function generateFmi2FunctionsC(
12351392

12361393
// ── Stubs for remaining FMI 2.0 functions ──
12371394
lines.push("/* --- Stubs --- */");
1238-
lines.push("fmi2Status fmi2Reset(fmi2Component c) { (void)c; return fmi2OK; }");
1395+
lines.push("fmi2Status fmi2Reset(fmi2Component c) {");
1396+
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
1397+
lines.push(` ${id}_initialize(&inst->model);`);
1398+
lines.push(" return fmi2OK;");
1399+
lines.push("}");
12391400

12401401
// ── fmi2SetString / fmi2GetString ──
12411402
// Build a value-reference → string-index mapping for string variables
@@ -1482,8 +1643,10 @@ function generateFmi2FunctionsC(
14821643
lines.push(
14831644
"fmi2Status fmi2SetRealInputDerivatives(fmi2Component c, const fmi2ValueReference vr[], size_t nvr, const fmi2Integer order[], const fmi2Real value[]) {",
14841645
);
1485-
lines.push(" (void)c; (void)vr; (void)nvr; (void)order; (void)value;");
1486-
lines.push(" /* Store input derivatives for higher-order interpolation */");
1646+
lines.push(" FMUInstance* inst = (FMUInstance*)c;");
1647+
lines.push(" for (size_t i = 0; i < nvr; i++) {");
1648+
lines.push(" if (order[i] == 1 && vr[i] < N_VARS) inst->inputDerivatives[vr[i]] = value[i];");
1649+
lines.push(" }");
14871650
lines.push(" return fmi2OK;");
14881651
lines.push("}");
14891652
lines.push(
@@ -1670,7 +1833,8 @@ function generateFmi2FunctionsC(
16701833

16711834
// ── CMakeLists.txt generator ──
16721835

1673-
function generateCMakeLists(id: string): string {
1836+
function generateCMakeLists(id: string, externalSources: string[] = []): string {
1837+
const extSourceLines = externalSources.map((s) => ` ${s}`).join("\n");
16741838
return `# Auto-generated by ModelScript — CMake build for FMU shared library
16751839
cmake_minimum_required(VERSION 3.10)
16761840
project(${id} C)
@@ -1696,7 +1860,7 @@ endif()
16961860
add_library(${id} SHARED
16971861
${id}_model.c
16981862
fmi2Functions.c
1699-
fmi3Functions.c
1863+
fmi3Functions.c${extSourceLines ? "\n" + extSourceLines : ""}
17001864
)
17011865
17021866
target_include_directories(${id} PRIVATE \${CMAKE_CURRENT_SOURCE_DIR})
@@ -1709,7 +1873,11 @@ set_target_properties(${id} PROPERTIES
17091873
)
17101874
17111875
if(MSVC)
1712-
target_compile_definitions(${id} PRIVATE FMI2_FUNCTION_PREFIX=)
1876+
target_compile_definitions(${id} PRIVATE FMI2_FUNCTION_PREFIX= _CRT_SECURE_NO_WARNINGS)
1877+
set_target_properties(${id} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
1878+
elseif(MINGW)
1879+
target_compile_options(${id} PRIVATE -Wall -O2)
1880+
target_link_options(${id} PRIVATE -static-libgcc)
17131881
else()
17141882
target_compile_options(${id} PRIVATE -Wall -Wextra -O2)
17151883
endif()

0 commit comments

Comments
 (0)