Model (pmrf.Model)

class pmrf.Model(*, name: str | None = None, metadata: Any = None)

Bases: Module

Base class for RF models.

Derived from this class to define your own, custom model.

This class should not be instantiated directly. It is created internally in ParamRF when models are built compositionally, or can be inherited from. When inheriting, at least one primary matrix method, such as or pmrf.Model.s(), pmrf.Model.a(), pmrf.Model.y(), pmrf.Model.z(), pmrf.Model.primary_matrix(), or pmrf.Model.build(), must be overridden.

The model is a Equinox Module (an immutable dataclass) and a JAX PyTree. Parameters are declared using standard dataclass field syntax and should be annotated with type pmrf.Param and field specifier pmrf.param(). For more details in parameter definitions, see pmrf.parameters.

Note that this class is not marked as “abstract” since it should be treated more like a mix-in than an ABC class with specific methods to implement.

Usage

  • Define new models by sub-classing the model and adding custom parameters and/or sub-models

  • Construct models by passing parameters and/or submodels to the initializer (like a dataclass).

  • Use pmrf.Model.at and methods such as terminated() and flipped() to create modified versions of your model.

Methods & Properties Summary

Core Methods

Method

Description

s()

Scattering (S) parameter matrix at impedance z0.

a()

ABCD parameter matrix.

z()

Impedance (Z) parameter matrix.

y()

Admittance (Y) parameter matrix.

mna()

Modified Nodal Analysis (MNA) stamp matrices.

primary_matrix()

Return the primary matrix. Can be overridden for dynamic dispatch.

primary_domain

The domain of the primary matrix as a string (e.g. "s", "a").

build()

Build the model. Can be overridden for advanced models.

expand()

Expands the model’s topology. Used for circuit model flattening.

Helper Methods

Method

Description

number_of_ports

Number of ports.

nports

Alias of number_of_ports.

port_tuples

All (m, n) port index pairs.

named_params()

Extracts all named parameters in the model.

Model Transformation

Method

Description

at()

Modify, filter or inspect a value at some path in the model.

flipped()

Return a version of the model with ports flipped.

renumbered()

Return a version of the model with ports renumbered.

terminated()

Return a new model terminated by another (e.g. load).

tied()

Tie certain parameters/sub-models together.

File & Conversion Utilities

Method

Description

to_skrf()

Convert the model at frequencies to an skrf.Network.

export_touchstone()

Export the model response to a Touchstone file.

Examples

A general PiCLC network model:

import jax.numpy as jnp
import pmrf as prf

class PiCLC(prf.Model):
    C1: prf.Param
    L:  prf.Param
    C2: prf.Param

    def a(self, freq: prf.Frequency) -> jnp.ndarray:
        w = freq.w
        Y1, Y2, Y3 = (1j * w * self.C1), (1j * w * self.C2), 1 / (1j * w * self.L)
        return jnp.array([
            [1 + Y2 / Y3,        1 / Y3],
            [Y1 + Y2 + Y1*Y2/Y3, 1 + Y1 / Y3],
        ]).transpose(2, 0, 1)

An RLC model built using cascading and with parameters specified:

import pmrf as prf
from pmrf.models import Resistor, Capacitor, Inductor

class RLC(prf.Model):
    res: Resistor = Resistor(prf.Bounded(9.0, 11.0))
    ind: Inductor = Inductor(prf.Bounded(0.0, 10.0, scale=1e-12))
    cap: Capacitor = Capacitor(prf.Bounded(0.0, 10.0, scale=1e-12))

    def build(self) -> prf.Model:
        return self.res ** self.ind ** self.cap.terminated()
a(frequency: Frequency) Array

ABCD parameter matrix.

If a different parameter type is primary, this converts it to A.

Parameters:

frequency (Frequency) – Frequency grid.

Returns:

ABCD matrix with shape (nf, 2, 2).

Return type:

jnp.ndarray

