nullpol 0.2.2__tar.gz → 0.2.4__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 (222) hide show
  1. nullpol-0.2.4/.github/workflows/ci.yml +66 -0
  2. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/codeql.yml +3 -3
  3. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/scheduled-release.yml +1 -1
  4. {nullpol-0.2.2 → nullpol-0.2.4}/.pre-commit-config.yaml +5 -5
  5. {nullpol-0.2.2 → nullpol-0.2.4}/PKG-INFO +1 -1
  6. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/data_analysis.py +1 -1
  7. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/input.py +13 -3
  8. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/tgrflow.py +88 -20
  9. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/utility.py +5 -2
  10. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/test_data_context.py +1 -1
  11. nullpol-0.2.4/tests/cli/test_data_analysis_validation.py +79 -0
  12. nullpol-0.2.4/tests/cli/test_input.py +179 -0
  13. nullpol-0.2.4/tests/integrations/asimov/__init__.py +1 -0
  14. nullpol-0.2.4/tests/integrations/asimov/conftest.py +78 -0
  15. nullpol-0.2.4/tests/integrations/asimov/test_tgrflow.py +310 -0
  16. nullpol-0.2.4/tests/integrations/asimov/test_tgrflow_integration.py +251 -0
  17. nullpol-0.2.4/tests/integrations/asimov/test_utility.py +85 -0
  18. {nullpol-0.2.2 → nullpol-0.2.4}/tests/smoke_test.py +15 -3
  19. {nullpol-0.2.2 → nullpol-0.2.4}/uv.lock +695 -685
  20. nullpol-0.2.2/.github/workflows/ci.yml +0 -36
  21. {nullpol-0.2.2 → nullpol-0.2.4}/.github/ISSUE_TEMPLATE/bug-report.md +0 -0
  22. {nullpol-0.2.2 → nullpol-0.2.4}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  23. {nullpol-0.2.2 → nullpol-0.2.4}/.github/ISSUE_TEMPLATE/feature-request.md +0 -0
  24. {nullpol-0.2.2 → nullpol-0.2.4}/.github/pull_request_template.md +0 -0
  25. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/documentation.yml +0 -0
  26. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/draft-release.yml +0 -0
  27. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/publish.yml +0 -0
  28. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/publish_testpypi.yml +0 -0
  29. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/release.yml +0 -0
  30. {nullpol-0.2.2 → nullpol-0.2.4}/.github/workflows/semantic-pr-check.yml +0 -0
  31. {nullpol-0.2.2 → nullpol-0.2.4}/.gitignore +0 -0
  32. {nullpol-0.2.2 → nullpol-0.2.4}/.markdownlint.yaml +0 -0
  33. {nullpol-0.2.2 → nullpol-0.2.4}/.prettierignore +0 -0
  34. {nullpol-0.2.2 → nullpol-0.2.4}/.prettierrc.yaml +0 -0
  35. {nullpol-0.2.2 → nullpol-0.2.4}/.typos.toml +0 -0
  36. {nullpol-0.2.2 → nullpol-0.2.4}/CITATION.cff +0 -0
  37. {nullpol-0.2.2 → nullpol-0.2.4}/CODE_OF_CONDUCT.md +0 -0
  38. {nullpol-0.2.2 → nullpol-0.2.4}/CONTRIBUTING.md +0 -0
  39. {nullpol-0.2.2 → nullpol-0.2.4}/LICENSE +0 -0
  40. {nullpol-0.2.2 → nullpol-0.2.4}/README.md +0 -0
  41. {nullpol-0.2.2 → nullpol-0.2.4}/SECURITY.md +0 -0
  42. {nullpol-0.2.2 → nullpol-0.2.4}/cliff.toml +0 -0
  43. {nullpol-0.2.2 → nullpol-0.2.4}/docs/.gitignore +0 -0
  44. {nullpol-0.2.2 → nullpol-0.2.4}/docs/asimov.md +0 -0
  45. {nullpol-0.2.2 → nullpol-0.2.4}/docs/citing.md +0 -0
  46. {nullpol-0.2.2 → nullpol-0.2.4}/docs/contributing.md +0 -0
  47. {nullpol-0.2.2 → nullpol-0.2.4}/docs/examples/scalar-tensor.md +0 -0
  48. {nullpol-0.2.2 → nullpol-0.2.4}/docs/index.md +0 -0
  49. {nullpol-0.2.2 → nullpol-0.2.4}/docs/injections.md +0 -0
  50. {nullpol-0.2.2 → nullpol-0.2.4}/docs/installation.md +0 -0
  51. {nullpol-0.2.2 → nullpol-0.2.4}/docs/javascripts/mathjax.js +0 -0
  52. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/index.md +0 -0
  53. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/antenna_patterns/index.md +0 -0
  54. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/clustering/index.md +0 -0
  55. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/index.md +0 -0
  56. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/likelihood/index.md +0 -0
  57. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/null_stream/index.md +0 -0
  58. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/prior/index.md +0 -0
  59. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/analysis/tf_transforms/index.md +0 -0
  60. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/cli/index.md +0 -0
  61. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/index.md +0 -0
  62. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/integrations/asimov/index.md +0 -0
  63. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/integrations/htcondor/index.md +0 -0
  64. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/integrations/index.md +0 -0
  65. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/simulation/index.md +0 -0
  66. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/utils/index.md +0 -0
  67. {nullpol-0.2.2 → nullpol-0.2.4}/docs/reference/nullpol/version.md +0 -0
  68. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/generate_injection_file.sh +0 -0
  69. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/injections.json +0 -0
  70. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/data/scalar_tensor_injection_data0_0_generation_data_dump.pickle +0 -0
  71. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1.err +0 -0
  72. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1.out +0 -0
  73. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1_final_result.err +0 -0
  74. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_analysis/scalar_tensor_injection_data0_0_analysis_H1L1V1_final_result.out +0 -0
  75. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_generation/scalar_tensor_injection_data0_0_generation.err +0 -0
  76. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/log_data_generation/scalar_tensor_injection_data0_0_generation.out +0 -0
  77. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/result/scalar_tensor_injection_data0_0_analysis_H1L1V1_dynesty.pickle +0 -0
  78. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/result/scalar_tensor_injection_data0_0_analysis_H1L1V1_resume.pickle +0 -0
  79. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/results_page/overview.html +0 -0
  80. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/scalar_tensor_injection_config_complete.ini +0 -0
  81. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/bash_scalar_tensor_injection.sh +0 -0
  82. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit +0 -0
  83. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.condor.sub +0 -0
  84. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.dagman.out +0 -0
  85. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.lib.err +0 -0
  86. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.lib.out +0 -0
  87. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/dag_scalar_tensor_injection.submit.metrics +0 -0
  88. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/scalar_tensor_injection_data0_0_analysis_H1L1V1.submit +0 -0
  89. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/scalar_tensor_injection_data0_0_analysis_H1L1V1_final_result.submit +0 -0
  90. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/outdir_scalar_tensor_injection/submit/scalar_tensor_injection_data0_0_generation.submit +0 -0
  91. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/prior.prior +0 -0
  92. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/scalar_tensor_injection.ini +0 -0
  93. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor/scalar_tensor_injection.prior +0 -0
  94. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_fractional_projection/generate_injection_file.sh +0 -0
  95. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_fractional_projection/scalar_tensor_injection.ini +0 -0
  96. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_fractional_projection/scalar_tensor_injection.prior +0 -0
  97. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_gaussian/generate_injection_file.sh +0 -0
  98. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_gaussian/scalar_tensor_injection.ini +0 -0
  99. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_gaussian/scalar_tensor_injection.prior +0 -0
  100. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_h_marginalized/generate_injection_file.sh +0 -0
  101. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_h_marginalized/scalar_tensor_injection.ini +0 -0
  102. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_h_marginalized/scalar_tensor_injection.prior +0 -0
  103. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_weighted_gaussian/generate_injection_file.sh +0 -0
  104. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_weighted_gaussian/scalar_tensor_injection.ini +0 -0
  105. {nullpol-0.2.2 → nullpol-0.2.4}/examples/injection/scalar_tensor_weighted_gaussian/scalar_tensor_injection.prior +0 -0
  106. {nullpol-0.2.2 → nullpol-0.2.4}/pyproject.toml +0 -0
  107. {nullpol-0.2.2 → nullpol-0.2.4}/renovate.json +0 -0
  108. {nullpol-0.2.2 → nullpol-0.2.4}/scripts/README.md +0 -0
  109. {nullpol-0.2.2 → nullpol-0.2.4}/scripts/auto_generate_init.py +0 -0
  110. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/.gitignore +0 -0
  111. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/__init__.py +0 -0
  112. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/__init__.py +0 -0
  113. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/antenna_patterns/__init__.py +0 -0
  114. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/antenna_patterns/base.py +0 -0
  115. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/antenna_patterns/conditioning.py +0 -0
  116. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/antenna_patterns/encoding.py +0 -0
  117. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/antenna_patterns/processor.py +0 -0
  118. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/clustering/__init__.py +0 -0
  119. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/clustering/algorithm.py +0 -0
  120. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/clustering/application.py +0 -0
  121. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/clustering/coherent_sky_scan.py +0 -0
  122. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/clustering/plotting.py +0 -0
  123. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/clustering/threshold_filter.py +0 -0
  124. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/data_context.py +0 -0
  125. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/likelihood/__init__.py +0 -0
  126. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/likelihood/chi2_tf_likelihood.py +0 -0
  127. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/likelihood/time_frequency_likelihood.py +0 -0
  128. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/null_stream/__init__.py +0 -0
  129. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/null_stream/calculator.py +0 -0
  130. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/null_stream/projections.py +0 -0
  131. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/prior/__init__.py +0 -0
  132. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/prior/default.py +0 -0
  133. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/prior/prior_files/polarization.prior +0 -0
  134. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/result.py +0 -0
  135. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/__init__.py +0 -0
  136. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/inverse_wavelet_freq.py +0 -0
  137. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/inverse_wavelet_time.py +0 -0
  138. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/stft.py +0 -0
  139. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/utils.py +0 -0
  140. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/wavelet_freq.py +0 -0
  141. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/wavelet_time.py +0 -0
  142. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/analysis/tf_transforms/wavelet_transforms.py +0 -0
  143. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/__init__.py +0 -0
  144. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/create_injection.py +0 -0
  145. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/create_time_frequency_filter_from_sample.py +0 -0
  146. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/data_generation.py +0 -0
  147. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/get_asimov_yaml.py +0 -0
  148. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/main.py +0 -0
  149. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/parser.py +0 -0
  150. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/templates/__init__.py +0 -0
  151. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/templates/config.ini +0 -0
  152. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/templates/default_config_create_injection.ini +0 -0
  153. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/templates/default_config_create_time_frequency_filter_from_sample.ini +0 -0
  154. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/cli/templates/example_signal_parameters_create_injection.json +0 -0
  155. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/__init__.py +0 -0
  156. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/__init__.py +0 -0
  157. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/asimov.py +0 -0
  158. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/pesummary.py +0 -0
  159. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/templates/__init__.py +0 -0
  160. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/templates/analysis_defaults.yaml +0 -0
  161. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/templates/nullpol.ini +0 -0
  162. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/asimov/templates/nullpol_analysis.yaml +0 -0
  163. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/htcondor/__init__.py +0 -0
  164. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/htcondor/analysis_node.py +0 -0
  165. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/htcondor/generation_node.py +0 -0
  166. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/integrations/htcondor/nullpol_pipe_dag_creator.py +0 -0
  167. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/simulation/__init__.py +0 -0
  168. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/simulation/injection.py +0 -0
  169. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/simulation/source.py +0 -0
  170. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/utils/__init__.py +0 -0
  171. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/utils/error.py +0 -0
  172. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/utils/filesystem.py +0 -0
  173. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/utils/imports.py +0 -0
  174. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/utils/json_utils.py +0 -0
  175. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/utils/log.py +0 -0
  176. {nullpol-0.2.2 → nullpol-0.2.4}/src/nullpol/version.py +0 -0
  177. {nullpol-0.2.2 → nullpol-0.2.4}/tests/.gitignore +0 -0
  178. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/__init__.py +0 -0
  179. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/antenna_patterns/__init__.py +0 -0
  180. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/antenna_patterns/test_base.py +0 -0
  181. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/antenna_patterns/test_conditioning.py +0 -0
  182. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/antenna_patterns/test_encoding.py +0 -0
  183. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/antenna_patterns/test_processor.py +0 -0
  184. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/clustering/__init__.py +0 -0
  185. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/clustering/test_algorithm.py +0 -0
  186. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/clustering/test_application.py +0 -0
  187. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/clustering/test_plotting.py +0 -0
  188. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/clustering/test_threshold_filter.py +0 -0
  189. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/fixtures/injections.json +0 -0
  190. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/likelihood/test_chi2_tf_likelihood.py +0 -0
  191. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/likelihood/test_time_frequency_likelihood.py +0 -0
  192. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/null_stream/__init__.py +0 -0
  193. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/null_stream/test_calculator.py +0 -0
  194. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/null_stream/test_projections.py +0 -0
  195. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/prior/__init__.py +0 -0
  196. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/prior/test_default.py +0 -0
  197. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/test_result.py +0 -0
  198. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/__init__.py +0 -0
  199. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_inverse_wavelet_freq.py +0 -0
  200. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_inverse_wavelet_time.py +0 -0
  201. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_stft.py +0 -0
  202. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_utils.py +0 -0
  203. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_wavelet_freq.py +0 -0
  204. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_wavelet_time.py +0 -0
  205. {nullpol-0.2.2 → nullpol-0.2.4}/tests/analysis/tf_transforms/test_wavelet_transforms.py +0 -0
  206. {nullpol-0.2.2 → nullpol-0.2.4}/tests/cli/__init__.py +0 -0
  207. {nullpol-0.2.2 → nullpol-0.2.4}/tests/cli/fixtures/mock_psd.txt +0 -0
  208. {nullpol-0.2.2 → nullpol-0.2.4}/tests/cli/test_create_injection.py +0 -0
  209. {nullpol-0.2.2 → nullpol-0.2.4}/tests/cli/test_create_time_frequency_filter.py +0 -0
  210. {nullpol-0.2.2 → nullpol-0.2.4}/tests/integrations/__init__.py +0 -0
  211. {nullpol-0.2.2 → nullpol-0.2.4}/tests/simulation/__init__.py +0 -0
  212. {nullpol-0.2.2 → nullpol-0.2.4}/tests/simulation/test_injection.py +0 -0
  213. {nullpol-0.2.2 → nullpol-0.2.4}/tests/simulation/test_source.py +0 -0
  214. {nullpol-0.2.2 → nullpol-0.2.4}/tests/test_import.py +0 -0
  215. {nullpol-0.2.2 → nullpol-0.2.4}/tests/test_version.py +0 -0
  216. {nullpol-0.2.2 → nullpol-0.2.4}/tests/utils/__init__.py +0 -0
  217. {nullpol-0.2.2 → nullpol-0.2.4}/tests/utils/test_error.py +0 -0
  218. {nullpol-0.2.2 → nullpol-0.2.4}/tests/utils/test_filesystem.py +0 -0
  219. {nullpol-0.2.2 → nullpol-0.2.4}/tests/utils/test_imports.py +0 -0
  220. {nullpol-0.2.2 → nullpol-0.2.4}/tests/utils/test_json_utils.py +0 -0
  221. {nullpol-0.2.2 → nullpol-0.2.4}/tests/utils/test_log.py +0 -0
  222. {nullpol-0.2.2 → nullpol-0.2.4}/zensical.toml +0 -0
