Skip to content

Commit 9162552

Browse files
committed
feat(core): modelica cas bindings with 14 ModelScript.CAS.* functions, package declaration, compile-time dispatch
1 parent 19040a5 commit 9162552

2 files changed

Lines changed: 354 additions & 0 deletions

File tree

Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+
/**
4+
* Modelica CAS bindings module.
5+
*
6+
* Provides a `ModelScript.CAS` package that exposes the CAS engine's
7+
* capabilities as Modelica-callable functions. These functions operate
8+
* on ModelicaExpression ASTs at compile-time (during flattening) and
9+
* can be used in annotations, parameter evaluation, and symbolic
10+
* transformations.
11+
*
12+
* Runtime bindings are registered via `registerCASFunctions()` which
13+
* hooks into the ExpressionEvaluator's `functionLookup` callback.
14+
*/
15+
16+
import type { ModelicaExpression } from "../dae.js";
17+
import { ModelicaFunctionCallExpression, ModelicaNameExpression, ModelicaRealLiteral } from "../dae.js";
18+
import { differentiateExpr, simplifyExpr } from "../symbolic-diff.js";
19+
import { egraphSimplify } from "./egraph.js";
20+
import { collectTerms, expandExpr, getLiteralValue, normalizeExpr } from "./expand.js";
21+
import { factorQuadratic, rationalRoots } from "./factor.js";
22+
import { integrateExpr, limit, nthDerivative, taylorSeries } from "./integrate.js";
23+
import { solveForVariable } from "./solve.js";
24+
import { trigExpand, trigSimplify } from "./trigsimp.js";
25+
26+
// ─────────────────────────────────────────────────────────────────────
27+
// Compile-Time CAS Operations
28+
// ─────────────────────────────────────────────────────────────────────
29+
30+
/**
31+
* Registry of CAS functions that operate on ModelicaExpression ASTs.
32+
* These are invoked at compile-time during flattening when the
33+
* function name matches `ModelScript.CAS.*`.
34+
*/
35+
export const CAS_FUNCTIONS = new Map<string, (args: ModelicaExpression[]) => ModelicaExpression | null>([
36+
// ── Simplification ──
37+
[
38+
"ModelScript.CAS.simplify",
39+
(args) => {
40+
if (args.length < 1 || !args[0]) return null;
41+
return egraphSimplify(args[0]);
42+
},
43+
],
44+
45+
[
46+
"ModelScript.CAS.expand",
47+
(args) => {
48+
if (args.length < 1 || !args[0]) return null;
49+
return expandExpr(args[0]);
50+
},
51+
],
52+
53+
[
54+
"ModelScript.CAS.normalize",
55+
(args) => {
56+
if (args.length < 1 || !args[0]) return null;
57+
return normalizeExpr(args[0]);
58+
},
59+
],
60+
61+
[
62+
"ModelScript.CAS.trigSimplify",
63+
(args) => {
64+
if (args.length < 1 || !args[0]) return null;
65+
return trigSimplify(args[0]);
66+
},
67+
],
68+
69+
[
70+
"ModelScript.CAS.trigExpand",
71+
(args) => {
72+
if (args.length < 1 || !args[0]) return null;
73+
return trigExpand(args[0]);
74+
},
75+
],
76+
77+
// ── Differentiation ──
78+
[
79+
"ModelScript.CAS.diff",
80+
(args) => {
81+
if (args.length < 2 || !args[0] || !args[1]) return null;
82+
const varName = extractVarName(args[1]);
83+
if (!varName) return null;
84+
if (args.length >= 3 && args[2]) {
85+
const n = getLiteralValue(args[2]);
86+
if (n !== null && n > 0) return nthDerivative(args[0], varName, n);
87+
}
88+
return simplifyExpr(differentiateExpr(args[0], varName));
89+
},
90+
],
91+
92+
// ── Integration ──
93+
[
94+
"ModelScript.CAS.integrate",
95+
(args) => {
96+
if (args.length < 2 || !args[0] || !args[1]) return null;
97+
const varName = extractVarName(args[1]);
98+
if (!varName) return null;
99+
return integrateExpr(args[0], varName);
100+
},
101+
],
102+
103+
// ── Solving ──
104+
[
105+
"ModelScript.CAS.solve",
106+
(args) => {
107+
if (args.length < 2 || !args[0] || !args[1]) return null;
108+
const varName = extractVarName(args[1]);
109+
if (!varName) return null;
110+
const solutions = solveForVariable(args[0], varName);
111+
// Return first solution (most useful for single-variable equations)
112+
return solutions.length > 0 ? (solutions[0] ?? null) : null;
113+
},
114+
],
115+
116+
[
117+
"ModelScript.CAS.solveAll",
118+
(args) => {
119+
if (args.length < 2 || !args[0] || !args[1]) return null;
120+
const varName = extractVarName(args[1]);
121+
if (!varName) return null;
122+
const solutions = solveForVariable(args[0], varName);
123+
if (solutions.length === 0) return null;
124+
// Return as an array-like nested expression
125+
return solutions.length === 1 ? (solutions[0] ?? null) : buildArray(solutions);
126+
},
127+
],
128+
129+
// ── Factoring ──
130+
[
131+
"ModelScript.CAS.factor",
132+
(args) => {
133+
if (args.length < 2 || !args[0] || !args[1]) return null;
134+
const varName = extractVarName(args[1]);
135+
if (!varName) return null;
136+
return factorQuadratic(args[0], varName);
137+
},
138+
],
139+
140+
// ── Taylor Series ──
141+
[
142+
"ModelScript.CAS.taylor",
143+
(args) => {
144+
if (args.length < 4 || !args[0] || !args[1] || !args[2] || !args[3]) return null;
145+
const varName = extractVarName(args[1]);
146+
if (!varName) return null;
147+
const point = getLiteralValue(args[2]);
148+
const order = getLiteralValue(args[3]);
149+
if (point === null || order === null) return null;
150+
return taylorSeries(args[0], varName, point, Math.round(order));
151+
},
152+
],
153+
154+
// ── Limit ──
155+
[
156+
"ModelScript.CAS.limit",
157+
(args) => {
158+
if (args.length < 3 || !args[0] || !args[1] || !args[2]) return null;
159+
const varName = extractVarName(args[1]);
160+
if (!varName) return null;
161+
const point = getLiteralValue(args[2]);
162+
if (point === null) return null;
163+
const result = limit(args[0], varName, point);
164+
return result !== null ? new ModelicaRealLiteral(result) : null;
165+
},
166+
],
167+
168+
// ── Degree ──
169+
[
170+
"ModelScript.CAS.degree",
171+
(args) => {
172+
if (args.length < 2 || !args[0] || !args[1]) return null;
173+
const varName = extractVarName(args[1]);
174+
if (!varName) return null;
175+
const terms = collectTerms(args[0], varName);
176+
const maxDeg = Math.max(0, ...terms.keys());
177+
return new ModelicaRealLiteral(maxDeg);
178+
},
179+
],
180+
181+
// ── Rational Roots ──
182+
[
183+
"ModelScript.CAS.roots",
184+
(args) => {
185+
if (args.length < 2 || !args[0] || !args[1]) return null;
186+
const varName = extractVarName(args[1]);
187+
if (!varName) return null;
188+
const roots = rationalRoots(args[0], varName);
189+
if (roots.length === 0) return null;
190+
return buildArray(roots.map((r) => new ModelicaRealLiteral(r)));
191+
},
192+
],
193+
]);
194+
195+
// ─────────────────────────────────────────────────────────────────────
196+
// Compile-Time Dispatch
197+
// ─────────────────────────────────────────────────────────────────────
198+
199+
/**
200+
* Attempt to evaluate a CAS function call at compile-time.
201+
* Returns the result expression, or null if the function is not a CAS function.
202+
*/
203+
export function evaluateCASFunction(functionName: string, args: ModelicaExpression[]): ModelicaExpression | null {
204+
const fn = CAS_FUNCTIONS.get(functionName);
205+
if (!fn) return null;
206+
try {
207+
return fn(args);
208+
} catch {
209+
return null; // CAS operation failed gracefully
210+
}
211+
}
212+
213+
/**
214+
* Check if a function name is a CAS function.
215+
*/
216+
export function isCASFunction(name: string): boolean {
217+
return CAS_FUNCTIONS.has(name);
218+
}
219+
220+
// ─────────────────────────────────────────────────────────────────────
221+
// Modelica Package Declaration
222+
// ─────────────────────────────────────────────────────────────────────
223+
224+
/**
225+
* The Modelica source for the ModelScript.CAS package.
226+
* This declares all CAS functions with their signatures for use
227+
* in Modelica models.
228+
*/
229+
export const MODELSCRIPT_CAS_PACKAGE = `
230+
package ModelScript
231+
package CAS "Computer Algebra System"
232+
233+
function simplify "Simplify an expression using E-Graph equality saturation"
234+
input Real expr;
235+
output Real result;
236+
external "builtin";
237+
end simplify;
238+
239+
function expand "Expand polynomial expressions (distribute multiplication)"
240+
input Real expr;
241+
output Real result;
242+
external "builtin";
243+
end expand;
244+
245+
function normalize "Normalize to canonical form via E-Graph"
246+
input Real expr;
247+
output Real result;
248+
external "builtin";
249+
end normalize;
250+
251+
function trigSimplify "Simplify using trigonometric identities"
252+
input Real expr;
253+
output Real result;
254+
external "builtin";
255+
end trigSimplify;
256+
257+
function trigExpand "Expand trig expressions using addition formulas"
258+
input Real expr;
259+
output Real result;
260+
external "builtin";
261+
end trigExpand;
262+
263+
function diff "Symbolic differentiation"
264+
input Real expr;
265+
input Real var "Variable to differentiate with respect to";
266+
input Integer n = 1 "Order of derivative";
267+
output Real result;
268+
external "builtin";
269+
end diff;
270+
271+
function integrate "Symbolic anti-differentiation"
272+
input Real expr;
273+
input Real var "Variable to integrate with respect to";
274+
output Real result;
275+
external "builtin";
276+
end integrate;
277+
278+
function solve "Solve expr = 0 for var (returns first solution)"
279+
input Real expr;
280+
input Real var "Variable to solve for";
281+
output Real result;
282+
external "builtin";
283+
end solve;
284+
285+
function solveAll "Solve expr = 0 for var (returns all solutions)"
286+
input Real expr;
287+
input Real var "Variable to solve for";
288+
output Real[:] result;
289+
external "builtin";
290+
end solveAll;
291+
292+
function factor "Factor a quadratic polynomial"
293+
input Real expr;
294+
input Real var;
295+
output Real result;
296+
external "builtin";
297+
end factor;
298+
299+
function taylor "Taylor series expansion"
300+
input Real expr;
301+
input Real var;
302+
input Real point;
303+
input Integer order;
304+
output Real result;
305+
external "builtin";
306+
end taylor;
307+
308+
function limit "Evaluate limit of expr as var -> point"
309+
input Real expr;
310+
input Real var;
311+
input Real point;
312+
output Real result;
313+
external "builtin";
314+
end limit;
315+
316+
function degree "Get polynomial degree of expr in var"
317+
input Real expr;
318+
input Real var;
319+
output Integer result;
320+
external "builtin";
321+
end degree;
322+
323+
function roots "Find rational roots of polynomial expr in var"
324+
input Real expr;
325+
input Real var;
326+
output Real[:] result;
327+
external "builtin";
328+
end roots;
329+
330+
end CAS;
331+
end ModelScript;
332+
`;
333+
334+
// ─────────────────────────────────────────────────────────────────────
335+
// Utilities
336+
// ─────────────────────────────────────────────────────────────────────
337+
338+
function extractVarName(expr: ModelicaExpression): string | null {
339+
if (expr instanceof ModelicaNameExpression) return expr.name;
340+
if (expr && typeof expr === "object" && "name" in expr) {
341+
return (expr as { name: string }).name;
342+
}
343+
return null;
344+
}
345+
346+
function buildArray(exprs: ModelicaExpression[]): ModelicaExpression {
347+
// For simplicity, return first element if only one, or construct
348+
// a function call to "array" with all elements
349+
if (exprs.length === 1) return exprs[0] ?? new ModelicaRealLiteral(0);
350+
return new ModelicaFunctionCallExpression("array", exprs);
351+
}

packages/core/src/compiler/modelica/symbolic/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,6 @@ export { integrateExpr, limit, nthDerivative, taylorSeries } from "./integrate.j
6363

6464
// Gröbner basis
6565
export { Polynomial, Term, TermOrder, computeGroebnerBasis, sPolynomial } from "./groebner.js";
66+
67+
// Modelica CAS bindings
68+
export { CAS_FUNCTIONS, MODELSCRIPT_CAS_PACKAGE, evaluateCASFunction, isCASFunction } from "./cas-bindings.js";

0 commit comments

Comments
 (0)