1/**
2 * The available models: their MATLAB source, and the metadata the host owns.
3 *
4 * A model's *algorithm* lives in its .m file. Everything around it lives here:
5 * the parameter names the .m may take as arguments, their defaults and slider
6 * ranges, which grid fields to render, and the dealiasing degree. The .m
7 * declares nothing about these — it just names the parameters it wants, and
8 * `CompiledModel` matches each against this table.
9 *
10 * Naming convention, documented in each .m:
11 * `u`, `v`, ... grid fields the model computes and the app renders
12 * `U`, `V`, ... the corresponding spectral state (uppercase)
13 */
14import schnakenbergSource from '../../models/schnakenberg.m?raw';
15import brusselatorSource from '../../models/brusselator.m?raw';
16import allencahnSource from '../../models/allencahn.m?raw';
18export type Params = Record<string, number>;
20/** A tunable scalar the .m may take as an argument. */
21export interface ParamSpec {
22 key: string;
23 label: string;
24 value: number;
25 min: number;
26 max: number;
27 step: number;
28}
30export interface MModel {
31 key: string;
32 label: string;
33 blurb: string;
34 /** Grid fields to render, one panel each. */
35 species: string[];
36 /** Spectral state names the .m advances. */
37 state: string[];
38 params: ParamSpec[];
39 /** Polynomial degree of the reaction in the fields, for grid dealiasing. */
40 pdeg: number;
41 /** Amplitude of the seeded perturbation handed to `init`. */
42 seedAmp: number;
43 /** MATLAB source — the algorithm itself. */
44 source: string;
45}
47/** Spectral state names follow the grid-field names, uppercased. */
48const stateFor = (species: string[]): string[] => species.map((s) => s.toUpperCase());
50const schnakenberg: MModel = {
51 key: 'schnakenberg',
52 label: 'Schnakenberg',
53 blurb:
54 'Turing spots. The homogeneous state is stable to uniform perturbations ' +
55 'but unstable to degrees 14 ≤ l ≤ 40, most strongly at l = 24.',
56 species: ['u', 'v'],
57 state: stateFor(['u', 'v']),
58 params: [
59 { key: 'a', label: 'a', value: 0.1, min: 0.01, max: 0.5, step: 0.01 },
60 { key: 'b', label: 'b', value: 0.9, min: 0.1, max: 2, step: 0.05 },
61 { key: 'D1', label: 'D₁', value: 4e-4, min: 1e-5, max: 5e-3, step: 1e-5 },
62 { key: 'D2', label: 'D₂', value: 8e-3, min: 1e-4, max: 5e-2, step: 1e-4 },
63 { key: 'dt', label: 'dt', value: 0.05, min: 0.005, max: 0.5, step: 0.005 },
64 ],
65 pdeg: 3,
66 seedAmp: 1e-2,
67 source: schnakenbergSource,
68};
70const brusselator: MModel = {
71 key: 'brusselator',
72 label: 'Brusselator',
73 blurb:
74 'Turing stripes and spots, from a smaller diffusivity contrast than ' +
75 'Schnakenberg but with a stiffer reaction.',
76 species: ['u', 'v'],
77 state: stateFor(['u', 'v']),
78 params: [
79 { key: 'A', label: 'A', value: 3, min: 0.5, max: 6, step: 0.1 },
80 { key: 'B', label: 'B', value: 9, min: 1, max: 15, step: 0.25 },
81 { key: 'D1', label: 'D₁', value: 3.33e-3, min: 1e-4, max: 2e-2, step: 1e-4 },
82 { key: 'D2', label: 'D₂', value: 1.67e-2, min: 1e-3, max: 1e-1, step: 1e-3 },
83 { key: 'dt', label: 'dt', value: 0.02, min: 0.002, max: 0.1, step: 0.002 },
84 ],
85 pdeg: 3,
86 seedAmp: 1e-2,
87 source: brusselatorSource,
88};
90const allencahn: MModel = {
91 key: 'allencahn',
92 label: 'Allen–Cahn',
93 blurb:
94 'A single species: interfaces form and then coarsen until one domain ' +
95 'swallows the sphere.',
96 species: ['u'],
97 state: stateFor(['u']),
98 params: [
99 { key: 'eps2', label: 'ε²', value: 1e-3, min: 1e-4, max: 1e-2, step: 1e-4 },
100 { key: 'dt', label: 'dt', value: 0.02, min: 0.002, max: 0.2, step: 0.002 },
101 ],
102 pdeg: 3,
103 seedAmp: 1e-2,
104 source: allencahnSource,
105};
107export const mModels: MModel[] = [schnakenberg, brusselator, allencahn];
109export const mModelByKey = (key: string): MModel | undefined =>
110 mModels.find((m) => m.key === key);
112export const defaultParams = (m: MModel): Params =>
113 Object.fromEntries(m.params.map((p) => [p.key, p.value]));
115/** Named parameter presets shown in the UI dropdown. The pattern length scale
116 * goes as 1/sqrt(D), so scaling both diffusivities moves the spot size without
117 * changing the dynamics. */
118export interface Preset {
119 key: string;
120 label: string;
121 modelKey: string;
122 /** Overrides applied on top of the model's default parameters. */
123 params?: Params;
124}
126export const presets: Preset[] = [
127 { key: 'schnak-spots', label: 'Schnakenberg — spots', modelKey: 'schnakenberg' },
128 {
129 key: 'schnak-coarse',
130 label: 'Schnakenberg — coarse spots',
131 modelKey: 'schnakenberg',
132 params: { D1: 1e-3, D2: 2e-2 },
133 },
134 {
135 key: 'schnak-fine',
136 label: 'Schnakenberg — fine spots',
137 modelKey: 'schnakenberg',
138 params: { D1: 1.6e-4, D2: 3.2e-3 },
139 },
140 { key: 'brussel', label: 'Brusselator — stripes & spots', modelKey: 'brusselator' },
141 { key: 'allencahn', label: 'Allen–Cahn — coarsening', modelKey: 'allencahn' },
142];