2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><circle cx=%2250%22 cy=%2250%22 r=%2245%22 fill=%22%232a5f7f%22/><circle cx=%2235%22 cy=%2238%22 r=%2211%22 fill=%22%23f5d547%22/><circle cx=%2265%22 cy=%2258%22 r=%229%22 fill=%22%23f5d547%22/><circle cx=%2248%22 cy=%2274%22 r=%227%22 fill=%22%23f5d547%22/><circle cx=%2268%22 cy=%2230%22 r=%226%22 fill=%22%23f5d547%22/></svg>" />
7 <title>turing-surface-cache — reaction-diffusion solutions from a shared cloud cache</title>
8 <style>
9 :root {
10 --bg: #ffffff;
11 --ink: #1f2328;
12 --ink-2: #57606a;
13 --line: #d0d7de;
14 --accent: #0969da;
15 --ok: #1a7f37;
16 --sphere-bg: #f4f6f8;
17 color-scheme: light dark;
18 }
19 @media (prefers-color-scheme: dark) {
20 :root {
21 --bg: #14171a;
22 --ink: #e6e9ec;
23 --ink-2: #9aa4af;
24 --line: #333b44;
25 --accent: #58a6ff;
26 --ok: #3fb950;
27 --sphere-bg: #14161c;
28 }
29 }
30 body {
31 margin: 0;
32 background: var(--bg);
33 color: var(--ink);
34 font: 15px/1.5 system-ui, -apple-system, sans-serif;
35 }
36 main { max-width: 1100px; margin: 0 auto; padding: 20px 16px 48px; }
37 h1 { font-size: 20px; margin: 0 0 2px; }
38 .sub { color: var(--ink-2); margin: 0 0 12px; font-size: 13px; }
39 .sub a { color: var(--accent); }
40 .controls {
41 display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
42 padding: 6px 0;
43 }
44 .controls[hidden] { display: none; }
45 .controls label { color: var(--ink-2); font-size: 13px; white-space: nowrap; }
46 select, input[type="password"], button {
47 font: inherit; font-size: 13px;
48 color: var(--ink); background: var(--bg);
49 border: 1px solid var(--line); border-radius: 6px;
50 padding: 4px 8px;
51 }
52 button { cursor: pointer; }
53 button:hover { border-color: var(--accent); }
54 button.primary { border-color: var(--accent); color: var(--accent); font-weight: 600; min-width: 8em; }
55 button:disabled { opacity: 0.5; cursor: default; }
56 #cachenote { font-size: 13px; color: var(--ink-2); }
57 #cachenote b { color: var(--ok); font-weight: 600; }
58 #status { margin-top: 10px; font-size: 13.5px; }
59 #status b { font-weight: 600; }
60 #panels { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px; }
61 .panel {
62 flex: 1 1 320px; min-width: 280px;
63 border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
64 display: flex;
65 }
66 .sphere-box { flex: 1; aspect-ratio: 1 / 1; max-height: 70vh; position: relative; }
67 .species-tag {
68 position: absolute; top: 8px; left: 10px; z-index: 2;
69 font-size: 15px; font-weight: 600; color: #fff;
70 background: rgba(0, 0, 0, 0.45);
71 padding: 1px 10px; border-radius: 12px;
72 pointer-events: none;
73 }
74 .colorbar {
75 display: flex; flex-direction: column; align-items: center; justify-content: center;
76 gap: 4px; padding: 8px 4px; background: var(--sphere-bg);
77 width: 52px; flex: none; box-sizing: border-box;
78 }
79 .colorbar canvas { border: 1px solid var(--line); border-radius: 2px; }
80 .colorbar-label { font-size: 11px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
81 .stats { margin-top: 10px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
82 .stats b { color: var(--ink); font-weight: 600; }
83 .cloud {
84 margin-top: 14px; border: 1px solid var(--line); border-radius: 8px;
85 padding: 8px 12px; font-size: 13px; color: var(--ink-2);
86 }
87 .cloud .controls { padding: 2px 0 0; }
88 /* The same walk, spelled as a command for a machine with no browser on
89 it. The key is masked here and real in the clipboard, so the command
90 is paste-and-go without the key ending up in a screenshot. */
91 #clicmd {
92 font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
93 color: var(--ink); background: var(--sphere-bg);
94 border: 1px solid var(--line); border-radius: 6px;
95 padding: 3px 8px; max-width: 100%;
96 overflow-x: auto; white-space: nowrap;
97 }
98 #clicopied { color: var(--ok); }
99 /* Troubleshooting, folded away: the failures below are undiscoverable
100 from the error alone, and the page is where people start. */
101 #clihelp { margin-top: 4px; font-size: 12.5px; }
102 #clihelp[hidden] { display: none; }
103 #clihelp summary { cursor: pointer; color: var(--accent); width: fit-content; }
104 #clihelp p { margin: 8px 0; max-width: 68ch; }
105 #clihelp code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
106 #clihelp pre {
107 font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
108 background: var(--sphere-bg); border: 1px solid var(--line);
109 border-radius: 6px; padding: 8px 10px; overflow-x: auto;
110 color: var(--ink);
111 }
112 #err { color: #b35900; white-space: pre-wrap; font-size: 13px; }
113 </style>
114 </head>
115 <body>
116 <main>
117 <h1>turing-surface-cache</h1>
118 <p class="sub">
119 Reaction-diffusion (Turing patterns) on curved surfaces, evaluated at a
120 chosen end time. Every setting is a choice from a short list, so each
121 combination names one exact solution. Solutions already in the shared
122 cloud cache load by themselves as you browse the choices; a
123 combination no one has computed shows empty surfaces until you press
124 <b>Compute solution</b>, which runs it here (with WebGPU, via
125 <a href="https://github.com/concept-collection/turing-surface">turing-surface</a>'s
126 spectral solver). Drag to rotate.
127 </p>
128 <div class="controls">
129 <label title="The reaction-diffusion system being solved">model
130 <select id="model"></select>
131 </label>
132 <span id="params" class="controls" style="padding: 0"></span>
133 </div>
134 <div class="controls">
135 <label title="The surface the pattern is solved on">geometry
136 <select id="geometry"></select>
137 </label>
138 <span id="geomparams" class="controls" style="padding: 0"></span>
139 </div>
140 <div class="controls">
141 <label title="Which random initial perturbation to start from">seed
142 <select id="seed"></select>
143 </label>
144 <label title="The solution is reported at this simulation time">end time
145 <select id="tend"></select>
146 </label>
147 <button id="solve" class="primary"
148 title="Compute the selected solution in your browser. Cached solutions load by themselves as you change the selection.">Compute solution</button>
149 <button id="stop" hidden>Stop</button>
150 <button id="reset" title="Set every selection back to its default">Reset to defaults</button>
151 <span id="cachenote"></span>
152 </div>
153 <p id="status"></p>
154 <div id="panels"></div>
155 <div class="controls">
156 <button id="resetview">Reset view</button>
157 <a id="download" hidden download>Download .h5</a>
158 </div>
159 <p class="stats" id="stats"></p>
160 <div class="cloud">
161 <span>Solutions computed here can be contributed back to the shared
162 cache, so the next visitor gets them instantly. Contributing
163 requires an upload API key.</span>
164 <div class="controls">
165 <label>upload API key
166 <input id="apikey" type="password" autocomplete="off" placeholder="(optional)" />
167 </label>
168 <span id="uploadnote"></span>
169 </div>
170 <div class="controls" id="autobar" hidden>
171 <button id="auto"
172 title="Work through the parameter space on this machine, nearest the defaults first, computing and uploading whatever is not cached yet. Runs until stopped.">Auto-fill the cache</button>
173 <span id="autonote"></span>
174 </div>
175 <div class="controls" id="clibar" hidden>
176 <span>or on a machine with no browser on it:</span>
177 <code id="clicmd"></code>
178 <button id="clicopy"
179 title="Copies the command with your key in it. The key is masked here so that it stays out of screenshots.">Copy command (includes your key)</button>
180 <span id="clicopied"></span>
181 </div>
182 <details id="clihelp" hidden>
183 <summary>if that command does not run on the other machine</summary>
184 <p><code>Unexpected token {</code>, or another syntax error, means that
185 machine's node is too old to read the program at all: node 18 or newer
186 is needed. nodejs.org has current builds, and nvm, fnm and asdf install
187 one per user without touching what the system depends on; where a
188 cluster uses environment modules, <code>module load node</code> may be
189 all it takes.</p>
190 <p><code>the webgpu package is installed but did not load … GLIBC_2.34
191 not found</code> means the machine's distribution is older than the
192 prebuilt Dawn binary — ordinary on a long-lived Linux workstation, since
193 Rocky and RHEL 8 have glibc 2.28 and Dawn wants 2.34. Running the
194 command inside a container is the obvious answer and the wrong one:
195 there the NVIDIA driver declines to bring up its Vulkan driver, while
196 on the host it comes up fine. What does work is borrowing a newer
197 userland from a container image and running node through <em>that</em>
198 loader, still on the host, so the GPU, <code>/dev</code> and
199 <code>/proc</code> are exactly what they were:</p>
200 <pre># 1. borrow a userland — podman or docker unpacks it; nothing runs inside it
201mkdir -p ~/turing-userland
202podman create --name u docker.io/library/node:22-bookworm-slim true
203podman export u | tar -x -C ~/turing-userland
204podman rm u
206# 2. a node that uses it, running on the host rather than in a container
207U=~/turing-userland; mkdir -p ~/turing-bin
208printf '#!/bin/sh\nexec %s --library-path %s %s "$@"\n' \
209 $U/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 \
210 $U/usr/lib/x86_64-linux-gnu:$U/lib/x86_64-linux-gnu:/usr/lib64 \
211 $U/usr/local/bin/node > ~/turing-bin/node
212chmod +x ~/turing-bin/node
214# 3. run the command above, with that node found first
215export PATH=~/turing-bin:$PATH</pre>
216 <p>The host's own <code>/usr/lib64</code> stays last on that library
217 path, and is where the NVIDIA libraries and the Vulkan loader come
218 from: those have to match the running kernel module, so they must be
219 the machine's own. If podman's storage lives on NFS it will fail to
220 unpack the image, which
221 <code>--root /tmp/$USER/store --runroot /tmp/$USER/run</code> on each
222 podman command works around.</p>
223 <p><code>WebGPU is not available</code>, or no adapter at all, means
224 there is no GPU to reach: Dawn goes through Vulkan on Linux and Metal
225 on macOS, so a machine without a GPU, or with one whose driver is not
226 installed, has nothing to offer.
227 <code>DAWN_FLAGS='backend=vulkan'</code> makes it say more about why.</p>
228 <p>A rate of a few steps per second rather than a few hundred means a
229 software rasterizer has stood in for the GPU. The command names its
230 adapter on the first line and says so when it notices, but it does not
231 refuse to run — a slow contribution is still a contribution, if that is
232 what you meant.</p>
233 </details>
234 </div>
235 <p id="err"></p>
236 </main>
237 <script type="module" src="/src/main.ts"></script>
238 </body>
239</html>