|
19 | 19 | import type { ModelicaExpression } from "./dae.js"; |
20 | 20 | import { |
21 | 21 | ModelicaBinaryExpression, |
| 22 | + ModelicaDAE, |
22 | 23 | ModelicaFunctionCallExpression, |
23 | 24 | ModelicaIntegerLiteral, |
24 | 25 | ModelicaNameExpression, |
25 | 26 | ModelicaRealLiteral, |
| 27 | + ModelicaRealVariable, |
| 28 | + ModelicaSimpleEquation, |
26 | 29 | ModelicaUnaryExpression, |
27 | 30 | } from "./dae.js"; |
28 | 31 | import { add, differentiateExpr, div, isOne, isZero, mul, simplifyExpr, sub, ZERO } from "./symbolic-diff.js"; |
29 | | -import { ModelicaBinaryOperator, ModelicaUnaryOperator } from "./syntax.js"; |
| 32 | +import { ModelicaBinaryOperator, ModelicaUnaryOperator, ModelicaVariability } from "./syntax.js"; |
30 | 33 |
|
31 | 34 | // ───────────────────────────────────────────────────────────────────── |
32 | 35 | // Public API |
@@ -81,7 +84,7 @@ function countOccurrences(expr: ModelicaExpression, varName: string): number { |
81 | 84 | /** |
82 | 85 | * Substitute all occurrences of `varName` with `replacement` in an expression. |
83 | 86 | */ |
84 | | -function substituteVariable( |
| 87 | +export function substituteVariable( |
85 | 88 | expr: ModelicaExpression, |
86 | 89 | varName: string, |
87 | 90 | replacement: ModelicaExpression, |
@@ -411,3 +414,121 @@ function isNegOne(expr: ModelicaExpression): boolean { |
411 | 414 | if (expr instanceof ModelicaIntegerLiteral) return expr.value === -1; |
412 | 415 | return false; |
413 | 416 | } |
| 417 | + |
| 418 | +// ───────────────────────────────────────────────────────────────────── |
| 419 | +// Alias Elimination |
| 420 | +// ───────────────────────────────────────────────────────────────────── |
| 421 | + |
| 422 | +/** |
| 423 | + * Substitute all occurrences of `varName` in all expressions of an equation. |
| 424 | + */ |
| 425 | +function substituteInEquation( |
| 426 | + eq: ModelicaSimpleEquation, |
| 427 | + varName: string, |
| 428 | + replacement: ModelicaExpression, |
| 429 | +): ModelicaSimpleEquation { |
| 430 | + const e1 = substituteVariable(eq.expression1, varName, replacement); |
| 431 | + const e2 = substituteVariable(eq.expression2, varName, replacement); |
| 432 | + if (e1 === eq.expression1 && e2 === eq.expression2) return eq; |
| 433 | + return new ModelicaSimpleEquation(e1, e2, eq.description); |
| 434 | +} |
| 435 | + |
| 436 | +/** |
| 437 | + * Detect if a `ModelicaSimpleEquation` is a trivial alias: `a = b` |
| 438 | + * where both sides are bare variable references (ModelicaNameExpression). |
| 439 | + * |
| 440 | + * @returns The pair [aliasVar, targetVar], or null. |
| 441 | + */ |
| 442 | +function detectTrivialAlias( |
| 443 | + eq: ModelicaSimpleEquation, |
| 444 | + unknowns: Set<string>, |
| 445 | +): { aliasVar: string; targetExpr: ModelicaExpression } | null { |
| 446 | + const lhs = eq.expression1; |
| 447 | + const rhs = eq.expression2; |
| 448 | + |
| 449 | + // Pattern: name = expr where name is an unknown |
| 450 | + if (lhs instanceof ModelicaNameExpression && unknowns.has(lhs.name)) { |
| 451 | + // a = b (trivial) or a = expr where expr doesn't contain a |
| 452 | + if (!containsVariable(rhs, lhs.name)) { |
| 453 | + return { aliasVar: lhs.name, targetExpr: rhs }; |
| 454 | + } |
| 455 | + } |
| 456 | + // Pattern: expr = name where name is an unknown |
| 457 | + if (rhs instanceof ModelicaNameExpression && unknowns.has(rhs.name)) { |
| 458 | + if (!containsVariable(lhs, rhs.name)) { |
| 459 | + return { aliasVar: rhs.name, targetExpr: lhs }; |
| 460 | + } |
| 461 | + } |
| 462 | + |
| 463 | + return null; |
| 464 | +} |
| 465 | + |
| 466 | +/** |
| 467 | + * Perform alias elimination on a DAE. |
| 468 | + * |
| 469 | + * Scans equations for trivial aliases (`a = b` or `a = expr`) where `a` is |
| 470 | + * a continuous unknown and `expr` does not reference `a`. Replaces all |
| 471 | + * occurrences of the alias variable with its target expression in all other |
| 472 | + * equations, then removes the alias equation and variable. |
| 473 | + * |
| 474 | + * This reduces the system size and eliminates redundant unknowns before |
| 475 | + * BLT analysis. |
| 476 | + */ |
| 477 | +export function eliminateAliases(dae: ModelicaDAE): void { |
| 478 | + // Build the set of continuous unknowns (same logic as BLT) |
| 479 | + const unknowns = new Set<string>(); |
| 480 | + for (const v of dae.variables) { |
| 481 | + if (v instanceof ModelicaRealVariable && v.variability === null) { |
| 482 | + unknowns.add(v.name); |
| 483 | + } |
| 484 | + } |
| 485 | + |
| 486 | + // Iterate until no more aliases are found (substitution may reveal new aliases) |
| 487 | + let changed = true; |
| 488 | + while (changed) { |
| 489 | + changed = false; |
| 490 | + |
| 491 | + for (let i = 0; i < dae.equations.length; i++) { |
| 492 | + const eq = dae.equations[i]; |
| 493 | + if (!(eq instanceof ModelicaSimpleEquation)) continue; |
| 494 | + |
| 495 | + const alias = detectTrivialAlias(eq, unknowns); |
| 496 | + if (!alias) continue; |
| 497 | + |
| 498 | + const { aliasVar, targetExpr } = alias; |
| 499 | + |
| 500 | + // Don't eliminate parameters, constants, or derivatives |
| 501 | + const varDef = dae.variables.find((v) => v.name === aliasVar); |
| 502 | + if (!varDef) continue; |
| 503 | + if (varDef.variability === ModelicaVariability.PARAMETER || varDef.variability === ModelicaVariability.CONSTANT) { |
| 504 | + continue; |
| 505 | + } |
| 506 | + if (aliasVar.startsWith("der(")) continue; |
| 507 | + |
| 508 | + // Don't eliminate variables that appear in the target expression |
| 509 | + // (would create cycles) |
| 510 | + if (containsVariable(targetExpr, aliasVar)) continue; |
| 511 | + |
| 512 | + // Substitute aliasVar → targetExpr in all OTHER equations |
| 513 | + for (let j = 0; j < dae.equations.length; j++) { |
| 514 | + if (j === i) continue; |
| 515 | + const otherEq = dae.equations[j]; |
| 516 | + if (!(otherEq instanceof ModelicaSimpleEquation)) continue; |
| 517 | + dae.equations[j] = substituteInEquation(otherEq, aliasVar, targetExpr); |
| 518 | + } |
| 519 | + |
| 520 | + // Remove the alias equation |
| 521 | + dae.equations.splice(i, 1); |
| 522 | + |
| 523 | + // Remove the alias variable |
| 524 | + const varIdx = dae.variables.findIndex((v) => v.name === aliasVar); |
| 525 | + if (varIdx >= 0) dae.variables.splice(varIdx, 1); |
| 526 | + |
| 527 | + // Remove from unknowns set |
| 528 | + unknowns.delete(aliasVar); |
| 529 | + |
| 530 | + changed = true; |
| 531 | + break; // restart scan from the beginning |
| 532 | + } |
| 533 | + } |
| 534 | +} |
0 commit comments