Skip to content

Commit 1071f0c

Browse files
committed
feat(core): fmi 3.0 array batching, precision types, structural parameters, directional derivatives
1 parent 6dd589e commit 1071f0c

2 files changed

Lines changed: 256 additions & 18 deletions

File tree

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

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -679,19 +679,99 @@ function mapVariable3(v: ModelicaVariable, valueRef: number): Fmi3Variable {
679679
return fv;
680680
}
681681

682-
/** Map Modelica type to FMI 3.0 precise type. */
682+
/** Map Modelica type to FMI 3.0 precise type.
683+
*
684+
* Priority:
685+
* 1. Explicit `__fmi3_type` vendor annotation (e.g., "Float32", "Int8")
686+
* 2. Integer range inference from `min`/`max` attributes
687+
* 3. Default mapping (Real→Float64, Integer→Int32, etc.)
688+
*/
683689
function mapType3(v: ModelicaVariable): Fmi3Type {
684-
if (v instanceof ModelicaRealVariable) return "Float64";
685-
if (v instanceof ModelicaIntegerVariable) return "Int32";
690+
// 1. Check for explicit vendor annotation override
691+
const fmi3TypeAttr = v.attributes.get("__fmi3_type");
692+
if (fmi3TypeAttr) {
693+
const val = extractStringLiteral(fmi3TypeAttr);
694+
if (val && isValidFmi3Type(val)) return val;
695+
}
696+
697+
// 2. Check for __fmi3_binary annotation
698+
if (v.attributes.has("__fmi3_binary")) return "Binary";
699+
700+
// 3. Type-specific mapping
701+
if (v instanceof ModelicaRealVariable) {
702+
// Check for single-precision annotation via min/max range or explicit Float32
703+
const minAttr = v.attributes.get("min");
704+
const maxAttr = v.attributes.get("max");
705+
if (minAttr && maxAttr) {
706+
const minVal = extractNumericLiteral(minAttr);
707+
const maxVal = extractNumericLiteral(maxAttr);
708+
// If range fits in Float32 (approx ±3.4e38) and explicitly annotated small
709+
if (minVal !== null && maxVal !== null && Math.abs(maxVal) <= 3.4e38 && Math.abs(minVal) <= 3.4e38) {
710+
// Only demote if explicitly requested via quantization hint
711+
const quantAttr = v.attributes.get("__fmi3_float32");
712+
if (quantAttr) return "Float32";
713+
}
714+
}
715+
return "Float64";
716+
}
717+
if (v instanceof ModelicaIntegerVariable) {
718+
// Infer narrow integer types from min/max range annotations
719+
const minAttr = v.attributes.get("min");
720+
const maxAttr = v.attributes.get("max");
721+
if (minAttr && maxAttr) {
722+
const minVal = extractNumericLiteral(minAttr);
723+
const maxVal = extractNumericLiteral(maxAttr);
724+
if (minVal !== null && maxVal !== null) {
725+
// Unsigned types
726+
if (minVal >= 0) {
727+
if (maxVal <= 255) return "UInt8";
728+
if (maxVal <= 65535) return "UInt16";
729+
if (maxVal <= 4294967295) return "UInt32";
730+
return "UInt64";
731+
}
732+
// Signed types
733+
if (minVal >= -128 && maxVal <= 127) return "Int8";
734+
if (minVal >= -32768 && maxVal <= 32767) return "Int16";
735+
if (minVal >= -2147483648 && maxVal <= 2147483647) return "Int32";
736+
return "Int64";
737+
}
738+
}
739+
return "Int32";
740+
}
686741
if (v instanceof ModelicaBooleanVariable) return "Boolean";
687742
if (v instanceof ModelicaStringVariable) return "String";
688743
if (v instanceof ModelicaEnumerationVariable) return "Enumeration";
689744
if (v instanceof ModelicaClockVariable) return "Clock";
690745
return "Float64";
691746
}
692747

