paramrf 0.37.2__tar.gz → 0.37.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 (267) hide show
  1. {paramrf-0.37.2 → paramrf-0.37.4}/CONTEXT.md +6 -0
  2. {paramrf-0.37.2/paramrf.egg-info → paramrf-0.37.4}/PKG-INFO +1 -1
  3. {paramrf-0.37.2 → paramrf-0.37.4/paramrf.egg-info}/PKG-INFO +1 -1
  4. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/empirical.py +20 -4
  5. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/objectives/evaluators.py +40 -18
  6. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/__init__.py +6 -0
  7. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/covariance_kernels.py +68 -0
  8. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/linearization.py +56 -17
  9. {paramrf-0.37.2 → paramrf-0.37.4}/pyproject.toml +1 -1
  10. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_covariance_kernels.py +132 -0
  11. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_linearization.py +109 -2
  12. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_lines.py +34 -2
  13. {paramrf-0.37.2 → paramrf-0.37.4}/.github/workflows/docs.yml +0 -0
  14. {paramrf-0.37.2 → paramrf-0.37.4}/.github/workflows/draft-pdf.yml +0 -0
  15. {paramrf-0.37.2 → paramrf-0.37.4}/.github/workflows/publish.yml +0 -0
  16. {paramrf-0.37.2 → paramrf-0.37.4}/.github/workflows/tests.yml +0 -0
  17. {paramrf-0.37.2 → paramrf-0.37.4}/.gitignore +0 -0
  18. {paramrf-0.37.2 → paramrf-0.37.4}/AGENTS.md +0 -0
  19. {paramrf-0.37.2 → paramrf-0.37.4}/CHANGELOG.md +0 -0
  20. {paramrf-0.37.2 → paramrf-0.37.4}/CITATION.cff +0 -0
  21. {paramrf-0.37.2 → paramrf-0.37.4}/CLAUDE.md +0 -0
  22. {paramrf-0.37.2 → paramrf-0.37.4}/CONTRIBUTING.md +0 -0
  23. {paramrf-0.37.2 → paramrf-0.37.4}/LICENSE +0 -0
  24. {paramrf-0.37.2 → paramrf-0.37.4}/NOTICE +0 -0
  25. {paramrf-0.37.2 → paramrf-0.37.4}/README.rst +0 -0
  26. {paramrf-0.37.2 → paramrf-0.37.4}/assets/logo.png +0 -0
  27. {paramrf-0.37.2 → paramrf-0.37.4}/docs/Makefile +0 -0
  28. {paramrf-0.37.2 → paramrf-0.37.4}/docs/_static/custom.css +0 -0
  29. {paramrf-0.37.2 → paramrf-0.37.4}/docs/_templates/autosummary/class.rst +0 -0
  30. {paramrf-0.37.2 → paramrf-0.37.4}/docs/_templates/autosummary/function.rst +0 -0
  31. {paramrf-0.37.2 → paramrf-0.37.4}/docs/_templates/autosummary/module.rst +0 -0
  32. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0001-line-modelling-architecture.md +0 -0
  33. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0002-parameter-api.md +0 -0
  34. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0003-derived-models.md +0 -0
  35. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0004-profiled-lines.md +0 -0
  36. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0005-priors-by-name.md +0 -0
  37. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0006-reference-impedance.md +0 -0
  38. {paramrf-0.37.2 → paramrf-0.37.4}/docs/adr/0007-edge-values.md +0 -0
  39. {paramrf-0.37.2 → paramrf-0.37.4}/docs/agents/domain.md +0 -0
  40. {paramrf-0.37.2 → paramrf-0.37.4}/docs/agents/issue-tracker.md +0 -0
  41. {paramrf-0.37.2 → paramrf-0.37.4}/docs/agents/triage-labels.md +0 -0
  42. {paramrf-0.37.2 → paramrf-0.37.4}/docs/api/index.rst +0 -0
  43. {paramrf-0.37.2 → paramrf-0.37.4}/docs/conf.py +0 -0
  44. {paramrf-0.37.2 → paramrf-0.37.4}/docs/core_concepts/core_primitives.rst +0 -0
  45. {paramrf-0.37.2 → paramrf-0.37.4}/docs/core_concepts/index.rst +0 -0
  46. {paramrf-0.37.2 → paramrf-0.37.4}/docs/core_concepts/jax_overview.rst +0 -0
  47. {paramrf-0.37.2 → paramrf-0.37.4}/docs/core_concepts/optimization_and_inference.rst +0 -0
  48. {paramrf-0.37.2 → paramrf-0.37.4}/docs/core_concepts/parameter_names.rst +0 -0
  49. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/cascading_and_terminating.rst +0 -0
  50. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/circuit_clc.png +0 -0
  51. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/circuit_models.rst +0 -0
  52. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/custom_models.rst +0 -0
  53. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/derivatives_and_sweeps.rst +0 -0
  54. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/discrepancy_prediction.rst +0 -0
  55. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/index.rst +0 -0
  56. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/model_optimization.rst +0 -0
  57. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
  58. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
  59. {paramrf-0.37.2 → paramrf-0.37.4}/docs/examples/shared_modules.rst +0 -0
  60. {paramrf-0.37.2 → paramrf-0.37.4}/docs/index.rst +0 -0
  61. {paramrf-0.37.2 → paramrf-0.37.4}/docs/license.rst +0 -0
  62. {paramrf-0.37.2 → paramrf-0.37.4}/docs/make.bat +0 -0
  63. {paramrf-0.37.2 → paramrf-0.37.4}/docs/research/conductor-loss-alternatives.md +0 -0
  64. {paramrf-0.37.2 → paramrf-0.37.4}/docs/research/holloway1994.pdf +0 -0
  65. {paramrf-0.37.2 → paramrf-0.37.4}/docs/research/microstrip-loss-conventions.md +0 -0
  66. {paramrf-0.37.2 → paramrf-0.37.4}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
  67. {paramrf-0.37.2 → paramrf-0.37.4}/docs/skrf_comparison/index.rst +0 -0
  68. {paramrf-0.37.2 → paramrf-0.37.4}/docs/skrf_comparison/overview.rst +0 -0
  69. {paramrf-0.37.2 → paramrf-0.37.4}/docs/skrf_comparison/performance.rst +0 -0
  70. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/1_cable_fitting.ipynb +0 -0
  71. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
  72. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/3_discrepancy_transfer.ipynb +0 -0
  73. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
  74. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
  75. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/index.rst +0 -0
  76. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/line_internal_discrepancy.py +0 -0
  77. {paramrf-0.37.2 → paramrf-0.37.4}/docs/tutorials/port_discrepancy.py +0 -0
  78. {paramrf-0.37.2 → paramrf-0.37.4}/paper/paper.bib +0 -0
  79. {paramrf-0.37.2 → paramrf-0.37.4}/paper/paper.md +0 -0
  80. {paramrf-0.37.2 → paramrf-0.37.4}/paper/rlc.png +0 -0
  81. {paramrf-0.37.2 → paramrf-0.37.4}/paramrf.egg-info/SOURCES.txt +0 -0
  82. {paramrf-0.37.2 → paramrf-0.37.4}/paramrf.egg-info/dependency_links.txt +0 -0
  83. {paramrf-0.37.2 → paramrf-0.37.4}/paramrf.egg-info/requires.txt +0 -0
  84. {paramrf-0.37.2 → paramrf-0.37.4}/paramrf.egg-info/top_level.txt +0 -0
  85. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/__init__.py +0 -0
  86. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/_solver_view.py +0 -0
  87. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/fitting/__init__.py +0 -0
  88. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/fitting/minimize.py +0 -0
  89. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/fitting/result.py +0 -0
  90. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/fitting/routers.py +0 -0
  91. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/fitting/sample.py +0 -0
  92. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/fitting/targets.py +0 -0
  93. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/frequency.py +0 -0
  94. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/__init__.py +0 -0
  95. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/base.py +0 -0
  96. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/result.py +0 -0
  97. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/sample.py +0 -0
  98. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/solvers/__init__.py +0 -0
  99. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/solvers/blackjax.py +0 -0
  100. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/infer/solvers/polychord.py +0 -0
  101. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/__init__.py +0 -0
  102. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/conductor.py +0 -0
  103. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/dielectric.py +0 -0
  104. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/properties.py +0 -0
  105. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/roughness.py +0 -0
  106. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/substrate.py +0 -0
  107. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/materials/surface_impedance.py +0 -0
  108. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/math/__init__.py +0 -0
  109. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/math/aggregations.py +0 -0
  110. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/math/bessel.py +0 -0
  111. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/math/conversions.py +0 -0
  112. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/math/losses.py +0 -0
  113. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/math/misc.py +0 -0
  114. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/__init__.py +0 -0
  115. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/__init__.py +0 -0
  116. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/base.py +0 -0
  117. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/bridge.py +0 -0
  118. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/callable.py +0 -0
  119. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/delegated.py +0 -0
  120. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/derived.py +0 -0
  121. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/static.py +0 -0
  122. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/adapters/wrapped.py +0 -0
  123. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/base.py +0 -0
  124. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/__init__.py +0 -0
  125. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/ideal.py +0 -0
  126. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/__init__.py +0 -0
  127. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/base.py +0 -0
  128. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/coaxial.py +0 -0
  129. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/ideal.py +0 -0
  130. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/microstrip.py +0 -0
  131. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/nodal.py +0 -0
  132. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/nonuniform.py +0 -0
  133. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/planar.py +0 -0
  134. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/profiles.py +0 -0
  135. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lines/stripline.py +0 -0
  136. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/lumped.py +0 -0
  137. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/components/sections.py +0 -0
  138. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/__init__.py +0 -0
  139. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/__init__.py +0 -0
  140. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/cascade.py +0 -0
  141. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
  142. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
  143. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/circuit/circuit.py +0 -0
  144. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
  145. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
  146. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
  147. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/interconnected/terminated.py +0 -0
  148. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/nodal.py +0 -0
  149. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/topological.py +0 -0
  150. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/composite/transformed.py +0 -0
  151. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/line_discrepancy.py +0 -0
  152. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/port_discrepancy.py +0 -0
  153. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/surrogates/__init__.py +0 -0
  154. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/surrogates/expansion.py +0 -0
  155. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/models/surrogates/rational.py +0 -0
  156. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/modules/__init__.py +0 -0
  157. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/modules/base.py +0 -0
  158. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/modules/wrapped.py +0 -0
  159. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/network_collection.py +0 -0
  160. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/objectives/__init__.py +0 -0
  161. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/objectives/losses.py +0 -0
  162. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/objectives/problems.py +0 -0
  163. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/objectives/terms.py +0 -0
  164. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/__init__.py +0 -0
  165. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/base.py +0 -0
  166. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/minimize.py +0 -0
  167. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/result.py +0 -0
  168. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/solvers/__init__.py +0 -0
  169. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/solvers/jaxopt.py +0 -0
  170. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/solvers/optimistix.py +0 -0
  171. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/optimize/solvers/scipy.py +0 -0
  172. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/parameters/__init__.py +0 -0
  173. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/parameters/constraints.py +0 -0
  174. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/rf/__init__.py +0 -0
  175. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/rf/cascade.py +0 -0
  176. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/rf/conversions.py +0 -0
  177. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/rf/mna.py +0 -0
  178. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/serialization.py +0 -0
  179. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/bijectors.py +0 -0
  180. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/discrepancy_models.py +0 -0
  181. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/distributions.py +0 -0
  182. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/likelihoods.py +0 -0
  183. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/stats/noise_models.py +0 -0
  184. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/types.py +0 -0
  185. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/__init__.py +0 -0
  186. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/array.py +0 -0
  187. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/debug.py +0 -0
  188. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/network.py +0 -0
  189. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/random.py +0 -0
  190. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/rf.py +0 -0
  191. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/transforms.py +0 -0
  192. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/tree.py +0 -0
  193. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/utils/type.py +0 -0
  194. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/viz/__init__.py +0 -0
  195. {paramrf-0.37.2 → paramrf-0.37.4}/pmrf/viz/plots.py +0 -0
  196. {paramrf-0.37.2 → paramrf-0.37.4}/scripts/benchmark_gp_marginal_likelihood.py +0 -0
  197. {paramrf-0.37.2 → paramrf-0.37.4}/scripts/install-test-deps.sh +0 -0
  198. {paramrf-0.37.2 → paramrf-0.37.4}/setup.cfg +0 -0
  199. {paramrf-0.37.2 → paramrf-0.37.4}/tests/__init__.py +0 -0
  200. {paramrf-0.37.2 → paramrf-0.37.4}/tests/_jit.py +0 -0
  201. {paramrf-0.37.2 → paramrf-0.37.4}/tests/conftest.py +0 -0
  202. {paramrf-0.37.2 → paramrf-0.37.4}/tests/data/10m_cable.s2p +0 -0
  203. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_autodiff.py +0 -0
  204. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_box_space_solving.py +0 -0
  205. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_conversions.py +0 -0
  206. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_dc_limits.py +0 -0
  207. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_discrepancy_models.py +0 -0
  208. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_evaluators.py +0 -0
  209. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_fitting_minimize.py +0 -0
  210. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_fitting_routers.py +0 -0
  211. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_fitting_sample.py +0 -0
  212. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_fitting_targets.py +0 -0
  213. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_frequency.py +0 -0
  214. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_grouped_exports.py +0 -0
  215. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_infer_base.py +0 -0
  216. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_infer_sample.py +0 -0
  217. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_line_internal_discrepancy_example.py +0 -0
  218. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_map_priors.py +0 -0
  219. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_materials/test_conductor.py +0 -0
  220. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_materials/test_dielectric.py +0 -0
  221. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_materials/test_serialization.py +0 -0
  222. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_materials/test_substrate.py +0 -0
  223. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_materials/test_surface_impedance.py +0 -0
  224. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_math/test_bessel.py +0 -0
  225. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_model.py +0 -0
  226. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_adapters.py +0 -0
  227. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_cascade.py +0 -0
  228. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_native_z0.py +0 -0
  229. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_nodal.py +0 -0
  230. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_port_order.py +0 -0
  231. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_scattering.py +0 -0
  232. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_shorted_ports.py +0 -0
  233. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_skrf_port_names.py +0 -0
  234. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_circuit_z0.py +0 -0
  235. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_current_distribution.py +0 -0
  236. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_derived.py +0 -0
  237. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_edge_values.py +0 -0
  238. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_interconnected.py +0 -0
  239. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_line_discrepancy.py +0 -0
  240. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_lines_skrf_matrix.py +0 -0
  241. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_lumped.py +0 -0
  242. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_nodal.py +0 -0
  243. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_port_discrepancy.py +0 -0
  244. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_profiles.py +0 -0
  245. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_rational.py +0 -0
  246. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_repeated_cascade.py +0 -0
  247. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_sections.py +0 -0
  248. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_transformed.py +0 -0
  249. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_models/test_transformers.py +0 -0
  250. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_module.py +0 -0
  251. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_naming.py +0 -0
  252. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_optimize_base.py +0 -0
  253. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_optimize_minimize.py +0 -0
  254. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_parameters.py +0 -0
  255. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_parameters_by_name.py +0 -0
  256. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_parameters_joint_prior.py +0 -0
  257. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_parameters_prior.py +0 -0
  258. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_parameters_validity.py +0 -0
  259. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_port_discrepancy_example.py +0 -0
  260. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_raw_space_solving.py +0 -0
  261. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_serialization.py +0 -0
  262. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_structural_updates.py +0 -0
  263. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_terms.py +0 -0
  264. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_transforms.py +0 -0
  265. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_utils/test_compress.py +0 -0
  266. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_utils/test_debug.py +0 -0
  267. {paramrf-0.37.2 → paramrf-0.37.4}/tests/test_utils/test_tree.py +0 -0
