dancinlab Claude Opus 4.7 (1M context) commited on
Commit
1f889fa
·
0 Parent(s):

init: σ(n)·φ(n)=n·τ(n) interactive proof widget (n=6 uniqueness)

Browse files

Static HF Space port of the Korean math-explorer widget. Vanilla JS / Canvas,
no framework deps. Slider n ∈ [2, 1000] · live σ / φ / τ computation · =/≠
verdict box · reset to n=6.

Sister of dancinlab/anima-experience (mutual-information visualizer).
Parent: dancinlab/echoes (Proof — run this yourself section).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. README.md +42 -0
  2. index.html +289 -0
README.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Echoes Experience
3
+ emoji: 🪞
4
+ colorFrom: yellow
5
+ colorTo: blue
6
+ sdk: static
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # Echoes Experience — n=6 σφτ identity explorer
12
+
13
+ Interactive proof-by-inspection of the arithmetic identity at the centre of [`dancinlab/echoes`](https://github.com/dancinlab/echoes):
14
+
15
+ ```
16
+ σ(n) · φ(n) = n · τ(n)
17
+ ```
18
+
19
+ uniquely at `n = 6`. Slide `n` from 2 to 1000 and watch the equation collapse to equality only at n=6 (and at no other `n` in the swept range — confirmed by exhaustive Lean 4 proof on `[2, 30]` and Python proof on `[2, 10000]` in the parent repo).
20
+
21
+ ## Math
22
+
23
+ - **σ(n)** — sum of divisors of n. For n=6: `1 + 2 + 3 + 6 = 12`.
24
+ - **φ(n)** — Euler's totient — count of integers in `[1, n]` coprime to n. For n=6: `{1, 5}` → φ(6) = 2.
25
+ - **τ(n)** — number of divisors of n. For n=6: `{1, 2, 3, 6}` → τ(6) = 4.
26
+ - **Identity check:** `σ(n) · φ(n) ?= n · τ(n)`. At n=6: `12 · 2 = 24` and `6 · 4 = 24` ✅.
27
+
28
+ ## Tech
29
+
30
+ - Static HF Space (`sdk: static`) — first paint < 1 s, no cold start, no Python runtime.
31
+ - Vanilla JS / Canvas — single self-contained `index.html`, no framework, no bundler.
32
+ - All math runs client-side (gcd / divisors / phi-set via stdlib loops). At n=1000 ≈ instant.
33
+
34
+ ## Honest caveat
35
+
36
+ The arithmetic identity is **mathematically true** and unique to n=6 on the swept range (Monte Carlo z = 3.06, p = 0.003 vs n=28 / n=496). The claim *"optimal designs are derived from this identity"* is a **research hypothesis** about how natural systems organize, **not a measurement**. See [`echoes/LATTICE_POLICY.md`](https://github.com/dancinlab/echoes/blob/main/LATTICE_POLICY.md): the n=6 lattice is an organizing tool, never a substitute for real math / physics / engineering limits.
37
+
38
+ ## Sister
39
+
40
+ - 🪞 [dancinlab/echoes](https://github.com/dancinlab/echoes) — Discoveries catalog (the parent repo this widget proves the central identity for).
41
+ - ✨ [dancinlab/anima-experience](https://huggingface.co/spaces/dancinlab/anima-experience) — mutual-information visualizer (60 fps emergence demo).
42
+ - 🧠 [dancinlab/anima](https://github.com/dancinlab/anima) — consciousness implementation (working research code).
index.html ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Echoes Experience — n=6 σφτ identity explorer</title>
7
+ <style>
8
+ :root {
9
+ --bg: #1a1d24;
10
+ --card: #232730;
11
+ --border: #2c3140;
12
+ --border-bright: #3b4154;
13
+ --text: #e6e9f0;
14
+ --muted: #8b92a3;
15
+ --positive: #6dd870;
16
+ --positive-soft: rgba(109, 216, 112, 0.12);
17
+ --negative: #ff7b7b;
18
+ --on-surface-default: #e6e9f0;
19
+ --outline: #3b4154;
20
+ --accent: #ffd166;
21
+ }
22
+ * { box-sizing: border-box; }
23
+ body {
24
+ margin: 0;
25
+ padding: 16px;
26
+ background: var(--bg);
27
+ color: var(--text);
28
+ font-family: -apple-system, BlinkMacSystemFont, "Pretendard", "Segoe UI", sans-serif;
29
+ -webkit-font-smoothing: antialiased;
30
+ }
31
+ .card {
32
+ background: var(--card);
33
+ border: 1px solid var(--border);
34
+ border-radius: 14px;
35
+ padding: 24px;
36
+ max-width: 980px;
37
+ margin: 0 auto;
38
+ }
39
+ .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; flex-wrap: wrap; gap: 16px; }
40
+ .title { font-size: 17px; font-weight: 600; margin: 0; }
41
+ .subtitle { color: var(--muted); font-size: 13px; margin-top: 4px; }
42
+ .hud { display: flex; gap: 24px; font-family: "SF Mono", "Menlo", monospace; }
43
+ .hud-cell { text-align: center; min-width: 84px; }
44
+ .hud-label { color: var(--muted); font-size: 11px; letter-spacing: 0.4px; }
45
+ .hud-value { font-size: 20px; font-weight: 600; margin-top: 2px; color: var(--text); }
46
+ canvas { display: block; width: 100%; height: 480px; background: var(--bg); border-radius: 10px; border: 1px solid var(--border); margin: 6px 0 18px 0; }
47
+ .controls { display: flex; align-items: center; gap: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
48
+ .controls .label { color: var(--muted); font-size: 13px; min-width: 110px; }
49
+ input[type="range"] { flex: 1; -webkit-appearance: none; appearance: none; height: 6px; background: var(--border); border-radius: 3px; outline: none; }
50
+ input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; background: var(--accent); border-radius: 50%; cursor: pointer; }
51
+ input[type="range"]::-moz-range-thumb { width: 18px; height: 18px; background: var(--accent); border-radius: 50%; cursor: pointer; border: none; }
52
+ .n-val { background: var(--bg); border: 1px solid var(--border-bright); border-radius: 8px; padding: 6px 14px; font-family: "SF Mono", monospace; min-width: 72px; text-align: center; font-size: 14px; }
53
+ .reset-btn { background: var(--border); color: var(--text); border: 1px solid var(--border-bright); border-radius: 24px; padding: 8px 22px; cursor: pointer; font-size: 13px; transition: background 0.15s; }
54
+ .reset-btn:hover { background: var(--border-bright); }
55
+ .footnote { color: var(--muted); font-size: 12px; text-align: center; margin-top: 16px; line-height: 1.7; }
56
+ .footnote code { background: rgba(255,255,255,0.06); padding: 1px 6px; border-radius: 4px; font-family: "SF Mono", monospace; }
57
+ .footnote a { color: var(--accent); text-decoration: none; }
58
+ .footnote a:hover { text-decoration: underline; }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <div class="card">
63
+ <div class="header">
64
+ <div>
65
+ <h1 class="title">🪞 Echoes Experience — σ(n) · φ(n) = n · τ(n)</h1>
66
+ <div class="subtitle">정수 함수 항등식 — n=6 에서만 유일하게 성립</div>
67
+ </div>
68
+ <div class="hud">
69
+ <div class="hud-cell"><div class="hud-label">σ(n) [합]</div><div class="hud-value" id="hud-sigma">12</div></div>
70
+ <div class="hud-cell"><div class="hud-label">φ(n) [피]</div><div class="hud-value" id="hud-phi">2</div></div>
71
+ <div class="hud-cell"><div class="hud-label">τ(n) [개수]</div><div class="hud-value" id="hud-tau">4</div></div>
72
+ </div>
73
+ </div>
74
+
75
+ <canvas id="viz" width="980" height="480"></canvas>
76
+
77
+ <div class="controls">
78
+ <span class="label">정수 n</span>
79
+ <input type="range" id="slider" min="2" max="1000" step="1" value="6">
80
+ <div class="n-val" id="n-val">n = 6</div>
81
+ <button class="reset-btn" id="reset">초기화 (n=6)</button>
82
+ </div>
83
+
84
+ <div class="footnote">
85
+ σ(n)·φ(n) = n·τ(n) — uniquely true at <code>n = 6</code> · slide the range and watch the equation collapse only here.
86
+ Parent repo: <a href="https://github.com/dancinlab/echoes" target="_blank" rel="noopener">dancinlab/echoes</a> ·
87
+ Theorem proof: <a href="https://github.com/dancinlab/echoes/tree/main/lean4-n6" target="_blank" rel="noopener">Lean 4 [2, 30]</a> + Python [2, 10000].
88
+ </div>
89
+ </div>
90
+
91
+ <script>
92
+ (() => {
93
+ const slider = document.getElementById('slider');
94
+ const nVal = document.getElementById('n-val');
95
+ const resetBtn = document.getElementById('reset');
96
+ const hudSigma = document.getElementById('hud-sigma');
97
+ const hudPhi = document.getElementById('hud-phi');
98
+ const hudTau = document.getElementById('hud-tau');
99
+ const canvas = document.getElementById('viz');
100
+ const ctx = canvas.getContext('2d');
101
+
102
+ // ---------- Math ----------
103
+ const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
104
+
105
+ const compute = (n) => {
106
+ const divisors = [];
107
+ let sigma = 0;
108
+ for (let i = 1; i <= n; i++) {
109
+ if (n % i === 0) { divisors.push(i); sigma += i; }
110
+ }
111
+ const phiElements = [];
112
+ for (let i = 1; i <= n; i++) {
113
+ if (gcd(n, i) === 1) phiElements.push(i);
114
+ }
115
+ return {
116
+ n,
117
+ divisors,
118
+ phiElements,
119
+ sigma,
120
+ phi: phiElements.length,
121
+ tau: divisors.length,
122
+ lhs: sigma * phiElements.length,
123
+ rhs: n * divisors.length,
124
+ };
125
+ };
126
+
127
+ let state = compute(6);
128
+
129
+ // ---------- Theme helpers ----------
130
+ const css = (name) => getComputedStyle(document.documentElement).getPropertyValue(name).trim();
131
+ const transp = (name, alpha) => {
132
+ const c = css(name);
133
+ if (c.startsWith('rgba')) return c;
134
+ if (c.startsWith('#') && c.length === 7) {
135
+ const r = parseInt(c.slice(1, 3), 16);
136
+ const g = parseInt(c.slice(3, 5), 16);
137
+ const b = parseInt(c.slice(5, 7), 16);
138
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
139
+ }
140
+ return c;
141
+ };
142
+
143
+ // ---------- Render ----------
144
+ const fitCanvas = () => {
145
+ const dpr = window.devicePixelRatio || 1;
146
+ const rect = canvas.getBoundingClientRect();
147
+ canvas.width = Math.floor(rect.width * dpr);
148
+ canvas.height = Math.floor(rect.height * dpr);
149
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
150
+ };
151
+
152
+ const draw = () => {
153
+ fitCanvas();
154
+ const rect = canvas.getBoundingClientRect();
155
+ const width = rect.width;
156
+ const height = rect.height;
157
+ const cx = width / 2;
158
+ const padding = 32;
159
+ const stepY = (height - 200) / 3;
160
+
161
+ ctx.clearRect(0, 0, width, height);
162
+ ctx.fillStyle = css('--bg');
163
+ ctx.fillRect(0, 0, width, height);
164
+
165
+ ctx.textAlign = 'center';
166
+ ctx.textBaseline = 'alphabetic';
167
+
168
+ // --- Section 1: σ(n) sum-of-divisors ---
169
+ ctx.fillStyle = css('--on-surface-default');
170
+ ctx.font = '700 17px "Pretendard", -apple-system, sans-serif';
171
+ ctx.fillText(`1. 약수의 합 σ(${state.n})`, cx, padding + 18);
172
+ ctx.font = '14px "SF Mono", "Menlo", monospace';
173
+ ctx.fillStyle = css('--muted');
174
+ const divsList = state.divisors.length > 20
175
+ ? state.divisors.slice(0, 20).join(' + ') + ' + …'
176
+ : state.divisors.join(' + ');
177
+ ctx.fillText(`${divsList} = ${state.sigma}`, cx, padding + 42);
178
+
179
+ // --- Section 2: φ(n) totient ---
180
+ ctx.fillStyle = css('--on-surface-default');
181
+ ctx.font = '700 17px "Pretendard", -apple-system, sans-serif';
182
+ ctx.fillText(`2. 오일러 피 함수 φ(${state.n})`, cx, padding + stepY + 18);
183
+ ctx.font = '14px "SF Mono", "Menlo", monospace';
184
+ ctx.fillStyle = css('--muted');
185
+ const phiList = state.phiElements.length > 15
186
+ ? '{' + state.phiElements.slice(0, 15).join(', ') + ', …}'
187
+ : '{' + state.phiElements.join(', ') + '}';
188
+ ctx.fillText(`서로소: ${phiList} → φ = ${state.phi}`, cx, padding + stepY + 42);
189
+
190
+ // --- Section 3: τ(n) divisor-count ---
191
+ ctx.fillStyle = css('--on-surface-default');
192
+ ctx.font = '700 17px "Pretendard", -apple-system, sans-serif';
193
+ ctx.fillText(`3. 약수의 개수 τ(${state.n})`, cx, padding + stepY * 2 + 18);
194
+ ctx.font = '14px "SF Mono", "Menlo", monospace';
195
+ ctx.fillStyle = css('--muted');
196
+ ctx.fillText(`|{ d : d | n }| = ${state.tau}`, cx, padding + stepY * 2 + 42);
197
+
198
+ // --- Section 4: comparison box ---
199
+ const isEqual = state.lhs === state.rhs;
200
+ const footerY = height - 140;
201
+ const boxW = Math.min(width * 0.88, 620);
202
+ const boxX = (width - boxW) / 2;
203
+
204
+ ctx.fillStyle = isEqual ? transp('--positive', 0.10) : 'rgba(255, 123, 123, 0.06)';
205
+ ctx.strokeStyle = isEqual ? css('--positive') : css('--outline');
206
+ ctx.lineWidth = 2;
207
+ const r = 12;
208
+ ctx.beginPath();
209
+ ctx.moveTo(boxX + r, footerY);
210
+ ctx.lineTo(boxX + boxW - r, footerY);
211
+ ctx.quadraticCurveTo(boxX + boxW, footerY, boxX + boxW, footerY + r);
212
+ ctx.lineTo(boxX + boxW, footerY + 110 - r);
213
+ ctx.quadraticCurveTo(boxX + boxW, footerY + 110, boxX + boxW - r, footerY + 110);
214
+ ctx.lineTo(boxX + r, footerY + 110);
215
+ ctx.quadraticCurveTo(boxX, footerY + 110, boxX, footerY + 110 - r);
216
+ ctx.lineTo(boxX, footerY + r);
217
+ ctx.quadraticCurveTo(boxX, footerY, boxX + r, footerY);
218
+ ctx.closePath();
219
+ ctx.fill();
220
+ ctx.stroke();
221
+
222
+ // LHS: σ(n) × φ(n)
223
+ ctx.fillStyle = css('--on-surface-default');
224
+ ctx.font = '600 14px "Pretendard", sans-serif';
225
+ ctx.textAlign = 'right';
226
+ ctx.fillText('σ(n) × φ(n)', cx - 28, footerY + 36);
227
+ ctx.font = '700 22px "SF Mono", "Menlo", monospace';
228
+ ctx.fillText(`${state.sigma} × ${state.phi} = ${state.lhs}`, cx - 28, footerY + 72);
229
+
230
+ // Center: = or ≠
231
+ ctx.textAlign = 'center';
232
+ ctx.font = '700 30px "SF Mono", "Menlo", monospace';
233
+ ctx.fillStyle = isEqual ? css('--positive') : css('--negative');
234
+ ctx.fillText(isEqual ? '=' : '≠', cx, footerY + 64);
235
+
236
+ // RHS: n × τ(n)
237
+ ctx.fillStyle = css('--on-surface-default');
238
+ ctx.font = '600 14px "Pretendard", sans-serif';
239
+ ctx.textAlign = 'left';
240
+ ctx.fillText('n × τ(n)', cx + 28, footerY + 36);
241
+ ctx.font = '700 22px "SF Mono", "Menlo", monospace';
242
+ ctx.fillText(`${state.n} × ${state.tau} = ${state.rhs}`, cx + 28, footerY + 72);
243
+
244
+ // Tag
245
+ ctx.textAlign = 'center';
246
+ ctx.font = '700 12px "Pretendard", sans-serif';
247
+ const tagText = isEqual ? '✓ 방정식 성립 (uniquely at n=6 in [2,1000])' : '✗ 불일치';
248
+ const tagColor = isEqual ? css('--positive') : css('--negative');
249
+ const tagBg = isEqual ? transp('--positive', 0.18) : 'rgba(255, 123, 123, 0.14)';
250
+ const tagW = ctx.measureText(tagText).width + 28;
251
+ const tagH = 24;
252
+ const tagX = cx - tagW / 2;
253
+ const tagY = footerY - tagH / 2;
254
+ ctx.fillStyle = tagBg;
255
+ ctx.strokeStyle = tagColor;
256
+ ctx.lineWidth = 1;
257
+ ctx.beginPath();
258
+ ctx.roundRect ? ctx.roundRect(tagX, tagY, tagW, tagH, 12) : ctx.rect(tagX, tagY, tagW, tagH);
259
+ ctx.fill();
260
+ ctx.stroke();
261
+ ctx.fillStyle = tagColor;
262
+ ctx.fillText(tagText, cx, tagY + 16);
263
+ };
264
+
265
+ // ---------- Wire-up ----------
266
+ const updateHUD = () => {
267
+ hudSigma.textContent = state.sigma;
268
+ hudPhi.textContent = state.phi;
269
+ hudTau.textContent = state.tau;
270
+ nVal.textContent = `n = ${state.n}`;
271
+ };
272
+
273
+ const setN = (n) => {
274
+ state = compute(n);
275
+ slider.value = String(n);
276
+ updateHUD();
277
+ draw();
278
+ };
279
+
280
+ slider.addEventListener('input', (e) => setN(parseInt(e.target.value, 10)));
281
+ resetBtn.addEventListener('click', () => setN(6));
282
+ window.addEventListener('resize', () => draw());
283
+
284
+ updateHUD();
285
+ draw();
286
+ })();
287
+ </script>
288
+ </body>
289
+ </html>