Commit 7747b61b authored by Sean Leavey's avatar Sean Leavey

Revert "Merge branch 'refactor-parser' into 'master'"

This reverts merge request !22
parent af331e05
......@@ -2,7 +2,6 @@
"""Finesse kat-script grammar definition."""
import abc
import operator
from collections import defaultdict
import logging
......@@ -25,61 +24,6 @@ _OPERATOR_SYMBOL_MAP = {
}
class PathClosure(metaclass=abc.ABCMeta):
"""Closure for resolving paths defined in kat-script to model objects.
Instances of this class can be called with a model to return the model object reference.
Parameters
----------
path : str
The kat-script path such as m1.p2.o in the case of a :class:`.ParameterClosure`.
"""
def __init__(self, path):
# Copy the path string explicitly to avoid reference issues.
self.pieces = str(path).split(".")
@abc.abstractmethod
def __call__(self, *args):
raise NotImplementedError
def __repr__(self):
return ".".join(self.pieces)
class ParameterClosure(PathClosure):
def __call__(self, model):
# Starting at the component name, drill down to the parameter field.
param = model.elements[self.pieces[0]]
for attr in self.pieces[1:]:
param = getattr(param, attr)
return param
class ReferenceClosure(PathClosure):
def __init__(self, path):
super().__init__(path.lstrip("&"))
def __call__(self, model):
# Starting at the component name, drill down to the referenced field.
param = model.elements[self.pieces[0]]
for attr in self.pieces[1:]:
param = getattr(param, attr)
try:
# If the param is a :class:`.Parameter`, return its :class:`.ParameterRef`.
param = param.ref
except AttributeError:
pass
return param
def __repr__(self):
return f'&{".".join(self.pieces)}'
class KatYACC(Parser):
# Silence shift/reduce conflict warning.
log = logging.getLogger()
......@@ -169,7 +113,7 @@ class KatYACC(Parser):
@_(
"KEYWORD",
"ID",
"PARAMETER",
"BOOLEAN",
"SINGLE_QUOTED_STRING",
"DOUBLE_QUOTED_STRING",
......@@ -180,19 +124,14 @@ class KatYACC(Parser):
def value(self, p):
return p[0]
@_("PARAMETER")
def value(self, p):
"""Convert a parameter like m1.p2.o to a lambda function that resolves to model.m1.p2.o."""
return ParameterClosure(p[0])
@_("key_value_dict key_value_dict")
def key_value_dict(self, p):
p[0].update(p[1])
return p[0]
@_("ID '=' value")
@_("PARAMETER '=' value")
def key_value_dict(self, p):
return {p.ID: p.value}
return {p.PARAMETER: p.value}
@_("ACTION params", "ACTION LPAREN params RPAREN")
def action(self, p):
......@@ -249,8 +188,26 @@ class KatYACC(Parser):
@_("REFERENCE")
def expr(self, p):
"""Convert a reference like &l1.P to a lambda function that resolves to model.l1.P."""
return ReferenceClosure(p[0])
ref = p[0]
def fn(model, ref):
param = ref.lstrip("&")
words = param.split(".")
for i in range(1, len(words) + 1):
p = ".".join(words[:i])
comp = model.elements[param.split(".")[0]]
for attr in param.split(".")[1:]:
comp = getattr(comp, attr)
try:
return comp.ref
except AttributeError:
return comp
return lambda model: fn(model, ref)
@_("expr", "expr_list COMMA expr")
def expr_list(self, p):
......
......@@ -137,7 +137,6 @@ class KatParameterLEX(Lexer):
KEYWORD,
ACTION,
PARAMETER,
ID,
PLUS,
MINUS,
TIMES,
......@@ -161,7 +160,7 @@ class KatParameterLEX(Lexer):
FLOORDIVIDE = r"\/\/" # Has to be above DIVIDE.
DIVIDE = r"\/"
COMMA = r"\,"
REFERENCE = r"&[a-zA-Z_][a-zA-Z0-9_.]*\b" # Has to be above ID.
REFERENCE = r"&[a-zA-Z_][a-zA-Z0-9_.]*\b"
# Ignored patterns.
ignore = " \t"
......@@ -180,8 +179,8 @@ class KatParameterLEX(Lexer):
self._need_whitespace(exempt=("#", "\n", ")", ",", "+", "-", "*", "/"))
def _post_match_constant(self):
# Characters which may follow a constant.
self._need_whitespace(exempt=("#", "\n", ")", ",", "+", "-", "*", "/"))
# Same as number.
self._post_match_number()
def _post_match_keyword(self):
# Characters which may follow a keyword or constant.
......@@ -203,10 +202,6 @@ class KatParameterLEX(Lexer):
# Characters which may follow a parameter.
self._need_whitespace(exempt=("#", "\n", ")", "="))
def _post_match_id(self):
# Characters which may follow an ID.
self._need_whitespace(exempt=("#", "\n", ")", "="))
def _need_whitespace(self, exempt):
"""Push a whitespace matching state, if appropriate."""
try:
......@@ -339,13 +334,9 @@ class KatParameterLEX(Lexer):
elif t.value in spec.actions:
t.type = "ACTION"
self._post_match_action()
elif "." in t.value:
# This is a reference to another element's property like a pd's node.
else:
t.type = "PARAMETER"
self._post_match_parameter()
else:
t.type = "ID"
self._post_match_id()
LOGGER.debug((self.__class__.__name__, t.type, t.value))
......
......@@ -8,30 +8,95 @@ from finesse.exceptions import KatParserError
from finesse.script.base import BaseKatParser
from finesse.script.spec import KatSpec
from finesse.script.lexer import KatLEX
from finesse.script.grammar import KatYACC, ParameterClosure, ReferenceClosure
from finesse.script.grammar import KatYACC
LOGGER = logging.getLogger(__name__)
def _resolve(model, args, kwargs):
"""Resolve parameters and lambda functions contained in an instruction's args and kwargs.
This parameters parameters like "l1.p1" to the corresponding model object, and lambda functions
built from expressions to parameter references.
"""
_resolve_parameters(model, args, kwargs)
_resolve_references(model, args, kwargs)
def _resolve_parameters(model, args, kwargs):
"""Resolve instruction parameters like m1.p2.o by executing closures built by the parser."""
for index, value in enumerate(args):
if isinstance(value, tuple):
# This is an action; value is (action_name, (args, kwargs)). Resolve nested args/kwargs.
_resolve_parameters(model, *value[1])
"""Convert parameter strings like "l1.p1" to references to Python objects.
This is called recursively in the case of actions, which can contain nested actions with their
own parameters.
"""
for i, v in enumerate(args):
if isinstance(v, tuple):
# This is an action; v is (action_name, (args, kwargs)).
_resolve_parameters(model, *v[1])
return
if not isinstance(v, str) or "." not in v:
continue
port = model.elements[v.split(".")[0]]
for attr in v.split(".")[1:]:
port = getattr(port, attr)
args[i] = port
for k, v in kwargs.items():
if isinstance(v, tuple):
# This is an action; v is (action_name, (args, kwargs)).
_resolve_parameters(model, *v[1])
return
if not isinstance(v, str) or "." not in v:
continue
port = model.elements[v.split(".")[0]]
for attr in v.split(".")[1:]:
port = getattr(port, attr)
kwargs[k] = port
def _resolve_references(model, args, kwargs):
"""Convert reference strings like "&l1.p1" to Finesse parameters.
This is called recursively in the case of actions, which can contain nested actions with their
own parameters.
"""
for i, v in enumerate(args):
if isinstance(v, tuple):
# This is an action; v is (action_name, (args, kwargs)).
_resolve_references(model, *v[1])
return
if isinstance(value, ParameterClosure):
args[index] = value(model)
if not callable(v):
continue
args[i] = v(model)
try:
args[i] = args[i].parameter
except AttributeError:
pass
for key, value in kwargs.items():
if isinstance(value, tuple):
# This is an action; value is (action_name, (args, kwargs)). Resolve nested args/kwargs.
_resolve_parameters(model, *value[1])
for k, v in kwargs.items():
if isinstance(v, tuple):
# This is an action; v is (action_name, (args, kwargs)).
_resolve_references(model, *v[1])
return
if isinstance(value, ParameterClosure):
kwargs[key] = value(model)
if not callable(v):
continue
kwargs[k] = v(model)
try:
kwargs[k] = kwargs[k].parameter
except AttributeError:
pass
class KatParser(BaseKatParser):
......@@ -97,9 +162,7 @@ class KatParser(BaseKatParser):
available_namespaces = list(KatSpec().namespaces)
order_func = lambda namespace: available_namespaces.index(namespace)
# Loop over instructions using the order of the parser map to define priority. This priority
# ensures that components like mirrors get added ahead of spaces, which may reference
# mirrors.
# Loop over instructions using the order of the parser map to define priority.
for namespace in sorted(parser.instructions, key=order_func):
spec_fields = KatSpec().namespace_fields(namespace)
......@@ -115,9 +178,11 @@ class KatParser(BaseKatParser):
else:
spec = spec_fields
# Resolve parameters like a pd's node argument to point to the object reference.
_resolve_parameters(model, args, kwargs)
if namespace == "analysis":
_resolve_references(model, args, kwargs)
try:
# Do what the instruction is supposed to do: add the component to the model,
# set the model field, etc.
......@@ -128,17 +193,14 @@ class KatParser(BaseKatParser):
e, spec, lineno, self.text
) from e
for el in model.elements.values():
for p in el.parameters:
p.resolve()
if model.analysis is None:
# No analysis was set by the script. Default to noxaxis.
analysis = KatSpec().actions["noxaxis"]()
LOGGER.info(f"No analysis specified; defaulting to '{analysis.name}'.")
model.analysis = analysis
# Resolve parameters containing references, like &l1.P or expressions like 1-&l1.P now that
# everything is parsed. This can't be done while each instruction is being executed above
# because some references may refer to elements not yet at that point instantiated.
for el in model.elements.values():
for p in el.parameters:
p.resolve()
return model
......@@ -26,7 +26,7 @@ def fabry_perot_with_analysis():
pd pdh ITM.p1.o [40k] [0]
pd circ ITM.p2.o
analysis xaxis ETM.phi lin 0 100 400
analysis xaxis &ETM.phi lin 0 100 400
"""
)
kat.add(locks.Lock("z", kat.pdh, kat.ITM.phi, -10, 1e-8))
......
......@@ -3,7 +3,6 @@
from collections import namedtuple
import pytest
from finesse.script import KatParser
from finesse.script.grammar import PathClosure
@pytest.fixture
......@@ -19,35 +18,9 @@ def mock_parser_fakebuild(monkeypatch):
monkeypatch.setattr(KatParser, "_build", mock_build)
def get_args(yacc, namespace="model", field="fakefield", replace_closures=True):
def get_args(yacc, namespace="model", field="fakefield"):
"""Convenience method to get args and kwargs from the YACC object."""
args, kwargs, _ = yacc.instructions[namespace][field]
def recurse_closures(args, kwargs):
# Substitute parameter and reference closures with strings.
for index, value in enumerate(args):
if isinstance(value, tuple):
# This is an action; value is (action_name, (args, kwargs)).
# Resolve nested args/kwargs.
recurse_closures(*value[1])
return
if isinstance(value, PathClosure):
args[index] = repr(value)
for key, value in kwargs.items():
if isinstance(value, tuple):
# This is an action; value is (action_name, (args, kwargs)).
# Resolve nested args/kwargs.
recurse_closures(*value[1])
return
if isinstance(value, PathClosure):
kwargs[key] = repr(value)
if replace_closures:
recurse_closures(args, kwargs)
return args, kwargs
struct = namedtuple("MockParser", ["get_args"])
......
......@@ -225,57 +225,3 @@ def test_script_with_nested_analysis__two_levels__args(parser, mock_parser_fakeb
},
)
)
def test_script_with_nested_analysis__two_levels__parameters(
parser, mock_parser_fakebuild
):
"""Test that a kat script with a two level nested analysis with parameters parses correctly."""
assert (
mock_parser_fakebuild.get_args(
parser.parse(
"""
analysis xaxis (
m1.phi lin 0 90 100
on_step=abcd(
"some string"
3.141
xaxis(
m2.phi
log
1
1k
100
)
label="abcd"
)
on_complete=print_model(
5.5e-9
label="mylabel"
)
name=mynested
)
"""
),
namespace="analysis",
field="xaxis",
)
== (
["m1.phi", "lin", 0, 90, 100],
{
"name": "mynested",
"on_step": (
"abcd",
(
[
"some string",
3.141,
("xaxis", (["m2.phi", "log", 1, 1e3, 100], {},),),
],
{"label": "abcd"},
),
),
"on_complete": ("print_model", ([5.5e-9], {"label": "mylabel"})),
},
)
)
......@@ -2,7 +2,7 @@
import pytest
from unittest import TestCase
from numpy.testing import assert_allclose
from numpy.testing import assert_allclose
import finesse
import numpy as np
......@@ -15,15 +15,15 @@ class MirrorTestCase(TestCase):
cls.MaxDiff = 0
@classmethod
def assert_allclose(cls, a, b, rtol=1e-5, atol=1e-8, msg=""):
rdiff = np.max(np.abs((a - b) / b))
print(msg + "relative diff: " + str(rdiff))
cls.MaxDiff = max(cls.MaxDiff, rdiff)
assert_allclose(a, b, rtol, atol)
def assert_allclose(cls,a,b,rtol=1e-5,atol=1e-8,msg=''):
rdiff = np.max(np.abs((a-b)/b))
print(msg+'relative diff: '+str(rdiff))
cls.MaxDiff = max(cls.MaxDiff,rdiff)
assert_allclose(a,b,rtol,atol)
@classmethod
def tearDownClass(cls):
print("\nMax relative difference: " + str(cls.MaxDiff))
print('\nMax relative difference: '+str(cls.MaxDiff))
def setUp(self):
self.kat = finesse.Model()
......@@ -47,10 +47,10 @@ class MirrorTestCase(TestCase):
ad ad_refl m1.p2.o 0 # `amplitude' detector for light reflected from the mirror
"""
)
print("")
print('')
# def tearDown(self):
# print('')
# def tearDown(self):
# print('')
def test_mirror_transmissivity(self):
"""Test sweeping a mirror's transmissivity"""
......@@ -58,7 +58,7 @@ class MirrorTestCase(TestCase):
kat.parse(
"""
analysis xaxis m1.T lin 0.5 0 100 # Sweep mirror transmissivity from 0.5 to 0 with 100 steps
analysis xaxis &m1.T lin 0.5 0 100 # Sweep mirror transmissivity from 0.5 to 0 with 100 steps
"""
)
......@@ -67,25 +67,23 @@ class MirrorTestCase(TestCase):
transmitted = 1j * xaxis ** 0.5
reflected = np.ones(101) * 0.5 ** 0.5
self.assert_allclose(
out["ad_trans"], transmitted, msg="Mirror transmissivity, t "
)
self.assert_allclose(out["ad_refl"], reflected, msg="Mirror transmissivity, r ")
self.assert_allclose(out["ad_trans"], transmitted,
msg='Mirror transmissivity, t ')
self.assert_allclose(out["ad_refl"], reflected,
msg='Mirror transmissivity, r ')
kat = self.reverse_kat.deepcopy()
kat.parse(
"""
analysis xaxis m1.T lin 0.5 0 100 # Sweep mirror transmissivity from 0.5 to 0 with 100 steps
analysis xaxis &m1.T lin 0.5 0 100 # Sweep mirror transmissivity from 0.5 to 0 with 100 steps
"""
)
out = kat.run()
self.assert_allclose(
out["ad_trans"], transmitted, msg="Reverse mirror transmissivity, t "
)
self.assert_allclose(
out["ad_refl"], reflected, msg="Reverse mirror transmissivity, r "
)
self.assert_allclose(out["ad_trans"], transmitted,
msg='Reverse mirror transmissivity, t ')
self.assert_allclose(out["ad_refl"], reflected,
msg='Reverse mirror transmissivity, r ')
def test_mirror_reflectivity(self):
"""Test sweeping a mirror's reflectivity"""
......@@ -93,7 +91,7 @@ class MirrorTestCase(TestCase):
kat.parse(
"""
analysis xaxis m1.R lin 0.5 0 100 # Sweep mirror reflectivity from 0.5 to 0 with 100 steps
analysis xaxis &m1.R lin 0.5 0 100 # Sweep mirror reflectivity from 0.5 to 0 with 100 steps
"""
)
......@@ -102,25 +100,23 @@ class MirrorTestCase(TestCase):
reflected = xaxis ** 0.5
transmitted = 1j * np.ones(101) * 0.5 ** 0.5
self.assert_allclose(
out["ad_trans"], transmitted, msg="Mirror reflectivity, t "
)
self.assert_allclose(out["ad_refl"], reflected, msg="Mirror reflectivity, r ")
self.assert_allclose(out["ad_trans"], transmitted,
msg='Mirror reflectivity, t ')
self.assert_allclose(out["ad_refl"], reflected,
msg='Mirror reflectivity, r ')
kat = self.reverse_kat.deepcopy()
kat.parse(
"""
analysis xaxis m1.R lin 0.5 0 100 # Sweep mirror reflectivity from 0.5 to 0 with 100 steps
analysis xaxis &m1.R lin 0.5 0 100 # Sweep mirror reflectivity from 0.5 to 0 with 100 steps
"""
)
out = kat.run()
self.assert_allclose(
out["ad_trans"], transmitted, msg="Reverse mirror reflectivity, t "
)
self.assert_allclose(
out["ad_refl"], reflected, msg="Reverse birror reflectivity, r "
)
self.assert_allclose(out["ad_trans"], transmitted,
msg='Reverse mirror reflectivity, t ')
self.assert_allclose(out["ad_refl"], reflected,
msg='Reverse birror reflectivity, r ')
@pytest.mark.xfail()
def test_mirror_loss(self):
......@@ -130,7 +126,7 @@ class MirrorTestCase(TestCase):
kat.parse(
"""
analysis xaxis m1.L lin 0 1 100 # Sweep mirror loss from 0 to 1 with 100 steps
analysis xaxis &m1.L lin 0 1 100 # Sweep mirror loss from 0 to 1 with 100 steps
"""
)
......@@ -139,21 +135,23 @@ class MirrorTestCase(TestCase):
reflected = (0.5 * (1 - xaxis)) ** 0.5
transmitted = 1j * reflected
self.assert_allclose(out["ad_trans"], transmitted, msg="Mirror loss, t ")
self.assert_allclose(out["ad_refl"], reflected, msg="Mirror loss, r ")
self.assert_allclose(out["ad_trans"], transmitted,
msg='Mirror loss, t ')
self.assert_allclose(out["ad_refl"], reflected,
msg='Mirror loss, r ')
kat = self.reverse_kat.deepcopy()
kat.parse(
"""
analysis xaxis m1.L lin 0 1 100 # Sweep mirror loss from 0 to 1 with 100 steps
analysis xaxis &m1.L lin 0 1 100 # Sweep mirror loss from 0 to 1 with 100 steps
"""
)
out = kat.run()
self.assert_allclose(
out["ad_trans"], transmitted, msg="Reverse mirror loss, t "
)
self.assert_allclose(out["ad_refl"], reflected, msg="Reverse mirror loss, r ")
self.assert_allclose(out["ad_trans"], transmitted,