{ "cells": [ { "cell_type": "raw", "metadata": {}, "source": [ ".. _circuit::\n", "\n", "|\n", "|\n", "\n", "Download This Notebook: :download:`Circuit.ipynb`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Circuits\n", "## Introduction\n", "\n", "The [Circuit class](../api/circuit.html) represents a circuit of arbitrary topology, consisting of an arbitrary number of N-ports [Networks](../api/network.html) connected together. Like in an electronic circuit simulator, the circuit must have one (or more) `Port` connected to the circuit. The `Circuit` object allows one retrieving the M-ports `Network` (and thus its network parameters: $S$, $Z$, etc.), where M is the number of ports defined. Moreover, the `Circuit` object also allows calculating the scattering matrix $S$ of the entire circuit, that is the \"internal\" scattering matrices for the various intersections in the circuit. The calculation algorithm is based on ref [[1](#ref1)]. \n", "\n", "The figure below illustrates a network with 2 ports, `Network` elements $N_i$ and intersections:\n", "\n", "\n", "\n", "\n", "one must must define the connection list (\"netlist\") of the circuit. This connection list is defined as a List of List of interconnected Tuples `(network, port_number)`:\n", "\n", "```\n", "connexions = [\n", " [(network1, network1_port_nb), (network2, network2_port_nb), (network2, network2_port_nb), ...],\n", " ...\n", "]\n", "```\n", "\n", "For example, the connection list to construct the above circuit could be: \n", "\n", "```\n", "connexions = [\n", " [(port1, 0), (network1, 0), (network4, 0)],\n", " [(network1, 1), (network2, 0), (network5, 0)],\n", " [(network1, 2), (network3, 0)],\n", " [(network2, 1), (network3, 1)],\n", " [(network2, 2), (port2, 0)],\n", " [(network5, 1), (ground1, 0)],\n", " [(network5, 2), (open1, 0)]\n", "]\n", "```\n", "\n", "where we have assumed that `port1`, `port2`, `ground1`, `open1` and all the `network1` to `network5` are scikit-rf [Networks](../api/network.html) objects with same `Frequency`. Networks can have different (real) characteristic impedances: mismatch are taken into account. Convenience methods are provided to create ports and grounded connexions:\n", "\n", "* [Circuit.Port()](../api/generated/skrf.circuit.Circuit.Port.html#skrf.circuit.Circuit.Port)\n", "* [Circuit.Ground()](../api/generated/skrf.circuit.Circuit.Ground.html#skrf.circuit.Circuit.Ground)\n", "* [Circuit.SeriesImpedance()](../api/generated/skrf.circuit.Circuit.SeriesImpedance.html#skrf.circuit.Circuit.SeriesImpedance)\n", "* [Circuit.ShuntAdmittance()](../api/generated/skrf.circuit.Circuit.ShuntAdmittance.html#skrf.circuit.Circuit.ShuntAdmittance)\n", "* [Circuit.Open()](../api/generated/skrf.circuit.Circuit.Open.html#skrf.circuit.Circuit.Open)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Warnings**\n", "\n", "[Circuit](https://scikit-rf.readthedocs.io/en/latest/api/circuit.html) requires distinct `Network` names for each `Network`. So, in case you would like to use multiple times the same `Network`, you have to duplicate this `Network` (for example using the `.copy()` method) and to specify a distinct `.name` property for each copy, like:\n", "```\n", "# Assuming network1 is the Network you would like to duplicate:\n", "network2 = network1.copy()\n", "network1.name = 'My first Network'\n", "network2.name = 'My second network'\n", "```\n", "\n", "In addition, a set `(network, port_number)` should appear only once in the connection list. In case you would like to connect multiple networks to a single ones (like grounding multiple networks), there is two solutions:\n", "* connecting the N networks to a single `Ground` object once like in:\n", "```\n", "# in the connection list:\n", "...\n", " [(network1,1),(network2,1),(network3,1),(gnd,0)]\n", "...\n", "```\n", "* Or create as many distinct `Ground` objects (also with unique `name` properties as mentioned above) as necessary.\n", "```\n", "# in the connection list:\n", "...\n", " [(network1,1), (gnd1,0)],\n", " [(network2,1), (gnd2,0)],\n", " [(network3,1), (gnd3,0)],\n", "...\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once the connection list is defined, the `Circuit` with:\n", "\n", "```\n", "resulting_circuit = rf.circuit.Circuit(connexions)\n", "```\n", "`resulting_circuit` is a [Circuit](../api/circuit.html) object. \n", "\n", "The resulting 2-ports `Network` is obtained with the [Circuit.network](../api/generated/skrf.circuit.Circuit.network.html#skrf.circuit.Circuit.network) parameter:\n", "\n", "```\n", "resulting_network = resulting_circuit.network\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that it is also possible to create manually a circuit of multiple `Network` objects using the [connecting methods](../api/network.html#connecting-networks) of `scikit-rf`. Although the `Circuit` approach to build a multiple `Network` may appear to be more verbose than the 'classic' way for building a circuit, as the circuit complexity increases, in particular when components are connected in parallel, the `Circuit` approach is interesting as it increases the readability of the code. Moreover, `Circuit` circuit topology can be plotted using its `plot_graph` method, which is useful to rapidly control if the circuit is built as expected. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Examples\n", "### Loaded transmission line\n", "Assume that a $50\\Omega$ lossless transmission line is loaded with a $Z_L=75\\Omega$ impedance. \n", "\n", "\n", "\n", "If the transmission line electric length is $\\theta=0$, then one would thus expect the reflection coefficient to be:\n", "\n", "$$\n", "\\rho = s = \\frac{Z_L - Z_0}{Z_L + Z_0} = 0.2\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import skrf as rf\n", "from skrf.circuit import Circuit\n", "\n", "rf.stylely()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Z_0 = 50\n", "Z_L = 75\n", "theta = 0\n", "\n", "# the necessary Frequency description\n", "freq = rf.Frequency(start=1, stop=2, unit='GHz', npoints=3)\n", "\n", "# The combination of a transmission line + a load can be created\n", "# using the convenience delay_load method\n", "# important: all the Network must have the parameter \"name\" defined\n", "tline_media = rf.media.DefinedGammaZ0(freq, z0=Z_0)\n", "delay_load = tline_media.delay_load(rf.tlineFunctions.zl_2_Gamma0(Z_0, Z_L), theta, unit='deg', name='delay_load')\n", "\n", "# the input port of the circuit is defined with the Circuit.Port method\n", "port1 = Circuit.Port(freq, 'port1', z0=Z_0)\n", "\n", "# connection list\n", "cnx = [\n", " [(port1, 0), (delay_load, 0)]\n", "]\n", "# building the circuit\n", "cir = Circuit(cnx)\n", "\n", "# getting the resulting Network from the 'network' parameter:\n", "ntw = cir.network\n", "print(ntw)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# as expected the reflection coefficient is:\n", "print(ntw.s[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is also possible to build the above circuit using a series impedance Network, then shorted:\n", "\n", "\n", "\n", "To do so, one would need to use the `Ground()` method to generate the required `Network` object. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "port1 = Circuit.Port(freq, 'port1', z0=Z_0)\n", "# piece of transmission line and series impedance\n", "trans_line = tline_media.line(theta, unit='deg', name='trans_line')\n", "load = tline_media.resistor(Z_L, name='delay_load')\n", "# ground network (short)\n", "ground = Circuit.Ground(freq, name='ground')\n", "\n", "# connection list\n", "cnx = [\n", " [(port1, 0), (trans_line, 0)],\n", " [(trans_line, 1), (load, 0)],\n", " [(load, 1), (ground, 0)]\n", "]\n", "# building the circuit\n", "cir = Circuit(cnx)\n", "# the result if the same :\n", "print(cir.network.s[0])\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### LC Filter\n", "Here we model a low-pass LC filter, with example values taken from [rf-tools.com](https://rf-tools.com/lc-filter/) :\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "freq = rf.Frequency(start=0.1, stop=10, unit='GHz', npoints=1001)\n", "tl_media = rf.media.DefinedGammaZ0(freq, z0=50, gamma=1j*freq.w/rf.constants.c)\n", "C1 = tl_media.capacitor(3.222e-12, name='C1')\n", "C2 = tl_media.capacitor(82.25e-15, name='C2')\n", "C3 = tl_media.capacitor(3.222e-12, name='C3')\n", "L2 = tl_media.inductor(8.893e-9, name='L2')\n", "RL = tl_media.resistor(50, name='RL')\n", "gnd = Circuit.Ground(freq, name='gnd')\n", "port1 = Circuit.Port(freq, name='port1', z0=50)\n", "port2 = Circuit.Port(freq, name='port2', z0=50)\n", "\n", "cnx = [\n", " [(port1, 0), (C1, 0), (L2, 0), (C2, 0)],\n", " [(L2, 1), (C2, 1), (C3, 0), (port2, 0)],\n", " [(gnd, 0), (C1, 1), (C3, 1)],\n", "]\n", "cir = Circuit(cnx)\n", "ntw = cir.network" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ntw.plot_s_db(m=0, n=0, lw=2, logx=True)\n", "ntw.plot_s_db(m=1, n=0, lw=2, logx=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When building a `Circuit` made of few networks, it can be useful to represent the connection graphically, in order to check for possible errors. This is possible using the [Circuit.plot_graph()](../api/circuit.html#representing-a-circuit) method. Ports are indicated by triangles, Network with squares and interconnections with circles. It is possible to display the network names as well as their associated ports (and characteristic impedances): " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cir.plot_graph(network_labels=True, network_fontsize=15,\n", " port_labels=True, port_fontsize=15,\n", " edge_labels=True, edge_fontsize=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Circuit Reduction\n", "Here we model a slightly more complex band-pass LC filter to demonstrate circuit reduction, with example values taken from [markimicrowave.com](https://markimicrowave.com/technical-resources/tools/lc-filter-design-tool/) :\n", "\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "freq = rf.Frequency(50, 200, 301, 'mhz')\n", "tl_media = rf.media.DefinedGammaZ0(frequency=freq, z0=50)\n", "gnd = Circuit.Ground(frequency=freq,name='ground')\n", "C1 = tl_media.capacitor(6.353e-12, name='C1')\n", "L1 = tl_media.inductor(402.7e-9, name='L1')\n", "C2 = tl_media.capacitor(61.08e-12, name='C2')\n", "L2 = tl_media.inductor(13.63e-9, name='L2')\n", "C3 = tl_media.capacitor(187.8e-12, name='C3')\n", "L3 = tl_media.inductor(41.89e-9, name='L3')\n", "C4 = tl_media.capacitor(6.353e-12, name='C4')\n", "L4 = tl_media.inductor(402.27e-9, name='L4')\n", "Port1 = Circuit.Port(frequency=freq, name='PortIn')\n", "Port2 = Circuit.Port(frequency=freq, name='PortOut')\n", "cnx = [\n", " [(Port1, 0), (C1, 0)],\n", " [(C1, 1), (L1, 0)],\n", " [(L1, 1), (C2, 0), (C3, 0), (C4, 0)],\n", " [(C2, 1), (L2, 0)],\n", " [(C3, 1), (L3, 0)],\n", " [(L2, 1), (L3, 1), (gnd, 0)],\n", " [(C4, 1), (L4, 0)],\n", " [(L4, 1), (Port2, 0)]\n", "]\n", "cir = Circuit(cnx)\n", "ntw = cir.network" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ntw.plot_s_db(m=0, n=0, lw=2)\n", "ntw.plot_s_db(m=1, n=0, lw=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As in the previous example, we can use [Circuit.plot_graph()](../api/circuit.html#representing-a-circuit) method to represent the connection graphically." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cir.plot_graph(network_labels=True, network_fontsize=15,\n", " port_labels=True, port_fontsize=15,\n", " edge_labels=True, edge_fontsize=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When focusing solely on `Ports` and disregarding internal `Networks`, we could utilize [rf.reduce_circuit()](../api/circuit.html#circuit-reduction) to streamline the circuit. This method will automatically series-connect adjacent networks through a `rf.connect()` approach, retaining only `Ports` and connections containing more than 2 `Networks` for circuit analysis." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "reduced_cnx = rf.circuit.reduce_circuit(cnx)\n", "reduced_cir = Circuit(reduced_cnx)\n", "reduced_cir.plot_graph(network_labels=True, network_fontsize=15,\n", " port_labels=True, port_fontsize=15,\n", " edge_labels=True, edge_fontsize=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additionally, optional parameters can influence the behavior of circuit reduction. For instance, `split_ground=True` indicates splitting the shared `Ground` in the circuit:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fully_reduced_cnx = rf.circuit.reduce_circuit(cnx, split_ground=True)\n", "fully_reduced_cir = Circuit(fully_reduced_cnx)\n", "\n", "fully_reduced_cir.plot_graph(network_labels=True, network_fontsize=15,\n", " port_labels=True, port_fontsize=15,\n", " edge_labels=True, edge_fontsize=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, the Circuit constructor offers an option for automatic circuit simplification. Setting `auto_reduce=True` enables the creation of a significantly streamlined `Circuit` object, enhancing efficiency for Port's S-parameter calculations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from timeit import timeit\n", "\n", "import numpy as np\n", "\n", "fully_reduced_ntw = fully_reduced_cir.network\n", "auto_reduced_cir = Circuit(cnx, auto_reduce=True)\n", "auto_reduced_ntw = auto_reduced_cir.network\n", "n_times = 10\n", "print(\"Unreduced Circuit calculate Network in \"\n", " f\"{timeit(lambda: cir.network, number=n_times)/n_times:.4f}s\")\n", "print(\"Auto-reduced Circuit calculate Network in \"\n", " f\"{timeit(lambda: auto_reduced_cir.network, number=n_times)/n_times:.4f}s\")\n", "\n", "print(\"Manually reduced circuit has the same s-parameters as the original circuit: \"\n", " f\"{np.allclose(ntw.s, fully_reduced_ntw.s)}\")\n", "print(\"Auto reduced circuit has the same s-parameters as the original circuit: \"\n", " f\"{np.allclose(ntw.s, auto_reduced_ntw.s)}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## References\n", "\n", "
[1] Hallbjörner, P., 2003. Method for calculating the scattering matrix of arbitrary microwave networks giving both internal and external scattering. Microw. Opt. Technol. Lett. 38, 99–102. https://doi.org/10/d27t7m\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.8.10" } }, "nbformat": 4, "nbformat_minor": 4 }