@@ -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. */
7777let delayBufferCounter = 0 ;
78+ /** Counter for deterministic spatialDistribution buffer indexing. */
79+ let spatialDistCounter = 0 ;
7880
7981function 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
522557function 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
16751839cmake_minimum_required(VERSION 3.10)
16761840project(${ id } C)
@@ -1696,7 +1860,7 @@ endif()
16961860add_library(${ id } SHARED
16971861 ${ id } _model.c
16981862 fmi2Functions.c
1699- fmi3Functions.c
1863+ fmi3Functions.c${ extSourceLines ? "\n" + extSourceLines : "" }
17001864)
17011865
17021866target_include_directories(${ id } PRIVATE \${CMAKE_CURRENT_SOURCE_DIR})
@@ -1709,7 +1873,11 @@ set_target_properties(${id} PROPERTIES
17091873)
17101874
17111875if(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)
17131881else()
17141882 target_compile_options(${ id } PRIVATE -Wall -Wextra -O2)
17151883endif()
0 commit comments