@@ -223,6 +223,12 @@ the prior precision $\Sigma_0^{-1}$ it gives the linearised posterior covariance
223
223
  $\Sigma_\text{post} = (F + \Sigma_0^{-1})^{-1}$, over parameter values in one space
224
224
  (declared by default). Fisher matrices of independent fits add.
225
225
 
226
+ The **posterior covariance factor** $W$ is a square root of this covariance:
227
+ $\Sigma_\text{post}=WW^\top$. It follows the same free-parameter order and space as
228
+ the linearisation. For a stack $A$ of whitened Jacobians and a square root of the
229
+ prior precision, $A=USV^\top$ gives $W=VS^{-1}$. Joint prediction uses $W$ to
230
+ carry parameter uncertainty without first forming the Fisher matrix.
231
+
226
232
  ### Joint prediction
227
233
 
228
234
  The Gaussian over the free parameters and the discrepancy at new frequencies
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramrf
3
- Version: 0.37.2
3
+ Version: 0.37.4
4
4
  Summary: Parametric radio frequency modeling
5
5
  Author-email: Gary Allen <gvcallen@gmail.com>
6
6
  Project-URL: homepage, https://github.com/gvcallen/paramrf
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramrf
3
- Version: 0.37.2
3
+ Version: 0.37.4
4
4
  Summary: Parametric radio frequency modeling
