@@ -16,6 +16,7 @@ import {
1616 ModelicaArray ,
1717 ModelicaBinaryExpression ,
1818 ModelicaBooleanVariable ,
19+ ModelicaClockVariable ,
1920 ModelicaEnumerationVariable ,
2021 ModelicaFunctionCallExpression ,
2122 ModelicaIfElseExpression ,
@@ -49,8 +50,8 @@ export interface FmiScalarVariable {
4950 variability : FmiVariability ;
5051 /** Description string (optional). */
5152 description ?: string ;
52- /** Data type: Real, Integer, Boolean, String. */
53- type : "Real" | "Integer" | "Boolean" | "String" ;
53+ /** Data type: Real, Integer, Boolean, String, Clock . */
54+ type : "Real" | "Integer" | "Boolean" | "String" | "Clock" ;
5455 /** Start value (initial condition or default). */
5556 start ?: number ;
5657 /** SI unit string (optional). */
@@ -427,12 +428,13 @@ function mapVariable(v: ModelicaVariable, valueRef: number): FmiScalarVariable {
427428}
428429
429430/** Determine the FMI type from a Modelica variable. */
430- function mapType ( v : ModelicaVariable ) : "Real" | "Integer" | "Boolean" | "String" {
431+ function mapType ( v : ModelicaVariable ) : "Real" | "Integer" | "Boolean" | "String" | "Clock" {
431432 if ( v instanceof ModelicaRealVariable ) return "Real" ;
432433 if ( v instanceof ModelicaIntegerVariable ) return "Integer" ;
433434 if ( v instanceof ModelicaBooleanVariable ) return "Boolean" ;
434435 if ( v instanceof ModelicaStringVariable ) return "String" ;
435436 if ( v instanceof ModelicaEnumerationVariable ) return "Integer" ;
437+ if ( v instanceof ModelicaClockVariable ) return "Clock" ;
436438 return "Real" ;
437439}
438440
@@ -681,3 +683,196 @@ function formatUnknown(
681683 const kindsAttr = ` dependenciesKind="${ depItems . map ( ( d ) => d . kind ) . join ( " " ) } "` ;
682684 return ` <Unknown index="${ index } "${ depsAttr } ${ kindsAttr } />` ;
683685}
686+ interface Fmi3ArrayVariable {
687+ baseName : string ;
688+ startRef : number ;
689+ dimensions : number [ ] ;
690+ sv : FmiScalarVariable ;
691+ elements : FmiScalarVariable [ ] ;
692+ }
693+
694+ function groupFmi3Variables ( scalarVariables : FmiScalarVariable [ ] ) : ( FmiScalarVariable | Fmi3ArrayVariable ) [ ] {
695+ const result : ( FmiScalarVariable | Fmi3ArrayVariable ) [ ] = [ ] ;
696+ const arrayMap = new Map < string , Fmi3ArrayVariable > ( ) ;
697+
698+ for ( const sv of scalarVariables ) {
699+ const match = sv . name . match ( / ^ ( .+ ) \[ ( [ \d , ] + ) \] $ / ) ;
700+ if ( match ) {
701+ const baseName = match [ 1 ] as string ;
702+ const indices = ( match [ 2 ] as string ) . split ( "," ) . map ( ( s ) => parseInt ( s . trim ( ) , 10 ) ) ;
703+
704+ let arr = arrayMap . get ( baseName ) ;
705+ if ( ! arr ) {
706+ arr = {
707+ baseName,
708+ startRef : sv . valueReference ,
709+ dimensions : indices . map ( ( ) => 0 ) ,
710+ sv : { ...sv , name : baseName } ,
711+ elements : [ ] ,
712+ } ;
713+ arrayMap . set ( baseName , arr ) ;
714+ result . push ( arr ) ;
715+ }
716+
717+ const currentArr = arr as Fmi3ArrayVariable ;
718+ for ( let i = 0 ; i < indices . length ; i ++ ) {
719+ const ind = indices [ i ] as number ;
720+ const currentDim = currentArr . dimensions [ i ] as number ;
721+ if ( ind !== undefined && currentDim !== undefined ) {
722+ if ( ind > currentDim ) {
723+ currentArr . dimensions [ i ] = ind ;
724+ }
725+ }
726+ }
727+ currentArr . elements . push ( sv ) ;
728+ } else {
729+ result . push ( sv ) ;
730+ }
731+ }
732+ return result ;
733+ }
734+
735+ export function generateFmi3ModelDescriptionXml (
736+ variables : FmiScalarVariable [ ] ,
737+ opts : FmuOptions & {
738+ guid : string ;
739+ outputRefs : number [ ] ;
740+ derivativeRefs : number [ ] ;
741+ initialUnknownRefs : number [ ] ;
742+ fmuType : FmuTypeFlags ;
743+ nEventIndicators : number ;
744+ deps : Map < number , DepEntry2 [ ] > ;
745+ aliasMap : Map < string , string > ;
746+ enumTypes : Map < string , { name : string ; description : string | null } [ ] > ;
747+ } ,
748+ ) : string {
749+ const lines : string [ ] = [ ] ;
750+
751+ lines . push ( '<?xml version="1.0" encoding="UTF-8"?>' ) ;
752+ lines . push ( "<fmiModelDescription" ) ;
753+ lines . push ( ' fmiVersion="3.0"' ) ;
754+ lines . push ( ` modelName="${ escapeXml ( opts . modelIdentifier ) } "` ) ;
755+ lines . push ( ` instantiationToken="${ escapeXml ( opts . guid ) } "` ) ;
756+ if ( opts . description ) lines . push ( ` description="${ escapeXml ( opts . description ) } "` ) ;
757+ if ( opts . author ) lines . push ( ` author="${ escapeXml ( opts . author ) } "` ) ;
758+ lines . push ( ` generationTool="${ escapeXml ( opts . generationTool ?? "ModelScript" ) } "` ) ;
759+ lines . push ( ` generationDateAndTime="${ new Date ( ) . toISOString ( ) } "` ) ;
760+ lines . push ( ' variableNamingConvention="structured">' ) ;
761+
762+ if ( opts . fmuType . modelExchange ) {
763+ lines . push ( "" ) ;
764+ lines . push (
765+ ` <ModelExchange modelIdentifier="${ escapeXml ( opts . modelIdentifier ) } " providesDirectionalDerivative="true" />` ,
766+ ) ;
767+ }
768+
769+ if ( opts . fmuType . coSimulation ) {
770+ lines . push ( "" ) ;
771+ lines . push (
772+ ` <CoSimulation modelIdentifier="${ escapeXml ( opts . modelIdentifier ) } " canHandleVariableCommunicationStepSize="true" providesDirectionalDerivative="true" />` ,
773+ ) ;
774+ }
775+
776+ lines . push ( "" ) ;
777+ lines . push ( " <DefaultExperiment" ) ;
778+ lines . push ( ` startTime="${ opts . startTime ?? 0 } "` ) ;
779+ lines . push ( ` stopTime="${ opts . stopTime ?? 1 } "` ) ;
780+ lines . push ( ` stepSize="${ opts . stepSize ?? 0.001 } " />` ) ;
781+
782+ lines . push ( "" ) ;
783+ lines . push ( " <ModelVariables>" ) ;
784+ const groupedVars = groupFmi3Variables ( variables ) ;
785+ for ( const item of groupedVars ) {
786+ let sv : FmiScalarVariable ;
787+ let dimensions : number [ ] | null = null ;
788+ if ( "elements" in item ) {
789+ sv = item . sv ;
790+ dimensions = item . dimensions ;
791+ } else {
792+ sv = item ;
793+ }
794+
795+ lines . push ( ` <!-- ${ escapeXml ( sv . name ) } -->` ) ;
796+ const descAttr = sv . description ? ` description="${ escapeXml ( sv . description ) } "` : "" ;
797+ const causalityAttr = sv . causality === "independent" ? ' causality="independent"' : ` causality="${ sv . causality } "` ;
798+ const variabilityAttr = ` variability="${ sv . variability } "` ;
799+ const initialAttr = sv . initial ? ` initial="${ sv . initial } "` : "" ;
800+
801+ let fmi3Type = sv . type as string ;
802+ if ( fmi3Type === "Real" ) fmi3Type = "Float64" ;
803+ else if ( fmi3Type === "Integer" ) fmi3Type = "Int32" ;
804+ else if ( fmi3Type === "Clock" ) {
805+ lines . push (
806+ ` <Clock name="${ escapeXml ( sv . name ) } " valueReference="${ sv . valueReference } "${ causalityAttr } ${ descAttr } ` ,
807+ ) ;
808+ if ( sv . start !== undefined || sv . derivative !== undefined || sv . declaredType || dimensions ) {
809+ lines . push ( `>` ) ;
810+ if ( dimensions ) {
811+ for ( const d of dimensions ) lines . push ( ` <Dimension start="${ d } " />` ) ;
812+ }
813+ lines . push ( ` </Clock>` ) ;
814+ } else {
815+ lines [ lines . length - 1 ] += " />" ;
816+ }
817+ continue ;
818+ }
819+
820+ lines . push (
821+ ` <${ fmi3Type } name="${ escapeXml ( sv . name ) } " valueReference="${ sv . valueReference } "${ causalityAttr } ${ variabilityAttr } ${ initialAttr } ${ descAttr } ` ,
822+ ) ;
823+
824+ if ( sv . start !== undefined || sv . derivative !== undefined || sv . declaredType || dimensions ) {
825+ const startAttr = sv . start !== undefined ? ` start="${ sv . start } "` : "" ;
826+ const derivAttr = sv . derivative !== undefined ? ` derivative="${ sv . derivative } "` : "" ;
827+ const declTypeAttr = sv . declaredType ? ` declaredType="${ escapeXml ( sv . declaredType ) } "` : "" ;
828+
829+ if ( dimensions ) {
830+ lines . push ( `>` ) ;
831+ for ( const d of dimensions ) lines . push ( ` <Dimension start="${ d } " />` ) ;
832+ lines . push ( ` </${ fmi3Type } >` ) ;
833+ } else {
834+ lines . push ( ` ${ startAttr } ${ derivAttr } ${ declTypeAttr } />` ) ;
835+ }
836+ } else {
837+ lines [ lines . length - 1 ] += " />" ;
838+ }
839+ }
840+ lines . push ( " </ModelVariables>" ) ;
841+
842+ lines . push ( "" ) ;
843+ lines . push ( " <ModelStructure>" ) ;
844+
845+ if ( opts . outputRefs . length > 0 ) {
846+ for ( const ref of opts . outputRefs ) {
847+ lines . push ( formatFmi3Unknown ( "Output" , ref , opts . deps ) ) ;
848+ }
849+ }
850+
851+ if ( opts . derivativeRefs . length > 0 ) {
852+ for ( const ref of opts . derivativeRefs ) {
853+ lines . push ( formatFmi3Unknown ( "ContinuousStateDerivative" , ref , opts . deps ) ) ;
854+ }
855+ }
856+
857+ if ( opts . initialUnknownRefs . length > 0 ) {
858+ for ( const ref of opts . initialUnknownRefs ) {
859+ lines . push ( formatFmi3Unknown ( "InitialUnknown" , ref , opts . deps ) ) ;
860+ }
861+ }
862+
863+ lines . push ( " </ModelStructure>" ) ;
864+ lines . push ( "" ) ;
865+ lines . push ( "</fmiModelDescription>" ) ;
866+
867+ return lines . join ( "\n" ) ;
868+ }
869+
870+ function formatFmi3Unknown ( tagName : string , ref : number , deps : Map < number , DepEntry2 [ ] > ) : string {
871+ const entries = deps . get ( ref ) ;
872+ if ( ! entries || entries . length === 0 ) {
873+ return ` <${ tagName } valueReference="${ ref } " />` ;
874+ }
875+ const depsAttr = ` dependencies="${ entries . map ( ( e ) => e . vr ) . join ( " " ) } "` ;
876+ const kindsAttr = ` dependenciesKind="${ entries . map ( ( e ) => e . kind ) . join ( " " ) } "` ;
877+ return ` <${ tagName } valueReference="${ ref } "${ depsAttr } ${ kindsAttr } />` ;
878+ }
0 commit comments