/ proof-of-concept / steelsim41
Sign in
proof-of-concept / steelsim41
steelsim41 / src / mhelpers.ts
143 lines · 4.9 KBBlameHistoryRaw
1// The MATLAB-syntax helper functions available to a design script, as numbl
2// workspace files. Each is an ordinary .m function; the design script calls
3// them like any other function. Everything is vectorized over grid matrices.
4// steelsim_field is internal: evaluatePlate uses it to sample a field.
6import type { WorkspaceFile } from "numbl";
8const files: Record<string, string> = {
9 "inch.m": `function v = inch()
10% inch: one inch in meters.
11v = 0.0254;
12end
13`,
14 "mm.m": `function v = mm()
15% mm: one millimeter in meters.
16v = 1e-3;
17end
18`,
19 "circle.m": `function f = circle(diameter, hole)
20% circle(diameter, hole): outline of a disc with an optional center hole.
21if nargin < 2, hole = 0; end
22f = @(x, y, r, theta) (r <= diameter/2) & (r >= hole/2);
23end
24`,
25 "ellipse.m": `function f = ellipse(width, height, hole)
26% ellipse(width, height, hole): outline of an ellipse with an optional center hole.
27if nargin < 3, hole = 0; end
28f = @(x, y, r, theta) ((x/(width/2)).^2 + (y/(height/2)).^2 <= 1) & (r >= hole/2);
29end
30`,
31 "rectangle.m": `function f = rectangle(width, height, hole)
32% rectangle(width, height, hole): outline of a centered rectangle with an optional center hole.
33if nargin < 3, hole = 0; end
34f = @(x, y, r, theta) (abs(x) <= width/2) & (abs(y) <= height/2) & (r >= hole/2);
35end
36`,
37 "clamp.m": `function v = clamp(x, lo, hi)
38% clamp(x, lo, hi): x limited to [lo, hi], elementwise.
39v = min(hi, max(lo, x));
40end
41`,
42 "lerp.m": `function v = lerp(a, b, t)
43% lerp(a, b, t): a at t = 0, b at t = 1, elementwise.
44v = a + (b - a) .* t;
45end
46`,
47 "smoothstep.m": `function v = smoothstep(e0, e1, x)
48% smoothstep(e0, e1, x): 0 below e0, 1 above e1, a smooth cubic between.
49t = clamp((x - e0) ./ (e1 - e0), 0, 1);
50v = t.^2 .* (3 - 2*t);
51end
52`,
53 "bump.m": `function v = bump(d, radius, height)
54% bump(d, radius, height): Gaussian bump of the given height, falling to
55% 1/e at the given radius; d is the distance from its center.
56v = height * exp(-(d.^2) / radius^2);
57end
58`,
59 "dome.m": `function v = dome(d, radius, height)
60% dome(d, radius, height): cosine dome of the given height and base radius,
61% with zero slope at the rim and the apex; d is the distance from its center.
62v = 0.5 * height * (1 + cos(pi * d / radius)) .* (d < radius);
63end
64`,
65 "bow.m": `function v = bow(s, L, sag, offset)
66% bow(s, L, sag, offset): parabolic bow of the given sag across a span L
67% centered on 0; s is the coordinate along the span, and the offset in
68% -1..1 moves the crest toward one end.
69if nargin < 4, offset = 0; end
70t = 2*s/L;
71v = sag * (1 - t.^2) .* (1 + offset*t);
72end
73`,
74 "dents.m": `function v = dents(x, y, cx, cy, radius, height)
75% dents(x, y, cx, cy, radius, height): the sum of Gaussian bumps centered
76% at (cx(k), cy(k)) with the given radii and heights (vectors, or scalars
77% shared by all the dents).
78v = zeros(size(x));
79for k = 1:numel(cx)
80 rk = radius(min(k, numel(radius)));
81 hk = height(min(k, numel(height)));
82 v = v + bump(hypot(x - cx(k), y - cy(k)), rk, hk);
83end
84end
85`,
86 "seededrand.m": `function u = seededrand(seed, n)
87% seededrand(seed, n): n uniform numbers in [0, 1) from a small seeded
88% generator, the same on every run and every machine.
89s = floor(seed);
90u = zeros(1, n);
91for k = 1:n
92 s = mod(s * 1664525 + 1013904223, 4294967296);
93 u(k) = s / 4294967296;
94end
95end
96`,
97 "noise2d.m": `function f = noise2d(seed, len)
98% noise2d(seed, len): a smooth random field f(x, y) of unit RMS with the
99% covariance exp(-d^2 / len^2), the same for a given seed on any grid.
100n = 64;
101u = reshape(seededrand(seed, 5*n), 5, n);
102kx = sqrt(-2*log(1 - u(1,:))) .* cos(2*pi*u(2,:)) * sqrt(2) / max(len, 1e-4);
103ky = sqrt(-2*log(1 - u(3,:))) .* cos(2*pi*u(4,:)) * sqrt(2) / max(len, 1e-4);
104ph = 2*pi*u(5,:);
105f = @(x, y) noise2d_eval(kx, ky, ph, x, y);
106end
107`,
108 "noise2d_eval.m": `function v = noise2d_eval(kx, ky, ph, x, y)
109v = zeros(size(x));
110for k = 1:numel(kx)
111 v = v + cos(kx(k) * x + ky(k) * y + ph(k));
112end
113v = v / sqrt(numel(kx) / 2);
114end
115`,
116 "steelsim_field.m": `function v = steelsim_field(f, what, x, y, r, theta)
117% Samples a design field (a number or a function handle) on the grid matrices.
118if isa(f, 'function_handle')
119 try
120 v = f(x, y, r, theta);
121 catch e
122 error('%s: %s', what, e.message);
123 end
124elseif isnumeric(f) || islogical(f)
125 v = f;
126else
127 error('%s must be a number or a function handle @(x, y, r, theta)', what);
128end
129if isscalar(v)
130 v = double(v) * ones(size(x));
131elseif ~isequal(size(v), size(x))
132 error('%s returned a %d x %d array for a %d x %d grid; use elementwise operators (.*, ./, .^)', what, size(v, 1), size(v, 2), size(x, 1), size(x, 2));
133else
134 v = double(v);
135end
136end
137`,
138};
140export const HELPER_FILES: WorkspaceFile[] = Object.entries(files).map(([name, source]) => ({ name, source }));
142/** The helper names a script may call (for the interface's hint). */
143export const HELPER_NAMES = Object.keys(files).filter((n) => !n.startsWith("steelsim_") && !n.startsWith("noise2d_eval")).map((n) => n.replace(/\.m$/, ""));
↑↓ move↵openescclose