jwest33 commited on
Commit
8990716
Β·
1 Parent(s): 51778fb

init commit

Browse files
Files changed (3) hide show
  1. README.md +32 -5
  2. index.html +1248 -18
  3. style.css +0 -28
README.md CHANGED
@@ -1,12 +1,39 @@
1
  ---
2
- title: Null Space Visualizer
3
- emoji: πŸ“Š
4
  colorFrom: blue
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
8
  license: mit
9
- short_description: Null-space projection for abliteration demo
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Null Space Projection Visualizer
3
+ emoji: πŸ“
4
  colorFrom: blue
5
+ colorTo: cyan
6
  sdk: static
7
  pinned: false
8
  license: mit
9
+ short_description: Interactive demo explaining null space projection for model abliteration
10
  ---
11
 
12
+ # Null Space Projection - Interactive Demo
13
+
14
+ An interactive visualization explaining how **null space projection** preserves model capabilities during abliteration.
15
+
16
+ ## What You'll Learn
17
+
18
+ 1. **The Problem**: How to modify model weights without breaking useful capabilities
19
+ 2. **Null Space Concept**: The mathematical space where modifications have zero effect on preservation inputs
20
+ 3. **The Projection**: How to decompose updates into safe and unsafe components
21
+
22
+ ## Features
23
+
24
+ - **Interactive 2D visualization** with adjustable vectors
25
+ - **Step-by-step flow** showing the projection process
26
+ - **Live math breakdown** with color-coded calculations
27
+ - **Runnable Python code** toy example
28
+
29
+ ## How It Works
30
+
31
+ When removing refusal behavior from language models, we want to:
32
+ - βœ… Remove the refusal direction from weights
33
+ - βœ… Preserve capabilities (math, coding, reasoning)
34
+
35
+ Null space projection ensures `K Β· Ξ”W' = 0`, meaning preservation inputs are completely unaffected by our modification.
36
+
37
+ ## Related
38
+
39
+ This demo accompanies the [Abliteration Toolkit](https://github.com/jwest33/abliterator) for removing refusal behavior from language models.
index.html CHANGED
@@ -1,19 +1,1249 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Null Space Projection - Interactive Demo</title>
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect fill='%231a1a2e' width='32' height='32' rx='4'/><line x1='4' y1='28' x2='28' y2='4' stroke='%234caf50' stroke-width='2'/><line x1='4' y1='16' x2='20' y2='8' stroke='%23f858fb' stroke-width='2.5'/><circle cx='20' cy='8' r='2' fill='%23f858fb'/><line x1='4' y1='16' x2='16' y2='16' stroke='%2300d4ff' stroke-width='2.5'/><circle cx='16' cy='16' r='2' fill='%2300d4ff'/><line x1='20' y1='8' x2='16' y2='16' stroke='white' stroke-width='1' stroke-dasharray='2'/></svg>">
8
+ <style>
9
+ * {
10
+ box-sizing: border-box;
11
+ margin: 0;
12
+ padding: 0;
13
+ }
14
+
15
+ body {
16
+ font-family: 'Segoe UI', system-ui, sans-serif;
17
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
18
+ color: #e0e0e0;
19
+ min-height: 100vh;
20
+ padding: 20px;
21
+ }
22
+
23
+ .container {
24
+ max-width: 1400px;
25
+ margin: 0 auto;
26
+ }
27
+
28
+ h1 {
29
+ text-align: center;
30
+ color: #00d4ff;
31
+ margin-bottom: 10px;
32
+ font-size: 2.2em;
33
+ text-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
34
+ }
35
+
36
+ .subtitle {
37
+ text-align: center;
38
+ color: #888;
39
+ margin-bottom: 30px;
40
+ font-size: 1.1em;
41
+ }
42
+
43
+ .main-grid {
44
+ display: grid;
45
+ grid-template-columns: 1fr 1fr;
46
+ gap: 20px;
47
+ margin-bottom: 20px;
48
+ }
49
+
50
+ .panel {
51
+ background: rgba(255, 255, 255, 0.05);
52
+ border-radius: 12px;
53
+ padding: 20px;
54
+ border: 1px solid rgba(255, 255, 255, 0.1);
55
+ }
56
+
57
+ .panel h2 {
58
+ color: #00d4ff;
59
+ margin-bottom: 15px;
60
+ font-size: 1.3em;
61
+ border-bottom: 1px solid rgba(0, 212, 255, 0.3);
62
+ padding-bottom: 10px;
63
+ }
64
+
65
+ .panel h3 {
66
+ color: #f858fb;
67
+ margin: 15px 0 10px 0;
68
+ font-size: 1.1em;
69
+ }
70
+
71
+ .canvas-container {
72
+ background: rgba(0, 0, 0, 0.3);
73
+ border-radius: 8px;
74
+ padding: 10px;
75
+ display: flex;
76
+ justify-content: center;
77
+ align-items: center;
78
+ }
79
+
80
+ canvas {
81
+ border-radius: 4px;
82
+ }
83
+
84
+ .controls {
85
+ display: grid;
86
+ gap: 15px;
87
+ }
88
+
89
+ .control-group {
90
+ background: rgba(0, 0, 0, 0.2);
91
+ padding: 15px;
92
+ border-radius: 8px;
93
+ }
94
+
95
+ .control-group label {
96
+ display: block;
97
+ margin-bottom: 8px;
98
+ color: #aaa;
99
+ font-size: 0.9em;
100
+ }
101
+
102
+ .slider-row {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 10px;
106
+ margin-bottom: 10px;
107
+ }
108
+
109
+ .slider-row span {
110
+ min-width: 80px;
111
+ color: #00d4ff;
112
+ }
113
+
114
+ .slider-row input[type="range"] {
115
+ flex: 1;
116
+ height: 6px;
117
+ border-radius: 3px;
118
+ background: #333;
119
+ outline: none;
120
+ -webkit-appearance: none;
121
+ }
122
+
123
+ .slider-row input[type="range"]::-webkit-slider-thumb {
124
+ -webkit-appearance: none;
125
+ width: 16px;
126
+ height: 16px;
127
+ border-radius: 50%;
128
+ background: #00d4ff;
129
+ cursor: pointer;
130
+ }
131
+
132
+ .slider-row .value {
133
+ min-width: 50px;
134
+ text-align: right;
135
+ font-family: monospace;
136
+ color: #fff;
137
+ }
138
+
139
+ button {
140
+ background: linear-gradient(135deg, #00d4ff 0%, #0099cc 100%);
141
+ color: #000;
142
+ border: none;
143
+ padding: 12px 24px;
144
+ border-radius: 6px;
145
+ font-size: 1em;
146
+ font-weight: bold;
147
+ cursor: pointer;
148
+ transition: all 0.3s ease;
149
+ width: 100%;
150
+ margin-top: 10px;
151
+ }
152
+
153
+ button:hover {
154
+ transform: translateY(-2px);
155
+ box-shadow: 0 5px 20px rgba(0, 212, 255, 0.4);
156
+ }
157
+
158
+ button.secondary {
159
+ background: linear-gradient(135deg, #f858fb 0%, #ee00ff 100%);
160
+ }
161
+
162
+ .legend {
163
+ display: flex;
164
+ flex-wrap: wrap;
165
+ gap: 15px;
166
+ margin-top: 15px;
167
+ padding: 10px;
168
+ background: rgba(0, 0, 0, 0.2);
169
+ border-radius: 6px;
170
+ }
171
+
172
+ .legend-item {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 8px;
176
+ font-size: 0.85em;
177
+ }
178
+
179
+ .legend-color {
180
+ width: 20px;
181
+ height: 4px;
182
+ border-radius: 2px;
183
+ }
184
+
185
+ .explanation {
186
+ background: rgba(0, 212, 255, 0.1);
187
+ border-left: 3px solid #00d4ff;
188
+ padding: 15px;
189
+ margin: 15px 0;
190
+ border-radius: 0 8px 8px 0;
191
+ font-size: 0.95em;
192
+ line-height: 1.6;
193
+ }
194
+
195
+ .math {
196
+ font-family: 'Cambria Math', 'Times New Roman', serif;
197
+ background: rgba(0, 0, 0, 0.3);
198
+ padding: 10px 15px;
199
+ border-radius: 6px;
200
+ margin: 10px 0;
201
+ overflow-x: auto;
202
+ font-size: 1.1em;
203
+ }
204
+
205
+ .math-inline {
206
+ font-family: 'Cambria Math', 'Times New Roman', serif;
207
+ color: #f858fb;
208
+ }
209
+
210
+ .results-grid {
211
+ display: grid;
212
+ grid-template-columns: repeat(3, 1fr);
213
+ gap: 10px;
214
+ margin-top: 15px;
215
+ }
216
+
217
+ .result-box {
218
+ background: rgba(0, 0, 0, 0.3);
219
+ padding: 12px;
220
+ border-radius: 6px;
221
+ text-align: center;
222
+ }
223
+
224
+ .result-box .label {
225
+ font-size: 0.8em;
226
+ color: #888;
227
+ margin-bottom: 5px;
228
+ }
229
+
230
+ .result-box .value {
231
+ font-family: monospace;
232
+ font-size: 1.1em;
233
+ color: #00d4ff;
234
+ }
235
+
236
+ .step-indicator {
237
+ display: flex;
238
+ justify-content: center;
239
+ gap: 10px;
240
+ margin: 20px 0;
241
+ }
242
+
243
+ .step {
244
+ width: 40px;
245
+ height: 40px;
246
+ border-radius: 50%;
247
+ background: rgba(255, 255, 255, 0.1);
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ font-weight: bold;
252
+ cursor: pointer;
253
+ transition: all 0.3s ease;
254
+ }
255
+
256
+ .step.active {
257
+ background: #00d4ff;
258
+ color: #000;
259
+ box-shadow: 0 0 20px rgba(0, 212, 255, 0.5);
260
+ }
261
+
262
+ .step.completed {
263
+ background: #4caf50;
264
+ color: #fff;
265
+ }
266
+
267
+ .full-width {
268
+ grid-column: 1 / -1;
269
+ }
270
+
271
+ .code-block {
272
+ background: #0d1117;
273
+ border-radius: 6px;
274
+ padding: 15px;
275
+ font-family: 'Consolas', 'Monaco', monospace;
276
+ font-size: 0.85em;
277
+ overflow-x: auto;
278
+ margin: 10px 0;
279
+ white-space: pre;
280
+ line-height: 1.5;
281
+ }
282
+
283
+ .code-block .comment {
284
+ color: #6a737d;
285
+ }
286
+
287
+ .code-block .keyword {
288
+ color: #ff7b72;
289
+ }
290
+
291
+ .code-block .function {
292
+ color: #d2a8ff;
293
+ }
294
+
295
+ .code-block .string {
296
+ color: #a5d6ff;
297
+ }
298
+
299
+ .code-block .number {
300
+ color: #79c0ff;
301
+ }
302
+
303
+ .tabs {
304
+ display: flex;
305
+ gap: 5px;
306
+ margin-bottom: 15px;
307
+ }
308
+
309
+ .tab {
310
+ padding: 10px 20px;
311
+ background: rgba(255, 255, 255, 0.05);
312
+ border: none;
313
+ color: #888;
314
+ cursor: pointer;
315
+ border-radius: 6px 6px 0 0;
316
+ transition: all 0.3s ease;
317
+ }
318
+
319
+ .tab.active {
320
+ background: rgba(0, 212, 255, 0.2);
321
+ color: #00d4ff;
322
+ }
323
+
324
+ .tab-content {
325
+ display: none;
326
+ }
327
+
328
+ .tab-content.active {
329
+ display: block;
330
+ }
331
+
332
+ .highlight {
333
+ color: #ffd700;
334
+ font-weight: bold;
335
+ }
336
+
337
+ .math-breakdown-bar {
338
+ grid-column: 1 / -1;
339
+ background: rgba(0, 0, 0, 0.4);
340
+ border-radius: 8px;
341
+ padding: 20px;
342
+ border: 1px solid rgba(255, 255, 255, 0.1);
343
+ }
344
+
345
+ .math-breakdown-bar .vectors-row {
346
+ display: flex;
347
+ justify-content: center;
348
+ gap: 50px;
349
+ font-family: 'Consolas', 'Monaco', monospace;
350
+ font-size: 1.1em;
351
+ margin-bottom: 15px;
352
+ padding-bottom: 15px;
353
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
354
+ }
355
+
356
+ .math-breakdown-bar .vector-item {
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 10px;
360
+ }
361
+
362
+ .math-breakdown-bar .vector-label {
363
+ font-weight: bold;
364
+ }
365
+
366
+ .math-breakdown-bar .vector-value {
367
+ color: #fff;
368
+ background: rgba(255, 255, 255, 0.05);
369
+ padding: 4px 12px;
370
+ border-radius: 4px;
371
+ }
372
+
373
+ .math-breakdown-bar .calculations-row {
374
+ display: grid;
375
+ grid-template-columns: 1fr 1fr 1fr;
376
+ gap: 20px;
377
+ font-family: 'Consolas', 'Monaco', monospace;
378
+ font-size: 0.85em;
379
+ line-height: 1.7;
380
+ }
381
+
382
+ .math-breakdown-bar .calc-section {
383
+ background: rgba(0, 0, 0, 0.3);
384
+ padding: 12px 15px;
385
+ border-radius: 6px;
386
+ }
387
+
388
+ .math-breakdown-bar .section-label {
389
+ color: #888;
390
+ font-size: 0.8em;
391
+ margin-bottom: 8px;
392
+ text-transform: uppercase;
393
+ letter-spacing: 1px;
394
+ }
395
+
396
+ .math-breakdown-bar .k-color { color: #4caf50; }
397
+ .math-breakdown-bar .dw-color { color: #f858fb; }
398
+ .math-breakdown-bar .dwp-color { color: #00d4ff; }
399
+ .math-breakdown-bar .calc-intermediate { color: #ffd700; }
400
+ .math-breakdown-bar .calc-result { color: #fff; font-weight: bold; }
401
+ .math-breakdown-bar .verification-pass { color: #4caf50; font-weight: bold; }
402
+ .math-breakdown-bar .verification-fail { color: #f858fb; font-weight: bold; }
403
+
404
+ @media (max-width: 1000px) {
405
+ .main-grid {
406
+ grid-template-columns: 1fr;
407
+ }
408
+
409
+ .math-breakdown-bar .vectors-row {
410
+ flex-wrap: wrap;
411
+ gap: 15px;
412
+ }
413
+
414
+ .math-breakdown-bar .calculations-row {
415
+ grid-template-columns: 1fr;
416
+ }
417
+ }
418
+ </style>
419
+ </head>
420
+ <body>
421
+ <div class="container">
422
+ <h1>Null Space Projection</h1>
423
+ <p class="subtitle">Interactive visualization of how null space constraints preserve capabilities during model modification</p>
424
+
425
+ <div class="step-indicator">
426
+ <div class="step active" data-step="1" onclick="setStep(1)">1</div>
427
+ <div class="step" data-step="2" onclick="setStep(2)">2</div>
428
+ <div class="step" data-step="3" onclick="setStep(3)">3</div>
429
+ </div>
430
+
431
+ <div class="main-grid">
432
+ <!-- Left Panel: Visualization -->
433
+ <div class="panel">
434
+ <h2>2D Vector Space Visualization</h2>
435
+ <div class="canvas-container">
436
+ <canvas id="mainCanvas" width="500" height="500"></canvas>
437
+ </div>
438
+ <div class="legend">
439
+ <div class="legend-item">
440
+ <div class="legend-color" style="background: #4caf50;"></div>
441
+ <span>Preservation Vector (K)</span>
442
+ </div>
443
+ <div class="legend-item">
444
+ <div class="legend-color" style="background: #f858fb;"></div>
445
+ <span>Original Update (Ξ”W)</span>
446
+ </div>
447
+ <div class="legend-item">
448
+ <div class="legend-color" style="background: #00d4ff;"></div>
449
+ <span>Projected Update (Ξ”W')</span>
450
+ </div>
451
+ <div class="legend-item">
452
+ <div class="legend-color" style="background: rgba(76, 175, 80, 0.2);"></div>
453
+ <span>Row Space of K</span>
454
+ </div>
455
+ <div class="legend-item">
456
+ <div class="legend-color" style="background: rgba(0, 212, 255, 0.2);"></div>
457
+ <span>Null Space of K</span>
458
+ </div>
459
+ </div>
460
+
461
+ </div>
462
+
463
+ <!-- Right Panel: Controls & Explanation -->
464
+ <div class="panel">
465
+ <h2>Interactive Controls</h2>
466
+
467
+ <div class="controls">
468
+ <div class="control-group">
469
+ <label>Preservation Vector K (what we want to preserve)</label>
470
+ <div class="slider-row">
471
+ <span>K<sub>x</sub></span>
472
+ <input type="range" id="kx" min="-100" max="100" value="80">
473
+ <span class="value" id="kx-val">0.80</span>
474
+ </div>
475
+ <div class="slider-row">
476
+ <span>K<sub>y</sub></span>
477
+ <input type="range" id="ky" min="-100" max="100" value="30">
478
+ <span class="value" id="ky-val">0.30</span>
479
+ </div>
480
+ </div>
481
+
482
+ <div class="control-group">
483
+ <label>Original Update Ξ”W (modification we want to apply)</label>
484
+ <div class="slider-row">
485
+ <span>Ξ”W<sub>x</sub></span>
486
+ <input type="range" id="dwx" min="-100" max="100" value="60">
487
+ <span class="value" id="dwx-val">0.60</span>
488
+ </div>
489
+ <div class="slider-row">
490
+ <span>Ξ”W<sub>y</sub></span>
491
+ <input type="range" id="dwy" min="-100" max="100" value="70">
492
+ <span class="value" id="dwy-val">0.70</span>
493
+ </div>
494
+ </div>
495
+
496
+ <button onclick="animateProjection()">Animate Projection</button>
497
+ <button class="secondary" onclick="randomize()">Randomize Vectors</button>
498
+ </div>
499
+
500
+ <div class="results-grid">
501
+ <div class="result-box">
502
+ <div class="label">K Β· Ξ”W (before)</div>
503
+ <div class="value" id="dot-before">-</div>
504
+ </div>
505
+ <div class="result-box">
506
+ <div class="label">K Β· Ξ”W' (after)</div>
507
+ <div class="value" id="dot-after">-</div>
508
+ </div>
509
+ <div class="result-box">
510
+ <div class="label">|Ξ”W'| / |Ξ”W|</div>
511
+ <div class="value" id="magnitude-ratio">-</div>
512
+ </div>
513
+ </div>
514
+ </div>
515
+
516
+ <!-- Full-width Math Breakdown -->
517
+ <div class="math-breakdown-bar">
518
+ <!-- Vector Coordinates Row -->
519
+ <div class="vectors-row">
520
+ <div class="vector-item">
521
+ <span class="vector-label k-color">K</span>
522
+ <span class="vector-value">[<span id="bar-kx">0.80</span>, <span id="bar-ky">0.30</span>]</span>
523
+ </div>
524
+ <div class="vector-item">
525
+ <span class="vector-label dw-color">Ξ”W</span>
526
+ <span class="vector-value">[<span id="bar-dwx">0.60</span>, <span id="bar-dwy">0.70</span>]</span>
527
+ </div>
528
+ <div class="vector-item">
529
+ <span class="vector-label dwp-color">Ξ”W'</span>
530
+ <span class="vector-value">[<span id="bar-dwpx">-0.16</span>, <span id="bar-dwpy">0.42</span>]</span>
531
+ </div>
532
+ </div>
533
+
534
+ <!-- Calculations Row (3 columns) -->
535
+ <div class="calculations-row">
536
+ <!-- Dot Product -->
537
+ <div class="calc-section">
538
+ <div class="section-label">Dot Product (K Β· Ξ”W)</div>
539
+ <div>= (<span class="k-color" id="dot-kx">0.80</span> Γ— <span class="dw-color" id="dot-dwx">0.60</span>) + (<span class="k-color" id="dot-ky">0.30</span> Γ— <span class="dw-color" id="dot-dwy">0.70</span>)</div>
540
+ <div>= <span class="calc-intermediate" id="dot-term1">0.48</span> + <span class="calc-intermediate" id="dot-term2">0.21</span></div>
541
+ <div>= <span class="calc-result" id="dot-result">0.69</span></div>
542
+ </div>
543
+
544
+ <!-- Projection Formula -->
545
+ <div class="calc-section">
546
+ <div class="section-label">Projection Formula</div>
547
+ <div><span class="dwp-color">Ξ”W'</span> = <span class="dw-color">Ξ”W</span> βˆ’ [(KΒ·Ξ”W) / |K|Β²] Γ— <span class="k-color">K</span></div>
548
+ <div style="color:#666">|K|Β² = <span id="k-mag-sq">0.73</span>, scale = <span id="proj-scale">0.95</span></div>
549
+ <div>= [<span class="dw-color" id="proj-dwx">0.60</span>, <span class="dw-color" id="proj-dwy">0.70</span>] βˆ’ [<span class="calc-intermediate" id="proj-subx">0.76</span>, <span class="calc-intermediate" id="proj-suby">0.28</span>]</div>
550
+ <div>= <span class="dwp-color">[<span id="math-dwpx">-0.16</span>, <span id="math-dwpy">0.42</span>]</span></div>
551
+ </div>
552
+
553
+ <!-- Verification -->
554
+ <div class="calc-section">
555
+ <div class="section-label">Verification (K Β· Ξ”W')</div>
556
+ <div>= (<span class="k-color" id="ver-kx">0.80</span> Γ— <span class="dwp-color" id="ver-dwpx">-0.16</span>) + (<span class="k-color" id="ver-ky">0.30</span> Γ— <span class="dwp-color" id="ver-dwpy">0.42</span>)</div>
557
+ <div>= <span class="calc-intermediate" id="ver-term1">-0.128</span> + <span class="calc-intermediate" id="ver-term2">0.126</span></div>
558
+ <div>= <span id="ver-result" class="verification-pass">β‰ˆ 0 βœ“</span></div>
559
+ </div>
560
+ </div>
561
+ </div>
562
+
563
+ <!-- Step-by-step explanation -->
564
+ <div class="panel full-width">
565
+ <div class="tabs">
566
+ <button class="tab active" onclick="showTab('concept')">Concept</button>
567
+ <button class="tab" onclick="showTab('math')">Math</button>
568
+ <button class="tab" onclick="showTab('code')">Code</button>
569
+ <button class="tab" onclick="showTab('application')">ML Application</button>
570
+ </div>
571
+
572
+ <div id="concept" class="tab-content active">
573
+ <div id="step1-content" class="step-content active">
574
+ <h3>Step 1: The Problem</h3>
575
+ <div class="explanation">
576
+ <p><strong>Goal:</strong> We want to modify a model's weights to remove unwanted behavior (like refusal),
577
+ but we don't want to break its useful capabilities (like math, coding, reasoning).</p>
578
+ <p style="margin-top: 10px;"><strong>The Challenge:</strong> A naive weight modification <span class="math-inline">Ξ”W</span>
579
+ might accidentally affect the outputs for inputs we care about preserving.</p>
580
+ <p style="margin-top: 10px;"><strong>Solution:</strong> Project <span class="math-inline">Ξ”W</span> into the
581
+ <span class="highlight">null space</span> of the preservation activations, ensuring the modification
582
+ has <em>zero effect</em> on preserved capabilities.</p>
583
+ </div>
584
+ </div>
585
+
586
+ <div id="step2-content" class="step-content">
587
+ <h3>Step 2: Understanding Null Space</h3>
588
+ <div class="explanation">
589
+ <p>The <span class="highlight">null space</span> of a matrix <span class="math-inline">K</span> contains
590
+ all vectors <span class="math-inline">x</span> where <span class="math-inline">Kx = 0</span>.</p>
591
+ <p style="margin-top: 10px;">In 2D visualization:</p>
592
+ <ul style="margin-left: 20px; margin-top: 5px;">
593
+ <li>The <span style="color: #4caf50;">green line</span> shows the "row space" of K (directions that K responds to)</li>
594
+ <li>The <span style="color: #00d4ff;">blue region</span> shows the "null space" (perpendicular to K)</li>
595
+ <li>Any vector in the null space, when multiplied by K, gives zero!</li>
596
+ </ul>
597
+ </div>
598
+ </div>
599
+
600
+ <div id="step3-content" class="step-content">
601
+ <h3>Step 3: Projection & Result</h3>
602
+ <div class="explanation">
603
+ <p>We decompose the original update <span class="math-inline">Ξ”W</span> into two parts:</p>
604
+ <ul style="margin-left: 20px; margin-top: 5px;">
605
+ <li><strong>Row space component:</strong> Part that affects preservation inputs (we <em>remove</em> this)</li>
606
+ <li><strong>Null space component:</strong> Part with zero effect on preservation (we <em>keep</em> this)</li>
607
+ </ul>
608
+ <p style="margin-top: 10px;">The projected update <span class="math-inline">Ξ”W'</span> = <span class="math-inline">Ξ”W</span> minus its row-space component.</p>
609
+ <p style="margin-top: 10px;"><strong>Result:</strong></p>
610
+ <ul style="margin-left: 20px; margin-top: 5px;">
611
+ <li><span style="color: #4caf50;">Preservation guaranteed:</span> <span class="math-inline">K Β· Ξ”W' = 0</span></li>
612
+ <li><span style="color: #f858fb;">Some modification lost:</span> <span class="math-inline">|Ξ”W'| ≀ |Ξ”W|</span></li>
613
+ </ul>
614
+ <p style="margin-top: 10px;"><strong>Trade-off:</strong> The more aligned your update is with preservation directions, the more gets removed.
615
+ In practice, refusal behavior often lives in directions somewhat orthogonal to general capabilities, so we can remove most of it while preserving capabilities!</p>
616
+ </div>
617
+ </div>
618
+ </div>
619
+
620
+ <div id="math" class="tab-content">
621
+ <h3>Mathematical Formulation</h3>
622
+ <div class="explanation">
623
+ <p>Given preservation activations <span class="math-inline">K ∈ ℝ<sup>nΓ—d</sup></span> (n samples, d dimensions):</p>
624
+ </div>
625
+
626
+ <div class="math">
627
+ <strong>1. Compute SVD of K:</strong><br>
628
+ K = UΞ£V<sup>T</sup>
629
+ </div>
630
+
631
+ <div class="math">
632
+ <strong>2. Build null space projector:</strong><br>
633
+ P<sub>null</sub> = I - V<sub>r</sub>V<sub>r</sub><sup>T</sup><br>
634
+ <span style="color: #888; font-size: 0.9em;">(where V<sub>r</sub> contains the r significant right singular vectors)</span>
635
+ </div>
636
+
637
+ <div class="math">
638
+ <strong>3. Project update into null space:</strong><br>
639
+ Ξ”W' = Ξ”W Β· P<sub>null</sub>
640
+ </div>
641
+
642
+ <div class="math">
643
+ <strong>4. Verify preservation:</strong><br>
644
+ K Β· Ξ”W' = K Β· Ξ”W Β· (I - VV<sup>T</sup>) = K Β· Ξ”W - K Β· Ξ”W Β· VV<sup>T</sup> β‰ˆ 0
645
+ </div>
646
+
647
+ <div class="explanation" style="margin-top: 20px;">
648
+ <p><strong>Why it works:</strong> The rows of V span the row space of K. Subtracting <span class="math-inline">VV<sup>T</sup></span>
649
+ removes all components in the row space, leaving only the null space.</p>
650
+ </div>
651
+ </div>
652
+
653
+ <div id="code" class="tab-content">
654
+ <h3>Implementation</h3>
655
+ <div class="code-block">
656
+ <span class="keyword">import</span> torch
657
+
658
+ <span class="keyword">def</span> <span class="function">compute_null_space_projector</span>(K):
659
+ <span class="string">"""
660
+ Compute projector onto null space of K.
661
+
662
+ Args:
663
+ K: Preservation activations [n_samples, d_model]
664
+
665
+ Returns:
666
+ P_null: Projector matrix [d_model, d_model]
667
+ """</span>
668
+ <span class="comment"># Compute SVD (no centering - we want exact null space of K)</span>
669
+ U, S, Vh = torch.linalg.svd(K, full_matrices=<span class="keyword">False</span>)
670
+
671
+ <span class="comment"># Use all right singular vectors (rows of Vh) as row space basis</span>
672
+ <span class="comment"># V_r columns span the row space of K</span>
673
+ V_r = Vh.T <span class="comment"># [d_model, rank] where rank = min(n_samples, d_model)</span>
674
+
675
+ <span class="comment"># Null space projector: I - V_r @ V_r.T</span>
676
+ <span class="comment"># Projects onto orthogonal complement of row space</span>
677
+ P_null = torch.eye(K.shape[<span class="number">1</span>], device=K.device, dtype=K.dtype) - V_r @ V_r.T
678
+
679
+ <span class="keyword">return</span> P_null
680
+
681
+
682
+ <span class="keyword">def</span> <span class="function">apply_null_space_projection</span>(delta_W, P_null):
683
+ <span class="string">"""Project weight update into null space."""</span>
684
+ <span class="keyword">return</span> delta_W @ P_null
685
+
686
+
687
+ <span class="comment"># ===========================================</span>
688
+ <span class="comment"># Runnable example with synthetic data:</span>
689
+ <span class="comment"># ===========================================</span>
690
+
691
+ <span class="comment"># Simulate preservation activations (e.g., from math/coding prompts)</span>
692
+ <span class="comment"># Shape: [n_samples, hidden_dim]</span>
693
+ n_samples, hidden_dim = <span class="number">50</span>, <span class="number">128</span>
694
+ K = torch.randn(n_samples, hidden_dim)
695
+
696
+ <span class="comment"># Compute the null space projector</span>
697
+ P_null = compute_null_space_projector(K)
698
+
699
+ <span class="comment"># Simulate a weight update we want to apply (e.g., refusal direction)</span>
700
+ delta_W = torch.randn(hidden_dim)
701
+
702
+ <span class="comment"># Project into null space (safe update)</span>
703
+ delta_W_safe = apply_null_space_projection(delta_W, P_null)
704
+
705
+ <span class="comment"># Verify: K @ delta_W_safe should be ~0 for all samples</span>
706
+ effect_on_preservation = K @ delta_W_safe
707
+ print(<span class="string">f"Max effect on preservation inputs: {effect_on_preservation.abs().max():.2e}"</span>)
708
+ <span class="comment"># Output: Max effect on preservation inputs: ~1e-6 (effectively zero!)</span>
709
+ </div>
710
+ </div>
711
+
712
+ <div id="application" class="tab-content">
713
+ <h3>Application to Model Abliteration</h3>
714
+ <div class="explanation">
715
+ <p>In the context of removing refusal behavior from language models:</p>
716
+ </div>
717
+
718
+ <table style="width: 100%; margin: 15px 0; border-collapse: collapse;">
719
+ <tr style="background: rgba(0,0,0,0.3);">
720
+ <th style="padding: 12px; text-align: left; border-bottom: 1px solid #333;">Component</th>
721
+ <th style="padding: 12px; text-align: left; border-bottom: 1px solid #333;">In Demo</th>
722
+ <th style="padding: 12px; text-align: left; border-bottom: 1px solid #333;">In Abliteration</th>
723
+ </tr>
724
+ <tr>
725
+ <td style="padding: 12px; border-bottom: 1px solid #222;">K</td>
726
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Preservation vector</td>
727
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Activations from math, coding, reasoning prompts</td>
728
+ </tr>
729
+ <tr>
730
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Ξ”W</td>
731
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Original update</td>
732
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Refusal direction projection</td>
733
+ </tr>
734
+ <tr>
735
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Ξ”W'</td>
736
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Projected update</td>
737
+ <td style="padding: 12px; border-bottom: 1px solid #222;">Safe refusal removal (preserves capabilities)</td>
738
+ </tr>
739
+ <tr>
740
+ <td style="padding: 12px;">K Β· Ξ”W' = 0</td>
741
+ <td style="padding: 12px;">Dot product is zero</td>
742
+ <td style="padding: 12px;">Math/coding outputs unchanged</td>
743
+ </tr>
744
+ </table>
745
+
746
+ <div class="explanation" style="margin-top: 15px;">
747
+ <p><strong>Practical considerations:</strong></p>
748
+ <ul style="margin-left: 20px; margin-top: 5px;">
749
+ <li>Use diverse preservation prompts (35+ covering math, coding, reasoning, etc.)</li>
750
+ <li>rank_ratio of 0.95 keeps most capability variance while allowing some modification</li>
751
+ <li>Lower rank_ratio = more aggressive preservation (but less refusal removal)</li>
752
+ <li>Compute P_null once per layer, reuse for all weight matrices in that layer</li>
753
+ </ul>
754
+ </div>
755
+ </div>
756
+ </div>
757
+ </div>
758
+ </div>
759
+
760
+ <script>
761
+ // Canvas setup
762
+ const canvas = document.getElementById('mainCanvas');
763
+ const ctx = canvas.getContext('2d');
764
+ const width = canvas.width;
765
+ const height = canvas.height;
766
+ const centerX = width / 2;
767
+ const centerY = height / 2;
768
+ const scale = 200;
769
+
770
+ // State
771
+ let animationProgress = 0;
772
+ let isAnimating = false;
773
+ let currentStep = 1;
774
+
775
+ // Get slider values
776
+ function getK() {
777
+ return {
778
+ x: parseFloat(document.getElementById('kx').value) / 100,
779
+ y: parseFloat(document.getElementById('ky').value) / 100
780
+ };
781
+ }
782
+
783
+ function getDW() {
784
+ return {
785
+ x: parseFloat(document.getElementById('dwx').value) / 100,
786
+ y: parseFloat(document.getElementById('dwy').value) / 100
787
+ };
788
+ }
789
+
790
+ // Math helpers
791
+ function dot(a, b) {
792
+ return a.x * b.x + a.y * b.y;
793
+ }
794
+
795
+ function magnitude(v) {
796
+ return Math.sqrt(v.x * v.x + v.y * v.y);
797
+ }
798
+
799
+ function normalize(v) {
800
+ const mag = magnitude(v);
801
+ return mag > 0 ? { x: v.x / mag, y: v.y / mag } : { x: 0, y: 0 };
802
+ }
803
+
804
+ function projectToNullSpace(dw, k) {
805
+ // Null space of K is perpendicular to K
806
+ // P_null = I - k*k^T / |k|^2
807
+ const kNorm = normalize(k);
808
+ const projection = dot(dw, kNorm);
809
+ return {
810
+ x: dw.x - projection * kNorm.x,
811
+ y: dw.y - projection * kNorm.y
812
+ };
813
+ }
814
+
815
+ // Drawing functions
816
+ function toCanvas(v) {
817
+ return {
818
+ x: centerX + v.x * scale,
819
+ y: centerY - v.y * scale // Flip Y for standard math coordinates
820
+ };
821
+ }
822
+
823
+ function drawGrid() {
824
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
825
+ ctx.lineWidth = 1;
826
+
827
+ // Grid lines
828
+ for (let i = -2; i <= 2; i++) {
829
+ ctx.beginPath();
830
+ ctx.moveTo(centerX + i * scale / 2, 0);
831
+ ctx.lineTo(centerX + i * scale / 2, height);
832
+ ctx.stroke();
833
+
834
+ ctx.beginPath();
835
+ ctx.moveTo(0, centerY + i * scale / 2);
836
+ ctx.lineTo(width, centerY + i * scale / 2);
837
+ ctx.stroke();
838
+ }
839
+
840
+ // Axes
841
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
842
+ ctx.lineWidth = 2;
843
+ ctx.beginPath();
844
+ ctx.moveTo(0, centerY);
845
+ ctx.lineTo(width, centerY);
846
+ ctx.stroke();
847
+
848
+ ctx.beginPath();
849
+ ctx.moveTo(centerX, 0);
850
+ ctx.lineTo(centerX, height);
851
+ ctx.stroke();
852
+ }
853
+
854
+ function drawVector(from, to, color, lineWidth = 3, arrowSize = 12) {
855
+ const fromPt = toCanvas(from);
856
+ const toPt = toCanvas(to);
857
+
858
+ ctx.strokeStyle = color;
859
+ ctx.fillStyle = color;
860
+ ctx.lineWidth = lineWidth;
861
+
862
+ // Line
863
+ ctx.beginPath();
864
+ ctx.moveTo(fromPt.x, fromPt.y);
865
+ ctx.lineTo(toPt.x, toPt.y);
866
+ ctx.stroke();
867
+
868
+ // Arrow head
869
+ const angle = Math.atan2(fromPt.y - toPt.y, fromPt.x - toPt.x);
870
+ ctx.beginPath();
871
+ ctx.moveTo(toPt.x, toPt.y);
872
+ ctx.lineTo(
873
+ toPt.x + arrowSize * Math.cos(angle - Math.PI / 6),
874
+ toPt.y + arrowSize * Math.sin(angle - Math.PI / 6)
875
+ );
876
+ ctx.lineTo(
877
+ toPt.x + arrowSize * Math.cos(angle + Math.PI / 6),
878
+ toPt.y + arrowSize * Math.sin(angle + Math.PI / 6)
879
+ );
880
+ ctx.closePath();
881
+ ctx.fill();
882
+ }
883
+
884
+ function drawSubspace(direction, color, label) {
885
+ const norm = normalize(direction);
886
+ const perpendicular = { x: -norm.y, y: norm.x };
887
+
888
+ // Draw the line representing the subspace
889
+ const lineStart = toCanvas({ x: norm.x * 2, y: norm.y * 2 });
890
+ const lineEnd = toCanvas({ x: -norm.x * 2, y: -norm.y * 2 });
891
+
892
+ ctx.strokeStyle = color;
893
+ ctx.lineWidth = 2;
894
+ ctx.setLineDash([5, 5]);
895
+ ctx.beginPath();
896
+ ctx.moveTo(lineStart.x, lineStart.y);
897
+ ctx.lineTo(lineEnd.x, lineEnd.y);
898
+ ctx.stroke();
899
+ ctx.setLineDash([]);
900
+ }
901
+
902
+ function drawNullSpaceRegion(k) {
903
+ const norm = normalize(k);
904
+ const perp = { x: -norm.y, y: norm.x };
905
+
906
+ // Draw semi-transparent region for null space
907
+ ctx.fillStyle = 'rgba(0, 212, 255, 0.1)';
908
+ ctx.beginPath();
909
+
910
+ const p1 = toCanvas({ x: perp.x * 2, y: perp.y * 2 });
911
+ const p2 = toCanvas({ x: -perp.x * 2, y: -perp.y * 2 });
912
+ const p3 = toCanvas({ x: -perp.x * 2 + norm.x * 0.3, y: -perp.y * 2 + norm.y * 0.3 });
913
+ const p4 = toCanvas({ x: perp.x * 2 + norm.x * 0.3, y: perp.y * 2 + norm.y * 0.3 });
914
+
915
+ ctx.moveTo(p1.x, p1.y);
916
+ ctx.lineTo(p2.x, p2.y);
917
+ ctx.lineTo(p3.x, p3.y);
918
+ ctx.lineTo(p4.x, p4.y);
919
+ ctx.closePath();
920
+ ctx.fill();
921
+
922
+ // Null space line
923
+ ctx.strokeStyle = 'rgba(0, 212, 255, 0.5)';
924
+ ctx.lineWidth = 2;
925
+ ctx.setLineDash([10, 5]);
926
+ ctx.beginPath();
927
+ ctx.moveTo(p1.x, p1.y);
928
+ ctx.lineTo(p2.x, p2.y);
929
+ ctx.stroke();
930
+ ctx.setLineDash([]);
931
+ }
932
+
933
+ function drawRowSpaceRegion(k) {
934
+ const norm = normalize(k);
935
+
936
+ // Draw semi-transparent region for row space
937
+ ctx.fillStyle = 'rgba(76, 175, 80, 0.1)';
938
+ ctx.beginPath();
939
+
940
+ const perp = { x: -norm.y, y: norm.x };
941
+ const p1 = toCanvas({ x: norm.x * 2, y: norm.y * 2 });
942
+ const p2 = toCanvas({ x: -norm.x * 2, y: -norm.y * 2 });
943
+ const p3 = toCanvas({ x: -norm.x * 2 + perp.x * 0.3, y: -norm.y * 2 + perp.y * 0.3 });
944
+ const p4 = toCanvas({ x: norm.x * 2 + perp.x * 0.3, y: norm.y * 2 + perp.y * 0.3 });
945
+
946
+ ctx.moveTo(p1.x, p1.y);
947
+ ctx.lineTo(p2.x, p2.y);
948
+ ctx.lineTo(p3.x, p3.y);
949
+ ctx.lineTo(p4.x, p4.y);
950
+ ctx.closePath();
951
+ ctx.fill();
952
+
953
+ // Row space line
954
+ ctx.strokeStyle = 'rgba(76, 175, 80, 0.5)';
955
+ ctx.lineWidth = 2;
956
+ ctx.setLineDash([10, 5]);
957
+ ctx.beginPath();
958
+ ctx.moveTo(p1.x, p1.y);
959
+ ctx.lineTo(p2.x, p2.y);
960
+ ctx.stroke();
961
+ ctx.setLineDash([]);
962
+ }
963
+
964
+ function drawProjectionLine(dw, dwProjected, k) {
965
+ const dwCanvas = toCanvas(dw);
966
+ const dwProjCanvas = toCanvas(dwProjected);
967
+
968
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
969
+ ctx.lineWidth = 1;
970
+ ctx.setLineDash([3, 3]);
971
+ ctx.beginPath();
972
+ ctx.moveTo(dwCanvas.x, dwCanvas.y);
973
+ ctx.lineTo(dwProjCanvas.x, dwProjCanvas.y);
974
+ ctx.stroke();
975
+ ctx.setLineDash([]);
976
+ }
977
+
978
+ function drawLabel(v, text, color, offsetX = 10, offsetY = -10, showCoords = false) {
979
+ const pos = toCanvas(v);
980
+ ctx.fillStyle = color;
981
+ ctx.font = 'bold 14px Segoe UI';
982
+ ctx.fillText(text, pos.x + offsetX, pos.y + offsetY);
983
+
984
+ // Draw coordinates below the label
985
+ if (showCoords) {
986
+ ctx.font = '11px Consolas, monospace';
987
+ ctx.fillStyle = 'rgba(255,255,255,0.7)';
988
+ const coordText = `[${v.x.toFixed(2)}, ${v.y.toFixed(2)}]`;
989
+ ctx.fillText(coordText, pos.x + offsetX, pos.y + offsetY + 14);
990
+ }
991
+ }
992
+
993
+ function draw() {
994
+ ctx.clearRect(0, 0, width, height);
995
+
996
+ const k = getK();
997
+ const dw = getDW();
998
+ const dwProjected = projectToNullSpace(dw, k);
999
+
1000
+ // Calculate animation progress for step 3 (vector projection phase)
1001
+ const step3Start = 0.4;
1002
+ const vectorAnimProgress = currentStep >= 3 && isAnimating
1003
+ ? Math.min(1, Math.max(0, (animationProgress - step3Start) / (1 - step3Start)))
1004
+ : 0;
1005
+
1006
+ // Interpolate for animation (only during step 3 vector projection phase)
1007
+ const dwAnimated = {
1008
+ x: dw.x + (dwProjected.x - dw.x) * vectorAnimProgress,
1009
+ y: dw.y + (dwProjected.y - dw.y) * vectorAnimProgress
1010
+ };
1011
+
1012
+ drawGrid();
1013
+
1014
+ // Draw subspace regions based on current step
1015
+ if (currentStep >= 2) {
1016
+ drawRowSpaceRegion(k);
1017
+ drawNullSpaceRegion(k);
1018
+ }
1019
+
1020
+ // Draw projection line
1021
+ if (currentStep >= 3 && !isAnimating) {
1022
+ drawProjectionLine(dw, dwProjected, k);
1023
+ }
1024
+
1025
+ // Draw vectors
1026
+ const origin = { x: 0, y: 0 };
1027
+
1028
+ // Preservation vector K (always show with coordinates)
1029
+ drawVector(origin, k, '#4caf50', 4);
1030
+ drawLabel(k, 'K', '#4caf50', 10, -10, true);
1031
+
1032
+ if (currentStep >= 3 && isAnimating) {
1033
+ // During animation: show Ξ”W fading and morphing into Ξ”W'
1034
+
1035
+ // Draw fading original Ξ”W (ghost)
1036
+ ctx.globalAlpha = 1 - vectorAnimProgress * 0.7;
1037
+ drawVector(origin, dw, '#f858fb', 3);
1038
+ drawLabel(dw, 'Ξ”W', '#f858fb', 10, 15, true);
1039
+ ctx.globalAlpha = 1;
1040
+
1041
+ // Draw the animating vector (transitioning from Ξ”W to Ξ”W')
1042
+ drawVector(origin, dwAnimated, '#00d4ff', 3);
1043
+ drawLabel(dwAnimated, "β†’ Ξ”W'", '#00d4ff', 10, -10, true);
1044
+ } else {
1045
+ // Original update Ξ”W (always show with coordinates when not in step 3+ animation)
1046
+ drawVector(origin, dw, '#f858fb', 3);
1047
+ drawLabel(dw, 'Ξ”W', '#f858fb', 10, 15, true);
1048
+
1049
+ // Projected update Ξ”W' (show from step 3 when not animating)
1050
+ if (currentStep >= 3) {
1051
+ drawVector(origin, dwProjected, '#00d4ff', 3);
1052
+ drawLabel(dwProjected, "Ξ”W'", '#00d4ff', 10, -10, true);
1053
+ }
1054
+ }
1055
+
1056
+ // Update results
1057
+ updateResults(k, dw, dwProjected);
1058
+ }
1059
+
1060
+ function updateResults(k, dw, dwProjected) {
1061
+ const dotBefore = dot(k, dw);
1062
+ const dotAfter = dot(k, dwProjected);
1063
+ const magRatio = magnitude(dwProjected) / magnitude(dw);
1064
+
1065
+ document.getElementById('dot-before').textContent = dotBefore.toFixed(4);
1066
+ document.getElementById('dot-after').textContent = dotAfter.toFixed(6);
1067
+ document.getElementById('magnitude-ratio').textContent = (magRatio * 100).toFixed(1) + '%';
1068
+
1069
+ // Color code the after value
1070
+ const afterEl = document.getElementById('dot-after');
1071
+ afterEl.style.color = Math.abs(dotAfter) < 0.0001 ? '#4caf50' : '#f858fb';
1072
+
1073
+ // Update the step-by-step math breakdown
1074
+ updateMathDisplay(k, dw, dwProjected);
1075
+ }
1076
+
1077
+ function updateMathDisplay(k, dw, dwProjected) {
1078
+ // Vector coordinates bar (full-width)
1079
+ document.getElementById('bar-kx').textContent = k.x.toFixed(2);
1080
+ document.getElementById('bar-ky').textContent = k.y.toFixed(2);
1081
+ document.getElementById('bar-dwx').textContent = dw.x.toFixed(2);
1082
+ document.getElementById('bar-dwy').textContent = dw.y.toFixed(2);
1083
+ document.getElementById('bar-dwpx').textContent = dwProjected.x.toFixed(2);
1084
+ document.getElementById('bar-dwpy').textContent = dwProjected.y.toFixed(2);
1085
+
1086
+ // Dot product breakdown
1087
+ document.getElementById('dot-kx').textContent = k.x.toFixed(2);
1088
+ document.getElementById('dot-dwx').textContent = dw.x.toFixed(2);
1089
+ document.getElementById('dot-ky').textContent = k.y.toFixed(2);
1090
+ document.getElementById('dot-dwy').textContent = dw.y.toFixed(2);
1091
+
1092
+ const term1 = k.x * dw.x;
1093
+ const term2 = k.y * dw.y;
1094
+ const dotResult = term1 + term2;
1095
+
1096
+ document.getElementById('dot-term1').textContent = term1.toFixed(3);
1097
+ document.getElementById('dot-term2').textContent = term2.toFixed(3);
1098
+ document.getElementById('dot-result').textContent = dotResult.toFixed(3);
1099
+
1100
+ // Projection formula
1101
+ const kMagSq = k.x * k.x + k.y * k.y;
1102
+ const scale = kMagSq > 0 ? dotResult / kMagSq : 0;
1103
+ const subX = scale * k.x;
1104
+ const subY = scale * k.y;
1105
+
1106
+ document.getElementById('k-mag-sq').textContent = kMagSq.toFixed(3);
1107
+ document.getElementById('proj-scale').textContent = scale.toFixed(3);
1108
+ document.getElementById('proj-dwx').textContent = dw.x.toFixed(2);
1109
+ document.getElementById('proj-dwy').textContent = dw.y.toFixed(2);
1110
+ document.getElementById('proj-subx').textContent = subX.toFixed(3);
1111
+ document.getElementById('proj-suby').textContent = subY.toFixed(3);
1112
+ document.getElementById('math-dwpx').textContent = dwProjected.x.toFixed(3);
1113
+ document.getElementById('math-dwpy').textContent = dwProjected.y.toFixed(3);
1114
+
1115
+ // Verification
1116
+ document.getElementById('ver-kx').textContent = k.x.toFixed(2);
1117
+ document.getElementById('ver-dwpx').textContent = dwProjected.x.toFixed(3);
1118
+ document.getElementById('ver-ky').textContent = k.y.toFixed(2);
1119
+ document.getElementById('ver-dwpy').textContent = dwProjected.y.toFixed(3);
1120
+
1121
+ const verTerm1 = k.x * dwProjected.x;
1122
+ const verTerm2 = k.y * dwProjected.y;
1123
+ const verResult = verTerm1 + verTerm2;
1124
+
1125
+ document.getElementById('ver-term1').textContent = verTerm1.toFixed(4);
1126
+ document.getElementById('ver-term2').textContent = verTerm2.toFixed(4);
1127
+
1128
+ const verEl = document.getElementById('ver-result');
1129
+ if (Math.abs(verResult) < 0.0001) {
1130
+ verEl.textContent = `β‰ˆ 0 βœ“`;
1131
+ verEl.className = 'verification-pass';
1132
+ } else {
1133
+ verEl.textContent = verResult.toFixed(6);
1134
+ verEl.className = 'verification-fail';
1135
+ }
1136
+ }
1137
+
1138
+ function animateProjection() {
1139
+ if (isAnimating) return;
1140
+
1141
+ isAnimating = true;
1142
+ animationProgress = 0;
1143
+
1144
+ // Start from step 1
1145
+ currentStep = 1;
1146
+ updateStepIndicators();
1147
+
1148
+ // Phase timings (in progress units where 1.0 = complete)
1149
+ const step2Start = 0.2; // Show subspaces at 20%
1150
+ const step3Start = 0.4; // Start vector projection at 40%
1151
+
1152
+ function animate() {
1153
+ animationProgress += 0.015;
1154
+
1155
+ // Progress through steps based on animation progress
1156
+ if (animationProgress >= step2Start && currentStep < 2) {
1157
+ currentStep = 2;
1158
+ updateStepIndicators();
1159
+ }
1160
+ if (animationProgress >= step3Start && currentStep < 3) {
1161
+ currentStep = 3;
1162
+ updateStepIndicators();
1163
+ }
1164
+
1165
+ if (animationProgress >= 1) {
1166
+ animationProgress = 1;
1167
+ isAnimating = false;
1168
+ currentStep = 3;
1169
+ updateStepIndicators();
1170
+ }
1171
+
1172
+ draw();
1173
+
1174
+ if (isAnimating) {
1175
+ requestAnimationFrame(animate);
1176
+ }
1177
+ }
1178
+ animate();
1179
+ }
1180
+
1181
+ function updateStepIndicators() {
1182
+ document.querySelectorAll('.step').forEach((el, i) => {
1183
+ el.classList.remove('active', 'completed');
1184
+ if (i + 1 < currentStep) el.classList.add('completed');
1185
+ if (i + 1 === currentStep) el.classList.add('active');
1186
+ });
1187
+
1188
+ // Update content
1189
+ document.querySelectorAll('.step-content').forEach(el => el.classList.remove('active'));
1190
+ const contentEl = document.getElementById(`step${currentStep}-content`);
1191
+ if (contentEl) contentEl.classList.add('active');
1192
+ }
1193
+
1194
+ function randomize() {
1195
+ document.getElementById('kx').value = Math.floor(Math.random() * 200 - 100);
1196
+ document.getElementById('ky').value = Math.floor(Math.random() * 200 - 100);
1197
+ document.getElementById('dwx').value = Math.floor(Math.random() * 200 - 100);
1198
+ document.getElementById('dwy').value = Math.floor(Math.random() * 200 - 100);
1199
+ updateSliderValues();
1200
+ draw();
1201
+ }
1202
+
1203
+ function updateSliderValues() {
1204
+ document.getElementById('kx-val').textContent = (parseFloat(document.getElementById('kx').value) / 100).toFixed(2);
1205
+ document.getElementById('ky-val').textContent = (parseFloat(document.getElementById('ky').value) / 100).toFixed(2);
1206
+ document.getElementById('dwx-val').textContent = (parseFloat(document.getElementById('dwx').value) / 100).toFixed(2);
1207
+ document.getElementById('dwy-val').textContent = (parseFloat(document.getElementById('dwy').value) / 100).toFixed(2);
1208
+ }
1209
+
1210
+ function setStep(step) {
1211
+ currentStep = step;
1212
+
1213
+ // Update step indicators
1214
+ document.querySelectorAll('.step').forEach((el, i) => {
1215
+ el.classList.remove('active', 'completed');
1216
+ if (i + 1 < step) el.classList.add('completed');
1217
+ if (i + 1 === step) el.classList.add('active');
1218
+ });
1219
+
1220
+ // Update content
1221
+ document.querySelectorAll('.step-content').forEach(el => el.classList.remove('active'));
1222
+ const contentEl = document.getElementById(`step${step}-content`);
1223
+ if (contentEl) contentEl.classList.add('active');
1224
+
1225
+ draw();
1226
+ }
1227
+
1228
+ function showTab(tabId) {
1229
+ document.querySelectorAll('.tab').forEach(el => el.classList.remove('active'));
1230
+ document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
1231
+
1232
+ event.target.classList.add('active');
1233
+ document.getElementById(tabId).classList.add('active');
1234
+ }
1235
+
1236
+ // Event listeners
1237
+ document.querySelectorAll('input[type="range"]').forEach(slider => {
1238
+ slider.addEventListener('input', () => {
1239
+ updateSliderValues();
1240
+ draw();
1241
+ });
1242
+ });
1243
+
1244
+ // Initial draw
1245
+ updateSliderValues();
1246
+ draw();
1247
+ </script>
1248
+ </body>
1249
  </html>
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }