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.
Files changed (178) hide show
  1. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/codeql.yml +3 -3
  2. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/scheduled_release.yml +8 -2
  3. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/semantic_pull_request.yml +1 -1
  4. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/support_floor_update.yml +1 -1
  5. {nullcal-0.3.3 → nullcal-0.4.0}/.pre-commit-config.yaml +3 -3
  6. {nullcal-0.3.3 → nullcal-0.4.0}/PKG-INFO +1 -1
  7. {nullcal-0.3.3 → nullcal-0.4.0}/pyproject.toml +5 -5
  8. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/spline_resolution_study.py +1 -1
  9. nullcal-0.4.0/src/nullcal/studies/bootstrap.py +126 -0
  10. nullcal-0.4.0/tests/studies/test_source_block_bootstrap.py +155 -0
  11. nullcal-0.4.0/tests/studies/test_spline_provenance.py +60 -0
  12. {nullcal-0.3.3 → nullcal-0.4.0}/uv.lock +360 -349
  13. {nullcal-0.3.3 → nullcal-0.4.0}/.coderabbit.yaml +0 -0
  14. {nullcal-0.3.3 → nullcal-0.4.0}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  15. {nullcal-0.3.3 → nullcal-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  16. {nullcal-0.3.3 → nullcal-0.4.0}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  17. {nullcal-0.3.3 → nullcal-0.4.0}/.github/pull_request_template.md +0 -0
  18. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/ci.yml +0 -0
  19. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/documentation.yml +0 -0
  20. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/draft_release.yml +0 -0
  21. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/publish.yml +0 -0
  22. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/publish_testpypi.yml +0 -0
  23. {nullcal-0.3.3 → nullcal-0.4.0}/.github/workflows/release.yml +0 -0
  24. {nullcal-0.3.3 → nullcal-0.4.0}/.gitignore +0 -0
  25. {nullcal-0.3.3 → nullcal-0.4.0}/.markdownlint.yaml +0 -0
  26. {nullcal-0.3.3 → nullcal-0.4.0}/.prettierignore +0 -0
  27. {nullcal-0.3.3 → nullcal-0.4.0}/.prettierrc.yaml +0 -0
  28. {nullcal-0.3.3 → nullcal-0.4.0}/.typos.toml +0 -0
  29. {nullcal-0.3.3 → nullcal-0.4.0}/CODE_OF_CONDUCT.md +0 -0
  30. {nullcal-0.3.3 → nullcal-0.4.0}/CONTRIBUTING.md +0 -0
  31. {nullcal-0.3.3 → nullcal-0.4.0}/LICENSE +0 -0
  32. {nullcal-0.3.3 → nullcal-0.4.0}/MANIFEST.in +0 -0
  33. {nullcal-0.3.3 → nullcal-0.4.0}/README.md +0 -0
  34. {nullcal-0.3.3 → nullcal-0.4.0}/cliff.toml +0 -0
  35. {nullcal-0.3.3 → nullcal-0.4.0}/docs/.gitignore +0 -0
  36. {nullcal-0.3.3 → nullcal-0.4.0}/docs/contributing.md +0 -0
  37. {nullcal-0.3.3 → nullcal-0.4.0}/docs/dev/architecture.md +0 -0
  38. {nullcal-0.3.3 → nullcal-0.4.0}/docs/index.md +0 -0
  39. {nullcal-0.3.3 → nullcal-0.4.0}/docs/javascripts/mathjax.js +0 -0
  40. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/index.md +0 -0
  41. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/base.md +0 -0
  42. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/index.md +0 -0
  43. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/injection.md +0 -0
  44. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/precompute.md +0 -0
  45. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/single.md +0 -0
  46. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/clustering/time_frequency_map.md +0 -0
  47. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/data.md +0 -0
  48. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/index.md +0 -0
  49. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/likelihood/index.md +0 -0
  50. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/likelihood/recalibration_likelihood.md +0 -0
  51. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/metadata/index.md +0 -0
  52. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/metadata/yaml.md +0 -0
  53. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/calibration.md +0 -0
  54. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/index.md +0 -0
  55. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/null_stream.md +0 -0
  56. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/projector.md +0 -0
  57. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/null_stream/whiten.md +0 -0
  58. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/result/index.md +0 -0
  59. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/result/result.md +0 -0
  60. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/result/utils.md +0 -0
  61. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/index.md +0 -0
  62. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/inverse_wavelet_freq_funcs.md +0 -0
  63. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/inverse_wavelet_time_funcs.md +0 -0
  64. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/stft.md +0 -0
  65. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/transform_freq_funcs.md +0 -0
  66. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/transform_time_funcs.md +0 -0
  67. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/utils.md +0 -0
  68. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/time_frequency_transform/wavelet_transforms.md +0 -0
  69. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/utils/index.md +0 -0
  70. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/utils/log.md +0 -0
  71. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/utils/snr.md +0 -0
  72. {nullcal-0.3.3 → nullcal-0.4.0}/docs/reference/nullcal/version.md +0 -0
  73. {nullcal-0.3.3 → nullcal-0.4.0}/docs/security.md +0 -0
  74. {nullcal-0.3.3 → nullcal-0.4.0}/docs/user-guide/installation.md +0 -0
  75. {nullcal-0.3.3 → nullcal-0.4.0}/docs/user-guide/quickstart.md +0 -0
  76. {nullcal-0.3.3 → nullcal-0.4.0}/renovate.json +0 -0
  77. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/calibration_bilby_anchor.py +0 -0
  78. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/diagnose_likelihood_agreement.py +0 -0
  79. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/jax_wdm_verification.py +0 -0
  80. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/lwa_leakage_study.py +0 -0
  81. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/release_version.py +0 -0
  82. {nullcal-0.3.3 → nullcal-0.4.0}/scripts/run_reference_blackjax.py +0 -0
  83. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/__init__.py +0 -0
  84. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/calibration.py +0 -0
  85. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/__init__.py +0 -0
  86. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/base.py +0 -0
  87. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/injection.py +0 -0
  88. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/precompute.py +0 -0
  89. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/single.py +0 -0
  90. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/clustering/time_frequency_map.py +0 -0
  91. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/data.py +0 -0
  92. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/likelihood/__init__.py +0 -0
  93. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/likelihood/recalibration_likelihood.py +0 -0
  94. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/metadata/__init__.py +0 -0
  95. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/metadata/yaml.py +0 -0
  96. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/__init__.py +0 -0
  97. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/calibration.py +0 -0
  98. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/null_stream.py +0 -0
  99. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/projector.py +0 -0
  100. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/null_stream/whiten.py +0 -0
  101. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/result/__init__.py +0 -0
  102. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/result/result.py +0 -0
  103. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/result/utils.py +0 -0
  104. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/sampler.py +0 -0
  105. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/__init__.py +0 -0
  106. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/gpu_cost.py +0 -0
  107. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/lwa_leakage.py +0 -0
  108. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/studies/spline_resolution.py +0 -0
  109. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/README.md +0 -0
  110. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/__init__.py +0 -0
  111. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/inverse_wavelet_freq_funcs.py +0 -0
  112. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/inverse_wavelet_time_funcs.py +0 -0
  113. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/stft.py +0 -0
  114. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/transform_freq_funcs.py +0 -0
  115. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/transform_time_funcs.py +0 -0
  116. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/utils.py +0 -0
  117. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/time_frequency_transform/wavelet_transforms.py +0 -0
  118. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/utils/__init__.py +0 -0
  119. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/utils/log.py +0 -0
  120. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/utils/snr.py +0 -0
  121. {nullcal-0.3.3 → nullcal-0.4.0}/src/nullcal/version.py +0 -0
  122. {nullcal-0.3.3 → nullcal-0.4.0}/tests/.gitignore +0 -0
  123. {nullcal-0.3.3 → nullcal-0.4.0}/tests/calibration/bilby_anchor.md +0 -0
  124. {nullcal-0.3.3 → nullcal-0.4.0}/tests/calibration/test_model.py +0 -0
  125. {nullcal-0.3.3 → nullcal-0.4.0}/tests/calibration/test_posterior_summary.py +0 -0
  126. {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/conftest.py +0 -0
  127. {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_clustering_algorithm.py +0 -0
  128. {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_clustering_classes.py +0 -0
  129. {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_single_clustering.py +0 -0
  130. {nullcal-0.3.3 → nullcal-0.4.0}/tests/clustering/test_time_frequency_map.py +0 -0
  131. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/README.md +0 -0
  132. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/__init__.py +0 -0
  133. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/config.py +0 -0
  134. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/diagnostics/likelihood_agreement.json +0 -0
  135. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/generate_reference.py +0 -0
  136. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/generate_reference_inputs.py +0 -0
  137. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/pipeline.py +0 -0
  138. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/artifacts.npz +0 -0
  139. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/blackjax_posterior_manifest.json +0 -0
  140. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/blackjax_posterior_samples.npz +0 -0
  141. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/inputs.npz +0 -0
  142. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/inputs_manifest.json +0 -0
  143. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/manifest.json +0 -0
  144. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/posterior_manifest.json +0 -0
  145. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/reference/posterior_samples.npz +0 -0
  146. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_blackjax_posterior.py +0 -0
  147. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_noise_log_likelihood_filter_domain.py +0 -0
  148. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_reference_artifacts.py +0 -0
  149. {nullcal-0.3.3 → nullcal-0.4.0}/tests/e2e/test_reference_posterior.py +0 -0
  150. {nullcal-0.3.3 → nullcal-0.4.0}/tests/likelihood/test_logdensity.py +0 -0
  151. {nullcal-0.3.3 → nullcal-0.4.0}/tests/likelihood/test_recalibration_direction.py +0 -0
  152. {nullcal-0.3.3 → nullcal-0.4.0}/tests/likelihood/test_recalibration_likelihood.py +0 -0
  153. {nullcal-0.3.3 → nullcal-0.4.0}/tests/metadata/test_yaml.py +0 -0
  154. {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_calibration_jax.py +0 -0
  155. {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_null_stream.py +0 -0
  156. {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_projector.py +0 -0
  157. {nullcal-0.3.3 → nullcal-0.4.0}/tests/null_stream/test_whiten.py +0 -0
  158. {nullcal-0.3.3 → nullcal-0.4.0}/tests/result/test_result.py +0 -0
  159. {nullcal-0.3.3 → nullcal-0.4.0}/tests/smoke_test.py +0 -0
  160. {nullcal-0.3.3 → nullcal-0.4.0}/tests/studies/test_gpu_cost.py +0 -0
  161. {nullcal-0.3.3 → nullcal-0.4.0}/tests/studies/test_lwa_leakage.py +0 -0
  162. {nullcal-0.3.3 → nullcal-0.4.0}/tests/studies/test_spline_resolution.py +0 -0
  163. {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_blackjax_boundary.py +0 -0
  164. {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_data.py +0 -0
  165. {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_dependencies_are_declared.py +0 -0
  166. {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_jax_wdm_verification.py +0 -0
  167. {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_release_version.py +0 -0
  168. {nullcal-0.3.3 → nullcal-0.4.0}/tests/test_sampler.py +0 -0
  169. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_jax_transform.py +0 -0
  170. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_stft.py +0 -0
  171. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_time_domain_transform.py +0 -0
  172. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_transform_freq_funcs.py +0 -0
  173. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_utils.py +0 -0
  174. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_wavelet_transforms.py +0 -0
  175. {nullcal-0.3.3 → nullcal-0.4.0}/tests/time_frequency_transform/test_wdm_window.py +0 -0
  176. {nullcal-0.3.3 → nullcal-0.4.0}/tests/utils/test_log.py +0 -0
  177. {nullcal-0.3.3 → nullcal-0.4.0}/tests/utils/test_snr.py +0 -0
  178. {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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
31
+ uses: github/codeql-action/autobuild@33d7b85da1a04171e211befe2c739303b9a4c066 # v4
32
32
 
33
33
  - name: Perform CodeQL Analysis
34
- uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
34
+ uses: github/codeql-action/analyze@33d7b85da1a04171e211befe2c739303b9a4c066 # v4
@@ -29,11 +29,17 @@ jobs:
29
29
  with:
30
30
  fetch-depth: 0
31
31
 
32
- - name: Check for new commits
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@7a2a8da1d825e42f95f1f088760332b18604e781 # v0.0.23
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@48f256284bd46cdaab1048c3721360e808335d50 # v6
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@67550aa285814b2b526b89871215604b36a1110f # v0.2.14
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.3
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.51.1
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.5
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.3
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.2",
57
- "prek>=0.5.7",
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.1.1",
61
+ "pytest>=9.2.0",
62
62
  ]
63
63
 
64
- docs = ["zensical>=0.0.72", "mkdocstrings-python>=2.0.10"]
65
- build = ["hatch>=1.18.1"]
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 `submitted_basis_misses_requirement` -> result `submitted-10` -> run
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