5
5
  Author-email: Gary Allen <gvcallen@gmail.com>
6
6
  Project-URL: homepage, https://github.com/gvcallen/paramrf
@@ -20,10 +20,14 @@ class PhysicalLine(AbstractImmittanceLine):
20
20
 
21
21
  The per-unit-length parameters are
22
22
  $$R = 2 z_n \alpha_c$$
23
- $$L = \frac{z_n \sqrt{\varepsilon_r}}{c}$$
23
+ $$L = \frac{z_n \sqrt{\varepsilon_r}}{c} + \frac{R}{\omega}$$
24
24
  $$G = \frac{2 \alpha_d}{z_n}$$
25
25
  $$C = \frac{\sqrt{\varepsilon_r}}{z_n c}$$
26
26
 
27
+ The skin-effect term adds conductor reactance to the series impedance:
28
+ $$Z = R + j\omega z_n\sqrt{\varepsilon_r}/c + jR.$$
29
+ This contribution vanishes at DC.
30
+
27
31
  Example
28
32
  --------
29
33
  .. code-block:: python
@@ -93,7 +97,11 @@ class PhysicalLine(AbstractImmittanceLine):
93
97
  G = G_val * ones
94
98
  C = C_val * ones
95
99
 
96
- return ImmittanceResult.from_rlgc(R, L, G, C, freq.w)
100
+ return ImmittanceResult(
101
+ Z=R + 1j * (freq.w * L + R),
102
+ Y=G + 1j * freq.w * C,
103
+ omega=freq.w,
104
+ )
97
105
 
