paramrf 0.36.4__tar.gz → 0.36.5__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 (260) hide show
  1. {paramrf-0.36.4 → paramrf-0.36.5}/CONTEXT.md +84 -1
  2. {paramrf-0.36.4/paramrf.egg-info → paramrf-0.36.5}/PKG-INFO +2 -2
  3. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0005-priors-by-name.md +32 -3
  4. paramrf-0.36.5/docs/examples/discrepancy_prediction.rst +77 -0
  5. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/index.rst +2 -0
  6. paramrf-0.36.5/docs/examples/port_discrepancy.py +273 -0
  7. paramrf-0.36.5/docs/examples/port_discrepancy.rst +81 -0
  8. {paramrf-0.36.4 → paramrf-0.36.5/paramrf.egg-info}/PKG-INFO +2 -2
  9. {paramrf-0.36.4 → paramrf-0.36.5}/paramrf.egg-info/SOURCES.txt +9 -0
  10. {paramrf-0.36.4 → paramrf-0.36.5}/paramrf.egg-info/requires.txt +1 -1
  11. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/covariance_kernels.py +52 -19
  12. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/discrepancy_models.py +136 -21
  13. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/evaluators.py +246 -9
  14. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/base.py +5 -4
  15. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/likelihoods.py +34 -40
  16. paramrf-0.36.5/pmrf/linearization.py +198 -0
  17. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/__init__.py +5 -0
  18. paramrf-0.36.5/pmrf/models/port_discrepancy.py +175 -0
  19. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/modules/wrapped.py +11 -0
  20. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/parameters.py +149 -68
  21. {paramrf-0.36.4 → paramrf-0.36.5}/pyproject.toml +2 -2
  22. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_covariance_kernels.py +20 -0
  23. paramrf-0.36.5/tests/test_discrepancy_models.py +248 -0
  24. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_evaluators.py +230 -14
  25. paramrf-0.36.5/tests/test_linearization.py +417 -0
  26. paramrf-0.36.5/tests/test_models/test_port_discrepancy.py +144 -0
  27. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_parameters_joint_prior.py +329 -8
  28. paramrf-0.36.5/tests/test_port_discrepancy_example.py +56 -0
  29. {paramrf-0.36.4 → paramrf-0.36.5}/.github/workflows/docs.yml +0 -0
  30. {paramrf-0.36.4 → paramrf-0.36.5}/.github/workflows/draft-pdf.yml +0 -0
  31. {paramrf-0.36.4 → paramrf-0.36.5}/.github/workflows/publish.yml +0 -0
  32. {paramrf-0.36.4 → paramrf-0.36.5}/.github/workflows/tests.yml +0 -0
  33. {paramrf-0.36.4 → paramrf-0.36.5}/.gitignore +0 -0
  34. {paramrf-0.36.4 → paramrf-0.36.5}/AGENTS.md +0 -0
  35. {paramrf-0.36.4 → paramrf-0.36.5}/CHANGELOG.md +0 -0
  36. {paramrf-0.36.4 → paramrf-0.36.5}/CITATION.cff +0 -0
  37. {paramrf-0.36.4 → paramrf-0.36.5}/CLAUDE.md +0 -0
  38. {paramrf-0.36.4 → paramrf-0.36.5}/CONTRIBUTING.md +0 -0
  39. {paramrf-0.36.4 → paramrf-0.36.5}/LICENSE +0 -0
  40. {paramrf-0.36.4 → paramrf-0.36.5}/NOTICE +0 -0
  41. {paramrf-0.36.4 → paramrf-0.36.5}/README.rst +0 -0
  42. {paramrf-0.36.4 → paramrf-0.36.5}/assets/logo.png +0 -0
  43. {paramrf-0.36.4 → paramrf-0.36.5}/docs/Makefile +0 -0
  44. {paramrf-0.36.4 → paramrf-0.36.5}/docs/_static/custom.css +0 -0
  45. {paramrf-0.36.4 → paramrf-0.36.5}/docs/_templates/autosummary/class.rst +0 -0
  46. {paramrf-0.36.4 → paramrf-0.36.5}/docs/_templates/autosummary/function.rst +0 -0
  47. {paramrf-0.36.4 → paramrf-0.36.5}/docs/_templates/autosummary/module.rst +0 -0
  48. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0001-line-modelling-architecture.md +0 -0
  49. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0002-parameter-api.md +0 -0
  50. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0003-derived-models.md +0 -0
  51. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0004-profiled-lines.md +0 -0
  52. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0006-reference-impedance.md +0 -0
  53. {paramrf-0.36.4 → paramrf-0.36.5}/docs/adr/0007-edge-values.md +0 -0
  54. {paramrf-0.36.4 → paramrf-0.36.5}/docs/agents/domain.md +0 -0
  55. {paramrf-0.36.4 → paramrf-0.36.5}/docs/agents/issue-tracker.md +0 -0
  56. {paramrf-0.36.4 → paramrf-0.36.5}/docs/agents/triage-labels.md +0 -0
  57. {paramrf-0.36.4 → paramrf-0.36.5}/docs/api/index.rst +0 -0
  58. {paramrf-0.36.4 → paramrf-0.36.5}/docs/conf.py +0 -0
  59. {paramrf-0.36.4 → paramrf-0.36.5}/docs/core_concepts/core_primitives.rst +0 -0
  60. {paramrf-0.36.4 → paramrf-0.36.5}/docs/core_concepts/index.rst +0 -0
  61. {paramrf-0.36.4 → paramrf-0.36.5}/docs/core_concepts/jax_overview.rst +0 -0
  62. {paramrf-0.36.4 → paramrf-0.36.5}/docs/core_concepts/optimization_and_inference.rst +0 -0
  63. {paramrf-0.36.4 → paramrf-0.36.5}/docs/core_concepts/parameter_names.rst +0 -0
  64. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/cascading_and_terminating.rst +0 -0
  65. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/circuit_clc.png +0 -0
  66. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/circuit_models.rst +0 -0
  67. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/custom_models.rst +0 -0
  68. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/derivatives_and_sweeps.rst +0 -0
  69. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/model_optimization.rst +0 -0
  70. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
  71. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
  72. {paramrf-0.36.4 → paramrf-0.36.5}/docs/examples/shared_substrates.rst +0 -0
  73. {paramrf-0.36.4 → paramrf-0.36.5}/docs/index.rst +0 -0
  74. {paramrf-0.36.4 → paramrf-0.36.5}/docs/license.rst +0 -0
  75. {paramrf-0.36.4 → paramrf-0.36.5}/docs/make.bat +0 -0
  76. {paramrf-0.36.4 → paramrf-0.36.5}/docs/research/conductor-loss-alternatives.md +0 -0
  77. {paramrf-0.36.4 → paramrf-0.36.5}/docs/research/holloway1994.pdf +0 -0
  78. {paramrf-0.36.4 → paramrf-0.36.5}/docs/research/microstrip-loss-conventions.md +0 -0
  79. {paramrf-0.36.4 → paramrf-0.36.5}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
  80. {paramrf-0.36.4 → paramrf-0.36.5}/docs/skrf_comparison/index.rst +0 -0
  81. {paramrf-0.36.4 → paramrf-0.36.5}/docs/skrf_comparison/overview.rst +0 -0
  82. {paramrf-0.36.4 → paramrf-0.36.5}/docs/skrf_comparison/performance.rst +0 -0
  83. {paramrf-0.36.4 → paramrf-0.36.5}/docs/tutorials/1_cable_fitting.ipynb +0 -0
  84. {paramrf-0.36.4 → paramrf-0.36.5}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
  85. {paramrf-0.36.4 → paramrf-0.36.5}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
  86. {paramrf-0.36.4 → paramrf-0.36.5}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
  87. {paramrf-0.36.4 → paramrf-0.36.5}/docs/tutorials/index.rst +0 -0
  88. {paramrf-0.36.4 → paramrf-0.36.5}/paper/paper.bib +0 -0
  89. {paramrf-0.36.4 → paramrf-0.36.5}/paper/paper.md +0 -0
  90. {paramrf-0.36.4 → paramrf-0.36.5}/paper/rlc.png +0 -0
  91. {paramrf-0.36.4 → paramrf-0.36.5}/paramrf.egg-info/dependency_links.txt +0 -0
  92. {paramrf-0.36.4 → paramrf-0.36.5}/paramrf.egg-info/top_level.txt +0 -0
  93. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/__init__.py +0 -0
  94. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/_solver_view.py +0 -0
  95. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/bijectors.py +0 -0
  96. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/constraints.py +0 -0
  97. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/distributions.py +0 -0
  98. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/fitting/__init__.py +0 -0
  99. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/fitting/minimize.py +0 -0
  100. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/fitting/result.py +0 -0
  101. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/fitting/routers.py +0 -0
  102. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/fitting/sample.py +0 -0
  103. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/fitting/targets.py +0 -0
  104. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/frequency.py +0 -0
  105. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/__init__.py +0 -0
  106. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/result.py +0 -0
  107. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/sample.py +0 -0
  108. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/solvers/__init__.py +0 -0
  109. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/solvers/blackjax.py +0 -0
  110. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/infer/solvers/polychord.py +0 -0
  111. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/losses.py +0 -0
  112. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/__init__.py +0 -0
  113. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/conductor.py +0 -0
  114. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/dielectric.py +0 -0
  115. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/properties.py +0 -0
  116. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/roughness.py +0 -0
  117. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/substrate.py +0 -0
  118. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/materials/surface_impedance.py +0 -0
  119. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/math/__init__.py +0 -0
  120. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/math/aggregations.py +0 -0
  121. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/math/bessel.py +0 -0
  122. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/math/conversions.py +0 -0
  123. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/math/losses.py +0 -0
  124. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/math/misc.py +0 -0
  125. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/__init__.py +0 -0
  126. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/base.py +0 -0
  127. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/bridge.py +0 -0
  128. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/callable.py +0 -0
  129. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/delegated.py +0 -0
  130. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/derived.py +0 -0
  131. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/static.py +0 -0
  132. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/adapters/wrapped.py +0 -0
  133. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/base.py +0 -0
  134. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/__init__.py +0 -0
  135. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/ideal.py +0 -0
  136. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/__init__.py +0 -0
  137. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/base.py +0 -0
  138. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/coaxial.py +0 -0
  139. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/empirical.py +0 -0
  140. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/ideal.py +0 -0
  141. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/microstrip.py +0 -0
  142. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/nodal.py +0 -0
  143. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/nonuniform.py +0 -0
  144. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/planar.py +0 -0
  145. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/profiles.py +0 -0
  146. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lines/stripline.py +0 -0
  147. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/lumped.py +0 -0
  148. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/components/sections.py +0 -0
  149. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/__init__.py +0 -0
  150. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/__init__.py +0 -0
  151. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/cascade.py +0 -0
  152. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
  153. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
  154. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/circuit/circuit.py +0 -0
  155. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
  156. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
  157. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
  158. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/interconnected/terminated.py +0 -0
  159. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/nodal.py +0 -0
  160. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/topological.py +0 -0
  161. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/composite/transformed.py +0 -0
  162. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/surrogates/__init__.py +0 -0
  163. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/surrogates/expansion.py +0 -0
  164. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/models/surrogates/rational.py +0 -0
  165. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/modules/__init__.py +0 -0
  166. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/modules/base.py +0 -0
  167. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/network_collection.py +0 -0
  168. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/noise_models.py +0 -0
  169. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/__init__.py +0 -0
  170. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/base.py +0 -0
  171. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/minimize.py +0 -0
  172. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/result.py +0 -0
  173. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/solvers/__init__.py +0 -0
  174. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/solvers/jaxopt.py +0 -0
  175. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/solvers/optimistix.py +0 -0
  176. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/optimize/solvers/scipy.py +0 -0
  177. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/problems.py +0 -0
  178. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/rf/__init__.py +0 -0
  179. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/rf/cascade.py +0 -0
  180. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/rf/conversions.py +0 -0
  181. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/rf/mna.py +0 -0
  182. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/serialization.py +0 -0
  183. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/terms.py +0 -0
  184. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/types.py +0 -0
  185. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/__init__.py +0 -0
  186. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/array.py +0 -0
  187. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/debug.py +0 -0
  188. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/network.py +0 -0
  189. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/random.py +0 -0
  190. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/rf.py +0 -0
  191. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/transforms.py +0 -0
  192. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/tree.py +0 -0
  193. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/utils/type.py +0 -0
  194. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/viz/__init__.py +0 -0
  195. {paramrf-0.36.4 → paramrf-0.36.5}/pmrf/viz/plots.py +0 -0
  196. {paramrf-0.36.4 → paramrf-0.36.5}/scripts/benchmark_gp_marginal_likelihood.py +0 -0
  197. {paramrf-0.36.4 → paramrf-0.36.5}/scripts/install-test-deps.sh +0 -0
  198. {paramrf-0.36.4 → paramrf-0.36.5}/setup.cfg +0 -0
  199. {paramrf-0.36.4 → paramrf-0.36.5}/tests/__init__.py +0 -0
  200. {paramrf-0.36.4 → paramrf-0.36.5}/tests/_jit.py +0 -0
  201. {paramrf-0.36.4 → paramrf-0.36.5}/tests/conftest.py +0 -0
  202. {paramrf-0.36.4 → paramrf-0.36.5}/tests/data/10m_cable.s2p +0 -0
  203. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_autodiff.py +0 -0
  204. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_box_space_solving.py +0 -0
  205. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_conversions.py +0 -0
  206. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_dc_limits.py +0 -0
  207. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_fitting_minimize.py +0 -0
  208. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_fitting_routers.py +0 -0
  209. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_fitting_sample.py +0 -0
  210. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_fitting_targets.py +0 -0
  211. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_frequency.py +0 -0
  212. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_infer_base.py +0 -0
  213. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_infer_sample.py +0 -0
  214. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_map_priors.py +0 -0
  215. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_materials/test_conductor.py +0 -0
  216. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_materials/test_dielectric.py +0 -0
  217. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_materials/test_serialization.py +0 -0
  218. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_materials/test_substrate.py +0 -0
  219. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_materials/test_surface_impedance.py +0 -0
  220. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_math/test_bessel.py +0 -0
  221. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_model.py +0 -0
  222. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_adapters.py +0 -0
  223. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_cascade.py +0 -0
  224. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_native_z0.py +0 -0
  225. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_nodal.py +0 -0
  226. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_port_order.py +0 -0
  227. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_scattering.py +0 -0
  228. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_shorted_ports.py +0 -0
  229. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_skrf_port_names.py +0 -0
  230. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_circuit_z0.py +0 -0
  231. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_current_distribution.py +0 -0
  232. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_derived.py +0 -0
  233. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_edge_values.py +0 -0
  234. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_interconnected.py +0 -0
  235. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_lines.py +0 -0
  236. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_lines_skrf_matrix.py +0 -0
  237. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_lumped.py +0 -0
  238. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_nodal.py +0 -0
  239. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_profiles.py +0 -0
  240. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_rational.py +0 -0
  241. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_repeated_cascade.py +0 -0
  242. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_sections.py +0 -0
  243. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_transformed.py +0 -0
  244. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_models/test_transformers.py +0 -0
  245. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_module.py +0 -0
  246. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_naming.py +0 -0
  247. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_optimize_base.py +0 -0
  248. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_optimize_minimize.py +0 -0
  249. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_parameters.py +0 -0
  250. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_parameters_by_name.py +0 -0
  251. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_parameters_prior.py +0 -0
  252. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_parameters_validity.py +0 -0
  253. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_raw_space_solving.py +0 -0
  254. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_serialization.py +0 -0
  255. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_structural_updates.py +0 -0
  256. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_terms.py +0 -0
  257. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_transforms.py +0 -0
  258. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_utils/test_compress.py +0 -0
  259. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_utils/test_debug.py +0 -0
  260. {paramrf-0.36.4 → paramrf-0.36.5}/tests/test_utils/test_tree.py +0 -0
