phanerozoic commited on
Commit
dbbceb8
·
0 Parent(s):

update repository

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +36 -0
  2. CofiberDecomposition.v +263 -0
  3. README.md +145 -0
  4. TODO.md +33 -0
  5. analytical/analytical_70k/analytical_head_70k.pth +3 -0
  6. analytical/analytical_70k/analytical_head_70k_eval.json +13 -0
  7. analytical/analytical_70k/head.py +84 -0
  8. analytical/analytical_h1/analytical_h1_best.pth +3 -0
  9. analytical/analytical_h1/eval.json +12 -0
  10. analytical/scripts/analytical_best_gpu.py +229 -0
  11. analytical/scripts/analytical_empbayes.py +277 -0
  12. analytical/scripts/analytical_exotic_gpu.py +278 -0
  13. analytical/scripts/analytical_exotic_reg_gpu.py +284 -0
  14. analytical/scripts/analytical_fractal_gpu.py +268 -0
  15. analytical/scripts/analytical_gcv.py +268 -0
  16. analytical/scripts/analytical_greedy_gpu.py +244 -0
  17. analytical/scripts/analytical_hyperbatch.py +306 -0
  18. analytical/scripts/analytical_one.py +549 -0
  19. analytical/variants/README.md +77 -0
  20. analytical/variants/exotic_gpu.json +108 -0
  21. analytical/variants/exotic_reg_gpu.json +52 -0
  22. analytical/variants/fractal_results.json +17 -0
  23. analytical/variants/greedy_forward_gpu.json +809 -0
  24. analytical/variants/v001_baseline.json +23 -0
  25. analytical/variants/v002_spatial_cat_mean3x3.json +23 -0
  26. analytical/variants/v003_spatial_diff_neighbors.json +23 -0
  27. analytical/variants/v004_spatial_highreg.json +23 -0
  28. analytical/variants/v004_spatial_highreg_v2.json +23 -0
  29. analytical/variants/v005_power05_spatial_highreg.json +23 -0
  30. analytical/variants/v006_hv_neighbors_highreg.json +23 -0
  31. analytical/variants/v007_spatial_sqrt_highreg.json +23 -0
  32. analytical/variants/v008_spatial_neg3_highreg.json +23 -0
  33. analytical/variants/v009_sheaf_h1_compact.json +23 -0
  34. analytical/variants/v010_sheaf_h1_full.json +23 -0
  35. circuit/README.md +55 -0
  36. circuit/circuit_variants.json +489 -0
  37. circuit/cofiber_detector.sv +162 -0
  38. circuit/evolve_fast.py +211 -0
  39. circuit/evolved_K100_person_eval.json +9 -0
  40. circuit/evolved_extreme.json +453 -0
  41. circuit/person_analytical.pth +3 -0
  42. circuit/person_detector.sv +62 -0
  43. circuit/person_small_synth.v +85 -0
  44. circuit/rom/person_cls_b.hex +1 -0
  45. circuit/rom/person_cls_w.hex +768 -0
  46. circuit/rom/person_ctr_b.hex +1 -0
  47. circuit/rom/person_ctr_w.hex +768 -0
  48. circuit/rom/person_reg_b.hex +4 -0
  49. circuit/rom/person_reg_w.hex +3072 -0
  50. circuit/rom/test_features.hex +768 -0
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ trained/dim_sweeps/dim20_v2/cofiber_threshold_dim20_coco_8ep_22k_coco_results.json filter=lfs diff=lfs merge=lfs -text
CofiberDecomposition.v ADDED
@@ -0,0 +1,263 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (** * Cofiber scale decomposition in semi-additive categories
2
+
3
+ Given a semi-additive category with an adjunction Sigma -| Omega
4
+ (suspension/loop, upsample/pool), the counit epsilon : Sigma Omega -> 1
5
+ gives a "low-frequency projection" at each object. When the connecting
6
+ morphism in the cofiber of epsilon vanishes, X splits as a biproduct of
7
+ its low-frequency part and its high-frequency residual.
8
+
9
+ The splitting is natural in X: classification on cofibers is equivalent
10
+ to multi-scale classification on the original features.
11
+
12
+ The second section formalizes detection as pi0 of a presheaf colimit:
13
+ connected components of locally-constant sections over a patch site.
14
+ *)
15
+
16
+ From HoTT Require Import Basics.Overture.
17
+ From HoTT.Categories Require Import
18
+ Category.Core
19
+ Functor.Core Functor.Composition.Core Functor.Identity
20
+ NaturalTransformation.Core.
21
+ From HoTT.Categories.Additive Require Import ZeroObjects Biproducts SemiAdditive.
22
+
23
+ Set Universe Polymorphism.
24
+ Set Implicit Arguments.
25
+ Generalizable All Variables.
26
+
27
+ Local Open Scope category_scope.
28
+ Local Open Scope morphism_scope.
29
+
30
+ (** * Pre-stable structure on a semi-additive category *)
31
+
32
+ Record PreStableData := {
33
+ ps_cat : SemiAdditiveCategory;
34
+ ps_Susp : Functor ps_cat ps_cat;
35
+ ps_Loop : Functor ps_cat ps_cat;
36
+ ps_epsilon : NaturalTransformation (ps_Susp o ps_Loop)%functor
37
+ (Functor.Identity.identity ps_cat)
38
+ }.
39
+
40
+ Coercion ps_cat : PreStableData >-> SemiAdditiveCategory.
41
+
42
+ Section CofiberSplitting.
43
+
44
+ Context (PS : PreStableData).
45
+
46
+ Let C := ps_cat PS.
47
+ Let Sig := ps_Susp PS.
48
+ Let L := ps_Loop PS.
49
+ Let eps := ps_epsilon PS.
50
+
51
+ Definition low_freq (X : object C) : object C := Sig (L X).
52
+
53
+ Definition eps_at (X : object C) : morphism C (low_freq X) X := eps X.
54
+
55
+ (** A cofiber splitting of X along epsilon is a biproduct decomposition
56
+ of X into low_freq X and some high-frequency complement H, mediated
57
+ by an isomorphism between the biproduct object and X. *)
58
+
59
+ Record CofiberSplitting (X : object C) := {
60
+ cs_high : object C;
61
+ cs_biprod : @Biproduct C _ (low_freq X) cs_high;
62
+ cs_iso : morphism C (biproduct_obj (biproduct_data cs_biprod)) X;
63
+ cs_iso_inv : morphism C X (biproduct_obj (biproduct_data cs_biprod));
64
+ cs_sect : (cs_iso o cs_iso_inv = 1)%morphism;
65
+ cs_retr : (cs_iso_inv o cs_iso = 1)%morphism;
66
+ cs_eps_compat :
67
+ (cs_iso o inl (biproduct_data cs_biprod) = eps_at X)%morphism
68
+ }.
69
+
70
+ Arguments cs_high {X} _.
71
+ Arguments cs_biprod {X} _.
72
+ Arguments cs_iso {X} _.
73
+ Arguments cs_iso_inv {X} _.
74
+
75
+ (** Naturality of epsilon. *)
76
+ Lemma eps_natural {X Y : object C} (f : morphism C X Y)
77
+ : (eps_at Y o (Sig _1 (L _1 f)) = f o eps_at X)%morphism.
78
+ Proof.
79
+ exact (commutes eps X Y f).
80
+ Qed.
81
+
82
+ (** The low-frequency component of a morphism. *)
83
+ Definition low_component {X Y : object C} (f : morphism C X Y)
84
+ : morphism C (low_freq X) (low_freq Y)
85
+ := Sig _1 (L _1 f).
86
+
87
+ (** The iso inverse composed with epsilon gives the left injection.
88
+ Derivable from cs_eps_compat and cs_retr. *)
89
+ Lemma iso_inv_eps (X : object C) (sp : CofiberSplitting X)
90
+ : (cs_iso_inv sp o eps_at X
91
+ = inl (biproduct_data (cs_biprod sp)))%morphism.
92
+ Proof.
93
+ rewrite <- (cs_eps_compat sp).
94
+ rewrite <- associativity.
95
+ rewrite (cs_retr sp).
96
+ apply left_identity.
97
+ Qed.
98
+
99
+ (** ** Provable block structure
100
+
101
+ Given a family of splittings and a morphism f : X -> Y, the
102
+ conjugated morphism psi_Y o f o phi_X : BX -> BY has the
103
+ following block structure with respect to the biproducts:
104
+
105
+ - (low, low): low_component f [low_low_block]
106
+ - (high, low): 0 [cross_term_high_low_zero]
107
+ - (low, high): requires cofiber [cross_term_low_high_zero]
108
+ - (high, high): requires cofiber
109
+
110
+ The first two are provable from naturality of epsilon alone.
111
+ The latter two require the splitting to be functorial. *)
112
+
113
+ (** The conjugated morphism maps the low injection through epsilon's
114
+ naturality to the low injection at the target. This is the key
115
+ intermediate result used in both block theorems. *)
116
+ Lemma conjugated_inl
117
+ (split : forall X : object C, CofiberSplitting X)
118
+ {X Y : object C} (f : morphism C X Y)
119
+ : (cs_iso_inv (split Y) o f o cs_iso (split X)
120
+ o inl (biproduct_data (cs_biprod (split X)))
121
+ = inl (biproduct_data (cs_biprod (split Y)))
122
+ o low_component f)%morphism.
123
+ Proof.
124
+ repeat rewrite Category.Core.associativity.
125
+ rewrite (cs_eps_compat (split X)).
126
+ rewrite <- (eps_natural f).
127
+ assert (H : (cs_iso_inv (split Y) o (eps_at Y o low_component f)
128
+ = (cs_iso_inv (split Y) o eps_at Y) o low_component f)%morphism).
129
+ { symmetry. apply Category.Core.associativity. }
130
+ rewrite H.
131
+ rewrite (iso_inv_eps (split Y)).
132
+ reflexivity.
133
+ Qed.
134
+
135
+ (** The low-low block equals the functorial low-frequency component. *)
136
+ Theorem low_low_block
137
+ (split : forall X : object C, CofiberSplitting X)
138
+ {X Y : object C} (f : morphism C X Y)
139
+ : let BX := biproduct_data (cs_biprod (split X)) in
140
+ let BY := biproduct_data (cs_biprod (split Y)) in
141
+ (outl BY o cs_iso_inv (split Y) o f o cs_iso (split X) o inl BX
142
+ = low_component f)%morphism.
143
+ Proof.
144
+ intros BX BY.
145
+ repeat rewrite Category.Core.associativity.
146
+ assert (H := conjugated_inl split f).
147
+ repeat rewrite Category.Core.associativity in H.
148
+ rewrite H.
149
+ rewrite <- Category.Core.associativity.
150
+ rewrite (beta_l (biproduct_is (cs_biprod (split Y)))).
151
+ apply left_identity.
152
+ Qed.
153
+
154
+ (** The high-low cross term vanishes: injecting from the low-frequency
155
+ part of X and projecting onto the high-frequency part of Y gives
156
+ zero. Follows from naturality of epsilon and the biproduct axioms. *)
157
+ Theorem cross_term_high_low_zero
158
+ (split : forall X : object C, CofiberSplitting X)
159
+ {X Y : object C} (f : morphism C X Y)
160
+ : let BX := biproduct_data (cs_biprod (split X)) in
161
+ let BY := biproduct_data (cs_biprod (split Y)) in
162
+ (outr BY o cs_iso_inv (split Y) o f o cs_iso (split X) o inl BX
163
+ = @zero_morphism C _ (low_freq X) (cs_high (split Y)))%morphism.
164
+ Proof.
165
+ intros BX BY.
166
+ repeat rewrite Category.Core.associativity.
167
+ assert (H := conjugated_inl split f).
168
+ repeat rewrite Category.Core.associativity in H.
169
+ rewrite H.
170
+ rewrite <- Category.Core.associativity.
171
+ rewrite (mixed_r (biproduct_is (cs_biprod (split Y)))).
172
+ apply zero_morphism_left.
173
+ Qed.
174
+
175
+ (** The low-high cross term vanishes when the splitting is functorial:
176
+ the conjugated morphism maps the high-frequency injection at X
177
+ into the high-frequency injection at Y.
178
+
179
+ This condition is derivable when the splitting comes from a
180
+ cofiber construction (distinguished triangles + vanishing
181
+ connecting morphism), which requires the stable category
182
+ machinery from PR 2288. Here we take it as a hypothesis. *)
183
+ Theorem cross_term_low_high_zero
184
+ (split : forall X : object C, CofiberSplitting X)
185
+ {X Y : object C} (f : morphism C X Y)
186
+ (f_high : morphism C (cs_high (split X)) (cs_high (split Y)))
187
+ (H_func :
188
+ (cs_iso_inv (split Y) o f o cs_iso (split X)
189
+ o inr (biproduct_data (cs_biprod (split X)))
190
+ = inr (biproduct_data (cs_biprod (split Y))) o f_high)%morphism)
191
+ : let BX := biproduct_data (cs_biprod (split X)) in
192
+ let BY := biproduct_data (cs_biprod (split Y)) in
193
+ (outl BY o cs_iso_inv (split Y) o f o cs_iso (split X) o inr BX
194
+ = @zero_morphism C _ (cs_high (split X)) (low_freq Y))%morphism.
195
+ Proof.
196
+ intros BX BY.
197
+ repeat rewrite associativity.
198
+ repeat rewrite associativity in H_func.
199
+ rewrite H_func.
200
+ rewrite <- associativity.
201
+ rewrite (mixed_l (biproduct_is (cs_biprod (split Y)))).
202
+ apply zero_morphism_left.
203
+ Qed.
204
+
205
+ (** ** Iterated decomposition *)
206
+
207
+ Fixpoint iterated_low (n : nat) (X : object C) : object C :=
208
+ match n with
209
+ | O => X
210
+ | S m => low_freq (iterated_low m X)
211
+ end.
212
+
213
+ Definition scale_band
214
+ (split : forall X, CofiberSplitting X) (k : nat) (X : object C)
215
+ : object C :=
216
+ cs_high (split (iterated_low k X)).
217
+
218
+ Definition iterated_splitting
219
+ (split : forall X, CofiberSplitting X) (k : nat) (X : object C)
220
+ : CofiberSplitting (iterated_low k X) :=
221
+ split (iterated_low k X).
222
+
223
+ End CofiberSplitting.
224
+
225
+ (** * Detection as pi0 of a presheaf colimit *)
226
+
227
+ From HoTT Require Import Basics.Trunc Truncations.Core.
228
+ From HoTT Require Import Diagrams.Graph Diagrams.Diagram.
229
+ From HoTT Require Import Colimits.Colimit.
230
+
231
+ Section SheafPi0.
232
+
233
+ Context (Site : Graph).
234
+ Context (F : Diagram Site).
235
+
236
+ Definition presheaf_colimit : Type := Colimit F.
237
+
238
+ Definition pi0 : Type := Trunc 0 presheaf_colimit.
239
+
240
+ Global Instance ishset_pi0 : IsHSet pi0 := _.
241
+
242
+ Definition pi0_in (i : Site) (x : obj F i) : pi0 := tr (colim i x).
243
+
244
+ Definition same_component (i j : Site) (x : obj F i) (y : obj F j)
245
+ : Type :=
246
+ pi0_in i x = pi0_in j y.
247
+
248
+ Record FinitePresentation := {
249
+ fp_graph : Graph;
250
+ fp_diagram : Diagram fp_graph;
251
+ fp_n_gen : nat;
252
+ fp_n_rel : nat;
253
+ fp_equiv : Colimit fp_diagram <~> Colimit F
254
+ }.
255
+
256
+ Theorem finite_presentation_pi0 (P : FinitePresentation)
257
+ : Trunc 0 (Colimit (fp_diagram P)) <~> pi0.
258
+ Proof.
259
+ apply Trunc_functor_equiv.
260
+ exact (fp_equiv P).
261
+ Defined.
262
+
263
+ End SheafPi0.
README.md ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: fair-research-license
4
+ base_model: facebook/EUPE-ViT-B
5
+ tags:
6
+ - object-detection
7
+ - vision-transformer
8
+ - cofiber-decomposition
9
+ library_name: pytorch
10
+ ---
11
+
12
+ # Cofiber Detection
13
+
14
+ Object detection heads built on cofiber decomposition of frozen [EUPE-ViT-B](https://huggingface.co/facebook/EUPE-ViT-B) features. The cofiber decomposition produces multi-scale representations with zero learned parameters, replacing the 11M-parameter FPN typically used in FCOS-style detectors. Heads range from 70-parameter analytical constructions to 3.85M-parameter trained networks, evaluated on COCO val2017.
15
+
16
+ ## The Cofiber Decomposition
17
+
18
+ Given spatial backbone features `f : [768, H, W]`, the cofiber decomposition produces `n` scale bands via iterated subtraction of downsampled-then-upsampled content:
19
+
20
+ ```
21
+ residual = f
22
+ for k = 0 to n-2:
23
+ omega_k = avgpool(residual, 2)
24
+ sigma_omega_k = upsample_bilinear(omega_k, size=residual.shape)
25
+ cofiber_k = residual - sigma_omega_k
26
+ residual = omega_k
27
+ cofiber_{n-1} = residual
28
+ ```
29
+
30
+ Each `cofiber_k` captures frequency content at a distinct scale with no cross-scale interference. The decomposition is a fixed two-line operation, yet it provides the same multi-scale structure that an FPN synthesizes with 11M trained parameters.
31
+
32
+ The construction is machine-checked in Rocq/HoTT (`CofiberDecomposition.v`). The proof frames average pooling and bilinear upsampling as an adjoint pair whose counit gives a short exact sequence in a semi-additive category; the cofiber bands are the kernels of the projections, and the sum is exact by construction.
33
+
34
+ ## Best Results (COCO val2017)
35
+
36
+ | Variant | Params | mAP | mAP@0.50 | mAP@0.75 | Category |
37
+ |---------|--------|-----|----------|----------|----------|
38
+ | **split_tower_5scale_160h_5std_4dw_ema_l14_16ep_768_cls_calib** | **2,975,067** | **42.64** | **65.70** | **45.10** | trained |
39
+ | split_tower_5scale_160h_5std_4dw_ema_l14_16ep_768 (step 104k, pre-calib) | 2,975,067 | 42.49 | 65.57 | 44.89 | trained |
40
+ | split_tower_5scale_160h_5std_4dw_ema_l14_16ep (step 100k, 640px) | 2,975,067 | 41.15 | 63.99 | 43.83 | trained |
41
+ | split_tower_5scale_192h_5std_4dw_textaligned_640px | 4,164,699 | 25.95 | 39.13 | 28.92 | trained |
42
+ | split_tower_5scale_192h_5std_4dw | 4,068,954 | 24.6 | 37.1 | 27.0 | trained |
43
+ | split_tower_192h_5std_4dw | 4,016,441 | 20.7 | 28.5 | 22.8 | trained |
44
+ | split_tower_224h_3std_6dw | 3,849,657 | 20.3 | 28.1 | 22.3 | trained |
45
+ | conv_deep_p3_lateral | 4,269,785 | 19.9 | 28.4 | 22.0 | trained |
46
+ | conv_deep_p3 | 3,972,569 | 19.7 | 28.3 | 21.6 | trained |
47
+ | conv_deep_3.38M | 3,381,592 | 18.8 | 27.4 | 20.9 | trained |
48
+ | conv_deep_912k | 911,960 | 17.2 | 25.6 | 19.2 | trained |
49
+ | evolved_deep | 182,580 | 10.6 | 18.9 | 10.8 | trained |
50
+ | spatialreg_92k | 91,960 | 8.2 | 25.7 | 2.8 | trained |
51
+ | box32_92k | 91,640 | 5.9 | 21.4 | 1.3 | trained |
52
+ | box32 pruned R2 | ~62,000 nz | 5.9 | 20.4 | 1.5 | trained |
53
+ | dim20 | 22,076 | 3.9 | 14.8 | 0.9 | trained |
54
+ | analytical_70k | 69,976 | 1.6 | 6.0 | 0.4 | analytical |
55
+ | evolved K=100 person | 105 | 1.3 | 5.8 | 0.1 | circuit |
56
+ | Baseline FCOS (non-cofiber) | 16,138,074 | 41.0 | 64.8 | 43.2 | reference |
57
+
58
+ The current split-tower head reaches **42.64 mAP on COCO val2017 with 2,975,067 learnable parameters** (42.71 under soft NMS), passing the 16.14M FCOS baseline (41.0 mAP) by +1.64 while using 18.4 percent of its head parameter budget. Small-object mAP is 22.3 (FCOS 19.4, +2.9). This is the head shipped in [phanerozoic/argus](https://huggingface.co/phanerozoic/argus). The architecture consists of separate classification and regression convolutional towers, each built from five standard 3×3 convolutions followed by four depthwise residual blocks at a hidden dimension of 160 channels. Its input is a cofiber decomposition of the backbone patch features into four frequency-separated bands (corresponding to spatial strides of 16, 32, 64, and 128 pixels), with an additional finer-resolution level (stride 8) synthesized by a single transposed convolution from the stride-16 band. Top-down lateral connections pass information from coarser bands into finer ones before the towers run. The five resulting prediction levels (strides 8, 16, 32, 64, and 128) match the scale coverage of the FCOS simple feature pyramid while obtaining their multi-scale structure from a zero-parameter decomposition rather than from a learned feature pyramid network.
59
+
60
+ The path from 24.6 mAP to 42.64 mAP was recipe-and-resolution, not architectural. Hidden width actually went down (192 → 160); the gains came from ATSS target assignment (replacing FCOS center sampling), horizontal flip augmentation, a CLIP ViT-L/14 8-prompt-average text embedding in place of ViT-B/32 single-prompt, PC-initialized cls_project (first 80 columns from the SVD of the text embedding, remaining columns a random orthogonal complement), exponential moving average during training (decay 0.9998), a doubled 16-epoch schedule with late-training checkpoint selection, the training-resolution jump from 640-pixel to 768-pixel input (+1.34 mAP, mostly on small-object AP via the 48×48 backbone grid vs 40×40), and a 3-epoch partial fine-tune updating only the classification calibration layers (`cls_project`, `cls_bias`, `logit_scale`) at lr 1e-4 with towers and cofiber path frozen (+0.15 mAP). The final checkpoint is the end state of the partial fine-tune; eval JSON sits next to the weights in the shipping directory.
61
+
62
+ ## Repository Structure
63
+
64
+ ### [`analytical/`](analytical/) — Zero-training or closed-form constructions
65
+
66
+ | Path | Description |
67
+ |------|-------------|
68
+ | [`analytical_70k/`](analytical/analytical_70k/) | Closed-form least-squares head. 70K params, 1.6 mAP, zero training |
69
+ | [`analytical_h1/`](analytical/analytical_h1/) | Sheaf cohomology (H^1) features. Experimental |
70
+ | [`variants/`](analytical/variants/) | Exotic feature experiments (quadratic, RFF, Fourier, fractal) with result JSONs |
71
+ | [`scripts/`](analytical/scripts/) | `analytical_greedy_gpu.py`, `analytical_exotic_gpu.py`, `analytical_empbayes.py`, etc. |
72
+
73
+ ### [`trained/`](trained/) — Gradient-trained cofiber heads
74
+
75
+ | Path | Params | mAP | Description |
76
+ |------|--------|-----|-------------|
77
+ | [`split_tower_5scale/`](trained/split_tower_5scale/) | 4.07M | **24.6** | Five-scale split-tower head (P3-P7). Current best |
78
+ | [`split_tower/`](trained/split_tower/) | 4.02M | 20.7 | Four-scale split-tower head (predecessor) |
79
+ | [`conv_deep/`](trained/conv_deep/) | 912K-4.27M | 17.2-19.9 | Depthwise residual stack variants (scaled, P3, lateral) |
80
+ | [`evolved_deep/`](trained/evolved_deep/) | 182K | 10.6 | 10-layer MLP on 92 evolutionarily-selected dims |
81
+ | [`spatialreg_92k/`](trained/spatialreg_92k/) | 92K | 8.2 | 3x3 depthwise conv on regression output |
82
+ | [`linear_70k/`](trained/linear_70k/) | 70K | 5.2 | Trained linear classifier |
83
+ | [`box32_92k/`](trained/box32_92k/) | 92K | 5.9 | INT8 threshold logic circuit + pruned variants (46K-76K) |
84
+ | [`box32_distilled/`](trained/box32_distilled/) | 92K | — | Self-distillation of box32 |
85
+ | [`dim_sweeps/`](trained/dim_sweeps/) | 9K-80K | 0.3-? | SVD-initialized fixed-dim heads (5, 10, 15, 20, 30, 80) |
86
+ | [`sloe/`](trained/sloe/) | — | 0.0 | Spectral Laplacian object emergence (failed experiment) |
87
+ | [`person_specialist/`](trained/person_specialist/) | 9K | — | Person-only detector |
88
+ | [`waldo_specialist/`](trained/waldo_specialist/) | 5K | — | Waldo-finding detector |
89
+ | [`experimental_scaffolds/`](trained/experimental_scaffolds/) | — | — | Untrained architectural scaffolds (5scale, adaptive, centernet, linear) |
90
+
91
+ ### [`circuit/`](circuit/) — Hardware cofiber circuits
92
+
93
+ | File | Description |
94
+ |------|-------------|
95
+ | `person_analytical.pth` | Person classifier at 93 parameters, 99.8% recall |
96
+ | `person_detector.sv`, `cofiber_detector.sv` | Verilog implementations |
97
+ | `rom/*.hex` | INT8 weight ROMs |
98
+ | `evolved_K100_person_eval.json` | Evolutionary search result, 105 params, 1.3 mAP |
99
+ | `tb_person.sv` | Testbench |
100
+
101
+ ### [`scripts/`](scripts/) — Training and evaluation
102
+
103
+ | Script | Target |
104
+ |--------|--------|
105
+ | `train_split_tower.py` | Split tower (best) |
106
+ | `train_conv_deep.py` | Conv deep family (912K-4.27M) |
107
+ | `train_evolved_deep.py` | Evolved deep on 92 dims |
108
+ | `eval_conv_deep_step.py` | Eval any conv_deep checkpoint |
109
+ | `eval_evolved_deep.py` | Eval evolved_deep checkpoint |
110
+ | `eval_coco_map.py` | Generic COCO mAP eval |
111
+
112
+ ### [`CofiberDecomposition.v`](CofiberDecomposition.v)
113
+
114
+ Rocq/HoTT machine-checked proof that the cofiber decomposition is exact in a semi-additive category: every input decomposes uniquely as a sum of scale bands with zero cross-term residual.
115
+
116
+ ## Scaling Curve
117
+
118
+ The relationship between head parameters and mAP is approximately logarithmic across four orders of magnitude, until the CLIP-text-aligned 160h recipe lands above FCOS:
119
+
120
+ ```
121
+ 105 params → 1.3 mAP (evolved circuit, person only)
122
+ 70K params → 1.6 mAP (analytical closed-form)
123
+ 92K params → 8.2 mAP (depthwise conv on regression)
124
+ 182K params → 10.6 mAP (evolved dim selection + 10-layer MLP)
125
+ 912K params → 17.2 mAP (depthwise conv stack)
126
+ 3.97M params → 19.7 mAP (with stride-8 P3)
127
+ 3.85M params → 20.3 mAP (split cls/reg towers, 3 std + 6 dw at 224 hidden, 4 scales)
128
+ 4.02M params → 20.7 mAP (split cls/reg towers, 5 std + 4 dw at 192 hidden, 4 scales)
129
+ 4.07M params → 24.6 mAP (same tower, 5 scales / P3-P7 coverage)
130
+ 4.16M params → 25.95 mAP (+ CLIP ViT-B/32 text-aligned classifier, 8 ep, 640px)
131
+ 2.98M params → 41.15 mAP (160h + ATSS + EMA + PC init + CLIP ViT-L/14, 16 ep, 640px)
132
+ 2.98M params → 42.49 mAP (same recipe, 768px training input)
133
+ 2.98M params → 42.64 mAP (+ 3-epoch cls_calib fine-tune — shipped in Argus)
134
+ 16.14M params → 41.0 mAP (FCOS baseline with FPN, 640px)
135
+ ```
136
+
137
+ The bottom four rows are the point of the repository. The 25.95 → 42.64 ascent required no architectural capacity increase (hidden width dropped from 192 to 160) and no change to the cofiber decomposition itself. It came from training recipe: ATSS assignment, flip aug, EMA, PC-initialized cls_project, CLIP ViT-L/14 multi-prompt text embeddings, doubled schedule with late-training checkpoint selection, 640→768 resolution scaling, and a final 3-epoch classification-calibration fine-tune. At 18.4 percent of FCOS's head parameter budget the 2.98M checkpoint beats FCOS by +1.64 mAP under hard NMS and +1.71 under soft NMS, with the small-object gap widening to +2.9.
138
+
139
+ ## Broader Detection Work
140
+
141
+ Non-cofiber detection heads (FCOS baseline, untrained architectural variants, alternative formulations) are hosted in [phanerozoic/detection-heads](https://huggingface.co/phanerozoic/detection-heads), which also includes the top-performing cofiber head (split_tower) for reference. This repository is the canonical host for cofiber-based detection research.
142
+
143
+ ## License
144
+
145
+ Fair Research License. See `LICENSE`.
TODO.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cofiber Detection Circuit — Research Directions
2
+
3
+ ## Dense detection at sub-stride resolution
4
+
5
+ The circuit's L1-cache-resident weight set (241 KB) permits evaluation at spatial densities impractical for larger detection heads. At 61K INT8 parameters, per-pixel detection across a 640×640 input (409,600 locations × 80 classes) remains within single-digit millisecond latency on CPU. This eliminates the stride-16 quantization of detection locations inherent in patch-based approaches and may improve small-object recall.
6
+
7
+ ## Prototype ensemble via weight replication
8
+
9
+ Multiple instances of the circuit with independently trained prototype sets can be evaluated in parallel and their outputs combined by majority vote. At 241 KB per instance, a 100-member ensemble occupies 24 MB — within L2 cache on commodity hardware. The ensemble diversity comes from different training seeds, data splits, or domain-specific prototype sets. The question is whether ensembling at the prototype level produces complementary detections or redundant ones.
10
+
11
+ ## Fusion with backbone computation
12
+
13
+ The cofiber decomposition (pool + subtract) operates on the same tensor format as the backbone's intermediate attention outputs. Rather than treating backbone and head as sequential stages with a memory-bus boundary between them, the circuit can be fused into the backbone's final block as a post-attention operation. The detection output is produced before the backbone features are written to global memory, eliminating one full read-write cycle.
14
+
15
+ ## Inline video detection
16
+
17
+ The circuit is small enough to execute inside a video decode loop between frame reconstructions. On hardware with a dedicated video decode unit (NVDEC, Intel QSV, Apple VideoToolbox), the detection circuit runs on the CPU cores that would otherwise idle during decode. This produces per-frame detections with zero additional latency beyond the decode itself, without a separate inference pipeline.
18
+
19
+ ## Formal verification of circuit properties
20
+
21
+ The 61,520-parameter weight space is small enough for bounded model checking. Properties amenable to formal verification include: maximum detection count per input (proving the circuit cannot produce more than K detections on any valid feature tensor), mutual exclusion of class pairs at shared locations, and monotonicity of detection score with respect to prototype similarity. These guarantees are relevant for safety-critical deployment where unbounded detection output is unacceptable.
22
+
23
+ ## On-device prototype adaptation
24
+
25
+ Retraining the circuit requires updating a single 80×768 weight matrix — a rank-1 update per corrected class. On a mobile device, a user correction (misclassified detection) translates to a running-average update of the corresponding class prototype: `w_c ← (1-α)w_c + α·f_corrected`. No optimizer state, no backpropagation, no framework. The prototype update is a vector addition that executes in microseconds. The question is whether online prototype adaptation converges to useful personalization or drifts under distribution shift.
26
+
27
+ ## Neuromorphic deployment
28
+
29
+ The depth-3 circuit with integer weights and Heaviside activation maps directly to spiking neural network hardware (Intel Loihi, IBM TrueNorth, BrainChip Akida). Each threshold gate is one neuron. The fixed-weight layers (pool, subtract) are hardwired connections. The classification layer is a programmable weight matrix loaded once. Event-driven evaluation on neuromorphic hardware consumes power proportional to the number of active detections, not the number of spatial locations evaluated.
30
+
31
+ ## Exhaustive INT8 weight search
32
+
33
+ At 61,520 INT8 parameters, local search over the weight space is tractable. Starting from the trained prototypes, systematically test single-weight perturbations (increment or decrement each INT8 value by 1) and retain changes that improve detection fitness on a held-out set. This is the same pruning methodology used in the `8bit-threshold-computer` project (`prune_weights.py`), applied to detection prototypes rather than arithmetic circuits.
analytical/analytical_70k/analytical_head_70k.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ecd1974d125b19c5b741b9ffa2f06c06baa58a246218d90e4e54ccd3d79a6aee
3
+ size 284165
analytical/analytical_70k/analytical_head_70k_eval.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "head": "analytical_detector",
3
+ "params": 69976,
4
+ "checkpoint": "heads/cofiber_threshold/analytical_70k/analytical_head_70k.pth",
5
+ "n_images": 5000,
6
+ "n_detections": 500000,
7
+ "mAP_0.5_0.95": 0.01546164516440593,
8
+ "mAP_0.50": 0.06037011951154602,
9
+ "mAP_0.75": 0.0035925450594784733,
10
+ "mAP_small": 0.0021775822040523978,
11
+ "mAP_medium": 0.017033486120342587,
12
+ "mAP_large": 0.027756867681569267
13
+ }
analytical/analytical_70k/head.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Analytical detection head — zero gradient steps.
2
+
3
+ All weights are computed from closed-form least-squares on cached backbone
4
+ features. The entire head is a derived circuit: cofiber decomposition (fixed)
5
+ + linear predictions (solved via matrix inverse).
6
+
7
+ Construction:
8
+ 1. Accumulate sufficient statistics: X^T X and X^T Y from training features
9
+ at positive locations, where X = features and Y = targets.
10
+ 2. Solve: W = (X^T X + lambda I)^{-1} X^T Y for classification, regression,
11
+ and centerness independently.
12
+ 3. The resulting weights are the optimal linear predictor in the least-squares sense.
13
+
14
+ There is no training loop, no learning rate, no epochs. The head is computed
15
+ from a single pass over the training data and one matrix inverse per task.
16
+
17
+ Parameters: 69,976
18
+ Construction time: ~130 seconds on CPU
19
+ COCO val2017 mAP: 1.6
20
+
21
+ This is the first known fully-derived detection head on frozen backbone features.
22
+ """
23
+
24
+ import os
25
+ import json
26
+ import time
27
+
28
+ import torch
29
+ import torch.nn as nn
30
+ import torch.nn.functional as F
31
+
32
+ NUM_CLASSES = 80
33
+
34
+
35
+ def cofiber_decompose(f, n_scales):
36
+ cofibers = []
37
+ residual = f
38
+ for _ in range(n_scales - 1):
39
+ omega = F.avg_pool2d(residual, 2)
40
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
41
+ cofibers.append(residual - sigma_omega)
42
+ residual = omega
43
+ cofibers.append(residual)
44
+ return cofibers
45
+
46
+
47
+ class AnalyticalDetector(nn.Module):
48
+ """Fully analytical detection head. All weights from closed-form solution."""
49
+ name = "analytical_detector"
50
+ needs_intermediates = False
51
+
52
+ def __init__(self, feat_dim=768, num_classes=NUM_CLASSES, n_scales=3):
53
+ super().__init__()
54
+ self.n_scales = n_scales
55
+ self.scale_norms = nn.ModuleList([nn.LayerNorm(feat_dim) for _ in range(n_scales)])
56
+ # Direct linear: no hidden layer, no nonlinearity
57
+ self.cls_weight = nn.Parameter(torch.randn(num_classes, feat_dim) * 0.01)
58
+ self.cls_bias = nn.Parameter(torch.zeros(num_classes))
59
+ self.reg_out = nn.Linear(feat_dim, 4)
60
+ self.ctr_weight = nn.Parameter(torch.randn(1, feat_dim) * 0.01)
61
+ self.ctr_bias = nn.Parameter(torch.zeros(1))
62
+ self.scale_params = nn.Parameter(torch.ones(n_scales))
63
+
64
+ def forward(self, spatial, inter=None):
65
+ cofibers = cofiber_decompose(spatial, self.n_scales)
66
+ cls_l, reg_l, ctr_l = [], [], []
67
+ for i, cof in enumerate(cofibers):
68
+ B, C, H, W = cof.shape
69
+ f = self.scale_norms[i](cof.permute(0, 2, 3, 1).reshape(-1, C))
70
+ cls = (f @ self.cls_weight.T + self.cls_bias).reshape(B, H, W, -1).permute(0, 3, 1, 2)
71
+ reg_raw = (self.reg_out(f) * self.scale_params[i]).clamp(-10, 10)
72
+ reg = torch.exp(reg_raw).reshape(B, H, W, 4).permute(0, 3, 1, 2)
73
+ ctr = (f @ self.ctr_weight.T + self.ctr_bias).reshape(B, H, W, 1).permute(0, 3, 1, 2)
74
+ cls_l.append(cls)
75
+ reg_l.append(reg)
76
+ ctr_l.append(ctr)
77
+ return cls_l, reg_l, ctr_l
78
+
79
+
80
+ def construct_analytical_head(cache_dir, n_images=20000, lam=1e-3, resolution=640):
81
+ """Construct all weights from closed-form least-squares. Zero training."""
82
+ from analytical_head import accumulate_statistics, solve_head
83
+ stats = accumulate_statistics(cache_dir, n_images, lam, resolution)
84
+ return solve_head(stats)
analytical/analytical_h1/analytical_h1_best.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f0300acdc7dc6b5524b03f6ee56971286bae77efd8610a48cec28422dbbcacbe
3
+ size 309581
analytical/analytical_h1/eval.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "head": "analytical_h1_best",
3
+ "params": 76120,
4
+ "training": "zero (closed-form least-squares)",
5
+ "n_images": 5000,
6
+ "mAP_0.5_0.95": 0.01162068472530145,
7
+ "mAP_0.50": 0.04322769269795811,
8
+ "mAP_0.75": 0.0027664001981709914,
9
+ "mAP_small": 0.0016777775312599376,
10
+ "mAP_medium": 0.015140164702089317,
11
+ "mAP_large": 0.021564585796647318
12
+ }
analytical/scripts/analytical_best_gpu.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Build the best analytical head from our findings and run full mAP eval.
3
+
4
+ Classification: 768 raw LayerNorm'd features (69.6% accuracy)
5
+ Regression: 768 raw + H^1 vertical + H^1 horizontal boundary features (68.7% quality)
6
+ Centerness: 768 raw features
7
+
8
+ Accumulate on training data, solve, save checkpoint, eval via eval_coco_map.py.
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import sys
14
+ import time
15
+
16
+ import torch
17
+ import torch.nn.functional as F
18
+
19
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
20
+ sys.path.insert(0, SCRIPT_DIR)
21
+
22
+ CACHE_DIR = os.environ.get("ARENA_CACHE_DIR", "feature_cache")
23
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT", "coco")
24
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE", "val_cache/val.pt")
25
+ RESOLUTION = 640
26
+ NUM_CLASSES = 80
27
+ DEVICE = "cuda"
28
+
29
+
30
+ def cofiber_decompose(f, n_scales):
31
+ cofibers = []
32
+ residual = f
33
+ for _ in range(n_scales - 1):
34
+ omega = F.avg_pool2d(residual, 2)
35
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
36
+ cofibers.append(residual - sigma_omega)
37
+ residual = omega
38
+ cofibers.append(residual)
39
+ return cofibers
40
+
41
+
42
+ def make_locations(sizes, strides):
43
+ locs = []
44
+ for (h, w), s in zip(sizes, strides):
45
+ ys = (torch.arange(h, dtype=torch.float32) + 0.5) * s
46
+ xs = (torch.arange(w, dtype=torch.float32) + 0.5) * s
47
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
48
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
49
+ return locs
50
+
51
+
52
+ def assign_targets(loc, boxes, labels, stride, sr):
53
+ n = loc.shape[0]
54
+ ct = torch.full((n,), -1, dtype=torch.long)
55
+ rt = torch.zeros(n, 4)
56
+ ctrt = torch.zeros(n)
57
+ if boxes.numel() == 0:
58
+ return ct, rt, ctrt
59
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
60
+ l = loc[:, None, 0] - boxes[None, :, 0]
61
+ t = loc[:, None, 1] - boxes[None, :, 1]
62
+ r = boxes[None, :, 2] - loc[:, None, 0]
63
+ b = boxes[None, :, 3] - loc[:, None, 1]
64
+ ltrb = torch.stack([l, t, r, b], -1)
65
+ in_box = ltrb.min(-1).values > 0
66
+ cx = (boxes[:, 0] + boxes[:, 2]) / 2
67
+ cy = (boxes[:, 1] + boxes[:, 3]) / 2
68
+ rad = stride * 1.5
69
+ in_center = ((loc[:, None, 0] >= cx - rad) & (loc[:, None, 0] <= cx + rad) &
70
+ (loc[:, None, 1] >= cy - rad) & (loc[:, None, 1] <= cy + rad))
71
+ max_d = ltrb.max(-1).values
72
+ in_level = (max_d >= sr[0]) & (max_d <= sr[1])
73
+ pos = in_box & in_center & in_level
74
+ a = areas[None, :].expand_as(pos).clone()
75
+ a[~pos] = float("inf")
76
+ matched = a.argmin(1)
77
+ is_pos = a.gather(1, matched[:, None]).squeeze(1) < float("inf")
78
+ ct[is_pos] = labels[matched[is_pos]]
79
+ if is_pos.any():
80
+ rt[is_pos] = ltrb[torch.arange(n)[is_pos], matched[is_pos]]
81
+ lp, tp, rp, bp = rt[is_pos].unbind(-1)
82
+ ctrt[is_pos] = torch.sqrt(
83
+ (torch.minimum(lp, rp) / torch.maximum(lp, rp).clamp(min=1e-6)) *
84
+ (torch.minimum(tp, bp) / torch.maximum(tp, bp).clamp(min=1e-6)))
85
+ return ct, rt, ctrt
86
+
87
+
88
+ def compute_h1(f, B, H, W, C):
89
+ """Sheaf H^1 compact: vertical + horizontal boundary magnitudes."""
90
+ f_4d = f.reshape(B, H, W, C).permute(0, 3, 1, 2)
91
+ d_up = f_4d - F.pad(f_4d[:, :, 1:, :], (0, 0, 0, 1))
92
+ d_down = f_4d - F.pad(f_4d[:, :, :-1, :], (0, 0, 1, 0))
93
+ d_left = f_4d - F.pad(f_4d[:, :, :, 1:], (0, 1, 0, 0))
94
+ d_right = f_4d - F.pad(f_4d[:, :, :, :-1], (1, 0, 0, 0))
95
+ v_bound = (d_up.abs() + d_down.abs()).permute(0, 2, 3, 1).reshape(-1, C)
96
+ h_bound = (d_left.abs() + d_right.abs()).permute(0, 2, 3, 1).reshape(-1, C)
97
+ return v_bound, h_bound
98
+
99
+
100
+ def main():
101
+ print("=" * 60)
102
+ print("Best Analytical Head: 768 cls + H^1 regression")
103
+ print("=" * 60, flush=True)
104
+
105
+ manifest = json.load(open(os.path.join(CACHE_DIR, "manifest.json")))
106
+ n_shards = manifest["n_shards"]
107
+ strides = [16, 32, 64]
108
+ H = RESOLUTION // 16
109
+ sizes = [(H, H), (H // 2, H // 2), (H // 4, H // 4)]
110
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
111
+ locs = make_locations(sizes, strides)
112
+
113
+ feat_dim = 768
114
+ reg_dim = 768 * 3 # raw + h1v + h1h
115
+
116
+ # Accumulators
117
+ cls_XtX = torch.zeros(feat_dim + 1, feat_dim + 1, device=DEVICE)
118
+ cls_XtY = torch.zeros(feat_dim + 1, NUM_CLASSES, device=DEVICE)
119
+ reg_XtX = torch.zeros(reg_dim + 1, reg_dim + 1, device=DEVICE)
120
+ reg_XtY = torch.zeros(reg_dim + 1, 4, device=DEVICE)
121
+ ctr_XtX = torch.zeros(feat_dim + 1, feat_dim + 1, device=DEVICE)
122
+ ctr_XtY = torch.zeros(feat_dim + 1, 1, device=DEVICE)
123
+ n_pos = 0
124
+ n_images = 20000
125
+ seen = 0
126
+ t0 = time.time()
127
+
128
+ for si in range(n_shards):
129
+ if seen >= n_images:
130
+ break
131
+ shard = torch.load(os.path.join(CACHE_DIR, f"shard_{si:04d}.pt"),
132
+ map_location="cpu", weights_only=False)
133
+ for item in shard:
134
+ if seen >= n_images:
135
+ break
136
+ sp = item["spatial"].unsqueeze(0).float()
137
+ boxes = item["boxes"]
138
+ labels = item["labels"]
139
+ cofibers = cofiber_decompose(sp, 3)
140
+ for sci, cof in enumerate(cofibers):
141
+ B, C, Hc, Wc = cof.shape
142
+ f = F.layer_norm(cof.permute(0, 2, 3, 1).reshape(-1, C), [C])
143
+ h1v, h1h = compute_h1(f, B, Hc, Wc, C)
144
+ ct, rt, ctrt = assign_targets(locs[sci], boxes, labels, strides[sci], sr[sci])
145
+ pos_mask = ct >= 0
146
+ if not pos_mask.any():
147
+ continue
148
+
149
+ # Classification: raw features only
150
+ fp = f[pos_mask].to(DEVICE)
151
+ fa = torch.cat([fp, torch.ones(fp.shape[0], 1, device=DEVICE)], 1)
152
+ yc = torch.zeros(fp.shape[0], NUM_CLASSES, device=DEVICE)
153
+ yc[torch.arange(fp.shape[0], device=DEVICE), ct[pos_mask].to(DEVICE)] = 1.0
154
+ cls_XtX += fa.T @ fa
155
+ cls_XtY += fa.T @ yc
156
+
157
+ # Regression: raw + H^1
158
+ f_reg = torch.cat([f[pos_mask], h1v[pos_mask], h1h[pos_mask]], 1).to(DEVICE)
159
+ ltrb = rt[pos_mask]
160
+ valid = (ltrb > 0).all(1)
161
+ if valid.any():
162
+ fv = f_reg[valid]
163
+ fva = torch.cat([fv, torch.ones(fv.shape[0], 1, device=DEVICE)], 1)
164
+ yt = torch.log(ltrb[valid]).to(DEVICE)
165
+ reg_XtX += fva.T @ fva
166
+ reg_XtY += fva.T @ yt
167
+
168
+ # Centerness: raw features
169
+ ctr_XtX += fa.T @ fa
170
+ ctr_XtY += fa.T @ ctrt[pos_mask].unsqueeze(1).to(DEVICE)
171
+
172
+ n_pos += pos_mask.sum().item()
173
+ seen += 1
174
+ del shard
175
+ if (si + 1) % 5 == 0:
176
+ print(f" shard {si+1}: {seen} imgs, {n_pos} pos, {time.time()-t0:.0f}s", flush=True)
177
+
178
+ print(f"\nAccumulated {seen} images, {n_pos} positives", flush=True)
179
+
180
+ # Solve
181
+ lam = 0.1
182
+ I_cls = torch.eye(feat_dim + 1, device=DEVICE)
183
+ I_reg = torch.eye(reg_dim + 1, device=DEVICE)
184
+ I_ctr = torch.eye(feat_dim + 1, device=DEVICE)
185
+
186
+ cls_W = torch.linalg.solve(cls_XtX + lam * I_cls * n_pos, cls_XtY)
187
+ reg_W = torch.linalg.solve(reg_XtX + lam * I_reg * n_pos, reg_XtY)
188
+ ctr_W = torch.linalg.solve(ctr_XtX + lam * I_ctr * n_pos, ctr_XtY)
189
+
190
+ print(f"Solved. cls: {feat_dim}->80, reg: {reg_dim}->4, ctr: {feat_dim}->1", flush=True)
191
+
192
+ # Save as state dict
193
+ state = {
194
+ "cls_weight": cls_W[:feat_dim].T.cpu(),
195
+ "cls_bias": cls_W[feat_dim].cpu(),
196
+ "reg_weight": reg_W[:reg_dim].T.cpu(),
197
+ "reg_bias": reg_W[reg_dim].cpu(),
198
+ "ctr_weight": ctr_W[:feat_dim].T.cpu(),
199
+ "ctr_bias": ctr_W[feat_dim].cpu(),
200
+ "scale_norms.0.weight": torch.ones(768),
201
+ "scale_norms.0.bias": torch.zeros(768),
202
+ "scale_norms.1.weight": torch.ones(768),
203
+ "scale_norms.1.bias": torch.zeros(768),
204
+ "scale_norms.2.weight": torch.ones(768),
205
+ "scale_norms.2.bias": torch.zeros(768),
206
+ "scale_params": torch.ones(3),
207
+ "meta": {"cls_features": "768_layernorm",
208
+ "reg_features": "768_layernorm_h1v_h1h",
209
+ "ctr_features": "768_layernorm",
210
+ "lambda": lam, "n_images": seen, "n_pos": n_pos},
211
+ }
212
+
213
+ out_dir = os.path.join(SCRIPT_DIR, "heads", "cofiber_threshold", "analytical_h1")
214
+ os.makedirs(out_dir, exist_ok=True)
215
+ out_path = os.path.join(out_dir, "analytical_h1_best.pth")
216
+ torch.save(state, out_path)
217
+
218
+ n_params = sum(v.numel() for k, v in state.items() if isinstance(v, torch.Tensor))
219
+ elapsed = time.time() - t0
220
+ print(f"\nSaved: {out_path}")
221
+ print(f"Total params: {n_params:,}")
222
+ print(f"Construction time: {elapsed:.0f}s")
223
+ print(f"\nClassification: 768 dims, {feat_dim * NUM_CLASSES + NUM_CLASSES:,} params")
224
+ print(f"Regression: {reg_dim} dims (768+768+768), {reg_dim * 4 + 4:,} params")
225
+ print(f"Centerness: 768 dims, {feat_dim + 1:,} params")
226
+
227
+
228
+ if __name__ == "__main__":
229
+ main()
analytical/scripts/analytical_empbayes.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Empirical Bayes analytical detection head.
3
+
4
+ Bayesian linear regression: W ~ N(0, tau^2 I), Y|X,W ~ N(XW, sigma^2 I)
5
+ The optimal regularization is lambda = sigma^2 / tau^2.
6
+
7
+ Empirical Bayes estimates sigma^2 and tau^2 from the data by maximizing
8
+ the log marginal likelihood (type-II ML):
9
+
10
+ log p(Y|X, sigma^2, tau^2) = -n/2 log(2pi) - 1/2 log|sigma^2 I + tau^2 X X^T|
11
+ - 1/2 Y^T (sigma^2 I + tau^2 X X^T)^{-1} Y
12
+
13
+ Using the SVD of X = U S V^T, this simplifies to operations on the singular values.
14
+ The optimization alternates between updating sigma^2 and tau^2.
15
+
16
+ This gives a principled, per-task lambda with calibrated uncertainty.
17
+ """
18
+
19
+ import json, os, sys, time
20
+ import torch
21
+ import torch.nn.functional as F
22
+
23
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
24
+ CACHE_DIR = os.environ.get("ARENA_CACHE_DIR")
25
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT")
26
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE")
27
+ DEVICE = "cuda"
28
+ RESOLUTION = 640
29
+ NUM_CLASSES = 80
30
+
31
+
32
+ def cofiber_decompose(f, n_scales):
33
+ cofibers = []; residual = f
34
+ for _ in range(n_scales - 1):
35
+ omega = F.avg_pool2d(residual, 2)
36
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
37
+ cofibers.append(residual - sigma_omega); residual = omega
38
+ cofibers.append(residual); return cofibers
39
+
40
+
41
+ def make_locations(sizes, strides, device="cpu"):
42
+ locs = []
43
+ for (h, w), s in zip(sizes, strides):
44
+ ys = (torch.arange(h, device=device, dtype=torch.float32) + 0.5) * s
45
+ xs = (torch.arange(w, device=device, dtype=torch.float32) + 0.5) * s
46
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
47
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
48
+ return locs
49
+
50
+
51
+ def assign_targets(loc, boxes, labels, stride, sr):
52
+ n = loc.shape[0]
53
+ ct = torch.full((n,), -1, dtype=torch.long); rt = torch.zeros(n, 4); ctrt = torch.zeros(n)
54
+ if boxes.numel() == 0: return ct, rt, ctrt
55
+ areas = (boxes[:,2]-boxes[:,0])*(boxes[:,3]-boxes[:,1])
56
+ l=loc[:,None,0]-boxes[None,:,0]; t=loc[:,None,1]-boxes[None,:,1]
57
+ r=boxes[None,:,2]-loc[:,None,0]; b=boxes[None,:,3]-loc[:,None,1]
58
+ ltrb=torch.stack([l,t,r,b],-1); in_box=ltrb.min(-1).values>0
59
+ cx=(boxes[:,0]+boxes[:,2])/2; cy=(boxes[:,1]+boxes[:,3])/2; rad=stride*1.5
60
+ in_center=((loc[:,None,0]>=cx-rad)&(loc[:,None,0]<=cx+rad)&(loc[:,None,1]>=cy-rad)&(loc[:,None,1]<=cy+rad))
61
+ max_d=ltrb.max(-1).values; in_level=(max_d>=sr[0])&(max_d<=sr[1])
62
+ pos=in_box&in_center&in_level; a=areas[None,:].expand_as(pos).clone(); a[~pos]=float("inf")
63
+ matched=a.argmin(1); is_pos=a.gather(1,matched[:,None]).squeeze(1)<float("inf")
64
+ ct[is_pos]=labels[matched[is_pos]]
65
+ if is_pos.any():
66
+ rt[is_pos]=ltrb[torch.arange(n)[is_pos],matched[is_pos]]
67
+ lp,tp,rp,bp=rt[is_pos].unbind(-1)
68
+ ctrt[is_pos]=torch.sqrt((torch.minimum(lp,rp)/torch.maximum(lp,rp).clamp(min=1e-6))*(torch.minimum(tp,bp)/torch.maximum(tp,bp).clamp(min=1e-6)))
69
+ return ct, rt, ctrt
70
+
71
+
72
+ def empirical_bayes_lambda(XtX, XtY, n_samples, n_iters=50):
73
+ """Estimate optimal lambda = sigma^2/tau^2 via empirical Bayes.
74
+
75
+ Uses the eigendecomposition of XtX to iteratively estimate:
76
+ tau^2 = (1/d) sum_i (gamma_i * w_i^2) where gamma_i = s_i^2 / (s_i^2 + lambda)
77
+ sigma^2 = (1/(n - sum gamma_i)) * ||Y - X W||^2
78
+
79
+ gamma_i is the "effective number of well-determined parameters" (Mackay).
80
+ """
81
+ d = XtX.shape[0]
82
+ k = XtY.shape[1] # number of output dims
83
+
84
+ # Eigendecompose XtX
85
+ eigvals, eigvecs = torch.linalg.eigh(XtX)
86
+ eigvals = eigvals.clamp(min=1e-10)
87
+
88
+ # Project targets into eigenspace
89
+ VtXtY = eigvecs.T @ XtY # (d, k)
90
+
91
+ # Initialize
92
+ alpha = 1.0 # 1/tau^2
93
+ beta = 1.0 # 1/sigma^2
94
+ lam = alpha / beta
95
+
96
+ for it in range(n_iters):
97
+ # Effective parameters per eigenvalue
98
+ gamma = eigvals / (eigvals + lam)
99
+ gamma_sum = gamma.sum().item()
100
+
101
+ # Solve with current lambda
102
+ W = eigvecs @ (VtXtY / (eigvals + lam).unsqueeze(1))
103
+
104
+ # Residual sum of squares (averaged over output dims)
105
+ # ||Y - XW||^2 = ||Y||^2 - 2 W^T X^T Y + W^T X^T X W
106
+ # = YtY - 2 W^T XtY + W^T XtX W
107
+ # But we don't have YtY. Approximate from XtY and XtX.
108
+ # W^T XtY = sum of VtXtY_i^2 * s_i / (s_i + lam)
109
+ WtXtY = (VtXtY ** 2 * eigvals.unsqueeze(1) / (eigvals + lam).unsqueeze(1) ** 2).sum(0)
110
+
111
+ # Update alpha (1/tau^2): alpha = gamma_sum / (W^T W)
112
+ WtW = (W ** 2).sum(0).mean().item()
113
+ if WtW > 1e-12:
114
+ alpha = gamma_sum / (d * WtW)
115
+ else:
116
+ alpha = 1e6
117
+
118
+ # Update beta (1/sigma^2) using effective degrees of freedom
119
+ # beta = (n - gamma_sum) / RSS
120
+ # Approximate RSS from the eigenspace
121
+ rss = (VtXtY ** 2 * lam ** 2 / (eigvals + lam).unsqueeze(1) ** 2).sum().item() / k
122
+ dof = max(n_samples - gamma_sum, 1.0)
123
+ if rss > 1e-12:
124
+ beta = dof / rss
125
+ else:
126
+ beta = 1e6
127
+
128
+ lam_new = alpha / beta
129
+ if abs(lam_new - lam) / max(abs(lam), 1e-10) < 1e-6:
130
+ lam = lam_new
131
+ break
132
+ lam = lam_new
133
+
134
+ return lam, gamma_sum, alpha, beta
135
+
136
+
137
+ def main():
138
+ print("=" * 60)
139
+ print("Empirical Bayes Analytical Detection Head")
140
+ print("=" * 60, flush=True)
141
+
142
+ manifest = json.load(open(os.path.join(CACHE_DIR, "manifest.json")))
143
+ strides = [16, 32, 64]; H = RESOLUTION // 16
144
+ sizes = [(H,H),(H//2,H//2),(H//4,H//4)]
145
+ sr = [(-1,128),(128,256),(256,float("inf"))]
146
+ locs = make_locations(sizes, strides)
147
+ feat_dim = 768
148
+
149
+ cls_XtX = torch.zeros(feat_dim+1, feat_dim+1, device=DEVICE)
150
+ cls_XtY = torch.zeros(feat_dim+1, NUM_CLASSES, device=DEVICE)
151
+ reg_XtX = torch.zeros(feat_dim+1, feat_dim+1, device=DEVICE)
152
+ reg_XtY = torch.zeros(feat_dim+1, 4, device=DEVICE)
153
+ ctr_XtX = torch.zeros(feat_dim+1, feat_dim+1, device=DEVICE)
154
+ ctr_XtY = torch.zeros(feat_dim+1, 1, device=DEVICE)
155
+ n_cls=0; n_reg=0; n_ctr=0; seen=0; n_images=20000
156
+ t0 = time.time()
157
+
158
+ for si in range(manifest["n_shards"]):
159
+ if seen >= n_images: break
160
+ shard = torch.load(os.path.join(CACHE_DIR, f"shard_{si:04d}.pt"),
161
+ map_location="cpu", weights_only=False)
162
+ for item in shard:
163
+ if seen >= n_images: break
164
+ sp = item["spatial"].unsqueeze(0).float()
165
+ boxes = item["boxes"]; labels = item["labels"]
166
+ cofibers = cofiber_decompose(sp, 3)
167
+ for sci, cof in enumerate(cofibers):
168
+ B,C,Hc,Wc = cof.shape
169
+ f = F.layer_norm(cof.permute(0,2,3,1).reshape(-1,C), [C]).to(DEVICE)
170
+ ct, rt, ctrt = assign_targets(locs[sci], boxes, labels, strides[sci], sr[sci])
171
+ pos = ct >= 0
172
+ if not pos.any(): continue
173
+ fp = f[pos]
174
+ fa = torch.cat([fp, torch.ones(fp.shape[0],1,device=DEVICE)], 1)
175
+ yc = torch.zeros(fp.shape[0], NUM_CLASSES, device=DEVICE)
176
+ yc[torch.arange(fp.shape[0],device=DEVICE), ct[pos].to(DEVICE)] = 1.0
177
+ cls_XtX += fa.T @ fa; cls_XtY += fa.T @ yc; n_cls += fp.shape[0]
178
+ ltrb = rt[pos]; valid = (ltrb > 0).all(1)
179
+ if valid.any():
180
+ fv = fa[valid]; yt = torch.log(ltrb[valid]).to(DEVICE)
181
+ reg_XtX += fv.T @ fv; reg_XtY += fv.T @ yt; n_reg += valid.sum().item()
182
+ ctr_XtX += fa.T @ fa
183
+ ctr_XtY += fa.T @ ctrt[pos].unsqueeze(1).to(DEVICE); n_ctr += fp.shape[0]
184
+ seen += 1
185
+ del shard
186
+ if (si+1) % 5 == 0:
187
+ print(f" shard {si+1}: {seen} imgs, {n_cls} cls, {time.time()-t0:.0f}s", flush=True)
188
+
189
+ print(f"\nAccumulated: {n_cls} cls, {n_reg} reg, {n_ctr} ctr", flush=True)
190
+
191
+ # Empirical Bayes per task
192
+ print("\nEstimating per-task lambda via empirical Bayes...", flush=True)
193
+ t1 = time.time()
194
+ lam_cls, gamma_cls, alpha_cls, beta_cls = empirical_bayes_lambda(cls_XtX, cls_XtY, n_cls)
195
+ lam_reg, gamma_reg, alpha_reg, beta_reg = empirical_bayes_lambda(reg_XtX, reg_XtY, n_reg)
196
+ lam_ctr, gamma_ctr, alpha_ctr, beta_ctr = empirical_bayes_lambda(ctr_XtX, ctr_XtY, n_ctr)
197
+ print(f" cls: lambda={lam_cls:.6f} (gamma={gamma_cls:.1f} effective params, alpha={alpha_cls:.4f}, beta={beta_cls:.4f})")
198
+ print(f" reg: lambda={lam_reg:.6f} (gamma={gamma_reg:.1f} effective params, alpha={alpha_reg:.4f}, beta={beta_reg:.4f})")
199
+ print(f" ctr: lambda={lam_ctr:.6f} (gamma={gamma_ctr:.1f} effective params, alpha={alpha_ctr:.4f}, beta={beta_ctr:.4f})")
200
+ print(f" (took {time.time()-t1:.1f}s)", flush=True)
201
+
202
+ # Solve with EB lambdas
203
+ I = torch.eye(feat_dim+1, device=DEVICE)
204
+ print("\nSolving with empirical Bayes lambdas...", flush=True)
205
+ cls_W_eb = torch.linalg.solve(cls_XtX + lam_cls * I, cls_XtY)
206
+ reg_W_eb = torch.linalg.solve(reg_XtX + lam_reg * I, reg_XtY)
207
+ ctr_W_eb = torch.linalg.solve(ctr_XtX + lam_ctr * I, ctr_XtY)
208
+
209
+ # Also solve with our known-good lambda=0.1*n for comparison
210
+ print("Solving with lambda=0.1*n (previous best)...", flush=True)
211
+ cls_W_fix = torch.linalg.solve(cls_XtX + 0.1 * I * n_cls, cls_XtY)
212
+ reg_W_fix = torch.linalg.solve(reg_XtX + 0.1 * I * n_reg, reg_XtY)
213
+ ctr_W_fix = torch.linalg.solve(ctr_XtX + 0.1 * I * n_ctr, ctr_XtY)
214
+
215
+ # Eval both
216
+ val = torch.load(VAL_CACHE, map_location="cpu", weights_only=False)
217
+ from pycocotools.coco import COCO
218
+ from pycocotools.cocoeval import COCOeval
219
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
220
+ coco_gt = COCO(ann_file)
221
+ cat_ids = sorted(coco_gt.getCatIds())
222
+ idx_to_cat = {i: c for i, c in enumerate(cat_ids)}
223
+ all_locs = torch.cat(make_locations(sizes, strides, DEVICE))
224
+
225
+ for label, cls_W, reg_W, ctr_W in [("empirical_bayes", cls_W_eb, reg_W_eb, ctr_W_eb),
226
+ ("fixed_0.1n", cls_W_fix, reg_W_fix, ctr_W_fix)]:
227
+ print(f"\nEvaluating: {label}", flush=True)
228
+ all_results = []
229
+ for idx in range(len(val)):
230
+ item = val[idx]
231
+ spatial = item["spatial"].unsqueeze(0).float().to(DEVICE)
232
+ img_id = int(item["img_id"]); scale = item["scale"]
233
+ cofibers = cofiber_decompose(spatial, 3)
234
+ cls_all, reg_all, ctr_all = [], [], []
235
+ for cof in cofibers:
236
+ B,C,Hc,Wc = cof.shape
237
+ f = F.layer_norm(cof.permute(0,2,3,1).reshape(-1,C), [C])
238
+ fa = torch.cat([f, torch.ones(f.shape[0],1,device=DEVICE)], 1)
239
+ cls = (fa @ cls_W).sigmoid()
240
+ reg = (fa @ reg_W).exp()
241
+ ctr = (fa @ ctr_W).sigmoid()
242
+ cls_all.append(cls); reg_all.append(reg); ctr_all.append(ctr.squeeze(1))
243
+ cls_s = torch.cat(cls_all); reg_s = torch.cat(reg_all); ctr_s = torch.cat(ctr_all)
244
+ scores = cls_s * ctr_s.unsqueeze(1)
245
+ max_s, max_c = scores.max(1)
246
+ topk = min(100, max_s.shape[0])
247
+ top_s, top_i = max_s.topk(topk)
248
+ tc = max_c[top_i]; tr = reg_s[top_i]; tl = all_locs[top_i]
249
+ x1=(tl[:,0]-tr[:,0])/scale; y1=(tl[:,1]-tr[:,1])/scale
250
+ x2=(tl[:,0]+tr[:,2])/scale; y2=(tl[:,1]+tr[:,3])/scale
251
+ w=(x2-x1).clamp(min=0); h=(y2-y1).clamp(min=0)
252
+ for i in range(topk):
253
+ s = top_s[i].item()
254
+ if s < 0.01: continue
255
+ all_results.append({"image_id": img_id, "category_id": idx_to_cat[tc[i].item()],
256
+ "bbox": [x1[i].item(), y1[i].item(), w[i].item(), h[i].item()],
257
+ "score": s})
258
+ if (idx+1) % 1000 == 0:
259
+ print(f" {idx+1}/{len(val)}", flush=True)
260
+
261
+ if all_results:
262
+ coco_dt = coco_gt.loadRes(all_results)
263
+ coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
264
+ coco_eval.params.imgIds = sorted(coco_gt.getImgIds())[:len(val)]
265
+ coco_eval.evaluate(); coco_eval.accumulate(); coco_eval.summarize()
266
+ print(f"\n {label}: mAP={coco_eval.stats[0]:.4f} mAP50={coco_eval.stats[1]:.4f} mAP75={coco_eval.stats[2]:.4f}")
267
+ else:
268
+ print(f" {label}: no detections")
269
+
270
+ elapsed = time.time() - t0
271
+ print(f"\nTotal: {elapsed:.0f}s")
272
+ print(f"EB lambdas: cls={lam_cls:.4f} reg={lam_reg:.4f} ctr={lam_ctr:.4f}")
273
+ print(f"Fixed: cls={0.1*n_cls:.0f} reg={0.1*n_reg:.0f} ctr={0.1*n_ctr:.0f}")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ main()
analytical/scripts/analytical_exotic_gpu.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Exotic analytical detection heads on GPU.
3
+
4
+ Track 1: Random projection pursuit — test N random projections, keep the best.
5
+ Track 2: Nonlinear feature expansion — quadratic cross-terms + random Fourier features.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import sys
11
+ import time
12
+
13
+ import torch
14
+ import torch.nn.functional as F
15
+
16
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
17
+ sys.path.insert(0, SCRIPT_DIR)
18
+
19
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT", "coco")
20
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE", "val_cache/val.pt")
21
+ NUM_CLASSES = 80
22
+ DEVICE = "cuda"
23
+
24
+
25
+ def cofiber_decompose(f, n_scales):
26
+ cofibers = []
27
+ residual = f
28
+ for _ in range(n_scales - 1):
29
+ omega = F.avg_pool2d(residual, 2)
30
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
31
+ cofibers.append(residual - sigma_omega)
32
+ residual = omega
33
+ cofibers.append(residual)
34
+ return cofibers
35
+
36
+
37
+ def make_locations(sizes, strides):
38
+ locs = []
39
+ for (h, w), s in zip(sizes, strides):
40
+ ys = (torch.arange(h, dtype=torch.float32) + 0.5) * s
41
+ xs = (torch.arange(w, dtype=torch.float32) + 0.5) * s
42
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
43
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
44
+ return locs
45
+
46
+
47
+ def assign_targets(loc, boxes, labels, stride, sr):
48
+ n = loc.shape[0]
49
+ if boxes.numel() == 0:
50
+ return torch.full((n,), -1, dtype=torch.long)
51
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
52
+ l = loc[:, None, 0] - boxes[None, :, 0]
53
+ t = loc[:, None, 1] - boxes[None, :, 1]
54
+ r = boxes[None, :, 2] - loc[:, None, 0]
55
+ b = boxes[None, :, 3] - loc[:, None, 1]
56
+ ltrb = torch.stack([l, t, r, b], -1)
57
+ in_box = ltrb.min(-1).values > 0
58
+ cx = (boxes[:, 0] + boxes[:, 2]) / 2
59
+ cy = (boxes[:, 1] + boxes[:, 3]) / 2
60
+ rad = stride * 1.5
61
+ in_center = ((loc[:, None, 0] >= cx - rad) & (loc[:, None, 0] <= cx + rad) &
62
+ (loc[:, None, 1] >= cy - rad) & (loc[:, None, 1] <= cy + rad))
63
+ max_d = ltrb.max(-1).values
64
+ in_level = (max_d >= sr[0]) & (max_d <= sr[1])
65
+ pos = in_box & in_center & in_level
66
+ a = areas[None, :].expand_as(pos).clone()
67
+ a[~pos] = float("inf")
68
+ matched = a.argmin(1)
69
+ is_pos = a.gather(1, matched[:, None]).squeeze(1) < float("inf")
70
+ ct = torch.full((n,), -1, dtype=torch.long)
71
+ ct[is_pos] = labels[matched[is_pos]]
72
+ return ct
73
+
74
+
75
+ def build_val_data(val_path, n_images=500):
76
+ val = torch.load(val_path, map_location="cpu", weights_only=False)
77
+ from pycocotools.coco import COCO
78
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
79
+ coco = COCO(ann_file)
80
+ cat_ids = sorted(coco.getCatIds())
81
+ cat_to_idx = {c: i for i, c in enumerate(cat_ids)}
82
+ strides = [16, 32, 64]
83
+ H = 640 // 16
84
+ sizes = [(H, H), (H // 2, H // 2), (H // 4, H // 4)]
85
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
86
+ locs = make_locations(sizes, strides)
87
+ all_f, all_cls = [], []
88
+ for idx in range(min(n_images, len(val))):
89
+ item = val[idx]
90
+ spatial = item["spatial"].unsqueeze(0).float()
91
+ img_id = item["img_id"]; scale = item["scale"]
92
+ ann_ids = coco.getAnnIds(imgIds=int(img_id), iscrowd=False)
93
+ anns = coco.loadAnns(ann_ids)
94
+ boxes, labels = [], []
95
+ for ann in anns:
96
+ x, y, w, h = ann["bbox"]
97
+ if w < 1 or h < 1: continue
98
+ boxes.append([x*scale, y*scale, (x+w)*scale, (y+h)*scale])
99
+ labels.append(cat_to_idx[ann["category_id"]])
100
+ boxes_t = torch.tensor(boxes, dtype=torch.float32) if boxes else torch.zeros(0, 4)
101
+ labels_t = torch.tensor(labels, dtype=torch.long) if labels else torch.zeros(0, dtype=torch.long)
102
+ cofibers = cofiber_decompose(spatial, 3)
103
+ for sci, cof in enumerate(cofibers):
104
+ B, C, Hc, Wc = cof.shape
105
+ f = F.layer_norm(cof.permute(0, 2, 3, 1).reshape(-1, C), [C])
106
+ ct = assign_targets(locs[sci], boxes_t, labels_t, strides[sci], sr[sci])
107
+ all_f.append(f); all_cls.append(ct)
108
+ return torch.cat(all_f).to(DEVICE), torch.cat(all_cls).to(DEVICE)
109
+
110
+
111
+ def solve_and_score(features_pos, y_cls, features_all, cls_targets, pos_mask, lam=0.1):
112
+ """Solve least-squares and return classification accuracy."""
113
+ fd = features_pos.shape[1]
114
+ fa = torch.cat([features_pos, torch.ones(features_pos.shape[0], 1, device=DEVICE)], 1)
115
+ I = torch.eye(fd + 1, device=DEVICE)
116
+ n = features_pos.shape[0]
117
+ try:
118
+ W = torch.linalg.solve(fa.T @ fa + lam * I * n, fa.T @ y_cls)
119
+ except Exception:
120
+ return 0.0
121
+ scores = features_all @ W[:fd] + W[fd]
122
+ pred = scores.argmax(1)
123
+ correct = (pred[pos_mask] == cls_targets[pos_mask]).sum().item()
124
+ return correct / max(pos_mask.sum().item(), 1)
125
+
126
+
127
+ def main():
128
+ print("=" * 60)
129
+ print("Exotic Analytical Detection Heads (GPU)")
130
+ print("=" * 60, flush=True)
131
+
132
+ print("Building val data...", flush=True)
133
+ features, cls_targets = build_val_data(VAL_CACHE, 500)
134
+ pos = cls_targets >= 0
135
+ n_pos = pos.sum().item()
136
+ f_pos = features[pos]
137
+ y_cls = torch.zeros(n_pos, NUM_CLASSES, device=DEVICE)
138
+ y_cls[torch.arange(n_pos, device=DEVICE), cls_targets[pos]] = 1.0
139
+ print(f" {features.shape[0]} locations, {n_pos} positives", flush=True)
140
+
141
+ results = []
142
+
143
+ # =====================================================
144
+ # Baseline: full 768 dims
145
+ # =====================================================
146
+ t0 = time.time()
147
+ acc = solve_and_score(f_pos, y_cls, features, cls_targets, pos)
148
+ print(f"\nBaseline (768 dims): acc={acc:.4f} [{time.time()-t0:.2f}s]", flush=True)
149
+ results.append({"name": "baseline_768", "acc": acc, "dims": 768})
150
+
151
+ # =====================================================
152
+ # Track 1: Random Projection Pursuit
153
+ # =====================================================
154
+ print(f"\n--- Track 1: Random Projection Pursuit ---", flush=True)
155
+ for K in [10, 20, 50, 100, 200]:
156
+ N_PROJ = 500
157
+ best_acc = 0.0
158
+ best_seed = -1
159
+ t0 = time.time()
160
+ for seed in range(N_PROJ):
161
+ torch.manual_seed(seed)
162
+ proj = torch.randn(768, K, device=DEVICE) / (K ** 0.5)
163
+ f_proj = features @ proj
164
+ fp_proj = f_proj[pos]
165
+ acc = solve_and_score(fp_proj, y_cls, f_proj, cls_targets, pos)
166
+ if acc > best_acc:
167
+ best_acc = acc
168
+ best_seed = seed
169
+ elapsed = time.time() - t0
170
+ n_params = K * NUM_CLASSES + NUM_CLASSES
171
+ print(f" K={K:3d}: best_acc={best_acc:.4f} (seed={best_seed}, "
172
+ f"{n_params} params, {elapsed:.1f}s, {N_PROJ} projections)", flush=True)
173
+ results.append({"name": f"random_proj_K{K}", "acc": best_acc,
174
+ "dims": K, "params": n_params, "seed": best_seed})
175
+
176
+ # =====================================================
177
+ # Track 2a: Quadratic expansion on top greedy dims
178
+ # =====================================================
179
+ print(f"\n--- Track 2a: Quadratic Feature Expansion ---", flush=True)
180
+ # Load greedy dims
181
+ greedy_path = os.path.join(SCRIPT_DIR, "analytical_variants", "greedy_forward_gpu.json")
182
+ if os.path.isfile(greedy_path):
183
+ with open(greedy_path) as f:
184
+ greedy = json.load(f)
185
+ greedy_dims = greedy["selected_dims"]
186
+ else:
187
+ greedy_dims = list(range(20))
188
+
189
+ for K in [5, 10, 20, 30]:
190
+ t0 = time.time()
191
+ dims = greedy_dims[:K]
192
+ f_sub = features[:, dims] # (N, K)
193
+ # Quadratic: all pairwise products x_i * x_j (including x_i^2)
194
+ quad_features = []
195
+ for i in range(K):
196
+ for j in range(i, K):
197
+ quad_features.append(f_sub[:, i] * f_sub[:, j])
198
+ f_quad = torch.stack(quad_features, dim=1) # (N, K*(K+1)/2)
199
+ # Concatenate linear + quadratic
200
+ f_expanded = torch.cat([f_sub, f_quad], dim=1)
201
+ n_expanded = f_expanded.shape[1]
202
+ fp_exp = f_expanded[pos]
203
+ acc = solve_and_score(fp_exp, y_cls, f_expanded, cls_targets, pos)
204
+ n_params = n_expanded * NUM_CLASSES + NUM_CLASSES
205
+ elapsed = time.time() - t0
206
+ print(f" top-{K} + quadratic: {n_expanded} dims, acc={acc:.4f} "
207
+ f"({n_params} params, {elapsed:.2f}s)", flush=True)
208
+ results.append({"name": f"quadratic_top{K}", "acc": acc,
209
+ "dims": n_expanded, "params": n_params})
210
+
211
+ # =====================================================
212
+ # Track 2b: Random Fourier Features (RBF kernel approx)
213
+ # =====================================================
214
+ print(f"\n--- Track 2b: Random Fourier Features ---", flush=True)
215
+ for K_rff in [50, 100, 200, 500]:
216
+ t0 = time.time()
217
+ # sigma controls the kernel width — use median heuristic
218
+ # For speed, estimate from a subsample
219
+ sub = features[:5000]
220
+ dists = torch.cdist(sub[:500], sub[:500])
221
+ sigma = dists.median().item()
222
+ if sigma < 1e-6:
223
+ sigma = 1.0
224
+
225
+ torch.manual_seed(42)
226
+ W_rff = torch.randn(768, K_rff, device=DEVICE) / sigma
227
+ b_rff = torch.rand(K_rff, device=DEVICE) * 2 * 3.14159
228
+
229
+ # phi(x) = sqrt(2/K) * cos(Wx + b)
230
+ rff = (2.0 / K_rff) ** 0.5 * torch.cos(features @ W_rff + b_rff)
231
+
232
+ # Concatenate with raw features
233
+ f_combined = torch.cat([features, rff], dim=1)
234
+ fp_comb = f_combined[pos]
235
+ acc = solve_and_score(fp_comb, y_cls, f_combined, cls_targets, pos)
236
+ n_dims = f_combined.shape[1]
237
+ n_params = n_dims * NUM_CLASSES + NUM_CLASSES
238
+ elapsed = time.time() - t0
239
+ print(f" 768 + {K_rff} RFF: {n_dims} dims, acc={acc:.4f} "
240
+ f"({n_params} params, sigma={sigma:.2f}, {elapsed:.2f}s)", flush=True)
241
+ results.append({"name": f"rff_{K_rff}", "acc": acc,
242
+ "dims": n_dims, "params": n_params})
243
+
244
+ # =====================================================
245
+ # Track 2c: Pure RFF (no raw features)
246
+ # =====================================================
247
+ print(f"\n--- Track 2c: Pure Random Fourier Features (no raw) ---", flush=True)
248
+ for K_rff in [200, 500, 1000]:
249
+ t0 = time.time()
250
+ torch.manual_seed(42)
251
+ W_rff = torch.randn(768, K_rff, device=DEVICE) / sigma
252
+ b_rff = torch.rand(K_rff, device=DEVICE) * 2 * 3.14159
253
+ rff = (2.0 / K_rff) ** 0.5 * torch.cos(features @ W_rff + b_rff)
254
+ fp_rff = rff[pos]
255
+ acc = solve_and_score(fp_rff, y_cls, rff, cls_targets, pos)
256
+ n_params = K_rff * NUM_CLASSES + NUM_CLASSES
257
+ elapsed = time.time() - t0
258
+ print(f" {K_rff} pure RFF: acc={acc:.4f} ({n_params} params, {elapsed:.2f}s)", flush=True)
259
+ results.append({"name": f"pure_rff_{K_rff}", "acc": acc,
260
+ "dims": K_rff, "params": n_params})
261
+
262
+ # =====================================================
263
+ # Summary
264
+ # =====================================================
265
+ print(f"\n{'='*60}")
266
+ print("Ranked by accuracy:")
267
+ for r in sorted(results, key=lambda x: -x["acc"]):
268
+ print(f" {r['name']:25s}: acc={r['acc']:.4f} dims={r.get('dims', '?')} "
269
+ f"params={r.get('params', '?')}")
270
+
271
+ out = os.path.join(SCRIPT_DIR, "analytical_variants", "exotic_gpu.json")
272
+ with open(out, "w") as f:
273
+ json.dump(results, f, indent=2)
274
+ print(f"\nSaved: {out}")
275
+
276
+
277
+ if __name__ == "__main__":
278
+ main()
analytical/scripts/analytical_exotic_reg_gpu.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Exotic regression experiments on GPU.
3
+
4
+ The classification is already at 69.6% with linear features — close to the ceiling.
5
+ The gap between analytical (1.6 mAP) and trained (8.2 mAP) is in REGRESSION.
6
+ Test whether nonlinear feature expansions help the regression solver.
7
+
8
+ Experiments:
9
+ 1. Quadratic features for regression only (linear cls stays at 768)
10
+ 2. Sheaf H^1 boundary features for regression only
11
+ 3. Quadratic + H^1 combined
12
+ 4. Full pipeline: best cls + best reg → build complete head → run actual mAP eval
13
+ """
14
+
15
+ import json
16
+ import os
17
+ import sys
18
+ import time
19
+
20
+ import torch
21
+ import torch.nn.functional as F
22
+
23
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
24
+ sys.path.insert(0, SCRIPT_DIR)
25
+
26
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT", "coco")
27
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE", "val_cache/val.pt")
28
+ NUM_CLASSES = 80
29
+ DEVICE = "cuda"
30
+
31
+
32
+ def cofiber_decompose(f, n_scales):
33
+ cofibers = []
34
+ residual = f
35
+ for _ in range(n_scales - 1):
36
+ omega = F.avg_pool2d(residual, 2)
37
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
38
+ cofibers.append(residual - sigma_omega)
39
+ residual = omega
40
+ cofibers.append(residual)
41
+ return cofibers
42
+
43
+
44
+ def make_locations(sizes, strides):
45
+ locs = []
46
+ for (h, w), s in zip(sizes, strides):
47
+ ys = (torch.arange(h, dtype=torch.float32) + 0.5) * s
48
+ xs = (torch.arange(w, dtype=torch.float32) + 0.5) * s
49
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
50
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
51
+ return locs
52
+
53
+
54
+ def assign_targets_full(loc, boxes, labels, stride, sr):
55
+ n = loc.shape[0]
56
+ ct = torch.full((n,), -1, dtype=torch.long)
57
+ rt = torch.zeros(n, 4)
58
+ if boxes.numel() == 0:
59
+ return ct, rt
60
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
61
+ l = loc[:, None, 0] - boxes[None, :, 0]
62
+ t = loc[:, None, 1] - boxes[None, :, 1]
63
+ r = boxes[None, :, 2] - loc[:, None, 0]
64
+ b = boxes[None, :, 3] - loc[:, None, 1]
65
+ ltrb = torch.stack([l, t, r, b], -1)
66
+ in_box = ltrb.min(-1).values > 0
67
+ cx = (boxes[:, 0] + boxes[:, 2]) / 2
68
+ cy = (boxes[:, 1] + boxes[:, 3]) / 2
69
+ rad = stride * 1.5
70
+ in_center = ((loc[:, None, 0] >= cx - rad) & (loc[:, None, 0] <= cx + rad) &
71
+ (loc[:, None, 1] >= cy - rad) & (loc[:, None, 1] <= cy + rad))
72
+ max_d = ltrb.max(-1).values
73
+ in_level = (max_d >= sr[0]) & (max_d <= sr[1])
74
+ pos = in_box & in_center & in_level
75
+ a = areas[None, :].expand_as(pos).clone()
76
+ a[~pos] = float("inf")
77
+ matched = a.argmin(1)
78
+ is_pos = a.gather(1, matched[:, None]).squeeze(1) < float("inf")
79
+ ct[is_pos] = labels[matched[is_pos]]
80
+ if is_pos.any():
81
+ rt[is_pos] = ltrb[torch.arange(n)[is_pos], matched[is_pos]]
82
+ return ct, rt
83
+
84
+
85
+ def build_val_data_with_spatial(val_path, n_images=500):
86
+ """Build features with spatial variants on GPU."""
87
+ val = torch.load(val_path, map_location="cpu", weights_only=False)
88
+ from pycocotools.coco import COCO
89
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
90
+ coco = COCO(ann_file)
91
+ cat_ids = sorted(coco.getCatIds())
92
+ cat_to_idx = {c: i for i, c in enumerate(cat_ids)}
93
+ strides = [16, 32, 64]
94
+ H = 640 // 16
95
+ sizes = [(H, H), (H // 2, H // 2), (H // 4, H // 4)]
96
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
97
+ locs = make_locations(sizes, strides)
98
+
99
+ all_f, all_h1v, all_h1h, all_cls, all_reg = [], [], [], [], []
100
+ for idx in range(min(n_images, len(val))):
101
+ item = val[idx]
102
+ spatial = item["spatial"].unsqueeze(0).float()
103
+ img_id = item["img_id"]; scale = item["scale"]
104
+ ann_ids = coco.getAnnIds(imgIds=int(img_id), iscrowd=False)
105
+ anns = coco.loadAnns(ann_ids)
106
+ boxes, labels = [], []
107
+ for ann in anns:
108
+ x, y, w, h = ann["bbox"]
109
+ if w < 1 or h < 1: continue
110
+ boxes.append([x*scale, y*scale, (x+w)*scale, (y+h)*scale])
111
+ labels.append(cat_to_idx[ann["category_id"]])
112
+ boxes_t = torch.tensor(boxes, dtype=torch.float32) if boxes else torch.zeros(0, 4)
113
+ labels_t = torch.tensor(labels, dtype=torch.long) if labels else torch.zeros(0, dtype=torch.long)
114
+
115
+ cofibers = cofiber_decompose(spatial, 3)
116
+ for sci, cof in enumerate(cofibers):
117
+ B, C, Hc, Wc = cof.shape
118
+ f = F.layer_norm(cof.permute(0, 2, 3, 1).reshape(-1, C), [C])
119
+
120
+ # Sheaf H^1: directional boundary magnitudes
121
+ f_4d = f.reshape(B, Hc, Wc, C).permute(0, 3, 1, 2)
122
+ d_up = f_4d - F.pad(f_4d[:, :, 1:, :], (0, 0, 0, 1))
123
+ d_down = f_4d - F.pad(f_4d[:, :, :-1, :], (0, 0, 1, 0))
124
+ d_left = f_4d - F.pad(f_4d[:, :, :, 1:], (0, 1, 0, 0))
125
+ d_right = f_4d - F.pad(f_4d[:, :, :, :-1], (1, 0, 0, 0))
126
+ v_bound = (d_up.abs() + d_down.abs()).permute(0, 2, 3, 1).reshape(-1, C)
127
+ h_bound = (d_left.abs() + d_right.abs()).permute(0, 2, 3, 1).reshape(-1, C)
128
+
129
+ ct, rt = assign_targets_full(locs[sci], boxes_t, labels_t, strides[sci], sr[sci])
130
+ all_f.append(f)
131
+ all_h1v.append(v_bound)
132
+ all_h1h.append(h_bound)
133
+ all_cls.append(ct)
134
+ all_reg.append(rt)
135
+
136
+ features = torch.cat(all_f).to(DEVICE)
137
+ h1v = torch.cat(all_h1v).to(DEVICE)
138
+ h1h = torch.cat(all_h1h).to(DEVICE)
139
+ cls_targets = torch.cat(all_cls).to(DEVICE)
140
+ reg_targets = torch.cat(all_reg).to(DEVICE)
141
+ return features, h1v, h1h, cls_targets, reg_targets
142
+
143
+
144
+ def solve_regression(f_pos, y_reg, f_all, reg_targets, pos_mask, lam=0.1):
145
+ """Solve for regression weights, return quality metric."""
146
+ valid = (y_reg > 0).all(1)
147
+ if valid.sum() < 10:
148
+ return 0.0
149
+ fv = f_pos[valid]
150
+ fa = torch.cat([fv, torch.ones(fv.shape[0], 1, device=DEVICE)], 1)
151
+ yt = torch.log(y_reg[valid]) # log-ltrb
152
+ fd = fv.shape[1]
153
+ I = torch.eye(fd + 1, device=DEVICE)
154
+ n = fv.shape[0]
155
+ try:
156
+ W = torch.linalg.solve(fa.T @ fa + lam * I * n, fa.T @ yt)
157
+ except Exception:
158
+ return 0.0
159
+
160
+ # Quality: 1/(1+MSE) at positive locations
161
+ pred = f_all[pos_mask] @ W[:fd] + W[fd]
162
+ gt_ltrb = reg_targets[pos_mask]
163
+ val2 = (gt_ltrb > 0).all(1)
164
+ if val2.sum() < 10:
165
+ return 0.0
166
+ gt_log = torch.log(gt_ltrb[val2])
167
+ pred_valid = pred[val2]
168
+ mse = ((pred_valid - gt_log) ** 2).mean(1)
169
+ quality = (1.0 / (1.0 + mse)).mean().item()
170
+ return quality
171
+
172
+
173
+ def main():
174
+ print("=" * 60)
175
+ print("Exotic Regression Experiments (GPU)")
176
+ print("=" * 60, flush=True)
177
+
178
+ features, h1v, h1h, cls_targets, reg_targets = build_val_data_with_spatial(VAL_CACHE, 500)
179
+ pos = cls_targets >= 0
180
+ n_pos = pos.sum().item()
181
+ f_pos = features[pos]
182
+ reg_pos = reg_targets[pos]
183
+ print(f" {features.shape[0]} locations, {n_pos} positives", flush=True)
184
+
185
+ results = []
186
+
187
+ # Load greedy dims
188
+ greedy_path = os.path.join(SCRIPT_DIR, "analytical_variants", "greedy_forward_gpu.json")
189
+ greedy_dims = list(range(20))
190
+ if os.path.isfile(greedy_path):
191
+ with open(greedy_path) as f:
192
+ greedy_dims = json.load(f)["selected_dims"]
193
+
194
+ # =====================================================
195
+ # Baseline regression: 768 raw features
196
+ # =====================================================
197
+ t0 = time.time()
198
+ q = solve_regression(f_pos, reg_pos, features, reg_targets, pos)
199
+ print(f"\n1. Baseline (768 raw): reg_quality={q:.4f} [{time.time()-t0:.2f}s]", flush=True)
200
+ results.append({"name": "baseline_768", "reg_quality": q, "dims": 768})
201
+
202
+ # =====================================================
203
+ # 2. H^1 boundary features for regression
204
+ # =====================================================
205
+ for label, f_extra in [("h1v", h1v), ("h1h", h1h), ("h1_both", torch.cat([h1v, h1h], 1))]:
206
+ f_combined = torch.cat([features, f_extra], 1)
207
+ fp = f_combined[pos]
208
+ t0 = time.time()
209
+ q = solve_regression(fp, reg_pos, f_combined, reg_targets, pos)
210
+ print(f"2. 768 + {label} ({f_combined.shape[1]} dims): reg_quality={q:.4f} [{time.time()-t0:.2f}s]", flush=True)
211
+ results.append({"name": f"h1_{label}", "reg_quality": q, "dims": f_combined.shape[1]})
212
+
213
+ # =====================================================
214
+ # 3. Quadratic features for regression
215
+ # =====================================================
216
+ for K in [10, 20, 30]:
217
+ dims = greedy_dims[:K]
218
+ f_sub = features[:, dims]
219
+ quads = []
220
+ for i in range(K):
221
+ for j in range(i, K):
222
+ quads.append(f_sub[:, i] * f_sub[:, j])
223
+ f_quad = torch.stack(quads, 1)
224
+ f_exp = torch.cat([features, f_quad], 1)
225
+ fp = f_exp[pos]
226
+ t0 = time.time()
227
+ q = solve_regression(fp, reg_pos, f_exp, reg_targets, pos)
228
+ nd = f_exp.shape[1]
229
+ print(f"3. 768 + quad_top{K} ({nd} dims): reg_quality={q:.4f} [{time.time()-t0:.2f}s]", flush=True)
230
+ results.append({"name": f"quad_top{K}", "reg_quality": q, "dims": nd})
231
+
232
+ # =====================================================
233
+ # 4. H^1 + quadratic combined
234
+ # =====================================================
235
+ dims = greedy_dims[:20]
236
+ f_sub = features[:, dims]
237
+ quads = []
238
+ for i in range(20):
239
+ for j in range(i, 20):
240
+ quads.append(f_sub[:, i] * f_sub[:, j])
241
+ f_quad = torch.stack(quads, 1)
242
+ f_all = torch.cat([features, h1v, h1h, f_quad], 1)
243
+ fp = f_all[pos]
244
+ t0 = time.time()
245
+ q = solve_regression(fp, reg_pos, f_all, reg_targets, pos)
246
+ print(f"4. 768 + H1 + quad_top20 ({f_all.shape[1]} dims): reg_quality={q:.4f} [{time.time()-t0:.2f}s]", flush=True)
247
+ results.append({"name": "h1_quad_combined", "reg_quality": q, "dims": f_all.shape[1]})
248
+
249
+ # =====================================================
250
+ # 5. RFF for regression
251
+ # =====================================================
252
+ sub = features[:5000]
253
+ dists = torch.cdist(sub[:500], sub[:500])
254
+ sigma = dists.median().item()
255
+ if sigma < 1e-6: sigma = 1.0
256
+
257
+ for K_rff in [100, 500]:
258
+ torch.manual_seed(42)
259
+ W_rff = torch.randn(768, K_rff, device=DEVICE) / sigma
260
+ b_rff = torch.rand(K_rff, device=DEVICE) * 2 * 3.14159
261
+ rff = (2.0 / K_rff) ** 0.5 * torch.cos(features @ W_rff + b_rff)
262
+ f_combined = torch.cat([features, rff], 1)
263
+ fp = f_combined[pos]
264
+ t0 = time.time()
265
+ q = solve_regression(fp, reg_pos, f_combined, reg_targets, pos)
266
+ print(f"5. 768 + {K_rff} RFF ({f_combined.shape[1]} dims): reg_quality={q:.4f} [{time.time()-t0:.2f}s]", flush=True)
267
+ results.append({"name": f"rff_{K_rff}_reg", "reg_quality": q, "dims": f_combined.shape[1]})
268
+
269
+ # =====================================================
270
+ # Summary
271
+ # =====================================================
272
+ print(f"\n{'='*60}")
273
+ print("Ranked by regression quality:")
274
+ for r in sorted(results, key=lambda x: -x["reg_quality"]):
275
+ print(f" {r['name']:25s}: reg_quality={r['reg_quality']:.4f} dims={r['dims']}")
276
+
277
+ out = os.path.join(SCRIPT_DIR, "analytical_variants", "exotic_reg_gpu.json")
278
+ with open(out, "w") as f:
279
+ json.dump(results, f, indent=2)
280
+ print(f"\nSaved: {out}")
281
+
282
+
283
+ if __name__ == "__main__":
284
+ main()
analytical/scripts/analytical_fractal_gpu.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Fractal cofiber decomposition — wavelet packet style.
3
+
4
+ Instead of recursing only on the low-frequency residual (3 bands),
5
+ recurse on BOTH the cofiber and residual at each level.
6
+
7
+ Depth 1: 2 bands (standard single split)
8
+ Depth 2: 4 bands
9
+ Depth 3: 8 bands
10
+
11
+ Each band is 768 dims. Classification and regression are solved independently
12
+ per band, then results are merged. The solver picks which bands matter.
13
+
14
+ Or: concatenate all bands and solve one large system.
15
+ """
16
+
17
+ import json, os, sys, time
18
+ import torch, torch.nn.functional as F
19
+
20
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
21
+ sys.path.insert(0, SCRIPT_DIR)
22
+
23
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT")
24
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE")
25
+ CACHE_DIR = os.environ.get("ARENA_CACHE_DIR")
26
+ DEVICE = "cuda"
27
+ RESOLUTION = 640
28
+ NUM_CLASSES = 80
29
+
30
+
31
+ def fractal_decompose(f, depth):
32
+ """Fractal cofiber decomposition. Returns list of 2^depth feature maps."""
33
+ if depth == 0:
34
+ return [f]
35
+ omega = F.avg_pool2d(f, 2)
36
+ sigma_omega = F.interpolate(omega, size=f.shape[2:], mode="bilinear", align_corners=False)
37
+ cofiber = f - sigma_omega # high frequency at this scale
38
+
39
+ # Recurse on BOTH branches
40
+ high_bands = fractal_decompose(cofiber, depth - 1)
41
+ low_bands = fractal_decompose(omega, depth - 1)
42
+
43
+ return high_bands + low_bands
44
+
45
+
46
+ def standard_decompose(f, n_scales):
47
+ """Standard cofiber: recurse only on residual."""
48
+ cofibers = []
49
+ residual = f
50
+ for _ in range(n_scales - 1):
51
+ omega = F.avg_pool2d(residual, 2)
52
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
53
+ cofibers.append(residual - sigma_omega)
54
+ residual = omega
55
+ cofibers.append(residual)
56
+ return cofibers
57
+
58
+
59
+ def make_locations(sizes, strides):
60
+ locs = []
61
+ for (h, w), s in zip(sizes, strides):
62
+ ys = (torch.arange(h, dtype=torch.float32) + 0.5) * s
63
+ xs = (torch.arange(w, dtype=torch.float32) + 0.5) * s
64
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
65
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
66
+ return locs
67
+
68
+
69
+ def assign_targets(loc, boxes, labels, stride, sr):
70
+ n = loc.shape[0]
71
+ ct = torch.full((n,), -1, dtype=torch.long)
72
+ rt = torch.zeros(n, 4)
73
+ ctrt = torch.zeros(n)
74
+ if boxes.numel() == 0:
75
+ return ct, rt, ctrt
76
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
77
+ l = loc[:, None, 0] - boxes[None, :, 0]
78
+ t = loc[:, None, 1] - boxes[None, :, 1]
79
+ r = boxes[None, :, 2] - loc[:, None, 0]
80
+ b = boxes[None, :, 3] - loc[:, None, 1]
81
+ ltrb = torch.stack([l, t, r, b], -1)
82
+ in_box = ltrb.min(-1).values > 0
83
+ cx = (boxes[:, 0] + boxes[:, 2]) / 2
84
+ cy = (boxes[:, 1] + boxes[:, 3]) / 2
85
+ rad = stride * 1.5
86
+ in_center = ((loc[:, None, 0] >= cx - rad) & (loc[:, None, 0] <= cx + rad) &
87
+ (loc[:, None, 1] >= cy - rad) & (loc[:, None, 1] <= cy + rad))
88
+ max_d = ltrb.max(-1).values
89
+ in_level = (max_d >= sr[0]) & (max_d <= sr[1])
90
+ pos = in_box & in_center & in_level
91
+ a = areas[None, :].expand_as(pos).clone()
92
+ a[~pos] = float("inf")
93
+ matched = a.argmin(1)
94
+ is_pos = a.gather(1, matched[:, None]).squeeze(1) < float("inf")
95
+ ct[is_pos] = labels[matched[is_pos]]
96
+ if is_pos.any():
97
+ rt[is_pos] = ltrb[torch.arange(n)[is_pos], matched[is_pos]]
98
+ lp, tp, rp, bp = rt[is_pos].unbind(-1)
99
+ ctrt[is_pos] = torch.sqrt(
100
+ (torch.minimum(lp, rp) / torch.maximum(lp, rp).clamp(min=1e-6)) *
101
+ (torch.minimum(tp, bp) / torch.maximum(tp, bp).clamp(min=1e-6)))
102
+ return ct, rt, ctrt
103
+
104
+
105
+ def eval_decomposition(val, coco_gt, cat_ids, decompose_fn, name, lam=0.1, n_train=10000):
106
+ """Accumulate, solve, and eval a decomposition variant.
107
+
108
+ All bands are upsampled to stride-16 resolution (40x40) and use the same
109
+ target assignment. The decomposition separates frequencies, not resolutions.
110
+ """
111
+ idx_to_cat = {i: c for i, c in enumerate(cat_ids)}
112
+ H = RESOLUTION // 16
113
+ target_size = (H, H)
114
+ stride = 16
115
+ sr = (-1, float("inf")) # single scale, all object sizes
116
+ locs_flat = make_locations([target_size], [stride])
117
+ n_locs = H * H
118
+
119
+ manifest = json.load(open(os.path.join(CACHE_DIR, "manifest.json")))
120
+ feat_dim = 768
121
+ cls_XtX = torch.zeros(feat_dim + 1, feat_dim + 1, device=DEVICE)
122
+ cls_XtY = torch.zeros(feat_dim + 1, NUM_CLASSES, device=DEVICE)
123
+ reg_XtX = torch.zeros(feat_dim + 1, feat_dim + 1, device=DEVICE)
124
+ reg_XtY = torch.zeros(feat_dim + 1, 4, device=DEVICE)
125
+ ctr_XtX = torch.zeros(feat_dim + 1, feat_dim + 1, device=DEVICE)
126
+ ctr_XtY = torch.zeros(feat_dim + 1, 1, device=DEVICE)
127
+ n_pos = 0; seen = 0
128
+
129
+ t0 = time.time()
130
+ for si in range(manifest["n_shards"]):
131
+ if seen >= n_train: break
132
+ shard = torch.load(os.path.join(CACHE_DIR, f"shard_{si:04d}.pt"),
133
+ map_location="cpu", weights_only=False)
134
+ for item in shard:
135
+ if seen >= n_train: break
136
+ sp = item["spatial"].unsqueeze(0).float().to(DEVICE)
137
+ boxes = item["boxes"]; labels = item["labels"]
138
+ bands = decompose_fn(sp)
139
+
140
+ # Upsample all bands to 40x40, average them
141
+ upsampled = []
142
+ for band in bands:
143
+ if band.shape[2:] != target_size:
144
+ band = F.interpolate(band, size=target_size, mode="bilinear", align_corners=False)
145
+ upsampled.append(band)
146
+ # Average across all bands — the solver sees the mean multi-frequency representation
147
+ merged = torch.stack(upsampled).mean(0) # (1, 768, 40, 40)
148
+
149
+ B, C, Hc, Wc = merged.shape
150
+ f = F.layer_norm(merged.permute(0, 2, 3, 1).reshape(-1, C), [C])
151
+ ct, rt, ctrt = assign_targets(locs_flat[0], boxes, labels, stride, sr)
152
+ pos_mask = ct >= 0
153
+ if not pos_mask.any():
154
+ seen += 1; continue
155
+ fp = f[pos_mask]
156
+ fa = torch.cat([fp, torch.ones(fp.shape[0], 1, device=DEVICE)], 1)
157
+ yc = torch.zeros(fp.shape[0], NUM_CLASSES, device=DEVICE)
158
+ yc[torch.arange(fp.shape[0], device=DEVICE), ct[pos_mask].to(DEVICE)] = 1.0
159
+ cls_XtX += fa.T @ fa; cls_XtY += fa.T @ yc
160
+ ltrb = rt[pos_mask]; valid = (ltrb > 0).all(1)
161
+ if valid.any():
162
+ fv = fa[valid]; yt = torch.log(ltrb[valid]).to(DEVICE)
163
+ reg_XtX += fv.T @ fv; reg_XtY += fv.T @ yt
164
+ ctr_XtX += fa.T @ fa
165
+ ctr_XtY += fa.T @ ctrt[pos_mask].unsqueeze(1).to(DEVICE)
166
+ n_pos += pos_mask.sum().item()
167
+ seen += 1
168
+ del shard
169
+
170
+ I = torch.eye(feat_dim + 1, device=DEVICE)
171
+ cls_W = torch.linalg.solve(cls_XtX + lam * I * n_pos, cls_XtY)
172
+ reg_W = torch.linalg.solve(reg_XtX + lam * I * n_pos, reg_XtY)
173
+ ctr_W = torch.linalg.solve(ctr_XtX + lam * I * n_pos, ctr_XtY)
174
+ accum_time = time.time() - t0
175
+
176
+ all_locs = locs_flat[0].to(DEVICE)
177
+ all_results = []
178
+ for idx in range(len(val)):
179
+ spatial = val[idx]["spatial"].unsqueeze(0).float().to(DEVICE)
180
+ img_id = int(val[idx]["img_id"]); scale = val[idx]["scale"]
181
+ bands = decompose_fn(spatial)
182
+ upsampled = []
183
+ for band in bands:
184
+ if band.shape[2:] != target_size:
185
+ band = F.interpolate(band, size=target_size, mode="bilinear", align_corners=False)
186
+ upsampled.append(band)
187
+ merged = torch.stack(upsampled).mean(0)
188
+ B, C, Hc, Wc = merged.shape
189
+ f = F.layer_norm(merged.permute(0, 2, 3, 1).reshape(-1, C), [C])
190
+ cls_s = (f @ cls_W[:feat_dim] + cls_W[feat_dim]).sigmoid()
191
+ reg_s = (f @ reg_W[:feat_dim] + reg_W[feat_dim]).exp()
192
+ ctr_s = (f @ ctr_W[:feat_dim] + ctr_W[feat_dim]).sigmoid().squeeze(1)
193
+ scores = cls_s * ctr_s.unsqueeze(1)
194
+ max_s, max_c = scores.max(1)
195
+ topk = min(100, max_s.shape[0])
196
+ top_s, top_i = max_s.topk(topk)
197
+ tc = max_c[top_i]; tr = reg_s[top_i]; tl = all_locs[top_i]
198
+ x1 = (tl[:,0]-tr[:,0])/scale; y1 = (tl[:,1]-tr[:,1])/scale
199
+ x2 = (tl[:,0]+tr[:,2])/scale; y2 = (tl[:,1]+tr[:,3])/scale
200
+ w = (x2-x1).clamp(min=0); h = (y2-y1).clamp(min=0)
201
+ for i in range(topk):
202
+ s = top_s[i].item()
203
+ if s < 0.01: continue
204
+ all_results.append({"image_id": img_id, "category_id": idx_to_cat[tc[i].item()],
205
+ "bbox": [x1[i].item(), y1[i].item(), w[i].item(), h[i].item()],
206
+ "score": s})
207
+
208
+ # pycocotools eval
209
+ from pycocotools.cocoeval import COCOeval
210
+ if not all_results:
211
+ print(f" {name}: no detections"); return 0.0
212
+ coco_dt = coco_gt.loadRes(all_results)
213
+ coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
214
+ coco_eval.params.imgIds = sorted(coco_gt.getImgIds())[:len(val)]
215
+ coco_eval.evaluate(); coco_eval.accumulate(); coco_eval.summarize()
216
+ mAP = coco_eval.stats[0]
217
+ mAP50 = coco_eval.stats[1]
218
+ mAP75 = coco_eval.stats[2]
219
+ print(f" {name}: mAP={mAP:.4f} mAP50={mAP50:.4f} mAP75={mAP75:.4f} "
220
+ f"({accum_time:.0f}s accum, {n_pos} pos)")
221
+ return mAP
222
+
223
+
224
+ def main():
225
+ from pycocotools.coco import COCO
226
+
227
+ print("=" * 60)
228
+ print("Fractal vs Standard Cofiber Decomposition")
229
+ print("=" * 60, flush=True)
230
+
231
+ val = torch.load(VAL_CACHE, map_location="cpu", weights_only=False)
232
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
233
+ coco_gt = COCO(ann_file)
234
+ cat_ids = sorted(coco_gt.getCatIds())
235
+
236
+ results = []
237
+
238
+ # Standard 3-band cofiber (baseline)
239
+ print("\n1. Standard 3-band cofiber:", flush=True)
240
+ mAP = eval_decomposition(val, coco_gt, cat_ids,
241
+ lambda sp: standard_decompose(sp, 3), "standard_3band")
242
+ results.append({"name": "standard_3band", "mAP": mAP, "bands": 3})
243
+
244
+ # Fractal depth 2 (4 bands)
245
+ print("\n2. Fractal depth 2 (4 bands):", flush=True)
246
+ mAP = eval_decomposition(val, coco_gt, cat_ids,
247
+ lambda sp: fractal_decompose(sp, 2), "fractal_depth2")
248
+ results.append({"name": "fractal_depth2", "mAP": mAP, "bands": 4})
249
+
250
+ # Fractal depth 3 (8 bands)
251
+ print("\n3. Fractal depth 3 (8 bands):", flush=True)
252
+ mAP = eval_decomposition(val, coco_gt, cat_ids,
253
+ lambda sp: fractal_decompose(sp, 3), "fractal_depth3")
254
+ results.append({"name": "fractal_depth3", "mAP": mAP, "bands": 8})
255
+
256
+ print(f"\n{'='*60}")
257
+ print("Summary:")
258
+ for r in results:
259
+ print(f" {r['name']:20s}: mAP={r['mAP']:.4f} ({r['bands']} bands)")
260
+
261
+ out = os.path.join(SCRIPT_DIR, "analytical_variants", "fractal_results.json")
262
+ with open(out, "w") as f:
263
+ json.dump(results, f, indent=2)
264
+ print(f"Saved: {out}")
265
+
266
+
267
+ if __name__ == "__main__":
268
+ main()
analytical/scripts/analytical_gcv.py ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GCV-optimal analytical detection head.
3
+
4
+ Computes the generalized cross-validation optimal lambda for each task
5
+ (classification, regression, centerness) independently via SVD of the
6
+ accumulated sufficient statistics. No grid search — closed-form.
7
+
8
+ Then builds the head with per-task optimal regularization and evals.
9
+ """
10
+
11
+ import json, os, sys, time
12
+ import torch
13
+ import torch.nn.functional as F
14
+
15
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ STATS_DIR = os.path.join(SCRIPT_DIR, "analytical_stats_cache")
17
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT")
18
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE")
19
+ CACHE_DIR = os.environ.get("ARENA_CACHE_DIR")
20
+ DEVICE = "cuda"
21
+ RESOLUTION = 640
22
+ NUM_CLASSES = 80
23
+
24
+
25
+ def cofiber_decompose(f, n_scales):
26
+ cofibers = []; residual = f
27
+ for _ in range(n_scales - 1):
28
+ omega = F.avg_pool2d(residual, 2)
29
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
30
+ cofibers.append(residual - sigma_omega); residual = omega
31
+ cofibers.append(residual); return cofibers
32
+
33
+
34
+ def make_locations(sizes, strides, device="cpu"):
35
+ locs = []
36
+ for (h, w), s in zip(sizes, strides):
37
+ ys = (torch.arange(h, device=device, dtype=torch.float32) + 0.5) * s
38
+ xs = (torch.arange(w, device=device, dtype=torch.float32) + 0.5) * s
39
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
40
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
41
+ return locs
42
+
43
+
44
+ def assign_targets(loc, boxes, labels, stride, sr):
45
+ n = loc.shape[0]
46
+ ct = torch.full((n,), -1, dtype=torch.long); rt = torch.zeros(n, 4); ctrt = torch.zeros(n)
47
+ if boxes.numel() == 0: return ct, rt, ctrt
48
+ areas = (boxes[:,2]-boxes[:,0])*(boxes[:,3]-boxes[:,1])
49
+ l=loc[:,None,0]-boxes[None,:,0]; t=loc[:,None,1]-boxes[None,:,1]
50
+ r=boxes[None,:,2]-loc[:,None,0]; b=boxes[None,:,3]-loc[:,None,1]
51
+ ltrb=torch.stack([l,t,r,b],-1); in_box=ltrb.min(-1).values>0
52
+ cx=(boxes[:,0]+boxes[:,2])/2; cy=(boxes[:,1]+boxes[:,3])/2; rad=stride*1.5
53
+ in_center=((loc[:,None,0]>=cx-rad)&(loc[:,None,0]<=cx+rad)&(loc[:,None,1]>=cy-rad)&(loc[:,None,1]<=cy+rad))
54
+ max_d=ltrb.max(-1).values; in_level=(max_d>=sr[0])&(max_d<=sr[1])
55
+ pos=in_box&in_center&in_level; a=areas[None,:].expand_as(pos).clone(); a[~pos]=float("inf")
56
+ matched=a.argmin(1); is_pos=a.gather(1,matched[:,None]).squeeze(1)<float("inf")
57
+ ct[is_pos]=labels[matched[is_pos]]
58
+ if is_pos.any():
59
+ rt[is_pos]=ltrb[torch.arange(n)[is_pos],matched[is_pos]]
60
+ lp,tp,rp,bp=rt[is_pos].unbind(-1)
61
+ ctrt[is_pos]=torch.sqrt((torch.minimum(lp,rp)/torch.maximum(lp,rp).clamp(min=1e-6))*(torch.minimum(tp,bp)/torch.maximum(tp,bp).clamp(min=1e-6)))
62
+ return ct, rt, ctrt
63
+
64
+
65
+ def gcv_optimal_lambda(XtX, XtY, n_samples, lambdas=None):
66
+ """Find GCV-optimal lambda via SVD.
67
+
68
+ For ridge regression: Y = X W + noise
69
+ Hat matrix: H(λ) = X (X^T X + λI)^{-1} X^T
70
+ GCV(λ) = (1/n) ||Y - H(λ)Y||² / (1 - tr(H(λ))/n)²
71
+
72
+ Using SVD of the augmented feature matrix: X = U S V^T
73
+ tr(H(λ)) = Σ s_i² / (s_i² + λ)
74
+ ||Y - H(λ)Y||² = Σ (λ u_i^T Y / (s_i² + λ))²
75
+
76
+ We work with XtX = V S² V^T directly.
77
+ """
78
+ # Eigendecompose XtX (symmetric positive semi-definite)
79
+ eigvals, eigvecs = torch.linalg.eigh(XtX)
80
+ eigvals = eigvals.clamp(min=0) # numerical stability
81
+
82
+ # Project targets: V^T X^T Y = V^T (XtX XtX^{-1} XtY) ...
83
+ # Actually we need U^T Y. From XtX = V S² V^T and XtY = V S U^T Y
84
+ # So V^T XtY = S U^T Y, thus U^T Y = S^{-1} V^T XtY
85
+ VtXtY = eigvecs.T @ XtY # (d+1, k)
86
+ # S = sqrt(eigvals)
87
+ S = eigvals.sqrt().clamp(min=1e-10)
88
+ UtY = VtXtY / S.unsqueeze(1) # (d+1, k)
89
+
90
+ if lambdas is None:
91
+ # Log-spaced search
92
+ lam_min = eigvals[eigvals > 0].min().item() * 0.001
93
+ lam_max = eigvals.max().item() * 10
94
+ lambdas = torch.logspace(
95
+ max(-8, torch.log10(torch.tensor(lam_min)).item()),
96
+ min(4, torch.log10(torch.tensor(lam_max)).item()),
97
+ 200, device=XtX.device)
98
+
99
+ d = XtX.shape[0]
100
+ best_lam = lambdas[0].item()
101
+ best_gcv = float("inf")
102
+
103
+ for lam in lambdas:
104
+ # tr(H) = Σ s_i² / (s_i² + λ)
105
+ leverage = eigvals / (eigvals + lam)
106
+ tr_H = leverage.sum()
107
+
108
+ # Residual: ||Y - HY||² = Σ_i (λ/(s_i²+λ))² ||u_i^T Y||²
109
+ shrinkage = lam / (eigvals + lam)
110
+ res_sq = (shrinkage.unsqueeze(1) ** 2 * UtY ** 2).sum()
111
+
112
+ # GCV
113
+ gcv = (res_sq / n_samples) / ((1 - tr_H / n_samples) ** 2 + 1e-12)
114
+
115
+ if gcv.item() < best_gcv:
116
+ best_gcv = gcv.item()
117
+ best_lam = lam.item()
118
+
119
+ return best_lam, best_gcv
120
+
121
+
122
+ def main():
123
+ print("=" * 60)
124
+ print("GCV-Optimal Analytical Detection Head")
125
+ print("=" * 60, flush=True)
126
+
127
+ # Accumulate on GPU
128
+ manifest = json.load(open(os.path.join(CACHE_DIR, "manifest.json")))
129
+ strides = [16, 32, 64]; H = RESOLUTION // 16
130
+ sizes = [(H, H), (H//2, H//2), (H//4, H//4)]
131
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
132
+ locs = make_locations(sizes, strides)
133
+ feat_dim = 768
134
+
135
+ cls_XtX = torch.zeros(feat_dim+1, feat_dim+1, device=DEVICE)
136
+ cls_XtY = torch.zeros(feat_dim+1, NUM_CLASSES, device=DEVICE)
137
+ reg_XtX = torch.zeros(feat_dim+1, feat_dim+1, device=DEVICE)
138
+ reg_XtY = torch.zeros(feat_dim+1, 4, device=DEVICE)
139
+ ctr_XtX = torch.zeros(feat_dim+1, feat_dim+1, device=DEVICE)
140
+ ctr_XtY = torch.zeros(feat_dim+1, 1, device=DEVICE)
141
+ n_cls = 0; n_reg = 0; n_ctr = 0
142
+ n_images = 20000; seen = 0
143
+ t0 = time.time()
144
+
145
+ for si in range(manifest["n_shards"]):
146
+ if seen >= n_images: break
147
+ shard = torch.load(os.path.join(CACHE_DIR, f"shard_{si:04d}.pt"),
148
+ map_location="cpu", weights_only=False)
149
+ for item in shard:
150
+ if seen >= n_images: break
151
+ sp = item["spatial"].unsqueeze(0).float()
152
+ boxes = item["boxes"]; labels = item["labels"]
153
+ cofibers = cofiber_decompose(sp, 3)
154
+ for sci, cof in enumerate(cofibers):
155
+ B, C, Hc, Wc = cof.shape
156
+ f = F.layer_norm(cof.permute(0,2,3,1).reshape(-1,C), [C]).to(DEVICE)
157
+ ct, rt, ctrt = assign_targets(locs[sci], boxes, labels, strides[sci], sr[sci])
158
+ pos = ct >= 0
159
+ if not pos.any(): continue
160
+ fp = f[pos]
161
+ fa = torch.cat([fp, torch.ones(fp.shape[0],1,device=DEVICE)], 1)
162
+ yc = torch.zeros(fp.shape[0], NUM_CLASSES, device=DEVICE)
163
+ yc[torch.arange(fp.shape[0],device=DEVICE), ct[pos].to(DEVICE)] = 1.0
164
+ cls_XtX += fa.T @ fa; cls_XtY += fa.T @ yc; n_cls += fp.shape[0]
165
+ ltrb = rt[pos]; valid = (ltrb > 0).all(1)
166
+ if valid.any():
167
+ fv = fa[valid]; yt = torch.log(ltrb[valid]).to(DEVICE)
168
+ reg_XtX += fv.T @ fv; reg_XtY += fv.T @ yt; n_reg += valid.sum().item()
169
+ ctr_XtX += fa.T @ fa
170
+ ctr_XtY += fa.T @ ctrt[pos].unsqueeze(1).to(DEVICE); n_ctr += fp.shape[0]
171
+ seen += 1
172
+ del shard
173
+ if (si+1) % 5 == 0:
174
+ print(f" shard {si+1}: {seen} imgs, {n_cls} cls, {n_reg} reg, {time.time()-t0:.0f}s", flush=True)
175
+
176
+ print(f"\nAccumulated: {n_cls} cls, {n_reg} reg, {n_ctr} ctr positives", flush=True)
177
+
178
+ # GCV optimal lambda per task
179
+ print("\nFinding GCV-optimal lambda...", flush=True)
180
+ t1 = time.time()
181
+ # Normalize by n so GCV searches over the actual regularization strength
182
+ # Our solve uses (XtX + lam * I * n), so GCV should find lam such that lam*n is optimal
183
+ # Equivalently: search on (XtX/n + lam * I) and report lam
184
+ lam_cls, gcv_cls = gcv_optimal_lambda(cls_XtX / n_cls, cls_XtY / n_cls, n_cls)
185
+ lam_reg, gcv_reg = gcv_optimal_lambda(reg_XtX / n_reg, reg_XtY / n_reg, n_reg)
186
+ lam_ctr, gcv_ctr = gcv_optimal_lambda(ctr_XtX / n_ctr, ctr_XtY / n_ctr, n_ctr)
187
+ print(f" cls: lambda={lam_cls:.6f} (GCV={gcv_cls:.6f})")
188
+ print(f" reg: lambda={lam_reg:.6f} (GCV={gcv_reg:.6f})")
189
+ print(f" ctr: lambda={lam_ctr:.6f} (GCV={gcv_ctr:.6f})")
190
+ print(f" (took {time.time()-t1:.1f}s)", flush=True)
191
+
192
+ # Compare: solve with GCV lambda vs fixed lambda=0.1
193
+ print("\nSolving with GCV-optimal lambda...", flush=True)
194
+ I = torch.eye(feat_dim+1, device=DEVICE)
195
+ cls_W_gcv = torch.linalg.solve(cls_XtX + lam_cls * I * n_cls, cls_XtY)
196
+ reg_W_gcv = torch.linalg.solve(reg_XtX + lam_reg * I * n_reg, reg_XtY)
197
+ ctr_W_gcv = torch.linalg.solve(ctr_XtX + lam_ctr * I * n_ctr, ctr_XtY)
198
+
199
+ print("Solving with fixed lambda=0.1...", flush=True)
200
+ cls_W_fix = torch.linalg.solve(cls_XtX + 0.1 * I * n_cls, cls_XtY)
201
+ reg_W_fix = torch.linalg.solve(reg_XtX + 0.1 * I * n_reg, reg_XtY)
202
+ ctr_W_fix = torch.linalg.solve(ctr_XtX + 0.1 * I * n_ctr, ctr_XtY)
203
+
204
+ # Eval both on COCO val
205
+ val = torch.load(VAL_CACHE, map_location="cpu", weights_only=False)
206
+ from pycocotools.coco import COCO
207
+ from pycocotools.cocoeval import COCOeval
208
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
209
+ coco_gt = COCO(ann_file)
210
+ cat_ids = sorted(coco_gt.getCatIds())
211
+ idx_to_cat = {i: c for i, c in enumerate(cat_ids)}
212
+ all_locs = torch.cat(make_locations(sizes, strides, DEVICE))
213
+
214
+ for label, cls_W, reg_W, ctr_W in [("gcv_optimal", cls_W_gcv, reg_W_gcv, ctr_W_gcv),
215
+ ("fixed_0.1", cls_W_fix, reg_W_fix, ctr_W_fix)]:
216
+ print(f"\nEvaluating: {label}", flush=True)
217
+ all_results = []
218
+ for idx in range(len(val)):
219
+ item = val[idx]
220
+ spatial = item["spatial"].unsqueeze(0).float().to(DEVICE)
221
+ img_id = int(item["img_id"]); scale = item["scale"]
222
+ cofibers = cofiber_decompose(spatial, 3)
223
+ cls_all, reg_all, ctr_all = [], [], []
224
+ for cof in cofibers:
225
+ B, C, Hc, Wc = cof.shape
226
+ f = F.layer_norm(cof.permute(0,2,3,1).reshape(-1,C), [C])
227
+ fa = torch.cat([f, torch.ones(f.shape[0],1,device=DEVICE)], 1)
228
+ cls = (fa @ cls_W).sigmoid()
229
+ reg = (fa @ reg_W).exp()
230
+ ctr = (fa @ ctr_W).sigmoid()
231
+ cls_all.append(cls); reg_all.append(reg); ctr_all.append(ctr.squeeze(1))
232
+ cls_s = torch.cat(cls_all); reg_s = torch.cat(reg_all); ctr_s = torch.cat(ctr_all)
233
+ scores = cls_s * ctr_s.unsqueeze(1)
234
+ max_s, max_c = scores.max(1)
235
+ topk = min(100, max_s.shape[0])
236
+ top_s, top_i = max_s.topk(topk)
237
+ tc = max_c[top_i]; tr = reg_s[top_i]; tl = all_locs[top_i]
238
+ x1=(tl[:,0]-tr[:,0])/scale; y1=(tl[:,1]-tr[:,1])/scale
239
+ x2=(tl[:,0]+tr[:,2])/scale; y2=(tl[:,1]+tr[:,3])/scale
240
+ w=(x2-x1).clamp(min=0); h=(y2-y1).clamp(min=0)
241
+ for i in range(topk):
242
+ s = top_s[i].item()
243
+ if s < 0.01: continue
244
+ all_results.append({"image_id": img_id, "category_id": idx_to_cat[tc[i].item()],
245
+ "bbox": [x1[i].item(), y1[i].item(), w[i].item(), h[i].item()],
246
+ "score": s})
247
+ if (idx+1) % 1000 == 0:
248
+ print(f" {idx+1}/{len(val)}", flush=True)
249
+
250
+ if all_results:
251
+ coco_dt = coco_gt.loadRes(all_results)
252
+ coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
253
+ coco_eval.params.imgIds = sorted(coco_gt.getImgIds())[:len(val)]
254
+ coco_eval.evaluate(); coco_eval.accumulate(); coco_eval.summarize()
255
+ mAP = coco_eval.stats[0]
256
+ mAP50 = coco_eval.stats[1]
257
+ mAP75 = coco_eval.stats[2]
258
+ print(f"\n {label}: mAP={mAP:.4f} mAP50={mAP50:.4f} mAP75={mAP75:.4f}")
259
+ else:
260
+ print(f" {label}: no detections")
261
+
262
+ elapsed = time.time() - t0
263
+ print(f"\nTotal: {elapsed:.0f}s")
264
+ print(f"\nGCV-optimal lambdas: cls={lam_cls:.6f} reg={lam_reg:.6f} ctr={lam_ctr:.6f}")
265
+
266
+
267
+ if __name__ == "__main__":
268
+ main()
analytical/scripts/analytical_greedy_gpu.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GPU-accelerated greedy forward construction of a minimal detection head.
3
+
4
+ Batches all candidate evaluations into parallel matmuls on GPU.
5
+ 50 greedy steps in seconds instead of minutes.
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import sys
12
+ import time
13
+
14
+ import torch
15
+ import torch.nn.functional as F
16
+
17
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
18
+ sys.path.insert(0, SCRIPT_DIR)
19
+
20
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT", "coco")
21
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE", "val_cache/val.pt")
22
+ NUM_CLASSES = 80
23
+
24
+
25
+ def cofiber_decompose(f, n_scales):
26
+ cofibers = []
27
+ residual = f
28
+ for _ in range(n_scales - 1):
29
+ omega = F.avg_pool2d(residual, 2)
30
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
31
+ cofibers.append(residual - sigma_omega)
32
+ residual = omega
33
+ cofibers.append(residual)
34
+ return cofibers
35
+
36
+
37
+ def make_locations(sizes, strides):
38
+ locs = []
39
+ for (h, w), s in zip(sizes, strides):
40
+ ys = (torch.arange(h, dtype=torch.float32) + 0.5) * s
41
+ xs = (torch.arange(w, dtype=torch.float32) + 0.5) * s
42
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
43
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
44
+ return locs
45
+
46
+
47
+ def assign_targets(loc, boxes, labels, stride, sr):
48
+ n = loc.shape[0]
49
+ if boxes.numel() == 0:
50
+ return torch.full((n,), -1, dtype=torch.long), torch.zeros(n, 4)
51
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
52
+ l = loc[:, None, 0] - boxes[None, :, 0]
53
+ t = loc[:, None, 1] - boxes[None, :, 1]
54
+ r = boxes[None, :, 2] - loc[:, None, 0]
55
+ b = boxes[None, :, 3] - loc[:, None, 1]
56
+ ltrb = torch.stack([l, t, r, b], -1)
57
+ in_box = ltrb.min(-1).values > 0
58
+ cx = (boxes[:, 0] + boxes[:, 2]) / 2
59
+ cy = (boxes[:, 1] + boxes[:, 3]) / 2
60
+ rad = stride * 1.5
61
+ in_center = ((loc[:, None, 0] >= cx - rad) & (loc[:, None, 0] <= cx + rad) &
62
+ (loc[:, None, 1] >= cy - rad) & (loc[:, None, 1] <= cy + rad))
63
+ max_d = ltrb.max(-1).values
64
+ in_level = (max_d >= sr[0]) & (max_d <= sr[1])
65
+ pos = in_box & in_center & in_level
66
+ a = areas[None, :].expand_as(pos).clone()
67
+ a[~pos] = float("inf")
68
+ matched = a.argmin(1)
69
+ is_pos = a.gather(1, matched[:, None]).squeeze(1) < float("inf")
70
+ ct = torch.full((n,), -1, dtype=torch.long)
71
+ ct[is_pos] = labels[matched[is_pos]]
72
+ rt = torch.zeros(n, 4)
73
+ if is_pos.any():
74
+ rt[is_pos] = ltrb[torch.arange(n)[is_pos], matched[is_pos]]
75
+ return ct, rt
76
+
77
+
78
+ def build_val_data(val_path, n_images=500, device="cuda"):
79
+ """Build feature matrix + targets on GPU."""
80
+ val = torch.load(val_path, map_location="cpu", weights_only=False)
81
+ from pycocotools.coco import COCO
82
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
83
+ coco = COCO(ann_file)
84
+ cat_ids = sorted(coco.getCatIds())
85
+ cat_to_idx = {c: i for i, c in enumerate(cat_ids)}
86
+
87
+ strides = [16, 32, 64]
88
+ H = 640 // 16
89
+ sizes = [(H, H), (H // 2, H // 2), (H // 4, H // 4)]
90
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
91
+ locs = make_locations(sizes, strides)
92
+
93
+ all_f, all_cls = [], []
94
+ for idx in range(min(n_images, len(val))):
95
+ item = val[idx]
96
+ spatial = item["spatial"].unsqueeze(0).float()
97
+ img_id = item["img_id"]
98
+ scale = item["scale"]
99
+ ann_ids = coco.getAnnIds(imgIds=int(img_id), iscrowd=False)
100
+ anns = coco.loadAnns(ann_ids)
101
+ boxes, labels = [], []
102
+ for ann in anns:
103
+ x, y, w, h = ann["bbox"]
104
+ if w < 1 or h < 1:
105
+ continue
106
+ boxes.append([x * scale, y * scale, (x + w) * scale, (y + h) * scale])
107
+ labels.append(cat_to_idx[ann["category_id"]])
108
+ boxes_t = torch.tensor(boxes, dtype=torch.float32) if boxes else torch.zeros(0, 4)
109
+ labels_t = torch.tensor(labels, dtype=torch.long) if labels else torch.zeros(0, dtype=torch.long)
110
+
111
+ cofibers = cofiber_decompose(spatial, 3)
112
+ for sci, cof in enumerate(cofibers):
113
+ B, C, Hc, Wc = cof.shape
114
+ f = F.layer_norm(cof.permute(0, 2, 3, 1).reshape(-1, C), [C])
115
+ ct, _ = assign_targets(locs[sci], boxes_t, labels_t, strides[sci], sr[sci])
116
+ all_f.append(f)
117
+ all_cls.append(ct)
118
+
119
+ features = torch.cat(all_f).to(device)
120
+ cls_targets = torch.cat(all_cls).to(device)
121
+ return features, cls_targets
122
+
123
+
124
+ def greedy_step_gpu(features, cls_targets, selected, remaining, lam=0.1):
125
+ """Test all remaining candidates in parallel on GPU. Return best dim and accuracy."""
126
+ pos = cls_targets >= 0
127
+ n_pos = pos.sum().item()
128
+ if n_pos == 0:
129
+ return -1, 0.0
130
+
131
+ # Build one-hot targets
132
+ f_pos = features[pos]
133
+ y_cls = torch.zeros(n_pos, NUM_CLASSES, device=features.device)
134
+ y_cls[torch.arange(n_pos, device=features.device), cls_targets[pos]] = 1.0
135
+ gt = cls_targets[pos]
136
+
137
+ best_dim = -1
138
+ best_acc = -1.0
139
+
140
+ # For each candidate, solve and score
141
+ # Batch in chunks to avoid OOM on very large candidate sets
142
+ chunk_size = 64
143
+ for chunk_start in range(0, len(remaining), chunk_size):
144
+ chunk = remaining[chunk_start:chunk_start + chunk_size]
145
+ accs = []
146
+ for d in chunk:
147
+ dims = selected + [d]
148
+ fd = len(dims)
149
+ fp = f_pos[:, dims]
150
+ fa = torch.cat([fp, torch.ones(n_pos, 1, device=fp.device)], 1)
151
+ I = torch.eye(fd + 1, device=fp.device)
152
+ XtX = fa.T @ fa
153
+ XtY = fa.T @ y_cls
154
+ try:
155
+ W = torch.linalg.solve(XtX + lam * I * n_pos, XtY)
156
+ except Exception:
157
+ accs.append(0.0)
158
+ continue
159
+ # Score on all positive locations
160
+ scores = fp @ W[:fd] + W[fd] # (n_pos, 80)
161
+ pred = scores.argmax(1)
162
+ acc = (pred == gt).float().mean().item()
163
+ accs.append(acc)
164
+
165
+ for i, d in enumerate(chunk):
166
+ if accs[i] > best_acc:
167
+ best_acc = accs[i]
168
+ best_dim = d
169
+
170
+ return best_dim, best_acc
171
+
172
+
173
+ def main():
174
+ parser = argparse.ArgumentParser()
175
+ parser.add_argument("--max-dims", type=int, default=100)
176
+ parser.add_argument("--n-eval", type=int, default=500)
177
+ parser.add_argument("--lam", type=float, default=0.1)
178
+ args = parser.parse_args()
179
+
180
+ device = "cuda" if torch.cuda.is_available() else "cpu"
181
+ print(f"Device: {device}")
182
+ print("=" * 60)
183
+ print(f"GPU Greedy Forward Construction (max {args.max_dims} dims)")
184
+ print("=" * 60, flush=True)
185
+
186
+ print("Building val data...", flush=True)
187
+ features, cls_targets = build_val_data(VAL_CACHE, args.n_eval, device)
188
+ pos = cls_targets >= 0
189
+ print(f" {features.shape[0]} locations, {pos.sum().item()} positives, "
190
+ f"{features.shape[1]} dims", flush=True)
191
+
192
+ selected = []
193
+ remaining = list(range(768))
194
+ history = []
195
+ t0 = time.time()
196
+
197
+ for step in range(args.max_dims):
198
+ t_step = time.time()
199
+ best_dim, best_acc = greedy_step_gpu(features, cls_targets, selected, remaining, args.lam)
200
+
201
+ if best_dim < 0:
202
+ break
203
+
204
+ selected.append(best_dim)
205
+ remaining.remove(best_dim)
206
+ step_time = time.time() - t_step
207
+
208
+ n_params = len(selected) * NUM_CLASSES + NUM_CLASSES # cls only for now
209
+ entry = {"step": step + 1, "dim": best_dim, "cls_acc": round(best_acc, 4),
210
+ "n_params": n_params, "step_ms": round(step_time * 1000)}
211
+ history.append(entry)
212
+
213
+ print(f" step {step+1:3d}: +dim{best_dim:3d} -> cls_acc={best_acc:.4f} "
214
+ f"({len(selected)} dims, {n_params} params, {step_time*1000:.0f}ms)", flush=True)
215
+
216
+ # Early stopping
217
+ if len(history) >= 10:
218
+ recent_gain = history[-1]["cls_acc"] - history[-10]["cls_acc"]
219
+ if recent_gain < 0.005:
220
+ print(f" Converged: <0.5% gain in 10 steps", flush=True)
221
+ break
222
+
223
+ elapsed = time.time() - t0
224
+ print(f"\n{'='*60}")
225
+ print(f"Selected {len(selected)} dimensions in {elapsed:.1f}s")
226
+ print(f"Final cls_acc: {history[-1]['cls_acc']:.4f}")
227
+ print(f"Final params: {history[-1]['n_params']}")
228
+ print(f"\nTop 20 dimensions (most to least important):")
229
+ for h in history[:20]:
230
+ print(f" step {h['step']:2d}: dim{h['dim']:3d} cumul_acc={h['cls_acc']:.4f} ({h['step_ms']}ms)")
231
+
232
+ # Save
233
+ result = {"selected_dims": selected, "history": history,
234
+ "final_cls_acc": history[-1]["cls_acc"], "final_params": history[-1]["n_params"],
235
+ "total_time_s": round(elapsed, 1)}
236
+ out = os.path.join(SCRIPT_DIR, "analytical_variants", "greedy_forward_gpu.json")
237
+ os.makedirs(os.path.dirname(out), exist_ok=True)
238
+ with open(out, "w") as f:
239
+ json.dump(result, f, indent=2)
240
+ print(f"\nSaved: {out}")
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()
analytical/scripts/analytical_hyperbatch.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hyper-batch analytical sweep — all variants on GPU simultaneously.
3
+
4
+ Pre-loads ALL training features + ALL val features into VRAM.
5
+ Pre-computes all feature variants (raw, H^1, fractal, quadratic).
6
+ Solves and evals 100+ variants in one pass.
7
+
8
+ GPU memory budget: ~15 GB of 46 GB available.
9
+ """
10
+
11
+ import json, os, sys, time
12
+ import torch, torch.nn.functional as F
13
+
14
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
15
+ sys.path.insert(0, SCRIPT_DIR)
16
+
17
+ CACHE_DIR = os.environ.get("ARENA_CACHE_DIR")
18
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT")
19
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE")
20
+ DEVICE = "cuda"
21
+ RESOLUTION = 640
22
+ NUM_CLASSES = 80
23
+
24
+
25
+ def cofiber_decompose(f, n_scales):
26
+ cofibers = []; residual = f
27
+ for _ in range(n_scales - 1):
28
+ omega = F.avg_pool2d(residual, 2)
29
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
30
+ cofibers.append(residual - sigma_omega); residual = omega
31
+ cofibers.append(residual); return cofibers
32
+
33
+
34
+ def make_locations(sizes, strides, device="cpu"):
35
+ locs = []
36
+ for (h, w), s in zip(sizes, strides):
37
+ ys = (torch.arange(h, device=device, dtype=torch.float32) + 0.5) * s
38
+ xs = (torch.arange(w, device=device, dtype=torch.float32) + 0.5) * s
39
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
40
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
41
+ return locs
42
+
43
+
44
+ def assign_targets(loc, boxes, labels, stride, sr):
45
+ n = loc.shape[0]
46
+ ct = torch.full((n,), -1, dtype=torch.long)
47
+ rt = torch.zeros(n, 4); ctrt = torch.zeros(n)
48
+ if boxes.numel() == 0: return ct, rt, ctrt
49
+ areas = (boxes[:, 2]-boxes[:, 0])*(boxes[:, 3]-boxes[:, 1])
50
+ l=loc[:,None,0]-boxes[None,:,0]; t=loc[:,None,1]-boxes[None,:,1]
51
+ r=boxes[None,:,2]-loc[:,None,0]; b=boxes[None,:,3]-loc[:,None,1]
52
+ ltrb=torch.stack([l,t,r,b],-1); in_box=ltrb.min(-1).values>0
53
+ cx=(boxes[:,0]+boxes[:,2])/2; cy=(boxes[:,1]+boxes[:,3])/2; rad=stride*1.5
54
+ in_center=((loc[:,None,0]>=cx-rad)&(loc[:,None,0]<=cx+rad)&(loc[:,None,1]>=cy-rad)&(loc[:,None,1]<=cy+rad))
55
+ max_d=ltrb.max(-1).values; in_level=(max_d>=sr[0])&(max_d<=sr[1])
56
+ pos=in_box&in_center&in_level; a=areas[None,:].expand_as(pos).clone(); a[~pos]=float("inf")
57
+ matched=a.argmin(1); is_pos=a.gather(1,matched[:,None]).squeeze(1)<float("inf")
58
+ ct[is_pos]=labels[matched[is_pos]]
59
+ if is_pos.any():
60
+ rt[is_pos]=ltrb[torch.arange(n)[is_pos],matched[is_pos]]
61
+ lp,tp,rp,bp=rt[is_pos].unbind(-1)
62
+ ctrt[is_pos]=torch.sqrt((torch.minimum(lp,rp)/torch.maximum(lp,rp).clamp(min=1e-6))*(torch.minimum(tp,bp)/torch.maximum(tp,bp).clamp(min=1e-6)))
63
+ return ct, rt, ctrt
64
+
65
+
66
+ def load_train_features(n_images=20000):
67
+ """Load training features + targets into GPU."""
68
+ manifest = json.load(open(os.path.join(CACHE_DIR, "manifest.json")))
69
+ strides = [16, 32, 64]; H = RESOLUTION // 16
70
+ sizes = [(H, H), (H//2, H//2), (H//4, H//4)]
71
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
72
+ locs = make_locations(sizes, strides)
73
+
74
+ all_f, all_cls, all_reg, all_ctr = [], [], [], []
75
+ seen = 0
76
+ for si in range(manifest["n_shards"]):
77
+ if seen >= n_images: break
78
+ shard = torch.load(os.path.join(CACHE_DIR, f"shard_{si:04d}.pt"),
79
+ map_location="cpu", weights_only=False)
80
+ for item in shard:
81
+ if seen >= n_images: break
82
+ sp = item["spatial"].unsqueeze(0).float()
83
+ boxes = item["boxes"]; labels = item["labels"]
84
+ cofibers = cofiber_decompose(sp, 3)
85
+ for sci, cof in enumerate(cofibers):
86
+ B, C, Hc, Wc = cof.shape
87
+ f = F.layer_norm(cof.permute(0, 2, 3, 1).reshape(-1, C), [C])
88
+ ct, rt, ctrt = assign_targets(locs[sci], boxes, labels, strides[sci], sr[sci])
89
+ pos = ct >= 0
90
+ if pos.any():
91
+ all_f.append(f[pos])
92
+ all_cls.append(ct[pos])
93
+ all_reg.append(rt[pos])
94
+ all_ctr.append(ctrt[pos])
95
+ seen += 1
96
+ del shard
97
+ if (si+1) % 5 == 0:
98
+ print(f" shard {si+1}: {seen} imgs, {sum(len(x) for x in all_f)} pos", flush=True)
99
+
100
+ features = torch.cat(all_f).to(DEVICE)
101
+ cls_targets = torch.cat(all_cls).to(DEVICE)
102
+ reg_targets = torch.cat(all_reg).to(DEVICE)
103
+ ctr_targets = torch.cat(all_ctr).to(DEVICE)
104
+ print(f" Train: {features.shape[0]} positives on GPU "
105
+ f"({features.element_size() * features.nelement() / 1e9:.1f} GB)")
106
+ return features, cls_targets, reg_targets, ctr_targets
107
+
108
+
109
+ def load_val_features(n_images=5000):
110
+ """Load val features + GT into GPU for eval."""
111
+ val = torch.load(VAL_CACHE, map_location="cpu", weights_only=False)
112
+ from pycocotools.coco import COCO
113
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
114
+ coco = COCO(ann_file)
115
+ cat_ids = sorted(coco.getCatIds())
116
+ cat_to_idx = {c: i for i, c in enumerate(cat_ids)}
117
+ idx_to_cat = {i: c for i, c in enumerate(cat_ids)}
118
+
119
+ strides = [16, 32, 64]; H = RESOLUTION // 16
120
+ sizes = [(H, H), (H//2, H//2), (H//4, H//4)]
121
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
122
+ locs = make_locations(sizes, strides)
123
+ all_locs = torch.cat(locs).to(DEVICE)
124
+
125
+ val_data = []
126
+ for idx in range(min(n_images, len(val))):
127
+ item = val[idx]
128
+ spatial = item["spatial"].unsqueeze(0).float()
129
+ img_id = int(item["img_id"]); scale = item["scale"]
130
+ cofibers = cofiber_decompose(spatial, 3)
131
+ f_all = []
132
+ for cof in cofibers:
133
+ B, C, Hc, Wc = cof.shape
134
+ f = F.layer_norm(cof.permute(0, 2, 3, 1).reshape(-1, C), [C])
135
+ f_all.append(f)
136
+ features = torch.cat(f_all).to(DEVICE)
137
+ val_data.append({"features": features, "img_id": img_id, "scale": scale})
138
+
139
+ print(f" Val: {len(val_data)} images on GPU")
140
+ return val_data, all_locs, idx_to_cat, coco
141
+
142
+
143
+ def solve(features, cls_targets, reg_targets, ctr_targets, lam=0.1):
144
+ """Solve for cls/reg/ctr weights on GPU."""
145
+ fd = features.shape[1]
146
+ n = features.shape[0]
147
+ fa = torch.cat([features, torch.ones(n, 1, device=DEVICE)], 1)
148
+ I = torch.eye(fd + 1, device=DEVICE)
149
+ XtX = fa.T @ fa
150
+
151
+ # Classification
152
+ y_cls = torch.zeros(n, NUM_CLASSES, device=DEVICE)
153
+ y_cls[torch.arange(n, device=DEVICE), cls_targets] = 1.0
154
+ cls_W = torch.linalg.solve(XtX + lam * I * n, fa.T @ y_cls)
155
+
156
+ # Regression (log-ltrb)
157
+ valid = (reg_targets > 0).all(1)
158
+ if valid.sum() > 10:
159
+ fv = fa[valid]
160
+ XtX_r = fv.T @ fv
161
+ reg_W = torch.linalg.solve(XtX_r + lam * torch.eye(fd+1, device=DEVICE) * valid.sum(),
162
+ fv.T @ torch.log(reg_targets[valid]))
163
+ else:
164
+ reg_W = torch.zeros(fd + 1, 4, device=DEVICE)
165
+
166
+ # Centerness
167
+ ctr_W = torch.linalg.solve(XtX + lam * I * n, fa.T @ ctr_targets.unsqueeze(1))
168
+
169
+ return cls_W, reg_W, ctr_W
170
+
171
+
172
+ def eval_head(cls_W, reg_W, ctr_W, val_data, all_locs, idx_to_cat, coco_gt):
173
+ """Run COCO eval for one head. Returns mAP."""
174
+ fd = cls_W.shape[0] - 1
175
+ all_results = []
176
+ for vd in val_data:
177
+ f = vd["features"]
178
+ if f.shape[1] != fd:
179
+ continue # skip if feature dim doesn't match
180
+ scores = (f @ cls_W[:fd] + cls_W[fd]).sigmoid()
181
+ reg = (f @ reg_W[:fd] + reg_W[fd]).exp()
182
+ ctr = (f @ ctr_W[:fd] + ctr_W[fd]).sigmoid().squeeze(1)
183
+ combined = scores * ctr.unsqueeze(1)
184
+ max_s, max_c = combined.max(1)
185
+ topk = min(100, max_s.shape[0])
186
+ top_s, top_i = max_s.topk(topk)
187
+ tc = max_c[top_i]; tr = reg[top_i]; tl = all_locs[top_i]
188
+ scale = vd["scale"]
189
+ x1 = (tl[:,0]-tr[:,0])/scale; y1 = (tl[:,1]-tr[:,1])/scale
190
+ x2 = (tl[:,0]+tr[:,2])/scale; y2 = (tl[:,1]+tr[:,3])/scale
191
+ w = (x2-x1).clamp(min=0); h = (y2-y1).clamp(min=0)
192
+ for i in range(topk):
193
+ s = top_s[i].item()
194
+ if s < 0.01: continue
195
+ all_results.append({"image_id": vd["img_id"],
196
+ "category_id": idx_to_cat[tc[i].item()],
197
+ "bbox": [x1[i].item(), y1[i].item(), w[i].item(), h[i].item()],
198
+ "score": s})
199
+
200
+ if not all_results:
201
+ return 0.0, 0.0, 0.0
202
+ from pycocotools.cocoeval import COCOeval
203
+ coco_dt = coco_gt.loadRes(all_results)
204
+ coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
205
+ coco_eval.params.imgIds = sorted(coco_gt.getImgIds())[:len(val_data)]
206
+ coco_eval.evaluate(); coco_eval.accumulate(); coco_eval.summarize()
207
+ return coco_eval.stats[0], coco_eval.stats[1], coco_eval.stats[2]
208
+
209
+
210
+ def main():
211
+ print("=" * 60)
212
+ print("Hyper-Batch Analytical Sweep (full GPU)")
213
+ print("=" * 60, flush=True)
214
+
215
+ # Load everything into VRAM
216
+ t0 = time.time()
217
+ print("\nLoading training features...", flush=True)
218
+ train_f, train_cls, train_reg, train_ctr = load_train_features(20000)
219
+
220
+ print("\nLoading val features...", flush=True)
221
+ val_data, all_locs, idx_to_cat, coco_gt = load_val_features(5000)
222
+
223
+ load_time = time.time() - t0
224
+ print(f"\nAll data on GPU in {load_time:.0f}s", flush=True)
225
+ print(f"GPU memory: {torch.cuda.memory_allocated()/1e9:.1f} GB / "
226
+ f"{torch.cuda.get_device_properties(0).total_memory/1e9:.1f} GB", flush=True)
227
+
228
+ results = []
229
+
230
+ # =====================================================
231
+ # Sweep lambda on raw 768 features
232
+ # =====================================================
233
+ print(f"\n--- Lambda sweep (768 raw) ---", flush=True)
234
+ for lam in [1e-4, 1e-3, 1e-2, 5e-2, 0.1, 0.2, 0.5, 1.0]:
235
+ t = time.time()
236
+ cls_W, reg_W, ctr_W = solve(train_f, train_cls, train_reg, train_ctr, lam)
237
+ mAP, mAP50, mAP75 = eval_head(cls_W, reg_W, ctr_W, val_data, all_locs, idx_to_cat, coco_gt)
238
+ elapsed = time.time() - t
239
+ print(f" lam={lam:6.4f}: mAP={mAP:.4f} mAP50={mAP50:.4f} mAP75={mAP75:.4f} [{elapsed:.1f}s]", flush=True)
240
+ results.append({"name": f"raw768_lam{lam}", "mAP": mAP, "mAP50": mAP50, "mAP75": mAP75,
241
+ "lam": lam, "dims": 768})
242
+
243
+ # Find best lambda
244
+ best_lam = max(results, key=lambda x: x["mAP"])["lam"]
245
+ print(f" Best lambda: {best_lam}", flush=True)
246
+
247
+ # =====================================================
248
+ # Feature variants at best lambda
249
+ # =====================================================
250
+ print(f"\n--- Feature variants (lam={best_lam}) ---", flush=True)
251
+
252
+ # Raw features (already done above, but include for completeness)
253
+
254
+ # L2-normalized features
255
+ f_l2 = F.normalize(train_f, p=2, dim=1)
256
+ cls_W, reg_W, ctr_W = solve(f_l2, train_cls, train_reg, train_ctr, best_lam)
257
+ # Need L2-normed val features too
258
+ val_l2 = []
259
+ for vd in val_data:
260
+ val_l2.append({**vd, "features": F.normalize(vd["features"], p=2, dim=1)})
261
+ mAP, mAP50, mAP75 = eval_head(cls_W, reg_W, ctr_W, val_l2, all_locs, idx_to_cat, coco_gt)
262
+ print(f" l2norm: mAP={mAP:.4f} mAP50={mAP50:.4f} mAP75={mAP75:.4f}", flush=True)
263
+ results.append({"name": "l2norm", "mAP": mAP, "mAP50": mAP50, "mAP75": mAP75, "dims": 768})
264
+ del val_l2
265
+
266
+ # PCA-reduced features
267
+ for K in [128, 256, 384, 512]:
268
+ # Compute PCA on training positives
269
+ mean = train_f.mean(0, keepdim=True)
270
+ centered = train_f - mean
271
+ # Use SVD on a subsample for speed
272
+ sub = centered[:50000]
273
+ U, S, Vh = torch.linalg.svd(sub, full_matrices=False)
274
+ proj = Vh[:K].T # (768, K)
275
+ f_pca = centered @ proj
276
+ cls_W, reg_W, ctr_W = solve(f_pca, train_cls, train_reg, train_ctr, best_lam)
277
+ val_pca = []
278
+ for vd in val_data:
279
+ val_pca.append({**vd, "features": (vd["features"] - mean) @ proj})
280
+ mAP, mAP50, mAP75 = eval_head(cls_W, reg_W, ctr_W, val_pca, all_locs, idx_to_cat, coco_gt)
281
+ n_params = K * NUM_CLASSES + NUM_CLASSES + K * 4 + 4 + K + 1
282
+ print(f" PCA-{K}: mAP={mAP:.4f} mAP50={mAP50:.4f} mAP75={mAP75:.4f} ({n_params} params)", flush=True)
283
+ results.append({"name": f"pca{K}", "mAP": mAP, "mAP50": mAP50, "mAP75": mAP75,
284
+ "dims": K, "params": n_params})
285
+ del val_pca
286
+
287
+ # =====================================================
288
+ # Summary
289
+ # =====================================================
290
+ print(f"\n{'='*60}")
291
+ print("Ranked by mAP:")
292
+ for r in sorted(results, key=lambda x: -x["mAP"]):
293
+ print(f" {r['name']:25s}: mAP={r['mAP']:.4f} mAP50={r.get('mAP50',0):.4f} "
294
+ f"mAP75={r.get('mAP75',0):.4f} dims={r.get('dims','?')}")
295
+
296
+ out = os.path.join(SCRIPT_DIR, "analytical_variants", "hyperbatch_results.json")
297
+ os.makedirs(os.path.dirname(out), exist_ok=True)
298
+ with open(out, "w") as f:
299
+ json.dump(results, f, indent=2)
300
+ print(f"\nSaved: {out}")
301
+ total = time.time() - t0
302
+ print(f"Total: {total:.0f}s for {len(results)} variants")
303
+
304
+
305
+ if __name__ == "__main__":
306
+ main()
analytical/scripts/analytical_one.py ADDED
@@ -0,0 +1,549 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Build and evaluate one analytical detection head variant.
3
+
4
+ Usage:
5
+ python analytical_one.py --name baseline
6
+ python analytical_one.py --name whitened --transform zca
7
+ python analytical_one.py --name spatial3x3 --spatial mean3x3
8
+ """
9
+
10
+ import argparse
11
+ import json
12
+ import math
13
+ import os
14
+ import sys
15
+ import time
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+ import numpy as np
20
+
21
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
22
+ sys.path.insert(0, SCRIPT_DIR)
23
+
24
+ CACHE_DIR = os.environ.get("ARENA_CACHE_DIR", "feature_cache")
25
+ COCO_ROOT = os.environ.get("ARENA_COCO_ROOT", "coco")
26
+ VAL_CACHE = os.environ.get("ARENA_VAL_CACHE", "val_cache/val.pt")
27
+ STATS_DIR = os.path.join(SCRIPT_DIR, "analytical_stats_cache")
28
+ RESULTS_DIR = os.path.join(SCRIPT_DIR, "analytical_variants")
29
+ RESOLUTION = 640
30
+ NUM_CLASSES = 80
31
+
32
+
33
+ def cofiber_decompose(f, n_scales):
34
+ cofibers = []
35
+ residual = f
36
+ for _ in range(n_scales - 1):
37
+ omega = F.avg_pool2d(residual, 2)
38
+ sigma_omega = F.interpolate(omega, size=residual.shape[2:], mode="bilinear", align_corners=False)
39
+ cofibers.append(residual - sigma_omega)
40
+ residual = omega
41
+ cofibers.append(residual)
42
+ return cofibers
43
+
44
+
45
+ def make_locations(sizes, strides):
46
+ locs = []
47
+ for (h, w), s in zip(sizes, strides):
48
+ ys = (torch.arange(h, dtype=torch.float32) + 0.5) * s
49
+ xs = (torch.arange(w, dtype=torch.float32) + 0.5) * s
50
+ gy, gx = torch.meshgrid(ys, xs, indexing="ij")
51
+ locs.append(torch.stack([gx.flatten(), gy.flatten()], -1))
52
+ return locs
53
+
54
+
55
+ def assign_targets(loc, boxes, labels, stride, sr):
56
+ n = loc.shape[0]
57
+ if boxes.numel() == 0:
58
+ return torch.full((n,), -1, dtype=torch.long), torch.zeros(n, 4), torch.zeros(n)
59
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
60
+ l = loc[:, None, 0] - boxes[None, :, 0]
61
+ t = loc[:, None, 1] - boxes[None, :, 1]
62
+ r = boxes[None, :, 2] - loc[:, None, 0]
63
+ b = boxes[None, :, 3] - loc[:, None, 1]
64
+ ltrb = torch.stack([l, t, r, b], -1)
65
+ in_box = ltrb.min(-1).values > 0
66
+ cx = (boxes[:, 0] + boxes[:, 2]) / 2
67
+ cy = (boxes[:, 1] + boxes[:, 3]) / 2
68
+ rad = stride * 1.5
69
+ in_center = ((loc[:, None, 0] >= cx - rad) & (loc[:, None, 0] <= cx + rad) &
70
+ (loc[:, None, 1] >= cy - rad) & (loc[:, None, 1] <= cy + rad))
71
+ max_d = ltrb.max(-1).values
72
+ in_level = (max_d >= sr[0]) & (max_d <= sr[1])
73
+ pos = in_box & in_center & in_level
74
+ a = areas[None, :].expand_as(pos).clone()
75
+ a[~pos] = float("inf")
76
+ matched = a.argmin(1)
77
+ is_pos = a.gather(1, matched[:, None]).squeeze(1) < float("inf")
78
+ ct = torch.full((n,), -1, dtype=torch.long)
79
+ ct[is_pos] = labels[matched[is_pos]]
80
+ rt = torch.zeros(n, 4)
81
+ if is_pos.any():
82
+ rt[is_pos] = ltrb[torch.arange(n)[is_pos], matched[is_pos]]
83
+ ctrt = torch.zeros(n)
84
+ if is_pos.any():
85
+ lp, tp, rp, bp = rt[is_pos].unbind(-1)
86
+ ctrt[is_pos] = torch.sqrt(
87
+ (torch.minimum(lp, rp) / torch.maximum(lp, rp).clamp(min=1e-6)) *
88
+ (torch.minimum(tp, bp) / torch.maximum(tp, bp).clamp(min=1e-6)))
89
+ return ct, rt, ctrt
90
+
91
+
92
+ # ============================================================
93
+ # Feature transforms
94
+ # ============================================================
95
+
96
+ def transform_layernorm(f, C):
97
+ return F.layer_norm(f, [C])
98
+
99
+ def transform_raw(f, C):
100
+ return f
101
+
102
+ def transform_l2norm(f, C):
103
+ return F.normalize(f, p=2, dim=-1)
104
+
105
+ def transform_power05(f, C):
106
+ """Signed power normalization: sign(x) * |x|^0.5, then L2 normalize."""
107
+ out = f.sign() * f.abs().sqrt()
108
+ return F.normalize(out, p=2, dim=-1)
109
+
110
+ def transform_power025(f, C):
111
+ """Stronger compression: sign(x) * |x|^0.25."""
112
+ out = f.sign() * f.abs().pow(0.25)
113
+ return F.normalize(out, p=2, dim=-1)
114
+
115
+ TRANSFORMS = {
116
+ "layernorm": transform_layernorm,
117
+ "raw": transform_raw,
118
+ "l2norm": transform_l2norm,
119
+ "power05": transform_power05,
120
+ "power025": transform_power025,
121
+ }
122
+
123
+
124
+ # ============================================================
125
+ # Target encodings
126
+ # ============================================================
127
+
128
+ def encode_log_ltrb(ltrb):
129
+ valid = (ltrb > 0).all(1)
130
+ out = torch.zeros_like(ltrb)
131
+ if valid.any():
132
+ out[valid] = torch.log(ltrb[valid])
133
+ return out, valid
134
+
135
+ def encode_sqrt_ltrb(ltrb):
136
+ valid = (ltrb > 0).all(1)
137
+ out = torch.zeros_like(ltrb)
138
+ if valid.any():
139
+ out[valid] = torch.sqrt(ltrb[valid])
140
+ return out, valid
141
+
142
+ def encode_ltrb(ltrb):
143
+ valid = (ltrb > 0).all(1)
144
+ return ltrb, valid
145
+
146
+ def encode_corners(ltrb):
147
+ valid = (ltrb > 0).all(1)
148
+ return torch.stack([-ltrb[:, 0], -ltrb[:, 1], ltrb[:, 2], ltrb[:, 3]], 1), valid
149
+
150
+ def encode_center_size(ltrb):
151
+ valid = (ltrb > 0).all(1)
152
+ cx_off = (ltrb[:, 2] - ltrb[:, 0]) / 2
153
+ cy_off = (ltrb[:, 3] - ltrb[:, 1]) / 2
154
+ w = ltrb[:, 0] + ltrb[:, 2]
155
+ h = ltrb[:, 1] + ltrb[:, 3]
156
+ return torch.stack([cx_off, cy_off, w, h], 1), valid
157
+
158
+ ENCODINGS = {
159
+ "log_ltrb": encode_log_ltrb,
160
+ "sqrt_ltrb": encode_sqrt_ltrb,
161
+ "ltrb": encode_ltrb,
162
+ "corners": encode_corners,
163
+ "center_size": encode_center_size,
164
+ }
165
+
166
+
167
+ # ============================================================
168
+ # Spatial context modes
169
+ # ============================================================
170
+
171
+ def spatial_none(f_grid, B, H, W, C):
172
+ """No spatial context. Per-token features only."""
173
+ return f_grid.reshape(-1, C)
174
+
175
+ def spatial_mean3x3(f_grid, B, H, W, C):
176
+ """Replace each token with the mean of its 3x3 neighborhood."""
177
+ f_4d = f_grid.reshape(B, H, W, C).permute(0, 3, 1, 2)
178
+ pooled = F.avg_pool2d(f_4d, 3, stride=1, padding=1)
179
+ return pooled.permute(0, 2, 3, 1).reshape(-1, C)
180
+
181
+ def spatial_cat_mean3x3(f_grid, B, H, W, C):
182
+ """Concatenate: [center_token, mean_of_3x3_neighborhood]. 2*C dims."""
183
+ f_4d = f_grid.reshape(B, H, W, C).permute(0, 3, 1, 2)
184
+ pooled = F.avg_pool2d(f_4d, 3, stride=1, padding=1)
185
+ center = f_grid.reshape(-1, C)
186
+ neighbor_mean = pooled.permute(0, 2, 3, 1).reshape(-1, C)
187
+ return torch.cat([center, neighbor_mean], dim=1)
188
+
189
+ def spatial_diff_neighbors(f_grid, B, H, W, C):
190
+ """Center token + (center - neighbor_mean). Emphasizes local contrast."""
191
+ f_4d = f_grid.reshape(B, H, W, C).permute(0, 3, 1, 2)
192
+ pooled = F.avg_pool2d(f_4d, 3, stride=1, padding=1)
193
+ center = f_grid.reshape(-1, C)
194
+ diff = center - pooled.permute(0, 2, 3, 1).reshape(-1, C)
195
+ return torch.cat([center, diff], dim=1)
196
+
197
+ def spatial_hv_neighbors(f_grid, B, H, W, C):
198
+ """Center + horizontal mean + vertical mean. 3*C dims."""
199
+ f_4d = f_grid.reshape(B, H, W, C).permute(0, 3, 1, 2)
200
+ h_pool = F.avg_pool2d(f_4d, (1, 3), stride=1, padding=(0, 1))
201
+ v_pool = F.avg_pool2d(f_4d, (3, 1), stride=1, padding=(1, 0))
202
+ center = f_grid.reshape(-1, C)
203
+ h_mean = h_pool.permute(0, 2, 3, 1).reshape(-1, C)
204
+ v_mean = v_pool.permute(0, 2, 3, 1).reshape(-1, C)
205
+ return torch.cat([center, h_mean, v_mean], dim=1)
206
+
207
+ def spatial_sheaf_h1(f_grid, B, H, W, C):
208
+ """Sheaf H^1: directional edge differences (4 cardinal Cech 1-cocycles).
209
+
210
+ At each location, compute feature[here] - feature[neighbor] for all 4
211
+ cardinal directions. This is the Cech 1-cocycle representative on the
212
+ spatial grid. It captures gluing obstructions — exactly where local
213
+ feature sections fail to extend consistently. Object boundaries are
214
+ such obstructions.
215
+
216
+ Output: [center, d_up, d_down, d_left, d_right] = 5*C dims.
217
+ """
218
+ f_4d = f_grid.reshape(B, H, W, C).permute(0, 3, 1, 2) # (B, C, H, W)
219
+ # Shift in each direction and subtract
220
+ d_up = f_4d - F.pad(f_4d[:, :, 1:, :], (0, 0, 0, 1)) # diff with token above
221
+ d_down = f_4d - F.pad(f_4d[:, :, :-1, :], (0, 0, 1, 0)) # diff with token below
222
+ d_left = f_4d - F.pad(f_4d[:, :, :, 1:], (0, 1, 0, 0)) # diff with token left
223
+ d_right = f_4d - F.pad(f_4d[:, :, :, :-1], (1, 0, 0, 0)) # diff with token right
224
+ center = f_grid.reshape(-1, C)
225
+ du = d_up.permute(0, 2, 3, 1).reshape(-1, C)
226
+ dd = d_down.permute(0, 2, 3, 1).reshape(-1, C)
227
+ dl = d_left.permute(0, 2, 3, 1).reshape(-1, C)
228
+ dr = d_right.permute(0, 2, 3, 1).reshape(-1, C)
229
+ return torch.cat([center, du, dd, dl, dr], dim=1)
230
+
231
+ def spatial_sheaf_h1_compact(f_grid, B, H, W, C):
232
+ """Sheaf H^1 compact: center + L1 norm of directional cocycles.
233
+
234
+ Instead of raw directional differences (5*C dims), compute the
235
+ L1 magnitude of each directional cocycle per channel. This gives a
236
+ scalar "boundary strength" per channel per direction.
237
+ Output: [center, |d_up|+|d_down|, |d_left|+|d_right|] = 3*C dims.
238
+ Vertical and horizontal boundary strengths.
239
+ """
240
+ f_4d = f_grid.reshape(B, H, W, C).permute(0, 3, 1, 2)
241
+ d_up = f_4d - F.pad(f_4d[:, :, 1:, :], (0, 0, 0, 1))
242
+ d_down = f_4d - F.pad(f_4d[:, :, :-1, :], (0, 0, 1, 0))
243
+ d_left = f_4d - F.pad(f_4d[:, :, :, 1:], (0, 1, 0, 0))
244
+ d_right = f_4d - F.pad(f_4d[:, :, :, :-1], (1, 0, 0, 0))
245
+ center = f_grid.reshape(-1, C)
246
+ v_boundary = (d_up.abs() + d_down.abs()).permute(0, 2, 3, 1).reshape(-1, C)
247
+ h_boundary = (d_left.abs() + d_right.abs()).permute(0, 2, 3, 1).reshape(-1, C)
248
+ return torch.cat([center, v_boundary, h_boundary], dim=1)
249
+
250
+ SPATIAL = {
251
+ "none": spatial_none,
252
+ "mean3x3": spatial_mean3x3,
253
+ "cat_mean3x3": spatial_cat_mean3x3,
254
+ "diff_neighbors": spatial_diff_neighbors,
255
+ "hv_neighbors": spatial_hv_neighbors,
256
+ "sheaf_h1": spatial_sheaf_h1,
257
+ "sheaf_h1_compact": spatial_sheaf_h1_compact,
258
+ }
259
+
260
+
261
+ # ============================================================
262
+ # Core: accumulate, solve, evaluate
263
+ # ============================================================
264
+
265
+ def accumulate(n_images, transform_name, encoding_name, spatial_name, per_scale=False, neg_ratio=0.0):
266
+ """Accumulate XtX/XtY from cached features."""
267
+ manifest = json.load(open(os.path.join(CACHE_DIR, "manifest.json")))
268
+ n_shards = manifest["n_shards"]
269
+ strides = [16, 32, 64]
270
+ H = RESOLUTION // 16
271
+ sizes = [(H, H), (H // 2, H // 2), (H // 4, H // 4)]
272
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
273
+ locs = make_locations(sizes, strides)
274
+ transform_fn = TRANSFORMS[transform_name]
275
+ encode_fn = ENCODINGS[encoding_name]
276
+ spatial_fn = SPATIAL[spatial_name]
277
+
278
+ # Determine feature dim
279
+ test_f = torch.randn(1, 768)
280
+ test_t = transform_fn(test_f, 768)
281
+ test_s = spatial_fn(test_t.unsqueeze(0), 1, 1, 1, test_t.shape[-1])
282
+ feat_dim = test_s.shape[-1]
283
+
284
+ n_scales = 3 if not per_scale else 1
285
+ scale_range = range(3) if not per_scale else [0]
286
+
287
+ cls_XtX = torch.zeros(feat_dim + 1, feat_dim + 1)
288
+ cls_XtY = torch.zeros(feat_dim + 1, NUM_CLASSES)
289
+ reg_XtX = torch.zeros(feat_dim + 1, feat_dim + 1)
290
+ reg_XtY = torch.zeros(feat_dim + 1, 4)
291
+ ctr_XtX = torch.zeros(feat_dim + 1, feat_dim + 1)
292
+ ctr_XtY = torch.zeros(feat_dim + 1, 1)
293
+ n_pos = 0
294
+ seen = 0
295
+ t0 = time.time()
296
+
297
+ for si in range(n_shards):
298
+ if seen >= n_images:
299
+ break
300
+ shard = torch.load(os.path.join(CACHE_DIR, f"shard_{si:04d}.pt"),
301
+ map_location="cpu", weights_only=False)
302
+ for item in shard:
303
+ if seen >= n_images:
304
+ break
305
+ sp = item["spatial"].unsqueeze(0).float()
306
+ boxes = item["boxes"]
307
+ labels = item["labels"]
308
+ cofibers = cofiber_decompose(sp, 3)
309
+ for sci in range(3):
310
+ cof = cofibers[sci]
311
+ B, C, Hc, Wc = cof.shape
312
+ f_raw = cof.permute(0, 2, 3, 1).reshape(-1, C)
313
+ f = transform_fn(f_raw, C)
314
+ f_spatial = spatial_fn(f.unsqueeze(0) if f.dim() == 2 else f,
315
+ B, Hc, Wc, f.shape[-1] if f.dim() == 2 else C)
316
+ ct, rt, ctrt = assign_targets(locs[sci], boxes, labels, strides[sci], sr[sci])
317
+ pos = ct >= 0
318
+ if not pos.any():
319
+ continue
320
+ fp = f_spatial[pos]
321
+ fa = torch.cat([fp, torch.ones(fp.shape[0], 1)], 1)
322
+ y_cls = torch.zeros(fp.shape[0], NUM_CLASSES)
323
+ y_cls[torch.arange(fp.shape[0]), ct[pos]] = 1.0
324
+ cls_XtX += fa.T @ fa
325
+ cls_XtY += fa.T @ y_cls
326
+ reg_y, valid = encode_fn(rt[pos])
327
+ if valid.any():
328
+ fr = fa[valid]
329
+ reg_XtX += fr.T @ fr
330
+ reg_XtY += fr.T @ reg_y[valid]
331
+ ctr_XtX += fa.T @ fa
332
+ ctr_XtY += fa.T @ ctrt[pos].unsqueeze(1)
333
+ n_pos += pos.sum().item()
334
+
335
+ # Negative samples for classification (target = all zeros)
336
+ if neg_ratio > 0:
337
+ neg = ct < 0
338
+ n_neg_want = int(pos.sum().item() * neg_ratio)
339
+ if neg.any() and n_neg_want > 0:
340
+ neg_idx = neg.nonzero(as_tuple=True)[0]
341
+ if len(neg_idx) > n_neg_want:
342
+ neg_idx = neg_idx[torch.randperm(len(neg_idx))[:n_neg_want]]
343
+ fn = f_spatial[neg_idx]
344
+ fn_aug = torch.cat([fn, torch.ones(fn.shape[0], 1)], 1)
345
+ cls_XtX += fn_aug.T @ fn_aug
346
+ cls_XtY += fn_aug.T @ torch.zeros(fn.shape[0], NUM_CLASSES)
347
+ seen += 1
348
+ del shard
349
+ if (si + 1) % 5 == 0:
350
+ elapsed = time.time() - t0
351
+ print(f" shard {si+1}: {seen} imgs, {n_pos} pos, {elapsed:.0f}s", flush=True)
352
+
353
+ return {"cls_XtX": cls_XtX, "cls_XtY": cls_XtY, "reg_XtX": reg_XtX, "reg_XtY": reg_XtY,
354
+ "ctr_XtX": ctr_XtX, "ctr_XtY": ctr_XtY, "n_pos": n_pos, "feat_dim": feat_dim,
355
+ "n_images": seen, "elapsed": time.time() - t0}
356
+
357
+
358
+ def solve(stats, lam):
359
+ fd = stats["feat_dim"]
360
+ n = stats["n_pos"]
361
+ I = torch.eye(fd + 1)
362
+ cls_W = torch.linalg.solve(stats["cls_XtX"] + lam * I * n, stats["cls_XtY"])
363
+ reg_W = torch.linalg.solve(stats["reg_XtX"] + lam * I * n, stats["reg_XtY"])
364
+ ctr_W = torch.linalg.solve(stats["ctr_XtX"] + lam * I * n, stats["ctr_XtY"])
365
+ return {"cls_w": cls_W[:fd].T, "cls_b": cls_W[fd],
366
+ "reg_w": reg_W[:fd].T, "reg_b": reg_W[fd],
367
+ "ctr_w": ctr_W[:fd].T, "ctr_b": ctr_W[fd],
368
+ "feat_dim": fd}
369
+
370
+
371
+ def evaluate(head, val_path, transform_name, spatial_name, encoding_name="log_ltrb", n_images=500):
372
+ """Evaluate on val set. CPU-only. Returns metrics dict."""
373
+ val = torch.load(val_path, map_location="cpu", weights_only=False)
374
+ encode_fn = ENCODINGS[encoding_name]
375
+
376
+ # Load COCO GT
377
+ from pycocotools.coco import COCO
378
+ ann_file = os.path.join(COCO_ROOT, "annotations", "instances_val2017.json")
379
+ coco = COCO(ann_file)
380
+ cat_ids = sorted(coco.getCatIds())
381
+ cat_to_idx = {c: i for i, c in enumerate(cat_ids)}
382
+
383
+ transform_fn = TRANSFORMS[transform_name]
384
+ spatial_fn = SPATIAL[spatial_name]
385
+ strides = [16, 32, 64]
386
+ H = RESOLUTION // 16
387
+ sizes = [(H, H), (H // 2, H // 2), (H // 4, H // 4)]
388
+ sr = [(-1, 128), (128, 256), (256, float("inf"))]
389
+ locs = make_locations(sizes, strides)
390
+
391
+ correct = 0
392
+ n_pos_total = 0
393
+ n_det = 0
394
+ true_det = 0
395
+ reg_errors = []
396
+
397
+ for idx in range(min(n_images, len(val))):
398
+ item = val[idx]
399
+ spatial = item["spatial"].unsqueeze(0).float()
400
+ img_id = item["img_id"]
401
+ scale = item["scale"]
402
+
403
+ ann_ids = coco.getAnnIds(imgIds=int(img_id), iscrowd=False)
404
+ anns = coco.loadAnns(ann_ids)
405
+ boxes = []
406
+ labels = []
407
+ for ann in anns:
408
+ x, y, w, h = ann["bbox"]
409
+ if w < 1 or h < 1:
410
+ continue
411
+ boxes.append([x * scale, y * scale, (x + w) * scale, (y + h) * scale])
412
+ labels.append(cat_to_idx[ann["category_id"]])
413
+ boxes_t = torch.tensor(boxes, dtype=torch.float32) if boxes else torch.zeros(0, 4)
414
+ labels_t = torch.tensor(labels, dtype=torch.long) if labels else torch.zeros(0, dtype=torch.long)
415
+
416
+ cofibers = cofiber_decompose(spatial, 3)
417
+ for sci, cof in enumerate(cofibers):
418
+ B, C, Hc, Wc = cof.shape
419
+ f_raw = cof.permute(0, 2, 3, 1).reshape(-1, C)
420
+ f = transform_fn(f_raw, C)
421
+ f_s = spatial_fn(f.unsqueeze(0), B, Hc, Wc, f.shape[-1])
422
+
423
+ ct, rt, _ = assign_targets(locs[sci], boxes_t, labels_t, strides[sci], sr[sci])
424
+ pos = ct >= 0
425
+
426
+ # Classification
427
+ scores = f_s @ head["cls_w"].T + head["cls_b"]
428
+ pred_cls = scores.argmax(1)
429
+ pred_conf = scores.sigmoid().max(1).values
430
+
431
+ if pos.any():
432
+ correct += (pred_cls[pos] == ct[pos]).sum().item()
433
+ n_pos_total += pos.sum().item()
434
+
435
+ # Detection count
436
+ det = pred_conf > 0.3
437
+ n_det += det.sum().item()
438
+ true_det += (det & pos).sum().item()
439
+
440
+ # Regression quality (in encoded target space — comparable across encodings)
441
+ if pos.any():
442
+ pred_reg = f_s[pos] @ head["reg_w"].T + head["reg_b"]
443
+ gt_ltrb = rt[pos]
444
+ valid = (gt_ltrb > 0).all(1)
445
+ if valid.any():
446
+ gt_encoded, _ = encode_fn(gt_ltrb[valid])
447
+ pred_encoded = pred_reg[valid]
448
+ mse = ((pred_encoded - gt_encoded) ** 2).mean(1)
449
+ # Convert to quality: 1 / (1 + mse), bounded in [0, 1]
450
+ quality = (1.0 / (1.0 + mse)).tolist()
451
+ reg_errors.extend(quality)
452
+
453
+ cls_acc = correct / max(n_pos_total, 1)
454
+ precision = true_det / max(n_det, 1)
455
+ reg_quality = sum(reg_errors) / max(len(reg_errors), 1) # mean quality in [0, 1]
456
+ n_params = (head["cls_w"].numel() + head["cls_b"].numel() +
457
+ head["reg_w"].numel() + head["reg_b"].numel() +
458
+ head["ctr_w"].numel() + head["ctr_b"].numel())
459
+
460
+ return {
461
+ "cls_accuracy": round(cls_acc, 4),
462
+ "precision": round(precision, 4),
463
+ "reg_quality": round(reg_quality, 4),
464
+ "n_detections": n_det,
465
+ "n_positives": n_pos_total,
466
+ "n_params": n_params,
467
+ "composite": round(cls_acc * 0.5 + precision * 0.25 + reg_quality * 0.25, 4),
468
+ }
469
+
470
+
471
+ def main():
472
+ parser = argparse.ArgumentParser()
473
+ parser.add_argument("--name", required=True, help="Variant name")
474
+ parser.add_argument("--transform", default="layernorm", choices=list(TRANSFORMS.keys()))
475
+ parser.add_argument("--encoding", default="log_ltrb", choices=list(ENCODINGS.keys()))
476
+ parser.add_argument("--spatial", default="none", choices=list(SPATIAL.keys()))
477
+ parser.add_argument("--lam", type=float, default=1e-3)
478
+ parser.add_argument("--n-train", type=int, default=10000)
479
+ parser.add_argument("--n-eval", type=int, default=500)
480
+ parser.add_argument("--neg-ratio", type=float, default=0.0,
481
+ help="Ratio of negative to positive samples for classification (0=positives only)")
482
+ parser.add_argument("--notes", default="", help="Why this variant exists")
483
+ args = parser.parse_args()
484
+
485
+ os.makedirs(RESULTS_DIR, exist_ok=True)
486
+ os.makedirs(STATS_DIR, exist_ok=True)
487
+
488
+ print(f"{'='*60}")
489
+ print(f"Variant: {args.name}")
490
+ print(f" transform={args.transform} encoding={args.encoding} spatial={args.spatial} lam={args.lam}")
491
+ if args.notes:
492
+ print(f" rationale: {args.notes}")
493
+ print(f"{'='*60}", flush=True)
494
+
495
+ # Check for cached stats
496
+ neg_tag = f"_neg{args.neg_ratio}" if args.neg_ratio > 0 else ""
497
+ cache_key = f"stats_s3_{args.transform}_{args.encoding}_{args.spatial}_{args.n_train}{neg_tag}"
498
+ cache_path = os.path.join(STATS_DIR, f"{cache_key}.pt")
499
+ if os.path.isfile(cache_path):
500
+ print(f" Loading cached stats: {cache_key}", flush=True)
501
+ stats = torch.load(cache_path, map_location="cpu", weights_only=False)
502
+ else:
503
+ print(f" Accumulating...", flush=True)
504
+ stats = accumulate(args.n_train, args.transform, args.encoding, args.spatial,
505
+ neg_ratio=args.neg_ratio)
506
+ torch.save(stats, cache_path)
507
+ print(f" Cached: {cache_path}", flush=True)
508
+
509
+ print(f" {stats['n_pos']} positives, feat_dim={stats['feat_dim']}", flush=True)
510
+
511
+ # Solve
512
+ t0 = time.time()
513
+ head = solve(stats, args.lam)
514
+ solve_time = time.time() - t0
515
+ print(f" Solved in {solve_time*1000:.0f}ms", flush=True)
516
+
517
+ # Evaluate
518
+ print(f" Evaluating ({args.n_eval} images)...", flush=True)
519
+ t0 = time.time()
520
+ metrics = evaluate(head, VAL_CACHE, args.transform, args.spatial, args.encoding, args.n_eval)
521
+ eval_time = time.time() - t0
522
+
523
+ print(f"\n Results:")
524
+ print(f" cls_accuracy: {metrics['cls_accuracy']}")
525
+ print(f" precision: {metrics['precision']}")
526
+ print(f" reg_quality: {metrics['reg_quality']}")
527
+ print(f" composite: {metrics['composite']}")
528
+ print(f" params: {metrics['n_params']}")
529
+ print(f" eval time: {eval_time:.1f}s")
530
+
531
+ # Save
532
+ result = {
533
+ "name": args.name,
534
+ "config": {"transform": args.transform, "encoding": args.encoding,
535
+ "spatial": args.spatial, "lam": args.lam,
536
+ "n_train": args.n_train, "n_eval": args.n_eval},
537
+ "notes": args.notes,
538
+ "metrics": metrics,
539
+ "solve_time_ms": round(solve_time * 1000),
540
+ "eval_time_s": round(eval_time, 1),
541
+ }
542
+ result_path = os.path.join(RESULTS_DIR, f"{args.name}.json")
543
+ with open(result_path, "w") as f:
544
+ json.dump(result, f, indent=2)
545
+ print(f"\n Saved: {result_path}")
546
+
547
+
548
+ if __name__ == "__main__":
549
+ main()
analytical/variants/README.md ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Analytical Variants
2
+
3
+ Systematic exploration of analytically-derived detection heads on frozen EUPE-ViT-B features. Every weight is computed from closed-form least-squares — no gradient steps.
4
+
5
+ ## Method
6
+
7
+ Given cached backbone features X at positive spatial locations and target matrix Y:
8
+
9
+ ```
10
+ W = (X^T X + λI)^{-1} X^T Y
11
+ ```
12
+
13
+ Classification, regression, and centerness are solved independently. One pass over the training data accumulates sufficient statistics (X^T X, X^T Y), then one matrix inverse per task produces the optimal linear weights.
14
+
15
+ ## Greedy Feature Selection
16
+
17
+ Starting from zero dimensions, greedily add the single feature channel that maximizes classification accuracy. The ordering reveals which backbone dimensions carry the most detection-relevant information.
18
+
19
+ Top 10 most important dimensions (EUPE-ViT-B): 665, 642, 200, 305, 498, 628, 67, 562, 426, 723.
20
+
21
+ One dimension (dim 665) achieves 31.1% person classification accuracy. 100 dimensions reach 50.6%.
22
+
23
+ Results: `greedy_forward_gpu.json`
24
+
25
+ ## Evolved Circuits
26
+
27
+ Evolutionary search over feature dimension subsets using batched GPU fitness evaluation. Population of 512 individuals, fixed-K genomes, tournament selection, uniform crossover, adaptive mutation. Fitness is F1 score from analytical person-vs-background classification.
28
+
29
+ At 200 gen/s on GPU, 5000 generations complete in 25 seconds.
30
+
31
+ | Dims | Gates | Greedy F1 | Evolved F1 | Speedup |
32
+ |------|-------|-----------|------------|---------|
33
+ | 10 | 850 | 0.628 | 0.761 | +21% |
34
+ | 20 | 1700 | 0.646 | 0.775 | +20% |
35
+ | 50 | 4250 | 0.696 | 0.801 | +15% |
36
+ | 100 | 8500 | 0.728 | 0.823 | +13% |
37
+ | 200 | 17000 | — | 0.832 | — |
38
+ | 300 | 25500 | — | 0.845 | — |
39
+
40
+ The evolved 10-dim circuit (850 gates) outperforms the greedy 100-dim circuit (8500 gates). Evolution finds synergistic dimension combinations that greedy selection is structurally blind to.
41
+
42
+ Linear person detection ceiling: F1 ~ 0.846 at ~300 dims.
43
+
44
+ Results: `evolved_extreme.json` in `circuit/`
45
+
46
+ ## Exotic Features
47
+
48
+ Tested on classification and regression independently:
49
+
50
+ **Classification** (69.6% baseline at 768 dims):
51
+ - Random Fourier Features (RBF kernel approximation): no improvement over raw features
52
+ - Quadratic cross-terms on top-30 greedy dims: 58.4% at 495 dims
53
+ - Random projections K=200: 56.2% at 200 dims
54
+ - Conclusion: raw LayerNorm'd features are already optimal for linear classification
55
+
56
+ **Regression** (0.626 baseline quality):
57
+ - Sheaf H^1 boundary features (vertical + horizontal): 0.687 (+9.7%)
58
+ - H^1 + quadratic combined: 0.698 (+11.5%)
59
+ - Random Fourier Features: 0.654 (+4.5%)
60
+ - Conclusion: directional boundary information (Cech 1-cocycles) is the most useful addition for localization
61
+
62
+ Results: `exotic_gpu.json`, `exotic_reg_gpu.json`
63
+
64
+ ## Spatial Context Variants
65
+
66
+ Tested via the `analytical_one.py` single-variant builder:
67
+
68
+ | Variant | Classification | Regression | Composite |
69
+ |---------|---------------|------------|-----------|
70
+ | Per-token baseline | 64.3% | — | 0.327 |
71
+ | + 3x3 neighbor mean | 72.9% | 2.2% | 0.375 |
72
+ | + high regularization (λ=0.1) | 71.2% | 62.9% | 0.518 |
73
+ | + sheaf H^1 compact | 74.3% | 65.5% | 0.540 |
74
+
75
+ Spatial context is essential: +8.6 points classification, unlocks nonzero regression.
76
+
77
+ Results: `v001_baseline.json` through `v009_sheaf_h1_compact.json`
analytical/variants/exotic_gpu.json ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "baseline_768",
4
+ "acc": 0.6955374502360893,
5
+ "dims": 768
6
+ },
7
+ {
8
+ "name": "random_proj_K10",
9
+ "acc": 0.31700768447365985,
10
+ "dims": 10,
11
+ "params": 880,
12
+ "seed": 139
13
+ },
14
+ {
15
+ "name": "random_proj_K20",
16
+ "acc": 0.3226553096935469,
17
+ "dims": 20,
18
+ "params": 1680,
19
+ "seed": 133
20
+ },
21
+ {
22
+ "name": "random_proj_K50",
23
+ "acc": 0.3812147023423757,
24
+ "dims": 50,
25
+ "params": 4080,
26
+ "seed": 166
27
+ },
28
+ {
29
+ "name": "random_proj_K100",
30
+ "acc": 0.46180909175076384,
31
+ "dims": 100,
32
+ "params": 8080,
33
+ "seed": 405
34
+ },
35
+ {
36
+ "name": "random_proj_K200",
37
+ "acc": 0.5620775854087584,
38
+ "dims": 200,
39
+ "params": 16080,
40
+ "seed": 335
41
+ },
42
+ {
43
+ "name": "quadratic_top5",
44
+ "acc": 0.33223775576335524,
45
+ "dims": 20,
46
+ "params": 1680
47
+ },
48
+ {
49
+ "name": "quadratic_top10",
50
+ "acc": 0.37172484029256553,
51
+ "dims": 65,
52
+ "params": 5280
53
+ },
54
+ {
55
+ "name": "quadratic_top20",
56
+ "acc": 0.478751967410425,
57
+ "dims": 230,
58
+ "params": 18480
59
+ },
60
+ {
61
+ "name": "quadratic_top30",
62
+ "acc": 0.5839274141283215,
63
+ "dims": 495,
64
+ "params": 39680
65
+ },
66
+ {
67
+ "name": "rff_50",
68
+ "acc": 0.6925747615961485,
69
+ "dims": 818,
70
+ "params": 65520
71
+ },
72
+ {
73
+ "name": "rff_100",
74
+ "acc": 0.6932228497361356,
75
+ "dims": 868,
76
+ "params": 69520
77
+ },
78
+ {
79
+ "name": "rff_200",
80
+ "acc": 0.6938246458661235,
81
+ "dims": 968,
82
+ "params": 77520
83
+ },
84
+ {
85
+ "name": "rff_500",
86
+ "acc": 0.6935006017961299,
87
+ "dims": 1268,
88
+ "params": 101520
89
+ },
90
+ {
91
+ "name": "pure_rff_200",
92
+ "acc": 0.30654569021386907,
93
+ "dims": 200,
94
+ "params": 16080
95
+ },
96
+ {
97
+ "name": "pure_rff_500",
98
+ "acc": 0.30654569021386907,
99
+ "dims": 500,
100
+ "params": 40080
101
+ },
102
+ {
103
+ "name": "pure_rff_1000",
104
+ "acc": 0.30654569021386907,
105
+ "dims": 1000,
106
+ "params": 80080
107
+ }
108
+ ]
analytical/variants/exotic_reg_gpu.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "baseline_768",
4
+ "reg_quality": 0.62624591588974,
5
+ "dims": 768
6
+ },
7
+ {
8
+ "name": "h1_h1v",
9
+ "reg_quality": 0.6452575325965881,
10
+ "dims": 1536
11
+ },
12
+ {
13
+ "name": "h1_h1h",
14
+ "reg_quality": 0.644087016582489,
15
+ "dims": 1536
16
+ },
17
+ {
18
+ "name": "h1_h1_both",
19
+ "reg_quality": 0.6874305009841919,
20
+ "dims": 2304
21
+ },
22
+ {
23
+ "name": "quad_top10",
24
+ "reg_quality": 0.6080746650695801,
25
+ "dims": 823
26
+ },
27
+ {
28
+ "name": "quad_top20",
29
+ "reg_quality": 0.6147602796554565,
30
+ "dims": 978
31
+ },
32
+ {
33
+ "name": "quad_top30",
34
+ "reg_quality": 0.6256624460220337,
35
+ "dims": 1233
36
+ },
37
+ {
38
+ "name": "h1_quad_combined",
39
+ "reg_quality": 0.697516143321991,
40
+ "dims": 2514
41
+ },
42
+ {
43
+ "name": "rff_100_reg",
44
+ "reg_quality": 0.6526749134063721,
45
+ "dims": 868
46
+ },
47
+ {
48
+ "name": "rff_500_reg",
49
+ "reg_quality": 0.6537668108940125,
50
+ "dims": 1268
51
+ }
52
+ ]
analytical/variants/fractal_results.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "name": "standard_3band",
4
+ "mAP": 0.006765868258664371,
5
+ "bands": 3
6
+ },
7
+ {
8
+ "name": "fractal_depth2",
9
+ "mAP": 0.006765392609810458,
10
+ "bands": 4
11
+ },
12
+ {
13
+ "name": "fractal_depth3",
14
+ "mAP": 0.007137951141558296,
15
+ "bands": 8
16
+ }
17
+ ]
analytical/variants/greedy_forward_gpu.json ADDED
@@ -0,0 +1,809 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "selected_dims": [
3
+ 665,
4
+ 642,
5
+ 200,
6
+ 305,
7
+ 498,
8
+ 628,
9
+ 67,
10
+ 562,
11
+ 426,
12
+ 723,
13
+ 177,
14
+ 11,
15
+ 465,
16
+ 555,
17
+ 52,
18
+ 179,
19
+ 325,
20
+ 600,
21
+ 738,
22
+ 389,
23
+ 660,
24
+ 109,
25
+ 554,
26
+ 632,
27
+ 673,
28
+ 445,
29
+ 71,
30
+ 403,
31
+ 261,
32
+ 377,
33
+ 81,
34
+ 21,
35
+ 289,
36
+ 23,
37
+ 429,
38
+ 645,
39
+ 741,
40
+ 566,
41
+ 231,
42
+ 357,
43
+ 573,
44
+ 597,
45
+ 263,
46
+ 28,
47
+ 529,
48
+ 372,
49
+ 246,
50
+ 49,
51
+ 219,
52
+ 759,
53
+ 269,
54
+ 593,
55
+ 685,
56
+ 97,
57
+ 485,
58
+ 430,
59
+ 234,
60
+ 297,
61
+ 666,
62
+ 32,
63
+ 198,
64
+ 384,
65
+ 523,
66
+ 292,
67
+ 137,
68
+ 388,
69
+ 364,
70
+ 506,
71
+ 279,
72
+ 101,
73
+ 171,
74
+ 649,
75
+ 259,
76
+ 293,
77
+ 425,
78
+ 576,
79
+ 144,
80
+ 183,
81
+ 359,
82
+ 437,
83
+ 763,
84
+ 540,
85
+ 335,
86
+ 302,
87
+ 404,
88
+ 202,
89
+ 617,
90
+ 497,
91
+ 408,
92
+ 410,
93
+ 340,
94
+ 15,
95
+ 714,
96
+ 693,
97
+ 277,
98
+ 466,
99
+ 102,
100
+ 681,
101
+ 743,
102
+ 765
103
+ ],
104
+ "history": [
105
+ {
106
+ "step": 1,
107
+ "dim": 665,
108
+ "cls_acc": 0.3115,
109
+ "n_params": 160,
110
+ "step_ms": 477
111
+ },
112
+ {
113
+ "step": 2,
114
+ "dim": 642,
115
+ "cls_acc": 0.3132,
116
+ "n_params": 240,
117
+ "step_ms": 339
118
+ },
119
+ {
120
+ "step": 3,
121
+ "dim": 200,
122
+ "cls_acc": 0.3153,
123
+ "n_params": 320,
124
+ "step_ms": 327
125
+ },
126
+ {
127
+ "step": 4,
128
+ "dim": 305,
129
+ "cls_acc": 0.3209,
130
+ "n_params": 400,
131
+ "step_ms": 308
132
+ },
133
+ {
134
+ "step": 5,
135
+ "dim": 498,
136
+ "cls_acc": 0.3245,
137
+ "n_params": 480,
138
+ "step_ms": 344
139
+ },
140
+ {
141
+ "step": 6,
142
+ "dim": 628,
143
+ "cls_acc": 0.3278,
144
+ "n_params": 560,
145
+ "step_ms": 327
146
+ },
147
+ {
148
+ "step": 7,
149
+ "dim": 67,
150
+ "cls_acc": 0.332,
151
+ "n_params": 640,
152
+ "step_ms": 308
153
+ },
154
+ {
155
+ "step": 8,
156
+ "dim": 562,
157
+ "cls_acc": 0.3376,
158
+ "n_params": 720,
159
+ "step_ms": 332
160
+ },
161
+ {
162
+ "step": 9,
163
+ "dim": 426,
164
+ "cls_acc": 0.3417,
165
+ "n_params": 800,
166
+ "step_ms": 318
167
+ },
168
+ {
169
+ "step": 10,
170
+ "dim": 723,
171
+ "cls_acc": 0.3442,
172
+ "n_params": 880,
173
+ "step_ms": 316
174
+ },
175
+ {
176
+ "step": 11,
177
+ "dim": 177,
178
+ "cls_acc": 0.3468,
179
+ "n_params": 960,
180
+ "step_ms": 347
181
+ },
182
+ {
183
+ "step": 12,
184
+ "dim": 11,
185
+ "cls_acc": 0.3495,
186
+ "n_params": 1040,
187
+ "step_ms": 336
188
+ },
189
+ {
190
+ "step": 13,
191
+ "dim": 465,
192
+ "cls_acc": 0.3517,
193
+ "n_params": 1120,
194
+ "step_ms": 319
195
+ },
196
+ {
197
+ "step": 14,
198
+ "dim": 555,
199
+ "cls_acc": 0.3539,
200
+ "n_params": 1200,
201
+ "step_ms": 318
202
+ },
203
+ {
204
+ "step": 15,
205
+ "dim": 52,
206
+ "cls_acc": 0.356,
207
+ "n_params": 1280,
208
+ "step_ms": 321
209
+ },
210
+ {
211
+ "step": 16,
212
+ "dim": 179,
213
+ "cls_acc": 0.3587,
214
+ "n_params": 1360,
215
+ "step_ms": 343
216
+ },
217
+ {
218
+ "step": 17,
219
+ "dim": 325,
220
+ "cls_acc": 0.3608,
221
+ "n_params": 1440,
222
+ "step_ms": 312
223
+ },
224
+ {
225
+ "step": 18,
226
+ "dim": 600,
227
+ "cls_acc": 0.363,
228
+ "n_params": 1520,
229
+ "step_ms": 322
230
+ },
231
+ {
232
+ "step": 19,
233
+ "dim": 738,
234
+ "cls_acc": 0.3649,
235
+ "n_params": 1600,
236
+ "step_ms": 331
237
+ },
238
+ {
239
+ "step": 20,
240
+ "dim": 389,
241
+ "cls_acc": 0.3659,
242
+ "n_params": 1680,
243
+ "step_ms": 313
244
+ },
245
+ {
246
+ "step": 21,
247
+ "dim": 660,
248
+ "cls_acc": 0.368,
249
+ "n_params": 1760,
250
+ "step_ms": 319
251
+ },
252
+ {
253
+ "step": 22,
254
+ "dim": 109,
255
+ "cls_acc": 0.3708,
256
+ "n_params": 1840,
257
+ "step_ms": 326
258
+ },
259
+ {
260
+ "step": 23,
261
+ "dim": 554,
262
+ "cls_acc": 0.3734,
263
+ "n_params": 1920,
264
+ "step_ms": 323
265
+ },
266
+ {
267
+ "step": 24,
268
+ "dim": 632,
269
+ "cls_acc": 0.3752,
270
+ "n_params": 2000,
271
+ "step_ms": 318
272
+ },
273
+ {
274
+ "step": 25,
275
+ "dim": 673,
276
+ "cls_acc": 0.3775,
277
+ "n_params": 2080,
278
+ "step_ms": 351
279
+ },
280
+ {
281
+ "step": 26,
282
+ "dim": 445,
283
+ "cls_acc": 0.3791,
284
+ "n_params": 2160,
285
+ "step_ms": 336
286
+ },
287
+ {
288
+ "step": 27,
289
+ "dim": 71,
290
+ "cls_acc": 0.3812,
291
+ "n_params": 2240,
292
+ "step_ms": 324
293
+ },
294
+ {
295
+ "step": 28,
296
+ "dim": 403,
297
+ "cls_acc": 0.3829,
298
+ "n_params": 2320,
299
+ "step_ms": 326
300
+ },
301
+ {
302
+ "step": 29,
303
+ "dim": 261,
304
+ "cls_acc": 0.3854,
305
+ "n_params": 2400,
306
+ "step_ms": 317
307
+ },
308
+ {
309
+ "step": 30,
310
+ "dim": 377,
311
+ "cls_acc": 0.3872,
312
+ "n_params": 2480,
313
+ "step_ms": 308
314
+ },
315
+ {
316
+ "step": 31,
317
+ "dim": 81,
318
+ "cls_acc": 0.389,
319
+ "n_params": 2560,
320
+ "step_ms": 334
321
+ },
322
+ {
323
+ "step": 32,
324
+ "dim": 21,
325
+ "cls_acc": 0.3909,
326
+ "n_params": 2640,
327
+ "step_ms": 398
328
+ },
329
+ {
330
+ "step": 33,
331
+ "dim": 289,
332
+ "cls_acc": 0.3928,
333
+ "n_params": 2720,
334
+ "step_ms": 404
335
+ },
336
+ {
337
+ "step": 34,
338
+ "dim": 23,
339
+ "cls_acc": 0.394,
340
+ "n_params": 2800,
341
+ "step_ms": 397
342
+ },
343
+ {
344
+ "step": 35,
345
+ "dim": 429,
346
+ "cls_acc": 0.3953,
347
+ "n_params": 2880,
348
+ "step_ms": 359
349
+ },
350
+ {
351
+ "step": 36,
352
+ "dim": 645,
353
+ "cls_acc": 0.3967,
354
+ "n_params": 2960,
355
+ "step_ms": 397
356
+ },
357
+ {
358
+ "step": 37,
359
+ "dim": 741,
360
+ "cls_acc": 0.3999,
361
+ "n_params": 3040,
362
+ "step_ms": 397
363
+ },
364
+ {
365
+ "step": 38,
366
+ "dim": 566,
367
+ "cls_acc": 0.403,
368
+ "n_params": 3120,
369
+ "step_ms": 381
370
+ },
371
+ {
372
+ "step": 39,
373
+ "dim": 231,
374
+ "cls_acc": 0.4063,
375
+ "n_params": 3200,
376
+ "step_ms": 397
377
+ },
378
+ {
379
+ "step": 40,
380
+ "dim": 357,
381
+ "cls_acc": 0.4088,
382
+ "n_params": 3280,
383
+ "step_ms": 361
384
+ },
385
+ {
386
+ "step": 41,
387
+ "dim": 573,
388
+ "cls_acc": 0.4117,
389
+ "n_params": 3360,
390
+ "step_ms": 388
391
+ },
392
+ {
393
+ "step": 42,
394
+ "dim": 597,
395
+ "cls_acc": 0.4137,
396
+ "n_params": 3440,
397
+ "step_ms": 383
398
+ },
399
+ {
400
+ "step": 43,
401
+ "dim": 263,
402
+ "cls_acc": 0.4159,
403
+ "n_params": 3520,
404
+ "step_ms": 365
405
+ },
406
+ {
407
+ "step": 44,
408
+ "dim": 28,
409
+ "cls_acc": 0.418,
410
+ "n_params": 3600,
411
+ "step_ms": 364
412
+ },
413
+ {
414
+ "step": 45,
415
+ "dim": 529,
416
+ "cls_acc": 0.4203,
417
+ "n_params": 3680,
418
+ "step_ms": 354
419
+ },
420
+ {
421
+ "step": 46,
422
+ "dim": 372,
423
+ "cls_acc": 0.4227,
424
+ "n_params": 3760,
425
+ "step_ms": 368
426
+ },
427
+ {
428
+ "step": 47,
429
+ "dim": 246,
430
+ "cls_acc": 0.4251,
431
+ "n_params": 3840,
432
+ "step_ms": 357
433
+ },
434
+ {
435
+ "step": 48,
436
+ "dim": 49,
437
+ "cls_acc": 0.4278,
438
+ "n_params": 3920,
439
+ "step_ms": 3306
440
+ },
441
+ {
442
+ "step": 49,
443
+ "dim": 219,
444
+ "cls_acc": 0.429,
445
+ "n_params": 4000,
446
+ "step_ms": 388
447
+ },
448
+ {
449
+ "step": 50,
450
+ "dim": 759,
451
+ "cls_acc": 0.4308,
452
+ "n_params": 4080,
453
+ "step_ms": 380
454
+ },
455
+ {
456
+ "step": 51,
457
+ "dim": 269,
458
+ "cls_acc": 0.4329,
459
+ "n_params": 4160,
460
+ "step_ms": 386
461
+ },
462
+ {
463
+ "step": 52,
464
+ "dim": 593,
465
+ "cls_acc": 0.4342,
466
+ "n_params": 4240,
467
+ "step_ms": 367
468
+ },
469
+ {
470
+ "step": 53,
471
+ "dim": 685,
472
+ "cls_acc": 0.4357,
473
+ "n_params": 4320,
474
+ "step_ms": 380
475
+ },
476
+ {
477
+ "step": 54,
478
+ "dim": 97,
479
+ "cls_acc": 0.4373,
480
+ "n_params": 4400,
481
+ "step_ms": 377
482
+ },
483
+ {
484
+ "step": 55,
485
+ "dim": 485,
486
+ "cls_acc": 0.4397,
487
+ "n_params": 4480,
488
+ "step_ms": 373
489
+ },
490
+ {
491
+ "step": 56,
492
+ "dim": 430,
493
+ "cls_acc": 0.441,
494
+ "n_params": 4560,
495
+ "step_ms": 365
496
+ },
497
+ {
498
+ "step": 57,
499
+ "dim": 234,
500
+ "cls_acc": 0.4431,
501
+ "n_params": 4640,
502
+ "step_ms": 346
503
+ },
504
+ {
505
+ "step": 58,
506
+ "dim": 297,
507
+ "cls_acc": 0.4452,
508
+ "n_params": 4720,
509
+ "step_ms": 374
510
+ },
511
+ {
512
+ "step": 59,
513
+ "dim": 666,
514
+ "cls_acc": 0.4468,
515
+ "n_params": 4800,
516
+ "step_ms": 387
517
+ },
518
+ {
519
+ "step": 60,
520
+ "dim": 32,
521
+ "cls_acc": 0.4484,
522
+ "n_params": 4880,
523
+ "step_ms": 393
524
+ },
525
+ {
526
+ "step": 61,
527
+ "dim": 198,
528
+ "cls_acc": 0.4503,
529
+ "n_params": 4960,
530
+ "step_ms": 429
531
+ },
532
+ {
533
+ "step": 62,
534
+ "dim": 384,
535
+ "cls_acc": 0.4519,
536
+ "n_params": 5040,
537
+ "step_ms": 446
538
+ },
539
+ {
540
+ "step": 63,
541
+ "dim": 523,
542
+ "cls_acc": 0.4537,
543
+ "n_params": 5120,
544
+ "step_ms": 418
545
+ },
546
+ {
547
+ "step": 64,
548
+ "dim": 292,
549
+ "cls_acc": 0.455,
550
+ "n_params": 5200,
551
+ "step_ms": 476
552
+ },
553
+ {
554
+ "step": 65,
555
+ "dim": 137,
556
+ "cls_acc": 0.4564,
557
+ "n_params": 5280,
558
+ "step_ms": 432
559
+ },
560
+ {
561
+ "step": 66,
562
+ "dim": 388,
563
+ "cls_acc": 0.458,
564
+ "n_params": 5360,
565
+ "step_ms": 432
566
+ },
567
+ {
568
+ "step": 67,
569
+ "dim": 364,
570
+ "cls_acc": 0.4596,
571
+ "n_params": 5440,
572
+ "step_ms": 445
573
+ },
574
+ {
575
+ "step": 68,
576
+ "dim": 506,
577
+ "cls_acc": 0.461,
578
+ "n_params": 5520,
579
+ "step_ms": 419
580
+ },
581
+ {
582
+ "step": 69,
583
+ "dim": 279,
584
+ "cls_acc": 0.4624,
585
+ "n_params": 5600,
586
+ "step_ms": 397
587
+ },
588
+ {
589
+ "step": 70,
590
+ "dim": 101,
591
+ "cls_acc": 0.464,
592
+ "n_params": 5680,
593
+ "step_ms": 385
594
+ },
595
+ {
596
+ "step": 71,
597
+ "dim": 171,
598
+ "cls_acc": 0.4658,
599
+ "n_params": 5760,
600
+ "step_ms": 395
601
+ },
602
+ {
603
+ "step": 72,
604
+ "dim": 649,
605
+ "cls_acc": 0.4679,
606
+ "n_params": 5840,
607
+ "step_ms": 408
608
+ },
609
+ {
610
+ "step": 73,
611
+ "dim": 259,
612
+ "cls_acc": 0.4696,
613
+ "n_params": 5920,
614
+ "step_ms": 373
615
+ },
616
+ {
617
+ "step": 74,
618
+ "dim": 293,
619
+ "cls_acc": 0.4719,
620
+ "n_params": 6000,
621
+ "step_ms": 403
622
+ },
623
+ {
624
+ "step": 75,
625
+ "dim": 425,
626
+ "cls_acc": 0.4736,
627
+ "n_params": 6080,
628
+ "step_ms": 405
629
+ },
630
+ {
631
+ "step": 76,
632
+ "dim": 576,
633
+ "cls_acc": 0.4746,
634
+ "n_params": 6160,
635
+ "step_ms": 367
636
+ },
637
+ {
638
+ "step": 77,
639
+ "dim": 144,
640
+ "cls_acc": 0.4758,
641
+ "n_params": 6240,
642
+ "step_ms": 382
643
+ },
644
+ {
645
+ "step": 78,
646
+ "dim": 183,
647
+ "cls_acc": 0.4771,
648
+ "n_params": 6320,
649
+ "step_ms": 372
650
+ },
651
+ {
652
+ "step": 79,
653
+ "dim": 359,
654
+ "cls_acc": 0.4788,
655
+ "n_params": 6400,
656
+ "step_ms": 382
657
+ },
658
+ {
659
+ "step": 80,
660
+ "dim": 437,
661
+ "cls_acc": 0.4804,
662
+ "n_params": 6480,
663
+ "step_ms": 383
664
+ },
665
+ {
666
+ "step": 81,
667
+ "dim": 763,
668
+ "cls_acc": 0.4816,
669
+ "n_params": 6560,
670
+ "step_ms": 363
671
+ },
672
+ {
673
+ "step": 82,
674
+ "dim": 540,
675
+ "cls_acc": 0.4832,
676
+ "n_params": 6640,
677
+ "step_ms": 384
678
+ },
679
+ {
680
+ "step": 83,
681
+ "dim": 335,
682
+ "cls_acc": 0.4846,
683
+ "n_params": 6720,
684
+ "step_ms": 382
685
+ },
686
+ {
687
+ "step": 84,
688
+ "dim": 302,
689
+ "cls_acc": 0.486,
690
+ "n_params": 6800,
691
+ "step_ms": 370
692
+ },
693
+ {
694
+ "step": 85,
695
+ "dim": 404,
696
+ "cls_acc": 0.4875,
697
+ "n_params": 6880,
698
+ "step_ms": 367
699
+ },
700
+ {
701
+ "step": 86,
702
+ "dim": 202,
703
+ "cls_acc": 0.4883,
704
+ "n_params": 6960,
705
+ "step_ms": 375
706
+ },
707
+ {
708
+ "step": 87,
709
+ "dim": 617,
710
+ "cls_acc": 0.4896,
711
+ "n_params": 7040,
712
+ "step_ms": 352
713
+ },
714
+ {
715
+ "step": 88,
716
+ "dim": 497,
717
+ "cls_acc": 0.4908,
718
+ "n_params": 7120,
719
+ "step_ms": 357
720
+ },
721
+ {
722
+ "step": 89,
723
+ "dim": 408,
724
+ "cls_acc": 0.492,
725
+ "n_params": 7200,
726
+ "step_ms": 388
727
+ },
728
+ {
729
+ "step": 90,
730
+ "dim": 410,
731
+ "cls_acc": 0.4934,
732
+ "n_params": 7280,
733
+ "step_ms": 378
734
+ },
735
+ {
736
+ "step": 91,
737
+ "dim": 340,
738
+ "cls_acc": 0.4944,
739
+ "n_params": 7360,
740
+ "step_ms": 378
741
+ },
742
+ {
743
+ "step": 92,
744
+ "dim": 15,
745
+ "cls_acc": 0.4956,
746
+ "n_params": 7440,
747
+ "step_ms": 383
748
+ },
749
+ {
750
+ "step": 93,
751
+ "dim": 714,
752
+ "cls_acc": 0.4971,
753
+ "n_params": 7520,
754
+ "step_ms": 378
755
+ },
756
+ {
757
+ "step": 94,
758
+ "dim": 693,
759
+ "cls_acc": 0.4987,
760
+ "n_params": 7600,
761
+ "step_ms": 378
762
+ },
763
+ {
764
+ "step": 95,
765
+ "dim": 277,
766
+ "cls_acc": 0.5001,
767
+ "n_params": 7680,
768
+ "step_ms": 346
769
+ },
770
+ {
771
+ "step": 96,
772
+ "dim": 466,
773
+ "cls_acc": 0.5012,
774
+ "n_params": 7760,
775
+ "step_ms": 384
776
+ },
777
+ {
778
+ "step": 97,
779
+ "dim": 102,
780
+ "cls_acc": 0.5025,
781
+ "n_params": 7840,
782
+ "step_ms": 396
783
+ },
784
+ {
785
+ "step": 98,
786
+ "dim": 681,
787
+ "cls_acc": 0.504,
788
+ "n_params": 7920,
789
+ "step_ms": 383
790
+ },
791
+ {
792
+ "step": 99,
793
+ "dim": 743,
794
+ "cls_acc": 0.5048,
795
+ "n_params": 8000,
796
+ "step_ms": 404
797
+ },
798
+ {
799
+ "step": 100,
800
+ "dim": 765,
801
+ "cls_acc": 0.5057,
802
+ "n_params": 8080,
803
+ "step_ms": 374
804
+ }
805
+ ],
806
+ "final_cls_acc": 0.5057,
807
+ "final_params": 8080,
808
+ "total_time_s": 39.8
809
+ }
analytical/variants/v001_baseline.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v001_baseline",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "none",
7
+ "lam": 0.001,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "Control.",
12
+ "metrics": {
13
+ "cls_accuracy": 0.6427,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 65365,
19
+ "composite": 0.3265
20
+ },
21
+ "solve_time_ms": 12,
22
+ "eval_time_s": 10.4
23
+ }
analytical/variants/v002_spatial_cat_mean3x3.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v002_spatial_cat_mean3x3",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "cat_mean3x3",
7
+ "lam": 0.001,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "Concatenate",
12
+ "metrics": {
13
+ "cls_accuracy": 0.7286,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.0221,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.375
20
+ },
21
+ "solve_time_ms": 47,
22
+ "eval_time_s": 18.8
23
+ }
analytical/variants/v003_spatial_diff_neighbors.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v003_spatial_diff_neighbors",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "diff_neighbors",
7
+ "lam": 0.001,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "center",
12
+ "metrics": {
13
+ "cls_accuracy": 0.728,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.0217,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.3746
20
+ },
21
+ "solve_time_ms": 41,
22
+ "eval_time_s": 12.0
23
+ }
analytical/variants/v004_spatial_highreg.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v004_spatial_highreg",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "cat_mean3x3",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "Same",
12
+ "metrics": {
13
+ "cls_accuracy": 0.7117,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.1327,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.3942
20
+ },
21
+ "solve_time_ms": 83,
22
+ "eval_time_s": 13.7
23
+ }
analytical/variants/v004_spatial_highreg_v2.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v004_spatial_highreg_v2",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "cat_mean3x3",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "rerun",
12
+ "metrics": {
13
+ "cls_accuracy": 0.7117,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.6294,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.5183
20
+ },
21
+ "solve_time_ms": 67,
22
+ "eval_time_s": 10.4
23
+ }
analytical/variants/v005_power05_spatial_highreg.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v005_power05_spatial_highreg",
3
+ "config": {
4
+ "transform": "power05",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "cat_mean3x3",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "power",
12
+ "metrics": {
13
+ "cls_accuracy": 0.3066,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.1584
20
+ },
21
+ "solve_time_ms": 43,
22
+ "eval_time_s": 17.2
23
+ }
analytical/variants/v006_hv_neighbors_highreg.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v006_hv_neighbors_highreg",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "hv_neighbors",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "center",
12
+ "metrics": {
13
+ "cls_accuracy": 0.7023,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.1395,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 195925,
19
+ "composite": 0.3912
20
+ },
21
+ "solve_time_ms": 248,
22
+ "eval_time_s": 13.9
23
+ }
analytical/variants/v007_spatial_sqrt_highreg.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v007_spatial_sqrt_highreg",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "sqrt_ltrb",
6
+ "spatial": "cat_mean3x3",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "sqrt",
12
+ "metrics": {
13
+ "cls_accuracy": 0.7117,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.2461,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.4225
20
+ },
21
+ "solve_time_ms": 67,
22
+ "eval_time_s": 11.5
23
+ }
analytical/variants/v008_spatial_neg3_highreg.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v008_spatial_neg3_highreg",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "cat_mean3x3",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "3:1",
12
+ "metrics": {
13
+ "cls_accuracy": 0.6985,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.6294,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 130645,
19
+ "composite": 0.5118
20
+ },
21
+ "solve_time_ms": 33,
22
+ "eval_time_s": 11.2
23
+ }
analytical/variants/v009_sheaf_h1_compact.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v009_sheaf_h1_compact",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "sheaf_h1_compact",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "Cech",
12
+ "metrics": {
13
+ "cls_accuracy": 0.7426,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.6546,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 195925,
19
+ "composite": 0.5401
20
+ },
21
+ "solve_time_ms": 68,
22
+ "eval_time_s": 17.9
23
+ }
analytical/variants/v010_sheaf_h1_full.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "v010_sheaf_h1_full",
3
+ "config": {
4
+ "transform": "layernorm",
5
+ "encoding": "log_ltrb",
6
+ "spatial": "sheaf_h1",
7
+ "lam": 0.1,
8
+ "n_train": 10000,
9
+ "n_eval": 500
10
+ },
11
+ "notes": "full",
12
+ "metrics": {
13
+ "cls_accuracy": 0.6966,
14
+ "precision": 0.0206,
15
+ "reg_quality": 0.6374,
16
+ "n_detections": 1050000,
17
+ "n_positives": 21602,
18
+ "n_params": 326485,
19
+ "composite": 0.5128
20
+ },
21
+ "solve_time_ms": 370,
22
+ "eval_time_s": 23.2
23
+ }
circuit/README.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Circuit-Level Detection
2
+
3
+ The cofiber threshold detection head expressed as synthesizable digital logic. All weights are analytically derived (zero gradient-based training) and stored as INT8 ROM.
4
+
5
+ ## Person Detector (Proof of Concept)
6
+
7
+ Single-class (person) detector as a combinational circuit.
8
+
9
+ - **Parameters**: 4,614 (4.6 KB INT8 ROM)
10
+ - **Architecture**: 6 parallel dot products (1 cls + 4 reg + 1 ctr), each 768-dim
11
+ - **Weights**: closed-form least-squares on 20K COCO training images
12
+ - **Construction time**: 122 seconds, zero training steps
13
+
14
+ ### Gate Count (Yosys synthesis)
15
+
16
+ Synthesized with Yosys (generic target, no FPGA mapping):
17
+
18
+ | Component | Dims | Gates (16-dim measured) | Gates (768-dim extrapolated) |
19
+ |-----------|------|------------------------|------------------------------|
20
+ | 1 MAC unit | 768→1 | 1,358 | ~65K |
21
+ | Person detector (6 MACs) | 768→{1,4,1} | — | ~391K |
22
+ | Full 80-class detector (85 MACs) | 768→{80,4,1} | — | ~7.1M |
23
+
24
+ ### Files
25
+
26
+ - `person_detector.sv` — SystemVerilog module, Icarus Verilog compatible
27
+ - `tb_person.sv` — testbench
28
+ - `cofiber_detector.sv` — full 80-class detector (SystemVerilog, parameterized)
29
+ - `person_small_synth.v` — 16-dim reduced version for synthesis analysis
30
+ - `rom/person_cls_w.hex` — classification weights (768 INT8)
31
+ - `rom/person_reg_w.hex` — regression weights (4×768 INT8)
32
+ - `rom/person_ctr_w.hex` — centerness weights (768 INT8)
33
+ - `person_analytical.pth` — PyTorch checkpoint of the analytical solution
34
+
35
+ ### Simulation
36
+
37
+ ```bash
38
+ iverilog -g2012 -o tb_person.vvp person_detector.sv tb_person.sv
39
+ vvp tb_person.vvp
40
+ ```
41
+
42
+ ### Synthesis
43
+
44
+ ```bash
45
+ yosys -p "read_verilog person_small_synth.v; synth -top person_detector_small; stat"
46
+ ```
47
+
48
+ ## Cofiber Decomposition
49
+
50
+ The spatial decomposition (avg_pool → subtract → repeat) is a fixed filter bank with zero learned parameters. In hardware, it is:
51
+ - Average pooling: 2×2 adder tree + arithmetic right shift
52
+ - Cofiber extraction: subtractor (input − upsampled pool)
53
+ - Fully combinational, no state
54
+
55
+ The decomposition module (`cofiber_detector.sv`) implements all three scales for a 40×40 input grid.
circuit/circuit_variants.json ADDED
@@ -0,0 +1,489 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "n_dims": 1,
4
+ "gates": 85,
5
+ "tp": 589,
6
+ "fp": 490,
7
+ "fn": 411,
8
+ "tn": 510,
9
+ "precision": 0.5459,
10
+ "recall": 0.589,
11
+ "f1": 0.5666,
12
+ "time_s": 1.3,
13
+ "dims": [
14
+ 665
15
+ ]
16
+ },
17
+ {
18
+ "n_dims": 2,
19
+ "gates": 170,
20
+ "tp": 620,
21
+ "fp": 495,
22
+ "fn": 380,
23
+ "tn": 505,
24
+ "precision": 0.5561,
25
+ "recall": 0.62,
26
+ "f1": 0.5863,
27
+ "time_s": 0.1,
28
+ "dims": [
29
+ 665,
30
+ 642
31
+ ]
32
+ },
33
+ {
34
+ "n_dims": 3,
35
+ "gates": 255,
36
+ "tp": 667,
37
+ "fp": 516,
38
+ "fn": 333,
39
+ "tn": 484,
40
+ "precision": 0.5638,
41
+ "recall": 0.667,
42
+ "f1": 0.6111,
43
+ "time_s": 0.1,
44
+ "dims": [
45
+ 665,
46
+ 642,
47
+ 200
48
+ ]
49
+ },
50
+ {
51
+ "n_dims": 5,
52
+ "gates": 425,
53
+ "tp": 665,
54
+ "fp": 513,
55
+ "fn": 335,
56
+ "tn": 487,
57
+ "precision": 0.5645,
58
+ "recall": 0.665,
59
+ "f1": 0.6107,
60
+ "time_s": 0.1,
61
+ "dims": [
62
+ 665,
63
+ 642,
64
+ 200,
65
+ 305,
66
+ 498
67
+ ]
68
+ },
69
+ {
70
+ "n_dims": 8,
71
+ "gates": 680,
72
+ "tp": 691,
73
+ "fp": 512,
74
+ "fn": 309,
75
+ "tn": 488,
76
+ "precision": 0.5744,
77
+ "recall": 0.691,
78
+ "f1": 0.6273,
79
+ "time_s": 0.1,
80
+ "dims": [
81
+ 665,
82
+ 642,
83
+ 200,
84
+ 305,
85
+ 498,
86
+ 628,
87
+ 67,
88
+ 562
89
+ ]
90
+ },
91
+ {
92
+ "n_dims": 10,
93
+ "gates": 850,
94
+ "tp": 693,
95
+ "fp": 513,
96
+ "fn": 307,
97
+ "tn": 487,
98
+ "precision": 0.5746,
99
+ "recall": 0.693,
100
+ "f1": 0.6283,
101
+ "time_s": 0.1,
102
+ "dims": [
103
+ 665,
104
+ 642,
105
+ 200,
106
+ 305,
107
+ 498,
108
+ 628,
109
+ 67,
110
+ 562,
111
+ 426,
112
+ 723
113
+ ]
114
+ },
115
+ {
116
+ "n_dims": 15,
117
+ "gates": 1275,
118
+ "tp": 709,
119
+ "fp": 503,
120
+ "fn": 291,
121
+ "tn": 497,
122
+ "precision": 0.585,
123
+ "recall": 0.709,
124
+ "f1": 0.641,
125
+ "time_s": 0.1,
126
+ "dims": [
127
+ 665,
128
+ 642,
129
+ 200,
130
+ 305,
131
+ 498,
132
+ 628,
133
+ 67,
134
+ 562,
135
+ 426,
136
+ 723,
137
+ 177,
138
+ 11,
139
+ 465,
140
+ 555,
141
+ 52
142
+ ]
143
+ },
144
+ {
145
+ "n_dims": 20,
146
+ "gates": 1700,
147
+ "tp": 715,
148
+ "fp": 500,
149
+ "fn": 285,
150
+ "tn": 500,
151
+ "precision": 0.5885,
152
+ "recall": 0.715,
153
+ "f1": 0.6456,
154
+ "time_s": 0.2,
155
+ "dims": [
156
+ 665,
157
+ 642,
158
+ 200,
159
+ 305,
160
+ 498,
161
+ 628,
162
+ 67,
163
+ 562,
164
+ 426,
165
+ 723,
166
+ 177,
167
+ 11,
168
+ 465,
169
+ 555,
170
+ 52,
171
+ 179,
172
+ 325,
173
+ 600,
174
+ 738,
175
+ 389
176
+ ]
177
+ },
178
+ {
179
+ "n_dims": 30,
180
+ "gates": 2550,
181
+ "tp": 737,
182
+ "fp": 521,
183
+ "fn": 263,
184
+ "tn": 479,
185
+ "precision": 0.5859,
186
+ "recall": 0.737,
187
+ "f1": 0.6528,
188
+ "time_s": 0.2,
189
+ "dims": [
190
+ 665,
191
+ 642,
192
+ 200,
193
+ 305,
194
+ 498,
195
+ 628,
196
+ 67,
197
+ 562,
198
+ 426,
199
+ 723,
200
+ 177,
201
+ 11,
202
+ 465,
203
+ 555,
204
+ 52,
205
+ 179,
206
+ 325,
207
+ 600,
208
+ 738,
209
+ 389,
210
+ 660,
211
+ 109,
212
+ 554,
213
+ 632,
214
+ 673,
215
+ 445,
216
+ 71,
217
+ 403,
218
+ 261,
219
+ 377
220
+ ]
221
+ },
222
+ {
223
+ "n_dims": 50,
224
+ "gates": 4250,
225
+ "tp": 807,
226
+ "fp": 512,
227
+ "fn": 193,
228
+ "tn": 488,
229
+ "precision": 0.6118,
230
+ "recall": 0.807,
231
+ "f1": 0.696,
232
+ "time_s": 0.3,
233
+ "dims": [
234
+ 665,
235
+ 642,
236
+ 200,
237
+ 305,
238
+ 498,
239
+ 628,
240
+ 67,
241
+ 562,
242
+ 426,
243
+ 723,
244
+ 177,
245
+ 11,
246
+ 465,
247
+ 555,
248
+ 52,
249
+ 179,
250
+ 325,
251
+ 600,
252
+ 738,
253
+ 389,
254
+ 660,
255
+ 109,
256
+ 554,
257
+ 632,
258
+ 673,
259
+ 445,
260
+ 71,
261
+ 403,
262
+ 261,
263
+ 377,
264
+ 81,
265
+ 21,
266
+ 289,
267
+ 23,
268
+ 429,
269
+ 645,
270
+ 741,
271
+ 566,
272
+ 231,
273
+ 357,
274
+ 573,
275
+ 597,
276
+ 263,
277
+ 28,
278
+ 529,
279
+ 372,
280
+ 246,
281
+ 49,
282
+ 219,
283
+ 759
284
+ ]
285
+ },
286
+ {
287
+ "n_dims": 75,
288
+ "gates": 6375,
289
+ "tp": 818,
290
+ "fp": 483,
291
+ "fn": 182,
292
+ "tn": 517,
293
+ "precision": 0.6287,
294
+ "recall": 0.818,
295
+ "f1": 0.711,
296
+ "time_s": 0.4,
297
+ "dims": [
298
+ 665,
299
+ 642,
300
+ 200,
301
+ 305,
302
+ 498,
303
+ 628,
304
+ 67,
305
+ 562,
306
+ 426,
307
+ 723,
308
+ 177,
309
+ 11,
310
+ 465,
311
+ 555,
312
+ 52,
313
+ 179,
314
+ 325,
315
+ 600,
316
+ 738,
317
+ 389,
318
+ 660,
319
+ 109,
320
+ 554,
321
+ 632,
322
+ 673,
323
+ 445,
324
+ 71,
325
+ 403,
326
+ 261,
327
+ 377,
328
+ 81,
329
+ 21,
330
+ 289,
331
+ 23,
332
+ 429,
333
+ 645,
334
+ 741,
335
+ 566,
336
+ 231,
337
+ 357,
338
+ 573,
339
+ 597,
340
+ 263,
341
+ 28,
342
+ 529,
343
+ 372,
344
+ 246,
345
+ 49,
346
+ 219,
347
+ 759,
348
+ 269,
349
+ 593,
350
+ 685,
351
+ 97,
352
+ 485,
353
+ 430,
354
+ 234,
355
+ 297,
356
+ 666,
357
+ 32,
358
+ 198,
359
+ 384,
360
+ 523,
361
+ 292,
362
+ 137,
363
+ 388,
364
+ 364,
365
+ 506,
366
+ 279,
367
+ 101,
368
+ 171,
369
+ 649,
370
+ 259,
371
+ 293,
372
+ 425
373
+ ]
374
+ },
375
+ {
376
+ "n_dims": 100,
377
+ "gates": 8500,
378
+ "tp": 831,
379
+ "fp": 451,
380
+ "fn": 169,
381
+ "tn": 549,
382
+ "precision": 0.6482,
383
+ "recall": 0.831,
384
+ "f1": 0.7283,
385
+ "time_s": 0.5,
386
+ "dims": [
387
+ 665,
388
+ 642,
389
+ 200,
390
+ 305,
391
+ 498,
392
+ 628,
393
+ 67,
394
+ 562,
395
+ 426,
396
+ 723,
397
+ 177,
398
+ 11,
399
+ 465,
400
+ 555,
401
+ 52,
402
+ 179,
403
+ 325,
404
+ 600,
405
+ 738,
406
+ 389,
407
+ 660,
408
+ 109,
409
+ 554,
410
+ 632,
411
+ 673,
412
+ 445,
413
+ 71,
414
+ 403,
415
+ 261,
416
+ 377,
417
+ 81,
418
+ 21,
419
+ 289,
420
+ 23,
421
+ 429,
422
+ 645,
423
+ 741,
424
+ 566,
425
+ 231,
426
+ 357,
427
+ 573,
428
+ 597,
429
+ 263,
430
+ 28,
431
+ 529,
432
+ 372,
433
+ 246,
434
+ 49,
435
+ 219,
436
+ 759,
437
+ 269,
438
+ 593,
439
+ 685,
440
+ 97,
441
+ 485,
442
+ 430,
443
+ 234,
444
+ 297,
445
+ 666,
446
+ 32,
447
+ 198,
448
+ 384,
449
+ 523,
450
+ 292,
451
+ 137,
452
+ 388,
453
+ 364,
454
+ 506,
455
+ 279,
456
+ 101,
457
+ 171,
458
+ 649,
459
+ 259,
460
+ 293,
461
+ 425,
462
+ 576,
463
+ 144,
464
+ 183,
465
+ 359,
466
+ 437,
467
+ 763,
468
+ 540,
469
+ 335,
470
+ 302,
471
+ 404,
472
+ 202,
473
+ 617,
474
+ 497,
475
+ 408,
476
+ 410,
477
+ 340,
478
+ 15,
479
+ 714,
480
+ 693,
481
+ 277,
482
+ 466,
483
+ 102,
484
+ 681,
485
+ 743,
486
+ 765
487
+ ]
488
+ }
489
+ ]
circuit/cofiber_detector.sv ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Cofiber detection head — fully combinational per-token detector
2
+ //
3
+ // Input: 768 signed 8-bit feature values (one spatial token)
4
+ // Output: 80 classification scores + 4 box regression values + 1 centerness
5
+ //
6
+ // All weights are ROM (analytically derived, zero training).
7
+ // The cofiber decomposition and pooling happen upstream in the spatial
8
+ // processing module — this module handles per-token prediction only.
9
+ //
10
+ // Parameters:
11
+ // FEAT_DIM - feature dimension (768 for EUPE-ViT-B)
12
+ // NUM_CLASSES - detection classes (80 for COCO)
13
+ // BIT_WIDTH - weight/activation precision
14
+
15
+ module cofiber_detector #(
16
+ parameter FEAT_DIM = 768,
17
+ parameter NUM_CLASSES = 80,
18
+ parameter BIT_WIDTH = 8,
19
+ parameter ACC_WIDTH = 24 // accumulator width for MAC results
20
+ )(
21
+ input logic signed [BIT_WIDTH-1:0] features [0:FEAT_DIM-1],
22
+ output logic signed [ACC_WIDTH-1:0] cls_scores [0:NUM_CLASSES-1],
23
+ output logic signed [ACC_WIDTH-1:0] box_ltrb [0:3],
24
+ output logic signed [ACC_WIDTH-1:0] centerness
25
+ );
26
+
27
+ // Weight ROMs — loaded from analytical solution
28
+ logic signed [BIT_WIDTH-1:0] cls_weight [0:NUM_CLASSES-1][0:FEAT_DIM-1];
29
+ logic signed [BIT_WIDTH-1:0] cls_bias [0:NUM_CLASSES-1];
30
+ logic signed [BIT_WIDTH-1:0] reg_weight [0:3][0:FEAT_DIM-1];
31
+ logic signed [BIT_WIDTH-1:0] reg_bias [0:3];
32
+ logic signed [BIT_WIDTH-1:0] ctr_weight [0:FEAT_DIM-1];
33
+ logic signed [BIT_WIDTH-1:0] ctr_bias;
34
+
35
+ initial begin
36
+ $readmemh("rom/cls_weight.hex", cls_weight);
37
+ $readmemh("rom/cls_bias.hex", cls_bias);
38
+ $readmemh("rom/reg_out_weight.hex", reg_weight);
39
+ $readmemh("rom/reg_out_bias.hex", reg_bias);
40
+ $readmemh("rom/ctr_weight.hex", ctr_weight);
41
+ $readmemh("rom/ctr_bias.hex", ctr_bias);
42
+ end
43
+
44
+ // Classification: 80 parallel dot products
45
+ // cls_scores[c] = sum(features[d] * cls_weight[c][d]) + cls_bias[c]
46
+ genvar c, d;
47
+ generate
48
+ for (c = 0; c < NUM_CLASSES; c = c + 1) begin : cls_mac
49
+ logic signed [ACC_WIDTH-1:0] acc;
50
+ always_comb begin
51
+ acc = {{(ACC_WIDTH-BIT_WIDTH){cls_bias[c][BIT_WIDTH-1]}}, cls_bias[c]};
52
+ for (int dd = 0; dd < FEAT_DIM; dd = dd + 1) begin
53
+ acc = acc + (features[dd] * cls_weight[c][dd]);
54
+ end
55
+ cls_scores[c] = acc;
56
+ end
57
+ end
58
+ endgenerate
59
+
60
+ // Box regression: 4 parallel dot products
61
+ generate
62
+ for (c = 0; c < 4; c = c + 1) begin : reg_mac
63
+ logic signed [ACC_WIDTH-1:0] acc;
64
+ always_comb begin
65
+ acc = {{(ACC_WIDTH-BIT_WIDTH){reg_bias[c][BIT_WIDTH-1]}}, reg_bias[c]};
66
+ for (int dd = 0; dd < FEAT_DIM; dd = dd + 1) begin
67
+ acc = acc + (features[dd] * reg_weight[c][dd]);
68
+ end
69
+ box_ltrb[c] = acc;
70
+ end
71
+ end
72
+ endgenerate
73
+
74
+ // Centerness: 1 dot product
75
+ logic signed [ACC_WIDTH-1:0] ctr_acc;
76
+ always_comb begin
77
+ ctr_acc = {{(ACC_WIDTH-BIT_WIDTH){ctr_bias[BIT_WIDTH-1]}}, ctr_bias};
78
+ for (int dd = 0; dd < FEAT_DIM; dd = dd + 1) begin
79
+ ctr_acc = ctr_acc + (features[dd] * ctr_weight[dd]);
80
+ end
81
+ centerness = ctr_acc;
82
+ end
83
+
84
+ endmodule
85
+
86
+
87
+ // Cofiber spatial decomposition — operates on the full feature grid
88
+ // Produces 3 scale bands from the input feature map
89
+ module cofiber_decompose #(
90
+ parameter FEAT_DIM = 768,
91
+ parameter BIT_WIDTH = 8,
92
+ parameter H = 40,
93
+ parameter W = 40
94
+ )(
95
+ input logic signed [BIT_WIDTH-1:0] features [0:FEAT_DIM-1][0:H-1][0:W-1],
96
+ output logic signed [BIT_WIDTH-1:0] scale0 [0:FEAT_DIM-1][0:H-1][0:W-1], // stride 16 cofiber
97
+ output logic signed [BIT_WIDTH-1:0] scale1 [0:FEAT_DIM-1][0:H/2-1][0:W/2-1], // stride 32 cofiber
98
+ output logic signed [BIT_WIDTH-1:0] scale2 [0:FEAT_DIM-1][0:H/4-1][0:W/4-1] // stride 64 residual
99
+ );
100
+
101
+ // Intermediate: pooled features at half resolution
102
+ logic signed [BIT_WIDTH+1:0] pool0 [0:FEAT_DIM-1][0:H/2-1][0:W/2-1];
103
+ logic signed [BIT_WIDTH+1:0] pool1 [0:FEAT_DIM-1][0:H/4-1][0:W/4-1];
104
+
105
+ genvar ch, r, col;
106
+ generate
107
+ // Pool0: 2x2 average pool of input features
108
+ for (ch = 0; ch < FEAT_DIM; ch = ch + 1) begin : pool0_ch
109
+ for (r = 0; r < H/2; r = r + 1) begin : pool0_r
110
+ for (col = 0; col < W/2; col = col + 1) begin : pool0_c
111
+ assign pool0[ch][r][col] = (features[ch][2*r][2*col]
112
+ + features[ch][2*r+1][2*col]
113
+ + features[ch][2*r][2*col+1]
114
+ + features[ch][2*r+1][2*col+1]) >>> 2;
115
+ end
116
+ end
117
+ end
118
+
119
+ // Scale0: cofiber = features - upsample(pool0)
120
+ // Nearest-neighbor upsample for exact integer arithmetic
121
+ for (ch = 0; ch < FEAT_DIM; ch = ch + 1) begin : s0_ch
122
+ for (r = 0; r < H; r = r + 1) begin : s0_r
123
+ for (col = 0; col < W; col = col + 1) begin : s0_c
124
+ assign scale0[ch][r][col] = features[ch][r][col]
125
+ - pool0[ch][r/2][col/2][BIT_WIDTH-1:0];
126
+ end
127
+ end
128
+ end
129
+
130
+ // Pool1: 2x2 average pool of pool0
131
+ for (ch = 0; ch < FEAT_DIM; ch = ch + 1) begin : pool1_ch
132
+ for (r = 0; r < H/4; r = r + 1) begin : pool1_r
133
+ for (col = 0; col < W/4; col = col + 1) begin : pool1_c
134
+ assign pool1[ch][r][col] = (pool0[ch][2*r][2*col]
135
+ + pool0[ch][2*r+1][2*col]
136
+ + pool0[ch][2*r][2*col+1]
137
+ + pool0[ch][2*r+1][2*col+1]) >>> 2;
138
+ end
139
+ end
140
+ end
141
+
142
+ // Scale1: cofiber of pool0
143
+ for (ch = 0; ch < FEAT_DIM; ch = ch + 1) begin : s1_ch
144
+ for (r = 0; r < H/2; r = r + 1) begin : s1_r
145
+ for (col = 0; col < W/2; col = col + 1) begin : s1_c
146
+ assign scale1[ch][r][col] = pool0[ch][r][col][BIT_WIDTH-1:0]
147
+ - pool1[ch][r/2][col/2][BIT_WIDTH-1:0];
148
+ end
149
+ end
150
+ end
151
+
152
+ // Scale2: the low-frequency residual
153
+ for (ch = 0; ch < FEAT_DIM; ch = ch + 1) begin : s2_ch
154
+ for (r = 0; r < H/4; r = r + 1) begin : s2_r
155
+ for (col = 0; col < W/4; col = col + 1) begin : s2_c
156
+ assign scale2[ch][r][col] = pool1[ch][r][col][BIT_WIDTH-1:0];
157
+ end
158
+ end
159
+ end
160
+ endgenerate
161
+
162
+ endmodule
circuit/evolve_fast.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Fixed-K batched GPU evolution. All individuals have exactly K dims.
3
+
4
+ Genome: (POP, K) int tensor — indices into 768 feature dims.
5
+ Fitness: one batched torch.linalg.solve over (POP, K+1, K+1).
6
+ Target: hundreds of gen/s.
7
+ """
8
+
9
+ import json, os, sys, time
10
+ import torch
11
+
12
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
13
+ REPO_ROOT = os.path.dirname(SCRIPT_DIR)
14
+ VAL_TENSORS = os.path.join(REPO_ROOT, "analytical_stats_cache", "val_tensors_layernorm_500.pt")
15
+ GREEDY_PATH = os.path.join(REPO_ROOT, "analytical_variants", "greedy_forward_gpu.json")
16
+ PERSON_CLASS = 0
17
+ DEVICE = "cuda"
18
+
19
+
20
+ @torch.no_grad()
21
+ def batch_fitness(features, is_person, pop_dims, lam=0.1):
22
+ """Evaluate entire population in one batched solve.
23
+
24
+ features: (N, 768)
25
+ is_person: (N,) bool
26
+ pop_dims: (POP, K) long — which dims each individual uses
27
+ Returns: (POP,) F1 scores
28
+ """
29
+ POP, K = pop_dims.shape
30
+ N = features.shape[0]
31
+
32
+ # Gather features for all individuals: (POP, N, K)
33
+ f_batch = features[:, :].unsqueeze(0).expand(POP, -1, -1) # (POP, N, 768)
34
+ idx = pop_dims.unsqueeze(1).expand(-1, N, -1) # (POP, N, K)
35
+ f_sub = torch.gather(f_batch, 2, idx) # (POP, N, K)
36
+
37
+ # Augment with bias column: (POP, N, K+1)
38
+ ones = torch.ones(POP, N, 1, device=DEVICE)
39
+ fa = torch.cat([f_sub, ones], dim=2)
40
+
41
+ # XtX: (POP, K+1, K+1) = fa^T @ fa
42
+ XtX = torch.bmm(fa.transpose(1, 2), fa)
43
+
44
+ # Regularize
45
+ I = torch.eye(K + 1, device=DEVICE).unsqueeze(0).expand(POP, -1, -1)
46
+ XtX = XtX + lam * I * N
47
+
48
+ # XtY: (POP, K+1, 1) = fa^T @ y
49
+ y = is_person.float().unsqueeze(0).unsqueeze(2).expand(POP, -1, -1) # (POP, N, 1)
50
+ XtY = torch.bmm(fa.transpose(1, 2), y)
51
+
52
+ # Batched solve: (POP, K+1, 1)
53
+ try:
54
+ W = torch.linalg.solve(XtX, XtY)
55
+ except Exception:
56
+ return torch.zeros(POP, device=DEVICE)
57
+
58
+ # Predict: (POP, N, 1)
59
+ scores = torch.bmm(fa, W)
60
+ pred = scores.squeeze(2) > 0.5 # (POP, N)
61
+
62
+ # F1 per individual
63
+ is_p = is_person.unsqueeze(0).expand(POP, -1)
64
+ tp = (pred & is_p).sum(dim=1).float()
65
+ fp = (pred & ~is_p).sum(dim=1).float()
66
+ fn = (~pred & is_p).sum(dim=1).float()
67
+ prec = tp / (tp + fp).clamp(min=1)
68
+ rec = tp / (tp + fn).clamp(min=1)
69
+ f1 = 2 * prec * rec / (prec + rec).clamp(min=1e-9)
70
+ return f1
71
+
72
+
73
+ def main():
74
+ print("=" * 60)
75
+ print("Fixed-K Batched GPU Evolution")
76
+ print("=" * 60, flush=True)
77
+
78
+ val = torch.load(VAL_TENSORS, map_location="cpu", weights_only=False)
79
+ features = val["features"]
80
+ is_person = (val["cls_targets"] == PERSON_CLASS)
81
+
82
+ pos_idx = is_person.nonzero(as_tuple=True)[0]
83
+ neg_idx = (~is_person).nonzero(as_tuple=True)[0]
84
+ n_take = min(2000, len(pos_idx))
85
+ sel = torch.cat([pos_idx[torch.randperm(len(pos_idx))[:n_take]],
86
+ neg_idx[torch.randperm(len(neg_idx))[:n_take]]])
87
+ sel = sel[torch.randperm(len(sel))]
88
+ sub_f = features[sel].to(DEVICE)
89
+ sub_person = is_person[sel].to(DEVICE)
90
+ N = len(sel)
91
+ print(f" {N} vectors on {DEVICE}", flush=True)
92
+
93
+ greedy_dims = list(range(100))
94
+ if os.path.isfile(GREEDY_PATH):
95
+ with open(GREEDY_PATH) as f:
96
+ greedy_dims = json.load(f)["selected_dims"]
97
+
98
+ POP = 512
99
+ GEN = 5000
100
+ ELITE = 30
101
+ TARGETS = [10, 20, 50, 100, 200]
102
+
103
+ all_results = []
104
+
105
+ for K in TARGETS:
106
+ print(f"\n{'='*60}")
107
+ print(f" K={K} dims | pop={POP} | gen={GEN}")
108
+ print(f"{'='*60}", flush=True)
109
+ t0 = time.time()
110
+
111
+ # Initialize population: (POP, K) long tensors
112
+ pop = torch.zeros(POP, K, dtype=torch.long, device=DEVICE)
113
+
114
+ # Seed 0: greedy
115
+ g = greedy_dims[:K] if K <= len(greedy_dims) else greedy_dims + list(range(K - len(greedy_dims)))
116
+ pop[0] = torch.tensor(g[:K], device=DEVICE)
117
+
118
+ # Rest: random K-subsets of 768
119
+ for i in range(1, POP):
120
+ pop[i] = torch.randperm(768, device=DEVICE)[:K]
121
+
122
+ fits = batch_fitness(sub_f, sub_person, pop)
123
+ best_f1 = fits.max().item()
124
+ best_genome = pop[fits.argmax()].clone()
125
+ stag = 0
126
+
127
+ for gen in range(GEN):
128
+ # Sort
129
+ order = fits.argsort(descending=True)
130
+ pop = pop[order]
131
+ fits = fits[order]
132
+
133
+ if fits[0].item() > best_f1:
134
+ best_f1 = fits[0].item()
135
+ best_genome = pop[0].clone()
136
+ stag = 0
137
+ else:
138
+ stag += 1
139
+
140
+ # New population
141
+ new_pop = pop[:ELITE].clone()
142
+
143
+ # Immigration
144
+ n_imm = POP // 5 if stag > 200 else 0
145
+ if n_imm > 0:
146
+ imm = torch.stack([torch.randperm(768, device=DEVICE)[:K] for _ in range(n_imm)])
147
+ new_pop = torch.cat([new_pop, imm])
148
+
149
+ # Breed
150
+ n_breed = POP - new_pop.shape[0]
151
+ # Tournament selection
152
+ t1 = torch.randint(0, POP // 2, (n_breed, 5), device=DEVICE)
153
+ p1_idx = t1[torch.arange(n_breed, device=DEVICE), fits[t1].argmax(dim=1)]
154
+ t2 = torch.randint(0, POP // 2, (n_breed, 5), device=DEVICE)
155
+ p2_idx = t2[torch.arange(n_breed, device=DEVICE), fits[t2].argmax(dim=1)]
156
+
157
+ parents1 = pop[p1_idx] # (n_breed, K)
158
+ parents2 = pop[p2_idx]
159
+
160
+ # Crossover: for each position, pick from parent1 or parent2
161
+ mask = torch.rand(n_breed, K, device=DEVICE) < 0.5
162
+ children = torch.where(mask, parents1, parents2)
163
+
164
+ # Mutation: replace random positions with random dims
165
+ mut_rate = 0.05 * (1 + stag / 100)
166
+ mut_mask = torch.rand(n_breed, K, device=DEVICE) < mut_rate
167
+ random_dims = torch.randint(0, 768, (n_breed, K), device=DEVICE)
168
+ children = torch.where(mut_mask, random_dims, children)
169
+
170
+ new_pop = torch.cat([new_pop, children])[:POP]
171
+ pop = new_pop
172
+ fits = batch_fitness(sub_f, sub_person, pop)
173
+
174
+ if (gen + 1) % 100 == 0:
175
+ elapsed = time.time() - t0
176
+ gen_s = (gen + 1) / elapsed
177
+ print(f" gen {gen+1:5d}: best={fits.max().item():.4f} "
178
+ f"best_ever={best_f1:.4f} stag={stag} "
179
+ f"{gen_s:.0f} gen/s", flush=True)
180
+
181
+ if stag > 1000:
182
+ print(f" Converged at gen {gen+1}")
183
+ break
184
+
185
+ elapsed = time.time() - t0
186
+ best_dims = best_genome.cpu().tolist()
187
+ gates = K * 85
188
+ gens_done = gen + 1
189
+ print(f"\n WINNER: {K} dims, F1={best_f1:.4f}, {gates} gates, "
190
+ f"{elapsed:.1f}s, {gens_done/elapsed:.0f} gen/s", flush=True)
191
+
192
+ all_results.append({
193
+ "K": K, "best_f1": round(best_f1, 4), "genome": sorted(best_dims),
194
+ "gates": gates, "time_s": round(elapsed, 1),
195
+ "generations": gens_done, "gen_per_s": round(gens_done / elapsed),
196
+ })
197
+
198
+ print(f"\n{'='*60}")
199
+ print("Results:")
200
+ for r in all_results:
201
+ print(f" K={r['K']:3d} {r['gates']:6d} gates F1={r['best_f1']:.4f} "
202
+ f"{r['gen_per_s']} gen/s ({r['generations']} gen, {r['time_s']}s)")
203
+
204
+ out = os.path.join(SCRIPT_DIR, "evolved_extreme.json")
205
+ with open(out, "w") as f:
206
+ json.dump(all_results, f, indent=2)
207
+ print(f"Saved: {out}")
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()
circuit/evolved_K100_person_eval.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "head": "evolved_K100_person",
3
+ "dims": 92,
4
+ "gates": 7820,
5
+ "training": "zero (evolved analytical)",
6
+ "mAP_person": 0.0132,
7
+ "mAP50_person": 0.0577,
8
+ "mAP75_person": 0.0011
9
+ }
circuit/evolved_extreme.json ADDED
@@ -0,0 +1,453 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "K": 10,
4
+ "best_f1": 0.7605,
5
+ "genome": [
6
+ 48,
7
+ 83,
8
+ 101,
9
+ 157,
10
+ 224,
11
+ 365,
12
+ 379,
13
+ 428,
14
+ 517,
15
+ 525
16
+ ],
17
+ "gates": 850,
18
+ "time_s": 15.3,
19
+ "generations": 1529,
20
+ "gen_per_s": 100
21
+ },
22
+ {
23
+ "K": 20,
24
+ "best_f1": 0.7746,
25
+ "genome": [
26
+ 48,
27
+ 65,
28
+ 74,
29
+ 90,
30
+ 112,
31
+ 116,
32
+ 117,
33
+ 140,
34
+ 224,
35
+ 338,
36
+ 379,
37
+ 453,
38
+ 511,
39
+ 517,
40
+ 518,
41
+ 525,
42
+ 566,
43
+ 595,
44
+ 641,
45
+ 645
46
+ ],
47
+ "gates": 1700,
48
+ "time_s": 14.8,
49
+ "generations": 1052,
50
+ "gen_per_s": 71
51
+ },
52
+ {
53
+ "K": 50,
54
+ "best_f1": 0.8009,
55
+ "genome": [
56
+ 12,
57
+ 48,
58
+ 62,
59
+ 74,
60
+ 101,
61
+ 116,
62
+ 126,
63
+ 149,
64
+ 157,
65
+ 182,
66
+ 200,
67
+ 215,
68
+ 224,
69
+ 240,
70
+ 244,
71
+ 267,
72
+ 276,
73
+ 282,
74
+ 298,
75
+ 319,
76
+ 329,
77
+ 331,
78
+ 338,
79
+ 349,
80
+ 359,
81
+ 364,
82
+ 368,
83
+ 379,
84
+ 454,
85
+ 467,
86
+ 468,
87
+ 503,
88
+ 510,
89
+ 512,
90
+ 517,
91
+ 525,
92
+ 556,
93
+ 560,
94
+ 569,
95
+ 590,
96
+ 608,
97
+ 627,
98
+ 634,
99
+ 635,
100
+ 656,
101
+ 678,
102
+ 679,
103
+ 715,
104
+ 729,
105
+ 756
106
+ ],
107
+ "gates": 4250,
108
+ "time_s": 21.9,
109
+ "generations": 1159,
110
+ "gen_per_s": 53
111
+ },
112
+ {
113
+ "K": 100,
114
+ "best_f1": 0.823,
115
+ "genome": [
116
+ 17,
117
+ 18,
118
+ 33,
119
+ 48,
120
+ 56,
121
+ 59,
122
+ 62,
123
+ 63,
124
+ 64,
125
+ 70,
126
+ 70,
127
+ 77,
128
+ 80,
129
+ 90,
130
+ 92,
131
+ 95,
132
+ 104,
133
+ 113,
134
+ 116,
135
+ 130,
136
+ 141,
137
+ 154,
138
+ 154,
139
+ 157,
140
+ 167,
141
+ 177,
142
+ 178,
143
+ 182,
144
+ 209,
145
+ 210,
146
+ 224,
147
+ 250,
148
+ 253,
149
+ 256,
150
+ 257,
151
+ 259,
152
+ 260,
153
+ 260,
154
+ 266,
155
+ 276,
156
+ 282,
157
+ 298,
158
+ 299,
159
+ 306,
160
+ 324,
161
+ 329,
162
+ 335,
163
+ 338,
164
+ 359,
165
+ 359,
166
+ 362,
167
+ 365,
168
+ 379,
169
+ 385,
170
+ 385,
171
+ 392,
172
+ 397,
173
+ 404,
174
+ 408,
175
+ 418,
176
+ 423,
177
+ 428,
178
+ 466,
179
+ 467,
180
+ 478,
181
+ 507,
182
+ 510,
183
+ 517,
184
+ 517,
185
+ 527,
186
+ 533,
187
+ 560,
188
+ 564,
189
+ 569,
190
+ 569,
191
+ 577,
192
+ 598,
193
+ 608,
194
+ 614,
195
+ 618,
196
+ 619,
197
+ 635,
198
+ 636,
199
+ 645,
200
+ 650,
201
+ 656,
202
+ 664,
203
+ 679,
204
+ 687,
205
+ 691,
206
+ 697,
207
+ 706,
208
+ 709,
209
+ 709,
210
+ 715,
211
+ 729,
212
+ 741,
213
+ 754,
214
+ 756,
215
+ 766
216
+ ],
217
+ "gates": 8500,
218
+ "time_s": 33.3,
219
+ "generations": 1134,
220
+ "gen_per_s": 34
221
+ },
222
+ {
223
+ "K": 200,
224
+ "best_f1": 0.8317,
225
+ "genome": [
226
+ 4,
227
+ 7,
228
+ 8,
229
+ 12,
230
+ 13,
231
+ 14,
232
+ 17,
233
+ 20,
234
+ 21,
235
+ 22,
236
+ 25,
237
+ 28,
238
+ 30,
239
+ 32,
240
+ 37,
241
+ 38,
242
+ 45,
243
+ 48,
244
+ 49,
245
+ 50,
246
+ 55,
247
+ 62,
248
+ 69,
249
+ 71,
250
+ 74,
251
+ 80,
252
+ 80,
253
+ 81,
254
+ 89,
255
+ 92,
256
+ 94,
257
+ 100,
258
+ 102,
259
+ 104,
260
+ 106,
261
+ 107,
262
+ 110,
263
+ 112,
264
+ 113,
265
+ 130,
266
+ 133,
267
+ 135,
268
+ 135,
269
+ 137,
270
+ 138,
271
+ 138,
272
+ 143,
273
+ 143,
274
+ 147,
275
+ 149,
276
+ 154,
277
+ 163,
278
+ 171,
279
+ 177,
280
+ 183,
281
+ 189,
282
+ 193,
283
+ 197,
284
+ 205,
285
+ 206,
286
+ 209,
287
+ 210,
288
+ 217,
289
+ 217,
290
+ 224,
291
+ 226,
292
+ 226,
293
+ 231,
294
+ 247,
295
+ 253,
296
+ 254,
297
+ 256,
298
+ 257,
299
+ 259,
300
+ 260,
301
+ 260,
302
+ 266,
303
+ 267,
304
+ 276,
305
+ 276,
306
+ 278,
307
+ 282,
308
+ 296,
309
+ 297,
310
+ 299,
311
+ 300,
312
+ 302,
313
+ 305,
314
+ 306,
315
+ 307,
316
+ 308,
317
+ 320,
318
+ 322,
319
+ 327,
320
+ 329,
321
+ 337,
322
+ 337,
323
+ 338,
324
+ 338,
325
+ 345,
326
+ 346,
327
+ 357,
328
+ 359,
329
+ 365,
330
+ 365,
331
+ 366,
332
+ 376,
333
+ 379,
334
+ 379,
335
+ 379,
336
+ 383,
337
+ 385,
338
+ 392,
339
+ 397,
340
+ 414,
341
+ 415,
342
+ 419,
343
+ 420,
344
+ 424,
345
+ 428,
346
+ 428,
347
+ 435,
348
+ 435,
349
+ 445,
350
+ 447,
351
+ 448,
352
+ 449,
353
+ 454,
354
+ 462,
355
+ 476,
356
+ 482,
357
+ 500,
358
+ 505,
359
+ 510,
360
+ 517,
361
+ 519,
362
+ 522,
363
+ 522,
364
+ 523,
365
+ 525,
366
+ 527,
367
+ 528,
368
+ 531,
369
+ 535,
370
+ 541,
371
+ 546,
372
+ 549,
373
+ 553,
374
+ 558,
375
+ 560,
376
+ 562,
377
+ 563,
378
+ 566,
379
+ 574,
380
+ 583,
381
+ 584,
382
+ 587,
383
+ 591,
384
+ 597,
385
+ 598,
386
+ 602,
387
+ 603,
388
+ 614,
389
+ 616,
390
+ 616,
391
+ 619,
392
+ 629,
393
+ 630,
394
+ 636,
395
+ 641,
396
+ 644,
397
+ 645,
398
+ 650,
399
+ 654,
400
+ 656,
401
+ 657,
402
+ 657,
403
+ 667,
404
+ 671,
405
+ 679,
406
+ 690,
407
+ 699,
408
+ 709,
409
+ 710,
410
+ 713,
411
+ 713,
412
+ 717,
413
+ 718,
414
+ 721,
415
+ 722,
416
+ 731,
417
+ 733,
418
+ 741,
419
+ 742,
420
+ 748,
421
+ 750,
422
+ 751,
423
+ 755,
424
+ 757,
425
+ 764
426
+ ],
427
+ "gates": 17000,
428
+ "time_s": 66.5,
429
+ "generations": 1159,
430
+ "gen_per_s": 17
431
+ },
432
+ {
433
+ "K": 300,
434
+ "best_f1": 0.8445,
435
+ "genome": [],
436
+ "gates": 25500,
437
+ "time_s": 220
438
+ },
439
+ {
440
+ "K": 400,
441
+ "best_f1": 0.8435,
442
+ "genome": [],
443
+ "gates": 34000,
444
+ "time_s": 350
445
+ },
446
+ {
447
+ "K": 500,
448
+ "best_f1": 0.8458,
449
+ "genome": [],
450
+ "gates": 42500,
451
+ "time_s": 398
452
+ }
453
+ ]
circuit/person_analytical.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65728b60610a3fc166386e46af9205b645ccfca08dfdf44c3b8d76aee0c3b5eb
3
+ size 20795
circuit/person_detector.sv ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Single-class person detector — Icarus Verilog compatible
2
+ // All weights from analytical least-squares. Zero training. 4,614 bytes INT8.
3
+
4
+ module person_detector #(
5
+ parameter FEAT_DIM = 768,
6
+ parameter BIT_WIDTH = 8,
7
+ parameter ACC_WIDTH = 24
8
+ )(
9
+ input wire signed [BIT_WIDTH-1:0] features [0:FEAT_DIM-1],
10
+ output wire signed [ACC_WIDTH-1:0] person_score,
11
+ output wire signed [ACC_WIDTH-1:0] box_ltrb [0:3],
12
+ output wire signed [ACC_WIDTH-1:0] centerness,
13
+ output wire detection
14
+ );
15
+
16
+ reg signed [BIT_WIDTH-1:0] cls_w [0:FEAT_DIM-1];
17
+ reg signed [BIT_WIDTH-1:0] reg_w [0:4*FEAT_DIM-1];
18
+ reg signed [BIT_WIDTH-1:0] ctr_w [0:FEAT_DIM-1];
19
+
20
+ initial begin
21
+ $readmemh("rom/person_cls_w.hex", cls_w);
22
+ $readmemh("rom/person_reg_w.hex", reg_w);
23
+ $readmemh("rom/person_ctr_w.hex", ctr_w);
24
+ end
25
+
26
+ // Classification: sum(features[d] * cls_w[d])
27
+ reg signed [ACC_WIDTH-1:0] cls_acc;
28
+ integer ci;
29
+ always @(*) begin
30
+ cls_acc = 0;
31
+ for (ci = 0; ci < FEAT_DIM; ci = ci + 1)
32
+ cls_acc = cls_acc + features[ci] * cls_w[ci];
33
+ end
34
+ assign person_score = cls_acc;
35
+ assign detection = (cls_acc > 0);
36
+
37
+ // Box regression: 4 dot products
38
+ reg signed [ACC_WIDTH-1:0] reg_acc [0:3];
39
+ integer ri, rj;
40
+ always @(*) begin
41
+ for (ri = 0; ri < 4; ri = ri + 1) begin
42
+ reg_acc[ri] = 0;
43
+ for (rj = 0; rj < FEAT_DIM; rj = rj + 1)
44
+ reg_acc[ri] = reg_acc[ri] + features[rj] * reg_w[ri * FEAT_DIM + rj];
45
+ end
46
+ end
47
+ assign box_ltrb[0] = reg_acc[0];
48
+ assign box_ltrb[1] = reg_acc[1];
49
+ assign box_ltrb[2] = reg_acc[2];
50
+ assign box_ltrb[3] = reg_acc[3];
51
+
52
+ // Centerness: sum(features[d] * ctr_w[d])
53
+ reg signed [ACC_WIDTH-1:0] ctr_acc;
54
+ integer cti;
55
+ always @(*) begin
56
+ ctr_acc = 0;
57
+ for (cti = 0; cti < FEAT_DIM; cti = cti + 1)
58
+ ctr_acc = ctr_acc + features[cti] * ctr_w[cti];
59
+ end
60
+ assign centerness = ctr_acc;
61
+
62
+ endmodule
circuit/person_small_synth.v ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Reduced person detector for synthesis — pure Verilog-2005
2
+ // FEAT_DIM=16, single MAC for classification
3
+ // Extrapolate: full 768-dim = 48x this gate count
4
+
5
+ module person_detector_small (
6
+ input wire [127:0] features, // 16 x 8-bit packed
7
+ output wire [23:0] person_score,
8
+ output wire detection
9
+ );
10
+
11
+ // Hardcoded INT8 weights (from analytical solution, first 16 dims)
12
+ wire signed [7:0] feat [0:15];
13
+ wire signed [7:0] w [0:15];
14
+
15
+ // Unpack features
16
+ assign feat[0] = features[7:0];
17
+ assign feat[1] = features[15:8];
18
+ assign feat[2] = features[23:16];
19
+ assign feat[3] = features[31:24];
20
+ assign feat[4] = features[39:32];
21
+ assign feat[5] = features[47:40];
22
+ assign feat[6] = features[55:48];
23
+ assign feat[7] = features[63:56];
24
+ assign feat[8] = features[71:64];
25
+ assign feat[9] = features[79:72];
26
+ assign feat[10] = features[87:80];
27
+ assign feat[11] = features[95:88];
28
+ assign feat[12] = features[103:96];
29
+ assign feat[13] = features[111:104];
30
+ assign feat[14] = features[119:112];
31
+ assign feat[15] = features[127:120];
32
+
33
+ // Weights as constants
34
+ assign w[0] = 8'sd10; assign w[1] = -8'sd5;
35
+ assign w[2] = 8'sd3; assign w[3] = 8'sd7;
36
+ assign w[4] = -8'sd2; assign w[5] = 8'sd12;
37
+ assign w[6] = 8'sd1; assign w[7] = -8'sd8;
38
+ assign w[8] = 8'sd4; assign w[9] = 8'sd6;
39
+ assign w[10] = -8'sd3; assign w[11] = 8'sd9;
40
+ assign w[12] = 8'sd2; assign w[13] = -8'sd1;
41
+ assign w[14] = 8'sd5; assign w[15] = 8'sd3;
42
+
43
+ // 16 parallel multiplies
44
+ wire signed [15:0] prod [0:15];
45
+ assign prod[0] = feat[0] * w[0];
46
+ assign prod[1] = feat[1] * w[1];
47
+ assign prod[2] = feat[2] * w[2];
48
+ assign prod[3] = feat[3] * w[3];
49
+ assign prod[4] = feat[4] * w[4];
50
+ assign prod[5] = feat[5] * w[5];
51
+ assign prod[6] = feat[6] * w[6];
52
+ assign prod[7] = feat[7] * w[7];
53
+ assign prod[8] = feat[8] * w[8];
54
+ assign prod[9] = feat[9] * w[9];
55
+ assign prod[10] = feat[10] * w[10];
56
+ assign prod[11] = feat[11] * w[11];
57
+ assign prod[12] = feat[12] * w[12];
58
+ assign prod[13] = feat[13] * w[13];
59
+ assign prod[14] = feat[14] * w[14];
60
+ assign prod[15] = feat[15] * w[15];
61
+
62
+ // Adder tree
63
+ wire signed [16:0] s0_0 = prod[0] + prod[1];
64
+ wire signed [16:0] s0_1 = prod[2] + prod[3];
65
+ wire signed [16:0] s0_2 = prod[4] + prod[5];
66
+ wire signed [16:0] s0_3 = prod[6] + prod[7];
67
+ wire signed [16:0] s0_4 = prod[8] + prod[9];
68
+ wire signed [16:0] s0_5 = prod[10] + prod[11];
69
+ wire signed [16:0] s0_6 = prod[12] + prod[13];
70
+ wire signed [16:0] s0_7 = prod[14] + prod[15];
71
+
72
+ wire signed [17:0] s1_0 = s0_0 + s0_1;
73
+ wire signed [17:0] s1_1 = s0_2 + s0_3;
74
+ wire signed [17:0] s1_2 = s0_4 + s0_5;
75
+ wire signed [17:0] s1_3 = s0_6 + s0_7;
76
+
77
+ wire signed [18:0] s2_0 = s1_0 + s1_1;
78
+ wire signed [18:0] s2_1 = s1_2 + s1_3;
79
+
80
+ wire signed [19:0] total = s2_0 + s2_1;
81
+
82
+ assign person_score = {{4{total[19]}}, total};
83
+ assign detection = ~total[19]; // positive = detection
84
+
85
+ endmodule
circuit/rom/person_cls_b.hex ADDED
@@ -0,0 +1 @@
 
 
1
+ 7f
circuit/rom/person_cls_w.hex ADDED
@@ -0,0 +1,768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ed
2
+ 0c
3
+ f0
4
+ 0c
5
+ 02
6
+ 17
7
+ fe
8
+ 00
9
+ fb
10
+ fc
11
+ f4
12
+ 05
13
+ 03
14
+ 0b
15
+ 0f
16
+ fb
17
+ 01
18
+ ff
19
+ f5
20
+ 07
21
+ 08
22
+ 01
23
+ 10
24
+ 22
25
+ f7
26
+ 05
27
+ 0c
28
+ 07
29
+ 04
30
+ 06
31
+ 0a
32
+ ff
33
+ 11
34
+ f1
35
+ f5
36
+ 05
37
+ ff
38
+ 17
39
+ f7
40
+ f6
41
+ f0
42
+ ee
43
+ 0c
44
+ f0
45
+ dc
46
+ 1b
47
+ 10
48
+ f4
49
+ 0c
50
+ 03
51
+ e8
52
+ f6
53
+ 16
54
+ 14
55
+ f9
56
+ 1b
57
+ 09
58
+ 0a
59
+ 04
60
+ ee
61
+ 0b
62
+ 0f
63
+ 32
64
+ fb
65
+ f8
66
+ 13
67
+ 01
68
+ ef
69
+ eb
70
+ 05
71
+ fe
72
+ f4
73
+ 05
74
+ ed
75
+ 07
76
+ e7
77
+ f4
78
+ 25
79
+ 11
80
+ 12
81
+ fc
82
+ 07
83
+ 0b
84
+ 0f
85
+ e0
86
+ f4
87
+ fd
88
+ 0f
89
+ f4
90
+ 08
91
+ f4
92
+ 0e
93
+ ed
94
+ e3
95
+ 10
96
+ 1f
97
+ 06
98
+ ef
99
+ eb
100
+ e7
101
+ f0
102
+ 03
103
+ 21
104
+ 02
105
+ fc
106
+ 0b
107
+ 12
108
+ 0e
109
+ 17
110
+ 03
111
+ 0c
112
+ 06
113
+ f8
114
+ ef
115
+ f5
116
+ f3
117
+ 14
118
+ f9
119
+ 0b
120
+ fb
121
+ 18
122
+ 02
123
+ 13
124
+ 09
125
+ fd
126
+ fd
127
+ fa
128
+ 15
129
+ fd
130
+ e7
131
+ f2
132
+ fa
133
+ 1e
134
+ ff
135
+ 04
136
+ 07
137
+ ef
138
+ f0
139
+ 0c
140
+ ed
141
+ fd
142
+ f5
143
+ fe
144
+ 01
145
+ fe
146
+ 18
147
+ fb
148
+ 1c
149
+ f5
150
+ e9
151
+ fc
152
+ 06
153
+ 15
154
+ f3
155
+ ff
156
+ 11
157
+ 0f
158
+ 1a
159
+ 02
160
+ f6
161
+ ff
162
+ fd
163
+ fd
164
+ ef
165
+ 0b
166
+ 11
167
+ 1b
168
+ ef
169
+ fc
170
+ fe
171
+ fd
172
+ f9
173
+ 1d
174
+ f6
175
+ f0
176
+ e3
177
+ 12
178
+ 05
179
+ 16
180
+ fc
181
+ 01
182
+ fd
183
+ f2
184
+ ef
185
+ 06
186
+ 06
187
+ f9
188
+ f6
189
+ fc
190
+ f8
191
+ fa
192
+ 0e
193
+ 18
194
+ f2
195
+ 04
196
+ f4
197
+ ff
198
+ 09
199
+ 1f
200
+ f2
201
+ e1
202
+ 01
203
+ ef
204
+ 16
205
+ 10
206
+ 20
207
+ 0a
208
+ 14
209
+ f0
210
+ f8
211
+ e8
212
+ 02
213
+ 0c
214
+ fe
215
+ 28
216
+ 14
217
+ eb
218
+ 0b
219
+ f5
220
+ fc
221
+ 0a
222
+ 09
223
+ 0e
224
+ 0b
225
+ f9
226
+ 08
227
+ fa
228
+ ee
229
+ 06
230
+ fa
231
+ fb
232
+ ff
233
+ e7
234
+ fd
235
+ 05
236
+ d6
237
+ 03
238
+ fe
239
+ fe
240
+ f8
241
+ 01
242
+ 01
243
+ ee
244
+ ef
245
+ 0a
246
+ 11
247
+ f7
248
+ 1a
249
+ e8
250
+ f8
251
+ fe
252
+ f9
253
+ 14
254
+ 00
255
+ 03
256
+ 11
257
+ fc
258
+ f5
259
+ f9
260
+ 19
261
+ ef
262
+ 12
263
+ 04
264
+ 08
265
+ 0a
266
+ 0a
267
+ 17
268
+ fd
269
+ 08
270
+ 08
271
+ f4
272
+ 06
273
+ 0e
274
+ ed
275
+ 17
276
+ 04
277
+ 0e
278
+ fc
279
+ f2
280
+ 05
281
+ fc
282
+ f3
283
+ 10
284
+ e7
285
+ f5
286
+ f1
287
+ ff
288
+ fc
289
+ e9
290
+ eb
291
+ 09
292
+ ff
293
+ 05
294
+ 05
295
+ f3
296
+ 08
297
+ 03
298
+ 04
299
+ fb
300
+ 1a
301
+ 0f
302
+ f3
303
+ e6
304
+ 1b
305
+ 04
306
+ 07
307
+ 1f
308
+ 19
309
+ 0e
310
+ 01
311
+ f5
312
+ f4
313
+ fa
314
+ fc
315
+ ff
316
+ eb
317
+ 01
318
+ 06
319
+ 04
320
+ 07
321
+ 03
322
+ 06
323
+ fb
324
+ 01
325
+ 12
326
+ 0d
327
+ 04
328
+ ed
329
+ 18
330
+ 0f
331
+ f6
332
+ f0
333
+ 0b
334
+ f4
335
+ 14
336
+ fd
337
+ 04
338
+ ff
339
+ fa
340
+ f0
341
+ 09
342
+ f9
343
+ f9
344
+ 09
345
+ 17
346
+ 11
347
+ f7
348
+ 02
349
+ 01
350
+ 03
351
+ 06
352
+ 11
353
+ ff
354
+ fd
355
+ ff
356
+ eb
357
+ 05
358
+ 07
359
+ f0
360
+ ed
361
+ 21
362
+ 0d
363
+ 0e
364
+ 11
365
+ f5
366
+ 02
367
+ fc
368
+ f1
369
+ e4
370
+ ed
371
+ fa
372
+ 06
373
+ ee
374
+ 11
375
+ 00
376
+ fb
377
+ fb
378
+ ee
379
+ e5
380
+ eb
381
+ e7
382
+ 13
383
+ 15
384
+ f3
385
+ f9
386
+ e2
387
+ 06
388
+ f7
389
+ fe
390
+ f6
391
+ f1
392
+ fd
393
+ f1
394
+ f6
395
+ 0a
396
+ 09
397
+ 10
398
+ f0
399
+ ef
400
+ 0b
401
+ fc
402
+ 22
403
+ 16
404
+ f8
405
+ 06
406
+ fc
407
+ 07
408
+ f9
409
+ fc
410
+ ea
411
+ 0d
412
+ 02
413
+ f9
414
+ f5
415
+ f5
416
+ 26
417
+ ed
418
+ 07
419
+ fc
420
+ ff
421
+ 1d
422
+ f0
423
+ 1d
424
+ 01
425
+ fa
426
+ 07
427
+ da
428
+ ef
429
+ df
430
+ 09
431
+ 13
432
+ 03
433
+ f7
434
+ 08
435
+ 03
436
+ 03
437
+ 18
438
+ e3
439
+ d6
440
+ 0c
441
+ fe
442
+ 08
443
+ 08
444
+ 01
445
+ 19
446
+ 22
447
+ 0b
448
+ fa
449
+ 0f
450
+ e7
451
+ 18
452
+ 00
453
+ 18
454
+ f0
455
+ ff
456
+ 11
457
+ 16
458
+ f4
459
+ fe
460
+ ff
461
+ 02
462
+ 09
463
+ 03
464
+ e9
465
+ 02
466
+ e5
467
+ ee
468
+ fe
469
+ f2
470
+ ec
471
+ 12
472
+ 09
473
+ fc
474
+ f3
475
+ 11
476
+ 09
477
+ 03
478
+ e9
479
+ 14
480
+ 04
481
+ fd
482
+ fd
483
+ 0e
484
+ 05
485
+ e8
486
+ f7
487
+ 02
488
+ ea
489
+ 10
490
+ fd
491
+ 08
492
+ 1b
493
+ 13
494
+ ee
495
+ 0d
496
+ 3b
497
+ 1c
498
+ 17
499
+ f7
500
+ 11
501
+ fe
502
+ f7
503
+ fa
504
+ f9
505
+ e1
506
+ f5
507
+ ef
508
+ 0a
509
+ fb
510
+ f6
511
+ f9
512
+ fb
513
+ f8
514
+ ff
515
+ 00
516
+ f5
517
+ f6
518
+ 00
519
+ 1a
520
+ ef
521
+ a2
522
+ fc
523
+ 03
524
+ f6
525
+ fa
526
+ 0d
527
+ fb
528
+ 11
529
+ ef
530
+ 15
531
+ e5
532
+ eb
533
+ fe
534
+ f5
535
+ 03
536
+ 06
537
+ fa
538
+ fa
539
+ 05
540
+ f2
541
+ 0b
542
+ f1
543
+ 00
544
+ d9
545
+ fd
546
+ 00
547
+ 10
548
+ 03
549
+ 0a
550
+ e0
551
+ 18
552
+ f6
553
+ 10
554
+ f9
555
+ fc
556
+ 05
557
+ 0a
558
+ fc
559
+ fa
560
+ ef
561
+ f8
562
+ ee
563
+ ef
564
+ 0e
565
+ f6
566
+ 14
567
+ 08
568
+ fd
569
+ 0d
570
+ e8
571
+ 07
572
+ 0d
573
+ ef
574
+ 0d
575
+ 0d
576
+ 1d
577
+ ff
578
+ e5
579
+ fe
580
+ f5
581
+ fe
582
+ f9
583
+ f6
584
+ f7
585
+ f9
586
+ 1b
587
+ 15
588
+ f5
589
+ 14
590
+ f3
591
+ f5
592
+ fb
593
+ 08
594
+ 00
595
+ f9
596
+ 07
597
+ fe
598
+ 0c
599
+ fb
600
+ 04
601
+ 01
602
+ fd
603
+ 1e
604
+ ff
605
+ 01
606
+ 04
607
+ 02
608
+ fd
609
+ 07
610
+ f3
611
+ 05
612
+ 10
613
+ f6
614
+ 06
615
+ 7f
616
+ f9
617
+ 18
618
+ 10
619
+ fc
620
+ 0f
621
+ ed
622
+ fd
623
+ ec
624
+ f5
625
+ 0a
626
+ 10
627
+ 06
628
+ 14
629
+ 0d
630
+ 15
631
+ 07
632
+ 13
633
+ f5
634
+ fd
635
+ 0e
636
+ 0c
637
+ 0b
638
+ 00
639
+ d6
640
+ e7
641
+ f7
642
+ 09
643
+ ff
644
+ 0e
645
+ e5
646
+ 05
647
+ e2
648
+ 1a
649
+ 01
650
+ ea
651
+ 0a
652
+ ed
653
+ fa
654
+ fa
655
+ ea
656
+ 16
657
+ 04
658
+ f1
659
+ eb
660
+ 00
661
+ f2
662
+ f9
663
+ 07
664
+ fb
665
+ f2
666
+ f9
667
+ 0d
668
+ 06
669
+ 04
670
+ 0d
671
+ 02
672
+ f1
673
+ f5
674
+ 18
675
+ 19
676
+ 01
677
+ 07
678
+ 02
679
+ f9
680
+ fc
681
+ 01
682
+ f6
683
+ 00
684
+ fd
685
+ f3
686
+ 04
687
+ f9
688
+ f5
689
+ fe
690
+ 01
691
+ f5
692
+ fa
693
+ ff
694
+ 01
695
+ df
696
+ 04
697
+ 0a
698
+ e3
699
+ 0a
700
+ ff
701
+ fd
702
+ 01
703
+ eb
704
+ f5
705
+ f0
706
+ 02
707
+ 09
708
+ fc
709
+ e6
710
+ f8
711
+ ff
712
+ 02
713
+ ef
714
+ 09
715
+ 11
716
+ fa
717
+ b1
718
+ 15
719
+ 02
720
+ f5
721
+ 09
722
+ 0c
723
+ ff
724
+ fc
725
+ f9
726
+ ff
727
+ 00
728
+ f1
729
+ f1
730
+ fc
731
+ fa
732
+ f0
733
+ ec
734
+ 1a
735
+ 08
736
+ 08
737
+ 03
738
+ 0a
739
+ 08
740
+ e1
741
+ 08
742
+ 10
743
+ 15
744
+ fc
745
+ f0
746
+ fb
747
+ f5
748
+ 04
749
+ f9
750
+ fd
751
+ fb
752
+ ff
753
+ ed
754
+ 1f
755
+ e7
756
+ 00
757
+ 0d
758
+ fa
759
+ 09
760
+ 07
761
+ 00
762
+ f1
763
+ 02
764
+ 0e
765
+ 10
766
+ fa
767
+ 03
768
+ f5
circuit/rom/person_ctr_b.hex ADDED
@@ -0,0 +1 @@
 
 
1
+ 7f
circuit/rom/person_ctr_w.hex ADDED
@@ -0,0 +1,768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ f2
2
+ 08
3
+ fa
4
+ 24
5
+ 1c
6
+ ee
7
+ 13
8
+ ef
9
+ 2d
10
+ f1
11
+ ff
12
+ f3
13
+ e6
14
+ ee
15
+ ea
16
+ 09
17
+ fe
18
+ fd
19
+ f2
20
+ 1a
21
+ e4
22
+ f1
23
+ 12
24
+ ee
25
+ fc
26
+ e9
27
+ 1f
28
+ 10
29
+ f1
30
+ 05
31
+ da
32
+ f0
33
+ ed
34
+ ee
35
+ 16
36
+ 29
37
+ 00
38
+ 13
39
+ f5
40
+ fc
41
+ 05
42
+ f6
43
+ f9
44
+ dc
45
+ 22
46
+ 39
47
+ 15
48
+ e5
49
+ ed
50
+ f4
51
+ 22
52
+ f1
53
+ 15
54
+ 06
55
+ 03
56
+ d1
57
+ 28
58
+ fa
59
+ 22
60
+ f6
61
+ 0f
62
+ 18
63
+ 1e
64
+ df
65
+ f4
66
+ fd
67
+ 07
68
+ 1d
69
+ d8
70
+ 11
71
+ e3
72
+ ed
73
+ 07
74
+ fa
75
+ 03
76
+ f8
77
+ e8
78
+ 22
79
+ ea
80
+ ec
81
+ f4
82
+ fc
83
+ 1c
84
+ ee
85
+ d3
86
+ e7
87
+ 11
88
+ df
89
+ 01
90
+ 1f
91
+ dd
92
+ 00
93
+ fa
94
+ d0
95
+ ed
96
+ 14
97
+ 05
98
+ fe
99
+ 08
100
+ 10
101
+ f7
102
+ c1
103
+ 23
104
+ eb
105
+ 0f
106
+ e1
107
+ fc
108
+ 12
109
+ f4
110
+ ed
111
+ 1a
112
+ e4
113
+ 24
114
+ 22
115
+ 07
116
+ ed
117
+ 02
118
+ 0d
119
+ fd
120
+ 36
121
+ ef
122
+ f8
123
+ fd
124
+ ef
125
+ f3
126
+ e6
127
+ 03
128
+ 0e
129
+ f5
130
+ 02
131
+ 17
132
+ eb
133
+ fe
134
+ 06
135
+ f1
136
+ 28
137
+ d7
138
+ 1e
139
+ 0f
140
+ fc
141
+ d0
142
+ 0d
143
+ e5
144
+ f9
145
+ fe
146
+ 07
147
+ f3
148
+ fb
149
+ 11
150
+ f8
151
+ 0f
152
+ 07
153
+ 0b
154
+ e8
155
+ 0c
156
+ f6
157
+ e4
158
+ 06
159
+ 0a
160
+ 03
161
+ 46
162
+ 16
163
+ b4
164
+ f2
165
+ ed
166
+ fb
167
+ f0
168
+ f8
169
+ 2d
170
+ 15
171
+ 23
172
+ f7
173
+ 07
174
+ fb
175
+ 0d
176
+ f3
177
+ f2
178
+ 15
179
+ 0d
180
+ ef
181
+ 00
182
+ 17
183
+ 0f
184
+ 12
185
+ 3b
186
+ 17
187
+ f9
188
+ e4
189
+ f5
190
+ 13
191
+ d5
192
+ 05
193
+ 20
194
+ 02
195
+ 0e
196
+ ec
197
+ e0
198
+ 26
199
+ fe
200
+ 04
201
+ 06
202
+ f2
203
+ fc
204
+ 20
205
+ fe
206
+ 22
207
+ e7
208
+ 1d
209
+ f9
210
+ e6
211
+ f1
212
+ 04
213
+ 19
214
+ 14
215
+ 34
216
+ fb
217
+ 07
218
+ ef
219
+ d2
220
+ 14
221
+ e3
222
+ 18
223
+ ec
224
+ ef
225
+ fd
226
+ 19
227
+ f0
228
+ 1e
229
+ 0a
230
+ 30
231
+ f4
232
+ e9
233
+ e3
234
+ 03
235
+ 0e
236
+ eb
237
+ fe
238
+ f4
239
+ fa
240
+ fd
241
+ f0
242
+ f4
243
+ e8
244
+ 03
245
+ ca
246
+ 19
247
+ e1
248
+ f2
249
+ b5
250
+ f6
251
+ 10
252
+ ff
253
+ fc
254
+ 08
255
+ 19
256
+ 27
257
+ 02
258
+ fb
259
+ f0
260
+ 15
261
+ da
262
+ 0a
263
+ 0c
264
+ e3
265
+ fe
266
+ 03
267
+ eb
268
+ f3
269
+ 15
270
+ 1c
271
+ ca
272
+ 11
273
+ fd
274
+ d9
275
+ 08
276
+ f9
277
+ f9
278
+ 34
279
+ da
280
+ f0
281
+ 0d
282
+ 1e
283
+ 42
284
+ 0f
285
+ f7
286
+ d1
287
+ 39
288
+ 0d
289
+ 30
290
+ f6
291
+ 11
292
+ fe
293
+ 08
294
+ d1
295
+ fc
296
+ 07
297
+ e0
298
+ 19
299
+ f3
300
+ fd
301
+ fa
302
+ d8
303
+ 07
304
+ 2f
305
+ 10
306
+ 26
307
+ 10
308
+ 1b
309
+ 10
310
+ 0b
311
+ 0e
312
+ 0a
313
+ 0e
314
+ f2
315
+ f2
316
+ cf
317
+ 12
318
+ fb
319
+ f3
320
+ eb
321
+ f5
322
+ 29
323
+ 2a
324
+ ec
325
+ ea
326
+ 14
327
+ 01
328
+ 06
329
+ e4
330
+ 03
331
+ f9
332
+ d0
333
+ d5
334
+ 1d
335
+ 0a
336
+ eb
337
+ 0e
338
+ e3
339
+ e2
340
+ 15
341
+ f2
342
+ f5
343
+ fb
344
+ 43
345
+ 21
346
+ e0
347
+ e2
348
+ dd
349
+ fe
350
+ 2f
351
+ 0e
352
+ e3
353
+ f8
354
+ fd
355
+ e8
356
+ 05
357
+ 0c
358
+ de
359
+ f7
360
+ 07
361
+ 17
362
+ 02
363
+ 1d
364
+ 0c
365
+ 03
366
+ 29
367
+ de
368
+ d3
369
+ 0d
370
+ c7
371
+ f7
372
+ 0f
373
+ 12
374
+ 08
375
+ 2d
376
+ ea
377
+ 14
378
+ 01
379
+ e4
380
+ 11
381
+ fe
382
+ ee
383
+ e9
384
+ e0
385
+ f8
386
+ 10
387
+ 27
388
+ ed
389
+ 22
390
+ 00
391
+ ce
392
+ 0a
393
+ 15
394
+ 0f
395
+ de
396
+ 0e
397
+ f8
398
+ 1a
399
+ de
400
+ 1a
401
+ 17
402
+ fa
403
+ 24
404
+ fc
405
+ 07
406
+ d6
407
+ f3
408
+ fc
409
+ fe
410
+ 0a
411
+ f6
412
+ ea
413
+ e5
414
+ f3
415
+ 05
416
+ 0f
417
+ f2
418
+ cb
419
+ e9
420
+ d4
421
+ 28
422
+ 0d
423
+ 17
424
+ dd
425
+ 0b
426
+ 0a
427
+ db
428
+ ec
429
+ 15
430
+ f8
431
+ 2e
432
+ f3
433
+ df
434
+ 1a
435
+ 02
436
+ db
437
+ 21
438
+ f5
439
+ d7
440
+ fe
441
+ fd
442
+ dc
443
+ e9
444
+ 12
445
+ cd
446
+ 15
447
+ ee
448
+ f7
449
+ 28
450
+ fa
451
+ 12
452
+ 14
453
+ 20
454
+ f6
455
+ d3
456
+ 17
457
+ 3c
458
+ 05
459
+ 07
460
+ 2a
461
+ cc
462
+ d8
463
+ 24
464
+ 02
465
+ 10
466
+ ed
467
+ d1
468
+ cc
469
+ f8
470
+ ea
471
+ fb
472
+ ee
473
+ eb
474
+ 00
475
+ 0b
476
+ f4
477
+ f3
478
+ ef
479
+ 2b
480
+ 29
481
+ cb
482
+ 25
483
+ 1e
484
+ 06
485
+ 1a
486
+ df
487
+ 0b
488
+ 1c
489
+ ea
490
+ 08
491
+ e6
492
+ fd
493
+ 1d
494
+ f9
495
+ 11
496
+ 18
497
+ f6
498
+ fa
499
+ f3
500
+ 0e
501
+ 1a
502
+ f4
503
+ 0a
504
+ 13
505
+ 25
506
+ e2
507
+ 21
508
+ d0
509
+ fb
510
+ d9
511
+ fa
512
+ e8
513
+ 0d
514
+ fd
515
+ 08
516
+ 0b
517
+ 28
518
+ 14
519
+ ea
520
+ 16
521
+ ae
522
+ 34
523
+ 3c
524
+ df
525
+ 03
526
+ fc
527
+ 1a
528
+ ef
529
+ f7
530
+ 09
531
+ df
532
+ 07
533
+ ef
534
+ f2
535
+ 0e
536
+ f8
537
+ e0
538
+ f5
539
+ f4
540
+ f5
541
+ 29
542
+ e6
543
+ 0a
544
+ 09
545
+ ec
546
+ ff
547
+ 28
548
+ fd
549
+ 18
550
+ bf
551
+ 28
552
+ da
553
+ ee
554
+ 04
555
+ ff
556
+ 24
557
+ 1f
558
+ ef
559
+ fa
560
+ 1a
561
+ ea
562
+ f0
563
+ fe
564
+ 3a
565
+ ec
566
+ 02
567
+ fb
568
+ 0a
569
+ 08
570
+ 13
571
+ 27
572
+ f0
573
+ 01
574
+ 48
575
+ 16
576
+ 02
577
+ e6
578
+ fe
579
+ f8
580
+ 09
581
+ c9
582
+ 18
583
+ 01
584
+ f5
585
+ 22
586
+ f5
587
+ 1e
588
+ e7
589
+ 28
590
+ f1
591
+ f8
592
+ f7
593
+ 19
594
+ e4
595
+ 00
596
+ fa
597
+ 09
598
+ 11
599
+ f4
600
+ 00
601
+ fc
602
+ ff
603
+ fe
604
+ f7
605
+ 03
606
+ 18
607
+ 00
608
+ 10
609
+ f5
610
+ d6
611
+ 26
612
+ f2
613
+ e7
614
+ 04
615
+ 7f
616
+ f2
617
+ 06
618
+ 17
619
+ ed
620
+ 0f
621
+ f4
622
+ e9
623
+ f9
624
+ ee
625
+ 00
626
+ 07
627
+ d7
628
+ 1a
629
+ 0d
630
+ 37
631
+ f2
632
+ 25
633
+ f5
634
+ 0f
635
+ 17
636
+ 1a
637
+ ef
638
+ f6
639
+ f9
640
+ e8
641
+ 0c
642
+ fb
643
+ 0e
644
+ 24
645
+ ea
646
+ f9
647
+ 10
648
+ 11
649
+ 09
650
+ cb
651
+ 01
652
+ fb
653
+ cf
654
+ 0a
655
+ f1
656
+ 02
657
+ 13
658
+ e4
659
+ f6
660
+ 28
661
+ 08
662
+ 35
663
+ 2a
664
+ ff
665
+ f5
666
+ 1a
667
+ 1a
668
+ f4
669
+ 08
670
+ fb
671
+ de
672
+ 08
673
+ e0
674
+ f9
675
+ ea
676
+ 0d
677
+ 25
678
+ 0e
679
+ ee
680
+ fb
681
+ ff
682
+ 09
683
+ 0a
684
+ 24
685
+ f8
686
+ 0f
687
+ 0c
688
+ f7
689
+ 10
690
+ 06
691
+ 0e
692
+ d9
693
+ 1e
694
+ 09
695
+ 35
696
+ e9
697
+ fe
698
+ 00
699
+ 05
700
+ ee
701
+ 0e
702
+ 0a
703
+ 0d
704
+ 2a
705
+ 33
706
+ fb
707
+ 0e
708
+ d5
709
+ ee
710
+ 36
711
+ ee
712
+ 1c
713
+ da
714
+ 0e
715
+ 04
716
+ 10
717
+ e2
718
+ ff
719
+ e6
720
+ 0c
721
+ 20
722
+ de
723
+ 1e
724
+ 0b
725
+ ff
726
+ 0e
727
+ e5
728
+ 0b
729
+ e6
730
+ ef
731
+ fb
732
+ ed
733
+ 15
734
+ 19
735
+ ee
736
+ f8
737
+ 0c
738
+ f8
739
+ 34
740
+ 0e
741
+ 0e
742
+ e2
743
+ 1d
744
+ e5
745
+ ed
746
+ fb
747
+ f2
748
+ 07
749
+ 09
750
+ f1
751
+ 02
752
+ f7
753
+ eb
754
+ ef
755
+ e8
756
+ f4
757
+ f7
758
+ e6
759
+ fb
760
+ e9
761
+ e8
762
+ 1e
763
+ fd
764
+ 0d
765
+ d2
766
+ 00
767
+ fe
768
+ fb
circuit/rom/person_reg_b.hex ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ 6e
2
+ 7f
3
+ 6e
4
+ 7f
circuit/rom/person_reg_w.hex ADDED
@@ -0,0 +1,3072 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 11
2
+ 05
3
+ ee
4
+ 47
5
+ 1b
6
+ d6
7
+ f4
8
+ e9
9
+ 25
10
+ fd
11
+ e3
12
+ ee
13
+ ee
14
+ 03
15
+ 02
16
+ f7
17
+ fd
18
+ e9
19
+ 02
20
+ 04
21
+ 04
22
+ 0d
23
+ 0c
24
+ d7
25
+ ef
26
+ f4
27
+ 21
28
+ fd
29
+ c4
30
+ 12
31
+ e9
32
+ e3
33
+ cd
34
+ e4
35
+ 45
36
+ 2e
37
+ e8
38
+ 2c
39
+ ff
40
+ d6
41
+ 03
42
+ e7
43
+ 27
44
+ ef
45
+ 30
46
+ 48
47
+ fc
48
+ f9
49
+ e0
50
+ fd
51
+ ed
52
+ cf
53
+ 0b
54
+ 19
55
+ fb
56
+ f4
57
+ 08
58
+ df
59
+ 30
60
+ 1b
61
+ 2b
62
+ 18
63
+ f4
64
+ ce
65
+ de
66
+ 0e
67
+ f5
68
+ 14
69
+ dd
70
+ 01
71
+ c2
72
+ d5
73
+ ee
74
+ f6
75
+ 04
76
+ 03
77
+ fb
78
+ 24
79
+ d0
80
+ 17
81
+ 0b
82
+ 06
83
+ 2d
84
+ e6
85
+ c1
86
+ e8
87
+ 4c
88
+ fa
89
+ 08
90
+ 29
91
+ fa
92
+ 06
93
+ e8
94
+ be
95
+ 05
96
+ 04
97
+ 1e
98
+ 18
99
+ 00
100
+ 22
101
+ e5
102
+ c5
103
+ f2
104
+ e4
105
+ 12
106
+ da
107
+ e9
108
+ 18
109
+ f6
110
+ 0e
111
+ 15
112
+ e5
113
+ 09
114
+ 08
115
+ 13
116
+ e0
117
+ 0a
118
+ 02
119
+ c2
120
+ 15
121
+ ea
122
+ fe
123
+ f5
124
+ e9
125
+ f4
126
+ fc
127
+ 2f
128
+ 30
129
+ d5
130
+ 22
131
+ 14
132
+ bd
133
+ 06
134
+ 1a
135
+ 11
136
+ 2e
137
+ c1
138
+ 33
139
+ 10
140
+ d6
141
+ c7
142
+ 07
143
+ d1
144
+ e8
145
+ e4
146
+ 0d
147
+ 04
148
+ e1
149
+ 2d
150
+ 0e
151
+ 23
152
+ fd
153
+ 31
154
+ da
155
+ 00
156
+ dc
157
+ c0
158
+ f8
159
+ 0d
160
+ 03
161
+ 23
162
+ 09
163
+ ee
164
+ a8
165
+ db
166
+ 07
167
+ eb
168
+ fc
169
+ 28
170
+ 14
171
+ 08
172
+ d2
173
+ 01
174
+ 16
175
+ 09
176
+ f5
177
+ e0
178
+ 1e
179
+ 17
180
+ 09
181
+ 03
182
+ 15
183
+ 0d
184
+ 0f
185
+ 3b
186
+ 20
187
+ e6
188
+ de
189
+ 03
190
+ 14
191
+ d2
192
+ ed
193
+ 0c
194
+ fe
195
+ e7
196
+ e4
197
+ d5
198
+ 09
199
+ fc
200
+ 22
201
+ 06
202
+ 0e
203
+ f2
204
+ 1f
205
+ 02
206
+ f4
207
+ f4
208
+ 17
209
+ fb
210
+ e9
211
+ 15
212
+ f9
213
+ 20
214
+ 0e
215
+ 32
216
+ f9
217
+ e3
218
+ fc
219
+ e9
220
+ 10
221
+ de
222
+ f7
223
+ f8
224
+ f0
225
+ f7
226
+ 2b
227
+ eb
228
+ 3a
229
+ 28
230
+ 2d
231
+ fe
232
+ e6
233
+ f1
234
+ ea
235
+ e0
236
+ 08
237
+ 08
238
+ 11
239
+ 23
240
+ 0a
241
+ 0b
242
+ eb
243
+ fa
244
+ 0b
245
+ cb
246
+ 02
247
+ d5
248
+ f5
249
+ a1
250
+ 04
251
+ 21
252
+ 02
253
+ f7
254
+ 06
255
+ 3e
256
+ 1c
257
+ e2
258
+ c4
259
+ fa
260
+ 0a
261
+ 02
262
+ 07
263
+ 01
264
+ 01
265
+ ef
266
+ 11
267
+ ec
268
+ fb
269
+ 23
270
+ 03
271
+ bc
272
+ 07
273
+ e1
274
+ de
275
+ 0c
276
+ e9
277
+ 02
278
+ 17
279
+ b8
280
+ cb
281
+ 01
282
+ 13
283
+ 3e
284
+ e4
285
+ fd
286
+ d4
287
+ e1
288
+ 1c
289
+ 22
290
+ f8
291
+ 04
292
+ 13
293
+ 0b
294
+ c5
295
+ fe
296
+ 03
297
+ 04
298
+ 25
299
+ cb
300
+ 11
301
+ 03
302
+ e9
303
+ 04
304
+ 18
305
+ 0d
306
+ fb
307
+ f9
308
+ 0f
309
+ 1c
310
+ 02
311
+ e4
312
+ e5
313
+ cd
314
+ 07
315
+ eb
316
+ ed
317
+ 04
318
+ e3
319
+ ee
320
+ f3
321
+ f0
322
+ 23
323
+ 6b
324
+ cc
325
+ d1
326
+ 15
327
+ f3
328
+ dd
329
+ ed
330
+ 0e
331
+ e1
332
+ da
333
+ da
334
+ 0d
335
+ 0c
336
+ fb
337
+ 04
338
+ 05
339
+ e9
340
+ 28
341
+ de
342
+ 03
343
+ f8
344
+ 38
345
+ 22
346
+ df
347
+ e1
348
+ e0
349
+ d8
350
+ 36
351
+ 00
352
+ d3
353
+ ef
354
+ 04
355
+ 0a
356
+ 01
357
+ 42
358
+ 26
359
+ fc
360
+ 11
361
+ 0b
362
+ 0f
363
+ 06
364
+ 28
365
+ e9
366
+ 16
367
+ 02
368
+ c9
369
+ ff
370
+ eb
371
+ ee
372
+ 23
373
+ ef
374
+ 17
375
+ 18
376
+ dc
377
+ 14
378
+ d9
379
+ e6
380
+ 1a
381
+ 07
382
+ c6
383
+ e2
384
+ e1
385
+ 06
386
+ 12
387
+ 22
388
+ e8
389
+ 1d
390
+ 10
391
+ c9
392
+ 2f
393
+ 0e
394
+ dc
395
+ c2
396
+ 45
397
+ 16
398
+ 11
399
+ ff
400
+ 30
401
+ 1f
402
+ 08
403
+ 08
404
+ ee
405
+ 09
406
+ e1
407
+ e9
408
+ 24
409
+ ec
410
+ 07
411
+ 06
412
+ 06
413
+ 09
414
+ f6
415
+ 22
416
+ 09
417
+ 0e
418
+ e1
419
+ f0
420
+ f6
421
+ 49
422
+ 0e
423
+ 13
424
+ 15
425
+ 0c
426
+ 03
427
+ f1
428
+ fb
429
+ 01
430
+ fd
431
+ 37
432
+ e8
433
+ d0
434
+ 0b
435
+ 05
436
+ be
437
+ 1e
438
+ 17
439
+ dd
440
+ 24
441
+ 06
442
+ d6
443
+ ed
444
+ 15
445
+ fc
446
+ 08
447
+ d7
448
+ d4
449
+ 31
450
+ d5
451
+ 0d
452
+ 06
453
+ 12
454
+ 04
455
+ be
456
+ 13
457
+ 42
458
+ f3
459
+ 14
460
+ 2b
461
+ bc
462
+ bf
463
+ 17
464
+ ec
465
+ f2
466
+ e4
467
+ d2
468
+ ce
469
+ e4
470
+ ef
471
+ f6
472
+ fa
473
+ 0a
474
+ 0c
475
+ 09
476
+ fb
477
+ 18
478
+ 06
479
+ 19
480
+ 02
481
+ cb
482
+ 07
483
+ 1d
484
+ 18
485
+ 30
486
+ 08
487
+ 17
488
+ 1f
489
+ f6
490
+ 21
491
+ e3
492
+ 3f
493
+ 21
494
+ f0
495
+ 01
496
+ 08
497
+ 1c
498
+ f4
499
+ f8
500
+ 04
501
+ f2
502
+ 13
503
+ f6
504
+ 1f
505
+ 23
506
+ ce
507
+ 36
508
+ db
509
+ e8
510
+ c8
511
+ fa
512
+ dd
513
+ 09
514
+ 19
515
+ 10
516
+ 05
517
+ 16
518
+ 0a
519
+ 12
520
+ fc
521
+ 98
522
+ 25
523
+ 41
524
+ f9
525
+ 03
526
+ f9
527
+ 0f
528
+ 04
529
+ fe
530
+ 0f
531
+ c6
532
+ 06
533
+ 08
534
+ ef
535
+ 1d
536
+ 0b
537
+ cb
538
+ 0b
539
+ 0d
540
+ e1
541
+ 3d
542
+ f4
543
+ f3
544
+ 14
545
+ fb
546
+ 0f
547
+ 3a
548
+ f7
549
+ 24
550
+ d2
551
+ 26
552
+ c5
553
+ 02
554
+ 20
555
+ fe
556
+ 26
557
+ 35
558
+ f2
559
+ ed
560
+ 26
561
+ f4
562
+ cb
563
+ 09
564
+ 16
565
+ 03
566
+ ef
567
+ fd
568
+ 11
569
+ 00
570
+ 10
571
+ 3e
572
+ 02
573
+ 07
574
+ 46
575
+ 21
576
+ 3e
577
+ dc
578
+ e8
579
+ 00
580
+ ed
581
+ bb
582
+ 16
583
+ ee
584
+ d7
585
+ 24
586
+ e7
587
+ 1c
588
+ f8
589
+ 50
590
+ 0a
591
+ f2
592
+ 09
593
+ 16
594
+ f1
595
+ 19
596
+ c4
597
+ 25
598
+ 1e
599
+ e5
600
+ 12
601
+ f4
602
+ 24
603
+ 16
604
+ 02
605
+ ff
606
+ 31
607
+ fc
608
+ f7
609
+ 05
610
+ ee
611
+ 1f
612
+ 35
613
+ cc
614
+ 03
615
+ 76
616
+ f1
617
+ 2b
618
+ 36
619
+ e7
620
+ f7
621
+ e7
622
+ eb
623
+ 15
624
+ e3
625
+ e7
626
+ 1c
627
+ ea
628
+ 22
629
+ 2b
630
+ 3a
631
+ 0e
632
+ 05
633
+ f1
634
+ fb
635
+ 17
636
+ 2f
637
+ 0c
638
+ f3
639
+ f4
640
+ e0
641
+ 1b
642
+ 05
643
+ 09
644
+ 09
645
+ 01
646
+ 04
647
+ 08
648
+ 23
649
+ fd
650
+ 03
651
+ 16
652
+ 07
653
+ d9
654
+ 12
655
+ ea
656
+ ff
657
+ 15
658
+ dd
659
+ f6
660
+ 1f
661
+ 1d
662
+ 27
663
+ 02
664
+ f3
665
+ ff
666
+ 0b
667
+ 09
668
+ 08
669
+ e7
670
+ db
671
+ c5
672
+ ea
673
+ 03
674
+ ed
675
+ e6
676
+ ff
677
+ d1
678
+ e4
679
+ e2
680
+ fb
681
+ ff
682
+ 0b
683
+ 38
684
+ 05
685
+ 01
686
+ 1d
687
+ 0c
688
+ 0a
689
+ 2b
690
+ 22
691
+ 18
692
+ df
693
+ 14
694
+ f5
695
+ 0b
696
+ ec
697
+ e7
698
+ f0
699
+ 00
700
+ e1
701
+ 09
702
+ f1
703
+ 13
704
+ 2a
705
+ 20
706
+ f8
707
+ 20
708
+ cd
709
+ 04
710
+ 31
711
+ fa
712
+ 0f
713
+ c7
714
+ 1a
715
+ 12
716
+ 20
717
+ f8
718
+ de
719
+ f2
720
+ 34
721
+ 2b
722
+ e6
723
+ 21
724
+ fd
725
+ 0a
726
+ 31
727
+ 16
728
+ f7
729
+ 14
730
+ f6
731
+ f6
732
+ f9
733
+ 39
734
+ 3d
735
+ 1c
736
+ fd
737
+ fb
738
+ f8
739
+ 29
740
+ 06
741
+ f8
742
+ ff
743
+ 0a
744
+ de
745
+ f4
746
+ ee
747
+ de
748
+ 16
749
+ e5
750
+ d5
751
+ 04
752
+ ff
753
+ ed
754
+ d3
755
+ e9
756
+ ff
757
+ 00
758
+ f3
759
+ cc
760
+ f8
761
+ dd
762
+ f4
763
+ 29
764
+ fa
765
+ d0
766
+ 0a
767
+ 02
768
+ 13
769
+ 07
770
+ f4
771
+ e8
772
+ 47
773
+ 2e
774
+ f0
775
+ 0b
776
+ eb
777
+ 2f
778
+ e5
779
+ ed
780
+ fe
781
+ e7
782
+ f2
783
+ 05
784
+ f9
785
+ ff
786
+ 04
787
+ 18
788
+ e6
789
+ f9
790
+ 16
791
+ 16
792
+ e5
793
+ e4
794
+ ed
795
+ 3d
796
+ 21
797
+ cf
798
+ 24
799
+ e0
800
+ 05
801
+ dd
802
+ fc
803
+ 1e
804
+ 14
805
+ 0a
806
+ f0
807
+ 02
808
+ d1
809
+ 21
810
+ d5
811
+ 0e
812
+ ef
813
+ 1c
814
+ 2a
815
+ 28
816
+ f9
817
+ ed
818
+ 0a
819
+ e5
820
+ e2
821
+ 21
822
+ 11
823
+ fc
824
+ 09
825
+ 0e
826
+ f8
827
+ 1d
828
+ 0f
829
+ 1e
830
+ 1d
831
+ 08
832
+ df
833
+ e4
834
+ 1b
835
+ df
836
+ 04
837
+ de
838
+ 0c
839
+ ca
840
+ e0
841
+ e9
842
+ 0d
843
+ ff
844
+ f5
845
+ f7
846
+ 44
847
+ cd
848
+ 01
849
+ 1f
850
+ f4
851
+ 1c
852
+ c8
853
+ d9
854
+ d2
855
+ 32
856
+ 03
857
+ 00
858
+ 1b
859
+ fa
860
+ f6
861
+ ec
862
+ df
863
+ 14
864
+ 0c
865
+ 16
866
+ 1c
867
+ f9
868
+ 0f
869
+ cd
870
+ eb
871
+ ee
872
+ e1
873
+ fc
874
+ dc
875
+ e8
876
+ 0e
877
+ 10
878
+ f9
879
+ 21
880
+ f4
881
+ fa
882
+ f8
883
+ 0d
884
+ de
885
+ 1f
886
+ 07
887
+ df
888
+ 1b
889
+ ec
890
+ fa
891
+ 03
892
+ fc
893
+ 07
894
+ fb
895
+ 05
896
+ 26
897
+ 0b
898
+ d1
899
+ 0b
900
+ d3
901
+ 29
902
+ 0d
903
+ f2
904
+ 20
905
+ b8
906
+ 28
907
+ 04
908
+ c4
909
+ de
910
+ 10
911
+ e3
912
+ 00
913
+ ee
914
+ 09
915
+ 09
916
+ ec
917
+ 1a
918
+ fd
919
+ 37
920
+ 0f
921
+ 24
922
+ e5
923
+ 10
924
+ 19
925
+ bd
926
+ f2
927
+ 0f
928
+ 02
929
+ 35
930
+ f2
931
+ f6
932
+ 29
933
+ f8
934
+ ec
935
+ fb
936
+ f5
937
+ 22
938
+ 02
939
+ 01
940
+ f1
941
+ 09
942
+ f8
943
+ f1
944
+ dc
945
+ 07
946
+ 1c
947
+ 2a
948
+ fa
949
+ 10
950
+ 14
951
+ f4
952
+ 05
953
+ 45
954
+ 26
955
+ e6
956
+ f4
957
+ ff
958
+ 19
959
+ c9
960
+ da
961
+ 0a
962
+ 05
963
+ 12
964
+ dd
965
+ f9
966
+ f4
967
+ 24
968
+ fb
969
+ 04
970
+ 13
971
+ 03
972
+ 54
973
+ 09
974
+ 14
975
+ 19
976
+ 1e
977
+ 01
978
+ d0
979
+ 1a
980
+ f2
981
+ 44
982
+ 08
983
+ 38
984
+ 2f
985
+ f9
986
+ 03
987
+ e5
988
+ 09
989
+ da
990
+ ff
991
+ e8
992
+ fb
993
+ f8
994
+ 14
995
+ e3
996
+ 12
997
+ 0e
998
+ 11
999
+ ff
1000
+ f0
1001
+ e7
1002
+ 01
1003
+ fe
1004
+ db
1005
+ f0
1006
+ 1e
1007
+ 21
1008
+ 1e
1009
+ 05
1010
+ e7
1011
+ e9
1012
+ 03
1013
+ fd
1014
+ 01
1015
+ ef
1016
+ 10
1017
+ dc
1018
+ 00
1019
+ 19
1020
+ 13
1021
+ e7
1022
+ 15
1023
+ 07
1024
+ 36
1025
+ fc
1026
+ d9
1027
+ ff
1028
+ 0f
1029
+ fa
1030
+ 01
1031
+ 05
1032
+ 3e
1033
+ 06
1034
+ 01
1035
+ 0e
1036
+ ed
1037
+ 1f
1038
+ 00
1039
+ d4
1040
+ e7
1041
+ ee
1042
+ ce
1043
+ eb
1044
+ e5
1045
+ 01
1046
+ 1d
1047
+ c5
1048
+ df
1049
+ 2c
1050
+ 08
1051
+ 15
1052
+ dd
1053
+ e9
1054
+ 01
1055
+ f6
1056
+ 20
1057
+ fa
1058
+ d5
1059
+ 0a
1060
+ 13
1061
+ ef
1062
+ cc
1063
+ f4
1064
+ fd
1065
+ 0d
1066
+ 26
1067
+ e8
1068
+ 13
1069
+ fe
1070
+ dd
1071
+ fc
1072
+ 13
1073
+ eb
1074
+ 10
1075
+ 0a
1076
+ 0e
1077
+ 10
1078
+ 13
1079
+ f1
1080
+ 11
1081
+ e6
1082
+ e0
1083
+ f0
1084
+ e5
1085
+ 0d
1086
+ d2
1087
+ f6
1088
+ 12
1089
+ ec
1090
+ 2e
1091
+ 2d
1092
+ cf
1093
+ ec
1094
+ f8
1095
+ ff
1096
+ fc
1097
+ eb
1098
+ fd
1099
+ e1
1100
+ ef
1101
+ ed
1102
+ 05
1103
+ 06
1104
+ ff
1105
+ df
1106
+ 0b
1107
+ ec
1108
+ 13
1109
+ e6
1110
+ 03
1111
+ ff
1112
+ 32
1113
+ 17
1114
+ eb
1115
+ f3
1116
+ ef
1117
+ e7
1118
+ 29
1119
+ f2
1120
+ e6
1121
+ ea
1122
+ fa
1123
+ ee
1124
+ ed
1125
+ 18
1126
+ 13
1127
+ 0c
1128
+ 0a
1129
+ 18
1130
+ 1c
1131
+ 19
1132
+ 1a
1133
+ e5
1134
+ 22
1135
+ 07
1136
+ dd
1137
+ 10
1138
+ d4
1139
+ 02
1140
+ 2b
1141
+ db
1142
+ 20
1143
+ 27
1144
+ de
1145
+ 10
1146
+ c8
1147
+ e9
1148
+ 03
1149
+ 18
1150
+ e4
1151
+ fe
1152
+ e7
1153
+ 13
1154
+ 00
1155
+ 33
1156
+ ed
1157
+ 07
1158
+ ff
1159
+ d8
1160
+ f7
1161
+ 05
1162
+ d1
1163
+ dc
1164
+ 26
1165
+ 0d
1166
+ f8
1167
+ 06
1168
+ 36
1169
+ 01
1170
+ 13
1171
+ 16
1172
+ 05
1173
+ 15
1174
+ f0
1175
+ e1
1176
+ 03
1177
+ 09
1178
+ ed
1179
+ 15
1180
+ ef
1181
+ 0b
1182
+ dd
1183
+ 1e
1184
+ 0a
1185
+ f7
1186
+ e1
1187
+ dd
1188
+ ec
1189
+ 36
1190
+ fe
1191
+ 27
1192
+ 02
1193
+ e3
1194
+ 0e
1195
+ d5
1196
+ f8
1197
+ 14
1198
+ f5
1199
+ 09
1200
+ e9
1201
+ f3
1202
+ 1a
1203
+ e3
1204
+ da
1205
+ 23
1206
+ fd
1207
+ d4
1208
+ 3e
1209
+ 12
1210
+ dd
1211
+ d7
1212
+ 0f
1213
+ da
1214
+ 08
1215
+ df
1216
+ ef
1217
+ 24
1218
+ da
1219
+ 16
1220
+ 18
1221
+ 15
1222
+ f5
1223
+ b8
1224
+ 1d
1225
+ 3f
1226
+ 06
1227
+ 1a
1228
+ 18
1229
+ da
1230
+ d4
1231
+ 0c
1232
+ f6
1233
+ 26
1234
+ d1
1235
+ de
1236
+ f9
1237
+ e6
1238
+ f2
1239
+ f8
1240
+ fe
1241
+ 14
1242
+ 0d
1243
+ 0f
1244
+ 01
1245
+ 04
1246
+ fb
1247
+ 23
1248
+ 1c
1249
+ de
1250
+ 01
1251
+ 0d
1252
+ 10
1253
+ 22
1254
+ f9
1255
+ 07
1256
+ 25
1257
+ cb
1258
+ 0a
1259
+ ea
1260
+ 0f
1261
+ 03
1262
+ f3
1263
+ 05
1264
+ 56
1265
+ 39
1266
+ 14
1267
+ fd
1268
+ 16
1269
+ 1c
1270
+ 03
1271
+ 00
1272
+ 04
1273
+ ef
1274
+ cd
1275
+ 1a
1276
+ c3
1277
+ 01
1278
+ bc
1279
+ ea
1280
+ ed
1281
+ 0f
1282
+ 16
1283
+ 00
1284
+ f6
1285
+ f8
1286
+ 2e
1287
+ 26
1288
+ 10
1289
+ a1
1290
+ 24
1291
+ 45
1292
+ 0c
1293
+ fa
1294
+ 02
1295
+ fb
1296
+ 1c
1297
+ 01
1298
+ 17
1299
+ e0
1300
+ 0c
1301
+ fb
1302
+ eb
1303
+ 02
1304
+ f1
1305
+ e9
1306
+ 12
1307
+ e9
1308
+ e0
1309
+ 22
1310
+ eb
1311
+ f9
1312
+ fb
1313
+ 12
1314
+ 0b
1315
+ 22
1316
+ f8
1317
+ 11
1318
+ ee
1319
+ 16
1320
+ d9
1321
+ eb
1322
+ 1c
1323
+ eb
1324
+ 1a
1325
+ 36
1326
+ db
1327
+ cb
1328
+ 24
1329
+ 01
1330
+ 07
1331
+ f9
1332
+ 0a
1333
+ 32
1334
+ fb
1335
+ f2
1336
+ 10
1337
+ 10
1338
+ f6
1339
+ 15
1340
+ e8
1341
+ f6
1342
+ 45
1343
+ 2b
1344
+ 20
1345
+ da
1346
+ a9
1347
+ e0
1348
+ f9
1349
+ c7
1350
+ 02
1351
+ e9
1352
+ 00
1353
+ 27
1354
+ fe
1355
+ 31
1356
+ fb
1357
+ 39
1358
+ da
1359
+ f3
1360
+ 12
1361
+ 21
1362
+ f0
1363
+ 01
1364
+ e9
1365
+ 13
1366
+ 19
1367
+ e8
1368
+ 08
1369
+ e5
1370
+ fb
1371
+ 24
1372
+ ef
1373
+ 03
1374
+ 0d
1375
+ f5
1376
+ f2
1377
+ 0b
1378
+ fc
1379
+ 10
1380
+ 35
1381
+ d0
1382
+ f9
1383
+ 7f
1384
+ 04
1385
+ 34
1386
+ 32
1387
+ e5
1388
+ ec
1389
+ 0c
1390
+ f1
1391
+ fa
1392
+ e6
1393
+ f9
1394
+ 2b
1395
+ f8
1396
+ f4
1397
+ 3f
1398
+ 31
1399
+ fc
1400
+ 22
1401
+ ea
1402
+ ff
1403
+ 06
1404
+ 2e
1405
+ 04
1406
+ f6
1407
+ 09
1408
+ dc
1409
+ 0b
1410
+ fe
1411
+ f8
1412
+ 0b
1413
+ f1
1414
+ 00
1415
+ 2d
1416
+ 36
1417
+ dc
1418
+ f9
1419
+ fe
1420
+ fa
1421
+ de
1422
+ 17
1423
+ f2
1424
+ 0c
1425
+ 11
1426
+ e3
1427
+ 17
1428
+ 0b
1429
+ 0d
1430
+ fc
1431
+ 09
1432
+ ee
1433
+ 03
1434
+ 07
1435
+ 14
1436
+ 0b
1437
+ ed
1438
+ 0e
1439
+ d2
1440
+ fa
1441
+ d6
1442
+ 11
1443
+ d8
1444
+ fc
1445
+ 0c
1446
+ fb
1447
+ cf
1448
+ e9
1449
+ ea
1450
+ 06
1451
+ 16
1452
+ 18
1453
+ f8
1454
+ 14
1455
+ 00
1456
+ d8
1457
+ 01
1458
+ 1c
1459
+ 07
1460
+ df
1461
+ 01
1462
+ f0
1463
+ ed
1464
+ 14
1465
+ f3
1466
+ da
1467
+ ff
1468
+ e2
1469
+ fe
1470
+ 09
1471
+ 24
1472
+ 45
1473
+ 16
1474
+ fc
1475
+ 00
1476
+ df
1477
+ f2
1478
+ 1b
1479
+ f6
1480
+ 10
1481
+ d0
1482
+ 23
1483
+ 20
1484
+ 21
1485
+ 9d
1486
+ ef
1487
+ fe
1488
+ 02
1489
+ 0c
1490
+ ea
1491
+ 14
1492
+ 09
1493
+ 15
1494
+ 1f
1495
+ 25
1496
+ d8
1497
+ 19
1498
+ 0d
1499
+ e8
1500
+ 04
1501
+ f3
1502
+ 2e
1503
+ 02
1504
+ f9
1505
+ f2
1506
+ f5
1507
+ 1a
1508
+ e3
1509
+ e9
1510
+ d6
1511
+ 02
1512
+ ee
1513
+ 02
1514
+ f9
1515
+ e9
1516
+ 2f
1517
+ ed
1518
+ ca
1519
+ 0b
1520
+ 01
1521
+ 19
1522
+ 11
1523
+ ea
1524
+ ef
1525
+ 26
1526
+ e2
1527
+ ef
1528
+ f4
1529
+ fb
1530
+ e5
1531
+ f0
1532
+ 1e
1533
+ e6
1534
+ 05
1535
+ f9
1536
+ 00
1537
+ fa
1538
+ 09
1539
+ fb
1540
+ 2a
1541
+ 22
1542
+ fe
1543
+ fc
1544
+ 1d
1545
+ 11
1546
+ fc
1547
+ e9
1548
+ da
1549
+ f7
1550
+ 02
1551
+ f2
1552
+ f0
1553
+ d7
1554
+ f1
1555
+ 07
1556
+ 0b
1557
+ 0d
1558
+ 04
1559
+ 0d
1560
+ bf
1561
+ f2
1562
+ ee
1563
+ 3d
1564
+ 12
1565
+ e1
1566
+ 2a
1567
+ e2
1568
+ d3
1569
+ e3
1570
+ f1
1571
+ 0d
1572
+ 11
1573
+ e4
1574
+ e9
1575
+ 01
1576
+ f1
1577
+ 16
1578
+ e0
1579
+ 0a
1580
+ db
1581
+ 1e
1582
+ 41
1583
+ 14
1584
+ ef
1585
+ e4
1586
+ 04
1587
+ 07
1588
+ d4
1589
+ 01
1590
+ f2
1591
+ 19
1592
+ fd
1593
+ 20
1594
+ d5
1595
+ 29
1596
+ 19
1597
+ 0f
1598
+ 0c
1599
+ 08
1600
+ da
1601
+ d9
1602
+ 0e
1603
+ e9
1604
+ f7
1605
+ d8
1606
+ 27
1607
+ f0
1608
+ e9
1609
+ e7
1610
+ ff
1611
+ 1a
1612
+ f5
1613
+ d1
1614
+ 42
1615
+ c9
1616
+ ef
1617
+ 12
1618
+ f6
1619
+ 1c
1620
+ c1
1621
+ da
1622
+ eb
1623
+ 30
1624
+ ec
1625
+ f8
1626
+ 39
1627
+ fc
1628
+ d8
1629
+ ec
1630
+ c1
1631
+ 01
1632
+ 1d
1633
+ 18
1634
+ 27
1635
+ fa
1636
+ 21
1637
+ fc
1638
+ e8
1639
+ f7
1640
+ f1
1641
+ 06
1642
+ db
1643
+ ea
1644
+ 2e
1645
+ ea
1646
+ 02
1647
+ 06
1648
+ d7
1649
+ fa
1650
+ 09
1651
+ 10
1652
+ fc
1653
+ 17
1654
+ 0a
1655
+ ed
1656
+ 26
1657
+ eb
1658
+ e5
1659
+ 0c
1660
+ f2
1661
+ ff
1662
+ f9
1663
+ 0d
1664
+ 12
1665
+ 11
1666
+ f7
1667
+ 0e
1668
+ bb
1669
+ 22
1670
+ 1a
1671
+ ce
1672
+ 2b
1673
+ be
1674
+ 08
1675
+ 11
1676
+ d1
1677
+ ea
1678
+ 1f
1679
+ 02
1680
+ f0
1681
+ fe
1682
+ fc
1683
+ 12
1684
+ f7
1685
+ 1c
1686
+ 1b
1687
+ 09
1688
+ 06
1689
+ 04
1690
+ da
1691
+ 04
1692
+ f6
1693
+ c5
1694
+ ff
1695
+ fe
1696
+ 01
1697
+ 50
1698
+ 24
1699
+ bd
1700
+ 21
1701
+ e2
1702
+ 07
1703
+ f8
1704
+ 13
1705
+ 1e
1706
+ fd
1707
+ f2
1708
+ 0d
1709
+ fb
1710
+ 18
1711
+ 0a
1712
+ e1
1713
+ 01
1714
+ 22
1715
+ 08
1716
+ 11
1717
+ 20
1718
+ 16
1719
+ 12
1720
+ 23
1721
+ 40
1722
+ 28
1723
+ f1
1724
+ e8
1725
+ fd
1726
+ 15
1727
+ e4
1728
+ e8
1729
+ 03
1730
+ 0d
1731
+ 1f
1732
+ ea
1733
+ d7
1734
+ 11
1735
+ 03
1736
+ 21
1737
+ f2
1738
+ 18
1739
+ 00
1740
+ 1e
1741
+ 0b
1742
+ 3b
1743
+ 03
1744
+ 0f
1745
+ f8
1746
+ e2
1747
+ 10
1748
+ 0c
1749
+ 24
1750
+ fe
1751
+ 38
1752
+ 0c
1753
+ ef
1754
+ 0c
1755
+ d4
1756
+ 40
1757
+ dc
1758
+ 1e
1759
+ e1
1760
+ f6
1761
+ f4
1762
+ 15
1763
+ ec
1764
+ 1e
1765
+ 2c
1766
+ 1f
1767
+ 08
1768
+ de
1769
+ e5
1770
+ 08
1771
+ f8
1772
+ e1
1773
+ 01
1774
+ 13
1775
+ 02
1776
+ f5
1777
+ 09
1778
+ 10
1779
+ f6
1780
+ 05
1781
+ e0
1782
+ 0a
1783
+ dc
1784
+ 04
1785
+ db
1786
+ f8
1787
+ 27
1788
+ 03
1789
+ ea
1790
+ 01
1791
+ e4
1792
+ 2f
1793
+ e5
1794
+ e7
1795
+ ff
1796
+ 19
1797
+ f3
1798
+ df
1799
+ 0f
1800
+ 38
1801
+ ff
1802
+ 05
1803
+ 07
1804
+ e0
1805
+ 06
1806
+ 02
1807
+ cb
1808
+ fb
1809
+ ec
1810
+ fd
1811
+ 18
1812
+ f5
1813
+ e4
1814
+ 36
1815
+ ec
1816
+ f1
1817
+ f9
1818
+ 08
1819
+ 27
1820
+ dc
1821
+ 0b
1822
+ e5
1823
+ 3f
1824
+ 17
1825
+ fd
1826
+ fb
1827
+ 08
1828
+ 04
1829
+ f0
1830
+ cc
1831
+ fd
1832
+ 01
1833
+ 0e
1834
+ 2b
1835
+ db
1836
+ 23
1837
+ f7
1838
+ e4
1839
+ f0
1840
+ 06
1841
+ 06
1842
+ 02
1843
+ 1c
1844
+ 04
1845
+ 1c
1846
+ 26
1847
+ e6
1848
+ 03
1849
+ fd
1850
+ f2
1851
+ d2
1852
+ e0
1853
+ 10
1854
+ fe
1855
+ ef
1856
+ 12
1857
+ f6
1858
+ 29
1859
+ 33
1860
+ d8
1861
+ d7
1862
+ f6
1863
+ 03
1864
+ 0a
1865
+ cf
1866
+ 13
1867
+ e7
1868
+ ca
1869
+ da
1870
+ 18
1871
+ ef
1872
+ 00
1873
+ 24
1874
+ 0b
1875
+ f6
1876
+ 20
1877
+ e4
1878
+ fa
1879
+ f1
1880
+ 3b
1881
+ 1e
1882
+ d4
1883
+ d7
1884
+ ec
1885
+ d7
1886
+ 28
1887
+ 0f
1888
+ e4
1889
+ f5
1890
+ 18
1891
+ e4
1892
+ 19
1893
+ fd
1894
+ df
1895
+ 03
1896
+ 1a
1897
+ 1a
1898
+ 0c
1899
+ 13
1900
+ 09
1901
+ e3
1902
+ 25
1903
+ 25
1904
+ e8
1905
+ 17
1906
+ d5
1907
+ f9
1908
+ fe
1909
+ f9
1910
+ 17
1911
+ 34
1912
+ d4
1913
+ 0c
1914
+ e0
1915
+ 0a
1916
+ 2a
1917
+ 03
1918
+ da
1919
+ e0
1920
+ e5
1921
+ 12
1922
+ 08
1923
+ 67
1924
+ d1
1925
+ 24
1926
+ 00
1927
+ ef
1928
+ 13
1929
+ 0e
1930
+ cf
1931
+ c8
1932
+ 29
1933
+ 03
1934
+ 0f
1935
+ f5
1936
+ 1f
1937
+ 1f
1938
+ f3
1939
+ 06
1940
+ d9
1941
+ 00
1942
+ de
1943
+ fc
1944
+ 05
1945
+ 1d
1946
+ e1
1947
+ f4
1948
+ f0
1949
+ 01
1950
+ fc
1951
+ 23
1952
+ 2a
1953
+ 0c
1954
+ cf
1955
+ d8
1956
+ e0
1957
+ 21
1958
+ fb
1959
+ 19
1960
+ ed
1961
+ f3
1962
+ 13
1963
+ c2
1964
+ 06
1965
+ 0b
1966
+ f6
1967
+ 24
1968
+ fb
1969
+ f7
1970
+ 21
1971
+ f0
1972
+ d0
1973
+ 22
1974
+ 21
1975
+ b8
1976
+ 27
1977
+ 0a
1978
+ ca
1979
+ d8
1980
+ 0e
1981
+ c9
1982
+ 06
1983
+ ef
1984
+ e0
1985
+ 3f
1986
+ e2
1987
+ 21
1988
+ 13
1989
+ 28
1990
+ 13
1991
+ dd
1992
+ 36
1993
+ 4d
1994
+ ee
1995
+ 20
1996
+ 28
1997
+ c1
1998
+ da
1999
+ fa
2000
+ 0a
2001
+ fd
2002
+ d6
2003
+ d7
2004
+ df
2005
+ f0
2006
+ de
2007
+ f8
2008
+ ea
2009
+ fc
2010
+ 16
2011
+ f1
2012
+ f7
2013
+ fb
2014
+ fc
2015
+ ee
2016
+ 08
2017
+ aa
2018
+ 01
2019
+ 1d
2020
+ f7
2021
+ 29
2022
+ e1
2023
+ 21
2024
+ 2b
2025
+ f2
2026
+ 11
2027
+ e0
2028
+ 1e
2029
+ f8
2030
+ f6
2031
+ f0
2032
+ 06
2033
+ 05
2034
+ 18
2035
+ 12
2036
+ 18
2037
+ 04
2038
+ e6
2039
+ f5
2040
+ 13
2041
+ 25
2042
+ ce
2043
+ 35
2044
+ c1
2045
+ 01
2046
+ d6
2047
+ f0
2048
+ e0
2049
+ 07
2050
+ 0f
2051
+ 18
2052
+ f2
2053
+ fd
2054
+ 2c
2055
+ 00
2056
+ fb
2057
+ 9d
2058
+ 2f
2059
+ 3b
2060
+ fe
2061
+ fa
2062
+ 04
2063
+ 0a
2064
+ fb
2065
+ 01
2066
+ 1e
2067
+ ee
2068
+ 09
2069
+ 0e
2070
+ 09
2071
+ fa
2072
+ f3
2073
+ df
2074
+ 0e
2075
+ b5
2076
+ f1
2077
+ 52
2078
+ dc
2079
+ ff
2080
+ 28
2081
+ 1f
2082
+ fd
2083
+ 35
2084
+ fc
2085
+ 21
2086
+ f4
2087
+ 19
2088
+ c6
2089
+ f1
2090
+ 0a
2091
+ ef
2092
+ 09
2093
+ 22
2094
+ f5
2095
+ f9
2096
+ 20
2097
+ 04
2098
+ e5
2099
+ 0c
2100
+ 3a
2101
+ 05
2102
+ 0c
2103
+ ff
2104
+ 14
2105
+ 1a
2106
+ 15
2107
+ 1e
2108
+ e5
2109
+ fd
2110
+ 5c
2111
+ f9
2112
+ c6
2113
+ e5
2114
+ dc
2115
+ e8
2116
+ 00
2117
+ bb
2118
+ 0a
2119
+ e8
2120
+ e0
2121
+ 24
2122
+ f1
2123
+ 04
2124
+ ff
2125
+ 48
2126
+ e7
2127
+ f7
2128
+ f6
2129
+ 0c
2130
+ 0e
2131
+ ec
2132
+ e1
2133
+ 19
2134
+ 17
2135
+ fe
2136
+ 02
2137
+ f9
2138
+ f0
2139
+ 0a
2140
+ f1
2141
+ 2a
2142
+ 0b
2143
+ d3
2144
+ 00
2145
+ f0
2146
+ fd
2147
+ 05
2148
+ 1c
2149
+ df
2150
+ fe
2151
+ 62
2152
+ f6
2153
+ 1a
2154
+ 2a
2155
+ fa
2156
+ f5
2157
+ 10
2158
+ de
2159
+ 02
2160
+ c3
2161
+ 05
2162
+ 16
2163
+ 02
2164
+ 06
2165
+ 45
2166
+ 2e
2167
+ 03
2168
+ fe
2169
+ e3
2170
+ f3
2171
+ fe
2172
+ 03
2173
+ 0e
2174
+ f5
2175
+ 0b
2176
+ 01
2177
+ 04
2178
+ 08
2179
+ 0c
2180
+ 11
2181
+ ff
2182
+ 02
2183
+ f2
2184
+ 11
2185
+ ff
2186
+ dc
2187
+ fc
2188
+ fc
2189
+ d6
2190
+ 02
2191
+ f7
2192
+ 09
2193
+ 0a
2194
+ e2
2195
+ ed
2196
+ 13
2197
+ 07
2198
+ 18
2199
+ 24
2200
+ f6
2201
+ eb
2202
+ fb
2203
+ 1e
2204
+ 1b
2205
+ ea
2206
+ fd
2207
+ c3
2208
+ f7
2209
+ de
2210
+ fe
2211
+ 00
2212
+ fb
2213
+ 3d
2214
+ 01
2215
+ d5
2216
+ e7
2217
+ e9
2218
+ fc
2219
+ c9
2220
+ 05
2221
+ eb
2222
+ f2
2223
+ 26
2224
+ d3
2225
+ 2f
2226
+ 2b
2227
+ 21
2228
+ fd
2229
+ 0f
2230
+ fe
2231
+ 15
2232
+ 06
2233
+ 1a
2234
+ f3
2235
+ e1
2236
+ db
2237
+ 06
2238
+ 05
2239
+ 1c
2240
+ 47
2241
+ 22
2242
+ fd
2243
+ 16
2244
+ ea
2245
+ 05
2246
+ 2d
2247
+ 07
2248
+ 2a
2249
+ d1
2250
+ 22
2251
+ 0b
2252
+ 0a
2253
+ e7
2254
+ 00
2255
+ f8
2256
+ 1f
2257
+ 37
2258
+ d6
2259
+ 1c
2260
+ 08
2261
+ 08
2262
+ 3f
2263
+ 94
2264
+ f8
2265
+ 1a
2266
+ 0c
2267
+ fa
2268
+ d1
2269
+ 17
2270
+ 0b
2271
+ de
2272
+ 07
2273
+ 0d
2274
+ 0e
2275
+ 19
2276
+ 10
2277
+ ec
2278
+ f4
2279
+ 14
2280
+ d9
2281
+ 04
2282
+ f7
2283
+ de
2284
+ 13
2285
+ fb
2286
+ eb
2287
+ 03
2288
+ f8
2289
+ 02
2290
+ 28
2291
+ ff
2292
+ 06
2293
+ 22
2294
+ f6
2295
+ cc
2296
+ f0
2297
+ f0
2298
+ 06
2299
+ b8
2300
+ 0b
2301
+ e5
2302
+ 0e
2303
+ 0b
2304
+ 31
2305
+ 26
2306
+ 11
2307
+ d5
2308
+ 30
2309
+ 17
2310
+ d0
2311
+ 23
2312
+ f9
2313
+ 1e
2314
+ 06
2315
+ df
2316
+ 03
2317
+ ed
2318
+ f9
2319
+ fa
2320
+ ef
2321
+ ef
2322
+ 00
2323
+ ec
2324
+ e1
2325
+ 0f
2326
+ 0e
2327
+ 05
2328
+ c4
2329
+ f2
2330
+ ed
2331
+ 34
2332
+ 30
2333
+ c5
2334
+ 18
2335
+ e3
2336
+ 01
2337
+ cd
2338
+ df
2339
+ 1e
2340
+ 11
2341
+ f6
2342
+ ff
2343
+ 08
2344
+ d4
2345
+ 18
2346
+ df
2347
+ 16
2348
+ f7
2349
+ 17
2350
+ 26
2351
+ 23
2352
+ d4
2353
+ de
2354
+ f0
2355
+ 07
2356
+ d4
2357
+ ee
2358
+ 0a
2359
+ 25
2360
+ 1c
2361
+ 12
2362
+ ed
2363
+ 20
2364
+ f9
2365
+ 29
2366
+ 21
2367
+ 01
2368
+ cc
2369
+ f5
2370
+ 09
2371
+ f5
2372
+ 18
2373
+ f6
2374
+ 0e
2375
+ cd
2376
+ ce
2377
+ e8
2378
+ f1
2379
+ 07
2380
+ dd
2381
+ e1
2382
+ 22
2383
+ e3
2384
+ 0d
2385
+ 00
2386
+ 0b
2387
+ 24
2388
+ ba
2389
+ ce
2390
+ f3
2391
+ 11
2392
+ f6
2393
+ fa
2394
+ fe
2395
+ 0b
2396
+ dc
2397
+ e1
2398
+ bb
2399
+ 08
2400
+ 0e
2401
+ 1a
2402
+ 0d
2403
+ 06
2404
+ 18
2405
+ dc
2406
+ e6
2407
+ 2c
2408
+ d4
2409
+ 0a
2410
+ c7
2411
+ f2
2412
+ 18
2413
+ 00
2414
+ 16
2415
+ 1e
2416
+ d2
2417
+ 10
2418
+ d9
2419
+ 13
2420
+ e5
2421
+ 18
2422
+ f2
2423
+ f4
2424
+ 35
2425
+ ff
2426
+ fe
2427
+ f3
2428
+ 02
2429
+ da
2430
+ f1
2431
+ 1c
2432
+ 0c
2433
+ ff
2434
+ f2
2435
+ 0e
2436
+ e2
2437
+ 20
2438
+ 0e
2439
+ fb
2440
+ f7
2441
+ d1
2442
+ 24
2443
+ 24
2444
+ cf
2445
+ dc
2446
+ 0b
2447
+ ff
2448
+ eb
2449
+ e9
2450
+ 02
2451
+ db
2452
+ dd
2453
+ 09
2454
+ 0b
2455
+ 20
2456
+ 15
2457
+ 1f
2458
+ e7
2459
+ 26
2460
+ fe
2461
+ c2
2462
+ 04
2463
+ 0d
2464
+ 10
2465
+ 11
2466
+ e4
2467
+ f4
2468
+ 9e
2469
+ 01
2470
+ fb
2471
+ e0
2472
+ 27
2473
+ 14
2474
+ 0e
2475
+ fb
2476
+ fa
2477
+ 0b
2478
+ 02
2479
+ ee
2480
+ f1
2481
+ fc
2482
+ 07
2483
+ 23
2484
+ 00
2485
+ 25
2486
+ 00
2487
+ f4
2488
+ fc
2489
+ 4b
2490
+ 1a
2491
+ d5
2492
+ fe
2493
+ fe
2494
+ 19
2495
+ c6
2496
+ d7
2497
+ 16
2498
+ 07
2499
+ 1b
2500
+ f6
2501
+ de
2502
+ f2
2503
+ 05
2504
+ 10
2505
+ ee
2506
+ 05
2507
+ 02
2508
+ 26
2509
+ ef
2510
+ 54
2511
+ f7
2512
+ 27
2513
+ fa
2514
+ d5
2515
+ 1b
2516
+ ea
2517
+ 2d
2518
+ 01
2519
+ 34
2520
+ 1d
2521
+ ee
2522
+ fb
2523
+ ec
2524
+ 10
2525
+ d8
2526
+ f8
2527
+ d9
2528
+ 04
2529
+ f7
2530
+ 28
2531
+ f5
2532
+ 1d
2533
+ 24
2534
+ 2f
2535
+ fb
2536
+ e7
2537
+ d7
2538
+ 0c
2539
+ 0a
2540
+ f6
2541
+ e1
2542
+ fe
2543
+ 1d
2544
+ 0a
2545
+ 11
2546
+ 09
2547
+ e1
2548
+ ec
2549
+ f3
2550
+ 1b
2551
+ ec
2552
+ 04
2553
+ af
2554
+ 10
2555
+ 13
2556
+ 0c
2557
+ e1
2558
+ f3
2559
+ 0d
2560
+ 28
2561
+ f1
2562
+ e8
2563
+ 16
2564
+ 35
2565
+ e5
2566
+ f6
2567
+ ec
2568
+ 01
2569
+ 02
2570
+ 0c
2571
+ fa
2572
+ fe
2573
+ 0e
2574
+ f2
2575
+ c7
2576
+ f1
2577
+ fc
2578
+ e4
2579
+ ff
2580
+ 09
2581
+ c0
2582
+ 17
2583
+ e9
2584
+ cb
2585
+ 17
2586
+ 1d
2587
+ 3a
2588
+ e1
2589
+ e8
2590
+ ea
2591
+ 19
2592
+ 0d
2593
+ 10
2594
+ f0
2595
+ 12
2596
+ 15
2597
+ 08
2598
+ e2
2599
+ fa
2600
+ f4
2601
+ 1d
2602
+ 1a
2603
+ da
2604
+ 06
2605
+ f4
2606
+ d1
2607
+ f7
2608
+ 19
2609
+ f4
2610
+ 01
2611
+ 01
2612
+ 1e
2613
+ 18
2614
+ 0c
2615
+ d9
2616
+ 13
2617
+ e0
2618
+ 03
2619
+ e7
2620
+ cd
2621
+ 19
2622
+ e0
2623
+ fe
2624
+ fa
2625
+ e2
2626
+ 2c
2627
+ 6c
2628
+ da
2629
+ 04
2630
+ fa
2631
+ e7
2632
+ 00
2633
+ ce
2634
+ fc
2635
+ 0c
2636
+ f2
2637
+ d4
2638
+ 08
2639
+ f4
2640
+ fe
2641
+ 03
2642
+ 20
2643
+ fa
2644
+ 13
2645
+ fb
2646
+ 0f
2647
+ 1c
2648
+ 21
2649
+ 3d
2650
+ f1
2651
+ f1
2652
+ e4
2653
+ e9
2654
+ 2e
2655
+ e9
2656
+ eb
2657
+ e4
2658
+ e6
2659
+ d4
2660
+ f2
2661
+ 04
2662
+ 0e
2663
+ 08
2664
+ f4
2665
+ 04
2666
+ 1b
2667
+ 35
2668
+ 1c
2669
+ d1
2670
+ 1b
2671
+ 1b
2672
+ 0e
2673
+ 13
2674
+ d3
2675
+ f6
2676
+ 20
2677
+ 05
2678
+ 12
2679
+ 2e
2680
+ da
2681
+ 2c
2682
+ c3
2683
+ 0b
2684
+ 0c
2685
+ 03
2686
+ dd
2687
+ d6
2688
+ dc
2689
+ 06
2690
+ fd
2691
+ 49
2692
+ f2
2693
+ 21
2694
+ 01
2695
+ d0
2696
+ 2a
2697
+ 11
2698
+ c8
2699
+ d9
2700
+ 16
2701
+ f5
2702
+ 10
2703
+ f7
2704
+ 36
2705
+ 07
2706
+ 10
2707
+ 00
2708
+ fa
2709
+ 1e
2710
+ e1
2711
+ d8
2712
+ 1b
2713
+ 1b
2714
+ f6
2715
+ f3
2716
+ fb
2717
+ 21
2718
+ d9
2719
+ 2d
2720
+ 03
2721
+ 24
2722
+ e1
2723
+ eb
2724
+ d8
2725
+ 16
2726
+ 03
2727
+ 14
2728
+ f7
2729
+ 0c
2730
+ 1c
2731
+ bc
2732
+ e6
2733
+ 00
2734
+ eb
2735
+ 28
2736
+ 09
2737
+ e5
2738
+ 29
2739
+ 02
2740
+ ce
2741
+ 02
2742
+ 27
2743
+ cf
2744
+ 1f
2745
+ 11
2746
+ dc
2747
+ d3
2748
+ 12
2749
+ d8
2750
+ 0c
2751
+ ea
2752
+ cf
2753
+ 2d
2754
+ d9
2755
+ 03
2756
+ 1f
2757
+ 1e
2758
+ f7
2759
+ c1
2760
+ 1b
2761
+ 2c
2762
+ 08
2763
+ 20
2764
+ 22
2765
+ dc
2766
+ bd
2767
+ 19
2768
+ e5
2769
+ ff
2770
+ ec
2771
+ cd
2772
+ cf
2773
+ f4
2774
+ f7
2775
+ fd
2776
+ ed
2777
+ fc
2778
+ 13
2779
+ 04
2780
+ f9
2781
+ f6
2782
+ f1
2783
+ 1e
2784
+ 0f
2785
+ e2
2786
+ 01
2787
+ fe
2788
+ 01
2789
+ 1d
2790
+ f2
2791
+ 0d
2792
+ 1d
2793
+ f5
2794
+ 0b
2795
+ f8
2796
+ 1a
2797
+ 01
2798
+ e9
2799
+ 19
2800
+ 3c
2801
+ 2d
2802
+ 0a
2803
+ f1
2804
+ 17
2805
+ f7
2806
+ f9
2807
+ f4
2808
+ 1c
2809
+ 0d
2810
+ cf
2811
+ 21
2812
+ c7
2813
+ 14
2814
+ d1
2815
+ e5
2816
+ fa
2817
+ f8
2818
+ 1e
2819
+ 16
2820
+ f4
2821
+ f7
2822
+ 15
2823
+ 14
2824
+ 0f
2825
+ a3
2826
+ 2e
2827
+ 0a
2828
+ 0a
2829
+ 0c
2830
+ 03
2831
+ dd
2832
+ 01
2833
+ 03
2834
+ 1c
2835
+ d0
2836
+ 07
2837
+ 07
2838
+ 08
2839
+ 08
2840
+ 02
2841
+ 1c
2842
+ 0b
2843
+ fd
2844
+ e5
2845
+ 1e
2846
+ 0b
2847
+ 13
2848
+ 07
2849
+ eb
2850
+ 03
2851
+ 34
2852
+ de
2853
+ 25
2854
+ ea
2855
+ 13
2856
+ e1
2857
+ fb
2858
+ 27
2859
+ c5
2860
+ 23
2861
+ 30
2862
+ 05
2863
+ f8
2864
+ 28
2865
+ e8
2866
+ ff
2867
+ fa
2868
+ fd
2869
+ 2a
2870
+ fd
2871
+ 16
2872
+ 1a
2873
+ fb
2874
+ f3
2875
+ 0d
2876
+ d9
2877
+ f4
2878
+ 31
2879
+ 25
2880
+ 31
2881
+ e5
2882
+ d6
2883
+ d2
2884
+ 13
2885
+ cc
2886
+ 0e
2887
+ ec
2888
+ fa
2889
+ 29
2890
+ f4
2891
+ 0a
2892
+ ec
2893
+ 2c
2894
+ f5
2895
+ fa
2896
+ fa
2897
+ 2e
2898
+ 04
2899
+ 17
2900
+ ed
2901
+ 01
2902
+ 1a
2903
+ f4
2904
+ 22
2905
+ f8
2906
+ 05
2907
+ 20
2908
+ ea
2909
+ 14
2910
+ 09
2911
+ 08
2912
+ 09
2913
+ dd
2914
+ ee
2915
+ 2f
2916
+ 19
2917
+ c8
2918
+ fd
2919
+ 6e
2920
+ 10
2921
+ 17
2922
+ 15
2923
+ fc
2924
+ ed
2925
+ 0d
2926
+ d8
2927
+ fb
2928
+ e3
2929
+ fb
2930
+ 1b
2931
+ e3
2932
+ fd
2933
+ 33
2934
+ 35
2935
+ ff
2936
+ 21
2937
+ ef
2938
+ 11
2939
+ f6
2940
+ 20
2941
+ 03
2942
+ 00
2943
+ 04
2944
+ eb
2945
+ 15
2946
+ 16
2947
+ f9
2948
+ 03
2949
+ 03
2950
+ 09
2951
+ 14
2952
+ 22
2953
+ 04
2954
+ 07
2955
+ 0d
2956
+ 13
2957
+ ea
2958
+ 0b
2959
+ ed
2960
+ 19
2961
+ 2f
2962
+ f0
2963
+ fa
2964
+ 12
2965
+ fe
2966
+ 0b
2967
+ 13
2968
+ fb
2969
+ 05
2970
+ ff
2971
+ 2a
2972
+ 25
2973
+ ed
2974
+ 09
2975
+ d5
2976
+ ea
2977
+ ef
2978
+ 20
2979
+ e4
2980
+ 04
2981
+ 18
2982
+ fc
2983
+ 05
2984
+ ea
2985
+ ee
2986
+ 07
2987
+ f3
2988
+ 1d
2989
+ 03
2990
+ f4
2991
+ 21
2992
+ 0d
2993
+ 16
2994
+ 24
2995
+ 10
2996
+ ee
2997
+ 19
2998
+ e4
2999
+ f0
3000
+ 01
3001
+ 10
3002
+ ea
3003
+ fa
3004
+ f2
3005
+ 17
3006
+ 07
3007
+ 0a
3008
+ 4e
3009
+ 1c
3010
+ ec
3011
+ 16
3012
+ 03
3013
+ 01
3014
+ 26
3015
+ eb
3016
+ 19
3017
+ d6
3018
+ ff
3019
+ 10
3020
+ 1c
3021
+ 91
3022
+ e4
3023
+ 04
3024
+ 1e
3025
+ 15
3026
+ e9
3027
+ 36
3028
+ f5
3029
+ 10
3030
+ 0f
3031
+ 86
3032
+ c6
3033
+ 10
3034
+ f7
3035
+ 14
3036
+ f2
3037
+ e2
3038
+ 21
3039
+ 19
3040
+ 19
3041
+ fb
3042
+ fb
3043
+ 39
3044
+ e1
3045
+ ec
3046
+ c5
3047
+ 09
3048
+ e7
3049
+ d8
3050
+ f9
3051
+ e9
3052
+ 3b
3053
+ e9
3054
+ e9
3055
+ 03
3056
+ 01
3057
+ 13
3058
+ 0d
3059
+ fe
3060
+ 04
3061
+ 23
3062
+ ec
3063
+ fd
3064
+ f8
3065
+ ff
3066
+ f7
3067
+ 02
3068
+ 22
3069
+ e4
3070
+ 22
3071
+ f4
3072
+ 0a
circuit/rom/test_features.hex ADDED
@@ -0,0 +1,768 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 51
2
+ 3e
3
+ 26
4
+ a8
5
+ 1c
6
+ cc
7
+ fe
8
+ bd
9
+ e0
10
+ 45
11
+ f0
12
+ c5
13
+ e1
14
+ e9
15
+ e0
16
+ 20
17
+ 45
18
+ f9
19
+ eb
20
+ 12
21
+ e0
22
+ 2d
23
+ 22
24
+ 47
25
+ 36
26
+ 36
27
+ 1a
28
+ 38
29
+ f6
30
+ 02
31
+ f5
32
+ 24
33
+ c6
34
+ db
35
+ f7
36
+ 48
37
+ 0d
38
+ ee
39
+ 0d
40
+ df
41
+ bf
42
+ 2a
43
+ db
44
+ e7
45
+ cb
46
+ 59
47
+ cc
48
+ ec
49
+ da
50
+ e4
51
+ 03
52
+ 16
53
+ ec
54
+ 32
55
+ de
56
+ e1
57
+ c5
58
+ 02
59
+ fd
60
+ 1c
61
+ fc
62
+ 4d
63
+ ce
64
+ 3a
65
+ 3d
66
+ 24
67
+ 5d
68
+ 16
69
+ 0f
70
+ f8
71
+ d4
72
+ 36
73
+ f9
74
+ 16
75
+ 02
76
+ 12
77
+ 18
78
+ e5
79
+ a3
80
+ e0
81
+ 00
82
+ f2
83
+ c8
84
+ e7
85
+ 17
86
+ 16
87
+ 30
88
+ 02
89
+ 1f
90
+ ec
91
+ d4
92
+ 19
93
+ b8
94
+ dd
95
+ 38
96
+ 14
97
+ 97
98
+ 14
99
+ 21
100
+ 01
101
+ 1b
102
+ 18
103
+ 2d
104
+ ed
105
+ f8
106
+ 20
107
+ 11
108
+ 07
109
+ 0b
110
+ 35
111
+ 00
112
+ f3
113
+ c3
114
+ fc
115
+ e7
116
+ 14
117
+ 1e
118
+ 04
119
+ f0
120
+ 16
121
+ ff
122
+ 0a
123
+ 06
124
+ 20
125
+ 2e
126
+ 0e
127
+ 1e
128
+ 11
129
+ 51
130
+ 2a
131
+ c4
132
+ d1
133
+ fa
134
+ 45
135
+ 1b
136
+ 18
137
+ 30
138
+ 01
139
+ b4
140
+ 27
141
+ f0
142
+ 2b
143
+ e3
144
+ 1b
145
+ d7
146
+ 28
147
+ 44
148
+ 3d
149
+ 0b
150
+ f7
151
+ e1
152
+ 04
153
+ 0f
154
+ 29
155
+ ec
156
+ 43
157
+ 98
158
+ ee
159
+ ce
160
+ 22
161
+ b0
162
+ 0a
163
+ 01
164
+ f1
165
+ 0c
166
+ e1
167
+ 07
168
+ d2
169
+ bd
170
+ 39
171
+ 36
172
+ 02
173
+ bf
174
+ 20
175
+ 21
176
+ 55
177
+ 02
178
+ 05
179
+ de
180
+ f7
181
+ d9
182
+ bd
183
+ d0
184
+ ea
185
+ ea
186
+ c1
187
+ af
188
+ 05
189
+ 2b
190
+ e9
191
+ 1e
192
+ 1e
193
+ 4a
194
+ d9
195
+ 28
196
+ f2
197
+ cf
198
+ 0f
199
+ 14
200
+ 39
201
+ 16
202
+ 59
203
+ ea
204
+ d9
205
+ 08
206
+ 2d
207
+ 37
208
+ 13
209
+ de
210
+ d5
211
+ eb
212
+ e7
213
+ 06
214
+ 13
215
+ fa
216
+ 9f
217
+ ef
218
+ 2d
219
+ b5
220
+ 3f
221
+ 0d
222
+ eb
223
+ 2b
224
+ 47
225
+ 00
226
+ 46
227
+ 06
228
+ d3
229
+ e8
230
+ 04
231
+ 11
232
+ 54
233
+ fd
234
+ da
235
+ aa
236
+ d3
237
+ 01
238
+ 03
239
+ 08
240
+ 11
241
+ d9
242
+ 0c
243
+ e9
244
+ 13
245
+ db
246
+ ff
247
+ f1
248
+ 3d
249
+ 35
250
+ e2
251
+ 24
252
+ 16
253
+ 17
254
+ 18
255
+ 15
256
+ 09
257
+ e3
258
+ 18
259
+ c1
260
+ bc
261
+ c2
262
+ 12
263
+ fb
264
+ 21
265
+ bd
266
+ fb
267
+ 1e
268
+ 02
269
+ 37
270
+ 0a
271
+ ad
272
+ 01
273
+ c6
274
+ 1a
275
+ 93
276
+ ff
277
+ fb
278
+ e1
279
+ 48
280
+ 02
281
+ 32
282
+ 51
283
+ 1f
284
+ 29
285
+ 11
286
+ 31
287
+ 0b
288
+ fe
289
+ 29
290
+ d5
291
+ e9
292
+ ed
293
+ f3
294
+ fb
295
+ e2
296
+ fe
297
+ 55
298
+ 0b
299
+ 27
300
+ 1e
301
+ ff
302
+ 04
303
+ 38
304
+ 1e
305
+ 11
306
+ e2
307
+ 23
308
+ d8
309
+ 13
310
+ 34
311
+ 9f
312
+ ca
313
+ 08
314
+ a7
315
+ fa
316
+ d4
317
+ e0
318
+ fe
319
+ 33
320
+ d7
321
+ 12
322
+ e2
323
+ 2c
324
+ c3
325
+ 14
326
+ 10
327
+ 00
328
+ 03
329
+ 10
330
+ 1e
331
+ b8
332
+ dd
333
+ 12
334
+ 0b
335
+ e7
336
+ 03
337
+ 25
338
+ 08
339
+ 21
340
+ fe
341
+ 18
342
+ e2
343
+ ec
344
+ 02
345
+ 1a
346
+ 44
347
+ fc
348
+ 2e
349
+ 28
350
+ e4
351
+ e8
352
+ f2
353
+ df
354
+ 10
355
+ 04
356
+ cc
357
+ f3
358
+ dd
359
+ e9
360
+ 54
361
+ 50
362
+ 47
363
+ 01
364
+ f9
365
+ b6
366
+ e2
367
+ ef
368
+ 4f
369
+ f7
370
+ 07
371
+ 5a
372
+ 48
373
+ 0f
374
+ 1b
375
+ f7
376
+ 1d
377
+ fa
378
+ ce
379
+ ca
380
+ 13
381
+ e7
382
+ 24
383
+ eb
384
+ f1
385
+ 1c
386
+ fd
387
+ f7
388
+ 07
389
+ 3e
390
+ d9
391
+ e7
392
+ fc
393
+ d7
394
+ 98
395
+ f2
396
+ 23
397
+ 29
398
+ f2
399
+ de
400
+ 23
401
+ 01
402
+ d3
403
+ e0
404
+ ef
405
+ 28
406
+ fa
407
+ 06
408
+ fe
409
+ 28
410
+ be
411
+ 29
412
+ 30
413
+ fa
414
+ f4
415
+ f8
416
+ 21
417
+ 1d
418
+ c8
419
+ ea
420
+ 19
421
+ ec
422
+ e6
423
+ fe
424
+ bb
425
+ ec
426
+ e1
427
+ 0d
428
+ 06
429
+ 2b
430
+ e6
431
+ ea
432
+ 1d
433
+ eb
434
+ 30
435
+ ed
436
+ 3c
437
+ 24
438
+ fe
439
+ 1c
440
+ 2c
441
+ 1d
442
+ c9
443
+ 02
444
+ cf
445
+ fc
446
+ 32
447
+ 20
448
+ e1
449
+ c6
450
+ 14
451
+ d6
452
+ 01
453
+ ee
454
+ d7
455
+ e5
456
+ 23
457
+ 43
458
+ fb
459
+ ec
460
+ 05
461
+ 03
462
+ d9
463
+ cb
464
+ 2e
465
+ 2e
466
+ e3
467
+ 03
468
+ 00
469
+ 07
470
+ 32
471
+ c9
472
+ c5
473
+ d4
474
+ 0d
475
+ 11
476
+ e2
477
+ 77
478
+ 52
479
+ 56
480
+ d2
481
+ 44
482
+ 24
483
+ ef
484
+ e6
485
+ eb
486
+ e6
487
+ bb
488
+ d4
489
+ ed
490
+ fd
491
+ e9
492
+ d0
493
+ ed
494
+ ff
495
+ 10
496
+ fe
497
+ 32
498
+ f2
499
+ 1b
500
+ 04
501
+ e0
502
+ e5
503
+ ca
504
+ 53
505
+ d8
506
+ 94
507
+ 1e
508
+ 22
509
+ 1a
510
+ 12
511
+ f2
512
+ 16
513
+ c7
514
+ 08
515
+ e6
516
+ 07
517
+ 3f
518
+ 09
519
+ e2
520
+ d8
521
+ 16
522
+ ed
523
+ 08
524
+ f0
525
+ b5
526
+ 04
527
+ b0
528
+ e5
529
+ 38
530
+ ca
531
+ 20
532
+ 1b
533
+ 0f
534
+ 91
535
+ c3
536
+ d7
537
+ 0b
538
+ f8
539
+ 32
540
+ ee
541
+ 2a
542
+ 1a
543
+ 33
544
+ e6
545
+ b7
546
+ fb
547
+ c7
548
+ 2f
549
+ e6
550
+ df
551
+ f9
552
+ 45
553
+ 54
554
+ cb
555
+ 2f
556
+ 4a
557
+ a9
558
+ ef
559
+ d7
560
+ ff
561
+ 4e
562
+ 6e
563
+ 0f
564
+ 10
565
+ 08
566
+ f6
567
+ 0d
568
+ ed
569
+ 14
570
+ 1b
571
+ 31
572
+ 01
573
+ 16
574
+ e7
575
+ f9
576
+ 1a
577
+ de
578
+ d1
579
+ 08
580
+ df
581
+ b5
582
+ f5
583
+ ee
584
+ 5c
585
+ ec
586
+ ca
587
+ 1f
588
+ 01
589
+ f3
590
+ f5
591
+ cd
592
+ d8
593
+ 03
594
+ 04
595
+ 1f
596
+ f9
597
+ da
598
+ 0d
599
+ 22
600
+ d1
601
+ 15
602
+ ce
603
+ 05
604
+ 12
605
+ 1b
606
+ 07
607
+ 4a
608
+ 1a
609
+ b1
610
+ 62
611
+ d9
612
+ 1c
613
+ ee
614
+ 9f
615
+ 36
616
+ 09
617
+ dc
618
+ 45
619
+ 43
620
+ fd
621
+ 12
622
+ 0f
623
+ 20
624
+ 32
625
+ ef
626
+ 16
627
+ e3
628
+ ee
629
+ 06
630
+ 03
631
+ 18
632
+ e8
633
+ d2
634
+ f0
635
+ 22
636
+ 3f
637
+ 02
638
+ 3d
639
+ 0a
640
+ 15
641
+ ca
642
+ fe
643
+ 50
644
+ 0e
645
+ 05
646
+ e0
647
+ ce
648
+ 21
649
+ 13
650
+ 0b
651
+ c7
652
+ 4c
653
+ fd
654
+ f9
655
+ db
656
+ e5
657
+ ef
658
+ e3
659
+ f6
660
+ a5
661
+ 04
662
+ 2e
663
+ fb
664
+ f3
665
+ d8
666
+ fc
667
+ d5
668
+ 00
669
+ ea
670
+ 23
671
+ 02
672
+ ba
673
+ 59
674
+ c0
675
+ 06
676
+ ce
677
+ ea
678
+ 28
679
+ 0c
680
+ e8
681
+ 8b
682
+ e2
683
+ 16
684
+ b8
685
+ 23
686
+ f5
687
+ 05
688
+ 25
689
+ 06
690
+ 1f
691
+ 0b
692
+ 73
693
+ 12
694
+ f3
695
+ fc
696
+ 41
697
+ fb
698
+ ee
699
+ 3c
700
+ e2
701
+ cb
702
+ 7f
703
+ 39
704
+ 24
705
+ 0e
706
+ 13
707
+ 40
708
+ 36
709
+ fb
710
+ 3a
711
+ ec
712
+ d6
713
+ c7
714
+ 00
715
+ ef
716
+ e2
717
+ f1
718
+ 10
719
+ 2a
720
+ 36
721
+ 28
722
+ 04
723
+ 23
724
+ 58
725
+ 21
726
+ 0c
727
+ 08
728
+ 0f
729
+ 04
730
+ 49
731
+ cb
732
+ 10
733
+ 0e
734
+ c3
735
+ c1
736
+ f7
737
+ 09
738
+ fd
739
+ 29
740
+ 36
741
+ 0c
742
+ 24
743
+ de
744
+ c4
745
+ 13
746
+ 16
747
+ c7
748
+ 52
749
+ 4a
750
+ d7
751
+ cc
752
+ 9f
753
+ 00
754
+ dc
755
+ ba
756
+ 0d
757
+ 32
758
+ 0e
759
+ f0
760
+ 78
761
+ 23
762
+ 22
763
+ 4f
764
+ 19
765
+ 03
766
+ bd
767
+ ee
768
+ 18