paramrf 0.35.0__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.0 → paramrf-0.35.3}/CONTEXT.md +15 -3
- {paramrf-0.35.0 → paramrf-0.35.3}/PKG-INFO +1 -1
- {paramrf-0.35.0 → 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.0 → paramrf-0.35.3}/docs/api/index.rst +1 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/core_concepts/parameter_names.rst +40 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paramrf.egg-info/PKG-INFO +1 -1
- {paramrf-0.35.0 → paramrf-0.35.3}/paramrf.egg-info/SOURCES.txt +4 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/__init__.py +2 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/__init__.py +5 -0
- {paramrf-0.35.0 → 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.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/circuit.py +53 -9
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/parameters.py +54 -10
- {paramrf-0.35.0 → paramrf-0.35.3}/pyproject.toml +1 -1
- paramrf-0.35.3/tests/test_models/test_circuit_port_order.py +168 -0
- paramrf-0.35.3/tests/test_models/test_derived.py +234 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_structural_updates.py +71 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/.github/workflows/docs.yml +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/.github/workflows/draft-pdf.yml +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/.github/workflows/publish.yml +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/.github/workflows/tests.yml +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/.gitignore +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/AGENTS.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/CHANGELOG.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/CITATION.cff +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/CLAUDE.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/CONTRIBUTING.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/LICENSE +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/NOTICE +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/README.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/assets/logo.png +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/Makefile +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/_static/custom.css +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/_templates/autosummary/class.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/_templates/autosummary/function.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/_templates/autosummary/module.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/adr/0001-line-modelling-architecture.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/agents/domain.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/agents/issue-tracker.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/agents/triage-labels.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/conf.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/core_concepts/core_primitives.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/core_concepts/index.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/core_concepts/jax_overview.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/core_concepts/optimization_and_inference.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/cascading_and_terminating.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/circuit_clc.png +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/circuit_models.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/custom_models.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/derivatives_and_sweeps.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/index.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/model_optimization.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/examples/shared_substrates.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/index.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/license.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/make.bat +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/research/conductor-loss-alternatives.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/research/holloway1994.pdf +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/research/microstrip-loss-conventions.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/skrf_comparison/index.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/skrf_comparison/overview.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/skrf_comparison/performance.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/tutorials/1_cable_fitting.ipynb +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/docs/tutorials/index.rst +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paper/paper.bib +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paper/paper.md +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paper/rlc.png +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paramrf.egg-info/dependency_links.txt +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paramrf.egg-info/requires.txt +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/paramrf.egg-info/top_level.txt +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/_solver_view.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/bijectors.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/constraints.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/covariance_kernels.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/discrepancy_models.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/distributions.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/evaluators.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/fitting/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/fitting/minimize.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/fitting/result.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/fitting/routers.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/fitting/sample.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/fitting/targets.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/frequency.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/result.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/sample.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/solvers/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/solvers/blackjax.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/infer/solvers/polychord.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/likelihoods.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/losses.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/conductor.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/dielectric.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/properties.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/roughness.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/substrate.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/materials/surface_impedance.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/math/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/math/aggregations.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/math/bessel.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/math/conversions.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/math/losses.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/math/misc.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/adapters/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/adapters/bridge.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/adapters/callable.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/adapters/delegated.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/adapters/static.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/adapters/wrapped.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/ideal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/coaxial.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/empirical.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/ideal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/microstrip.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/nodal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/nonuniform.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/planar.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lines/stripline.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/lumped.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/components/sections.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/cascade.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/interconnected/terminated.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/nodal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/topological.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/composite/transformed.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/surrogates/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/surrogates/expansion.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/models/surrogates/rational.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/modules/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/modules/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/modules/wrapped.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/network_collection.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/noise_models.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/minimize.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/result.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/solvers/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/solvers/jaxopt.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/solvers/optimistix.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/optimize/solvers/scipy.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/problems.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/rf/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/rf/conversions.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/rf/mna.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/serialization.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/terms.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/types.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/array.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/debug.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/network.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/random.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/rf.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/transforms.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/tree.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/utils/type.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/viz/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/pmrf/viz/plots.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/scripts/install-test-deps.sh +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/setup.cfg +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/__init__.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/_dependency_checks.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/_jit.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/conftest.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/data/10m_cable.s2p +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_autodiff.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_conversions.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_covariance_kernels.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_dc_limits.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_evaluators.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_fitting_minimize.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_fitting_routers.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_fitting_sample.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_fitting_targets.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_frequency.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_infer_base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_infer_sample.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_map_priors.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_materials/test_conductor.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_materials/test_dielectric.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_materials/test_serialization.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_materials/test_substrate.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_materials/test_surface_impedance.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_math/test_bessel.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_model.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_adapters.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_circuit_nodal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_circuit_scattering.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_current_distribution.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_interconnected.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_lines.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_lines_skrf_matrix.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_lumped.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_nodal.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_sections.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_transformed.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_models/test_transformers.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_module.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_naming.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_optimize_base.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_optimize_minimize.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_parameters.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_parameters_by_name.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_raw_space_solving.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_serialization.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_terms.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_transforms.py +0 -0
- {paramrf-0.35.0 → paramrf-0.35.3}/tests/test_utils/test_compress.py +0 -0
- {paramrf-0.35.0 → 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
|
|
@@ -211,8 +213,10 @@ tests/test_materials/test_surface_impedance.py
|
|
|
211
213
|
tests/test_math/test_bessel.py
|
|
212
214
|
tests/test_models/test_adapters.py
|
|
213
215
|
tests/test_models/test_circuit_nodal.py
|
|
216
|
+
tests/test_models/test_circuit_port_order.py
|
|
214
217
|
tests/test_models/test_circuit_scattering.py
|
|
215
218
|
tests/test_models/test_current_distribution.py
|
|
219
|
+
tests/test_models/test_derived.py
|
|
216
220
|
tests/test_models/test_interconnected.py
|
|
217
221
|
tests/test_models/test_lines.py
|
|
218
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"]
|
|
@@ -48,18 +48,26 @@ class Circuit(Model):
|
|
|
48
48
|
of nodes. Each node connects one or more ports of the constituent models
|
|
49
49
|
to form a composite network.
|
|
50
50
|
|
|
51
|
+
The external ports follow `Port` declaration order: scanning `connections`
|
|
52
|
+
node by node, left to right, the first `Port` found is port 0. This holds for
|
|
53
|
+
every solver, nested or flattened.
|
|
54
|
+
|
|
51
55
|
Parameters
|
|
52
56
|
----------
|
|
53
57
|
connections : list[list[tuple[Model, int]]]
|
|
54
58
|
A list representing the nodes of the circuit. Each node is a list of
|
|
55
59
|
tuples, where each tuple contains a model and the integer
|
|
56
60
|
index of the port to connect to that node.
|
|
57
|
-
flatten: bool, default=
|
|
61
|
+
flatten: bool, default=False
|
|
58
62
|
Flattens sub-circuits and sub-cascades to perform a single solve.
|
|
59
|
-
|
|
60
|
-
solver : AbstractCircuitSolver, default=GlobalScatteringCircuitSolver()
|
|
63
|
+
solver : AbstractCircuitSolver, default=GlobalMNACircuitSolver()
|
|
61
64
|
The circuit solver to use. Available solvers can be found in :mod:`pmrf.models`.
|
|
62
65
|
|
|
66
|
+
Raises
|
|
67
|
+
------
|
|
68
|
+
ValueError
|
|
69
|
+
If two `Port`\\ s share a net, or a `Port` shares a net with a `Ground`.
|
|
70
|
+
|
|
63
71
|
Examples
|
|
64
72
|
--------
|
|
65
73
|
Create a two-port PI-CLC network. External nodes are defined using `Port`,
|
|
@@ -75,8 +83,8 @@ class Circuit(Model):
|
|
|
75
83
|
>>>
|
|
76
84
|
>>> # Create the connections list
|
|
77
85
|
>>> connections = [
|
|
78
|
-
... [(p0, 0), (C1, 1), (L, 1)], # Node 0 -> Port
|
|
79
|
-
... [(p1, 0), (C2, 1), (L, 0)], # Node 1 -> Port
|
|
86
|
+
... [(p0, 0), (C1, 1), (L, 1)], # Node 0 -> Port 0
|
|
87
|
+
... [(p1, 0), (C2, 1), (L, 0)], # Node 1 -> Port 1
|
|
80
88
|
... [(ground, 0), (C1, 0), (C2, 0)], # Node 2 -> Ground
|
|
81
89
|
... ]
|
|
82
90
|
>>>
|
|
@@ -149,6 +157,34 @@ class Circuit(Model):
|
|
|
149
157
|
|
|
150
158
|
self.circuit = models
|
|
151
159
|
self.indexed_connections = indexed_connections
|
|
160
|
+
self._validate_port_nets()
|
|
161
|
+
|
|
162
|
+
def _validate_port_nets(self):
|
|
163
|
+
"""Ensures every `Port` has a net to itself that is not grounded."""
|
|
164
|
+
global_net_map = compute_unique_nets(self.circuit, self.indexed_connections)
|
|
165
|
+
|
|
166
|
+
net_to_port = {}
|
|
167
|
+
ground_nets = set()
|
|
168
|
+
offset = 0
|
|
169
|
+
port_idx = 0
|
|
170
|
+
for model in self.circuit:
|
|
171
|
+
if isinstance(model, Ground):
|
|
172
|
+
ground_nets.update(global_net_map[offset : offset + model.nports].tolist())
|
|
173
|
+
elif isinstance(model, Port):
|
|
174
|
+
net = int(global_net_map[offset])
|
|
175
|
+
label = f"Port {port_idx} ('{getattr(model, 'name', None) or 'unnamed'}')"
|
|
176
|
+
if net in net_to_port:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"{net_to_port[net]} and {label} are connected to the same net. "
|
|
179
|
+
"Each Port must be on its own net."
|
|
180
|
+
)
|
|
181
|
+
net_to_port[net] = label
|
|
182
|
+
port_idx += 1
|
|
183
|
+
offset += model.nports
|
|
184
|
+
|
|
185
|
+
for net, label in net_to_port.items():
|
|
186
|
+
if net in ground_nets:
|
|
187
|
+
raise ValueError(f"{label} is connected to a Ground net. A Port cannot be grounded.")
|
|
152
188
|
|
|
153
189
|
def expand(self):
|
|
154
190
|
internal_ports = [comp for comp in self.circuit if isinstance(comp, Port)]
|
|
@@ -209,14 +245,14 @@ class Circuit(Model):
|
|
|
209
245
|
global_net_map = compute_unique_nets(self.circuit, self.indexed_connections)
|
|
210
246
|
|
|
211
247
|
ground_nets = set()
|
|
212
|
-
ext_nets =
|
|
248
|
+
ext_nets = [] # In `Port` declaration order, which fixes the external port order
|
|
213
249
|
offset = 0
|
|
214
250
|
for model in self.circuit:
|
|
215
251
|
nets = global_net_map[offset : offset + model.nports]
|
|
216
252
|
if isinstance(model, Ground):
|
|
217
253
|
ground_nets.update(nets)
|
|
218
254
|
elif isinstance(model, Port):
|
|
219
|
-
ext_nets.
|
|
255
|
+
ext_nets.extend(nets)
|
|
220
256
|
offset += model.nports
|
|
221
257
|
|
|
222
258
|
unique_all_nets = np.unique(global_net_map)
|
|
@@ -243,7 +279,7 @@ class Circuit(Model):
|
|
|
243
279
|
r_idx_mapped = mapping[_safe_cat(r_idx)]
|
|
244
280
|
c_idx_mapped = mapping[_safe_cat(c_idx)]
|
|
245
281
|
|
|
246
|
-
ext_active = np.array(
|
|
282
|
+
ext_active = np.array(ext_nets, dtype=int)
|
|
247
283
|
int_active = np.setdiff1d(active_nets, ext_active)
|
|
248
284
|
|
|
249
285
|
return NodalRepresentation(
|
|
@@ -723,6 +759,14 @@ def flatten_hierarchy(connections: list[list[tuple[Model, int]]]) -> list[list[t
|
|
|
723
759
|
group.sort(key=lambda x: (model_discovery_order[id(x[0])], x[1]))
|
|
724
760
|
valid_groups.append(group)
|
|
725
761
|
|
|
726
|
-
|
|
762
|
+
# Nets holding a top-level `Port` come first, in `Port` declaration order, since
|
|
763
|
+
# the flat circuit's external port order is the order its `Port`s first appear.
|
|
764
|
+
def group_order(group):
|
|
765
|
+
port_orders = [model_discovery_order[id(m)] for m, _ in group if isinstance(m, Port)]
|
|
766
|
+
if port_orders:
|
|
767
|
+
return (0, min(port_orders), 0)
|
|
768
|
+
return (1, model_discovery_order[id(group[0][0])], group[0][1])
|
|
769
|
+
|
|
770
|
+
valid_groups.sort(key=group_order)
|
|
727
771
|
|
|
728
772
|
return valid_groups
|