laser.py 5.58 KB
Newer Older
1 2 3
"""
Laser-type optical components for producing beams.
"""
4
import math
5

6 7
import numpy as np

8
from finesse.cymath.complex import crotate
9
from finesse.parameter import model_parameter, Rebuild
10 11 12

from finesse.components.general import Connector, FrequencyGenerator
from finesse.components.node import NodeType, NodeDirection
13

14

15 16 17 18 19
@model_parameter("P", "Power", 1, Rebuild.PlaneWave, units="W")
@model_parameter("phase", "Phase", 0, Rebuild.PlaneWave, units="degrees")
@model_parameter(
    "f", "Frequency", 0, Rebuild.Frequencies & Rebuild.PlaneWave, units="Hz"
)
20
class Laser(Connector, FrequencyGenerator):
21
    """Represents a laser producing a beam with associated properties such as power and frequency.
Samuel Rowlinson's avatar
Samuel Rowlinson committed
22

23 24 25 26
    Parameters
    ----------
    name : str
        Name of the newly created laser.
27

28 29
    P : float, optional
        Power of the laser (in Watts), defaults to 1 W.
30

31 32 33
    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.
34

35 36 37
    phase : float, optional
        Phase-offset of the laser from the default, defaults to zero.
    """
38

39
    def __init__(self, name, P=1, f=0, phase=0):
40
        Connector.__init__(self, name)
Daniel Brown's avatar
Daniel Brown committed
41
        FrequencyGenerator.__init__(self)
42

Samuel Rowlinson's avatar
Samuel Rowlinson committed
43
        self._add_port("p1", NodeType.OPTICAL)
44 45
        self.p1._add_node("i", NodeDirection.INPUT)
        self.p1._add_node("o", NodeDirection.OUTPUT)
46 47

        # Modulation inputs
Daniel Brown's avatar
Daniel Brown committed
48 49 50 51 52 53 54 55 56 57
        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)
Phil Jones's avatar
Phil Jones committed
58

59
        self.f = f
60 61
        self.P = P
        self.phase = phase
Samuel Rowlinson's avatar
Samuel Rowlinson committed
62
        self.__power_coeffs = {(0, 0): 1.0 + 0j}
63

Daniel Brown's avatar
Daniel Brown committed
64 65
    def _source_frequencies(self):
        return [self.f.ref]
66

67 68 69
    @property
    def power_coeffs(self):
        return self.__power_coeffs
70

71 72
    def tem(self, n, m, factor, phase=0.0):
        if not self._model.is_modal:
73
            raise ValueError(f"model is not modal, cannot set TEMs for laser: {self!r}")
74

75
        self.__power_coeffs[(n, m)] = crotate(
Samuel Rowlinson's avatar
Samuel Rowlinson committed
76 77
            complex(math.sqrt(factor), 0), math.radians(phase)
        )
78

Daniel Brown's avatar
Daniel Brown committed
79 80 81
    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
82
        for f in sim.optical_frequencies.frequencies:
83 84
            if not self.f.is_changing:
                # Don't match changing frequency bins if ours won't match
85 86 87 88
                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
                ):
89 90 91 92 93 94 95
                    # 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
Daniel Brown's avatar
Daniel Brown committed
96 97
        return None

98
    def _get_workspace(self, sim):
99 100 101 102
        from finesse.components.modal.laser import (
            laser_carrier_fill_rhs,
            laser_fill_signal,
            LaserWorkspace,
103
            laser_set_gouy
104
        )
105
        ws = LaserWorkspace(self, sim, True)
106
        ws.node_car_id = sim.carrier.node_id(self.p1.o)
Daniel Brown's avatar
Daniel Brown committed
107
        ws.fsrc_car_idx = -1
108 109
        
        ws.set_gouy_function(laser_set_gouy)
110 111
        # Carrier just fills RHS
        ws.carrier.set_fill_rhs_fn(laser_carrier_fill_rhs)
112

113 114 115 116 117
        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
Daniel Brown's avatar
Daniel Brown committed
118

119 120 121 122 123 124
        if sim.is_modal:
            scaling = 0
            ws.power_coeffs = np.zeros(sim.model_data.num_HOMs, dtype=np.complex128)
            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]
125

126 127
                ws.power_coeffs[i] = self.power_coeffs.get((n, m), 0j)
                scaling += abs(ws.power_coeffs[i]) ** 2
128

129 130 131 132 133
            for i in range(sim.model_data.num_HOMs):
                ws.power_coeffs[i] /= np.sqrt(scaling)

        if sim.signal:
            ws.node_sig_id = sim.signal.node_id(self.p1.o)
Daniel Brown's avatar
Daniel Brown committed
134 135
            # Audio sim requies matrix filling
            # for signal couplings
Daniel Brown's avatar
Daniel Brown committed
136
            ws.signal.set_fill_function(laser_fill_signal)
137
            # Find the sideband frequencies
Daniel Brown's avatar
Daniel Brown committed
138
            sb = tuple(
Daniel Brown's avatar
Daniel Brown committed
139
                (f for f in sim.signal.optical_frequencies.frequencies if f.audio_carrier_index == fsrc.index)
Daniel Brown's avatar
Daniel Brown committed
140 141 142 143 144 145
            )
            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)
146

Samuel Rowlinson's avatar
Samuel Rowlinson committed
147
        # if sim.is_modal: self._update_tem_gouy_phases(sim)
148
        return ws
149

150
    def _couples_frequency(self, ws, connection, frequency_in, frequency_out):
151 152 153
        # The only connections we have are signal inputs to optical output
        # And all the inputs should generate any output.
        return True