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