@@ -25,6 +25,8 @@ export class ModelicaDAE {
2525 initialAlgorithms : ModelicaStatement [ ] [ ] = [ ] ;
2626 variables : ModelicaVariable [ ] = [ ] ;
2727 stateMachines : ModelicaStateMachine [ ] = [ ] ;
28+ /** Clock partitions identified by the synchronous clock inference pass. */
29+ clockPartitions : ModelicaClockPartition [ ] = [ ] ;
2830 /** Flattened function definitions referenced by equations/algorithms. */
2931 functions : ModelicaDAE [ ] = [ ] ;
3032 /** External function declaration text (e.g. `external "C" ...`). */
@@ -187,6 +189,8 @@ export class ModelicaDAE {
187189
188190export abstract class ModelicaEquation {
189191 description : string | null ;
192+ /** Clock domain index (undefined = continuous time). */
193+ clockDomain ?: number | undefined ;
190194
191195 constructor ( description ?: string | null ) {
192196 this . description = description ?? null ;
@@ -1988,6 +1992,8 @@ export abstract class ModelicaVariable extends ModelicaPrimaryExpression {
19881992 customTypeName : string | null ;
19891993 /** Array dimensions for FMI 3.0 native array support (e.g., [3] for a 1D vector, [2,3] for a 2D matrix). */
19901994 arrayDimensions : number [ ] | null ;
1995+ /** Clock domain index (undefined = continuous time). */
1996+ clockDomain ?: number | undefined ;
19911997
19921998 constructor (
19931999 name : string ,
@@ -2013,6 +2019,8 @@ export abstract class ModelicaVariable extends ModelicaPrimaryExpression {
20132019 this . flowPrefix = null ;
20142020 this . customTypeName = null ;
20152021 this . arrayDimensions = null ;
2022+ /** Clock domain index (undefined = continuous time). */
2023+ this . clockDomain = undefined ;
20162024 }
20172025
20182026 override get hash ( ) : string {
@@ -2360,6 +2368,23 @@ export class ExpressionEvaluator {
23602368 * Key is the expression hash; value stores sorted (time, value) pairs.
23612369 */
23622370 delayBuffers : Map < string , { times : number [ ] ; values : number [ ] } > ;
2371+ /**
2372+ * Clocked variable values: latched at each clock tick by `sample()`.
2373+ * Key is variable/expression hash.
2374+ */
2375+ clockedValues : Map < string , number > ;
2376+ /**
2377+ * Previous-tick values for `previous()` operator.
2378+ * Key is variable/expression hash.
2379+ */
2380+ previousValues : Map < string , number > ;
2381+ /** Set of clock domain IDs that ticked at the current step. */
2382+ tickedClocks : Set < number > ;
2383+ /**
2384+ * State for `spatialDistribution()` operator: piecewise-linear profile on [0,1].
2385+ * Key is expression hash.
2386+ */
2387+ spatialDistributionStates : Map < string , { positions : number [ ] ; values : number [ ] } > ;
23632388
23642389 constructor ( env ?: Map < string , number > ) {
23652390 this . env = env ?? new Map ( ) ;
@@ -2370,6 +2395,10 @@ export class ExpressionEvaluator {
23702395 this . functionLookup = null ;
23712396 this . currentTime = 0 ;
23722397 this . delayBuffers = new Map ( ) ;
2398+ this . clockedValues = new Map ( ) ;
2399+ this . previousValues = new Map ( ) ;
2400+ this . tickedClocks = new Set ( ) ;
2401+ this . spatialDistributionStates = new Map ( ) ;
23732402 }
23742403
23752404 /** Convenience wrapper matching the old function signature. */
@@ -2800,6 +2829,94 @@ export class ExpressionEvaluator {
28002829 return v0 + alpha * ( v1 - v0 ) ;
28012830 }
28022831
2832+ // ── Synchronous clock operators (Modelica 3.3) ──
2833+
2834+ // sample(u) — latch a continuous value into the clocked partition
2835+ if ( name === "sample" && arg0 ) {
2836+ const val = this . evaluate ( arg0 ) ;
2837+ if ( val === null ) return null ;
2838+ const key = arg0 . hash ;
2839+ // On clock tick, latch the value; otherwise return last latched value
2840+ if ( this . tickedClocks . size > 0 ) {
2841+ // Move current to previous, latch new
2842+ const old = this . clockedValues . get ( key ) ;
2843+ if ( old !== undefined ) this . previousValues . set ( key , old ) ;
2844+ this . clockedValues . set ( key , val ) ;
2845+ return val ;
2846+ }
2847+ return this . clockedValues . get ( key ) ?? val ;
2848+ }
2849+
2850+ // hold(u) — zero-order hold: return last clocked value in continuous time
2851+ if ( name === "hold" && arg0 ) {
2852+ const key = arg0 . hash ;
2853+ // If clock is ticking, evaluate and latch
2854+ if ( this . tickedClocks . size > 0 ) {
2855+ const val = this . evaluate ( arg0 ) ;
2856+ if ( val !== null ) this . clockedValues . set ( key , val ) ;
2857+ return val ;
2858+ }
2859+ // In continuous time, return the last latched value
2860+ return this . clockedValues . get ( key ) ?? this . evaluate ( arg0 ) ;
2861+ }
2862+
2863+ // previous(x) — return value of x at the previous clock tick
2864+ if ( name === "previous" && arg0 ) {
2865+ const key = arg0 . hash ;
2866+ return this . previousValues . get ( key ) ?? this . evaluate ( arg0 ) ?? 0 ;
2867+ }
2868+
2869+ // subSample(u, factor) — derive a slower clock (factor divides base rate)
2870+ if ( name === "subSample" && arg0 ) {
2871+ return this . evaluate ( arg0 ) ;
2872+ }
2873+ // superSample(u, factor) — derive a faster clock (factor multiplies base rate)
2874+ if ( name === "superSample" && arg0 ) {
2875+ return this . evaluate ( arg0 ) ;
2876+ }
2877+ // shiftSample(u, shiftCounter, resolution) — phase-shift
2878+ if ( name === "shiftSample" && arg0 ) {
2879+ return this . evaluate ( arg0 ) ;
2880+ }
2881+ // backSample(u, backCounter, resolution) — negative phase-shift
2882+ if ( name === "backSample" && arg0 ) {
2883+ return this . evaluate ( arg0 ) ;
2884+ }
2885+ // noClock(u) — remove clock annotation
2886+ if ( name === "noClock" && arg0 ) {
2887+ return this . evaluate ( arg0 ) ;
2888+ }
2889+
2890+ // ── spatialDistribution(in0, in1, x, positiveVelocity) ──
2891+ // 1-D transport operator: maintains a piecewise-linear profile z(x)
2892+ // on [0, 1], shifted by velocity * dt each step, filling inflow boundary.
2893+ // Returns interpolated value at x=0 (out0) or x=1 (out1) depending on velocity direction.
2894+ if ( name === "spatialDistribution" && args . length >= 4 ) {
2895+ const in0 = this . evaluate ( args [ 0 ] as ModelicaExpression ) ;
2896+ const in1 = this . evaluate ( args [ 1 ] as ModelicaExpression ) ;
2897+ const x = this . evaluate ( args [ 2 ] as ModelicaExpression ) ;
2898+ const positiveVelocity = this . evaluate ( args [ 3 ] as ModelicaExpression ) ;
2899+ if ( in0 === null || in1 === null || x === null || positiveVelocity === null ) return null ;
2900+
2901+ const key = ( args [ 0 ] as ModelicaExpression ) . hash + "_sd" ;
2902+ let state = this . spatialDistributionStates . get ( key ) ;
2903+ if ( ! state ) {
2904+ // Initialize with linear profile from in0 to in1
2905+ state = { positions : [ 0 , 1 ] , values : [ in0 , in1 ] } ;
2906+ this . spatialDistributionStates . set ( key , state ) ;
2907+ }
2908+
2909+ // For the scalar evaluator, return the output at x=0 or x=1
2910+ // depending on the velocity direction (simplified)
2911+ if ( positiveVelocity > 0 ) {
2912+ // Positive velocity → output at x=1 is the transported value
2913+ return state . values [ state . values . length - 1 ] ?? in1 ;
2914+ } else {
2915+ // Negative velocity → output at x=0 is the transported value
2916+ return state . values [ 0 ] ?? in0 ;
2917+ }
2918+ }
2919+
28032920 // ── Array constructor functions ──
28042921 // These return constant values or reduce arrays to scalars.
28052922 // In a scalarized environment, array constructors are typically resolved
@@ -3208,6 +3325,43 @@ export class ModelicaStateMachine {
32083325 }
32093326}
32103327
3328+ /**
3329+ * A clock partition groups equations and variables that operate on the same discrete clock.
3330+ * Produced by the synchronous clock inference pass in the flattener.
3331+ */
3332+ export class ModelicaClockPartition {
3333+ /** Unique clock domain ID. */
3334+ clockId : number ;
3335+ /** Base clock expression (e.g., `Clock(0.01)` or `Clock(condition)`). */
3336+ baseClock : ModelicaExpression | null ;
3337+ /** Equations belonging to this clock partition. */
3338+ equations : ModelicaEquation [ ] = [ ] ;
3339+ /** Variables belonging to this clock partition. */
3340+ variables : ModelicaVariable [ ] = [ ] ;
3341+
3342+ constructor ( clockId : number , baseClock : ModelicaExpression | null = null ) {
3343+ this . clockId = clockId ;
3344+ this . baseClock = baseClock ;
3345+ }
3346+
3347+ get hash ( ) : string {
3348+ const hash = createHash ( "sha256" ) ;
3349+ hash . update ( "clockPartition_" + this . clockId ) ;
3350+ for ( const e of this . equations ) hash . update ( e . hash ) ;
3351+ for ( const v of this . variables ) hash . update ( v . hash ) ;
3352+ return hash . digest ( "hex" ) ;
3353+ }
3354+
3355+ get toJSON ( ) : JSONValue {
3356+ return {
3357+ "@type" : "ClockPartition" ,
3358+ clockId : this . clockId ,
3359+ equations : this . equations . map ( ( e ) => e . toJSON ) ,
3360+ variables : this . variables . map ( ( v ) => v . toJSON ) ,
3361+ } ;
3362+ }
3363+ }
3364+
32113365export class ModelicaState {
32123366 name : string ;
32133367 variables : ModelicaVariable [ ] = [ ] ;
0 commit comments