98
106
 
99
107
  class DatasheetLine(AbstractImmittanceLine):
@@ -108,10 +116,14 @@ class DatasheetLine(AbstractImmittanceLine):
108
116
 
109
117
  and the per-unit-length parameters are
110
118
  $$R = 2 z_n \alpha_c$$
111
- $$L = \frac{z_n}{v_f c}$$
119
+ $$L = \frac{z_n}{v_f c} + \frac{R}{\omega}$$
112
120
  $$G = \frac{2 \alpha_d}{z_n}$$
113
121
  $$C = \frac{1}{z_n v_f c}$$
114
122
 
123
+ The skin-effect term adds conductor reactance to the series impedance:
124
+ $$Z = R + j\omega z_n/(v_f c) + jR.$$
125
+ This contribution vanishes at DC.
126
+
115
127
  Example
116
128
  --------
117
129
  .. code-block:: python
@@ -186,5 +198,9 @@ class DatasheetLine(AbstractImmittanceLine):
186
198
  L = (zn / (vf * c)) * jnp.ones_like(w)
187
199
  C = (1.0 / (zn * vf * c)) * jnp.ones_like(w)
188
200
 
189
- return ImmittanceResult.from_rlgc(R, L, G, C, w)
201
+ return ImmittanceResult(
202
+ Z=R + 1j * (w * L + R),
203
+ Y=G + 1j * w * C,
204
+ omega=w,
205
+ )
190
206
 
@@ -22,7 +22,7 @@ from pmrf.objectives.losses import HingeLoss, RMSELoss
22
22
  from pmrf.stats.likelihoods import GaussianLikelihood
23
23
  from pmrf.stats.covariance_kernels import cross_gram, gram
24
24
  from pmrf.stats.discrepancy_models import GaussianProcess, _add_noise
25
- from pmrf.stats.linearization import Linearization, _linearize, posterior_covariance
25
+ from pmrf.stats.linearization import Linearization, _linearize, posterior_covariance_factor
26
26
  from pmrf.modules.base import Module
27
27
  from pmrf.parameters import Space, values as _values, update
28
28
  from pmrf.utils import derivative, field, unwrap, unwrap_self
@@ -593,6 +593,8 @@ class MarginalLogLikelihood(AbstractEvaluator):
593
593
  frequency: Frequency,
594
594
  new_frequency: Frequency,
595
595
  covariance: jnp.ndarray | None = None,
596
+ *,
597
+ covariance_factor: jnp.ndarray | None = None,
596
598
  **kwargs,
597
599
  ) -> tuple[tuple[str, ...], dist.AbstractDistribution]:
598
600
  r"""Predict the free parameters and discrepancy together as a joint prior.
@@ -600,13 +602,14 @@ class MarginalLogLikelihood(AbstractEvaluator):
600
602
  **Mathematical Formulation**
601
603
 
602
604
  At the fitted model, let $A = K_{*h}\Sigma_D^{-1}$ and let
603
- $\Sigma_\text{post}$ be the linearised parameter covariance. Then
605
+ $W W^\top = \Sigma_\text{post}$ factor the linearised parameter
606
+ covariance. Then
604
607
 
605
608
  $$\hat\delta = A r, \qquad
606
609
  \Sigma_\delta = K_{**} - A K_{*h}^\top
607
- + A J\Sigma_\text{post}J^\top A^\top$$
610
+ + (AJW)(AJW)^\top$$
608
611
 
609
- $$\Sigma_{\delta\theta} = -A J\Sigma_\text{post}$$
612
+ $$\Sigma_{\delta\theta} = -(AJW)W^\top$$
610
613
 
611
614
  The parameter term couples event blocks, giving one dense Gaussian over
612
615
  $[\theta;\delta]$. Kernel hyperparameters are held fixed, as in
@@ -632,6 +635,12 @@ class MarginalLogLikelihood(AbstractEvaluator):
632
635
  order. If omitted, computed from this fit and the model's prior with
633
636
  :func:`pmrf.stats.linearization.posterior_covariance`. Supply a covariance
634
637
  from combined linearisations when several fits inform the parameters.
638
+ covariance_factor : jnp.ndarray or None, default=None
639
+ A factor $W$ of the parameter covariance in declared space and
640
+ linearisation name order, with $W W^\top$ equal to the covariance.
641
+ Obtain one from :func:`pmrf.stats.linearization.posterior_covariance_factor`
642
+ when several fits inform the parameters. Cannot be combined with
643
+ covariance.
635
644
  **kwargs
636
645
  Passed to the predictor.
637
646
 
@@ -653,18 +662,29 @@ class MarginalLogLikelihood(AbstractEvaluator):
653
662
  If the discrepancy is not a Gaussian process or the likelihood is not
654
663
  Gaussian.
655
664
  ValueError
656
- If orthogonal discrepancy is enabled or ``covariance`` has the wrong
657
- shape for the free parameters.
665
+ If orthogonal discrepancy is enabled, an input has the wrong shape,
666
+ or both covariance arguments are supplied.
658
667
  """
659
668
  if not isinstance(self.discrepancy, GaussianProcess):
660
669
  raise TypeError("Joint prediction requires a `GaussianProcess` discrepancy.")
661
670
  linearization = self.linearize(model, frequency, **kwargs)
662
671
  p = linearization.J.shape[-1]
