nullcal 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/codeql.yml +3 -3
  2. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/draft_release.yml +1 -1
  3. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/release.yml +1 -1
  4. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/scheduled_release.yml +1 -1
  5. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/support_floor_update.yml +1 -1
  6. {nullcal-0.2.0 → nullcal-0.2.1}/.pre-commit-config.yaml +4 -4
  7. {nullcal-0.2.0 → nullcal-0.2.1}/PKG-INFO +1 -1
  8. {nullcal-0.2.0 → nullcal-0.2.1}/docs/user-guide/quickstart.md +23 -0
  9. {nullcal-0.2.0 → nullcal-0.2.1}/pyproject.toml +4 -3
  10. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/calibration.py +34 -0
  11. nullcal-0.2.1/tests/calibration/test_posterior_summary.py +23 -0
  12. nullcal-0.2.1/tests/likelihood/test_recalibration_direction.py +107 -0
  13. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_time_domain_transform.py +16 -1
  14. {nullcal-0.2.0 → nullcal-0.2.1}/uv.lock +192 -191
  15. {nullcal-0.2.0 → nullcal-0.2.1}/.coderabbit.yaml +0 -0
  16. {nullcal-0.2.0 → nullcal-0.2.1}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  17. {nullcal-0.2.0 → nullcal-0.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  18. {nullcal-0.2.0 → nullcal-0.2.1}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  19. {nullcal-0.2.0 → nullcal-0.2.1}/.github/pull_request_template.md +0 -0
  20. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/ci.yml +0 -0
  21. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/documentation.yml +0 -0
  22. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/publish.yml +0 -0
  23. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/publish_testpypi.yml +0 -0
  24. {nullcal-0.2.0 → nullcal-0.2.1}/.github/workflows/semantic_pull_request.yml +0 -0
  25. {nullcal-0.2.0 → nullcal-0.2.1}/.gitignore +0 -0
  26. {nullcal-0.2.0 → nullcal-0.2.1}/.markdownlint.yaml +0 -0
  27. {nullcal-0.2.0 → nullcal-0.2.1}/.prettierignore +0 -0
  28. {nullcal-0.2.0 → nullcal-0.2.1}/.prettierrc.yaml +0 -0
  29. {nullcal-0.2.0 → nullcal-0.2.1}/.typos.toml +0 -0
  30. {nullcal-0.2.0 → nullcal-0.2.1}/CODE_OF_CONDUCT.md +0 -0
  31. {nullcal-0.2.0 → nullcal-0.2.1}/CONTRIBUTING.md +0 -0
  32. {nullcal-0.2.0 → nullcal-0.2.1}/LICENSE +0 -0
  33. {nullcal-0.2.0 → nullcal-0.2.1}/MANIFEST.in +0 -0
  34. {nullcal-0.2.0 → nullcal-0.2.1}/README.md +0 -0
  35. {nullcal-0.2.0 → nullcal-0.2.1}/cliff.toml +0 -0
  36. {nullcal-0.2.0 → nullcal-0.2.1}/docs/.gitignore +0 -0
  37. {nullcal-0.2.0 → nullcal-0.2.1}/docs/contributing.md +0 -0
  38. {nullcal-0.2.0 → nullcal-0.2.1}/docs/dev/architecture.md +0 -0
  39. {nullcal-0.2.0 → nullcal-0.2.1}/docs/index.md +0 -0
  40. {nullcal-0.2.0 → nullcal-0.2.1}/docs/javascripts/mathjax.js +0 -0
  41. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/index.md +0 -0
  42. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/clustering/base.md +0 -0
  43. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/clustering/index.md +0 -0
  44. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/clustering/injection.md +0 -0
  45. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/clustering/precompute.md +0 -0
  46. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/clustering/single.md +0 -0
  47. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/clustering/time_frequency_map.md +0 -0
  48. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/data.md +0 -0
  49. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/index.md +0 -0
  50. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/likelihood/index.md +0 -0
  51. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/likelihood/recalibration_likelihood.md +0 -0
  52. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/metadata/index.md +0 -0
  53. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/metadata/yaml.md +0 -0
  54. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/null_stream/calibration.md +0 -0
  55. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/null_stream/index.md +0 -0
  56. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/null_stream/null_stream.md +0 -0
  57. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/null_stream/projector.md +0 -0
  58. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/null_stream/whiten.md +0 -0
  59. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/result/index.md +0 -0
  60. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/result/result.md +0 -0
  61. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/result/utils.md +0 -0
  62. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/index.md +0 -0
  63. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/inverse_wavelet_freq_funcs.md +0 -0
  64. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/inverse_wavelet_time_funcs.md +0 -0
  65. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/stft.md +0 -0
  66. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/transform_freq_funcs.md +0 -0
  67. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/transform_time_funcs.md +0 -0
  68. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/utils.md +0 -0
  69. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/time_frequency_transform/wavelet_transforms.md +0 -0
  70. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/utils/index.md +0 -0
  71. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/utils/log.md +0 -0
  72. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/utils/snr.md +0 -0
  73. {nullcal-0.2.0 → nullcal-0.2.1}/docs/reference/nullcal/version.md +0 -0
  74. {nullcal-0.2.0 → nullcal-0.2.1}/docs/security.md +0 -0
  75. {nullcal-0.2.0 → nullcal-0.2.1}/docs/user-guide/installation.md +0 -0
  76. {nullcal-0.2.0 → nullcal-0.2.1}/renovate.json +0 -0
  77. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/calibration_bilby_anchor.py +0 -0
  78. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/diagnose_likelihood_agreement.py +0 -0
  79. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/jax_wdm_verification.py +0 -0
  80. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/lwa_leakage_study.py +0 -0
  81. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/release_version.py +0 -0
  82. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/run_reference_blackjax.py +0 -0
  83. {nullcal-0.2.0 → nullcal-0.2.1}/scripts/spline_resolution_study.py +0 -0
  84. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/__init__.py +0 -0
  85. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/clustering/__init__.py +0 -0
  86. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/clustering/base.py +0 -0
  87. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/clustering/injection.py +0 -0
  88. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/clustering/precompute.py +0 -0
  89. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/clustering/single.py +0 -0
  90. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/clustering/time_frequency_map.py +0 -0
  91. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/data.py +0 -0
  92. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/likelihood/__init__.py +0 -0
  93. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/likelihood/recalibration_likelihood.py +0 -0
  94. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/metadata/__init__.py +0 -0
  95. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/metadata/yaml.py +0 -0
  96. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/null_stream/__init__.py +0 -0
  97. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/null_stream/calibration.py +0 -0
  98. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/null_stream/null_stream.py +0 -0
  99. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/null_stream/projector.py +0 -0
  100. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/null_stream/whiten.py +0 -0
  101. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/result/__init__.py +0 -0
  102. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/result/result.py +0 -0
  103. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/result/utils.py +0 -0
  104. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/sampler.py +0 -0
  105. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/studies/__init__.py +0 -0
  106. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/studies/lwa_leakage.py +0 -0
  107. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/studies/spline_resolution.py +0 -0
  108. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/README.md +0 -0
  109. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/__init__.py +0 -0
  110. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/inverse_wavelet_freq_funcs.py +0 -0
  111. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/inverse_wavelet_time_funcs.py +0 -0
  112. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/stft.py +0 -0
  113. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/transform_freq_funcs.py +0 -0
  114. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/transform_time_funcs.py +0 -0
  115. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/utils.py +0 -0
  116. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/time_frequency_transform/wavelet_transforms.py +0 -0
  117. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/utils/__init__.py +0 -0
  118. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/utils/log.py +0 -0
  119. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/utils/snr.py +0 -0
  120. {nullcal-0.2.0 → nullcal-0.2.1}/src/nullcal/version.py +0 -0
  121. {nullcal-0.2.0 → nullcal-0.2.1}/tests/.gitignore +0 -0
  122. {nullcal-0.2.0 → nullcal-0.2.1}/tests/calibration/bilby_anchor.md +0 -0
  123. {nullcal-0.2.0 → nullcal-0.2.1}/tests/calibration/test_model.py +0 -0
  124. {nullcal-0.2.0 → nullcal-0.2.1}/tests/clustering/conftest.py +0 -0
  125. {nullcal-0.2.0 → nullcal-0.2.1}/tests/clustering/test_clustering_algorithm.py +0 -0
  126. {nullcal-0.2.0 → nullcal-0.2.1}/tests/clustering/test_clustering_classes.py +0 -0
  127. {nullcal-0.2.0 → nullcal-0.2.1}/tests/clustering/test_single_clustering.py +0 -0
  128. {nullcal-0.2.0 → nullcal-0.2.1}/tests/clustering/test_time_frequency_map.py +0 -0
  129. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/README.md +0 -0
  130. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/__init__.py +0 -0
  131. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/config.py +0 -0
  132. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/diagnostics/likelihood_agreement.json +0 -0
  133. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/generate_reference.py +0 -0
  134. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/generate_reference_inputs.py +0 -0
  135. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/pipeline.py +0 -0
  136. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/artifacts.npz +0 -0
  137. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/blackjax_posterior_manifest.json +0 -0
  138. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/blackjax_posterior_samples.npz +0 -0
  139. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/inputs.npz +0 -0
  140. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/inputs_manifest.json +0 -0
  141. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/manifest.json +0 -0
  142. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/posterior_manifest.json +0 -0
  143. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/reference/posterior_samples.npz +0 -0
  144. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/test_blackjax_posterior.py +0 -0
  145. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/test_noise_log_likelihood_filter_domain.py +0 -0
  146. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/test_reference_artifacts.py +0 -0
  147. {nullcal-0.2.0 → nullcal-0.2.1}/tests/e2e/test_reference_posterior.py +0 -0
  148. {nullcal-0.2.0 → nullcal-0.2.1}/tests/likelihood/test_logdensity.py +0 -0
  149. {nullcal-0.2.0 → nullcal-0.2.1}/tests/likelihood/test_recalibration_likelihood.py +0 -0
  150. {nullcal-0.2.0 → nullcal-0.2.1}/tests/metadata/test_yaml.py +0 -0
  151. {nullcal-0.2.0 → nullcal-0.2.1}/tests/null_stream/test_calibration_jax.py +0 -0
  152. {nullcal-0.2.0 → nullcal-0.2.1}/tests/null_stream/test_null_stream.py +0 -0
  153. {nullcal-0.2.0 → nullcal-0.2.1}/tests/null_stream/test_projector.py +0 -0
  154. {nullcal-0.2.0 → nullcal-0.2.1}/tests/null_stream/test_whiten.py +0 -0
  155. {nullcal-0.2.0 → nullcal-0.2.1}/tests/result/test_result.py +0 -0
  156. {nullcal-0.2.0 → nullcal-0.2.1}/tests/smoke_test.py +0 -0
  157. {nullcal-0.2.0 → nullcal-0.2.1}/tests/studies/test_lwa_leakage.py +0 -0
  158. {nullcal-0.2.0 → nullcal-0.2.1}/tests/studies/test_spline_resolution.py +0 -0
  159. {nullcal-0.2.0 → nullcal-0.2.1}/tests/test_blackjax_boundary.py +0 -0
  160. {nullcal-0.2.0 → nullcal-0.2.1}/tests/test_data.py +0 -0
  161. {nullcal-0.2.0 → nullcal-0.2.1}/tests/test_dependencies_are_declared.py +0 -0
  162. {nullcal-0.2.0 → nullcal-0.2.1}/tests/test_jax_wdm_verification.py +0 -0
  163. {nullcal-0.2.0 → nullcal-0.2.1}/tests/test_release_version.py +0 -0
  164. {nullcal-0.2.0 → nullcal-0.2.1}/tests/test_sampler.py +0 -0
  165. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_jax_transform.py +0 -0
  166. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_stft.py +0 -0
  167. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_transform_freq_funcs.py +0 -0
  168. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_utils.py +0 -0
  169. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_wavelet_transforms.py +0 -0
  170. {nullcal-0.2.0 → nullcal-0.2.1}/tests/time_frequency_transform/test_wdm_window.py +0 -0
  171. {nullcal-0.2.0 → nullcal-0.2.1}/tests/utils/test_log.py +0 -0
  172. {nullcal-0.2.0 → nullcal-0.2.1}/tests/utils/test_snr.py +0 -0
  173. {nullcal-0.2.0 → nullcal-0.2.1}/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@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
25
+ uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # 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@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
31
+ uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
32
32
 
33
33
  - name: Perform CodeQL Analysis
34
- uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4
34
+ uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4
@@ -22,7 +22,7 @@ jobs:
22
22
 
23
23
  - name: Generate changelog
24
24
  id: changelog
25
- uses: orhun/git-cliff-action@3d96a18cc4ec17e9dc69ddcc424ccafaf1f78ce2 # v4.9.0
25
+ uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
26
26
  with:
27
27
  config: cliff.toml
28
28
  args: --unreleased --verbose
@@ -30,7 +30,7 @@ jobs:
30
30
 
31
31
  - name: Generate changelog
32
32
  id: changelog
33
- uses: orhun/git-cliff-action@3d96a18cc4ec17e9dc69ddcc424ccafaf1f78ce2 # v4.9.0
33
+ uses: orhun/git-cliff-action@a9a95522b26fe6403f7bb24031f21fb573d0f5ff # v4.9.1
34
34
  with:
35
35
  config: cliff.toml
36
36
  args: --current --verbose
@@ -31,7 +31,7 @@ jobs:
31
31
 
32
32
  - name: Check for new commits
33
33
  id: check_commits
34
- uses: isaac-cf-wong/detect-git-changes-action@10d1e3ac7c5ee0997d7b370e9929cacac9c87013 # v0.0.19
34
+ uses: isaac-cf-wong/detect-git-changes-action@a8b0d224a4fdff017607079f83f01f013b9d66a6 # v0.0.20
35
35
  with:
36
36
  use-latest-tag: true
37
37
 
@@ -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@74dfcfcc079cd22b26713242940f65ba2b3c70c7 # v0.2.9
25
+ uses: isaac-cf-wong/dependency-support-policy-action@dfc24273d70d9730ede96b5fdef09a8d7887cf04 # v0.2.10
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.16.8
20
+ rev: v0.16.9
21
21
  hooks:
22
22
  - id: ruff
23
23
  args: [--fix, --config=pyproject.toml]
@@ -29,13 +29,13 @@ repos:
29
29
  - id: taplo-format
30
30
 
31
31
  - repo: https://github.com/crate-ci/typos
32
- rev: v1.50.2
32
+ rev: v1.50.3
33
33
  hooks:
34
34
  - id: typos
35
35
  args: []
36
36
 
37
37
  - repo: https://github.com/rbubley/mirrors-prettier
38
- rev: v3.9.8
38
+ rev: v3.9.9
39
39
  hooks:
40
40
  - id: prettier
41
41
  exclude: ^tests/e2e/diagnostics/likelihood_agreement\.json$
@@ -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.12.17
57
+ rev: 0.12.20
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.2.0
3
+ Version: 0.2.1
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
@@ -77,6 +77,29 @@ log_posterior = likelihood.logdensity_fn(params)
77
77
  Use `nullcal.sampler.sample_nuts` when the standard window-adapted NUTS run and
78
78
  R-hat, ESS, and divergence diagnostics are desired together.
79
79
 
80
+ ## 5. Summarize and Apply a Calibration Posterior
81
+
82
+ ```python
83
+ from nullcal.calibration import posterior_median_calibration_factor
84
+
85
+ # result is the output of nullcal.sampler.sample_nuts(...).
86
+ frequencies = data.frequency_array[np.all(data.mask, axis=0)]
87
+ factor = posterior_median_calibration_factor(
88
+ frequencies,
89
+ likelihood.knot_frequencies,
90
+ result.samples["amplitude"],
91
+ result.samples["phase"],
92
+ )
93
+ corrected_strain = np.asarray(data.strain).copy()
94
+ corrected_strain[:, np.all(data.mask, axis=0)] /= np.asarray(factor)
95
+ ```
96
+
97
+ The likelihood multiplies the whitened antenna response by the calibration
98
+ factor. Correcting observed strain uses its inverse, as above. The point
99
+ estimate evaluates the complex factor for every posterior sample at every
100
+ frequency, then takes separate medians of its real and imaginary parts.
101
+ Evaluating a spline from median knot values is a different estimator.
102
+
80
103
  ## Next Steps
81
104
 
82
105
  - See [Installation](installation.md) for environment setup
@@ -53,7 +53,7 @@ Tracker = "https://github.com/Leuven-Gravity-Institute/nullcal/issues"
53
53
 
54
54
  [dependency-groups]
55
55
  dev = [
56
- "ruff>=0.16.8",
56
+ "ruff>=0.16.9",
57
57
  "prek>=0.5.3",
58
58
  "pytest-github-actions-annotate-failures>=0.4.2",
59
59
  "pytest-cov>=7.1.0",
@@ -61,7 +61,7 @@ dev = [
61
61
  "pytest>=9.1.1",
62
62
  ]
63
63
 
64
- docs = ["zensical>=0.0.62", "mkdocstrings-python>=2.0.8"]
64
+ docs = ["zensical>=0.0.65", "mkdocstrings-python>=2.0.9"]
65
65
  build = ["hatch>=1.18.1"]
66
66
  release = ["git-cliff>=2.14.2"]
67
67
 
@@ -84,7 +84,8 @@ parallel = true
84
84
  source = ["src"]
85
85
 
86
86
  [tool.coverage.report]
87
- fail_under = 55
87
+ # Policy target: keep at least three-fifths of the package covered while retaining regression headroom.
88
+ fail_under = 60
88
89
  omit = ["*/tests/*", "*/site-packages/*"]
89
90
 
90
91
  [tool.coverage.paths]
@@ -7,6 +7,12 @@ uniform log-frequency grid and also permits nonuniform knot placement.
7
7
  Importing this module enables JAX's process-wide ``jax_enable_x64`` setting.
8
8
  Calibration inference requires float64 precision, so this side effect is an
9
9
  intentional part of the module's public behavior.
10
+
11
+ For a posterior point estimate, evaluate the complex calibration factor for
12
+ each sample at each frequency, then take the componentwise median of its real
13
+ and imaginary parts. Interpolating median knot values gives a different curve.
14
+ The factor multiplies the antenna response; to correct observed strain, divide
15
+ the strain by the estimated factor.
10
16
  """
11
17
 
12
18
  from __future__ import annotations
@@ -21,6 +27,7 @@ jax.config.update("jax_enable_x64", True)
21
27
  import jax.numpy as jnp # noqa: E402
22
28
 
23
29
  MINIMUM_CUBIC_SPLINE_KNOTS = 4
30
+ POSTERIOR_ARRAY_NDIM = 3
24
31
 
25
32
 
26
33
  def _float64(values):
@@ -109,6 +116,32 @@ def calibration_factor(frequencies, knot_frequencies, amplitude, phase):
109
116
  return (1.0 + delta_amplitude) * (2.0 + imaginary_phase) / (2.0 - imaginary_phase)
110
117
 
111
118
 
119
+ def posterior_median_calibration_factor(frequencies, knot_frequencies, amplitudes, phases):
120
+ """Return the per-frequency posterior median of the complex factor.
121
+
122
+ ``amplitudes`` and ``phases`` have shape ``(sample, detector, knot)``;
123
+ knots may have shape ``(knot,)`` or ``(detector, knot)``. The complex median
124
+ is defined componentwise in real and imaginary coordinates. This evaluates
125
+ every posterior sample before taking the median, rather than evaluating a
126
+ curve from median knot values.
127
+ """
128
+ amplitudes = _float64(amplitudes)
129
+ phases = _float64(phases)
130
+ knots = _float64(knot_frequencies)
131
+ if amplitudes.ndim != POSTERIOR_ARRAY_NDIM or phases.shape != amplitudes.shape or amplitudes.shape[0] == 0:
132
+ raise ValueError("amplitudes and phases must have matching nonempty (sample, detector, knot) shapes")
133
+ if knots.ndim == 1:
134
+ knots = jnp.broadcast_to(knots, amplitudes.shape[1:])
135
+ if knots.shape != amplitudes.shape[1:]:
136
+ raise ValueError("knot_frequencies must match the detector-by-knot shape")
137
+
138
+ factors = jax.vmap(
139
+ jax.vmap(calibration_factor, in_axes=(None, 0, 0, 0)),
140
+ in_axes=(None, None, 0, 0),
141
+ )(frequencies, knots, amplitudes, phases)
142
+ return jnp.median(jnp.real(factors), axis=0) + 1j * jnp.median(jnp.imag(factors), axis=0)
143
+
144
+
112
145
  def calibration_log_prior(
113
146
  amplitude,
114
147
  phase,
@@ -185,5 +218,6 @@ __all__ = [
185
218
  "calibration_factor",
186
219
  "calibration_log_prior",
187
220
  "calibration_parameters_to_unconstrained",
221
+ "posterior_median_calibration_factor",
188
222
  "unconstrained_to_calibration_parameters",
189
223
  ]
@@ -0,0 +1,23 @@
1
+ """Input validation for the per-frequency calibration posterior summary."""
2
+
3
+ import numpy as np
4
+ import pytest
5
+
6
+ from nullcal.calibration import posterior_median_calibration_factor
7
+
8
+
9
+ def test_posterior_median_rejects_empty_samples():
10
+ knots = np.geomspace(4.0, 32.0, 4)
11
+ empty = np.empty((0, 1, knots.size))
12
+
13
+ with pytest.raises(ValueError, match="matching nonempty"):
14
+ posterior_median_calibration_factor(knots, knots, empty, empty)
15
+
16
+
17
+ def test_posterior_median_rejects_detector_by_knot_mismatch():
18
+ knots = np.geomspace(4.0, 32.0, 4)
19
+ samples = np.zeros((2, 2, knots.size))
20
+ one_detector_knots = knots[np.newaxis, :]
21
+
22
+ with pytest.raises(ValueError, match="detector-by-knot shape"):
23
+ posterior_median_calibration_factor(knots, one_detector_knots, samples, samples)
@@ -0,0 +1,107 @@
1
+ """Zero-noise calibration-direction and posterior-summary checks."""
2
+
3
+ import numpy as np
4
+ import pytest
5
+
6
+ from nullcal.calibration import calibration_factor, posterior_median_calibration_factor
7
+ from nullcal.data import InterferometerData
8
+ from nullcal.likelihood import RecalibrationLikelihood
9
+ from nullcal.likelihood.recalibration_likelihood import ET_BEAM_PATTERN
10
+ from nullcal.null_stream.null_stream import NullStream
11
+ from nullcal.time_frequency_transform.wavelet_transforms import WaveletTransform
12
+
13
+
14
+ @pytest.mark.parametrize("path", ["likelihood", "null_stream"])
15
+ @pytest.mark.parametrize(
16
+ "correction",
17
+ [
18
+ "truth",
19
+ pytest.param("inverse", marks=pytest.mark.xfail(strict=True, raises=AssertionError)),
20
+ pytest.param("conjugate", marks=pytest.mark.xfail(strict=True, raises=AssertionError)),
21
+ ],
22
+ )
23
+ def test_exact_calibration_truth_removes_zero_noise_null_residual(path, correction, monkeypatch):
24
+ frequencies = np.fft.rfftfreq(64, 1.0 / 64.0)
25
+ mask = (frequencies >= 4.0) & (frequencies <= 32.0)
26
+ knots = np.geomspace(4.0, 32.0, 4)
27
+ amplitude = np.array([[0.08, 0.04, -0.03, 0.02], [-0.06, 0.03, 0.05, -0.02], [0.02, -0.04, 0.07, 0.01]])
28
+ phase = np.array([[0.06, -0.03, 0.02, 0.04], [-0.05, 0.02, 0.04, -0.03], [0.03, 0.05, -0.02, 0.01]])
29
+ factors = np.asarray(
30
+ [calibration_factor(frequencies[mask], knots, a, p) for a, p in zip(amplitude, phase, strict=True)]
31
+ )
32
+ waveforms = np.vstack(
33
+ (
34
+ np.exp(0.04j * frequencies[mask]),
35
+ 0.7 * np.exp(-0.07j * frequencies[mask]),
36
+ )
37
+ )
38
+ clean_signal = ET_BEAM_PATTERN @ waveforms
39
+ strain = np.zeros((3, frequencies.size), dtype=np.complex128)
40
+ strain[:, mask] = factors * clean_signal
41
+ data = InterferometerData(
42
+ psd=np.ones((3, frequencies.size)),
43
+ strain=strain,
44
+ mask=np.broadcast_to(mask, strain.shape),
45
+ frequency_array=frequencies,
46
+ duration=1.0,
47
+ sampling_frequency=64.0,
48
+ start_time=0.0,
49
+ name=("ET1", "ET2", "ET3"),
50
+ )
51
+ likelihood = RecalibrationLikelihood(
52
+ data,
53
+ knots,
54
+ time_frequency_filter=np.ones((8, 8), dtype=bool),
55
+ wavelet_transform_frequency_resolution=4.0,
56
+ )
57
+
58
+ transform = {"truth": lambda factor: factor, "inverse": lambda factor: 1.0 / factor, "conjugate": np.conj}[
59
+ correction
60
+ ]
61
+ if path == "likelihood":
62
+ if correction != "truth":
63
+ original_factor = likelihood._calibration_factor
64
+ monkeypatch.setattr(likelihood, "_calibration_factor", lambda a, p: transform(original_factor(a, p)))
65
+ residual = np.asarray(likelihood._frequency_domain_null_stream(amplitude, phase))[:, mask]
66
+ else:
67
+ null_stream = NullStream(
68
+ data,
69
+ WaveletTransform(duration=1.0, sampling_frequency=64.0, frequency_resolution=4.0, nx=4.0),
70
+ np.ones((8, 8), dtype=bool),
71
+ )
72
+ full_factors = np.zeros_like(strain)
73
+ full_factors[:, mask] = transform(factors)
74
+ residual = np.asarray(null_stream.compute_calibrated_frequency_domain_null_stream(full_factors))[:, mask]
75
+
76
+ relative_residual = np.max(np.abs(residual)) / np.max(np.abs(strain))
77
+ assert relative_residual < 2e-15, f"relative residual {relative_residual:.17g} exceeds 2e-15"
78
+
79
+
80
+ def test_posterior_median_is_taken_after_evaluating_each_factor():
81
+ frequencies = np.geomspace(4.0, 32.0, 17)
82
+ knots = np.geomspace(4.0, 32.0, 4)
83
+ amplitudes = np.zeros((3, 1, 4))
84
+ amplitudes[0, 0, 0] = 0.12
85
+ amplitudes[1, 0, 1] = 0.12
86
+ amplitudes[2, 0, 2] = 0.12
87
+ phases = np.zeros_like(amplitudes)
88
+ phases[0, 0, 1] = 0.04
89
+ phases[1, 0, 2] = -0.03
90
+ phases[2, 0, 3] = 0.05
91
+
92
+ sample_factors = np.asarray(
93
+ [calibration_factor(frequencies, knots, a[0], p[0]) for a, p in zip(amplitudes, phases, strict=True)]
94
+ )
95
+ expected = np.median(sample_factors.real, axis=0) + 1j * np.median(sample_factors.imag, axis=0)
96
+ actual = np.asarray(posterior_median_calibration_factor(frequencies, knots, amplitudes, phases))
97
+ median_knot_curve = np.asarray(
98
+ calibration_factor(
99
+ frequencies,
100
+ knots,
101
+ np.median(amplitudes[:, 0], axis=0),
102
+ np.median(phases[:, 0], axis=0),
103
+ )
104
+ )
105
+
106
+ np.testing.assert_allclose(actual[0], expected, rtol=0.0, atol=1e-15)
107
+ assert np.max(np.abs(actual[0] - median_knot_curve)) > 1e-3
@@ -19,7 +19,10 @@ import numpy as np
19
19
  import pytest
20
20
 
21
21
  from nullcal.time_frequency_transform import get_shape_of_wavelet_transform, transform_wavelet_freq_time
22
- from nullcal.time_frequency_transform.inverse_wavelet_time_funcs import inverse_wavelet_time_helper_fast
22
+ from nullcal.time_frequency_transform.inverse_wavelet_time_funcs import (
23
+ _compact_packets,
24
+ inverse_wavelet_time_helper_fast,
25
+ )
23
26
  from nullcal.time_frequency_transform.transform_time_funcs import phi_vec, transform_wavelet_time_helper
24
27
  from nullcal.time_frequency_transform.wavelet_transforms import inverse_wavelet_time, transform_wavelet_time
25
28
 
@@ -59,6 +62,18 @@ def noise():
59
62
  return np.random.default_rng(20260914).normal(size=N_SAMPLES)
60
63
 
61
64
 
65
+ @pytest.mark.unit
66
+ def test_compact_packets_preserve_odd_row_sign_for_even_modes():
67
+ """Odd-row even modes enter the low and mirrored packets with opposite signs."""
68
+ wave = np.zeros((2, 4))
69
+ wave[0, 2] = 5.0
70
+ wave[1, 2] = 2.0
71
+
72
+ packets = np.asarray(_compact_packets(wave, n_f=4, n_t=2))
73
+
74
+ np.testing.assert_array_equal(packets[0, [2, 6]], [3.0, 7.0])
75
+
76
+
62
77
  @pytest.mark.unit
63
78
  def test_phi_vec_has_the_length_the_kernels_index(shape, phi):
64
79
  """The filter spans ``mult * 2 * n_f`` samples, the window length the kernels loop over.