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.
- {paramrf-0.35.2 → paramrf-0.35.4}/AGENTS.md +14 -1
- {paramrf-0.35.2 → paramrf-0.35.4}/CLAUDE.md +14 -1
- {paramrf-0.35.2 → paramrf-0.35.4}/CONTEXT.md +40 -3
- {paramrf-0.35.2 → paramrf-0.35.4}/PKG-INFO +1 -1
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/adr/0002-parameter-api.md +16 -7
- paramrf-0.35.4/docs/adr/0003-derived-models.md +80 -0
- paramrf-0.35.4/docs/adr/0004-profiled-lines.md +214 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/api/index.rst +2 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/parameter_names.rst +40 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/PKG-INFO +1 -1
- {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/SOURCES.txt +8 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/__init__.py +4 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/__init__.py +16 -1
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/__init__.py +2 -1
- paramrf-0.35.4/pmrf/models/adapters/derived.py +144 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/__init__.py +6 -0
- paramrf-0.35.4/pmrf/models/components/lines/profiles.py +341 -0
- paramrf-0.35.4/pmrf/models/composite/interconnected/cascade.py +390 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/parameters.py +217 -26
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/rf/__init__.py +7 -0
- paramrf-0.35.4/pmrf/rf/cascade.py +208 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pyproject.toml +1 -1
- paramrf-0.35.4/tests/test_models/test_derived.py +234 -0
- paramrf-0.35.4/tests/test_models/test_profiles.py +357 -0
- paramrf-0.35.4/tests/test_models/test_repeated_cascade.py +274 -0
- paramrf-0.35.4/tests/test_structural_updates.py +607 -0
- paramrf-0.35.2/pmrf/models/composite/interconnected/cascade.py +0 -250
- paramrf-0.35.2/tests/test_structural_updates.py +0 -235
- {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/docs.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/draft-pdf.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/publish.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/.github/workflows/tests.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/.gitignore +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/CHANGELOG.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/CITATION.cff +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/CONTRIBUTING.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/LICENSE +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/NOTICE +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/README.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/assets/logo.png +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/Makefile +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/_static/custom.css +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/_templates/autosummary/class.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/_templates/autosummary/function.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/_templates/autosummary/module.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/adr/0001-line-modelling-architecture.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/agents/domain.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/agents/issue-tracker.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/agents/triage-labels.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/conf.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/core_primitives.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/jax_overview.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/core_concepts/optimization_and_inference.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/cascading_and_terminating.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/circuit_clc.png +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/circuit_models.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/custom_models.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/derivatives_and_sweeps.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/model_optimization.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/examples/shared_substrates.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/license.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/make.bat +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/conductor-loss-alternatives.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/holloway1994.pdf +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/microstrip-loss-conventions.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/skrf_comparison/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/skrf_comparison/overview.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/skrf_comparison/performance.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/1_cable_fitting.ipynb +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/docs/tutorials/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paper/paper.bib +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paper/paper.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paper/rlc.png +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/dependency_links.txt +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/requires.txt +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/paramrf.egg-info/top_level.txt +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/_solver_view.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/bijectors.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/constraints.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/covariance_kernels.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/discrepancy_models.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/distributions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/evaluators.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/result.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/routers.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/fitting/targets.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/frequency.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/result.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/solvers/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/solvers/blackjax.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/infer/solvers/polychord.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/likelihoods.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/losses.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/conductor.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/dielectric.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/properties.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/roughness.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/substrate.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/materials/surface_impedance.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/aggregations.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/bessel.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/conversions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/losses.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/math/misc.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/bridge.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/callable.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/delegated.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/static.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/adapters/wrapped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/ideal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/coaxial.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/empirical.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/ideal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/microstrip.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/nonuniform.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/planar.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lines/stripline.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/lumped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/components/sections.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/circuit.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/interconnected/terminated.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/topological.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/composite/transformed.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/surrogates/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/surrogates/expansion.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/models/surrogates/rational.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/modules/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/modules/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/modules/wrapped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/network_collection.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/noise_models.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/result.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/jaxopt.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/optimistix.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/optimize/solvers/scipy.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/problems.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/rf/conversions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/rf/mna.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/serialization.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/terms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/types.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/array.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/debug.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/network.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/random.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/rf.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/transforms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/tree.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/utils/type.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/viz/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/pmrf/viz/plots.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/scripts/install-test-deps.sh +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/setup.cfg +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/_dependency_checks.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/_jit.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/conftest.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/data/10m_cable.s2p +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_autodiff.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_conversions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_covariance_kernels.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_dc_limits.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_evaluators.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_routers.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_fitting_targets.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_frequency.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_infer_base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_infer_sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_map_priors.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_conductor.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_dielectric.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_serialization.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_substrate.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_materials/test_surface_impedance.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_math/test_bessel.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_model.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_adapters.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_circuit_nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_circuit_port_order.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_circuit_scattering.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_current_distribution.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_interconnected.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_lines.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_lines_skrf_matrix.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_lumped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_sections.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_transformed.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_models/test_transformers.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_module.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_naming.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_optimize_base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_optimize_minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_parameters.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_parameters_by_name.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_raw_space_solving.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_serialization.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_terms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_transforms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.4}/tests/test_utils/test_compress.py +0 -0
- {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
|
|
50
|
-
update** (a new node, `fn
|
|
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
|
|
@@ -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
|
|
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
|
|
104
|
-
structural: they bypass converters and
|
|
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.
|
|
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.**
|
|
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.
|
|
@@ -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.
|
|
@@ -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
|
|
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
|
|