paramrf 0.35.2__tar.gz → 0.35.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 (238) hide show
  1. {paramrf-0.35.2 → paramrf-0.35.4}/AGENTS.md +14 -1
  2. {paramrf-0.35.2 → paramrf-0.35.4}/CLAUDE.md +14 -1
  3. {paramrf-0.35.2 → paramrf-0.35.4}/CONTEXT.md +40 -3
  4. {paramrf-0.35.2 → paramrf-0.35.4}/PKG-INFO +1 -1
  5. {paramrf-0.35.2 → paramrf-0.35.4}/docs/adr/0002-parameter-api.md +16 -7
  6. paramrf-0.35.4/docs/adr/0003-derived-models.md +80 -0
  7. paramrf-0.35.4/docs/adr/0004-profiled-lines.md +214 -0
  8. {paramrf-0.35.2 → paramrf-0.35.4}/docs/api/index.rst +2 -0
  9. {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/parameter_names.rst +40 -0
  10. {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/PKG-INFO +1 -1
  11. {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/SOURCES.txt +8 -0
  12. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/__init__.py +4 -0
  13. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/__init__.py +16 -1
  14. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/__init__.py +2 -1
  15. paramrf-0.35.4/pmrf/models/adapters/derived.py +144 -0
  16. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/__init__.py +6 -0
  17. paramrf-0.35.4/pmrf/models/components/lines/profiles.py +341 -0
  18. paramrf-0.35.4/pmrf/models/composite/interconnected/cascade.py +390 -0
  19. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/parameters.py +217 -26
  20. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/rf/__init__.py +7 -0
  21. paramrf-0.35.4/pmrf/rf/cascade.py +208 -0
  22. {paramrf-0.35.2 → paramrf-0.35.4}/pyproject.toml +1 -1
  23. paramrf-0.35.4/tests/test_models/test_derived.py +234 -0
  24. paramrf-0.35.4/tests/test_models/test_profiles.py +357 -0
  25. paramrf-0.35.4/tests/test_models/test_repeated_cascade.py +274 -0
  26. paramrf-0.35.4/tests/test_structural_updates.py +607 -0
  27. paramrf-0.35.2/pmrf/models/composite/interconnected/cascade.py +0 -250
  28. paramrf-0.35.2/tests/test_structural_updates.py +0 -235
  29. {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/docs.yml +0 -0
  30. {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/draft-pdf.yml +0 -0
  31. {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/publish.yml +0 -0
  32. {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/tests.yml +0 -0
  33. {paramrf-0.35.2 → paramrf-0.35.4}/.gitignore +0 -0
  34. {paramrf-0.35.2 → paramrf-0.35.4}/CHANGELOG.md +0 -0
  35. {paramrf-0.35.2 → paramrf-0.35.4}/CITATION.cff +0 -0
  36. {paramrf-0.35.2 → paramrf-0.35.4}/CONTRIBUTING.md +0 -0
  37. {paramrf-0.35.2 → paramrf-0.35.4}/LICENSE +0 -0
  38. {paramrf-0.35.2 → paramrf-0.35.4}/NOTICE +0 -0
  39. {paramrf-0.35.2 → paramrf-0.35.4}/README.rst +0 -0
  40. {paramrf-0.35.2 → paramrf-0.35.4}/assets/logo.png +0 -0
  41. {paramrf-0.35.2 → paramrf-0.35.4}/docs/Makefile +0 -0
  42. {paramrf-0.35.2 → paramrf-0.35.4}/docs/_static/custom.css +0 -0
  43. {paramrf-0.35.2 → paramrf-0.35.4}/docs/_templates/autosummary/class.rst +0 -0
  44. {paramrf-0.35.2 → paramrf-0.35.4}/docs/_templates/autosummary/function.rst +0 -0
  45. {paramrf-0.35.2 → paramrf-0.35.4}/docs/_templates/autosummary/module.rst +0 -0
  46. {paramrf-0.35.2 → paramrf-0.35.4}/docs/adr/0001-line-modelling-architecture.md +0 -0
  47. {paramrf-0.35.2 → paramrf-0.35.4}/docs/agents/domain.md +0 -0
  48. {paramrf-0.35.2 → paramrf-0.35.4}/docs/agents/issue-tracker.md +0 -0
  49. {paramrf-0.35.2 → paramrf-0.35.4}/docs/agents/triage-labels.md +0 -0
  50. {paramrf-0.35.2 → paramrf-0.35.4}/docs/conf.py +0 -0
  51. {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/core_primitives.rst +0 -0
  52. {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/index.rst +0 -0
  53. {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/jax_overview.rst +0 -0
  54. {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/optimization_and_inference.rst +0 -0
  55. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/cascading_and_terminating.rst +0 -0
  56. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/circuit_clc.png +0 -0
  57. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/circuit_models.rst +0 -0
  58. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/custom_models.rst +0 -0
  59. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/derivatives_and_sweeps.rst +0 -0
  60. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/index.rst +0 -0
  61. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/model_optimization.rst +0 -0
  62. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
  63. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
  64. {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/shared_substrates.rst +0 -0
  65. {paramrf-0.35.2 → paramrf-0.35.4}/docs/index.rst +0 -0
  66. {paramrf-0.35.2 → paramrf-0.35.4}/docs/license.rst +0 -0
  67. {paramrf-0.35.2 → paramrf-0.35.4}/docs/make.bat +0 -0
  68. {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/conductor-loss-alternatives.md +0 -0
  69. {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/holloway1994.pdf +0 -0
  70. {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/microstrip-loss-conventions.md +0 -0
  71. {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
  72. {paramrf-0.35.2 → paramrf-0.35.4}/docs/skrf_comparison/index.rst +0 -0
  73. {paramrf-0.35.2 → paramrf-0.35.4}/docs/skrf_comparison/overview.rst +0 -0
  74. {paramrf-0.35.2 → paramrf-0.35.4}/docs/skrf_comparison/performance.rst +0 -0
  75. {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/1_cable_fitting.ipynb +0 -0
  76. {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
  77. {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
  78. {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
  79. {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/index.rst +0 -0
  80. {paramrf-0.35.2 → paramrf-0.35.4}/paper/paper.bib +0 -0
  81. {paramrf-0.35.2 → paramrf-0.35.4}/paper/paper.md +0 -0
  82. {paramrf-0.35.2 → paramrf-0.35.4}/paper/rlc.png +0 -0
  83. {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/dependency_links.txt +0 -0
  84. {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/requires.txt +0 -0
  85. {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/top_level.txt +0 -0
  86. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/_solver_view.py +0 -0
  87. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/bijectors.py +0 -0
  88. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/constraints.py +0 -0
  89. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/covariance_kernels.py +0 -0
  90. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/discrepancy_models.py +0 -0
  91. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/distributions.py +0 -0
  92. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/evaluators.py +0 -0
  93. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/__init__.py +0 -0
  94. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/minimize.py +0 -0
  95. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/result.py +0 -0
  96. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/routers.py +0 -0
  97. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/sample.py +0 -0
  98. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/targets.py +0 -0
  99. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/frequency.py +0 -0
  100. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/__init__.py +0 -0
  101. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/base.py +0 -0
  102. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/result.py +0 -0
  103. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/sample.py +0 -0
  104. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/solvers/__init__.py +0 -0
  105. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/solvers/blackjax.py +0 -0
  106. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/solvers/polychord.py +0 -0
  107. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/likelihoods.py +0 -0
  108. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/losses.py +0 -0
  109. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/__init__.py +0 -0
  110. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/conductor.py +0 -0
  111. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/dielectric.py +0 -0
  112. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/properties.py +0 -0
  113. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/roughness.py +0 -0
  114. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/substrate.py +0 -0
  115. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/surface_impedance.py +0 -0
  116. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/__init__.py +0 -0
  117. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/aggregations.py +0 -0
  118. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/bessel.py +0 -0
  119. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/conversions.py +0 -0
  120. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/losses.py +0 -0
  121. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/misc.py +0 -0
  122. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/base.py +0 -0
  123. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/bridge.py +0 -0
  124. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/callable.py +0 -0
  125. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/delegated.py +0 -0
  126. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/static.py +0 -0
  127. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/wrapped.py +0 -0
  128. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/base.py +0 -0
  129. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/__init__.py +0 -0
  130. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/ideal.py +0 -0
  131. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/base.py +0 -0
  132. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/coaxial.py +0 -0
  133. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/empirical.py +0 -0
  134. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/ideal.py +0 -0
  135. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/microstrip.py +0 -0
  136. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/nodal.py +0 -0
  137. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/nonuniform.py +0 -0
  138. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/planar.py +0 -0
  139. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/stripline.py +0 -0
  140. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lumped.py +0 -0
  141. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/sections.py +0 -0
  142. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/__init__.py +0 -0
  143. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/__init__.py +0 -0
  144. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
  145. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
  146. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/circuit.py +0 -0
  147. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
  148. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
  149. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
  150. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/terminated.py +0 -0
  151. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/nodal.py +0 -0
  152. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/topological.py +0 -0
  153. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/transformed.py +0 -0
  154. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/surrogates/__init__.py +0 -0
  155. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/surrogates/expansion.py +0 -0
  156. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/surrogates/rational.py +0 -0
  157. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/modules/__init__.py +0 -0
  158. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/modules/base.py +0 -0
  159. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/modules/wrapped.py +0 -0
  160. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/network_collection.py +0 -0
  161. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/noise_models.py +0 -0
  162. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/__init__.py +0 -0
  163. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/base.py +0 -0
  164. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/minimize.py +0 -0
  165. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/result.py +0 -0
  166. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/__init__.py +0 -0
  167. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/jaxopt.py +0 -0
  168. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/optimistix.py +0 -0
  169. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/scipy.py +0 -0
  170. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/problems.py +0 -0
  171. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/rf/conversions.py +0 -0
  172. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/rf/mna.py +0 -0
  173. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/serialization.py +0 -0
  174. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/terms.py +0 -0
  175. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/types.py +0 -0
  176. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/__init__.py +0 -0
  177. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/array.py +0 -0
  178. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/debug.py +0 -0
  179. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/network.py +0 -0
  180. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/random.py +0 -0
  181. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/rf.py +0 -0
  182. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/transforms.py +0 -0
  183. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/tree.py +0 -0
  184. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/type.py +0 -0
  185. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/viz/__init__.py +0 -0
  186. {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/viz/plots.py +0 -0
  187. {paramrf-0.35.2 → paramrf-0.35.4}/scripts/install-test-deps.sh +0 -0
  188. {paramrf-0.35.2 → paramrf-0.35.4}/setup.cfg +0 -0
  189. {paramrf-0.35.2 → paramrf-0.35.4}/tests/__init__.py +0 -0
  190. {paramrf-0.35.2 → paramrf-0.35.4}/tests/_dependency_checks.py +0 -0
  191. {paramrf-0.35.2 → paramrf-0.35.4}/tests/_jit.py +0 -0
  192. {paramrf-0.35.2 → paramrf-0.35.4}/tests/conftest.py +0 -0
  193. {paramrf-0.35.2 → paramrf-0.35.4}/tests/data/10m_cable.s2p +0 -0
  194. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_autodiff.py +0 -0
  195. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_conversions.py +0 -0
  196. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_covariance_kernels.py +0 -0
  197. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_dc_limits.py +0 -0
  198. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_evaluators.py +0 -0
  199. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_minimize.py +0 -0
  200. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_routers.py +0 -0
  201. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_sample.py +0 -0
  202. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_targets.py +0 -0
  203. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_frequency.py +0 -0
  204. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_infer_base.py +0 -0
  205. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_infer_sample.py +0 -0
  206. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_map_priors.py +0 -0
  207. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_conductor.py +0 -0
  208. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_dielectric.py +0 -0
  209. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_serialization.py +0 -0
  210. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_substrate.py +0 -0
  211. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_surface_impedance.py +0 -0
  212. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_math/test_bessel.py +0 -0
  213. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_model.py +0 -0
  214. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_adapters.py +0 -0
  215. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_circuit_nodal.py +0 -0
  216. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_circuit_port_order.py +0 -0
  217. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_circuit_scattering.py +0 -0
  218. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_current_distribution.py +0 -0
  219. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_interconnected.py +0 -0
  220. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_lines.py +0 -0
  221. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_lines_skrf_matrix.py +0 -0
  222. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_lumped.py +0 -0
  223. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_nodal.py +0 -0
  224. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_sections.py +0 -0
  225. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_transformed.py +0 -0
  226. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_transformers.py +0 -0
  227. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_module.py +0 -0
  228. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_naming.py +0 -0
  229. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_optimize_base.py +0 -0
  230. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_optimize_minimize.py +0 -0
  231. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_parameters.py +0 -0
  232. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_parameters_by_name.py +0 -0
  233. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_raw_space_solving.py +0 -0
  234. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_serialization.py +0 -0
  235. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_terms.py +0 -0
  236. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_transforms.py +0 -0
  237. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_utils/test_compress.py +0 -0
  238. {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_utils/test_tree.py +0 -0
@@ -8,7 +8,20 @@ dataclasses that are also JAX PyTrees. Built on `jax`, `equinox`, and
8
8
  ## Breaking changes are acceptable
9
9
 
10
10
  The library is pre-1.0 and moves quickly. Do not add backwards-compatibility shims,
11
- deprecation aliases, or legacy code paths unless explicitly asked. Prefer the clean design.
11
+ deprecation aliases, or legacy code paths unless explicitly asked. Prefer the clean design,
12
+ within the seam the ticket names.
13
+
14
+ ## Seams
15
+
16
+ A ticket names one seam, either a file, a set of files or a folder. If a cross-seam change is needed, implmentation should be stopped and decision left to the human.
17
+
18
+ `docs/adr/` and `CONTEXT.md` change in their own commit, by the reviewer. An
19
+ implementation reaching for a word absent from the ticket, the ADR and `CONTEXT.md` has
20
+ reached a decision it does not have: ask.
21
+
22
+ Open every PR body with `## Cross-seam`: each file touched outside the ticket's seam,
23
+ with the ADR or issue line sanctioning it, or `DECISION NEEDED`. Write `none` when there
24
+ are none.
12
25
 
13
26
  ## Commands
14
27
 
@@ -8,7 +8,20 @@ dataclasses that are also JAX PyTrees. Built on `jax`, `equinox`, and
8
8
  ## Breaking changes are acceptable
9
9
 
10
10
  The library is pre-1.0 and moves quickly. Do not add backwards-compatibility shims,
11
- deprecation aliases, or legacy code paths unless explicitly asked. Prefer the clean design.
11
+ deprecation aliases, or legacy code paths unless explicitly asked. Prefer the clean design,
12
+ within the seam the ticket names.
13
+
14
+ ## Seams
15
+
16
+ A ticket names one seam, either a file, a set of files or a folder. If a cross-seam change is needed, implmentation should be stopped and decision left to the human.
17
+
18
+ `docs/adr/` and `CONTEXT.md` change in their own commit, by the reviewer. An
19
+ implementation reaching for a word absent from the ticket, the ADR and `CONTEXT.md` has
20
+ reached a decision it does not have: ask.
21
+
22
+ Open every PR body with `## Cross-seam`: each file touched outside the ticket's seam,
23
+ with the ADR or issue line sanctioning it, or `DECISION NEEDED`. Write `none` when there
24
+ are none.
12
25
 
13
26
  ## Commands
14
27
 
@@ -45,13 +45,50 @@ on a value overrides a field's default; scales never multiply.
45
45
 
46
46
  `prf.update` returns a copy of a model with the parts a **selector** picks
47
47
  replaced. A selector is a parameter name, a glob over names, a sequence of
48
- names, or a callable. A **validated update** (a name → value mapping,
49
- `value=`, `fixed=`) goes through each parameter's constructor; a **structural
50
- update** (a new node, `fn=`) bypasses validation. `prf.replace` is the plain
48
+ names, or a callable. A **validated update** (a name → value mapping
49
+ entry, `value=`, `fixed=`) goes through each parameter's constructor; a
50
+ **structural update** (a new node, `fn=`, or a mapping entry whose value is a
51
+ `Model`, keyed by sub-model name) bypasses validation. In a mapping the tier is
52
+ decided per entry by the value's type. `prf.replace` is the plain
51
53
  dataclass field replace, not an update.
52
54
 
53
55
  *Avoid:* "set values", "with values"; "update" for an optimiser step.
54
56
 
57
+ ### Tie and resolve
58
+
59
+ `prf.tie` derives one part of a tree from another: the **target** is removed from
60
+ the parameters and recomputed as `fn(source)` whenever the tree is resolved, so
61
+ it follows the source through `prf.update`, optimisation and sampling. Target
62
+ and source are selected by name, on either side, and a name that picks a
63
+ sub-model ties the parameters beneath it, pairing by suffix.
64
+
65
+ Two operations read a tied tree back, and they are not the same thing.
66
+
67
+ - **Resolve** (`prf.resolve`): structural. Ties are applied and every wrapper
68
+ that only describes structure is discharged; parameters stay parameters. A
69
+ container resolves to its own shape, so a `dict` of components comes back a
70
+ `dict`, still parameterised.
71
+ - **Evaluation** (`prf.unwrap`): every parameter becomes its physical value.
72
+ The result is numbers, not priors, and nothing rebuilt from it is
73
+ parameterised. A joint prior (`prf.modules.Probabilistic`) is discharged the
74
+ same way and for the same reason, so resolve leaves it standing.
75
+
76
+ Resolve is the one ordinary modelling code wants; `prf.unwrap` is a low-level
77
+ evaluation primitive. A tie's target resolves to a plain value either way — it
78
+ is derived, so it has no prior of its own.
79
+
80
+ *Avoid:* "unwrap" for reading a tied container; "apply the ties" for evaluation.
81
+
82
+ ### Derived model
83
+
84
+ A model computed from a **base** model and **new parameters** by a function,
85
+ `f(base, **new)`, built with `prf.derived` (ADR-0003). The base and the new
86
+ parameters are held once; the base keeps its names and each new parameter is
87
+ named by its keyword. Used to derive a more complete model from a nominal one
88
+ (a wet section, a cut) and, by nesting, to share a parameter across parts.
89
+
90
+ *Avoid:* "tie with new parameters", "shared parameter" as a separate concept.
91
+
55
92
  ### Parameter values
56
93
 
57
94
  `prf.param_values`: a name-keyed dict of arrays in one space, the form values
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramrf
3
- Version: 0.35.2
3
+ Version: 0.35.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
@@ -85,6 +85,7 @@ replaced. Exactly one form says what with:
85
85
 
86
86
  ```python
87
87
  prf.update(model, {'L1.L': 3.0, 'C1.C': 2.0}) # parameter values by name
88
+ prf.update(model, {'east': wet(model.east), 'west': wet(model.west)}) # sub-models by name
88
89
  prf.update(model, 'L1.L', value=3.0) # parameter fields on a selection
89
90
  prf.update(model, 'cable.*', fixed=True) # fixed state
90
91
  prf.update(model, 'cascade[1]', Short()) # a new sub-model or node
@@ -98,19 +99,26 @@ It replaces `with_values`, `with_free`, `with_fixed`, `Module.map`,
98
99
 
99
100
  - **Selectors** are parameter names, `fnmatch` globs over them, sequences of
100
101
  names, or callables, resolved by the #133 resolver.
101
- - **Two tiers, set by the form.** The mapping, `value=` and `fixed=` forms go
102
+ - **Two tiers, set by the form, or per entry in a mapping.** The `value=` and
103
+ `fixed=` forms, and mapping entries whose value is an array or `Param`, go
102
104
  through each parameter's constructor: they validate bounds and keep the prior,
103
- constraint, scale, name and metadata. The node and `fn=` forms are
104
- structural: they bypass converters and validation, and the docstring says so.
105
+ constraint, scale, name and metadata. The node and `fn=` forms, and mapping
106
+ entries whose value is a `Model`, are structural: they bypass converters and
107
+ validation, and the docstring says so.
105
108
  - **The mapping form** is recognised only as the second positional argument
106
- with every key a string. Its values may be arrays or `Param` objects. Any
109
+ with every key a string. A value that is an array or `Param` is keyed by a
110
+ parameter name, and `space=` applies to it. A value that is a `Model` is keyed
111
+ by a sub-model name (`'cascade[1]'`, a named module's name) and replaces that
112
+ sub-model as the node form does (amended by #168, so several sub-models can be
113
+ replaced in one call). One mapping may mix both; a model for a parameter name,
114
+ or a value for a sub-model name, raises. Any
107
115
  other second argument is a selector, and a form mismatch raises an error that
108
116
  lists the forms.
109
117
  - **`fixed=`** is additive. `fixed=False` frees a parameter even if it was
110
118
  created fixed, and parameters the selector does not match are untouched.
111
119
  "Only these free" is `update(update(m, '*', fixed=True), names, fixed=False)`.
112
- - **Value forms keep the jit cache key.** The mapping, `value=` and `space=`
113
- forms never change the treedef, or any leaf's dtype, shape or `weak_type`
120
+ - **Value forms keep the jit cache key.** Mappings of values only, and the
121
+ `value=` and `space=` forms, never change the treedef, or any leaf's dtype, shape or `weak_type`
114
122
  (decision 10). Changing `fixed=`, or any structural form, changes the model's
115
123
  structure, and recompiling is expected.
116
124
  - **Not an optimiser step.** In fitting, "updates" also means Optax gradient
@@ -122,7 +130,8 @@ anything name-based.
122
130
 
123
131
  `prf.tie(model, target, source, fn=identity)` stays a separate verb. A tie is
124
132
  not a replacement: its target is recomputed from its source every time the
125
- model is unwrapped.
133
+ model is unwrapped. A relation that needs a quantity the model does not yet hold
134
+ is a derived model (`prf.derived`, ADR-0003), not a tie.
126
135
 
127
136
  ### 4. Reading: `prf.params` and `prf.param_values`
128
137
 
@@ -0,0 +1,80 @@
1
+ # ADR-0003: Derived models add parameters by deriving from a whole base
2
+
3
+ Status: accepted (2026-09)
4
+
5
+ ## Context
6
+
7
+ Users constrain parameters by relations such as "this part is computed from
8
+ those". `prf.tie` (ADR-0002) covers this only when every source already exists
9
+ in the model. It fails when the relation needs a quantity that has no home.
10
+
11
+ The driving case (#167): a coaxial cable of total length L is wet for its first
12
+ w and dry for the rest. The model is `wet ** dry`, but neither section can hold
13
+ L. L must stay a parameter with its own prior, possibly a joint lab prior with
14
+ the cable's geometry and material, and must not drift as w changes. w is a new
15
+ parameter with its own prior. Several parts may share one such parameter (one
16
+ water level on both arms of a balun).
17
+
18
+ ## Decision
19
+
20
+ `prf.derived` turns a function `f(base, **new)` returning a model into a
21
+ constructor of a **derived model**: a `pmrf.Model` (`pmrf.models.Derived`, an
22
+ `AbstractBuilder`) that holds the base and the new parameters once and calls
23
+ `f` on their unwrapped values whenever the model is used. `f` is a static field,
24
+ so models derived with one function share a jit cache entry.
25
+
26
+ ### The base stays whole
27
+
28
+ The derived model holds the base model itself, not parameters extracted from
29
+ it. This is what makes two things work with no extra machinery:
30
+
31
+ - **Joint priors.** A `Probabilistic` prior over the base (length with
32
+ geometry) is still in the tree, over the same parameters, under the same
33
+ names, and scores unchanged.
34
+ - **Sharing with no ties.** `f` uses the base's geometry in both sections; it
35
+ is one parameter because it is stored once. No tie is needed to keep the wet
36
+ and dry sections consistent.
37
+
38
+ ### Naming rule
39
+
40
+ - The base's parameters keep exactly the names they have on the base. The
41
+ wrapper is transparent to the name resolver, and the base's own name moves to
42
+ the derived model (unless `name=` is given), so a container prefixes as usual.
43
+ - Each new parameter is named by its keyword; a keyword that clashes with a
44
+ base name raises.
45
+ - Nothing produced inside `f` is named: it is not in the tree.
46
+
47
+ A values dict saved from a fit of the base therefore applies unchanged to the
48
+ derived model.
49
+
50
+ ### Sharing by nesting
51
+
52
+ A parameter shared by several parts is a new parameter of a derived model at
53
+ the level that owns them. Its `f` derives each part, passing the same value,
54
+ and puts them back with the multi-model `prf.update` mapping form (#168). A
55
+ derived model can be the base of another; names accumulate flat. There is no
56
+ separate "shared parameter" concept.
57
+
58
+ ## Rejected options
59
+
60
+ - **A tie with new parameters** (`tie(..., new={...})` and other tie-centric
61
+ designs). L ends up separate from the geometry and material, so no joint prior
62
+ covers them, and the dry section needs extra ties to share the geometry.
63
+ - **A separate "add parameters" function** followed by a tie. Two steps for one
64
+ idea, and the added parameters have no relation to the model until tied, with
65
+ the same joint-prior problem.
66
+ - **Reparametrising a parameter in place** (replace L by a function of new
67
+ parameters). The relation is not a function of one parameter: it replaces
68
+ part of the model's structure (one section becomes two), and L loses its prior.
69
+ - **Builder classes.** They work, but an engineer should not need a class for
70
+ a one-off constraint, and topology code had to reach into the class.
71
+ `prf.derived` is a builder whose class is generated from a function.
72
+
73
+ ## Consequences
74
+
75
+ - `tie` is unchanged.
76
+ - `f` must be pure and its output's structure must not depend on parameter
77
+ values, as for `AbstractBuilder.build`. It should be defined once at module
78
+ level; a lambda made on every call recompiles.
79
+ - A non-model return is only detected when the model is used, since calling
80
+ `f` eagerly at construction would repeat the work at every nesting level.
@@ -0,0 +1,214 @@
1
+ # ADR-0004: Profiled lines vary parameters along a line through target-agnostic profiles
2
+
3
+ Status: accepted (2026-09)
4
+
5
+ ## Context
6
+
7
+ ParamRF's line models (ADR-0001) are uniform: a `MicrostripLine` has one width,
8
+ one substrate, one conductor, and its `zc_and_gammaL` holds everywhere along it.
9
+ Real structures are not. A taper's width varies along z, a cable's permittivity
10
+ drifts with a moisture gradient, a deposited conductor's conductivity varies with
11
+ position. The quantity that varies is always an existing parameter of an existing
12
+ line, and it must keep taking part in fitting: its shape is described by a few
13
+ new parameters with their own priors, and those are what the user fits.
14
+
15
+ The repository already held a commented-out `ProfiledLine` from an earlier API:
16
+ a `line_fn` plus parallel `profile_fns` (static callables) and `profile_params`
17
+ dicts, a constructor that sniffed keyword types to decide what was a profile, and
18
+ a `method='stepped'|'riccati'` switch whose Riccati branch raised
19
+ `NotImplementedError`. It predates the parameter API (ADR-0002) and derived
20
+ models (ADR-0003).
21
+
22
+ Two further pressures shaped this. First (#45), a cascade of ten microstrip lines
23
+ with independent widths is built by a Python list comprehension, which traces
24
+ once per member and gives every member its own name set — the same batching
25
+ problem a profiled line has, at a different level. Second, users do not know what
26
+ section count a given taper needs for an accurate answer, and an under-resolved
27
+ line is not merely inaccurate: an optimiser will exploit it.
28
+
29
+ ## Decision
30
+
31
+ `pmrf.models.ProfiledLine` takes a uniform line and a mapping from parameter
32
+ targets to **profiles**, and evaluates as a cascade of `n` uniform sections whose
33
+ parameters are sampled at the section midpoints.
34
+
35
+ ### A profile is a shape, not a line
36
+
37
+ `AbstractProfile` is a `pmrf.Module` — parameter-aware, named, validated, but not
38
+ a `Model`, exactly as materials and the line strategy objects are. It declares one
39
+ method, `evaluate(t)`, on normalised `t`, documented as elementwise so the
40
+ container passes the whole midpoint array in one call. Its coefficients are
41
+ ordinary `prf.param` fields carrying physical units and their own constraints, so
42
+ `ExponentialProfile(start=2e-3, end=8e-3)` and
43
+ `ExponentialProfile(start=Bounded(...), end=Bounded(...))` both work with no
44
+ profile-specific machinery.
45
+
46
+ A profile knows nothing about the line, the parameter, or the family. This is the
47
+ central constraint: an exponential taper must apply to microstrip width, stripline
48
+ width or coaxial inner diameter without being rewritten. `evaluate` returns a
49
+ plain physical value, in the units of whatever it is attached to.
50
+
51
+ `t = 0` is at port 1 and `t = 1` at port 2, fixed on `AbstractProfile` and
52
+ restated on `ProfiledLine`. Reversing a taper swaps coefficients through
53
+ `prf.update`; there is no orientation flag, because a flag that silently mirrors
54
+ geometry inside a cascade is a debugging hazard.
55
+
56
+ The library ships `LinearProfile`, `ExponentialProfile` and `KlopfensteinProfile`.
57
+ Klopfenstein is the linearised-Riccati *design* solution: it defines a shape worth
58
+ simulating and is not a reference the simulator is checked against.
59
+
60
+ ### Targets are dotted paths; the base tree is untouched
61
+
62
+ `ProfiledLine` holds the base line and a mapping from a **dotted path** naming a
63
+ `Param` on that base to the profile driving it. The base tree is left exactly as
64
+ built. Profiles are not substituted into it, and not declared as model fields.
65
+
66
+ Targets are exact dotted paths only — `w`, `substrate.dielectric.ep_r` — not
67
+ globs, sequences or callables. A glob matching three parameters would silently
68
+ create three independent profiles that happen to share a shape object, which is
69
+ never what the user meant.
70
+
71
+ `length` is not profilable and is rejected. It is the base's own parameter and it
72
+ is the **total** length; `ProfiledLine` divides by `n` internally and the user
73
+ never sees a per-section length. `ProfiledLine` has no `length` field, exposing
74
+ the base's as a read-only forwarding property.
75
+
76
+ A profiled target's value on the base is discarded, and the parameter is removed
77
+ from the profiled line's parameter set, replaced by the profile's coefficients.
78
+ Passing a value explicitly for a target that is also profiled raises: silently
79
+ discarded input is how an afternoon is lost. A field default that is discarded
80
+ does not raise, because the user did not type it.
81
+
82
+ Coefficients are named under the target: `w.start`, `w.end`,
83
+ `substrate.dielectric.ep_r.start` — the name the parameter would have had, with
84
+ the coefficient below it. `ProfiledLine` names them itself rather than letting
85
+ the mapping's keys be named as dict keys, so a non-identifier path does not leak
86
+ a bracket form into parameter names. Globs such as `'w.*'` and `'*.start'` then
87
+ both do the obvious thing, and the container's internal field layout never
88
+ appears in a parameter name.
89
+
90
+ ### Two ways in, one representation
91
+
92
+ The base may be given as a constructed line or as a class. A class is constructed
93
+ from the plain keywords, with a small reserved set (`n`, `extrapolate`, `name`,
94
+ `metadata`) always belonging to the container and a clear error on collision. A
95
+ user usually thinks "I want to profile a microstrip line", not "I want to profile
96
+ this particular line", and the class form lets them say so without constructing a
97
+ line whose width they are about to throw away. A base built this way is
98
+ deliberately unnamed, so parameter paths flatten to the container root.
99
+
100
+ Whichever form is used, one representation is stored: a base model plus the
101
+ target-to-profile mapping.
102
+
103
+ ### Evaluation: midpoint sections, extrapolated
104
+
105
+ `ProfiledLine` is a `TransmissionLine` and an `AbstractBuilder`. `build` returns a
106
+ `pmrf.models.RepeatedCascade`, so every representation delegates for free and no
107
+ line-specific evaluation code is written twice.
108
+
109
+ `RepeatedCascade` is public in its own right: one model, a mapping from dotted
110
+ names to arrays whose leading axis is the repeat axis, and everything else shared.
111
+ `n` is static by construction, from that leading axis. It is what `ProfiledLine`
112
+ produces after evaluating profiles at the midpoints, and it is also the answer to
113
+ #45's cascade of independently-parametrised lines. The reduction itself moves into
114
+ `pmrf.rf` and is shared with `Cascade`: the ABCD domain by default, reduced with a
115
+ sequential `lax.scan`.
116
+
117
+ Sampling each profile at the section midpoints and building exact hyperbolic ABCD
118
+ sections is the exponential midpoint rule, an order-2 Magnus integrator. It is
119
+ time-symmetric, so its error expands in **even** powers of `h`, and Richardson
120
+ extrapolation of `T(n)` and `T(2n)` as `(4·T(2n) − T(n))/3` is **O(h⁴)**, not
121
+ O(h³). Extrapolation is on by default (`extrapolate=True`), costing `3n` section
122
+ builds, all vmapped. `n` defaults to 64, which holds the per-section electrical
123
+ length inside the guard below well past any realistic taper. `extrapolate=False`
124
+ is the escape for users who want the plain midpoint result.
125
+
126
+ Extrapolation is applied to the complex ABCD entries, and the per-frequency error
127
+ estimate is exposed.
128
+
129
+ ### The guard is on by default and hard-errors
130
+
131
+ Two conditions are checked at evaluation: the Richardson error estimate against a
132
+ tolerance, and per-section electrical length against `βh ≲ 0.2 rad`. Both are
133
+ traced, so both use `eqx.error_if`, as the degenerate-DC guards in the line base
134
+ already do. A static `check=True` field turns them off.
135
+
136
+ The error estimate is taken relative to a matrix norm, never to `|S11|`:
137
+ normalising by a reflection coefficient makes the guard fire spuriously at every
138
+ reflection null.
139
+
140
+ Erroring rather than warning is deliberate. Silently wrong S-parameters feeding a
141
+ likelihood is the worst outcome available, and an under-resolved taper is exactly
142
+ the kind of numerical slack an optimiser finds and exploits. The accepted cost is
143
+ that a fit whose trial point wanders past the guard dies mid-run rather than being
144
+ penalised; `check=False` is the lever.
145
+
146
+ ### Introspection
147
+
148
+ `ProfiledLine.at(t)` returns the base model with the profiled targets substituted
149
+ at that `t` — the same substitution the evaluator performs at the midpoints,
150
+ exposed for one position. It composes: `line.at(0.5).zc_and_gammaL(f)`, and `t`
151
+ as an array vmaps to give the whole profile at once.
152
+
153
+ ## Rejected options
154
+
155
+ - **Profiles declared as model fields** (`w: Param | Profile`). A `prf.param`
156
+ field runs an `as_param` converter, so a profile node is coerced or rejected;
157
+ and where a foreign node is stored untouched (ADR-0003), the field's constraint
158
+ and scale deliberately do not apply. A `MicrostripLine` whose width validation
159
+ is silently bypassed by an intermediate shape object is worse than no feature.
160
+ - **Substituting the profile into the base tree in place**, replacing the `Param`
161
+ at the target. Naming would fall out for free, but it is the same validation
162
+ bypass at a different entry point.
163
+ - **A section function returning a concrete line** (`lambda t: MicrostripLine(w=...)`).
164
+ It couples the taper definition to the line family: an exponential taper for
165
+ microstrip width would have to be rewritten for stripline width and again for
166
+ coaxial diameter. This is what forced profiles to be plain `f(t) -> value`.
167
+ - **Authoring through `prf.update`** — construct a line, then update targets with
168
+ profile nodes. Unintuitive as the primary interface, and it reintroduces
169
+ in-tree substitution.
170
+ - **A profile as a `Model`.** It has no ports and no S-parameters. `pmrf.Module`
171
+ is the base for parameter-aware objects that are not models.
172
+ - **`ProfiledLine` as an `AbstractUniformLine`**, with `zc_and_gammaL` returning
173
+ the midpoint value. A characteristic impedance that is silently the value at
174
+ one position invites exactly the misuse the guard exists to prevent.
175
+ - **Relative profiles** (`w(t) = w_base × profile(t)`). It keeps the base value
176
+ meaningful, but requires a reference value at every target and makes a
177
+ dimensionless profile of permittivity mean something odd. Profiles are absolute.
178
+ - **A per-section `length`.** The base's `length` already carries a name, a prior
179
+ and possibly a joint lab prior with the geometry; dividing it internally keeps
180
+ all three and keeps a values dict from an earlier fit applicable.
181
+ - **`BatchedCascade` as the name.** "Batch" in ParamRF already means the
182
+ parameter batch dimension (`prf.batch_axes`, `prf.sweep`), and a
183
+ `RepeatedCascade` can itself be batched in that sense.
184
+ - **A `method` field and the Riccati path.** The old Riccati branch never ran. A
185
+ field with one legal value is an API promise bought before it is needed, and a
186
+ higher-order integrator would change `n` and `extrapolate` semantics too, so it
187
+ deserves its own design rather than a pre-cut slot.
188
+ - **Automatic `n` selection.** `n` must be static under `jit` — it is a vmap axis
189
+ and a scan length — while `Frequency.f` is a dynamic array leaf, so no rule that
190
+ reads the frequency band can run inside `jit`. A manual `refine()` for power
191
+ users is deferred to its own ticket.
192
+ - **Arbitrary-shape profiles** (polynomial, spline). They raise the smoothness
193
+ question the extrapolation depends on and belong in a later ticket.
194
+
195
+ ## Consequences
196
+
197
+ - The even-power error expansion, and therefore the O(h⁴) claim, requires the
198
+ profile to be C² in `t`. This is documented on `AbstractProfile`. A deliberate
199
+ kink is expressed by cascading two `ProfiledLine`s with `**`, not by a
200
+ discontinuous profile.
201
+ - Periodic uniform sections have an artificial Bragg stopband at `βh = π`. The
202
+ `βh ≲ 0.2` guard keeps evaluation far from it, but the mechanism is why the
203
+ guard is always on rather than advisory.
204
+ - Richardson measures the *discretisation* error only. It is blind to the model
205
+ error floor — mode conversion, radiation, quasi-TEM breakdown — so a small
206
+ estimate is not a statement that the answer is physically right.
207
+ - The convergence-order test is the slow one in the suite: it measures O(h²)
208
+ without extrapolation and O(h⁴) with it. Validation otherwise uses the
209
+ exponential taper's closed form, a diffrax RK reference, and exactness at
210
+ `n = 1`.
211
+ - `Cascade` and `RepeatedCascade` share one reduction in `pmrf.rf`; changes to the
212
+ cascade numerics now affect both.
213
+ - The commented-out `nonuniform.py` is deleted rather than ported. Per `AGENTS.md`
214
+ there is no back-compatibility obligation.
@@ -31,6 +31,8 @@ Working with Parameters
31
31
  pmrf.log_prior
32
32
  pmrf.update
33
33
  pmrf.tie
34
+ pmrf.resolve
35
+ pmrf.derived
34
36
 
35
37
 
36
38
  Main Modules
@@ -40,9 +40,49 @@ Models are immutable, so :func:`pmrf.update` returns a changed copy rather than
40
40
 
41
41
  **Structural changes** replace a part of the model outright, either with a new sub-model or parameter, or with the result of a function applied to the old part. These put exactly what they are given in place, without validation. An exact name can select a whole sub-model here, whereas a glob only ever matches parameters.
42
42
 
43
+ A dictionary passed to :func:`pmrf.update` can hold both kinds. Each entry is decided by its value: a :class:`pmrf.Model` keyed by a sub-model name is a structural change, unvalidated, while an array or parameter keyed by a parameter name is a value change. For example, ``prf.update(system, {'east_coax': new_east, 'west_coax': new_west})`` replaces two sub-models in one call. A model given for a parameter name, or a value given for a sub-model name, raises an error.
44
+
43
45
  The distinction matters for performance. RF methods such as :meth:`pmrf.Model.s` are compiled just-in-time, and the compiled code is only reused while the model's structure is unchanged. Changing a parameter's value keeps that structure, so it never triggers a recompile. Fixing or freeing a parameter, making a structural change, or swapping in a parameter with a different constraint or scale all change the structure, and so recompile. On a large circuit this can take noticeably longer than an evaluation, so value changes should be preferred inside loops.
44
46
 
45
47
  Tied Parameters
46
48
  ~~~~~~~~~~~~~~~
47
49
 
48
50
  Rather than setting a parameter once, :func:`pmrf.tie` derives it from another parameter. The target is removed from the model's parameters and is recomputed from its source every time the model is evaluated, so it follows the source through updates, optimization and sampling. Because it is no longer a parameter, it also no longer has a name. The tie function receives and returns physical values, and derivatives with respect to the source include the path through the tie.
51
+
52
+ Derived Models
53
+ ~~~~~~~~~~~~~~
54
+
55
+ A tie can only relate parameters that already exist. Sometimes a relation needs a quantity the model has no place for. Take a coaxial cable of total length ``L`` that is wet for its first ``w``: the model is a wet section cascaded with a dry one, but neither section can hold ``L``, and ``L`` should keep its own prior (perhaps a joint lab prior with the cable's geometry) rather than drift as ``w`` changes.
56
+
57
+ :func:`pmrf.derived` handles this by starting from the nominal model and deriving a more complete one. It turns a function ``f(base, **new)`` into a constructor: the base model and the new parameters are held once, and ``f`` is called on them whenever the model is used.
58
+
59
+ .. code-block:: python
60
+
61
+ @prf.derived
62
+ def wet(cable, wet_length, wet_ep_r):
63
+ wet = prf.replace(cable, length=wet_length,
64
+ dielectric=prf.replace(cable.dielectric, ep_r=wet_ep_r))
65
+ dry = prf.replace(cable, length=cable.length - wet_length)
66
+ return wet ** dry
67
+
68
+ coax = wet(coax, wet_length=prf.Random(Uniform(0, 20), scale=1e-3),
69
+ wet_ep_r=prf.Random(Uniform(1, 80)))
70
+
71
+ The result is an ordinary :class:`pmrf.Model` with the same port count, so it can be cascaded, wrapped, tied and fitted like any other. Its parameters are the base's, under exactly the names they had on the base, plus one per keyword (``wet_length`` and ``wet_ep_r``). The cable's geometry is used by both sections but is still one parameter, and a values dictionary saved from a fit of the dry cable applies unchanged. Nothing built inside ``f`` is named, and the derived model takes the base's name, so inside a named container everything is prefixed as usual.
72
+
73
+ Like a tie function, ``f`` receives physical values. Inside it, use :func:`pmrf.replace` to change fields of the object in hand, and :func:`pmrf.update` to change parts reached by name. ``f`` must be pure and must return a model whose structure does not depend on parameter values. It is part of the model's static structure, so define it once at module level: a lambda created anew on every call recompiles.
74
+
75
+ A parameter shared by several parts is expressed by deriving at the level that owns it. A derived model can be the base of another, and names accumulate flat, so one water level for both arms of a balun is:
76
+
77
+ .. code-block:: python
78
+
79
+ @prf.derived
80
+ def wet_balun(system, wet_length):
81
+ return prf.update(system, {
82
+ 'east_coax': wet(system.east_coax, wet_length=wet_length, wet_ep_r=80.0),
83
+ 'west_coax': wet(system.west_coax, wet_length=wet_length, wet_ep_r=80.0),
84
+ })
85
+
86
+ balun = wet_balun(balun, wet_length=prf.Random(Uniform(0, 20), scale=1e-3))
87
+
88
+ Here ``prf.params(balun)`` holds a single ``wet_length``, and changing it changes both cables. The reasoning behind this design is recorded in ADR-0003.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: paramrf
3
- Version: 0.35.2
3
+ Version: 0.35.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
@@ -25,6 +25,8 @@ docs/_templates/autosummary/function.rst
25
25
  docs/_templates/autosummary/module.rst
26
26
  docs/adr/0001-line-modelling-architecture.md
27
27
  docs/adr/0002-parameter-api.md
28
+ docs/adr/0003-derived-models.md
29
+ docs/adr/0004-profiled-lines.md
28
30
  docs/agents/domain.md
29
31
  docs/agents/issue-tracker.md
30
32
  docs/agents/triage-labels.md
@@ -115,6 +117,7 @@ pmrf/models/adapters/base.py
115
117
  pmrf/models/adapters/bridge.py
116
118
  pmrf/models/adapters/callable.py
117
119
  pmrf/models/adapters/delegated.py
120
+ pmrf/models/adapters/derived.py
118
121
  pmrf/models/adapters/static.py
119
122
  pmrf/models/adapters/wrapped.py
120
123
  pmrf/models/components/__init__.py
@@ -130,6 +133,7 @@ pmrf/models/components/lines/microstrip.py
130
133
  pmrf/models/components/lines/nodal.py
131
134
  pmrf/models/components/lines/nonuniform.py
132
135
  pmrf/models/components/lines/planar.py
136
+ pmrf/models/components/lines/profiles.py
133
137
  pmrf/models/components/lines/stripline.py
134
138
  pmrf/models/composite/__init__.py
135
139
  pmrf/models/composite/nodal.py
@@ -159,6 +163,7 @@ pmrf/optimize/solvers/jaxopt.py
159
163
  pmrf/optimize/solvers/optimistix.py
160
164
  pmrf/optimize/solvers/scipy.py
161
165
  pmrf/rf/__init__.py
166
+ pmrf/rf/cascade.py
162
167
  pmrf/rf/conversions.py
163
168
  pmrf/rf/mna.py
164
169
  pmrf/utils/__init__.py
@@ -214,11 +219,14 @@ tests/test_models/test_circuit_nodal.py
214
219
  tests/test_models/test_circuit_port_order.py
215
220
  tests/test_models/test_circuit_scattering.py
216
221
  tests/test_models/test_current_distribution.py
222
+ tests/test_models/test_derived.py
217
223
  tests/test_models/test_interconnected.py
218
224
  tests/test_models/test_lines.py
219
225
  tests/test_models/test_lines_skrf_matrix.py
220
226
  tests/test_models/test_lumped.py
221
227
  tests/test_models/test_nodal.py
228
+ tests/test_models/test_profiles.py
229
+ tests/test_models/test_repeated_cascade.py
222
230
  tests/test_models/test_sections.py
223
231
  tests/test_models/test_transformed.py
224
232
  tests/test_models/test_transformers.py
@@ -46,6 +46,7 @@ except PackageNotFoundError:
46
46
  from pmrf.models import (
47
47
  Model as Model,
48
48
  is_model as is_model,
49
+ derived as derived,
49
50
  )
50
51
  from pmrf.modules.base import Module as Module, is_module as is_module
51
52
  from pmrf.frequency import Frequency as Frequency
@@ -68,6 +69,7 @@ from pmrf.parameters import (
68
69
  log_prior as log_prior,
69
70
  update as update,
70
71
  tie as tie,
72
+ resolve as resolve,
71
73
  )
72
74
 
73
75
  from pmrf.serialization import (
@@ -125,6 +127,7 @@ __all__ = [
125
127
  # Base/Core
126
128
  "Model",
127
129
  "is_model",
130
+ "derived",
128
131
  "Module",
129
132
  "is_module",
130
133
  "Frequency",
@@ -158,6 +161,7 @@ __all__ = [
158
161
  "log_prior",
159
162
  "update",
160
163
  "tie",
164
+ "resolve",
161
165
  "unwrap",
162
166
  "unwrap_self",
163
167
  "derivative",
@@ -70,6 +70,13 @@ from pmrf.models.components.lines.base import (
70
70
  ImmittanceResult as ImmittanceResult,
71
71
  )
72
72
 
73
+ from pmrf.models.components.lines.profiles import (
74
+ AbstractProfile as AbstractProfile,
75
+ LinearProfile as LinearProfile,
76
+ ExponentialProfile as ExponentialProfile,
77
+ KlopfensteinProfile as KlopfensteinProfile,
78
+ )
79
+
73
80
  from pmrf.models.components.lines.nodal import (
74
81
  FloatingLine as FloatingLine,
75
82
  )
@@ -170,7 +177,10 @@ from pmrf.models.composite.interconnected.circuit.solvers.nodal import (
170
177
  )
171
178
 
172
179
  from pmrf.models.composite.interconnected.circuit.circuit import Circuit as Circuit
173
- from pmrf.models.composite.interconnected.cascade import Cascade as Cascade
180
+ from pmrf.models.composite.interconnected.cascade import (
181
+ Cascade as Cascade,
182
+ RepeatedCascade as RepeatedCascade,
183
+ )
174
184
  from pmrf.models.composite.interconnected.terminated import Terminated as Terminated
175
185
 
176
186
 
@@ -198,6 +208,11 @@ from pmrf.models.adapters.wrapped import (
198
208
  Wrapped as Wrapped,
199
209
  )
200
210
 
211
+ from pmrf.models.adapters.derived import (
212
+ Derived as Derived,
213
+ derived as derived,
214
+ )
215
+
201
216
  # Compatibility re-exports. Parameter-aware wrappers live under ``pmrf.modules``.
202
217
  from pmrf.modules import Tied as Tied, Probabilistic as Probabilistic
203
218
 
@@ -7,7 +7,7 @@ This includes scikit-rf Networks, EM simulation software, and generic Equinox mo
7
7
  """
8
8
 
9
9
  from pmrf.models.adapters import base
10
- from pmrf.models.adapters import bridge, static, callable, delegated, wrapped
10
+ from pmrf.models.adapters import bridge, static, callable, delegated, derived, wrapped
11
11
 
12
12
  __all__ = [
13
13
  "base",
@@ -15,6 +15,7 @@ __all__ = [
15
15
  "static",
16
16
  "callable",
17
17
  "delegated",
18
+ "derived",
18
19
  "wrapped",
19
20
  ]
20
21