import type { ProblemSpec } from "../spec/types"; import type { MethodDefinition } from "../methods/types"; import { generateParamsBlock } from "./params"; import { LIBRARY_FILES, type SourceFile } from "./library"; /** A complete, self-contained set of `.m` files plus the one to run. */ export interface SourceBundle { files: SourceFile[]; /** File executed as the script */ entry: string; /** Workspace variable read back after the run */ resultVariable: string; } export const MAIN_FILE = "main.m"; /** The script a method runs: the params block followed by the solver call. */ export function generateMain(spec: ProblemSpec, method: MethodDefinition): string { return [ generateParamsBlock(spec), "", `% Solve. ${method.solverFunction} and the functions it calls are the library files in the other tabs.`, `result = ${method.solverFunction}(params);`, "", ].join("\n"); } /** Bundle for running one method on a spec. */ export function buildRunBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle { return { files: [{ name: MAIN_FILE, source: generateMain(spec, method) }, ...LIBRARY_FILES], entry: MAIN_FILE, resultVariable: "result", }; } /** Bundle that evaluates a method's cost plan (currently only the FD method has one). */ export function buildPlanBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle | null { if (!method.planFunction) return null; const main = [generateParamsBlock(spec), "", `plan = ${method.planFunction}(params);`, ""].join("\n"); return { files: [{ name: "plan.m", source: main }, ...LIBRARY_FILES], entry: "plan.m", resultVariable: "plan", }; } /** Bundle comparing two results, which the runner passes in as workspace variables A and B. */ export function buildCompareBundle(): SourceBundle { const main = [ "% A and B are two result structs for the same problem (A: modal, B: finite differences).", "cmp = plate_compare(A, B);", "", ].join("\n"); return { files: [{ name: "compare.m", source: main }, ...LIBRARY_FILES], entry: "compare.m", resultVariable: "cmp", }; } /** * Replace files in a bundle with edited versions, keeping everything else. Used * when the user edits source in the code view: the run then uses exactly the * text on screen. */ export function withEdits(bundle: SourceBundle, edits: Record): SourceBundle { return { ...bundle, files: bundle.files.map((f) => (edits[f.name] !== undefined ? { ...f, source: edits[f.name] } : f)), }; }