[1]:
import abc
from cosapp.base import System, Port
class Voltage(Port):
def setup(self):
self.add_variable('V', unit='V')
class Intensity(Port):
def setup(self):
self.add_variable('I', unit='A')
class Dipole(System):
"""Abstract directional dipole model computing
current from end values of electric potential.
"""
def setup(self):
self.add_input(Voltage, 'V_in')
self.add_input(Voltage, 'V_out')
self.add_output(Intensity, 'I')
self.add_outward('deltaV', unit='V')
def compute(self):
self.deltaV = self.V_in.V - self.V_out.V
self.compute_I()
@abc.abstractmethod
def compute_I(self) -> None:
pass
Next, we define the following elements:
Resistors
Diodes
Electric nodes
Source
Ground
[2]:
from __future__ import annotations
from cosapp.base import System
import math
class Resistor(Dipole):
def setup(self, R=1.0):
super().setup()
self.add_inward('R', R, desc="Resistance in Ohms")
def compute_I(self):
self.I.I = self.deltaV / self.R
class Diode(Dipole):
"""Regularized diode model
The current intensity flowing through the diode is calculated based on
$ I = I_s \\exp \\left( \\dfrac{V_{in} - V_{out}}{V_t} - 1 \\right) $
"""
def setup(self):
super().setup()
self.add_inward('Is', 1e-15, desc='Saturation current in Amps')
self.add_inward('Vt', 0.025875, desc='Thermal voltage in Volts')
def compute_I(self):
"""Regularized diode model"""
self.I.I = self.Is * math.exp(self.deltaV / self.Vt - 1.)
class Node(System):
"""Electric node model with `n_in` incoming and `n_out` outgoing currents.
"""
def setup(self, n_in=1, n_out=1):
self.add_property('n_in', max(1, int(n_in)))
self.add_property('n_out', max(1, int(n_out)))
incoming = tuple(
self.add_input(Intensity, f"I_in{i}")
for i in range(self.n_in)
)
outgoing = tuple(
self.add_input(Intensity, f"I_out{i}")
for i in range(self.n_out)
)
self.add_property('incoming_currents', incoming)
self.add_property('outgoing_currents', outgoing)
self.add_inward('V', 1.0, unit='V')
self.add_outward('sum_I_in', 0., unit='A', desc='Sum of all incoming currents')
self.add_outward('sum_I_out', 0., unit='A', desc='Sum of all outgoing currents')
self.add_unknown('V')
self.add_equation('sum_I_in == sum_I_out', name='current balance')
def compute(self):
self.sum_I_in = sum(current.I for current in self.incoming_currents)
self.sum_I_out = sum(current.I for current in self.outgoing_currents)
@classmethod
def make(
cls,
name: str,
parent: System,
incoming: list[Dipole]=[],
outgoing: list[Dipole]=[],
pulling=None,
) -> Node:
"""Factory creating new node within `parent`, with
appropriate connections with incoming and outgoing dipoles.
"""
node = cls(name, n_in=len(incoming), n_out=len(outgoing))
parent.add_child(node, pulling=pulling)
for dipole, current in zip(incoming, node.incoming_currents):
# print(dipole.name, type(dipole), dipole.I)
parent.connect(dipole.I, current)
parent.connect(dipole.V_out, node.inwards, 'V')
for dipole, current in zip(outgoing, node.outgoing_currents):
parent.connect(dipole.I, current)
parent.connect(dipole.V_in, node.inwards, 'V')
return node
class Source(System):
def setup(self, I=0.1):
self.add_inward('I', I, unit='A')
self.add_output(Intensity, 'I_out', {'I': I})
def compute(self):
self.I_out.I = self.I
class Ground(System):
def setup(self, V=0.):
self.add_inward('V', V, unit='V')
self.add_output(Voltage, 'V_out', {'V': V})
def compute(self):
self.V_out.V = self.V
Building the circuit¶
The circuit is built by reusing Node
and Resistor
elements customized thanks to keywords arguments that will be processed in the setup
method of those classes.
[3]:
class Circuit(System):
def setup(self):
R1 = self.add_child(Resistor('R1', R=100.), pulling={'V_out': 'Vg'})
R2 = self.add_child(Resistor('R2', R=10000.))
D1 = self.add_child(Diode('D1'), pulling={'V_out': 'Vg'})
# Define nodes
Node.make('n1',
parent=self,
pulling={'I_in0': 'I_in'},
outgoing=[R1, R2],
)
Node.make('n2',
parent=self,
incoming=[R2],
outgoing=[D1],
)
p = System('model')
p.add_child(Source('source', I=0.1))
p.add_child(Ground('ground', V=0.0))
p.add_child(Circuit('circuit'))
p.connect(p.source.I_out, p.circuit.I_in)
p.connect(p.ground.V_out, p.circuit.Vg)
Solving the problem¶
[4]:
from cosapp.drivers import NonLinearSolver
p.add_driver(NonLinearSolver('solver'))
p.run_drivers()
print(
f"{p.circuit.n1.V = }",
f"{p.circuit.n2.V = }",
f"{p.circuit.R1.I = }",
f"{p.circuit.R2.I = }",
f"{p.circuit.D1.I = }",
sep="\n",
)
print(f"\nSanity check: {p.circuit.R1.I.I + p.circuit.D1.I.I = } ({p.source.I} expected)")
p.circuit.n1.V = 9.908302820430912
p.circuit.n2.V = 0.7385848635220567
p.circuit.R1.I = Intensity: {'I': 0.09908302820430911}
p.circuit.R2.I = Intensity: {'I': 0.0009169717956908855}
p.circuit.D1.I = Intensity: {'I': 0.0009169717956908876}
Sanity check: p.circuit.R1.I.I + p.circuit.D1.I.I = 0.1 (0.1 expected)
Show mathematical problem:
[5]:
p.drivers['solver'].problem
[5]:
Unknowns [2]
circuit.n1_V = 9.908302820430912
circuit.n2_V = 0.7385848635220567
Equations [2]
circuit.n1: current balance := 0.0
circuit.n2: current balance := -2.0599841277224584e-18