paramrf 0.36.1__tar.gz → 0.36.2__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.36.1 → paramrf-0.36.2}/CONTEXT.md +17 -2
- {paramrf-0.36.1/paramrf.egg-info → paramrf-0.36.2}/PKG-INFO +2 -2
- paramrf-0.36.2/docs/adr/0007-edge-values.md +130 -0
- {paramrf-0.36.1 → paramrf-0.36.2/paramrf.egg-info}/PKG-INFO +2 -2
- {paramrf-0.36.1 → paramrf-0.36.2}/paramrf.egg-info/SOURCES.txt +4 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/paramrf.egg-info/requires.txt +1 -1
- paramrf-0.36.2/pmrf/_solver_view.py +284 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/constraints.py +4 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/base.py +5 -4
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/dielectric.py +10 -10
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/roughness.py +2 -2
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/surface_impedance.py +22 -7
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/base.py +5 -3
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/ideal.py +11 -13
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/coaxial.py +4 -1
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/empirical.py +5 -5
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/ideal.py +3 -3
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/microstrip.py +12 -1
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lumped.py +133 -33
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/sections.py +25 -12
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/circuit/base.py +9 -3
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/circuit/circuit.py +28 -10
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +56 -40
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/base.py +50 -24
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/minimize.py +3 -3
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/solvers/scipy.py +14 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/rf/__init__.py +2 -1
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/rf/conversions.py +80 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/transforms.py +28 -12
- {paramrf-0.36.1 → paramrf-0.36.2}/pyproject.toml +2 -2
- paramrf-0.36.2/tests/test_box_space_solving.py +197 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_conversions.py +47 -2
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_fitting_targets.py +4 -3
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_materials/test_conductor.py +27 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_materials/test_dielectric.py +79 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_circuit_nodal.py +12 -8
- paramrf-0.36.2/tests/test_models/test_circuit_shorted_ports.py +138 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_derived.py +4 -1
- paramrf-0.36.2/tests/test_models/test_edge_values.py +256 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_lines.py +91 -0
- paramrf-0.36.2/tests/test_models/test_lumped.py +245 -0
- paramrf-0.36.2/tests/test_models/test_sections.py +215 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_transformers.py +21 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_optimize_base.py +4 -3
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_raw_space_solving.py +97 -14
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_transforms.py +99 -0
- paramrf-0.36.1/pmrf/_solver_view.py +0 -125
- paramrf-0.36.1/tests/test_models/test_lumped.py +0 -82
- paramrf-0.36.1/tests/test_models/test_sections.py +0 -107
- {paramrf-0.36.1 → paramrf-0.36.2}/.github/workflows/docs.yml +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/.github/workflows/draft-pdf.yml +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/.github/workflows/publish.yml +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/.github/workflows/tests.yml +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/.gitignore +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/AGENTS.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/CHANGELOG.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/CITATION.cff +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/CLAUDE.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/CONTRIBUTING.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/LICENSE +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/NOTICE +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/README.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/assets/logo.png +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/Makefile +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/_static/custom.css +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/_templates/autosummary/class.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/_templates/autosummary/function.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/_templates/autosummary/module.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/adr/0001-line-modelling-architecture.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/adr/0002-parameter-api.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/adr/0003-derived-models.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/adr/0004-profiled-lines.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/adr/0005-priors-by-name.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/adr/0006-reference-impedance.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/agents/domain.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/agents/issue-tracker.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/agents/triage-labels.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/api/index.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/conf.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/core_concepts/core_primitives.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/core_concepts/index.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/core_concepts/jax_overview.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/core_concepts/optimization_and_inference.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/core_concepts/parameter_names.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/cascading_and_terminating.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/circuit_clc.png +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/circuit_models.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/custom_models.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/derivatives_and_sweeps.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/index.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/model_optimization.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/examples/shared_substrates.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/index.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/license.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/make.bat +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/research/conductor-loss-alternatives.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/research/holloway1994.pdf +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/research/microstrip-loss-conventions.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/skrf_comparison/index.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/skrf_comparison/overview.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/skrf_comparison/performance.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/tutorials/1_cable_fitting.ipynb +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/docs/tutorials/index.rst +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/paper/paper.bib +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/paper/paper.md +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/paper/rlc.png +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/paramrf.egg-info/dependency_links.txt +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/paramrf.egg-info/top_level.txt +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/bijectors.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/covariance_kernels.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/discrepancy_models.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/distributions.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/evaluators.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/fitting/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/fitting/minimize.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/fitting/result.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/fitting/routers.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/fitting/sample.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/fitting/targets.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/frequency.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/result.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/sample.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/solvers/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/solvers/blackjax.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/infer/solvers/polychord.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/likelihoods.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/losses.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/conductor.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/properties.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/materials/substrate.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/math/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/math/aggregations.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/math/bessel.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/math/conversions.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/math/losses.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/math/misc.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/base.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/bridge.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/callable.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/delegated.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/derived.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/static.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/adapters/wrapped.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/base.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/nodal.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/nonuniform.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/planar.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/profiles.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/components/lines/stripline.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/cascade.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/interconnected/terminated.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/nodal.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/topological.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/composite/transformed.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/surrogates/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/surrogates/expansion.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/models/surrogates/rational.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/modules/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/modules/base.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/modules/wrapped.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/network_collection.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/noise_models.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/result.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/solvers/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/solvers/jaxopt.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/optimize/solvers/optimistix.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/parameters.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/problems.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/rf/cascade.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/rf/mna.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/serialization.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/terms.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/types.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/array.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/debug.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/network.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/random.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/rf.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/tree.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/utils/type.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/viz/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/pmrf/viz/plots.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/scripts/install-test-deps.sh +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/setup.cfg +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/__init__.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/_jit.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/conftest.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/data/10m_cable.s2p +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_autodiff.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_covariance_kernels.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_dc_limits.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_evaluators.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_fitting_minimize.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_fitting_routers.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_fitting_sample.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_frequency.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_infer_base.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_infer_sample.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_map_priors.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_materials/test_serialization.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_materials/test_substrate.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_materials/test_surface_impedance.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_math/test_bessel.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_model.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_adapters.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_cascade.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_circuit_native_z0.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_circuit_port_order.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_circuit_scattering.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_circuit_skrf_port_names.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_circuit_z0.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_current_distribution.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_interconnected.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_lines_skrf_matrix.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_nodal.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_profiles.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_rational.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_repeated_cascade.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_models/test_transformed.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_module.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_naming.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_optimize_minimize.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_parameters.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_parameters_by_name.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_parameters_joint_prior.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_parameters_prior.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_parameters_validity.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_serialization.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_structural_updates.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_terms.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_utils/test_compress.py +0 -0
- {paramrf-0.36.1 → paramrf-0.36.2}/tests/test_utils/test_tree.py +0 -0
|
@@ -26,10 +26,23 @@ Where a parameter's number lives. Every surface defaults to **declared**.
|
|
|
26
26
|
declares (2.0 for 2 pF). Construction, bounds, priors and `Param.value` are
|
|
27
27
|
in declared space.
|
|
28
28
|
- **physical**: the scaled, SI value (2e-12).
|
|
29
|
+
- **box**: the box a bounded minimiser searches, built from a parameter's
|
|
30
|
+
bounds, not its prior: the unit box when both bounds are finite, declared
|
|
31
|
+
space otherwise (ADR-0007).
|
|
29
32
|
|
|
30
33
|
*Avoid:* "unconstrained space" (reads as a parameter without bounds; that is
|
|
31
34
|
`prf.Unconstrained`), "unscaled" and "constrained" for declared space, "unit
|
|
32
|
-
space" (the unit hypercube
|
|
35
|
+
space" (the unit hypercube; box space is the unit box only when both bounds are
|
|
36
|
+
finite), "base space" for box space (Parax's name for it, but ADR-0005's "base"
|
|
37
|
+
is a flow's latent, which is raw).
|
|
38
|
+
|
|
39
|
+
### Open and closed bounds
|
|
40
|
+
|
|
41
|
+
Each bound of a parameter is **closed** (the bound is a valid value, and a
|
|
42
|
+
model must evaluate there) or **open** (the bound is excluded, and a value on
|
|
43
|
+
it is rejected at construction). Closedness belongs to the constraint:
|
|
44
|
+
`pmrf.Bounded` and a `Uniform` support are closed, `Positive()` is open, and
|
|
45
|
+
where two constraints meet at one bound, open wins (ADR-0007).
|
|
33
46
|
|
|
34
47
|
### Scale
|
|
35
48
|
|
|
@@ -184,4 +197,6 @@ paper with no ParamRF objects in sight.
|
|
|
184
197
|
interface or a default, and `docs/adr/0002-parameter-api.md` before adding a
|
|
185
198
|
method, a public function or a value space. Read
|
|
186
199
|
`docs/adr/0005-priors-by-name.md` before changing how priors are attached or
|
|
187
|
-
scored.
|
|
200
|
+
scored, and `docs/adr/0007-edge-values.md` before changing how a
|
|
201
|
+
circuit solver produces S, how bounds are validated, or which space a solver
|
|
202
|
+
moves through.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paramrf
|
|
3
|
-
Version: 0.36.
|
|
3
|
+
Version: 0.36.2
|
|
4
4
|
Summary: Parametric radio frequency modeling
|
|
5
5
|
Author-email: Gary Allen <gvcallen@gmail.com>
|
|
6
6
|
Project-URL: homepage, https://github.com/gvcallen/paramrf
|
|
7
7
|
Description-Content-Type: text/x-rst
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
License-File: NOTICE
|
|
10
|
-
Requires-Dist: parax>=0.11.
|
|
10
|
+
Requires-Dist: parax>=0.11.5
|
|
11
11
|
Requires-Dist: threadpoolctl
|
|
12
12
|
Requires-Dist: eqxpress
|
|
13
13
|
Requires-Dist: jax
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# ADR-0007: Values at the edge of a domain: zero impedance and parameters on bounds
|
|
2
|
+
|
|
3
|
+
Status: accepted (2026-09)
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
#215 reported zero inductance breaking the default `GlobalMNACircuitSolver`, and
|
|
8
|
+
parameters that start exactly on a bound being unable to move. Both are values
|
|
9
|
+
at the edge of a domain, and both failed quietly:
|
|
10
|
+
|
|
11
|
+
- `Inductor.y()` is `1/(jωL)`, which is not finite at L = 0. `Model.mna()`
|
|
12
|
+
stamps from `y()`, so the MNA solve returns NaN.
|
|
13
|
+
- With a finite stamp (`PiSectionCLC` goes through ABCD), the MNA solver still
|
|
14
|
+
returns Y, and two ports joined by a short have no finite Y. The solver
|
|
15
|
+
regularises it to about 1e12, and `y2s` of that Y has a condition number
|
|
16
|
+
around 1e14: |S21|² came out 0.7% wrong and dL wrong by ten orders of
|
|
17
|
+
magnitude, without a warning. Removing the regularisation from the auxiliary
|
|
18
|
+
rows made it worse: the singular block went to a least-squares solve, and
|
|
19
|
+
the short came out as an open.
|
|
20
|
+
- `BoxSectionCLCC` substitutes eps for L = 0, which fixes the value and zeroes
|
|
21
|
+
the gradient.
|
|
22
|
+
- Parax documents `Interval` and `GreaterThan` as exclusive, but validates them
|
|
23
|
+
as inclusive, and maps them onto open raw spaces. So `Bounded(0, 10, value=0)`
|
|
24
|
+
is accepted, has raw value −∞, and cannot move. `minimize` rejects it.
|
|
25
|
+
`Random(Uniform(0, 10), value=0)` is silently moved to 2.2e-15 by a clip in
|
|
26
|
+
parax. `minimize` gives even bounded solvers raw space with infinite bounds,
|
|
27
|
+
so no solver ever sees the box the user wrote.
|
|
28
|
+
|
|
29
|
+
Engineers expect both to work: an inductor fitted down to 0, and a parameter
|
|
30
|
+
started on, or converging to, the bound they gave it.
|
|
31
|
+
|
|
32
|
+
## Decision
|
|
33
|
+
|
|
34
|
+
### Circuit solvers return S at the probe reference
|
|
35
|
+
|
|
36
|
+
A circuit solver that assembles a nodal system loads each external port with
|
|
37
|
+
the probe reference impedance $Z_r$ (ADR-0006) and drives it through $Z_r$. With
|
|
38
|
+
$H$ the port-voltage response to the drive, it returns power-wave S directly:
|
|
39
|
+
|
|
40
|
+
$$H = (Y + Z_r^{-1})^{-1} Z_r^{-1}, \qquad
|
|
41
|
+
S = F\,\big(2\,\mathrm{Re}(Z_r)\,Z_r^{-1} H - Z_r^{*} Z_r^{-1}\big)\,F^{-1}, \qquad
|
|
42
|
+
F = \mathrm{diag}\big(1 / (2\sqrt{\mathrm{Re}\,Z_r})\big)$$
|
|
43
|
+
|
|
44
|
+
where $Y + Z_r^{-1}$ is eliminated inside the full MNA system, never formed.
|
|
45
|
+
Loaded ports keep the system non-singular when ports are shorted together, so
|
|
46
|
+
the result is exact there. $Z_r$ may be complex and per-port. For real $Z_r$
|
|
47
|
+
this reduces to $S = 2\sqrt{G}\,(Y+G)^{-1}\sqrt{G} - I$; that short form is
|
|
48
|
+
wrong for complex $Z_r$.
|
|
49
|
+
|
|
50
|
+
- **One implementation.** `pmrf.rf.mna2s(stamp, z0)` holds the formula. The
|
|
51
|
+
MNA solver uses it, and so does `Model.s()` for a model whose primary domain
|
|
52
|
+
is MNA.
|
|
53
|
+
- **The solve depends on the probe.** `Circuit.s(z0=...)` passes its probe into
|
|
54
|
+
the solve rather than renormalising a probe-free Y afterwards.
|
|
55
|
+
- **Regularisation is physical.** GMIN is a conductance from every node to
|
|
56
|
+
ground. Each auxiliary branch gets a +eps Ω series resistance, which resolves
|
|
57
|
+
loops of zero-impedance branches. The system is therefore never singular,
|
|
58
|
+
and the default linear solver is LU (`well_posed=True`): a singular system is
|
|
59
|
+
a bug and shows up as NaN, not as a least-squares answer.
|
|
60
|
+
- **Y stays honest.** `Circuit.y()` is `s2y` of S at the native reference, and
|
|
61
|
+
is non-finite for shorted ports. `Circuit.mna()` is `s2mna` of S at the hub
|
|
62
|
+
reference, which stays finite, so nested circuits are exact.
|
|
63
|
+
- **Series elements that can short stamp as branches.** An element whose
|
|
64
|
+
impedance can be zero (`Inductor`, `InductorQ`, the inductor in
|
|
65
|
+
`BoxSectionCLCC`) stamps with an auxiliary branch current,
|
|
66
|
+
$V_1 - V_2 - Z I = 0$, not from `y()`. Its `y()` is undefined at zero and may
|
|
67
|
+
be non-finite there. No model substitutes eps for a zero value.
|
|
68
|
+
- **The nodal solver stays Y-only.** `GlobalNodalCircuitSolver` cannot
|
|
69
|
+
represent a zero impedance and documents it.
|
|
70
|
+
|
|
71
|
+
### Bounds are open or closed, and solvers see the box
|
|
72
|
+
|
|
73
|
+
- **Each bound is open or closed**, declared by its Parax constraint.
|
|
74
|
+
`Positive` and `Negative` are open; `NonNegative` and `NonPositive` are
|
|
75
|
+
closed; `Interval`, `GreaterThan` and `LessThan` take `closed` and default to
|
|
76
|
+
closed. `pmrf.Bounded` and a `Uniform` support are closed. Where two
|
|
77
|
+
constraints meet at one bound, open wins. Validation respects closedness, so
|
|
78
|
+
a value on an open bound is rejected at construction.
|
|
79
|
+
- **A closed bound is part of the model's domain.** A model evaluates, with a
|
|
80
|
+
finite value and gradient, at every closed bound of its parameters and at
|
|
81
|
+
zero where zero is valid. A value it cannot evaluate gets an open validity
|
|
82
|
+
constraint instead: a line width is `Positive()`, a quality factor is
|
|
83
|
+
`Positive()`. Lumped L, C and R stay unconstrained, because fitted
|
|
84
|
+
equivalent circuits use negative values.
|
|
85
|
+
- **Box space** is a fourth space: the box a bounded minimiser searches. It is
|
|
86
|
+
built from a parameter's bounds, not its prior: the unit box when both bounds
|
|
87
|
+
are finite, declared space otherwise. A closed edge of the box is the bound
|
|
88
|
+
itself; an open edge is inset by δ = 1e-6.
|
|
89
|
+
- **Minimisers say whether they honour bounds**, through the minimiser
|
|
90
|
+
interface. `SolverView` hands box space to one that does and raw space to one
|
|
91
|
+
that does not. Nothing in `SolverView` knows about a specific backend.
|
|
92
|
+
- **A start on a closed bound is nudged for raw space.** For raw-space
|
|
93
|
+
consumers (unbounded minimisers and samplers), `SolverView` moves a start on a
|
|
94
|
+
closed bound δ = 1e-6 inward in box space before mapping it to raw. The nudge
|
|
95
|
+
is silent: it is the kind of difference engineers expect between optimisers.
|
|
96
|
+
- **Raw space is unchanged.** It stays whitened by the prior (ADR-0005).
|
|
97
|
+
|
|
98
|
+
## Rejected options
|
|
99
|
+
|
|
100
|
+
- **Keep Y from the MNA solver and detect ill-conditioning.** Wrong answers get
|
|
101
|
+
louder but stay wrong.
|
|
102
|
+
- **Substitute eps for zero in models.** It is what `BoxSectionCLCC` did, and
|
|
103
|
+
it zeroes the gradient at exactly the value being fitted.
|
|
104
|
+
- **A tanh map for `Interval`.** Any map ending in `lower + width·u` has an
|
|
105
|
+
absolute precision of about eps·width. tanh only moves the exact point from
|
|
106
|
+
the lower bound to the midpoint, and loses it at a lower bound of 0, the
|
|
107
|
+
common case.
|
|
108
|
+
- **Reject every start on a bound.** Correct, but engineers write bounds they
|
|
109
|
+
mean to include.
|
|
110
|
+
- **Nudge by a fixed raw value.** A raw value of −14 is 1e-6 of a sigmoid's
|
|
111
|
+
width, but 1e-43 of a prior-whitened parameter's slope. The nudge has to be
|
|
112
|
+
in box space to mean the same thing for every parameter.
|
|
113
|
+
- **Track whether a bound came from a range or a validity constraint.**
|
|
114
|
+
Closedness is a property of the constraint; recording it there needs no
|
|
115
|
+
bookkeeping when constraints are intersected.
|
|
116
|
+
- **Box space whitened by the prior**, as Parax does for `Random`. A prior is a
|
|
117
|
+
belief, not the shape of the search; a `Normal` prior made the box edges ±∞,
|
|
118
|
+
kept finite only by a clip.
|
|
119
|
+
|
|
120
|
+
## Consequences
|
|
121
|
+
|
|
122
|
+
- Circuits with shorted ports, and every component at L, C or R = 0, give the
|
|
123
|
+
same S and gradient under MNA and scattering solvers.
|
|
124
|
+
- `value=0` on a `Positive` field now raises at construction.
|
|
125
|
+
- A `Random` parameter on a bound has raw value ±∞ once Parax drops its clip,
|
|
126
|
+
and is nudged like any other.
|
|
127
|
+
- Fits run through a minimiser that honours bounds now evaluate the objective
|
|
128
|
+
exactly on closed bounds. A test sweep evaluates every component at its
|
|
129
|
+
closed bounds and at zero so this is safe.
|
|
130
|
+
- A fit that started on a bound gives slightly different results than before.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: paramrf
|
|
3
|
-
Version: 0.36.
|
|
3
|
+
Version: 0.36.2
|
|
4
4
|
Summary: Parametric radio frequency modeling
|
|
5
5
|
Author-email: Gary Allen <gvcallen@gmail.com>
|
|
6
6
|
Project-URL: homepage, https://github.com/gvcallen/paramrf
|
|
7
7
|
Description-Content-Type: text/x-rst
|
|
8
8
|
License-File: LICENSE
|
|
9
9
|
License-File: NOTICE
|
|
10
|
-
Requires-Dist: parax>=0.11.
|
|
10
|
+
Requires-Dist: parax>=0.11.5
|
|
11
11
|
Requires-Dist: threadpoolctl
|
|
12
12
|
Requires-Dist: eqxpress
|
|
13
13
|
Requires-Dist: jax
|
|
@@ -29,6 +29,7 @@ docs/adr/0003-derived-models.md
|
|
|
29
29
|
docs/adr/0004-profiled-lines.md
|
|
30
30
|
docs/adr/0005-priors-by-name.md
|
|
31
31
|
docs/adr/0006-reference-impedance.md
|
|
32
|
+
docs/adr/0007-edge-values.md
|
|
32
33
|
docs/agents/domain.md
|
|
33
34
|
docs/agents/issue-tracker.md
|
|
34
35
|
docs/agents/triage-labels.md
|
|
@@ -184,6 +185,7 @@ tests/__init__.py
|
|
|
184
185
|
tests/_jit.py
|
|
185
186
|
tests/conftest.py
|
|
186
187
|
tests/test_autodiff.py
|
|
188
|
+
tests/test_box_space_solving.py
|
|
187
189
|
tests/test_conversions.py
|
|
188
190
|
tests/test_covariance_kernels.py
|
|
189
191
|
tests/test_dc_limits.py
|
|
@@ -224,10 +226,12 @@ tests/test_models/test_circuit_native_z0.py
|
|
|
224
226
|
tests/test_models/test_circuit_nodal.py
|
|
225
227
|
tests/test_models/test_circuit_port_order.py
|
|
226
228
|
tests/test_models/test_circuit_scattering.py
|
|
229
|
+
tests/test_models/test_circuit_shorted_ports.py
|
|
227
230
|
tests/test_models/test_circuit_skrf_port_names.py
|
|
228
231
|
tests/test_models/test_circuit_z0.py
|
|
229
232
|
tests/test_models/test_current_distribution.py
|
|
230
233
|
tests/test_models/test_derived.py
|
|
234
|
+
tests/test_models/test_edge_values.py
|
|
231
235
|
tests/test_models/test_interconnected.py
|
|
232
236
|
tests/test_models/test_lines.py
|
|
233
237
|
tests/test_models/test_lines_skrf_matrix.py
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The view of a model a solver moves through, shared by the minimiser and every sampler.
|
|
3
|
+
|
|
4
|
+
A solver never sees the model itself: it sees the free parameters as a name-keyed dict
|
|
5
|
+
of values: in box space for a minimiser that honours bounds, in raw space for one that
|
|
6
|
+
does not and for the joint and split samplers, and in declared space for the hypercube
|
|
7
|
+
samplers. Values are read and written through the public :func:`pmrf.values`,
|
|
8
|
+
:func:`pmrf.update` and :func:`pmrf.log_prior`, so a solver sees exactly what a user
|
|
9
|
+
would with those functions.
|
|
10
|
+
|
|
11
|
+
Box space is the box a bounded minimiser searches (ADR-0007). Along each parameter it is
|
|
12
|
+
built from the constraint's bounds, not its prior (Parax's ``base_bounds`` and
|
|
13
|
+
``base_bijector``): the unit box between two finite bounds, declared space otherwise. A closed edge of the box is the
|
|
14
|
+
bound itself; an open edge is inset by 1e-6.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import dataclasses
|
|
18
|
+
from typing import Any, Callable
|
|
19
|
+
|
|
20
|
+
from jaxtyping import Array, PyTree, Scalar
|
|
21
|
+
|
|
22
|
+
import equinox as eqx
|
|
23
|
+
import jax
|
|
24
|
+
import jax.numpy as jnp
|
|
25
|
+
import parax as prx
|
|
26
|
+
|
|
27
|
+
from pmrf.parameters import (
|
|
28
|
+
Param, is_param, log_prior as _log_prior, params, tree_param_paths, values as _values, update,
|
|
29
|
+
)
|
|
30
|
+
from pmrf.utils import unwrap
|
|
31
|
+
from pmrf.utils.tree import Pathgetter
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# How far a start on a closed bound is moved inward, and an open edge of the box is
|
|
35
|
+
# inset, in box space (ADR-0007).
|
|
36
|
+
_NUDGE = 1e-6
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _box_constraint(node: Param | Array) -> prx.constraints.AbstractConstraint | None:
|
|
40
|
+
"""Returns the constraint whose bounds give the box of `node`, or None if `node` is
|
|
41
|
+
a raw array or an unconstrained parameter, whose box is its declared space."""
|
|
42
|
+
return prx.unwrap(node.constraint) if is_param(node) else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _box_edges(node: Param | Array) -> tuple[Array, Array]:
|
|
46
|
+
"""Returns the lower and upper edges of the box of `node`, shaped like its value."""
|
|
47
|
+
value = jnp.asarray(node.value if is_param(node) else node)
|
|
48
|
+
constraint = _box_constraint(node)
|
|
49
|
+
if constraint is None:
|
|
50
|
+
return jnp.full_like(value, -jnp.inf), jnp.full_like(value, jnp.inf)
|
|
51
|
+
|
|
52
|
+
def _like_value(x):
|
|
53
|
+
return jnp.broadcast_to(jnp.asarray(x), value.shape).astype(value.dtype)
|
|
54
|
+
|
|
55
|
+
lower, upper = (_like_value(b) for b in constraint.base_bounds)
|
|
56
|
+
lower_closed, upper_closed = (jnp.broadcast_to(jnp.asarray(c), value.shape) for c in constraint.closed)
|
|
57
|
+
return jnp.where(lower_closed, lower, lower + _NUDGE), jnp.where(upper_closed, upper, upper - _NUDGE)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _to_box(node: Param | Array) -> Array:
|
|
61
|
+
"""Returns the declared value of `node` in its box space."""
|
|
62
|
+
value = jnp.asarray(node.value if is_param(node) else node)
|
|
63
|
+
constraint = _box_constraint(node)
|
|
64
|
+
if constraint is None:
|
|
65
|
+
return value
|
|
66
|
+
return jnp.broadcast_to(constraint.base_bijector.inverse(value), value.shape).astype(value.dtype)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _from_box(node: Param | Array, box: Array) -> Array:
|
|
70
|
+
"""Returns the box-space value `box` of `node` in declared space.
|
|
71
|
+
|
|
72
|
+
The result is kept inside the bounds, which rounding at a closed edge could
|
|
73
|
+
otherwise step past. The comparison leaves the gradient at the edge intact.
|
|
74
|
+
"""
|
|
75
|
+
constraint = _box_constraint(node)
|
|
76
|
+
if constraint is None:
|
|
77
|
+
return box
|
|
78
|
+
declared = constraint.base_bijector.forward(box)
|
|
79
|
+
lower, upper = constraint.bounds
|
|
80
|
+
return jnp.where(declared < lower, lower, jnp.where(declared > upper, upper, declared))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _node_from_box(node: Param | Array, box: Array) -> Param | Array:
|
|
84
|
+
"""Returns `node` holding the box-space value `box`, for evaluating the objective.
|
|
85
|
+
|
|
86
|
+
A parameter stays a parameter, with its scale, name and validity, so a derived
|
|
87
|
+
model's builder sees what it would otherwise. It holds its declared value directly,
|
|
88
|
+
not through its raw value, which is infinite on a closed bound, so the value and
|
|
89
|
+
its gradient are finite there.
|
|
90
|
+
"""
|
|
91
|
+
declared = _from_box(node, box)
|
|
92
|
+
if not is_param(node):
|
|
93
|
+
return declared
|
|
94
|
+
return dataclasses.replace(node, variable=prx.Real(declared))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _nudged(param: Param) -> Array | None:
|
|
98
|
+
"""Returns the declared value of `param` with each element on a closed bound moved
|
|
99
|
+
1e-6 inward in box space, or None if no element is on one.
|
|
100
|
+
|
|
101
|
+
Box space is built from the constraint's bounds: the unit box between two finite
|
|
102
|
+
bounds, declared space otherwise. An element also counts as on a bound when it is
|
|
103
|
+
no further from it, in box space, than twice the bound's image through the
|
|
104
|
+
raw-to-declared bijector. A Parax prior whitening clips on the way out of raw
|
|
105
|
+
space, so a prior's value set on its bound reads back slightly inside it, with a
|
|
106
|
+
raw value too far out to move.
|
|
107
|
+
"""
|
|
108
|
+
constraint = prx.unwrap(param.constraint)
|
|
109
|
+
if constraint is None:
|
|
110
|
+
return None
|
|
111
|
+
value = param.value
|
|
112
|
+
to_declared = constraint.base_bijector
|
|
113
|
+
|
|
114
|
+
def _like_value(x):
|
|
115
|
+
return jnp.broadcast_to(jnp.asarray(x), value.shape)
|
|
116
|
+
|
|
117
|
+
box = _like_value(to_declared.inverse(value))
|
|
118
|
+
lower, upper = (_like_value(b).astype(value.dtype) for b in constraint.base_bounds)
|
|
119
|
+
lower_closed, upper_closed = (_like_value(c) for c in constraint.closed)
|
|
120
|
+
ends = [
|
|
121
|
+
_like_value(to_declared.inverse(constraint.bijector.forward(jnp.full(value.shape, x, value.dtype))))
|
|
122
|
+
for x in (-jnp.inf, jnp.inf)
|
|
123
|
+
]
|
|
124
|
+
unknown = jnp.isnan(ends[0]) | jnp.isnan(ends[1])
|
|
125
|
+
lower_image = jnp.where(unknown, lower, jnp.minimum(*ends))
|
|
126
|
+
upper_image = jnp.where(unknown, upper, jnp.maximum(*ends))
|
|
127
|
+
|
|
128
|
+
at_lower = lower_closed & jnp.isfinite(lower) & (box - lower <= 2 * (lower_image - lower))
|
|
129
|
+
at_upper = upper_closed & jnp.isfinite(upper) & ~at_lower & (upper - box <= 2 * (upper - upper_image))
|
|
130
|
+
if not bool(jnp.any(at_lower | at_upper)):
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
nudged = to_declared.forward(jnp.where(at_lower, lower + _NUDGE, upper - _NUDGE))
|
|
134
|
+
return jnp.where(at_lower | at_upper, nudged, value).astype(value.dtype)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class SolverView:
|
|
138
|
+
"""
|
|
139
|
+
The free parameters of a model, as the dict of values a solver moves through.
|
|
140
|
+
|
|
141
|
+
The starting values :attr:`y0` are raw, and :meth:`updated` and :meth:`objective`
|
|
142
|
+
take a ``space`` so a solver working in declared space, such as a hypercube
|
|
143
|
+
sampler, or in box space, such as a minimiser that honours bounds, can use the
|
|
144
|
+
same view. :meth:`box` gives the start and edges in box space.
|
|
145
|
+
|
|
146
|
+
A parameter starting on a closed bound has an infinite raw value, so :attr:`y0`
|
|
147
|
+
starts it 1e-6 inward in box space instead (ADR-0007). The model keeps
|
|
148
|
+
the value it was given.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
model : PyTree
|
|
153
|
+
A model, or any collection of models and parameters.
|
|
154
|
+
action : str
|
|
155
|
+
What the solver does, such as ``'optimize'``, for the error raised when there
|
|
156
|
+
is nothing to move.
|
|
157
|
+
|
|
158
|
+
Raises
|
|
159
|
+
------
|
|
160
|
+
ValueError
|
|
161
|
+
If `model` has no free parameters.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, model: PyTree, action: str):
|
|
165
|
+
#: The model the values are written into.
|
|
166
|
+
self.model = model
|
|
167
|
+
#: What the solver does, used in error messages.
|
|
168
|
+
self.action = action
|
|
169
|
+
raw = _values(model, free_only=True, space='raw')
|
|
170
|
+
if not raw:
|
|
171
|
+
raise ValueError(
|
|
172
|
+
f"Nothing to {action}: the tree has no free parameters. Every parameter is "
|
|
173
|
+
"either fixed or a plain value."
|
|
174
|
+
)
|
|
175
|
+
nudged = {}
|
|
176
|
+
for name, param in params(model, free_only=True).items():
|
|
177
|
+
if is_param(param) and (value := _nudged(param)) is not None:
|
|
178
|
+
nudged[name] = value
|
|
179
|
+
if nudged:
|
|
180
|
+
raw = _values(update(model, nudged), free_only=True, space='raw')
|
|
181
|
+
#: The starting raw values of the free parameters, by name, with each start on
|
|
182
|
+
#: a closed bound nudged inward.
|
|
183
|
+
self.y0 = raw
|
|
184
|
+
# The free parameters' paths and nodes, by name, for box space.
|
|
185
|
+
self._free = tree_param_paths(model, free_only=True)
|
|
186
|
+
|
|
187
|
+
def box(self) -> tuple[dict, tuple[dict, dict]]:
|
|
188
|
+
"""Returns the start and the box a bounded minimiser searches, in box space.
|
|
189
|
+
|
|
190
|
+
A start on a closed bound stays on it. A start outside the box, which can only
|
|
191
|
+
be within 1e-6 of an open bound, is moved onto its edge.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
tuple
|
|
196
|
+
``(y0, (lower, upper))``: the starting box values of the free parameters
|
|
197
|
+
and the lower and upper edges of the box, each a dict by name.
|
|
198
|
+
"""
|
|
199
|
+
lower, upper, y0 = {}, {}, {}
|
|
200
|
+
for name, (_, node) in self._free.items():
|
|
201
|
+
lower[name], upper[name] = _box_edges(node)
|
|
202
|
+
y0[name] = jnp.clip(_to_box(node), lower[name], upper[name])
|
|
203
|
+
return y0, (lower, upper)
|
|
204
|
+
|
|
205
|
+
def check_finite(self) -> None:
|
|
206
|
+
"""Checks that no starting raw value is NaN, so a solver can move it.
|
|
207
|
+
|
|
208
|
+
Only for solvers that move raw values.
|
|
209
|
+
|
|
210
|
+
Raises
|
|
211
|
+
------
|
|
212
|
+
ValueError
|
|
213
|
+
If a starting raw value is NaN.
|
|
214
|
+
"""
|
|
215
|
+
def _leaves(v):
|
|
216
|
+
return [jnp.asarray(x) for x in jax.tree.leaves(v)]
|
|
217
|
+
|
|
218
|
+
nan = [name for name, v in self.y0.items() if any(bool(jnp.any(jnp.isnan(x))) for x in _leaves(v))]
|
|
219
|
+
if nan:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"Cannot {self.action}: {', '.join(repr(name) for name in nan)} start at NaN, "
|
|
222
|
+
"which no solver can move. Give them a finite starting value."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def updated(self, values: dict, space: str = 'raw') -> PyTree:
|
|
226
|
+
"""Returns the model with `values`, by name and in `space`, written into it.
|
|
227
|
+
|
|
228
|
+
`space` is ``'box'`` or any space :func:`pmrf.update` takes. Batched values give
|
|
229
|
+
a batched model; fixed parameters stay unbatched.
|
|
230
|
+
"""
|
|
231
|
+
if space == 'box':
|
|
232
|
+
declared = {name: _from_box(self._free[name][1], v) for name, v in values.items()}
|
|
233
|
+
return update(self.model, declared, space='declared')
|
|
234
|
+
return update(self.model, values, space=space)
|
|
235
|
+
|
|
236
|
+
def read(self, batch: PyTree) -> dict:
|
|
237
|
+
"""Returns the raw values of the free parameters of `batch`, a model like this one.
|
|
238
|
+
|
|
239
|
+
Raises
|
|
240
|
+
------
|
|
241
|
+
ValueError
|
|
242
|
+
If a free parameter of this model is missing from `batch`, or is fixed there.
|
|
243
|
+
"""
|
|
244
|
+
values = _values(batch, free_only=True, space='raw')
|
|
245
|
+
missing = [name for name in self.y0 if name not in values]
|
|
246
|
+
if missing:
|
|
247
|
+
raise ValueError(
|
|
248
|
+
f"Cannot {self.action}: {', '.join(repr(name) for name in missing)} "
|
|
249
|
+
f"{'is' if len(missing) == 1 else 'are'} free in the model but missing or "
|
|
250
|
+
"fixed in the given tree. It must have the same free parameters as the model."
|
|
251
|
+
)
|
|
252
|
+
return {name: values[name] for name in self.y0}
|
|
253
|
+
|
|
254
|
+
def objective(self, fn: Callable[[PyTree, Any], Scalar], space: str = 'raw') -> Callable[[dict, Any], Scalar]:
|
|
255
|
+
"""Returns `fn`, which takes the unwrapped model, as a function of values in `space`.
|
|
256
|
+
|
|
257
|
+
In box space each free parameter holds its declared value directly, rather than
|
|
258
|
+
through its raw value, which is infinite on a closed bound. So the objective and
|
|
259
|
+
its gradient are finite there.
|
|
260
|
+
"""
|
|
261
|
+
if space == 'box':
|
|
262
|
+
names = list(self._free)
|
|
263
|
+
getter = Pathgetter(*(path for path, _ in self._free.values()))
|
|
264
|
+
|
|
265
|
+
def box_value_fn(values: dict, args: Any) -> Scalar:
|
|
266
|
+
nodes = [_node_from_box(self._free[name][1], values[name]) for name in names]
|
|
267
|
+
model = eqx.tree_at(getter, self.model, nodes[0] if len(nodes) == 1 else tuple(nodes))
|
|
268
|
+
return fn(unwrap(model), args)
|
|
269
|
+
return box_value_fn
|
|
270
|
+
|
|
271
|
+
def value_fn(values: dict, args: Any) -> Scalar:
|
|
272
|
+
return fn(unwrap(self.updated(values, space)), args)
|
|
273
|
+
return value_fn
|
|
274
|
+
|
|
275
|
+
def log_prior(self, values: dict, _args: Any = None) -> Scalar:
|
|
276
|
+
"""Returns the raw-space log prior of the model at `values`."""
|
|
277
|
+
return _log_prior(self.updated(values), space='raw')
|
|
278
|
+
|
|
279
|
+
def log_posterior(self, loglikelihood_fn: Callable[[PyTree, Any], Scalar]) -> Callable[[dict, Any], Scalar]:
|
|
280
|
+
"""Returns the raw-space log posterior: `loglikelihood_fn` plus the raw log prior."""
|
|
281
|
+
def value_fn(values: dict, args: Any) -> Scalar:
|
|
282
|
+
model = self.updated(values)
|
|
283
|
+
return loglikelihood_fn(unwrap(model), args) + _log_prior(model, space='raw')
|
|
284
|
+
return value_fn
|
|
@@ -12,6 +12,8 @@ from parax.constraints import (
|
|
|
12
12
|
RealLine as RealLine,
|
|
13
13
|
Positive as Positive,
|
|
14
14
|
Negative as Negative,
|
|
15
|
+
NonNegative as NonNegative,
|
|
16
|
+
NonPositive as NonPositive,
|
|
15
17
|
Interval as Interval,
|
|
16
18
|
GreaterThan as GreaterThan,
|
|
17
19
|
LessThan as LessThan,
|
|
@@ -25,6 +27,8 @@ __all__ = [
|
|
|
25
27
|
'RealLine',
|
|
26
28
|
'Positive',
|
|
27
29
|
'Negative',
|
|
30
|
+
'NonNegative',
|
|
31
|
+
'NonPositive',
|
|
28
32
|
'Interval',
|
|
29
33
|
'GreaterThan',
|
|
30
34
|
'LessThan',
|
|
@@ -237,9 +237,10 @@ def run_sampler(
|
|
|
237
237
|
:func:`pmrf.update`, so fixed parameters, names, scales and priors are unchanged,
|
|
238
238
|
and only free parameters are batched.
|
|
239
239
|
|
|
240
|
-
A parameter starting exactly on
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
A parameter starting exactly on a closed bound has an infinite raw value, so a
|
|
241
|
+
joint or split sampler starts it just inside: by 1e-6 of the width between two
|
|
242
|
+
finite bounds, or 1e-6 in declared units otherwise. A hypercube sampler works in
|
|
243
|
+
declared space and starts it on the bound.
|
|
243
244
|
|
|
244
245
|
Parameters
|
|
245
246
|
----------
|
|
@@ -271,7 +272,7 @@ def run_sampler(
|
|
|
271
272
|
------
|
|
272
273
|
ValueError
|
|
273
274
|
If `model` has no free parameters, a joint or split sampler is given a
|
|
274
|
-
parameter starting
|
|
275
|
+
parameter starting at NaN, `init_samples` is missing a free parameter, or
|
|
275
276
|
a hypercube sampler is given a free parameter without a prior, a prior with no
|
|
276
277
|
inverse CDF, or a joint prior with no independent normal base.
|
|
277
278
|
"""
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from abc import abstractmethod
|
|
11
11
|
|
|
12
12
|
import jax.numpy as jnp
|
|
13
|
-
from pmrf.constraints import Positive, GreaterThan, Interval
|
|
13
|
+
from pmrf.constraints import Positive, NonNegative, GreaterThan, Interval
|
|
14
14
|
from pmrf.frequency import Frequency
|
|
15
15
|
from pmrf.modules.base import Module
|
|
16
16
|
from pmrf.parameters import Param, param
|
|
@@ -89,10 +89,10 @@ class ConstantDielectric(AbstractDielectric):
|
|
|
89
89
|
ep_r: Param = param(default=1.0, constraint=GreaterThan(1.0))
|
|
90
90
|
|
|
91
91
|
#: Dielectric loss tangent
|
|
92
|
-
tand: Param = param(default=0.0, constraint=
|
|
92
|
+
tand: Param = param(default=0.0, constraint=NonNegative())
|
|
93
93
|
|
|
94
94
|
#: Static bulk conductivity in S/m
|
|
95
|
-
sigma: Param = param(default=0.0, constraint=
|
|
95
|
+
sigma: Param = param(default=0.0, constraint=NonNegative())
|
|
96
96
|
|
|
97
97
|
#: Relative permeability of the medium
|
|
98
98
|
mu_r: Param = param(default=1.0, constraint=Positive())
|
|
@@ -155,7 +155,7 @@ class DjordjevicSarkarDielectric(AbstractDielectric):
|
|
|
155
155
|
ep_r: Param = param(default=1.0, constraint=GreaterThan(1.0))
|
|
156
156
|
|
|
157
157
|
#: Dielectric loss tangent at `f_ref`
|
|
158
|
-
tand: Param = param(default=0.0, constraint=
|
|
158
|
+
tand: Param = param(default=0.0, constraint=NonNegative())
|
|
159
159
|
|
|
160
160
|
#: Lower bound of the relaxation-time distribution
|
|
161
161
|
f_low: Param = param(default=1e3, constraint=Positive())
|
|
@@ -167,7 +167,7 @@ class DjordjevicSarkarDielectric(AbstractDielectric):
|
|
|
167
167
|
f_ref: Param = param(default=1e9, constraint=Positive())
|
|
168
168
|
|
|
169
169
|
#: Static bulk conductivity in S/m
|
|
170
|
-
sigma: Param = param(default=0.0, constraint=
|
|
170
|
+
sigma: Param = param(default=0.0, constraint=NonNegative())
|
|
171
171
|
|
|
172
172
|
def properties(self, freq: Frequency) -> DielectricProperties:
|
|
173
173
|
f = freq.f
|
|
@@ -201,7 +201,7 @@ class DebyePole(Module):
|
|
|
201
201
|
Relaxation frequency of the pole in Hz.
|
|
202
202
|
"""
|
|
203
203
|
#: Permittivity increment of the pole
|
|
204
|
-
dep_r: Param = param(default=0.0, constraint=
|
|
204
|
+
dep_r: Param = param(default=0.0, constraint=NonNegative())
|
|
205
205
|
|
|
206
206
|
#: Relaxation frequency of the pole
|
|
207
207
|
f_relax: Param = param(default=1e9, constraint=Positive())
|
|
@@ -256,7 +256,7 @@ class MultipoleDebyeDielectric(AbstractDielectric):
|
|
|
256
256
|
poles: tuple[DebyePole, ...] = field(default=(), converter=_as_poles)
|
|
257
257
|
|
|
258
258
|
#: Static bulk conductivity in S/m
|
|
259
|
-
sigma: Param = param(default=0.0, constraint=
|
|
259
|
+
sigma: Param = param(default=0.0, constraint=NonNegative())
|
|
260
260
|
|
|
261
261
|
def properties(self, freq: Frequency) -> DielectricProperties:
|
|
262
262
|
eps = self.ep_inf * jnp.ones(freq.npoints, dtype=complex)
|
|
@@ -300,7 +300,7 @@ class ColeColeDielectric(AbstractDielectric):
|
|
|
300
300
|
ep_inf: Param = param(default=1.0, constraint=GreaterThan(1.0))
|
|
301
301
|
|
|
302
302
|
#: Permittivity increment
|
|
303
|
-
dep_r: Param = param(default=0.0, constraint=
|
|
303
|
+
dep_r: Param = param(default=0.0, constraint=NonNegative())
|
|
304
304
|
|
|
305
305
|
#: Relaxation frequency
|
|
306
306
|
f_relax: Param = param(default=1e9, constraint=Positive())
|
|
@@ -309,7 +309,7 @@ class ColeColeDielectric(AbstractDielectric):
|
|
|
309
309
|
alpha: Param = param(default=0.0, constraint=Interval(0.0, 1.0))
|
|
310
310
|
|
|
311
311
|
#: Static bulk conductivity in S/m
|
|
312
|
-
sigma: Param = param(default=0.0, constraint=
|
|
312
|
+
sigma: Param = param(default=0.0, constraint=NonNegative())
|
|
313
313
|
|
|
314
314
|
def properties(self, freq: Frequency) -> DielectricProperties:
|
|
315
315
|
# Guard f = 0, where (jf/f_r)**(1-alpha) is a branch point.
|
|
@@ -355,7 +355,7 @@ class TabulatedDielectric(AbstractDielectric):
|
|
|
355
355
|
ep_r: jnp.ndarray = field(converter=lambda x: jnp.asarray(x, dtype=complex))
|
|
356
356
|
|
|
357
357
|
#: Static bulk conductivity in S/m
|
|
358
|
-
sigma: Param = param(default=0.0, constraint=
|
|
358
|
+
sigma: Param = param(default=0.0, constraint=NonNegative())
|
|
359
359
|
|
|
360
360
|
def __check_init__(self):
|
|
361
361
|
if self.f.ndim != 1 or self.ep_r.ndim != 1:
|
|
@@ -4,7 +4,7 @@ from abc import abstractmethod
|
|
|
4
4
|
import jax.numpy as jnp
|
|
5
5
|
from scipy.constants import mu_0
|
|
6
6
|
|
|
7
|
-
from pmrf.constraints import
|
|
7
|
+
from pmrf.constraints import NonNegative
|
|
8
8
|
from pmrf.frequency import Frequency
|
|
9
9
|
from pmrf.modules.base import Module
|
|
10
10
|
from pmrf.parameters import Param, param
|
|
@@ -55,7 +55,7 @@ class HammerstadRoughness(AbstractRoughness):
|
|
|
55
55
|
RMS surface roughness in meters.
|
|
56
56
|
"""
|
|
57
57
|
#: RMS surface roughness in meters
|
|
58
|
-
rms: Param = param(default=0.0, constraint=
|
|
58
|
+
rms: Param = param(default=0.0, constraint=NonNegative())
|
|
59
59
|
|
|
60
60
|
def factor(self, freq: Frequency, sigma, mu_r) -> jnp.ndarray:
|
|
61
61
|
w = jnp.asarray(freq.w)
|