paramrf 0.35.2__tar.gz → 0.35.3__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.3}/CONTEXT.md +15 -3
- {paramrf-0.35.2 → paramrf-0.35.3}/PKG-INFO +1 -1
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/adr/0002-parameter-api.md +16 -7
- paramrf-0.35.3/docs/adr/0003-derived-models.md +80 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/api/index.rst +1 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/core_concepts/parameter_names.rst +40 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paramrf.egg-info/PKG-INFO +1 -1
- {paramrf-0.35.2 → paramrf-0.35.3}/paramrf.egg-info/SOURCES.txt +3 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/__init__.py +2 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/__init__.py +5 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/__init__.py +2 -1
- paramrf-0.35.3/pmrf/models/adapters/derived.py +144 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/parameters.py +54 -10
- {paramrf-0.35.2 → paramrf-0.35.3}/pyproject.toml +1 -1
- paramrf-0.35.3/tests/test_models/test_derived.py +234 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_structural_updates.py +71 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/.github/workflows/docs.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/.github/workflows/draft-pdf.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/.github/workflows/publish.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/.github/workflows/tests.yml +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/.gitignore +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/AGENTS.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/CHANGELOG.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/CITATION.cff +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/CLAUDE.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/CONTRIBUTING.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/LICENSE +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/NOTICE +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/README.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/assets/logo.png +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/Makefile +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/_static/custom.css +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/_templates/autosummary/class.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/_templates/autosummary/function.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/_templates/autosummary/module.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/adr/0001-line-modelling-architecture.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/agents/domain.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/agents/issue-tracker.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/agents/triage-labels.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/conf.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/core_concepts/core_primitives.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/core_concepts/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/core_concepts/jax_overview.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/core_concepts/optimization_and_inference.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/cascading_and_terminating.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/circuit_clc.png +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/circuit_models.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/custom_models.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/derivatives_and_sweeps.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/model_optimization.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/examples/shared_substrates.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/license.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/make.bat +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/research/conductor-loss-alternatives.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/research/holloway1994.pdf +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/research/microstrip-loss-conventions.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/skrf_comparison/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/skrf_comparison/overview.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/skrf_comparison/performance.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/tutorials/1_cable_fitting.ipynb +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/docs/tutorials/index.rst +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paper/paper.bib +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paper/paper.md +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paper/rlc.png +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paramrf.egg-info/dependency_links.txt +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paramrf.egg-info/requires.txt +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/paramrf.egg-info/top_level.txt +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/_solver_view.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/bijectors.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/constraints.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/covariance_kernels.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/discrepancy_models.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/distributions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/evaluators.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/fitting/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/fitting/minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/fitting/result.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/fitting/routers.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/fitting/sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/fitting/targets.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/frequency.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/result.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/solvers/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/solvers/blackjax.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/infer/solvers/polychord.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/likelihoods.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/losses.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/conductor.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/dielectric.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/properties.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/roughness.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/substrate.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/materials/surface_impedance.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/math/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/math/aggregations.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/math/bessel.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/math/conversions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/math/losses.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/math/misc.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/bridge.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/callable.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/delegated.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/static.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/adapters/wrapped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/ideal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/coaxial.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/empirical.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/ideal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/microstrip.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/nonuniform.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/planar.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lines/stripline.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/lumped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/components/sections.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/cascade.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/circuit.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/interconnected/terminated.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/topological.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/composite/transformed.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/surrogates/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/surrogates/expansion.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/models/surrogates/rational.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/modules/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/modules/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/modules/wrapped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/network_collection.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/noise_models.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/result.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/solvers/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/solvers/jaxopt.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/solvers/optimistix.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/optimize/solvers/scipy.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/problems.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/rf/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/rf/conversions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/rf/mna.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/serialization.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/terms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/types.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/array.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/debug.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/network.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/random.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/rf.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/transforms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/tree.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/utils/type.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/viz/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/pmrf/viz/plots.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/scripts/install-test-deps.sh +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/setup.cfg +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/__init__.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/_dependency_checks.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/_jit.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/conftest.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/data/10m_cable.s2p +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_autodiff.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_conversions.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_covariance_kernels.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_dc_limits.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_evaluators.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_fitting_minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_fitting_routers.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_fitting_sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_fitting_targets.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_frequency.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_infer_base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_infer_sample.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_map_priors.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_materials/test_conductor.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_materials/test_dielectric.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_materials/test_serialization.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_materials/test_substrate.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_materials/test_surface_impedance.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_math/test_bessel.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_model.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_adapters.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_circuit_nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_circuit_port_order.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_circuit_scattering.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_current_distribution.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_interconnected.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_lines.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_lines_skrf_matrix.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_lumped.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_nodal.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_sections.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_transformed.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_models/test_transformers.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_module.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_naming.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_optimize_base.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_optimize_minimize.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_parameters.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_parameters_by_name.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_raw_space_solving.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_serialization.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_terms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_transforms.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_utils/test_compress.py +0 -0
- {paramrf-0.35.2 → paramrf-0.35.3}/tests/test_utils/test_tree.py +0 -0
|
@@ -45,13 +45,25 @@ 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
|
+
### Derived model
|
|
58
|
+
|
|
59
|
+
A model computed from a **base** model and **new parameters** by a function,
|
|
60
|
+
`f(base, **new)`, built with `prf.derived` (ADR-0003). The base and the new
|
|
61
|
+
parameters are held once; the base keeps its names and each new parameter is
|
|
62
|
+
named by its keyword. Used to derive a more complete model from a nominal one
|
|
63
|
+
(a wet section, a cut) and, by nesting, to share a parameter across parts.
|
|
64
|
+
|
|
65
|
+
*Avoid:* "tie with new parameters", "shared parameter" as a separate concept.
|
|
66
|
+
|
|
55
67
|
### Parameter values
|
|
56
68
|
|
|
57
69
|
`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.
|
|
@@ -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,7 @@ 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
|
|
28
29
|
docs/agents/domain.md
|
|
29
30
|
docs/agents/issue-tracker.md
|
|
30
31
|
docs/agents/triage-labels.md
|
|
@@ -115,6 +116,7 @@ pmrf/models/adapters/base.py
|
|
|
115
116
|
pmrf/models/adapters/bridge.py
|
|
116
117
|
pmrf/models/adapters/callable.py
|
|
117
118
|
pmrf/models/adapters/delegated.py
|
|
119
|
+
pmrf/models/adapters/derived.py
|
|
118
120
|
pmrf/models/adapters/static.py
|
|
119
121
|
pmrf/models/adapters/wrapped.py
|
|
120
122
|
pmrf/models/components/__init__.py
|
|
@@ -214,6 +216,7 @@ tests/test_models/test_circuit_nodal.py
|
|
|
214
216
|
tests/test_models/test_circuit_port_order.py
|
|
215
217
|
tests/test_models/test_circuit_scattering.py
|
|
216
218
|
tests/test_models/test_current_distribution.py
|
|
219
|
+
tests/test_models/test_derived.py
|
|
217
220
|
tests/test_models/test_interconnected.py
|
|
218
221
|
tests/test_models/test_lines.py
|
|
219
222
|
tests/test_models/test_lines_skrf_matrix.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
|
|
@@ -125,6 +126,7 @@ __all__ = [
|
|
|
125
126
|
# Base/Core
|
|
126
127
|
"Model",
|
|
127
128
|
"is_model",
|
|
129
|
+
"derived",
|
|
128
130
|
"Module",
|
|
129
131
|
"is_module",
|
|
130
132
|
"Frequency",
|
|
@@ -198,6 +198,11 @@ from pmrf.models.adapters.wrapped import (
|
|
|
198
198
|
Wrapped as Wrapped,
|
|
199
199
|
)
|
|
200
200
|
|
|
201
|
+
from pmrf.models.adapters.derived import (
|
|
202
|
+
Derived as Derived,
|
|
203
|
+
derived as derived,
|
|
204
|
+
)
|
|
205
|
+
|
|
201
206
|
# Compatibility re-exports. Parameter-aware wrappers live under ``pmrf.modules``.
|
|
202
207
|
from pmrf.modules import Tied as Tied, Probabilistic as Probabilistic
|
|
203
208
|
|
|
@@ -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
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Models derived from an existing model with new parameters."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import functools
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from pmrf.models.base import Model
|
|
8
|
+
from pmrf.models.adapters.delegated import AbstractBuilder
|
|
9
|
+
from pmrf.utils import field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Derived(AbstractBuilder):
|
|
13
|
+
"""A model computed by a function from a base and new named parameters.
|
|
14
|
+
|
|
15
|
+
Built by :func:`derived`, which documents the contract. The base and the new
|
|
16
|
+
parameters are held once, as ``operands = (base, new)``, and ``fn(base, **new)``
|
|
17
|
+
is called on their unwrapped values whenever the model is used. Parameter names
|
|
18
|
+
see through this wrapper: the base's names are unchanged and each new parameter
|
|
19
|
+
is named by its keyword.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
#: The base, and the new parameters keyed by keyword.
|
|
23
|
+
operands: tuple[Any, dict[str, Any]]
|
|
24
|
+
|
|
25
|
+
#: The function building the model from the base and the new parameters.
|
|
26
|
+
fn: Callable[..., Model] = field(static=True, kw_only=True)
|
|
27
|
+
|
|
28
|
+
def build(self) -> Model:
|
|
29
|
+
base, new = self.operands
|
|
30
|
+
model = self.fn(base, **new)
|
|
31
|
+
if not isinstance(model, Model):
|
|
32
|
+
raise TypeError(
|
|
33
|
+
f"The derived function {getattr(self.fn, '__qualname__', self.fn)!r} must "
|
|
34
|
+
f"return a pmrf.Model; got {type(model).__name__}."
|
|
35
|
+
)
|
|
36
|
+
return model
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _unnamed(node: Any) -> Any:
|
|
40
|
+
"""Returns `node` without its own name, so its parameters keep their names below it."""
|
|
41
|
+
if getattr(node, 'name', None) is None:
|
|
42
|
+
return node
|
|
43
|
+
node = copy.copy(node)
|
|
44
|
+
object.__setattr__(node, 'name', None)
|
|
45
|
+
return node
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def derived(fn: Callable[..., Model]) -> Callable[..., Derived]:
|
|
49
|
+
"""
|
|
50
|
+
Turns a function of a base model and new parameters into a derived model constructor.
|
|
51
|
+
|
|
52
|
+
A derived model starts from a nominal model and derives a more complete one that
|
|
53
|
+
captures some artifact (a wet section, degraded copper, a cut). The decorated
|
|
54
|
+
function is called as ``f(base, **new)``: exactly one positional base (a model,
|
|
55
|
+
or any collection of models and parameters) and keyword arguments that are the
|
|
56
|
+
new parameters (a :class:`pmrf.Param`, or anything :func:`pmrf.as_param`
|
|
57
|
+
accepts). It returns a :class:`pmrf.Model` that holds the base and the new
|
|
58
|
+
parameters once, and calls `fn` on them whenever the model is used.
|
|
59
|
+
|
|
60
|
+
`fn` receives the base and the new parameters as the model is unwrapped, so
|
|
61
|
+
parameters arrive as physical values, as for :func:`pmrf.tie`. It must return a
|
|
62
|
+
:class:`pmrf.Model`, must be pure, and its output's structure and port count must
|
|
63
|
+
not depend on parameter values. Inside `fn`, use :func:`pmrf.replace` for fields
|
|
64
|
+
of the object in hand and :func:`pmrf.update` for parts reached by name.
|
|
65
|
+
|
|
66
|
+
**Names.** The base's parameters keep the names they have on the base, and each
|
|
67
|
+
new parameter is named by its keyword. Nothing produced inside `fn` is named. The
|
|
68
|
+
derived model takes the base's name unless ``name=`` is passed, so a container
|
|
69
|
+
prefixes both as usual. A derived model can be the base of another, and names
|
|
70
|
+
accumulate flat; a parameter shared by several parts is expressed by deriving at
|
|
71
|
+
the level that owns it.
|
|
72
|
+
|
|
73
|
+
`fn` is a static part of the model: two models derived with the same function
|
|
74
|
+
share a jit cache entry. Define it once at module level; a lambda created on every
|
|
75
|
+
call recompiles.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
fn : Callable
|
|
80
|
+
``fn(base, **new) -> Model``.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
Callable
|
|
85
|
+
``constructor(base, *, name=None, **new) -> Derived``.
|
|
86
|
+
|
|
87
|
+
Raises
|
|
88
|
+
------
|
|
89
|
+
TypeError
|
|
90
|
+
On a call with other than one positional base or with no new parameters, or
|
|
91
|
+
(when the model is used) if `fn` does not return a model.
|
|
92
|
+
ValueError
|
|
93
|
+
If a keyword clashes with a parameter name of the base.
|
|
94
|
+
|
|
95
|
+
Examples
|
|
96
|
+
--------
|
|
97
|
+
A coaxial cable of total length ``L`` that is wet for its first ``wet_length``:
|
|
98
|
+
|
|
99
|
+
.. code-block:: python
|
|
100
|
+
|
|
101
|
+
@prf.derived
|
|
102
|
+
def wet(cable, wet_length, wet_ep_r):
|
|
103
|
+
wet = prf.replace(cable, length=wet_length,
|
|
104
|
+
dielectric=prf.replace(cable.dielectric, ep_r=wet_ep_r))
|
|
105
|
+
dry = prf.replace(cable, length=cable.length - wet_length)
|
|
106
|
+
return wet ** dry
|
|
107
|
+
|
|
108
|
+
coax = wet(coax, wet_length=prf.Random(Uniform(0, 20), scale=1e-3),
|
|
109
|
+
wet_ep_r=prf.Random(Uniform(1, 80)))
|
|
110
|
+
prf.params(coax) # the cable's names, plus 'wet_length' and 'wet_ep_r'
|
|
111
|
+
"""
|
|
112
|
+
from pmrf.parameters import as_param, params
|
|
113
|
+
|
|
114
|
+
@functools.wraps(fn)
|
|
115
|
+
def constructor(*args, name: str | None = None, **new) -> Derived:
|
|
116
|
+
if len(args) != 1:
|
|
117
|
+
raise TypeError(
|
|
118
|
+
f"{fn.__name__}() takes exactly one positional base and new parameters as "
|
|
119
|
+
f"keywords, e.g. {fn.__name__}(model, wet_length=...); got {len(args)} "
|
|
120
|
+
"positional arguments."
|
|
121
|
+
)
|
|
122
|
+
if not new:
|
|
123
|
+
raise TypeError(f"{fn.__name__}() needs at least one new parameter as a keyword.")
|
|
124
|
+
base = args[0]
|
|
125
|
+
clashes = sorted(set(new) & set(params(base)))
|
|
126
|
+
if clashes:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"{fn.__name__}(): new parameter names {clashes} clash with parameters of "
|
|
129
|
+
"the base. Choose different keywords."
|
|
130
|
+
)
|
|
131
|
+
coerced = {}
|
|
132
|
+
for key, value in new.items():
|
|
133
|
+
value = as_param(value)
|
|
134
|
+
if value.name is not None:
|
|
135
|
+
value = _unnamed(value)
|
|
136
|
+
coerced[key] = value
|
|
137
|
+
if name is None:
|
|
138
|
+
name = getattr(base, 'name', None)
|
|
139
|
+
return Derived((_unnamed(base), coerced), fn=fn, name=name)
|
|
140
|
+
|
|
141
|
+
return constructor
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
__all__ = ["Derived", "derived"]
|
|
@@ -955,11 +955,12 @@ def _is_name_leaf(x: Any) -> bool:
|
|
|
955
955
|
|
|
956
956
|
def _is_name_transparent(x: Any) -> bool:
|
|
957
957
|
"""Wrappers whose own path parts are omitted from parameter names."""
|
|
958
|
+
from pmrf.models.adapters.derived import Derived
|
|
958
959
|
from pmrf.models.adapters.wrapped import Wrapped
|
|
959
960
|
from pmrf.modules.base import Module
|
|
960
961
|
from pmrf.modules.wrapped import Probabilistic, Tied
|
|
961
962
|
|
|
962
|
-
if isinstance(x, (Tied, Probabilistic, Wrapped)):
|
|
963
|
+
if isinstance(x, (Tied, Probabilistic, Wrapped, Derived)):
|
|
963
964
|
return True
|
|
964
965
|
return isinstance(x, prx.AbstractUnwrappable) and not isinstance(x, Module) and not is_param(x)
|
|
965
966
|
|
|
@@ -1378,15 +1379,20 @@ def _select_parts(tree, where: Selector) -> list[tuple[Any, ...]]:
|
|
|
1378
1379
|
paths.extend(resolved[name][0] for name in hits)
|
|
1379
1380
|
|
|
1380
1381
|
unique = list(dict.fromkeys(paths))
|
|
1381
|
-
|
|
1382
|
-
|
|
1382
|
+
_check_no_overlap(unique)
|
|
1383
|
+
return unique
|
|
1384
|
+
|
|
1385
|
+
|
|
1386
|
+
def _check_no_overlap(paths: list[tuple[Any, ...]]):
|
|
1387
|
+
"""Raises if one of the selected paths contains another."""
|
|
1388
|
+
for a in paths:
|
|
1389
|
+
for b in paths:
|
|
1383
1390
|
if a != b and b[:len(a)] == a:
|
|
1384
1391
|
raise ValueError("The selected parts overlap: one contains another.")
|
|
1385
|
-
return unique
|
|
1386
1392
|
|
|
1387
1393
|
|
|
1388
1394
|
_UPDATE_FORMS = """prf.update takes one of these forms:
|
|
1389
|
-
update(model, {'name': value, ...}, space=...) values by name
|
|
1395
|
+
update(model, {'name': value, ...}, space=...) values by name, or sub-models by name
|
|
1390
1396
|
update(model, where, value=..., space=...) one value for the selected parameters
|
|
1391
1397
|
update(model, where, fixed=True or False) fixed state of the selected parameters
|
|
1392
1398
|
update(model, where, node) replace the selected parts with `node`
|
|
@@ -1417,6 +1423,7 @@ def update(
|
|
|
1417
1423
|
.. code-block:: python
|
|
1418
1424
|
|
|
1419
1425
|
prf.update(model, {'L1.L': 3.0, 'C1.C': 2.0}) # values by name
|
|
1426
|
+
prf.update(model, {'load': Short(), 'L1.L': 3.0}) # sub-models and values by name
|
|
1420
1427
|
prf.update(model, 'L1.*', value=3.0) # one value for a selection
|
|
1421
1428
|
prf.update(model, 'cable.*', fixed=True) # fixed state
|
|
1422
1429
|
prf.update(model, 'cascade[1]', Short()) # a new sub-model or node
|
|
@@ -1434,6 +1441,13 @@ def update(
|
|
|
1434
1441
|
and put exactly what they are given in place of each selected part, so the caller
|
|
1435
1442
|
keeps field invariants. They usually change the structure, and recompile.
|
|
1436
1443
|
|
|
1444
|
+
In the mapping form the tier is decided per entry by the value's type. A
|
|
1445
|
+
:class:`pmrf.Model` value is a structural replacement of the sub-model its key
|
|
1446
|
+
names, as in ``update(model, key, node)``: it is unvalidated and usually
|
|
1447
|
+
recompiles, and `space` does not apply to it. Any other value is a validated
|
|
1448
|
+
value update of the parameter its key names. A mapping of values only keeps the
|
|
1449
|
+
jit cache key.
|
|
1450
|
+
|
|
1437
1451
|
``fixed=`` is additive: parameters the selector does not match are untouched,
|
|
1438
1452
|
and ``fixed=False`` frees a parameter even if it was created fixed. It does not
|
|
1439
1453
|
unfreeze: a parameter inside a :func:`pmrf.freeze` sub-tree stays frozen. To
|
|
@@ -1452,7 +1466,9 @@ def update(
|
|
|
1452
1466
|
Either a mapping from names to values, or a selector: a name, an `fnmatch`
|
|
1453
1467
|
glob over names, a sequence of them, or a callable returning nodes of
|
|
1454
1468
|
`tree`. A mapping is recognised only when every key is a string. Its values
|
|
1455
|
-
may be arrays, or parameters, whose value in `space` is used
|
|
1469
|
+
may be arrays, or parameters, whose value in `space` is used, keyed by
|
|
1470
|
+
parameter name; or models, keyed by sub-model name, which replace that
|
|
1471
|
+
sub-model structurally. Omit it to
|
|
1456
1472
|
update `tree` itself, which must then be a parameter. In the structural
|
|
1457
1473
|
forms, an exact name may also name a sub-model (``'cascade[1]'``, or a named
|
|
1458
1474
|
module's name), a glob matches parameter names only, and a callable selects
|
|
@@ -1480,7 +1496,8 @@ def update(
|
|
|
1480
1496
|
structurally selected parts overlap. Under `jax.jit` the bounds check raises
|
|
1481
1497
|
at runtime.
|
|
1482
1498
|
TypeError
|
|
1483
|
-
If the arguments match none of the forms
|
|
1499
|
+
If the arguments match none of the forms, or a mapping gives a model for a
|
|
1500
|
+
parameter name or a non-model for a sub-model name.
|
|
1484
1501
|
"""
|
|
1485
1502
|
has_value = value is not _MISSING
|
|
1486
1503
|
has_fixed = fixed is not None
|
|
@@ -1517,12 +1534,39 @@ def update(
|
|
|
1517
1534
|
if isinstance(selection, Mapping):
|
|
1518
1535
|
if has_value or has_fixed or not all(isinstance(k, str) for k in selection):
|
|
1519
1536
|
raise form_error()
|
|
1537
|
+
from pmrf.models.base import Model
|
|
1538
|
+
|
|
1520
1539
|
resolved = tree_param_paths(tree)
|
|
1521
|
-
|
|
1540
|
+
submodels = None
|
|
1541
|
+
paths, nodes, unknown = [], [], []
|
|
1542
|
+
for name, v in selection.items():
|
|
1543
|
+
# A joint target is named like a parameter, and its value may itself be a model.
|
|
1544
|
+
if isinstance(v, Model) and not (name in resolved and _is_joint_target(resolved[name][1])):
|
|
1545
|
+
if name in resolved:
|
|
1546
|
+
raise TypeError(f"'{name}' is a parameter name, but its value is a model; "
|
|
1547
|
+
"a model can only replace a sub-model.")
|
|
1548
|
+
if submodels is None:
|
|
1549
|
+
submodels = _tree_submodel_paths(tree)
|
|
1550
|
+
if name not in submodels:
|
|
1551
|
+
raise ValueError(f"Unknown sub-model name: '{name}'")
|
|
1552
|
+
if len(submodels[name]) > 1:
|
|
1553
|
+
raise ValueError(f"Sub-model name '{name}' is ambiguous: several sub-models have it.")
|
|
1554
|
+
paths.append(submodels[name][0])
|
|
1555
|
+
nodes.append(v)
|
|
1556
|
+
elif name in resolved:
|
|
1557
|
+
paths.append(resolved[name][0])
|
|
1558
|
+
nodes.append(_write(resolved[name][1], v, space))
|
|
1559
|
+
else:
|
|
1560
|
+
if submodels is None:
|
|
1561
|
+
submodels = _tree_submodel_paths(tree)
|
|
1562
|
+
if name in submodels:
|
|
1563
|
+
raise TypeError(f"'{name}' names a sub-model, but its value is not a model; "
|
|
1564
|
+
"only a pmrf.Model can replace a sub-model.")
|
|
1565
|
+
unknown.append(name)
|
|
1522
1566
|
if unknown:
|
|
1523
1567
|
raise ValueError(f"Unknown parameter names: {unknown}")
|
|
1524
|
-
|
|
1525
|
-
|
|
1568
|
+
if submodels is not None:
|
|
1569
|
+
_check_no_overlap(paths)
|
|
1526
1570
|
return _set_paths(tree, paths, nodes)
|
|
1527
1571
|
|
|
1528
1572
|
if not _is_selector(selection) or has_value == has_fixed:
|