hypertopos 0.2.2__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hypertopos-0.2.2 → hypertopos-0.3.1}/CHANGELOG.md +61 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/LICENSE.md +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.1}/PKG-INFO +3 -5
- {hypertopos-0.2.2 → hypertopos-0.3.1}/README.md +2 -2
- {hypertopos-0.2.2 → hypertopos-0.3.1}/ROADMAP.md +54 -18
- {hypertopos-0.2.2 → hypertopos-0.3.1}/SECURITY.md +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/api-reference.md +4 -4
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/architecture.md +1 -1
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/concepts.md +10 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/configuration.md +1 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/data-format.md +2 -1
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/__init__.py +1 -1
- hypertopos-0.3.1/hypertopos/builder/_scatter.py +72 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/builder/_stats.py +25 -1
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/builder/builder.py +632 -213
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/builder/derived.py +23 -19
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/build.py +9 -1
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/aggregation.py +282 -523
- hypertopos-0.3.1/hypertopos/engine/fdr.py +89 -0
- hypertopos-0.3.1/hypertopos/engine/lance_sql_agg.py +798 -0
- hypertopos-0.3.1/hypertopos/engine/selection.py +102 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/navigation/navigator.py +281 -62
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/navigation/scanner.py +41 -64
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/storage/reader.py +34 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/storage/writer.py +35 -3
- {hypertopos-0.2.2 → hypertopos-0.3.1}/pyproject.toml +3 -6
- hypertopos-0.3.1/tests/conftest.py +89 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_aggregation_engine.py +0 -66
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_builder.py +28 -6
- hypertopos-0.3.1/tests/test_builder_chunking.py +93 -0
- hypertopos-0.3.1/tests/test_builder_contagion.py +99 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_builder_derived.py +18 -3
- hypertopos-0.3.1/tests/test_builder_rolling_z.py +83 -0
- hypertopos-0.3.1/tests/test_builder_scatter.py +177 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_calibration.py +10 -8
- hypertopos-0.3.1/tests/test_fdr.py +128 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_fts.py +15 -5
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_geometric_path.py +7 -8
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_incremental.py +4 -3
- hypertopos-0.3.1/tests/test_lance_sql_agg.py +548 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_navigation.py +34 -21
- hypertopos-0.3.1/tests/test_navigator_fdr_select.py +540 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_scanner.py +45 -0
- hypertopos-0.3.1/tests/test_selection.py +131 -0
- hypertopos-0.2.2/hypertopos/engine/datafusion_agg.py +0 -602
- hypertopos-0.2.2/hypertopos/engine/subprocess_agg.py +0 -742
- hypertopos-0.2.2/tests/conftest.py +0 -83
- hypertopos-0.2.2/tests/test_datafusion_agg.py +0 -788
- hypertopos-0.2.2/tests/test_subprocess_agg.py +0 -961
- {hypertopos-0.2.2 → hypertopos-0.3.1}/.github/workflows/publish-pypi.yml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/.gitignore +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/LICENSE +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r4/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r5/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/results/0.1.0/r6/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_cpty_banks.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_districts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_loan_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_operations.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_orders.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/prepare_tx_types.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/berka/sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/_layer2_cross_eval.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/_layer3_cross_eval.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/hi_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/hi_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/layer1_passive_scan.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/layer2_xgboost_sweep.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/layer3_typology_filter.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/li_small_sphere/prepare_accounts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/li_small_sphere/prepare_currencies.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/li_small_sphere/prepare_payment_formats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/li_small_sphere/prepare_transactions.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/li_small_sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/ibm-aml/passive-scan-calibration.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/README.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/SUMMARY.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r1/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r2/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-a-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-b-scoring.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-exp-c-report.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/results/0.1.0/r3/0.1.0-settings.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/sphere/prepare_trips.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmark/nyc-taxi/sphere/sphere.yaml +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/benchmarks.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/build-pipeline.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/core-objects.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/cutting-plane.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/directory-layout.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/hypertopos-overview.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/model-lifecycle.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/navigation-primitives.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/images/three-scales.svg +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/docs/quickstart.md +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/builder/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/builder/_writer.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/builder/mapping.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/__main__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/info.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/main.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/schema.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/cli/sources.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/calibration.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/chains.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/composition.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/forecast.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/geometry.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/invalidation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/engine/investigation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/model/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/model/manifest.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/model/objects.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/model/sphere.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/navigation/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/sphere.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/storage/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/storage/_schemas.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/storage/cache.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/utils/__init__.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/hypertopos/utils/arrow.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/.gitkeep +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/fixtures/generate_fixtures.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_arrow_utils.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_builder_alias.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_builder_edges.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_cache.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_chains.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_cli.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_cli_alias.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_cli_sources.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_compaction.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_composition.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_continuous_event_dims.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_data_quality_prop.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_detection.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_edge_table.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_engine.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_forecast.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_geometry_stats.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_hybrid_search.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_invalidation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_investigation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_mapping.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_model.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_mvcc_session.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_navigator_alerts.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_observability.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_precision_stack.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_reputation.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_row_id_cache.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_sphere.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_sphere_overview_perf.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_stats_guard.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_storage.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_streaming_reads.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_temporal_centroids.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_trajectory_ann.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_witness_cohort.py +0 -0
- {hypertopos-0.2.2 → hypertopos-0.3.1}/tests/test_witness_set.py +0 -0
|
@@ -5,6 +5,67 @@ All notable changes to hypertopos will be documented in this file.
|
|
|
5
5
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.1] — 2026-04-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Benjamini-Hochberg FDR control on attract primitives. `fdr_alpha` parameter on `pi5_attract_anomaly`, `pi6_attract_boundary`, `pi7_attract_hub`, and `pi9_attract_drift` applies BH multiple-testing correction to empirical-null p-values derived from delta rank percentile. Entities with `q_value > alpha` are removed before the top-N step. Guarantees `E[FDR] <= alpha`. Each retained entity carries a `q_value` — the minimum alpha at which it would still be retained. Off by default; legacy behavior preserved when omitted.
|
|
13
|
+
- Diverse selection via submodular facility location. `select="diverse"` on the same four primitives replaces top-N ranking with a lazy-greedy maximisation of the facility location objective over pairwise cosine similarity of delta vectors. Achieves `(1 - 1/e)` approximation to the optimal K-subset. Each selected entity reports a `representativeness` count — how many population members are nearest to it among the selected set. Covers the geometric space of the result set instead of clustering around a single extreme region.
|
|
14
|
+
- `engine/fdr.py` module: `benjamini_hochberg`, `empirical_p_values_from_rank`, `q_values_from_p_values` — pure NumPy, no state, no I/O.
|
|
15
|
+
- `engine/selection.py` module: `lazy_greedy_facility_location`, `compute_similarity_matrix` — pure NumPy, Nemhauser-Wolsey-Fisher optimality guarantee.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Build pipeline: derived dimension scatter loops replaced with Arrow `pc.index_in` + numpy fancy indexing. Eliminates per-row Python iteration in both static (`compute_derived_batch`) and temporal (`_precompute_shape_tensor`) build paths.
|
|
20
|
+
- Build pipeline: contagion stats computation (`_build_contagion_stats`) rewritten from Python loops over edge table to Arrow group_by + join. Eliminates O(E+N) Python iterations.
|
|
21
|
+
- Build pipeline: rolling z-score (`_compute_max_rolling_z`) uses Welford online algorithm. Reduces time complexity from O(n_buckets² × N × D) to O(n_buckets × N × D) and memory from O(N × n_buckets × D) to O(N × D).
|
|
22
|
+
- Build pipeline: temporal Arrow assembly uses single numpy reshape instead of per-bucket table construction loop.
|
|
23
|
+
- Build pipeline: graph dims temporal computation uses pre-sorted event table with `searchsorted` for O(1) per-bucket slicing instead of O(E) scan per bucket.
|
|
24
|
+
- Build pipeline: `build_temporal` adaptively chunks entities when the shape tensor would exceed available RAM. Auto-detects memory via platform APIs, falls back to 4 GB budget. Single-pass path (zero overhead) when tensor fits.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `empirical_p_values_from_rank` used a `1/N` floor where N was the input array length. When the input was a small `top_n` slice (e.g. 5 entities), the floor clipped all p-values to 0.2, causing BH to reject everything at `alpha=0.05`. Now uses a fixed `1e-10` floor — the rank percentile is already computed against the full population, so the floor only needs to prevent exact zero for numerical stability.
|
|
29
|
+
- `π7_attract_hub` and `π9_attract_drift` FDR computed p-values as `(N-i)/N` where N was the `top_n` result count (e.g. 10), making the best possible p-value `1/top_n` — mathematically unable to pass BH at any `alpha < 1/top_n`. Now uses population-level rank percentiles: hubs use the empirical CDF from the full scores array, drift uses the rank position relative to the total pre-truncation population.
|
|
30
|
+
|
|
31
|
+
## [0.3.0] — 2026-04-12
|
|
32
|
+
|
|
33
|
+
> **Theme:** Lance 3.x perf upgrade. Aggregate engine moves from a persistent subprocess worker plus an optional external DataFusion package to the Lance scanner's built-in DataFusion executor via `LanceDataset.sql(...)`. The build-time contagion stats table replaces the runtime edge-table replay in the graph contagion scanner. Format 2.2 is the new write default.
|
|
34
|
+
>
|
|
35
|
+
> **Mandatory rebuild for spheres built before this release.** See the Migration section below.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- `pylance` minimum bumped to 4.x. New writes target Lance format 2.2 with structural-decode parallelism, BSS float compression, cascading codecs, and block-level RLE on top of the previous baseline. Older spheres written with format 2.0 / 2.1 remain readable transparently.
|
|
40
|
+
- The aggregate engine now pushes GROUP BY computation directly into the Lance scanner. The new `lance_sql_agg` module hosts `aggregate_count`, `aggregate_metric` (sum / avg / min / max), `aggregate_filtered_metric`, `aggregate_pivot`, `aggregate_property`, `aggregate_percentile`, and `find_anomalies`. Each helper streams the columns it needs from the geometry / event-line / property-line Lance datasets through `LanceDataset.sql(...)` and finishes the join + aggregate in pyarrow on the small post-projection result. The full geometry table is never loaded into Python memory and the persistent subprocess worker is no longer involved for any of these paths.
|
|
41
|
+
- Test fixture cloning uses `lance.LanceDataset.shallow_clone` for every Lance dataset directory inside a sphere tree, with a thin `clone_sphere` helper in `tests/conftest.py` that walks the directory and falls back to `shutil.copy2` for non-Lance files. Shallow clone copies only metadata and references — measurably faster on Windows where deep `copytree` over a sphere built from many small Lance fragment files is dominated by per-file NTFS overhead.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `GDSBuilder` rejects patterns with zero declared dimensions (no relations, no event dimensions, no derived dimensions, no tracked properties) at validation time. A pattern with width-0 delta vectors had no meaningful geometry to compute even before this release; format 2.0 silently allowed the construct, format 2.1+ refuses to encode the resulting `fixed_size_list[0]` columns at all. The validation surfaces a typed error up front instead of letting the call descend into an opaque write-time panic.
|
|
46
|
+
- Builder precomputes per-entity contagion statistics for every pattern with an edge table and writes them to `_gds_meta/contagion_stats/{pattern_id}.lance` with a BTREE index on `primary_key`. The table holds `(primary_key, neighbor_count, anomalous_neighbor_count, contagion_ratio)` snapshotted at build time against the just-calibrated anchor geometry.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `_scan_graph` (the graph contagion source feeding `passive_scan` and `composite_risk`) reads the precomputed `_gds_meta/contagion_stats/{pattern_id}.lance` table directly and threshold-filters inline. The legacy edge-table-replay path is gone; spheres built before 0.3.0 must be rebuilt to get graph contagion hits.
|
|
51
|
+
- `contagion_score` anchor pattern resolution now correctly resolves via sibling lines when the anchor pattern is not directly associated with the target entity's line. Previously crashed with `KeyError` on spheres where the anchor pattern entity line differed from the navigated line.
|
|
52
|
+
- Edge table auto-detect excludes metadata columns (`created_at`, `changed_at`, `version`) from the timestamp type-based fallback, preventing incorrect timestamp column selection on entity lines with versioning columns. Amount candidate list widened to cover `fare_amount`, `total_amount`, `amount_received`, `amount_paid`.
|
|
53
|
+
|
|
54
|
+
### Security
|
|
55
|
+
|
|
56
|
+
- `lance_sql_agg` validates user-controlled SQL inputs up front. Lance's `LanceDataset.sql(...)` does not expose parameter binding, so values and identifiers are inlined into the query string. Filter values (entity primary keys) are escaped with `_escape_sql_string`, which doubles single quotes and rejects backslash and ASCII control characters. Column-name identifiers (`metric_col`, `pivot_field`, `prop_name`) are validated against `^[A-Za-z_][A-Za-z0-9_]*$` via `_validate_sql_identifier` before being inlined into SQL strings — anything outside that pattern raises immediately. Both helpers fail loud rather than silently produce wrong results or pass injected SQL through to the parser.
|
|
57
|
+
|
|
58
|
+
### Removed
|
|
59
|
+
|
|
60
|
+
- `hypertopos.engine.subprocess_agg` (the persistent subprocess worker and its JSON command protocol) is deleted; every entry point it served is now routed through `lance_sql_agg`.
|
|
61
|
+
- `hypertopos.engine.datafusion_agg` (the in-memory external-DataFusion fast path for count / metric / pivot / gbp / percentile) is deleted along with the optional `datafusion` extra in `pyproject.toml`. Lance 4.x's built-in DataFusion executor covers every query the external package was carrying.
|
|
62
|
+
- The matching `tests/test_subprocess_agg.py` and `tests/test_datafusion_agg.py` are deleted. Aggregate semantics are exercised through `tests/test_aggregation_engine.py` and the MCP tool tests.
|
|
63
|
+
|
|
64
|
+
### Migration
|
|
65
|
+
|
|
66
|
+
- **Rebuild required.** Spheres built before 0.3.0 are still openable but degrade gracefully on graph contagion — the precomputed `contagion_stats` table is created at build time only, and the runtime scanner returns zero hits without it. Rebuild with `hypertopos build --config sphere.yaml --force`. There are no in-place upgrade paths.
|
|
67
|
+
- The optional `datafusion` package is no longer a dependency of any aggregate path. If your environment installed it via `pip install hypertopos[datafusion]`, that extra now resolves to nothing — you can drop it from your install command.
|
|
68
|
+
|
|
8
69
|
## [0.2.2] — 2026-04-11
|
|
9
70
|
|
|
10
71
|
### Added
|
|
@@ -26,7 +26,7 @@ For purposes of this License:
|
|
|
26
26
|
- **Internal use** within an organization is not a competitive offering. Your
|
|
27
27
|
organization includes entities under common control.
|
|
28
28
|
|
|
29
|
-
**Change Date:** 2029-04-
|
|
29
|
+
**Change Date:** 2029-04-12
|
|
30
30
|
|
|
31
31
|
**Change License:** Apache License, Version 2.0
|
|
32
32
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertopos
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Understand the structure of your data — without training machine learning models
|
|
5
5
|
Project-URL: Homepage, https://github.com/hypertopos/hypertopos-py
|
|
6
6
|
Project-URL: Documentation, https://github.com/hypertopos/hypertopos-py/tree/main/docs
|
|
7
7
|
Project-URL: Repository, https://github.com/hypertopos/hypertopos-py
|
|
@@ -12,10 +12,8 @@ License-File: LICENSE.md
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Requires-Dist: numpy>=1.26
|
|
14
14
|
Requires-Dist: pyarrow>=14.0
|
|
15
|
-
Requires-Dist: pylance
|
|
15
|
+
Requires-Dist: pylance<5.0,>=4.0
|
|
16
16
|
Requires-Dist: pyyaml>=6.0
|
|
17
|
-
Provides-Extra: datafusion
|
|
18
|
-
Requires-Dist: datafusion>=40.0; extra == 'datafusion'
|
|
19
17
|
Provides-Extra: dev
|
|
20
18
|
Requires-Dist: mypy; extra == 'dev'
|
|
21
19
|
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[](LICENSE.md)
|
|
7
7
|
[](https://doi.org/10.5281/zenodo.19482069)
|
|
8
8
|
[](https://arrow.apache.org/docs/python/)
|
|
9
|
-
[](https://github.com/
|
|
9
|
+
[](https://github.com/lance-format/lance)
|
|
10
10
|
[](https://modelcontextprotocol.io)
|
|
11
|
-
[](pyproject.toml)
|
|
12
12
|
|
|
13
13
|
**hypertopos** transforms relational data into navigable geometric space.
|
|
14
14
|
|
|
@@ -2,7 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
> Planned direction. Priorities may change based on feedback.
|
|
4
4
|
|
|
5
|
-
## Current: 0.
|
|
5
|
+
## Current: 0.3.1
|
|
6
|
+
|
|
7
|
+
FDR control, diverse anomaly summarisation, and build pipeline optimization — Benjamini-Hochberg multiple-testing correction, submodular facility location, vectorized build with adaptive memory chunking. See CHANGELOG.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 0.3.0
|
|
12
|
+
|
|
13
|
+
Lance 3.x perf upgrade — aggregate engine rewritten around Lance SQL, precomputed contagion stats, format 2.2, contagion anchor resolution fix, edge table auto-detect fix. See CHANGELOG.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 0.2.2
|
|
6
18
|
|
|
7
19
|
As-of graph reconstruction across edge-table graph primitives + latency fix on `detect_cross_pattern_discrepancy`. See CHANGELOG.
|
|
8
20
|
|
|
@@ -26,33 +38,57 @@ First public release — full GDS stack, π1–π12, builder, MCP server, valida
|
|
|
26
38
|
|
|
27
39
|
---
|
|
28
40
|
|
|
29
|
-
## Plan: 0.3.
|
|
41
|
+
## Plan: 0.3.1
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
- Upgrade `pylance 2.0.1 → 3.0.1` — format 2.2, ~300% faster scans, LZ4/zstd compression
|
|
33
|
-
- Replace `shutil.copytree` with `lance.dataset.clone()` in test fixtures (O(1) metadata clone)
|
|
34
|
-
- Builder: incremental rebuild (geometry-only without `--force` wipe)
|
|
43
|
+
Two production-quality improvements on top of the 0.3.0 storage layer:
|
|
35
44
|
|
|
36
|
-
**
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- Tighten error handling in hot paths so failures are surfaced more consistently
|
|
40
|
-
- Consolidate repeated orchestration logic into shared helpers instead of duplicating it across modules
|
|
41
|
-
- Make the refactor pass incremental: preserve behavior, improve structure, then revisit deeper architectural boundaries
|
|
45
|
+
- **FDR control on anomaly queries** — Benjamini–Hochberg `fdr_alpha` parameter + per-row `q_value` column on `find_anomalies` and friends. Replaces unbounded "top-K by `delta_norm`" with a set whose false-discovery rate is bounded by α.
|
|
46
|
+
- **Diverse-coverage anomaly summarisation** — `select="diverse"` option using lazy-greedy submodular maximisation, plus a `representativeness` column ("this 1 anomaly stands in for 38 cases").
|
|
47
|
+
- **Build pipeline optimization** — vectorized scatter (Arrow `pc.index_in` + numpy), Welford rolling z-score, Arrow-native contagion stats, adaptive memory chunking for temporal build, pre-sorted graph dims bucketing.
|
|
42
48
|
|
|
43
|
-
|
|
44
|
-
- Native temporal source support for direct temporal inputs, without requiring manual dataset plumbing in benchmark scripts
|
|
45
|
-
- Optional weighted scoring mode that uses continuous intensity instead of binary counts
|
|
46
|
-
- **SphereProfiler** — autonomous sphere scanner that profiles all patterns, runs calibration sweeps across source combinations, proposes optimal PassiveScanner composition for Layer 1 surveillance. Core loop: enumerate patterns → single-source anomaly scans → greedy multi-source combination → ranked composition report. No labeled GT needed; optional GT file unlocks supervised calibration.
|
|
49
|
+
---
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
## Plan: 0.3.2
|
|
52
|
+
|
|
53
|
+
TBD.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Plan: 0.4.0
|
|
58
|
+
|
|
59
|
+
TBD.
|
|
50
60
|
|
|
51
61
|
---
|
|
52
62
|
|
|
53
63
|
## Future
|
|
54
64
|
|
|
65
|
+
**Detection quality**
|
|
66
|
+
- Edge-derived dimensions + temporal motif matcher
|
|
67
|
+
- Confidence scoring, robust estimators, multi-scale resolution — improve anomaly precision and reduce false positives on heavy-tail and multi-modal populations
|
|
68
|
+
- Lazy chain geometry — on-demand chain delta vectors via sampled population calibration; supplements the build-time `chain_lines` path
|
|
69
|
+
|
|
70
|
+
**Builder evolution**
|
|
71
|
+
- Incremental rebuild — geometry-only without `--force` wipe
|
|
72
|
+
- Generalized (d+m+g+t+s) dimension blocks — geographic / metric / semantic dimension support
|
|
73
|
+
|
|
74
|
+
**PassiveScanner evolution**
|
|
75
|
+
- Native temporal source support for direct temporal inputs, without requiring manual dataset plumbing in benchmark scripts
|
|
76
|
+
- Optional weighted scoring mode that uses continuous intensity instead of binary counts
|
|
77
|
+
- **SphereProfiler** — autonomous sphere scanner that profiles all patterns, runs calibration sweeps across source combinations, proposes optimal PassiveScanner composition for Layer 1 surveillance. Core loop: enumerate patterns → single-source anomaly scans → greedy multi-source combination → ranked composition report. No labeled GT needed; optional GT file unlocks supervised calibration.
|
|
78
|
+
|
|
79
|
+
**Code refactoring**
|
|
80
|
+
- Break up oversized modules into smaller, domain-focused components
|
|
81
|
+
- Reduce coupling between core layers by replacing private cross-layer access with explicit interfaces where practical
|
|
82
|
+
- Tighten error handling in hot paths so failures are surfaced more consistently
|
|
83
|
+
- Consolidate repeated orchestration logic into shared helpers instead of duplicating it across modules
|
|
84
|
+
- Incremental refactor: preserve behavior, improve structure, then revisit deeper architectural boundaries
|
|
85
|
+
|
|
86
|
+
**Cross-sphere capabilities**
|
|
55
87
|
- Cross-sphere comparison — dimensionless metrics across independently calibrated coordinate spaces
|
|
56
88
|
- What-if analysis — hypothetical edge changes producing modified coordinate vectors
|
|
89
|
+
|
|
90
|
+
**Enterprise / governance**
|
|
57
91
|
- Dimension access control — per-agent visibility constraints on delta dimensions
|
|
92
|
+
|
|
93
|
+
**Tooling**
|
|
58
94
|
- Runtime latency benchmarks in package docs
|
|
@@ -103,9 +103,9 @@ with sphere.session("agent-1") as session:
|
|
|
103
103
|
|
|
104
104
|
| Primitive | Method | What it does | Returns |
|
|
105
105
|
|-----------|--------|--------------|---------|
|
|
106
|
-
| π5 | `π5_attract_anomaly(pattern_id, radius, top_n)` | Find most anomalous polygons | `(list[Polygon], int, list, dict)` |
|
|
107
|
-
| π6 | `π6_attract_boundary(alias_id, pattern_id, direction, top_n)` | Find entities nearest to alias cutting plane | `list[(Polygon, float)]` |
|
|
108
|
-
| π7 | `π7_attract_hub(pattern_id, top_n, line_id_filter)` | Find entities with highest connectivity | `list[(str, int, float)]` |
|
|
106
|
+
| π5 | `π5_attract_anomaly(pattern_id, radius, top_n, fdr_alpha, fdr_method, select)` | Find most anomalous polygons | `(list[Polygon], int, list, dict)` |
|
|
107
|
+
| π6 | `π6_attract_boundary(alias_id, pattern_id, direction, top_n, fdr_alpha, fdr_method, select)` | Find entities nearest to alias cutting plane | `list[(Polygon, float)]` |
|
|
108
|
+
| π7 | `π7_attract_hub(pattern_id, top_n, line_id_filter, fdr_alpha, fdr_method, select)` | Find entities with highest connectivity | `list[(str, int, float)]` |
|
|
109
109
|
| π7+ | `π7_attract_hub_and_stats(pattern_id, top_n, line_id_filter)` | Hub ranking + population hub score statistics in one scan | `(list, dict)` |
|
|
110
110
|
| π8 | `π8_attract_cluster(pattern_id, n_clusters, top_n, sample_size)` | Discover geometric archetypes via k-means++ | `list[dict]` |
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@ with sphere.session("agent-1") as session:
|
|
|
113
113
|
|
|
114
114
|
| Primitive | Method | What it does | Returns |
|
|
115
115
|
|-----------|--------|--------------|---------|
|
|
116
|
-
| π9 | `π9_attract_drift(pattern_id, top_n, sample_size)` | Find entities with highest temporal drift | `list[dict]` |
|
|
116
|
+
| π9 | `π9_attract_drift(pattern_id, top_n, sample_size, fdr_alpha, fdr_method, select)` | Find entities with highest temporal drift | `list[dict]` |
|
|
117
117
|
| π10 | `π10_attract_trajectory(primary_key, pattern_id, top_n)` | Find entities with similar temporal trajectory | `list[dict]` |
|
|
118
118
|
| π11 | `π11_attract_population_compare(pattern_id, window_a_from, window_a_to, window_b_from, window_b_to)` | Compare population geometry between two time windows | `dict` |
|
|
119
119
|
| π12 | `π12_attract_regime_change(pattern_id, timestamp_from, timestamp_to)` | Detect population geometry regime shifts | `list[dict]` |
|
|
@@ -24,7 +24,7 @@ The core package is split into a few layers:
|
|
|
24
24
|
|
|
25
25
|
- **Model**: sphere, line, pattern, alias, point, edge, polygon, solid, manifest, contract
|
|
26
26
|
- **Storage**: readers, writers, caches, and physical layout handling
|
|
27
|
-
- **Engine**: geometry, anomaly scoring, similarity, drift, forecasting, aggregation, calibration tracking
|
|
27
|
+
- **Engine**: geometry, anomaly scoring, similarity, drift, forecasting, aggregation (Lance SQL pushdown via `lance_sql_agg`), calibration tracking
|
|
28
28
|
- **Navigation**: stateful step-by-step movement through the sphere
|
|
29
29
|
- **PassiveScanner**: multi-source batch screening over navigation primitives
|
|
30
30
|
- **Builder**: transforms source data into a complete sphere
|
|
@@ -58,6 +58,16 @@ where μ = E[s] is the population mean, σ = max(std(s), ε) the clamped standar
|
|
|
58
58
|
- **Cross-sphere comparison** *(planned)* — dimensionless scalar metrics from independently calibrated coordinate spaces enable comparison across separate data systems
|
|
59
59
|
- **What-if analysis** *(planned)* — hypothetical edge changes produce a modified coordinate vector showing the geometric effect
|
|
60
60
|
|
|
61
|
+
## Statistical Guarantees
|
|
62
|
+
|
|
63
|
+
Two opt-in post-processing stages can be applied to any attract primitive that scans a population (pi5, pi6, pi7, pi9). Both are off by default — omitting them preserves the legacy behavior exactly.
|
|
64
|
+
|
|
65
|
+
**FDR control.** When `fdr_alpha` is set, the result set passes through a Benjamini-Hochberg (BH) procedure. Each entity's empirical-null p-value is derived from its population rank percentile: `p = 1 - rank_pct / 100`, where `rank_pct` is the entity's position in the full population sorted by the primitive's ranking metric (delta norm for pi5/pi6, hub score for pi7, displacement for pi9). The BH procedure guarantees `E[FDR] <= alpha` under independence or positive regression dependency (PRDS). Every retained entity carries a q-value — the minimum alpha at which it would still be retained. Entities with `q_value > alpha` are removed before the top-N / selection step.
|
|
66
|
+
|
|
67
|
+
**Diverse selection.** When `select="diverse"`, the top-N step is replaced by a lazy-greedy facility location algorithm that maximizes a monotone submodular objective over pairwise cosine similarity of delta vectors. The greedy selection achieves a `(1 - 1/e)` approximation to the optimal K-subset. Each selected entity reports a representativeness count — how many population members are closest to it among the selected set. The result is K entities that cover the geometric space of the result set rather than clustering around a single extreme region.
|
|
68
|
+
|
|
69
|
+
Both stages compose: `fdr_alpha=0.05, select="diverse"` first filters to FDR-controlled discoveries, then picks the K most geometrically diverse among them.
|
|
70
|
+
|
|
61
71
|
## Three Scales
|
|
62
72
|
|
|
63
73
|

|
|
@@ -871,6 +871,7 @@ hypertopos build --config sphere.yaml --force --verbose
|
|
|
871
871
|
The builder parallelizes where possible:
|
|
872
872
|
- **Points write** — parallel across lines (ThreadPoolExecutor, up to 4 workers)
|
|
873
873
|
- **Geometry build** — parallel across patterns (one thread per pattern)
|
|
874
|
+
- **Temporal build** — parallel across patterns; adaptive memory chunking when the shape tensor exceeds available RAM (auto-detected via platform API, 4 GB fallback)
|
|
874
875
|
|
|
875
876
|
### Build timing
|
|
876
877
|
|
|
@@ -17,7 +17,8 @@ gds_{sphere_id}/
|
|
|
17
17
|
│ ├── geometry_stats/ # precomputed population summaries
|
|
18
18
|
│ ├── trajectory/ # ANN index for trajectory similarity search
|
|
19
19
|
│ ├── temporal_centroids/ # cached population centroids per time window
|
|
20
|
-
│
|
|
20
|
+
│ ├── edge_stats/ # per-event-pattern edge table summary cache (row count, unique from/to, ts/amount range)
|
|
21
|
+
│ └── contagion_stats/ # per-pattern (primary_key, neighbor_count, anomalous_neighbor_count, contagion_ratio) — feeds the graph contagion scanner directly, BTREE-indexed on primary_key
|
|
21
22
|
├── points/
|
|
22
23
|
│ ├── {line_id}/v={n}/
|
|
23
24
|
│ │ └── data.lance # entity records for this line version
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Copyright (C) 2026 Karol Kędzia
|
|
2
|
+
# Licensed under the Business Source License 1.1 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# See LICENSE.md in the repository root for full terms.
|
|
5
|
+
"""Vectorized Arrow→tensor scatter utilities.
|
|
6
|
+
|
|
7
|
+
Replaces Python-level row iteration with Arrow pc.index_in + numpy
|
|
8
|
+
fancy indexing. Used by both static (compute_derived_batch) and
|
|
9
|
+
temporal (_precompute_shape_tensor) build paths.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pyarrow as pa
|
|
15
|
+
import pyarrow.compute as pc
|
|
16
|
+
from numpy.typing import NDArray
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def vectorized_scatter(
|
|
20
|
+
tensor: NDArray[np.float32],
|
|
21
|
+
dim_idx: int,
|
|
22
|
+
edge_max: float,
|
|
23
|
+
anchor_keys_arr: pa.Array,
|
|
24
|
+
grouped_fk_col: pa.Array,
|
|
25
|
+
grouped_bucket_col: pa.Array,
|
|
26
|
+
grouped_values_col: pa.Array,
|
|
27
|
+
) -> None:
|
|
28
|
+
if len(grouped_fk_col) == 0:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
entity_idx_arr = pc.index_in(grouped_fk_col, anchor_keys_arr)
|
|
32
|
+
valid = pc.and_(pc.is_valid(entity_idx_arr), pc.is_valid(grouped_values_col))
|
|
33
|
+
|
|
34
|
+
entity_indices = entity_idx_arr.filter(valid).to_numpy(
|
|
35
|
+
zero_copy_only=False,
|
|
36
|
+
).astype(np.intp)
|
|
37
|
+
bucket_indices = grouped_bucket_col.filter(valid).to_numpy(
|
|
38
|
+
zero_copy_only=False,
|
|
39
|
+
).astype(np.intp)
|
|
40
|
+
values = grouped_values_col.filter(valid).to_numpy(
|
|
41
|
+
zero_copy_only=False,
|
|
42
|
+
).astype(np.float64)
|
|
43
|
+
|
|
44
|
+
nan_ok = ~np.isnan(values)
|
|
45
|
+
entity_indices = entity_indices[nan_ok]
|
|
46
|
+
bucket_indices = bucket_indices[nan_ok]
|
|
47
|
+
values = np.clip(values[nan_ok], 0.0, edge_max) / edge_max
|
|
48
|
+
|
|
49
|
+
tensor[entity_indices, bucket_indices, dim_idx] = values.astype(np.float32)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def vectorized_scatter_1d(
|
|
53
|
+
result: NDArray[np.float64],
|
|
54
|
+
anchor_keys_arr: pa.Array,
|
|
55
|
+
grouped_fk_col: pa.Array,
|
|
56
|
+
grouped_values_col: pa.Array,
|
|
57
|
+
) -> None:
|
|
58
|
+
if len(grouped_fk_col) == 0:
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
entity_idx_arr = pc.index_in(grouped_fk_col, anchor_keys_arr)
|
|
62
|
+
valid = pc.and_(pc.is_valid(entity_idx_arr), pc.is_valid(grouped_values_col))
|
|
63
|
+
|
|
64
|
+
entity_indices = entity_idx_arr.filter(valid).to_numpy(
|
|
65
|
+
zero_copy_only=False,
|
|
66
|
+
).astype(np.intp)
|
|
67
|
+
values = grouped_values_col.filter(valid).to_numpy(
|
|
68
|
+
zero_copy_only=False,
|
|
69
|
+
).astype(np.float64)
|
|
70
|
+
|
|
71
|
+
nan_ok = ~np.isnan(values)
|
|
72
|
+
result[entity_indices[nan_ok]] = values[nan_ok]
|
|
@@ -116,6 +116,7 @@ def compute_stats_grouped(
|
|
|
116
116
|
shape_vectors: np.ndarray,
|
|
117
117
|
group_ids: np.ndarray,
|
|
118
118
|
anomaly_percentile: float = 95.0,
|
|
119
|
+
prop_dim_start: int | None = None,
|
|
119
120
|
) -> tuple[
|
|
120
121
|
dict[str, tuple[np.ndarray, np.ndarray, np.ndarray, int]],
|
|
121
122
|
np.ndarray,
|
|
@@ -123,6 +124,10 @@ def compute_stats_grouped(
|
|
|
123
124
|
]:
|
|
124
125
|
"""Compute per-group population statistics.
|
|
125
126
|
|
|
127
|
+
prop_dim_start: if set, apply SIGMA_EPS_PROP floor to sigma[prop_dim_start:]
|
|
128
|
+
for each group before computing deltas. Eliminates the need for a separate
|
|
129
|
+
re-run pass in the caller.
|
|
130
|
+
|
|
126
131
|
Returns:
|
|
127
132
|
group_results: {group_name: (mu, sigma, theta, population_size)}
|
|
128
133
|
deltas: (N, D) float32 — z-scored against per-group mu/sigma
|
|
@@ -140,9 +145,12 @@ def compute_stats_grouped(
|
|
|
140
145
|
group_shape = shape_vectors[mask]
|
|
141
146
|
|
|
142
147
|
if len(group_shape) < 2:
|
|
143
|
-
# Too few entities for meaningful stats — use global-like single point
|
|
144
148
|
mu_g = group_shape.mean(axis=0).astype(np.float32)
|
|
145
149
|
sigma_g = np.full(D, SIGMA_EPS, dtype=np.float32)
|
|
150
|
+
if prop_dim_start is not None:
|
|
151
|
+
sigma_g[prop_dim_start:] = np.maximum(
|
|
152
|
+
sigma_g[prop_dim_start:], SIGMA_EPS_PROP,
|
|
153
|
+
)
|
|
146
154
|
theta_g = np.zeros(D, dtype=np.float32)
|
|
147
155
|
deltas[mask] = ((group_shape - mu_g) / sigma_g).astype(np.float32)
|
|
148
156
|
_masked = deltas[mask]
|
|
@@ -155,6 +163,22 @@ def compute_stats_grouped(
|
|
|
155
163
|
mu_g, sigma_g, theta_g, group_deltas, group_norms, _ = compute_stats(
|
|
156
164
|
group_shape, anomaly_percentile,
|
|
157
165
|
)
|
|
166
|
+
|
|
167
|
+
if prop_dim_start is not None:
|
|
168
|
+
sigma_g[prop_dim_start:] = np.maximum(
|
|
169
|
+
sigma_g[prop_dim_start:], SIGMA_EPS_PROP,
|
|
170
|
+
)
|
|
171
|
+
group_deltas = ((group_shape - mu_g) / sigma_g).astype(np.float32)
|
|
172
|
+
group_norms = np.sqrt(
|
|
173
|
+
np.einsum('ij,ij->i', group_deltas, group_deltas),
|
|
174
|
+
).astype(np.float32)
|
|
175
|
+
g_theta_scalar = (
|
|
176
|
+
float(np.percentile(group_norms, anomaly_percentile))
|
|
177
|
+
if len(group_norms) > 1 else 0.0
|
|
178
|
+
)
|
|
179
|
+
g_comp = g_theta_scalar / np.sqrt(D) if D > 0 else 0.0
|
|
180
|
+
theta_g = np.full(D, g_comp, dtype=np.float32)
|
|
181
|
+
|
|
158
182
|
deltas[mask] = group_deltas
|
|
159
183
|
delta_norms[mask] = group_norms
|
|
160
184
|
group_results[gid_str] = (mu_g, sigma_g, theta_g, int(mask.sum()))
|