Commit 9fc5cf04 authored by Sean Leavey's avatar Sean Leavey

Merge branch 'develop'

parents a402126f df473d37
Pipeline #40927 passed with stages
in 24 minutes and 6 seconds
......@@ -3,17 +3,23 @@ docs-html:
test: test-all
test-all:
python tests/runner.py all
test-unit:
python tests/runner.py unit
python -m tests run unit
test-integration:
python tests/runner.py integration
python -m tests run integration
test-validation:
python tests/runner.py validation
python -m tests run validation
test-validation-fast:
python -m tests run validation-fast
test-all:
python -m tests run all
test-all-fast:
python -m tests run all-fast
lint:
pylint --rcfile=.pylintrc zero
......
This diff is collapsed.
......@@ -7,24 +7,18 @@ Command line interface
|Zero| provides a command line interface to perform some common tasks:
- :ref:`Run LISO scripts <cli/liso:LISO tools>`
- :ref:`Find, open search the op-amp library <cli/library:Op-amp library tools>`
- :ref:`Edit the user configuration <cli/settings:Settings>`
- :ref:`Find, open search the component library <cli/library:Component library tools>`
- :ref:`Download and display datasheets <cli/datasheets:Datasheets>`
===========
Subcommands
===========
========
Commands
========
.. toctree::
:maxdepth: 2
liso
settings
library
datasheets
====================
Command line options
====================
.. click:: zero.__main__:cli
:prog: zero
:show-nested:
liso
.. include:: /defs.txt
####################
Op-amp library tools
####################
#######################
Component library tools
#######################
|Zero|'s command line interface can be used to search the :class:`op-amp <.OpAmp>`
library bundled with the project.
Listing the user library path
-----------------------------
Listing the user library file path
----------------------------------
The built-in op-amp definitions can be supplemented or overridden by a user-defined
op-amp library. This library is stored in the user's home directory in a location
op-amp library. This library is stored within the user's home directory in a location
that depends on the operating system.
The path to this file can be listed with the command ``zero library path``.
Creating a user library
-----------------------
An empty user library can be created with ``zero library create``.
Opening the user library for editing
------------------------------------
The built-in library can be opened with the command ``zero library open``. If the
file does not yet exist, it is created before opening.
The user library can be opened with the command ``zero library edit``.
Removing the user library
-------------------------
The user library can be removed with ``zero library remove``.
Showing the library
-------------------
The combined contents of the built-in library and any user-defined additions or overrides can be
printed to the screen with ``zero library show``. For large libraries, it is often useful to
specify the ``--paged`` flag to allow the contents to be navigated.
Search queries
--------------
......
.. include:: /defs.txt
########
Settings
########
|Zero|'s command line interface can be used to create, edit, remove and list the settings file.
Listing the user configuration file path
----------------------------------------
The default settings can be supplemented or overridden by a user-defined configuration file. This
file is stored within the user's home directory in a location that depends on the operating system.
The path to this file can be listed with the command ``zero config path``.
Creating a user configuration
-----------------------------
An empty user configuration can be created with ``zero config create``.
Opening the user configuration for editing
------------------------------------------
The user configuration can be opened with the command ``zero config edit``.
Removing the user configuration
-------------------------------
The user library can be removed with ``zero library remove``.
Showing the configuration
-------------------------
The combined contents of the built-in configuration and any user-defined additions or overrides can
be printed to the screen with ``zero config show``. For large configurations, it is often useful to
specify the ``--paged`` flag to allow the contents to be navigated.
Styling plots
-------------
Plots generated with Matplotlib can have their style overridden by specifying commands in the
``plot.matplotlib`` section. For example, to specify the default line thickness, use the following
configuration::
plot:
matplotlib:
lines.linewidth: 3
Refer to `this Matplotlib sample configuration file <https://matplotlib.org/users/customizing.html#a-sample-matplotlibrc-file>`_
for more configuration parameters.
Command reference
-----------------
.. click:: zero.__main__:config
:prog: zero config
:show-nested:
......@@ -7,3 +7,75 @@ Solutions
The :class:`.Solution` class provides a mechanism for storing, displaying and saving
the output of an :ref:`analysis <analyses/index:Analyses>`.
Combining solutions
-------------------
Solutions from different analyses can be combined and plotted together. The method :meth:`.Solution.combine`
takes as an argument another solution, and returns a new solution containing functions from both.
.. warning::
In order to be combined, the solutions must have identical frequency vectors, but *no* identical
functions.
Here is an example using a :ref:`LISO model <liso/index:LISO compatibility>` of an RF summing box
with two inputs and one output:
.. code-block:: python
from zero.liso import LisoInputParser
# create parser
parser = LisoInputParser()
base_circuit = """
l l2 420n nlf nout
c c4 47p nlf nout
c c1 1n nrf gnd
r r1 1k nrf gnd
l l1 600n nrf n_l1_c2
c c2 330p n_l1_c2 n_c2_c3
c c3 33p n_c2_c3 nout
c load 20p nout gnd
freq log 100k 100M 1000
uoutput nout
"""
# parse base circuit
parser.parse(base_circuit)
# set input to low frequency port
parser.parse("uinput nlf 50")
# ground unused input
parser.parse("r nrfsrc 5 nrf gnd")
# calculate solution
solutionlf = parser.solution()
# reset parser state
parser.reset()
# parse base circuit
parser.parse(base_circuit)
# set input to radio frequency port
parser.parse("uinput nrf 50")
# ground unused input
parser.parse("r nlfsrc 5 nlf gnd")
# calculate solution
solutionrf = parser.solution()
# combine solutions
solution = solutionlf.combine(solutionrf)
# plot
solution.plot()
solution.show()
.. image:: /_static/solution-combination.svg
.. hint::
Where solutions containing incompatible results are combined, such as with :ref:`signal <analyses/ac/signal:Small AC signal analysis>`
and :ref:`noise <analyses/ac/noise:Small AC noise analysis>` analyses, the functions are combined
but plotted separately.
......@@ -12,7 +12,8 @@ REQUIREMENTS = [
"tabulate",
"setuptools_scm",
"ply",
"click"
"click",
"PyYAML"
]
# extra dependencies
......@@ -41,8 +42,8 @@ setup(
url="https://git.ligo.org/sean-leavey/zero",
packages=find_packages(),
package_data={
"zero": ["zero.conf.dist", "zero.conf.dist.default",
"library.conf.dist", "library.conf.dist.default"]
"zero.config": ["zero.yaml.dist", "zero.yaml.dist.default",
"components.yaml.dist", "components.yaml.dist.default"]
},
entry_points={
"console_scripts": [
......
"""Test suite runner"""
import os
import sys
import logging
from unittest import TestSuite, TestLoader, TextTestRunner
from click import group, argument, option
from zero import set_log_verbosity
from .validation import LisoTestSuite
# this directory
THIS_DIR = os.path.dirname(os.path.realpath(__file__))
# LISO validation scripts
LISO_SCRIPT_DIR = os.path.join(THIS_DIR, "scripts/liso")
# test loader
LOADER = TestLoader()
# test suites
UNIT_TESTS = LOADER.discover("unit", top_level_dir=THIS_DIR)
INTEGRATION_TESTS = LOADER.discover("integration", top_level_dir=THIS_DIR)
FAST_VALIDATION_TESTS = LisoTestSuite(os.path.join(LISO_SCRIPT_DIR, "fast"))
SLOW_VALIDATION_TESTS = LisoTestSuite(os.path.join(LISO_SCRIPT_DIR, "slow"))
# derived suites
VALIDATION_TESTS = TestSuite((FAST_VALIDATION_TESTS, SLOW_VALIDATION_TESTS))
ALL_FAST_TESTS = TestSuite((UNIT_TESTS, INTEGRATION_TESTS, FAST_VALIDATION_TESTS))
ALL_TESTS = TestSuite((ALL_FAST_TESTS, SLOW_VALIDATION_TESTS))
# test name map
TESTS = {
"unit": UNIT_TESTS,
"integration": INTEGRATION_TESTS,
"validation": VALIDATION_TESTS,
"validation-fast": FAST_VALIDATION_TESTS,
"all": ALL_TESTS,
"all-fast": ALL_FAST_TESTS
}
@group()
def tests():
"""Zero testing facility."""
pass
@tests.command()
@argument("suite_names", nargs=-1)
@option("-v", "--verbose", count=True, default=0,
help="Enable verbose output. Supply extra flag for greater verbosity, i.e. \"-vv\".")
def run(suite_names, verbose):
"""Run test suites."""
if verbose > 2:
verbose = 2
# tune in to zero's logs
logger = logging.getLogger("zero")
# show only warnings with no verbosity, or more if higher
set_log_verbosity(logging.WARNING - 10 * verbose, logger)
# test suite to run
suite = TestSuite([TESTS.get(suite_name) for suite_name in suite_names])
print("Running %i tests" % suite.countTestCases())
run_and_exit(suite, verbosity=verbose)
@tests.command()
def suites():
print(", ".join(TESTS))
def run_and_exit(suite, verbosity=1):
"""Run tests and exit with a status code representing the test result"""
runner = TextTestRunner(verbosity=verbosity)
result = runner.run(suite)
sys.exit(not result.wasSuccessful())
if __name__ == '__main__':
tests()
"""LISO input parser tests"""
import unittest
import tempfile
from zero.liso import LisoInputParser, LisoParserError
class LisoInputParserTestCase(unittest.TestCase):
"""Base test case class for input parser"""
def setUp(self):
......@@ -13,6 +15,99 @@ class LisoInputParserTestCase(unittest.TestCase):
"""Reset input parser"""
self.parser = LisoInputParser()
class InvalidFileTestCase(LisoInputParserTestCase):
"""Voltage output command tests"""
def test_empty_string(self):
"""Test empty file"""
self.parser.parse("")
self.assertRaisesRegex(LisoParserError, "no circuit defined", self.parser.solution)
def test_empty_file(self):
"""Test empty file"""
with tempfile.NamedTemporaryFile(mode="w") as fp:
# write empty line
fp.write("")
# parse
self.parser.parse(path=fp.name)
self.assertRaisesRegex(LisoParserError, "no circuit defined", self.parser.solution)
def test_file_with_blank_line(self):
"""Test file with only a blank line"""
with tempfile.NamedTemporaryFile(mode="w") as fp:
# write empty line
fp.write("\n")
# parse
self.parser.parse(path=fp.name)
self.assertRaisesRegex(LisoParserError, "no circuit defined", self.parser.solution)
class ParserReuseTestCase(LisoInputParserTestCase):
"""Test reusing input parser for the same or different circuits"""
def test_parser_reuse_for_different_circuit(self):
"""Test reusing input parser for different circuits"""
circuit1 = """
r r1 1k n1 n2
op op1 OP00 gnd n2 n3
r r2 2k n2 n3
freq log 1 1k 100
uinput n1
uoutput n3
"""
circuit2 = """
r r1 2k n1 n2
op op1 OP00 gnd n2 n3
r r2 4k n2 n3
freq log 1 2k 100
uinput n1
uoutput n3
"""
# parse first circuit
self.parser.parse(circuit1)
_ = self.parser.solution()
# parse second circuit with same parser, but with reset state
self.parser.reset()
self.parser.parse(circuit2)
sol2a = self.parser.solution()
# parse second circuit using a newly instantiated parser
self.reset()
self.parser.parse(circuit2)
sol2b = self.parser.solution()
self.assertTrue(sol2a.equivalent_to(sol2b))
def test_parser_reuse_for_same_circuit(self):
"""Test reusing input parser for same circuits"""
circuit1a = """
r r1 1k n1 n2
op op1 OP00 gnd n2 n3
r r2 2k n2 n3
"""
circuit1b = """
freq log 1 1k 100
uinput n1
uoutput n3
"""
# parse first and second parts together
self.parser.parse(circuit1a + circuit1b)
sol1a = self.parser.solution()
# parse first and second parts subsequently
self.reset()
self.parser.parse(circuit1a)
self.parser.parse(circuit1b)
sol1b = self.parser.solution()
self.assertTrue(sol1a.equivalent_to(sol1b))
class VoltageOutputTestCase(LisoInputParserTestCase):
"""Voltage output command tests"""
def test_invalid_output_node(self):
......@@ -80,6 +175,7 @@ uoutput no ni allop
# 3 op-amp outputs, one of which is no, plus ni
self.assertEqual(4, self.parser.n_tf_outputs)
class CurrentOutputTestCase(LisoInputParserTestCase):
"""Current output command tests"""
def test_invalid_output_node(self):
......@@ -147,6 +243,7 @@ ioutput load ri1 allop
# 3 op-amp outputs, plus two independent
self.assertEqual(5, self.parser.n_tf_outputs)
class NoiseOutputTestCase(LisoInputParserTestCase):
"""Noise output command tests"""
def test_invalid_noise_output_element(self):
......@@ -241,3 +338,18 @@ noise no rin n2a allop
self.parser.build()
# there should be 2 noise outputs per op-amp, so 6 total, plus one extra; one is duplicate
self.assertEqual(7, self.parser.n_displayed_noise)
def test_cannot_compute_noise_sum_without_noisy_command(self):
self.parser.parse("""
r r1 1k n1 n2
r r2 10k n2 n3
op op1 op00 gnd n2 n3
freq log 1 100 10
uinput n1
noise op1 sum
""")
# noise sum requires noisy command
self.assertRaisesRegex(LisoParserError,
r"noise sum requires noisy components to be defined",
self.parser.solution)
"""LISO output parser tests"""
import unittest
import tempfile
from zero.liso import LisoOutputParser, LisoParserError
class LisoOutputParserTestCase(unittest.TestCase):
"""Base test case class for output parser"""
def setUp(self):
self.reset()
def reset(self):
"""Reset output parser"""
self.parser = LisoOutputParser()
class ParserReuseTestCase(LisoOutputParserTestCase):
"""Test reusing output parser for the same or different circuits"""
CIRCUIT1 = """#
1 9.263522296 68.32570322
10 28.4194282 72.77463838
100 39.53081597 19.99386514
1000 40.08145198 0.9824328213
10000 39.96892382 -10.87320803
100000 34.16115885 -75.92584954