@@ -98,7 +98,9 @@ is derived, so it has no prior of its own.
98
98
 
99
99
  `prf.prior` attaches a prior to the parameters a selector picks (ADR-0005). A
100
100
  scalar distribution gives each of them its own prior. A distribution whose
101
- event size equals the number of parameters is a **joint prior** over them: a
101
+ event size equals the parameters' total size (their number, when they are
102
+ scalars; an array parameter counts each of its values) is a **joint prior** over
103
+ them: a
102
104
  wrapper (`prf.modules.Probabilistic`) around the unchanged tree, so every
103
105
  parameter keeps its name. A joint prior replaces its parameters' own priors,
104
106
  and its `space` says which space its distribution is over. Raw space for its
@@ -170,6 +172,87 @@ Modifies conductor behaviour rather than line state — it scales a surface
170
172
  impedance — so it belongs to the material, not the geometry: it is a field of
171
173
  `RoughConductor` in `pmrf.materials.conductor`.
172
174
 
175
+ ## Discrepancy modelling
176
+
177
+ A fit in Kennedy–O'Hagan form treats an observation as $\tilde h = h(\theta) +
178
+ \delta + \varepsilon$: the model prediction, a **discrepancy** and measurement
179
+ noise. `MarginalLogLikelihood` scores it; see `pmrf.discrepancy_models` and
180
+ `pmrf.likelihoods`.
181
+
182
+ ### Event space and event block
183
+
184
+ The space in which probability is defined: the prediction after the event
185
+ transform, with frequency as the last (event) axis. Every other axis is a batch
186
+ axis. One entry of that batch, for example the real part of S21, is an **event
187
+ block**. Blocks are independent: a kernel chooses the covariance *within* each
188
+ block (`AutoCrossKernel` routes by block, `SharedIndependentKernel` shares
189
+ hyperparameters across blocks) and never couples two blocks.
190
+
191
+ *Avoid:* "output" or "task" for a block; "cross-covariance" for the kernel an
192
+ `AutoCrossKernel` gives a transmission block.
193
+
194
+ ### Discrepancy
195
+
196
+ $\delta$, the systematic misfit between model and reality, as opposed to noise
197
+ $\varepsilon$. Either deterministic or a `GaussianProcess` over each event block.
198
+ It is on the observable unless it is a port discrepancy.
199
+
200
+ ### Residual
201
+
202
+ The observation minus the model prediction, in event space: $r = \tilde h -
203
+ h(\theta)$. It contains both discrepancy and noise.
204
+
205
+ ### Discrepancy prediction
206
+
207
+ The distribution of $\delta$ at new frequencies $x_B$, conditioned on residuals
208
+ at the fit frequencies $x_A$, per event block:
209
+ $\mu = K_{BA}(K_{AA}+\Sigma_n)^{-1} r$ and
210
+ $\Sigma = K_{BB} - K_{BA}(K_{AA}+\Sigma_n)^{-1}K_{AB}$. It excludes noise: it is
211
+ the discrepancy, not a predicted observation.
212
+
213
+ *Avoid:* "posterior predictive" (that includes noise); "discrepancy posterior"
214
+ (ambiguous with a posterior over hyperparameters).
215
+
216
+ ### Linearisation
217
+
218
+ A Gauss–Newton approximation of a fit at its MAP, with the discrepancy's
219
+ hyperparameters held fixed: the Jacobian $J = -\partial r / \partial \theta$ of the
220
+ residual with respect to the model's free parameters, and the Fisher matrix
221
+ $F = J^\top \Sigma_D^{-1} J$ with $\Sigma_D = K + \Sigma_n$ per event block. With
222
+ the prior precision $\Sigma_0^{-1}$ it gives the linearised posterior covariance
223
+ $\Sigma_\text{post} = (F + \Sigma_0^{-1})^{-1}$, over parameter values in one space
224
+ (declared by default). Fisher matrices of independent fits add.
225
+
226
+ ### Joint prediction
227
+
228
+ The Gaussian over the free parameters and the discrepancy at new frequencies
229
+ together, from a linearisation: unlike a discrepancy prediction, it carries the
230
+ parameters' uncertainty into $\delta$ and the cross-covariance between them. It is
231
+ the form in which one fit's posterior becomes the next fit's joint prior.
232
+
233
+ ### Port discrepancy
234
+
235
+ A discrepancy embedded at a component's ports, on its S-parameters rather than on
236
+ an observable: $\check S_{ii} = S_{ii} + \delta_{ii}$ for reflection and
237
+ $\check S_{ij} = S_{ij} e^{\delta_{ij}}$ for transmission. A circuit containing
238
+ the corrected component carries $\delta$ to any observable or quantity of
239
+ interest, so it transfers between fits that observe the component differently.
240
+ Transmission is split into a symmetric and an antisymmetric block,
241
+ $\delta_{ij} = \delta^s_{ij} \pm \delta^a_{ij}$; a reciprocal component has no
242
+ antisymmetric block. $\delta$ is defined at a reference impedance and is learnt
243
+ at that impedance.
244
+
245
+ When the component is observed directly, in an event space of the same form
246
+ (additive for reflection, logarithmic for transmission), the port discrepancy *is*
247
+ the event-space discrepancy, and is marginalised there: this is the **reference
248
+ fit**. A later fit with the component inside a larger circuit holds $\delta$ as
249
+ explicit parameters, under the reference fit's joint prediction as their prior:
250
+ this is a **transfer fit**.
251
+
252
+ *Avoid:* "embedded discrepancy" or "embedded model error" (in the literature
253
+ these embed the error in the parameters); "stage 1" and "stage 2" (say reference
254
+ fit and transfer fit).
255
+
173
256
  ## Records at the boundaries