663
- if covariance is None:
664
- covariance = posterior_covariance([linearization], model)
665
- covariance = jnp.asarray(covariance)
666
- if covariance.shape != (p, p):
667
- raise ValueError(f"Parameter covariance must have shape {(p, p)}.")
672
+ if covariance is not None and covariance_factor is not None:
673
+ raise ValueError("Supply either covariance or covariance_factor, not both.")
674
+ if covariance_factor is None:
675
+ if covariance is None:
676
+ covariance_factor = posterior_covariance_factor([linearization], model)
677
+ else:
678
+ covariance = jnp.asarray(covariance)
679
+ if covariance.shape != (p, p):
680
+ raise ValueError(f"Parameter covariance must have shape {(p, p)}.")
681
+ covariance_factor = jnp.linalg.cholesky(covariance)
682
+ covariance_factor = jnp.asarray(covariance_factor)
683
+ if covariance_factor.shape != (p, p):
684
+ raise ValueError(f"Parameter covariance factor must have shape {(p, p)}.")
685
+ # QR gives a triangular scale without first forming an ill-conditioned covariance.
686
+ _, R = jnp.linalg.qr(covariance_factor.T)
687
+ parameter_scale = R.T
668
688
 
669
689
  batch_shape = linearization.residual.shape[:-1]
670
690
  n = linearization.residual.shape[-1]
@@ -682,17 +702,19 @@ class MarginalLogLikelihood(AbstractEvaluator):
682
702
  conditional = conditional - jnp.swapaxes(V, -1, -2) @ V
683
703
  conditional = conditional.reshape((-1, n_new, n_new))
684
704
  blocks = conditional.shape[0]
685
- conditional = jnp.einsum('bij,bc->bicj', conditional, jnp.eye(blocks)).reshape(
686
- (blocks * n_new, blocks * n_new)
687
- )
705
+ conditional_scale = jnp.linalg.cholesky(conditional)
706
+ conditional_scale = jnp.einsum(
707
+ 'bij,bc->bicj', conditional_scale, jnp.eye(blocks)
708
+ ).reshape((blocks * n_new, blocks * n_new))
688
709
  AJ = (A @ linearization.J).reshape((-1, p))
689
- cross = -AJ @ covariance
690
- sigma_delta = conditional + AJ @ covariance @ AJ.T
691
- joint_covariance = jnp.block([[covariance, cross.T], [cross, sigma_delta]])
710
+ joint_scale = jnp.block([
711
+ [parameter_scale, jnp.zeros((p, blocks * n_new))],
712
+ [-AJ @ parameter_scale, conditional_scale],
713
+ ])
692
714
  named_values = _values(model, free_only=True, space='declared')
693
715
  theta = jnp.concatenate([jnp.ravel(named_values[name]) for name in linearization.names])
694
716
  mean = jnp.concatenate([theta, mean_delta])
695
- return linearization.names, dist.MultivariateNormalFullCovariance(mean, joint_covariance)
717
+ return linearization.names, dist.MultivariateNormalTri(mean, joint_scale)
696
718
 
697
719
  @unwrap_self