at(target: Callable[[Self], T] | str | tuple[str, ...] | list[str]) Lens[Self, T]

A functional interface for model manipulation.

This is a wrapper around equinox.tree_at via the jax-optix library.

Pass in a callable, a string parameter name, or a tuple of names that returns the values you would like to retrieve/modify. Then, use methods like .get() and .set() to retrieve values or an updated model.

Note that this method does not work directly on static values, like strings or booleans. To perform replacements on these values, this method can be used in combination with pmrf.replace().

WARNING: All updates made by this method are “surgical”. In order words, values are replaced as-is without any converters or verification applied (a new instance is still returned). Any invariants must therefore be enforced or checked manually. For example, when replacing parameters, ensure to pass in a fully constructed parameter and not a float.

Examples

>>> import pmrf as prf
>>> from pmrf.models import Resistor
>>> model = Resistor(R=50.0, name="res")
>>> # Retrieve a value using the lens with a parameter name
>>> model.at("res.R").get()
50.0
>>> # Return a new model instance with the updated value
>>> updated_model = model.at("res.R").set(100.0)
Returns:

A lens object focused on the root of the current instance.

Return type:

Lens

build() Model

Build the model.

This function can be over-ridden by sub-classes.

It is useful to define advanced models that are built using several sub-models or parameters, as opposed to simpler models built using standard equations.

Return type:

Model

Raises:

NotImplementedError – In the base class; override in derived classes to build a compositional representation.

cascaded(other, **kwargs) Model

Cascade this model with another, returning a new model.

See pmrf.models.composite.interconnected.Cascade.

Return type:

Model

expand() tuple[list[tuple[Model, int]], list[list[tuple[Model, int]]]] | None

Expands this model into its internal graph representation for circuit flattening.

This method is used by graph algorithms (like the solver in Circuit.flattened) to unpack composite models, wrappers, and nested hierarchies into a single flat netlist. This allows global matrix solves to be used, where desired.

Note that expand is automatically implemented if pmrf.Model.build() is overridden and the built model also implements expand. This means that most user-classes do NOT need to manually implement this method, and it is mainly intended for built-in composite models in ParamRF to override e.g. pmrf.models.Cascade or pmrf.models.Renumbered.

Returns:

If the model is a composite or routing container, it returns a tuple of: - port_mapping: A list of length nports mapping each external port index

of this model to an internal (Model, port_index) tuple.

  • internal_connections: A list of sub-nodes (connections) to add to the netlist. Each node is a list of (Model, port_index) tuples.

If the model is a fundamental leaf component, it returns None.

Return type:

tuple or None

Examples

Imagine a custom 2-port model that internally connects an Inductor and Capacitor in series. When asked to expand, it exposes the inner components and their wiring:

>>> def expand(self):
...     # 1. Grab internal components
...     L, C = self.inductor, self.capacitor
...
...     # 2. Map our external ports to the internal components
...     port_mapping = [
...         (L, 0),  # External port 0 maps to Inductor port 0
...         (C, 1)   # External port 1 maps to Capacitor port 1
...     ]
...
...     # 3. Define the internal connections (the netlist)
...     # Connect Inductor port 1 to Capacitor port 0
...     internal_connections = [
...         [(L, 1), (C, 0)]
...     ]
...
...     return port_mapping, internal_connections
export_touchstone(filename: str, frequency: Frequency | Any, sigma: float = 0.0, **skrf_kwargs)

Export the model response to a Touchstone file via scikit-rf.

Parameters:
  • filename (str)

  • frequency (Frequency | skrf.Frequency)

  • sigma (float, default=0.0) – Additive complex noise std for S-parameters.

  • **skrf_kwargs – Forwarded to skrf.Network.write_touchstone().

Returns:

Return value of Network.write_touchstone.

Return type:

Any

flipped(**kwargs) Model

Return a version of the model with ports flipped.

See pmrf.models.composite.transformed.Flipped.

Return type:

Model

map(fn: Callable[[Any], Any], is_target: Callable | None = None) Self

