Commit e5d34d2c authored by Sean Leavey's avatar Sean Leavey
Browse files

Add traceorder command for explicit trace ordering and change implicit...

Add traceorder command for explicit trace ordering and change implicit ordering to use component names in alphabetical order
parent 3c1bbfdf
......@@ -768,67 +768,87 @@ class Model:
@property
def trace_order(self):
"""A list of beam tracing dependencies, ordered by their tracing priority.
"""Explicit beam tracing dependencies, ordered by their tracing priority.
Dependencies (i.e. :class:`.Cavity` and :class:`.Gauss`) objects are ordered
in this list according to the priority in which they will be traced during
the beam tracing routine.
When setting this list, you can specify any number of dependencies less than or
equal to the number of dependencies in the model. Any dependencies not specified
in the sequence you give will retain their original order, i.e. the order in which
they already appear in this list. This original order will generally be the order
in which the dependencies were added to the model; unless you modified this order
manually via this setter in the meantime.
When setting this order, you can specify any number of dependencies less than or equal to
the number of dependencies in the model. Any dependencies not specified in the given
sequence are traced after the last explicit dependency using a sorting criterion that is
stable across runs of Finesse.
.. note::
Regardless of their positions in this list, the *internal* traces of
:class:`.Cavity` objects will always be performed first. Internal cavity
traces are defined as the traces which propagate the cavity eigenmode
through all the nodes of the cavity path.
Regardless of their positions in this list, the *internal* traces of :class:`.Cavity`
objects will always be performed first. Internal cavity traces are defined as the traces
which propagate the cavity eigenmode through all the nodes of the cavity path.
Importantly, however, the order in which :class:`.Cavity` objects appear
in this trace order list *will* also determine the order in which their internal
traces are performed. This is relevant only for when there are overlapping
cavities in the model - recycling cavities in dual-recycled Michelson interferometer
configurations are a typical case of this.
Importantly, however, the order in which :class:`.Cavity` objects appear in this trace
order list *will* also determine the order in which their internal traces are performed.
This is relevant only for when there are overlapping cavities in the model - recycling
cavities in dual-recycled Michelson interferometer configurations are a typical case of
this.
As always see :meth:`.Model.beam_trace` and :ref:`tracing_manual` for more details
on the inner workings of the beam tracing routines.
As always see :meth:`.Model.beam_trace` and :ref:`tracing_manual` for more details on
the inner workings of the beam tracing routines.
Temporary overriding of this order for a given :meth:`.Model.beam_trace` call
can be performed by specifying the ``order`` argument for this method call.
Temporary overriding of this order for a given :meth:`.Model.beam_trace` call can be
performed by specifying the ``order`` argument for this method call.
To override this ordering for a simulation, one can similarly use the setter
of this property *or* set the ``"order"`` key of :attr:`.Model.beam_trace_args`
to any arbitrary dependency order (these are equivalent).
To override this ordering for a simulation, one can similarly use the setter of this
property *or* set the ``"order"`` key of :attr:`.Model.beam_trace_args` to any arbitrary
dependency order (these are equivalent).
:getter: Returns a list giving the order in which dependencies will be traced.
:setter: Sets the order in which dependencies will be traced.
:getter: Returns a list giving the explicit order in which dependencies are to be traced.
:setter: Sets the explicit order (sequence of dependency names or objects) in which
dependencies are to be traced.
"""
return self.__trace_order
def __make_trace_order_list(self, value, reflist=None):
if not is_iterable(value):
value = []
@trace_order.setter
def trace_order(self, trace_order):
self.__trace_order = list(trace_order)
def _implicit_trace_order(self):
"""Determine the implicit trace dependency order.
# Make a shallow copy of the current dependency list
rlcopy = self.trace_order.copy()
if reflist is not None:
reflist.clear()
else:
reflist = []
This uses alphabetic name ordering as a sort criterion since this is stable across multiple
executions.
.. note::
The ordering criterion used here is not part of the public API and may be subject to
change in the future. To set an explicit order, use :meth:`.trace_order`.
"""
# Trace dependency objects.
dependencies = list(self.__cavities) + list(self.__gauss_commands.values())
return sorted(dependencies, key=lambda dep: dep.name)
def _full_trace_order(self, explicit=None):
"""Determine the full trace order dependency sequence from explicit and implicit orders.
This combines the explicit trace order either passed directly to this method or else defined
by the user using :meth:`.trace_order` with the implicit trace order determined from a
stable sorting criterion of the remaining dependencies (i.e. :class:`.Cavity` and
:class:`.Gauss`). The resulting list defines the priority in which dependencies will be
traced during the beam tracing routine.
"""
if explicit is None:
# Get the previously defined explicit trace order.
explicit = self.trace_order
if not is_iterable(explicit):
explicit = []
order = []
implicit = self._implicit_trace_order()
# For all the items in value, append these in order
# order to the new dependency order list
for item in value:
# Check specified items are valid.
for item in explicit:
if isinstance(item, str):
dep = self.__elements.get(item)
if dep is None:
raise ValueError(
f"Error in trace_order setter:\n"
f" No item of name {dep} exists in the model."
f" No item with name {dep} exists in the model."
)
else:
dep = item
......@@ -848,63 +868,21 @@ class Model:
else:
raise ValueError(
f"Error in trace_order setter:\n"
f" Item {dep} is neither a Cavity nor a Gauss object."
f" Item {dep} must be a Cavity or a Gauss object."
)
if dep in reflist:
if dep in order:
LOGGER.warning(
"Ignoring duplicate dependency %s in setting of trace_order.",
dep.name,
)
else:
reflist.append(dep)
order.append(dep)
# Take all the remaining dependencies that were not in value and
# append these to the end of the dependency order list
# -> this means that the original order of those dependencies is
# retained, the only ordering that has changed is those that
# were included in the value passed to the setter
for item in rlcopy:
if item not in reflist:
reflist.append(item)
# Remove the dependency from the implicit order.
implicit.remove(dep)
return reflist
@trace_order.setter
def trace_order(self, value):
self.__make_trace_order_list(value, reflist=self.trace_order)
def _re_order_trace_deps_from_linenos(self, dep_line_map, traceorder_args=None):
"""Re-order self.trace_order based on parsed order.
This requires a mapping of dependencies to line_numbers and optionally the arguments passed
to the `traceorder` command.
To be used only by the parser for post-processing.
"""
if traceorder_args is None:
traceorder_args = []
over_trord_dep_idx = 0
else:
# Get the index beyond the last dependency
# passed to the traceorder args command
# -> this will be where the other dependencies
# begin
over_trord_dep_idx = 1 + self.trace_order.index(traceorder_args[-1])
# All dependencies in the model were passed via the traceorder command
# already so don't need to do anything
if len(traceorder_args) == len(dep_line_map):
return
# Obtain the dependencies which weren't included in traceorder command
trailing_deps = self.trace_order[over_trord_dep_idx:]
# and sort them based on their line numbers
trailing_deps.sort(key=lambda x: dep_line_map[x])
# Now set trace_order to the original slice of dependencies included
# in traceorder command plus the sorted trailing dependencies
self.__trace_order = self.__trace_order[:over_trord_dep_idx] + trailing_deps
return order + implicit
@property
def beam_trace_args(self):
......@@ -1291,9 +1269,6 @@ class Model:
# compute all the cavity properties (including path determination)
obj.initialise()
# append the cavity to the ordered dependency list
self.__trace_order.append(obj)
if not obj.is_stable:
LOGGER.warning("Cavity (%s) added to the model is unstable.", obj.name)
......@@ -1347,9 +1322,6 @@ class Model:
self.__gauss_commands[obj.node] = obj
# append the Gauss to the ordered dependency list
self.__trace_order.append(obj)
# Turn on HOMs if model is still plane-wave
if not self.is_modal:
self.select_modes(maxtem=0)
......@@ -2989,12 +2961,7 @@ class Model:
"need to be resolved before it can be used."
)
# No override given
if order is None:
order = self.trace_order
# Override to model trace_order given, so verify and make it
else:
order = self.__make_trace_order_list(order)
order = self._full_trace_order(order)
# If trace_order is an empty list need to verify that this
# is fine (i.e. model doesn't have multiple cavs, gausses)
......
......@@ -1039,6 +1039,32 @@ class LinkCommandAdapter(CommandAdapter):
model.link(*statement.args, **statement.kwargs)
class TraceOrderCommandAdapter(CommandAdapter):
"""Adapter for the "traceorder" command."""
def __init__(self):
super().__init__(
"traceorder",
Model.trace_order,
singular=True,
named=False,
callspec={"exclude": ["self"]},
)
def apply(self, model, statement):
"""The "setter" for the equivalent Python type.
Parameters
----------
model : :class:`.Model`
The model to use to execute this instruction.
statement : ParsedStatement
The parsed statement.
"""
setattr(model, "trace_order", (*statement.args, *statement.kwargs.values()))
class AnalysisAdapter(InstructionAdapter):
"""Adapter for analyses."""
......@@ -1281,6 +1307,7 @@ class KatSpec(metaclass=Singleton):
LambdaCommandAdapter(),
ModesCommandAdapter(),
LinkCommandAdapter(),
TraceOrderCommandAdapter(),
]
# List of default action names and types.
......
"""Test traceorder command parsing."""
from itertools import combinations, permutations
import pytest
def powerset_permutations(iterable):
"([1,2]) --> (1,) (2,) (1,2) (2,1)"
s = list(iterable)
for r in range(1, len(s)):
yield from combinations(s, r)
yield from permutations(s)
@pytest.mark.parametrize(
"order", powerset_permutations(("cav1", "cav2", "cav3", "g1", "g2"))
)
def test_traceorder(parser, order):
"""Test that the traceorder command parses correctly."""
model = parser.parse(
f"""
mirror m1
mirror m2
space s1 m1.p2 m2.p1
cav cav1 m1.p2.o
mirror m3
mirror m4
space s2 m3.p2 m4.p1
cav cav2 m3.p2.o
gauss g1 m4.p1.o q=1
mirror m5
mirror m6
space s3 m5.p2 m6.p1
cav cav3 m5.p2.o
gauss g2 m6.p1.o q=1
traceorder {' '.join(order)}
"""
)
assert model.trace_order == [model.elements[dep] for dep in order]
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