@@ -0,0 +1,66 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+ workflow_call:
9
+
10
+ jobs:
11
+ test:
12
+ strategy:
13
+ matrix:
14
+ python-version: ['3.12', '3.13', '3.14']
15
+ os: [ubuntu-latest, macos-latest]
16
+ runs-on: ${{ matrix.os }}
17
+ steps:
18
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
19
+ with:
20
+ persist-credentials: false
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+ enable-cache: 'true'
27
+
28
+ - name: Install dependencies
29
+ run: uv sync --group dev --frozen
30
+
31
+ - name: Run tests with pytest
32
+ run: uv run --frozen pytest
33
+
34
+ - name: Upload coverage to Codecov
35
+ if: ${{ !cancelled() }}
36
+ uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5
37
+ with:
38
+ token: ${{ secrets.CODECOV_TOKEN }}
39
+
40
+ integration:
41
+ # Runs the @pytest.mark.integration suite once (not in the matrix) to
42
+ # keep the fast unit-test matrix cheap while still gating on the
43
+ # heavier integration tests. Installs the optional `asimov` extra so
44
+ # real asimov/cbcflow paths can be exercised now and in the future.
45
+ needs: test
46
+ runs-on: ubuntu-latest
47
+ steps:
48
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
49
+ with:
50
+ persist-credentials: false
51
+
52
+ - name: Install uv
53
+ uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
54
+ with:
55
+ python-version: '3.13'
56
+ enable-cache: 'true'
57
+
58
+ - name: Install dependencies (with asimov extra)
59
+ run: uv sync --group dev --extra asimov --frozen
60
+
61
+ - name: Run integration tests
62
+ # Clear the default addopts (which selects `-m 'not integration'`)
63
+ # and explicitly select the integration marker instead.
64
+ run:
65
+ uv run --frozen pytest -o addopts="" -m integration --cov src
66
+ --cov-report=term-missing
@@ -18,12 +18,12 @@ jobs:
18
18
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
19
19
 
