nullcal 0.3.3__tar.gz → 0.4.0__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.
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/codeql.yml +3 -3
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/scheduled_release.yml +8 -2
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/semantic_pull_request.yml +1 -1
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/support_floor_update.yml +1 -1
- {nullcal-0.3.3 → nullcal-0.4.0}/.pre-commit-config.yaml +3 -3
- {nullcal-0.3.3 → nullcal-0.4.0}/PKG-INFO +1 -1
- {nullcal-0.3.3 → nullcal-0.4.0}/pyproject.toml +5 -5
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/spline_resolution_study.py +1 -1
- nullcal-0.4.0/src/nullcal/studies/bootstrap.py +126 -0
- nullcal-0.4.0/tests/studies/test_source_block_bootstrap.py +155 -0
- nullcal-0.4.0/tests/studies/test_spline_provenance.py +60 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/uv.lock +360 -349
- {nullcal-0.3.3 → nullcal-0.4.0}/.coderabbit.yaml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/pull_request_template.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/ci.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/documentation.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/draft_release.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/publish.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/publish_testpypi.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/release.yml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.gitignore +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.markdownlint.yaml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.prettierignore +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.prettierrc.yaml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/.typos.toml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/CONTRIBUTING.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/LICENSE +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/MANIFEST.in +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/README.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/cliff.toml +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/.gitignore +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/contributing.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/dev/architecture.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/javascripts/mathjax.js +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/base.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/injection.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/precompute.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/single.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/time_frequency_map.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/data.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/likelihood/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/likelihood/recalibration_likelihood.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/metadata/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/metadata/yaml.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/calibration.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/null_stream.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/projector.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/whiten.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/result/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/result/result.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/result/utils.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/inverse_wavelet_freq_funcs.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/inverse_wavelet_time_funcs.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/stft.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/transform_freq_funcs.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/transform_time_funcs.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/utils.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/wavelet_transforms.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/utils/index.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/utils/log.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/utils/snr.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/version.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/security.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/user-guide/installation.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/docs/user-guide/quickstart.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/renovate.json +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/calibration_bilby_anchor.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/diagnose_likelihood_agreement.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/jax_wdm_verification.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/lwa_leakage_study.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/release_version.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/scripts/run_reference_blackjax.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/calibration.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/base.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/injection.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/precompute.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/single.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/time_frequency_map.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/data.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/likelihood/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/likelihood/recalibration_likelihood.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/metadata/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/metadata/yaml.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/calibration.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/null_stream.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/projector.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/whiten.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/result/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/result/result.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/result/utils.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/sampler.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/gpu_cost.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/lwa_leakage.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/spline_resolution.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/README.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/inverse_wavelet_freq_funcs.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/inverse_wavelet_time_funcs.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/stft.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/transform_freq_funcs.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/transform_time_funcs.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/utils.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/wavelet_transforms.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/utils/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/utils/log.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/utils/snr.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/version.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/.gitignore +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/calibration/bilby_anchor.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/calibration/test_model.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/calibration/test_posterior_summary.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/conftest.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_clustering_algorithm.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_clustering_classes.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_single_clustering.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_time_frequency_map.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/README.md +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/__init__.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/config.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/diagnostics/likelihood_agreement.json +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/generate_reference.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/generate_reference_inputs.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/pipeline.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/artifacts.npz +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/blackjax_posterior_manifest.json +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/blackjax_posterior_samples.npz +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/inputs.npz +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/inputs_manifest.json +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/manifest.json +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/posterior_manifest.json +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/posterior_samples.npz +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_blackjax_posterior.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_noise_log_likelihood_filter_domain.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_reference_artifacts.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_reference_posterior.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/likelihood/test_logdensity.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/likelihood/test_recalibration_direction.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/likelihood/test_recalibration_likelihood.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/metadata/test_yaml.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_calibration_jax.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_null_stream.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_projector.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_whiten.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/result/test_result.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/smoke_test.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/studies/test_gpu_cost.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/studies/test_lwa_leakage.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/studies/test_spline_resolution.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_blackjax_boundary.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_data.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_dependencies_are_declared.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_jax_wdm_verification.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_release_version.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_sampler.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_jax_transform.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_stft.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_time_domain_transform.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_transform_freq_funcs.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_utils.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_wavelet_transforms.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_wdm_window.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/utils/test_log.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/tests/utils/test_snr.py +0 -0
- {nullcal-0.3.3 → nullcal-0.4.0}/zensical.toml +0 -0
|
@@ -22,13 +22,13 @@ jobs:
|
|
|
22
22
|
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
23
23
|
|
|
24
24
|
- name: Initialize CodeQL
|
|
25
|
-
uses: github/codeql-action/init@
|
|
25
|
+
uses: github/codeql-action/init@33d7b85da1a04171e211befe2c739303b9a4c066 # v4
|
|
26
26
|
with:
|
|
27
27
|
languages: python
|
|
28
28
|
queries: security-and-quality
|
|
29
29
|
|
|
30
30
|
- name: Autobuild
|
|
31
|
-
uses: github/codeql-action/autobuild@
|
|
31
|
+
uses: github/codeql-action/autobuild@33d7b85da1a04171e211befe2c739303b9a4c066 # v4
|
|
32
32
|
|
|
33
33
|
- name: Perform CodeQL Analysis
|
|
34
|
-
uses: github/codeql-action/analyze@
|
|
34
|
+
uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4
|
|
@@ -29,11 +29,17 @@ jobs:
|
|
|
29
29
|
with:
|
|
30
30
|
fetch-depth: 0
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
# Release only when something that reaches the built package changed
|
|
33
|
+
# since the latest tag: its sources, packaging config, readme, license
|
|
34
|
+
# or dynamic metadata files. Dev-dependency bumps, lock-file
|
|
35
|
+
# maintenance, CI, tests and docs do not count. Add more files to
|
|
36
|
+
# count with `paths`, or leave some out with `exclude-paths`.
|
|
37
|
+
- name: Check for package changes
|
|
33
38
|
id: check_commits
|
|
34
|
-
uses: isaac-cf-wong/detect-git-changes-action@
|
|
39
|
+
uses: isaac-cf-wong/detect-git-changes-action@fd41a9c5553a9148e8f78399855fd19e04801501 # v0.2.0
|
|
35
40
|
with:
|
|
36
41
|
use-latest-tag: true
|
|
42
|
+
preset: python
|
|
37
43
|
|
|
38
44
|
ci:
|
|
39
45
|
needs: check_new_commits
|
|
@@ -15,6 +15,6 @@ jobs:
|
|
|
15
15
|
permissions:
|
|
16
16
|
pull-requests: read
|
|
17
17
|
steps:
|
|
18
|
-
- uses: amannn/action-semantic-pull-request@
|
|
18
|
+
- uses: amannn/action-semantic-pull-request@2252a14e810aa9b85c2d560bd7070c802206ecf6 # v6
|
|
19
19
|
env:
|
|
20
20
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
|
|
23
23
|
- name: Apply support policy (spec0, from pyproject tool table)
|
|
24
24
|
id: policy
|
|
25
|
-
uses: isaac-cf-wong/dependency-support-policy-action@
|
|
25
|
+
uses: isaac-cf-wong/dependency-support-policy-action@b6cbe3a70e072d31c537978451edbbcbd3401ec9 # v0.3.0
|
|
26
26
|
with:
|
|
27
27
|
mode: update
|
|
28
28
|
|
|
@@ -17,7 +17,7 @@ repos:
|
|
|
17
17
|
- id: check-json
|
|
18
18
|
|
|
19
19
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
20
|
-
rev: v0.17.
|
|
20
|
+
rev: v0.17.4
|
|
21
21
|
hooks:
|
|
22
22
|
- id: ruff
|
|
23
23
|
args: [--fix, --config=pyproject.toml]
|
|
@@ -29,7 +29,7 @@ repos:
|
|
|
29
29
|
- id: taplo-format
|
|
30
30
|
|
|
31
31
|
- repo: https://github.com/crate-ci/typos
|
|
32
|
-
rev: v1.
|
|
32
|
+
rev: v1.52.0
|
|
33
33
|
hooks:
|
|
34
34
|
- id: typos
|
|
35
35
|
args: []
|
|
@@ -54,7 +54,7 @@ repos:
|
|
|
54
54
|
- id: actionlint
|
|
55
55
|
|
|
56
56
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
57
|
-
rev: 0.13.
|
|
57
|
+
rev: 0.13.7
|
|
58
58
|
hooks:
|
|
59
59
|
# Update the uv lockfile
|
|
60
60
|
- id: uv-lock
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nullcal
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: A package to constrain calibration errors of a closed-geometry network of gravitational-wave detectors.
|
|
5
5
|
Project-URL: Source, https://github.com/Leuven-Gravity-Institute/nullcal
|
|
6
6
|
Project-URL: Tracker, https://github.com/Leuven-Gravity-Institute/nullcal/issues
|
|
@@ -53,16 +53,16 @@ Tracker = "https://github.com/Leuven-Gravity-Institute/nullcal/issues"
|
|
|
53
53
|
|
|
54
54
|
[dependency-groups]
|
|
55
55
|
dev = [
|
|
56
|
-
"ruff>=0.17.
|
|
57
|
-
"prek>=0.5.
|
|
56
|
+
"ruff>=0.17.4",
|
|
57
|
+
"prek>=0.5.10",
|
|
58
58
|
"pytest-github-actions-annotate-failures>=0.4.2",
|
|
59
59
|
"pytest-cov>=7.1.0",
|
|
60
60
|
"pytest-mock>=3.17.0",
|
|
61
|
-
"pytest>=9.
|
|
61
|
+
"pytest>=9.2.0",
|
|
62
62
|
]
|
|
63
63
|
|
|
64
|
-
docs = ["zensical>=0.0
|
|
65
|
-
build = ["hatch>=1.18.
|
|
64
|
+
docs = ["zensical>=0.1.0", "mkdocstrings-python>=2.0.10"]
|
|
65
|
+
build = ["hatch>=1.18.2"]
|
|
66
66
|
release = ["git-cliff>=2.14.2"]
|
|
67
67
|
|
|
68
68
|
[tool.dependency-support-policy]
|
|
@@ -289,7 +289,7 @@ detector relative to 21 knots.
|
|
|
289
289
|
|
|
290
290
|
## Provenance and limitations
|
|
291
291
|
|
|
292
|
-
- Claim `
|
|
292
|
+
- Claim `submitted_basis_not_demonstrated_over_20_2000_hz` -> result `submitted-10` -> run
|
|
293
293
|
`2026-09-15-spline-resolution-study` in `run.json`.
|
|
294
294
|
- Claim `recommended_basis_resolves_bump_over_20_2000_hz` -> result `qnm-dense-19` -> the same
|
|
295
295
|
run snapshot.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Source-cluster quantile uncertainty utilities."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from numbers import Integral, Real
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy.typing import ArrayLike
|
|
9
|
+
from scipy.stats import DegenerateDataWarning, bootstrap
|
|
10
|
+
|
|
11
|
+
_MIN_RESAMPLES = 2
|
|
12
|
+
_MIN_SOURCE_COUNT = 3
|
|
13
|
+
_SOURCE_MATRIX_NDIM = 2
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class SourceBlockQuantileBound:
|
|
18
|
+
"""A nominal one-sided BCa result and its resampling distribution."""
|
|
19
|
+
|
|
20
|
+
estimate: float
|
|
21
|
+
upper_bound: float
|
|
22
|
+
standard_error: float
|
|
23
|
+
bootstrap_distribution: tuple[float, ...]
|
|
24
|
+
support_saturated: bool
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _probability(value: float, name: str) -> float:
|
|
28
|
+
if isinstance(value, bool) or not isinstance(value, Real) or not np.isfinite(value) or not 0 < value < 1:
|
|
29
|
+
raise ValueError(f"{name} must be a finite probability strictly between zero and one")
|
|
30
|
+
return float(value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def source_block_quantile_upper(
|
|
34
|
+
values: ArrayLike,
|
|
35
|
+
*,
|
|
36
|
+
quantile: float = 0.95,
|
|
37
|
+
confidence_level: float = 0.95,
|
|
38
|
+
resamples: int = 9999,
|
|
39
|
+
seed: int = 0,
|
|
40
|
+
) -> SourceBlockQuantileBound:
|
|
41
|
+
"""Return a pointwise BCa upper bound for a pooled repeated-source quantile.
|
|
42
|
+
|
|
43
|
+
``values`` has shape (independent sources, repeats per source). A resample
|
|
44
|
+
selects complete source rows, retaining all repeats, and the acceleration
|
|
45
|
+
jackknife deletes one complete source. The quantile uses NumPy's ``linear``
|
|
46
|
+
convention over the pooled values. At least three sources and one repeat
|
|
47
|
+
are required; unequal-sized clusters or non-finite/missing values are not
|
|
48
|
+
accepted, including masks nested in row sequences. Input dtypes must be
|
|
49
|
+
integer or floating point, excluding boolean/string/object arrays. Sources,
|
|
50
|
+
rather than individual repeats, must be independent.
|
|
51
|
+
|
|
52
|
+
This delegates BCa to ``scipy.stats.bootstrap`` with ``alternative='less'``
|
|
53
|
+
(https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.bootstrap.html).
|
|
54
|
+
It does not clamp the upper bound above the observed estimate. Degenerate
|
|
55
|
+
or non-finite intervals raise ValueError, with no percentile fallback.
|
|
56
|
+
The returned nominal level is not evidence of empirical coverage for a
|
|
57
|
+
non-smooth quantile, nor a simultaneous confidence band across many calls.
|
|
58
|
+
``support_saturated`` flags an endpoint at the largest bootstrap estimate;
|
|
59
|
+
it does not establish coverage or universally invalidate a discrete-data
|
|
60
|
+
interval. Callers must assess resampling precision and sampling-model validity.
|
|
61
|
+
"""
|
|
62
|
+
quantile = _probability(quantile, "quantile")
|
|
63
|
+
confidence_level = _probability(confidence_level, "confidence_level")
|
|
64
|
+
if isinstance(resamples, bool) or not isinstance(resamples, Integral) or resamples < _MIN_RESAMPLES:
|
|
65
|
+
raise ValueError("resamples must be an integer of at least two")
|
|
66
|
+
if isinstance(seed, bool) or not isinstance(seed, Integral) or seed < 0:
|
|
67
|
+
raise ValueError("seed must be a nonnegative integer")
|
|
68
|
+
try:
|
|
69
|
+
with warnings.catch_warnings():
|
|
70
|
+
warnings.simplefilter("error", UserWarning)
|
|
71
|
+
warnings.simplefilter("error", RuntimeWarning)
|
|
72
|
+
masked = np.ma.asarray(values)
|
|
73
|
+
if np.ma.is_masked(masked):
|
|
74
|
+
raise ValueError("values must not contain masked entries")
|
|
75
|
+
raw = np.asarray(masked)
|
|
76
|
+
if raw.dtype.kind not in "iuf":
|
|
77
|
+
raise ValueError("values must have a real integer or floating-point dtype")
|
|
78
|
+
matrix = np.asarray(raw, dtype=np.float64)
|
|
79
|
+
except (TypeError, ValueError, UserWarning, RuntimeWarning) as exc:
|
|
80
|
+
raise ValueError("values must be a finite real source-by-repeat matrix without masked entries") from exc
|
|
81
|
+
if (
|
|
82
|
+
matrix.ndim != _SOURCE_MATRIX_NDIM
|
|
83
|
+
or matrix.shape[0] < _MIN_SOURCE_COUNT
|
|
84
|
+
or matrix.shape[1] < 1
|
|
85
|
+
or not np.all(np.isfinite(matrix))
|
|
86
|
+
):
|
|
87
|
+
raise ValueError("values must be finite, with at least three source rows and one repeat column")
|
|
88
|
+
|
|
89
|
+
def statistic(indices: np.ndarray) -> float:
|
|
90
|
+
return float(np.quantile(matrix[indices], quantile, method="linear"))
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
with warnings.catch_warnings():
|
|
94
|
+
warnings.simplefilter("error", DegenerateDataWarning)
|
|
95
|
+
warnings.simplefilter("error", RuntimeWarning)
|
|
96
|
+
result = bootstrap(
|
|
97
|
+
(np.arange(matrix.shape[0]),),
|
|
98
|
+
statistic,
|
|
99
|
+
vectorized=False,
|
|
100
|
+
method="BCa",
|
|
101
|
+
alternative="less",
|
|
102
|
+
confidence_level=confidence_level,
|
|
103
|
+
n_resamples=int(resamples),
|
|
104
|
+
batch=32,
|
|
105
|
+
rng=np.random.default_rng(int(seed)),
|
|
106
|
+
)
|
|
107
|
+
except (DegenerateDataWarning, RuntimeWarning) as exc:
|
|
108
|
+
raise ValueError("BCa bound unresolved: degenerate or numerically invalid resampling distribution") from exc
|
|
109
|
+
upper_bound = float(result.confidence_interval.high)
|
|
110
|
+
standard_error = float(result.standard_error)
|
|
111
|
+
if (
|
|
112
|
+
not np.isfinite(upper_bound)
|
|
113
|
+
or not np.isfinite(standard_error)
|
|
114
|
+
or not np.all(np.isfinite(result.bootstrap_distribution))
|
|
115
|
+
):
|
|
116
|
+
raise ValueError("BCa bound unresolved: non-finite interval or resampling distribution")
|
|
117
|
+
return SourceBlockQuantileBound(
|
|
118
|
+
estimate=float(np.quantile(matrix, quantile, method="linear")),
|
|
119
|
+
upper_bound=upper_bound,
|
|
120
|
+
standard_error=standard_error,
|
|
121
|
+
bootstrap_distribution=tuple(float(value) for value in result.bootstrap_distribution),
|
|
122
|
+
support_saturated=bool(upper_bound >= np.max(result.bootstrap_distribution)),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = ["SourceBlockQuantileBound", "source_block_quantile_upper"]
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""References for whole-source resampling and one-sided BCa bounds."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pytest
|
|
5
|
+
from scipy.stats import bootstrap
|
|
6
|
+
|
|
7
|
+
from nullcal.studies.bootstrap import SourceBlockQuantileBound, source_block_quantile_upper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize("confidence", [0.1, 0.9, 0.95])
|
|
11
|
+
def test_repeated_rows_reduce_to_independent_scalar_median(confidence):
|
|
12
|
+
# Repeating each scalar an odd number of times preserves the median for
|
|
13
|
+
# every resample and every delete-one-source jackknife sample.
|
|
14
|
+
sources = np.array([-4.0, -2.0, -1.0, 0.0, 0.5, 2.0, 8.0])
|
|
15
|
+
values = np.repeat(sources[:, None], 3, axis=1)
|
|
16
|
+
reference = bootstrap(
|
|
17
|
+
(sources,),
|
|
18
|
+
np.median,
|
|
19
|
+
method="BCa",
|
|
20
|
+
alternative="less",
|
|
21
|
+
confidence_level=confidence,
|
|
22
|
+
n_resamples=256,
|
|
23
|
+
batch=32,
|
|
24
|
+
rng=np.random.default_rng(42),
|
|
25
|
+
)
|
|
26
|
+
result = source_block_quantile_upper(
|
|
27
|
+
values,
|
|
28
|
+
quantile=0.5,
|
|
29
|
+
confidence_level=confidence,
|
|
30
|
+
resamples=256,
|
|
31
|
+
seed=42,
|
|
32
|
+
)
|
|
33
|
+
assert isinstance(result, SourceBlockQuantileBound)
|
|
34
|
+
assert result.estimate == np.median(sources)
|
|
35
|
+
assert result.upper_bound == reference.confidence_interval.high
|
|
36
|
+
assert result.standard_error == reference.standard_error
|
|
37
|
+
np.testing.assert_array_equal(result.bootstrap_distribution, reference.bootstrap_distribution)
|
|
38
|
+
if confidence == 0.1:
|
|
39
|
+
assert result.upper_bound < result.estimate
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_bootstrap_distribution_preserves_complete_rows():
|
|
43
|
+
values = np.arange(33.0).reshape(11, 3) ** 2
|
|
44
|
+
indices = np.random.default_rng(123).integers(0, 11, size=(128, 11))
|
|
45
|
+
expected = [np.quantile(values[row].ravel(), 0.8, method="linear") for row in indices]
|
|
46
|
+
result = source_block_quantile_upper(values, quantile=0.8, resamples=128, seed=123)
|
|
47
|
+
np.testing.assert_array_equal(result.bootstrap_distribution, expected)
|
|
48
|
+
assert len(result.bootstrap_distribution) == 128
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize(
|
|
52
|
+
"values",
|
|
53
|
+
[
|
|
54
|
+
[],
|
|
55
|
+
[[]],
|
|
56
|
+
np.zeros((2, 4)),
|
|
57
|
+
np.zeros((3, 0)),
|
|
58
|
+
np.zeros(4),
|
|
59
|
+
np.zeros((3, 2, 1)),
|
|
60
|
+
np.full((3, 2), np.nan),
|
|
61
|
+
np.full((3, 2), np.inf),
|
|
62
|
+
np.ones((3, 2), dtype=complex),
|
|
63
|
+
],
|
|
64
|
+
)
|
|
65
|
+
def test_rejects_invalid_source_matrices(values):
|
|
66
|
+
with pytest.raises(ValueError, match="values"):
|
|
67
|
+
source_block_quantile_upper(values, resamples=32)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize(
|
|
71
|
+
"kwargs",
|
|
72
|
+
[
|
|
73
|
+
{"quantile": 0},
|
|
74
|
+
{"quantile": 1},
|
|
75
|
+
{"quantile": np.nan},
|
|
76
|
+
{"quantile": True},
|
|
77
|
+
{"confidence_level": 0},
|
|
78
|
+
{"confidence_level": 1},
|
|
79
|
+
{"confidence_level": np.inf},
|
|
80
|
+
{"resamples": 0},
|
|
81
|
+
{"resamples": 1},
|
|
82
|
+
{"resamples": 2.5},
|
|
83
|
+
{"resamples": True},
|
|
84
|
+
{"seed": -1},
|
|
85
|
+
{"seed": 1.5},
|
|
86
|
+
{"seed": True},
|
|
87
|
+
],
|
|
88
|
+
)
|
|
89
|
+
def test_rejects_invalid_controls(kwargs):
|
|
90
|
+
with pytest.raises(ValueError, match=next(iter(kwargs))):
|
|
91
|
+
source_block_quantile_upper(np.arange(12.0).reshape(4, 3), **kwargs)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_degenerate_bca_is_unresolved():
|
|
95
|
+
with pytest.raises(ValueError, match=r"BCa.*unresolved"):
|
|
96
|
+
source_block_quantile_upper(np.ones((8, 3)), resamples=128)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_bca_is_distinct_from_the_percentile_bound():
|
|
100
|
+
sources = np.arange(50.0) ** 3
|
|
101
|
+
|
|
102
|
+
def statistic(sample):
|
|
103
|
+
return np.quantile(sample, 0.8, method="linear")
|
|
104
|
+
|
|
105
|
+
reference = bootstrap(
|
|
106
|
+
(sources,),
|
|
107
|
+
statistic,
|
|
108
|
+
vectorized=False,
|
|
109
|
+
method="BCa",
|
|
110
|
+
alternative="less",
|
|
111
|
+
confidence_level=0.95,
|
|
112
|
+
n_resamples=1024,
|
|
113
|
+
batch=32,
|
|
114
|
+
rng=np.random.default_rng(741),
|
|
115
|
+
)
|
|
116
|
+
percentile = np.quantile(reference.bootstrap_distribution, 0.95, method="linear")
|
|
117
|
+
assert reference.confidence_interval.high != percentile
|
|
118
|
+
result = source_block_quantile_upper(
|
|
119
|
+
sources[:, None],
|
|
120
|
+
quantile=0.8,
|
|
121
|
+
confidence_level=0.95,
|
|
122
|
+
resamples=1024,
|
|
123
|
+
seed=741,
|
|
124
|
+
)
|
|
125
|
+
assert result.upper_bound == reference.confidence_interval.high
|
|
126
|
+
assert result.upper_bound != percentile
|
|
127
|
+
assert result.support_saturated is False
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_rejects_masked_values_instead_of_using_hidden_entries():
|
|
131
|
+
values = np.ma.masked_equal(np.arange(12.0).reshape(4, 3), 3.0)
|
|
132
|
+
with pytest.raises(ValueError, match="values"):
|
|
133
|
+
source_block_quantile_upper(values, resamples=128)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.parametrize("container", [list, tuple])
|
|
137
|
+
def test_rejects_masked_rows_inside_a_sequence(container):
|
|
138
|
+
matrix = np.ma.masked_equal(np.arange(12.0).reshape(4, 3), 3.0)
|
|
139
|
+
values = container(matrix[row] for row in range(4))
|
|
140
|
+
with pytest.raises(ValueError, match="values"):
|
|
141
|
+
source_block_quantile_upper(values, resamples=64)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.mark.parametrize("dtype", [str, bool, object])
|
|
145
|
+
def test_rejects_non_numeric_or_boolean_matrix_types(dtype):
|
|
146
|
+
values = np.arange(12.0).reshape(4, 3).astype(dtype)
|
|
147
|
+
with pytest.raises(ValueError, match="values"):
|
|
148
|
+
source_block_quantile_upper(values, resamples=64)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_reports_bootstrap_support_boundary_saturation():
|
|
152
|
+
values = np.array([0.0, 0.01, 0.02, 0.03, 100.0])[:, None]
|
|
153
|
+
result = source_block_quantile_upper(values, resamples=256, seed=5)
|
|
154
|
+
assert result.upper_bound == max(result.bootstrap_distribution)
|
|
155
|
+
assert getattr(result, "support_saturated", None) is True
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Check generated claim links using synthetic results, independently of plotting."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import runpy
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from types import ModuleType, SimpleNamespace
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_readme_claim_links_resolve_to_run_claims(tmp_path, monkeypatch):
|
|
12
|
+
# Plotting is optional and irrelevant to this metadata contract.
|
|
13
|
+
plotting = ModuleType("matplotlib.pyplot")
|
|
14
|
+
package = ModuleType("matplotlib")
|
|
15
|
+
package.__path__ = []
|
|
16
|
+
package.pyplot = plotting
|
|
17
|
+
monkeypatch.setitem(sys.modules, "matplotlib", package)
|
|
18
|
+
monkeypatch.setitem(sys.modules, "matplotlib.pyplot", plotting)
|
|
19
|
+
script = Path(__file__).parents[2] / "scripts" / "spline_resolution_study.py"
|
|
20
|
+
main = runpy.run_path(script)["main"]
|
|
21
|
+
namespace = main.__globals__
|
|
22
|
+
results = [
|
|
23
|
+
{
|
|
24
|
+
"name": "submitted-10",
|
|
25
|
+
"knot_count": 10,
|
|
26
|
+
"knots_hz": [20.0 + 1980.0 * i / 9 for i in range(10)],
|
|
27
|
+
"evaluation_minimum_hz": 20.0,
|
|
28
|
+
"evaluation_maximum_hz": 512.0,
|
|
29
|
+
"max_amplitude_residual_percent": 1.5,
|
|
30
|
+
"max_phase_residual_degrees": 2.5,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "qnm-dense-19",
|
|
34
|
+
"knot_count": 19,
|
|
35
|
+
"knots_hz": [20.0 + 1980.0 * i / 18 for i in range(19)],
|
|
36
|
+
"evaluation_minimum_hz": 20.0,
|
|
37
|
+
"evaluation_maximum_hz": 2000.0,
|
|
38
|
+
"max_amplitude_residual_percent": 0.5,
|
|
39
|
+
"max_phase_residual_degrees": 0.25,
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
by_name = {row["name"]: row for row in results}
|
|
43
|
+
monkeypatch.setitem(namespace, "configurations", lambda: [SimpleNamespace(name=name) for name in by_name])
|
|
44
|
+
monkeypatch.setitem(namespace, "evaluate", lambda config: by_name[config.name])
|
|
45
|
+
monkeypatch.setitem(namespace, "git_metadata", lambda: ("0" * 40, False))
|
|
46
|
+
monkeypatch.setitem(namespace, "write_figure", lambda *_: None)
|
|
47
|
+
monkeypatch.setattr(sys, "argv", [str(script), "--output-directory", str(tmp_path)])
|
|
48
|
+
|
|
49
|
+
main()
|
|
50
|
+
|
|
51
|
+
run = json.loads((tmp_path / "run.json").read_text())
|
|
52
|
+
links = dict(
|
|
53
|
+
re.findall(r"^- Claim `([^`]+)` -> result `([^`]+)`", (tmp_path / "README.md").read_text(), re.MULTILINE)
|
|
54
|
+
)
|
|
55
|
+
assert set(run["claims"]) == {
|
|
56
|
+
"submitted_basis_not_demonstrated_over_20_2000_hz",
|
|
57
|
+
"recommended_basis_resolves_bump_over_20_2000_hz",
|
|
58
|
+
}
|
|
59
|
+
assert links == {key: claim["supported_by"] for key, claim in run["claims"].items()}
|
|
60
|
+
assert run["results"] == results
|