Commit 13cfed5e authored by Sean Leavey's avatar Sean Leavey

Merge branch 'master' into feature/traceorder

parents e5d34d2c be61c2ae
## GitLab CI configuration.
# The quay igwn image was suggested by Duncan Macleod (https://chat.ligo.org/ligo/pl/j7dsz1m9zbdkupro8t8zkeyswh)
# and is directly derived from the Miniconda3 image provided by the makers of conda.
image: quay.io/igwn/base:conda
# This LVK image is directly derived from the Miniconda3 image provided by the makers of conda.
image: igwn/base:conda
stages:
- build
......
......@@ -3,8 +3,7 @@
sphinx == 3.1.2
sphinx_rtd_theme == 0.4.3
# TODO (sjr) pin package version
sphinxcontrib-bibtex
sphinxcontrib-bibtex < 2.0.0 # Can't use v2 due to bug; see setup.py requirements for more info.
sphinxcontrib-katex == 0.6.1
sphinx-click == 2.5.0
jupyter-sphinx == 0.3.2
......
......@@ -217,13 +217,16 @@ EXTRAS = {
"dev": [
"sphinx",
"sphinx_rtd_theme",
"sphinxcontrib-bibtex",
# Can't use latest sphinxcontrib-bibtex version due to a dependency incompatibility as of
# 2020-12-14 (https://github.com/executablebooks/jupyter-book/issues/1137). The version
# pinning can be removed once Jupyter Book > 0.8.3 is released.
"sphinxcontrib-bibtex < 2.0.0",
"sphinxcontrib-katex",
"jupyter-sphinx",
"numpydoc",
"pytest",
"pytest-cov",
# Can't use latest coverage version due to Cython bug
# Can't use latest coverage version due to Cython bug as of 2020-11-26
# (https://github.com/cython/cython/issues/3515).
"coverage == 4.5.4",
"pycobertura",
......
......@@ -90,7 +90,7 @@ class Action:
p_info.add(self.copy_info())
def setup(self, s_prev, model):
# By default simple actions should just run the
# By default simple actions should just run their 'do' method
ws = ActionWorkspace(s_prev, model)
ws.fn_do = self.do
return ws
......@@ -686,7 +686,7 @@ class Debug(Action):
self.shell()
class RunLocksWorkspace:
class RunLocksWorkspace(ActionWorkspace):
pass
......@@ -705,7 +705,7 @@ class RunLocks(Action):
p_info.add(self.copy_info())
def setup(self, s_prev, model: finesse.model.Model):
ws = RunLocksWorkspace()
ws = RunLocksWorkspace(s_prev, model)
ws.locks = tuple(model.elements[l] for l in self.locks)
ws.det_ws = [None,] * self.num_locks
......
......@@ -211,9 +211,6 @@ class Beamsplitter(Surface):
(self.R, self.T, self.phi, self.alpha, self.xbeta, self.ybeta)
)
# Set up symbolic ABCD matrices
self._resymbolise_ABCDs()
def _resymbolise_ABCDs(self):
# -> reflections
self.__symbolise_ABCD(self.p1.i, self.p2.o, "x")
......@@ -593,15 +590,7 @@ class Beamsplitter(Surface):
tre : :class:`.TotalReflectionError`
If total reflection occurs for the specified coupling - i.e. if :math:`\sin{\alpha_2} > 1.0`.
"""
from_node, to_node = self._parse_from_to_nodes(from_node, to_node)
self.check_coupling(from_node, to_node)
M_sym, M_num = self._abcd_matrices[(from_node, to_node, direction)]
if M_sym is None: # Symbolic M is None if refl coupling resulted in total refl.
# M_num is a TotalReflectionError instance
raise M_num
return _conn_abcd_return(M_sym, M_num, symbolic, copy, retboth)
return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
def _get_workspace(self, sim):
from finesse.components.modal.beamsplitter import (
......
......@@ -147,7 +147,9 @@ class Filter(Connector):
else:
w = None
w, mag, phase = scipy.signal.bode(self.sys)
w, mag, phase = scipy.signal.bode(
tuple(np.array(_, dtype=float) for _ in self.sys)
)
fig, axs = plt.subplots(2, 1, sharex=True)
axs[0].semilogx(w / 2 / np.pi, mag)
......
......@@ -190,6 +190,7 @@ class Connector(ModelElement):
self.__interaction_types = {}
self._abcd_matrices = {}
self.__abcds_symbolised = False
self.knm_logging = set() # set of "Knm" strings representing matrices to log
......@@ -454,6 +455,10 @@ class Connector(ModelElement):
to_node = to_node.o
return from_node, to_node
def _resymbolise_ABCDs(self):
# By default components will not have to resymbolise optical ABCD matrices
pass
def ABCD(
self,
......@@ -463,9 +468,9 @@ class Connector(ModelElement):
symbolic=False,
copy=True,
retboth=False,
):
"""ABCD matrix computation for a generic component.
): # This docstring is appended too in inheriting classes with more info
# Don't add before Parameters section
"""
Parameters
----------
from_node : :class:`.OpticalNode` or :class:`.Port` or str or int
......@@ -510,13 +515,31 @@ class Connector(ModelElement):
from_node, to_node = self._parse_from_to_nodes(from_node, to_node)
self.check_coupling(from_node, to_node)
M_sym = np.eye(2, dtype=object)
M_num = np.eye(2)
if not self.__abcds_symbolised:
self._resymbolise_ABCDs()
self.__abcds_symbolised = True
key = (from_node, to_node, direction)
if key not in self._abcd_matrices:
# If an optical coupling exists and no abcd has been specfied
# then we can assume it is an identity transformation
M_sym = np.eye(2, dtype=object)
M_num = np.eye(2)
else:
M_sym, M_num = self._abcd_matrices[key]
if M_sym is None :
# Symbolic M is None if an error occurred during the tracing
# In such a case M_num is a TotalReflectionError or
# some other exception instance
raise M_num
return _conn_abcd_return(M_sym, M_num, symbolic, copy, retboth)
def _conn_abcd_return(M_sym, M_num, symbolic: bool, copy: bool, retboth: bool):
"""Helper function for handling symbolic and numeric ABCD matrices in functions."""
if copy:
Ms = M_sym.copy()
Mn = M_num.copy()
......
......@@ -74,26 +74,25 @@ class Lens(Connector):
interaction_type=InteractionType.TRANSMISSION,
)
# Set up symbolic ABCD matrices
self.__symbolise_ABCDs("x")
self.__symbolise_ABCDs("y")
def _check_f(self, value):
if value == 0:
raise ValueError("Focal length of lens must be non-zero.")
return value
def _resymbolise_ABCDs(self):
self.__symbolise_ABCDs("x")
self.__symbolise_ABCDs("y")
def __symbolise_ABCDs(self, direction):
# TODO (sjr) Not using direction currently but will split
# into fx, fy soon to support astigmatic lenses
M_sym = np.array([[1.0, 0.0], [-1.0 / self.f.ref, 1.0]])
M_num = np.array(M_sym, dtype=np.float64)
self._abcd_matrices[direction] = (
M_sym,
M_num,
)
key = (self.p1.i, self.p2.o, direction)
self._abcd_matrices[key] = ( M_sym, M_num )
key = (self.p2.i, self.p1.o, direction)
self._abcd_matrices[key] = ( M_sym, M_num )
@property
def abcdx(self):
......@@ -103,7 +102,7 @@ class Lens(Connector):
:getter: Returns a copy of the (numeric) ABCD matrix for this coupling (read-only).
"""
_, M = self._abcd_matrices["x"]
_, M = self._abcd_matrices[(self.p1.i, self.p2.o, "x")]
return M.copy()
@property
......@@ -114,7 +113,7 @@ class Lens(Connector):
:getter: Returns a copy of the (numeric) ABCD matrix for this coupling (read-only).
"""
_, M = self._abcd_matrices["y"]
_, M = self._abcd_matrices[(self.p1.i, self.p2.o, "y")]
return M.copy()
def ABCD(
......@@ -145,12 +144,7 @@ class Lens(Connector):
See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible
exceptions.
"""
from_node, to_node = self._parse_from_to_nodes(from_node, to_node)
self.check_coupling(from_node, to_node)
M_sym, M_num = self._abcd_matrices[direction]
return _conn_abcd_return(M_sym, M_num, symbolic, copy, retboth)
return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
def actuate_f(self, dioptres, direction=("x", "y")):
r"""Actuate on the focal length of the lens with a
......@@ -213,8 +207,10 @@ class Lens(Connector):
ws.set_fill_fn(self._fill_matrix)
if sim.is_modal:
_, ws.abcd_x = self._abcd_matrices["x"]
_, ws.abcd_y = self._abcd_matrices["y"]
key = (self.p1.i, self.p2.o, "x")
_, ws.abcd_x = self._abcd_matrices[key]
key = (self.p1.i, self.p2.o, "y")
_, ws.abcd_y = self._abcd_matrices[key]
return ws
......
......@@ -150,9 +150,6 @@ class Mirror(Surface):
self.__changing_check = set((self.R, self.T, self.phi, self.xbeta, self.ybeta))
# Set up symbolic ABCD matrices
self._resymbolise_ABCDs()
def _resymbolise_ABCDs(self):
# -> reflections
self.__symbolise_ABCD(self.p1.i, self.p1.o, "x")
......@@ -337,12 +334,8 @@ class Mirror(Surface):
See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible
exceptions.
"""
from_node, to_node = self._parse_from_to_nodes(from_node, to_node)
self.check_coupling(from_node, to_node)
M_sym, M_num = self._abcd_matrices[(from_node, to_node, direction)]
return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
return _conn_abcd_return(M_sym, M_num, symbolic, copy, retboth)
def _get_workspace(self, sim):
_, is_changing = self._eval_parameters()
......
......@@ -122,7 +122,7 @@ class Photodiode1Demod(Connector):
for f1 in ws.sim.DC.frequencies:
for f2 in ws.sim.DC.frequencies:
df = f1.f - f2.f
if df == ws.values.f or -df == ws.values.f:
if df == ws.values.f or df == -ws.values.f:
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_I_idx,
......@@ -134,7 +134,7 @@ class Photodiode1Demod(Connector):
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_I_idx,
ws.sim.DC.frequency_info[f2.index]["audio_upper_index"],
ws.sim.DC.frequency_info[f1.index]["audio_upper_index"],
0,
) as mat:
mat[:] = 0
......@@ -150,7 +150,7 @@ class Photodiode1Demod(Connector):
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_Q_idx,
ws.sim.DC.frequency_info[f2.index]["audio_upper_index"],
ws.sim.DC.frequency_info[f1.index]["audio_upper_index"],
0,
) as mat:
mat[:] = 0
......@@ -181,6 +181,28 @@ class Photodiode1Demod(Connector):
car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k)
mat[:][k] += factorI.conjugate() * car.conjugate()
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_Q_idx,
ws.sim.DC.frequency_info[f2.index]["audio_lower_index"],
0,
) as mat:
for k in range(ws.sim.DC.model_data.num_HOMs):
car = ws.sim.DC.get_DC_out(self.p1.i, f1.index, k)
# This will apply a conjugation internally as it's
# a lower SB connection
mat[:][k] += (factorQ.conjugate() * car)
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_Q_idx,
ws.sim.DC.frequency_info[f1.index]["audio_upper_index"],
0,
) as mat:
for k in range(ws.sim.DC.model_data.num_HOMs):
car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k)
mat[:][k] += factorQ.conjugate() * car.conjugate()
if df == ws.values.f:
with ws.sim.component_edge_fill3(
ws.owner_id,
......@@ -203,3 +225,25 @@ class Photodiode1Demod(Connector):
for k in range(ws.sim.DC.model_data.num_HOMs):
car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k)
mat[:][k] += factorI * car.conjugate()
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_Q_idx,
ws.sim.DC.frequency_info[f2.index]["audio_lower_index"],
0,
) as mat:
for k in range(ws.sim.DC.model_data.num_HOMs):
car = ws.sim.DC.get_DC_out(self.p1.i, f1.index, k)
# This will apply a conjugation internally as it's
# a lower SB connection
mat[:][k] += (factorQ * car)
with ws.sim.component_edge_fill3(
ws.owner_id,
ws.connections.P1i_Q_idx,
ws.sim.DC.frequency_info[f1.index]["audio_upper_index"],
0,
) as mat:
for k in range(ws.sim.DC.model_data.num_HOMs):
car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k)
mat[:][k] += factorQ * car.conjugate()
......@@ -185,7 +185,8 @@ class Space(Connector):
# Initialise the ABCD matrix memory-views
if sim.is_modal:
_, ws.abcd = self._abcd_matrices["all"]
key = (self.p1.i, self.p2.o, "x") # x and y are the same so doesn't matter
_, ws.abcd = self._abcd_matrices[key]
return ws
......@@ -201,6 +202,9 @@ class Space(Connector):
return value
def _resymbolise_ABCDs(self):
self.__symbolise_ABCDs()
def __symbolise_ABCDs(self):
L = self.L.ref
nr = self.nr.ref
......@@ -208,9 +212,11 @@ class Space(Connector):
M_sym = np.array([[1.0, L / nr], [0.0, 1.0]])
M_num = np.array(M_sym, dtype=np.float64)
# Put into a dict as GeometricParameter.update_abcd_matrices (method for
# updating ABCDs outside of a simulation) expects this type
self._abcd_matrices["all"] = M_sym, M_num
for direction in ['x', 'y']:
key = (self.p1.i, self.p2.o, direction)
self._abcd_matrices[key] = ( M_sym, M_num )
key = (self.p2.i, self.p1.o, direction)
self._abcd_matrices[key] = ( M_sym, M_num )
@property
def abcd(self):
......@@ -221,7 +227,8 @@ class Space(Connector):
:getter: Returns a copy of the (numeric) ABCD matrix (read-only).
"""
_, M = self._abcd_matrices["x"]
key = (self.p1.i, self.p2.o, "x")
_, M = self._abcd_matrices[key]
return M.copy()
def ABCD(
......@@ -253,9 +260,4 @@ class Space(Connector):
See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible
exceptions.
"""
self._parse_from_to_nodes(from_node, to_node)
self.check_coupling(from_node, to_node)
M_sym, M_num = self._abcd_matrices["all"]
return _conn_abcd_return(M_sym, M_num, symbolic, copy, retboth)
return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
......@@ -2828,7 +2828,7 @@ class Model:
fn, tn, vn, path, q_in, direction, symbolic, solution_name, **kwargs,
)
def astigmatism(
def propagate_beam_astig(
self,
from_node=None,
to_node=None,
......@@ -2840,7 +2840,7 @@ class Model:
solution_name=None,
**kwargs,
):
"""See :func:`finesse.tracing.tools.astigmatism`
"""See :func:`finesse.tracing.tools.propagate_beam_astig`
.. note::
......@@ -2848,7 +2848,7 @@ class Model:
`via_node` can be specified as strings.
"""
fn, tn, vn = self.__parse_path_nodes(from_node, to_node, via_node)
return tracetools.astigmatism(
return tracetools.propagate_beam_astig(
fn, tn, vn, path, qx_in, qy_in, symbolic, solution_name, **kwargs,
)
......
......@@ -9,17 +9,15 @@ from finesse.solutions.array import ArraySolution
from finesse.solutions.beamtrace import (
ABCDSolution,
PropagationSolution,
AstigmatismSolution,
AstigmaticPropagationSolution,
SimulationTraceSolution,
TraceDataEntry,
)
__all__ = (
"BaseSolution",
"ArraySolution",
"ABCDSolution",
"AstigmatismSolution",
"PropagationSolution",
"AstigmaticPropagationSolution",
"SimulationTraceSolution",
"TraceDataEntry",
)
......@@ -2,7 +2,6 @@
Output data structure for non-utility beam trace executions.
"""
from collections import OrderedDict, namedtuple
import logging
import numpy as np
......@@ -15,8 +14,6 @@ from finesse.symbols import evaluate, Symbol
from finesse.solutions.base import BaseSolution
from finesse.utilities.misc import is_iterable
TraceDataEntry = namedtuple("TraceDataEntry", "qx qy set_mode")
LOGGER = logging.getLogger(__name__)
......@@ -1442,12 +1439,12 @@ class PropagationSolution(BaseSolution):
return fig, axs, an
class AstigmatismSolution(BaseSolution):
"""Solution representation of a call to :func:`finesse.tracing.tools.astigmatism`.
class AstigmaticPropagationSolution(BaseSolution):
"""Solution representation of a call to :func:`finesse.tracing.tools.propagate_beam_astig`.
Internally this stores two :class:`.PropagationSolution` instances which are used
to access the per-plane beam parameters. These propagation solutions can be accessed
via :class:`.AstigmatismSolution.ps_x` and :class:`.AstigmatismSolution.ps_y` for
via :class:`.AstigmaticPropagationSolution.ps_x` and :class:`.AstigmaticPropagationSolution.ps_y` for
the tangential and sagittal planes, respectively.
"""
......@@ -1527,20 +1524,20 @@ class AstigmatismSolution(BaseSolution):
def overlap(self, at, which=None):
"""Overlap between tangential and sagittal beam parameters
at a given node of the path.
at a given location of the path.
See :meth:`.PropagationSolution.q` for parameters description.
"""
if not isinstance(at, OpticalNode) or which is not None:
raise NotImplementedError(
"Overlaps at ports / connectors not yet implemented."
)
qxs = self.qx(at, which)
qys = self.qy(at, which)
qx = self.qx(at, which)
qy = self.qy(at, which)
if isinstance(at, OpticalNode) or (isinstance(at, Connector) and which):
return BeamParam.overlap(qxs, qys)
ov = BeamParam.overlap(qx, qy)
return ov
return {
node: BeamParam.overlap(qx, qy)
for node, qx, qy in zip(qxs.keys(), qxs.values(), qys.values())
}
def plot(
self,
......@@ -1642,7 +1639,8 @@ class AstigmatismSolution(BaseSolution):
if not self.symbolic and subs is not None:
LOGGER.warning(
"Ignoring subs=%s kwarg as AstigmatismSolution is non-symbolic.", subs,
"Ignoring subs=%s kwarg as AstigmaticPropagationSolution is non-symbolic.",
subs,
)
N = len(args)
......@@ -1831,10 +1829,7 @@ class SimulationTraceSolution(BaseSolution):
:getter: Returns a dictionary of traced nodes with corresponding
tangential plane beam parameters (read-only).
"""
qxs = OrderedDict()
for node, (qx, _) in self._data.items():
qxs[node] = qx
return qxs
return {node: qx for node, (qx, _) in self._data.items()}
@property
def data_qy(self):
......@@ -1844,10 +1839,7 @@ class SimulationTraceSolution(BaseSolution):
:getter: Returns a dictionary of traced nodes with corresponding
sagittal plane beam parameters (read-only).
"""
qys = OrderedDict()
for node, (_, qy) in self._data.items():
qys[node] = qy
return qys
return {node: qy for node, (_, qy) in self._data.items()}
def __getitem__(self, node):
if isinstance(node, OpticalNode):
......
......@@ -26,7 +26,7 @@ from finesse.paths import OpticalPath
from finesse.solutions import (
ABCDSolution,
PropagationSolution,
AstigmatismSolution,
AstigmaticPropagationSolution,
)
import logging
......@@ -544,7 +544,7 @@ cdef tuple propagate_beam_symbolic(
### Astigmatism in a path
def astigmatism(
def propagate_beam_astig(
from_node=None,
to_node=None,
via_node=None,
......@@ -555,7 +555,8 @@ def astigmatism(
solution_name=None,
**kwargs,
):
"""Computes the astigmatism in a given path of a model.
"""Propagates the beam through a specified path over both the tangential and
sagittal planes.
Internally this calls :func:`.propagate_beam` twice - for both the tangential
and sagittal planes - and returns a solution object which stores the returns
......@@ -590,7 +591,7 @@ def astigmatism(
Returns
-------
astig_sol : :class:`.AstigmatismSolution`
astig_sol : :class:`.AstigmaticPropagationSolution`
A solution object consisting of the propagation solutions for both planes
and methods for accessing the per-plane beam parameters and overlaps.
"""
......@@ -603,11 +604,11 @@ def astigmatism(
fn = path.nodes_only[0]
tn = path.nodes_only[-1]
solution_name = f"Astigmatism_{fn.full_name}_{tn.full_name}"
solution_name = f"AstigProp_{fn.full_name}_{tn.full_name}"
if symbolic:
solution_name += "_sym"
return AstigmatismSolution(solution_name, ps_x, ps_y)
return AstigmaticPropagationSolution(solution_name, ps_x, ps_y)
### Computing mode mismatches ###
......
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