20
20
  - name: Initialize CodeQL
21
- uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
21
+ uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
22
22
  with:
23
23
  languages: python
24
24
 
25
25
  - name: Autobuild
26
- uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
26
+ uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
27
27
 
28
28
  - name: Perform CodeQL Analysis
29
- uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
29
+ uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
@@ -20,7 +20,7 @@ jobs:
20
20
 
21
21
  - name: Check for new commits
22
22
  id: check_commits
23
- uses: isaac-cf-wong/detect-git-changes-action@484da6639676d11009fc70cf1a09b73d31737fe9 # v0.0.13
23
+ uses: isaac-cf-wong/detect-git-changes-action@c49bec37fa3496222c70649af3a09e2c2612310e # v0.0.15
24
24
  with:
25
25
  use-latest-tag: true
26
26
 
@@ -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.15.20
20
+ rev: v0.15.21
21
21
  hooks:
22
22
  - id: ruff
23
23
  args: [--fix, --config=pyproject.toml]
@@ -29,18 +29,18 @@ repos:
29
29
  - id: taplo-format
30
30
 
31
31
  - repo: https://github.com/crate-ci/typos
32
- rev: v1.47.2
32
+ rev: v1.48.0
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.1
38
+ rev: v3.9.5
39
39
  hooks:
