paramrf 0.28.2__tar.gz → 0.28.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {paramrf-0.28.2/paramrf.egg-info → paramrf-0.28.3}/PKG-INFO +1 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/skrf_comparison/index.rst +1 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/skrf_comparison/overview.rst +1 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/skrf_comparison/performance.rst +7 -7
- {paramrf-0.28.2 → paramrf-0.28.3/paramrf.egg-info}/PKG-INFO +1 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/paramrf.egg-info/SOURCES.txt +0 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/ideal.py +4 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/base.py +7 -13
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/circuit.py +75 -64
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py +51 -69
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/rf/conversions.py +23 -12
- {paramrf-0.28.2 → paramrf-0.28.3}/pyproject.toml +1 -1
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_circuit_scattering.py +30 -30
- paramrf-0.28.2/docs/skrf_comparison.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/.github/workflows/docs.yml +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/.github/workflows/draft-pdf.yml +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/.github/workflows/publish.yml +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/.github/workflows/tests.yml +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/.gitignore +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/CONTRIBUTING.md +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/LICENSE +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/README.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/assets/logo.png +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/Makefile +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/_static/custom.css +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/_templates/autosummary/class.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/_templates/autosummary/function.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/_templates/autosummary/module.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/api/index.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/conf.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/core_concepts/core_primitives.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/core_concepts/index.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/core_concepts/jax_overview.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/core_concepts/optimization_and_inference.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/cascading_and_terminating.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/circuit_clc.png +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/circuit_models.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/custom_composite_models.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/custom_parametric_models.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/derivatives_and_sweeps.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/index.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/model_optimization.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/examples/parameter_naming_and_model_manipulation.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/index.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/license.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/make.bat +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/tutorials/1_cable_fitting.ipynb +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/tutorials/2_chip_inductor_fitting.ipynb +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/tutorials/data/CBN-1.5FT-SMSM.s2p +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/tutorials/data/on-chip-inductor.s2p +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/docs/tutorials/index.rst +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/paper/paper.bib +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/paper/paper.md +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/paper/rlc.png +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/paramrf.egg-info/dependency_links.txt +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/paramrf.egg-info/requires.txt +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/paramrf.egg-info/top_level.txt +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/constraints.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/covariance_kernels.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/discrepancy_models.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/distributions.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/evaluators.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/fitting/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/fitting/minimize.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/fitting/result.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/fitting/routers.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/fitting/sample.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/frequency.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/result.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/sample.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/solvers/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/solvers/blackjax.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/infer/solvers/polychord.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/likelihoods.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/losses.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/math/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/math/aggregations.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/math/conversions.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/math/losses.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/math/misc.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/adapters/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/adapters/base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/adapters/bridge.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/adapters/callable.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/adapters/static.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lines/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lines/base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lines/ideal.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lines/nodal.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lines/nonuniform.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lines/physical.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/lumped.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/components/sections.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/cascade.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/solvers/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/solvers/nodal.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/terminated.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/nodal.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/topological.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/transformed.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/wrapped.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/surrogates/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/surrogates/expansion.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/surrogates/rational.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/network_collection.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/noise_models.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/minimize.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/result.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/solvers/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/solvers/jaxopt.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/solvers/optimistix.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/optimize/solvers/scipy.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/parameters.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/problem.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/rf/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/rf/mna.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/serialization.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/types.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/array.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/debug.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/network.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/optix.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/random.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/rf.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/transforms.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/tree.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/utils/type.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/viz/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/pmrf/viz/plots.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/setup.cfg +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/__init__.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/data/10m_cable.s2p +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_autodiff.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_conversions.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_evaluators.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_fitting_minimize.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_fitting_routers.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_fitting_sample.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_frequency.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_infer_base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_infer_sample.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_model.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_adapters.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_circuit_nodal.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_interconnected.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_lines.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_lumped.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_nodal.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_sections.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_models/test_transformed.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_naming.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_optimize_base.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_optimize_minimize.py +0 -0
- {paramrf-0.28.2 → paramrf-0.28.3}/tests/test_transforms.py +0 -0
|
@@ -3,7 +3,7 @@ scikit-rf Comparison
|
|
|
3
3
|
|
|
4
4
|
ParamRF aims to complement and not replace scikit-rf. While scikit-rf excels at static network manipulation, it does not take a parameter-first modeling approach, and also suffers from Python interpreter overhead and finite difference approximations which JAX eleviates.
|
|
5
5
|
|
|
6
|
-
This section provides a brief comparison between the core philosophies of scikit-rf and ParamRF, and also demonstrates a short benchmarking
|
|
6
|
+
This section provides a brief comparison between the core philosophies of scikit-rf and ParamRF, and also demonstrates a short benchmarking script showing the performance benefits when using JAX.
|
|
7
7
|
|
|
8
8
|
.. toctree::
|
|
9
9
|
:maxdepth: 2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
Overview
|
|
2
2
|
=====================
|
|
3
3
|
**ParamRF** is designed to complement **scikit-rf** and not replace it, offering a fundamentally different approach to RF modeling. A very brief summary of the differences, strengths and weakness of the libraries is presented below.
|
|
4
4
|
|
|
@@ -181,18 +181,18 @@ Running this script on a standard modern CPU yields results similar to the follo
|
|
|
181
181
|
=======================================================
|
|
182
182
|
FORWARD PASS EXECUTION TIMES (2001 Points)
|
|
183
183
|
=======================================================
|
|
184
|
-
scikit-rf (Baseline) |
|
|
184
|
+
scikit-rf (Baseline) | 25.343 ms | 1.00x
|
|
185
185
|
-------------------------------------------------------
|
|
186
|
-
ParamRF Scattering Solver |
|
|
187
|
-
ParamRF MNA Solver |
|
|
186
|
+
ParamRF Scattering Solver | 4.823 ms | 5.25x
|
|
187
|
+
ParamRF MNA Solver | 2.979 ms | 8.51x
|
|
188
188
|
=======================================================
|
|
189
189
|
|
|
190
|
-
By vectorizing the linear matrix solutions and bypassing the Python interpreter via JIT compilation, ParamRF's MNA solver evaluates nearly
|
|
190
|
+
By vectorizing the linear matrix solutions and bypassing the Python interpreter via JIT compilation, ParamRF's MNA solver evaluates nearly 9x faster, while the scattering solver evaluates 5x faster (which is most similar to the scattering algorithm in scikit-rf's Circuit).
|
|
191
191
|
|
|
192
192
|
The Autodiff Advantage
|
|
193
193
|
----------------------
|
|
194
|
-
While a
|
|
194
|
+
While a 5-10x speedup on the forward pass is significant, the true performance gain of ParamRF is realized during gradient-based optimization (the *backward pass*).
|
|
195
195
|
|
|
196
|
-
In traditional libraries, calculating the gradient for an $N$-parameter circuit requires Finite Differences (FD), meaning the circuit must be simulated $N+1$ times per iteration. For a 50-parameter model, this means a single gradient step in ``scikit-rf`` would take
|
|
196
|
+
In traditional libraries, calculating the gradient for an $N$-parameter circuit requires Finite Differences (FD), meaning the circuit must be simulated $N+1$ times per iteration. For a 50-parameter model, this means a single gradient step in ``scikit-rf`` would take **1.3 seconds** (``51 * 25.3 ms``).
|
|
197
197
|
|
|
198
|
-
In ParamRF, because the entire circuit math is traced, calling ``jax.value_and_grad`` yields the exact analytic gradients for all 50 parameters simultaneously in a single, compiled backward pass—often executing in **less than
|
|
198
|
+
In ParamRF, because the entire circuit math is traced, calling ``jax.value_and_grad`` yields the exact analytic gradients for all 50 parameters simultaneously in a single, compiled backward pass—often executing in **less than 10 ms**. This results in a massive speedup (often 40x to 100x) when optimizing complex circuits.
|
|
@@ -104,7 +104,10 @@ class Port(Model):
|
|
|
104
104
|
"""
|
|
105
105
|
Represents a circuit port with a specific characteristic impedance.
|
|
106
106
|
|
|
107
|
-
This class serves as a placeholder or marker for external connections
|
|
107
|
+
This class serves as a placeholder or marker for external connections
|
|
108
|
+
in a :class:`pmrf.models.Circuit` definition, though can also be used
|
|
109
|
+
as a simple tagged load in other types of models.
|
|
110
|
+
|
|
108
111
|
Calling `build` returns a matched load model.
|
|
109
112
|
"""
|
|
110
113
|
#: Port characteristic impedance
|
|
@@ -69,25 +69,19 @@ class MNARepresentation(eqx.Module):
|
|
|
69
69
|
def num_aux(self) -> int:
|
|
70
70
|
"""The total number of global auxiliary variables."""
|
|
71
71
|
return self.aux_idx.shape[0]
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
|
|
73
|
+
|
|
74
74
|
class PortRepresentation(eqx.Module):
|
|
75
|
-
|
|
76
|
-
Static topological representation for scattering parameter connection and reduction.
|
|
77
|
-
"""
|
|
78
|
-
#: A 1D array of port indices designated as external (unconnected or specifically retained).
|
|
79
|
-
ext_idx: np.ndarray
|
|
80
|
-
|
|
81
|
-
#: A 1D array of port indices designated as internal (connected to other ports).
|
|
82
|
-
int_idx: np.ndarray
|
|
83
|
-
|
|
84
|
-
#: A 1D array of length `num_ports` mapping each port to the integer ID of its connected net.
|
|
75
|
+
#: Array mapping each active device port to a net ID
|
|
85
76
|
port_to_net_map: np.ndarray
|
|
86
77
|
|
|
78
|
+
#: Ordered array of net IDs that act as external ports
|
|
79
|
+
ext_net_ids: np.ndarray
|
|
80
|
+
|
|
87
81
|
@property
|
|
88
82
|
def num_ports(self) -> int:
|
|
89
83
|
"""The total number of ports across all components in the topology."""
|
|
90
|
-
return self.port_to_net_map.shape[0]
|
|
84
|
+
return self.port_to_net_map.shape[0]
|
|
91
85
|
|
|
92
86
|
|
|
93
87
|
class ScatteringResult(eqx.Module):
|
|
@@ -14,7 +14,7 @@ from pmrf.frequency import Frequency
|
|
|
14
14
|
from collections import defaultdict
|
|
15
15
|
from pmrf.utils import field
|
|
16
16
|
from pmrf.types import ArrayLike
|
|
17
|
-
from pmrf.rf import y2s, s2y
|
|
17
|
+
from pmrf.rf import y2s, s2y, renormalize_s
|
|
18
18
|
|
|
19
19
|
from pmrf.models.composite.interconnected.circuit.base import (
|
|
20
20
|
AbstractCircuitSolver,
|
|
@@ -175,72 +175,77 @@ class Circuit(Model):
|
|
|
175
175
|
@cached_property
|
|
176
176
|
def port_representation(self) -> PortRepresentation:
|
|
177
177
|
"""Generates the static topological map for scattering connection and reduction."""
|
|
178
|
-
|
|
178
|
+
global_net_map = compute_unique_nets(self.circuit, self.indexed_connections)
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
ext_net_ids = []
|
|
181
|
+
|
|
181
182
|
offset = 0
|
|
182
183
|
for model in self.circuit:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
ext_idx.append(offset + p)
|
|
186
|
-
else:
|
|
187
|
-
int_idx.append(offset + p)
|
|
184
|
+
if isinstance(model, Port):
|
|
185
|
+
ext_net_ids.append(global_net_map[offset])
|
|
188
186
|
offset += model.nports
|
|
189
187
|
|
|
188
|
+
active_net_map = []
|
|
189
|
+
offset = 0
|
|
190
|
+
for model in self.circuit:
|
|
191
|
+
n = model.nports
|
|
192
|
+
if not isinstance(model, Port):
|
|
193
|
+
active_net_map.extend(global_net_map[offset : offset + n])
|
|
194
|
+
offset += n
|
|
195
|
+
|
|
190
196
|
return PortRepresentation(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
port_to_net_map=port_to_net_map
|
|
197
|
+
ext_net_ids=np.array(ext_net_ids, dtype=int),
|
|
198
|
+
port_to_net_map=np.array(active_net_map, dtype=int)
|
|
194
199
|
)
|
|
195
200
|
|
|
196
201
|
@cached_property
|
|
197
202
|
def nodal_representation(self) -> NodalRepresentation:
|
|
198
203
|
"""Generates the static topological map for nodal admittance assembly and reduction."""
|
|
199
|
-
|
|
200
|
-
port_to_net_map = port_rep.port_to_net_map
|
|
204
|
+
global_net_map = compute_unique_nets(self.circuit, self.indexed_connections)
|
|
201
205
|
|
|
202
|
-
r_idx, c_idx = [], []
|
|
203
|
-
offset = 0
|
|
204
|
-
for model in self.circuit:
|
|
205
|
-
n = model.nports
|
|
206
|
-
nets = port_to_net_map[offset:offset+n]
|
|
207
|
-
R, C = np.meshgrid(nets, nets, indexing='ij')
|
|
208
|
-
r_idx.append(R.flatten())
|
|
209
|
-
c_idx.append(C.flatten())
|
|
210
|
-
offset += n
|
|
211
|
-
|
|
212
|
-
unique_ext_nets = np.unique(port_to_net_map[port_rep.ext_idx])
|
|
213
|
-
unique_int_nets = np.setdiff1d(np.unique(port_to_net_map), unique_ext_nets)
|
|
214
|
-
|
|
215
|
-
# Drop ground nodes
|
|
216
206
|
ground_nets = set()
|
|
207
|
+
ext_nets = set()
|
|
217
208
|
offset = 0
|
|
218
209
|
for model in self.circuit:
|
|
210
|
+
nets = global_net_map[offset : offset + model.nports]
|
|
219
211
|
if isinstance(model, Ground):
|
|
220
|
-
ground_nets.update(
|
|
212
|
+
ground_nets.update(nets)
|
|
213
|
+
elif isinstance(model, Port):
|
|
214
|
+
ext_nets.update(nets)
|
|
221
215
|
offset += model.nports
|
|
222
216
|
|
|
223
|
-
unique_all_nets = np.unique(
|
|
217
|
+
unique_all_nets = np.unique(global_net_map)
|
|
224
218
|
active_nets = np.array([n for n in unique_all_nets if n not in ground_nets], dtype=int)
|
|
225
219
|
|
|
226
|
-
# Create mapping: active nodes -> 0..N-1. Ground nodes -> N (to be safely dropped)
|
|
227
220
|
N_active = len(active_nets)
|
|
228
221
|
mapping = np.full(np.max(unique_all_nets) + 1, N_active, dtype=int)
|
|
229
222
|
mapping[active_nets] = np.arange(N_active)
|
|
230
223
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
224
|
+
r_idx, c_idx = [], []
|
|
225
|
+
offset = 0
|
|
226
|
+
for model in self.circuit:
|
|
227
|
+
n = model.nports
|
|
228
|
+
if not isinstance(model, (Port, Ground)):
|
|
229
|
+
nets = global_net_map[offset : offset + n]
|
|
230
|
+
R, C = np.meshgrid(nets, nets, indexing='ij')
|
|
231
|
+
r_idx.append(R.flatten())
|
|
232
|
+
c_idx.append(C.flatten())
|
|
233
|
+
offset += n
|
|
234
|
+
|
|
235
|
+
def _safe_cat(lst):
|
|
236
|
+
return np.concatenate(lst).astype(int) if lst else np.array([], dtype=int)
|
|
237
|
+
|
|
238
|
+
r_idx_mapped = mapping[_safe_cat(r_idx)]
|
|
239
|
+
c_idx_mapped = mapping[_safe_cat(c_idx)]
|
|
234
240
|
|
|
235
|
-
|
|
236
|
-
ext_active = np.array([n for n in unique_ext_nets if n not in ground_nets], dtype=int)
|
|
241
|
+
ext_active = np.array([n for n in ext_nets if n not in ground_nets], dtype=int)
|
|
237
242
|
int_active = np.setdiff1d(active_nets, ext_active)
|
|
238
243
|
|
|
239
244
|
return NodalRepresentation(
|
|
240
245
|
r_idx=r_idx_mapped,
|
|
241
246
|
c_idx=c_idx_mapped,
|
|
242
|
-
ext_idx=mapping[ext_active],
|
|
243
|
-
int_idx=mapping[int_active]
|
|
247
|
+
ext_idx=mapping[ext_active],
|
|
248
|
+
int_idx=mapping[int_active]
|
|
244
249
|
)
|
|
245
250
|
|
|
246
251
|
@property
|
|
@@ -257,7 +262,8 @@ class Circuit(Model):
|
|
|
257
262
|
offset_y = 0 # Tracks the index in the flattened n*n nodal arrays
|
|
258
263
|
freq_dummy = Frequency(1, 2, 2)
|
|
259
264
|
|
|
260
|
-
for
|
|
265
|
+
nodal_components = [m for m in self.circuit if not isinstance(m, (Port, Ground))]
|
|
266
|
+
for model in nodal_components:
|
|
261
267
|
stamp_shape = jax.eval_shape(lambda m=model: m.mna(freq_dummy))
|
|
262
268
|
k = stamp_shape.D.shape[1]
|
|
263
269
|
n = model.nports
|
|
@@ -301,47 +307,50 @@ class Circuit(Model):
|
|
|
301
307
|
|
|
302
308
|
# --- DATA EVALUATION ---
|
|
303
309
|
|
|
304
|
-
def _evaluate_scattering(self, freq: Frequency, z0: ArrayLike = 50.0) -> tuple[jnp.ndarray, jnp.ndarray]:
|
|
310
|
+
def _evaluate_scattering(self, freq: Frequency, z0: ArrayLike = 50.0) -> tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]:
|
|
305
311
|
"""Evaluates and block-diagonalizes the scattering matrices of all contained components."""
|
|
306
|
-
|
|
312
|
+
scattering_components = [c for c in self.circuit if not isinstance(c, Port)]
|
|
313
|
+
S_blocks = [c.s(freq, z0=z0) for c in scattering_components]
|
|
307
314
|
|
|
308
|
-
|
|
315
|
+
if S_blocks:
|
|
316
|
+
batched_S = jax.vmap(block_diag)(*S_blocks)
|
|
317
|
+
else:
|
|
318
|
+
batched_S = jnp.zeros((freq.npoints, 0, 0), dtype=jnp.complex128)
|
|
319
|
+
|
|
309
320
|
num_ports = batched_S.shape[-1]
|
|
310
321
|
dtype = batched_S.dtype
|
|
311
322
|
z0_ports = jnp.broadcast_to(jnp.asarray(z0, dtype=dtype), (num_ports,))
|
|
312
323
|
|
|
313
|
-
|
|
324
|
+
# Dynamically extract the specific z0 constraints of the external ports
|
|
325
|
+
ext_z0_list = [jnp.asarray(getattr(c, 'z0', EVAL_Z0), dtype=dtype) for c in self.circuit if isinstance(c, Port)]
|
|
326
|
+
z0_ext = jnp.stack(ext_z0_list) if ext_z0_list else jnp.zeros((0,), dtype=dtype)
|
|
327
|
+
|
|
328
|
+
return batched_S, z0_ports, z0_ext
|
|
314
329
|
|
|
315
330
|
def _evaluate_admittance(self, freq: Frequency) -> jnp.ndarray:
|
|
316
331
|
"""Evaluates and flattens the admittance matrices of all contained components."""
|
|
317
|
-
|
|
318
|
-
for c in
|
|
319
|
-
|
|
320
|
-
Y_blocks.append(jnp.zeros((freq.npoints, c.nports, c.nports), dtype=jnp.complex128))
|
|
321
|
-
else:
|
|
322
|
-
Y_blocks.append(c.y(freq))
|
|
323
|
-
|
|
332
|
+
nodal_components = [c for c in self.circuit if not isinstance(c, (Port, Ground))]
|
|
333
|
+
Y_blocks = [c.y(freq) for c in nodal_components]
|
|
334
|
+
|
|
324
335
|
flat_Y_list = []
|
|
325
336
|
for Y in Y_blocks:
|
|
326
337
|
Nf, n, _ = Y.shape
|
|
327
338
|
flat_Y_list.append(Y.reshape(Nf, n * n))
|
|
328
|
-
|
|
339
|
+
|
|
340
|
+
if flat_Y_list:
|
|
341
|
+
return jnp.concatenate(flat_Y_list, axis=1)
|
|
342
|
+
else:
|
|
343
|
+
return jnp.zeros((freq.npoints, 0), dtype=jnp.complex128)
|
|
329
344
|
|
|
330
345
|
def _evaluate_mna(self, freq: Frequency) -> tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray, jnp.ndarray]:
|
|
331
346
|
"""Evaluates and flattens the MNA stamps of all contained components."""
|
|
332
347
|
flat_Y, flat_B, flat_C, flat_D = [], [], [], []
|
|
333
348
|
|
|
334
|
-
for c in self.circuit
|
|
349
|
+
nodal_components = [c for c in self.circuit if not isinstance(c, (Port, Ground))]
|
|
350
|
+
for c in nodal_components:
|
|
335
351
|
stamp = c.mna(freq)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
Y = jnp.zeros_like(stamp.Y)
|
|
339
|
-
B = jnp.zeros_like(stamp.B)
|
|
340
|
-
C = jnp.zeros_like(stamp.C)
|
|
341
|
-
D = jnp.zeros_like(stamp.D)
|
|
342
|
-
else:
|
|
343
|
-
Y, B, C, D = stamp.Y, stamp.B, stamp.C, stamp.D
|
|
344
|
-
|
|
352
|
+
Y, B, C, D = stamp.Y, stamp.B, stamp.C, stamp.D
|
|
353
|
+
|
|
345
354
|
Nf, n, _ = Y.shape
|
|
346
355
|
k = D.shape[1]
|
|
347
356
|
|
|
@@ -366,9 +375,9 @@ class Circuit(Model):
|
|
|
366
375
|
flat = self
|
|
367
376
|
|
|
368
377
|
if isinstance(flat.solver, AbstractScatteringCircuitSolver):
|
|
369
|
-
s_bdiag, z0_ports = flat._evaluate_scattering(freq, z0)
|
|
370
|
-
run_vmap = jax.vmap(flat.solver.run, in_axes=(0, None, None))
|
|
371
|
-
return run_vmap(s_bdiag, z0_ports, flat.port_representation)
|
|
378
|
+
s_bdiag, z0_ports, z0_ext = flat._evaluate_scattering(freq, z0)
|
|
379
|
+
run_vmap = jax.vmap(flat.solver.run, in_axes=(0, None, None, None))
|
|
380
|
+
return run_vmap(s_bdiag, z0_ports, z0_ext, flat.port_representation)
|
|
372
381
|
|
|
373
382
|
elif isinstance(flat.solver, AbstractAdmittanceCircuitSolver):
|
|
374
383
|
y_flat = flat._evaluate_admittance(freq)
|
|
@@ -386,8 +395,10 @@ class Circuit(Model):
|
|
|
386
395
|
def s(self, freq: Frequency, z0: ArrayLike = 50.0) -> jnp.ndarray:
|
|
387
396
|
"""Evaluates the composite scattering parameters of the circuit."""
|
|
388
397
|
result = self._solve(freq, z0)
|
|
398
|
+
|
|
389
399
|
if isinstance(result, ScatteringResult):
|
|
390
|
-
|
|
400
|
+
# Renormalize from native port z0 to the requested measurement z0
|
|
401
|
+
return renormalize_s(result.s, z_old=result.z0, z_new=z0)
|
|
391
402
|
elif isinstance(result, AdmittanceResult):
|
|
392
403
|
return y2s(result.y, z0=z0)
|
|
393
404
|
else:
|
{paramrf-0.28.2 → paramrf-0.28.3}/pmrf/models/composite/interconnected/circuit/solvers/scattering.py
RENAMED
|
@@ -15,7 +15,6 @@ from pmrf.models.composite.interconnected.circuit.base import (
|
|
|
15
15
|
|
|
16
16
|
from pmrf.rf.conversions import s2s
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
class GlobalScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
20
19
|
"""
|
|
21
20
|
Global S-parameter reduction solver.
|
|
@@ -32,30 +31,32 @@ class GlobalScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
|
32
31
|
linear_solver: lx.AbstractLinearSolver = eqx.field(
|
|
33
32
|
default=lx.AutoLinearSolver(well_posed=None), static=True
|
|
34
33
|
)
|
|
35
|
-
|
|
36
|
-
def run(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
z0_ports: jax.Array,
|
|
40
|
-
topology: PortRepresentation,
|
|
41
|
-
) -> ScatteringResult:
|
|
34
|
+
|
|
35
|
+
def run(self, s_block_diagonal: jax.Array, z0_ports: jax.Array, z0_ext: jax.Array, topology: PortRepresentation) -> ScatteringResult:
|
|
36
|
+
N_act = s_block_diagonal.shape[-1]
|
|
37
|
+
N_ext = len(topology.ext_net_ids)
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
np.array(topology.int_idx),
|
|
47
|
-
np.array(topology.port_to_net_map)
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
S_trav = s2s(s_bd, z0, 'traveling', 'power')
|
|
51
|
-
X = build_connection_matrix(net_map_np, z0, S_trav.dtype)
|
|
39
|
+
# Statically inject the VNA probes (S=0)
|
|
40
|
+
pad_width = ((0, N_ext), (0, N_ext))
|
|
41
|
+
s_bd = jnp.pad(s_block_diagonal, pad_width, mode='constant')
|
|
52
42
|
|
|
53
|
-
|
|
43
|
+
z0 = jnp.concatenate([z0_ports, z0_ext])
|
|
54
44
|
|
|
55
|
-
|
|
45
|
+
# Wire the active topology and the VNA probes together
|
|
46
|
+
# The active ports use port_to_net_map. The VNA probes connect to ext_net_ids.
|
|
47
|
+
net_map = jnp.concatenate([topology.port_to_net_map, topology.ext_net_ids])
|
|
56
48
|
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
# The external indices are simply the newly added probes at the end of the array
|
|
50
|
+
ext_idx = jnp.arange(N_act, N_act + N_ext)
|
|
51
|
+
|
|
52
|
+
# Standard Hallbjörner generalized wave reduction
|
|
53
|
+
S_trav = s2s(s_bd, z0, 'traveling', 'power')
|
|
54
|
+
X = build_connection_matrix(net_map, z0, S_trav.dtype)
|
|
55
|
+
T = jnp.eye(net_map.shape[0], dtype=S_trav.dtype) - S_trav @ X
|
|
56
|
+
|
|
57
|
+
S_ext_trav = reduce_to_external(T, X, ext_idx, self.eps, self.linear_solver)
|
|
58
|
+
|
|
59
|
+
return ScatteringResult(s=s2s(S_ext_trav, z0_ext, 'power', 'traveling'), z0=z0_ext)
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
class HierarchicalScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
@@ -79,18 +80,26 @@ class HierarchicalScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
|
79
80
|
self,
|
|
80
81
|
s_block_diagonal: jax.Array,
|
|
81
82
|
z0_ports: jax.Array,
|
|
83
|
+
z0_ext: jax.Array,
|
|
82
84
|
topology: PortRepresentation,
|
|
83
85
|
) -> ScatteringResult:
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
87
|
+
N_act = s_block_diagonal.shape[-1]
|
|
88
|
+
N_ext = len(topology.ext_net_ids)
|
|
89
|
+
|
|
90
|
+
# Statically inject the VNA probes (S=0)
|
|
91
|
+
pad_width = ((0, N_ext), (0, N_ext))
|
|
92
|
+
s_bd = jnp.pad(s_block_diagonal, pad_width, mode='constant')
|
|
93
|
+
|
|
94
|
+
z0 = jnp.concatenate([z0_ports, z0_ext])
|
|
95
|
+
|
|
96
|
+
# Wire the active topology and the VNA probes together
|
|
97
|
+
net_map_np = np.concatenate([topology.port_to_net_map, topology.ext_net_ids])
|
|
98
|
+
ext_idx_np = np.arange(N_act, N_act + N_ext)
|
|
91
99
|
|
|
100
|
+
# Map the nets
|
|
92
101
|
all_net_ids = np.unique(net_map_np)
|
|
93
|
-
ext_net_ids = np.unique(
|
|
102
|
+
ext_net_ids = np.unique(topology.ext_net_ids)
|
|
94
103
|
pure_int_net_ids = np.setdiff1d(all_net_ids, ext_net_ids)
|
|
95
104
|
|
|
96
105
|
S = s2s(s_bd, z0, 'traveling', 'power')
|
|
@@ -129,7 +138,6 @@ class HierarchicalScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
|
129
138
|
|
|
130
139
|
S_ext_trav = reduce_to_external(T, X, jnp.array(ext_idx_np), self.eps, self.linear_solver)
|
|
131
140
|
|
|
132
|
-
z0_ext = z0[ext_idx_np]
|
|
133
141
|
return ScatteringResult(s=s2s(S_ext_trav, z0_ext, 'power', 'traveling'), z0=z0_ext)
|
|
134
142
|
|
|
135
143
|
|
|
@@ -145,7 +153,6 @@ class SequentialScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
|
145
153
|
|
|
146
154
|
Best suited for long chain-like/separated networks,
|
|
147
155
|
though consider using an explicit Cascade if possible.
|
|
148
|
-
|
|
149
156
|
"""
|
|
150
157
|
eps: float = eqx.field(default=1e-12, static=True)
|
|
151
158
|
linear_solver: lx.AbstractLinearSolver = eqx.field(
|
|
@@ -156,21 +163,28 @@ class SequentialScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
|
156
163
|
self,
|
|
157
164
|
s_block_diagonal: jax.Array,
|
|
158
165
|
z0_ports: jax.Array,
|
|
166
|
+
z0_ext: jax.Array,
|
|
159
167
|
topology: PortRepresentation,
|
|
160
168
|
) -> ScatteringResult:
|
|
161
169
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
)
|
|
170
|
+
N_act = s_block_diagonal.shape[-1]
|
|
171
|
+
N_ext = len(topology.ext_net_ids)
|
|
172
|
+
|
|
173
|
+
# Statically inject the VNA probes (S=0)
|
|
174
|
+
pad_width = ((0, N_ext), (0, N_ext))
|
|
175
|
+
s_bd = jnp.pad(s_block_diagonal, pad_width, mode='constant')
|
|
176
|
+
|
|
177
|
+
z0 = jnp.concatenate([z0_ports, z0_ext])
|
|
178
|
+
|
|
179
|
+
# Wire the active topology and the VNA probes together
|
|
180
|
+
net_map_np = np.concatenate([topology.port_to_net_map, topology.ext_net_ids])
|
|
181
|
+
ext_idx_np = np.arange(N_act, N_act + N_ext)
|
|
168
182
|
|
|
169
183
|
S = s2s(s_bd, z0, 'traveling', 'power')
|
|
170
184
|
active_ports = list(range(S.shape[-1]))
|
|
171
185
|
|
|
172
186
|
all_net_ids = np.unique(net_map_np)
|
|
173
|
-
ext_net_ids = np.unique(
|
|
187
|
+
ext_net_ids = np.unique(topology.ext_net_ids)
|
|
174
188
|
pure_int_net_ids = np.setdiff1d(all_net_ids, ext_net_ids)
|
|
175
189
|
|
|
176
190
|
# Sequentially contract purely internal nets
|
|
@@ -222,39 +236,7 @@ class SequentialScatteringCircuitSolver(AbstractScatteringCircuitSolver):
|
|
|
222
236
|
|
|
223
237
|
S_ext_trav = reduce_to_external(T, X, local_ext_idx, self.eps, self.linear_solver)
|
|
224
238
|
|
|
225
|
-
z0_ext = z0[ext_idx_np]
|
|
226
239
|
return ScatteringResult(s=s2s(S_ext_trav, z0_ext, 'power', 'traveling'), z0=z0_ext)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def inject_virtual_probes(
|
|
230
|
-
s_block_diagonal: jax.Array,
|
|
231
|
-
z0_ports: jax.Array,
|
|
232
|
-
ext_idx: np.ndarray,
|
|
233
|
-
int_idx: np.ndarray,
|
|
234
|
-
net_map: np.ndarray
|
|
235
|
-
) -> Tuple[jax.Array, jax.Array, np.ndarray, np.ndarray, np.ndarray]:
|
|
236
|
-
"""Handles dangling external ports by injecting virtual matched probes."""
|
|
237
|
-
net_counts = np.bincount(net_map)
|
|
238
|
-
ext_net_counts = net_counts[net_map[ext_idx]]
|
|
239
|
-
|
|
240
|
-
dangling_mask = ext_net_counts == 1
|
|
241
|
-
num_dangling = np.sum(dangling_mask)
|
|
242
|
-
|
|
243
|
-
if num_dangling > 0:
|
|
244
|
-
N_orig = s_block_diagonal.shape[-1]
|
|
245
|
-
pad_width = ((0, num_dangling), (0, num_dangling))
|
|
246
|
-
s_block_diagonal = jnp.pad(s_block_diagonal, pad_width, mode='constant')
|
|
247
|
-
|
|
248
|
-
dangling_ext_indices = ext_idx[dangling_mask]
|
|
249
|
-
z0_new = z0_ports[dangling_ext_indices]
|
|
250
|
-
z0_ports = jnp.concatenate([z0_ports, z0_new])
|
|
251
|
-
|
|
252
|
-
new_port_indices = np.arange(N_orig, N_orig + num_dangling)
|
|
253
|
-
net_map = np.concatenate([net_map, net_map[dangling_ext_indices]])
|
|
254
|
-
int_idx = np.concatenate([int_idx, dangling_ext_indices])
|
|
255
|
-
ext_idx[dangling_mask] = new_port_indices
|
|
256
|
-
|
|
257
|
-
return s_block_diagonal, z0_ports, ext_idx, int_idx, net_map
|
|
258
240
|
|
|
259
241
|
|
|
260
242
|
def build_connection_matrix(
|
|
@@ -722,13 +722,14 @@ def a2mna(a: ArrayLike) -> MNAStamp:
|
|
|
722
722
|
else:
|
|
723
723
|
raise ValueError(f"ABCD parameters must be 2D or 3D. Got {a_arr.ndim}D.")
|
|
724
724
|
|
|
725
|
-
def s2mna(s: ArrayLike, z0: ArrayLike) -> MNAStamp:
|
|
725
|
+
def s2mna(s: ArrayLike, z0: ArrayLike, s_def: str = 'power') -> MNAStamp:
|
|
726
726
|
"""
|
|
727
727
|
Convert S-parameters to a Modified Nodal Analysis (MNA) stamp.
|
|
728
728
|
|
|
729
|
-
This formulation maps the incident
|
|
729
|
+
This formulation maps the incident waves as the auxiliary
|
|
730
730
|
variables, allowing raw S-parameters to be stamped directly into
|
|
731
|
-
the MNA system without any matrix inversions.
|
|
731
|
+
the MNA system without any matrix inversions. Natively supports both
|
|
732
|
+
traveling and power wave definitions.
|
|
732
733
|
|
|
733
734
|
Parameters
|
|
734
735
|
----------
|
|
@@ -737,6 +738,9 @@ def s2mna(s: ArrayLike, z0: ArrayLike) -> MNAStamp:
|
|
|
737
738
|
z0 : ArrayLike
|
|
738
739
|
The characteristic impedance. Can be a scalar or an array broadcastable
|
|
739
740
|
to `(nports,)` or `(nfreqs, nports)`.
|
|
741
|
+
s_def : str, optional
|
|
742
|
+
The wave definition used for the S-parameters. Options are 'traveling'
|
|
743
|
+
(pseudo-voltage) or 'power' (Kurokawa). Default is 'traveling'.
|
|
740
744
|
|
|
741
745
|
Returns
|
|
742
746
|
-------
|
|
@@ -748,22 +752,29 @@ def s2mna(s: ArrayLike, z0: ArrayLike) -> MNAStamp:
|
|
|
748
752
|
if s_arr.ndim == 3:
|
|
749
753
|
nfreqs, nports, _ = s_arr.shape
|
|
750
754
|
z0_fixed = fix_z0_shape(z0, nfreqs, nports)
|
|
751
|
-
return jax.vmap(s2mna, in_axes=(0, 0))(s_arr, z0_fixed)
|
|
755
|
+
return jax.vmap(s2mna, in_axes=(0, 0, None))(s_arr, z0_fixed, s_def)
|
|
756
|
+
|
|
752
757
|
elif s_arr.ndim == 2:
|
|
753
758
|
nports = s_arr.shape[0]
|
|
754
759
|
z0_arr = fix_z0_shape(z0, 1, nports)[0]
|
|
755
760
|
|
|
756
|
-
# Calculate Y0 and expand dims to broadcast across the columns of (I - S)
|
|
757
|
-
y0 = 1.0 / z0_arr
|
|
758
|
-
y0 = jnp.expand_dims(y0, axis=-1)
|
|
759
|
-
|
|
760
761
|
I = jnp.eye(nports, dtype=s_arr.dtype)
|
|
761
|
-
|
|
762
|
-
# Universal S-parameter MNA derivation
|
|
763
762
|
Y = jnp.zeros_like(s_arr)
|
|
764
|
-
B = y0 * (I - s_arr)
|
|
765
763
|
C = I
|
|
766
|
-
|
|
764
|
+
|
|
765
|
+
if s_def.lower() == 'traveling':
|
|
766
|
+
pre_B = jnp.expand_dims(1.0 / z0_arr, axis=-1)
|
|
767
|
+
pre_D = 1.0
|
|
768
|
+
|
|
769
|
+
elif s_def.lower() == 'power':
|
|
770
|
+
pre_B = jnp.expand_dims(1.0 / jnp.sqrt(z0_arr), axis=-1)
|
|
771
|
+
pre_D = jnp.expand_dims(jnp.sqrt(z0_arr), axis=-1)
|
|
772
|
+
|
|
773
|
+
else:
|
|
774
|
+
raise ValueError(f"s_def must be 'traveling' or 'power'. Got: '{s_def}'")
|
|
775
|
+
|
|
776
|
+
B = pre_B * (I - s_arr)
|
|
777
|
+
D = -(pre_D * (I + s_arr))
|
|
767
778
|
|
|
768
779
|
return MNAStamp(Y=Y, B=B, C=C, D=D)
|
|
769
780
|
else:
|