A functional interface for model mapping.

This is a wrapper around jax.tree.map.

To map parameters, pass is_target=prf.is_param.

Examples

>>> import pmrf as prf
>>> from pmrf.models import Resistor, Capacitor
>>> model = Resistor(R=50.0) ** Capacitor(C=1e-12)
>>> # Scale all parameters in the model by a factor of 2
>>> scaled_model = model.map(lambda p: p * 2.0, is_target=prf.is_param)

Returns the mapped model.

mna(frequency: Frequency) MNAStamp

(experimental) Modified Nodal Analysis (MNA) stamp.

Can be overridden in sub-classes.

If the model does not explicitly define an MNA stamp, this automatically delegates to the appropriate conversion utility (s2mna, z2mna, etc.). Explicitly defined Y-matrices are prioritized to maximize matrix sparsity, while other domains fall back to auxiliary variables to guarantee stability.

named_params(full_params: bool = False, free_only: bool = False, namespace_separator: str = '_') dict[str, float | Array | Param]

Returns a named dictionary of parameters in the model.

Parameters and models can be given names upon construction. The naming convention is as follows:

  1. If no names are present in the path of a parameter, its Python path is used.

  2. If there are any named models in the path of a parameter, the path up until the left of that model is collapsed, forming a namespace prefix. If multiple models in the path have names, they are joined using the supplied namespace separator.

  3. If the parameter itself is named, its path to the left is collapsed, either to the root or to the first named model.

This enables you to choose your own naming convention:

  1. For flat parameter names across your entire root model, name all your parameters but none of your models.

  2. For flat model names across your entire root model, name all your leaf models but none of your parameters or composite models.

  3. For fully nested naming, name all of your models and optionally your parameters.

Parameters:
  • full_params (bool, default=True) – Returns the full parameter objects as opposed to their resultant floats/array values.

  • free_only (bool, default=False) – Returns only free parameters.

  • namespace_separator (str) – The separator to use to create a parameter namespace using model names.

Returns:

A dictionary mapping string paths (e.g., ‘.ind.value’) to their corresponding JAX arrays or parameter objects.

Return type:

dict[str, Any]

primary_matrix(freq: Frequency, **kwargs) Array

The primary matrix (e.g. s, a etc.) as a function of frequency.

The primary matrix represents the matrix returned by pmrf.Model.primary_domain, which is either overridden by sub-classes, or is the first proprerty directly overriden out of pmrf.Model.s(), pmrf.Model.a(), pmrf.Model.y(), pmrf.Model.z() (in that order), unless :meth:pmrf.Model.build is overridden, in which case the primary matrix of the built model is returned.

This method can also be overriden itself in order to to dynamically implement one of the matrices as opposed to overriding it explicitly.

If this method is called and self.primary_domain is ‘s’, then ‘z0’ should be passed in kwargs.

Parameters:
  • freq (Frequency) – Frequency grid.

  • kwargs – Key-word arguments forwarded to the primary matrix function, such as z0.

Return type:

jnp.ndarray

Raises:

NotImplementedError – If no primary property is overridden.

renumbered(from_ports: tuple[int], to_ports: tuple[int] = None, **kwargs) Model

Return a version of the model with ports renumbered.

See pmrf.models.composite.transformed.Renumbered.

Parameters:
  • from_ports (tuple[int]) – The original port indices that map to to_ports.

  • to_ports (tuple[int]) – The new port indices.

Return type:

Model

s(frequency: Frequency, z0: Array | ndarray | bool | number | bool | int | float | complex = 50.0) Array

Scattering parameter matrix at port impedance z0.

If a different parameter type (a, z, y) is primary, this converts it to S.

To convert between port impedances, use pmrf.rf.renormalize_s().

Note that, derived classes should use the power wave definition of S-parameters when implementing components using S-parameters. If you have a formulation in terms of another definition (such as traveling waves), simply use pmrf.rf.s2s().

Parameters:

frequency (Frequency) – Frequency grid.

Returns:

S-parameter matrix with shape (nf, n, n).

Return type:

jnp.ndarray

terminated(other: Model = 'short', **kwargs) Model

Terminate this model in another, returning a new model.

See pmrf.models.composite.transformed.Terminated.

Parameters:

other (Model | str, optional) – The model to terminate this one in. Can be literals ‘short’, ‘open’ or any model with half the ports of this one. Defaults to a ‘short’.

Return type:

Model

tied(target: ~typing.Callable[[~typing.Any], ~typing.Any] | str | tuple[str, ...] | list[str], source: ~typing.Callable[[~typing.Any], ~typing.Any] | str | tuple[str, ...] | list[str], tie_fn: ~typing.Callable[[~typing.Any], ~typing.Any] = <function Model.<lambda>>, **kwargs) Model

Tie parameters or sub-models within this model together.

See pmrf.models.composite.wrapped.Tied for more details.

Note that if a model is tied that has already been tied, the target and source location/name refers to the original, untied model.

Examples

>>> import pmrf as prf
>>> from pmrf.models import Resistor, Capacitor
>>>
>>> rc = Resistor(R=50.0, name="res") ** Capacitor(C=1.0e-12, name="cap")
>>>
>>> # Tie the resistor's R to always be 50e12 times the capacitor's C
>>> tied_rc = rc.tied(
...     target="res.R",
...     source="cap.C",
...     tie_fn=lambda c: c * 50e12
... )
>>>
>>> # The optimizer will now only see the Capacitor's C parameter.
>>> # When evaluated, R will automatically track C.
Parameters:
  • target (callable | str | tuple[str, ...] | list[str]) – A callable extracting the parameter to be overwritten (e.g., lambda m: m.resistor.R), or the parameter’s name.

  • source (callable | str | tuple[str, ...] | list[str]) – A callable extracting the parameter to draw the value from (e.g., lambda m: m.capacitor.C), or the parameter’s name.

  • tie_fn (callable, optional) – An optional transformation function applied to the source before injecting it into the target. Defaults to the identity function (lambda x: x).

Return type:

Model

to_skrf(frequency: Frequency | Any, z0: Array | ndarray | bool | number | bool | int | float | complex = 50.0, sigma=0.0, **kwargs) Network

Convert the model at frequencies to an skrf.Network.

The active primary property (self.primary_domain) is used.

Parameters:
  • frequency (pmrf.frequency.Frequency | skrf.Frequency) – Frequency grid.

  • z0 (ArrayLike, default=50.0) – The charactestic impedance.

  • sigma (float, default=0.0) – If nonzero, add complex Gaussian noise with stdev sigma to s.

  • **kwargs – Forwarded to skrf.Network constructor.

Return type:

skrf.Network

y(frequency: Frequency) Array

Admittance (Y) parameter matrix.

If a different parameter type is primary, this converts it to Y.

Parameters:

frequency (Frequency) – Frequency grid.

Returns:

Y matrix with shape (nf, n, n).

Return type:

jnp.ndarray

z(frequency: Frequency) Array

Impedance (Z) parameter matrix.

If a different parameter type is primary, this converts it to Z.

Parameters:

frequency (Frequency) – Frequency grid.

Returns:

Z matrix with shape (nf, n, n).

Return type:

jnp.ndarray

metadata: Any = None

Arbitrary metadata to store alongside the model.

name: str | None = None

A name for the model.

property nports: int

Alias of number_of_ports.

property number_of_ports: int

Number of ports.

Return type:

int

property port_tuples: list[tuple[int, int]]

All (m, n) port index pairs.

Return type:

list[tuple[int, int]]

property primary_domain: str

The primary domain (e.g. "s", "a") as a string.

The primary property is the first overridden among PRIMARY_DOMAINS, unless build is overridden, in which case the primary property of the built model is returned.

Return type:

str

Raises:

NotImplementedError – If no primary property is overridden.