40
40
  - id: prettier
41
41
 
42
42
  - repo: https://github.com/DavidAnson/markdownlint-cli2
43
- rev: v0.22.1
43
+ rev: v0.23.0
44
44
  hooks:
45
45
  - id: markdownlint-cli2
46
46
  args:
@@ -48,7 +48,7 @@ repos:
48
48
  - .markdownlint.yaml
49
49
 
50
50
  - repo: https://github.com/astral-sh/uv-pre-commit
51
- rev: 0.11.25
51
+ rev: 0.11.28
52
52
  hooks:
53
53
  # Update the uv lockfile
54
54
  - id: uv-lock
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nullpol
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A package to perform model-independent polarization test of gravitational-wave signals.
5
5
  Project-URL: Documentation, https://isaac-cf-wong.github.io/nullpol/
6
6
  Project-URL: Source, https://github.com/isaac-cf-wong/nullpol
@@ -147,7 +147,7 @@ class DataAnalysisInput(BInput, Input):
147
147
  raise NullpolError(f"Unsupported polarization-basis input: {modes}")
148
148
 
149
149
  def _validate_polarization_model_setting(self):
150
- supported_modes = ["b", "c", "b", "l", "x", "y"]
150
+ supported_modes = ["b", "c", "p", "l", "x", "y"]
151
151
  # Check whether the modes are supported.
152
152
  for mode in self.polarization_modes:
153
153
  if mode not in supported_modes:
@@ -113,22 +113,32 @@ class Input(BilbyInput):
113
113
  "time_frequency_filter": self.meta_data["time_frequency_filter"], # pylint: disable=no-member
114
114
  "priors": self.search_priors,
115
115
  }
116
+ extra_likelihood_kwargs = self.extra_likelihood_kwargs
116
117
  if self.likelihood_type == "Chi2TimeFrequencyLikelihood":
117
118
  Likelihood = Chi2TimeFrequencyLikelihood
118
- likelihood_kwargs.update(self.extra_likelihood_kwargs)
119
+ likelihood_kwargs.update(extra_likelihood_kwargs)
119
120
  elif "." in self.likelihood_type:
120
121
  split_path = self.likelihood_type.split(".")
121
122
  module = ".".join(split_path[:-1])
122
123
  likelihood_class = split_path[-1]
123
124
  Likelihood = getattr(import_module(module), likelihood_class)
124
- likelihood_kwargs.update(self.extra_likelihood_kwargs)
125
+ likelihood_kwargs.update(extra_likelihood_kwargs)
125
126
  else:
126
127
  raise ValueError(f"Unknown Likelihood class {self.likelihood_type}")
127
128
 
129
+ likelihood_signature = inspect.signature(Likelihood.__init__)
130
+ accepted_keyword_names = {
131
+ name
132
+ for name, parameter in likelihood_signature.parameters.items()
133
+ if parameter.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
134
+ }
135
+ accepts_extra_kwargs = any(
136
+ parameter.kind == inspect.Parameter.VAR_KEYWORD for parameter in likelihood_signature.parameters.values()
137
+ )
128
138
  likelihood_kwargs = {
129
139
  key: value
130
140
  for key, value in likelihood_kwargs.items()
131
- if key in inspect.getfullargspec(Likelihood.__init__).args
141
+ if key in accepted_keyword_names or (accepts_extra_kwargs and key in extra_likelihood_kwargs)
132
142
  }
133
143
 
134
144
  logger.debug(f"Initialise likelihood {Likelihood} with kwargs: \n{likelihood_kwargs}")
@@ -166,12 +166,12 @@ class Collector:
166
166
  analysis_output["Notes"] = []
167
167
 
168
168
  if analysis.comment is not None and (
169
- corresponding_analysis is None or analysis.comment not in corresponding_analysis["Notes"]
169
+ corresponding_analysis is None or analysis.comment not in corresponding_analysis.get("Notes", [])
170
170
  ):
171
171
  # We only want to add the comment to the notes if it doesn't already exist
172
172
  analysis_output["Notes"].append(analysis.comment)
173
173
 
174
- if analysis.review.status:
174
+ if analysis.review is not None and analysis.review.status:
175
175
  if analysis.review.status.lower() == "approved":
176
176
  analysis_output["ReviewStatus"] = "pass"
177
177
  elif analysis.review.status.lower() == "rejected":
@@ -181,7 +181,8 @@ class Collector:
181
181
  messages = sorted(analysis.review.messages, key=lambda k: k.timestamp)
182
182
  if len(messages) > 0 and (
183
183
  corresponding_analysis is None
184
- or f"{messages[0].timestamp:%Y-%m-%d}: {messages[0].message}" in corresponding_analysis["Notes"]
184
+ or f"{messages[0].timestamp:%Y-%m-%d}: {messages[0].message}"
185
+ not in corresponding_analysis.get("Notes", [])
185
186
  ):
186
187
  analysis_output["Notes"].append(f"{messages[0].timestamp:%Y-%m-%d}: {messages[0].message}")
187
188
 
@@ -285,6 +286,69 @@ class Collector:
285
286
  return {}
286
287
 
287
288
 
289
+ def validate_gr_pe_result(result: dict) -> bool:
290
+ """Validate whether a PE result is suitable as the basis for a TGR analysis.
291
+
292
+ The result must be complete, non-deprecated, and not an online or
293
+ exploratory run.
294
+
295
+ Parameters
296
+ ----------
297
+ result : dict
298
+ Metadata for the result to be validated.
299
+
300
+ Returns:
301
+ -------
302
+ bool
303
+ Validity of the result.
304
+ """
305
+ uid = result.get("UID", "").lower()
306
+ if not uid or uid == "online" or uid.startswith("exp") or uid.startswith("detchar"):
307
+ return False
308
+ if result.get("RunStatus") != "complete":
309
+ return False
310
+ if result.get("ReviewStatus") != "pass":
311
+ return False
312
+ if result.get("Deprecated", False):
313
+ return False
314
+ # Ensure required files are present for actual PE results
315
+ if "ResultFile" not in result or "ConfigFile" not in result:
316
+ return False
317
+ return "PESummaryResultFile" in result
318
+
319
+
320
+ def identify_basis_production(metadata) -> dict:
321
+ """Identify the PE result that will be used as the basis for TGR analyses.
322
+
323
+ The IllustrativeResult is tried first. If it passes full validation
324
+ (complete, non-deprecated, not online/exp*/detchar*, and has all required
325
+ files) it is returned. If it is not available or does not pass, the function
326
+ falls back to the first result that passes the full validation. Returns an
327
+ empty dict if no valid result is found.
328
+
329
+ Parameters
330
+ ----------
331
+ metadata :
332
+ CBCFlow superevent metadata.
333
+
334
+ Returns:
335
+ -------
336
+ dict
337
+ Result metadata for the basis production, or empty dict if none found.
338
+ """
339
+ illustrative_result_name = metadata.data["ParameterEstimation"].get("IllustrativeResult", None)
340
+ if illustrative_result_name is not None:
341
+ for result in metadata.data["ParameterEstimation"].get("Results", []):
342
+ if result["UID"] == illustrative_result_name and validate_gr_pe_result(result):
343
+ return result
344
+
345
+ for result in metadata.data["ParameterEstimation"]["Results"]:
346
+ if validate_gr_pe_result(result):
347
+ return result
348
+
349
+ return {}
350
+
351
+
288
352
  # Functionality for information which flows from cbcflow into Asimov
289
353
  # pylint: disable=too-few-public-methods
290
354
  class Applicator:
@@ -369,6 +433,8 @@ class Applicator:
369
433
  ligo["preferred event"] = event["UID"]
370
434
  ligo["false alarm rate"] = event["FAR"]
371
435
  event_time = event["GPSTime"]
436
+ if event_time is None:
437
+ raise ValueError(f"No preferred GraceDB event found for {sid}. Cannot determine event time.")
372
438
  ligo["sname"] = sid
