1import type { ProblemSpec } from "../spec/types";
2import type { MethodDefinition } from "../methods/types";
3import { generateParamsBlock } from "./params";
4import { LIBRARY_FILES, type SourceFile } from "./library";
6/** A complete, self-contained set of `.m` files plus the one to run. */
7export interface SourceBundle {
8 files: SourceFile[];
9 /** File executed as the script */
10 entry: string;
11 /** Workspace variable read back after the run */
12 resultVariable: string;
13}
15export const MAIN_FILE = "main.m";
17/** The script a method runs: the params block followed by the solver call. */
18export function generateMain(spec: ProblemSpec, method: MethodDefinition): string {
19 return [
20 generateParamsBlock(spec),
21 "",
22 `% Solve. ${method.solverFunction} and the functions it calls are the library files in the other tabs.`,
23 `result = ${method.solverFunction}(params);`,
24 "",
25 ].join("\n");
26}
28/** Bundle for running one method on a spec. */
29export function buildRunBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle {
30 return {
31 files: [{ name: MAIN_FILE, source: generateMain(spec, method) }, ...LIBRARY_FILES],
32 entry: MAIN_FILE,
33 resultVariable: "result",
34 };
35}
37/** Bundle that evaluates a method's cost plan (currently only the FD method has one). */
38export function buildPlanBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle | null {
39 if (!method.planFunction) return null;
40 const main = [generateParamsBlock(spec), "", `plan = ${method.planFunction}(params);`, ""].join("\n");
41 return {
42 files: [{ name: "plan.m", source: main }, ...LIBRARY_FILES],
43 entry: "plan.m",
44 resultVariable: "plan",
45 };
46}
48/** Bundle comparing two results, which the runner passes in as workspace variables A and B. */
49export function buildCompareBundle(): SourceBundle {
50 const main = [
51 "% A and B are two result structs for the same problem (A: modal, B: finite differences).",
52 "cmp = plate_compare(A, B);",
53 "",
54 ].join("\n");
55 return {
56 files: [{ name: "compare.m", source: main }, ...LIBRARY_FILES],
57 entry: "compare.m",
58 resultVariable: "cmp",
59 };
60}
62/**
63 * Replace files in a bundle with edited versions, keeping everything else. Used
64 * when the user edits source in the code view: the run then uses exactly the
65 * text on screen.
66 */
67export function withEdits(bundle: SourceBundle, edits: Record<string, string>): SourceBundle {
68 return {
69 ...bundle,
70 files: bundle.files.map((f) => (edits[f.name] !== undefined ? { ...f, source: edits[f.name] } : f)),
71 };
72}