Source code for finesse.components.laser

"""Laser-type optical components for producing beams."""
import logging
import math
import types
import numpy as np

from finesse.cymath.complex import crotate
from finesse.parameter import float_parameter

from finesse.components.general import (
    Connector,
    FrequencyGenerator,
    NoiseType,
    DOFDefinition,
)
from finesse.components.node import NodeType, NodeDirection

LOGGER = logging.getLogger(__name__)


[docs]@float_parameter("P", "Power", units="W") @float_parameter("phase", "Phase", units="degrees") @float_parameter("f", "Frequency", units="Hz") class Laser(Connector, FrequencyGenerator): """Represents a laser producing a beam with associated properties such as power and frequency. Parameters ---------- name : str Name of the newly created laser. P : float, optional Power of the laser (in Watts), defaults to 1 W. f : float or :class:`.Frequency`, optional Frequency-offset of the laser from the default (in Hz) or :class:`.Frequency` object. Defaults to 0 Hz offset. phase : float, optional Phase-offset of the laser from the default, defaults to zero. """ _DEFAULT_POWER_COEFFS = {(0, 0): (1.0, 0.0)}
[docs] def __init__(self, name, P=1, f=0, phase=0): Connector.__init__(self, name) FrequencyGenerator.__init__(self) self._add_port("p1", NodeType.OPTICAL) self.p1._add_node("i", NodeDirection.INPUT) self.p1._add_node("o", NodeDirection.OUTPUT) # Modulation inputs self._add_port("amp", NodeType.ELECTRICAL) self.amp._add_node("i", NodeDirection.INPUT) self._add_port("phs", NodeType.ELECTRICAL) self.phs._add_node("i", NodeDirection.INPUT) self._add_port("frq", NodeType.ELECTRICAL) self.frq._add_node("i", NodeDirection.INPUT) self._register_node_coupling("SIGAMP_P1o", self.amp.i, self.p1.o) self._register_node_coupling("SIGPHS_P1o", self.phs.i, self.p1.o) self._register_node_coupling("SIGFRQ_P1o", self.frq.i, self.p1.o) self.f = f self.P = P self.phase = phase self.__power_coeffs = self._DEFAULT_POWER_COEFFS.copy() # Define typical degrees of freedom for this component self.dofs = types.SimpleNamespace() self.dofs.amp = DOFDefinition(self.P, self.amp.i, 1) self.dofs.phs = DOFDefinition(self.phase, self.phs.i, 1) self.dofs.frq = DOFDefinition(self.f, self.frq.i, 1)
def _source_frequencies(self): return [self.f.ref] @property def power_coeffs(self): """The relative power factors and phase offsets for each HGnm mode. :getter: Returns the mode factors and phase offsets as a dict with the mode indices as keys. Read-only. """ return self.__power_coeffs.copy() @property def non_default_power_coeffs(self): """The power factors and phase offsets excluding the default. :getter: Returns the non-default mode factors and phase offsets as a dict with the mode indices as keys. Read-only. """ coeffs = self.power_coeffs default_coeffs = self._DEFAULT_POWER_COEFFS if default_coeffs.items() <= coeffs.items(): # The current coefficients contain the defaults, so remove them. for key in default_coeffs: coeffs.pop(key) return coeffs
[docs] def tem(self, n, m, factor, phase=0.0): """Distributes power into the mode HGnm. .. note:: This does not change the total power of the laser, rather, it re-distributes this power into / out of the specified mode. Parameters ---------- n, m : int Mode indices. factor : number Relative power factor, modes with equal `factor` will have equivalent power distributed to them. phase : number, optional; default = 0 Phase offset for the field, in degrees. """ self.__power_coeffs[(n, m)] = factor, phase
def __find_src_freq(self, sim): # if it's tunable we want to look for the symbol that is just this # lasers frequency, as it will be changing for f in sim.optical_frequencies.frequencies: if not self.f.is_changing: # Don't match changing frequency bins if ours won't match if not f.symbol.is_changing and ( f.f == self.f.value # match potential param refs or f.f == float(self.f.value) # match numeric values ): # If nothing is changing then we can just match freq values return f else: # If our frequency is changing then we have to have a frequency bin that # matches our symbol if f.symbol == self.f.ref: return f # Simple case return None def _get_workspace(self, sim): from finesse.components.modal.laser import ( laser_carrier_fill_rhs, laser_fill_signal, laser_fill_qnoise, LaserWorkspace, laser_set_gouy, ) ws = LaserWorkspace(self, sim, True) ws.node_car_id = sim.carrier.node_id(self.p1.o) ws.fsrc_car_idx = -1 ws.set_gouy_function(laser_set_gouy) # Carrier just fills RHS ws.carrier.set_fill_rhs_fn(laser_carrier_fill_rhs) fsrc = self.__find_src_freq(sim.carrier) # Didn't find a Frequency bin for this laser in carrier simulation if fsrc is None: raise Exception(f"Could not find a frequency bin at {self.f} for {self}") ws.fsrc_car_idx = fsrc.index if sim.is_modal: scaling = 0 ws.power_coeffs = np.zeros(sim.model_data.num_HOMs, dtype=np.complex128) coeffs = self.power_coeffs for i in range(sim.model_data.num_HOMs): n = sim.model_data.homs_view[i][0] m = sim.model_data.homs_view[i][1] try: factor, phase = coeffs.pop((n, m)) except KeyError: factor = phase = 0 ws.power_coeffs[i] = crotate( complex(math.sqrt(factor), 0), math.radians(phase) ) scaling += abs(ws.power_coeffs[i]) ** 2 if not scaling: raise RuntimeError( f"No power in any modes of {self.name}! At least one mode " "must have a non-zero power factor applied to it." ) for i in range(sim.model_data.num_HOMs): ws.power_coeffs[i] /= np.sqrt(scaling) if coeffs: LOGGER.warning( "The following modes, included in the coeffs of %s, are not being " "modelled and will be ignored: %s", self.name, list(coeffs.keys()), ) if sim.signal: ws.node_sig_id = sim.signal.node_id(self.p1.o) # Audio sim requies matrix filling # for signal couplings ws.signal.add_fill_function(laser_fill_signal, True) # TODO sort out refill flag here ws.signal.set_fill_noise_function(NoiseType.QUANTUM, laser_fill_qnoise) # Find the sideband frequencies sb = tuple( ( f for f in sim.signal.optical_frequencies.frequencies if f.audio_carrier_index == fsrc.index ) ) if len(sb) != 2: raise Exception( f"Only something other than two audio sidebands {sb} for carrier {fsrc}" ) ws.fcar_sig_sb_idx = (sb[0].index, sb[1].index) # if sim.is_modal: self._update_tem_gouy_phases(sim) return ws def _couples_frequency(self, ws, connection, frequency_in, frequency_out): # The only connections we have are signal inputs to optical output # And all the inputs should generate any output. return True