Skip to content

Commit a0bfa4a

Browse files
committed
feat(core): sparse dependency kinds, cs event mode state machine, fmu build metadata
1 parent daf9d9d commit a0bfa4a

4 files changed

Lines changed: 120 additions & 32 deletions

File tree

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

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -295,14 +295,34 @@ function detectAliases(dae: ModelicaDAE, scalarVariables: FmiScalarVariable[]):
295295
* Compute variable dependency graph for ModelStructure.
296296
* For each output/derivative/initial-unknown, find which inputs/states it depends on.
297297
*/
298+
/** A single entry in the enriched FMI 2.0 dependency graph. */
299+
interface DepEntry2 {
300+
vr: number;
301+
kind: "dependent" | "constant" | "fixed" | "tunable";
302+
}
303+
304+
/** Map FMI 2.0 variability to dependency kind. */
305+
function variabilityToDepKind2(v: FmiVariability): DepEntry2["kind"] {
306+
switch (v) {
307+
case "constant":
308+
return "constant";
309+
case "fixed":
310+
return "fixed";
311+
case "tunable":
312+
return "tunable";
313+
default:
314+
return "dependent";
315+
}
316+
}
317+
298318
function computeDependencies(
299319
dae: ModelicaDAE,
300320
scalarVariables: FmiScalarVariable[],
301321
outputRefs: number[],
302322
derivativeRefs: number[],
303323
initialUnknownRefs: number[],
304-
): Map<number, number[]> {
305-
const deps = new Map<number, number[]>();
324+
): Map<number, DepEntry2[]> {
325+
const deps = new Map<number, DepEntry2[]>();
306326
const svByName = new Map<string, FmiScalarVariable>();
307327
for (const sv of scalarVariables) svByName.set(sv.name, sv);
308328

@@ -326,18 +346,18 @@ function computeDependencies(
326346
const rhsNames = equationDeps.get(sv.name);
327347
if (!rhsNames) continue;
328348

329-
// Map referenced names to their value references
330-
const depRefs: number[] = [];
349+
// Map referenced names to their value references with enriched kinds
350+
const entries: DepEntry2[] = [];
331351
for (const name of rhsNames) {
332352
const depSv = svByName.get(name);
333353
if (depSv && depSv.valueReference !== ref) {
334-
depRefs.push(depSv.valueReference);
354+
entries.push({ vr: depSv.valueReference, kind: variabilityToDepKind2(depSv.variability) });
335355
}
336356
}
337-
if (depRefs.length > 0) {
357+
if (entries.length > 0) {
338358
deps.set(
339359
ref,
340-
depRefs.sort((a, b) => a - b),
360+
entries.sort((a, b) => a.vr - b.vr),
341361
);
342362
}
343363
}
@@ -506,7 +526,7 @@ function generateModelDescriptionXml(
506526
initialUnknownRefs: number[];
507527
fmuType: FmuTypeFlags;
508528
nEventIndicators: number;
509-
deps: Map<number, number[]>;
529+
deps: Map<number, DepEntry2[]>;
510530
aliasMap: Map<string, string>;
511531
enumTypes: Map<string, { name: string; description: string | null }[]>;
512532
},
@@ -654,16 +674,18 @@ function generateModelDescriptionXml(
654674
function formatUnknown(
655675
index: number,
656676
ref: number,
657-
deps: Map<number, number[]>,
677+
deps: Map<number, DepEntry2[]>,
658678
variables: FmiScalarVariable[],
659679
): string {
660-
const depRefs = deps.get(ref);
661-
if (!depRefs || depRefs.length === 0) {
680+
const entries = deps.get(ref);
681+
if (!entries || entries.length === 0) {
662682
return ` <Unknown index="${index}" />`;
663683
}
664684
// Convert VRs to 1-based indices
665-
const depIndices = depRefs.map((vr) => variables.findIndex((v) => v.valueReference === vr) + 1).filter((i) => i > 0);
666-
const depsAttr = ` dependencies="${depIndices.join(" ")}"`;
667-
const kindsAttr = ` dependenciesKind="${depIndices.map(() => "dependent").join(" ")}"`;
685+
const depItems = entries
686+
.map((e) => ({ idx: variables.findIndex((v) => v.valueReference === e.vr) + 1, kind: e.kind }))
687+
.filter((d) => d.idx > 0);
688+
const depsAttr = ` dependencies="${depItems.map((d) => d.idx).join(" ")}"`;
689+
const kindsAttr = ` dependenciesKind="${depItems.map((d) => d.kind).join(" ")}"`;
668690
return ` <Unknown index="${index}"${depsAttr}${kindsAttr} />`;
669691
}

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

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -543,14 +543,34 @@ function detectAliases3(dae: ModelicaDAE, variables: Fmi3Variable[]): Map<string
543543
return aliasMap;
544544
}
545545

546+
/** A single entry in the enriched dependency graph. */
547+
interface DepEntry3 {
548+
vr: number;
549+
kind: "dependent" | "constant" | "fixed" | "tunable";
550+
}
551+
552+
/** Map variable variability to FMI dependency kind. */
553+
function variabilityToDepKind(v: Fmi3Variability): DepEntry3["kind"] {
554+
switch (v) {
555+
case "constant":
556+
return "constant";
557+
case "fixed":
558+
return "fixed";
559+
case "tunable":
560+
return "tunable";
561+
default:
562+
return "dependent";
563+
}
564+
}
565+
546566
function computeDependencies3(
547567
dae: ModelicaDAE,
548568
variables: Fmi3Variable[],
549569
outputRefs: number[],
550570
derivativeRefs: number[],
551571
initialUnknownRefs: number[],
552-
): Map<number, number[]> {
553-
const deps = new Map<number, number[]>();
572+
): Map<number, DepEntry3[]> {
573+
const deps = new Map<number, DepEntry3[]>();
554574
const svByName = new Map<string, Fmi3Variable>();
555575
for (const sv of variables) svByName.set(sv.name, sv);
556576

@@ -572,17 +592,17 @@ function computeDependencies3(
572592
const rhsNames = equationDeps.get(sv.name);
573593
if (!rhsNames) continue;
574594

575-
const depRefs: number[] = [];
595+
const entries: DepEntry3[] = [];
576596
for (const name of rhsNames) {
577597
const depSv = svByName.get(name);
578598
if (depSv && depSv.valueReference !== ref) {
579-
depRefs.push(depSv.valueReference);
599+
entries.push({ vr: depSv.valueReference, kind: variabilityToDepKind(depSv.variability) });
580600
}
581601
}
582-
if (depRefs.length > 0) {
602+
if (entries.length > 0) {
583603
deps.set(
584604
ref,
585-
depRefs.sort((a, b) => a - b),
605+
entries.sort((a, b) => a.vr - b.vr),
586606
);
587607
}
588608
}
@@ -816,7 +836,7 @@ function generateModelDescriptionXml3(
816836
clockRefs: number[];
817837
fmuType: Fmi3TypeFlags;
818838
nEventIndicators: number;
819-
deps: Map<number, number[]>;
839+
deps: Map<number, DepEntry3[]>;
820840
aliasMap: Map<string, string>;
821841
enumTypes: Map<string, { name: string; description: string | null }[]>;
822842
terminals: Fmi3Terminal[];
@@ -1026,13 +1046,13 @@ function generateModelDescriptionXml3(
10261046
}
10271047

10281048
/** Format an Unknown/Output/ContinuousStateDerivative element. */
1029-
function formatUnknown3(elementName: string, ref: number, deps: Map<number, number[]>): string {
1030-
const depRefs = deps.get(ref);
1031-
if (!depRefs || depRefs.length === 0) {
1049+
function formatUnknown3(elementName: string, ref: number, deps: Map<number, DepEntry3[]>): string {
1050+
const entries = deps.get(ref);
1051+
if (!entries || entries.length === 0) {
10321052
return ` <${elementName} valueReference="${ref}" />`;
10331053
}
1034-
const depsAttr = ` dependencies="${depRefs.join(" ")}"`;
1035-
const kindsAttr = ` dependenciesKind="${depRefs.map(() => "dependent").join(" ")}"`;
1054+
const depsAttr = ` dependencies="${entries.map((e) => e.vr).join(" ")}"`;
1055+
const kindsAttr = ` dependenciesKind="${entries.map((e) => e.kind).join(" ")}"`;
10361056
return ` <${elementName} valueReference="${ref}"${depsAttr}${kindsAttr} />`;
10371057
}
10381058

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,19 @@ export function buildFmuArchive(
9595
}
9696
}
9797

98+
// ── buildDescription.xml (FMI 3.0 §2.5) ──
99+
const buildDescLines = [
100+
'<?xml version="1.0" encoding="UTF-8"?>',
101+
'<fmiBuildDescription fmiVersion="3.0">',
102+
` <BuildConfiguration modelIdentifier="${id}">`,
103+
' <SourceFileSet language="C">',
104+
` <SourceFile name="${id}_model.c" />`,
105+
" </SourceFileSet>",
106+
" </BuildConfiguration>",
107+
"</fmiBuildDescription>",
108+
];
109+
files.set("extra/buildDescription.xml", encoder.encode(buildDescLines.join("\n")));
110+
98111
// ── Build ZIP archive ──
99112
const archive = createZip(files);
100113

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

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,14 @@ function generateFmi3FunctionsC(
371371
L.push(" fmi3Float64 startTime;");
372372
L.push(" fmi3Float64 stopTime;");
373373
L.push(" fmi3Float64 stepSize;");
374+
L.push(" fmi3Boolean eventModeUsed;");
375+
L.push(" fmi3Boolean earlyReturnAllowed;");
376+
L.push(" int state; /* 0=INIT, 1=STEP, 2=EVENT, 3=TERMINATED */");
374377
L.push("} FMU3Instance;");
378+
L.push("#define FMU3_STATE_INIT 0");
379+
L.push("#define FMU3_STATE_STEP 1");
380+
L.push("#define FMU3_STATE_EVENT 2");
381+
L.push("#define FMU3_STATE_TERM 3");
375382
L.push("");
376383
L.push("/* Variable size lookup table for array batching (FMI 3.0 §2.2.7) */");
377384
L.push("static const size_t varSizes[N_VARS + 1] = {");
@@ -402,7 +409,6 @@ function generateFmi3FunctionsC(
402409
L.push(" fmi3InstanceEnvironment instanceEnvironment, fmi3LogMessageCallback logMessage,");
403410
L.push(" fmi3IntermediateUpdateCallback intermediateUpdate) {");
404411
L.push(" (void)instantiationToken; (void)resourcePath; (void)visible;");
405-
L.push(" (void)eventModeUsed; (void)earlyReturnAllowed;");
406412
L.push(" (void)requiredIntermediateVariables; (void)nRequiredIntermediateVariables;");
407413
L.push(" FMU3Instance* inst = (FMU3Instance*)calloc(1, sizeof(FMU3Instance));");
408414
L.push(" if (!inst) return NULL;");
@@ -412,6 +418,9 @@ function generateFmi3FunctionsC(
412418
L.push(" inst->instanceEnvironment = instanceEnvironment;");
413419
L.push(" inst->loggingOn = loggingOn;");
414420
L.push(" inst->stepSize = 0.001;");
421+
L.push(" inst->eventModeUsed = eventModeUsed;");
422+
L.push(" inst->earlyReturnAllowed = earlyReturnAllowed;");
423+
L.push(" inst->state = FMU3_STATE_INIT;");
415424
L.push(` ${id}_initialize(&inst->model);`);
416425
L.push(" return (fmi3Instance)inst;");
417426
L.push("}");
@@ -444,10 +453,16 @@ function generateFmi3FunctionsC(
444453
L.push(" if (stopTimeDefined) inst->stopTime = stopTime;");
445454
L.push(" return fmi3OK;");
446455
L.push("}");
447-
L.push("fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) { (void)instance; return fmi3OK; }");
448-
L.push("fmi3Status fmi3Terminate(fmi3Instance instance) { (void)instance; return fmi3OK; }");
456+
L.push(
457+
"fmi3Status fmi3ExitInitializationMode(fmi3Instance instance) { ((FMU3Instance*)instance)->state = FMU3_STATE_STEP; return fmi3OK; }",
458+
);
459+
L.push(
460+
"fmi3Status fmi3Terminate(fmi3Instance instance) { ((FMU3Instance*)instance)->state = FMU3_STATE_TERM; return fmi3OK; }",
461+
);
449462
L.push("void fmi3FreeInstance(fmi3Instance instance) { if (instance) free(instance); }");
450-
L.push("fmi3Status fmi3Reset(fmi3Instance instance) { (void)instance; return fmi3OK; }");
463+
L.push(
464+
"fmi3Status fmi3Reset(fmi3Instance instance) { ((FMU3Instance*)instance)->state = FMU3_STATE_INIT; return fmi3OK; }",
465+
);
451466
L.push("");
452467

453468
const numericTypes = ["Float32", "Float64", "Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64"];
@@ -710,6 +725,20 @@ function generateFmi3FunctionsC(
710725
);
711726
L.push(" if (canReturnEarly) { *earlyReturn = fmi3True; *lastSuccessfulTime = t; return fmi3OK; }");
712727
L.push(" }");
728+
L.push(" /* FMI 3.0: CS Event Mode — check event indicators for zero crossings */");
729+
L.push(" if (inst->eventModeUsed) {");
730+
L.push(" double ei[N_EVENT_INDICATORS + 1];");
731+
L.push(` ${id}_getEventIndicators(&inst->model, ei);`);
732+
L.push(" for (int k = 0; k < N_EVENT_INDICATORS; k++) {");
733+
L.push(" if ((inst->model.eventPrev[k] <= 0 && ei[k] > 0) || (inst->model.eventPrev[k] >= 0 && ei[k] < 0)) {");
734+
L.push(" *eventHandlingNeeded = fmi3True;");
735+
L.push(" if (inst->earlyReturnAllowed) { *earlyReturn = fmi3True; *lastSuccessfulTime = t; }");
736+
L.push(" break;");
737+
L.push(" }");
738+
L.push(" }");
739+
L.push(" for (int k = 0; k < N_EVENT_INDICATORS; k++) inst->model.eventPrev[k] = ei[k];");
740+
L.push(" if (*earlyReturn) return fmi3OK;");
741+
L.push(" }");
713742
L.push(" }");
714743
L.push(" inst->model.time = tEnd; *lastSuccessfulTime = tEnd;");
715744
L.push(" return fmi3OK;");
@@ -718,9 +747,13 @@ function generateFmi3FunctionsC(
718747

719748
// Remaining stubs
720749
L.push("/* --- Stubs --- */");
721-
L.push("fmi3Status fmi3EnterEventMode(fmi3Instance instance) { (void)instance; return fmi3OK; }");
750+
L.push(
751+
"fmi3Status fmi3EnterEventMode(fmi3Instance instance) { ((FMU3Instance*)instance)->state = FMU3_STATE_EVENT; return fmi3OK; }",
752+
);
722753
L.push("fmi3Status fmi3EnterContinuousTimeMode(fmi3Instance instance) { (void)instance; return fmi3OK; }");
723-
L.push("fmi3Status fmi3EnterStepMode(fmi3Instance instance) { (void)instance; return fmi3OK; }");
754+
L.push(
755+
"fmi3Status fmi3EnterStepMode(fmi3Instance instance) { ((FMU3Instance*)instance)->state = FMU3_STATE_STEP; return fmi3OK; }",
756+
);
724757
L.push(
725758
"fmi3Status fmi3CompletedIntegratorStep(fmi3Instance instance, fmi3Boolean noSetFMUStatePriorToCurrentPoint, fmi3Boolean* enterEventMode, fmi3Boolean* terminateSimulation) { (void)instance; (void)noSetFMUStatePriorToCurrentPoint; *enterEventMode = fmi3False; *terminateSimulation = fmi3False; return fmi3OK; }",
726759
);

0 commit comments

Comments
 (0)