748+
/** Check if a string is a valid FMI 3.0 type name. */
749+
function isValidFmi3Type(s: string): s is Fmi3Type {
750+
return [
751+
"Float64",
752+
"Float32",
753+
"Int64",
754+
"Int32",
755+
"Int16",
756+
"Int8",
757+
"UInt64",
758+
"UInt32",
759+
"UInt16",
760+
"UInt8",
761+
"Boolean",
762+
"String",
763+
"Binary",
764+
"Enumeration",
765+
"Clock",
766+
].includes(s);
767+
}
768+
693769
function mapCausality3(v: ModelicaVariable): Fmi3Causality {
694-
if (v.variability === ModelicaVariability.PARAMETER) return "parameter";
770+
// FMI 3.0 structural parameter: controls array dimensions at init time
771+
if (v.variability === ModelicaVariability.PARAMETER) {
772+
if (v.attributes.has("__fmi3_structuralParameter")) return "structuralParameter";
773+
return "parameter";
774+
}
695775
if (v.variability === ModelicaVariability.CONSTANT) return "calculatedParameter";
696776
if (v.causality === "input") return "input";
697777
if (v.causality === "output") return "output";

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

Lines changed: 172 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,25 @@ function generateFmi3FunctionsC(
373373
L.push(" fmi3Float64 stepSize;");
374374
L.push("} FMU3Instance;");
375375
L.push("");
376+
L.push("/* Variable size lookup table for array batching (FMI 3.0 §2.2.7) */");
377+
L.push("static const size_t varSizes[N_VARS + 1] = {");
378+
const sizeEntries: number[] = new Array(nVars).fill(1);
379+
for (const sv of result.variables) {
380+
if (sv.dimensions && sv.dimensions.length > 0) {
381+
let totalSize = 1;
382+
for (const dim of sv.dimensions) {
383+
if (dim.start !== undefined) totalSize *= dim.start;
384+
}
385+
if (sv.valueReference < nVars) sizeEntries[sv.valueReference] = totalSize;
386+
}
387+
}
388+
for (let i = 0; i < sizeEntries.length; i += 20) {
389+
const batch = sizeEntries.slice(i, Math.min(i + 20, sizeEntries.length));
390+
const isLast = i + 20 >= sizeEntries.length;
391+
L.push(` ${batch.join(",")}${isLast ? "" : ","}`);
392+
}
393+
L.push("};");
394+
L.push("");
376395

377396
// fmi3InstantiateCoSimulation
378397
L.push("fmi3Instance fmi3InstantiateCoSimulation(");
@@ -431,53 +450,133 @@ function generateFmi3FunctionsC(
431450
L.push("fmi3Status fmi3Reset(fmi3Instance instance) { (void)instance; return fmi3OK; }");
432451
L.push("");
433452

434-
// Get/Set Float64
453+
// Get/Set Float64 (array-aware batching)
435454
L.push(
436455
"fmi3Status fmi3GetFloat64(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3Float64 value[], size_t nValues) {",
437456
);
438-
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
439-
L.push(" for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) value[i] = inst->model.vars[vr[i]]; }");
457+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
458+
L.push(" size_t vi = 0;");
459+
L.push(" for (size_t i = 0; i < nvr && vi < nValues; i++) {");
460+
L.push(" if (vr[i] < N_VARS) {");
461+
L.push(" size_t sz = varSizes[vr[i]];");
462+
L.push(" for (size_t j = 0; j < sz && vi < nValues; j++) value[vi++] = inst->model.vars[vr[i] + j];");
463+
L.push(" }");
464+
L.push(" }");
440465
L.push(" return fmi3OK;");
441466
L.push("}");
442467
L.push(
443468
"fmi3Status fmi3SetFloat64(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3Float64 value[], size_t nValues) {",
444469
);
445-
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
446-
L.push(" for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) inst->model.vars[vr[i]] = value[i]; }");
470+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
471+
L.push(" size_t vi = 0;");
472+
L.push(" for (size_t i = 0; i < nvr && vi < nValues; i++) {");
473+
L.push(" if (vr[i] < N_VARS) {");
474+
L.push(" size_t sz = varSizes[vr[i]];");
475+
L.push(" for (size_t j = 0; j < sz && vi < nValues; j++) inst->model.vars[vr[i] + j] = value[vi++];");
476+
L.push(" }");
477+
L.push(" }");
447478
L.push(" return fmi3OK;");
448479
L.push("}");
449480
L.push("");
450481

451-
// Get/Set Int32
482+
// Get/Set Int32 (array-aware batching)
452483
L.push(
453484
"fmi3Status fmi3GetInt32(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3Int32 value[], size_t nValues) {",
454485
);
455-
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
456-
L.push(" for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) value[i] = (fmi3Int32)inst->model.vars[vr[i]]; }");
486+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
487+
L.push(" size_t vi = 0;");
488+
L.push(" for (size_t i = 0; i < nvr && vi < nValues; i++) {");
489+
L.push(" if (vr[i] < N_VARS) {");
490+
L.push(" size_t sz = varSizes[vr[i]];");
491+
L.push(" for (size_t j = 0; j < sz && vi < nValues; j++) value[vi++] = (fmi3Int32)inst->model.vars[vr[i] + j];");
492+
L.push(" }");
493+
L.push(" }");
457494
L.push(" return fmi3OK;");
458495
L.push("}");
459496
L.push(
460497
"fmi3Status fmi3SetInt32(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3Int32 value[], size_t nValues) {",
461498
);
462-
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
463-
L.push(" for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) inst->model.vars[vr[i]] = (double)value[i]; }");
499+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
500+
L.push(" size_t vi = 0;");
501+
L.push(" for (size_t i = 0; i < nvr && vi < nValues; i++) {");
502+
L.push(" if (vr[i] < N_VARS) {");
503+
L.push(" size_t sz = varSizes[vr[i]];");
504+
L.push(" for (size_t j = 0; j < sz && vi < nValues; j++) inst->model.vars[vr[i] + j] = (double)value[vi++];");
505+
L.push(" }");
506+
L.push(" }");
464507
L.push(" return fmi3OK;");
465508
L.push("}");
466509
L.push("");
467510

468-
// Get/Set Boolean
511+
// Get/Set Boolean (array-aware batching)
469512
L.push(
470513
"fmi3Status fmi3GetBoolean(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3Boolean value[], size_t nValues) {",
471514
);
472-
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
473-
L.push(" for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) value[i] = inst->model.vars[vr[i]] != 0.0; }");
515+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
516+
L.push(" size_t vi = 0;");
517+
L.push(" for (size_t i = 0; i < nvr && vi < nValues; i++) {");
518+
L.push(" if (vr[i] < N_VARS) {");
519+
L.push(" size_t sz = varSizes[vr[i]];");
520+
L.push(" for (size_t j = 0; j < sz && vi < nValues; j++) value[vi++] = inst->model.vars[vr[i] + j] != 0.0;");
521+
L.push(" }");
522+
L.push(" }");
474523
L.push(" return fmi3OK;");
475524
L.push("}");
476525
L.push(
477526
"fmi3Status fmi3SetBoolean(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3Boolean value[], size_t nValues) {",
478527
);
528+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
529+
L.push(" size_t vi = 0;");
530+
L.push(" for (size_t i = 0; i < nvr && vi < nValues; i++) {");
531+
L.push(" if (vr[i] < N_VARS) {");
532+
L.push(" size_t sz = varSizes[vr[i]];");
533+
L.push(
534+
" for (size_t j = 0; j < sz && vi < nValues; j++) inst->model.vars[vr[i] + j] = value[vi++] ? 1.0 : 0.0;",
535+
);
536+
L.push(" }");
537+
L.push(" }");
538+
L.push(" return fmi3OK;");
539+
L.push("}");
540+
L.push("");
541+
542+
// Get/Set String
543+
L.push(
544+
"fmi3Status fmi3GetString(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, fmi3String value[], size_t nValues) {",
545+
);
479546
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
480-
L.push(" for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_VARS) inst->model.vars[vr[i]] = value[i] ? 1.0 : 0.0; }");
547+
L.push(
548+
' for (size_t i = 0; i < nvr; i++) { if (vr[i] < N_STRING_VARS) value[i] = inst->model.stringVars[vr[i]] ? inst->model.stringVars[vr[i]] : ""; }',
549+
);
550+
L.push(" return fmi3OK;");
551+
L.push("}");
552+
L.push(
553+
"fmi3Status fmi3SetString(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const fmi3String value[], size_t nValues) {",
554+
);
555+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance; (void)nValues;");
556+
L.push(" for (size_t i = 0; i < nvr; i++) {");
557+
L.push(" if (vr[i] < N_STRING_VARS) {");
558+
L.push(" if (inst->model.stringVars[vr[i]]) free(inst->model.stringVars[vr[i]]);");
559+
L.push(" inst->model.stringVars[vr[i]] = value[i] ? strdup(value[i]) : NULL;");
560+
L.push(" }");
561+
L.push(" }");
562+
L.push(" return fmi3OK;");
563+
L.push("}");
564+
L.push("");
565+
566+
// Get/Set Binary (FMI 3.0 opaque payload)
567+
L.push(
568+
"fmi3Status fmi3GetBinary(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, size_t sizes[], fmi3Binary value[], size_t nValues) {",
569+
);
570+
L.push(" (void)instance; (void)vr; (void)nvr; (void)sizes; (void)value; (void)nValues;");
571+
L.push(" /* Binary variables are not produced by Modelica — stub returns empty */");
572+
L.push(" for (size_t i = 0; i < nvr; i++) { sizes[i] = 0; value[i] = NULL; }");
573+
L.push(" return fmi3OK;");
574+
L.push("}");
575+
L.push(
576+
"fmi3Status fmi3SetBinary(fmi3Instance instance, const fmi3ValueReference vr[], size_t nvr, const size_t sizes[], const fmi3Binary value[], size_t nValues) {",
577+
);
578+
L.push(" (void)instance; (void)vr; (void)nvr; (void)sizes; (void)value; (void)nValues;");
579+
L.push(" /* Binary variables are not produced by Modelica — stub ignores */");
481580
L.push(" return fmi3OK;");
482581
L.push("}");
483582
L.push("");
@@ -627,6 +726,65 @@ function generateFmi3FunctionsC(
627726
L.push(
628727
"fmi3Status fmi3SetDebugLogging(fmi3Instance instance, fmi3Boolean loggingOn, size_t nCategories, const fmi3String categories[]) { ((FMU3Instance*)instance)->loggingOn = loggingOn; (void)nCategories; (void)categories; return fmi3OK; }",
629728
);
729+
L.push("");
730+
731+
// fmi3GetDirectionalDerivative — forward-mode Jacobian-vector product via finite differences
732+
L.push("/* Directional derivative: dz = (∂z/∂v) · dv (forward-mode via finite differences) */");
733+
L.push(
734+
"fmi3Status fmi3GetDirectionalDerivative(fmi3Instance instance, const fmi3ValueReference unknowns[], size_t nUnknowns, const fmi3ValueReference knowns[], size_t nKnowns, const fmi3Float64 seed[], size_t nSeed, fmi3Float64 sensitivity[], size_t nSensitivity) {",
735+
);
736+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
737+
L.push(" (void)nSeed; (void)nSensitivity;");
738+
L.push(" const double h = 1e-8;");
739+
L.push(" /* Save original known values */");
740+
L.push(" double* saved = (double*)malloc(nKnowns * sizeof(double));");
741+
L.push(" if (!saved) return fmi3Error;");
742+
L.push(" for (size_t i = 0; i < nKnowns; i++) saved[i] = inst->model.vars[knowns[i]];");
743+
L.push(" /* Evaluate f(v) */");
744+
L.push(" double* f0 = (double*)malloc(nUnknowns * sizeof(double));");
745+
L.push(" if (!f0) { free(saved); return fmi3Error; }");
746+
L.push(` ${id}_getDerivatives(&inst->model);`);
747+
L.push(" for (size_t i = 0; i < nUnknowns; i++) f0[i] = inst->model.vars[unknowns[i]];");
748+
L.push(" /* Perturb: v += h * seed */");
749+
L.push(" for (size_t i = 0; i < nKnowns; i++) inst->model.vars[knowns[i]] = saved[i] + h * seed[i];");
750+
L.push(` ${id}_getDerivatives(&inst->model);`);
751+
L.push(" /* sensitivity = (f(v + h*seed) - f(v)) / h */");
752+
L.push(" for (size_t i = 0; i < nUnknowns; i++) sensitivity[i] = (inst->model.vars[unknowns[i]] - f0[i]) / h;");
753+
L.push(" /* Restore */");
754+
L.push(" for (size_t i = 0; i < nKnowns; i++) inst->model.vars[knowns[i]] = saved[i];");
755+
L.push(` ${id}_getDerivatives(&inst->model);`);
756+
L.push(" free(saved); free(f0);");
757+
L.push(" return fmi3OK;");
758+
L.push("}");
759+
L.push("");
760+
761+
// fmi3GetAdjointDerivative — reverse-mode via column-wise finite differences
762+
L.push("/* Adjoint derivative: dv = (∂z/∂v)ᵀ · δz (reverse-mode via column-wise FD) */");
763+
L.push(
764+
"fmi3Status fmi3GetAdjointDerivative(fmi3Instance instance, const fmi3ValueReference unknowns[], size_t nUnknowns, const fmi3ValueReference knowns[], size_t nKnowns, const fmi3Float64 seed[], size_t nSeed, fmi3Float64 sensitivity[], size_t nSensitivity) {",
765+
);
766+
L.push(" FMU3Instance* inst = (FMU3Instance*)instance;");
767+
L.push(" (void)nSeed; (void)nSensitivity;");
768+
L.push(" const double h = 1e-8;");
769+
L.push(" /* Evaluate f(v) */");
770+
L.push(` ${id}_getDerivatives(&inst->model);`);
771+
L.push(" double* f0 = (double*)malloc(nUnknowns * sizeof(double));");
772+
L.push(" if (!f0) return fmi3Error;");
773+
L.push(" for (size_t i = 0; i < nUnknowns; i++) f0[i] = inst->model.vars[unknowns[i]];");
774+
L.push(" /* Column-wise FD: for each known, perturb and compute Jacobian column, then dot with seed */");
775+
L.push(" for (size_t j = 0; j < nKnowns; j++) {");
776+
L.push(" double orig = inst->model.vars[knowns[j]];");
777+
L.push(" inst->model.vars[knowns[j]] = orig + h;");
778+
L.push(` ${id}_getDerivatives(&inst->model);`);
779+
L.push(" double dot = 0.0;");
780+
L.push(" for (size_t i = 0; i < nUnknowns; i++) dot += ((inst->model.vars[unknowns[i]] - f0[i]) / h) * seed[i];");
781+
L.push(" sensitivity[j] = dot;");
782+
L.push(" inst->model.vars[knowns[j]] = orig;");
783+
L.push(" }");
784+
L.push(` ${id}_getDerivatives(&inst->model);`);
785+
L.push(" free(f0);");
786+
L.push(" return fmi3OK;");
787+
L.push("}");
630788

631789
// FMU state management
632790
L.push(

0 commit comments

Comments
 (0)