Skip to content

Commit 54c0377

Browse files
committed
refactor: migrate experimental zoneless change detection to stable provideZonelessChangeDetection across test suites
1 parent 5f65b3a commit 54c0377

57 files changed

Lines changed: 1274 additions & 345 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

animated-transformer/angular.json

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,10 @@
8989
}
9090
},
9191
"test": {
92-
"builder": "@angular/build:karma",
92+
"builder": "@angular/build:unit-test",
9393
"options": {
94-
"polyfills": [],
9594
"tsConfig": "tsconfig.spec.json",
96-
"inlineStyleLanguage": "scss",
97-
"assets": [
98-
"src/favicon.ico",
99-
"src/assets"
100-
],
101-
"styles": [
102-
"src/styles.scss"
103-
],
104-
"scripts": [],
105-
"webWorkerTsConfig": "tsconfig.worker.json",
106-
"karmaConfig": "karma.conf.js"
95+
"browsers": ["chromium"]
10796
}
10897
}
10998
}

animated-transformer/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,19 @@
6161
"@types/underscore": "^1.11.4",
6262
"@types/wicg-file-system-access": "^2023.10.5",
6363
"@types/yargs": "^17.0.24",
64+
"@vitest/browser-playwright": "^4.1.7",
6465
"concurrently": "^9.1.2",
6566
"esbuild": "0.24.2",
6667
"jasmine-core": "~4.5.0",
68+
"jsdom": "^29.1.1",
6769
"karma": "~6.4.0",
6870
"karma-chrome-launcher": "~3.1.0",
6971
"karma-coverage": "~2.2.0",
7072
"karma-jasmine": "~5.1.0",
7173
"karma-jasmine-html-reporter": "~2.0.0",
7274
"prettier": "3.4.2",
7375
"typescript": "^5.7.0",
74-
"vite": "^5.4.11"
76+
"vite": "^6.4.2",
77+
"vitest": "^4.1.7"
7578
}
7679
}

animated-transformer/pnpm-lock.yaml

Lines changed: 917 additions & 205 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

animated-transformer/src/app/activation-vis/activation-manager/activation-manager.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ limitations under the License.
1616
import { ComponentFixture, TestBed } from '@angular/core/testing';
1717

1818
import { ActivationManagerComponent } from './activation-manager.component';
19-
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
19+
import { provideZonelessChangeDetection } from '@angular/core';
2020