373
439
 
374
440
  # also add the sampling rate to the output
@@ -387,22 +453,22 @@ class Applicator:
387
453
  "event time": event_time,
388
454
  }
389
455
 
390
- metadata_pe_results = metadata.data["ParameterEstimation"]["Results"]
391
- illustrative_prod = metadata.data["ParameterEstimation"]["IllustrativeResult"]
392
- gr_pe = {"available": False}
393
- for result in metadata_pe_results:
394
- if result["UID"] == illustrative_prod and result["RunStatus"] == "complete":
395
- gr_pe = {
396
- "available": True,
397
- "UID GR PE": result["UID"],
398
- "result file path": "/" + result["ResultFile"]["Path"].split(":/")[-1],
399
- "config file path": "/" + result["ConfigFile"]["Path"].split(":/")[-1],
400
- "pesummary result path": "/" + result["PESummaryResultFile"]["Path"].split(":/")[-1],
401
- }
402
- if "WaveformApproximant" in result:
403
- quality["waveform approximant"] = result["WaveformApproximant"]
404
- if gr_pe["available"] is False:
405
- raise AttributeError("IllustrativeResult not in the library or the PE run is incomplete.")
456
+ basis_result = identify_basis_production(metadata)
457
+ if not basis_result:
458
+ raise ValueError(
459
+ "No valid GR PE result found in the library. The IllustrativeResult "
460
+ "(if set) and all other results either have incomplete run status, "
461
+ "are deprecated, or are excluded by UID."
462
+ )
463
+ gr_pe = {
464
+ "available": True,
465
+ "UID GR PE": basis_result["UID"],
466
+ "result file path": "/" + basis_result["ResultFile"]["Path"].split(":/")[-1],
467
+ "config file path": "/" + basis_result["ConfigFile"]["Path"].split(":/")[-1],
468
+ "pesummary result path": "/" + basis_result["PESummaryResultFile"]["Path"].split(":/")[-1],
469
+ }
470
+ if "WaveformApproximant" in basis_result:
471
+ quality["waveform approximant"] = basis_result["WaveformApproximant"]
406
472
 
407
473
  # now, we need to read in the information from the config file
408
474
  # we need the reference frequency, the psd dict and the calibration dict
@@ -410,8 +476,10 @@ class Applicator:
410
476
  bilby_config = bilby_config_to_asimov(config_file)
411
477
 
412
478
  # read in the psd information if present
413
- if "psds" in bilby_config:
479
+ if bilby_config.get("psds") is not None:
414
480
  gr_pe["psds"] = bilby_config.pop("psds")
481
+ elif "psds" in bilby_config:
482
+ bilby_config.pop("psds")
415
483
  output["gr pe info"] = gr_pe.copy()
416
484
  output["gr pe info"]["approximant"] = bilby_config["waveform"]["approximant"]
417
485
  # config file and cbcflow can disagree about which data to use (for example, preferred frame was updated)
@@ -37,8 +37,11 @@ def read_bilby_ini_file(file: str) -> dict:
37
37
  def _convert_string_to_dict(string):
38
38
  try:
39
39
  return bilby_pipe.utils.convert_string_to_dict(string)
40
- except (bilby_pipe.utils.BilbyPipeError, AttributeError):
41
- return string
40
+ except (bilby_pipe.utils.BilbyPipeError, AttributeError, TypeError):
41
+ try:
42
+ return literal_eval(string)
43
+ except (ValueError, SyntaxError):
44
+ return string
42
45
 
43
46
 
44
47
  def fill_in_pol_specific_metadata(analysis, corresponding_analysis): # pylint: disable=unused-argument
@@ -479,7 +479,7 @@ class TestTimeFrequencyDataContext:
479
479
  # Create filter with wrong shape
480
480
  wrong_filter = np.random.rand(10, 20) # Wrong dimensions
481
481
 
