1========= 2Internals 3========= 4 5 6Circuits 7======== 8 9Circuits are represented using netlists of Mnacpts. These are 10wrappers around Oneports. Analysis is performed with modified nodal 11analysis (MNA). 12 13 14Networks 15======== 16 17Networks are comprised of Oneport and Twoport components and are 18stored as an abstract syntax tree (AST). 19 20The main attributes are `Voc` (open-circuit s-domain voltage), `Isc` 21(short-circuit s-domain voltage), `Z` (s-domain impedance), `Y` 22(s-domain admittance). In addition, there is `Vocac` (open-circuit 23phasor voltage) and `Iscac` (short-circuit phasor current). In 24addition, `I` is the component through the one-port (zero by 25definition) and `V` is equivalent to the open-circuit voltage `Voc`. 26 27Formerly all Oneport components were either a Thevenin or Norton 28component. As these components were combined (in series or parallel) 29a new Thevenin or Norton component was created. This was efficient 30and worked well. It was also more robust when converting zero 31impedances to admittances and vice-versa. For example, 1 / (1 / 0) 32should give 0. However, it is tricky to handle superposition of 33multiple independent sources, say AC and DC. So instead, the same 34circuit analysis is performed as for Circuit objects by converting the 35network to a netlist. 36 37 38Values and Expressions 39====================== 40 41Lcapy uses a number of classes to represent a value or expression. 42These classes all inherit from the 'Expr' base class; this is a 43wrapper for a SymPy expression. Unfortunately, SymPy does not provide 44a generic SymPy expression class so 'Expr' stores the SymPy expression 45as its 'expr' attribute. 46 47'cExpr' represents a constant, such as the resistance of a resistor. 48The constant must be real and positive. 49 50'tExpr' represents a time domain expression. This should be real. 51 52'sExpr' represents an s-domain expression. This can be complex. 53 54'omegaExpr' represents an angular frequency domain expression. This 55can be complex. 56 57'fExpr' represents a frequency domain expression. This can be 58complex. 59 60'noiseExpr' represents a noise expression (amplitude spectral 61density). This is real. 62 63'Super' represents a superposition of different domains. 64 65 66Symbols 67------- 68 69Consider that the two expressions: 70 x1 = sym.symbols('x') 71 x2 = sym.symbols('x', real=True) 72 73SymPy regards x1 and x2 as being different since the symbol x is 74defined with different conditions. Thus x1 - x2 does not simplify to 75zero. To overcome this problem, Lcapy maintains a symbol cache and 76tries to replace symbols with their first definition. The downside is 77that this may prevent simplification if the symbol is first defined 78without any conditions. 79 80Lcapy maintains a set of symbols for each circuit plus a set of 81additional symbols defined when creating other objects, such as V 82or C. Symbol names are converted into a canonical format, V1 -> V_1. 83 84SymPy defines symbols Q, C, O, S, I, N, E. It also consider E1 as the 85generalized exponential integral function. Thus sympify(5 * E1) fails. 86 87Assumptions are useful for SymPy to simplify expressions. For 88example, knowing that a symbol is real or real and positive. 89 90 91Assumptions 92=========== 93 94Assumptions are required to simplify expressions and to help with 95inverse Laplace transforms. 96 97There are two types of assumptions. 981. Assumptions used by SymPy, such as real, positive, etc. 992. Assumptions used by Lcapy, such as dc, real, causal, etc. 100 101 102SymPy assumptions 103----------------- 104 105To confuse matters, SymPy has two assumptions mechanisms, old and new. 106The old method attaches attributes to symbols, for example, 107 108from sympy import Symbol, Q, exp, I, pi 109x = Symbol('x', integer=True) 110z = exp(2 * pi * I * x) 111 112The simplify function (or method) uses these attributes. 113 114The new method stores facts, these need not just be about symbols, for 115example, 116 117from sympy import Symbol, Q, exp, I, pi 118from sympy.assumptions.assume import global_assumptions 119 120x = Symbol('x') 121global_assumptions.add(Q.integer(x)) 122z = exp(2 * pi * I * x) 123z = z.refine() 124 125The new method has the advantage that we can collect facts about a 126symbol, say from different nets in a netlist. Since they refer to the 127same symbol, there is no problem updating these facts. The big 128problem is how to deal with context, say if we are analysing two 129circuits at the same time. The simplest approach is to create a 130context for each circuit and to switch the global_assumptions. 131 132A resistor should have a positive resistance, but what about {a - b}. 133We could add an assumption that a - b > 0 but we cannot assume that 134both a and b are positive. Unfortunately, this is the status quo but 135is uncommon. 136 137 138Lcapy assumptions 139----------------- 140 141Lcapy expressions have associated assumptions, ac, dc, and causal. 142These influence how the result of an inverse Laplace transform is 143determined for :math:`t < 0`. 144 145These assumptions are currently not propagated during expression 146manipulation. If so, do we check the assumptions during tests for 147equality? 148 149Rather than propagating assumptions, Lcapy assigns them to expressions 150after circuit analysis. 151 152 153Adding new components 154===================== 155 156# Define in grammar.py 157# Add class in mnacpts.py for simulation 158# Add class in schemcpts.py for drawing 159 160 161Schematic layout 162================ 163 164The current layout algorithm assumes that all one-port components such 165as resistors and diodes are stretchy. The x and y positions of 166component nodes are determined independently using directed acyclic 167graphs. 168 169The steps of the algorithm are: 170 1711. Construct a graph where the edges are the components. Electrical 172 nodes with a common x or y position are combined to reduce the 173 graph size. 174 1752. Find longest path through graph. This determines the maximum 176 dimension. Nodes along this longest path are assigned positions 177 based on the maximum distance from the start. Note, there may be 178 multiple parallel paths of the same length; it does not matter 179 which is chosen. 180 1813. For each component with an unknown position, find the longest path 182 in both forward and backward directions to a node with a known 183 position. This path is traversed counting the number of stretchy 184 components and summing their sizes. Using the distance between the 185 positions of the known nodes the stretch per stretchy component can 186 be calculated and thus the position of the node. If the component 187 has a dangling node the stretch is zero. 188 189 190Expression manipulation 191----------------------- 192 193cos(x).rewrite(exp) -> exp(j*x) / 2 + exp(-j*x)/2 194(exp(j*x) / 2 + exp(-j*x)/2).rewrite(cos) -> cos(x) 195(exp(j*x) / 2 + exp(-j*x)/2).rewrite(sin) -> cos(x) 196