Source code for finesse.detectors.optimal_q

"""Single-frequency array of complex amplitudes detector."""

import logging

import numpy as np

from finesse import BeamParam
from finesse.components.node import Node, NodeType
from finesse.detectors.general import Detector
from finesse.exceptions import ConvergenceException, FinesseException
from finesse.parameter import float_parameter, bool_parameter
from finesse.detectors.workspace import DetectorWorkspace
from finesse.gaussian import optimise_HG00_q, optimise_HG00_q_scipy

LOGGER = logging.getLogger(__name__)


[docs]class OptimalQWorkspace(DetectorWorkspace): """Workspace for calculating the output of the optimial beamparameter (q) detectors. Parameters ---------- owner : OptimalQ Detector which owns this workspace sim Simulation this workspace should be created for """ def __init__(self, owner, sim): needs_carrier = False needs_signal = False self.is_f_changing = owner.f.is_changing if owner.f.eval() is None: raise ValueError( f"{owner.f}: frequency value is `None`, check values have been set correctly." ) fval = float(owner.f) fs = [] if sim.carrier: f = sim.carrier.get_frequency_object(fval, owner.node) if f is not None: needs_carrier = True fs.append((f, sim.carrier)) if sim.signal: f = sim.signal.get_frequency_object(fval, owner.node) if f is not None: needs_signal = True fs.append((f, sim.signal)) if len(fs) == 0: raise Exception( f"Error in OptimalQ detector {owner.name}:\n" f" Could not find a frequency bin at {owner.f}" ) elif len(fs) > 1: raise Exception( f"Error in OptimalQ detector {owner.name}:\n" f" Found multiple frequency bins at {owner.f}" ) super().__init__( owner, sim, needs_carrier=needs_carrier, needs_signal=needs_signal ) freq, self.mtx = fs[0] self.idx = self.mtx.field(owner.node, freq.index, 0) self.set_output_fn(self.__output) self.fix_spot_size = bool(owner.fix_spot_size.value) self.astigmatic = bool(owner.astigmatic.value) self.accuracy = owner.accuracy self.direction = owner.direction def __output(self, ws): E = np.asarray( ws.mtx.out_view[ws.idx : (ws.idx + ws.sim.model_settings.num_HOMs)] ) # Directly accessing the node q doesn't work during # a simulation as they are not updated # qx = ws.oinfo.nodes[0].qx # qy = ws.oinfo.nodes[0].qy # Neither does accessing the last trace # qx = ws.sim.model.last_trace[ws.oinfo.nodes[0]].qx # qy = ws.sim.model.last_trace[ws.oinfo.nodes[0]].qy qx, qy = ws.sim.get_q(ws.oinfo.nodes[0]) try: if ws.fix_spot_size or not ws.astigmatic: result = np.asarray( optimise_HG00_q_scipy( E, (qx, qy), ws.sim.model.homs, fix_spot_size=ws.fix_spot_size, astigmatic=ws.astigmatic, accuracy=self.accuracy, ) ) else: result = np.asarray(optimise_HG00_q(E, (qx, qy), ws.sim.model.homs)) except ConvergenceException: q = BeamParam(w0=np.nan, z=np.nan) result = np.array([q, q]) if ws.direction == "both": return result elif ws.direction == "x": return result[0] else: return result[1]
[docs]@float_parameter("f", "Frequency", units="Hz") @bool_parameter( "fix_spot_size", "Fix spot size", units="", ) @bool_parameter( "astigmatic", "Astigmatic", units="", ) class OptimalQ(Detector): """This detector tries to compute an optimal beam parameter (`q`) for a specified optical frequency at a node. Output of this detector into an array solution will be a tuple of :class:`.BeamParam` in each transverse direction, (qx, qy). If the optimisation process fails beam parameter objects will NaN values will be returned. Parameters ---------- name : str Name of the detector node : [str | finesse.components.node] Node name or object to put this detector at f : float Frequency component tro compute the optimal beam parameter for. fix_spot_size : bool, optional When True the optimised will keep the current spot size at the node fixed and just optimise the curvature. astigmatic : bool, optional When True qx and qy will be optimised separately accuracy : float, optional Approximate mismatch accuracy to try and compute the optimised beam parameter to. mismatch(q_actual, q_optimal) < accuracy direction : str, optional Return either `both` or just the `x` or `y` modes Notes ----- This method uses the :meth:`finesse.gaussian.optimise_HG00_q` or :meth:`optimise_HG00_q` for optimising the HG mode amplitudes at the node and frequency requested. This particular method finds a new set of {qx, qy} values which maximise the HG00 mode content, whilst reducing the HG20 and HG02 mode content. .. rubric:: Failure If the optical field being optimised does not have a HG00 like appearance then. For example, trying to optimise the shape of an RF sideband field inside a cavity that it is not resonant in. """ def __init__( self, name, node: Node, f, *, fix_spot_size=False, astigmatic=False, accuracy=1e-6, direction="both", ): if node.type is not NodeType.OPTICAL: raise Exception(f"Must be an optical node used for OptimalQ {name}") Detector.__init__( self, name, node, shape=(2,), dtype=BeamParam, label="Optimal q" ) self.f = f self.fix_spot_size = fix_spot_size self.astigmatic = astigmatic self.accuracy = accuracy self.direction = direction @property def direction(self): """Sets the output of the detector. If `both` then both qx and qy are returned. if `x` or `y` are used then only the required one is returned. """ return self.__direction @direction.setter def direction(self, value): if value not in ("both", "x", "y"): raise FinesseException("Direction should be `both`, `x`, or `y`") self.__direction = value if value == "both": self._update_dtype_shape((2,)) else: self._update_dtype_shape((1,)) def _get_workspace(self, sim): if not sim.is_modal: raise FinesseException( f"OptimalQ detector {self} needs higher order modes to be enabled." ) if (2, 0) not in sim.model.mode_index_map: raise FinesseException( f"OptimalQ detector {self} needs HG20 mode in the simulation" ) if (0, 2) not in sim.model.mode_index_map: raise FinesseException( f"OptimalQ detector {self} needs HG02 mode in the simulation" ) return OptimalQWorkspace(self, sim)