482
- with pytest.raises(AssertionError):
482
+ with pytest.raises(ValueError, match="does not match the time frequency filter"):
483
483
  TimeFrequencyDataContext(
484
484
  interferometers=ifos,
485
485
  wavelet_frequency_resolution=2.0,
@@ -0,0 +1,79 @@
1
+ """Unit tests for :meth:`DataAnalysisInput._validate_polarization_model_setting`.
2
+
3
+ This guards the fix in commit ``74b4fc3`` where the supported-modes list
4
+ contained a duplicate ``"b"`` and was missing ``"p"`` (plus), causing a valid
5
+ ``"p"`` mode to be rejected in both ``polarization-modes`` and
6
+ ``polarization-basis`` validation.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pytest
12
+
13
+ from nullpol.cli.data_analysis import DataAnalysisInput
14
+ from nullpol.utils import NullpolError
15
+
16
+ SUPPORTED = ["b", "c", "p", "l", "x", "y"]
17
+
18
+
19
+ def _make_input(modes, basis):
20
+ """Build a DataAnalysisInput bypassing the heavy __init__.
21
+
22
+ The validation method only reads the ``polarization_modes`` and
23
+ ``polarization_basis`` properties, which are backed by the private
24
+ ``_polarization_modes`` / ``_polarization_basis`` attributes.
25
+ """
26
+ obj = object.__new__(DataAnalysisInput)
27
+ obj._polarization_modes = list(modes)
28
+ obj._polarization_basis = list(basis)
29
+ return obj
30
+
31
+
32
+ class TestValidatePolarizationModelSetting:
33
+ """Tests for :meth:`_validate_polarization_model_setting`."""
34
+
35
+ @pytest.mark.parametrize("mode", SUPPORTED)
36
+ def test_each_supported_mode_accepted_alone(self, mode):
37
+ """Every one of the six canonical modes is accepted in polarization-modes."""
38
+ _make_input([mode], [mode])._validate_polarization_model_setting()
39
+
40
+ def test_all_six_modes_accepted_together(self):
41
+ """All six modes (b, c, p, l, x, y) pass when used together."""
42
+ _make_input(SUPPORTED, SUPPORTED)._validate_polarization_model_setting()
43
+
44
+ def test_plus_mode_accepted(self):
45
+ """Regression: ``"p"`` (plus) must be accepted -- it was rejected before the fix."""
46
+ _make_input(["p", "c"], ["p"])._validate_polarization_model_setting()
47
+
48
+ def test_plus_mode_rejected_before_fix_list(self):
49
+ """Sanity check: the fixed list no longer contains a duplicate ``b``."""
50
+ # If the old buggy list ["b","c","b","l","x","y"] were restored, "p" would fail.
51
+ assert SUPPORTED == ["b", "c", "p", "l", "x", "y"]
52
+ assert SUPPORTED.count("b") == 1
53
+ assert "p" in SUPPORTED
54
+
55
+ @pytest.mark.parametrize("bad_mode", ["z", "a", "B", "pc", "", "plus"])
56
+ def test_invalid_mode_in_modes_rejected(self, bad_mode):
57
+ """An unsupported mode in ``polarization-modes`` raises NullpolError."""
58
+ with pytest.raises(NullpolError, match="polarization-modes"):
59
+ _make_input([bad_mode], [bad_mode])._validate_polarization_model_setting()
60
+
61
+ @pytest.mark.parametrize("bad_mode", ["z", "a", "P"])
62
+ def test_invalid_mode_in_basis_rejected(self, bad_mode):
63
+ """An unsupported mode in ``polarization-basis`` raises NullpolError."""
64
+ with pytest.raises(NullpolError, match="polarization-basis"):
65
+ _make_input(["p", "c"], [bad_mode])._validate_polarization_model_setting()
66
+
67
+ def test_basis_not_subset_of_modes_rejected(self):
68
+ """A basis mode that is supported but not in ``polarization-modes`` is rejected."""
69
+ # "b" is a supported mode, but it is not in the modes list here.
70
+ with pytest.raises(NullpolError, match="Basis mode"):
71
+ _make_input(["p", "c"], ["b"])._validate_polarization_model_setting()
72
+
73
+ def test_empty_basis_accepted(self):
74
+ """An empty basis is valid (all modes are derived)."""
75
+ _make_input(["p", "c"], [])._validate_polarization_model_setting()
76
+
77
+ def test_basis_subset_of_modes_accepted(self):
78
+ """A basis that is a strict subset of the modes is valid."""
79
+ _make_input(["p", "c", "b", "l"], ["p", "c"])._validate_polarization_model_setting()
@@ -0,0 +1,179 @@
1
+ """Tests for likelihood construction from CLI input."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import types
6
+
7
+ from bilby_pipe.main import parse_args
8
+
9
+ from nullpol.cli import input as input_module
10
+ from nullpol.cli.data_analysis import DataAnalysisInput
11
+ from nullpol.cli.parser import create_nullpol_parser
12
+
13
+
14
+ class _KeywordOnlyLikelihood:
15
+ """Capture the arguments supplied by :attr:`Input.likelihood`."""
16
+
17
+ def __init__(
18
+ self,
19
+ interferometers,
20
+ wavelet_frequency_resolution,
21
+ wavelet_nx,
22
+ polarization_modes,
23
+ *args,
24
+ polarization_basis=None,
25
+ time_frequency_filter=None,
26
+ **kwargs,
27
+ ):
28
+ self.interferometers = interferometers
29
+ self.wavelet_frequency_resolution = wavelet_frequency_resolution
30
+ self.wavelet_nx = wavelet_nx
31
+ self.polarization_modes = polarization_modes
32
+ self.polarization_basis = polarization_basis
33
+ self.time_frequency_filter = time_frequency_filter
34
+ self.extra_kwargs = kwargs
35
+
36
+
37
+ class _StrictLikelihood:
38
+ """Likelihood without ``**kwargs``; unknown extras must be dropped, not forwarded."""
39
+
40
+ def __init__(
41
+ self,
42
+ interferometers,
43
+ wavelet_frequency_resolution,
44
+ wavelet_nx,
45
+ polarization_modes,
46
+ polarization_basis=None,
47
+ time_frequency_filter=None,
48
+ priors=None,
49
+ ):
50
+ self.interferometers = interferometers
51
+ self.wavelet_frequency_resolution = wavelet_frequency_resolution
52
+ self.wavelet_nx = wavelet_nx
53
+ self.polarization_modes = polarization_modes
54
+ self.polarization_basis = polarization_basis
55
+ self.time_frequency_filter = time_frequency_filter
56
+ self.priors = priors
57
+
58
+
59
+ def _build_inputs(args, *, interferometers, time_frequency_filter, priors, likelihood_type=None):
60
+ """Assemble a :class:`DataAnalysisInput` wired for :attr:`Input.likelihood`."""
61
+ inputs = object.__new__(DataAnalysisInput)
62
+ inputs._interferometers = interferometers
63
+ inputs.search_priors = priors
64
+ inputs.wavelet_frequency_resolution = 16.0
65
+ inputs.wavelet_nx = 4.0
66
+ inputs.polarization_modes = args.polarization_modes
67
+ inputs.polarization_basis = args.polarization_basis
68
+ inputs.meta_data = {"time_frequency_filter": time_frequency_filter}
69
+ inputs.likelihood_type = likelihood_type if likelihood_type is not None else args.likelihood_type
70
+ inputs.extra_likelihood_kwargs = args.extra_likelihood_kwargs
71
+ return inputs
72
+
73
+
74
+ def _parse_config(tmp_path, body):
75
+ config_file = tmp_path / "analysis.ini"
76
+ config_file.write_text(body, encoding="utf-8")
77
+ args, unknown_args = parse_args([str(config_file)], create_nullpol_parser(top_level=False))
78
+ assert unknown_args == []
79
+ return args
80
+
81
+
82
+ def test_analysis_config_forwards_keyword_only_parameters(monkeypatch, tmp_path):
83
+ """A configured analysis forwards its normalized basis to the likelihood."""
84
+ args = _parse_config(
85
+ tmp_path,
86
+ "polarization-modes = pc\n"
87
+ "polarization-basis = p\n"
88
+ "extra-likelihood-kwargs = {'regularization_constant': 0.99}\n",
89
+ )
90
+
91
+ assert args.polarization_modes == ["pc"]
92
+ assert args.polarization_basis == ["p"]
93
+
94
+ interferometers = object()
95
+ time_frequency_filter = object()
96
+ priors = object()
97
+ inputs = _build_inputs(
98
+ args,
99
+ interferometers=interferometers,
100
+ time_frequency_filter=time_frequency_filter,
101
+ priors=priors,
102
+ )
103
+
104
+ assert inputs.polarization_modes == "pc"
105
+ assert inputs.polarization_basis == "p"
106
+
107
+ monkeypatch.setattr(input_module, "Chi2TimeFrequencyLikelihood", _KeywordOnlyLikelihood)
108
+
109
+ likelihood = input_module.Input.likelihood.fget(inputs)
110
+
111
+ assert likelihood.interferometers is interferometers
112
+ assert likelihood.wavelet_frequency_resolution == 16.0
113
+ assert likelihood.wavelet_nx == 4.0
114
+ assert likelihood.polarization_modes == "pc"
115
+ assert likelihood.polarization_basis == "p"
116
+ assert likelihood.time_frequency_filter is time_frequency_filter
117
+ assert likelihood.extra_kwargs == {"regularization_constant": 0.99}
118
+
119
+
120
+ def test_extra_kwargs_dropped_when_likelihood_rejects_variadic(monkeypatch, tmp_path):
121
+ """A likelihood without ``**kwargs`` must not receive unknown extras (no TypeError)."""
122
+ args = _parse_config(
123
+ tmp_path,
124
+ "polarization-modes = pc\n"
125
+ "polarization-basis = p\n"
126
+ "extra-likelihood-kwargs = {'regularization_constant': 0.99}\n",
127
+ )
128
+
129
+ interferometers = object()
130
+ time_frequency_filter = object()
131
+ priors = object()
132
+ inputs = _build_inputs(
133
+ args,
134
+ interferometers=interferometers,
135
+ time_frequency_filter=time_frequency_filter,
136
+ priors=priors,
137
+ )
138
+
139
+ monkeypatch.setattr(input_module, "Chi2TimeFrequencyLikelihood", _StrictLikelihood)
140
+
141
+ # Would raise ``TypeError: unexpected keyword argument 'regularization_constant'``
142
+ # if the unknown extra were forwarded to a signature without ``**kwargs``.
143
+ likelihood = input_module.Input.likelihood.fget(inputs)
144
+
145
+ assert likelihood.interferometers is interferometers
146
+ assert likelihood.polarization_basis == "p"
147
+ assert likelihood.time_frequency_filter is time_frequency_filter
148
+ assert likelihood.priors is priors
149
+
150
+
151
+ def test_dotted_path_forwards_keyword_only_parameters(monkeypatch, tmp_path):
152
+ """The ``module.Class`` likelihood_type branch also forwards keyword-only params."""
153
+ args = _parse_config(
154
+ tmp_path,
155
+ "polarization-modes = pc\n"
156
+ "polarization-basis = p\n"
157
+ "extra-likelihood-kwargs = {'regularization_constant': 0.99}\n",
158
+ )
159
+
160
+ interferometers = object()
161
+ time_frequency_filter = object()
162
+ priors = object()
163
+ inputs = _build_inputs(
164
+ args,
165
+ interferometers=interferometers,
166
+ time_frequency_filter=time_frequency_filter,
167
+ priors=priors,
168
+ likelihood_type="my_pkg.CustomLikelihood",
169
+ )
170
+
171
+ fake_module = types.ModuleType("my_pkg")
172
+ fake_module.CustomLikelihood = _KeywordOnlyLikelihood
173
+ monkeypatch.setattr(input_module, "import_module", lambda name: fake_module)
174
+
175
+ likelihood = input_module.Input.likelihood.fget(inputs)
176
+
177
+ assert likelihood.polarization_basis == "p"
178
+ assert likelihood.time_frequency_filter is time_frequency_filter
179
+ assert likelihood.extra_kwargs == {"regularization_constant": 0.99}
@@ -0,0 +1 @@
1
+ """Asimov integration tests."""