Commit 02e02241 authored by Sean Leavey's avatar Sean Leavey

Merge branch 'release-0.5.1'

parents e611864f 787d30e7
Pipeline #36042 passed with stages
in 3 minutes and 51 seconds
......@@ -10,27 +10,10 @@ re-simulate their contents.
## Documentation
See the [online documentation](https://docs.ligo.org/sean-leavey/zero/).
## Program and library
The simulator tries to replicate LISO's operation: a small signal ac analysis.
The electrical components in a circuit are defined using their nodal
connections, and the program solves a matrix equation to obtain either transfer
functions between nodes/components or noise at a particular node.
Under the hood, components in the circuit generate their own equations in the
circuit matrix, using Kirchoff's voltage and current laws as described in the
[LISO manual](https://wiki.projekt.uni-hannover.de/aei-geo-q/start/software/liso#manual).
Credit goes to Tobin Fricke's [Elektrotickle](https://github.com/tobin/Elektrotickle/)
for providing easy to read source code showing an approach to solving a circuit
problem. This library evolves Elektrotickle to use a more object-oriented
structure, support for current transfer functions, much more comprehensive plotting,
tools to make direct comparisons to LISO, displaying of circuit equations and more.
## Installation
This library requires that Python 3 is installed. It has been tested on version 3.6,
but may work on earlier versions of Python 3. Python 2 is not supported.
If you don't have Python 3 installed, have a look at [this](https://www.python.org/downloads/).
This library requires that Python 3 is installed. It has been tested on 3.5, 3.6 and 3.7,
but may work on earlier versions of Python 3. Python 2 is not supported. You may wish to use
`virtualenv` or `conda` to manage a separate environment with Python 3.
This library contains a `setup.py` file which tells Python how it should be
installed. Installation can be automated using `pip`. Open up a terminal or
......@@ -47,83 +30,9 @@ installed it, run:
pip install git+https://git.ligo.org/sean-leavey/zero.git --upgrade
```
## Basic usage
There is a basic CLI provided by the program. Open up a terminal and type:
```bash
zero --help
```
for a list of available commands. Run `zero [command] --help` for more detailed
help for a particular `[command]`.
### Run LISO files
```bash
zero liso path/to/liso/file
```
`Zero` can parse both LISO input (`.fil`) and LISO output (`.out`) files.
The above command will display the results. Some commands are not yet supported
(see `LISO parsing` below).
#### Parser hints
To force a file to be parsed as either an input or an output file, specify the
`--force-input` or `--force-output` flags.
### Comparing results to LISO
A comparison between `Zero`'s native result and that of LISO can be made
with `zero liso-compare path/to/liso/file`. Note that any operations that
involve running LISO (e.g. `liso-compare`) require the LISO binary to be set
using the `LISO_DIR` environment variable.
LISO can also be run by `Zero` directly, using the `zero liso-external`
command. To allow LISO to plot its own results, instead of plotting the results
in `Zero`, specify the `--liso-plot` flag.
### As a library
`Zero` can also be included as a library within other Python code. For
examples of how to build simulation scripts with Python, see the `examples`
directory.
## Tests
The script in `/tests/runner.py` can be run to automatically test `Zero`.
There are various tests which compare the results of simulations to LISO; these
can be run with `runner.py validation`. To run all tests, call `runner.py` with
the `all` argument.
## Limitations
See the documentation for LISO [input](https://docs.ligo.org/sean-leavey/zero/liso/input.html#known-incompatibilities)
and [output](https://docs.ligo.org/sean-leavey/zero/liso/output.html#known-incompatibilities)
parsing.
### Op-amp library
The op-amp library is implemented in a different format to that of LISO,
primarily for logistical reasons: Python contains a convenient `ConfigParser`
library which can read and write config files similar to Windows `INI` files,
but in a slightly different format to LISO's op-amp library format. The main
difference is that in `ConfigParser` files, repeated terms are not allowed in
the same entry, so LISO's use of multiple "pole" or "zero" entries under an
op-amp are not supported. Instead, the library represents poles and zeros as
single line expressions of comma separated values:
```
[op177]
...
poles = 7.53M 1.78, 1.66M # fitted from measurement
...
```
Furthermore, the library improves on that of LISO's by allowing an
"alias" setting where you can specify other op-amps with the same properties:
```
[tl074]
aliases = tl084
...
```
Finally, the English convention of using "v" to represent voltage instead of "u"
has been used, so `un` and `uc` are instead `vn` and `vc`.
A LISO op-amp library parser may be added at a later date.
## Contributing
Bug reports and feature requests are always welcome, as are contributions to the
code. Please use the project's [issue tracker](https://git.ligo.org/sean-leavey/zero/issues).
Bug reports and feature requests are always welcome, as are code contributions. Please use the
project's [issue tracker](https://git.ligo.org/sean-leavey/zero/issues).
## Future ideas
- Return plot objects to allow user to modify them
......
This diff is collapsed.
This diff is collapsed.
......@@ -7,8 +7,15 @@ Command line interface
|Zero| provides a command line interface to perform some common tasks, mainly focused on the
running, display and comparison of LISO scripts.
.. hint::
Also see the documentation on :ref:`LISO compatibility <liso/index:LISO Compatibility>`.
===========
Subcommands
===========
.. toctree::
:maxdepth: 2
liso
opamp-library
====================
Command line options
......
.. include:: /defs.txt
##########
LISO tools
##########
.. hint::
Also see the documentation on :ref:`LISO compatibility <liso/index:LISO Compatibility>`.
|Zero| can parse LISO input and output files, run them natively or run them via a local
LISO binary and display the results. It can also compare its native results to that of
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.
Verbose output
--------------
By default, the command line utility does not output any text except that which is requested.
Verbosity can be switched on with the ``-v`` flag. Specify ``-vv`` for greater verbosity.
.. note::
The ``-v`` flag must be specified before the ``liso`` subcommand, i.e. ``zero -v liso [FILE]``.
An error will occur if the flag is specified after a subcommand.
Simulating a LISO input script with |Zero|
------------------------------------------
LISO input scripts can be run natively with the ``zero liso`` command. The input file is first
parsed and then built into an :class:`analysis <.BaseAnalysis>` which is then solved.
.. code-block:: bash
$ zero liso /path/to/liso/script.fil
The plotted functions specified in the LISO input file are reproduced in the default |Zero| plot,
including noise sums.
Re-simulating a LISO output file with |Zero|
--------------------------------------------
LISO result files contain a complete description of the simulated circuit, and as such can be
parsed by |Zero| and re-simulated natively.
.. code-block:: bash
$ zero liso /path/to/liso/script.out
Simulating a LISO input script with an external LISO binary
-----------------------------------------------------------
|Zero| can simulate a LISO input script with a locally installed LISO binary using the ``--liso``
flag. |Zero| runs the script with LISO and then parses the output file so that you can take
advantage of its plotting capabilities.
The LISO binary path must be specified with the ``--liso-path`` option. This must point to the exact
binary file, not just its directory, but may be relative to the current directory.
.. code-block:: bash
$ zero liso /path/to/liso/script.fil --liso --liso-path /path/to/liso/fil
An alternative is to set the ``LISO_PATH`` environment variable to point to the LISO binary. Since
LISO anyway requests that users set the ``LISO_DIR`` environment variable, on Unix systems this can
be used to set ``LISO_PATH`` either in the terminal profile (e.g. during the call with e.g.
``export LISO_PATH=$LISO_DIR/fil_static``) or as part of the call:
.. code-block:: bash
$ LISO_PATH=$LISO_DIR/fil_static zero liso /path/to/liso/script.fil --liso
.. warning::
LISO uses a separate op-amp library to |Zero|, and these may differ if modifications have been
made to one but not the other. Take care when comparing results between the two tools.
Comparing a native simulation to LISO
-------------------------------------
As |Zero| can simulate LISO input scripts both natively and using the LISO binary, it can also
overlay the results on one plot, or report the difference between the results textually.
To overlay the results in a plot, specify the ``--compare`` flag. |Zero| will then run the specified
input file itself and with LISO, then it will parse the LISO results and combine them with its own.
The resulting plot then contains each function, with the native results with solid lines and the
LISO results with dashed lines:
.. image:: /_static/liso-compare-tf.svg
A textual representation of the differences can also be displayed by specifying ``--diff``. This
must be provided in addition to ``--compare``. When specified, this prints a table containing
the worst relative and absolute differences between the two solutions, and the frequencies at which
they occur:
.. code-block:: text
╒══════════════════╤═══════════════════════════════╤═══════════════════════════════╕
│ │ Worst difference (absolute) │ Worst difference (relative) │
╞══════════════════╪═══════════════════════════════╪═══════════════════════════════╡
│ nin to op1 (A/V) │ 1.08e-11 (f = 316.23 kHz) │ 9.78e-10 (f = 316.23 kHz) │
├──────────────────┼───────────────────────────────┼───────────────────────────────┤
│ nin to no (V/V) │ 1.04e-08 (f = 79.433 kHz) │ 9.54e-10 (f = 79.433 kHz) │
╘══════════════════╧═══════════════════════════════╧═══════════════════════════════╛
Prescaling
----------
|Zero| can prescale its circuit matrices in the same way that LISO does, to help improve numerical
precision. By default, this behaviour is switched on, but can be disabled with the
``--no-prescale`` flag. This option is only available when a native simulation is being performed.
Saving figures
--------------
Figures can be saved using the ``--save-figure`` option, which must be followed by a file path.
The format of the figure is controlled by the specified file extension. For example, save PNGs, PDFs
and SVGs with ``--save-figure tf.png``, ``--save-figure tf.pdf`` and ``--save-figure tf.svg``,
respectively.
The ``--save-figure`` option can be specified multiple times to save multiple figures, e.g.:
.. code-block:: bash
$ zero liso /path/to/liso/script.fil --save-figure tf.png --save-figure tf.pdf
Command reference
-----------------
.. click:: zero.__main__:liso
:prog: zero liso
:show-nested:
.. include:: /defs.txt
####################
Op-amp library tools
####################
|Zero|'s command line interface can be used to search the :class:`op-amp <.OpAmp>`
library bundled with the project.
Search queries
--------------
Search queries are specified as a set of declarative filters after the ``zero opamp``
command. |Zero| implements an expression parser which allows queries to be
arbitrarily long and complex, e.g.:
.. code-block:: text
$ zero opamp "model != OP27 & ((vnoise <= 2n & vcorner < 10) | (vnoise <= 25n & inoise < 100f & icorner < 100))" --vnoise --vcorner --inoise --icorner
╒═════════╤════════════════════╤════════════╤════════════════════╤════════════╕
│ Model │ vnoise │ vcorner │ inoise │ icorner │
╞═════════╪════════════════════╪════════════╪════════════════════╪════════════╡
│ OPA671 │ 10.000 nV/sqrt(Hz) │ 1.0000 kHz │ 2.0000 fA/sqrt(Hz) │ 2.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ OPA657 │ 4.8000 nV/sqrt(Hz) │ 2.0000 kHz │ 1.3000 fA/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ OP00 │ 0.0000 V/sqrt(Hz) │ 1.0000 Hz │ 0.0000 A/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ PZTFET1 │ 1.0000 nV/sqrt(Hz) │ 1.0000 Hz │ 1.0000 pA/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ OPA2604 │ 10.000 nV/sqrt(Hz) │ 200.00 Hz │ 6.0000 fA/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ PZTFET2 │ 1.0000 nV/sqrt(Hz) │ 1.0000 Hz │ 1.0000 pA/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ LT1028 │ 850.00 pV/sqrt(Hz) │ 3.5000 Hz │ 1.0000 pA/sqrt(Hz) │ 250.00 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ OPA604 │ 10.000 nV/sqrt(Hz) │ 200.00 Hz │ 6.0000 fA/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ PZTFET3 │ 1.0000 nV/sqrt(Hz) │ 1.0000 Hz │ 1.0000 pA/sqrt(Hz) │ 1.0000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ AD706 │ 17.000 nV/sqrt(Hz) │ 3.0000 Hz │ 50.000 fA/sqrt(Hz) │ 10.000 Hz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ AD8628 │ 22.000 nV/sqrt(Hz) │ 1.0000 µHz │ 5.0000 fA/sqrt(Hz) │ 1.0000 µHz │
├─────────┼────────────────────┼────────────┼────────────────────┼────────────┤
│ OPA655 │ 6.0000 nV/sqrt(Hz) │ 5.0000 kHz │ 1.3000 fA/sqrt(Hz) │ 1.0000 Hz │
╘═════════╧════════════════════╧════════════╧════════════════════╧════════════╛
The expression must be defined on one line. Whitespace is ignored. Where values are specified,
such as "1n", these are parsed by :class:`.Quantity`
(see :ref:`Formatting and parsing quantities <format/index:Formatting and parsing quantities>`).
Available parameters
~~~~~~~~~~~~~~~~~~~~
The following op-amp library parameters can be searched:
``model``
Model name, e.g. `OP27`.
``a0``
Open loop gain.
``gbw``
Gain-bandwidth product.
``delay``
Delay.
``vnoise``
Flat voltage noise.
``vcorner``
Voltage noise corner frequency.
``inoise``
Flat current noise.
``icorner``
Current noise corner frequency.
``vmax``
Maximum output voltage.
``imax``
Maximum output current.
``sr``
Slew rate.
Operators
~~~~~~~~~
Expressions can use the following operators:
``=``
Equal.
``!=``
Not equal.
``<``
Less than.
``<=``
Less than or equal.
``>``
Greater than.
``>=``
Greater than or equal.
``&``
Logical AND.
``|``
Logical OR.
Groups
~~~~~~
Parentheses may be used to delimit groups:
.. code-block:: text
(vnoise < 10n & inoise < 10p) | (vnoise < 100n & inoise < 1p)
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.
Command reference
-----------------
.. click:: zero.__main__:opamp
:prog: zero opamp
:show-nested:
......@@ -8,13 +8,25 @@ Suggestions and bug reports
Please submit suggestions using the `issue tracker`_.
Op-amp library additions
------------------------
Please submit op-amp library entries using the `issue tracker`_. Ideally also submit a datasheet
URL and any measured data from which your op-amp parameters have been derived.
Code contributions
------------------
Submission of small bug fixes and features is encouraged. For larger features, please discuss these
first with the author to discuss feasibility and structure.
Code style
----------
==========
Follow `PEP 8`_ where possible.
Documentation style
-------------------
===================
Use `NumPy docstring format`_. Language and grammar should follow `Google style`_.
......
......@@ -38,13 +38,16 @@ Or point it to a file using the :code:`path` parameter:
parser.parse(path="/path/to/liso/script.fil")
Plot the result with :meth:`~.LisoParser.show`:
Get the solution with :meth:`~.LisoParser.solution` and plot and show it with
:meth:`.Solution.plot` and :meth:`.Solution.show`:
.. code:: python
parser.show()
solution = parser.solution()
solution.plot()
solution.show()
.. image:: /_static/liso-input-tf.png
.. image:: /_static/liso-input-tf.svg
You can at any time list the circuit's constituent components:
......
......@@ -7,6 +7,17 @@ LISO compatibility
input and output files. It is also capable of running a locally available LISO binary
and then plotting its results.
.. note::
In order to solve a circuit, |Zero| implicitly calculates transfer functions to all sinks or noise
from all sources, depending on the type of analysis. LISO, however, only outputs the functions
specified as outputs or noise sources in the script. Instead of throwing away this extra data,
|Zero| stores all calculated functions in its :ref:`solution <solution/index:Solutions>`.
In order for the produced plots to be identical to those of LISO, the functions requested in
LISO are set as `default` in the solution such that they are plotted by :meth:`.Solution.plot`.
The other functions, however, are still available to be plotted by calling
:meth:`.Solution.plot_tfs` or :meth:`.Solution.plot_noise` with appropriate arguments.
Parsing LISO files
------------------
......
......@@ -9,8 +9,8 @@ Known incompatibilities
Outputs
~~~~~~~
|Zero| does not support the `deg+` or `deg-` output coordinates. Please use `deg` instead.
It also throws an error when a LISO script's `ioutput` or `uoutput` commands contain only a
|Zero| does not support the ``deg+`` or ``deg-`` output coordinates. Please use ``deg`` instead.
It also throws an error when a LISO script's ``ioutput`` or ``uoutput`` commands contain only a
phase coordinate, e.g.:
.. code-block:: text
......@@ -67,7 +67,7 @@ primarily for logistical reasons: Python contains a convenient :class:`~configpa
library which can read and write config files similar to Windows :code:`INI` files,
but in a slightly different format to LISO's op-amp library format. The main
difference is that in :class:`~configparser.ConfigParser` files, repeated terms are not allowed in
the same entry, so LISO's use of multiple "pole" or "zero" entries under an
the same entry, so LISO's use of multiple ``pole`` or ``zero`` entries under an
op-amp are not supported. Instead, the library represents poles and zeros as
single line expressions of comma separated values:
......@@ -78,8 +78,8 @@ single line expressions of comma separated values:
poles = 7.53M 1.78, 1.66M # fitted from measurement
...
Furthermore, the library improves on that of LISO's by allowing an
"alias" setting where you can specify other op-amps with the same properties:
Furthermore, the library improves on that of LISO's by allowing an ``alias`` setting where you can
specify other op-amps with the same properties:
.. code-block:: text
......@@ -88,10 +88,11 @@ Furthermore, the library improves on that of LISO's by allowing an
aliases = ad711, ad713
...
Finally, the English convention of using "v" to represent voltage instead of "u"
has been used, so :code:`un` and :code:`uc` are instead :code:`vn` and :code:`vc`.
Finally, the parameters ``un``, ``uc`` ``in`` and ``ic`` have been renamed ``vnoise``, ``vcorner``,
``inoise`` and ``icorner``, respectively.
A LISO op-amp library parser may be added at a later date.
Submissions of op-amp parameters to |Zero|'s library are strongly encouraged
(see :ref:`contributing/index:Op-amp library additions`).
LISO Perl commands
~~~~~~~~~~~~~~~~~~
......
......@@ -7,6 +7,16 @@ from zero.format import Quantity
class QuantityParserTestCase(TestCase):
"""Quantity parsing tests"""
def test_invalid(self):
# invalid characters
for test_value in r" !\"€£$%^&\*\(\)\{\}\[\];:'@#~/\?><\\\|¬`":
with self.subTest(msg="Test invalid quantity", quantity=test_value):
self.assertRaisesRegex(ValueError, r"unrecognised quantity", Quantity, test_value)
# invalid strings
self.assertRaisesRegex(ValueError, r"unrecognised quantity", Quantity, "")
self.assertRaisesRegex(ValueError, r"unrecognised quantity", Quantity, "invalid")
def test_float_values(self):
"""Test parsing of float quantities"""
self.assertAlmostEqual(Quantity(1.23), 1.23)
......
......@@ -7,13 +7,14 @@ from tabulate import tabulate
from . import __version__, PROGRAM, DESCRIPTION, set_log_verbosity
from .liso import LisoInputParser, LisoOutputParser, LisoRunner, LisoParserError
from .config import ZeroConfig
from .library import LibraryQueryEngine
CONF = ZeroConfig()
LOGGER = logging.getLogger(__name__)
# Shared arguments:
# https://github.com/pallets/click/issues/108
class State:
"""CLI state"""
MIN_VERBOSITY = logging.WARNING
......@@ -44,6 +45,7 @@ class State:
"""
return self.verbosity <= logging.INFO
def set_verbosity(ctx, _, value):
"""Set stdout verbosity"""
state = ctx.ensure_object(State)
......@@ -67,7 +69,8 @@ def cli():
@option("--diff", is_flag=True, default=False,
help="Show difference between results of comparison.")
@option("--plot/--no-plot", default=True, show_default=True, help="Display results as figure.")
@option("--save-figure", type=File("wb", lazy=False), help="Save image of figure to file.")
@option("--save-figure", type=File("wb", lazy=False), multiple=True,
help="Save image of figure to file. Can be specified multiple times.")
@option("--prescale/--no-prescale", default=True, show_default=True,
help="Prescale matrices to improve numerical precision.")
@option("--print-equations", is_flag=True, help="Print circuit equations.")
......@@ -151,8 +154,95 @@ def liso(ctx, file, liso, liso_path, compare, diff, plot, save_figure, prescale,
figure = solution.plot_noise()
if save_figure:
# there should only be one figure produced in CLI mode
solution.save_figure(figure, save_figure)
for save_path in save_figure:
# NOTE: use figure file's name so that Matplotlib can identify the file type
# appropriately
solution.save_figure(figure, save_path.name)
if plot:
solution.show()
@cli.command()
@argument("query")
@option("--a0", is_flag=True, default=False, help="Show open loop gain.")
@option("--gbw", is_flag=True, default=False, help="Show gain-bandwidth product.")
@option("--vnoise", is_flag=True, default=False, help="Show flat voltage noise.")
@option("--vcorner", is_flag=True, default=False, help="Show voltage noise corner frequency.")
@option("--inoise", is_flag=True, default=False, help="Show flat current noise.")
@option("--icorner", is_flag=True, default=False, help="Show current noise corner frequency.")
@option("--vmax", is_flag=True, default=False, help="Show maximum output voltage.")
@option("--imax", is_flag=True, default=False, help="Show maximum output current.")
@option("--sr", is_flag=True, default=False, help="Show slew rate.")
@pass_context
def opamp(ctx, query, a0, gbw, vnoise, vcorner, inoise, icorner, vmax, imax, sr):
"""Search Zero op-amp library.
Op-amp parameters listed in the library can be searched:
model (model name), a0 (open loop gain), gbw (gain-bandwidth product),
delay, vnoise (flat voltage noise), vcorner (voltage noise corner frequency),
inoise (flat current noise), icorner (current noise corner frequency),
vmax (maximum output voltage), imax (maximum output current), sr (slew rate)
The parser supports basic comparison and logic operators:
= (equal), != (not equal), > (greater than), >= (greater than or equal),
< (less than), <= (less than or equal), & (logic AND), | (logic OR)
Clauses can be grouped together with parentheses:
(vnoise < 10n & inoise < 10p) | (vnoise < 100n & inoise < 1p)
The query engine supports arbitrary expressions.
Example: all op-amps with noise less than 10 nV/sqrt(Hz) and corner frequency
below 10 Hz:
vnoise < 10n & vcorner < 10
"""
engine = LibraryQueryEngine()
# build parameter list
params = []
if a0:
params.append("a0")
if gbw:
params.append("gbw")
if vnoise:
params.append("vnoise")
if vcorner:
params.append("vcorner")
if inoise:
params.append("inoise")
if icorner:
params.append("icorner")
if vmax:
params.append("vmax")
if imax:
params.append("imax")
if sr:
params.append("sr")
# get results