Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ PathSim-RF extends the [PathSim](https://github.com/pathsim/pathsim) simulation
|-------|-------------|----------------|
| `RFNetwork` | N-port network from S-parameter data (Touchstone) via vector fitting | `ntwk`, `auto_fit` |
| `NetlistStateSpace` | Conversion of spice netlist into pathsim StateSpace | `netlist`, `output_voltages`, `output_currents` |
| `NetlistCircuit` | Inherited class of SparseLinearDAE, importing spice netlist | `netlist`, `output_voltages`, `output_currents`, `initial_value` |
| `TransmissionLine` | Lossy delay-based transmission line (scattering domain) | `length`, `er`, `attenuation`, `Z0` |
| `RFAmplifier` | Amplifier with optional IP3 nonlinearity | `gain` [dB], `IIP3` [dBm], `P1dB` [dBm], `Z0` |
| `RFMixer` | Ideal frequency converter (time-domain multiplication) | `conversion_gain` [dB], `Z0` |
Expand Down
224 changes: 224 additions & 0 deletions docs/source/examples/sparse_netlist_circuit.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "9949413d",
"metadata": {},
"source": [
"# Sparse Circuit from a Netlist\n",
"\n",
"This example simulates a pulse-driven LC low-pass filter with a resistive load using the `NetlistCircuit` block. [ElecSolver](https://github.com/williampiat3/ElecSolver) parses the SPICE-like netlist; PathSim then integrates the circuit as a sparse linear differential-algebraic equation (DAE).\n",
"\n",
"The block behaves like a state space model of the netlist:\n",
"\n",
"- the **inputs** are the independent sources, named like in the netlist (`V1`, `Bload`, ...)\n",
"- the **outputs** are all node voltages `V(node)` and all element currents `I(name)`\n",
"\n",
"so the circuit is wired to other blocks by name.\n",
"\n",
"**Before running:** select a Python environment with `pathsim`, `pathsim_rf`, `matplotlib`, and the optional dependency `ElecSolver` (`pip install ElecSolver`). If you use local source checkouts, keep their directories on `PYTHONPATH`. Run the cells from top to bottom: inspect the available ports, select the measurements, connect the blocks, and run the simulation."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7f789358",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"from pathsim import Simulation, Connection\n",
"from pathsim.blocks import PulseSource, Amplifier, Scope\n",
"from pathsim.solvers import BDF2\n",
"from pathsim_rf import NetlistCircuit"
]
},
{
"cell_type": "markdown",
"id": "cd74bc6b",
"metadata": {},
"source": [
"## Netlist\n",
"\n",
"The source `V1` drives node `in` through a 0.5 mH series inductor (`Lfilter`). A 50 µF capacitor (`Cfilter`) connects the output node `n1` to ground, node `0`.\n",
"\n",
"The 50 Ω load is represented by a behavioral current source, `Bload`. Its expression `I=V(n1)/50` describes the intended load, but the block does **not** evaluate it: the current is an input supplied by the PathSim feedback connection below.\n",
"\n",
"Likewise, the `PULSE(...)` waveform and `.tran` directive do not control the PathSim run. We set the matching pulse parameters in `PulseSource` and the timestep and duration in the simulation cells."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d28cb83",
"metadata": {},
"outputs": [],
"source": [
"netlist = \"\"\"\n",
"*validation netlist for pathsim\n",
".param FREQ = 9k\n",
".param DUTY = 0.5\n",
".param V_LOW = 0\n",
".param V_HIGH = 1\n",
".param T_PER = {1/FREQ}\n",
".param T_ON = {T_PER * DUTY}\n",
".param T_RISE = 10n\n",
".param T_FALL = 10n\n",
"\n",
"V1 in 0 PULSE({V_LOW} {V_HIGH} 0 {T_RISE} {T_FALL} {T_ON} {T_PER})\n",
"Bload n1 0 I=V(n1)/50\n",
"Lfilter in n1 0.5e-3\n",
"Cfilter n1 0 50e-6\n",
".tran 0.5e-6 40e-3 0 0.00001\n",
".end\n",
"\"\"\""
]
},
{
"cell_type": "markdown",
"id": "2bf01163",
"metadata": {},
"source": [
"## Circuit block\n",
"\n",
"First, create a block with all outputs exposed and print the available port names. Inputs list voltage sources first, then current sources. Outputs list node voltages relative to ground (ground itself is excluded), then element currents. A current is positive from the first node to the second node in that element's netlist declaration.\n",
"\n",
"The default initial state is zero: the capacitor is uncharged and the inductor carries no current. You can set these quantities by name with `initial_value`, for example `{\"V(n1)\": 1.0, \"I(Lfilter)\": 0.5}` (volts and amperes)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "023c710b",
"metadata": {},
"outputs": [],
"source": [
"circuit = NetlistCircuit(netlist)\n",
"\n",
"print(\"inputs :\", circuit.input_names)\n",
"print(\"outputs:\", circuit.output_names)"
]
},
{
"cell_type": "markdown",
"id": "77692372",
"metadata": {},
"source": [
"Now replace the inspection block with one exposing only the two measurements used below: `V(n1)` for the load feedback and plot, and `I(Lfilter)` for the plot. The selection lists take bare node and element names, not the formatted port labels.\n",
"\n",
"This reduces the rows of the sparse output matrices `C` and `D` inherited from `SparseLinearDAE`; it does not remove components or reduce the internal circuit state. Selecting outputs without direct feedthrough can also avoid unnecessary algebraic dependencies, but output selection alone does not guarantee that a feedback loop disappears."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a03c84e",
"metadata": {},
"outputs": [],
"source": [
"circuit = NetlistCircuit(netlist, output_voltages=[\"n1\"], output_currents=[\"Lfilter\"])\n",
"\n",
"print(\"inputs :\", circuit.input_names)\n",
"print(\"outputs:\", circuit.output_names)"
]
},
{
"cell_type": "markdown",
"id": "6c82fba0",
"metadata": {},
"source": [
"## Blocks and connections\n",
"\n",
"The load feedback is implemented outside the circuit: `V(n1)` passes through a gain of `1/50` A/V and returns to the input `Bload`. This implements Ohm's law, drawing current from `n1` to ground just like a 50 Ω resistor.\n",
"\n",
"`PulseSource` drives `V1` with a 0-to-1 V pulse at 9 kHz and 50% duty cycle, matching the netlist. The scope records the source voltage, filtered output voltage, and inductor current. Named circuit ports make these connections independent of their numeric port indices."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7682fbde",
"metadata": {},
"outputs": [],
"source": [
"Src = PulseSource(T=1/9000, amplitude=1, duty=0.5, t_rise=10e-9, t_fall=10e-9)\n",
"Load = Amplifier(1/50)\n",
"Sco = Scope(labels=[\"pulse V1\", \"V(n1)\", \"I(Lfilter)\"])\n",
"\n",
"blocks = [Src, circuit, Load, Sco]\n",
"\n",
"connections = [\n",
" Connection(Src, circuit[\"V1\"], Sco[0]),\n",
" Connection(circuit[\"V(n1)\"], Load, Sco[1]),\n",
" Connection(Load, circuit[\"Bload\"]),\n",
" Connection(circuit[\"I(Lfilter)\"], Sco[2]),\n",
" ]"
]
},
{
"cell_type": "markdown",
"id": "ccf4cd9a",
"metadata": {},
"source": [
"## Simulation\n",
"\n",
"Use the implicit BDF2 solver to integrate the circuit DAE, with an initial timestep of 0.5 µs. The simulation starts from the zero initial state described above; `log=True` enables progress logging. The next cell constructs the simulation; the final cell advances it for 40 ms."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ddf8d3bb",
"metadata": {},
"outputs": [],
"source": [
"Sim = Simulation(blocks, connections, Solver=BDF2, dt=0.5e-6, log=True)"
]
},
{
"cell_type": "markdown",
"id": "20aaee67",
"metadata": {},
"source": [
"## Results\n",
"\n",
"Run the simulation and plot the three recorded signals. `pulse V1` and `V(n1)` are in volts, while `I(Lfilter)` is in amperes, so their plotted magnitudes have different units.\n",
"\n",
"Look for the LC startup oscillation and the smoothing of the input pulses at `V(n1)`. With a 0-to-1 V input and 50% duty cycle, the long-term mean output should be near 0.5 V; zoom in on the end of the trace to inspect the remaining switching ripple."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Sim.run(40e-3)\n",
"Sco.plot(lw=1.5)\n",
"plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.x"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
239 changes: 239 additions & 0 deletions docs/source/examples/transformer_thermal_pathsim.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies = [
"numpy>=1.15",
"scipy>=1.2",
"scikit-rf>=0.30",
"ElecSolver>=2.2.1",

@williampiat3 williampiat3 Oct 29, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@milanofthe ElecSolver>=2.2.1 is a python lightweight sparse linear system builder for electric systems (or anything that can be expressed in a similar circuit form), it only needs numpy and scipy (I removed networkx in the 2.2.0 release to make it minimal).
In case you were worried about adding deps to pathSim, this one does not add extra packages (except itself)

# If a dependency ships native code that can't be installed in Pyodide,
# tag it with `; sys_platform != 'emscripten'` so a normal pip install
# still pulls it eagerly but micropip in the browser skips it. Pair
Expand Down
50 changes: 27 additions & 23 deletions src/pathsim_rf/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
"""
PathSim-RF: RF Engineering toolbox for PathSim

A toolbox providing specialized blocks for RF and microwave engineering
simulations in the PathSim framework.
"""

try:
from ._version import version as __version__
except ImportError:
__version__ = "unknown"

__all__ = ["__version__"]

from .transmission_line import *
from .amplifier import *
from .mixer import *
from .netlist_to_statespace import NetlistStateSpace

try:
from .network import *
except ImportError:
pass
"""
PathSim-RF: RF Engineering toolbox for PathSim

A toolbox providing specialized blocks for RF and microwave engineering
simulations in the PathSim framework.
"""

try:
from ._version import version as __version__
except ImportError:
__version__ = "unknown"

__all__ = ["__version__"]

from .transmission_line import *
from .amplifier import *
from .mixer import *
from .netlist_to_statespace import NetlistStateSpace

try: # test if scikit-rf is present in env (pip install scikit-rf)
from .network import *
except ImportError:
pass
try: # test if Elecsolver is present in env (pip install ElecSolver>=2.2.1)
from .circuit import *
except ImportError:
pass
Loading
Loading