@@ -817,6 +817,7 @@ export class ModelicaFlattener extends ModelicaModelVisitor<[string, ModelicaDAE
817817 if ( this . activeClassStack . length === 0 ) {
818818 this . #assembleStateMachines( args [ 1 ] ) ;
819819 this . #partitionClocks( args [ 1 ] ) ;
820+ this . #extractEventIndicators( args [ 1 ] ) ;
820821
821822 // Extract experiment annotation (StartTime, StopTime, Tolerance, Interval)
822823 for ( const ann of node . annotations ) {
@@ -2440,6 +2441,81 @@ export class ModelicaFlattener extends ModelicaModelVisitor<[string, ModelicaDAE
24402441 partitionMap . set ( i , new ModelicaClockPartition ( i ) ) ;
24412442 }
24422443
2444+ // Pass 2.5: Base-Clock Inference (GCD/LCM)
2445+ // Extract Clock(num, den) arguments from sample() calls and compute the global base clock
2446+ const clockExprs = new Map < number , ModelicaFunctionCallExpression > ( ) ;
2447+ for ( const eq of dae . equations ) {
2448+ if ( eq . clockDomain !== undefined && eq instanceof ModelicaSimpleEquation ) {
2449+ if ( ! clockExprs . has ( eq . clockDomain ) ) {
2450+ const findSample = ( expr : ModelicaExpression ) : ModelicaFunctionCallExpression | null => {
2451+ if ( expr instanceof ModelicaFunctionCallExpression && clockOps . has ( expr . functionName ) ) return expr ;
2452+ if ( expr instanceof ModelicaFunctionCallExpression ) {
2453+ for ( const arg of expr . args ) {
2454+ const found = findSample ( arg ) ;
2455+ if ( found ) return found ;
2456+ }
2457+ }
2458+ if ( "expression1" in expr && expr . expression1 ) {
2459+ const f = findSample ( expr . expression1 as ModelicaExpression ) ;
2460+ if ( f ) return f ;
2461+ }
2462+ if ( "expression2" in expr && expr . expression2 ) {
2463+ const f = findSample ( expr . expression2 as ModelicaExpression ) ;
2464+ if ( f ) return f ;
2465+ }
2466+ if ( "expression" in expr && expr . expression && expr . expression !== expr ) {
2467+ const f = findSample ( expr . expression as ModelicaExpression ) ;
2468+ if ( f ) return f ;
2469+ }
2470+ return null ;
2471+ } ;
2472+ const call = findSample ( eq . expression1 ) ?? findSample ( eq . expression2 ) ;
2473+ if ( call ) clockExprs . set ( eq . clockDomain , call ) ;
2474+ }
2475+ }
2476+ }
2477+
2478+ // Helper math functions for base-clock inference
2479+ const gcd = ( a : number , b : number ) : number => ( b === 0 ? a : gcd ( b , a % b ) ) ;
2480+ const lcm = ( a : number , b : number ) : number => ( a * b ) / gcd ( a , b ) ;
2481+
2482+ // Compute base clock: GCD(nums) / LCM(dens)
2483+ let baseNum = 0 ;
2484+ let baseDen = 1 ;
2485+ let hasFractionalClocks = false ;
2486+
2487+ for ( const call of clockExprs . values ( ) ) {
2488+ // Look for Clock(num, den) in the arguments of sample/hold
2489+ const clockArg = call . args . find ( ( a ) => a instanceof ModelicaFunctionCallExpression && a . functionName === "Clock" ) ;
2490+ if ( clockArg instanceof ModelicaFunctionCallExpression && clockArg . args . length === 2 ) {
2491+ const numExpr = clockArg . args [ 0 ] ;
2492+ const denExpr = clockArg . args [ 1 ] ;
2493+ if ( numExpr instanceof ModelicaIntegerLiteral && denExpr instanceof ModelicaIntegerLiteral ) {
2494+ const num = numExpr . value ;
2495+ const den = denExpr . value ;
2496+ if ( hasFractionalClocks ) {
2497+ baseNum = gcd ( baseNum , num ) ;
2498+ baseDen = lcm ( baseDen , den ) ;
2499+ } else {
2500+ baseNum = num ;
2501+ baseDen = den ;
2502+ hasFractionalClocks = true ;
2503+ }
2504+ }
2505+ }
2506+ }
2507+
2508+ const globalBaseClock = hasFractionalClocks
2509+ ? new ModelicaFunctionCallExpression ( "Clock" , [
2510+ new ModelicaIntegerLiteral ( baseNum ) ,
2511+ new ModelicaIntegerLiteral ( baseDen ) ,
2512+ ] )
2513+ : null ;
2514+
2515+ for ( const partition of partitionMap . values ( ) ) {
2516+ partition . baseClock = globalBaseClock ;
2517+ }
2518+
24432519 // Assign equations to partitions
24442520 for ( const eq of dae . equations ) {
24452521 if ( eq . clockDomain !== undefined ) {
@@ -2478,6 +2554,80 @@ export class ModelicaFlattener extends ModelicaModelVisitor<[string, ModelicaDAE
24782554
24792555 dae . clockPartitions = [ ...partitionMap . values ( ) ] . filter ( ( p ) => p . equations . length > 0 ) ;
24802556 }
2557+
2558+ /**
2559+ * Scans the fully assembled DAE for relational operators and `when` clauses.
2560+ * Extracts zero-crossing expressions into DAE `eventIndicators` and discrete
2561+ * updates into `whenClauses`.
2562+ */
2563+ #extractEventIndicators( dae : ModelicaDAE ) : void {
2564+ const indicators = new Set < string > ( ) ;
2565+
2566+ const extractExpr = ( expr : ModelicaExpression ) => {
2567+ if ( expr instanceof ModelicaBinaryExpression ) {
2568+ if (
2569+ expr . operator === ModelicaBinaryOperator . LESS_THAN ||
2570+ expr . operator === ModelicaBinaryOperator . LESS_THAN_OR_EQUAL ||
2571+ expr . operator === ModelicaBinaryOperator . GREATER_THAN ||
2572+ expr . operator === ModelicaBinaryOperator . GREATER_THAN_OR_EQUAL ||
2573+ expr . operator === ModelicaBinaryOperator . EQUALITY ||
2574+ expr . operator === ModelicaBinaryOperator . INEQUALITY
2575+ ) {
2576+ // Add `expr.operand1 - expr.operand2` as an event indicator
2577+ const diff = new ModelicaBinaryExpression ( ModelicaBinaryOperator . SUBTRACTION , expr . operand1 , expr . operand2 ) ;
2578+ const hash = diff . hash ;
2579+ if ( ! indicators . has ( hash ) ) {
2580+ indicators . add ( hash ) ;
2581+ dae . eventIndicators . push ( diff ) ;
2582+ }
2583+ }
2584+ extractExpr ( expr . operand1 ) ;
2585+ extractExpr ( expr . operand2 ) ;
2586+ } else if ( expr instanceof ModelicaUnaryExpression ) {
2587+ extractExpr ( expr . operand ) ;
2588+ } else if ( expr instanceof ModelicaFunctionCallExpression ) {
2589+ for ( const arg of expr . args ) extractExpr ( arg ) ;
2590+ } else if ( expr instanceof ModelicaIfElseExpression ) {
2591+ extractExpr ( expr . condition ) ;
2592+ extractExpr ( expr . thenExpression ) ;
2593+ for ( const clause of expr . elseIfClauses ) {
2594+ extractExpr ( clause . condition ) ;
2595+ extractExpr ( clause . expression ) ;
2596+ }
2597+ extractExpr ( expr . elseExpression ) ;
2598+ } else if ( expr instanceof ModelicaArray ) {
2599+ for ( const el of expr . elements ) extractExpr ( el ) ;
2600+ }
2601+ } ;
2602+
2603+ const extractEq = ( eq : ModelicaEquation ) => {
2604+ if ( eq instanceof ModelicaSimpleEquation ) {
2605+ extractExpr ( eq . expression1 ) ;
2606+ extractExpr ( eq . expression2 ) ;
2607+ } else if ( eq instanceof ModelicaIfEquation ) {
2608+ extractExpr ( eq . condition ) ;
2609+ for ( const e of eq . equations ) extractEq ( e ) ;
2610+ for ( const elseIf of eq . elseIfClauses ) {
2611+ extractExpr ( elseIf . condition ) ;
2612+ for ( const e of elseIf . equations ) extractEq ( e ) ;
2613+ }
2614+ for ( const e of eq . elseEquations ) extractEq ( e ) ;
2615+ } else if ( eq instanceof ModelicaWhenEquation ) {
2616+ dae . whenClauses . push ( eq ) ;
2617+ extractExpr ( eq . condition ) ;
2618+ for ( const e of eq . equations ) extractEq ( e ) ;
2619+ for ( const elseWhen of eq . elseWhenClauses ) {
2620+ extractExpr ( elseWhen . condition ) ;
2621+ for ( const e of elseWhen . equations ) extractEq ( e ) ;
2622+ }
2623+ } else if ( eq instanceof ModelicaForEquation ) {
2624+ for ( const e of eq . equations ) extractEq ( e ) ;
2625+ }
2626+ } ;
2627+
2628+ for ( const eq of dae . equations ) extractEq ( eq ) ;
2629+ for ( const eq of dae . initialEquations ) extractEq ( eq ) ;
2630+ }
24812631}
24822632
24832633/**
0 commit comments