Commit aec40581 authored by Sean's avatar Sean

Merge branch 'release/0.8.0'

parents 20ac4f5b c217f559
Pipeline #99968 passed with stages
in 34 minutes and 49 seconds
......@@ -3,7 +3,6 @@
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache"
LISO_GIT_ARCHIVE_URL: "https://gitlab.aei.uni-hannover.de/api/v4/projects/143/repository/archive?private_token=${LISO_ACCESS_TOKEN}"
cache:
paths:
......@@ -30,6 +29,10 @@ stages:
- make test-integration
.template:test:validation: &template-test-validation
only:
variables:
- $LISO_GIT_ARCHIVE_ACCESS_TOKEN
- $LISO_GIT_ARCHIVE_URL
script:
- mkdir liso
- cd liso
......@@ -45,27 +48,6 @@ stages:
- pip install .
- make test-validation
.template:deploy:docs: &template-deploy-docs
script:
- mkdir liso
- cd liso
- wget -O liso.tar.gz ${LISO_GIT_ARCHIVE_URL}
- tar -xvf liso.tar.gz -C . --wildcards liso-linux*/ --strip-components=1
- chmod +x fil_static
- cd ..
- export LISO_DIR=$(pwd)/liso
- export LISO_PATH=$LISO_DIR/fil_static
- python --version
- apt update -qy
- apt install --assume-yes python-pip
- pip install .[dev]
- cd docs
- make html
- mv _build/html ../public
artifacts:
paths:
- public
test unit:latest:
image: python:latest
stage: test
......@@ -112,9 +94,49 @@ test validation:3.7:
<<: *template-test-validation
# Generate the documentation only on creation of new tags.
pages:
deploy pages:
image: python:3.7
stage: deploy
only:
refs:
- tags
variables:
- $LISO_GIT_ARCHIVE_ACCESS_TOKEN
- $LISO_GIT_ARCHIVE_URL
script:
- mkdir liso
- cd liso
- wget -O liso.tar.gz ${LISO_GIT_ARCHIVE_URL}
- tar -xvf liso.tar.gz -C . --wildcards liso-linux*/ --strip-components=1
- chmod +x fil_static
- cd ..
- export LISO_DIR=$(pwd)/liso
- export LISO_PATH=$LISO_DIR/fil_static
- python --version
- apt update -qy
- apt install --assume-yes python-pip
- pip install .[dev]
- cd docs
- make html
- mv _build/html ../public
artifacts:
paths:
- public
# Generate PyPI release only on creation of new tags.
deploy pypi:
image: python:3.7
stage: deploy
only:
- tags
<<: *template-deploy-docs
refs:
- tags
variables:
- $TWINE_USERNAME
- $TWINE_PASSWORD
script:
- python --version
- apt update -qy
- apt install --assume-yes python-pip
- pip install twine
- python setup.py sdist bdist_wheel
- python -m twine upload dist/zero-*
......@@ -10,4 +10,5 @@
"restructuredtext.linter.extraArgs": [
"--max-line-length 100",
],
"restructuredtext.confPath": "${workspaceFolder}/docs",
}
docs-html:
$(MAKE) -C docs html
xdg-open docs/_build/html/index.html
test: test-all
......
......@@ -20,9 +20,15 @@ STATICDIR = _static
# Static files
ZEROSTATICDEPS = $(STATICDIR)/liso-input-node-graph.svg
# Zero Python files.
ZEROPYTHONDEPS = $(STATICDIR)/liso-input-response.svg $(STATICDIR)/solution-combination.svg
# LISO script comparisons.
ZEROLISODEPS = $(STATICDIR)/liso-compare-response.svg
ZEROPYTHONDEPS = $(STATICDIR)/liso-input-response.svg $(STATICDIR)/solution-combination.svg \
$(STATICDIR)/resistor-current-noise.svg
# Native plots with LISO files.
ZEROLISODEPS = $(STATICDIR)/liso-two-noises.svg
# Native/LISO script comparisons.
ZEROLISOCOMPAREDEPS = $(STATICDIR)/liso-compare-response.svg
# CLI example plots.
CLIOPAMPGAINDEP = $(STATICDIR)/cli-opamp-gain.svg
ZEROCLIPLOTDEPS = $(CLIOPAMPGAINDEP)
.PHONY: help
help:
......@@ -64,16 +70,29 @@ apidoc:
sphinx-apidoc -o developers/api -e ../zero
.PHONY: static
static: plots
static: plots cli-plots
.PHONY: plots
plots: $(ZEROSTATICDEPS) $(ZEROPYTHONDEPS) $(ZEROLISODEPS)
plots: $(ZEROSTATICDEPS) $(ZEROPYTHONDEPS) $(ZEROLISODEPS) $(ZEROLISOCOMPAREDEPS)
.PHONY: cli-plots
cli-plots: $(ZEROCLIPLOTDEPS)
# CLI op-amp gain plot.
$(CLIOPAMPGAINDEP):
@echo "Generating $@"
zero library search "gbw > 800M & ((vnoise < 10n & inoise < 10p) | (vnoise < 100n & inoise < 1p)) & model != OP00" --no-plot-gain --save-gain-figure $@ --fstop 1M
# Generate SVG plots of Zero output using Python script.
$(STATICDIR)/%.svg: $(STATICDIR)/zero-python/%.py
@echo "Generating $@ from $<"
python $< $@
# Generate SVG plot of two noise example.
$(STATICDIR)/liso-two-noises.svg:
@echo "Generating $@"
zero liso $(STATICDIR)/liso-two-noises/noise1.fil $(STATICDIR)/liso-two-noises/noise2.fil --no-plot --save-figure $@
# Generate SVG plots comparing Zero to LISO using LISO files.
$(STATICDIR)/%.svg: $(STATICDIR)/liso-compare/%.fil
@echo "Generating $@ from $<"
......
r r1 400k nin n1
r r2 400k n1 n2
r r3 50 n5 n3
r rs 230 n5 n6
r led 48.6 n6 gnd
c c1 20u n1 n3
c c2 10u n2 gnd
op op1 op27 n2 n3 n4
op op2 buf634 n4 n5 n5
freq log .003 300 1000
uinput nin 0
noise n6 sum
noisy all
r r1 400k nin n1
r r2 400k n1 n2
r r3 50 n5 n3
r rs 230 n5 n6
r led 48.6 n6 gnd
c c1 20u n1 n3
c c2 10u n2 gnd
op op1 op27 n2 n3 n4
op op2 buf634 n4 n5 n5
freq log .003 300 1000
uinput nin 0
inputnoise n6 sum
noisy all
......@@ -19,5 +19,5 @@ uoutput nout:db:deg
""")
solution = parser.solution()
plot = solution.plot_responses()
solution.save_figure(plot, sys.argv[1])
plotter = solution.plot_responses()
plotter.save(sys.argv[1])
"""Documentation example. Requires target figure filename as argument."""
import sys
import numpy as np
from zero import Circuit
from zero.analysis import AcNoiseAnalysis
from zero.noise import VoltageNoise
# Create a new noise type.
class ResistorCurrentNoise(VoltageNoise):
"""Resistor current noise source.
This models resistor current noise. See e.g. https://dcc.ligo.org/LIGO-T0900200/public
for more details. This noise depends on resistor composition and on its current. Be
careful when using this noise - it generally does not transfer to different circuits
with identical resistors as it depends on the voltage drop across the resistor.
Parameters
----------
vnoise : :class:`float`
The voltage noise at the specified frequency (V/sqrt(Hz)).
frequency : :class:`float`
The frequency at which the specified voltage noise is defined (Hz).
exponent : :class:`float`
The frequency exponent to use for calculating the frequency response.
"""
def __init__(self, vnoise, frequency=1.0, exponent=0.5, **kwargs):
super().__init__(**kwargs)
self.vnoise = vnoise
self.frequency = frequency
self.exponent = exponent
def noise_voltage(self, frequencies, **kwargs):
return self.vnoise * self.frequency / frequencies ** self.exponent
@property
def label(self):
return f"RE({self.component.name})"
# 1000 frequencies between 0.1 Hz to 10 kHz
frequencies = np.logspace(-1, 4, 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", name="r1")
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")
# Add resistor current noise to r1 with 10 nV/sqrt(Hz) at 1 Hz, with 1/f^2 drop-off.
r1 = circuit["r1"]
r1.add_noise(ResistorCurrentNoise(vnoise=1e-8, frequency=1.0, exponent=0.5))
# Solve circuit.
analysis = AcNoiseAnalysis(circuit=circuit)
solution = analysis.calculate(frequencies=frequencies, input_type="voltage", node="n1",
sink="nout", incoherent_sum=True)
# Plot.
plotter = solution.plot_noise(sink="nout")
plotter.save(sys.argv[1])
......@@ -50,5 +50,5 @@ solutionrf.name = "RF Circuit"
solution = solutionlf.combine(solutionrf)
# Plot.
plot = solution.plot_responses()
solution.save_figure(plot, sys.argv[1])
plotter = solution.plot_responses()
plotter.save(sys.argv[1])
......@@ -32,3 +32,19 @@ You can print the circuit to retrieve a list of its constituents:
Circuits are only useful once you add components. This is achieved using the various ``add_``
methods, such as :meth:`.add_resistor`, :meth:`.add_capacitor`, :meth:`.add_inductor` and
:meth:`.add_opamp`.
====================
Circuit manipulation
====================
Circuits can be modified before and after applying :ref:`analyses <analyses/index:Analyses>`.
Circuit components can be removed with :meth:`.remove_component` or replaced with
:meth:`.replace_component`.
When a component is removed, any connected nodes shared by other components are preserved.
When a component is replaced with another one, its nodes are copied to the new component and the new
component's nodes are overwritten. The components being swapped must be compatible: the number of
nodes in the current and replacement component must be the same, meaning that :ref:`passive
components <components/passive-components:Passive components>` can only be swapped for other passive
components, and :ref:`op-amps <components/op-amps:Op-amps>` can only be swapped for other op-amps.
......@@ -140,30 +140,48 @@ Parentheses may be used to delimit groups:
Display
~~~~~~~
The results are displayed in a table. By default, only the op-amp model names
matching a given query are displayed in the table. To add extra columns,
specify the corresponding flag as part of the call:
``--a0``
Show open loop gain.
``--gbw``
Show gain-bandwidth product.
``--delay``
Show delay.
``--vnoise``
Show flat voltage noise.
``--vcorner``
Show voltage noise corner frequency.
``--inoise``
Show flat current noise.
``--icorner``
Show current noise corner frequency.
``--vmax``
Show maximum output voltage.
``--imax``
Show maximum output current.
``--sr``
Show slew rate.
The results are by default displayed in a table. The rows are sorted based on the order in which the
parameters are defined in the search query, from left to right, with the leftmost parameter being
sorted last. The default sort direction is defined based on the parameter. The sort direction can be
specified explicitly as ``ASC`` (ascending) or ``DESC`` (descending) with the corresponding
``--sort`` parameter:
================== =========== =================
Flag Parameter Default direction
================== =========== =================
``--sort-a0`` ``a0`` descending
``--sort-gbw`` ``gbw`` descending
``--sort-delay`` ``delay`` ascending
``--sort-vnoise`` ``vnoise`` ascending
``--sort-vcorner`` ``vcorner`` ascending
``--sort-inoise`` ``inoise`` ascending
``--sort-icorner`` ``icorner`` ascending
``--sort-vmax`` ``vmax`` descending
``--sort-imax`` ``imax`` descending
``--sort-sr`` ``sr`` ascending
================== =========== =================
Parameters that are not explicitly searched are not ordered.
The display of the results table can be disabled using the ``--no-show-table`` flag. The results
can also be saved into a text file by specifying it with ``--save-data``. The specified file
extension will be used to guess the format to use, e.g. `csv` for comma-separated values or `txt`
for tab-separated values.
Results can also be plotted. The flags ``--plot-voltage-noise``, ``--plot-current-noise`` and
``--plot-gain`` can be used to plot the voltage and current noise or open loop gain of the op-amp,
respectively. Generated plots can also be saved by specifying a filename (or multiple filenames,
if you like) with the ``--save-voltage-noise-figure``, ``--save-current-noise-figure`` and
``--save-gain-figure`` options, respectively. Figures can be saved without being displayed with
``--no-plot-voltage-noise``, ``--no-plot-current-noise`` and ``--no-plot-gain``, respectively.
The following command will produce the plot below.
.. code-block:: bash
$ zero library search "gbw > 800M & ((vnoise < 10n & inoise < 10p) | (vnoise < 100n & inoise < 1p)) & model != OP00" --plot-gain --fstop 1M
.. image:: /_static/cli-opamp-gain.svg
Command reference
-----------------
......
......@@ -14,9 +14,12 @@ LISO by overlaying results in a plot or displaying a table of values.
Script path
-----------
For all calls to ``zero liso``, a script path (``FILE``) must be specified. This can either be a
LISO input or output file (commonly given ``.fil`` and ``.out`` extensions, respectively), and
|Zero| will choose an appropriate parser based on what it finds.
For all calls to ``zero liso``, one or more script paths (``FILE``) must be specified. These can
either be LISO input or output file (commonly given ``.fil`` and ``.out`` extensions, respectively),
and |Zero| will choose an appropriate parser based on what it finds.
When more than one script is specified, they are simulated separately and the results combined. See
:ref:`cli/liso:Simulating multiple input files together` for more information.
Verbose output
--------------
......@@ -108,6 +111,66 @@ they occur:
│ nin to no (V/V) │ 1.04e-08 (f = 79.433 kHz) │ 9.54e-10 (f = 79.433 kHz) │
╘══════════════════╧═══════════════════════════════╧═══════════════════════════════╛
Simulating multiple input files together
----------------------------------------
Multiple input or output files may be specified in the ``zero liso`` call. These are simulated
separately and the results are merged together such that they can be plotted on one graph if
possible. The results can only be combined with the simulations contain the same frequency vectors.
If they do not have the same frequency vectors, an error is displayed and the program exits.
This can be useful for example for simulating similar circuits with different component values on
one graph. Each script is plotted with a different line style and a gradually lighter colour map.
Here is an example that shows the noise at an output node and the same noise referred to the input
on one plot:
.. code-block:: bash
$ zero liso noise1.fil noise2.fil
.. image:: /_static/liso-two-noises.svg
Contents of ``noise1.fil``:
.. code-block:: text
r r1 400k nin n1
r r2 400k n1 n2
r r3 50 n5 n3
r rs 230 n5 n6
r led 48.6 n6 gnd
c c1 20u n1 n3
c c2 10u n2 gnd
op op1 op27 n2 n3 n4
op op2 buf634 n4 n5 n5
freq log .003 300 1000
uinput nin 0
noise n6 sum
noisy all
Contents of ``noise2.fil``:
.. code-block:: text
r r1 400k nin n1
r r2 400k n1 n2
r r3 50 n5 n3
r rs 230 n5 n6
r led 48.6 n6 gnd
c c1 20u n1 n3
c c2 10u n2 gnd
op op1 op27 n2 n3 n4
op op2 buf634 n4 n5 n5
freq log .003 300 1000
uinput nin 0
inputnoise n6 sum
noisy all
Scaling response plots
----------------------
......
......@@ -14,6 +14,7 @@ Components
passive-components
op-amps
noise
What is a 'component'?
----------------------
......@@ -91,14 +92,6 @@ Inductor l l1
Op-amp op op1
========= ====== =======
Component noise sources
-----------------------
Some components directly produce noise at a node they are connected to (:class:`.NodeNoise`).
Others create noise affecting current flow (:class:`.ComponentNoise`). The type and amount
of noise depends on the component; for example, :class:`capacitors <.Capacitor>` do not
produce noise, whereas :class:`resistors <.Resistor>` do (:class:`Johnson noise <.JohnsonNoise>`).
Setting a component's value
---------------------------
......
.. include:: /defs.txt
.. currentmodule:: zero.components
Noise
=====
Some components in |Zero| produce noise, such as resistors (:ref:`components/noise:Johnson noise`)
and op-amps (:ref:`voltage <components/noise:Op-amp voltage noise>` and :ref:`current
<components/noise:Op-amp current noise>` noise). Other components such as :ref:`capacitors
<components/passive-components:Capacitors>` and :ref:`inductors
<components/passive-components:Inductors>` do not produce noise by default, although noise can be
:ref:`added by the user <components/noise:Defining new noise sources>`.
Johnson noise
-------------
`Johnson noise <https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise>`__ is a type of
voltage noise in resistors that arises from thermal agitation of charge carriers. This is a function
of temperature but has no dependence on applied voltage or current.
The default temperature assumed in |Zero| calculations is set in the :ref:`configuration
<configuration/index:Configuration>`.
Op-amp noise
------------
Op-amps produce voltage noise across their input and output nodes, and current noise is present at
their input nodes.
Op-amp voltage noise
~~~~~~~~~~~~~~~~~~~~
Op-amps produce voltage noise across their input and output nodes. The noise is a function of
frequency, usually with a flat component at all frequencies and a component rising towards low
frequencies. The cross-over between these two noise components is typically around 1 to 100 Hz,
though this varies depending on the type of op-amp. BJT-based op-amps typically have the lowest
voltage noise.
Op-amp current noise
~~~~~~~~~~~~~~~~~~~~
Current noise is present at op-amps' inputs. The noise is a function of frequency, usually with a
flat component at all frequencies and a component rising towards low frequencies. The cross-over
between these two noise components is typically around 100 Hz to 1 kHz, though this varies depending
on the type of op-amp. FET-based op-amps typically have the lowest current noise.
Current noise is converted to voltage noise by resistors connected to the op-amp inputs. That means
that in a standard op-amp circuit with a feedback resistor, the current noise scales with the
feedback resistance.
In |Zero|, current noise is considered identical for both input nodes. This is usually a valid
assumption for voltage-feedback op-amps, which are the type that |Zero| models.
Defining new noise sources
--------------------------
New noise sources can be defined in |Zero| and added to components. The noise will then appear in
:ref:`noise analyses <analyses/ac/noise:Small AC noise analysis>`.
Noise sources can be created by subclassing one of the available noise types: :class:`.VoltageNoise`
or :class:`.CurrentNoise`. The implementation must define a ``label`` property and set a method to
call when computing the noise. This method will receive the current frequency vector and it must
return the corresponding noise.
Here is an example of defining a resistor current noise source and using it in a circuit:
.. code-block:: python
import numpy as np
from zero import Circuit
from zero.analysis import AcNoiseAnalysis
from zero.noise import VoltageNoise
# Create a new noise type.
class ResistorCurrentNoise(VoltageNoise):
"""Resistor current noise source.
This models resistor current noise. See e.g. https://dcc.ligo.org/LIGO-T0900200/public
for more details. This noise depends on resistor composition and on its current. Be
careful when using this noise - it generally does not transfer to different circuits
with identical resistors as it depends on the voltage drop across the resistor.
Parameters
----------
vnoise : :class:`float`
The voltage noise at the specified frequency (V/sqrt(Hz)).
frequency : :class:`float`
The frequency at which the specified voltage noise is defined (Hz).
exponent : :class:`float`
The frequency exponent to use for calculating the frequency response.
"""
def __init__(self, vnoise, frequency=1.0, exponent=0.5, **kwargs):
super().__init__(**kwargs)
self.vnoise = vnoise
self.frequency = frequency
self.exponent = exponent
def noise_voltage(self, frequencies, **kwargs):
return self.vnoise * self.frequency / frequencies ** self.exponent
@property
def label(self):
return f"RE({self.component.name})"
# 1000 frequencies between 0.1 Hz to 10 kHz
frequencies = np.logspace(-1, 4, 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", name="r1")
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")
# Add resistor current noise to r1 with 10 nV/sqrt(Hz) at 1 Hz, with 1/f^2 drop-off.
r1 = circuit["r1"]
r1.add_noise(ResistorCurrentNoise(vnoise=1e-8, frequency=1.0, exponent=0.5))
# Solve circuit.
analysis = AcNoiseAnalysis(circuit=circuit)
solution = analysis.calculate(frequencies=frequencies, input_type="voltage", node="n1",
sink="nout", incoherent_sum=True)
# Plot.
solution.plot_noise(sink="nout")
solution.show()
.. image:: /_static/resistor-current-noise.svg