2121
describe('ActivationManagerComponent', () => {
2222
let component: ActivationManagerComponent;
2323
let fixture: ComponentFixture<ActivationManagerComponent>;
2424

2525
beforeEach(() => {
2626
TestBed.configureTestingModule({
27-
providers: [provideExperimentalZonelessChangeDetection()],
27+
providers: [provideZonelessChangeDetection()],
2828
imports: [ActivationManagerComponent],
2929
});
3030
fixture = TestBed.createComponent(ActivationManagerComponent);

animated-transformer/src/app/activation-vis/activation-vis.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,10 @@
8080
</div>
8181
</mat-card-content>
8282
</mat-card>
83+
84+
<mat-card class="card explainer-card">
85+
<mat-card-content>
86+
<markdown [data]="explainerMarkdown"></markdown>
87+
</mat-card-content>
88+
</mat-card>
8389
</div>

animated-transformer/src/app/activation-vis/activation-vis.component.scss

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,101 @@
5656
.card {
5757
margin: 1em;
5858
}
59+
60+
.explainer-card {
61+
flex: 1 1 100%; /* Span full width */
62+
margin: 1.5em 1em 3em 1em;
63+
padding: 20px 24px;
64+
background: linear-gradient(135deg, #faf9fc 0%, #f5f3f7 100%);
65+
border-left: 5px solid #673ab7; /* Deep Purple theme color */
66+
border-radius: 8px;
67+
box-shadow: 0 4px 20px rgba(103, 58, 183, 0.08);
68+
transition: transform 0.3s ease, box-shadow 0.3s ease;
69+
70+
&:hover {
71+
transform: translateY(-2px);
72+
box-shadow: 0 8px 30px rgba(103, 58, 183, 0.12);
73+
}
74+
75+
markdown {
76+
color: #333333;
77+
font-family: 'Roboto', sans-serif;
78+
line-height: 1.6;
79+
80+
h3 {
81+
color: #673ab7; /* Deep Purple primary theme color */
82+
font-size: 1.8rem;
83+
font-weight: 500;
84+
margin-top: 0;
85+
margin-bottom: 0.5em;
86+
letter-spacing: -0.02em;
87+
}
88+
89+
h4 {
90+
color: #ffb300; /* Amber accent color */
91+
font-size: 1.3rem;
92+
font-weight: 600;
93+
margin-top: 1.5em;
94+
margin-bottom: 0.5em;
95+
border-bottom: 2px solid rgba(255, 179, 0, 0.15);
96+
padding-bottom: 4px;
97+
width: fit-content;
98+
}
99+
100+
hr {
101+
border: 0;
102+
height: 1px;
103+
background: linear-gradient(to right, rgba(103, 58, 183, 0.2), rgba(103, 58, 183, 0));
104+
margin: 1.5em 0;
105+
}
106+
107+
p {
108+
margin-bottom: 1em;
109+
font-size: 1.05rem;
110+
}
111+
112+
ul {
113+
padding-left: 1.5em;
114+
margin-bottom: 1em;
115+
list-style-type: square;
116+
117+
li {
118+
margin-bottom: 0.5em;
119+
font-size: 1.05rem;
120+
}
121+
}
122+
123+
strong {
124+
color: #673ab7;
125+
font-weight: 600;
126+
}
127+
128+
/* Style block formulas */
129+
p > span.katex-display,
130+
p > em > strong,
131+
p > strong {
132+
display: block;
133+
text-align: center;
134+
font-family: 'Courier New', Courier, monospace;
135+
font-size: 1.15rem;
136+
background-color: #ffffff;
137+
padding: 12px 20px;
138+
border-radius: 6px;
139+
border: 1px solid rgba(103, 58, 183, 0.1);
140+
margin: 1em auto;
141+
width: fit-content;
142+
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.02);
143+
color: #673ab7;
144+
}
145+
146+
/* Ensure we don't format normal inline bolding as block math */
147+
p > strong:not(:only-child) {
148+
display: inline;
149+
background: none;
150+
border: none;
151+
padding: 0;
152+
box-shadow: none;
153+
margin: 0;
154+
}
155+
}
156+
}

animated-transformer/src/app/activation-vis/activation-vis.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
1818
import { ActivationVisComponent } from './activation-vis.component';
1919
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
2020
import { CornerActivationComponent } from './corner-activation/corner-activation.component';
21-
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
21+
import { provideZonelessChangeDetection } from '@angular/core';
2222

2323
describe('ActivationVisComponent', () => {
2424
let component: ActivationVisComponent;
2525
let fixture: ComponentFixture<ActivationVisComponent>;
2626

2727
beforeEach(async () => {
2828
await TestBed.configureTestingModule({
29-
providers: [provideExperimentalZonelessChangeDetection()],
29+
providers: [provideZonelessChangeDetection()],
3030
imports: [NoopAnimationsModule, ActivationVisComponent, CornerActivationComponent],
3131
}).compileComponents();
3232
});

animated-transformer/src/app/activation-vis/activation-vis.component.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { AutoCompletedTextInputComponent } from '../auto-completed-text-input/au
4747
import { ActivationManagerComponent } from './activation-manager/activation-manager.component';
4848
import { NanValidatorDirective } from '../form-validators/nan-validator.directive';
4949
import { BoundedFloatValidatorDirective } from '../form-validators/bounded-float-validator.directive';
50+
import { MarkdownModule } from 'ngx-markdown';
5051

5152
interface DatasetExample {
5253
input: number[];
@@ -77,12 +78,73 @@ interface DatasetExample {
7778
// ActivationManagerDirective,
7879
// NanValidatorDirective,
7980
// BoundedFloatValidatorDirective,
81+
MarkdownModule,
8082
],
8183
templateUrl: './activation-vis.component.html',
8284
styleUrls: ['./activation-vis.component.scss'],
8385
changeDetection: ChangeDetectionStrategy.OnPush,
8486
})
8587
export class ActivationVisComponent implements OnInit {
88+
explainerMarkdown = `
89+
### Learning Boolean Circuits via Continuous Relaxation
90+
91+
This visualization demonstrates how **gradient descent** can be used to learn arbitrary boolean circuits (such as **AND**, **OR**, **XOR**, etc.) by relaxing a discrete, boolean search space into a continuous, differentiable field.
92+
93+
---
94+
95+
#### 1. The Key Idea: Hypercube Relaxation
96+
A standard boolean function of two inputs **x, y ∈ {0, 1}** maps the four corners of a unit square to an output **z ∈ {0, 1}**:
97+
* **Corner p₀₀ = (0, 0)** &rarr; parameter **w₀₀**
98+
* **Corner p₁₀ = (1, 0)** &rarr; parameter **w₁₀**
99+
* **Corner p₀₁ = (0, 1)** &rarr; parameter **w₀₁**
100+
* **Corner p₁₁ = (1, 1)** &rarr; parameter **w₁₁**
101+
102+
To allow gradient-based learning, we relax the discrete domain. Instead of discrete lookups, the output at any continuous coordinate **v = (x, y) ∈ [0, 1]²** is defined as a **smooth interpolation** of the four corner parameters **w_p ∈ [0, 1]**.
103+
104+
---
105+
106+
#### 2. How Activations are Interpolated
107+
For any input point **v = (x, y)**, we compute a **coordinate-wise similarity weight** **w_p(x, y)** for each corner **p = (p_x, p_y)**:
108+
109+
**w_p(x, y) = (1 - (x - p_x)²) · (1 - (y - p_y)²)**
110+
111+
* When **(x, y)** is exactly at a corner **p**, its similarity weight **w_p(x, y)** is **1**, while all other corner weights are **0**.
112+
* As **(x, y)** moves away from a corner, the weight decays quadratically.
113+
114+
The final predicted activation **ẑ(x, y)** is the **normalized weighted average** of the corner values:
115+
116+
**ẑ(x, y) = Σ [w_p(x, y) · w_p] / Σ w_p(x, y)**
117+
118+
This formulation yields the smooth, continuous landscape visualized in the **Param eval matrix**. It perfectly matches the parameters at the corners and blends them smoothly across the space.
119+
120+
---
121+
122+
#### 3. How Gradients are Computed & Learned
123+
When you select a dataset (e.g., XOR):
124+
1. **Define a Loss Function**: We measure the Mean Squared Error (MSE) between the model's predictions **ẑ(x, y)** and the true target outputs **z** across the dataset examples:
125+
126+
**Loss = Σ (ẑ(x_j, y_j) - z_j)²**
127+
128+
2. **Backpropagation**: Since **ẑ(x, y)** is fully differentiable, we can compute the exact gradient of the loss with respect to each corner parameter:
129+
130+
**∂Loss / ∂w_p**
131+
132+
This gradient represents the direction and magnitude of the error contribution for each corner parameter.
133+
134+
3. **Gradient Descent Steps**: Clicking **"Apply Gradient Step"** subtracts a fraction of this gradient (scaled by the **Learning Rate** **η**) from the parameters:
135+
136+
**w_p &larr; w_p - η · (∂Loss / ∂w_p)**
137+
138+
By taking successive gradient steps, the corner values converge to the exact truth table of the target boolean function!
139+
140+
---
141+
142+
#### 4. Properties & Theoretical Trade-offs
143+
This continuous relaxation approach has remarkable theoretical properties:
144+
* **Guaranteed Convergence**: Because the interpolated output is a linear function of the corner parameters **w_p**, the Mean Squared Error (MSE) loss is a convex quadratic function with respect to these parameters. This means there are **no local minima**&mdash;gradient descent is mathematically guaranteed to learn any target boolean circuit from **any arbitrary initialization**!
145+
* **Exponential Parameter Cost**: The main drawback is scalability. Since we define a separate parameter **w_p** for every single corner of the input hypercube, the number of parameters required is **2ⁿ** (where **n** is the number of input variables). For complex functions with many inputs, this parameter growth becomes exponentially expensive.
146+
`;
147+
86148
view = signal('vis' as 'edit' | 'vis');
87149

88150
readonly activationManager = viewChild.required(ActivationManagerDirective);

animated-transformer/src/app/activation-vis/axis-wrapper/axis-wrapper.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ limitations under the License.
1616
import { ComponentFixture, TestBed } from '@angular/core/testing';
1717

1818
import { AxisWrapperComponent } from './axis-wrapper.component';
19-
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
19+
import { provideZonelessChangeDetection } from '@angular/core';
2020

2121
describe('AxisWrapperComponent', () => {
2222
let component: AxisWrapperComponent;
2323
let fixture: ComponentFixture<AxisWrapperComponent>;
2424

2525
beforeEach(async () => {
2626
await TestBed.configureTestingModule({
27-
providers: [provideExperimentalZonelessChangeDetection()],
27+
providers: [provideZonelessChangeDetection()],
2828
imports: [AxisWrapperComponent],
2929
}).compileComponents();
3030
});

animated-transformer/src/app/activation-vis/corner-activation/corner-activation.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
OnDestroy,
2727
Signal,
2828
WritableSignal,
29-
provideExperimentalZonelessChangeDetection,
29+
provideZonelessChangeDetection,
3030
} from '@angular/core';
3131

3232
import { TwoVarGTensorDataset } from 'src/lib/gtensor/the_16_two_var_bool_fns';
@@ -45,7 +45,7 @@ describe('CornerActivationComponent', () => {
4545

4646
beforeEach(() => {
4747
TestBed.configureTestingModule({
48-
providers: [provideExperimentalZonelessChangeDetection(), provideNoopAnimations()],
48+
providers: [provideZonelessChangeDetection(), provideNoopAnimations()],
4949
imports: [
5050
CommonModule,
5151
FormsModule,

0 commit comments

Comments
 (0)