/ concept-collection / mesh-pde-solver
Sign in
concept-collection / mesh-pde-solver
mesh-pde-solver / src / engine / engine.ts
97 lines · 3.2 KBBlameHistoryRaw
1// Run-per-solve engine: each solve boots a fresh numbl/browser session that
2// runs matlab/main.m standalone (mip load + solve_pde), reads result.json
3// back from the session VFS, and disposes the worker. numbl persists the
4// installed packages in IndexedDB, so only the first-ever run downloads them;
5// prewarm() triggers that download at page load.
7import { createNumblSession, type NumblSession } from 'numbl/browser'
8import main from '../../matlab/main.m?raw'
9import solvePde from '../../matlab/solve_pde.m?raw'
11const SOLVE_TIMEOUT_MS = 300_000
13export interface SolveParams {
14 pde: 'poisson' | 'helmholtz'
15 /** RHS f(x,y,z), a MATLAB expression */
16 f: string
17 /** zeroth-order coefficient c(x,y,z) (helmholtz only) */
18 c: string
19 /** polynomial order per patch */
20 p: number
21 /** every mesh edge shared by exactly two cells (from edgeClassification) */
22 closed: boolean
25/** Per-patch solution data, as packed by matlab/solve_pde.m. */
26export interface SolutionData {
27 type: 'solution'
28 /** points per patch edge (p + 1); quad patches carry n*n points
29 * (column-major grid), triangle patches n*(n+1)/2 (trianglepts order) */
30 n: number
31 /** patch type of the mesh — never mixed */
32 ptype: 'quad' | 'tri'
33 npatches: number
34 x: number[][]
35 y: number[][]
36 z: number[][]
37 u: number[][]
38 umin: number
39 umax: number
40 pde: string
43export interface EngineHooks {
44 /** Boot progress (package downloads, engine start). */
45 onProgress?: (message: string) => void
46 /** MATLAB console output (mip install logs etc.). */
47 onOutput?: (text: string) => void
50/** Install the MATLAB packages ahead of the first solve (fire at page load). */
51export async function prewarm(hooks: EngineHooks = {}): Promise<void> {
52 const session = await createNumblSession({
53 files: [{ path: 'main.m', content: 'mip load --install surfacefun;\n' }],
54 mainFile: 'main.m',
55 onProgress: hooks.onProgress,
56 onOutput: hooks.onOutput,
57 })
58 session.dispose()
61/**
62 * Solve params.pde on the mesh in a fresh session. Rejects with the MATLAB
63 * error message if the solve fails (bad expression, degenerate mesh, ...).
64 */
65export async function solve(
66 meshBytes: Uint8Array,
67 params: SolveParams,
68 hooks: EngineHooks = {},
69): Promise<SolutionData> {
70 let session: NumblSession | null = null
71 let timeoutId: ReturnType<typeof setTimeout> | undefined
72 try {
73 return await Promise.race([
74 new Promise<never>((_, reject) => {
75 timeoutId = setTimeout(() => reject(new Error('solve timed out')), SOLVE_TIMEOUT_MS)
76 }),
77 (async () => {
78 session = await createNumblSession({
79 files: [
80 { path: 'main.m', content: main },
81 { path: 'solve_pde.m', content: solvePde },
82 { path: 'params.json', content: JSON.stringify(params) },
83 { path: 'mesh.msh', content: meshBytes },
84 ],
85 mainFile: 'main.m',
86 onProgress: hooks.onProgress,
87 onOutput: hooks.onOutput,
88 })
89 const bytes = await session.readFile('result.json')
90 return JSON.parse(new TextDecoder().decode(bytes)) as SolutionData
91 })(),
92 ])
93 } finally {
94 clearTimeout(timeoutId)
95 ;(session as NumblSession | null)?.dispose()
96 }
moveopenescclose