174
257
 
175
258
  These frozen records are the seams that keep `Param`s and `Module`s out of the
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramrf
3
- Version: 0.36.4
3
+ Version: 0.36.5
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
7
7
  Description-Content-Type: text/x-rst
8
8
  License-File: LICENSE
9
9
  License-File: NOTICE
10
- Requires-Dist: parax>=0.11.5
10
+ Requires-Dist: parax>=0.11.6
11
11
  Requires-Dist: threadpoolctl
12
12
  Requires-Dist: eqxpress
13
13
  Requires-Dist: jax
@@ -1,6 +1,7 @@
1
1
  # ADR-0005: Priors are attached by name, and a joint prior keeps its parameters
2
2
 
3
- Status: accepted (2026-09)
3
+ Status: accepted (2026-09); amended (2026-10) to allow array-valued parameters
4
+ under a joint prior, and to compute a constant whitening log-determinant once.
4
5
 
5
6
  ## Context
6
7
 
@@ -43,8 +44,8 @@ The distribution's event size decides which:
43
44
  `prf.Random`. Its raw space becomes the new prior's whitening, as for
44
45
  `prf.Random`, and the prior is truncated to the parameter's existing bounds so
45
46
  the bounds still hold.
46
- - **Event size equal to the number of selected parameters**: a **joint prior**
47
- over them.
47
+ - **Event size equal to the total size of the selected parameters**: a **joint
48
+ prior** over them. For scalar parameters the total size is their number.
48
49
  - Anything else raises.
