Commit 37dbae16 authored by Sean Leavey's avatar Sean Leavey
Browse files

Change library search utility to show all parameters, and allow sorting

parent da92a6e0
......@@ -140,30 +140,28 @@ 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 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.
Command reference
-----------------
......
......@@ -17,6 +17,9 @@ LOGGER = logging.getLogger(__name__)
CONF = ZeroConfig()
LIBRARY = OpAmpLibrary()
# Library search filter order.
LIBRARY_FILTER_CHOICE = click.Choice(("ASC", "DESC"), case_sensitive=False)
# Shared arguments:
# https://github.com/pallets/click/issues/108
......@@ -263,17 +266,19 @@ def library_show(paged):
@library.command("search")
@click.argument("query")
@click.option("--a0", is_flag=True, default=False, help="Show open loop gain.")
@click.option("--gbw", is_flag=True, default=False, help="Show gain-bandwidth product.")
@click.option("--vnoise", is_flag=True, default=False, help="Show flat voltage noise.")
@click.option("--vcorner", is_flag=True, default=False, help="Show voltage noise corner frequency.")
@click.option("--inoise", is_flag=True, default=False, help="Show flat current noise.")
@click.option("--icorner", is_flag=True, default=False, help="Show current noise corner frequency.")
@click.option("--vmax", is_flag=True, default=False, help="Show maximum output voltage.")
@click.option("--imax", is_flag=True, default=False, help="Show maximum output current.")
@click.option("--sr", is_flag=True, default=False, help="Show slew rate.")
@click.option("--sort-a0", type=LIBRARY_FILTER_CHOICE, default="DESC", show_default=True)
@click.option("--sort-gbw", type=LIBRARY_FILTER_CHOICE, default="DESC", show_default=True)
@click.option("--sort-delay", type=LIBRARY_FILTER_CHOICE, default="ASC", show_default=True)
@click.option("--sort-vnoise", type=LIBRARY_FILTER_CHOICE, default="ASC", show_default=True)
@click.option("--sort-vcorner", type=LIBRARY_FILTER_CHOICE, default="ASC", show_default=True)
@click.option("--sort-inoise", type=LIBRARY_FILTER_CHOICE, default="ASC", show_default=True)
@click.option("--sort-icorner", type=LIBRARY_FILTER_CHOICE, default="ASC", show_default=True)
@click.option("--sort-vmax", type=LIBRARY_FILTER_CHOICE, default="DESC", show_default=True)
@click.option("--sort-imax", type=LIBRARY_FILTER_CHOICE, default="DESC", show_default=True)
@click.option("--sort-sr", type=LIBRARY_FILTER_CHOICE, default="ASC", show_default=True)
@click.option("--paged", is_flag=True, default=False, help="Print results with paging.")
def library_search(query, a0, gbw, vnoise, vcorner, inoise, icorner, vmax, imax, sr, paged):
def library_search(query, sort_a0, sort_gbw, sort_delay, sort_vnoise, sort_vcorner, sort_inoise,
sort_icorner, sort_vmax, sort_imax, sort_sr, paged):
"""Search Zero op-amp library.
Op-amp parameters listed in the library can be searched:
......@@ -294,60 +299,38 @@ def library_search(query, a0, gbw, vnoise, vcorner, inoise, icorner, vmax, imax,
The query engine supports arbitrary expressions.
Example: all op-amps with noise less than 10 nV/sqrt(Hz) and corner frequency
below 10 Hz:
The 'a0' parameter can be specified in magnitude or decibels. For decibels, append 'dB' (case
insensitive) to the value.
vnoise < 10n & vcorner < 10
The results are sorted sequentially in the order that each parameter appears in the
search query (left to right). The sort direction (descending or ascending) depends on the type
of parameter. The sort direction for parameter 'X' can be overridden using the corresponding
'--sort-X' flag. Specify 'ASC' for ascending and 'DESC' for descending order.
"""
echo = click.echo_via_pager if paged else click.echo
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
devices = engine.query(query)
sort_order = {"a0": sort_a0 == "DESC", "gbw": sort_gbw == "DESC", "delay": sort_delay == "DESC",
"vnoise": sort_vnoise == "DESC", "vcorner": sort_vcorner == "DESC",
"inoise": sort_inoise == "DESC", "icorner": sort_icorner == "DESC",
"vmax": sort_vmax == "DESC", "imax": sort_imax == "DESC", "sr": sort_sr == "DESC"}
# Get results.
devices = engine.query(query, sort_order=sort_order)
if not devices:
click.echo("No op-amps found", err=True)
sys.exit()
sys.exit(1)
nmodel = len(devices)
if nmodel == 1:
opstr = "op-amp"
else:
opstr = "op-amps"
click.echo(f"{nmodel} {opstr} found:")
header = ["Model"] + params
rows = []
for device in devices:
row = [device.model]
row.extend([str(getattr(device, param)) for param in params])
rows.append(row)
echo(tabulate(rows, header, tablefmt=CONF["format"]["table"]))
rows.append([str(getattr(device, param)) for param in engine.parameters])
table = tabulate(rows, engine.parameters, tablefmt=CONF["format"]["table"])
if paged:
click.echo_via_pager(table)
else:
click.echo(table)
@cli.group()
def config():
......
......@@ -18,8 +18,7 @@ class LibraryQueryParser:
This implements a lexer to identify search terms for the Zero op-amp library, and returns
lambda functions that perform corresponding checks.
"""
# parameter tokens
# Parameter tokens.
parameters = {
"model": "MODEL",
"a0": "OPEN_LOOP_GAIN",
......@@ -83,10 +82,11 @@ class LibraryQueryParser:
t_RPAREN = r'\)'
def __init__(self):
# parsed search filters
# Parsed search filters.
self._filters = None
# create lexer and parser handlers
# Order in which parameters have been queried.
self.parameter_query_order = []
# Create lexer and parser handlers.
self.lexer = lex.lex(module=self)
self.parser = yacc.yacc(module=self)
......@@ -187,8 +187,14 @@ class LibraryQueryParser:
| LESS_THAN_EQUAL'''
t[0] = getattr(operator, self._operators[t[1]])
def p_value_with_unit(self, t):
'value_with_unit : VALUE VALUE'
# Matches a value with a unit.
t[0] = t[1] + t[2]
def p_comparison_expression(self, t):
'expression : PARAMETER comparison_operator VALUE'
'''expression : PARAMETER comparison_operator VALUE
| PARAMETER comparison_operator value_with_unit'''
# parse value
try:
value = Quantity(t[3])
......@@ -199,6 +205,10 @@ class LibraryQueryParser:
parameter = t[1]
comparison = t[2]
if parameter not in self.parameter_query_order:
# This parameter has not been seen yet.
self.parameter_query_order.append(parameter)
# change comparison method if necessary (e.g. text comparison)
comparison = self._get_comparison_method(comparison, parameter)
......@@ -245,13 +255,38 @@ class LibraryQueryEngine:
def __init__(self):
self._parser = LibraryQueryParser()
def query(self, text):
# parse
def query(self, text, sort_order=None):
"""Query the library.
Parameters
----------
text : :class:`str`
The query text.
sort_order : :class:`dict`, optional
The sort order map. If specified, the items in this dictionary are used to determine the
sort order for the returned results. The keys represent the parameter to filter, and
the values represent the order (standard or reverse). The sorting is applied in the
order that the parameter appears in the query text (left to right).
Returns
-------
:class:`list`
The matched op-amps.
"""
expression = self._parser.parse(text)
# run with op-amp set so we can use support for binary operators
return expression(self.opamp_set)
# Run query with op-amp set so we can use support for binary operators.
parts = list(expression(self.opamp_set))
if sort_order is not None:
# Sort the results in the order they were specified (left to right).
for parameter in reversed(self._parser.parameter_query_order):
reverse = sort_order[parameter]
parts = sorted(parts, key=lambda part: getattr(part, parameter), reverse=reverse)
return parts
@property
def opamp_set(self):
return set(LIBRARY.opamps)
@property
def parameters(self):
return self._parser.parameters.keys()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment