Source code for finesse.components.cavity

"""Optical cavities with associated properties."""

from copy import deepcopy
import cmath
import logging
import math

import networkx as nx
import numpy as np

from finesse.config import config_instance
import finesse.components as components
import finesse.tracing.ctracer as ctracer
from finesse.components.node import OpticalNode, Port
from finesse.cymath.math import sgn
from finesse.constants import values as constants
from finesse.parameter import info_parameter
from finesse.gaussian import BeamParam

from .trace_dependency import TraceDependency


LOGGER = logging.getLogger(__name__)

# TODO (sjr) Add Cavity.gain property
#            - optical gain eqn? simple for 2 mirror cav, but need general eqn.


[docs]@info_parameter("FSR", "FSR", units="Hz") @info_parameter("loss", "Loss") @info_parameter("finesse", "Finesse") @info_parameter("FWHM", "FWHM", units="Hz") @info_parameter("storage_time", "Storage time", units="s") @info_parameter("pole", "Pole", units="Hz") @info_parameter("round_trip_length", "Round trip length", units="m") @info_parameter("w0", "Waist size", units="m") @info_parameter("waistpos", "Waist position", units="m") @info_parameter("m", "Stability (m-factor)") @info_parameter("g", "Stability (g-factor)") @info_parameter("gouy", "Round trip gouy phase", units="°") @info_parameter("mode_separation", "Mode separation", units="Hz") @info_parameter("S", "Resolution") @info_parameter("is_stable", "Stable") @info_parameter("is_critical", "Critically stable") class Cavity(TraceDependency): """Represents a cavity in an interferometer configuration. This class stores the shortest path between the start node and end node of the cavity and holds symbolic expressions for each physical attribute of the cavity. Numeric values corresponding to these attributes are obtained through the relevant properties. Adding a Cavity to a :class:`.Model` results in the beam parameters of all nodes in the cavity path being set according to the cavity eigenmode (:attr:`Cavity.q`) when a beam trace is performed (e.g. at the start of a modal based simulation). The mode of the cavity is then also used as a trace starting point when setting beam parameters at nodes outside of the cavity - see :ref:`tracing_manual` for details on the beam tracing algorithm. Parameters ---------- name : str Name of newly created cavity. source : :class:`.OpticalNode` or :class:`.Port` Node / Port that the cavity path starts from. If no `via` node is specified, then the cavity path will be given by the shortest path from source back to the component that owns source. If a port is given then the *output* optical node of that port will be used as the source. via : :class:`.OpticalNode`, optional Node that the cavity path must traverse via; defaults to `None`. Note that, unlike `source`, this cannot be a :class:`.Port` object as this would be ambiguous for beamsplitter type components - i.e. determination of which node to use cannot be assumed automatically. priority : number, optional; default: 0 Priority value for beam tracing. Beam tracing dependencies are sorted in descending order of priority - i.e. higher priority value dependencies will be traced first. Any dependency with a priority value of zero will be traced, after non-zero priority dependencies, in alphabetic order of the dependency names. """
[docs] def __init__(self, name, source, via=None, priority=0): super().__init__(name, priority) # If the source is a port then use the output node # of this port as the source for the cavity path if isinstance(source, Port): source = source.o if not isinstance(source, OpticalNode): raise TypeError("Expected source to be an OpticalNode.") else: if source.is_input: raise ValueError("Source must be an output node.") if not isinstance(source.component, components.Surface): msg = ( "Expected owner of source node to be a Surface, " f"but got a {type(self.source.component)}" ) raise TypeError(msg) if via is not None and not isinstance(via, OpticalNode): raise TypeError("Expected via to be an OpticalNode.") self.__source = source self.__via = via self.__path = None
def __deepcopy__(self, memo): # TraceTree instances are non-copyable by design so # temporarily set __tree field to None to avoid copy tmp = self.__tree self.__tree = None new = super().__deepcopy__(memo) memo[id(self)] = new new.__dict__.update(deepcopy(self.__dict__, memo)) new_model = memo.get(id(self._model)) new._reset_model(new_model) def update_later(): new.initialise() new_model.after_deepcopy.append(update_later) # Re-build the tree on self due to above - not an # expensive operation so no big deal for now self.__tree = tmp return new
[docs] def draw(self): """A string representation of the cavity route. Returns ------- s : str The node path of the cavity as a string. """ return self.__tree.draw()
@property def path(self): """The :class:`.OpticalPath` instance of the cavity. :getter: Returns the path of the cavity (read-only). See Also -------- finesse.model.Model.path : Retrieves an ordered container of the path trace between two specified nodes. """ return self.__path @property def source(self): """Starting node of the cavity. :getter: Returns the cavity starting node (read-only). """ return self.__source @property def via(self): """Via node of the cavity. :getter: Returns the cavity via node (read-only). """ return self.__via @property def is_fabry_perot(self): """Flag indicating whether the cavity is a Fabry-Perot cavity. :getter: Returns true if the cavity is a Fabry-Perot, false otherwise (read-only). """ if self.path is None: return False unique_spaces = set(self.path.spaces) # cavity is Fabry-Perot if there's only one space in the path return len(unique_spaces) == 1 ### Non-geometric properties ### @property def FSR(self): r"""The free-spectral-range (FSR) of the cavity. This quantity is defined as, .. math:: \mathrm{FSR} = \frac{c}{2L}, where :math:`c` is the speed of light and :math:`L` is the length of the cavity. :getter: Returns the cavity free-spectral-range (read-only). """ return self._FSR.eval() @property def loss(self): r"""The round-trip loss of the cavity as a fraction of the incoming power. This quantity is computed via, .. math:: L = 1 - \prod_{\mathrm{i}}^{N_{\mathrm{refl}}} R_{\mathrm{i}} \times \prod_{\mathrm{i}}^{N_{\mathrm{trns}}} T_{\mathrm{i}}, i.e. one minus the product of all reflections multiplied with the product of all transmissions for a round-trip of the cavity. :getter: Returns the fractional round-trip cavity loss (read-only). """ return self._loss.eval() @property def finesse(self): r"""The finesse of the cavity. This quantity is defined as, .. math:: \mathcal{F} = \frac{\pi}{2\arcsin{\left( \frac{1 - \widetilde{l}}{2\sqrt{\widetilde{l}}} \right) }}, where :math:`\widetilde{l} = \sqrt{1 - l}` and :math:`l` is the cavity loss. :getter: Returns the cavity finesse (read-only). """ return self._finesse.eval() @property def FWHM(self): r"""The cavity full-width-half-maximum (FWHM). This quantity is defined as, .. math:: \mathrm{FWHM} = \frac{\mathrm{FSR}}{\mathcal{F}}, where :math:`\mathcal{F}` is the cavity finesse. :getter: Returns the FWHM of the cavity (read-only). See Also -------- Cavity.FSR : Free-spectral-range of a cavity. Cavity.finesse : Finesse of a cavity. """ return self._FWHM.eval() @property def storage_time(self): r"""The cavity storage time (:math:`\tau`). This quantity is defined as, .. math:: \tau = \frac{1}{\pi\mathrm{FWHM}}, where :math:`\mathrm{FWHM}` is the full-width at half-maximum of the cavity resonance. :getter: Returns the storage time of the cavity (read-only). See Also -------- Cavity.FWHM : Full-width at half-maximum (FWHM) of a cavity. """ return self._tau.eval() @property def pole(self): r"""The pole-frequency of the cavity. This quantity is defined as, .. math:: f_{\mathrm{pole}} = \frac{\mathrm{FWHM}}{2}, where :math:`\mathrm{FWHM}` is the full-width at half-maximum of the cavity resonance. :getter: Returns the cavity pole-frequency (read-only). See Also -------- Cavity.FWHM : Full-width at half-maximum (FWHM) of a cavity. """ return self._pole.eval() ### Geometric properties ### @property def round_trip_length(self): """The round-trip length of the cavity (in metres). :getter: Returns the length of a single round-trip of the cavity (read-only). """ return self._length.eval() @property def ABCD(self): """The round-trip ABCD matrix of the cavity in both plannes. :getter: Returns a :class:`numpy.ndarray` with shape ``(2, 2, 2)`` of the cavity round-trip matrices in the tangential and sagittal planes, respectively. (read-only). """ ctracer.update_rt_abcd(self.__tree, self._ABCDx, self._ABCDy) Ms = np.zeros((2, 2, 2)) Ms[0][:] = self._ABCDx[:] Ms[1][:] = self._ABCDy[:] return Ms def __get_ABCD(self, direction): return getattr(self, f"ABCD{direction}") @property def ABCDx(self): """The tangential round-trip ABCD matrix of the cavity. :getter: Returns the cavity round-trip matrix in the tangential plane (read-only). """ ctracer.update_single_rt_abcd(self.__tree, self._ABCDx, "x") return self._ABCDx @property def ABCDy(self): """The sagittal round-trip ABCD matrix of the cavity. :getter: Returns the cavity round-trip matrix in the sagittal plane (read-only). """ ctracer.update_single_rt_abcd(self.__tree, self._ABCDy, "y") return self._ABCDy @property def q(self): r"""The eigenmode of the cavity in both planes. For a single plane, the cavity eigenmode :math:`q_{\mathrm{cav}}` is computed by solving, .. math:: C q_{\mathrm{cav}}^2+(D-A)q_{\mathrm{cav}} - B = 0, where :math:`A`, :math:`B`, :math:`C` and :math:`D` are the elements of the round-trip ABCD matrix of the cavity for this plane. :getter: Returns a :class:`numpy.ndarray` of the cavity eigenmodes in the tangential and sagittal planes, respectively, where both values are :class:`.BeamParam` instances. (read-only). """ return np.array([self.qx, self.qy]) def __get_lambda0(self): if self.has_model: lambda0 = self._model.lambda0 else: lambda0 = config_instance()["constants"].getfloat("lambda0") return lambda0 def __compute_eigenmode(self, direction): ABCD = self.__get_ABCD(direction) C = ABCD[1][0] if C == 0.0: # confocal cavity - g = 0 (critical) return None half_inv_C = 0.5 / C D_minus_A = ABCD[1][1] - ABCD[0][0] minus_B = -1 * ABCD[0][1] sqrt_term = cmath.sqrt(D_minus_A * D_minus_A - 4 * C * minus_B) lower = (-D_minus_A - sqrt_term) * half_inv_C upper = (-D_minus_A + sqrt_term) * half_inv_C if lower.imag > 0: q = lower elif upper.imag > 0: q = upper else: return None return q @property def qx(self): """The eigenmode of the cavity in the tangential plane. :getter: Returns the cavity's tangential plane eigenmode (read-only). See Also -------- Cavity.q """ q = self.__compute_eigenmode("x") if q is None: return None return BeamParam(q=q, wavelength=self.__get_lambda0()) @property def qy(self): """The eigenmode of the cavity in the sagittal plane. :getter: Returns the cavity's sagittal plane eigenmode (read-only). See Also -------- Cavity.q """ q = self.__compute_eigenmode("y") if q is None: return None return BeamParam(q=q, wavelength=self.__get_lambda0()) @property def w0(self): """The waist size of the cavity in both planes. :getter: Returns a :class:`numpy.ndarray` of the cavity waist size in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.w0x, self.w0y]) @property def w0x(self): """The waist size of the cavity in the tangential plane. Equivalent to ``cavity.qx.w0``. :getter: Returns the cavity waist size in the tangential plane (read-only). """ if not self.is_stable_x: return np.nan return self.qx.w0 @property def w0y(self): """The waist size of the cavity in the sagittal plane. Equivalent to ``cavity.qy.w0``. :getter: Returns the cavity waist size in the sagittal plane (read-only). """ if not self.is_stable_y: return np.nan return self.qy.w0 @property def waistpos(self): """The position of the cavity waist in both planes. This distance to the waist is measured using the position of :attr:`Cavity.source` node as the origin. :getter: Returns a :class:`numpy.ndarray` of the cavity waist position in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.waistpos_x, self.waistpos_y]) @property def waistpos_x(self): """The waist position of the cavity in the tangential plane. Equivalent to ``cavity.qx.z``. :getter: Returns the cavity waist position in the tangential plane (read-only). """ if not self.is_stable_x: return np.nan return self.qx.z @property def waistpos_y(self): """The waist position of the cavity in the sagittal plane. Equivalent to ``cavity.qy.z``. :getter: Returns the cavity waist position in the sagittal plane (read-only). """ if not self.is_stable_y: return np.nan return self.qy.z @property def m(self): r"""The stability of the cavity, in both planes, given by the :math:`m`-factor: .. math:: m = \frac{A + D}{2}, where :math:`A` and :math:`D` are the relevant entries of the cavity round-trip ABCD matrix. The cavity is stable if the following condition is satisfied: .. math:: -1 \leq m \leq 1. :getter: Returns a :class:`numpy.ndarray` of the cavity stability in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.mx, self.my]) def __compute_m(self, direction): ABCD = self.__get_ABCD(direction) A = ABCD[0][0] D = ABCD[1][1] return 0.5 * (A + D) @property def mx(self): """The stability, m, of the cavity in the tangential plane. :getter: Returns the tangential plane m-factor (read-only). See Also -------- Cavity.m Cavity.gx """ return self.__compute_m("x") @property def my(self): """The stability, m, of the cavity in the sagittal plane. :getter: Returns the sagittal plane m-factor (read-only). See Also -------- Cavity.m Cavity.gy """ return self.__compute_m("y") @property def g(self): r"""The stability of the cavity, in both planes, given by the :math:`g`-factor: .. math:: g = \frac{A + D + 2}{4}, where :math:`A` and :math:`D` are the relevant entries of the cavity round-trip ABCD matrix. The cavity is stable if the following condition is satisfied: .. math:: 0 \leq g \leq 1. :getter: Returns a :class:`numpy.ndarray` of the cavity stability in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.gx, self.gy]) def __compute_g(self, direction): m = self.__compute_m(direction) return 0.5 * (1 + m) @property def gx(self): """The stability, g, of the cavity in the tangential plane. :getter: Returns the tangential plane g-factor (read-only). See Also -------- Cavity.g Cavity.mx """ return self.__compute_g("x") @property def gy(self): """The stability, g, of the cavity in the sagittal plane. :getter: Returns the sagittal plane g-factor (read-only). See Also -------- Cavity.g Cavity.my """ return self.__compute_g("y") @property def gouy(self): r"""The accumulated round-trip Gouy phase of the cavity in both planes (in degrees). This is given by, .. math:: \psi_{\mathrm{rt}} = 2\,\arccos{\left( \mathrm{sgn}(B) \sqrt{g} \right)}, where :math:`B` is the corresponding element of the round-trip ABCD matrix and :math:`g` is the cavity stability parameter returned by :attr:`Cavity.g`. :getter: Returns a :class:`numpy.ndarray` of the accumulated round-trip Gouy phase in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.gouy_x, self.gouy_y]) def __compute_round_trip_gouy(self, direction, deg=True): ABCD = self.__get_ABCD(direction) B = ABCD[0][1] g = getattr(self, f"g{direction}") psi = 2 * math.acos(sgn(B) * math.sqrt(g)) if deg: return math.degrees(psi) return psi @property def gouy_x(self): """The round-trip Gouy phase in the tangential plane (in degrees). If the cavity is not stable, then ``np.nan`` is returned. :getter: Returns the tangential plane round-trip Gouy phase (read-only). See Also -------- Cavity.gouy """ if not self.is_stable_x: return np.nan return self.__compute_round_trip_gouy("x") @property def gouy_y(self): """The round-trip Gouy phase in the sagittal plane (in degrees). If the cavity is not stable, then ``np.nan`` is returned. :getter: Returns the sagittal plane round-trip Gouy phase (read-only). See Also -------- Cavity.gouy """ if not self.is_stable_y: return np.nan return self.__compute_round_trip_gouy("y") @property def mode_separation(self): r"""The mode separation frequency of the cavity in both planes. This is defined by, .. math:: \delta f = \begin{cases} \frac{\psi_{\mathrm{rt}}}{2\pi} \Delta f, & \text{if } \psi_{\mathrm{rt}} \leq \pi\\ (1 - \frac{\psi_{\mathrm{rt}}}{2\pi}) \Delta f, & \text{if } \psi_{\mathrm{rt}} > \pi, \end{cases} where :math:`\psi_{\mathrm{rt}}` is the accumulated round-trip Gouy phase and :math:`\Delta f` is the FSR of the cavity. :getter: Returns a :class:`numpy.ndarray` of the mode separation frequency in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.mode_separation_x, self.mode_separation_y]) def __compute_mode_separation(self, direction): gouy = self.__compute_round_trip_gouy(direction, deg=False) fsr = self.FSR df = 0.5 * fsr * gouy / np.pi if gouy > np.pi: df = fsr - df return df @property def mode_separation_x(self): """The mode separation frequency in the tangential plane. If the cavity is not stable, then ``np.nan`` is returned. :getter: Returns the tangential plane mode separation frequency (read-only). See Also -------- Cavity.mode_separation """ if not self.is_stable_x: return np.nan return self.__compute_mode_separation("x") @property def mode_separation_y(self): """The mode separation frequency in the sagittal plane. If the cavity is not stable, then ``np.nan`` is returned. :getter: Returns the sagittal plane mode separation frequency (read-only). See Also -------- Cavity.mode_separation """ if not self.is_stable_y: return np.nan return self.__compute_mode_separation("y") @property def S(self): r"""The resolution of the cavity in both planes. Cavity resolution, :math:`S`, is defined by, .. math:: S = \begin{cases} \frac{\psi_{\mathrm{rt}}}{2\pi} \mathcal{F}, & \text{if } \psi_{\mathrm{rt}} \leq \pi\\ (1 - \frac{\psi_{\mathrm{rt}}}{2\pi}) \mathcal{F}, & \text{if } \psi_{\mathrm{rt}} > \pi, \end{cases} where :math:`\psi_{\mathrm{rt}}` is the round-trip Gouy phase and :math:`\mathcal{F}` is the cavity finesse. :getter: Returns a :class:`numpy.ndarray` of the cavity resolution in the tangential and sagittal planes, respectively. (read-only). """ return np.array([self.Sx, self.Sy]) def __compute_resolution(self, direction): gouy = self.__compute_round_trip_gouy(direction, deg=False) f = self.finesse s = 0.5 * f * gouy / np.pi if gouy > np.pi: s = f - s return s @property def Sx(self): """The resolution of cavity in the tangential plane. If the cavity is not stable, then ``np.nan`` is returned. :getter: Returns the tangential plane resolution (read-only). See Also -------- Cavity.S """ if not self.is_stable_x: return np.nan return self.__compute_resolution("x") @property def Sy(self): """The resolution of cavity in the sagittal plane. If the cavity is not stable, then ``np.nan`` is returned. :getter: Returns the sagittal plane resolution (read-only). See Also -------- Cavity.S """ if not self.is_stable_y: return np.nan return self.__compute_resolution("y") ### Stability flags ### @property def is_stable(self): r"""Flag indicating whether the cavity is stable. This only returns `True` if *both* planes of the cavity eigenmode are stable. :getter: Returns `True` if :math:`0 \leq g \leq 1`, `False` otherwise (for both tangential, sagittal planes). See Also -------- Cavity.g """ return self.is_stable_x and self.is_stable_y @property def is_stable_x(self): r"""Flag indicating whether cavity is stable in the tangential plane. :getter: Returns `True` if :math:`0 \leq g_x \leq 1`, `False` otherwise. See Also -------- Cavity.is_stable Cavity.gx """ return 0 < self.gx < 1 @property def is_stable_y(self): r"""Flag indicating whether cavity is stable in the sagittal plane. :getter: Returns `True` if :math:`0 \leq g_y \leq 1`, `False` otherwise. See Also -------- Cavity.is_stable Cavity.gy """ return 0 < self.gy < 1 @property def is_critical(self): r"""Flag indicating whether the cavity is critically stable. This only returns `True` if *both* planes of the cavity eigenmode are critically stable. :getter: Returns `True` if :math:`g = 0` or :math:`g = 1`, `False` otherwise (for both tangential, sagittal planes). See Also -------- Cavity.g """ return self.is_critical_x and self.is_critical_y @property def is_critical_x(self): r"""Flag indicating whether the cavity is critically stable in the tangential plane. :getter: Returns `True` if :math:`g_x = 0` or :math:`g_x = 1`, `False` otherwise. See Also -------- Cavity.is_critical Cavity.gx """ gx = self.gx return gx == 0 or gx == 1 @property def is_critical_y(self): r"""Flag indicating whether the cavity is critically stable in the sagittal plane. :getter: Returns `True` if :math:`g_x = 0` or :math:`g_x = 1`, `False` otherwise. See Also -------- Cavity.is_critical Cavity.gy """ gy = self.gy return gy == 0 or gy == 1 ### Methods for setting up the symbolic equations for each property ### def __symbolise_round_trip_length(self): self._length = 0.0 for comp in self.__path.spaces: self._length += comp.L.ref def __symbolise_FSR(self): self._FSR = constants.C_LIGHT / self._length def __symbolise_loss(self): from finesse.components.general import InteractionType power = 1.0 t = self.__tree while t.left is not None: if t.node.is_input: comp = t.node.component else: comp = t.node.space if isinstance(comp, components.Surface): if ( comp.interaction_type(t.node, t.left.node) == InteractionType.REFLECTION ): power *= comp.R.ref else: power *= comp.T.ref t = t.left # Need to do final reflection from root component power *= self.__tree.node.component.R.ref self._loss = 1.0 - power def __symbolise_finesse(self): _loss = np.sqrt(1.0 - self._loss) self._finesse = 0.5 * np.pi / np.arcsin(0.5 * (1.0 - _loss) / np.sqrt(_loss)) def __symbolise_FWHM(self): self._FWHM = self._FSR / self._finesse def __symbolise_storage_time(self): self._tau = 1 / (np.pi * self._FWHM) def __symbolise_pole(self): self._pole = 0.5 * self._FWHM def __initialise_ABCD(self): self.__tree = ctracer.TraceTree.from_cavity(self) self._ABCDx = np.eye(2) self._ABCDy = np.eye(2) ctracer.update_rt_abcd(self.__tree, self._ABCDx, self._ABCDy) def _get_workspace(self, sim): from finesse.components.modal.cavity import CavityWorkspace return CavityWorkspace(self, sim)
[docs] def initialise(self): """Initialises the symbolic equations of the cavity and calculates the cavity path from the associated model.""" # determine the target node of the cavity path if isinstance(self.source.component, components.Mirror): target = self.source.opposite else: bs = self.source.component if self.source.port is bs.p1: target = bs.p2.i elif self.source.port is bs.p2: target = bs.p1.i elif self.source.port is bs.p3: target = bs.p4.i else: target = bs.p3.i self.__path = self._model.path(self.source, target, self.via) self.__initialise_ABCD() # Set up all the symbolic equations for each property self.__symbolise_round_trip_length() self.__symbolise_loss() self.__symbolise_finesse() self.__symbolise_FSR() self.__symbolise_FWHM() self.__symbolise_storage_time() self.__symbolise_pole() if self.is_fabry_perot: comps = list( filter( lambda x: isinstance(x, components.Surface), self.path.components_only, ) ) M1, M2 = comps[0], comps[-1] R1x, R1y = M1.Rcx.value, M1.Rcy.value R2x, R2y = M2.Rcx.value, M1.Rcy.value if sgn(R1x) == sgn(R2x) and sgn(R1y) == sgn(R2y) and not self.is_stable: LOGGER.warning( "The signs of the radii of curvature of mirrors " "%s and %s in the unstable Fabry-Perot cavity %s are equal.", M1.name, M2.name, self.name, )
[docs] def any_changing_params(self, geometric=False): """Determines whether any parameter of any component inside the cavity is changing. If the optional argument `geometric` is True, then this will only check that the following parameters are changing: - radii of curvature of surfaces, - lengths of spaces, - refractive indices of spaces, - focal lengths of lenses, - angles of incidence of beam splitters. Parameters ---------- geometric : bool If true then only checks parameters which affect ABCD matrices. Returns ------- flag : bool True if any parameter is changing (subject to the condition outlined above), False otherwise. """ geometric_params = ["Rcx", "Rcy", "L", "f", "nr", "alpha"] for comp in self.path.components_only: for param in comp.parameters: if param.is_changing: if geometric and param.name not in geometric_params: continue return True return False
@property def is_changing(self): """Flag indicating whether any geometric parameter inside the cavity is changing. A geometric parameter is defined as one of: - radii of curvature of surfaces, - focal lengths of lenses, - angles of incidence of beam splitters, - lengths of spaces, - refractive indices of spaces, i.e. any parameter which can affect the ABCD matrix values. """ return self.any_changing_params(geometric=True)
[docs] def get_exit_nodes(self): """Obtains a dictionary of `source: target` mappings where source -> target and target is an exit node of the cavity. An exit node is defined to be a node that is not internal to the cavity, rather it is obtained on propagation from an internal node to outside the cavity. Returns ------- exit_nodes : dict A dictionary of `source: target` mappings. """ source_succ = nx.dfs_successors( self._model.optical_network, self.source.full_name ) node_from_name = lambda n: self._model.network.nodes[n]["weakref"]() cav_nodes = self.path.nodes_only exit_nodes = {} for source_name, target_names in source_succ.items(): source = node_from_name(source_name) if source in cav_nodes: for target_name in target_names: target = node_from_name(target_name) if target not in cav_nodes: exit_nodes[source] = target return exit_nodes
[docs] def trace_beam(self): """Traces the cavity eigenmode through the cavity path. Returns ------- out : :class:`.BeamTraceSolution` An object representing the results of the tracing routine. """ from ..solutions import BeamTraceSolution trace = {} wavelength = self.__get_lambda0() self.__tree.trace_beam(trace, wavelength, symmetric=True) return BeamTraceSolution(f"{self.name}_trace", trace, [self.__tree])
[docs] def plot(self, direction=None, *args, **kwargs): """Plots the beam representing the cavity eigenmode over the path of the cavity. See :meth:`.PropagationSolution.plot` if specifying `direction` or :meth:`.AstigmaticPropagationSolution.plot` otherwise. Returns ------- fig : Figure Handle to the figure. axs : axes The axis handles. """ nodes = self.path.nodes_only # From node is always first node of cavity path fn = nodes[0] traversed_ports = set() tn = None for node in nodes: # traverse all the nodes # If port has already been encountered, stop # as we don't want to repeat the same space # (this is applicable to linear cavs only) if node.port in traversed_ports: tn = node break traversed_ports.add(node.port) # If we didn't traverse the same port twice (e.g. this # is a ring or bow-tie type cavity) then set to node as # final node of the cavity path for the round-trip if tn is None: tn = nodes[-1] if direction is None: ps = self._model.propagate_beam_astig(from_node=fn, to_node=tn) else: ps = self._model.propagate_beam( from_node=fn, to_node=tn, direction=direction ) return ps.plot(*args, **kwargs)