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

Merge branch 'master' into feature/traceorder

parents e5d34d2c be61c2ae
## GitLab CI configuration. ## GitLab CI configuration.
# The quay igwn image was suggested by Duncan Macleod (https://chat.ligo.org/ligo/pl/j7dsz1m9zbdkupro8t8zkeyswh) # This LVK image is directly derived from the Miniconda3 image provided by the makers of conda.
# and is directly derived from the Miniconda3 image provided by the makers of conda. image: igwn/base:conda
image: quay.io/igwn/base:conda
stages: stages:
- build - build
......
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
sphinx == 3.1.2 sphinx == 3.1.2
sphinx_rtd_theme == 0.4.3 sphinx_rtd_theme == 0.4.3
# TODO (sjr) pin package version sphinxcontrib-bibtex < 2.0.0 # Can't use v2 due to bug; see setup.py requirements for more info.
sphinxcontrib-bibtex
sphinxcontrib-katex == 0.6.1 sphinxcontrib-katex == 0.6.1
sphinx-click == 2.5.0 sphinx-click == 2.5.0
jupyter-sphinx == 0.3.2 jupyter-sphinx == 0.3.2
......
...@@ -217,13 +217,16 @@ EXTRAS = { ...@@ -217,13 +217,16 @@ EXTRAS = {
"dev": [ "dev": [
"sphinx", "sphinx",
"sphinx_rtd_theme", "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", "sphinxcontrib-katex",
"jupyter-sphinx", "jupyter-sphinx",
"numpydoc", "numpydoc",
"pytest", "pytest",
"pytest-cov", "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). # (https://github.com/cython/cython/issues/3515).
"coverage == 4.5.4", "coverage == 4.5.4",
"pycobertura", "pycobertura",
......
...@@ -90,7 +90,7 @@ class Action: ...@@ -90,7 +90,7 @@ class Action:
p_info.add(self.copy_info()) p_info.add(self.copy_info())
def setup(self, s_prev, model): 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 = ActionWorkspace(s_prev, model)
ws.fn_do = self.do ws.fn_do = self.do
return ws return ws
...@@ -686,7 +686,7 @@ class Debug(Action): ...@@ -686,7 +686,7 @@ class Debug(Action):
self.shell() self.shell()
class RunLocksWorkspace: class RunLocksWorkspace(ActionWorkspace):
pass pass
...@@ -705,7 +705,7 @@ class RunLocks(Action): ...@@ -705,7 +705,7 @@ class RunLocks(Action):
p_info.add(self.copy_info()) p_info.add(self.copy_info())
def setup(self, s_prev, model: finesse.model.Model): 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.locks = tuple(model.elements[l] for l in self.locks)
ws.det_ws = [None,] * self.num_locks ws.det_ws = [None,] * self.num_locks
......
...@@ -211,9 +211,6 @@ class Beamsplitter(Surface): ...@@ -211,9 +211,6 @@ class Beamsplitter(Surface):
(self.R, self.T, self.phi, self.alpha, self.xbeta, self.ybeta) (self.R, self.T, self.phi, self.alpha, self.xbeta, self.ybeta)
) )
# Set up symbolic ABCD matrices
self._resymbolise_ABCDs()
def _resymbolise_ABCDs(self): def _resymbolise_ABCDs(self):
# -> reflections # -> reflections
self.__symbolise_ABCD(self.p1.i, self.p2.o, "x") self.__symbolise_ABCD(self.p1.i, self.p2.o, "x")
...@@ -593,15 +590,7 @@ class Beamsplitter(Surface): ...@@ -593,15 +590,7 @@ class Beamsplitter(Surface):
tre : :class:`.TotalReflectionError` tre : :class:`.TotalReflectionError`
If total reflection occurs for the specified coupling - i.e. if :math:`\sin{\alpha_2} > 1.0`. 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) return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
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)
def _get_workspace(self, sim): def _get_workspace(self, sim):
from finesse.components.modal.beamsplitter import ( from finesse.components.modal.beamsplitter import (
......
...@@ -147,7 +147,9 @@ class Filter(Connector): ...@@ -147,7 +147,9 @@ class Filter(Connector):
else: else:
w = None 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) fig, axs = plt.subplots(2, 1, sharex=True)
axs[0].semilogx(w / 2 / np.pi, mag) axs[0].semilogx(w / 2 / np.pi, mag)
......
...@@ -190,6 +190,7 @@ class Connector(ModelElement): ...@@ -190,6 +190,7 @@ class Connector(ModelElement):
self.__interaction_types = {} self.__interaction_types = {}
self._abcd_matrices = {} self._abcd_matrices = {}
self.__abcds_symbolised = False
self.knm_logging = set() # set of "Knm" strings representing matrices to log self.knm_logging = set() # set of "Knm" strings representing matrices to log
...@@ -454,6 +455,10 @@ class Connector(ModelElement): ...@@ -454,6 +455,10 @@ class Connector(ModelElement):
to_node = to_node.o to_node = to_node.o
return from_node, to_node return from_node, to_node
def _resymbolise_ABCDs(self):
# By default components will not have to resymbolise optical ABCD matrices
pass
def ABCD( def ABCD(
self, self,
...@@ -463,9 +468,9 @@ class Connector(ModelElement): ...@@ -463,9 +468,9 @@ class Connector(ModelElement):
symbolic=False, symbolic=False,
copy=True, copy=True,
retboth=False, retboth=False,
): ): # This docstring is appended too in inheriting classes with more info
"""ABCD matrix computation for a generic component. # Don't add before Parameters section
"""
Parameters Parameters
---------- ----------
from_node : :class:`.OpticalNode` or :class:`.Port` or str or int from_node : :class:`.OpticalNode` or :class:`.Port` or str or int
...@@ -510,13 +515,31 @@ class Connector(ModelElement): ...@@ -510,13 +515,31 @@ class Connector(ModelElement):
from_node, to_node = self._parse_from_to_nodes(from_node, to_node) from_node, to_node = self._parse_from_to_nodes(from_node, to_node)
self.check_coupling(from_node, to_node) self.check_coupling(from_node, to_node)
M_sym = np.eye(2, dtype=object) if not self.__abcds_symbolised:
M_num = np.eye(2) 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) 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): 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: if copy:
Ms = M_sym.copy() Ms = M_sym.copy()
Mn = M_num.copy() Mn = M_num.copy()
......
...@@ -74,26 +74,25 @@ class Lens(Connector): ...@@ -74,26 +74,25 @@ class Lens(Connector):
interaction_type=InteractionType.TRANSMISSION, interaction_type=InteractionType.TRANSMISSION,
) )
# Set up symbolic ABCD matrices
self.__symbolise_ABCDs("x")
self.__symbolise_ABCDs("y")
def _check_f(self, value): def _check_f(self, value):
if value == 0: if value == 0:
raise ValueError("Focal length of lens must be non-zero.") raise ValueError("Focal length of lens must be non-zero.")
return value return value
def _resymbolise_ABCDs(self):
self.__symbolise_ABCDs("x")
self.__symbolise_ABCDs("y")
def __symbolise_ABCDs(self, direction): def __symbolise_ABCDs(self, direction):
# TODO (sjr) Not using direction currently but will split # TODO (sjr) Not using direction currently but will split
# into fx, fy soon to support astigmatic lenses # into fx, fy soon to support astigmatic lenses
M_sym = np.array([[1.0, 0.0], [-1.0 / self.f.ref, 1.0]]) M_sym = np.array([[1.0, 0.0], [-1.0 / self.f.ref, 1.0]])
M_num = np.array(M_sym, dtype=np.float64) M_num = np.array(M_sym, dtype=np.float64)
key = (self.p1.i, self.p2.o, direction)
self._abcd_matrices[direction] = ( self._abcd_matrices[key] = ( M_sym, M_num )
M_sym, key = (self.p2.i, self.p1.o, direction)
M_num, self._abcd_matrices[key] = ( M_sym, M_num )
)
@property @property
def abcdx(self): def abcdx(self):
...@@ -103,7 +102,7 @@ class Lens(Connector): ...@@ -103,7 +102,7 @@ class Lens(Connector):
:getter: Returns a copy of the (numeric) ABCD matrix for this coupling (read-only). :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() return M.copy()
@property @property
...@@ -114,7 +113,7 @@ class Lens(Connector): ...@@ -114,7 +113,7 @@ class Lens(Connector):
:getter: Returns a copy of the (numeric) ABCD matrix for this coupling (read-only). :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() return M.copy()
def ABCD( def ABCD(
...@@ -145,12 +144,7 @@ class Lens(Connector): ...@@ -145,12 +144,7 @@ class Lens(Connector):
See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible
exceptions. exceptions.
""" """
from_node, to_node = self._parse_from_to_nodes(from_node, to_node) return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
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)
def actuate_f(self, dioptres, direction=("x", "y")): def actuate_f(self, dioptres, direction=("x", "y")):
r"""Actuate on the focal length of the lens with a r"""Actuate on the focal length of the lens with a
...@@ -213,8 +207,10 @@ class Lens(Connector): ...@@ -213,8 +207,10 @@ class Lens(Connector):
ws.set_fill_fn(self._fill_matrix) ws.set_fill_fn(self._fill_matrix)
if sim.is_modal: if sim.is_modal:
_, ws.abcd_x = self._abcd_matrices["x"] key = (self.p1.i, self.p2.o, "x")
_, ws.abcd_y = self._abcd_matrices["y"] _, ws.abcd_x = self._abcd_matrices[key]
key = (self.p1.i, self.p2.o, "y")
_, ws.abcd_y = self._abcd_matrices[key]
return ws return ws
......
...@@ -150,9 +150,6 @@ class Mirror(Surface): ...@@ -150,9 +150,6 @@ class Mirror(Surface):
self.__changing_check = set((self.R, self.T, self.phi, self.xbeta, self.ybeta)) 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): def _resymbolise_ABCDs(self):
# -> reflections # -> reflections
self.__symbolise_ABCD(self.p1.i, self.p1.o, "x") self.__symbolise_ABCD(self.p1.i, self.p1.o, "x")
...@@ -337,12 +334,8 @@ class Mirror(Surface): ...@@ -337,12 +334,8 @@ class Mirror(Surface):
See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible
exceptions. exceptions.
""" """
from_node, to_node = self._parse_from_to_nodes(from_node, to_node) return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
self.check_coupling(from_node, to_node)
M_sym, M_num = self._abcd_matrices[(from_node, to_node, direction)]
return _conn_abcd_return(M_sym, M_num, symbolic, copy, retboth)
def _get_workspace(self, sim): def _get_workspace(self, sim):
_, is_changing = self._eval_parameters() _, is_changing = self._eval_parameters()
......
...@@ -122,7 +122,7 @@ class Photodiode1Demod(Connector): ...@@ -122,7 +122,7 @@ class Photodiode1Demod(Connector):
for f1 in ws.sim.DC.frequencies: for f1 in ws.sim.DC.frequencies:
for f2 in ws.sim.DC.frequencies: for f2 in ws.sim.DC.frequencies:
df = f1.f - f2.f 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( with ws.sim.component_edge_fill3(
ws.owner_id, ws.owner_id,
ws.connections.P1i_I_idx, ws.connections.P1i_I_idx,
...@@ -134,7 +134,7 @@ class Photodiode1Demod(Connector): ...@@ -134,7 +134,7 @@ class Photodiode1Demod(Connector):
with ws.sim.component_edge_fill3( with ws.sim.component_edge_fill3(
ws.owner_id, ws.owner_id,
ws.connections.P1i_I_idx, 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, 0,
) as mat: ) as mat:
mat[:] = 0 mat[:] = 0
...@@ -150,7 +150,7 @@ class Photodiode1Demod(Connector): ...@@ -150,7 +150,7 @@ class Photodiode1Demod(Connector):
with ws.sim.component_edge_fill3( with ws.sim.component_edge_fill3(
ws.owner_id, ws.owner_id,
ws.connections.P1i_Q_idx, 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, 0,
) as mat: ) as mat:
mat[:] = 0 mat[:] = 0
...@@ -181,6 +181,28 @@ class Photodiode1Demod(Connector): ...@@ -181,6 +181,28 @@ class Photodiode1Demod(Connector):
car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k) car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k)
mat[:][k] += factorI.conjugate() * car.conjugate() 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: if df == ws.values.f:
with ws.sim.component_edge_fill3( with ws.sim.component_edge_fill3(
ws.owner_id, ws.owner_id,
...@@ -203,3 +225,25 @@ class Photodiode1Demod(Connector): ...@@ -203,3 +225,25 @@ class Photodiode1Demod(Connector):
for k in range(ws.sim.DC.model_data.num_HOMs): for k in range(ws.sim.DC.model_data.num_HOMs):
car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k) car = ws.sim.DC.get_DC_out(self.p1.i, f2.index, k)
mat[:][k] += factorI * car.conjugate() 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): ...@@ -185,7 +185,8 @@ class Space(Connector):
# Initialise the ABCD matrix memory-views # Initialise the ABCD matrix memory-views
if sim.is_modal: 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 return ws
...@@ -201,6 +202,9 @@ class Space(Connector): ...@@ -201,6 +202,9 @@ class Space(Connector):
return value return value
def _resymbolise_ABCDs(self):
self.__symbolise_ABCDs()
def __symbolise_ABCDs(self): def __symbolise_ABCDs(self):
L = self.L.ref L = self.L.ref
nr = self.nr.ref nr = self.nr.ref
...@@ -208,9 +212,11 @@ class Space(Connector): ...@@ -208,9 +212,11 @@ class Space(Connector):
M_sym = np.array([[1.0, L / nr], [0.0, 1.0]]) M_sym = np.array([[1.0, L / nr], [0.0, 1.0]])
M_num = np.array(M_sym, dtype=np.float64) M_num = np.array(M_sym, dtype=np.float64)
# Put into a dict as GeometricParameter.update_abcd_matrices (method for for direction in ['x', 'y']:
# updating ABCDs outside of a simulation) expects this type key = (self.p1.i, self.p2.o, direction)
self._abcd_matrices["all"] = M_sym, M_num 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 @property
def abcd(self): def abcd(self):
...@@ -221,7 +227,8 @@ class Space(Connector): ...@@ -221,7 +227,8 @@ class Space(Connector):
:getter: Returns a copy of the (numeric) ABCD matrix (read-only). :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() return M.copy()
def ABCD( def ABCD(
...@@ -253,9 +260,4 @@ class Space(Connector): ...@@ -253,9 +260,4 @@ class Space(Connector):
See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible See :meth:`.Connector.ABCD` for descriptions of parameters, return values and possible
exceptions. exceptions.
""" """
self._parse_from_to_nodes(from_node, to_node) return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth)
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)
...@@ -2828,7 +2828,7 @@ class Model: ...@@ -2828,7 +2828,7 @@ class Model:
fn, tn, vn, path, q_in, direction, symbolic, solution_name, **kwargs, fn, tn, vn, path, q_in, direction, symbolic, solution_name, **kwargs,
) )
def astigmatism( def propagate_beam_astig(
self, self,
from_node=None, from_node=None,
to_node=None, to_node=None,
...@@ -2840,7 +2840,7 @@ class Model: ...@@ -2840,7 +2840,7 @@ class Model:
solution_name=None, solution_name=None,
**kwargs, **kwargs,
): ):
"""See :func:`finesse.tracing.tools.astigmatism` """See :func:`finesse.tracing.tools.propagate_beam_astig`
.. note:: .. note::
...@@ -2848,7 +2848,7 @@ class Model: ...@@ -2848,7 +2848,7 @@ class Model:
`via_node` can be specified as strings. `via_node` can be specified as strings.
""" """
fn, tn, vn = self.__parse_path_nodes(from_node, to_node, via_node) 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, fn, tn, vn, path, qx_in, qy_in, symbolic, solution_name, **kwargs,