49
50
 
50
51
  ### A joint prior keeps its parameters
@@ -63,6 +64,22 @@ transparent to the name resolver, as `Tied` is. So:
63
64
  would count the earlier fit's priors twice, since its posterior already
64
65
  includes them.
65
66
 
67
+ ### Array-valued parameters
68
+
69
+ A parameter under a joint prior may be an array, such as a port discrepancy's
70
+ values on a frequency grid. The distribution's vector is the selected parameters'
71
+ values, each flattened in C order, concatenated in name order. A parameter's own
72
+ shape is its declared shape: leading batch axes of a batched model stay batch
73
+ axes. Its raw value under the joint prior is its slice of the whitened vector,
74
+ reshaped to its shape, so it keeps one name however many values it holds.
75
+ Bounds are checked element by element, and fixing or tying the parameter raises,
76
+ as for a scalar.
77
+
78
+ The driving case is a port discrepancy (`CONTEXT.md`), whose values number
79
+ about $6 N_f$ for a reciprocal two-port, a few thousand. One parameter per value
80
+ would give it thousands of names and makes attaching and compiling the prior
81
+ slow (7 s and 52 s at 6000 scalars).
82
+
66
83
  ### Space
67
84
 
68
85
  `space` states the space the distribution is over: `'raw'`, `'declared'` or
@@ -130,6 +147,14 @@ CDF.
130
147
  - `prf.resolve` keeps a joint prior, since it is not a tie and does not change
131
148
  what the parameters are. `prf.unwrap` drops it, as it drops every prior.
132
149
 
150
+ ### A constant whitening log-determinant
151
+
152
+ Scoring in raw space adds the log-determinant of the whitening. When the
153
+ whitening's Jacobian is constant, as for a multivariate normal, that term is a
154
+ constant: it is computed once, when the prior is attached, and held by the joint
155
+ prior. Recomputing it as a dense Jacobian and its determinant on every call cost
156
+ 15.7 s per evaluation at 6000 values.
157
+
133
158
  ## Rejected options
134
159
 
