paramrf 0.36.2__tar.gz → 0.36.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {paramrf-0.36.2 → paramrf-0.36.4}/CHANGELOG.md +20 -0
- {paramrf-0.36.2/paramrf.egg-info → paramrf-0.36.4}/PKG-INFO +1 -1
- {paramrf-0.36.2 → paramrf-0.36.4/paramrf.egg-info}/PKG-INFO +1 -1
- {paramrf-0.36.2 → paramrf-0.36.4}/paramrf.egg-info/SOURCES.txt +2 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/covariance_kernels.py +58 -17
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/discrepancy_models.py +100 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/evaluators.py +49 -14
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/fitting/minimize.py +8 -4
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/likelihoods.py +38 -9
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/parameters.py +4 -1
- paramrf-0.36.4/pmrf/utils/debug.py +83 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/transforms.py +23 -4
- {paramrf-0.36.2 → paramrf-0.36.4}/pyproject.toml +1 -1
- paramrf-0.36.4/scripts/benchmark_gp_marginal_likelihood.py +121 -0
- paramrf-0.36.4/tests/test_covariance_kernels.py +320 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_evaluators.py +222 -1
- paramrf-0.36.4/tests/test_fitting_minimize.py +148 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_edge_values.py +65 -3
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_parameters_validity.py +78 -1
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_transforms.py +98 -0
- paramrf-0.36.4/tests/test_utils/test_debug.py +61 -0
- paramrf-0.36.2/pmrf/utils/debug.py +0 -72
- paramrf-0.36.2/tests/test_covariance_kernels.py +0 -132
- paramrf-0.36.2/tests/test_fitting_minimize.py +0 -84
- {paramrf-0.36.2 → paramrf-0.36.4}/.github/workflows/docs.yml +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/.github/workflows/draft-pdf.yml +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/.github/workflows/publish.yml +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/.github/workflows/tests.yml +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/.gitignore +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/AGENTS.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/CITATION.cff +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/CLAUDE.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/CONTEXT.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/CONTRIBUTING.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/LICENSE +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/NOTICE +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/README.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/assets/logo.png +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/Makefile +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/_static/custom.css +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/_templates/autosummary/class.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/_templates/autosummary/function.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/_templates/autosummary/module.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0001-line-modelling-architecture.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0002-parameter-api.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0003-derived-models.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0004-profiled-lines.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0005-priors-by-name.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0006-reference-impedance.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/adr/0007-edge-values.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/agents/domain.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/agents/issue-tracker.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/agents/triage-labels.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/api/index.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/conf.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/core_concepts/core_primitives.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/core_concepts/index.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/core_concepts/jax_overview.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/core_concepts/optimization_and_inference.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/core_concepts/parameter_names.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/cascading_and_terminating.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/circuit_clc.png +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/circuit_models.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/custom_models.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/derivatives_and_sweeps.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/index.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/model_optimization.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/multiple_models_one_parameter_set.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/examples/shared_substrates.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/index.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/license.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/make.bat +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/research/conductor-loss-alternatives.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/research/holloway1994.pdf +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/research/microstrip-loss-conventions.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/research/precision-coax-microstrip-10-500mhz.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/skrf_comparison/index.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/skrf_comparison/overview.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/skrf_comparison/performance.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/tutorials/1_cable_fitting.ipynb +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/docs/tutorials/index.rst +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/paper/paper.bib +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/paper/paper.md +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/paper/rlc.png +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/paramrf.egg-info/dependency_links.txt +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/paramrf.egg-info/requires.txt +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/paramrf.egg-info/top_level.txt +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/_solver_view.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/bijectors.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/constraints.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/distributions.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/fitting/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/fitting/result.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/fitting/routers.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/fitting/sample.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/fitting/targets.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/frequency.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/result.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/sample.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/solvers/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/solvers/blackjax.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/infer/solvers/polychord.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/losses.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/conductor.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/dielectric.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/properties.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/roughness.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/substrate.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/materials/surface_impedance.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/math/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/math/aggregations.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/math/bessel.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/math/conversions.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/math/losses.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/math/misc.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/bridge.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/callable.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/delegated.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/derived.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/static.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/adapters/wrapped.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/ideal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/coaxial.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/empirical.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/ideal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/microstrip.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/nodal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/nonuniform.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/planar.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/profiles.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lines/stripline.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/lumped.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/components/sections.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/cascade.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/circuit/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/circuit/circuit.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/interconnected/terminated.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/nodal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/topological.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/composite/transformed.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/surrogates/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/surrogates/expansion.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/models/surrogates/rational.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/modules/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/modules/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/modules/wrapped.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/network_collection.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/noise_models.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/minimize.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/result.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/solvers/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/solvers/jaxopt.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/solvers/optimistix.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/optimize/solvers/scipy.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/problems.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/rf/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/rf/cascade.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/rf/conversions.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/rf/mna.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/serialization.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/terms.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/types.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/array.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/network.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/random.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/rf.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/tree.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/utils/type.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/viz/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/pmrf/viz/plots.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/scripts/install-test-deps.sh +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/setup.cfg +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/__init__.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/_jit.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/conftest.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/data/10m_cable.s2p +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_autodiff.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_box_space_solving.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_conversions.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_dc_limits.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_fitting_routers.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_fitting_sample.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_fitting_targets.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_frequency.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_infer_base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_infer_sample.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_map_priors.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_materials/test_conductor.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_materials/test_dielectric.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_materials/test_serialization.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_materials/test_substrate.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_materials/test_surface_impedance.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_math/test_bessel.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_model.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_adapters.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_cascade.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_native_z0.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_nodal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_port_order.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_scattering.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_shorted_ports.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_skrf_port_names.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_circuit_z0.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_current_distribution.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_derived.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_interconnected.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_lines.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_lines_skrf_matrix.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_lumped.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_nodal.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_profiles.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_rational.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_repeated_cascade.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_sections.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_transformed.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_models/test_transformers.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_module.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_naming.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_optimize_base.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_optimize_minimize.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_parameters.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_parameters_by_name.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_parameters_joint_prior.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_parameters_prior.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_raw_space_solving.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_serialization.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_structural_updates.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_terms.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_utils/test_compress.py +0 -0
- {paramrf-0.36.2 → paramrf-0.36.4}/tests/test_utils/test_tree.py +0 -0
|
@@ -117,6 +117,24 @@ longer recompiles a model's RF methods.
|
|
|
117
117
|
|
|
118
118
|
## Unreleased
|
|
119
119
|
|
|
120
|
+
- **Breaking (#245):** a frequentist fit with neither `loss` nor `likelihood`
|
|
121
|
+
now minimises the mean squared error (`MSELoss`) instead of the RMSE. MSE is
|
|
122
|
+
smooth in the residuals, pools every output's residuals equally rather than
|
|
123
|
+
averaging per-output RMSEs, and matches the fixed-noise Gaussian likelihood
|
|
124
|
+
of the Bayesian path. Fits relying on the default stop at slightly different
|
|
125
|
+
points and report loss values on a squared scale; pass `loss=RMSELoss()` for
|
|
126
|
+
the old objective. SciPy's default `gtol`, and its `ftol` (absolute below an
|
|
127
|
+
objective of 1), can now stop a fit whose MSE is already tiny before it
|
|
128
|
+
moves: tighten them through `ScipyMinimize(options=...)`.
|
|
129
|
+
- **Breaking (#247):** `SharedIndependentKernel` appends its shared axes as
|
|
130
|
+
size-1 trailing axes instead of materializing them, which fixes its routing
|
|
131
|
+
over a batched base kernel. Wrapping an `AutoCrossKernel` previously
|
|
132
|
+
prepended the shared axes, so half the real/imaginary entries got the wrong
|
|
133
|
+
kernel (S11 Im got cross, S21 Re got auto). Log-likelihoods of every model
|
|
134
|
+
using this composition change. `output_shape` is replaced by
|
|
135
|
+
`num_shared_axes` (default 1), since only its length was ever meaningful;
|
|
136
|
+
`gram` now returns e.g. `(2, 2, 1, N, N)` rather than `(2, 2, 2, N, N)`, and
|
|
137
|
+
the orthogonal discrepancy path factorizes the smaller batch.
|
|
120
138
|
- Complete Tesche coaxial conductor physics across the low-frequency regime,
|
|
121
139
|
and pass evaluated material properties to pure coaxial formulations.
|
|
122
140
|
- Correct microstrip results by enabling Kirschning--Jansen modal dispersion by
|
|
@@ -124,3 +142,5 @@ longer recompiles a model's RF methods.
|
|
|
124
142
|
pipeline explicitly.
|
|
125
143
|
- Add the Hammerstad--Jensen microstrip formulation and selectable complex
|
|
126
144
|
(ADS-like) and real (QUCS-like) permittivity conventions.
|
|
145
|
+
- Add `CosineKernel` to `pmrf.covariance_kernels` for zero-mean oscillatory
|
|
146
|
+
(negative) correlation. Multiply it by a decaying kernel for a damped cosine.
|
|
@@ -180,6 +180,7 @@ pmrf/utils/tree.py
|
|
|
180
180
|
pmrf/utils/type.py
|
|
181
181
|
pmrf/viz/__init__.py
|
|
182
182
|
pmrf/viz/plots.py
|
|
183
|
+
scripts/benchmark_gp_marginal_likelihood.py
|
|
183
184
|
scripts/install-test-deps.sh
|
|
184
185
|
tests/__init__.py
|
|
185
186
|
tests/_jit.py
|
|
@@ -244,4 +245,5 @@ tests/test_models/test_sections.py
|
|
|
244
245
|
tests/test_models/test_transformed.py
|
|
245
246
|
tests/test_models/test_transformers.py
|
|
246
247
|
tests/test_utils/test_compress.py
|
|
248
|
+
tests/test_utils/test_debug.py
|
|
247
249
|
tests/test_utils/test_tree.py
|
|
@@ -7,10 +7,11 @@ for more details.
|
|
|
7
7
|
from abc import abstractmethod
|
|
8
8
|
from collections.abc import Callable
|
|
9
9
|
|
|
10
|
+
import equinox as eqx
|
|
10
11
|
import jax
|
|
11
12
|
import jax.numpy as jnp
|
|
12
13
|
|
|
13
|
-
from pmrf.utils import field
|
|
14
|
+
from pmrf.utils import field, unwrap
|
|
14
15
|
from pmrf.types import ArrayLike
|
|
15
16
|
from pmrf.modules.base import Module
|
|
16
17
|
|
|
@@ -51,6 +52,12 @@ def gram(
|
|
|
51
52
|
The Gram matrix, of shape ``(*batch_shape, N, N)``, where
|
|
52
53
|
``batch_shape`` is the shape returned by the kernel for a single pair.
|
|
53
54
|
"""
|
|
55
|
+
# Unwrap the hyperparameters to their values once, behind a barrier. Otherwise
|
|
56
|
+
# XLA can fuse each one's raw-to-value bijector chain into the elementwise loop
|
|
57
|
+
# that builds the matrix and recompute it per entry.
|
|
58
|
+
leaves, structure = eqx.partition(unwrap(kernel), eqx.is_array)
|
|
59
|
+
kernel = eqx.combine(jax.lax.optimization_barrier(leaves), structure)
|
|
60
|
+
|
|
54
61
|
x = jnp.asarray(x)
|
|
55
62
|
if x.ndim == 1:
|
|
56
63
|
x_feat = x[:, None]
|
|
@@ -231,6 +238,40 @@ class PeriodicKernel(AbstractCovarianceKernel):
|
|
|
231
238
|
return jnp.exp(-2.0 * (sin_term / self.lengthscale)**2)
|
|
232
239
|
|
|
233
240
|
|
|
241
|
+
class CosineKernel(AbstractCovarianceKernel):
|
|
242
|
+
r"""
|
|
243
|
+
Cosine kernel.
|
|
244
|
+
|
|
245
|
+
Models zero-mean oscillatory correlation with a single period, which can be
|
|
246
|
+
negative. Unlike :class:`PeriodicKernel`, the correlation goes to $-1$ at
|
|
247
|
+
half a period.
|
|
248
|
+
|
|
249
|
+
$$k(x_1, x_2) = \cos\left(\frac{2\pi\, \mathbf{1}^\top (x_1 - x_2)}{p}\right)$$
|
|
250
|
+
|
|
251
|
+
where $p$ is the ``period``.
|
|
252
|
+
|
|
253
|
+
For $d$-dimensional inputs the oscillation runs along the all-ones direction:
|
|
254
|
+
the differences are summed, not combined into a Euclidean distance, since
|
|
255
|
+
$\cos(2\pi \lVert x_1 - x_2 \rVert / p)$ is not positive semi-definite
|
|
256
|
+
for $d > 1$.
|
|
257
|
+
|
|
258
|
+
Its Gram matrix has rank two, so it is singular on its own. Multiply by a
|
|
259
|
+
decaying kernel (e.g. :class:`Matern52Kernel`) for a damped oscillation,
|
|
260
|
+
and by a float for its variance.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
period : ArrayLike
|
|
265
|
+
The period of the oscillation. A period of shape ``(D,)`` gives ``D``
|
|
266
|
+
batched kernels, as for :class:`PeriodicKernel`.
|
|
267
|
+
"""
|
|
268
|
+
#: The period.
|
|
269
|
+
period: ArrayLike
|
|
270
|
+
|
|
271
|
+
def __call__(self, x1, x2, key=None):
|
|
272
|
+
return jnp.cos(2 * jnp.pi * jnp.sum(x1 - x2) / self.period)
|
|
273
|
+
|
|
274
|
+
|
|
234
275
|
class WhiteNoiseKernel(AbstractCovarianceKernel):
|
|
235
276
|
"""
|
|
236
277
|
Kernel representing independent Gaussian noise.
|
|
@@ -353,32 +394,32 @@ class AutoCrossKernel(AbstractCovarianceKernel):
|
|
|
353
394
|
|
|
354
395
|
class SharedIndependentKernel(AbstractCovarianceKernel):
|
|
355
396
|
"""
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
397
|
+
Shares a base kernel across trailing independent batch axes (e.g. the real
|
|
398
|
+
and imaginary parts), so that every element on those axes uses the same
|
|
399
|
+
hyperparameters.
|
|
400
|
+
|
|
401
|
+
The shared axes are appended to the base kernel's output as size-1 axes
|
|
402
|
+
rather than materialized. A base output of shape ``batch_shape`` becomes
|
|
403
|
+
``(*batch_shape, 1, ..., 1)``, which broadcasts to any size along the
|
|
404
|
+
shared axes, so a batched base kernel (e.g. :class:`AutoCrossKernel`)
|
|
405
|
+
keeps routing along its own leading axes.
|
|
359
406
|
|
|
360
407
|
Parameters
|
|
361
408
|
----------
|
|
362
|
-
base_kernel :
|
|
409
|
+
base_kernel : AbstractCovarianceKernel
|
|
363
410
|
The underlying kernel whose parameters are shared.
|
|
364
|
-
|
|
365
|
-
The
|
|
411
|
+
num_shared_axes : int, default=1
|
|
412
|
+
The number of trailing batch axes the base kernel is shared across.
|
|
366
413
|
"""
|
|
367
414
|
#: The base kernel.
|
|
368
415
|
base_kernel: AbstractCovarianceKernel
|
|
369
416
|
|
|
370
|
-
#: The
|
|
371
|
-
|
|
417
|
+
#: The number of trailing shared axes.
|
|
418
|
+
num_shared_axes: int = field(default=1, static=True)
|
|
372
419
|
|
|
373
420
|
def __call__(self, x1, x2, key=None):
|
|
374
|
-
|
|
375
|
-
val
|
|
376
|
-
|
|
377
|
-
# Broadcast the evaluation to the target shape.
|
|
378
|
-
# By appending the output_shape to the end of val.shape,
|
|
379
|
-
# this safely handles both scalar evaluations and already-batched evaluations.
|
|
380
|
-
target_shape = val.shape + self.output_shape
|
|
381
|
-
return jnp.broadcast_to(val, target_shape)
|
|
421
|
+
val = jnp.asarray(self.base_kernel(x1, x2, key=key))
|
|
422
|
+
return val.reshape(val.shape + (1,) * self.num_shared_axes)
|
|
382
423
|
|
|
383
424
|
|
|
384
425
|
class ZeroKernel(AbstractCovarianceKernel):
|
|
@@ -6,7 +6,10 @@ Useful for modeling discrepancy of RF models during fitting.
|
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from abc import abstractmethod
|
|
8
8
|
|
|
9
|
+
import math
|
|
10
|
+
|
|
9
11
|
import equinox as eqx
|
|
12
|
+
import jax
|
|
10
13
|
import jax.scipy as jsp
|
|
11
14
|
import jax.numpy as jnp
|
|
12
15
|
import parax.distributions as dist
|
|
@@ -49,6 +52,44 @@ class AbstractDiscrepancyModel(Module):
|
|
|
49
52
|
"""
|
|
50
53
|
raise NotImplementedError
|
|
51
54
|
|
|
55
|
+
|
|
56
|
+
@jax.custom_vjp
|
|
57
|
+
def _gaussian_log_prob(M: jnp.ndarray, R: jnp.ndarray) -> jnp.ndarray:
|
|
58
|
+
"""Sum the zero-mean Gaussian log densities of the columns of ``R`` under ``M``.
|
|
59
|
+
|
|
60
|
+
``M`` has shape ``(*batch, N, N)`` and ``R`` has shape ``(*batch, N, k)``: the
|
|
61
|
+
``k`` residuals in ``R[b]`` share the covariance ``M[b]`` and are solved together.
|
|
62
|
+
"""
|
|
63
|
+
return _gaussian_log_prob_fwd(M, R)[0]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _gaussian_log_prob_fwd(M, R):
|
|
67
|
+
L = jnp.linalg.cholesky(M)
|
|
68
|
+
A = jsp.linalg.cho_solve((L, True), R)
|
|
69
|
+
n, k = R.shape[-2:]
|
|
70
|
+
num_matrices = math.prod(M.shape[:-2])
|
|
71
|
+
logdet = 2 * jnp.sum(jnp.log(jnp.diagonal(L, axis1=-2, axis2=-1)))
|
|
72
|
+
value = -0.5 * (
|
|
73
|
+
jnp.sum(R * A) + k * logdet + k * num_matrices * n * jnp.log(2 * jnp.pi)
|
|
74
|
+
)
|
|
75
|
+
return value, (L, A)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _gaussian_log_prob_bwd(residuals, g):
|
|
79
|
+
# With alpha = M^-1 r, d/dM = (sum alpha alpha^T - k M^-1) / 2 and d/dr = -alpha.
|
|
80
|
+
# JAX has no potri, so M^-1 = L^-T L^-1 from one triangular solve and a matmul.
|
|
81
|
+
L, A = residuals
|
|
82
|
+
n, k = A.shape[-2:]
|
|
83
|
+
identity = jnp.broadcast_to(jnp.eye(n, dtype=L.dtype), L.shape)
|
|
84
|
+
L_inv = jsp.linalg.solve_triangular(L, identity, lower=True)
|
|
85
|
+
M_inv = jnp.swapaxes(L_inv, -1, -2) @ L_inv
|
|
86
|
+
dM = 0.5 * g * (A @ jnp.swapaxes(A, -1, -2) - k * M_inv)
|
|
87
|
+
return dM, -g * A
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_gaussian_log_prob.defvjp(_gaussian_log_prob_fwd, _gaussian_log_prob_bwd)
|
|
91
|
+
|
|
92
|
+
|
|
52
93
|
class GaussianProcess(AbstractDiscrepancyModel):
|
|
53
94
|
"""
|
|
54
95
|
Gaussian process discrepancy model with a covariance kernel.
|
|
@@ -82,6 +123,65 @@ class GaussianProcess(AbstractDiscrepancyModel):
|
|
|
82
123
|
#: The added jitter.
|
|
83
124
|
jitter: float = field(default=1e-10, static=True)
|
|
84
125
|
|
|
126
|
+
def log_prob(
|
|
127
|
+
self,
|
|
128
|
+
y_event: jnp.ndarray,
|
|
129
|
+
observed: jnp.ndarray,
|
|
130
|
+
x: jnp.ndarray,
|
|
131
|
+
noise_variance: jnp.ndarray,
|
|
132
|
+
) -> jnp.ndarray:
|
|
133
|
+
r"""Evaluate the summed log density of ``observed`` under the GP plus Gaussian noise.
|
|
134
|
+
|
|
135
|
+
Each batch entry of ``observed`` is distributed as
|
|
136
|
+
$\mathcal{N}(y, K + \sigma^2 I)$. Equal to the log probability of the
|
|
137
|
+
distribution built by :meth:`__call__` and
|
|
138
|
+
:class:`pmrf.likelihoods.GaussianLikelihood`, summed over the batch.
|
|
139
|
+
|
|
140
|
+
``M = K + sigma^2 I`` is formed and factorized at the broadcast shape of the
|
|
141
|
+
kernel's Gram batch and ``noise_variance``, rather than the full batch shape,
|
|
142
|
+
and the residuals sharing each ``M`` are solved together.
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
y_event : jnp.ndarray
|
|
147
|
+
The model prediction in event space, with shape ``(*batch_shape, N)``.
|
|
148
|
+
observed : jnp.ndarray
|
|
149
|
+
The observation in event space, broadcastable to ``y_event``.
|
|
150
|
+
x : jnp.ndarray
|
|
151
|
+
The frequency points, with shape ``(N,)``.
|
|
152
|
+
noise_variance : jnp.ndarray
|
|
153
|
+
The noise variance, constant along the event axis and broadcastable to
|
|
154
|
+
``batch_shape``.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
jnp.ndarray
|
|
159
|
+
The scalar log density, summed over the batch.
|
|
160
|
+
"""
|
|
161
|
+
# Materialize K before forming M. Otherwise XLA on CPU fuses the Gram
|
|
162
|
+
# construction into the transpose to LAPACK's column-major layout, and that
|
|
163
|
+
# strided build nearly doubles the value's cost at N = 1000.
|
|
164
|
+
K = jax.lax.optimization_barrier(gram(self.kernel, x, jitter=self.jitter))
|
|
165
|
+
variance = jnp.asarray(noise_variance)
|
|
166
|
+
batch_shape = y_event.shape[:-1]
|
|
167
|
+
n = y_event.shape[-1]
|
|
168
|
+
M = K + variance[..., None, None] * jnp.eye(n, dtype=K.dtype)
|
|
169
|
+
if jnp.broadcast_shapes(M.shape[:-2], batch_shape) != batch_shape:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"The kernel's batch shape {K.shape[:-2]} and noise variance shape "
|
|
172
|
+
f"{variance.shape} do not broadcast to the event batch shape {batch_shape}."
|
|
173
|
+
)
|
|
174
|
+
matrix_batch = (1,) * (len(batch_shape) - (M.ndim - 2)) + M.shape[:-2]
|
|
175
|
+
# Batch axes along which M is shared become extra right-hand sides.
|
|
176
|
+
matrix_axes = [i for i, size in enumerate(matrix_batch) if size == batch_shape[i]]
|
|
177
|
+
shared_axes = [i for i in range(len(batch_shape)) if i not in matrix_axes]
|
|
178
|
+
residual = jnp.broadcast_to(observed - y_event, y_event.shape)
|
|
179
|
+
R = jnp.transpose(residual, matrix_axes + [len(batch_shape)] + shared_axes)
|
|
180
|
+
matrix_shape = tuple(batch_shape[i] for i in matrix_axes)
|
|
181
|
+
R = R.reshape(matrix_shape + (n, -1))
|
|
182
|
+
M = M.reshape(matrix_shape + (n, n))
|
|
183
|
+
return _gaussian_log_prob(M, R)
|
|
184
|
+
|
|
85
185
|
def orthogonal_log_prob(
|
|
86
186
|
self,
|
|
87
187
|
y_event: jnp.ndarray,
|
|
@@ -255,6 +255,11 @@ class MarginalLogLikelihood(AbstractEvaluator):
|
|
|
255
255
|
An optional discrepancy model to cater for model misspecification.
|
|
256
256
|
Can be a function or a PyTree with optional parameters.
|
|
257
257
|
See :class:`pmrf.discrepancy_models` for common discrepancy models.
|
|
258
|
+
A :class:`~pmrf.discrepancy_models.GaussianProcess` with a
|
|
259
|
+
:class:`~pmrf.likelihoods.GaussianLikelihood` whose noise is constant along
|
|
260
|
+
the event axis is evaluated in closed form
|
|
261
|
+
(:meth:`~pmrf.discrepancy_models.GaussianProcess.log_prob`), which factorizes
|
|
262
|
+
each distinct covariance matrix once.
|
|
258
263
|
use_orthogonal_discrepancy
|
|
259
264
|
Constrain a Gaussian-process discrepancy to the complement of the free-parameter
|
|
260
265
|
tangent space. This retains the full-data likelihood; it is not REML.
|
|
@@ -403,17 +408,55 @@ class MarginalLogLikelihood(AbstractEvaluator):
|
|
|
403
408
|
log_prob, event_transform = self._orthogonal_log_prob(
|
|
404
409
|
model, frequency, **kwargs
|
|
405
410
|
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
411
|
+
else:
|
|
412
|
+
closed_form = self._closed_form_log_prob(model, frequency, **kwargs)
|
|
413
|
+
if closed_form is not None:
|
|
414
|
+
log_prob, event_transform = closed_form
|
|
415
|
+
else:
|
|
416
|
+
log_prob, event_transform = self._distribution_log_prob(
|
|
417
|
+
model, frequency, **kwargs
|
|
409
418
|
)
|
|
410
|
-
return log_prob
|
|
411
419
|
|
|
420
|
+
# A prediction-dependent change of variables carries a Jacobian determinant that
|
|
421
|
+
# varies with the model, so it must be included for the density to be normalized.
|
|
422
|
+
# For a static transform the term is a constant offset, so it is omitted.
|
|
423
|
+
if self.has_conditional_event_transform:
|
|
424
|
+
log_prob = log_prob + jnp.sum(event_transform.forward_log_det_jacobian(observed))
|
|
425
|
+
|
|
426
|
+
return log_prob
|
|
427
|
+
|
|
428
|
+
def _closed_form_log_prob(
|
|
429
|
+
self, model: PyTree, frequency: Frequency, **kwargs
|
|
430
|
+
) -> tuple[jnp.ndarray, bij.AbstractBijector] | None:
|
|
431
|
+
"""The log-likelihood of a GP with Gaussian noise constant over the event axis.
|
|
432
|
+
|
|
433
|
+
Returns ``None`` when it does not apply, in which case the distribution path
|
|
434
|
+
gives the same value.
|
|
435
|
+
"""
|
|
436
|
+
if not isinstance(self.discrepancy, GaussianProcess) or not isinstance(
|
|
437
|
+
self.likelihood, GaussianLikelihood
|
|
438
|
+
):
|
|
439
|
+
return None
|
|
440
|
+
pred_event, event_transform = self._event(model, frequency, **kwargs)
|
|
441
|
+
variance = self.likelihood._constant_variance(pred_event)
|
|
442
|
+
if variance is None:
|
|
443
|
+
return None
|
|
444
|
+
log_prob = self.discrepancy.log_prob(
|
|
445
|
+
pred_event,
|
|
446
|
+
event_transform.forward(self.observed),
|
|
447
|
+
frequency.f_scaled,
|
|
448
|
+
variance,
|
|
449
|
+
)
|
|
450
|
+
return log_prob, event_transform
|
|
451
|
+
|
|
452
|
+
def _distribution_log_prob(
|
|
453
|
+
self, model: PyTree, frequency: Frequency, **kwargs
|
|
454
|
+
) -> tuple[jnp.ndarray, bij.AbstractBijector]:
|
|
412
455
|
# Get the distribution over obs_event and the actual observed event.
|
|
413
456
|
# The observation is mapped by the *same* resolved transform as the prediction,
|
|
414
457
|
# which for a conditional transform depends on the model.
|
|
415
458
|
obs_dist, event_transform = self._predictive(model, frequency, **kwargs)
|
|
416
|
-
obs_event = event_transform.forward(observed)
|
|
459
|
+
obs_event = event_transform.forward(self.observed)
|
|
417
460
|
batch_ndims = obs_event.ndim - self.event_ndims
|
|
418
461
|
|
|
419
462
|
# We evaluate the log prob `batch_ndims` many times
|
|
@@ -424,15 +467,7 @@ class MarginalLogLikelihood(AbstractEvaluator):
|
|
|
424
467
|
mapped_log_prob = eqx.filter_vmap(mapped_log_prob)
|
|
425
468
|
|
|
426
469
|
log_probs = mapped_log_prob(obs_dist, obs_event)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
# A prediction-dependent change of variables carries a Jacobian determinant that
|
|
430
|
-
# varies with the model, so it must be included for the density to be normalized.
|
|
431
|
-
# For a static transform the term is a constant offset, so it is omitted.
|
|
432
|
-
if self.has_conditional_event_transform:
|
|
433
|
-
log_prob = log_prob + jnp.sum(event_transform.forward_log_det_jacobian(observed))
|
|
434
|
-
|
|
435
|
-
return log_prob
|
|
470
|
+
return jnp.sum(log_probs), event_transform
|
|
436
471
|
|
|
437
472
|
def with_orthogonal_reference(
|
|
438
473
|
self, model: PyTree, frequency: Frequency, **kwargs
|
|
@@ -17,7 +17,7 @@ from pmrf.problems import SummedTerms, PriorPenalized
|
|
|
17
17
|
from pmrf.terms import as_terms
|
|
18
18
|
from pmrf.fitting.targets import resolve_datasets, union_frequency
|
|
19
19
|
from pmrf.likelihoods import GaussianLikelihood
|
|
20
|
-
from pmrf.losses import
|
|
20
|
+
from pmrf.losses import MSELoss
|
|
21
21
|
from pmrf.parameters import Random
|
|
22
22
|
from pmrf.distributions import Uniform
|
|
23
23
|
|
|
@@ -74,16 +74,20 @@ def fit_minimize(
|
|
|
74
74
|
Can be a function or a callable PyTree with optional parameters.
|
|
75
75
|
Used to internally create a :class:`pmrf.evaluators.TargetLoss` evaluator.
|
|
76
76
|
Mutually exclusive with `likelihood`. If neither `loss` nor `likelihood` is passed,
|
|
77
|
-
:class:`pmrf.losses.
|
|
77
|
+
:class:`pmrf.losses.MSELoss` is used for `loss` if `inference` is 'frequentist',
|
|
78
78
|
otherwise :class:`pmrf.likelihoods.GaussianLikelihood` is used for `likelihood`.
|
|
79
79
|
See :mod:`pmrf.losses` for common losses.
|
|
80
|
+
|
|
81
|
+
An MSE that starts very small (e.g. fitting a feature that barely moves) can
|
|
82
|
+
fall below SciPy's absolute `gtol` and `ftol` before the fit moves; tighten them
|
|
83
|
+
via ``ScipyMinimize(options=...)``.
|
|
80
84
|
likelihood : str | Callable, optional
|
|
81
85
|
A likelihood model representing the probability of observing the data.
|
|
82
86
|
Can be a function or a callable PyTree with optional parameters.
|
|
83
87
|
Used to internally create a :class:`pmrf.evaluators.MarginalLogLikelihood`,
|
|
84
88
|
wrapped in a :class:`pmrf.evaluators.Negated` evaluator.
|
|
85
89
|
Mutually exclusive with `loss`. If neither `loss` nor `likelihood` is passed,
|
|
86
|
-
:class:`pmrf.losses.
|
|
90
|
+
:class:`pmrf.losses.MSELoss` is used for `loss` if `inference` is 'frequentist',
|
|
87
91
|
otherwise :class:`pmrf.likelihoods.GaussianLikelihood` is used for `likelihood`.
|
|
88
92
|
See :mod:`pmrf.losses` for common losses.
|
|
89
93
|
noise : prf.Param | Callable[[jnp.ndarray], jnp.ndarray], optional
|
|
@@ -135,7 +139,7 @@ def fit_minimize(
|
|
|
135
139
|
# Resolve defaults e.g. loss vs MLE vs MAP optimization
|
|
136
140
|
if loss is None and likelihood is None:
|
|
137
141
|
if inference == 'frequentist':
|
|
138
|
-
loss =
|
|
142
|
+
loss = MSELoss()
|
|
139
143
|
else:
|
|
140
144
|
if noise is None:
|
|
141
145
|
# Lower bound kept strictly positive: at noise=0 the Gaussian likelihood
|
|
@@ -92,6 +92,43 @@ class GaussianLikelihood(AbstractLikelihood):
|
|
|
92
92
|
)
|
|
93
93
|
return var
|
|
94
94
|
|
|
95
|
+
def _constant_variance(self, y_event: jnp.ndarray) -> jnp.ndarray | None:
|
|
96
|
+
"""Return the noise variance over the batch shape if it is constant along the event axis.
|
|
97
|
+
|
|
98
|
+
The variance is interpreted exactly as in :meth:`__call__`, and kept at its own
|
|
99
|
+
shape, which broadcasts to the non-event batch shape of ``y_event``.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
jnp.ndarray | None
|
|
104
|
+
The variance, or ``None`` if it varies along the event axis or does not
|
|
105
|
+
broadcast to ``y_event``.
|
|
106
|
+
"""
|
|
107
|
+
var = self._event_variance(y_event)
|
|
108
|
+
try:
|
|
109
|
+
broadcast_shape = jnp.broadcast_shapes(var.shape, y_event.shape)
|
|
110
|
+
except ValueError:
|
|
111
|
+
return None
|
|
112
|
+
if broadcast_shape != y_event.shape:
|
|
113
|
+
return None
|
|
114
|
+
if var.ndim == 0:
|
|
115
|
+
return var
|
|
116
|
+
if var.shape[-1] != 1:
|
|
117
|
+
return None
|
|
118
|
+
return var[..., 0]
|
|
119
|
+
|
|
120
|
+
def _event_variance(self, y_event: jnp.ndarray) -> jnp.ndarray:
|
|
121
|
+
"""The noise variance with the event as its last axis, broadcastable to ``y_event``.
|
|
122
|
+
|
|
123
|
+
A variance of exactly the batch shape is per batch entry; any other shape
|
|
124
|
+
aligns its trailing axis with the event axis.
|
|
125
|
+
"""
|
|
126
|
+
var = self.noise(y_event) if callable(self.noise) else self.noise
|
|
127
|
+
var = jnp.asarray(var)
|
|
128
|
+
if var.shape == y_event.shape[:-1]:
|
|
129
|
+
var = var[..., None]
|
|
130
|
+
return var
|
|
131
|
+
|
|
95
132
|
def __call__(self, y_event: jnp.ndarray | AbstractDistribution) -> AbstractDistribution:
|
|
96
133
|
# If y_event is an array, the prediction is deterministic
|
|
97
134
|
# and we can simply use a regular gaussian likelihood.
|
|
@@ -101,15 +138,7 @@ class GaussianLikelihood(AbstractLikelihood):
|
|
|
101
138
|
is_dist = isinstance(y_event, AbstractDistribution)
|
|
102
139
|
y_mean = y_event.mean() if is_dist else y_event
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
var = self.noise(y_mean) if callable(self.noise) else self.noise
|
|
106
|
-
var = jnp.asarray(var)
|
|
107
|
-
|
|
108
|
-
# Broadcast noise onto y_mean
|
|
109
|
-
batch_shape = y_mean.shape[:-1]
|
|
110
|
-
if var.shape == batch_shape:
|
|
111
|
-
var = var[..., None]
|
|
112
|
-
mapped_var = jnp.broadcast_to(var, y_mean.shape)
|
|
141
|
+
mapped_var = jnp.broadcast_to(self._event_variance(y_mean), y_mean.shape)
|
|
113
142
|
num_batch_dims = y_mean.ndim - 1
|
|
114
143
|
|
|
115
144
|
if not is_dist:
|
|
@@ -450,11 +450,14 @@ def _check_in_constraint(value: ArrayLike, constraint: AbstractConstraint) -> Ar
|
|
|
450
450
|
The returned array must be used: under `jax.jit` the check lives only in its graph.
|
|
451
451
|
"""
|
|
452
452
|
value_array = jnp.asarray(value)
|
|
453
|
+
# Escaped so that braces in the constraint's repr are not read as format fields.
|
|
454
|
+
constraint_text = str(constraint).replace("{", "{{").replace("}", "}}")
|
|
453
455
|
return error_if(
|
|
454
456
|
value_array,
|
|
455
457
|
constraint.is_outside(value_array),
|
|
456
|
-
f"\n\nA parameter value falls outside the constraint ({
|
|
458
|
+
f"\n\nA parameter value falls outside the constraint ({{}} is not in {constraint_text}). "
|
|
457
459
|
f"\nMake sure the values match the parameter and model constraints.",
|
|
460
|
+
value_array,
|
|
458
461
|
)
|
|
459
462
|
|
|
460
463
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import equinox as eqx
|
|
2
|
+
import jax
|
|
3
|
+
import jax.numpy as jnp
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def error_if(x, pred, msg, *print_args, on_error="default", **print_kwargs):
|
|
8
|
+
"""
|
|
9
|
+
Conditionally halts JAX execution and prints formatted debug values at runtime.
|
|
10
|
+
|
|
11
|
+
This function evaluates a boolean condition inside JIT-compiled code. If the
|
|
12
|
+
condition is met (e.g., an unphysical parameter like negative impedance is
|
|
13
|
+
detected), it outputs the formatted runtime arrays to standard output before
|
|
14
|
+
raising. Whether to print is decided on the host, so nothing is printed when
|
|
15
|
+
the check passes, under any transform. Under `jax.vmap`, the message is printed
|
|
16
|
+
once for each failing batch element, with that element's values. Without print
|
|
17
|
+
arguments nothing is printed: the raised error carries `msg`.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
x : Any
|
|
22
|
+
The input data (JAX array or PyTree) to pass through. The check runs only
|
|
23
|
+
if the returned value is used.
|
|
24
|
+
pred : bool or jax.Array
|
|
25
|
+
A boolean condition or boolean array. If any element evaluates to `True`,
|
|
26
|
+
execution halts, printing the message if print arguments are given.
|
|
27
|
+
msg : str
|
|
28
|
+
The format string for the error message and console output. Uses standard
|
|
29
|
+
Python `{}` formatting to dynamically inject JAX arrays.
|
|
30
|
+
*print_args : Any
|
|
31
|
+
Dynamic tensors or values to sequentially format into the `msg` string.
|
|
32
|
+
on_error : str, optional
|
|
33
|
+
The internal error handling mode. Default is "default".
|
|
34
|
+
**print_kwargs : Any
|
|
35
|
+
Dynamic tensors or values to format into the `msg` string via keywords.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
Any
|
|
40
|
+
The unmodified input `x`.
|
|
41
|
+
|
|
42
|
+
Raises
|
|
43
|
+
------
|
|
44
|
+
equinox.EquinoxRuntimeError
|
|
45
|
+
At runtime, if `pred` contains any `True` elements.
|
|
46
|
+
|
|
47
|
+
Examples
|
|
48
|
+
--------
|
|
49
|
+
Catching an invalid characteristic impedance inside a JIT-compiled simulation:
|
|
50
|
+
|
|
51
|
+
>>> z0 = jnp.array(-50.0)
|
|
52
|
+
>>> is_invalid = z0 < 0
|
|
53
|
+
>>> z0 = error_if(
|
|
54
|
+
... z0,
|
|
55
|
+
... is_invalid,
|
|
56
|
+
... "Characteristic impedance must be positive, got Z0 = {} Ohms",
|
|
57
|
+
... z0
|
|
58
|
+
... )
|
|
59
|
+
"""
|
|
60
|
+
error_msg = msg
|
|
61
|
+
if print_args or print_kwargs:
|
|
62
|
+
# Decided on the host: a `lax.cond` on a batched predicate lowers to `select`
|
|
63
|
+
# under `vmap`, which runs the print whether or not the check fails. A
|
|
64
|
+
# `pure_callback` rather than `jax.debug.callback`, so that `eqx.error_if` can
|
|
65
|
+
# consume its output: that data dependency makes the print precede the raise.
|
|
66
|
+
def print_if(host_pred, args, kwargs):
|
|
67
|
+
if np.any(host_pred):
|
|
68
|
+
print(msg.format(*args, **kwargs))
|
|
69
|
+
return host_pred
|
|
70
|
+
|
|
71
|
+
pred = jnp.asarray(pred)
|
|
72
|
+
args, kwargs = jax.lax.stop_gradient((print_args, print_kwargs))
|
|
73
|
+
pred = jax.pure_callback(
|
|
74
|
+
print_if,
|
|
75
|
+
jax.ShapeDtypeStruct(pred.shape, pred.dtype),
|
|
76
|
+
pred,
|
|
77
|
+
args,
|
|
78
|
+
kwargs,
|
|
79
|
+
vmap_method="sequential",
|
|
80
|
+
)
|
|
81
|
+
error_msg = f"{msg} (Check standard output for runtime values)"
|
|
82
|
+
|
|
83
|
+
return eqx.error_if(x, pred, error_msg, on_error=on_error)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import dataclasses
|
|
2
|
+
|
|
1
3
|
import jax
|
|
2
4
|
import jax.numpy as jnp
|
|
3
5
|
import equinox as eqx
|
|
6
|
+
import parax as prx
|
|
4
7
|
from typing import Callable, Any, Tuple, TypeVarTuple
|
|
5
8
|
|
|
6
9
|
from pmrf.utils.tree import unwrap, batch_axes
|
|
@@ -35,6 +38,11 @@ def derivative(eval_fn: Callable[..., Any], *args: *Ts, space: str = 'declared')
|
|
|
35
38
|
inside the differentiated function, so the source's derivative includes the
|
|
36
39
|
path through the tie. Raw JAX arrays are differentiated as they are.
|
|
37
40
|
|
|
41
|
+
In declared and physical space a parameter on a closed bound, such as a
|
|
42
|
+
``NonNegative`` loss at 0, has a finite derivative. In raw space it has none:
|
|
43
|
+
its raw value there is infinite, so a derivative with respect to it has no
|
|
44
|
+
finite meaning.
|
|
45
|
+
|
|
38
46
|
Safely handles models as inputs by filtering out non-differentiable
|
|
39
47
|
static fields (like strings, booleans, integers, AND NumPy arrays).
|
|
40
48
|
|
|
@@ -82,15 +90,15 @@ def derivative(eval_fn: Callable[..., Any], *args: *Ts, space: str = 'declared')
|
|
|
82
90
|
-1.164e-01
|
|
83
91
|
"""
|
|
84
92
|
# Imported here: pmrf.parameters imports pmrf.utils.
|
|
85
|
-
from pmrf.parameters import _check_space, _read, _set_paths, _with_fixed, _write, tree_param_paths
|
|
93
|
+
from pmrf.parameters import _check_space, _read, _set_paths, _with_fixed, _write, is_param, tree_param_paths
|
|
86
94
|
from pmrf.utils.tree import Pathgetter
|
|
87
95
|
|
|
88
96
|
_check_space(space)
|
|
89
97
|
|
|
90
98
|
# Each parameter is replaced by its value in `space`, and rebuilt from it inside
|
|
91
99
|
# the differentiated function, so the derivative is taken with respect to that
|
|
92
|
-
# value and the chain rule carries it through scale
|
|
93
|
-
#
|
|
100
|
+
# value and the chain rule carries it through scale and ties. Fixed is an
|
|
101
|
+
# optimisation state, not a zero sensitivity, so parameters are rebuilt free.
|
|
94
102
|
rebuilds = []
|
|
95
103
|
surrogates = []
|
|
96
104
|
for arg in args:
|
|
@@ -103,12 +111,23 @@ def derivative(eval_fn: Callable[..., Any], *args: *Ts, space: str = 'declared')
|
|
|
103
111
|
rebuilds.append((paths, nodes))
|
|
104
112
|
surrogates.append(_set_paths(arg, paths, [_read(node, space) for node in nodes]))
|
|
105
113
|
|
|
114
|
+
def _rebuild_node(node, value):
|
|
115
|
+
# In declared and physical space a parameter holds its declared value directly,
|
|
116
|
+
# as in box space (#226), not through its raw value: on a closed bound that is
|
|
117
|
+
# infinite, the constraint's bijector gives 0 * inf = NaN, and forward mode
|
|
118
|
+
# spreads the NaN to every parameter. The value is the one the parameter was
|
|
119
|
+
# built with, so its bounds need no checking again. A `Real` is free.
|
|
120
|
+
if not is_param(node) or space == 'raw':
|
|
121
|
+
return _with_fixed(_write(node, value, space), False)
|
|
122
|
+
declared = value / node._scale if space == 'physical' and node._scale != 1.0 else value
|
|
123
|
+
return dataclasses.replace(node, variable=prx.Real(declared))
|
|
124
|
+
|
|
106
125
|
def _rebuild(surrogate_args):
|
|
107
126
|
rebuilt = []
|
|
108
127
|
for surrogate, (paths, nodes) in zip(surrogate_args, rebuilds):
|
|
109
128
|
values = [Pathgetter(path)(surrogate) for path in paths]
|
|
110
129
|
rebuilt.append(_set_paths(
|
|
111
|
-
surrogate, paths, [
|
|
130
|
+
surrogate, paths, [_rebuild_node(node, value) for node, value in zip(nodes, values)]
|
|
112
131
|
))
|
|
113
132
|
return unwrap(tuple(rebuilt))
|
|
114
133
|
|