diff options
| -rw-r--r-- | artifacts/vg_5k/field_anatomy.json | 16 | ||||
| -rw-r--r-- | artifacts/vg_5k/np_struct128.json | 11 | ||||
| -rw-r--r-- | artifacts/vg_5k/np_struct64.json | 11 | ||||
| -rw-r--r-- | artifacts/vg_5k/shared_rank_phrase.json | 265 | ||||
| -rw-r--r-- | logs/obj_base_gpu.log | 2 | ||||
| -rw-r--r-- | logs/rho_full_width.log | 0 | ||||
| -rw-r--r-- | tests/test_core.py | 89 | ||||
| -rw-r--r-- | worldalign/field_anatomy.py | 29 | ||||
| -rw-r--r-- | worldalign/natural_pipeline.py | 42 | ||||
| -rw-r--r-- | worldalign/rho_at_full_width.py | 137 |
10 files changed, 580 insertions, 22 deletions
diff --git a/artifacts/vg_5k/field_anatomy.json b/artifacts/vg_5k/field_anatomy.json index 740136c..1e0f276 100644 --- a/artifacts/vg_5k/field_anatomy.json +++ b/artifacts/vg_5k/field_anatomy.json @@ -9,31 +9,31 @@ }, { "component": "degree model only", - "correlation": 0.22252881188815218 + "correlation": 0.2225288118881524 }, { "component": "residual after degree", - "correlation": 0.6709906297296612 + "correlation": 0.670990871461728 }, { "component": "residual after top-1 eigen", - "correlation": 0.4579725907578625 + "correlation": 0.45797259075786245 }, { "component": "residual after top-2 eigen", - "correlation": 0.4555001418827857 + "correlation": 0.45550014188278554 }, { "component": "residual after top-5 eigen", - "correlation": 0.3413229646188751 + "correlation": 0.3413229646188753 }, { "component": "residual after top-10 eigen", - "correlation": 0.07969017846523709 + "correlation": 0.07969017846523718 } ], - "visual_degree_variance_share": 0.009036045331312099, - "text_degree_variance_share": 0.04921092158161093, + "visual_degree_variance_share": 0.00910733535353353, + "text_degree_variance_share": 0.049599171923929736, "it_threshold_at_size": 0.29435250562886867, "residual_above_it_threshold": true, "reading": "If the residual correlation is far below the raw correlation, the headline statistic is inflated by nuisance structure that carries no matching information, and the residual is the number that should be compared against the recovery threshold." diff --git a/artifacts/vg_5k/np_struct128.json b/artifacts/vg_5k/np_struct128.json new file mode 100644 index 0000000..c035e3b --- /dev/null +++ b/artifacts/vg_5k/np_struct128.json @@ -0,0 +1,11 @@ +{ + "protocol": "Continuous states, content projection fitted per modality on scenes outside the evaluated set. Hidden pairs are read only for the correlation.", + "samples": 256, + "views": 1, + "kept_directions": 64, + "weight_power": 0.5, + "moment_degree": 2, + "phrase_encoding": "structured", + "field_correlation_at_truth": 0.7252267991153296, + "recovery_threshold": 0.9 +}
\ No newline at end of file diff --git a/artifacts/vg_5k/np_struct64.json b/artifacts/vg_5k/np_struct64.json new file mode 100644 index 0000000..1a98e55 --- /dev/null +++ b/artifacts/vg_5k/np_struct64.json @@ -0,0 +1,11 @@ +{ + "protocol": "Continuous states, content projection fitted per modality on scenes outside the evaluated set. Hidden pairs are read only for the correlation.", + "samples": 256, + "views": 1, + "kept_directions": 64, + "weight_power": 0.5, + "moment_degree": 2, + "phrase_encoding": "structured", + "field_correlation_at_truth": 0.7246401498403082, + "recovery_threshold": 0.9 +}
\ No newline at end of file diff --git a/artifacts/vg_5k/shared_rank_phrase.json b/artifacts/vg_5k/shared_rank_phrase.json new file mode 100644 index 0000000..80d6565 --- /dev/null +++ b/artifacts/vg_5k/shared_rank_phrase.json @@ -0,0 +1,265 @@ +{ + "protocol": "Effective rank from the participation ratio of each field's spectrum; shared directions from principal angles between the two leading eigenspaces. No pairing enters beyond the alignment the fields already carry.", + "width": 32, + "rows": [ + { + "label": "mean-phrase-0.716", + "size": 256, + "correlation": 0.7157944505313559, + "visual_effective_rank": 39.69132496762981, + "text_effective_rank": 47.98422953390366, + "shared_directions_cos_gt_0.7": 16, + "shuffled_null": 1.0, + "shared_above_null": 15.0, + "principal_cosines_top8": [ + 0.987, + 0.978, + 0.96, + 0.941, + 0.934, + 0.922, + 0.899, + 0.89 + ], + "spectrum_profile": [ + { + "index": 0, + "best_text_overlap": 0.8406807387214822 + }, + { + "index": 1, + "best_text_overlap": 0.6748753041856854 + }, + { + "index": 2, + "best_text_overlap": 0.7174150319587717 + }, + { + "index": 3, + "best_text_overlap": 0.4742164704027202 + }, + { + "index": 4, + "best_text_overlap": 0.7105187496542062 + }, + { + "index": 5, + "best_text_overlap": 0.5435228035336661 + }, + { + "index": 6, + "best_text_overlap": 0.5173316630294161 + }, + { + "index": 7, + "best_text_overlap": 0.3720956303455928 + }, + { + "index": 8, + "best_text_overlap": 0.4031225977235423 + }, + { + "index": 9, + "best_text_overlap": 0.5909531802492844 + }, + { + "index": 10, + "best_text_overlap": 0.373881786434362 + }, + { + "index": 11, + "best_text_overlap": 0.2816693575587397 + }, + { + "index": 12, + "best_text_overlap": 0.21822240451244762 + }, + { + "index": 13, + "best_text_overlap": 0.3929212823566225 + }, + { + "index": 14, + "best_text_overlap": 0.4024276537497661 + }, + { + "index": 15, + "best_text_overlap": 0.2659834432053085 + } + ] + }, + { + "label": "structured-wv64", + "size": 256, + "correlation": 0.7246401498403082, + "visual_effective_rank": 39.69132496762981, + "text_effective_rank": 39.044391234462196, + "shared_directions_cos_gt_0.7": 15, + "shuffled_null": 1.0, + "shared_above_null": 14.0, + "principal_cosines_top8": [ + 0.991, + 0.979, + 0.957, + 0.94, + 0.924, + 0.913, + 0.884, + 0.877 + ], + "spectrum_profile": [ + { + "index": 0, + "best_text_overlap": 0.8699128451599867 + }, + { + "index": 1, + "best_text_overlap": 0.5919394988285451 + }, + { + "index": 2, + "best_text_overlap": 0.664910963201995 + }, + { + "index": 3, + "best_text_overlap": 0.5718467670534315 + }, + { + "index": 4, + "best_text_overlap": 0.6155246177540001 + }, + { + "index": 5, + "best_text_overlap": 0.520789559417958 + }, + { + "index": 6, + "best_text_overlap": 0.5726871443670466 + }, + { + "index": 7, + "best_text_overlap": 0.359948345526124 + }, + { + "index": 8, + "best_text_overlap": 0.3629235665536516 + }, + { + "index": 9, + "best_text_overlap": 0.3191775375612409 + }, + { + "index": 10, + "best_text_overlap": 0.23839966477101518 + }, + { + "index": 11, + "best_text_overlap": 0.3902646088886306 + }, + { + "index": 12, + "best_text_overlap": 0.2525566443339542 + }, + { + "index": 13, + "best_text_overlap": 0.4143287992176083 + }, + { + "index": 14, + "best_text_overlap": 0.35641603055069626 + }, + { + "index": 15, + "best_text_overlap": 0.1905065808689813 + } + ] + }, + { + "label": "structured-wv128", + "size": 256, + "correlation": 0.7252267991153296, + "visual_effective_rank": 39.69132496762981, + "text_effective_rank": 48.61469812212951, + "shared_directions_cos_gt_0.7": 16, + "shuffled_null": 1.0, + "shared_above_null": 15.0, + "principal_cosines_top8": [ + 0.987, + 0.978, + 0.956, + 0.936, + 0.933, + 0.919, + 0.908, + 0.882 + ], + "spectrum_profile": [ + { + "index": 0, + "best_text_overlap": 0.8836896656200444 + }, + { + "index": 1, + "best_text_overlap": 0.6662527722108562 + }, + { + "index": 2, + "best_text_overlap": 0.6895209294138209 + }, + { + "index": 3, + "best_text_overlap": 0.5068523620638157 + }, + { + "index": 4, + "best_text_overlap": 0.6826302737796026 + }, + { + "index": 5, + "best_text_overlap": 0.5697249361142291 + }, + { + "index": 6, + "best_text_overlap": 0.5326573257977141 + }, + { + "index": 7, + "best_text_overlap": 0.37933461331439877 + }, + { + "index": 8, + "best_text_overlap": 0.3322445785255883 + }, + { + "index": 9, + "best_text_overlap": 0.5083883525562566 + }, + { + "index": 10, + "best_text_overlap": 0.3237366787528124 + }, + { + "index": 11, + "best_text_overlap": 0.38425794698324 + }, + { + "index": 12, + "best_text_overlap": 0.1960410706899702 + }, + { + "index": 13, + "best_text_overlap": 0.4644524929154713 + }, + { + "index": 14, + "best_text_overlap": 0.2559185730403851 + }, + { + "index": 15, + "best_text_overlap": 0.2515038306148867 + } + ] + } + ], + "reading": "The narrow modality is the one to upgrade. A shared count far below both effective ranks means each side is rich but they are rich about different things." +}
\ No newline at end of file diff --git a/logs/obj_base_gpu.log b/logs/obj_base_gpu.log index de196aa..c3ea786 100644 --- a/logs/obj_base_gpu.log +++ b/logs/obj_base_gpu.log @@ -1,3 +1,3 @@ Using a slow image processor as `use_fast` is unset and a slow processor was saved with this model. `use_fast=True` will be the default behavior in v4.52, even if the model was saved with a slow processor. This will result in minor differences in outputs. You'll still be able to use a slow processor with `use_fast=False`. `torch_dtype` is deprecated! Use `dtype` instead! -
segment: 0%| | 0/250 [00:00<?, ?it/s]
segment: 0%| | 1/250 [00:02<10:41, 2.58s/it]
segment: 1%| | 2/250 [00:04<08:49, 2.13s/it]
segment: 1%| | 3/250 [00:06<08:11, 1.99s/it]
segment: 2%|▏ | 4/250 [00:08<07:52, 1.92s/it]
segment: 2%|▏ | 5/250 [00:09<07:41, 1.88s/it]
segment: 2%|▏ | 6/250 [00:11<07:02, 1.73s/it]
segment: 3%|▎ | 7/250 [00:12<06:28, 1.60s/it]
segment: 3%|▎ | 8/250 [00:13<06:06, 1.52s/it]
segment: 4%|▎ | 9/250 [00:15<05:50, 1.45s/it]
segment: 4%|▍ | 10/250 [00:16<05:40, 1.42s/it]
segment: 4%|▍ | 11/250 [00:17<05:31, 1.39s/it]
segment: 5%|▍ | 12/250 [00:19<05:26, 1.37s/it]
segment: 5%|▌ | 13/250 [00:20<05:23, 1.37s/it]
segment: 6%|▌ | 14/250 [00:21<05:20, 1.36s/it]
segment: 6%|▌ | 15/250 [00:23<05:16, 1.35s/it]
segment: 6%|▋ | 16/250 [00:24<05:14, 1.34s/it]
segment: 7%|▋ | 17/250 [00:25<05:12, 1.34s/it]
segment: 7%|▋ | 18/250 [00:27<05:10, 1.34s/it]
segment: 8%|▊ | 19/250 [00:28<05:07, 1.33s/it]
segment: 8%|▊ | 20/250 [00:29<05:06, 1.33s/it]
segment: 8%|▊ | 21/250 [00:31<05:04, 1.33s/it]
segment: 9%|▉ | 22/250 [00:32<05:01, 1.32s/it]
segment: 9%|▉ | 23/250 [00:33<04:59, 1.32s/it]
segment: 10%|▉ | 24/250 [00:35<04:58, 1.32s/it]
segment: 10%|█ | 25/250 [00:36<04:57, 1.32s/it]
segment: 10%|█ | 26/250 [00:37<04:56, 1.32s/it]
segment: 11%|█ | 27/250 [00:39<04:56, 1.33s/it]
segment: 11%|█ | 28/250 [00:40<04:55, 1.33s/it]
segment: 12%|█▏ | 29/250 [00:41<04:54, 1.33s/it]
segment: 12%|█▏ | 30/250 [00:43<04:53, 1.33s/it]
segment: 12%|█▏ | 31/250 [00:44<04:51, 1.33s/it]
segment: 13%|█▎ | 32/250 [00:45<04:48, 1.32s/it]
segment: 13%|█▎ | 33/250 [00:47<04:46, 1.32s/it]
segment: 14%|█▎ | 34/250 [00:48<04:44, 1.32s/it]
segment: 14%|█▍ | 35/250 [00:49<04:43, 1.32s/it]
segment: 14%|█▍ | 36/250 [00:51<04:43, 1.32s/it]
segment: 15%|█▍ | 37/250 [00:52<04:41, 1.32s/it]
segment: 15%|█▌ | 38/250 [00:53<04:40, 1.32s/it]
segment: 16%|█▌ | 39/250 [00:55<04:39, 1.32s/it]
segment: 16%|█▌ | 40/250 [00:56<04:36, 1.32s/it]
segment: 16%|█▋ | 41/250 [00:57<04:34, 1.31s/it]
segment: 17%|█▋ | 42/250 [00:59<04:33, 1.32s/it]
segment: 17%|█▋ | 43/250 [01:00<04:32, 1.32s/it]
segment: 18%|█▊ | 44/250 [01:01<04:31, 1.32s/it]
segment: 18%|█▊ | 45/250 [01:03<04:34, 1.34s/it]
segment: 18%|█▊ | 46/250 [01:04<04:31, 1.33s/it]
segment: 19%|█▉ | 47/250 [01:05<04:27, 1.32s/it]
segment: 19%|█▉ | 48/250 [01:06<04:25, 1.32s/it]
segment: 20%|█▉ | 49/250 [01:08<04:23, 1.31s/it]
segment: 20%|██ | 50/250 [01:09<04:21, 1.31s/it]
segment: 20%|██ | 51/250 [01:10<04:21, 1.31s/it]
segment: 21%|██ | 52/250 [01:12<04:20, 1.31s/it]
segment: 21%|██ | 53/250 [01:13<04:19, 1.32s/it]
segment: 22%|██▏ | 54/250 [01:14<04:17, 1.32s/it]
segment: 22%|██▏ | 55/250 [01:16<04:16, 1.32s/it]
segment: 22%|██▏ | 56/250 [01:17<04:15, 1.32s/it]
segment: 23%|██▎ | 57/250 [01:18<04:14, 1.32s/it]
segment: 23%|██▎ | 58/250 [01:20<04:13, 1.32s/it]
\ No newline at end of file +
segment: 0%| | 0/250 [00:00<?, ?it/s]
segment: 0%| | 1/250 [00:02<10:41, 2.58s/it]
segment: 1%| | 2/250 [00:04<08:49, 2.13s/it]
segment: 1%| | 3/250 [00:06<08:11, 1.99s/it]
segment: 2%|▏ | 4/250 [00:08<07:52, 1.92s/it]
segment: 2%|▏ | 5/250 [00:09<07:41, 1.88s/it]
segment: 2%|▏ | 6/250 [00:11<07:02, 1.73s/it]
segment: 3%|▎ | 7/250 [00:12<06:28, 1.60s/it]
segment: 3%|▎ | 8/250 [00:13<06:06, 1.52s/it]
segment: 4%|▎ | 9/250 [00:15<05:50, 1.45s/it]
segment: 4%|▍ | 10/250 [00:16<05:40, 1.42s/it]
segment: 4%|▍ | 11/250 [00:17<05:31, 1.39s/it]
segment: 5%|▍ | 12/250 [00:19<05:26, 1.37s/it]
segment: 5%|▌ | 13/250 [00:20<05:23, 1.37s/it]
segment: 6%|▌ | 14/250 [00:21<05:20, 1.36s/it]
segment: 6%|▌ | 15/250 [00:23<05:16, 1.35s/it]
segment: 6%|▋ | 16/250 [00:24<05:14, 1.34s/it]
segment: 7%|▋ | 17/250 [00:25<05:12, 1.34s/it]
segment: 7%|▋ | 18/250 [00:27<05:10, 1.34s/it]
segment: 8%|▊ | 19/250 [00:28<05:07, 1.33s/it]
segment: 8%|▊ | 20/250 [00:29<05:06, 1.33s/it]
segment: 8%|▊ | 21/250 [00:31<05:04, 1.33s/it]
segment: 9%|▉ | 22/250 [00:32<05:01, 1.32s/it]
segment: 9%|▉ | 23/250 [00:33<04:59, 1.32s/it]
segment: 10%|▉ | 24/250 [00:35<04:58, 1.32s/it]
segment: 10%|█ | 25/250 [00:36<04:57, 1.32s/it]
segment: 10%|█ | 26/250 [00:37<04:56, 1.32s/it]
segment: 11%|█ | 27/250 [00:39<04:56, 1.33s/it]
segment: 11%|█ | 28/250 [00:40<04:55, 1.33s/it]
segment: 12%|█▏ | 29/250 [00:41<04:54, 1.33s/it]
segment: 12%|█▏ | 30/250 [00:43<04:53, 1.33s/it]
segment: 12%|█▏ | 31/250 [00:44<04:51, 1.33s/it]
segment: 13%|█▎ | 32/250 [00:45<04:48, 1.32s/it]
segment: 13%|█▎ | 33/250 [00:47<04:46, 1.32s/it]
segment: 14%|█▎ | 34/250 [00:48<04:44, 1.32s/it]
segment: 14%|█▍ | 35/250 [00:49<04:43, 1.32s/it]
segment: 14%|█▍ | 36/250 [00:51<04:43, 1.32s/it]
segment: 15%|█▍ | 37/250 [00:52<04:41, 1.32s/it]
segment: 15%|█▌ | 38/250 [00:53<04:40, 1.32s/it]
segment: 16%|█▌ | 39/250 [00:55<04:39, 1.32s/it]
segment: 16%|█▌ | 40/250 [00:56<04:36, 1.32s/it]
segment: 16%|█▋ | 41/250 [00:57<04:34, 1.31s/it]
segment: 17%|█▋ | 42/250 [00:59<04:33, 1.32s/it]
segment: 17%|█▋ | 43/250 [01:00<04:32, 1.32s/it]
segment: 18%|█▊ | 44/250 [01:01<04:31, 1.32s/it]
segment: 18%|█▊ | 45/250 [01:03<04:34, 1.34s/it]
segment: 18%|█▊ | 46/250 [01:04<04:31, 1.33s/it]
segment: 19%|█▉ | 47/250 [01:05<04:27, 1.32s/it]
segment: 19%|█▉ | 48/250 [01:06<04:25, 1.32s/it]
segment: 20%|█▉ | 49/250 [01:08<04:23, 1.31s/it]
segment: 20%|██ | 50/250 [01:09<04:21, 1.31s/it]
segment: 20%|██ | 51/250 [01:10<04:21, 1.31s/it]
segment: 21%|██ | 52/250 [01:12<04:20, 1.31s/it]
segment: 21%|██ | 53/250 [01:13<04:19, 1.32s/it]
segment: 22%|██▏ | 54/250 [01:14<04:17, 1.32s/it]
segment: 22%|██▏ | 55/250 [01:16<04:16, 1.32s/it]
segment: 22%|██▏ | 56/250 [01:17<04:15, 1.32s/it]
segment: 23%|██▎ | 57/250 [01:18<04:14, 1.32s/it]
segment: 23%|██▎ | 58/250 [01:20<04:13, 1.32s/it]
segment: 24%|██▎ | 59/250 [01:21<04:11, 1.32s/it]
segment: 24%|██▍ | 60/250 [01:22<04:10, 1.32s/it]
segment: 24%|██▍ | 61/250 [01:24<04:08, 1.32s/it]
segment: 25%|██▍ | 62/250 [01:25<04:07, 1.32s/it]
segment: 25%|██▌ | 63/250 [01:26<04:06, 1.32s/it]
segment: 26%|██▌ | 64/250 [01:27<04:04, 1.31s/it]
segment: 26%|██▌ | 65/250 [01:29<04:03, 1.32s/it]
segment: 26%|██▋ | 66/250 [01:30<04:01, 1.32s/it]
segment: 27%|██▋ | 67/250 [01:31<04:01, 1.32s/it]
segment: 27%|██▋ | 68/250 [01:33<03:58, 1.31s/it]
segment: 28%|██▊ | 69/250 [01:34<03:57, 1.31s/it]
segment: 28%|██▊ | 70/250 [01:35<03:56, 1.31s/it]
segment: 28%|██▊ | 71/250 [01:37<03:56, 1.32s/it]
segment: 29%|██▉ | 72/250 [01:38<03:54, 1.32s/it]
segment: 29%|██▉ | 73/250 [01:39<03:52, 1.31s/it]
segment: 30%|██▉ | 74/250 [01:41<03:52, 1.32s/it]
segment: 30%|███ | 75/250 [01:42<03:51, 1.32s/it]
segment: 30%|███ | 76/250 [01:43<03:49, 1.32s/it]
segment: 31%|███ | 77/250 [01:45<03:47, 1.32s/it]
segment: 31%|███ | 78/250 [01:46<03:47, 1.32s/it]
segment: 32%|███▏ | 79/250 [01:47<03:44, 1.31s/it]
segment: 32%|███▏ | 80/250 [01:49<03:43, 1.32s/it]
segment: 32%|███▏ | 81/250 [01:50<03:42, 1.32s/it]
segment: 33%|███▎ | 82/250 [01:51<03:41, 1.32s/it]
segment: 33%|███▎ | 83/250 [01:53<03:39, 1.31s/it]
segment: 34%|███▎ | 84/250 [01:54<03:37, 1.31s/it]
segment: 34%|███▍ | 85/250 [01:55<03:37, 1.32s/it]
segment: 34%|███▍ | 86/250 [01:56<03:35, 1.31s/it]
segment: 35%|███▍ | 87/250 [01:58<03:34, 1.31s/it]
segment: 35%|███▌ | 88/250 [01:59<03:33, 1.32s/it]
segment: 36%|███▌ | 89/250 [02:00<03:32, 1.32s/it]
segment: 36%|███▌ | 90/250 [02:02<03:30, 1.32s/it]
segment: 36%|███▋ | 91/250 [02:03<03:30, 1.32s/it]
segment: 37%|███▋ | 92/250 [02:04<03:29, 1.32s/it]
segment: 37%|███▋ | 93/250 [02:06<03:28, 1.32s/it]
segment: 38%|███▊ | 94/250 [02:07<03:26, 1.33s/it]
segment: 38%|███▊ | 95/250 [02:08<03:24, 1.32s/it]
segment: 38%|███▊ | 96/250 [02:10<03:23, 1.32s/it]
segment: 39%|███▉ | 97/250 [02:11<03:21, 1.32s/it]
segment: 39%|███▉ | 98/250 [02:12<03:20, 1.32s/it]
segment: 40%|███▉ | 99/250 [02:14<03:18, 1.32s/it]
segment: 40%|████ | 100/250 [02:15<03:16, 1.31s/it]
segment: 40%|████ | 101/250 [02:16<03:15, 1.31s/it]
segment: 41%|████ | 102/250 [02:18<03:14, 1.31s/it]
segment: 41%|████ | 103/250 [02:19<03:13, 1.31s/it]
segment: 42%|████▏ | 104/250 [02:20<03:11, 1.31s/it]
segment: 42%|████▏ | 105/250 [02:21<03:10, 1.31s/it]
segment: 42%|████▏ | 106/250 [02:23<03:09, 1.32s/it]
segment: 43%|████▎ | 107/250 [02:24<03:08, 1.32s/it]
segment: 43%|████▎ | 108/250 [02:25<03:06, 1.31s/it]
segment: 44%|████▎ | 109/250 [02:27<03:04, 1.31s/it]
segment: 44%|████▍ | 110/250 [02:28<03:03, 1.31s/it]
segment: 44%|████▍ | 111/250 [02:29<03:03, 1.32s/it]
segment: 45%|████▍ | 112/250 [02:31<03:02, 1.32s/it]
segment: 45%|████▌ | 113/250 [02:32<03:00, 1.32s/it]
segment: 46%|████▌ | 114/250 [02:33<02:59, 1.32s/it]
segment: 46%|████▌ | 115/250 [02:35<02:57, 1.31s/it]
segment: 46%|████▋ | 116/250 [02:36<02:56, 1.32s/it]
segment: 47%|████▋ | 117/250 [02:37<02:55, 1.32s/it]
segment: 47%|████▋ | 118/250 [02:39<02:54, 1.32s/it]
segment: 48%|████▊ | 119/250 [02:40<02:52, 1.32s/it]
segment: 48%|████▊ | 120/250 [02:41<02:50, 1.31s/it]
segment: 48%|████▊ | 121/250 [02:43<02:48, 1.31s/it]
segment: 49%|████▉ | 122/250 [02:44<02:47, 1.31s/it]
segment: 49%|████▉ | 123/250 [02:45<02:46, 1.31s/it]
segment: 50%|████▉ | 124/250 [02:46<02:45, 1.32s/it]
segment: 50%|█████ | 125/250 [02:48<02:43, 1.31s/it]
segment: 50%|█████ | 126/250 [02:49<02:42, 1.31s/it]
segment: 51%|█████ | 127/250 [02:50<02:40, 1.31s/it]
segment: 51%|█████ | 128/250 [02:52<02:39, 1.31s/it]
segment: 52%|█████▏ | 129/250 [02:53<02:38, 1.31s/it]
segment: 52%|█████▏ | 130/250 [02:54<02:37, 1.31s/it]
segment: 52%|█████▏ | 131/250 [02:56<02:36, 1.32s/it]
segment: 53%|█████▎ | 132/250 [02:57<02:35, 1.32s/it]
segment: 53%|█████▎ | 133/250 [02:58<02:33, 1.31s/it]
segment: 54%|█████▎ | 134/250 [03:00<02:31, 1.31s/it]
segment: 54%|█████▍ | 135/250 [03:01<02:30, 1.31s/it]
segment: 54%|█████▍ | 136/250 [03:02<02:28, 1.31s/it]
segment: 55%|█████▍ | 137/250 [03:03<02:28, 1.31s/it]
segment: 55%|█████▌ | 138/250 [03:05<02:26, 1.31s/it]
segment: 56%|█████▌ | 139/250 [03:06<02:25, 1.31s/it]
segment: 56%|█████▌ | 140/250 [03:07<02:23, 1.31s/it]
segment: 56%|█████▋ | 141/250 [03:09<02:22, 1.31s/it]
segment: 57%|█████▋ | 142/250 [03:10<02:21, 1.31s/it]
segment: 57%|█████▋ | 143/250 [03:11<02:20, 1.31s/it]
segment: 58%|█████▊ | 144/250 [03:13<02:19, 1.32s/it]
segment: 58%|█████▊ | 145/250 [03:14<02:18, 1.32s/it]
segment: 58%|█████▊ | 146/250 [03:15<02:18, 1.33s/it]
segment: 59%|█████▉ | 147/250 [03:17<02:15, 1.31s/it]
segment: 59%|█████▉ | 148/250 [03:18<02:13, 1.31s/it]
segment: 60%|█████▉ | 149/250 [03:19<02:12, 1.31s/it]
segment: 60%|██████ | 150/250 [03:21<02:11, 1.32s/it]
segment: 60%|██████ | 151/250 [03:22<02:10, 1.32s/it]
segment: 61%|██████ | 152/250 [03:23<02:09, 1.32s/it]
segment: 61%|██████ | 153/250 [03:25<02:08, 1.32s/it]
segment: 62%|██████▏ | 154/250 [03:26<02:07, 1.33s/it]
segment: 62%|██████▏ | 155/250 [03:27<02:05, 1.32s/it]
segment: 62%|██████▏ | 156/250 [03:29<02:04, 1.33s/it]
segment: 63%|██████▎ | 157/250 [03:30<02:02, 1.32s/it]
segment: 63%|██████▎ | 158/250 [03:31<02:01, 1.32s/it]
segment: 64%|██████▎ | 159/250 [03:33<02:02, 1.35s/it]
segment: 64%|██████▍ | 160/250 [03:34<02:00, 1.34s/it]
segment: 64%|██████▍ | 161/250 [03:35<01:58, 1.34s/it]
segment: 65%|██████▍ | 162/250 [03:37<01:57, 1.33s/it]
segment: 65%|██████▌ | 163/250 [03:38<01:55, 1.33s/it]
segment: 66%|██████▌ | 164/250 [03:39<02:00, 1.40s/it]
segment: 66%|██████▌ | 165/250 [03:41<02:09, 1.53s/it]
segment: 66%|██████▋ | 166/250 [03:43<02:16, 1.62s/it]
segment: 67%|██████▋ | 167/250 [03:45<02:19, 1.68s/it]
segment: 67%|██████▋ | 168/250 [03:47<02:21, 1.73s/it]
segment: 68%|██████▊ | 169/250 [03:49<02:22, 1.76s/it]
segment: 68%|██████▊ | 170/250 [03:50<02:22, 1.79s/it]
segment: 68%|██████▊ | 171/250 [03:52<02:22, 1.80s/it]
segment: 69%|██████▉ | 172/250 [03:54<02:21, 1.82s/it]
segment: 69%|██████▉ | 173/250 [03:56<02:20, 1.82s/it]
segment: 70%|██████▉ | 174/250 [03:58<02:19, 1.83s/it]
segment: 70%|███████ | 175/250 [04:00<02:19, 1.86s/it]
segment: 70%|███████ | 176/250 [04:02<02:17, 1.85s/it]
segment: 71%|███████ | 177/250 [04:03<02:15, 1.85s/it]
segment: 71%|███████ | 178/250 [04:05<02:13, 1.86s/it]
segment: 72%|███████▏ | 179/250 [04:07<02:11, 1.86s/it]
segment: 72%|███████▏ | 180/250 [04:09<02:09, 1.85s/it]
segment: 72%|███████▏ | 181/250 [04:11<02:07, 1.84s/it]
segment: 73%|███████▎ | 182/250 [04:13<02:05, 1.84s/it]
segment: 73%|███████▎ | 183/250 [04:15<02:03, 1.84s/it]
segment: 74%|███████▎ | 184/250 [04:16<02:01, 1.84s/it]
segment: 74%|███████▍ | 185/250 [04:18<01:59, 1.84s/it]
segment: 74%|███████▍ | 186/250 [04:20<01:58, 1.85s/it]
segment: 75%|███████▍ | 187/250 [04:22<01:56, 1.85s/it]
segment: 75%|███████▌ | 188/250 [04:24<01:54, 1.85s/it]
segment: 76%|███████▌ | 189/250 [04:26<01:52, 1.85s/it]
segment: 76%|███████▌ | 190/250 [04:27<01:50, 1.85s/it]
segment: 76%|███████▋ | 191/250 [04:29<01:49, 1.86s/it]
segment: 77%|███████▋ | 192/250 [04:31<01:47, 1.86s/it]
segment: 77%|███████▋ | 193/250 [04:33<01:45, 1.85s/it]
segment: 78%|███████▊ | 194/250 [04:35<01:43, 1.85s/it]
segment: 78%|███████▊ | 195/250 [04:37<01:41, 1.85s/it]
segment: 78%|███████▊ | 196/250 [04:39<01:40, 1.86s/it]
segment: 79%|███████▉ | 197/250 [04:40<01:38, 1.86s/it]
segment: 79%|███████▉ | 198/250 [04:42<01:36, 1.85s/it]
segment: 80%|███████▉ | 199/250 [04:44<01:34, 1.85s/it]
segment: 80%|████████ | 200/250 [04:46<01:32, 1.85s/it]
segment: 80%|████████ | 201/250 [04:48<01:30, 1.85s/it]
segment: 81%|████████ | 202/250 [04:50<01:28, 1.85s/it]
segment: 81%|████████ | 203/250 [04:52<01:26, 1.85s/it]
segment: 82%|████████▏ | 204/250 [04:53<01:25, 1.85s/it]
segment: 82%|████████▏ | 205/250 [04:55<01:23, 1.85s/it]
segment: 82%|████████▏ | 206/250 [04:57<01:22, 1.87s/it]
segment: 83%|████████▎ | 207/250 [04:59<01:20, 1.86s/it]
segment: 83%|████████▎ | 208/250 [05:01<01:18, 1.86s/it]
segment: 84%|████████▎ | 209/250 [05:03<01:16, 1.86s/it]
segment: 84%|████████▍ | 210/250 [05:05<01:15, 1.88s/it]
segment: 84%|████████▍ | 211/250 [05:06<01:12, 1.87s/it]
segment: 85%|████████▍ | 212/250 [05:08<01:10, 1.86s/it]
segment: 85%|████████▌ | 213/250 [05:10<01:08, 1.85s/it]
segment: 86%|████████▌ | 214/250 [05:12<01:06, 1.86s/it]
segment: 86%|████████▌ | 215/250 [05:14<01:04, 1.85s/it]
segment: 86%|████████▋ | 216/250 [05:16<01:02, 1.85s/it]
segment: 87%|████████▋ | 217/250 [05:18<01:01, 1.85s/it]
segment: 87%|████████▋ | 218/250 [05:19<00:59, 1.86s/it]
segment: 88%|████████▊ | 219/250 [05:21<00:57, 1.85s/it]
segment: 88%|████████▊ | 220/250 [05:23<00:55, 1.85s/it]
segment: 88%|████████▊ | 221/250 [05:25<00:53, 1.85s/it]
segment: 89%|████████▉ | 222/250 [05:27<00:51, 1.85s/it]
segment: 89%|████████▉ | 223/250 [05:29<00:49, 1.85s/it]
segment: 90%|████████▉ | 224/250 [05:31<00:48, 1.85s/it]
segment: 90%|█████████ | 225/250 [05:32<00:46, 1.84s/it]
segment: 90%|█████████ | 226/250 [05:34<00:44, 1.84s/it]
segment: 91%|█████████ | 227/250 [05:36<00:42, 1.84s/it]
segment: 91%|█████████ | 228/250 [05:38<00:40, 1.85s/it]
segment: 92%|█████████▏| 229/250 [05:40<00:38, 1.85s/it]
segment: 92%|█████████▏| 230/250 [05:42<00:36, 1.84s/it]
segment: 92%|█████████▏| 231/250 [05:43<00:34, 1.84s/it]
segment: 93%|█████████▎| 232/250 [05:45<00:33, 1.84s/it]
segment: 93%|█████████▎| 233/250 [05:47<00:31, 1.85s/it]
segment: 94%|█████████▎| 234/250 [05:49<00:29, 1.86s/it]
segment: 94%|█████████▍| 235/250 [05:51<00:28, 1.88s/it]
segment: 94%|█████████▍| 236/250 [05:53<00:26, 1.87s/it]
segment: 95%|█████████▍| 237/250 [05:56<00:30, 2.35s/it]
segment: 95%|█████████▌| 238/250 [05:58<00:26, 2.20s/it]
segment: 96%|█████████▌| 239/250 [06:00<00:22, 2.09s/it]
segment: 96%|█████████▌| 240/250 [06:02<00:20, 2.01s/it]
segment: 96%|█████████▋| 241/250 [06:04<00:17, 1.96s/it]
segment: 97%|█████████▋| 242/250 [06:05<00:15, 1.92s/it]
segment: 97%|█████████▋| 243/250 [06:07<00:13, 1.90s/it]
segment: 98%|█████████▊| 244/250 [06:09<00:11, 1.88s/it]
segment: 98%|█████████▊| 245/250 [06:11<00:09, 1.87s/it]
segment: 98%|█████████▊| 246/250 [06:13<00:07, 1.86s/it]
segment: 99%|█████████▉| 247/250 [06:15<00:05, 1.86s/it]
\ No newline at end of file diff --git a/logs/rho_full_width.log b/logs/rho_full_width.log new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/logs/rho_full_width.log diff --git a/tests/test_core.py b/tests/test_core.py index 50d3aad..09e58f1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -234,3 +234,92 @@ def test_caption_encoding_matches_declared_factors(): assert states.shape == (2, 9) assert states[0][0] > 0 and states[0][2 + 2] > 0 and states[0][2 + 4 + 0] > 0 assert states[1][1] > 0 and states[1][2 + 0] > 0 and states[1][2 + 4 + 1] > 0 + + +def test_shared_rank_separates_aligned_from_shuffled(): + """The shared-direction count must sit at the null when nothing is shared.""" + import numpy as np + from worldalign.shared_rank import spectrum + + size, width = 192, 16 + generator = np.random.default_rng(0) + common = generator.normal(size=(size, 8)) + shared_field = common @ common.T + visual = shared_field + 0.05 * generator.normal(size=(size, size)) + text = shared_field + 0.05 * generator.normal(size=(size, size)) + visual, text = (visual + visual.T) / 2, (text + text.T) / 2 + + def count(first, second): + _, first_vectors = spectrum(first) + _, second_vectors = spectrum(second) + cosines = np.linalg.svd( + first_vectors[:, :width].T @ second_vectors[:, :width], compute_uv=False + ) + return int((np.clip(cosines, 0, 1) > 0.7).sum()) + + order = generator.permutation(size) + aligned = count(visual, text) + shuffled = count(visual, text[np.ix_(order, order)]) + assert aligned >= 8, aligned + assert shuffled <= 2, shuffled + + +def test_degree_decomposition_is_exact_and_orthogonal(): + """Fitted plus residual must reconstruct the field off the diagonal.""" + import numpy as np + from worldalign.field_anatomy import degree_part + + generator = np.random.default_rng(1) + size = 32 + rows = generator.normal(size=(size, 1)) + field = rows + rows.T + 0.1 * generator.normal(size=(size, size)) + np.fill_diagonal(field, 0.0) + fitted, residual = degree_part(field) + mask = ~np.eye(size, dtype=bool) + assert np.allclose(fitted[mask] + residual[mask], field[mask]) + # a field that is purely additive leaves almost nothing in the residual + pure = rows + rows.T + np.fill_diagonal(pure, 0.0) + _, pure_residual = degree_part(pure) + assert np.abs(pure_residual[mask]).max() < 1e-9 + + +def test_third_moment_kernel_raises_field_rank(): + """A degree-3 set kernel must span more directions than a degree-2 one.""" + import numpy as np + from worldalign.natural_pipeline import moment_field_degree + + generator = torch.Generator().manual_seed(0) + sets = [torch.randn(4, 8, generator=generator) for _ in range(24)] + second = moment_field_degree(sets, 2, 8).double().numpy() + third = moment_field_degree(sets, 3, 8).double().numpy() + + def effective_rank(matrix): + values = np.abs(np.linalg.eigvalsh((matrix + matrix.T) / 2)) + weights = values / values.sum() + weights = weights[weights > 1e-15] + return float(np.exp(-(weights * np.log(weights)).sum())) + + assert effective_rank(third) > effective_rank(second) + + +def test_structured_phrase_encoding_separates_head_from_modifier(): + """Word order must change the encoding, which averaging cannot express.""" + import numpy as np + + vectors = {"red": np.array([1.0, 0.0]), "bus": np.array([0.0, 1.0])} + + def structured(tokens): + head = vectors[tokens[-1]] + rest = ( + np.mean([vectors[t] for t in tokens[:-1]], axis=0) + if len(tokens) > 1 + else np.zeros(2) + ) + return np.concatenate([head, rest]) + + assert not np.allclose(structured(["red", "bus"]), structured(["bus", "red"])) + assert np.allclose( + np.mean([vectors[t] for t in ["red", "bus"]], axis=0), + np.mean([vectors[t] for t in ["bus", "red"]], axis=0), + ) diff --git a/worldalign/field_anatomy.py b/worldalign/field_anatomy.py index 6156018..9e04ec6 100644 --- a/worldalign/field_anatomy.py +++ b/worldalign/field_anatomy.py @@ -49,23 +49,38 @@ def correlate(first: np.ndarray, second: np.ndarray) -> float: return float(np.corrcoef(offdiagonal(first), offdiagonal(second))[0, 1]) -def degree_part(matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]: +def degree_part( + matrix: np.ndarray, sweeps: int = 200 +) -> tuple[np.ndarray, np.ndarray]: """Split into the additive row/column-mean model and its residual. The two-way additive fit m + r_i + c_j is what a pure busyness effect produces: every entry explained by how prominent each of its two scenes is, with nothing said about the pair. + + The fit is swept to convergence rather than taken in one pass. With the + diagonal excluded the design is unbalanced, so a single pass of row and + column means leaves an O(1/n) piece of a pure degree effect behind -- + small, but it is exactly the quantity this function exists to remove. + Alternating the two sweeps converges to the correct fit. """ size = len(matrix) mask = ~np.eye(size, dtype=bool) - work = matrix.copy().astype(np.float64) - np.fill_diagonal(work, np.nan) + work = np.where(mask, matrix.astype(np.float64), np.nan) grand = np.nanmean(work) - rows = np.nanmean(work, axis=1, keepdims=True) - grand - cols = np.nanmean(work, axis=0, keepdims=True) - grand + rows = np.zeros((size, 1)) + cols = np.zeros((1, size)) + for _ in range(sweeps): + residual = work - (grand + rows + cols) + step = np.nanmean(residual, axis=1, keepdims=True) + rows = rows + step + residual = work - (grand + rows + cols) + step_columns = np.nanmean(residual, axis=0, keepdims=True) + cols = cols + step_columns + if max(np.abs(step).max(), np.abs(step_columns).max()) < 1e-12: + break fitted = grand + rows + cols - residual = np.where(mask, work - fitted, 0.0) - return np.where(mask, fitted, 0.0), residual + return np.where(mask, fitted, 0.0), np.where(mask, work - fitted, 0.0) def spectral_strip(matrix: np.ndarray, components: int) -> np.ndarray: diff --git a/worldalign/natural_pipeline.py b/worldalign/natural_pipeline.py index 1a4f63e..1844a6d 100644 --- a/worldalign/natural_pipeline.py +++ b/worldalign/natural_pipeline.py @@ -53,6 +53,9 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--views", type=int, default=1) parser.add_argument("--moment-degree", type=int, default=2, choices=[2, 3]) parser.add_argument("--third-width", type=int, default=12) + parser.add_argument( + "--phrase-encoding", default="mean", choices=["mean", "structured"] + ) parser.add_argument("--seed", type=int, default=0) parser.add_argument("--output", default="artifacts/vg_5k/natural_pipeline.pt") return parser.parse_args() @@ -160,13 +163,39 @@ def main() -> None: vectors = word_vectors(load_phrases(vg_dir / "text_nodes.jsonl", "region_closed"), args) + width = args.word_vectors + + def encode_phrase(phrase: str) -> np.ndarray | None: + """One vector per region description. + + Averaging every word of a phrase collapses "red bus" and "bus red" + and, worse, mixes the thing named with what is said about it. The + synthetic world never had to face this because its captions were + encoded into separate factor blocks. Splitting the head from its + modifiers recovers part of that structure from English word order + alone -- the last token of an English noun phrase is its head -- which + is a fact about one language, not a claim about the other modality, + so it stays inside Tier 0. + """ + tokens = [t for t in re.findall(r"[a-z]+", phrase.lower()) if t in vectors] + if not tokens: + return None + if args.phrase_encoding == "mean": + return np.mean([vectors[t] for t in tokens], axis=0) + head = vectors[tokens[-1]] + modifiers = ( + np.mean([vectors[t] for t in tokens[:-1]], axis=0) + if len(tokens) > 1 + else np.zeros(width) + ) + return np.concatenate([head, modifiers]) + def language_state(node: str) -> np.ndarray | None: - rows = [] - for phrase in records[node]["region_closed"]: - hits = [vectors[token] for token in re.findall(r"[a-z]+", phrase.lower()) - if token in vectors] - if hits: - rows.append(np.mean(hits, axis=0)) + rows = [ + row + for row in (encode_phrase(p) for p in records[node]["region_closed"]) + if row is not None + ] return np.stack(rows) if rows else None def vision_state(node: str, view: int) -> np.ndarray | None: @@ -245,6 +274,7 @@ def main() -> None: "kept_directions": keep, "weight_power": args.weight_power, "moment_degree": args.moment_degree, + "phrase_encoding": args.phrase_encoding, "field_correlation_at_truth": correlation, "recovery_threshold": 0.9, } diff --git a/worldalign/rho_at_full_width.py b/worldalign/rho_at_full_width.py new file mode 100644 index 0000000..c30c5cc --- /dev/null +++ b/worldalign/rho_at_full_width.py @@ -0,0 +1,137 @@ +"""The other axis: recovery against correlation with the shared width intact. + +The truncation ladder varied the width at nearly fixed correlation. This +varies the correlation at full width, by adding independent noise to both +fields, and the two together give the picture the single statistic could not. + +The control matters because it guards against overcorrecting. Showing that +width is a separate necessary condition does not show that correlation stopped +mattering, and natural data is short on both -- correlation 0.725 against 0.93, +width 15 against 26. If full-width fields still recover at a correlation near +0.72, then width is what natural data lacks; if they do not, both gaps are real +and the correlation remains a target rather than a discredited one. +""" + +from __future__ import annotations + +import argparse +import json + +import numpy as np +import torch + +from .common import write_json +from .shared_rank import spectrum +from .spectral_match import grampa +from .synth_fast_gate import ClosedFormEnergy, all_swaps, steepest_descent +from .synth_triangle_gate import standardized + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--fields", default="artifacts/synth_v1/fields_tier0_ws_256.pt") + parser.add_argument("--trials", type=int, default=3) + parser.add_argument("--device", default="cuda:3") + parser.add_argument( + "--noise", type=float, nargs="+", + default=[0.0, 0.2, 0.35, 0.5, 0.7, 0.9, 1.2], + ) + parser.add_argument("--output", default="artifacts/synth_v1/rho_at_full_width.json") + return parser.parse_args() + + +def offdiagonal(matrix: np.ndarray) -> np.ndarray: + return matrix[~np.eye(len(matrix), dtype=bool)] + + +def normalise(matrix: np.ndarray) -> np.ndarray: + values = offdiagonal(matrix) + out = (matrix - values.mean()) / values.std() + np.fill_diagonal(out, 0.0) + return out + + +def symmetric_noise(size: int, generator) -> np.ndarray: + raw = generator.normal(size=(size, size)) + out = (raw + raw.T) / np.sqrt(2.0) + np.fill_diagonal(out, 0.0) + return out + + +def shared_count(visual: np.ndarray, text: np.ndarray, width: int = 32) -> int: + _, visual_vectors = spectrum(visual) + _, text_vectors = spectrum(text) + cosines = np.clip( + np.linalg.svd( + visual_vectors[:, :width].T @ text_vectors[:, :width], compute_uv=False + ), 0.0, 1.0, + ) + return int((cosines > 0.7).sum()) + + +def main() -> None: + args = parse_args() + state = torch.load(args.fields, map_location="cpu", weights_only=False) + visual_clean = normalise(state["visual_field"].double().numpy()) + text_clean = normalise(state["text_field"].double().numpy()) + size = len(visual_clean) + device = torch.device(args.device) + swaps = all_swaps(size, device) + + rows = [] + for level in args.noise: + correlations, accuracies, widths = [], [], [] + for trial in range(args.trials): + generator = np.random.default_rng(1000 + trial) + visual = normalise( + visual_clean + level * symmetric_noise(size, generator) + ) + text = normalise(text_clean + level * symmetric_noise(size, generator)) + mask = ~np.eye(size, dtype=bool) + correlations.append(float(np.corrcoef(visual[mask], text[mask])[0, 1])) + widths.append(shared_count(visual, text)) + + hidden = generator.permutation(size) + shuffled = text[np.ix_(hidden, hidden)] + columns = grampa(visual, shuffled, 1.0) + energy = ClosedFormEnergy( + standardized(torch.from_numpy(shuffled).to(device)).float(), + standardized(torch.from_numpy(visual).to(device)).float(), + 1.0, 1.0, 256, + ) + final, _ = steepest_descent( + energy, torch.from_numpy(columns.copy()).to(device), swaps, 4000 + ) + accuracies.append( + float((hidden[final.cpu().numpy()] == np.arange(size)).mean()) + ) + row = { + "noise": level, + "correlation": float(np.mean(correlations)), + "shared_directions": float(np.mean(widths)), + "recovery": float(np.mean(accuracies)), + } + rows.append(row) + print( + f"noise={level:<5} rho={row['correlation']:.3f} " + f"shared={row['shared_directions']:5.1f} " + f"recovery={row['recovery']:.3f}", + flush=True, + ) + + summary = { + "protocol": ( + "Independent symmetric noise added to both fields, so the shared " + "spectrum stays wide while the correlation falls. Hidden pairing " + "generated per trial and read only for scoring." + ), + "size": size, + "chance": 1.0 / size, + "rows": rows, + } + print(json.dumps({"chance": 1.0 / size})) + write_json(args.output, summary) + + +if __name__ == "__main__": + main() |
