Zero issueshttps://git.ligo.org/sean-leavey/zero/-/issues2022-08-30T08:50:46Zhttps://git.ligo.org/sean-leavey/zero/-/issues/54Subtracting OpAmp - Unclear Documentation/Examples.2022-08-30T08:50:46ZNathan HollandSubtracting OpAmp - Unclear Documentation/Examples.Follows from [this mattermost thread](https://chat.ligo.org/ligo/pl/rwfcox68ytb5ifwju5h8uby9ro).
The documentation/examples for OpAmps are unclear about how to use both ports, for example [this subtracting arrangement](/uploads/3ce315b2...Follows from [this mattermost thread](https://chat.ligo.org/ligo/pl/rwfcox68ytb5ifwju5h8uby9ro).
The documentation/examples for OpAmps are unclear about how to use both ports, for example [this subtracting arrangement](/uploads/3ce315b25ae215abd797d6ba6a0e43e5/non_inverting_test-Schematic_v1p00.pdf). Below is a minimum working example, with some inspiration from the CIT 40m fork of Zero.
```python
import numpy as np
from zero import Circuit
from zero.components import OpAmp
from zero.analysis import AcSignalAnalysis
opa188 = {# Part name.
'model' : 'OPA188',
# Open-loop gain: USE HIGH VOLTAGE - Supply.
'a0' : 10**(136/20),
# Gain bandwidth product.
'gbw' : 2e6,
# Flat voltage noise level - spectral density.
'vnoise' : 8.8e-9,
# Corner for voltage noise, in Hz - extrapolation.
'vcorner' : 65e-3,
# Flat current noise level.
'inoise' : 7e-15,
# Corner for current noise, in Hz - guess.
'icorner' : 65e-3,
# Maximum voltage output.
'vmax' : 15,
# Maximum current output.
'imax' : 16e-3,
# Slew rate
'sr' : 0.8e6}
# Non-inverting amplifier.
non_invert_amp = Circuit()
# Around non-inverting input.
# Input to non-inverting port.
non_invert_amp.add_resistor(name = 'R2', value = 1e3, node1 = 'Vin',
node2 = 'Vp')
# Non-inverting port to ground.
non_invert_amp.add_resistor(name = 'R4', value = 6e3, node1 = 'Vp',
node2 = 'gnd')
non_invert_amp.add_capacitor(name = 'C2', value = 1.5e-6, node1 = 'Vp',
node2 = 'gnd')
# Around inverting input.
# Inverting input to ground.
non_invert_amp.add_resistor(name = 'R1', value = 1e3, node1 = 'gnd',
node2 = 'Vn')
# Inverting input to output.
non_invert_amp.add_resistor(name = 'R3', value = 6e3, node1 = 'Vn',
node2 = 'Vo')
non_invert_amp.add_capacitor(name = 'C1', value = 1.5e-6, node1 = 'Vn',
node2 = 'Vo')
# OpAmp.
non_invert_amp.add_component(OpAmp(name = 'U1', node1 = 'Vp', node2 = 'Vn',
node3 = 'Vo', **opa188))
# Output LPF.
non_invert_amp.add_resistor(name = 'R5', value = 5e3, node1 = 'Vo',
node2 = 'Vout')
non_invert_amp.add_capacitor(name = 'C3', value = 2e-6, node1 = 'Vout',
node2 = 'gnd')
# Analysis.
# Frequency vector.
f = np.geomspace(30e-3, 3e2, num = 401)
# Setup analysis?
analyse_NonInvert = AcSignalAnalysis(circuit = non_invert_amp)
# Do analysis.
tf_NonInvert = analyse_NonInvert.calculate(frequencies = f,
node = 'Vin', input_type = 'voltage')
# Subtracting amplifier.
subtract_amp = Circuit()
# Around non-inverting input.
# Input to non-inverting port.
subtract_amp.add_resistor(name = 'R2', value = 1e3, node1 = 'VinP',
node2 = 'Vp')
# Non-inverting port to ground.
subtract_amp.add_resistor(name = 'R4', value = 6e3, node1 = 'Vp',
node2 = 'gnd')
subtract_amp.add_capacitor(name = 'C2', value = 1.5e-6, node1 = 'Vp',
node2 = 'gnd')
# Around inverting input.
# Inverting input to ground.
subtract_amp.add_resistor(name = 'R1', value = 1e3, node1 = 'VinN',
node2 = 'Vn')
# Inverting input to output.
subtract_amp.add_resistor(name = 'R3', value = 6e3, node1 = 'Vn',
node2 = 'Vo')
subtract_amp.add_capacitor(name = 'C1', value = 1.5e-6, node1 = 'Vn',
node2 = 'Vo')
# OpAmp.
subtract_amp.add_component(OpAmp(name = 'U1', node1 = 'Vp', node2 = 'Vn',
node3 = 'Vo', **opa188))
# Output LPF.
subtract_amp.add_resistor(name = 'R5', value = 5e3, node1 = 'Vo',
node2 = 'Vout')
subtract_amp.add_capacitor(name = 'C3', value = 2e-6, node1 = 'Vout',
node2 = 'gnd')
# Analysis.
# Setup
analyse_Subtractor = AcSignalAnalysis(circuit = subtract_amp)
# Analyse.
tf_Subtractor = analyse_Subtractor.calculate(frequencies = f,
node = 'VinP', input_type = 'voltage')
```
The non-inverting OpAmp `tf_NonInvert.plot_response(sink='Vout').show()`, and subtracting OpAmp `tf_Subtractor.plot_response(sink='Vout').show()` transfer functions should be identical but aren't: `15.5 dB` vs `-1 dB` respectively.https://git.ligo.org/sean-leavey/zero/-/issues/53Can we use a different color map that the matplotlib default in the plot_nois...2021-10-05T08:00:07ZJeffrey KisselCan we use a different color map that the matplotlib default in the plot_noise() and plot_responses() methods of the solution class?Apologies for the green-ness of everything I'm about to say.
When @christopher.wipf uses LISO (and thus zero) to model the LIGO suspension coil drivers in [suspensions/ligo/electronics/models/coildriver](https://git.ligo.org/suspensions...Apologies for the green-ness of everything I'm about to say.
When @christopher.wipf uses LISO (and thus zero) to model the LIGO suspension coil drivers in [suspensions/ligo/electronics/models/coildriver](https://git.ligo.org/suspensions/ligo/electronics/-/tree/master/models/coildriver), some of the noise budget plots, where I assume he's using the plot_noise() method of the Solutions class (using words that I hope are correct from zero's help [plotting documentation](https://docs.ligo.org/sean-leavey/zero/plotting/index.html),
> Plotting in analysis scripts
In scripts, plots are generated by the call to the solution’s plot_responses() or plot_noise() methods. These support many display options, as listed below. The return value from these methods is the Figure, which can be further modified.
> After calling either plot_responses() or plot_noise(), you can show the generated plots with show(). This method is called separately to allow you to show a number of plots simultaneously.
create plots like this:
https://git.ligo.org/christopher.wipf/electronics/-/jobs/1598746/artifacts/file/models/coildriver/omcs_top/D1003116-v2_LISO_SWLP-0_NoiseIcoil.png
The problem is that even *if* one ignores all the sub-dominant noise components, one can't identify what the dominant noise component is because the colormap loops over after ~10 curves or so, the components in orange could be the dominant or the subdominant.
A different colormap might solve this.
- maybe user defined, or you, zero, don't want to be so insistent as to chose one for everyone to solve all problems, or
- maybe, if we want to be smart about it, the plot_noise() methods could only plot the top 10 most dominant terms, and add all of the subdominant terms in quadrature, lumping them in to one subdominant curve, so they don't consume so many legend colors.
- maybe, once the feature request [Issue 49](https://git.ligo.org/sean-leavey/zero/-/issues/49) / Node Graphs milestone gets implemented / hit, then this point will be come moot.
Thanks! And sorry again if I've used all the wrong language to describe my problem and ideas for a solution!https://git.ligo.org/sean-leavey/zero/-/issues/52get_window_title deprecated2021-09-29T01:27:16ZChristopher Wipfget_window_title deprecatedWhen plotting from zero a deprecation warning is emitted:
```
/Users/ccw/Library/Python/3.9/lib/python/site-packages/zero/display.py:651: MatplotlibDeprecationWarning:
The get_window_title function was deprecated in Matplotlib 3.4 and w...When plotting from zero a deprecation warning is emitted:
```
/Users/ccw/Library/Python/3.9/lib/python/site-packages/zero/display.py:651: MatplotlibDeprecationWarning:
The get_window_title function was deprecated in Matplotlib 3.4 and will be removed two minor releases later. Use manager.get_window_title or GUI-specific methods instead.
LOGGER.info("figure created on %s", figure.canvas.get_window_title())
```
Version info:
```
>>> sys.version
'3.9.7 (default, Sep 1 2021, 12:35:15) \n[Clang 12.0.5 (clang-1205.0.22.9)]'
>>> matplotlib.__version__
'3.4.3'
>>> zero.__version__
'0.9.1'
```https://git.ligo.org/sean-leavey/zero/-/issues/51Give warning when using wrong node name in AcSignalAnalysis.calculate()2020-01-29T22:36:19ZAnchal GuptaGive warning when using wrong node name in AcSignalAnalysis.calculate()I discovered that AcSignalAnalysis.calculate() does not give any warning or error if the wrong node name is given as an argument to it. AcNoiseAnalysis, on the other hand, gives an error for the same mistake by the user. We should correc...I discovered that AcSignalAnalysis.calculate() does not give any warning or error if the wrong node name is given as an argument to it. AcNoiseAnalysis, on the other hand, gives an error for the same mistake by the user. We should correct this. Attaching a simple code that tests this case.[zeroNodeWrongNameTest.py](/uploads/71dd61dd38cce35b1fd3e1a22ed4ff38/zeroNodeWrongNameTest.py)Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/50Consider changing nodes to be circuit-specific2020-01-29T22:36:20ZSean LeaveyConsider changing nodes to be circuit-specific`Node`s are currently singletons. This was originally to avoid having to require the user having to call `circuit.get_node()` all the time to reference nodes in order to perform analyses, but in reality it is also possible to specify nod...`Node`s are currently singletons. This was originally to avoid having to require the user having to call `circuit.get_node()` all the time to reference nodes in order to perform analyses, but in reality it is also possible to specify nodes by their string names only in most (all?) parts of the public API, making this singleton property a bit pointless. Furthermore, the singleton property of nodes makes deep copying problematic (see `circuit.__deepcopy__()` added in 65bbad6).
Since this property seems not necessary, it might be worth removing it. Then nodes would be stored in a circuit and not the global state.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/49Allow connecting sub-circuits together to form a larger circuits2021-10-02T01:00:13ZAnchal GuptaAllow connecting sub-circuits together to form a larger circuitsWe should take advantage of the circuit being a class fully by allowing connecting different circuit objects together to form bigger circuits. This will make zero scalable and will make debugging easier.
I think we can define a function ...We should take advantage of the circuit being a class fully by allowing connecting different circuit objects together to form bigger circuits. This will make zero scalable and will make debugging easier.
I think we can define a function like:
```
def combineCircuits(Cir1,Cir2,['cir1n1->cir2n2','cir1n2->cir2n5',.....]):
.
.
.
return combinedCircuit
```
Here, Cir1 and Cir2 are two different circuit objects and the third argument is the mapping of node connections to be made. The combined circuit will have all nodes of Cir1 and all nodes of Cir2 except for those which have been merged with nodes of Cir1.Node graphshttps://git.ligo.org/sean-leavey/zero/-/issues/48Node regeneration required when circuit nodes are manipulated2020-01-28T15:06:09ZAnchal GuptaNode regeneration required when circuit nodes are manipulatedI have attached a notebook example which illustrates this issue.[NodeRegenerationOnCircuitChanges.ipynb](/uploads/e7c5a5fa96d331884c3bfddd0098bc9a/NodeRegenerationOnCircuitChanges.ipynb). Right after someone changes any node of a circuit...I have attached a notebook example which illustrates this issue.[NodeRegenerationOnCircuitChanges.ipynb](/uploads/e7c5a5fa96d331884c3bfddd0098bc9a/NodeRegenerationOnCircuitChanges.ipynb). Right after someone changes any node of a circuit, there are no calls made to _regenerate_node_set() function. I see this is because nodes are set through property setter function and in that scope, it doesn't have knowledge of the circuit object it is part of.
Due to this, right after a node is manipulated, the number of nodes circuit.n_nodes and the list of nodes circuit.nodes is outdated and may have errors in them unless _regenerate_node_set() is called. There are two solutions that I can think of:
1) Ban changing the nodes of the circuit object directly and define the set_node function to ensure _regenerate_node_set is called inside it.
2) Some clever __setitem__ function for the circuit object which works with dictionary-like functionality of circuit as well. I'm not really sure if this is possible, but this will be way more intuitive and will make using circuit['r1'].node2='n3' legal.Node graphshttps://git.ligo.org/sean-leavey/zero/-/issues/47circuit.components broken due to new __repr__ for opamps2019-08-06T07:43:37ZAnchal Guptacircuit.components broken due to new __repr__ for opampsThis is regarding [046001b7](https://git.ligo.org/sean-leavey/zero/commit/046001b737296e5601687e3f5d95bdddd33e3ad0) commit on develop branch.
I think this has broken the representation feature for opamps as you can see in a copy of examp...This is regarding [046001b7](https://git.ligo.org/sean-leavey/zero/commit/046001b737296e5601687e3f5d95bdddd33e3ad0) commit on develop branch.
I think this has broken the representation feature for opamps as you can see in a copy of example2.py attached here.[exampl2-copy.py.ipynb](/uploads/75b01897324ed11c4c0e7c7674bcb8fa/exampl2-copy.py.ipynb)
The error is with using self.poles and self.zeros as boolean object when they are a list. Let me know if this is a bug due to my python environment or you missed something here.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/46Add replace_component, replace_opamp, replace_resistor etc functions2019-08-01T06:48:16ZAnchal GuptaAdd replace_component, replace_opamp, replace_resistor etc functionsI think one of the major benefits of zero over liso is that everything is present as python objects during runtime. This is helpful particularly when someone is designing a circuit and want to be able to change the circuit quickly. Howev...I think one of the major benefits of zero over liso is that everything is present as python objects during runtime. This is helpful particularly when someone is designing a circuit and want to be able to change the circuit quickly. However, for replacing a component, at present we are lacking in having direct simple functions that can do it. Right now, one would need to use remove_component and carefully use add_* functions to add the replaced component. **Suggested new features:**
* We can make functions called replace_opamp, replace_resistor, etc which will do an error check and replace components with the same kind of component only and copying the nodes information.
* We can also have a generic replace_component function which will give an error if apples are changes with oranges but will work as expected when someone says "replace that 'opa827' with 'lt1128'".Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/45Support dB scaling in op-amp library2019-07-21T19:23:18ZSean LeaveySupport dB scaling in op-amp libraryLots of `a0` (open loop gain) parameters are defined in decibels in op-amp datasheets, yet the library requires absolute magnitudes. Support parsing of `dB` scales.
This probably requires an overhaul of the `Quantity` class.Lots of `a0` (open loop gain) parameters are defined in decibels in op-amp datasheets, yet the library requires absolute magnitudes. Support parsing of `dB` scales.
This probably requires an overhaul of the `Quantity` class.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/44Make it easier to create and add noise sums to solutions2019-06-27T20:54:12ZSean LeaveyMake it easier to create and add noise sums to solutionsThis is really clunky - make it nicer:
```python
import numpy as np
from zero import Circuit
from zero.components import Node
from zero.data import MultiNoiseDensity
from zero.analysis import AcNoiseAnalysis
if __name__ == "__main__":
...This is really clunky - make it nicer:
```python
import numpy as np
from zero import Circuit
from zero.components import Node
from zero.data import MultiNoiseDensity
from zero.analysis import AcNoiseAnalysis
if __name__ == "__main__":
# 1000 frequencies between 1 Hz to 1 MHz
frequencies = np.logspace(0, 6, 1000)
# Create circuit object.
circuit = Circuit()
# Add components.
circuit.add_capacitor(value="10u", node1="gnd", node2="n1")
circuit.add_resistor(value="430", node1="n1", node2="nm")
circuit.add_resistor(value="43k", node1="nm", node2="nout")
circuit.add_capacitor(value="47p", node1="nm", node2="nout")
circuit.add_library_opamp(model="LT1124", node1="gnd", node2="nm", node3="nout")
# Solve circuit.
analysis = AcNoiseAnalysis(circuit=circuit)
solution = analysis.calculate(frequencies=frequencies, input_type="voltage", node="n1",
sink="nout")
# Create a total sum noise.
sum_noise = list(solution.filter_noise(sinks=["nout"]).values())[0]
solution.add_noise_sum(MultiNoiseDensity(constituents=sum_noise, sink=Node("nout"),
label="Sum"))
# Plot.
solution.plot_noise(sinks=["nout"])
solution.show()
```
Props to Rana.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/43Allow individual noise or response functions to be plotted on their own2019-06-13T20:27:53ZSean LeaveyAllow individual noise or response functions to be plotted on their ownCurrently `NoiseDensity` and `Response` functions can `draw` their data to an existing `axis` provided by a `Solution`, but they can't just plot themselves on their own. Furthermore, if you provide the `axis` of a figure you made yoursel...Currently `NoiseDensity` and `Response` functions can `draw` their data to an existing `axis` provided by a `Solution`, but they can't just plot themselves on their own. Furthermore, if you provide the `axis` of a figure you made yourself (e.g. with `Solution.noise_figure()`), the function doesn't add axis labels or grids etc.
The functions should be able to access the same plotting tools that `Solution` uses. Maybe split the plotting stuff out of `Solution` and into somewhere else where it can be used by the functions separately.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/42Don't run LISO tests if LISO_ACCESS_TOKEN is not found2019-07-16T07:23:24ZSean LeaveyDon't run LISO tests if LISO_ACCESS_TOKEN is not foundSince LISO is not open source, the project's GitLab CI runner grabs the LISO binary from a private AEI git server using an access token (`LISO_ACCESS_TOKEN`) defined in the private project settings, only accessible to me. This has the si...Since LISO is not open source, the project's GitLab CI runner grabs the LISO binary from a private AEI git server using an access token (`LISO_ACCESS_TOKEN`) defined in the private project settings, only accessible to me. This has the side effect that anyone forking the repository and committing changes will have the validation tests fail out.
Fix this by disabling the validation tests if the token is not found.
(props Anchal Gupta)Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/41Option of not plotting in dB units and plotting as loglog2019-06-17T12:06:34ZAnchal GuptaOption of not plotting in dB units and plotting as loglogI noticed that the response plots are always in dB units. It would be good to have an option between plotting semilogx in db units or in loglog plot directly.I noticed that the response plots are always in dB units. It would be good to have an option between plotting semilogx in db units or in loglog plot directly.https://git.ligo.org/sean-leavey/zero/-/issues/40Differentiate between LISO and Zero when using `zero liso --compare`2019-05-08T17:28:01ZSean LeaveyDifferentiate between LISO and Zero when using `zero liso --compare`Plotted functions have same labels. Include the group in the label.Plotted functions have same labels. Include the group in the label.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/39Support vinput/voutput in addition to uinput/uoutput?2019-02-04T19:23:27ZSebastian SteinlechnerSupport vinput/voutput in addition to uinput/uoutput?Just a quick thought, maybe it would be helpful for the international audience if the parser would understand `vinput`/`voutput` as an alias of `uinput`/`uoutput`?Just a quick thought, maybe it would be helpful for the international audience if the parser would understand `vinput`/`voutput` as an alias of `uinput`/`uoutput`?Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/38Wrong name (=model) in circuit description2019-01-28T20:11:31ZSebastian SteinlechnerWrong name (=model) in circuit descriptionIf I say
```python
p = LisoInputParser()
p.parse("""
op OP1 LT1028 nopp nopm nout
""")
p.circuit
```
I get the result
```
Circuit with 1 component and 3 nodes
1. LT1028(a0=30.000M, gbw=72.000 MHz, delay=7.5000 ns) [in+=nopp, in-=nopm, ...If I say
```python
p = LisoInputParser()
p.parse("""
op OP1 LT1028 nopp nopm nout
""")
p.circuit
```
I get the result
```
Circuit with 1 component and 3 nodes
1. LT1028(a0=30.000M, gbw=72.000 MHz, delay=7.5000 ns) [in+=nopp, in-=nopm, out=nout, model=LT1028]
```
... but would have expected it to say **OP1**(a0=... ). Somehow, the model ends up in the name field.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/37unsupported operand type(s) for /: 'complex' and 'list' if poles or zeros is ...2019-01-28T20:12:14ZSebastian Steinlechnerunsupported operand type(s) for /: 'complex' and 'list' if poles or zeros is a listHave a look at this code:
```from zero.liso import LisoInputParser
p = LisoInputParser()
p.parse("""
r R1 1k gnd nopm
r R2 1k nopm nout
op OP1 AD829 nopp nopm nout
uinput nopp
freq log 10 10M 1000
uoutput nout
""")
sol = p.solution()
sol...Have a look at this code:
```from zero.liso import LisoInputParser
p = LisoInputParser()
p.parse("""
r R1 1k gnd nopm
r R2 1k nopm nout
op OP1 AD829 nopp nopm nout
uinput nopp
freq log 10 10M 1000
uoutput nout
""")
sol = p.solution()
sol.plot()
```
... throws the following error:
```
/usr/local/lib/python3.6/site-packages/zero/config/components.py in gain(self, frequency)
432 * np.exp(-2j * np.pi * self.delay * frequency)
433 * np.prod(1 + 1j * frequency / self.zeros)
--> 434 / np.prod(1 + 1j * frequency / self.poles))
435
436 def inverse_gain(self, *args, **kwargs):
TypeError: unsupported operand type(s) for /: 'complex' and 'list'
```
... but it works when replacing the AD829 with something that does not have two poles/zeros, but just a single one, e.g. OP27.Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/36Test for empty input2018-11-22T20:41:33ZSean LeaveyTest for empty inputTest for empty LISO files / empty parsed string. Right now the error message is "input node(s) must be specified", which is not helpful or indicative of the problem. This is because the parser's circuit builder first adds the input compo...Test for empty LISO files / empty parsed string. Right now the error message is "input node(s) must be specified", which is not helpful or indicative of the problem. This is because the parser's circuit builder first adds the input component, which needs nodes to be defined via `uinput` or `iinput`. The presence of these commands is only checked later, when the circuit is being validated.
Solution might be to store parsed stuff in a dict, and check if this is empty, rather than using class properties - but that's a lot of work.
Example test case:
```python
class InvalidFileTestCase(LisoInputParserTestCase):
"""Voltage output command tests"""
def test_empty_string(self):
"""Test empty file"""
self.parser.parse("")
self.assertRaisesRegex(LisoParserError,
r"no circuit defined",
self.parser.solution)
```Sean LeaveySean Leaveyhttps://git.ligo.org/sean-leavey/zero/-/issues/35Add solution data dump functions2019-01-28T20:11:30ZSean LeaveyAdd solution data dump functionsSave to CSV, tab separated value, etc...
Eventually it'd be nice to have a standard data container to hold results alongside their plotting logic, and allow further manipulations (e.g. +, -, /, *, etc.) with data provenance stored in th...Save to CSV, tab separated value, etc...
Eventually it'd be nice to have a standard data container to hold results alongside their plotting logic, and allow further manipulations (e.g. +, -, /, *, etc.) with data provenance stored in the object.Sean LeaveySean Leavey