698
720
  def linearize(
@@ -28,9 +28,11 @@ if TYPE_CHECKING:
28
28
  AutoCrossKernel as AutoCrossKernel,
29
29
  ConstantKernel as ConstantKernel,
30
30
  CosineKernel as CosineKernel,
31
+ LogFrequencyKernel as LogFrequencyKernel,
31
32
  Matern32Kernel as Matern32Kernel,
32
33
  Matern52Kernel as Matern52Kernel,
33
34
  PeriodicKernel as PeriodicKernel,
35
+ PowerEnvelopeKernel as PowerEnvelopeKernel,
34
36
  ProductKernel as ProductKernel,
35
37
  RBFKernel as RBFKernel,
36
38
  SharedIndependentKernel as SharedIndependentKernel,
@@ -65,6 +67,7 @@ if TYPE_CHECKING:
65
67
  from .linearization import (
66
68
  Linearization as Linearization,
67
69
  posterior_covariance as posterior_covariance,
70
+ posterior_covariance_factor as posterior_covariance_factor,
68
71
  )
69
72
  from .noise_models import (
70
73
  AbstractNoiseModel as AbstractNoiseModel,
@@ -94,11 +97,13 @@ _EXPORTS = {
94
97
  'Joint': 'distributions',
95
98
  'Leafwise': 'bijectors',
96
99
  'Linearization': 'linearization',
100
+ 'LogFrequencyKernel': 'covariance_kernels',
97
101
  'LogNormal': 'distributions',
98
102
  'Matern32Kernel': 'covariance_kernels',
99
103
  'Matern52Kernel': 'covariance_kernels',
100
104
  'Normal': 'distributions',
101
105
  'PeriodicKernel': 'covariance_kernels',
106
+ 'PowerEnvelopeKernel': 'covariance_kernels',
102
107
  'Permute': 'bijectors',
103
108
  'ProductKernel': 'covariance_kernels',
104
109
  'R2ToComplex': 'bijectors',
@@ -122,6 +127,7 @@ _EXPORTS = {
122
127
  'cross_gram': 'covariance_kernels',
123
128
  'gram': 'covariance_kernels',
124
129
  'posterior_covariance': 'linearization',
130
+ 'posterior_covariance_factor': 'linearization',
125
131
  'truncate': 'distributions',
126
132
  }
127
133
  _MODULES = {'bijectors', 'discrepancy_models', 'linearization', 'likelihoods', 'noise_models', 'distributions', 'covariance_kernels'}
@@ -304,6 +304,74 @@ class CosineKernel(AbstractCovarianceKernel):
304
304
  return jnp.cos(2 * jnp.pi * jnp.sum(x1 - x2) / self.period)
305
305
 
306
306
 
307
+ class LogFrequencyKernel(AbstractCovarianceKernel):
308
+ r"""
309
+ Evaluate a kernel on log frequency.
310
+
311
+ $$k(x_1, x_2) = k_\text{inner}(\ln x_1, \ln x_2)$$
312
+
313
+ A stationary inner kernel depends on the ratio $x_1/x_2$. For a
314
+ :class:`CosineKernel` with period $p$, one cycle spans a factor of $e^p$
315
+ in frequency. The warp needs no frequency unit: rescaling all inputs adds
316
+ the same constant to their logarithms, leaving stationary inner kernels
317
+ unchanged. Inputs must be strictly positive.
318
+
319
+ Parameters
320
+ ----------
321
+ kernel : AbstractCovarianceKernel
322
+ The kernel evaluated on log frequency. Its batching is preserved.
323
+ """
324
+ #: The inner kernel.
325
+ kernel: AbstractCovarianceKernel
326
+
327
+ def __call__(self, x1, x2, key=None):
328
+ x1, x2 = jnp.asarray(x1), jnp.asarray(x2)
329
+ x1 = eqx.error_if(x1, jnp.any(x1 <= 0),
330
+ "LogFrequencyKernel inputs must be strictly positive")
331
+ x2 = eqx.error_if(x2, jnp.any(x2 <= 0),
332
+ "LogFrequencyKernel inputs must be strictly positive")
333
+ return self.kernel(jnp.log(x1), jnp.log(x2), key=key)
334
+
335
+
336
+ class PowerEnvelopeKernel(AbstractCovarianceKernel):
337
+ r"""
338
+ Power-law amplitude envelope for a one-dimensional frequency input.
339
+
340
+ $$k(x_1, x_2) = s(x_1)s(x_2), \qquad
341
+ s(x) = \left(\frac{x}{x_\text{ref}}\right)^{-\alpha}$$
342
+
343
+ This rank-one kernel is singular on its own. Multiply it by another
344
+ kernel to make that kernel's prior standard deviation scale as $s(x)$.
345
+ Inputs must be strictly positive and contain exactly one feature.
346
+ ``reference`` must use the same units as the GP input (``frequency.f_scaled``
347
+ in the fitting path). A unit mismatch silently rescales the prior variance
348
+ by $(\text{unit ratio})^{-2\alpha}$.
349
+
350
+ Parameters
351
+ ----------
352
+ reference : ArrayLike
353
+ Input at which the amplitude is one, in the same units as the GP input.
354
+ exponent : ArrayLike
355
+ Decay exponent. A shape ``(D,)`` gives ``D`` batched kernels.
356
+ """
357
+ #: The reference input.
358
+ reference: ArrayLike
359
+
360
+ #: The decay exponent.
361
+ exponent: ArrayLike
362
+
363
+ def _amplitude(self, x):
364
+ x = jnp.asarray(x)
365
+ if x.size != 1:
366
+ raise ValueError("PowerEnvelopeKernel requires one-dimensional input points")
367
+ x = eqx.error_if(x, jnp.any(x <= 0),
368
+ "PowerEnvelopeKernel inputs must be strictly positive")
369
+ return (x.reshape(()) / self.reference) ** (-jnp.asarray(self.exponent))
370
+
371
+ def __call__(self, x1, x2, key=None):
372
+ return self._amplitude(x1) * self._amplitude(x2)
373
+
374
+
307
375
  class WhiteNoiseKernel(AbstractCovarianceKernel):
308
376
  """
309
377
  Kernel representing independent Gaussian noise.
@@ -122,34 +122,45 @@ def _linearize(
122
122
  residual = residual_fn(model)
123
123
  J = -jax.jacfwd(lambda x: residual_fn(rebuild(x)))(x0)
124
124
 
125
+ W = _whitened_jacobian(J, chol)
126
+ F = jnp.einsum('...nps,...nqs->pq', W, W)
127
+ return Linearization(J, residual, chol, F, names, shapes, space)
128
+
129
+
130
+ def _whitened_jacobian(J: Array, chol: Array) -> Array:
131
+ """Return $L^{-1}J$ grouped by shared covariance factors."""
132
+
125
133
  # Event blocks sharing a factor are solved together, with the parameter axis
126
134
  # carried as one more batch axis the factor is shared along.
127
135
  n, p = J.shape[-2], J.shape[-1]
128
136
  matrix_shape, R, _ = _group_by_matrix(chol.shape[:-2], jnp.moveaxis(J, -1, 0))
129
137
  L = jnp.broadcast_to(chol, chol.shape[:-2] + (n, n)).reshape(matrix_shape + (n, n))
130
138
  W = jsp.linalg.solve_triangular(L, R, lower=True)
131
- W = W.reshape(W.shape[:-1] + (p, -1))
132
- F = jnp.einsum('...nps,...nqs->pq', W, W)
133
- return Linearization(J, residual, chol, F, names, shapes, space)
139
+ return W.reshape(W.shape[:-1] + (p, -1))
134
140
 
135
141
 
136
- def posterior_covariance(
142
+ def posterior_covariance_factor(
137
143
  linearizations: Sequence[Linearization],
138
144
  model: PyTree,
139
145
  space: Space = 'declared',
140
146
  ) -> Array:
141
- r"""The linearised posterior covariance of a model's free parameters.
147
+ r"""A square root of the linearised posterior covariance of free parameters.
142
148
 
143
149
  **Mathematical Formulation**
144
150
 
145
- The Fisher matrices of independent fits add, and the prior enters through its
146
- precision at `model`:
151
+ Stack the whitened Jacobians of independent fits with a square root of the
152
+ prior precision, then use the singular values of that stack:
147
153
 
148
- $$\Sigma_\text{post} = \left(\sum_k F_k + \Sigma_0^{-1}\right)^{-1}, \qquad
149
- \Sigma_0^{-1} = -\nabla^2 \log p(\theta)$$
154
+ $$A = \begin{bmatrix}L_1^{-1}J_1 \\ \cdots \\ L_k^{-1}J_k \\
155
+ \Sigma_0^{-1/2}\end{bmatrix}, \qquad
156
+ A = U S V^\top, \qquad W = V S^{-1}, \qquad
157
+ \Sigma_\text{post} = W W^\top,$$
150
158
 
151
- with the Hessian of :func:`pmrf.log_prior` taken in `space`. A parameter without a
152
- prior, or with a uniform prior inside its bounds, adds zero to $\Sigma_0^{-1}$.
159
+ where $\Sigma_0^{-1} = -\nabla^2\log p(\theta)$ in `space`. Its symmetric
160
+ eigendecomposition supplies the square root; negative eigenvalues are clipped to
161
+ zero. This local approximation is only meaningful where the prior is log-concave.
162
+ A parameter without a prior, or with a uniform prior inside its bounds, adds zero
163
+ prior precision.
153
164
 
154
165
  Parameters
155
166
  ----------
@@ -164,8 +175,8 @@ def posterior_covariance(
164
175
  Returns
165
176
  -------
166
177
  jax.Array
167
- The covariance, with shape ``(p, p)``, over the free parameters in the
168
- linearisations' :attr:`~Linearization.names` order.
178
+ A factor ``W`` with shape ``(p, p)`` such that ``W @ W.T`` is the
179
+ covariance over free parameters in :attr:`~Linearization.names` order.
169
180
 
170
181
  Raises
171
182
  ------
@@ -185,14 +196,42 @@ def posterior_covariance(
185
196
  "A linearisation is over different free parameters from the model: "
186
197
  f"{linearization.names} rather than {names}."
187
198
  )
188
- fisher = sum(linearization.F for linearization in linearizations)
189
199
  prior_precision = -jax.hessian(lambda x: log_prior(rebuild(x), space=space))(x0)
190
- precision = fisher + prior_precision
191
- chol = jnp.linalg.cholesky(0.5 * (precision + precision.T))
192
- return jsp.linalg.cho_solve((chol, True), jnp.eye(precision.shape[-1], dtype=precision.dtype))
200
+ eigenvalues, eigenvectors = jnp.linalg.eigh(0.5 * (prior_precision + prior_precision.T))
201
+ prior_root = jnp.sqrt(jnp.maximum(eigenvalues, 0.0))[:, None] * eigenvectors.T
202
+ p = x0.size
203
+ factors = [
204
+ jnp.moveaxis(_whitened_jacobian(lin.J, lin.chol), -2, -1).reshape(-1, p)
205
+ for lin in linearizations
206
+ ]
207
+ A = jnp.concatenate([*factors, prior_root], axis=0)
208
+ _, singular_values, vt = jnp.linalg.svd(A, full_matrices=False)
209
+ threshold = singular_values[0] * max(A.shape) * jnp.finfo(A.dtype).eps
210
+ singular_values = eqx.error_if(
211
+ singular_values, singular_values[-1] <= threshold,
212
+ "A parameter direction has neither data nor prior information.",
213
+ )
214
+ V = vt.T
215
+ return V / singular_values
216
+
217
+
218
+ def posterior_covariance(
219
+ linearizations: Sequence[Linearization],
220
+ model: PyTree,
221
+ space: Space = 'declared',
222
+ ) -> Array:
223
+ r"""The linearised posterior covariance of a model's free parameters.
224
+
225
+ See :func:`posterior_covariance_factor` for the factorisation and its
226
+ assumptions. The result has shape ``(p, p)`` in the linearisations'
227
+ :attr:`~Linearization.names` order.
228
+ """
229
+ W = posterior_covariance_factor(linearizations, model, space)
230
+ return W @ W.T
193
231
 
194
232
 
195
233
  __all__ = [
196
234
  'Linearization',
197
235
  'posterior_covariance',
236
+ 'posterior_covariance_factor',
198
237
  ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "paramrf"
3
- version = "0.37.2"
3
+ version = "0.37.4"
4
4
  description = "Parametric radio frequency modeling"
5
5
  readme = "README.rst"
6
6
  authors = [
@@ -15,6 +15,8 @@ from pmrf.stats.covariance_kernels import (
15
15
  Matern52Kernel,
16
16
  ConstantKernel,
17
17
  CosineKernel,
18
+ LogFrequencyKernel,
19
+ PowerEnvelopeKernel,
18
20
  AutoCrossKernel,
19
21
  SharedIndependentKernel,
20
22
  )
@@ -38,6 +40,12 @@ def x_periods():
38
40
  return jnp.linspace(0.0, 50.0, 200)
39
41
 
40
42
 
43
+ @pytest.fixture
44
+ def x_log():
45
+ assert jax.config.jax_enable_x64
46
+ return jnp.geomspace(10e6, 500e6, 300)
47
+
48
+
41
49
  def _damped_cosine():
42
50
  return CosineKernel(period=7.3) * Matern52Kernel(lengthscale=20.0) * 2.0
43
51
 
@@ -210,6 +218,130 @@ def test_cosine_kernel_under_jit_and_vmap(x_periods):
210
218
  assert jnp.allclose(vmapped, CosineKernel(period=periods).gram(x_periods))
211
219
 
212
220
 
221
+ def test_log_frequency_kernel_has_ratio_period():
222
+ """One cycle in log frequency spans a fixed frequency ratio."""
223
+ period = 1.7
224
+ start = jnp.array([10e6])
225
+ kernel = LogFrequencyKernel(CosineKernel(period))
226
+ assert jnp.allclose(kernel(start, start * jnp.exp(period)), 1.0, atol=1e-12)
227
+ assert jnp.allclose(kernel(start, start * jnp.exp(period / 2)), -1.0, atol=1e-12)
228
+
229
+
230
+ def test_log_frequency_kernel_unit_invariance_and_psd(x_log):
231
+ """Changing frequency units leaves the damped covariance unchanged and PSD."""
232
+ kernel = LogFrequencyKernel(CosineKernel(1.7) * Matern52Kernel(1.5))
233
+ covariance = kernel.gram(x_log)
234
+ assert jnp.max(jnp.abs(covariance - kernel.gram(x_log / 1e6))) < 1e-12
235
+ eigenvalues = jnp.linalg.eigvalsh(covariance)
236
+ assert eigenvalues.min() >= -1e-8 * eigenvalues.max()
237
+
238
+
239
+ def test_log_frequency_kernel_preserves_inner_batching(x_log):
240
+ """A vector of inner periods gives one Gram matrix per period."""
241
+ periods = jnp.array([1.0, 2.0])
242
+ covariance = LogFrequencyKernel(CosineKernel(periods)).gram(x_log)
243
+ assert covariance.shape == (2, 300, 300)
244
+ for index, period in enumerate(periods):
245
+ expected = LogFrequencyKernel(CosineKernel(period)).gram(x_log)
246
+ assert jnp.allclose(covariance[index], expected)
247
+
248
+
249
+ @pytest.mark.parametrize("invalid", [0.0, -1.0])
250
+ @pytest.mark.parametrize("side", [0, 1])
251
+ def test_log_frequency_kernel_rejects_nonpositive_inputs(invalid, side):
252
+ """Both arguments require positive frequencies, eagerly and under jit."""
253
+ kernel = LogFrequencyKernel(CosineKernel(1.7))
254
+ inputs = [jnp.array([10e6]), jnp.array([10e6])]
255
+ inputs[side] = jnp.array([invalid])
256
+ with pytest.raises(Exception, match="strictly positive"):
257
+ kernel(*inputs)
258
+ with pytest.raises(Exception, match="strictly positive"):
259
+ jax.jit(kernel)(*inputs)
260
+
261
+
262
+ def test_log_frequency_kernel_is_exported():
263
+ from pmrf.stats import LogFrequencyKernel as PublicLogFrequencyKernel
264
+ assert PublicLogFrequencyKernel is LogFrequencyKernel
265
+
266
+
267
+ def test_power_envelope_kernel_value():
268
+ """The covariance is the outer product of two power-law amplitudes."""
269
+ kernel = PowerEnvelopeKernel(10e6, 0.5)
270
+ assert jnp.allclose(kernel(40e6, 90e6), 1 / 6, rtol=1e-14)
271
+
272
+
273
+ def _damped_log_periodic_envelope(exponent=0.65, period=1.7, lengthscale=1.5):
274
+ return (PowerEnvelopeKernel(10e6, exponent)
275
+ * LogFrequencyKernel(CosineKernel(period) * Matern52Kernel(lengthscale))
276
+ * 2.5e-3 ** 2)
277
+
278
+
279
+ def test_power_envelope_kernel_diagonal_and_psd(x_log):
280
+ """The envelope sets the prior variance and preserves positive semidefiniteness."""
281
+ kernel = _damped_log_periodic_envelope()
282
+ for index in [0, 150, 299]:
283
+ expected = 2.5e-3 ** 2 * (x_log[index] / 10e6) ** -1.3
284
+ assert jnp.allclose(kernel(x_log[index], x_log[index]), expected, rtol=1e-10)
285
+ eigenvalues = jnp.linalg.eigvalsh(kernel.gram(x_log))
286
+ assert eigenvalues.min() >= -1e-8 * eigenvalues.max()
287
+
288
+
289
+ def test_power_envelope_kernel_batches_exponents(x_log):
290
+ """A vector of exponents gives the corresponding scalar Gram matrices."""
291
+ exponents = jnp.array([0.5, 1.0])
292
+ covariance = PowerEnvelopeKernel(10e6, exponents).gram(x_log)
293
+ assert covariance.shape == (2, 300, 300)
294
+ for index, exponent in enumerate(exponents):
295
+ expected = PowerEnvelopeKernel(10e6, exponent).gram(x_log)
296
+ assert jnp.allclose(covariance[index], expected)
297
+
298
+
299
+ def test_power_envelope_kernel_composition_gradients_are_finite(x_log):
300
+ """Envelope, period, lengthscale, and coincident input gradients stay finite."""
301
+ def covariance_sum(parameters):
302
+ return _damped_log_periodic_envelope(*parameters).gram(x_log).sum()
303
+
304
+ gradients = jax.grad(covariance_sum)(jnp.array([0.65, 1.7, 1.5]))
305
+ assert jnp.all(jnp.isfinite(gradients))
306
+
307
+ kernel = _damped_log_periodic_envelope()
308
+ input_gradient = jax.grad(lambda x1: kernel(x1, jnp.array([10e6])))(jnp.array([10e6]))
309
+ assert jnp.all(jnp.isfinite(input_gradient))
310
+
311
+
312
+ def test_power_envelope_kernel_composition_under_jit(x_log):
313
+ """Compilation preserves the composed covariance."""
314
+ kernel = _damped_log_periodic_envelope()
315
+ assert jnp.allclose(jax.jit(lambda x: kernel.gram(x))(x_log), kernel.gram(x_log))
316
+
317
+
318
+ @pytest.mark.parametrize("invalid", [0.0, -1.0])
319
+ @pytest.mark.parametrize("side", [0, 1])
320
+ def test_power_envelope_kernel_rejects_nonpositive_inputs(invalid, side):
321
+ """Both arguments require positive frequencies, eagerly and under jit."""
322
+ kernel = PowerEnvelopeKernel(10e6, 0.65)
323
+ inputs = [jnp.array([10e6]), jnp.array([10e6])]
324
+ inputs[side] = jnp.array([invalid])
325
+ with pytest.raises(Exception, match="strictly positive"):
326
+ kernel(*inputs)
327
+ with pytest.raises(Exception, match="strictly positive"):
328
+ jax.jit(kernel)(*inputs)
329
+
330
+
331
+ def test_power_envelope_kernel_rejects_multidimensional_inputs():
332
+ """A point with multiple features is rejected while tracing."""
333
+ kernel = PowerEnvelopeKernel(10e6, 0.65)
334
+ with pytest.raises(ValueError, match="one-dimensional"):
335
+ kernel(jnp.array([10e6, 20e6]), jnp.array([10e6]))
336
+ with pytest.raises(ValueError, match="one-dimensional"):
337
+ jax.jit(kernel)(jnp.array([10e6, 20e6]), jnp.array([10e6]))
338
+
339
+
340
+ def test_power_envelope_kernel_is_exported():
341
+ from pmrf.stats import PowerEnvelopeKernel as PublicPowerEnvelopeKernel
342
+ assert PublicPowerEnvelopeKernel is PowerEnvelopeKernel
343
+
344
+
213
345
  _AUTO = RBFKernel(lengthscale=0.3)
214
346
  _CROSS = Matern32Kernel(lengthscale=2.0) * 0.5
215
347