135
160
  - **Polishing the collapsing `Probabilistic`** (keeping the names of the
@@ -148,6 +173,10 @@ CDF.
148
173
  - **`Transformed.icdf` in distreqx.** It would not be an inverse CDF, and it would
149
174
  put ParamRF's sampler logic in another repository.
150
175
 
176
+ - **A separate Gaussian-prior node for large arrays** (for a port discrepancy).
177
+ It would be a second way to attach a joint prior, which this ADR removed, and a
178
+ flow or Gaussian posterior over any array parameter needs the same thing.
179
+
151
180
  ## Consequences
152
181
 
153
182
  - The old `Probabilistic` constructor (`target=`, `static=`, tuple targets) and
@@ -0,0 +1,77 @@
1
+ Discrepancy Prediction
2
+ ======================
3
+
4
+ A fit with a Gaussian-process discrepancy learns how the model differs from the data, not just the model parameters. That discrepancy can be predicted at frequencies the fit never saw, with its uncertainty. In this example we fit the S11 of a 10 m coaxial line with a GP discrepancy, then predict the discrepancy off the fit grid.
5
+
6
+ Fitting with a Discrepancy
7
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
8
+
9
+ The measured S11 is a coaxial line plus a slow ripple that the model cannot produce, and a little measurement noise. We fit the dielectric constant, with a :class:`~pmrf.discrepancy_models.GaussianProcess` to absorb the ripple. Its kernel's length scale is in the fit frequency's unit, here MHz:
10
+
11
+ .. plot::
12
+ :context: reset
13
+ :include-source:
14
+ :nofigs:
15
+
16
+ import numpy as np
17
+ import pmrf as prf
18
+ from pmrf.models import CoaxialLine
19
+ from pmrf.materials import BulkConductor, ConstantDielectric
20
+ from pmrf.fitting import fit
21
+ from pmrf.likelihoods import GaussianLikelihood
22
+ from pmrf.discrepancy_models import GaussianProcess
23
+ from pmrf.covariance_kernels import Matern52Kernel
24
+
25
+ def coax(ep_r):
26
+ return CoaxialLine(
27
+ d_in=1.05e-3, d_out=3.2e-3,
28
+ dielectric=ConstantDielectric(ep_r=ep_r, tand=1e-3),
29
+ conductor=BulkConductor(sigma=1 / 1.7e-8),
30
+ length=10.0,
31
+ )
32
+
33
+ freq = prf.Frequency(10, 500, 100, 'MHz')
34
+ ripple = 0.01 * np.exp(2j * np.pi * freq.f_scaled / 150.0)
35
+ rng = np.random.default_rng(0)
36
+ noise = 1e-3 * (rng.normal(size=freq.npoints) + 1j * rng.normal(size=freq.npoints))
37
+ s11 = np.asarray(coax(1.40).s(freq)[:, 0, 0]) + ripple + noise
38
+
39
+ gp = GaussianProcess(
40
+ Matern52Kernel(prf.Bounded(10.0, 200.0, value=50.0)) * prf.Bounded(1e-5, 1e-3, value=1e-4)
41
+ )
42
+ result = fit(
43
+ coax(prf.Bounded(1.38, 1.42, value=1.39)), s11, frequency=freq, features='s11',
44
+ likelihood=GaussianLikelihood(prf.Bounded(1e-7, 1e-4, value=1e-6)),
45
+ discrepancy=gp,
46
+ )
47
+
48
+ Predicting the Discrepancy
49
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
50
+
51
+ The fitted :class:`~pmrf.evaluators.MarginalLogLikelihood`, with its optimized GP hyperparameters and noise, is the one term of the fit's objective. :meth:`~pmrf.evaluators.MarginalLogLikelihood.predict_discrepancy` conditions the GP on the fit's residuals and returns the distribution of the discrepancy at new frequencies. Here they extend past the fit band and are given in GHz; they are converted to the fit's unit first.
52
+
53
+ .. plot::
54
+ :context:
55
+ :include-source:
56
+
57
+ import matplotlib.pyplot as plt
58
+
59
+ (term,) = result.solution.objective
60
+ mll = term.evaluator.evaluator
61
+
62
+ new_freq = prf.Frequency(0.005, 0.6, 300, 'GHz')
63
+ delta = mll.predict_discrepancy(result.model, freq, new_freq)
64
+
65
+ mean = delta.mean()
66
+ std = np.sqrt(np.diagonal(delta.covariance(), axis1=-2, axis2=-1))
67
+
68
+ fig, axes = plt.subplots(2, 1, sharex=True)
69
+ for ax, m, s, r, part in zip(axes, mean, std, (ripple.real, ripple.imag), ('Re', 'Im')):
70
+ ax.fill_between(new_freq.f_scaled * 1e3, m - s, m + s, alpha=0.3, label=r'$\pm 1\sigma$')
71
+ ax.plot(new_freq.f_scaled * 1e3, m, label='predicted mean')
72
+ ax.plot(freq.f_scaled, r, 'k.', markersize=3, label='true ripple')
73
+ ax.set_ylabel(rf'{part} $\delta$')
74
+ axes[0].legend()
75
+ axes[1].set_xlabel('Frequency (MHz)')
76
+
77
+ The prediction is in event space: one block for the real part of S11 and one for the imaginary part, with frequency last. It is of the discrepancy alone, and excludes measurement noise. Inside the fit band it follows the ripple closely; beyond it, the mean relaxes towards zero and the uncertainty grows back to the GP's prior.
@@ -9,6 +9,8 @@ This chapter includes some basic examples to demonstrate ParamRF's core features
9
9
  cascading_and_terminating
10
10
  circuit_models
11
11
  model_optimization
12
+ discrepancy_prediction
13
+ port_discrepancy
12
14
  derivatives_and_sweeps
13
15
  parameter_naming_and_model_manipulation
14
16
  custom_models
@@ -0,0 +1,273 @@
1
+ """Port discrepancy from a reference two-port to a reflection-only transfer fit.
2
+
3
+ Run with ``python docs/examples/port_discrepancy.py --n-frequency 1000``.
4
+ """
5
+ import argparse
6
+ from time import perf_counter
7
+
8
+ import equinox as eqx
9
+ import jax
10
+ import jax.numpy as jnp
11
+ import numpy as np
12
+ from distreqx.bijectors import AbstractBijector
13
+ from distreqx.distributions import Normal
14
+ from scipy.optimize import minimize
15
+
16
+ import pmrf as prf
17
+ from pmrf.covariance_kernels import Matern52Kernel
18
+ from pmrf.discrepancy_models import GaussianProcess
19
+ from pmrf.evaluators import MarginalLogLikelihood
20
+ from pmrf.likelihoods import GaussianLikelihood
21
+ from pmrf.linearization import posterior_covariance
22
+ from pmrf.models import GridPortDiscrepancy, Load, PortCorrected, RLGCLine, SModel
23
+
24
+ jax.config.update('jax_enable_x64', True)
25
+
26
+
27
+ def line(resistance):
28
+ return RLGCLine(length=10.0, R=resistance, L=250e-9, C=100e-12, G=1e-6)
29
+
30
+
31
+ @prf.derived
32
+ def fitted_line(base, *, log_R):
33
+ return prf.replace(base, R=jnp.exp(log_R))
34
+
35
+
36
+ def truth(frequency):
37
+ f = frequency.f_scaled
38
+ # Skin-effect R and a small inductive dispersion absent from the fit model.
39
+ dispersive = RLGCLine(length=10.0, R=0.5 * jnp.sqrt(f / 250),
40
+ L=250e-9 * (1 + 0.0005 * jnp.log(f / 250)), C=100e-12, G=1e-6)
41
+ return SModel(dispersive.s(frequency), frequency, 50.0)
42
+
43
+
44
+ class AbstractPortEventTransform(AbstractBijector, strict=True):
45
+ """Example-local event transform conditioned on a predicted response."""
46
+
47
+ #: Prediction selecting the local unwrapped logarithm branch.
48
+ predicted: jax.Array
49
+ #: Log-transmission derivatives depend on the response.
50
+ _is_constant_jacobian: bool = eqx.field(static=True, init=False, default=False)
51
+ #: The log determinant depends on transmission magnitude.
52
+ _is_constant_log_det: bool = eqx.field(static=True, init=False, default=False)
53
+
54
+ def inverse_log_det_jacobian(self, event):
55
+ return -self.forward_log_det_jacobian(self.inverse(event))
56
+
57
+ def forward_and_log_det(self, s):
58
+ return self.forward(s), self.forward_log_det_jacobian(s)
59
+
60
+ def inverse_and_log_det(self, event):
61
+ return self.inverse(event), self.inverse_log_det_jacobian(event)
62
+
63
+ def same_as(self, other):
64
+ return self is other
65
+
66
+
67
+ class PortEventTransform(AbstractPortEventTransform, strict=True):
68
+ r"""Invertible full two-port transform, with frequency last in event space.
69
+
70
+ **Mathematical Formulation**
71
+
72
+ $$h(\widetilde S; S)=\ln|\widetilde S| + i[\operatorname{unwrap}(\arg S)
73
+ + \operatorname{unwrap}(\arg(\widetilde S/S))].$$
74
+
75
+ Reflections remain additive. Directional transmission logs become their
76
+ mean and half-difference. Each complex log contributes
77
+ $$-2\ln|\widetilde S|$$ to the forward log determinant, and the rotation
78
+ contributes $$-2\ln 2$$ per frequency.
79
+ """
80
+
81
+ def forward(self, s):
82
+ anchor = jnp.unwrap(jnp.angle(self.predicted), axis=0)
83
+ phase = anchor + jnp.unwrap(jnp.angle(s / self.predicted), axis=0)
84
+ logs = jnp.log(jnp.abs(s)) + 1j * phase
85
+ blocks = jnp.stack((s[:, 0, 0], s[:, 1, 1],
86
+ (logs[:, 1, 0] + logs[:, 0, 1]) / 2,
87
+ (logs[:, 1, 0] - logs[:, 0, 1]) / 2))
88
+ return jnp.stack((blocks.real, blocks.imag), axis=1)
89
+
90
+ def inverse(self, event):
91
+ blocks = event[:, 0] + 1j * event[:, 1]
92
+ return jnp.stack((jnp.stack((blocks[0], jnp.exp(blocks[2] - blocks[3])), axis=-1),
93
+ jnp.stack((jnp.exp(blocks[2] + blocks[3]), blocks[1]), axis=-1)), axis=-2)
94
+
95
+ def forward_log_det_jacobian(self, s):
96
+ # The mean/half-difference rotation has determinant 1/2 for each Re/Im pair.
97
+ return -2 * jnp.log(jnp.abs(s[:, 0, 1])) - 2 * jnp.log(jnp.abs(s[:, 1, 0])) - 2 * jnp.log(2.0)
98
+
99
+
100
+ class ReciprocalEventTransform(AbstractPortEventTransform, strict=True):
101
+ r"""Transform the retained reflections and symmetric transmission only.
102
+
103
+ **Mathematical Formulation**
104
+
105
+ The symmetric transmission uses the anchored logarithm of
106
+ :class:`PortEventTransform`, with forward log determinant
107
+ $$-2\ln|\widetilde S^s|.$$ Reflections remain additive.
108
+ """
109
+
110
+ def forward(self, s):
111
+ anchor = jnp.unwrap(jnp.angle(self.predicted[:, 2]))
112
+ log_t = jnp.log(jnp.abs(s[:, 2])) + 1j * (
113
+ anchor + jnp.unwrap(jnp.angle(s[:, 2] / self.predicted[:, 2])))
114
+ blocks = jnp.stack((s[:, 0], s[:, 1], log_t))
115
+ return jnp.stack((blocks.real, blocks.imag), axis=1)
116
+
117
+ def inverse(self, event):
118
+ blocks = event[:, 0] + 1j * event[:, 1]
119
+ return jnp.stack((blocks[0], blocks[1], jnp.exp(blocks[2])), axis=-1)
120
+
121
+ def forward_log_det_jacobian(self, s):
122
+ return -2 * jnp.log(jnp.abs(s[:, 2]))
123
+
124
+
125
+ def reciprocal_predictor(model, frequency):
126
+ s = model.s(frequency)
127
+ return jnp.stack((s[:, 0, 0], s[:, 1, 1], s[:, 1, 0]), axis=-1)
128
+
129
+
130
+ def reference_fit(mll, model, frequency):
131
+ """Fit the reference line's one log-resistance coordinate."""
132
+ def loss(vector):
133
+ candidate = prf.update(model, {'log_R': vector[0]})
134
+ return -mll(candidate, frequency) - prf.log_prior(candidate)
135
+
136
+ value_gradient = jax.jit(jax.value_and_grad(loss))
137
+ result = minimize(lambda x: tuple(np.asarray(v) for v in value_gradient(jnp.asarray(x))),
138
+ np.array([prf.values(model)['log_R']]), jac=True, method='L-BFGS-B',
139
+ bounds=[(np.log(0.05), np.log(2.0))],
140
+ options={'gtol': 1e-6, 'ftol': 1e-14, 'maxiter': 200})
141
+ if not np.isfinite(result.fun) or np.linalg.norm(result.jac, ord=np.inf) > 1e-3:
142
+ raise RuntimeError(f'MAP fit failed: {result.message}; gradient {result.jac}')
143
+ return prf.update(model, {'log_R': jnp.asarray(result.x[0])})
144
+
145
+
146
+ def run(n_frequency=1000, n_transfer=None, mc_samples=10000):
147
+ """Run the complete recipe, returning diagnostics and synchronized timings."""
148
+ n_transfer = n_frequency if n_transfer is None else n_transfer
149
+ timings = {}
150
+ before = perf_counter()
151
+
152
+ def finish(name, value):
153
+ nonlocal before
154
+ jax.block_until_ready(value)
155
+ now = perf_counter()
156
+ timings[name] = now - before
157
+ before = now
158
+ print(f'{name}: {timings[name]:.3f} s', flush=True)
159
+
160
+ frequency = prf.Frequency(10, 500, n_frequency, 'MHz')
161
+ transfer_frequency = prf.Frequency(10.1, 499.9, n_transfer, 'MHz')
162
+ true_s = truth(frequency).s(frequency)
163
+ rng = np.random.default_rng(265)
164
+ sigma = 0.002
165
+ observed = true_s + sigma * (rng.normal(size=true_s.shape) + 1j * rng.normal(size=true_s.shape))
166
+ initial = fitted_line(line(0.5), log_R=prf.Unconstrained(jnp.log(0.5)))
167
+ initial = prf.prior(initial, 'log_R', Normal(jnp.log(0.5), 0.02))
168
+ full_event = PortEventTransform(initial.s(frequency)).forward(observed)
169
+ # Discard only the antisymmetric block, then encode the symmetric log in S space.
170
+ retained = full_event[:3, 0] + 1j * full_event[:3, 1]
171
+ reciprocal_observed = jnp.stack((retained[0], retained[1], jnp.exp(retained[2])), axis=-1)
172
+ variance = jnp.ones((3, 2, n_frequency)) * sigma**2
173
+ variance = variance.at[2].set(sigma**2 / (2 * jnp.abs(reciprocal_observed[:, 2])**2))
174
+ reference = MarginalLogLikelihood(
175
+ predictor=reciprocal_predictor, observed=reciprocal_observed,
176
+ likelihood=GaussianLikelihood(variance),
177
+ discrepancy=GaussianProcess(Matern52Kernel(100.0) * 0.05**2, jitter=1e-8),
178
+ event_transform=ReciprocalEventTransform,
179
+ )
180
+ finish('synthetic data and event transform', reciprocal_observed)
181
+ fitted = reference_fit(reference, initial, frequency)
182
+ finish('reference MAP', fitted.s(frequency))
183
+ event = ReciprocalEventTransform(reciprocal_predictor(fitted, frequency))
184
+ log_residual = event.forward(reciprocal_observed)[2] - event.forward(reciprocal_predictor(fitted, frequency))[2]
185
+ additive_residual = true_s[:, 1, 0] - fitted.s(frequency)[:, 1, 0]
186
+ names, joint = reference.predict_joint(fitted, frequency, transfer_frequency)
187
+ finish('joint prediction', joint.covariance())
188
+ corrected = PortCorrected(fitted, GridPortDiscrepancy(
189
+ prf.Unconstrained(joint.mean()[1:].reshape(3, 2, n_transfer)),
190
+ transfer_frequency, ('11', '22', 's21')))
191
+ corrected = prf.prior(corrected, [*names, 'discrepancy.values'], joint)
192
+ finish('joint prior attachment', prf.log_prior(corrected))
193
+ # Keep the cable as the base: its parameter names and joint prior survive.
194
+ @prf.derived
195
+ def terminated(cable, *, load):
196
+ return cable.terminated(Load(gamma=(load - 50) / (load + 50)))
197
+
198
+ transfer = terminated(corrected, load=prf.Unconstrained(75.0))
199
+ true_reflection = truth(transfer_frequency).terminated(Load(gamma=(76.0 - 50) / (76.0 + 50))).s(transfer_frequency)[:, 0, 0]
200
+ transfer_observed = true_reflection + sigma * (
201
+ rng.normal(size=n_transfer) + 1j * rng.normal(size=n_transfer))
202
+ transfer_mll = MarginalLogLikelihood(
203
+ predictor=lambda m, f: m.s(f)[:, 0, 0], observed=transfer_observed,
204
+ likelihood=GaussianLikelihood(sigma**2),
205
+ )
206
+ # Whiten the cable's joint prior, leaving the new load in ohms.
207
+ mean = joint.mean()
208
+ factor = jnp.linalg.cholesky(joint.covariance())
209
+
210
+ def from_white(x):
211
+ cable_vector = mean + factor @ x[:-1]
212
+ return prf.update(transfer, {'log_R': cable_vector[0],
213
+ 'discrepancy.values': cable_vector[1:].reshape(3, 2, n_transfer),
214
+ 'load': x[-1]})
215
+
216
+ def loss_white(x):
217
+ return -transfer_mll(from_white(x), transfer_frequency) + jnp.sum(x[:-1]**2) / 2
218
+
219
+ value_gradient = jax.jit(jax.value_and_grad(loss_white))
220
+ solution = minimize(lambda x: tuple(np.asarray(v) for v in value_gradient(jnp.asarray(x))),
221
+ np.r_[np.zeros(len(mean)), 75.0], jac=True, method='L-BFGS-B',
222
+ options={'maxiter': 300, 'gtol': 1e-5, 'ftol': 1e-12})
223
+ if not solution.success:
224
+ raise RuntimeError(f'Transfer fit failed: {solution.message}')
225
+ transfer_map = from_white(jnp.asarray(solution.x))
226
+ finish('transfer MAP', transfer_map.s(transfer_frequency))
227
+ linearization = transfer_mll.linearize(transfer_map, transfer_frequency)
228
+ covariance = posterior_covariance([linearization], transfer_map)
229
+ finish('transfer linearisation and posterior', covariance)
230
+
231
+ # QoI belongs to the unterminated corrected cable; the fitted load does not enter it.
232
+ def qoi(model):
233
+ cable = model.operands[0]
234
+ return jnp.mean(jnp.abs(cable.s(transfer_frequency)[:, 1, 0])**2)
235
+
236
+ (gradient,) = prf.derivative(qoi, transfer_map)
237
+ gradient_values = prf.values(gradient)
238
+ J_q = jnp.concatenate([jnp.ravel(gradient_values[name]) for name in linearization.names])
239
+ q_variance = J_q @ covariance @ J_q
240
+ J = linearization.J.reshape(-1, covariance.shape[0])
241
+ # Independent real/imaginary reflection noise makes Sigma_D diagonal here.
242
+ G = covariance @ J.T / sigma**2
243
+ R = G @ J
244
+ P_q = J_q @ (jnp.eye(len(J_q)) - R)
245
+ G_q = J_q @ G
246
+ finish('QoI and gain diagnostics', (q_variance, P_q, G_q))
247
+ parameter_mean = jnp.concatenate([jnp.ravel(prf.values(transfer_map)[name]) for name in linearization.names])
248
+ posterior_factor = jnp.linalg.cholesky(covariance)
249
+ evaluate = jax.jit(jax.vmap(lambda x: qoi(prf.update(transfer_map, linearization.unflatten(x)))))
250
+ # Batch both sampling and evaluation: 10^4 by 6002 need not stay resident.
251
+ q_samples = []
252
+ for start in range(0, mc_samples, 100):
253
+ count = min(100, mc_samples - start)
254
+ draws = parameter_mean + jnp.asarray(rng.normal(size=(count, len(parameter_mean)))) @ posterior_factor.T
255
+ q_samples.append(np.asarray(evaluate(draws)))
256
+ mc_std = np.std(np.concatenate(q_samples), ddof=1)
257
+ delta_std = float(jnp.sqrt(q_variance))
258
+ finish('Monte Carlo push-forward', q_variance)
259
+ relative_std_error = abs(mc_std / delta_std - 1)
260
+ print(f'QoI {float(qoi(transfer_map)):.6f}; linear std {delta_std:.6g}; '
261
+ f'MC std {mc_std:.6g}; relative std error {relative_std_error:.2%}', flush=True)
262
+ return dict(timings=timings, relative_std_error=relative_std_error,
263
+ frequency=frequency, additive_residual=additive_residual,
264
+ log_residual=log_residual, P_q=P_q, G_q=G_q,
265
+ fitted_load=float(prf.values(transfer_map)['load']),
266
+ delta_std=delta_std, mc_std=mc_std)
267
+
268
+
269
+ if __name__ == '__main__':
270
+ parser = argparse.ArgumentParser(description=__doc__)
271
+ parser.add_argument('--n-frequency', type=int, default=1000)
272
+ args = parser.parse_args()
273
+ run(args.n_frequency)
@@ -0,0 +1,81 @@
1
+ Port Discrepancy Transfer
2
+ ===========================
3
+
4
+ A port discrepancy learned from a two-port reference fit can inform a reflection-only transfer fit. The joint prediction retains the correlation between the cable parameters and its discrepancy. Attach that prediction to :class:`~pmrf.models.PortCorrected` before terminating the cable; the correction then changes the reflection seen through the load as well as the cable's transmission.
5
+
6
+ The runnable :download:`example <port_discrepancy.py>` uses a synthetic 10 m line over 10–500 MHz. The truth has skin-effect resistance and a weak frequency dependence in its inductance. The fit uses constant RLGC, with resistance parameterised by an unconstrained ``log_R`` and a docs-local :func:`~pmrf.derived` constructor. This coordinate admits a Gaussian joint prior while keeping physical resistance positive. The reference fit has a 2% prior standard deviation on log-resistance, representing a separate resistance calibration. This limits the resistance–discrepancy confounding and keeps the subsequent local Gaussian approximation useful.
7
+
8
+ Log Transmission Event Space
9
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10
+
11
+ Reflection errors are additive. A cable's transmission phase winds many times over the band, so an additive transmission residual oscillates on the inverse-delay scale. A log-ratio residual removes that winding. The example defines its own bijectors, using the ``AbstractBijector`` interface available in released distreqx. For predicted transmission $S$ and observed transmission $\widetilde S$, the transform uses
12
+
13
+ $$h(\widetilde S;S)=\ln|\widetilde S| + i\left[\operatorname{unwrap}(\arg S)+\operatorname{unwrap}\left(\arg\frac{\widetilde S}{S}\right)\right].$$
14
+
15
+ Subtracting $h(S;S)$ gives the unwrapped complex log ratio. Unwrap the ratio along frequency, rather than independently unwrapping the two transmissions. Their initial ratio phase selects the local branch; transmissions must be nonzero and the frequency spacing must resolve their phase. The forward log determinant is $-2\ln|\widetilde S|$ per complex transmission entry.
16
+
17
+ .. literalinclude:: port_discrepancy.py
18
+ :pyobject: PortEventTransform
19
+
20
+ The symmetric transmission block is the mean of the two directional logs; the antisymmetric block is their half-difference. This rotation adds the constant $-2\ln2$ to the log determinant per frequency. The full ``PortEventTransform`` is an invertible, example-local subclass of distreqx's ``AbstractBijector``. For this reciprocal model, the antisymmetric residual has no parameter sensitivity and is independent of the retained blocks in the first-order, equal-directional-noise approximation, so the fit discards it before constructing a reduced, invertible three-block transform. The discarded likelihood and rotation determinant are constants for the reference parameters.
21
+
22
+ .. literalinclude:: port_discrepancy.py
23
+ :pyobject: ReciprocalEventTransform
24
+
25
+ Each directional transmission has noise variance $\sigma^2/\lvert S\rvert^2$ in log space to first order. Averaging independent directional measurements halves that variance. The example fixes this approximation at the observed symmetric transmission, while each additive reflection retains variance $\sigma^2$. This is a small-noise approximation, with GP hyperparameters held fixed.
26
+
27
+ Reference Fit and Joint Handoff
28
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29
+
30
+ The reference likelihood uses a block-independent Matérn 5/2 GP with discrepancy amplitude 0.05 and length scale 100 MHz. Its event layout is ``(block, real_or_imaginary, frequency)``, with blocks ``('11', '22', 's21')``. The fitted additive S21 residual oscillates while the log residual changes slowly:
31
+
32
+ .. plot::
33
+ :include-source:
34
+
35
+ import importlib.util
36
+ from pathlib import Path
37
+ import matplotlib.pyplot as plt
38
+
39
+ path = Path('port_discrepancy.py')
40
+ spec = importlib.util.spec_from_file_location('port_discrepancy_example', path)
41
+ example = importlib.util.module_from_spec(spec)
42
+ spec.loader.exec_module(example)
43
+ result = example.run(n_frequency=150, n_transfer=30)
44
+ frequency = result['frequency'].f_scaled
45
+ fig, axes = plt.subplots(2, 1, sharex=True)
46
+ axes[0].plot(frequency, result['additive_residual'].real)
47
+ axes[0].set_ylabel('Re additive S21 residual')
48
+ axes[1].plot(frequency, result['log_residual'][0])
49
+ axes[1].set_ylabel('Re log-ratio residual')
50
+ axes[1].set_xlabel('Frequency (MHz)')
51
+
52
+ :meth:`~pmrf.evaluators.MarginalLogLikelihood.predict_joint` returns a Gaussian over ``[log_R; discrepancy]``, with discrepancy flattened in that event order on the transfer grid. Its cross-covariance must survive the handoff:
53
+
54
+ .. code-block:: python
55
+
56
+ names, joint = reference.predict_joint(fitted, frequency, transfer_frequency)
57
+ corrected = PortCorrected(fitted, GridPortDiscrepancy(
58
+ prf.Unconstrained(joint.mean()[1:].reshape(3, 2, n_transfer)),
59
+ transfer_frequency, ('11', '22', 's21')))
60
+ corrected = prf.prior(corrected, [*names, 'discrepancy.values'], joint)
61
+
62
+ The transfer fit terminates this corrected cable in a load whose resistance is a new parameter. It observes S11 only. The example optimises the cable parameters in whitened joint-prior coordinates, then linearises the transfer likelihood in declared coordinates. The dense prior costs memory quadratically and dense posterior operations cost time cubically in the number of transfer points. The plot uses a smaller transfer grid; the standalone command exercises 1000 points in both grids and reports synchronized timings:
63
+
64
+ .. code-block:: bash
65
+
66
+ python docs/examples/port_discrepancy.py --n-frequency 1000
67
+
68
+ Quantity of Interest and Diagnostics
69
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
70
+
71
+ The quantity of interest $q$ is band-mean $|S_{21}|^2$ of the corrected, unterminated cable on the transfer grid. The fitted load informs the cable through the posterior, though $q$ has no direct load dependence. With transfer parameters $\vartheta$, :func:`~pmrf.derivative` supplies $J_q$, and :func:`~pmrf.linearization.posterior_covariance` supplies $\Sigma_\vartheta$:
72
+
73
+ $$\Sigma_q=J_q\Sigma_\vartheta J_q^\top.$$
74
+
75
+ The diagnostic recipe uses the transfer linearisation's event Jacobian $J$ and data covariance $\Sigma_D$:
76
+
77
+ $$G=\Sigma_\vartheta J^\top\Sigma_D^{-1},\qquad R=GJ,\qquad P_q=J_q(I-R),\qquad G_q=J_qG.$$
78
+
79
+ Here $\Sigma_D=\sigma^2 I$: the discrepancy is an explicit parameter with a joint prior, rather than another marginal GP in the transfer likelihood. ``P_q`` measures the QoI sensitivity left to the prior; ``G_q`` maps small reflection-data changes to QoI changes. Both are local diagnostics at the transfer MAP.
80
+
81
+ The script compares the propagated standard deviation with a nonlinear push-forward of 10,000 draws from $N(\hat\vartheta,\Sigma_\vartheta)$, evaluated in batches. The 5% standard-deviation criterion applies at amplitude 0.05; stronger log corrections can make the exponential nonlinearity too large for this linear approximation.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramrf
3
- Version: 0.36.4
3
+ Version: 0.36.5
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
7
7
  Description-Content-Type: text/x-rst
8
8
  License-File: LICENSE
9
9
  License-File: NOTICE
10
- Requires-Dist: parax>=0.11.5
10
+ Requires-Dist: parax>=0.11.6
11
11
  Requires-Dist: threadpoolctl
12
12
  Requires-Dist: eqxpress
13
13
  Requires-Dist: jax