beqforge 0.2.3__tar.gz → 0.2.5__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.
- beqforge-0.2.5/.gitattributes +1 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.github/workflows/build-designer-image.yml +6 -3
- {beqforge-0.2.3 → beqforge-0.2.5}/.github/workflows/build-executable.yml +20 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/AGENTS.md +2 -1
- {beqforge-0.2.3 → beqforge-0.2.5}/PKG-INFO +48 -5
- {beqforge-0.2.3 → beqforge-0.2.5}/README.md +47 -4
- {beqforge-0.2.3 → beqforge-0.2.5}/TODO.md +1 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beq_common/__init__.py +1 -1
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/AGENTS.md +1 -1
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/diagnose.py +98 -8
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/AGENTS.md +5 -1
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/__init__.py +15 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/cache.py +182 -4
- beqforge-0.2.5/beqoptimiser/parameters.py +500 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser.md +50 -0
- beqforge-0.2.5/packaging/designer/beqforge-designer.cmd +33 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/done-design-and-pipeline.md +70 -0
- beqforge-0.2.5/plans/parameter-space-optimisation.md +126 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_designer_server.py +61 -5
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_diagnose.py +49 -1
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_designer_server_startup.py +30 -2
- beqforge-0.2.5/tests/test_optimiser_parameters.py +200 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/designer_server.py +98 -14
- beqforge-0.2.5/tools/experiments/parameter_benchmark.py +152 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/smoke_test_exe.py +21 -10
- {beqforge-0.2.3 → beqforge-0.2.5}/.dockerignore +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.github/dependabot.yml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.github/workflows/build-device-check.yml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.github/workflows/dependabot-auto-merge.yaml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.github/workflows/tests.yml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.gitignore +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/.gitignore +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/beqforge.iml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/editor.xml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/jsonSchemas.xml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/misc.xml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/modules.xml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/.idea/vcs.xml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/CLAUDE.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/LICENCE.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beq_common/AGENTS.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beq_common/biquad.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beq_common/provenance.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beq_common/publication.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beq_common/types.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/__init__.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/accept.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/beqd.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/biquad.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/cache.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/charts.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/cli.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/design.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/designer.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/explain.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/extraction.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/filters.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/harness.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/identify.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/material.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/pipeline.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/record.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/reference.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/rolloff.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge/verify.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge-device-check.spec +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge.spec +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/AGENTS.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/__init__.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/analyse.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/audio.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/catalogue.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/cli.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/coefficients.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/engines.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/evidence.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/manifest.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/measurement.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/profiles.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/qualification.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/report.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/transactions.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqforge_device_check/verify.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/cli.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/core.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/data/seed-manifest.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/beqoptimiser/data/seed.json.gz +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/device-check-examples/minidsp-2x4hd-usb-loopback.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/device-check-examples/simulation.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/device-check.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/README.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/assets/aggregate-48000.png +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/assets/aggregate-96000.png +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/assets/example-1.png +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/assets/example-2.png +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/entry-results.json.gz +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/docs/optimiser-report/statistics.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/evidence_validation.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/negative_corpus.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/packaging/designer/Dockerfile +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/packaging/designer/smoke.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/packaging/device-check/NOTICE.txt +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/F2-catalogue-filter-optimisation.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/F2-device-precision-validation.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/F2-package-consolidation.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/R2a-server-stage-cache.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/done-baseline-evidence.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/research-design-decisions.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/test-portability-and-runtime.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/texture-yardstick-sub-band.md +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/plans/texture-yardstick-sub-band.patch +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/pyproject.toml +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/__init__.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/conftest.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/fixtures/optimiser_seed_entry.json +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_cli.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_accept.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_cache.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_cache_stores.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_channel_evidence.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_charts.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_corpus.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_design.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_designer.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_device_rate.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_evidence.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_evidence_excess.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_exclusions.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_explain.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_extraction.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_filters.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_frozen_cache.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_guard.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_harness.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_headroom.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_hum.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_identify.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_jacobian.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_parallel.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_pipeline.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_playback.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_playback_headroom.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_publication.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_record.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_record_fingerprint.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_reference.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_references.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_ripple_band.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_rolloff.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_strategy_config.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_timings.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_design_verify.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_designer_image.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_analyse.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_catalogue.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_catalogue_predict.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_cli.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_engines.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_evidence.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_examples.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_manifest.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_measurement.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_qualification.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_transactions.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_device_check_verify.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_extract_layout.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_optimiser.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_optimiser_cache.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_optimiser_report.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tests/test_package_boundaries.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/__init__.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/build_optimiser_seed.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/design_beq.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/a1_sweep.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/compare_records.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/compare_verdicts.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/inject_variants.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p14_compare.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p14_jacobian.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p14_run_pipeline.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p14_surrogate.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p18_compare_greedy.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p18_compare_seeded.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p18_compare_structure.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p18_greedy.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p18_seeded.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p18_structure.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/p20_smooth_drift.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/probe.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/refit_probe.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/request_timing.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/score_injected.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/experiments/t8_floor_rules.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/extract.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/ledger_template.html +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/negative_corpus.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/optimiser_catalogue_report.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/prepare_device_check_helper.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/render_ledger.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/replay.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/seed_optimiser_cache.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/smoke_test_device_check.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/smoke_test_install.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/summarise.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/tools/validate_evidence.py +0 -0
- {beqforge-0.2.3 → beqforge-0.2.5}/uv.lock +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.cmd text eol=crlf
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
name: Designer image
|
|
2
2
|
|
|
3
3
|
# The designer as a container, beside a beqdesigner pipeline service (README, "Running the
|
|
4
|
-
# designer in a container"). Every change to what goes into it is built and smoke-tested
|
|
5
|
-
#
|
|
4
|
+
# designer in a container"). Every change to what goes into it is built and smoke-tested.
|
|
5
|
+
# A release publishes linux/amd64 and linux/arm64 to GHCR as ghcr.io/<owner>/beqforge-designer:
|
|
6
|
+
# build-executable.yml calls this for a vX.Y.Z tag once the release tests have passed, as it
|
|
7
|
+
# does for the executables and PyPI, so an image never ships ahead of its tests.
|
|
6
8
|
# A manual run with `publish` publishes the image of an existing release from a later commit,
|
|
7
9
|
# only if the designer's runtime code is identical to that release's tag (as when the image
|
|
8
10
|
# recipe arrived after the release).
|
|
9
11
|
on:
|
|
10
12
|
push:
|
|
11
13
|
branches: [main]
|
|
12
|
-
tags: ['v*']
|
|
13
14
|
paths:
|
|
14
15
|
- 'packaging/designer/**'
|
|
15
16
|
- '.dockerignore'
|
|
@@ -30,6 +31,8 @@ on:
|
|
|
30
31
|
description: Publish the image as beq_common.__version__ (its vX.Y.Z tag must exist, with identical designer code)
|
|
31
32
|
type: boolean
|
|
32
33
|
default: false
|
|
34
|
+
# the release: build-executable.yml, from a tag, after its test gate
|
|
35
|
+
workflow_call:
|
|
33
36
|
|
|
34
37
|
permissions:
|
|
35
38
|
contents: read
|
|
@@ -226,6 +226,26 @@ jobs:
|
|
|
226
226
|
env:
|
|
227
227
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
228
228
|
|
|
229
|
+
- name: Attach the Windows designer launcher
|
|
230
|
+
if: runner.os == 'Windows' && startsWith(github.ref, 'refs/tags/')
|
|
231
|
+
uses: softprops/action-gh-release@v3
|
|
232
|
+
with:
|
|
233
|
+
files: packaging/designer/beqforge-designer.cmd
|
|
234
|
+
env:
|
|
235
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
236
|
+
|
|
237
|
+
image:
|
|
238
|
+
name: Publish designer image
|
|
239
|
+
needs: [validated_tests, package]
|
|
240
|
+
# see create_release for why the needed results are checked explicitly
|
|
241
|
+
if: >-
|
|
242
|
+
!cancelled() && startsWith(github.ref, 'refs/tags/') &&
|
|
243
|
+
needs.validated_tests.result == 'success' && needs.package.result == 'success'
|
|
244
|
+
uses: ./.github/workflows/build-designer-image.yml
|
|
245
|
+
permissions:
|
|
246
|
+
contents: read
|
|
247
|
+
packages: write
|
|
248
|
+
|
|
229
249
|
publish:
|
|
230
250
|
name: Publish Python package to PyPI
|
|
231
251
|
needs: [validated_tests, package, build, create_release]
|
|
@@ -60,7 +60,8 @@ or fixed ports), with coverage (`--cov`, configured in `pyproject.toml`, `multip
|
|
|
60
60
|
children included) uploaded to Codecov by each test job using the `CODECOV_TOKEN` secret;
|
|
61
61
|
release runs inherit it. Feature branch pushes test on Linux only and a newer push cancels the branch's
|
|
62
62
|
previous run; `main`, tags and manual runs test Linux, macOS and Windows and are never cancelled. `.github/workflows/build-executable.yml` builds release executables and distributions;
|
|
63
|
-
tag pushes publish to PyPI after validation and successful executable builds
|
|
63
|
+
tag pushes publish to PyPI after validation and successful executable builds, and the designer
|
|
64
|
+
image to GHCR after validation. Tags must match
|
|
64
65
|
the shared version exactly (`vX.Y.Z`). Manual runs build artifacts without publishing.
|
|
65
66
|
PyPI uses the `pypi` environment and Trusted Publishing; setup details are in the README.
|
|
66
67
|
Device-check preview executable packaging has its own workflow, not a separate Python release;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: beqforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: Audio-derived BEQ design, published filter optimisation and DSP validation
|
|
5
5
|
License-File: LICENCE.md
|
|
6
6
|
Requires-Python: <3.15,>=3.13
|
|
@@ -187,6 +187,13 @@ naming the array, rather than a design decline. Inline requests remain supported
|
|
|
187
187
|
logs read, parse, decode, design and response timings; `tools/experiments/request_timing.py`
|
|
188
188
|
measures cold and warm requests.
|
|
189
189
|
|
|
190
|
+
**Several designs at once.** `--workers N` (1-8, default 1) runs N designs at once, each in
|
|
191
|
+
its own worker process; further requests queue. `GET /health` reports it as `parallel`, so a
|
|
192
|
+
caller can send each server as many designs as it takes rather than be configured with that
|
|
193
|
+
number. Concurrent designs share the machine's logical processors, SMT threads included, each
|
|
194
|
+
at most the width one design uses alone (`BEQ_FIT_WORKERS`, if set, stays each design's own).
|
|
195
|
+
The share changes how long a fit takes, never its answer.
|
|
196
|
+
|
|
190
197
|
Every `serve-designer` configuration option can also be set through an environment variable.
|
|
191
198
|
Explicit CLI options take precedence; for repeatable options, CLI values replace the entire
|
|
192
199
|
environment list. `--help` lists the variable beside each option. Other subcommands retain
|
|
@@ -207,6 +214,7 @@ their existing CLI configuration.
|
|
|
207
214
|
| `--record-dir` | `BEQFORGE_RECORD_DIR` | path |
|
|
208
215
|
| `--cache-dir` | `BEQFORGE_CACHE_DIR` | path |
|
|
209
216
|
| `--shared-root` | `BEQFORGE_SHARED_ROOT` | path |
|
|
217
|
+
| `--workers` | `BEQFORGE_WORKERS` | integer, 1-8 |
|
|
210
218
|
| `--quiet` | `BEQFORGE_QUIET` | boolean |
|
|
211
219
|
|
|
212
220
|
Booleans accept `true`/`false`, `yes`/`no`, `on`/`off` or `1`/`0`, ignoring case.
|
|
@@ -252,8 +260,11 @@ pure `design(request) -> response` adapter, independently testable without a soc
|
|
|
252
260
|
|
|
253
261
|
`ghcr.io/3ll3d00d/beqforge-designer:<version>` runs `beqforge serve-designer` for a beqdesigner
|
|
254
262
|
pipeline service beside it (beqdesigner's `docker/compose.example.yaml` runs both). It is
|
|
255
|
-
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml`
|
|
263
|
+
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml` for
|
|
256
264
|
each `vX.Y.Z` tag, from the same commit as that release; `latest` follows the newest release.
|
|
265
|
+
The release workflow (`build-executable.yml`) calls it only once the release tests have passed,
|
|
266
|
+
as it does for the executables and PyPI; pushes to `main` and pull requests build and
|
|
267
|
+
smoke-test the image without publishing it.
|
|
257
268
|
A manual run of that workflow with `publish` set publishes the current version's image from a
|
|
258
269
|
later commit, and refuses unless the designer's code is identical to that version's tag (0.2.0's
|
|
259
270
|
image was published this way, the recipe having arrived after the release).
|
|
@@ -270,9 +281,9 @@ The image's defaults, all overridable with the environment variables above:
|
|
|
270
281
|
|
|
271
282
|
It runs as uid/gid 1000 (`--build-arg PUID=… PGID=…` to change), which must be able to write
|
|
272
283
|
the shared root and the cache: the server checks both with a write at startup. Its
|
|
273
|
-
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in
|
|
274
|
-
worker
|
|
275
|
-
caller's timeout for that. `docker stop` stops a design in progress.
|
|
284
|
+
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in separate
|
|
285
|
+
worker processes, `BEQFORGE_WORKERS` (default 1) at a time, so a caller sending more at once
|
|
286
|
+
queues them; size the caller's timeout for that. `docker stop` stops a design in progress.
|
|
276
287
|
|
|
277
288
|
Build and check it locally from the repository root:
|
|
278
289
|
|
|
@@ -284,6 +295,38 @@ python3 packaging/designer/smoke.py beqforge-designer
|
|
|
284
295
|
The smoke script checks `/health` and one design inline and one by reference;
|
|
285
296
|
`tests/test_designer_image.py` runs the same check against an in-process server.
|
|
286
297
|
|
|
298
|
+
### Running the designer on Windows
|
|
299
|
+
|
|
300
|
+
The release's `beqforge-windows-x86_64.exe` runs the same server natively, for a beqdesigner
|
|
301
|
+
container (or install) that calls out to a Windows machine. Put `beqforge-designer.cmd`, which
|
|
302
|
+
is attached to the same release (source: `packaging/designer/beqforge-designer.cmd`), beside the
|
|
303
|
+
exe and double-click it, or run the exe directly:
|
|
304
|
+
|
|
305
|
+
```bat
|
|
306
|
+
beqforge-windows-x86_64.exe serve-designer --host 0.0.0.0 --port 8420 ^
|
|
307
|
+
--shared-root D:\beq\work --cache-dir %LOCALAPPDATA%\beqforge\cache
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The launcher sets the image's defaults through the environment variables above, each only if
|
|
311
|
+
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420` and
|
|
312
|
+
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache`. Arguments pass through to `serve-designer`.
|
|
313
|
+
|
|
314
|
+
* **Listen on the network.** The CLI's default host is `127.0.0.1`, which a container cannot
|
|
315
|
+
reach; the launcher uses `0.0.0.0`. Windows Firewall asks the first time: allow private
|
|
316
|
+
networks.
|
|
317
|
+
* **Shared root.** For audio by reference, set `BEQFORGE_SHARED_ROOT` (or edit the commented
|
|
318
|
+
line in the launcher) to the Windows folder bind-mounted into beqdesigner's container as its
|
|
319
|
+
work directory. Without it, beqdesigner must send audio inline.
|
|
320
|
+
* **beqdesigner's side.** Under Docker Desktop on the same machine, register
|
|
321
|
+
`http://host.docker.internal:8420/design`; from another machine, use this one's LAN address.
|
|
322
|
+
Check `http://<address>:8420/health` from there first.
|
|
323
|
+
* **Several at once.** Set `BEQFORGE_WORKERS` (or pass `--workers 2`) for a machine that can
|
|
324
|
+
design more than one title at a time; `/health` reports it as `parallel`.
|
|
325
|
+
* **The rest.** The exe is unsigned, so SmartScreen warns on first run. `serve-designer` does not need `ffmpeg`. Close the window, or
|
|
326
|
+
press Ctrl+C, to stop.
|
|
327
|
+
|
|
328
|
+
### Building and releasing
|
|
329
|
+
|
|
287
330
|
For a machine without Python/`uv`, a standalone `beqforge` executable (every subcommand,
|
|
288
331
|
including `serve-designer`) is built for Linux/macOS/Windows by
|
|
289
332
|
`.github/workflows/build-executable.yml` on every `vX.Y.Z` tag, and attached to the matching
|
|
@@ -161,6 +161,13 @@ naming the array, rather than a design decline. Inline requests remain supported
|
|
|
161
161
|
logs read, parse, decode, design and response timings; `tools/experiments/request_timing.py`
|
|
162
162
|
measures cold and warm requests.
|
|
163
163
|
|
|
164
|
+
**Several designs at once.** `--workers N` (1-8, default 1) runs N designs at once, each in
|
|
165
|
+
its own worker process; further requests queue. `GET /health` reports it as `parallel`, so a
|
|
166
|
+
caller can send each server as many designs as it takes rather than be configured with that
|
|
167
|
+
number. Concurrent designs share the machine's logical processors, SMT threads included, each
|
|
168
|
+
at most the width one design uses alone (`BEQ_FIT_WORKERS`, if set, stays each design's own).
|
|
169
|
+
The share changes how long a fit takes, never its answer.
|
|
170
|
+
|
|
164
171
|
Every `serve-designer` configuration option can also be set through an environment variable.
|
|
165
172
|
Explicit CLI options take precedence; for repeatable options, CLI values replace the entire
|
|
166
173
|
environment list. `--help` lists the variable beside each option. Other subcommands retain
|
|
@@ -181,6 +188,7 @@ their existing CLI configuration.
|
|
|
181
188
|
| `--record-dir` | `BEQFORGE_RECORD_DIR` | path |
|
|
182
189
|
| `--cache-dir` | `BEQFORGE_CACHE_DIR` | path |
|
|
183
190
|
| `--shared-root` | `BEQFORGE_SHARED_ROOT` | path |
|
|
191
|
+
| `--workers` | `BEQFORGE_WORKERS` | integer, 1-8 |
|
|
184
192
|
| `--quiet` | `BEQFORGE_QUIET` | boolean |
|
|
185
193
|
|
|
186
194
|
Booleans accept `true`/`false`, `yes`/`no`, `on`/`off` or `1`/`0`, ignoring case.
|
|
@@ -226,8 +234,11 @@ pure `design(request) -> response` adapter, independently testable without a soc
|
|
|
226
234
|
|
|
227
235
|
`ghcr.io/3ll3d00d/beqforge-designer:<version>` runs `beqforge serve-designer` for a beqdesigner
|
|
228
236
|
pipeline service beside it (beqdesigner's `docker/compose.example.yaml` runs both). It is
|
|
229
|
-
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml`
|
|
237
|
+
published for linux/amd64 and linux/arm64 by `.github/workflows/build-designer-image.yml` for
|
|
230
238
|
each `vX.Y.Z` tag, from the same commit as that release; `latest` follows the newest release.
|
|
239
|
+
The release workflow (`build-executable.yml`) calls it only once the release tests have passed,
|
|
240
|
+
as it does for the executables and PyPI; pushes to `main` and pull requests build and
|
|
241
|
+
smoke-test the image without publishing it.
|
|
231
242
|
A manual run of that workflow with `publish` set publishes the current version's image from a
|
|
232
243
|
later commit, and refuses unless the designer's code is identical to that version's tag (0.2.0's
|
|
233
244
|
image was published this way, the recipe having arrived after the release).
|
|
@@ -244,9 +255,9 @@ The image's defaults, all overridable with the environment variables above:
|
|
|
244
255
|
|
|
245
256
|
It runs as uid/gid 1000 (`--build-arg PUID=… PGID=…` to change), which must be able to write
|
|
246
257
|
the shared root and the cache: the server checks both with a write at startup. Its
|
|
247
|
-
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in
|
|
248
|
-
worker
|
|
249
|
-
caller's timeout for that. `docker stop` stops a design in progress.
|
|
258
|
+
`HEALTHCHECK` asks `/health`, which answers while a design runs: designs run in separate
|
|
259
|
+
worker processes, `BEQFORGE_WORKERS` (default 1) at a time, so a caller sending more at once
|
|
260
|
+
queues them; size the caller's timeout for that. `docker stop` stops a design in progress.
|
|
250
261
|
|
|
251
262
|
Build and check it locally from the repository root:
|
|
252
263
|
|
|
@@ -258,6 +269,38 @@ python3 packaging/designer/smoke.py beqforge-designer
|
|
|
258
269
|
The smoke script checks `/health` and one design inline and one by reference;
|
|
259
270
|
`tests/test_designer_image.py` runs the same check against an in-process server.
|
|
260
271
|
|
|
272
|
+
### Running the designer on Windows
|
|
273
|
+
|
|
274
|
+
The release's `beqforge-windows-x86_64.exe` runs the same server natively, for a beqdesigner
|
|
275
|
+
container (or install) that calls out to a Windows machine. Put `beqforge-designer.cmd`, which
|
|
276
|
+
is attached to the same release (source: `packaging/designer/beqforge-designer.cmd`), beside the
|
|
277
|
+
exe and double-click it, or run the exe directly:
|
|
278
|
+
|
|
279
|
+
```bat
|
|
280
|
+
beqforge-windows-x86_64.exe serve-designer --host 0.0.0.0 --port 8420 ^
|
|
281
|
+
--shared-root D:\beq\work --cache-dir %LOCALAPPDATA%\beqforge\cache
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
The launcher sets the image's defaults through the environment variables above, each only if
|
|
285
|
+
not already set: `BEQFORGE_HOST=0.0.0.0`, `BEQFORGE_PORT=8420` and
|
|
286
|
+
`BEQFORGE_CACHE_DIR=%LOCALAPPDATA%\beqforge\cache`. Arguments pass through to `serve-designer`.
|
|
287
|
+
|
|
288
|
+
* **Listen on the network.** The CLI's default host is `127.0.0.1`, which a container cannot
|
|
289
|
+
reach; the launcher uses `0.0.0.0`. Windows Firewall asks the first time: allow private
|
|
290
|
+
networks.
|
|
291
|
+
* **Shared root.** For audio by reference, set `BEQFORGE_SHARED_ROOT` (or edit the commented
|
|
292
|
+
line in the launcher) to the Windows folder bind-mounted into beqdesigner's container as its
|
|
293
|
+
work directory. Without it, beqdesigner must send audio inline.
|
|
294
|
+
* **beqdesigner's side.** Under Docker Desktop on the same machine, register
|
|
295
|
+
`http://host.docker.internal:8420/design`; from another machine, use this one's LAN address.
|
|
296
|
+
Check `http://<address>:8420/health` from there first.
|
|
297
|
+
* **Several at once.** Set `BEQFORGE_WORKERS` (or pass `--workers 2`) for a machine that can
|
|
298
|
+
design more than one title at a time; `/health` reports it as `parallel`.
|
|
299
|
+
* **The rest.** The exe is unsigned, so SmartScreen warns on first run. `serve-designer` does not need `ffmpeg`. Close the window, or
|
|
300
|
+
press Ctrl+C, to stop.
|
|
301
|
+
|
|
302
|
+
### Building and releasing
|
|
303
|
+
|
|
261
304
|
For a machine without Python/`uv`, a standalone `beqforge` executable (every subcommand,
|
|
262
305
|
including `serve-designer`) is built for Linux/macOS/Windows by
|
|
263
306
|
`.github/workflows/build-executable.yml` on every `vX.Y.Z` tag, and attached to the matching
|
|
@@ -43,6 +43,7 @@ are retained so records and code comments remain traceable. Completed items are
|
|
|
43
43
|
| 24 | Chart peak bias | parked | Deliberate catalogue-compatible per-bin maximum; it affects presentation only. Reopen if a reader needs an alternative percentile view; keep it distinct from the 95th-percentile evidence envelope. |
|
|
44
44
|
| 25 | Documentation citations | open | Remove or repoint orphan references and stale descriptions of shipped behaviour. Low filter impact, but prevents future changes being based on obsolete assumptions. |
|
|
45
45
|
| 26 | Texture yardstick retune (library review) | parked | The cubic (row 2) keeps the 200 Hz band only because the sub-band cap admitted one synthetic negative, `broadband/5`: chance unevenness in noise bursts, at a 2.6 dB boost. With 45 gated cases a single case moves the gate's upper bound from 0.079 to 0.118. Carrie is still declined (0.85x about a cubic, 1.33x with the cap). Reopen when the beqdesigner library review (~1,290 titles) is complete. Then retune against its declines: the band edge, the trend degree, and whether the gate weights a false acceptance by its boost. The library holds no known negative, so it can expose wrong declines but not measure false acceptances. Any retune still needs the corpus or a fresh E10 set for that, ideally with negatives built from real programme texture (row 5). See [the outcome](plans/texture-yardstick-sub-band.md#outcome-17-october-2026). |
|
|
46
|
+
| 27 | Parameter-space optimisation for several profiles | implemented; release pending | `beqoptimiser.optimise_parameters` built and benchmarked (18 October 2026): within margin on 21/25 and 10/25 of the worst 48/96 kHz float32 entries against coefficient search's 20/25 and 16/25; see [the outcome](plans/parameter-space-optimisation.md#outcome-18-october-2026). Done once released in a version beqdesigner can pin. Originally: beqdesigner's main window will let a filter designer optimise a filter for chosen device profiles before publishing, in freq/Q/gain terms (a BEQ is published and loaded as parameters, so no catalogue schema change), minimising the worst error across all chosen profiles at once. The search belongs in `beqoptimiser`, beside `optimise()`; beqdesigner builds the interface. See [the request](plans/parameter-space-optimisation.md). |
|
|
46
47
|
|
|
47
48
|
## Review evidence
|
|
48
49
|
|
|
@@ -43,7 +43,7 @@ still holds: no other API, no other service, and the server is a thin transport
|
|
|
43
43
|
| `beqforge/explain.py` | The plain-language account a designer response leads with — `found` (reference plateau, which channels carry it and which are silent, down to where there is real bass content, judged band, goal), `correction` (the cascade, then frequency by frequency what was wanted, what the content supports and before → after), `clipping` (the sub-feed peak and how far to turn the sub down, on the request's or the assumed bass management) and `alternatives` (why each other candidate lost). Reads the `Report`; measures and decides nothing. |
|
|
44
44
|
| `beqforge/designer.py` | The `design(request) -> response` adapter between beqdesigner's `designer-interface.md` v1.1 and `beqforge.pipeline.run` — request/response dataclasses (1.1: every candidate the judge failed goes back in `rejected` with its failures, review only, beside the answer or the decline), wire-format (de)serialisation and the field-by-field mapping onto `Report`/`Candidate`/`Verdict`. Also `validate_response`, an independent re-implementation of beqdesigner's own §3-§5 rules (that repo isn't importable — PyQt6), called by the server before a response ever goes on the wire. Pure and independently testable; no socket. |
|
|
45
45
|
| `tools/design_beq.py` | **The entry point.** One command, a filter and its reasoning. `--charts DIR` for the pictures; writes a `.run.json.gz` record beside the material unless `--no-record`. |
|
|
46
|
-
| `tools/designer_server.py` | `beqforge serve-designer` — the HTTP transport (`designer-interface.md` §7.1) around `beqforge/designer.py`. Server-wide flags for device realisation/strategies/exclusions; everything per-title comes from the request. A `ThreadingHTTPServer`, so `/health` answers during a design, with every design run in
|
|
46
|
+
| `tools/designer_server.py` | `beqforge serve-designer` — the HTTP transport (`designer-interface.md` §7.1) around `beqforge/designer.py`. Server-wide flags for device realisation/strategies/exclusions; everything per-title comes from the request. A `ThreadingHTTPServer`, so `/health` answers during a design, with every design run in a long-lived worker process started with "spawn" (`_DesignWorker`): the fitter forks worker processes (`beqforge.filters.PARALLEL_FITS`) when a fit escalates past one section count, and forking a multi-threaded process risks a deadlock, so the process that forks must stay single-threaded — the worker's log records come back over a `SimpleQueue`, not a `Queue`, for that reason. A worker designs one title at a time; `--workers N` (1-8) runs N of them, each request taking the next idle one, and `/health` reports N as `parallel` so a caller can size its sends from the designer. With N > 1 each worker's fit pool is the logical processors less one, divided by N and capped at `filters.FIT_WORKERS` (`fit_share`), unless `BEQ_FIT_WORKERS` is set: concurrent designs are what can use SMT threads, and pool size changes timing, never an answer. A worker that dies answers 500 and is replaced; the others carry on. SIGTERM shuts down like Ctrl-C, stopping the worker's process group, fit pool included. Every response names its build (`beqforge_revision`); `--record-dir DIR` also writes each request's run record, named by a digest of the request audio, so a review-queue entry can be replayed. `--cache-dir DIR` keeps the stage cache there (`cache.DirStore`, one file per entry, atomic): a repeat request for a title skips the analysis — 110 s cold, 50 s warm on a 2-hour 8-channel title. Each request logs a timing line (read, parse, decode, design, respond, queued); `tools/experiments/request_timing.py` reports them. `--shared-root DIR` accepts audio by reference (contract 1.2, beqdesigner's `design/designer-by-reference.md` §3): an array names a WAV relative to DIR plus the SHA-256 of its decoded column, and `beqforge/reference.py` resolves, decodes and checks it; one that cannot be honoured is a 422 naming the array, never a decline. `GET /health` reports `contract_version`, `shared_root` and `parallel`. |
|
|
47
47
|
| `tools/replay.py` | Redraw charts and export to beqdesigner from a record — no rerun, no extraction. Refuses on a stale record unless `--force`. |
|
|
48
48
|
| `tools/extract.py` | ffmpeg → 1 kHz per-channel `.npz`. Requires an explicit supported channel layout; preserves layout provenance. Needs no beqdesigner. |
|
|
49
49
|
| `tools/summarise.py` | Sanity-check an extraction before using it. |
|
|
@@ -8,6 +8,7 @@ unknown source spectrum.
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import math
|
|
11
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
11
12
|
from dataclasses import dataclass, field, replace
|
|
12
13
|
from typing import TYPE_CHECKING
|
|
13
14
|
|
|
@@ -633,7 +634,7 @@ def diagnose(
|
|
|
633
634
|
# and reported the band top as where tracking stopped (A Few Good Men: 150.5-200 Hz found by
|
|
634
635
|
# one, none by the other).
|
|
635
636
|
_, mix_plateau = plateau_reference(mix_spectrum, spectrum_freqs, params)
|
|
636
|
-
from beqforge.extraction import ExtractionParams
|
|
637
|
+
from beqforge.extraction import ExtractionParams
|
|
637
638
|
|
|
638
639
|
extraction_params = extraction_params or ExtractionParams()
|
|
639
640
|
extraction_params = replace(
|
|
@@ -645,12 +646,18 @@ def diagnose(
|
|
|
645
646
|
),
|
|
646
647
|
)
|
|
647
648
|
|
|
649
|
+
plateaus = {
|
|
650
|
+
name: plateau_reference(
|
|
651
|
+
spectra[name][band], freqs, params, one_sided_fallback=False
|
|
652
|
+
)
|
|
653
|
+
for name in material.channels
|
|
654
|
+
}
|
|
655
|
+
evidence = _channel_evidence(material, params, extraction_params, plateaus, freqs)
|
|
656
|
+
|
|
648
657
|
channels: dict[str, ChannelDiagnosis] = {}
|
|
649
658
|
for name in material.channels:
|
|
650
659
|
response = spectra[name][band]
|
|
651
|
-
level, plateau =
|
|
652
|
-
response, freqs, params, one_sided_fallback=False
|
|
653
|
-
)
|
|
660
|
+
level, plateau = plateaus[name]
|
|
654
661
|
response = np.where(
|
|
655
662
|
unexcluded(freqs, params.exclude_bands_hz), response, np.nan
|
|
656
663
|
)
|
|
@@ -666,10 +673,19 @@ def diagnose(
|
|
|
666
673
|
passband_share = (
|
|
667
674
|
float(share[in_share_band].mean()) if in_share_band.any() else 0.0
|
|
668
675
|
)
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
676
|
+
if name in evidence:
|
|
677
|
+
(_, channel_spread, _, _, tracking), envelope, records = evidence[name]
|
|
678
|
+
for record in records:
|
|
679
|
+
logging.getLogger(record.name).handle(record)
|
|
680
|
+
else:
|
|
681
|
+
(_, channel_spread, _, _, tracking), envelope = _one_channel(
|
|
682
|
+
material.channels[name],
|
|
683
|
+
material.fs,
|
|
684
|
+
params,
|
|
685
|
+
plateau,
|
|
686
|
+
freqs,
|
|
687
|
+
extraction_params,
|
|
688
|
+
)
|
|
673
689
|
channels[name] = ChannelDiagnosis(
|
|
674
690
|
name=name,
|
|
675
691
|
response_db=response,
|
|
@@ -707,6 +723,80 @@ def diagnose(
|
|
|
707
723
|
)
|
|
708
724
|
|
|
709
725
|
|
|
726
|
+
def _one_channel(samples, fs, params, plateau, freqs, extraction_params):
|
|
727
|
+
"""A channel's temporal evidence and envelopes: most of a cold analysis's time, and
|
|
728
|
+
independent of every other channel's."""
|
|
729
|
+
from beqforge.extraction import extract
|
|
730
|
+
|
|
731
|
+
return (
|
|
732
|
+
_temporal_evidence(samples, fs, params, plateau, freqs),
|
|
733
|
+
extract(samples, fs, extraction_params),
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
class _Collect(logging.Handler):
|
|
738
|
+
def __init__(self):
|
|
739
|
+
super().__init__()
|
|
740
|
+
self.records: list[logging.LogRecord] = []
|
|
741
|
+
|
|
742
|
+
def emit(self, record: logging.LogRecord) -> None:
|
|
743
|
+
# made picklable: the message rendered, any traceback as text
|
|
744
|
+
record.msg, record.args = record.getMessage(), None
|
|
745
|
+
if record.exc_info:
|
|
746
|
+
record.exc_text = logging.Formatter().formatException(record.exc_info)
|
|
747
|
+
record.exc_info = None
|
|
748
|
+
self.records.append(record)
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def _one_channel_logged(level, *args):
|
|
752
|
+
"""`_one_channel` in a pool process, its log records returned rather than emitted, so the
|
|
753
|
+
parent logs them in channel order on every platform: a spawned process has none of the
|
|
754
|
+
parent's handlers, and a forked one would print them out of order."""
|
|
755
|
+
root = logging.getLogger()
|
|
756
|
+
handlers, previous = root.handlers[:], root.level
|
|
757
|
+
collect = _Collect()
|
|
758
|
+
root.handlers[:] = [collect]
|
|
759
|
+
root.setLevel(level)
|
|
760
|
+
try:
|
|
761
|
+
return (*_one_channel(*args), collect.records)
|
|
762
|
+
finally:
|
|
763
|
+
root.handlers[:] = handlers
|
|
764
|
+
root.setLevel(previous)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
def _channel_evidence(material, params, extraction_params, plateaus, freqs):
|
|
768
|
+
"""`_one_channel` for every channel, with its log records, computed in parallel: a
|
|
769
|
+
process per channel up to the fitter's pool size, when the fitter may run in parallel
|
|
770
|
+
(`filters.PARALLEL_FITS`, `filters.FIT_WORKERS`). Empty otherwise, and `diagnose` works
|
|
771
|
+
through the channels in turn as it always did. Each channel's work is seeded and
|
|
772
|
+
self-contained, so the answer is the serial one; only the time differs."""
|
|
773
|
+
from beqforge import filters
|
|
774
|
+
|
|
775
|
+
names = list(material.channels)
|
|
776
|
+
tasks = [
|
|
777
|
+
(
|
|
778
|
+
material.channels[n],
|
|
779
|
+
material.fs,
|
|
780
|
+
params,
|
|
781
|
+
plateaus[n][1],
|
|
782
|
+
freqs,
|
|
783
|
+
extraction_params,
|
|
784
|
+
)
|
|
785
|
+
for n in names
|
|
786
|
+
]
|
|
787
|
+
workers = min(len(names), filters.FIT_WORKERS) if filters.PARALLEL_FITS else 1
|
|
788
|
+
if workers < 2:
|
|
789
|
+
return {}
|
|
790
|
+
level = logging.getLogger().getEffectiveLevel()
|
|
791
|
+
with ProcessPoolExecutor(max_workers=workers) as pool:
|
|
792
|
+
results = list(
|
|
793
|
+
pool.map(
|
|
794
|
+
_one_channel_logged, [level] * len(tasks), *zip(*tasks, strict=True)
|
|
795
|
+
)
|
|
796
|
+
)
|
|
797
|
+
return dict(zip(names, results, strict=True))
|
|
798
|
+
|
|
799
|
+
|
|
710
800
|
def _temporal_evidence(
|
|
711
801
|
subject: np.ndarray,
|
|
712
802
|
fs: float,
|
|
@@ -41,7 +41,8 @@ strings with additive feedback; internal SOS uses subtractive feedback.
|
|
|
41
41
|
| File | Responsibility |
|
|
42
42
|
| --- | --- |
|
|
43
43
|
| `core.py` | Precision models, deterministic bounded search and independent validation |
|
|
44
|
-
| `
|
|
44
|
+
| `parameters.py` | Parameter (freq/Q/gain) search for several (rate, precision) profiles at once, on the publication grid |
|
|
45
|
+
| `cache.py` | Public cached optimisation (coefficients and parameters), immutable numerical results and seed lookup |
|
|
45
46
|
| `cli.py` | Catalogue preparation, current-entry metadata, variant export and CLI |
|
|
46
47
|
| `__init__.py` | Public library exports, including the cached `optimise` wrapper |
|
|
47
48
|
| `data/seed.json.gz`, `data/seed-manifest.json` | Bundled numerical results and provenance |
|
|
@@ -53,6 +54,9 @@ dependency versions and execution architecture.
|
|
|
53
54
|
Custom precision implementations without a complete explicit `cache_identity()` bypass caching.
|
|
54
55
|
Corrupt entries are misses; unwritable storage must not prevent calculation or seed reuse.
|
|
55
56
|
Use atomic writes and retain checksums, request identity and publication-policy validation.
|
|
57
|
+
Parameter results are keyed by that identity plus `parameters.py`'s own hash and
|
|
58
|
+
`PARAMETER_VERSION`, kept out of `implementation_identity()` so they never invalidate the seed;
|
|
59
|
+
put parameter-search code in `parameters.py`, never `core.py`, for the same reason.
|
|
56
60
|
|
|
57
61
|
Bundled seeds are reusable only in their recorded numerical environment. Changes to numerical
|
|
58
62
|
code or settings invalidate them; do not relabel old results as compatible. Bump
|
|
@@ -8,6 +8,9 @@ from .cache import (
|
|
|
8
8
|
from .cache import (
|
|
9
9
|
optimise as optimise,
|
|
10
10
|
)
|
|
11
|
+
from .cache import (
|
|
12
|
+
optimise_parameters as optimise_parameters,
|
|
13
|
+
)
|
|
11
14
|
from .core import (
|
|
12
15
|
FixedPoint as FixedPoint,
|
|
13
16
|
)
|
|
@@ -32,3 +35,15 @@ from .core import (
|
|
|
32
35
|
from .core import (
|
|
33
36
|
stable as stable,
|
|
34
37
|
)
|
|
38
|
+
from .parameters import (
|
|
39
|
+
Fixed as Fixed,
|
|
40
|
+
)
|
|
41
|
+
from .parameters import (
|
|
42
|
+
ParameterResult as ParameterResult,
|
|
43
|
+
)
|
|
44
|
+
from .parameters import (
|
|
45
|
+
ParameterSettings as ParameterSettings,
|
|
46
|
+
)
|
|
47
|
+
from .parameters import (
|
|
48
|
+
ProfileError as ProfileError,
|
|
49
|
+
)
|