|
| 1 | +// SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | + |
| 3 | +/** |
| 4 | + * Generate a Modelica wrapper model for multi-FMU co-simulation. |
| 5 | + * |
| 6 | + * Produces a valid `.mo` file containing component declarations for each |
| 7 | + * FMU participant (with `fileName` parameters and `Placement` annotations) |
| 8 | + * and optional `connect()` equations for wiring outputs to inputs. |
| 9 | + */ |
| 10 | + |
| 11 | +/** Descriptor for an FMU participant in the wrapper model. */ |
| 12 | +export interface WrapperFmuDescriptor { |
| 13 | + /** Modelica class name (e.g. "SineWave"). */ |
| 14 | + className: string; |
| 15 | + /** Instance name in the wrapper model (e.g. "sineWave"). */ |
| 16 | + instanceName: string; |
| 17 | + /** FMU file name (e.g. "SineWave.fmu" or "SineWave.xml"). */ |
| 18 | + fileName: string; |
| 19 | +} |
| 20 | + |
| 21 | +/** A connection between two FMU ports. */ |
| 22 | +export interface WrapperConnection { |
| 23 | + /** Qualified source (e.g. "sineWave.y"). */ |
| 24 | + source: string; |
| 25 | + /** Qualified target (e.g. "controller.u"). */ |
| 26 | + target: string; |
| 27 | +} |
| 28 | + |
| 29 | +/** |
| 30 | + * Generate a Modelica wrapper model source string from FMU descriptors. |
| 31 | + * |
| 32 | + * The generated model: |
| 33 | + * - Declares each FMU as a component with a `fileName` parameter |
| 34 | + * - Adds `Placement` annotations to lay out blocks in a horizontal row |
| 35 | + * - Includes `connect()` equations for any specified connections |
| 36 | + * |
| 37 | + * @param modelName Name of the wrapper model (e.g. "CosimWrapper") |
| 38 | + * @param fmus List of FMU participants |
| 39 | + * @param connections Optional connections between FMU ports |
| 40 | + * @returns Valid Modelica source text |
| 41 | + */ |
| 42 | +export function generateMultiModelWrapper( |
| 43 | + modelName: string, |
| 44 | + fmus: WrapperFmuDescriptor[], |
| 45 | + connections: WrapperConnection[] = [], |
| 46 | +): string { |
| 47 | + const lines: string[] = []; |
| 48 | + |
| 49 | + lines.push(`model ${modelName}`); |
| 50 | + lines.push(` "Multi-FMU co-simulation wrapper model"`); |
| 51 | + |
| 52 | + // Layout: distribute FMU blocks horizontally with spacing |
| 53 | + const spacing = 120; // Modelica units between block centers |
| 54 | + const blockSize = 40; // half-extent of each block |
| 55 | + const startX = -Math.floor(((fmus.length - 1) * spacing) / 2); |
| 56 | + |
| 57 | + for (const [i, fmu] of fmus.entries()) { |
| 58 | + const cx = startX + i * spacing; |
| 59 | + const cy = 0; |
| 60 | + const ext1x = cx - blockSize; |
| 61 | + const ext1y = cy - blockSize; |
| 62 | + const ext2x = cx + blockSize; |
| 63 | + const ext2y = cy + blockSize; |
| 64 | + |
| 65 | + lines.push(` ${fmu.className} ${fmu.instanceName}(fileName="${fmu.fileName}")`); |
| 66 | + lines.push( |
| 67 | + ` annotation(Placement(transformation(origin={${cx},${cy}}, extent={{${ext1x - cx},${ext1y - cy}},{${ext2x - cx},${ext2y - cy}}})));`, |
| 68 | + ); |
| 69 | + } |
| 70 | + |
| 71 | + if (connections.length > 0) { |
| 72 | + lines.push(`equation`); |
| 73 | + for (const conn of connections) { |
| 74 | + lines.push(` connect(${conn.source}, ${conn.target});`); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + lines.push(` annotation(`); |
| 79 | + lines.push(` Diagram(`); |
| 80 | + lines.push(` coordinateSystem(`); |
| 81 | + lines.push(` extent={{-200,-200},{200,200}},`); |
| 82 | + lines.push(` preserveAspectRatio=true`); |
| 83 | + lines.push(` )`); |
| 84 | + lines.push(` )`); |
| 85 | + lines.push(` );`); |
| 86 | + lines.push(`end ${modelName};`); |
| 87 | + lines.push(``); |
| 88 | + |
| 89 | + return lines.join("\n"); |
| 90 | +} |
0 commit comments