diff --git a/playground/micro_resonator.py b/playground/micro_resonator.py index c88d8a2e7856ff2cf65562b1b0d3e85cf9d73537..a1fed954844c33fcfb40c5eddc779d5d80d255c2 100644 --- a/playground/micro_resonator.py +++ b/playground/micro_resonator.py @@ -46,25 +46,26 @@ class MicroResonator(Connector): self.mech.F_z.frequencies = self.frequencies def _get_workspace(self, sim): - if sim.is_audio: + if sim.carrier.any_frequencies_changing: + # ddb - This case probably needs some more thought on what beatings change + raise NotImplementedError( + "Changing carrier frequencies whilst using a MicroResonant not supported yet." + ) + + if sim.signal: refill = ( sim.model.fsig.f.is_changing or self.mass.is_changing or self.f.is_changing or self.Q.is_changing ) - ws = MicroResonatorWorkspace(self, sim, refill) - ws.set_fill_fn(self.fill) - ws.Fz_frequencies = sim.mechanical_frequencies[self.mech.F_z] - ws.z_frequencies = sim.mechanical_frequencies[self.mech.z] + ws = MicroResonatorWorkspace(self, sim, refill, refill) + ws.signal.set_fill_function(self.fill) + ws.Fz_frequencies = sim.signal.mechanical_frequencies[self.mech.F_z] + ws.z_frequencies = sim.signal.mechanical_frequencies[self.mech.z] return ws - else: - if sim.any_frequencies_changing: - # ddb - This case probably needs some more thought on what beatings change - raise NotImplementedError( - "Changing carrier frequencies whilst using a MicroResonant not supported yet." - ) - return None + + return None def _couples_frequency(self, ws, connection, frequency_in, frequency_out): return True @@ -88,7 +89,7 @@ class MicroResonator(Connector): for fi in ws.Fz_frequencies.frequencies: for fo in ws.z_frequencies.frequencies: if fi.f == fo.f: - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.connections.F_2_Z_idx, fi.index, fo.index ) as mat: mat[:] = Hz(2 * np.pi * fo.f) diff --git a/src/finesse/analysis/actions.py b/src/finesse/analysis/actions.py index 486a84a37a9cc6ba83705c7583759ceb79b891dc..1e3d284a61b258deb18c8470ba32a52706e06a60 100644 --- a/src/finesse/analysis/actions.py +++ b/src/finesse/analysis/actions.py @@ -40,9 +40,10 @@ def get_sweep_array(start: float, stop: float, steps: int, mode="lin"): class ActionWorkspace: - def __init__(self, s_prev, model): + def __init__(self, s_prev, sim): self.s_prev = s_prev - self.model = model + self.sim = sim + self.model = sim.model self.fn_do = None @@ -89,9 +90,9 @@ class Action: def fill_info(self, p_info): p_info.add(self.copy_info()) - def setup(self, s_prev, model): + def setup(self, s_prev, sim): # By default simple actions should just run their 'do' method - ws = ActionWorkspace(s_prev, model) + ws = ActionWorkspace(s_prev, sim) ws.fn_do = self.do return ws @@ -112,10 +113,10 @@ class Action: p.is_tunable = True initial_param_values[p] = p.value - with model.built(): + with model.built() as sim: try: s = BaseSolution(None, None) - ws = self.setup(s, model) + ws = self.setup(s, sim) ws.fn_do(ws) except StopIteration: raise Exception("Should we reach this point? Probably unexpected") @@ -185,8 +186,7 @@ class ABCD(Action): class StepParamNDWorkspace(ActionWorkspace): - def __init__(self): - pass + pass class StepParamND(Action): @@ -227,47 +227,37 @@ class StepParamND(Action): if self.on_complete: Folder("on_complete", self.on_complete).fill_info(info) - def setup(self, s_prev, model: finesse.model.Model): - ws = StepParamNDWorkspace() - ws.s_prev = s_prev - ws.model = model + def setup(self, s_prev, sim): + ws = StepParamNDWorkspace(s_prev, sim) ws.info = self.copy_info() ws.fn_do = self.do - ws.params = tuple(get_param(model, p) for p in self._info.parameters_changing) + ws.params = tuple(get_param(ws.model, p) for p in self._info.parameters_changing) for p in ws.params: if not p.is_tunable: raise ParameterLocked( f"{repr(p)} must set as tunable " "before building the simulation" ) - - ws.carrier = model.carrier_simulation - try: - ws.signal = model.signal_simulation - except AttributeError: - ws.signal = None - return ws def do(self, ws: StepParamNDWorkspace): ws.sol = ArraySolution( - self.name, ws.s_prev, ws.model, self.out_shape, self.axes, ws.params + self.name, ws.s_prev, ws.sim.detector_workspaces, self.out_shape, self.axes, ws.params ) if self.pre_step: - ws.pre_step = Folder("pre_step", self.pre_step).setup(ws.sol, ws.model) + ws.pre_step = Folder("pre_step", self.pre_step).setup(ws.sol, ws.sim) else: ws.pre_step = None if self.post_step: - ws.post_step = Folder("post_step", self.post_step).setup(ws.sol, ws.model) + ws.post_step = Folder("post_step", self.post_step).setup(ws.sol, ws.sim) else: ws.post_step = None # Now we loop over the actual simulation and run each point run_axes_scan( - ws.carrier, - ws.signal, + ws.sim, self.axes, ws.params, self.offsets, @@ -278,7 +268,7 @@ class StepParamND(Action): ) if self.on_complete: - ws = Folder("on_complete", self.on_complete).setup(ws.sol, ws.model) + ws = Folder("on_complete", self.on_complete).setup(ws.sol, ws.sim) ws.fn_do(ws) @@ -319,29 +309,32 @@ class XNaxis(StepParamND): on_complete=on_complete, ) - def setup(self, s_prev, model): + def setup(self, s_prev, sim): # If the model has locks, set them up to happen on the # pre-step of StepParamND action - if len(model.locks) > 0: - self.pre_step = RunLocks(*model.locks) + if len(sim.model.locks) > 0: + self.pre_step = RunLocks(*sim.model.locks) - return super().setup(s_prev, model) + return super().setup(s_prev, sim) def do(self, ws: StepParamNDWorkspace): - model = ws.model.deepcopy() + model = ws.sim.model.deepcopy() params = ( get_param(model, pstr) for pstr in ws.info.get_all_parameters_changing() ) - for p in params: - p.is_tunable = True + try: + for p in params: + p.is_tunable = True - with model.built(): - super().do(ws) + with model.built(): + super().do(ws) + + finally: + for p in params: + p.is_tunable = False - for p in params: - p.is_tunable = False def __getattr__(self, key): res = re.match("(parameter|mode|start|stop|steps|offset)([0-9]*)", key) @@ -524,15 +517,15 @@ class Serial(Action): if len(p_info.children) == 0: raise Exception("Analysis information object should have been made") - def setup(self, s_prev, model): - ws = ActionWorkspace(s_prev, model) + def setup(self, s_prev, sim): + ws = ActionWorkspace(s_prev, sim) ws.wss = [] ws.fn_do = self.do curr_children = len(ws.s_prev.children) # Here we get workspaces for each of the # actions we need to run for arg in self.args: - ws.wss.append(arg.setup(s_prev, model)) + ws.wss.append(arg.setup(s_prev, sim)) if len(ws.s_prev.children) > curr_children: # If a solution was made and added as a child in the previous coroutine # then that becomes the next solution in the serial chain @@ -556,10 +549,10 @@ class Folder(Action): self.action.fill_info(info) self.folder = None - def setup(self, s_prev, model): - ws = ActionWorkspace(s_prev, model) + def setup(self, s_prev, sim): + ws = ActionWorkspace(s_prev, sim) ws.folder = BaseSolution(self.name, ws.s_prev) - ws.action_ws = self.action.setup(ws.folder, model) + ws.action_ws = self.action.setup(ws.folder, sim) ws.fn_do = self.do return ws @@ -704,24 +697,23 @@ class RunLocks(Action): def fill_info(self, p_info): p_info.add(self.copy_info()) - def setup(self, s_prev, model: finesse.model.Model): - ws = RunLocksWorkspace(s_prev, model) - ws.locks = tuple(model.elements[l] for l in self.locks) + def setup(self, s_prev, sim): + ws = RunLocksWorkspace(s_prev, sim) + ws.locks = tuple(sim.model.elements[l] for l in self.locks) ws.det_ws = [None,] * self.num_locks for i, l in enumerate(ws.locks): - for j, d in enumerate(model.detectors): + for j, d in enumerate(sim.model.detectors): if l.error_signal.name == d.name: - ws.det_ws[i] = model._detector_workspaces[j] + ws.det_ws[i] = sim.detector_workspaces[j] if any(_ is None for _ in ws.det_ws): raise Exception("Could not find detector workspaces for all locks") ws.info = self.copy_info() ws.s_prev = s_prev - ws.sim = model.carrier_simulation ws.fn_do = do_lock - ws.params = tuple(get_param(model, p) for p in self._info.parameters_changing) + ws.params = tuple(get_param(sim.model, p) for p in self._info.parameters_changing) ws.max_iterations = self.max_iterations for p in ws.params: @@ -737,7 +729,7 @@ def do_lock(ws: RunLocksWorkspace): iters = 0 while recompute and iters < ws.max_iterations: iters += 1 - ws.sim.run(True) + ws.sim.run_carrier() recompute = False for i, dws in enumerate(ws.det_ws): acc = ws.locks[i].accuracy diff --git a/src/finesse/analysis/runners.pyx b/src/finesse/analysis/runners.pyx index 5cc8c74d3a83377163692f67a7d2bb4252680032..4b92aeda57ba006162702bf2eeba954a5790dc0c 100644 --- a/src/finesse/analysis/runners.pyx +++ b/src/finesse/analysis/runners.pyx @@ -8,7 +8,7 @@ from cpython.ref cimport PyObject from finesse.cymath cimport complex_t from finesse.parameter cimport Parameter -from finesse.simulations cimport BaseSimulation +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation from finesse.solutions.array import ArraySolution from finesse.solutions.array cimport ArraySolution @@ -18,8 +18,7 @@ LOGGER = logging.getLogger(__name__) @cython.wraparound(False) @cython.initializedcheck(False) cpdef run_fsig_sweep( - BaseSimulation carrier, - BaseSimulation signal, + CarrierSignalMatrixSimulation sim, double[::1] axis, long[::1] input_rhs_indices, long[::1] output_rhs_indices, @@ -39,7 +38,7 @@ cpdef run_fsig_sweep( int No = len(output_rhs_indices) int i, o, j complex_t denom - Parameter f = signal.model.fsig.f + Parameter f = sim.model.fsig.f if out is None: out = np.zeros((Na, Ni, No), dtype=np.complex_128) @@ -48,36 +47,39 @@ cpdef run_fsig_sweep( assert(out.shape[1] == Na) assert(out.shape[2] == No) + # We'll be making our own RHS inputs for this simulation + sim.signal.manual_rhs = True + for i in range(Ni): for j in range(Na): f.set_double_value(axis[j]) - signal.model_data.fsig = axis[j] - signal._clear_rhs() - signal.set_source_fast_2( + sim.signal.model_data.fsig = axis[j] + sim.signal.clear_rhs() + sim.signal.set_source_fast_2( input_rhs_indices[i], 1 ) - signal.run(False) + sim.signal.run() if not compute_open_loop: for o in range(No): - out[i][j][o] = signal.out_view[output_rhs_indices[o]] + out[i][j][o] = sim.signal.out_view[output_rhs_indices[o]] else: for o in range(No): # We can divide out the 1/(1-H) closed loop behaviours by # using the coupling computed back into the same input node - denom = signal.out_view[input_rhs_indices[i]] + denom = sim.signal.out_view[input_rhs_indices[i]] if denom.real == denom.imag == 0: out[i][j][o] = 0 else: - out[i][j][o] = signal.out_view[output_rhs_indices[o]] / denom + out[i][j][o] = sim.signal.out_view[output_rhs_indices[o]] / denom + sim.signal.manual_rhs = False return out @cython.boundscheck(False) @cython.wraparound(False) @cython.initializedcheck(False) def run_axes_scan( - BaseSimulation carrier, - BaseSimulation signal, + CarrierSignalMatrixSimulation sim, tuple axes, tuple params, double[:] offsets, @@ -151,11 +153,10 @@ def run_axes_scan( # ------------------------------------------------------ # DO STEP # ------------------------------------------------------ - mask_this_point = carrier.run(True) + mask_this_point = sim.run_carrier() - if not mask_this_point and signal is not None: - signal.model_data.fsig = signal.model.fsig.f.value - mask_this_point = signal.run(True) + if not mask_this_point and sim.signal: + sim.run_signal() # ------------------------------------------------------ # POST STEP # ------------------------------------------------------ diff --git a/src/finesse/components/beamsplitter.py b/src/finesse/components/beamsplitter.py index de147553e729a878424e1d7f60dfc8e9aa8ba815..a2dfbcab496a129fd92d1dda842c43aa4f77d520 100644 --- a/src/finesse/components/beamsplitter.py +++ b/src/finesse/components/beamsplitter.py @@ -586,16 +586,17 @@ class Beamsplitter(Surface): def _get_workspace(self, sim): from finesse.components.modal.beamsplitter import ( - beamsplitter_fill, + beamsplitter_carrier_fill, + beamsplitter_signal_fill, BeamsplitterWorkspace, ) _, is_changing = self._eval_parameters() refill = ( - (sim.is_audio and sim.model.fsig.f.is_changing) + (sim.signal and sim.model.fsig.f.is_changing) or self in sim.trace_forest.changing_components - or sim.any_frequencies_changing + or sim.carrier.any_frequencies_changing or (len(is_changing) and is_changing.issubset(self.__changing_check)) ) @@ -604,7 +605,8 @@ class Beamsplitter(Surface): ws.nr1 = refractive_index(self.p1) or refractive_index(self.p2) or 1 ws.nr2 = refractive_index(self.p3) or refractive_index(self.p4) or 1 - ws.set_fill_fn(beamsplitter_fill) + ws.carrier.set_fill_function(beamsplitter_carrier_fill) + ws.signal.set_fill_function(beamsplitter_signal_fill) # Initialise the ABCD matrix memory-views if sim.is_modal: @@ -650,24 +652,4 @@ class Beamsplitter(Surface): ws.abcd_p4p2_x = self.ABCD(self.p4.i, self.p2.o, "x", copy=False) ws.abcd_p4p2_y = self.ABCD(self.p4.i, self.p2.o, "y", copy=False) - return ws - - def _fill_qnoise_rhs(self, sim): - ws = sim.ws[self] - if sim.is_modal: - for freq in sim.frequencies: - for k in range(sim.nhoms): - L1 = self.R * ws.K21_loss[k] + self.T * ws.K31_loss[k] - L2 = self.R * ws.K12_loss[k] + self.T * ws.K42_loss[k] - L3 = self.R * ws.K43_loss[k] + self.T * ws.K13_loss[k] - L4 = self.R * ws.K34_loss[k] + self.T * ws.K24_loss[k] - # TODO, can't access matrix like this! - sim.Mq[sim.field(self.p1.o, freq.index, k)] = (L1 + self.L) / 2 - sim.Mq[sim.field(self.p2.o, freq.index, k)] = (L2 + self.L) / 2 - sim.Mq[sim.field(self.p3.o, freq.index, k)] = (L3 + self.L) / 2 - sim.Mq[sim.field(self.p4.o, freq.index, k)] = (L4 + self.L) / 2 - else: - for freq in sim.frequencies: - # TODO, can't access matrix like this! - sim.Mq[sim.field(self.p1.o, freq.index, 0)] = self.L / 2 - sim.Mq[sim.field(self.p2.o, freq.index, 0)] = self.L / 2 + return ws \ No newline at end of file diff --git a/src/finesse/components/directional_beamsplitter.py b/src/finesse/components/directional_beamsplitter.py index cd6ed47695d6cb4d1c4ca2a2275b05ed107c5d1b..33147bbc8b9c8a2a1e1ef1ffa7b79060631fc518 100644 --- a/src/finesse/components/directional_beamsplitter.py +++ b/src/finesse/components/directional_beamsplitter.py @@ -75,8 +75,8 @@ class DirectionalBeamsplitter(Connector): def _get_workspace(self, sim): ws = DBSWorkspace(self, sim, False) - ws.I = np.eye(sim.nhoms, dtype=np.complex128) - ws.set_fill_fn(self._fill_matrix) + ws.I = np.eye(sim.model_data.num_HOMs, dtype=np.complex128) + ws.set_fill_function(self._fill_matrix) return ws def _fill_matrix(self, ws): diff --git a/src/finesse/components/dof.py b/src/finesse/components/dof.py index 7f160f2f998a2a9532554aa862fae60ad3ddb0b6..88cb0b9dcc2e93cf40873119ce7722e2da94274f 100644 --- a/src/finesse/components/dof.py +++ b/src/finesse/components/dof.py @@ -72,9 +72,9 @@ class DegreeOfFreedom(Connector): return self.__amplitudes def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: ws = DOFWorkspace(self, sim, False) - ws.set_fill_fn(self.__fill) + ws.set_fill_function(self.__fill) ws.drives = self.drives ws.amplitudes = self.amplitudes return ws diff --git a/src/finesse/components/electronics.py b/src/finesse/components/electronics.py index 8606ab02ee63ef837d2954691ce68468e29e0937..c9854c609ed75aed3eb4f25e007db0004726b48c 100644 --- a/src/finesse/components/electronics.py +++ b/src/finesse/components/electronics.py @@ -34,12 +34,12 @@ class ZPKNodeActuator(Connector): self._register_node_coupling("P1_ACT", self.p1.i, mechanical_node) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: refill = sim.model.fsig.f.is_changing or any( p.is_changing for p in self.parameters ) - ws = FilterWorkspace(self, sim, refill) - ws.set_fill_fn(self.fill) + ws = FilterWorkspace(self, sim, refill, refill) + ws.signal.set_fill_function(self.fill) ws.frequencies = sim.electrical_frequencies[self.p1.i].frequencies return ws else: @@ -48,7 +48,7 @@ class ZPKNodeActuator(Connector): def fill(self, ws): for _ in ws.frequencies: # Assumes only couples to first mech frequency - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.connections.P1_ACT_idx, _.index, 0, ) as mat: mat[:] = ws.values.gain @@ -69,12 +69,12 @@ class Amplifier(Connector): self._register_node_coupling("P1_P2", self.p1.i, self.p2.o) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: refill = sim.model.fsig.f.is_changing or any( p.is_changing for p in self.parameters ) - ws = FilterWorkspace(self, sim, refill) - ws.set_fill_fn(self.fill) + ws = FilterWorkspace(self, sim, refill, refill) + ws.signal.set_fill_function(self.fill) ws.frequencies = sim.electrical_frequencies[self.p1.i].frequencies return ws else: @@ -82,7 +82,7 @@ class Amplifier(Connector): def fill(self, ws): for _ in ws.frequencies: - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.connections.P1_P2_idx, 0, 0, ) as mat: mat[:] = ws.values.gain @@ -106,13 +106,13 @@ class Filter(Connector): self._register_node_coupling("P1_P2", self.p1.i, self.p2.o) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: refill = sim.model.fsig.f.is_changing or any( p.is_changing for p in self.parameters ) - ws = FilterWorkspace(self, sim, refill) - ws.set_fill_fn(self.fill) - ws.frequencies = sim.electrical_frequencies[self.p1.i].frequencies + ws = FilterWorkspace(self, sim, refill, refill) + ws.signal.set_fill_function(self.fill) + ws.frequencies = sim.signal.electrical_frequencies[self.p1.i].frequencies return ws else: return None @@ -121,7 +121,7 @@ class Filter(Connector): Hz = self.eval(ws.sim.model_data.fsig) for _ in ws.frequencies: - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, ws.connections.P1_P2_idx, 0, 0, ) as mat: mat[:] = Hz diff --git a/src/finesse/components/isolator.py b/src/finesse/components/isolator.py index af664f5546bacd27a3bf616e2c9c26c4205d4b45..238084bee5a4a02e9c77b0437733344d938d1403 100644 --- a/src/finesse/components/isolator.py +++ b/src/finesse/components/isolator.py @@ -66,7 +66,7 @@ class Isolator(Connector): ws.nr1 = refractive_index(self.p1) or 1 ws.nr2 = refractive_index(self.p2) or 1 - ws.set_fill_fn(self._fill_matrix) + ws.set_fill_function(self._fill_matrix) return ws def _fill_matrix(self, ws): diff --git a/src/finesse/components/laser.py b/src/finesse/components/laser.py index c11ec13358d71c5a3ad95f67569285040f4e5f7b..205d627830dbd1500287db6a89a3e7dac6b53ca5 100644 --- a/src/finesse/components/laser.py +++ b/src/finesse/components/laser.py @@ -9,11 +9,6 @@ from finesse.cymath.complex import crotate from finesse.parameter import model_parameter, Rebuild from finesse.components.general import Connector, FrequencyGenerator -from finesse.components.modal.laser import ( - laser_fill_rhs, - laser_fill_signal, - LaserWorkspace, -) from finesse.components.node import NodeType, NodeDirection @@ -84,7 +79,7 @@ class Laser(Connector, FrequencyGenerator): 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.frequencies: + 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 ( @@ -101,48 +96,47 @@ class Laser(Connector, FrequencyGenerator): return None def _get_workspace(self, sim): + from finesse.components.modal.laser import ( + laser_carrier_fill_rhs, + laser_fill_signal, + LaserWorkspace, + laser_set_gouy + ) ws = LaserWorkspace(self, sim, True) - ws.node_id = sim.node_id(self.p1.o) + ws.node_car_id = sim.carrier.node_id(self.p1.o) ws.fsrc_car_idx = -1 - - if not sim.is_audio: - # Carrier just fills RHS - ws.set_fill_rhs_fn(laser_fill_rhs) - fsrc = self.__find_src_freq(sim) - # 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) - 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] - - ws.power_coeffs[i] = self.power_coeffs.get((n, m), 0j) - scaling += abs(ws.power_coeffs[i]) ** 2 - - for i in range(sim.model_data.num_HOMs): - ws.power_coeffs[i] /= np.sqrt(scaling) - - else: - ws.node_id_car = sim.DC.node_id(self.p1.o) + + 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) + 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] + + ws.power_coeffs[i] = self.power_coeffs.get((n, m), 0j) + scaling += abs(ws.power_coeffs[i]) ** 2 + + 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) # Audio sim requies matrix filling # for signal couplings - ws.set_fill_fn(laser_fill_signal) - fsrc = self.__find_src_freq(sim.DC) - # 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 in carrier sim at {self.f} for {self}" - ) - ws.fsrc_car_idx = fsrc.index + ws.signal.set_fill_function(laser_fill_signal) + # Find the sideband frequencies sb = tuple( - (f for f in sim.frequencies if f.audio_carrier_index == fsrc.index) + (f for f in sim.signal.optical_frequencies.frequencies if f.audio_carrier_index == fsrc.index) ) if len(sb) != 2: raise Exception( @@ -153,16 +147,6 @@ class Laser(Connector, FrequencyGenerator): # if sim.is_modal: self._update_tem_gouy_phases(sim) return ws - def _fill_qnoise_rhs(self, sim): - for freq in sim.frequencies: - for hom in range(sim.nhoms): - idx = sim.field(self.p1.o, freq.index, hom) - # TODO: this should be max(UNIT_VACUUM, self.noise) once/if - # laser noise attribute is implemented - sim.Mq[idx] = ( - sim.model._UNIT_VACUUM / 2 * (1 + freq.carrier.f / sim.model.f0) - ) - 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. diff --git a/src/finesse/components/lens.py b/src/finesse/components/lens.py index 57cd2258ac479f85baf4bf25acffb320144e8b18..e8aa3f2e4fd3eca6760237f15f209d9b1a8074d2 100644 --- a/src/finesse/components/lens.py +++ b/src/finesse/components/lens.py @@ -173,33 +173,45 @@ class Lens(Connector): if "y" in direction: # TODO (sjr) Change to fy self.f = fnew(self.f.value) - def _fill_matrix(self, ws): - for freq in ws.sim.frequencies: - with ws.sim.component_edge_fill3( - ws.owner_id, ws.connections.P1i_P2o_idx, freq.index, freq.index, + def _fill_optical_matrix(self, ws, matrix, connections): + for freq in matrix.optical_frequencies.frequencies: + with matrix.component_edge_fill3( + ws.owner_id, connections.P1i_P2o_idx, freq.index, freq.index, ) as mat: mat[:] = ws.K12.data - with ws.sim.component_edge_fill3( - ws.owner_id, ws.connections.P2i_P1o_idx, freq.index, freq.index, + with matrix.component_edge_fill3( + ws.owner_id, connections.P2i_P1o_idx, freq.index, freq.index, ) as mat: mat[:] = ws.K21.data - def _get_workspace(self, sim): - _, is_changing = self._eval_parameters() - refill = self in sim.trace_forest.changing_components or len(is_changing) - - ws = LensWorkspace(self, sim, refill) - # This assumes that nr1/nr2 cannot change during a simulation - ws.nr1 = refractive_index(self.p1) or 1 - ws.nr2 = refractive_index(self.p2) or 1 + def _fill_carrier(self, ws): + self._fill_optical_matrix(ws, ws.sim.carrier, ws.carrier.connections) - ws.set_fill_fn(self._fill_matrix) + def _fill_signal(self, ws): + self._fill_optical_matrix(ws, ws.sim.signal, ws.signal.connections) - if sim.is_modal: - key = (self.p1.i, self.p2.o, "x") - _, ws.abcd_x = self._abcd_matrices[key] - key = (self.p1.i, self.p2.o, "y") - _, ws.abcd_y = self._abcd_matrices[key] - - return ws + def _get_workspace(self, sim): + from finesse.simulations.basematrix import CarrierSignalMatrixSimulation + + if isinstance(sim, CarrierSignalMatrixSimulation): + _, is_changing = self._eval_parameters() + refill = self in sim.trace_forest.changing_components or len(is_changing) + + ws = LensWorkspace(self, sim, refill) + # This assumes that nr1/nr2 cannot change during a simulation + ws.nr1 = refractive_index(self.p1) or 1 + ws.nr2 = refractive_index(self.p2) or 1 + + ws.carrier.set_fill_function(self._fill_carrier) + ws.signal.set_fill_function(self._fill_signal) + + if sim.is_modal: + key = (self.p1.i, self.p2.o, "x") + _, ws.abcd_x = self._abcd_matrices[key] + key = (self.p1.i, self.p2.o, "y") + _, ws.abcd_y = self._abcd_matrices[key] + + return ws + else: + raise Exception(f"Lens does not handle a simulation of type {sim}") diff --git a/src/finesse/components/ligo/suspensions.py b/src/finesse/components/ligo/suspensions.py index 060b7cc25e9766e0791bfec3c86d575510535d57..689cea07f3172fdee50cd41b57287d8e5d52c6f0 100644 --- a/src/finesse/components/ligo/suspensions.py +++ b/src/finesse/components/ligo/suspensions.py @@ -109,11 +109,11 @@ class LIGOTripleSuspension(Connector): ) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: refill = sim.model.fsig.f.is_changing # Need to recompute H(f) N = len(self._registered_connections) ws = MIMOTFWorkspace(self, sim, refill, N) - ws.set_fill_fn(mimo_fill) + ws.signal.set_fill_function(mimo_fill) ws.set_denominator(self.tfs.den[0][0]) name_map = {n.port.name: "tst" for n in self.tst.nodes} diff --git a/src/finesse/components/mechanical.pyx b/src/finesse/components/mechanical.pyx index 8e17ecf3957ccf7e24d2389d1147ac44e393d455..d79f97102204b1d458863a07748efcc61294724b 100644 --- a/src/finesse/components/mechanical.pyx +++ b/src/finesse/components/mechanical.pyx @@ -236,14 +236,13 @@ class Joint(Connector): def _on_init(self, sim): # TODO: (sjr) does this need to have rows > 1 for HOM simulations? - self.__I = np.eye(sim.nhoms, dtype=np.complex128) + self.__I = np.eye(sim.model_data.num_HOMs, dtype=np.complex128) def _fill_matrix(self, sim): - if not sim.is_audio: - return - for c in list(self._registered_connections): - with sim.component_edge_fill(self, c, None, None) as mat: - mat[:] = self.__I + if sim.signal: + for c in list(self._registered_connections): + with sim.signal.component_edge_fill(self, c, None, None) as mat: + mat[:] = self.__I class FreeMassWorkspace(ConnectorWorkspace): @@ -282,10 +281,10 @@ class FreeMass(Connector): self._register_node_coupling("F_to_PITCH", self.mech.F_pitch, self.mech.pitch) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: refill = sim.model.fsig.f.is_changing or any(p.is_changing for p in self.parameters) - ws = FreeMassWorkspace(self, sim, refill) - ws.set_fill_fn(self.fill) + ws = FreeMassWorkspace(self, sim, refill, refill) + ws.signal.set_fill_function(self.fill) return ws else: return None @@ -293,7 +292,7 @@ class FreeMass(Connector): def fill(self, ws): f = ws.sim.model_data.fsig Hz = -1 / (self.mass.value * (2*PI*f)**2) - with ws.sim.component_edge_fill3( - ws.owner_id, ws.connections.F_to_Z_idx, 0, 0, + with ws.sim.signal.component_edge_fill3( + ws.owner_id, ws.signal.connections.F_to_Z_idx, 0, 0, ) as mat: mat[:] = Hz diff --git a/src/finesse/components/mirror.py b/src/finesse/components/mirror.py index 202aaa66958c1f3acc0cacc8ab1b312b47e531ff..f9c11587b8458aa1d6d6b24c17d71fdd13d5d7ef 100644 --- a/src/finesse/components/mirror.py +++ b/src/finesse/components/mirror.py @@ -9,10 +9,6 @@ import numpy as np from finesse.parameter import model_parameter, Rebuild from finesse.utilities import refractive_index -from finesse.components.modal.mirror import ( - mirror_fill, - MirrorWorkspace, -) from finesse.components.general import InteractionType from finesse.components.surface import Surface from finesse.components.node import NodeDirection, NodeType @@ -335,45 +331,41 @@ class Mirror(Surface): return super().ABCD(from_node, to_node, direction, symbolic, copy, retboth) def _get_workspace(self, sim): - _, is_changing = self._eval_parameters() - - refill = ( - (sim.is_audio and sim.model.fsig.f.is_changing) - or self in sim.trace_forest.changing_components - or sim.any_frequencies_changing - or (len(is_changing) and is_changing.issubset(self.__changing_check)) - ) - - ws = MirrorWorkspace(self, sim, refill) - # This assumes that nr1/nr2 cannot change during a simulation - ws.nr1 = refractive_index(self.p1) or 1 - ws.nr2 = refractive_index(self.p2) or 1 - - ws.set_fill_fn(mirror_fill) - - # Initialise the ABCD matrix memory-views - if sim.is_modal: - ws.abcd_p1p1_x = self.ABCD(self.p1.i, self.p1.o, "x", copy=False) - ws.abcd_p1p1_y = self.ABCD(self.p1.i, self.p1.o, "y", copy=False) - ws.abcd_p2p2_x = self.ABCD(self.p2.i, self.p2.o, "x", copy=False) - ws.abcd_p2p2_y = self.ABCD(self.p2.i, self.p2.o, "y", copy=False) - - ws.abcd_p1p2_x = self.ABCD(self.p1.i, self.p2.o, "x", copy=False) - ws.abcd_p1p2_y = self.ABCD(self.p1.i, self.p2.o, "y", copy=False) - ws.abcd_p2p1_x = self.ABCD(self.p2.i, self.p1.o, "x", copy=False) - ws.abcd_p2p1_y = self.ABCD(self.p2.i, self.p1.o, "y", copy=False) - - return ws - - def _fill_qnoise_rhs(self, sim): - if sim.is_modal: - for freq in sim.frequencies: - for k in range(sim.nhoms): - L1 = self.R * self.K11_loss[k] + self.T * self.K21_loss[k] - L2 = self.R * self.K22_loss[k] + self.T * self.K12_loss[k] - sim.Mq[sim.field(self.p1.o, freq.index, k)] = (L1 + self.L) / 2 - sim.Mq[sim.field(self.p2.o, freq.index, k)] = (L2 + self.L) / 2 - else: - for freq in sim.frequencies: - sim.Mq[sim.field(self.p1.o, freq.index, 0)] = self.L / 2 - sim.Mq[sim.field(self.p2.o, freq.index, 0)] = self.L / 2 + from finesse.simulations.basematrix import CarrierSignalMatrixSimulation + + if isinstance(sim, CarrierSignalMatrixSimulation): + from finesse.components.modal.mirror import ( + mirror_carrier_fill, + mirror_signal_fill, + MirrorWorkspace, + ) + _, is_changing = self._eval_parameters() + + refill = ( + self in sim.trace_forest.changing_components + or sim.carrier.any_frequencies_changing + or (sim.signal.any_frequencies_changing if sim.signal else False) + or (len(is_changing) and is_changing.issubset(self.__changing_check)) + ) + + ws = MirrorWorkspace(self, sim, refill) + # This assumes that nr1/nr2 cannot change during a simulation + ws.nr1 = refractive_index(self.p1) or 1 + ws.nr2 = refractive_index(self.p2) or 1 + + ws.carrier.set_fill_function(mirror_carrier_fill) + ws.signal.set_fill_function(mirror_signal_fill) + + # Initialise the ABCD matrix memory-views + if sim.is_modal: + ws.abcd_p1p1_x = self.ABCD(self.p1.i, self.p1.o, "x", copy=False) + ws.abcd_p1p1_y = self.ABCD(self.p1.i, self.p1.o, "y", copy=False) + ws.abcd_p2p2_x = self.ABCD(self.p2.i, self.p2.o, "x", copy=False) + ws.abcd_p2p2_y = self.ABCD(self.p2.i, self.p2.o, "y", copy=False) + + ws.abcd_p1p2_x = self.ABCD(self.p1.i, self.p2.o, "x", copy=False) + ws.abcd_p1p2_y = self.ABCD(self.p1.i, self.p2.o, "y", copy=False) + ws.abcd_p2p1_x = self.ABCD(self.p2.i, self.p1.o, "x", copy=False) + ws.abcd_p2p1_y = self.ABCD(self.p2.i, self.p1.o, "y", copy=False) + + return ws diff --git a/src/finesse/components/modal/beamsplitter.pxd b/src/finesse/components/modal/beamsplitter.pxd index 74e3259884a99cb97729b874045d6862cce6f8fa..81334d1b02d59c067ec7770abca895c3cf70719b 100644 --- a/src/finesse/components/modal/beamsplitter.pxd +++ b/src/finesse/components/modal/beamsplitter.pxd @@ -1,8 +1,9 @@ from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray, SubCCSView2DArray from finesse.knm cimport KnmMatrix, KnmWorkspace from finesse.cymath cimport complex_t -from finesse.simulations.base cimport frequency_info_t, ModelData, NodeBeamParam -from finesse.simulations cimport BaseSimulation +from finesse.simulations.base cimport ModelData, NodeBeamParam +from finesse.frequency cimport frequency_info_t +from finesse.simulations.basematrix cimport MatrixSystemSolver, CarrierSignalMatrixSimulation from finesse.element cimport BaseCValues from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper from finesse.components.modal.workspace cimport KnmConnectorWorkspace @@ -20,7 +21,31 @@ from cpython.ref cimport PyObject import numpy as np cimport numpy as np -cdef class BeamsplitterConnections: +cdef struct bs_optical_connections: + PyObject** P1i_P2o + PyObject** P2i_P1o + PyObject** P3i_P4o + PyObject** P4i_P3o + PyObject** P1i_P3o + PyObject** P3i_P1o + PyObject** P2i_P4o + PyObject** P4i_P2o + +cdef struct bs_signal_connections: + PyObject*** P1i_Fz + PyObject*** P1o_Fz + PyObject*** P2i_Fz + PyObject*** P2o_Fz + PyObject*** P3i_Fz + PyObject*** P3o_Fz + PyObject*** P4i_Fz + PyObject*** P4o_Fz + PyObject*** Z_P1o + PyObject*** Z_P2o + PyObject*** Z_P3o + PyObject*** Z_P4o + +cdef class BeamsplitterOpticalConnections: cdef public: Py_ssize_t P1i_P2o_idx Py_ssize_t P2i_P1o_idx @@ -30,19 +55,7 @@ cdef class BeamsplitterConnections: Py_ssize_t P3i_P1o_idx Py_ssize_t P2i_P4o_idx Py_ssize_t P4i_P2o_idx - Py_ssize_t P1i_Z_idx - Py_ssize_t P1o_Z_idx - Py_ssize_t P2i_Z_idx - Py_ssize_t P2o_Z_idx - Py_ssize_t P3i_Z_idx - Py_ssize_t P3o_Z_idx - Py_ssize_t P4i_Z_idx - Py_ssize_t P4o_Z_idx - Py_ssize_t Z_P1o_idx - Py_ssize_t Z_P2o_idx - Py_ssize_t Z_P3o_idx - Py_ssize_t Z_P4o_idx - + cdef readonly: SubCCSView1DArray P1i_P2o SubCCSView1DArray P2i_P1o SubCCSView1DArray P3i_P4o @@ -51,20 +64,39 @@ cdef class BeamsplitterConnections: SubCCSView1DArray P3i_P1o SubCCSView1DArray P2i_P4o SubCCSView1DArray P4i_P2o + cdef: + bs_optical_connections opt_conn_ptrs + - SubCCSView2DArray P1i_Z - SubCCSView2DArray P1o_Z - SubCCSView2DArray P2i_Z - SubCCSView2DArray P2o_Z - SubCCSView2DArray P3i_Z - SubCCSView2DArray P3o_Z - SubCCSView2DArray P4i_Z - SubCCSView2DArray P4o_Z +cdef class BeamsplitterSignalConnections(BeamsplitterOpticalConnections): + cdef public: + Py_ssize_t P1i_Fz_idx + Py_ssize_t P1o_Fz_idx + Py_ssize_t P2i_Fz_idx + Py_ssize_t P2o_Fz_idx + Py_ssize_t P3i_Fz_idx + Py_ssize_t P3o_Fz_idx + Py_ssize_t P4i_Fz_idx + Py_ssize_t P4o_Fz_idx + Py_ssize_t Z_P1o_idx + Py_ssize_t Z_P2o_idx + Py_ssize_t Z_P3o_idx + Py_ssize_t Z_P4o_idx + cdef readonly: + SubCCSView2DArray P1i_Fz + SubCCSView2DArray P1o_Fz + SubCCSView2DArray P2i_Fz + SubCCSView2DArray P2o_Fz + SubCCSView2DArray P3i_Fz + SubCCSView2DArray P3o_Fz + SubCCSView2DArray P4i_Fz + SubCCSView2DArray P4o_Fz SubCCSView2DArray Z_P1o SubCCSView2DArray Z_P2o SubCCSView2DArray Z_P3o SubCCSView2DArray Z_P4o - + cdef: + bs_signal_connections sig_conn_ptrs cdef class BeamsplitterValues(BaseCValues): cdef public: @@ -142,7 +174,8 @@ cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): cdef: BeamsplitterValues bv - BeamsplitterConnections bc + BeamsplitterOpticalConnections boc + BeamsplitterSignalConnections bsc # Indices (in sim._c_node_info and sim.trace) of nodes Py_ssize_t P1i_id @@ -193,4 +226,5 @@ cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): cdef void compute_scattering_matrices(self) -cdef object c_beamsplitter_fill(ConnectorWorkspace cws) +cdef object c_beamsplitter_carrier_fill(ConnectorWorkspace cws) +cdef object c_beamsplitter_signal_fill(ConnectorWorkspace cws) diff --git a/src/finesse/components/modal/beamsplitter.pyx b/src/finesse/components/modal/beamsplitter.pyx index e4c1fff783cd1ae78b888fbf42efab6a03e4e424..7c7e13648983841bdfb7d5b1e4825c8c83238ffe 100644 --- a/src/finesse/components/modal/beamsplitter.pyx +++ b/src/finesse/components/modal/beamsplitter.pyx @@ -46,33 +46,59 @@ cdef extern from "constants.h": LOGGER = logging.getLogger(__name__) -cdef class BeamsplitterConnections: - def __cinit__(self, BaseSimulation sim): +cdef class BeamsplitterOpticalConnections: + def __cinit__(self, MatrixSystemSolver mtx): # Only 1D arrays of views as spaces don't # couple frequencies together. - Nf = sim.num_frequencies - self.P1i_P2o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P1o = SubCCSView1DArray(sim.num_frequencies) - self.P3i_P4o = SubCCSView1DArray(sim.num_frequencies) - self.P4i_P3o = SubCCSView1DArray(sim.num_frequencies) - self.P1i_P3o = SubCCSView1DArray(sim.num_frequencies) - self.P3i_P1o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P4o = SubCCSView1DArray(sim.num_frequencies) - self.P4i_P2o = SubCCSView1DArray(sim.num_frequencies) - - self.P1i_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P1o_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P2i_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P2o_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P3i_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P3o_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P4i_Z = SubCCSView2DArray(sim.num_frequencies, 1) - self.P4o_Z = SubCCSView2DArray(sim.num_frequencies, 1) - - self.Z_P1o = SubCCSView2DArray(1, sim.num_frequencies) - self.Z_P2o = SubCCSView2DArray(1, sim.num_frequencies) - self.Z_P3o = SubCCSView2DArray(1, sim.num_frequencies) - self.Z_P4o = SubCCSView2DArray(1, sim.num_frequencies) + Nf = mtx.optical_frequencies.size + self.P1i_P2o = SubCCSView1DArray(Nf) + self.P2i_P1o = SubCCSView1DArray(Nf) + self.P3i_P4o = SubCCSView1DArray(Nf) + self.P4i_P3o = SubCCSView1DArray(Nf) + self.P1i_P3o = SubCCSView1DArray(Nf) + self.P3i_P1o = SubCCSView1DArray(Nf) + self.P2i_P4o = SubCCSView1DArray(Nf) + self.P4i_P2o = SubCCSView1DArray(Nf) + + self.opt_conn_ptrs.P1i_P2o = self.P1i_P2o.views + self.opt_conn_ptrs.P2i_P1o = self.P2i_P1o.views + self.opt_conn_ptrs.P3i_P4o = self.P3i_P4o.views + self.opt_conn_ptrs.P4i_P3o = self.P4i_P3o.views + self.opt_conn_ptrs.P1i_P3o = self.P1i_P3o.views + self.opt_conn_ptrs.P3i_P1o = self.P3i_P1o.views + self.opt_conn_ptrs.P2i_P4o = self.P2i_P4o.views + self.opt_conn_ptrs.P4i_P2o = self.P4i_P2o.views + +cdef class BeamsplitterSignalConnections(BeamsplitterOpticalConnections): + def __cinit__(self, MatrixSystemSolver mtx): + Nf = mtx.optical_frequencies.size + self.P1i_Z = SubCCSView2DArray(Nf, 1) + self.P1o_Z = SubCCSView2DArray(Nf, 1) + self.P2i_Z = SubCCSView2DArray(Nf, 1) + self.P2o_Z = SubCCSView2DArray(Nf, 1) + self.P3i_Z = SubCCSView2DArray(Nf, 1) + self.P3o_Z = SubCCSView2DArray(Nf, 1) + self.P4i_Z = SubCCSView2DArray(Nf, 1) + self.P4o_Z = SubCCSView2DArray(Nf, 1) + + self.Z_P1o = SubCCSView2DArray(1, Nf) + self.Z_P2o = SubCCSView2DArray(1, Nf) + self.Z_P3o = SubCCSView2DArray(1, Nf) + self.Z_P4o = SubCCSView2DArray(1, Nf) + + self.sig_conn_ptrs.P1i_Fz = self.P1i_Fz.views + self.sig_conn_ptrs.P1o_Fz = self.P1o_Fz.views + self.sig_conn_ptrs.P2i_Fz = self.P2i_Fz.views + self.sig_conn_ptrs.P2o_Fz = self.P2o_Fz.views + self.sig_conn_ptrs.P3i_Fz = self.P3i_Fz.views + self.sig_conn_ptrs.P3o_Fz = self.P3o_Fz.views + self.sig_conn_ptrs.P4i_Fz = self.P4i_Fz.views + self.sig_conn_ptrs.P4o_Fz = self.P4o_Fz.views + self.sig_conn_ptrs.Z_P1o = self.Z_P1o.views + self.sig_conn_ptrs.Z_P2o = self.Z_P2o.views + self.sig_conn_ptrs.Z_P3o = self.Z_P3o.views + self.sig_conn_ptrs.Z_P4o = self.Z_P4o.views + cdef class BeamsplitterValues(BaseCValues): def __init__(self): @@ -81,38 +107,46 @@ cdef class BeamsplitterValues(BaseCValues): self.setup(params, sizeof(ptr), &ptr) cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): - def __init__(self, owner, BaseSimulation sim, refill): + def __init__(self, owner, CarrierSignalMatrixSimulation sim, refill): self.str_couplings = ("12", "21", "34", "43", "13", "31", "24", "42") super().__init__( owner, sim, refill, - BeamsplitterConnections(sim), + refill, + BeamsplitterOpticalConnections(sim.carrier), + BeamsplitterSignalConnections(sim.signal) if sim.signal else None, BeamsplitterValues() ) - self.bc = self.connections + # Store direct type cast for C access + self.boc = self.carrier.connections + if sim.signal: + self.bsc = self.signal.connections + else: + self.bsc = None self.bv = self.values - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) - self.P2i_id = sim.node_id(owner.p2.i) - self.P2o_id = sim.node_id(owner.p2.o) - self.P3i_id = sim.node_id(owner.p3.i) - self.P3o_id = sim.node_id(owner.p3.o) - self.P4i_id = sim.node_id(owner.p4.i) - self.P4o_id = sim.node_id(owner.p4.o) - - if sim.DC is not None: - self.car_p1o_rhs_idx = sim.DC.get_node_info(owner.p1.o)['rhs_index'] - self.car_p1i_rhs_idx = sim.DC.get_node_info(owner.p1.i)['rhs_index'] - self.car_p2o_rhs_idx = sim.DC.get_node_info(owner.p2.o)['rhs_index'] - self.car_p2i_rhs_idx = sim.DC.get_node_info(owner.p2.i)['rhs_index'] - self.car_p3o_rhs_idx = sim.DC.get_node_info(owner.p3.o)['rhs_index'] - self.car_p3i_rhs_idx = sim.DC.get_node_info(owner.p3.i)['rhs_index'] - self.car_p4o_rhs_idx = sim.DC.get_node_info(owner.p4.o)['rhs_index'] - self.car_p4i_rhs_idx = sim.DC.get_node_info(owner.p4.i)['rhs_index'] - self.car_p_num_hom = sim.DC.get_node_info(owner.p1.o)['nhoms'] + # tracing node information + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) + self.P2i_id = sim.carrier.node_id(owner.p2.i) + self.P2o_id = sim.carrier.node_id(owner.p2.o) + self.P3i_id = sim.carrier.node_id(owner.p3.i) + self.P3o_id = sim.carrier.node_id(owner.p3.o) + self.P4i_id = sim.carrier.node_id(owner.p4.i) + self.P4o_id = sim.carrier.node_id(owner.p4.o) + + if sim.signal: + self.car_p1o_rhs_idx = sim.carrier.get_node_info(owner.p1.o)['rhs_index'] + self.car_p1i_rhs_idx = sim.carrier.get_node_info(owner.p1.i)['rhs_index'] + self.car_p2o_rhs_idx = sim.carrier.get_node_info(owner.p2.o)['rhs_index'] + self.car_p2i_rhs_idx = sim.carrier.get_node_info(owner.p2.i)['rhs_index'] + self.car_p3o_rhs_idx = sim.carrier.get_node_info(owner.p3.o)['rhs_index'] + self.car_p3i_rhs_idx = sim.carrier.get_node_info(owner.p3.i)['rhs_index'] + self.car_p4o_rhs_idx = sim.carrier.get_node_info(owner.p4.o)['rhs_index'] + self.car_p4i_rhs_idx = sim.carrier.get_node_info(owner.p4.i)['rhs_index'] + self.car_p_num_hom = sim.carrier.get_node_info(owner.p1.o)['nhoms'] self.sym_abcd_elements[:] = [ calloc(4, sizeof(cy_expr*)), calloc(4, sizeof(cy_expr*)), @@ -607,59 +641,59 @@ cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): # Reflection p1 -> p2 if not self.K12_ws_x.total_reflection and not self.K12_ws_y.total_reflection: if knm_ws_is_changing(&self.K12_ws_x) or knm_ws_is_changing(&self.K12_ws_y): - compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.homs_view, self.K12.data_view) - reverse_gouy_phases(self.K12.data_view, self.sim.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) + compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.model_data.homs_view, self.K12.data_view) + reverse_gouy_phases(self.K12.data_view, self.sim.model_data.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K12.data_view, self.K12.data_view) - flip_odd_horizontal(self.K12.data_view, self.sim.homs_view, self.K12.data_view) + flip_odd_horizontal(self.K12.data_view, self.sim.model_data.homs_view, self.K12.data_view) knm_loss(self.K12.data_view, self.K12_loss) # Reflection p2 -> p1 if not self.K21_ws_x.total_reflection and not self.K21_ws_y.total_reflection: if knm_ws_is_changing(&self.K21_ws_x) or knm_ws_is_changing(&self.K21_ws_y): - compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.homs_view, self.K21.data_view) - reverse_gouy_phases(self.K21.data_view, self.sim.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) + compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.model_data.homs_view, self.K21.data_view) + reverse_gouy_phases(self.K21.data_view, self.sim.model_data.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K21.data_view, self.K21.data_view) - flip_odd_horizontal(self.K21.data_view, self.sim.homs_view, self.K21.data_view) + flip_odd_horizontal(self.K21.data_view, self.sim.model_data.homs_view, self.K21.data_view) knm_loss(self.K21.data_view, self.K21_loss) # Reflection p3 -> p4 if not self.K34_ws_x.total_reflection and not self.K34_ws_y.total_reflection: if knm_ws_is_changing(&self.K34_ws_x) or knm_ws_is_changing(&self.K34_ws_y): - compute_knm_matrix_bh(&self.K34_ws_x, &self.K34_ws_y, self.sim.homs_view, self.K34.data_view) - reverse_gouy_phases(self.K34.data_view, self.sim.homs_view, &self.K34_ws_x, &self.K34_ws_y, self.K34.data_view) + compute_knm_matrix_bh(&self.K34_ws_x, &self.K34_ws_y, self.sim.model_data.homs_view, self.K34.data_view) + reverse_gouy_phases(self.K34.data_view, self.sim.model_data.homs_view, &self.K34_ws_x, &self.K34_ws_y, self.K34.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K34.data_view, self.K34.data_view) - flip_odd_horizontal(self.K34.data_view, self.sim.homs_view, self.K34.data_view) + flip_odd_horizontal(self.K34.data_view, self.sim.model_data.homs_view, self.K34.data_view) knm_loss(self.K34.data_view, self.K34_loss) # Reflection p4 -> p3 if not self.K43_ws_x.total_reflection and not self.K43_ws_y.total_reflection: if knm_ws_is_changing(&self.K43_ws_x) or knm_ws_is_changing(&self.K43_ws_y): - compute_knm_matrix_bh(&self.K43_ws_x, &self.K43_ws_y, self.sim.homs_view, self.K43.data_view) - reverse_gouy_phases(self.K43.data_view, self.sim.homs_view, &self.K43_ws_x, &self.K43_ws_y, self.K43.data_view) + compute_knm_matrix_bh(&self.K43_ws_x, &self.K43_ws_y, self.sim.model_data.homs_view, self.K43.data_view) + reverse_gouy_phases(self.K43.data_view, self.sim.model_data.homs_view, &self.K43_ws_x, &self.K43_ws_y, self.K43.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K43.data_view, self.K43.data_view) - flip_odd_horizontal(self.K43.data_view, self.sim.homs_view, self.K43.data_view) + flip_odd_horizontal(self.K43.data_view, self.sim.model_data.homs_view, self.K43.data_view) knm_loss(self.K43.data_view, self.K43_loss) # Transmission p1 -> p3 if knm_ws_is_changing(&self.K13_ws_x) or knm_ws_is_changing(&self.K13_ws_y): - compute_knm_matrix_bh(&self.K13_ws_x, &self.K13_ws_y, self.sim.homs_view, self.K13.data_view) - reverse_gouy_phases(self.K13.data_view, self.sim.homs_view, &self.K13_ws_x, &self.K13_ws_y, self.K13.data_view) + compute_knm_matrix_bh(&self.K13_ws_x, &self.K13_ws_y, self.sim.model_data.homs_view, self.K13.data_view) + reverse_gouy_phases(self.K13.data_view, self.sim.model_data.homs_view, &self.K13_ws_x, &self.K13_ws_y, self.K13.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K13.data_view, self.K13.data_view) @@ -668,8 +702,8 @@ cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): # Transmission p3 -> p1 if knm_ws_is_changing(&self.K31_ws_x) or knm_ws_is_changing(&self.K31_ws_y): - compute_knm_matrix_bh(&self.K31_ws_x, &self.K31_ws_y, self.sim.homs_view, self.K31.data_view) - reverse_gouy_phases(self.K31.data_view, self.sim.homs_view, &self.K31_ws_x, &self.K31_ws_y, self.K31.data_view) + compute_knm_matrix_bh(&self.K31_ws_x, &self.K31_ws_y, self.sim.model_data.homs_view, self.K31.data_view) + reverse_gouy_phases(self.K31.data_view, self.sim.model_data.homs_view, &self.K31_ws_x, &self.K31_ws_y, self.K31.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K31.data_view, self.K31.data_view) @@ -678,8 +712,8 @@ cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): # Transmission p2 -> p4 if knm_ws_is_changing(&self.K24_ws_x) or knm_ws_is_changing(&self.K24_ws_y): - compute_knm_matrix_bh(&self.K24_ws_x, &self.K24_ws_y, self.sim.homs_view, self.K24.data_view) - reverse_gouy_phases(self.K24.data_view, self.sim.homs_view, &self.K24_ws_x, &self.K24_ws_y, self.K24.data_view) + compute_knm_matrix_bh(&self.K24_ws_x, &self.K24_ws_y, self.sim.model_data.homs_view, self.K24.data_view) + reverse_gouy_phases(self.K24.data_view, self.sim.model_data.homs_view, &self.K24_ws_x, &self.K24_ws_y, self.K24.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K24.data_view, self.K24.data_view) @@ -688,17 +722,54 @@ cdef class BeamsplitterWorkspace(KnmConnectorWorkspace): # Transmission p4 -> p2 if knm_ws_is_changing(&self.K42_ws_x) or knm_ws_is_changing(&self.K42_ws_y): - compute_knm_matrix_bh(&self.K42_ws_x, &self.K42_ws_y, self.sim.homs_view, self.K42.data_view) - reverse_gouy_phases(self.K42.data_view, self.sim.homs_view, &self.K42_ws_x, &self.K42_ws_y, self.K42.data_view) + compute_knm_matrix_bh(&self.K42_ws_x, &self.K42_ws_y, self.sim.model_data.homs_view, self.K42.data_view) + reverse_gouy_phases(self.K42.data_view, self.sim.model_data.homs_view, &self.K42_ws_x, &self.K42_ws_y, self.K42.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K42.data_view, self.K42.data_view) knm_loss(self.K42.data_view, self.K42_loss) +cdef inline void beamsplitter_fill_optical_2_optical( + bs_optical_connections *conn, + BeamsplitterWorkspace ws, + frequency_info_t *freq, + double r, + double t, + double phi, + double alpha + ): + + if ws.bv.alpha == 0.0: + ws.cos_alpha = 1 + ws.cos_alpha_2 = 1 + else: + ws.cos_alpha = cos(alpha) + ws.cos_alpha_2 = cos(asin(ws.nr1 / ws.nr2 * sin(alpha))) -beamsplitter_fill = FillFuncWrapper.make_from_ptr(c_beamsplitter_fill) -cdef object c_beamsplitter_fill(ConnectorWorkspace cws): + # Phase on reflection is not equal if nr1 != nr2 and AoI != 0 + # so the usual i on transmission phase no longer works. + # Uses N=-1, Eq.2.25 in Living Rev Relativ (2016) 19:3 DOI 10.1007/s41114-016-0002-8 + cdef double phi_r1 = 2 * phi * ws.cos_alpha + cdef double phi_r2 = -2 * phi * ws.cos_alpha_2 + cdef double phi_t = 0.5 * (phi_r1 + phi_r2) + (np.pi/2) + cdef double phase_shift_scaling = (1 + freq.f / ws.sim.model_data.f0) + cdef complex_t _r1 = r * cexp(1j * phi_r1 * phase_shift_scaling) + cdef complex_t _r2 = r * cexp(1j * phi_r2 * phase_shift_scaling) + cdef complex_t _t0 = t * cexp(1j * phi_t * phase_shift_scaling) + # reflections + (conn.P1i_P2o[freq.index]).fill_za_zm_2(_r1, ws.K12.ptr, ws.K12.stride1, ws.K12.stride2) + (conn.P2i_P1o[freq.index]).fill_za_zm_2(_r1, ws.K21.ptr, ws.K21.stride1, ws.K21.stride2) + (conn.P3i_P4o[freq.index]).fill_za_zm_2(_r2, ws.K34.ptr, ws.K34.stride1, ws.K34.stride2) + (conn.P4i_P3o[freq.index]).fill_za_zm_2(_r2, ws.K43.ptr, ws.K43.stride1, ws.K43.stride2) + # transmissions + (conn.P1i_P3o[freq.index]).fill_za_zm_2(_t0, ws.K13.ptr, ws.K13.stride1, ws.K13.stride2) + (conn.P3i_P1o[freq.index]).fill_za_zm_2(_t0, ws.K31.ptr, ws.K31.stride1, ws.K31.stride2) + (conn.P2i_P4o[freq.index]).fill_za_zm_2(_t0, ws.K24.ptr, ws.K24.stride1, ws.K24.stride2) + (conn.P4i_P2o[freq.index]).fill_za_zm_2(_t0, ws.K42.ptr, ws.K42.stride1, ws.K42.stride2) + +beamsplitter_carrier_fill = FillFuncWrapper.make_from_ptr(c_beamsplitter_carrier_fill) +cdef object c_beamsplitter_carrier_fill(ConnectorWorkspace cws): r""" Fills the sub-matrix of the interferometer matrix held by `sim`, corresponding to the `beamsplitter` component. @@ -749,7 +820,7 @@ cdef object c_beamsplitter_fill(ConnectorWorkspace cws): beamsplitter : :class:`.Beamsplitter` The beamsplitter object to fill. - sim : :class:`.BaseSimulation` + sim : :class:`.CarrierSignalMatrixSimulation` A handle to the simulation. values : dict @@ -769,9 +840,33 @@ cdef object c_beamsplitter_fill(ConnectorWorkspace cws): double f0 = ws.sim.model_data.f0 double k0 = ws.sim.model_data.k0 - double _r = sqrt(ws.bv.R) - double _t = sqrt(ws.bv.T) - complex_t _r1, _r2, _t0 + double r = sqrt(ws.bv.R) + double t = sqrt(ws.bv.T) + double phi = DEG2RAD * ws.bv.phi + double alpha = DEG2RAD * ws.bv.alpha + double phase_shift_scaling + + Py_ssize_t _i, size + frequency_info_t *frequencies + bs_optical_connections *conn = &ws.boc.opt_conn_ptrs + + size = ws.sim.carrier.optical_frequencies.size + frequencies = ws.sim.carrier.optical_frequencies.frequency_info + + for i in range(size): + beamsplitter_fill_optical_2_optical( + conn, ws, &frequencies[i], r, t, phi, alpha + ) + +beamsplitter_signal_fill = FillFuncWrapper.make_from_ptr(c_beamsplitter_signal_fill) +cdef object c_beamsplitter_signal_fill(ConnectorWorkspace cws): + cdef: + BeamsplitterWorkspace ws = cws + double f0 = ws.sim.model_data.f0 + double k0 = ws.sim.model_data.k0 + + double r = sqrt(ws.bv.R) + double t = sqrt(ws.bv.T) double phi = DEG2RAD * ws.bv.phi double alpha = DEG2RAD * ws.bv.alpha @@ -781,240 +876,16 @@ cdef object c_beamsplitter_fill(ConnectorWorkspace cws): double f_car # current value of the frequency object's carrier double x_scale = ws.sim.model_data.x_scale # scaling for optical signals to motion - # scaling factors for longitudinal motion - # TODO (phil): should this k be carrier-specific? - complex_t long_to_field = 1j * k0 * _r * x_scale - complex_t field_to_long - - BaseSimulation carrier - Py_ssize_t _i - const complex_t *c_p1_i - const complex_t *c_p2_i - const complex_t *c_p3_i - const complex_t *c_p4_i - const complex_t *c_p1_o - const complex_t *c_p2_o - const complex_t *c_p3_o - const complex_t *c_p4_o - - bint is_lower_sb - frequency_info_t freq + Py_ssize_t _i, size - if ws.bv.alpha == 0.0: - ws.cos_alpha = 1 - ws.cos_alpha_2 = 1 - else: - ws.cos_alpha = cos(alpha) - ws.cos_alpha_2 = cos(asin(ws.nr1 / ws.nr2 * sin(alpha))) + frequency_info_t *frequencies + bs_optical_connections *conn = &ws.bsc.opt_conn_ptrs + bs_signal_connections *sconn = &ws.bsc.sig_conn_ptrs - # Phase on reflection is not equal if nr1 != nr2 and AoI != 0 - # so the usual i on transmission phase no longer works. - # Uses N=-1, Eq.2.25 in Living Rev Relativ (2016) 19:3 DOI 10.1007/s41114-016-0002-8 - phi_r1 = 2 * phi * ws.cos_alpha * ws.nr1 - phi_r2 = -2 * phi * ws.cos_alpha_2 * ws.nr2 - phi_t = 0.5 * (phi_r1 + phi_r2) + (np.pi/2) - - for i in range(ws.sim.num_frequencies): - freq = ws.sim.frequency_info[i] - phase_shift_scaling = (1 + freq.f / f0) - _r1 = _r * cexp(1j * phi_r1 * phase_shift_scaling) - _r2 = _r * cexp(1j * phi_r2 * phase_shift_scaling) - _t0 = _t * cexp(1j * phi_t * phase_shift_scaling) - - (ws.bc.P1i_P2o.views[freq.index]).fill_za_zm_2(_r1, ws.K12.ptr, ws.K12.stride1, ws.K12.stride2) - (ws.bc.P2i_P1o.views[freq.index]).fill_za_zm_2(_r1, ws.K21.ptr, ws.K21.stride1, ws.K21.stride2) - - (ws.bc.P3i_P4o.views[freq.index]).fill_za_zm_2(_r2, ws.K34.ptr, ws.K34.stride1, ws.K34.stride2) - (ws.bc.P4i_P3o.views[freq.index]).fill_za_zm_2(_r2, ws.K43.ptr, ws.K43.stride1, ws.K43.stride2) - - (ws.bc.P1i_P3o.views[freq.index]).fill_za_zm_2(_t0, ws.K13.ptr, ws.K13.stride1, ws.K13.stride2) - (ws.bc.P3i_P1o.views[freq.index]).fill_za_zm_2(_t0, ws.K31.ptr, ws.K31.stride1, ws.K31.stride2) - - (ws.bc.P2i_P4o.views[freq.index]).fill_za_zm_2(_t0, ws.K24.ptr, ws.K24.stride1, ws.K24.stride2) - (ws.bc.P4i_P2o.views[freq.index]).fill_za_zm_2(_t0, ws.K42.ptr, ws.K42.stride1, ws.K42.stride2) - - if ws.sim.is_audio: - # carrier = sim.DC - # f_car = carrier.frequencies[freq.audio_carrier_index].f - - # with sim.component_edge_fill( - # beamsplitter, "phase_sig.i->mech.z", None, None - # ) as mat: - # mat[:] = np.atleast_2d(signal_gain) - - # # Carrier field amplitudes - # _i = carrier.field(beamsplitter.p1.i, freq.audio_carrier_index, 0) - # c_p1_i = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p2.i, freq.audio_carrier_index, 0) - # c_p2_i = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p3.i, freq.audio_carrier_index, 0) - # c_p3_i = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p4.i, freq.audio_carrier_index, 0) - # c_p4_i = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p1.o, freq.audio_carrier_index, 0) - # c_p1_o = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p2.o, freq.audio_carrier_index, 0) - # c_p2_o = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p3.o, freq.audio_carrier_index, 0) - # c_p3_o = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # _i = carrier.field(beamsplitter.p4.o, freq.audio_carrier_index, 0) - # c_p4_o = np.atleast_2d(carrier.out[_i:(_i + sim.nhoms)]).T - - # # ------------------------------------------------- - # # Optical to mechanical connections - # # ------------------------------------------------- - # # - Longitudinal - # # ------------------------------------------------- - # field_to_long = free_mass_tf(fval - f_car) / (mass * C_LIGHT * x_scale) - - # with sim.component_edge_fill( - # beamsplitter, 'p1.i->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(-1 * field_to_long * cos_alpha, c_p1_i.T, out=mat[:]) - # else: - # np.multiply(-1 * field_to_long * cos_alpha, c_p1_i.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p1.o->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(-1 * field_to_long * cos_alpha, c_p1_o.T, out=mat[:]) - # else: - # np.multiply(-1 * field_to_long * cos_alpha, c_p1_o.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p2.i->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(-1 * field_to_long * cos_alpha, c_p2_i.T, out=mat[:]) - # else: - # np.multiply(-1 * field_to_long * cos_alpha, c_p2_i.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p2.o->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(-1 * field_to_long * cos_alpha, c_p2_o.T, out=mat[:]) - # else: - # np.multiply(-1 * field_to_long * cos_alpha, c_p2_o.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p3.i->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(field_to_long * cos_alpha_2, c_p3_i.T, out=mat[:]) - # else: - # np.multiply(field_to_long * cos_alpha_2, c_p3_i.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p3.o->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(field_to_long * cos_alpha_2, c_p3_o.T, out=mat[:]) - # else: - # np.multiply(field_to_long * cos_alpha_2, c_p3_o.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p4.i->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(field_to_long * cos_alpha_2, c_p4_i.T, out=mat[:]) - # else: - # np.multiply(field_to_long * cos_alpha_2, c_p4_i.conj().T, out=mat[:]) - - # with sim.component_edge_fill( - # beamsplitter, 'p4.o->mech.z', freq, None - # ) as mat: - # if is_lower_sb: - # np.multiply(field_to_long * cos_alpha_2, c_p4_o.T, out=mat[:]) - # else: - # np.multiply(field_to_long * cos_alpha_2, c_p4_o.conj().T, out=mat[:]) - - - # ----------------------------------------------------------------- - # Mechanical to optical connections - # ----------------------------------------------------------------- - # - Longitudinal - # ----------------------------------------------------------------- - # As the output has a mixture of both refl and transmitted we only - # modulate the incoming and refl'd field so we have to propagate - # the input - - # Detuning phase is the phase the carrier picks up from reference point - # to the mirror surface, then scattered into a different frequency and - # propagated back to the reference point from the mirror surface. - # - # phase_shift = phi * (1 + car.f / f0) + phi * (1 + freq.f/f0) - # = phi * (2 + (order*fsig + freq.f) / f0) - phase_shift = phi * (2 + (freq.f + freq.f_car)/f0) - _tuning = cexp(1.0j * phase_shift * ws.cos_alpha) - - # ----------------------------------------------------------------- - # Signal generation z->p1.o - # Get incoming carrier field amplitudes - c_p2_i = &ws.sim.DC.out_view[ws.sim.field_fast_2( - ws.car_p2i_rhs_idx, - ws.car_p_num_hom, - freq.audio_carrier_index, - 0 - )] - (ws.bc.Z_P1o[0, freq.index]).fill_za_zmv ( - long_to_field*_tuning, - ws.K21.ptr, ws.K21.stride1, ws.K21.stride2, - c_p2_i, 1 # 1D contiguous array from outview - ) - # ----------------------------------------------------------------- - # Signal generation z->p2.o - # Get incoming carrier field amplitudes - c_p1_i = &ws.sim.DC.out_view[ws.sim.field_fast_2( - ws.car_p1i_rhs_idx, - ws.car_p_num_hom, - freq.audio_carrier_index, - 0 - )] - (ws.bc.Z_P2o[0, freq.index]).fill_za_zmv ( - long_to_field*_tuning, - ws.K12.ptr, ws.K12.stride1, ws.K12.stride2, - c_p1_i, 1 # 1D contiguous array from outview - ) + size = ws.sim.signal.optical_frequencies.size + frequencies = ws.sim.signal.optical_frequencies.frequency_info - # Tuning has different cos_alpha_2 because of nr/alpha difference - _ctuning = conj(cexp(1.0j * phase_shift * ws.cos_alpha_2)) - # ----------------------------------------------------------------- - # Signal generation z->p3.o - # extra 180 phase here as we're doing the opposite - # modulation when looked at from the other side of the mirror - c_p4_i = &ws.sim.DC.out_view[ws.sim.field_fast_2( - ws.car_p4i_rhs_idx, - ws.car_p_num_hom, - freq.audio_carrier_index, - 0 - )] - (ws.bc.Z_P3o[0, freq.index]).fill_za_zmv ( - -long_to_field*_ctuning, - ws.K43.ptr, ws.K43.stride1, ws.K43.stride2, - c_p4_i, 1 # 1D contiguous array from outview - ) - # ----------------------------------------------------------------- - # Signal generation z->p4.o - # extra 180 phase here as we're doing the opposite - # modulation when looked at from the other side of the mirror - c_p3_i = &ws.sim.DC.out_view[ws.sim.field_fast_2( - ws.car_p3i_rhs_idx, - ws.car_p_num_hom, - freq.audio_carrier_index, - 0 - )] - (ws.bc.Z_P4o[0, freq.index]).fill_za_zmv ( - -long_to_field*_ctuning, - ws.K34.ptr, ws.K34.stride1, ws.K34.stride2, - c_p3_i, 1 # 1D contiguous array from outview - ) + for i in range(size): + beamsplitter_fill_optical_2_optical( + conn, ws, &frequencies[i], r, t, phi, alpha + ) diff --git a/src/finesse/components/modal/cavity.pxd b/src/finesse/components/modal/cavity.pxd index 0fa80690dd42d1e27e635b6726b01526c2eadb5a..37b471e56d9c4e25a679a8a220792a19272cc945 100644 --- a/src/finesse/components/modal/cavity.pxd +++ b/src/finesse/components/modal/cavity.pxd @@ -8,7 +8,7 @@ from finesse.cyexpr cimport ( ) from finesse.element cimport ElementWorkspace from finesse.tracing.ctracer cimport TraceTree - +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation cdef enum cavity_plane: X, diff --git a/src/finesse/components/modal/cavity.pyx b/src/finesse/components/modal/cavity.pyx index 382993cdb69540c34479facf638deacdf3606d78..0366bef1b13ad057bfc74a846af58bd4100af378 100644 --- a/src/finesse/components/modal/cavity.pyx +++ b/src/finesse/components/modal/cavity.pyx @@ -4,7 +4,6 @@ from finesse.cymath.complex cimport csqrt, creal, cimag from finesse.cymath.math cimport acos, sqrt, PI from finesse.cymath.math cimport sgn -from finesse.simulations.base cimport BaseSimulation from finesse.tracing.ctracer cimport update_rt_abcd from finesse.symbols import Symbol @@ -15,7 +14,7 @@ cdef extern from "constants.h": cdef class CavityWorkspace(ElementWorkspace): - def __init__(self, owner, BaseSimulation sim): + def __init__(self, owner, CarrierSignalMatrixSimulation sim): cdef: TraceTree tree Py_ssize_t tree_idx diff --git a/src/finesse/components/modal/isolator.pxd b/src/finesse/components/modal/isolator.pxd index 30384b46b4400c017aba3810c2de97ca55876ba1..1e890ca9526a8cd0fb4ff51ed0d44cf4c3e63053 100644 --- a/src/finesse/components/modal/isolator.pxd +++ b/src/finesse/components/modal/isolator.pxd @@ -2,7 +2,7 @@ from finesse.cmatrix cimport SubCCSView1DArray from finesse.knm cimport KnmMatrix, KnmWorkspace from finesse.components.workspace cimport ConnectorWorkspace from finesse.element cimport BaseCValues -from finesse.simulations.base cimport BaseSimulation +from finesse.simulations.basematrix cimport MatrixSystemSolver, CarrierSignalMatrixSimulation from finesse.components.modal.workspace cimport KnmConnectorWorkspace from cpython.ref cimport PyObject @@ -41,7 +41,8 @@ cdef class IsolatorWorkspace(KnmConnectorWorkspace): cdef: IsolatorValues iv - IsolatorConnections ic + IsolatorConnections icc + IsolatorConnections ics # Indices (in sim._c_node_info and sim.trace) of nodes Py_ssize_t P1i_id diff --git a/src/finesse/components/modal/isolator.pyx b/src/finesse/components/modal/isolator.pyx index 07a9f856e90f1586dee37ee714c95efca7a46a66..97a93510215df2471791be34e8d2a110411483f7 100644 --- a/src/finesse/components/modal/isolator.pyx +++ b/src/finesse/components/modal/isolator.pyx @@ -1,7 +1,7 @@ #cython: profile=True, boundscheck=False, wraparound=False, initializedcheck=False from finesse.cymath cimport complex_t -from finesse.simulations.base cimport BaseSimulation, NodeBeamParam +from finesse.simulations.base cimport NodeBeamParam from finesse.cmatrix cimport SubCCSView1DArray from finesse.knm cimport ( KnmMatrix, @@ -34,29 +34,32 @@ cdef class IsolatorValues(BaseCValues): cdef class IsolatorConnections: - def __cinit__(self, BaseSimulation sim): - self.P1i_P2o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P1o = SubCCSView1DArray(sim.num_frequencies) + def __cinit__(self, MatrixSystemSolver mtx): + size = mtx.optical_frequencies.size + self.P1i_P2o = SubCCSView1DArray(size) + self.P2i_P1o = SubCCSView1DArray(size) self.conn_ptrs.P1i_P2o = self.P1i_P2o.views self.conn_ptrs.P2i_P1o = self.P2i_P1o.views cdef class IsolatorWorkspace(KnmConnectorWorkspace): - def __init__(self, owner, BaseSimulation sim, refill): + def __init__(self, owner, CarrierSignalMatrixSimulation sim, bint refill): self.str_couplings = ("12", "21") super().__init__( owner, sim, refill, - IsolatorConnections(sim), + refill, + IsolatorConnections(sim.carrier), + IsolatorConnections(sim.signal), IsolatorValues() ) - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) - self.P2i_id = sim.node_id(owner.p2.i) - self.P2o_id = sim.node_id(owner.p2.o) + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) + self.P2i_id = sim.carrier.node_id(owner.p2.i) + self.P2o_id = sim.carrier.node_id(owner.p2.o) cdef void initialise_knm_workspaces(self) nogil: cdef: @@ -148,16 +151,16 @@ cdef class IsolatorWorkspace(KnmConnectorWorkspace): cdef void compute_scattering_matrices(self): # Transmission p1 -> p2 if knm_ws_is_changing(&self.K12_ws_x) or knm_ws_is_changing(&self.K12_ws_y): - compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.homs_view, self.K12.data_view) - reverse_gouy_phases(self.K12.data_view, self.sim.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) + compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.model_data.homs_view, self.K12.data_view) + reverse_gouy_phases(self.K12.data_view, self.sim.model_data.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K12.data_view, self.K12.data_view) # Transmission p2 -> p1 if knm_ws_is_changing(&self.K21_ws_x) or knm_ws_is_changing(&self.K21_ws_y): - compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.homs_view, self.K21.data_view) - reverse_gouy_phases(self.K21.data_view, self.sim.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) + compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.model_data.homs_view, self.K21.data_view) + reverse_gouy_phases(self.K21.data_view, self.sim.model_data.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K21.data_view, self.K21.data_view) diff --git a/src/finesse/components/modal/laser.pxd b/src/finesse/components/modal/laser.pxd index ee8a81f35232822d055cb3a7ca2a9caf770c711c..3bc2591680bba0535a6dd2af8f3803724755149c 100644 --- a/src/finesse/components/modal/laser.pxd +++ b/src/finesse/components/modal/laser.pxd @@ -2,9 +2,10 @@ from finesse.cmatrix cimport SubCCSView, SubCCSView2DArray from finesse.knm cimport KnmMatrix from finesse.cymath cimport complex_t from finesse.cymath.complex cimport conj, cexp -from finesse.simulations.base cimport frequency_info_t, ModelData -from finesse.simulations cimport BaseSimulation -from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper +from finesse.simulations.base cimport ModelData +from finesse.frequency cimport frequency_info_t +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation +from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper, GouyFuncWrapper from finesse.element cimport BaseCValues import numpy as np @@ -44,7 +45,7 @@ cdef class LaserWorkspace(ConnectorWorkspace): Py_ssize_t fsrc_car_idx Py_ssize_t fcar_sig_sb_idx[2] complex_t[::1] power_coeffs # length sim.model_data.num_HOMs - Py_ssize_t node_id, node_id_car + Py_ssize_t node_car_id, node_sig_id LaserValues v LaserConnections lc complex_t PIj_2 @@ -54,6 +55,5 @@ cdef class LaserWorkspace(ConnectorWorkspace): Py_ssize_t P1o_id -cdef object c_laser_fill_rhs(ConnectorWorkspace cws) +cdef object c_laser_carrier_fill_rhs(ConnectorWorkspace cws) -cdef void set_tem_gouy_phases(LaserWorkspace ws) diff --git a/src/finesse/components/modal/laser.pyx b/src/finesse/components/modal/laser.pyx index 96ffebcabd57c393297b565e9a2703644bf4a99c..6d8893305105d368d9e86e5bc4d2f27253f38921 100644 --- a/src/finesse/components/modal/laser.pyx +++ b/src/finesse/components/modal/laser.pyx @@ -4,9 +4,11 @@ from finesse.cymath.math cimport sqrt, radians from finesse.cymath.gaussbeam cimport gouy from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray from finesse.knm cimport KnmMatrix -from finesse.simulations.base cimport frequency_info_t, NodeBeamParam -from finesse.simulations cimport BaseSimulation +from finesse.simulations.base cimport NodeBeamParam +from finesse.frequency cimport frequency_info_t from finesse.cymath.math cimport sgn +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation + import numpy as np cimport numpy as np cimport cython @@ -23,39 +25,39 @@ cdef extern from "constants.h": double DEG2RAD cdef class LaserConnections: - def __cinit__(self, BaseSimulation sim): - # Only 1D arrays of views as lasers don't - # couple frequencies together. - Nf = sim.num_frequencies - self.SIGAMP_P1o = SubCCSView2DArray(1, sim.num_frequencies) - self.SIGFRQ_P1o = SubCCSView2DArray(1, sim.num_frequencies) - self.SIGPHS_P1o = SubCCSView2DArray(1, sim.num_frequencies) + def __cinit__(self, CarrierSignalMatrixSimulation sim): + if sim.signal: + # There are no carrier connections at a laser, just signals + self.SIGAMP_P1o = SubCCSView2DArray(1, sim.signal.optical_frequencies.size) + self.SIGFRQ_P1o = SubCCSView2DArray(1, sim.signal.optical_frequencies.size) + self.SIGPHS_P1o = SubCCSView2DArray(1, sim.signal.optical_frequencies.size) - self.ptrs.SIGAMP_P1o = self.SIGAMP_P1o.views - self.ptrs.SIGFRQ_P1o = self.SIGFRQ_P1o.views - self.ptrs.SIGPHS_P1o = self.SIGPHS_P1o.views + self.ptrs.SIGAMP_P1o = self.SIGAMP_P1o.views + self.ptrs.SIGFRQ_P1o = self.SIGFRQ_P1o.views + self.ptrs.SIGPHS_P1o = self.SIGPHS_P1o.views cdef class LaserValues(BaseCValues): def __init__(self): cdef ptr_tuple_3 ptr = (&self.P, &self.phase, &self.f) - cdef tuple params = ("P","phase","f") + cdef tuple params = ("P", "phase", "f") self.setup(params, sizeof(ptr), &ptr) cdef class LaserWorkspace(ConnectorWorkspace): - def __init__(self, object owner, BaseSimulation sim, bint refill): - super().__init__(owner, sim, refill, LaserConnections(sim), LaserValues()) + def __init__(self, object owner, CarrierSignalMatrixSimulation sim, bint refill): + super().__init__(owner, sim, refill, refill, None, LaserConnections(sim), LaserValues()) self.v = self.values - self.lc = self.connections + self.lc = self.signal.connections self.PIj_2 = PI*0.5j - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) + # indexes for beam tracing + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) -laser_fill_rhs = FillFuncWrapper.make_from_ptr(c_laser_fill_rhs) -cdef object c_laser_fill_rhs(ConnectorWorkspace cws): +laser_carrier_fill_rhs = FillFuncWrapper.make_from_ptr(c_laser_carrier_fill_rhs) +cdef object c_laser_carrier_fill_rhs(ConnectorWorkspace cws): r""" Fills the right hand side (RHS) vector corresponding to the light source `laser`. @@ -73,7 +75,7 @@ cdef object c_laser_fill_rhs(ConnectorWorkspace cws): laser : :class:`.Laser` The laser object to fill. - sim : :class:`.BaseSimulation` + sim : :class:`.CarrierSignalMatrixSimulation` A handle to the simulation. values : dict @@ -92,13 +94,13 @@ cdef object c_laser_fill_rhs(ConnectorWorkspace cws): Py_ssize_t i if not ws.sim.is_modal: - ws.sim.set_source_fast( - ws.node_id, ws.fsrc_car_idx, 0, Ein, + ws.sim.carrier.set_source_fast( + ws.node_car_id, ws.fsrc_car_idx, 0, Ein, ) else: for i in range(ws.sim.model_data.num_HOMs): - ws.sim.set_source_fast( - ws.node_id, ws.fsrc_car_idx, i, Ein * ws.power_coeffs[i], + ws.sim.carrier.set_source_fast( + ws.node_car_id, ws.fsrc_car_idx, i, Ein * ws.power_coeffs[i], ) @@ -113,16 +115,16 @@ cdef object c_laser_fill_signal(ConnectorWorkspace cws): complex_t phs_sig complex_t frq_sig - frequency_info_t f + frequency_info_t *f complex_t* car for i in range(2): - f = ws.sim.frequency_info[ws.fcar_sig_sb_idx[i]] + f = &ws.sim.signal.optical_frequencies.frequency_info[ws.fcar_sig_sb_idx[i]] # NOTE shouldn't need multiplication by corresponding power_coeff # factor as this was taken into account in DC computations # Update this so that frequency rhs idx is store in workspace - j = ws.sim.DC.field_fast(ws.node_id_car, f.audio_carrier_index, 0) - car = &(ws.sim.DC.out_view[j]) + j = ws.sim.carrier.field_fast(ws.node_car_id, f.audio_carrier_index, 0) + car = &(ws.sim.carrier.out_view[j]) (conns.SIGAMP_P1o[0][f.index]).fill_za_zm_2(factor * 0.5 * 0.5, car, 1, 1) @@ -132,11 +134,11 @@ cdef object c_laser_fill_signal(ConnectorWorkspace cws): phs_sig = 1j * 0.5 (conns.SIGPHS_P1o[0][f.index]).fill_za_zm_2(factor * phs_sig, car, 1, 1) - +laser_set_gouy = GouyFuncWrapper.make_from_ptr(set_tem_gouy_phases) @cython.boundscheck(False) @cython.wraparound(False) @cython.initializedcheck(False) -cdef void set_tem_gouy_phases(LaserWorkspace ws): +cdef int set_tem_gouy_phases(ConnectorWorkspace ws) except -1: cdef: NodeBeamParam q_p1o = ws.sim.trace[ws.P1o_id] @@ -149,7 +151,7 @@ cdef void set_tem_gouy_phases(LaserWorkspace ws): int n, m if not q_p1o.is_changing: - return + return 0 gouy_x = gouy(q_p1o.qx) gouy_y = gouy(q_p1o.qy) @@ -163,3 +165,5 @@ cdef void set_tem_gouy_phases(LaserWorkspace ws): phase = (n + 0.5) * gouy_x + (m + 0.5) * gouy_y ws.power_coeffs[i] = crotate(ws.power_coeffs[i], phase - phase00) + + return 0 diff --git a/src/finesse/components/modal/lens.pxd b/src/finesse/components/modal/lens.pxd index 84c7848f66d8d237022068ad14cad978419963ea..3f7fb37de22eb7443a15f73602461486451318ce 100644 --- a/src/finesse/components/modal/lens.pxd +++ b/src/finesse/components/modal/lens.pxd @@ -4,7 +4,7 @@ from finesse.cmatrix cimport SubCCSView1DArray from finesse.knm cimport KnmMatrix, KnmWorkspace from finesse.components.workspace cimport ConnectorWorkspace from finesse.element cimport BaseCValues -from finesse.simulations.base cimport BaseSimulation +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation from finesse.components.modal.workspace cimport KnmConnectorWorkspace from finesse.cyexpr cimport ( diff --git a/src/finesse/components/modal/lens.pyx b/src/finesse/components/modal/lens.pyx index 4a242b04660dae4a47fa2fa200d0a0d793135ca9..65da03386ed0387fcba1e9cb6685ad41213772d7 100644 --- a/src/finesse/components/modal/lens.pyx +++ b/src/finesse/components/modal/lens.pyx @@ -4,7 +4,8 @@ cimport cython from finesse.cymath cimport complex_t from finesse.cymath.gaussbeam cimport transform_q -from finesse.simulations.base cimport BaseSimulation, NodeBeamParam +from finesse.simulations.base cimport NodeBeamParam +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation from finesse.cmatrix cimport SubCCSView1DArray from finesse.knm cimport ( KnmMatrix, @@ -37,32 +38,34 @@ cdef class LensValues(BaseCValues): cdef class LensConnections: - def __cinit__(self, BaseSimulation sim): - self.P1i_P2o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P1o = SubCCSView1DArray(sim.num_frequencies) + def __cinit__(self, CarrierSignalMatrixSimulation sim): + self.P1i_P2o = SubCCSView1DArray(sim.carrier.optical_frequencies.size) + self.P2i_P1o = SubCCSView1DArray(sim.carrier.optical_frequencies.size) self.conn_ptrs.P1i_P2o = self.P1i_P2o.views self.conn_ptrs.P2i_P1o = self.P2i_P1o.views cdef class LensWorkspace(KnmConnectorWorkspace): - def __init__(self, owner, BaseSimulation sim, refill): + def __init__(self, owner, CarrierSignalMatrixSimulation sim, refill): self.str_couplings = ("12", "21") super().__init__( owner, sim, refill, + refill, LensConnections(sim), + None, LensValues() ) - self.lc = self.connections + self.lc = self.carrier.connections self.lv = self.values - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) - self.P2i_id = sim.node_id(owner.p2.i) - self.P2o_id = sim.node_id(owner.p2.o) + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) + self.P2i_id = sim.carrier.node_id(owner.p2.i) + self.P2o_id = sim.carrier.node_id(owner.p2.o) self.sym_abcd_Cx = NULL self.sym_abcd_Cy = NULL @@ -196,16 +199,16 @@ cdef class LensWorkspace(KnmConnectorWorkspace): cdef void compute_scattering_matrices(self): # Transmission p1 -> p2 if knm_ws_is_changing(&self.K12_ws_x) or knm_ws_is_changing(&self.K12_ws_y): - compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.homs_view, self.K12.data_view) - reverse_gouy_phases(self.K12.data_view, self.sim.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) + compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.model_data.homs_view, self.K12.data_view) + reverse_gouy_phases(self.K12.data_view, self.sim.model_data.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K12.data_view, self.K12.data_view) # Transmission p2 -> p1 if knm_ws_is_changing(&self.K21_ws_x) or knm_ws_is_changing(&self.K21_ws_y): - compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.homs_view, self.K21.data_view) - reverse_gouy_phases(self.K21.data_view, self.sim.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) + compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.model_data.homs_view, self.K21.data_view) + reverse_gouy_phases(self.K21.data_view, self.sim.model_data.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K21.data_view, self.K21.data_view) diff --git a/src/finesse/components/modal/mirror.pxd b/src/finesse/components/modal/mirror.pxd index 407706ab18885d61b59261e2d6f8e6722d84244e..09d01934a24482d04adb64f196eada87b0493ef9 100644 --- a/src/finesse/components/modal/mirror.pxd +++ b/src/finesse/components/modal/mirror.pxd @@ -1,8 +1,8 @@ +from finesse.frequency cimport frequency_info_t from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray, SubCCSView2DArray from finesse.knm cimport KnmMatrix, KnmWorkspace from finesse.cymath cimport complex_t -from finesse.simulations.base cimport frequency_info_t, ModelData, NodeBeamParam -from finesse.simulations cimport BaseSimulation +from finesse.simulations.base cimport ModelData, NodeBeamParam from finesse.element cimport BaseCValues from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper from finesse.components.modal.workspace cimport KnmConnectorWorkspace @@ -31,12 +31,13 @@ cdef class MirrorValues(BaseCValues): double xbeta double ybeta -cdef struct mirror_connections: +cdef struct mirror_optical_connections: PyObject** P1i_P1o PyObject** P1i_P2o PyObject** P2i_P1o PyObject** P2i_P2o +cdef struct mirror_signal_connections: PyObject*** P1i_Fz PyObject*** P1o_Fz PyObject*** P2i_Fz @@ -44,20 +45,27 @@ cdef struct mirror_connections: PyObject*** Z_P1o PyObject*** Z_P2o -cdef class MirrorConnections: +cdef class MirrorOpticalConnections: cdef public: int P1i_P1o_idx int P2i_P2o_idx int P1i_P2o_idx int P2i_P1o_idx - int P1i_Fz_idx, P2i_Fz_idx, P1o_Fz_idx, P2o_Fz_idx - int Z_P1o_idx, Z_P2o_idx cdef readonly: SubCCSView1DArray P1i_P1o SubCCSView1DArray P2i_P2o SubCCSView1DArray P1i_P2o SubCCSView1DArray P2i_P1o + cdef: + mirror_optical_connections opt_conn_ptrs + +cdef class MirrorSignalConnections(MirrorOpticalConnections): + cdef public: + int P1i_Fz_idx, P2i_Fz_idx, P1o_Fz_idx, P2o_Fz_idx + int Z_P1o_idx, Z_P2o_idx + + cdef readonly: SubCCSView2DArray P1i_Fz SubCCSView2DArray P1o_Fz SubCCSView2DArray P2i_Fz @@ -65,13 +73,12 @@ cdef class MirrorConnections: SubCCSView2DArray Z_P1o SubCCSView2DArray Z_P2o cdef: - mirror_connections conn_ptrs + mirror_signal_connections sig_conn_ptrs cdef class MirrorWorkspace(KnmConnectorWorkspace): cdef public: complex_t field_to_Fz complex_t z_to_field - int audio_fill_mode # Complete scattering matrices for each propagation direction KnmMatrix K11 @@ -92,8 +99,7 @@ cdef class MirrorWorkspace(KnmConnectorWorkspace): Py_ssize_t car_p1o_rhs_idx, car_p1i_rhs_idx Py_ssize_t car_p2o_rhs_idx, car_p2i_rhs_idx Py_ssize_t car_p_num_hom - frequency_info_t[::1] z_mech_freqs - + # Refractive indices of adjacent spaces double nr1 double nr2 @@ -111,7 +117,8 @@ cdef class MirrorWorkspace(KnmConnectorWorkspace): double[:, ::1] abcd_p2p1_y cdef: MirrorValues mv - MirrorConnections mc + MirrorOpticalConnections mcc + MirrorSignalConnections mcs # Indices (in sim._c_node_info and sim.trace) of nodes Py_ssize_t P1i_id @@ -139,6 +146,9 @@ cdef class MirrorWorkspace(KnmConnectorWorkspace): KnmWorkspace K21_ws_x KnmWorkspace K21_ws_y + frequency_info_t *z_mech_freqs + Py_ssize_t z_mech_freqs_size + cpdef compile_abcd_cy_exprs(self) cpdef update_parameter_values(self) diff --git a/src/finesse/components/modal/mirror.pyx b/src/finesse/components/modal/mirror.pyx index c80f55b3a1898126d5004001e879dee851bca5b1..cbc9e57bd2c95ffd311736ce30250d74352d9931 100644 --- a/src/finesse/components/modal/mirror.pyx +++ b/src/finesse/components/modal/mirror.pyx @@ -24,8 +24,10 @@ from finesse.knm cimport ( flip_odd_horizontal, knm_loss ) +from finesse.frequency cimport FrequencyContainer from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray from finesse.symbols import Symbol +from finesse.simulations.basematrix cimport MatrixSystemSolver, CarrierSignalMatrixSimulation from cpython.ref cimport PyObject, Py_XINCREF, Py_XDECREF from libc.string cimport strcmp, memcpy @@ -51,63 +53,90 @@ cdef class MirrorValues(BaseCValues): self.setup(params, sizeof(ptr), &ptr) -cdef class MirrorConnections: - def __cinit__(self, object mirror, BaseSimulation sim): - # Only 1D arrays of views as spaces don't - # couple frequencies together. - Nf = sim.num_frequencies - self.P1i_P1o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P2o = SubCCSView1DArray(sim.num_frequencies) - self.P1i_P2o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P1o = SubCCSView1DArray(sim.num_frequencies) - self.conn_ptrs.P1i_P1o = self.P1i_P1o.views - self.conn_ptrs.P2i_P2o = self.P2i_P2o.views - self.conn_ptrs.P1i_P2o = self.P1i_P2o.views - self.conn_ptrs.P2i_P1o = self.P2i_P1o.views +cdef class MirrorOpticalConnections: + """Contains C accessible references to submatrices for + optical connections for this element. + """ + def __cinit__(self, object mirror, MatrixSystemSolver mtx): + # Only 1D arrays of submatrices as no frequency coupling happening + cdef int Nf = mtx.optical_frequencies.size + + self.P1i_P1o = SubCCSView1DArray(Nf) + self.P2i_P2o = SubCCSView1DArray(Nf) + self.P1i_P2o = SubCCSView1DArray(Nf) + self.P2i_P1o = SubCCSView1DArray(Nf) + self.opt_conn_ptrs.P1i_P1o = self.P1i_P1o.views + self.opt_conn_ptrs.P2i_P2o = self.P2i_P2o.views + self.opt_conn_ptrs.P1i_P2o = self.P1i_P2o.views + self.opt_conn_ptrs.P2i_P1o = self.P2i_P1o.views + + +cdef class MirrorSignalConnections(MirrorOpticalConnections): + """Contains C accessible references to submatrices for + optical, electrical, and mechanical connections for this + element. + """ + def __cinit__(self, object mirror, MatrixSystemSolver mtx): + cdef: + int Nfo = mtx.optical_frequencies.size Nmz = mirror.mech.z.num_frequencies # num of mechanic frequencies - self.P1i_Fz = SubCCSView2DArray(sim.num_frequencies, Nmz) - self.P1o_Fz = SubCCSView2DArray(sim.num_frequencies, Nmz) - self.P2i_Fz = SubCCSView2DArray(sim.num_frequencies, Nmz) - self.P2o_Fz = SubCCSView2DArray(sim.num_frequencies, Nmz) - self.Z_P1o = SubCCSView2DArray(Nmz, sim.num_frequencies) - self.Z_P2o = SubCCSView2DArray(Nmz, sim.num_frequencies) + self.P1i_Fz = SubCCSView2DArray(Nfo, Nmz) + self.P1o_Fz = SubCCSView2DArray(Nfo, Nmz) + self.P2i_Fz = SubCCSView2DArray(Nfo, Nmz) + self.P2o_Fz = SubCCSView2DArray(Nfo, Nmz) + self.Z_P1o = SubCCSView2DArray(Nmz, Nfo) + self.Z_P2o = SubCCSView2DArray(Nmz, Nfo) - self.conn_ptrs.P1i_Fz = self.P1i_Fz.views - self.conn_ptrs.P2i_Fz = self.P2i_Fz.views - self.conn_ptrs.P1o_Fz = self.P1o_Fz.views - self.conn_ptrs.P2o_Fz = self.P2o_Fz.views - self.conn_ptrs.Z_P1o = self.Z_P1o.views - self.conn_ptrs.Z_P2o = self.Z_P2o.views + self.sig_conn_ptrs.P1i_Fz = self.P1i_Fz.views + self.sig_conn_ptrs.P2i_Fz = self.P2i_Fz.views + self.sig_conn_ptrs.P1o_Fz = self.P1o_Fz.views + self.sig_conn_ptrs.P2o_Fz = self.P2o_Fz.views + self.sig_conn_ptrs.Z_P1o = self.Z_P1o.views + self.sig_conn_ptrs.Z_P2o = self.Z_P2o.views cdef class MirrorWorkspace(KnmConnectorWorkspace): - def __init__(self, owner, BaseSimulation sim, refill): + def __init__(self, owner, CarrierSignalMatrixSimulation sim, refill): + cdef FrequencyContainer fcnt self.str_couplings = ("11", "12", "21", "22") super().__init__( owner, sim, refill, - MirrorConnections(owner, sim), + refill, + MirrorOpticalConnections(owner, sim.carrier), + MirrorSignalConnections(owner, sim.signal) if sim.signal else None, MirrorValues() ) - self.mc = self.connections + self.mcc = self.carrier.connections + if sim.signal: + self.mcs = self.signal.connections + else: + self.mcs = None + self.mv = self.values - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) - self.P2i_id = sim.node_id(owner.p2.i) - self.P2o_id = sim.node_id(owner.p2.o) - - if sim.DC is not None: - self.car_p1o_rhs_idx = sim.DC.get_node_info(owner.p1.o)['rhs_index'] - self.car_p2o_rhs_idx = sim.DC.get_node_info(owner.p2.o)['rhs_index'] - self.car_p1i_rhs_idx = sim.DC.get_node_info(owner.p1.i)['rhs_index'] - self.car_p2i_rhs_idx = sim.DC.get_node_info(owner.p2.i)['rhs_index'] - self.car_p_num_hom = sim.DC.get_node_info(owner.p1.o)['nhoms'] + # These are only use for beam tracing as far as I can tell + # for the beam tracing, which only has optical nodes. + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) + self.P2i_id = sim.carrier.node_id(owner.p2.i) + self.P2o_id = sim.carrier.node_id(owner.p2.o) + + if sim.signal: + # If we have a signal simulation then we need to cache some indicies + # for grabbing data when filling + self.car_p1o_rhs_idx = sim.carrier.get_node_info(owner.p1.o)['rhs_index'] + self.car_p2o_rhs_idx = sim.carrier.get_node_info(owner.p2.o)['rhs_index'] + self.car_p1i_rhs_idx = sim.carrier.get_node_info(owner.p1.i)['rhs_index'] + self.car_p2i_rhs_idx = sim.carrier.get_node_info(owner.p2.i)['rhs_index'] + self.car_p_num_hom = sim.carrier.get_node_info(owner.p1.o)['nhoms'] # Get a reference to the mechanical node frequencies - self.z_mech_freqs = sim.mechanical_frequencies[owner.mech.z].frequency_info + fcnt = sim.signal.mechanical_frequencies[owner.mech.z] + self.z_mech_freqs = fcnt.frequency_info + self.z_mech_freqs_size = sim.signal.mechanical_frequencies[owner.mech.z].size self.sym_abcd_Cs[:] = [ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL @@ -279,32 +308,32 @@ cdef class MirrorWorkspace(KnmConnectorWorkspace): cdef void compute_scattering_matrices(self): # Reflection p1 -> p1 if knm_ws_is_changing(&self.K11_ws_x) or knm_ws_is_changing(&self.K11_ws_y): - compute_knm_matrix_bh(&self.K11_ws_x, &self.K11_ws_y, self.sim.homs_view, self.K11.data_view) - reverse_gouy_phases(self.K11.data_view, self.sim.homs_view, &self.K11_ws_x, &self.K11_ws_y, self.K11.data_view) - + compute_knm_matrix_bh(&self.K11_ws_x, &self.K11_ws_y, self.sim.model_data.homs_view, self.K11.data_view) + reverse_gouy_phases(self.K11.data_view, self.sim.model_data.homs_view, &self.K11_ws_x, &self.K11_ws_y, self.K11.data_view) + if self.sim.model_data.zero_K00: zero_tem00_phase(self.K11.data_view, self.K11.data_view) - - flip_odd_horizontal(self.K11.data_view, self.sim.homs_view, self.K11.data_view) - + + flip_odd_horizontal(self.K11.data_view, self.sim.model_data.homs_view, self.K11.data_view) + knm_loss(self.K11.data_view, self.K11_loss) - + # Reflection p2 -> p2 if knm_ws_is_changing(&self.K22_ws_x) or knm_ws_is_changing(&self.K22_ws_y): - compute_knm_matrix_bh(&self.K22_ws_x, &self.K22_ws_y, self.sim.homs_view, self.K22.data_view) - reverse_gouy_phases(self.K22.data_view, self.sim.homs_view, &self.K22_ws_x, &self.K22_ws_y, self.K22.data_view) + compute_knm_matrix_bh(&self.K22_ws_x, &self.K22_ws_y, self.sim.model_data.homs_view, self.K22.data_view) + reverse_gouy_phases(self.K22.data_view, self.sim.model_data.homs_view, &self.K22_ws_x, &self.K22_ws_y, self.K22.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K22.data_view, self.K22.data_view) - flip_odd_horizontal(self.K22.data_view, self.sim.homs_view, self.K22.data_view) + flip_odd_horizontal(self.K22.data_view, self.sim.model_data.homs_view, self.K22.data_view) knm_loss(self.K22.data_view, self.K22_loss) # Transmission p1 -> p2 if knm_ws_is_changing(&self.K12_ws_x) or knm_ws_is_changing(&self.K12_ws_y): - compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.homs_view, self.K12.data_view) - reverse_gouy_phases(self.K12.data_view, self.sim.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) + compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.model_data.homs_view, self.K12.data_view) + reverse_gouy_phases(self.K12.data_view, self.sim.model_data.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K12.data_view, self.K12.data_view) @@ -313,8 +342,8 @@ cdef class MirrorWorkspace(KnmConnectorWorkspace): # Transmission p2 -> p1 if knm_ws_is_changing(&self.K21_ws_x) or knm_ws_is_changing(&self.K21_ws_y): - compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.homs_view, self.K21.data_view) - reverse_gouy_phases(self.K21.data_view, self.sim.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) + compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.model_data.homs_view, self.K21.data_view) + reverse_gouy_phases(self.K21.data_view, self.sim.model_data.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K21.data_view, self.K21.data_view) @@ -387,9 +416,34 @@ cdef class MirrorWorkspace(KnmConnectorWorkspace): &self.K21_ws_y, qy_p1o_matched_trns, q_p1o.qy, ybeta, beta_factor_21, self.nr1, lambda0, maxtem, ) +cdef inline void mirror_fill_optical_2_optical( + mirror_optical_connections *conn, + MirrorWorkspace ws, + frequency_info_t *freq, + double r, + complex_t it, + double phi + ): + cdef double phase_shift = 2.0 * phi * (1 + freq.f / ws.sim.model_data.f0) + cdef complex_t tuning = cexp(1.0j * phase_shift) + cdef complex_t ctuning = conj(tuning) + # Reflections + (conn.P1i_P1o[freq.index]).fill_za_zm_2( + r * tuning, ws.K11.ptr, ws.K11.stride1, ws.K11.stride2 + ) + (conn.P2i_P2o[freq.index]).fill_za_zm_2( + r * ctuning, ws.K22.ptr, ws.K22.stride1, ws.K22.stride2 + ) + # Transmission + (conn.P1i_P2o[freq.index]).fill_za_zm_2( + it, ws.K12.ptr, ws.K12.stride1, ws.K12.stride2 + ) + (conn.P2i_P1o[freq.index]).fill_za_zm_2( + it, ws.K21.ptr, ws.K21.stride1, ws.K21.stride2 + ) -mirror_fill = FillFuncWrapper.make_from_ptr(c_mirror_fill) -cdef object c_mirror_fill(ConnectorWorkspace cws): +mirror_carrier_fill = FillFuncWrapper.make_from_ptr(c_mirror_carrier_fill) +cdef object c_mirror_carrier_fill(ConnectorWorkspace cws): r""" Fills the sub-matrix of the interferometer matrix held by `sim`, corresponding to the `mirror` component. @@ -455,55 +509,49 @@ cdef object c_mirror_fill(ConnectorWorkspace cws): MirrorWorkspace ws = cws complex_t _it = 1.0j * sqrt(ws.mv.T) double _r = sqrt(ws.mv.R) + double phi = radians(ws.mv.phi) + Py_ssize_t i, size + mirror_optical_connections *conn = &ws.mcc.opt_conn_ptrs + frequency_info_t *frequencies + + size = ws.sim.carrier.optical_frequencies.size + frequencies = ws.sim.carrier.optical_frequencies.frequency_info + + for i in range(size): + mirror_fill_optical_2_optical(conn, ws, &(frequencies[i]), _r, _it, phi) + +mirror_signal_fill = FillFuncWrapper.make_from_ptr(c_mirror_signal_fill) +cdef object c_mirror_signal_fill(ConnectorWorkspace cws): + cdef: + MirrorWorkspace ws = cws + complex_t _it = 1.0j * sqrt(ws.mv.T) + double _r = sqrt(ws.mv.R) double phi = radians(ws.mv.phi) - double phase_shift - double phi_c - double f_car - complex_t _tuning - complex_t _ctuning - - Py_ssize_t _i - mirror_connections conn = ws.mc.conn_ptrs - mirror_connections *ptr_conn = &ws.mc.conn_ptrs + Py_ssize_t i, size + mirror_optical_connections *conn = &ws.mcs.opt_conn_ptrs + mirror_signal_connections *sconn = &ws.mcs.sig_conn_ptrs frequency_info_t *freq - int i + frequency_info_t *frequencies - # TODO these can all be computed in the workspace init + size = ws.sim.signal.optical_frequencies.size + frequencies = ws.sim.signal.optical_frequencies.frequency_info ws.z_to_field = 1j * ws.sim.model_data.k0 * _r * ws.sim.model_data.x_scale ws.field_to_Fz = 1 / (C_LIGHT * ws.sim.model_data.x_scale) - for i in range(ws.sim.num_frequencies): - freq = &(ws.sim.frequency_info[i]) - phase_shift = 2.0 * phi * (1 + freq.f / ws.sim.model_data.f0) - _tuning = cexp(1.0j * phase_shift) - _ctuning = conj(_tuning) - - # Reflections - (conn.P1i_P1o[freq.index]).fill_za_zm_2( - _r * _tuning, ws.K11.ptr, ws.K11.stride1, ws.K11.stride2 - ) - (conn.P2i_P2o[freq.index]).fill_za_zm_2( - _r * _ctuning, ws.K22.ptr, ws.K22.stride1, ws.K22.stride2 - ) - # Transmission - (conn.P1i_P2o[freq.index]).fill_za_zm_2( - _it, ws.K12.ptr, ws.K12.stride1, ws.K12.stride2 - ) - (conn.P2i_P1o[freq.index]).fill_za_zm_2( - _it, ws.K21.ptr, ws.K21.stride1, ws.K21.stride2 - ) + for i in range(size): + freq = &(frequencies[i]) + mirror_fill_optical_2_optical(conn, ws, freq, _r, _it, phi) - if ws.sim.is_audio: - if ws.z_mech_freqs.size == 1: - single_z_mechanical_frequency_signal_calc(ws, ptr_conn, freq, phi, 0, freq.audio_carrier_index) - else: - multiple_z_mechanical_freq_signal_calc(ws, ptr_conn, freq, phi) + if ws.z_mech_freqs_size == 1: + single_z_mechanical_frequency_signal_calc(ws, sconn, freq, phi, 0, freq.audio_carrier_index) + else: + multiple_z_mechanical_freq_signal_calc(ws, sconn, freq, phi) cdef void multiple_z_mechanical_freq_signal_calc ( MirrorWorkspace ws, - mirror_connections *conn, + mirror_signal_connections *conn, frequency_info_t *freq, double phi ): @@ -516,13 +564,16 @@ cdef void multiple_z_mechanical_freq_signal_calc ( const complex_t* c_p1_o = NULL const complex_t* c_p2_o = NULL Py_ssize_t i, j - frequency_info_t c, s + frequency_info_t *c + frequency_info_t *s + frequency_info_t *ofrequencies = ws.sim.carrier.optical_frequencies.frequency_info + Py_ssize_t osize = ws.sim.carrier.optical_frequencies.size double fs, fc, fm - for i in range(ws.sim.DC.num_frequencies): # Loop over optical DC - for j in range(ws.z_mech_freqs.size): # Loop over mechanical frequencies + for i in range(osize): # Loop over optical DC + for j in range(ws.z_mech_freqs_size): # Loop over mechanical frequencies fs = freq.f - fc = ws.sim.DC.frequency_info[i].f + fc = ofrequencies[i].f fm = ws.z_mech_freqs[j].f if (fc-fs == fm) or (fs-fc == fm): @@ -535,38 +586,39 @@ cdef void multiple_z_mechanical_freq_signal_calc ( cdef void single_z_mechanical_frequency_signal_calc ( MirrorWorkspace ws, - mirror_connections *conn, + mirror_signal_connections *conn, frequency_info_t *freq, double phi, Py_ssize_t z_freq_idx, Py_ssize_t carrier_index ): cdef: + complex_t _tuning, _ctuning const complex_t* c_p1_i = NULL const complex_t* c_p2_i = NULL const complex_t* c_p1_o = NULL const complex_t* c_p2_o = NULL # Get incoming/outgoing carrier field amplitudes - c_p1_i = &ws.sim.DC.out_view[ws.sim.field_fast_2( + c_p1_i = &ws.sim.carrier.out_view[ws.sim.carrier.field_fast_2( ws.car_p1i_rhs_idx, ws.car_p_num_hom, carrier_index, 0 )] - c_p2_i = &ws.sim.DC.out_view[ws.sim.field_fast_2( + c_p2_i = &ws.sim.carrier.out_view[ws.sim.carrier.field_fast_2( ws.car_p2i_rhs_idx, ws.car_p_num_hom, carrier_index, 0 )] - c_p1_o = &ws.sim.DC.out_view[ws.sim.field_fast_2( + c_p1_o = &ws.sim.carrier.out_view[ws.sim.carrier.field_fast_2( ws.car_p1o_rhs_idx, ws.car_p_num_hom, carrier_index, 0 )] - c_p2_o = &ws.sim.DC.out_view[ws.sim.field_fast_2( + c_p2_o = &ws.sim.carrier.out_view[ws.sim.carrier.field_fast_2( ws.car_p2o_rhs_idx, ws.car_p_num_hom, carrier_index, @@ -615,7 +667,7 @@ cdef void single_z_mechanical_frequency_signal_calc ( # # phase_shift = phi * (1 + car.f / f0) + phi * (1 + freq.f/f0) # = phi * (2 + (order*fsig + freq.f) / f0) - phase_shift = phi * (2 + (freq.f + freq.f_car) / ws.sim.model_data.f0) + phase_shift = phi * (2 + (freq.f + freq.f_car[0]) / ws.sim.model_data.f0) # ----------------------------------------------------------------- # Signal generation z->p1.o diff --git a/src/finesse/components/modal/modulator.pxd b/src/finesse/components/modal/modulator.pxd index a37f83b0988cacb1e8c70e2377c0f13f2519377c..9c9ac5a52795e370a6613b5391f573d321478586 100644 --- a/src/finesse/components/modal/modulator.pxd +++ b/src/finesse/components/modal/modulator.pxd @@ -2,7 +2,8 @@ from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper from finesse.element cimport BaseCValues from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray from finesse.knm cimport KnmMatrix, KnmWorkspace -from finesse.simulations.base cimport frequency_info_t, ModelData, BaseSimulation +from finesse.simulations.base cimport ModelData +from finesse.frequency cimport frequency_info_t from finesse.components.modal.workspace cimport KnmConnectorWorkspace import numpy as np @@ -19,7 +20,8 @@ cdef class ModulatorWorkspace(KnmConnectorWorkspace): double nr1 double nr2 - dict coupling_orders + dict sig_coupling_orders + dict car_coupling_orders cdef: # Indices (in sim._c_node_info and sim.trace) of nodes diff --git a/src/finesse/components/modal/modulator.pyx b/src/finesse/components/modal/modulator.pyx index 5cf3923808100d58aa3d497b5243017a86b28c49..4dc24a1a614a018db21218e4ff852feb08dacdfd 100644 --- a/src/finesse/components/modal/modulator.pyx +++ b/src/finesse/components/modal/modulator.pyx @@ -2,7 +2,9 @@ from finesse.cymath cimport complex_t from finesse.cymath.complex cimport conj, cexp from finesse.cymath.math cimport exp, sqrt, cos, sin, asin, isfinite from finesse.cymath.math cimport radians -from finesse.simulations.base cimport frequency_info_t, BaseSimulation, NodeBeamParam +from finesse.simulations.base cimport NodeBeamParam +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation, MatrixSystemSolver +from finesse.frequency cimport frequency_info_t from finesse.knm cimport ( KnmMatrix, @@ -32,17 +34,17 @@ LOGGER = logging.getLogger(__name__) cdef class ModulatorWorkspace(KnmConnectorWorkspace): - def __init__(self, object owner, BaseSimulation sim, bint refill): + def __init__(self, object owner, CarrierSignalMatrixSimulation sim, bint refill): from finesse.components.workspace import Connections self.str_couplings = ("12", "21") - super().__init__(owner, sim, refill, Connections(sim)) + super().__init__(owner, sim, refill, refill, Connections(), Connections()) - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) - self.P2i_id = sim.node_id(owner.p2.i) - self.P2o_id = sim.node_id(owner.p2.o) + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) + self.P2i_id = sim.carrier.node_id(owner.p2.i) + self.P2o_id = sim.carrier.node_id(owner.p2.o) cdef void initialise_knm_workspaces(self) nogil: cdef: @@ -134,22 +136,22 @@ cdef class ModulatorWorkspace(KnmConnectorWorkspace): cdef void compute_scattering_matrices(self): # Transmission p1 -> p2 if knm_ws_is_changing(&self.K12_ws_x) or knm_ws_is_changing(&self.K12_ws_y): - compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.homs_view, self.K12.data_view) - reverse_gouy_phases(self.K12.data_view, self.sim.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) + compute_knm_matrix_bh(&self.K12_ws_x, &self.K12_ws_y, self.sim.model_data.homs_view, self.K12.data_view) + reverse_gouy_phases(self.K12.data_view, self.sim.model_data.homs_view, &self.K12_ws_x, &self.K12_ws_y, self.K12.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K12.data_view, self.K12.data_view) # Transmission p2 -> p1 if knm_ws_is_changing(&self.K21_ws_x) or knm_ws_is_changing(&self.K21_ws_y): - compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.homs_view, self.K21.data_view) - reverse_gouy_phases(self.K21.data_view, self.sim.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) + compute_knm_matrix_bh(&self.K21_ws_x, &self.K21_ws_y, self.sim.model_data.homs_view, self.K21.data_view) + reverse_gouy_phases(self.K21.data_view, self.sim.model_data.homs_view, &self.K21_ws_x, &self.K21_ws_y, self.K21.data_view) if self.sim.model_data.zero_K00: zero_tem00_phase(self.K21.data_view, self.K21.data_view) -def modulator_fill(object modulator, ModulatorWorkspace ws, dict values, dict cporders, unicode mod_type): +def modulator_fill(object modulator, ModulatorWorkspace ws, MatrixSystemSolver mtx, connections, dict values, dict cporders, unicode mod_type): cdef: double phi = radians(values["phase"]) double phi_factor = phi @@ -166,7 +168,6 @@ def modulator_fill(object modulator, ModulatorWorkspace ws, dict values, dict cp np.ndarray[double, ndim=1] amps double[::1] amps_view - BaseSimulation sim = ws.sim for i in range(-order, order + 1): if i < 0: @@ -193,9 +194,9 @@ def modulator_fill(object modulator, ModulatorWorkspace ws, dict values, dict cp factors_12 = np.multiply.outer(amps * phases, ws.K12.data) factors_21 = np.multiply.outer(amps * phases, ws.K21.data) - + for (f1, f2), _order in cporders.items(): - with sim.component_edge_fill3(ws.owner_id, ws.connections.P1i_P2o_idx, f1.index, f2.index) as mat: + with mtx.component_edge_fill3(ws.owner_id, connections.P1i_P2o_idx, f1.index, f2.index) as mat: mat[:] = factors_12[_order][:] - with sim.component_edge_fill3(ws.owner_id, ws.connections.P2i_P1o_idx, f1.index, f2.index) as mat: + with mtx.component_edge_fill3(ws.owner_id, connections.P2i_P1o_idx, f1.index, f2.index) as mat: mat[:] = factors_21[_order][:] diff --git a/src/finesse/components/modal/signal.pyx b/src/finesse/components/modal/signal.pyx index 3d0694400417e189e9d43d9ec33d17ac503df740..1dc3bd512401ff2ef7571cdf338c4ba187700598 100644 --- a/src/finesse/components/modal/signal.pyx +++ b/src/finesse/components/modal/signal.pyx @@ -22,7 +22,9 @@ cdef class SignalGeneratorWorkspace(ConnectorWorkspace): owner, sim, refill, - Connections(sim), + refill, + Connections(), + Connections(), SignalGeneratorValues() ) self.v = self.values @@ -35,4 +37,4 @@ cdef object c_siggen_fill_rhs(ConnectorWorkspace cws): complex_t A A = (ws.v.amplitude * ws.scaling) * cexp(1j * ws.v.phase * DEG2RAD) - ws.sim.set_source_fast_2(ws.rhs_idx, A) + ws.sim.signal.set_source_fast_2(ws.rhs_idx, A) diff --git a/src/finesse/components/modal/space.pxd b/src/finesse/components/modal/space.pxd index dfa8944b90349e619676f7da1ebc47e37ac2fad0..de3ab5f351618db66dbf4bb28bb6572f313ac0d4 100644 --- a/src/finesse/components/modal/space.pxd +++ b/src/finesse/components/modal/space.pxd @@ -1,9 +1,9 @@ from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray, SubCCSView2DArray from finesse.knm cimport KnmMatrix from finesse.cymath cimport complex_t -from finesse.simulations.base cimport frequency_info_t, ModelData -from finesse.simulations cimport BaseSimulation -from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper +from finesse.simulations.base cimport ModelData +from finesse.frequency cimport frequency_info_t +from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper, GouyFuncWrapper from finesse.element cimport BaseCValues from finesse.cyexpr cimport ( @@ -19,24 +19,36 @@ from cpython.ref cimport PyObject import numpy as np cimport numpy as np -cdef struct space_connections: +cdef struct space_optical_connections: + # 1D array of SubCCSViews PyObject** P1i_P2o - PyObject** P2i_P1o # 1D array of SubCCSViews + PyObject** P2i_P1o +cdef struct space_signal_connections: + # 2D array of SubCCSViews + PyObject*** SIGPHS_P1o + PyObject*** SIGPHS_P2o -cdef class SpaceConnections: + +cdef class SpaceOpticalConnections: cdef public: int P1i_P2o_idx int P2i_P1o_idx - int SIGPHS_P1o_idx - int SIGPHS_P2o_idx cdef readonly: SubCCSView1DArray P1i_P2o SubCCSView1DArray P2i_P1o + cdef: + space_optical_connections opt_ptrs + +cdef class SpaceSignalConnections(SpaceOpticalConnections): + cdef public: + int SIGPHS_P1o_idx + int SIGPHS_P2o_idx + cdef readonly: SubCCSView2DArray SIGPHS_P1o SubCCSView2DArray SIGPHS_P2o cdef: - space_connections ptrs + space_signal_connections sig_ptrs cdef class SpaceValues(BaseCValues): @@ -58,7 +70,8 @@ cdef class SpaceWorkspace(ConnectorWorkspace): # ABCD matrix view (note same for all couplings) double[:, ::1] abcd cdef: - SpaceConnections sc + SpaceOpticalConnections sco + SpaceSignalConnections scs SpaceValues sv # Only element B of ABCD matrix can change, store pointer @@ -71,6 +84,4 @@ cdef class SpaceWorkspace(ConnectorWorkspace): cpdef update_parameter_values(self) -cdef object c_space_fill(ConnectorWorkspace cws) - -cdef int set_gouy_phase(SpaceWorkspace ws) except -1 +cdef object c_space_carrier_fill(ConnectorWorkspace cws) diff --git a/src/finesse/components/modal/space.pyx b/src/finesse/components/modal/space.pyx index 90c47c7a924d18e10179e90be8f861968e44c87a..8cf576dea6213bd42566111b3d44c91201fba6ff 100644 --- a/src/finesse/components/modal/space.pyx +++ b/src/finesse/components/modal/space.pyx @@ -4,8 +4,9 @@ from finesse.cymath.math cimport fabs, radians, degrees from finesse.cymath.gaussbeam cimport gouy, transform_q from finesse.cmatrix cimport SubCCSView, SubCCSView1DArray, SubCCSView2DArray from finesse.knm cimport KnmMatrix -from finesse.simulations.base cimport frequency_info_t, ModelData, NodeBeamParam -from finesse.simulations cimport BaseSimulation +from finesse.simulations.base cimport ModelData, NodeBeamParam +from finesse.frequency cimport frequency_info_t +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation, MatrixSystemSolver from finesse.symbols import Symbol import numpy as np @@ -23,18 +24,27 @@ cdef extern from "constants.h": double C_LIGHT double DEG2RAD -cdef class SpaceConnections: - def __cinit__(self, BaseSimulation sim): +cdef class SpaceOpticalConnections: + def __cinit__(self, MatrixSystemSolver mtx): # Only 1D arrays of views as spaces don't # couple frequencies together. - Nf = sim.num_frequencies - self.P1i_P2o = SubCCSView1DArray(sim.num_frequencies) - self.P2i_P1o = SubCCSView1DArray(sim.num_frequencies) - self.SIGPHS_P1o = SubCCSView2DArray(1, sim.num_frequencies) - self.SIGPHS_P2o = SubCCSView2DArray(1, sim.num_frequencies) - - self.ptrs.P1i_P2o = self.P1i_P2o.views - self.ptrs.P2i_P1o = self.P2i_P1o.views + Nf = mtx.optical_frequencies.size + self.P1i_P2o = SubCCSView1DArray(Nf) + self.P2i_P1o = SubCCSView1DArray(Nf) + self.opt_ptrs.P1i_P2o = self.P1i_P2o.views + self.opt_ptrs.P2i_P1o = self.P2i_P1o.views + +cdef class SpaceSignalConnections(SpaceOpticalConnections): + def __cinit__(self, MatrixSystemSolver mtx): + # Only 1D arrays of views as spaces don't + # couple frequencies together. + if not mtx.is_signal_matrix: + raise Exception("Signal simulation not enabled") + Nf = mtx.optical_frequencies.size + self.SIGPHS_P1o = SubCCSView2DArray(1, Nf) + self.SIGPHS_P2o = SubCCSView2DArray(1, Nf) + self.sig_ptrs.SIGPHS_P1o = self.SIGPHS_P1o.views + self.sig_ptrs.SIGPHS_P2o = self.SIGPHS_P2o.views cdef class SpaceValues(BaseCValues): def __init__(self): @@ -44,19 +54,25 @@ cdef class SpaceValues(BaseCValues): cdef class SpaceWorkspace(ConnectorWorkspace): - def __init__(self, object owner, BaseSimulation sim, bint refill): - super().__init__(owner, sim, refill, SpaceConnections(sim), SpaceValues()) + def __init__(self, object owner, CarrierSignalMatrixSimulation sim, bint refill): + super().__init__( + owner, sim, refill, refill, + SpaceOpticalConnections(sim.carrier), + SpaceSignalConnections(sim.signal) if sim.signal else None, + SpaceValues() + ) # Here we cast connections and values to a non-specific object # so the fill functions don't have to and cython can optimise better - self.sc = self.connections + self.sco = self.carrier.connections + self.scs = self.signal.connections if sim.signal else None self.sv = self.values - self.P1i_id = sim.node_id(owner.p1.i) - self.P1o_id = sim.node_id(owner.p1.o) - self.P2i_id = sim.node_id(owner.p2.i) - self.P2o_id = sim.node_id(owner.p2.o) + self.P1i_id = sim.carrier.node_id(owner.p1.i) + self.P1o_id = sim.carrier.node_id(owner.p1.o) + self.P2i_id = sim.carrier.node_id(owner.p2.i) + self.P2o_id = sim.carrier.node_id(owner.p2.o) - self.couplings = calloc(sim.nhoms, sizeof(complex_t)) + self.couplings = calloc(sim.model_data.num_HOMs, sizeof(complex_t)) self.sym_abcd_B = NULL def __dealloc__(self): @@ -87,8 +103,8 @@ cdef class SpaceWorkspace(ConnectorWorkspace): self.abcd[0][1] = cy_expr_eval(self.sym_abcd_B) -space_fill = FillFuncWrapper.make_from_ptr(c_space_fill) -cdef object c_space_fill(ConnectorWorkspace cws): +space_carrier_fill = FillFuncWrapper.make_from_ptr(c_space_carrier_fill) +cdef object c_space_carrier_fill(ConnectorWorkspace cws): r""" Fills the sub-matrix of the interferometer matrix held by `sim`, corresponding to the `space` component. @@ -127,11 +143,10 @@ cdef object c_space_fill(ConnectorWorkspace cws): """ cdef: SpaceWorkspace ws = cws - space_connections conns = ws.sc.ptrs + space_optical_connections conns = ws.sco.opt_ptrs double f0 = ws.sim.model_data.f0 - double nr = ws.sv.nr - double L = ws.sv.L - double pre_factor = 2 * PI * nr * L / C_LIGHT + + double pre_factor = 2 * PI * ws.sv.nr * ws.sv.L / C_LIGHT double epsilon0_c = ws.sim.model_data.EPSILON0_C double f_car double fs @@ -141,13 +156,17 @@ cdef object c_space_fill(ConnectorWorkspace cws): double gouy_x = radians(ws.sv.gouy_x) double gouy_y = radians(ws.sv.gouy_y) - Py_ssize_t field_idx + Py_ssize_t field_idx, size int n, m, i double ni, mi, gouy - frequency_info_t freq + frequency_info_t *frequencies + frequency_info_t *freq - for i in range(ws.sim.num_frequencies): - freq = ws.sim.frequency_info[i] + frequencies = ws.sim.carrier.optical_frequencies.frequency_info + size = ws.sim.carrier.optical_frequencies.size + + for i in range(size): + freq = &frequencies[i] phi = pre_factor * freq.f for field_idx in range(ws.sim.model_data.num_HOMs): @@ -168,6 +187,55 @@ cdef object c_space_fill(ConnectorWorkspace cws): (conns.P1i_P2o[freq.index]).fill_zd_2(ws.couplings, 1) (conns.P2i_P1o[freq.index]).fill_zd_2(ws.couplings, 1) +space_signal_fill = FillFuncWrapper.make_from_ptr(c_space_signal_fill) +cdef object c_space_signal_fill(ConnectorWorkspace cws): + cdef: + SpaceWorkspace ws = cws + space_optical_connections oconns = ws.scs.opt_ptrs + space_signal_connections sconns = ws.scs.sig_ptrs + double f0 = ws.sim.model_data.f0 + + double pre_factor = 2 * PI * ws.sv.nr * ws.sv.L / C_LIGHT + double epsilon0_c = ws.sim.model_data.EPSILON0_C + double f_car + double fs + double phi + complex_t factor + + double gouy_x = radians(ws.sv.gouy_x) + double gouy_y = radians(ws.sv.gouy_y) + + Py_ssize_t field_idx, size + int n, m, i + double ni, mi, gouy + frequency_info_t *frequencies + frequency_info_t *freq + + frequencies = ws.sim.signal.optical_frequencies.frequency_info + size = ws.sim.signal.optical_frequencies.size + + for i in range(size): + freq = &frequencies[i] + phi = pre_factor * freq.f + + for field_idx in range(ws.sim.model_data.num_HOMs): + n = ws.sim.model_data.homs_view[field_idx][0] + m = ws.sim.model_data.homs_view[field_idx][1] + + if ws.sim.model_data.zero_tem00_gouy: + ni = n + mi = m + else: + ni = n + 0.5 + mi = m + 0.5 + + gouy = ni * gouy_x + mi * gouy_y + ws.couplings[field_idx] = cexp(1j * (-phi + gouy)) + + # Fill diagonals with the vector + (oconns.P1i_P2o[freq.index]).fill_zd_2(ws.couplings, 1) + (oconns.P2i_P1o[freq.index]).fill_zd_2(ws.couplings, 1) + # if sim.is_audio: # carrier = sim.DC # fs = sim.model.fsig.f.eval() @@ -201,9 +269,11 @@ cdef object c_space_fill(ConnectorWorkspace cws): # np.multiply(c_p1_i, factor, out=mat[:]) +space_set_gouy = GouyFuncWrapper.make_from_ptr(c_set_gouy_phase) @cython.initializedcheck(False) -cdef int set_gouy_phase(SpaceWorkspace ws) except -1: - cdef: +cdef int c_set_gouy_phase(ConnectorWorkspace cws) except -1: + cdef: + SpaceWorkspace ws = cws NodeBeamParam q_p1o = ws.sim.trace[ws.P1i_id] NodeBeamParam q_p2i = ws.sim.trace[ws.P2o_id] diff --git a/src/finesse/components/modal/workspace.pxd b/src/finesse/components/modal/workspace.pxd index 473596c525932efbf05cc26d1b7be847bde4641c..943608acd1f6bb1b6902c0cc9ade61876370c7d3 100644 --- a/src/finesse/components/modal/workspace.pxd +++ b/src/finesse/components/modal/workspace.pxd @@ -5,7 +5,6 @@ cdef class KnmConnectorWorkspace(ConnectorWorkspace): # used by initialise_knm_matrices for convenience cdef readonly tuple str_couplings - cdef int initialise_knm_matrices(self) except -1 cdef void initialise_knm_workspaces(KnmConnectorWorkspace ws) nogil cdef void free_knm_workspaces(KnmConnectorWorkspace ws) nogil cdef void flag_changing_knm_workspaces(KnmConnectorWorkspace ws) diff --git a/src/finesse/components/modal/workspace.pyx b/src/finesse/components/modal/workspace.pyx index a1ebb2c136e3afcbe047198b2c5052fcb78603a6..43077195b9a4db5e200b0284ffa097d10902951e 100644 --- a/src/finesse/components/modal/workspace.pyx +++ b/src/finesse/components/modal/workspace.pyx @@ -10,45 +10,16 @@ cdef class KnmConnectorWorkspace(ConnectorWorkspace): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.initialise_knm_matrices() - - cdef int initialise_knm_matrices(self) except -1: # K_loss arrays should also be allocated for Surfaces # -> these give scattering losses from each (n,m) -> all (n',m') cdef bint make_knm_losses = isinstance(self.owner, components.Surface) - # Only allocate new memory for scattering matrices for the carrier simulation... - if not self.sim.is_audio: - for coupling in self.str_couplings: - kmat = KnmMatrix(self.sim.homs_view, self.owner.name, coupling) - setattr(self, f"K{coupling}", kmat) - - if make_knm_losses: - setattr(self, f"K{coupling}_loss", np.ones(self.sim.nhoms)) - - # ... audio simulation will use the carrier Knm matrices. - else: - DC = self.sim.DC - DC_workspaces = list(filter(lambda x: x.owner is self.owner, DC.workspaces)) - if not DC_workspaces: - raise RuntimeError( - "Bug encountered! Could not find a DC simulation workspace " - f"associated with component of name {self.owner.name}" - ) - if len(DC_workspaces) > 1: - raise RuntimeError( - "Bug encountered! Found multiple workspaces in the DC simulation " - f"associated with component of name {self.owner.name}" - ) - - DC_ws = DC_workspaces[0] - - for coupling in self.str_couplings: - setattr(self, f"K{coupling}", getattr(DC_ws, f"K{coupling}")) - if make_knm_losses: - setattr(self, f"K{coupling}_loss", getattr(DC_ws, f"K{coupling}_loss")) + for coupling in self.str_couplings: + kmat = KnmMatrix(self.sim.model_data.homs_view, self.owner.name, coupling) + setattr(self, f"K{coupling}", kmat) - return 0 + if make_knm_losses: + setattr(self, f"K{coupling}_loss", np.ones(self.sim.model_data.num_HOMs)) cdef void initialise_knm_workspaces(KnmConnectorWorkspace self) nogil: pass diff --git a/src/finesse/components/modulator.py b/src/finesse/components/modulator.py index e2b7bb7f6ebb35dfb6d9fe0325601aaeaededff0..76ae7193c14265c2e37f71f5b16a9655db530e69 100644 --- a/src/finesse/components/modulator.py +++ b/src/finesse/components/modulator.py @@ -104,9 +104,13 @@ class Modulator(Connector, FrequencyGenerator): def _couples_frequency(self, ws, connection, f_in, f_out): if f_in == f_out: return True - elif (f_in, f_out) in ws.coupling_orders: + elif (f_in, f_out) in ws.car_coupling_orders: return True - elif (f_out, f_in) in ws.coupling_orders: + elif (f_out, f_in) in ws.car_coupling_orders: + return True + elif (f_in, f_out) in ws.sig_coupling_orders: + return True + elif (f_out, f_in) in ws.sig_coupling_orders: return True else: return False @@ -143,8 +147,12 @@ class Modulator(Connector, FrequencyGenerator): self.__order = int(value) def _get_workspace(self, sim): - refill = sim.any_frequencies_changing or any( - _.is_changing for _ in self.parameters + refill = ( + sim.carrier.any_frequencies_changing + or (sim.signal.any_frequencies_changing if sim.signal is not None else False) + or any( + _.is_changing for _ in self.parameters + ) ) ws = ModulatorWorkspace(self, sim, refill) @@ -152,58 +160,62 @@ class Modulator(Connector, FrequencyGenerator): ws.nr1 = refractive_index(self.p1) or 1 ws.nr2 = refractive_index(self.p2) or 1 - ws.set_fill_fn(self._fill_matrix) + ws.carrier.set_fill_function(self._fill_carrier_matrix) + ws.signal.set_fill_function(self._fill_signal_matrix) # TODO ddb : change this from a dict to an array, it should just be # doing a direct frequency index lookup to a boolean or something # rather than having to use `in` checks all the time. - ws.coupling_orders = {} + ws.sig_coupling_orders = {} + ws.car_coupling_orders = {} - for f1 in sim.frequencies: - ws.coupling_orders[f1, f1] = 0 - for f2 in sim.frequencies: - df = f2.f - f1.f - order = df / float(self.f) - if hasattr(order, "eval"): - order = order.eval() + def fn(mtx, coupling_orders): + for f1 in mtx.optical_frequencies.frequencies: + coupling_orders[f1, f1] = 0 + for f2 in mtx.optical_frequencies.frequencies: + df = f2.f - f1.f + order = df / float(self.f) + if hasattr(order, "eval"): + order = order.eval() - if not order.is_integer(): - continue + if not order.is_integer(): + continue - order = int(round(order)) + order = int(round(order)) - if abs(order) <= self.order: - ws.coupling_orders[f1, f2] = order - ws.coupling_orders[f2, f1] = -order + if abs(order) <= self.order: + coupling_orders[f1, f2] = order + coupling_orders[f2, f1] = -order - return ws + fn(sim.carrier, ws.car_coupling_orders) + if sim.signal: + fn(sim.signal, ws.sig_coupling_orders) - # def _on_unbuild(self, sim): + return ws + + # TODO ddb this doesn't get called by anything, probably should + # def _update_source_frequency(self, sim, freq): # ws = sim.ws[self] - # del ws.coupling_orders - - def _update_source_frequency(self, sim, freq): - ws = sim.ws[self] - - for f2 in self._frequencies(): - if (freq, f2) not in ws.coupling_orders: - continue - - df = f2.f - freq.f - order = df / self.f.f.eval() - if hasattr(order, "eval"): - order = order.eval() - - if not order.is_integer(): - continue - - order = int(round(order)) - - if abs(order) <= self.order: - ws.coupling_orders[freq, f2] = order - ws.coupling_orders[f2, freq] = -order + # for f2 in self._frequencies(): + # if (freq, f2) not in ws.coupling_orders: + # continue + # df = f2.f - freq.f + # order = df / self.f.f.eval() + # if hasattr(order, "eval"): + # order = order.eval() + # if not order.is_integer(): + # continue + # order = int(round(order)) + # if abs(order) <= self.order: + # ws.coupling_orders[freq, f2] = order + # ws.coupling_orders[f2, freq] = -order + + def _fill_carrier_matrix(self, ws): + # TODO ddb : move all this over to new C verions + values, is_changing = self._eval_parameters() + modulator_fill(self, ws, ws.sim.carrier, ws.carrier.connections, values, ws.car_coupling_orders, self.mod_type) - def _fill_matrix(self, ws): + def _fill_signal_matrix(self, ws): # TODO ddb : move all this over to new C verions values, is_changing = self._eval_parameters() - modulator_fill(self, ws, values, ws.coupling_orders, self.mod_type) + modulator_fill(self, ws, ws.sim.signal, ws.signal.connections, values, ws.sig_coupling_orders, self.mod_type) diff --git a/src/finesse/components/nothing.py b/src/finesse/components/nothing.py index 7959ab54438dbb79d989f70935416de2c5901822..9c115d0e12cd390c59699e5a9610f1eda902a8af 100644 --- a/src/finesse/components/nothing.py +++ b/src/finesse/components/nothing.py @@ -11,8 +11,8 @@ from finesse.components.workspace import ConnectorWorkspace, Connections class NothingWorkspace(ConnectorWorkspace): - def __init__(self, owner, sim, refill): - super().__init__(owner, sim, refill, Connections(sim)) + def __init__(self, owner, sim): + super().__init__(owner, sim, False, False) class Nothing(Connector): @@ -59,18 +59,27 @@ class Nothing(Connector): ) def _get_workspace(self, sim): - ws = NothingWorkspace(self, sim, False) - ws.I = np.eye(sim.nhoms, dtype=np.complex128) - ws.set_fill_fn(self._fill_matrix) + ws = NothingWorkspace(self, sim) + ws.I = np.eye(sim.model_data.num_HOMs, dtype=np.complex128) + ws.carrier.set_fill_function(self._fill_carrier) + ws.signal.set_fill_function(self._fill_signal) return ws - def _fill_matrix(self, ws): - for freq in ws.sim.frequencies: - with ws.sim.component_edge_fill3( - ws.owner_id, ws.connections.P1i_P2o_idx, freq.index, freq.index, + def _fill_optical_matrix(self, ws, matrix, connections): + for freq in matrix.optical_frequencies.frequencies: + with matrix.component_edge_fill3( + ws.owner_id, connections.P1i_P2o_idx, freq.index, freq.index, ) as mat: mat[:] = ws.I - with ws.sim.component_edge_fill3( - ws.owner_id, ws.connections.P2i_P1o_idx, freq.index, freq.index, + + with matrix.component_edge_fill3( + ws.owner_id, connections.P2i_P1o_idx, freq.index, freq.index, ) as mat: mat[:] = ws.I + + def _fill_carrier(self, ws): + self._fill_optical_matrix(ws, ws.sim.carrier, ws.carrier.connections) + + def _fill_signal(self, ws): + self._fill_optical_matrix(ws, ws.sim.signal, ws.signal.connections) + diff --git a/src/finesse/components/photodiode.py b/src/finesse/components/photodiode.py index 552b303ef7147d2e9e227826dbed4f80472e7cc9..25250d48698982a4a903e752782f5011b47049f2 100644 --- a/src/finesse/components/photodiode.py +++ b/src/finesse/components/photodiode.py @@ -45,11 +45,11 @@ class Photodiode(Connector): return self.p1.i def _get_workspace(self, sim): - if sim.is_audio: - ws = PhotodiodeWorkspace(self, sim, True) - ws.I = np.eye(sim.nhoms, dtype=np.complex128) - ws.set_fill_fn(self._fill_matrix) - ws.frequencies = sim.electrical_frequencies[self.DC.o].frequencies + if sim.signal: + ws = PhotodiodeWorkspace(self, sim, True, True) + ws.I = np.eye(sim.model_data.num_HOMs, dtype=np.complex128) + ws.signal.set_fill_function(self._fill_matrix) + ws.frequencies = sim.signal.electrical_frequencies[self.DC.o].frequencies return ws else: return None @@ -58,13 +58,13 @@ class Photodiode(Connector): """ Computing E.conj() * upper + E * lower.conj() """ - for freq in ws.sim.frequencies: + for freq in ws.sim.signal.optical_frequencies.frequencies: # Get the carrier HOMs for this frequency - E = ws.sim.DC.get_DC_out(self.p1.i, freq.audio_carrier_index) + E = ws.sim.carrier.get_out(self.p1.i, freq.audio_carrier_index) # is_lower_sb = freq.audio_order < 0 for efreq in ws.frequencies: - with ws.sim.component_edge_fill3( - ws.owner_id, ws.connections.P1i_DC_idx, freq.index, efreq.index, + with ws.sim.signal.component_edge_fill3( + ws.owner_id, ws.signal.connections.P1i_DC_idx, freq.index, efreq.index, ) as mat: mat[:] = E.conjugate() * ws.values.qeff @@ -95,11 +95,11 @@ class Photodiode1Demod(Connector): self._register_node_coupling("P1i_Q", self.p1.i, self.Q.o) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: ws = PhotodiodeWorkspace(self, sim, True) - ws.set_fill_fn(self._fill_matrix) - ws.frequencies = sim.electrical_frequencies[self.I.o].frequencies - ws.dc_node_id = sim.DC.node_id(self.p1.i) + ws.signal.set_fill_function(self._fill_matrix) + ws.frequencies = sim.signal.electrical_frequencies[self.I.o].frequencies + ws.dc_node_id = sim.carrier.node_id(self.p1.i) return ws else: return None @@ -119,131 +119,131 @@ class Photodiode1Demod(Connector): # factorQ = 1j * factorI # cfactorQ = factorQ.conjugate() - for f1 in ws.sim.DC.frequencies: - for f2 in ws.sim.DC.frequencies: + for f1 in ws.sim.carrier.optical_frequencies.frequencies: + for f2 in ws.sim.carrier.optical_frequencies.frequencies: df = f1.f - f2.f if df == ws.values.f or df == -ws.values.f: - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_I_idx, - ws.sim.DC.frequency_info[f2.index]["audio_lower_index"], + ws.signal.connections.P1i_I_idx, + ws.sim.carrier.optical_frequency.frequency_info[f2.index]["audio_lower_index"], 0, ) as mat: mat[:] = 0 - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_I_idx, - ws.sim.DC.frequency_info[f1.index]["audio_upper_index"], + ws.signal.connections.P1i_I_idx, + ws.sim.carrier.optical_frequency.frequency_info[f1.index]["audio_upper_index"], 0, ) as mat: mat[:] = 0 - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_Q_idx, - ws.sim.DC.frequency_info[f2.index]["audio_lower_index"], + ws.signal.connections.P1i_Q_idx, + ws.sim.carrier.optical_frequency.frequency_info[f2.index]["audio_lower_index"], 0, ) as mat: mat[:] = 0 - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_Q_idx, - ws.sim.DC.frequency_info[f1.index]["audio_upper_index"], + ws.signal.connections.P1i_Q_idx, + ws.sim.carrier.optical_frequency.frequency_info[f1.index]["audio_upper_index"], 0, ) as mat: mat[:] = 0 - for f1 in ws.sim.DC.frequencies: - for f2 in ws.sim.DC.frequencies: + for f1 in ws.sim.carrier.optical_frequencies.frequencies: + for f2 in ws.sim.carrier.optical_frequencies.frequencies: df = f1.f - f2.f if df == -ws.values.f: - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_I_idx, - ws.sim.DC.frequency_info[f2.index]["audio_lower_index"], + ws.signal.connections.P1i_I_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_out(self.p1.i, f1.index, k) # This will apply a conjugation internally as it's # a lower SB connection mat[:][k] += factorI.conjugate() * car - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_I_idx, - ws.sim.DC.frequency_info[f1.index]["audio_upper_index"], + ws.signal.connections.P1i_I_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_out(self.p1.i, f2.index, k) mat[:][k] += factorI.conjugate() * car.conjugate() - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_Q_idx, - ws.sim.DC.frequency_info[f2.index]["audio_lower_index"], + ws.signal.connections.P1i_Q_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_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( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_Q_idx, - ws.sim.DC.frequency_info[f1.index]["audio_upper_index"], + ws.signal.connections.P1i_Q_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_out(self.p1.i, f2.index, k) mat[:][k] += factorQ.conjugate() * car.conjugate() if df == ws.values.f: - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_I_idx, - ws.sim.DC.frequency_info[f2.index]["audio_lower_index"], + ws.signal.connections.P1i_I_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_out(self.p1.i, f1.index, k) # This will apply a conjugation internally as it's # a lower SB connection mat[:][k] += factorI * car - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_I_idx, - ws.sim.DC.frequency_info[f1.index]["audio_upper_index"], + ws.signal.connections.P1i_I_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_out(self.p1.i, f2.index, k) mat[:][k] += factorI * car.conjugate() - with ws.sim.component_edge_fill3( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_Q_idx, - ws.sim.DC.frequency_info[f2.index]["audio_lower_index"], + ws.signal.connections.P1i_Q_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_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( + with ws.sim.signal.component_edge_fill3( ws.owner_id, - ws.connections.P1i_Q_idx, - ws.sim.DC.frequency_info[f1.index]["audio_upper_index"], + ws.signal.connections.P1i_Q_idx, + ws.sim.carrier.optical_frequency.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) + for k in range(ws.sim.model_data.num_HOMs): + car = ws.sim.carrier.get_out(self.p1.i, f2.index, k) mat[:][k] += factorQ * car.conjugate() diff --git a/src/finesse/components/signal.py b/src/finesse/components/signal.py index 1de5e1ed22f65ac2eeac52c3fd620cca0a1bb0e9..fe1f6af1e5c0a28a9975f9e99bafaca537fc8b77 100644 --- a/src/finesse/components/signal.py +++ b/src/finesse/components/signal.py @@ -59,10 +59,10 @@ class SignalGenerator(Connector): return self._model.fsig.f def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: ws = SignalGeneratorWorkspace(self, sim, True) - ws.rhs_idx = ws.sim.field(self.port.o, 0, 0) - ws.set_fill_rhs_fn(siggen_fill_rhs) + ws.rhs_idx = ws.sim.signal.field(self.port.o, 0, 0) + ws.signal.set_fill_rhs_fn(siggen_fill_rhs) if self.port.o.type is NodeType.MECHANICAL: ws.scaling = 1 / sim.model_data.x_scale diff --git a/src/finesse/components/space.py b/src/finesse/components/space.py index bb7b7e5c23909a5eb5cc2e4333970ce788f477b7..a71c5f1e94b29a290422f06a7f49bdc893538e93 100644 --- a/src/finesse/components/space.py +++ b/src/finesse/components/space.py @@ -13,7 +13,6 @@ from finesse.components.general import ( _conn_abcd_return, ) from finesse.components.workspace import ConnectionSetting -from finesse.components.modal.space import space_fill, SpaceWorkspace from finesse.components.node import NodeDirection, NodeType, Port @@ -165,23 +164,34 @@ class Space(Connector): self.__symbolise_ABCDs() def _get_workspace(self, sim): + from finesse.components.modal.space import space_carrier_fill, space_signal_fill, space_set_gouy, SpaceWorkspace + _, is_changing = self._eval_parameters() refill = ( - (sim.is_audio and sim.model.fsig.f.is_changing) - or self in sim.trace_forest.changing_components - or sim.any_frequencies_changing + self in sim.trace_forest.changing_components + or sim.carrier.any_frequencies_changing + or (sim.signal.any_frequencies_changing if sim.signal is not None else False) or (len(is_changing) and is_changing.issubset(self.__changing_check)) ) ws = SpaceWorkspace(self, sim, refill) - # Set the fill function for this simulation - ws.set_fill_fn(space_fill) - ws.connection_settings["P1i_P2o"] = ConnectionSetting.DIAGONAL - ws.connection_settings["P2i_P1o"] = ConnectionSetting.DIAGONAL - ws.connection_settings["SIGPHS_P1o"] = ConnectionSetting.DIAGONAL - ws.connection_settings["SIGPHS_P2o"] = ConnectionSetting.DIAGONAL + ws.set_gouy_function(space_set_gouy) + # Set the fill function for this simulation + ws.carrier.set_fill_function(space_carrier_fill) + + ws.carrier.connection_settings["P1i_P2o"] = ConnectionSetting.DIAGONAL + ws.carrier.connection_settings["P2i_P1o"] = ConnectionSetting.DIAGONAL + + if sim.signal: + refill |= sim.model.fsig.f.is_changing + refill |= sim.signal.any_frequencies_changing + ws.signal.set_fill_function(space_signal_fill) + ws.signal.connection_settings["P1i_P2o"] = ConnectionSetting.DIAGONAL + ws.signal.connection_settings["P2i_P1o"] = ConnectionSetting.DIAGONAL + ws.signal.connection_settings["SIGPHS_P1o"] = ConnectionSetting.DIAGONAL + ws.signal.connection_settings["SIGPHS_P2o"] = ConnectionSetting.DIAGONAL # Initialise the ABCD matrix memory-views if sim.is_modal: diff --git a/src/finesse/components/wire.py b/src/finesse/components/wire.py index 2308cba059bf1e04d22fee8d844f4e4d0a9c49b6..16a3ccf6ada6a55665690c95520349658c13cddf 100644 --- a/src/finesse/components/wire.py +++ b/src/finesse/components/wire.py @@ -112,20 +112,19 @@ class Wire(Connector): self._register_node_coupling("P1i_P2o", self.p1.i, self.p2.o) def _get_workspace(self, sim): - if sim.is_audio: + if sim.signal: _, is_changing = self._eval_parameters() # Most wires are zero delay, so don't bother refilling them all the time refill = ( - sim.is_audio - and (sim.model.fsig.f.is_changing or sim.any_frequencies_changing) + (sim.model.fsig.f.is_changing or sim.signal.any_frequencies_changing) and self.delay.is_changing and self.delay != 0 ) - ws = WireWorkspace(self, sim, refill) - ws.frequencies = sim.electrical_frequencies[self.p1.i].frequencies + ws = WireWorkspace(self, sim, refill, refill) + ws.frequencies = sim.signal.electrical_frequencies[self.p1.i].frequencies # Set the fill function for this simulation - ws.set_fill_fn(self.fill) + ws.signal.set_fill_function(self.fill) return ws else: return None diff --git a/src/finesse/components/workspace.pxd b/src/finesse/components/workspace.pxd index de4b0c294e218a283107e2a84ed04bef61148f7a..794981313b8459642dd8692b84f055ae61d36e95 100644 --- a/src/finesse/components/workspace.pxd +++ b/src/finesse/components/workspace.pxd @@ -1,7 +1,8 @@ -from finesse.simulations.base cimport BaseSimulation from finesse.element cimport ElementWorkspace +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation ctypedef object (*fptr_c_fill)(ConnectorWorkspace) +ctypedef int (*fptr_c_gouy)(ConnectorWorkspace) except -1 cpdef enum ConnectorCallbacks: FILL_MATRIX = 1, @@ -19,17 +20,29 @@ cdef class FillFuncWrapper: @staticmethod cdef FillFuncWrapper make_from_ptr(fptr_c_fill f) +cdef class GouyFuncWrapper: + cdef fptr_c_gouy func + @staticmethod + cdef GouyFuncWrapper make_from_ptr(fptr_c_gouy f) -cdef class ConnectorWorkspace(ElementWorkspace): +cdef class ConnectorMatrixSimulationInfo: cdef: readonly object connections FillFuncWrapper fn_c # C fill function object fn_py # Python fill function FillFuncWrapper fn_rhs_c # C RHS fill function object fn_rhs_py # Python RHS fill function - readonly bint refill # Should this matrix be refilled continuously - readonly BaseSimulation sim readonly ConnectorCallbacks callback_flag # flag stating which fill methods are to be called + readonly bint refill # Should this matrix be refilled continuously readonly dict connection_settings +cdef class ConnectorWorkspace(ElementWorkspace): + cdef readonly: + ConnectorMatrixSimulationInfo carrier + ConnectorMatrixSimulationInfo signal + CarrierSignalMatrixSimulation sim + cdef: + readonly GouyFuncWrapper fn_gouy_c # C set Gouy phase function + readonly object fn_gouy_py # Python set gouy function + cpdef compile_abcd_cy_exprs(self) diff --git a/src/finesse/components/workspace.pyx b/src/finesse/components/workspace.pyx index c8b9ed751426d664bcecfee6cbbef257319a704c..b9634be795f03a47cd9c9b53f5d6d6c28cd6152e 100644 --- a/src/finesse/components/workspace.pyx +++ b/src/finesse/components/workspace.pyx @@ -26,34 +26,58 @@ cdef class FillFuncWrapper: return out +cdef class GouyFuncWrapper: + """ + Helper class for wrapping a C fill function that + can be referenced from Python by objects. This + allows a direct C call to the function from other + cdef functions. + + Wraps a cdef for setting the gouy phase during a + modal simulation. + + Examples + -------- + Create a C function then wrap it using this class: + + >>> cdef int c_set_gouy(ConnectorWorkspace ptr_ws): + >>> cdef MirrorWorkspace ws = ptr_ws + >>> ... + >>> + >>> fill = GouyFuncWrapper.make_from_ptr(c_set_gouy) + """ + def __cinit__(self): + self.func = NULL + + @staticmethod + cdef GouyFuncWrapper make_from_ptr(fptr_c_gouy f): + cdef GouyFuncWrapper out = GouyFuncWrapper() + out.func = f + return out + + class Connections: """This is a container object for storing the connection submatrices defined by a component. This is used in the default case where no optimised C class is provided.""" - def __init__(self, sim): - self.sim = sim + pass + # TODO ddb not entirely sure this sim reference is required + #def __init__(self, sim): + # self.sim = sim - -cdef class ConnectorWorkspace(ElementWorkspace): - """ - This workspace represents the basic container for storing details - for modelling Connector elements - those which form edges and nodes - in a model. - """ - def __init__(self, object owner, BaseSimulation sim, bint refill, object connections=None, object values=None): - super().__init__(owner, values) +cdef class ConnectorMatrixSimulationInfo: + def __init__(self, bint refill, object connections=None): self.fn_c = None self.fn_py = None self.fn_rhs_c = None self.fn_rhs_py = None self.refill = refill - self.sim = sim - self.connections = connections or Connections(sim) + self.connections = connections or Connections() self.connection_settings = {} - def set_fill_fn(self, callback): + def set_fill_function(self, callback): """ This sets the callback function that will be used by the model to - fill the matrix elements. This can either be a Python function, which - accepts a + fill the matrix elements. This can either be a Python function or cdef + wrapped with `FillFuncWrapper`. """ if type(callback) is FillFuncWrapper: self.fn_c = callback @@ -64,8 +88,8 @@ cdef class ConnectorWorkspace(ElementWorkspace): def set_fill_rhs_fn(self, callback): """ This sets the callback function that will be used by the model to - fill the RHS vector elements. This can either be a Python function, which - accepts a + fill the RHS vector elements. This can either be a Python function or cdef + wrapped with `FillFuncWrapper`. """ if type(callback) is FillFuncWrapper: self.fn_rhs_c = callback @@ -76,5 +100,39 @@ cdef class ConnectorWorkspace(ElementWorkspace): self.callback_flag = (self.callback_flag | ConnectorCallbacks.FILL_RHS) + +cdef class ConnectorWorkspace(ElementWorkspace): + """ + This workspace represents the basic container for storing details + for modelling Connector elements - those which form edges and nodes + in a model. + """ + def __init__( + self, + object owner, + CarrierSignalMatrixSimulation sim, + bint carrier_refill, + bint signal_refill, + object carrier_connections=None, + object signal_connections=None, + object values=None + ): + super().__init__(owner, values) + self.sim = sim + self.carrier = ConnectorMatrixSimulationInfo(carrier_refill, carrier_connections) + self.signal = ConnectorMatrixSimulationInfo(signal_refill, signal_connections) + + def set_gouy_function(self, callback): + """ This sets the callback function that will be used by the model to + compute the gouy phase terms for an element. + This can either be a Python function or cdef wrapped with `GouyFuncWrapper`. + + """ + if type(callback) is GouyFuncWrapper: + self.fn_gouy_c = callback + else: + self.fn_gouy_py = callback + cpdef compile_abcd_cy_exprs(self): + # TODO ddb shouldn't this raise a not implemented error? pass diff --git a/src/finesse/detectors/amplitude_detector.py b/src/finesse/detectors/amplitude_detector.py index 38950ecf7c5426b63f734d01809b956bd755e32e..e4cf11d430597b8f71c3e5fe10fffb65a56138f5 100644 --- a/src/finesse/detectors/amplitude_detector.py +++ b/src/finesse/detectors/amplitude_detector.py @@ -90,5 +90,5 @@ class AmplitudeDetector(Detector): """ return self.__m - def _get_workspace(self, sims): - return ADWorkspace(self, sims) + def _get_workspace(self, sim): + return ADWorkspace(self, sim) diff --git a/src/finesse/detectors/astigmatism_detector.py b/src/finesse/detectors/astigmatism_detector.py index 71c30b46260f36ac8e77054bec0458470d8e704f..fafaf576fa83b0ec663aa157ae08ec083bd28e6a 100644 --- a/src/finesse/detectors/astigmatism_detector.py +++ b/src/finesse/detectors/astigmatism_detector.py @@ -25,5 +25,5 @@ class AstigmatismDetector(Detector): def needs_fields(self): return False - def _get_workspace(self, sims): - return AstigmatismDetectorWorkspace(self, sims) + def _get_workspace(self, sim): + return AstigmatismDetectorWorkspace(self, sim) diff --git a/src/finesse/detectors/bpdetector.py b/src/finesse/detectors/bpdetector.py index de3959f0f4b02827919b92f4585f99728663bfee..eaba5efd1fe37327b276e260e4f69753ffa4a66e 100644 --- a/src/finesse/detectors/bpdetector.py +++ b/src/finesse/detectors/bpdetector.py @@ -96,8 +96,8 @@ class BeamPropertyDetector(Detector): """ return self.__prop - def _get_workspace(self, sims): - ws = BPDetectorWorkspace(self, sims) + def _get_workspace(self, sim): + ws = BPDetectorWorkspace(self, sim) ws.nr = refractive_index(self.node) or 1.0 ws.q_as_bp = self.q_as_bp return ws diff --git a/src/finesse/detectors/camera.py b/src/finesse/detectors/camera.py index 2b70b599592663d4911b148e61a4268abea672c2..35791240f8df08c6e38cf5d67af7588942054a02 100644 --- a/src/finesse/detectors/camera.py +++ b/src/finesse/detectors/camera.py @@ -78,8 +78,8 @@ class Camera(Detector, ABC): # TODO (sjr) Need to re-implement a method for re-constructing CameraWorkspace.frequencies # if any frequency in DC.frequencies is changing - def _common_workspace_params(self, DC): - node_q_map = DC.model.last_trace + def _common_workspace_params(self, sim): + node_q_map = sim.model.last_trace qx, qy = node_q_map[self._node] if self.is_waist_scaled: @@ -365,12 +365,10 @@ class CCD(CCDCamera, Image): Image.__init__(self, xlim, ylim, npts, dtype=np.float64) CCDCamera.__init__(self, name, node, w0_scaled) - def _get_workspace(self, sims): - DC = sims[0] + def _get_workspace(self, sim): + x, y, qx, qy = self._common_workspace_params(sim) - x, y, qx, qy = self._common_workspace_params(DC) - - ws = CCDWorkspace(self, sims, x, y, self._out) + ws = CCDWorkspace(self, sim, x, y, self._out) ws.pre_compute_geometries(qx, qy) ws.cache() @@ -421,12 +419,10 @@ class CCDScanLine(CCDCamera, ScanLine): ScanLine.__init__(self, x, y, npts, direction, dtype=np.float64) CCDCamera.__init__(self, name, node, w0_scaled, label=label, unit=unit) - def _get_workspace(self, sims): - DC = sims[0] - - x, y, qx, qy = self._common_workspace_params(DC) + def _get_workspace(self, sim): + x, y, qx, qy = self._common_workspace_params(sim) - ws = CCDLineWorkspace(self, sims, x, y, self._out) + ws = CCDLineWorkspace(self, sim, x, y, self._out) ws.pre_compute_geometries(qx, qy) ws.cache() @@ -464,12 +460,10 @@ class CCDPixel(CCDCamera, Pixel): Pixel.__init__(self, x, y, dtype=np.float64) CCDCamera.__init__(self, name, node, w0_scaled, label=label, unit=unit) - def _get_workspace(self, sims): - DC = sims[0] - - x, y, qx, qy = self._common_workspace_params(DC) + def _get_workspace(self, sim): + x, y, qx, qy = self._common_workspace_params(sim) - ws = CameraWorkspace(self, sims, x, y) + ws = CameraWorkspace(self, sim, x, y) ws.pre_compute_geometries(qx, qy) ws.cache() @@ -557,12 +551,10 @@ class FieldCamera(ComplexCamera, Image): Image.__init__(self, xlim, ylim, npts, dtype=np.complex128) ComplexCamera.__init__(self, name, node, f=f, w0_scaled=w0_scaled) - def _get_workspace(self, sims): - DC = sims[0] + def _get_workspace(self, sim): + x, y, qx, qy = self._common_workspace_params(sim) - x, y, qx, qy = self._common_workspace_params(DC) - - ws = FieldCameraWorkspace(self, sims, x, y, self._out) + ws = FieldCameraWorkspace(self, sim, x, y, self._out) ws.pre_compute_geometries(qx, qy) ws.cache() @@ -616,12 +608,10 @@ class FieldScanLine(ComplexCamera, ScanLine): ScanLine.__init__(self, x, y, npts, direction, dtype=np.complex128) ComplexCamera.__init__(self, name, node, f=f, w0_scaled=w0_scaled) - def _get_workspace(self, sims): - DC = sims[0] - - x, y, qx, qy = self._common_workspace_params(DC) + def _get_workspace(self, sim): + x, y, qx, qy = self._common_workspace_params(sim) - ws = FieldLineWorkspace(self, sims, x, y, self._out) + ws = FieldLineWorkspace(self, sim, x, y, self._out) ws.pre_compute_geometries(qx, qy) ws.cache() @@ -661,12 +651,10 @@ class FieldPixel(ComplexCamera, Pixel): Pixel.__init__(self, x, y, dtype=np.complex128) ComplexCamera.__init__(self, name, node, f=f, w0_scaled=w0_scaled) - def _get_workspace(self, sims): - DC = sims[0] - - x, y, qx, qy = self._common_workspace_params(DC) + def _get_workspace(self, sim): + x, y, qx, qy = self._common_workspace_params(sim) - ws = FieldPixelWorkspace(self, sims, x, y) + ws = FieldPixelWorkspace(self, sim, x, y) ws.pre_compute_geometries(qx, qy) ws.cache() diff --git a/src/finesse/detectors/cavity_detector.py b/src/finesse/detectors/cavity_detector.py index 1fbcc8806c1003b7638f00789e9b7fdc61bec48f..06abf8d61c19d2130cc919361fb74489b6882651 100644 --- a/src/finesse/detectors/cavity_detector.py +++ b/src/finesse/detectors/cavity_detector.py @@ -145,8 +145,8 @@ class CavityPropertyDetector(Detector): self.__cavity = cavity - def _get_workspace(self, sims): - ws = CPDetectorWorkspace(self, sims) + def _get_workspace(self, sim): + ws = CPDetectorWorkspace(self, sim) ws.nr = refractive_index(self.cavity.source) or 1.0 ws.q_as_bp = self.q_as_bp return ws diff --git a/src/finesse/detectors/compute/__init__.py b/src/finesse/detectors/compute/__init__.py index a8cfa0e777ca5558bb4a1dcb1333ae145e58f61e..07f3164d2283a9ba7a8ed52f961a3824edf4ce00 100644 --- a/src/finesse/detectors/compute/__init__.py +++ b/src/finesse/detectors/compute/__init__.py @@ -7,8 +7,6 @@ from finesse.detectors.compute.camera import ( ccd_output, ) from finesse.detectors.compute.power import ( - dc_pd_output, - mixer_pd_output, pd0_DC_output, pd0_DC_output_masked, pd1_DC_output, @@ -25,8 +23,6 @@ __all__ = ( "ccd_line_output", "field_camera_output", "ccd_output", - "dc_pd_output", - "mixer_pd_output", "pd0_DC_output", "pd0_DC_output_masked", "pd1_DC_output", diff --git a/src/finesse/detectors/compute/amplitude.pyx b/src/finesse/detectors/compute/amplitude.pyx index c487ce792563e68fbcd1a7a8e2c091ce36284d16..a118cbcbb57452b28bca273ea541ff22b3590dc5 100644 --- a/src/finesse/detectors/compute/amplitude.pyx +++ b/src/finesse/detectors/compute/amplitude.pyx @@ -11,12 +11,12 @@ from finesse.detectors.workspace cimport ( MaskedDetectorWorkspace, OutputFuncWrapper, ) -from finesse.simulations cimport BaseSimulation from finesse.knm cimport KnmMatrix import logging from finesse.components.node import NodeType +from finesse.simulations.basematrix cimport MatrixSystemSolver cdef extern from "constants.h": double complex COMPLEX_0 @@ -28,22 +28,24 @@ LOGGER = logging.getLogger(__name__) cdef class ADWorkspace(MaskedDetectorWorkspace): cdef public: - BaseSimulation sim Py_ssize_t idx object ntype double scaling bint is_f_changing + MatrixSystemSolver mtx - def __init__(self, owner, sims): - super().__init__(owner, sims) + def __init__(self, owner, sim): + super().__init__(owner, sim) self.is_f_changing = owner.f.is_changing fval = float(owner.f) fs = [] - for sim in sims: - f = sim.get_frequency_object(fval) - if f is not None: - fs.append((f, sim)) + for mtx in (sim.carrier, sim.signal): + if mtx: + f = mtx.get_frequency_object(fval, owner.node) + if f is not None: + fs.append((f, mtx)) + if len(fs) == 0: raise Exception( f"Error in amplitude detector {owner.name}:\n" @@ -55,9 +57,8 @@ cdef class ADWorkspace(MaskedDetectorWorkspace): f" Found multiple frequency bins at {owner.f}" ) - freq, sim = fs[0] - self.sim = sim - self.setup_mask(self.sim) + freq, self.mtx = fs[0] + self.setup_mask() cdef bint multi_field_mode = False cdef Py_ssize_t hom_idx = 0 @@ -72,7 +73,7 @@ cdef class ADWorkspace(MaskedDetectorWorkspace): f" HOM (n={owner.n}, m={owner.m}) not found in the model" ) - self.idx = self.sim.field(owner.node, freq.index, hom_idx) + self.idx = self.mtx.field(owner.node, freq.index, hom_idx) if not multi_field_mode: if self.has_mask and not self.hom_in_modes(hom_idx): @@ -108,12 +109,12 @@ cdef class ADWorkspace(MaskedDetectorWorkspace): ad_single_field_output = OutputFuncWrapper.make_from_ptr(c_ad_single_field_output) cdef c_ad_single_field_output(DetectorWorkspace dws): cdef ADWorkspace ws = dws - return ws.scaling * ws.sim.out_view[ws.idx] + return ws.scaling * ws.mtx.out_view[ws.idx] ad_conj_single_field_output = OutputFuncWrapper.make_from_ptr(c_ad_conj_single_field_output) cdef c_ad_conj_single_field_output(DetectorWorkspace dws): cdef ADWorkspace ws = dws - return ws.scaling * conj(ws.sim.out_view[ws.idx]) + return ws.scaling * conj(ws.mtx.out_view[ws.idx]) ad_single_field_output_masked = OutputFuncWrapper.make_from_ptr(c_ad_single_field_output_masked) cdef c_ad_single_field_output_masked(DetectorWorkspace dws): @@ -131,7 +132,7 @@ cdef c_ad_multi_field_output(DetectorWorkspace dws): Py_ssize_t i # field index for i in range(ws.sim.model_data.num_HOMs): - field_amp = ws.sim.out_view[ws.idx + i] + field_amp = ws.mtx.out_view[ws.idx + i] fields_sum += field_amp amp += cnorm(field_amp) @@ -151,7 +152,7 @@ cdef c_ad_multi_field_output_masked(DetectorWorkspace dws): Py_ssize_t i # field index for i in range(ws.num_HOMs): - field_amp = ws.sim.out_view[ws.idx + ws.mode_indices[i]] + field_amp = ws.mtx.out_view[ws.idx + ws.mode_indices[i]] fields_sum += field_amp amp += cnorm(field_amp) @@ -171,7 +172,7 @@ cdef c_ad_conj_multi_field_output(DetectorWorkspace dws): Py_ssize_t i # field index for i in range(ws.sim.model_data.num_HOMs): - field_amp = conj(ws.sim.out_view[ws.idx + i]) + field_amp = conj(ws.mtx.out_view[ws.idx + i]) fields_sum += field_amp amp += cnorm(field_amp) @@ -191,7 +192,7 @@ cdef c_ad_conj_multi_field_output_masked(DetectorWorkspace dws): Py_ssize_t i # field index for i in range(ws.num_HOMs): - field_amp = conj(ws.sim.out_view[ws.idx + ws.mode_indices[i]]) + field_amp = conj(ws.mtx.out_view[ws.idx + ws.mode_indices[i]]) fields_sum += field_amp amp += cnorm(field_amp) @@ -207,8 +208,8 @@ cdef class KnmDetectorWorkspace(DetectorWorkspace): int n1, m1, n2, m2 KnmMatrix knm_matrix - def __init__(self, owner, sims): - super().__init__(owner, sims) + def __init__(self, owner, sim): + super().__init__(owner, sim) knm_detector_output = OutputFuncWrapper.make_from_ptr(c_knm_detector_output) cdef c_knm_detector_output(DetectorWorkspace dws): diff --git a/src/finesse/detectors/compute/camera.pxd b/src/finesse/detectors/compute/camera.pxd index 591dec585752d7a145a222280fe58d5c362a79db..ced249279c41b0bda8fed857c985bb7b7ea76e43 100644 --- a/src/finesse/detectors/compute/camera.pxd +++ b/src/finesse/detectors/compute/camera.pxd @@ -7,8 +7,8 @@ from finesse.detectors.workspace cimport ( OutputFuncWrapper, ) from finesse.element cimport BaseCValues -from finesse.simulations.base cimport BaseSimulation, frequency_info_t - +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation +from finesse.frequency cimport frequency_info_t cdef enum ScanningAxis: XAXIS, @@ -16,7 +16,6 @@ cdef enum ScanningAxis: cdef class CameraWorkspace(MaskedDetectorWorkspace): cdef: - BaseSimulation DC Py_ssize_t node_id UnmPrecomputedData upd diff --git a/src/finesse/detectors/compute/camera.pyx b/src/finesse/detectors/compute/camera.pyx index 3a4687f57ec1535d5bf6dfb6d0a19e5e490b4c10..ff383881fbb7e4153873ef0cd2c7aa6a70f258a4 100644 --- a/src/finesse/detectors/compute/camera.pyx +++ b/src/finesse/detectors/compute/camera.pyx @@ -51,7 +51,7 @@ from finesse.cymath.math cimport sqrt, cos, sin from finesse.cymath.math cimport float_eq from finesse.cymath.homs cimport UnmPrecomputedData, cache_unm_params, u_nm, in_mask -from finesse.simulations.base cimport BaseSimulation, NodeBeamParam +from finesse.simulations.base cimport NodeBeamParam ctypedef (double*,) ptr_tuple_1 @@ -66,14 +66,13 @@ LOGGER = logging.getLogger(__name__) cdef class CameraWorkspace(MaskedDetectorWorkspace): """Workspace class for cameras.""" - def __init__(self, owner, sims, x, y, values=None): - super().__init__(owner, sims, values) + def __init__(self, owner, sim, x, y, values=None): + super().__init__(owner, sim, values) cdef int unm_cache_size - self.DC = sims[0] - self.setup_mask(self.DC) - self.node_id = self.DC.node_id(owner._node) + self.setup_mask() + self.node_id = self.sim.carrier.node_id(owner._node) self.nr = owner._nr if isinstance(x, np.ndarray): @@ -121,7 +120,7 @@ cdef class CameraWorkspace(MaskedDetectorWorkspace): Beam parameter in sagittal plane. """ self.upd = cache_unm_params( - qx, qy, self.nr, self.DC.model_data.lambda0 + qx, qy, self.nr, self.sim.model_data.lambda0 ) cpdef cache(self): @@ -136,7 +135,7 @@ cdef class CameraWorkspace(MaskedDetectorWorkspace): double m0 = 0.0 double phase - if not self.DC.model_data.zero_tem00_gouy: + if not self.sim.model_data.zero_tem00_gouy: n0 = 0.5 m0 = 0.5 @@ -148,15 +147,15 @@ cdef class CameraWorkspace(MaskedDetectorWorkspace): for k in range(self.num_HOMs): p = self.mode_indices[k] - n = self.DC.homs_view[p][0] - m = self.DC.homs_view[p][1] + n = self.sim.model_data.homs_view[p][0] + m = self.sim.model_data.homs_view[p][1] self.unm_cache[i][j][k] = u_nm(n, m, xval, yval, &self.upd) for i in range(self.num_HOMs): p = self.mode_indices[i] - n = self.DC.homs_view[p][0] - m = self.DC.homs_view[p][1] + n = self.sim.model_data.homs_view[p][0] + m = self.sim.model_data.homs_view[p][1] phase = (n + n0) * self.upd.gouyx + (m + m0) * self.upd.gouyy @@ -166,15 +165,15 @@ cdef class CameraWorkspace(MaskedDetectorWorkspace): ### CCD type camera workspaces ### cdef class CCDWorkspace(CameraWorkspace): - def __init__(self, owner, sims, x, y, out): - super().__init__(owner, sims, x, y) + def __init__(self, owner, sim, x, y, out): + super().__init__(owner, sim, x, y) self.out = out cdef class CCDLineWorkspace(CameraWorkspace): - def __init__(self, owner, sims, x, y, out): - super().__init__(owner, sims, x, y) + def __init__(self, owner, sim, x, y, out): + super().__init__(owner, sim, x, y) self.out = out @@ -187,19 +186,19 @@ cdef class ComplexCameraValues(BaseCValues): self.setup(params, sizeof(ptr), &ptr) cdef class ComplexCameraWorkspace(CameraWorkspace): - def __init__(self, owner, sims, x, y): - super().__init__(owner, sims, x, y, ComplexCameraValues()) + def __init__(self, owner, sim, x, y): + super().__init__(owner, sim, x, y, ComplexCameraValues()) self.v = self.values cdef class FieldCameraWorkspace(ComplexCameraWorkspace): - def __init__(self, owner, sims, x, y, out): - super().__init__(owner, sims, x, y) + def __init__(self, owner, sim, x, y, out): + super().__init__(owner, sim, x, y) self.out = out cdef class FieldLineWorkspace(ComplexCameraWorkspace): - def __init__(self, owner, sims, x, y, out): - super().__init__(owner, sims, x, y) + def __init__(self, owner, sim, x, y, out): + super().__init__(owner, sim, x, y) self.out = out @@ -215,21 +214,21 @@ cdef complex_t field_beam_pixel( complex_t z_ij = COMPLEX_0 complex_t unm, at double cph, sph - frequency_info_t freq + frequency_info_t *freq Py_ssize_t node_id = cws.node_id - Py_ssize_t Nfreqs = cws.DC.num_frequencies + Py_ssize_t Nfreqs = cws.sim.carrier.optical_frequencies.size Py_ssize_t Nfields = cws.num_HOMs for freq_idx in range(Nfreqs): - freq = cws.DC.frequency_info[freq_idx] + freq = &cws.sim.carrier.optical_frequencies.frequency_info[freq_idx] if float_eq(freq.f, cws.v.f): at = COMPLEX_0 for k in range(Nfields): field_idx = cws.mode_indices[k] - n = cws.DC.homs_view[field_idx][0] - m = cws.DC.homs_view[field_idx][1] + n = cws.sim.model_data.homs_view[field_idx][0] + m = cws.sim.model_data.homs_view[field_idx][1] cph = cws.phase_cache[k][0] sph = cws.phase_cache[k][1] @@ -237,7 +236,7 @@ cdef complex_t field_beam_pixel( at += ( unm * crotate2( - cws.DC.get_DC_out_fast(node_id, freq_idx, field_idx), + cws.sim.carrier.get_out_fast(node_id, freq_idx, field_idx), cph, sph ) @@ -259,14 +258,14 @@ cdef double ccd_beam_pixel(CameraWorkspace cws, Py_ssize_t i, Py_ssize_t j) nogi Py_ssize_t node_id = cws.node_id - Py_ssize_t Nfreqs = cws.DC.num_frequencies + Py_ssize_t Nfreqs = cws.sim.carrier.optical_frequencies.size Py_ssize_t Nfields = cws.num_HOMs for freq_idx_outer in range(Nfreqs): for freq_idx_inner in range(Nfreqs): if not float_eq( - cws.DC.frequency_info[freq_idx_outer].f, - cws.DC.frequency_info[freq_idx_inner].f + cws.sim.carrier.optical_frequencies.frequency_info[freq_idx_outer].f, + cws.sim.carrier.optical_frequencies.frequency_info[freq_idx_inner].f ): continue @@ -274,8 +273,8 @@ cdef double ccd_beam_pixel(CameraWorkspace cws, Py_ssize_t i, Py_ssize_t j) nogi for k in range(Nfields): field_idx = cws.mode_indices[k] - n = cws.DC.homs_view[field_idx][0] - m = cws.DC.homs_view[field_idx][1] + n = cws.sim.model_data.homs_view[field_idx][0] + m = cws.sim.model_data.homs_view[field_idx][1] cph = cws.phase_cache[k][0] sph = cws.phase_cache[k][1] @@ -283,7 +282,7 @@ cdef double ccd_beam_pixel(CameraWorkspace cws, Py_ssize_t i, Py_ssize_t j) nogi at1 += ( unm * crotate2( - cws.DC.get_DC_out_fast(node_id, freq_idx_outer, field_idx), + cws.sim.carrier.get_out_fast(node_id, freq_idx_outer, field_idx), cph, sph ) @@ -292,7 +291,7 @@ cdef double ccd_beam_pixel(CameraWorkspace cws, Py_ssize_t i, Py_ssize_t j) nogi at2 += ( unm * crotate2( - cws.DC.get_DC_out_fast(node_id, freq_idx_inner, field_idx), + cws.sim.carrier.get_out_fast(node_id, freq_idx_inner, field_idx), cph, sph ) @@ -320,7 +319,7 @@ cdef complex_t get_unm( cdef void check_changing_q(CameraWorkspace ws): cdef: - NodeBeamParam q = ws.DC.trace[ws.node_id] + NodeBeamParam q = ws.sim.trace[ws.node_id] if q.is_changing: ws.pre_compute_geometries(q.qx, q.qy) diff --git a/src/finesse/detectors/compute/gaussian.pyx b/src/finesse/detectors/compute/gaussian.pyx index add084169e9d68b39d37620a568b46d41c2b2eea..d48f375f561aa21d8609e486eb90bc8d581e090b 100644 --- a/src/finesse/detectors/compute/gaussian.pyx +++ b/src/finesse/detectors/compute/gaussian.pyx @@ -18,7 +18,7 @@ from finesse.detectors.workspace cimport ( ) from finesse.components.modal.cavity cimport CavityWorkspace from finesse.components.modal.space cimport SpaceWorkspace -from finesse.simulations.base cimport BaseSimulation, NodeBeamParam +from finesse.simulations.base cimport NodeBeamParam from finesse.gaussian import BeamParam @@ -41,7 +41,6 @@ cpdef enum BeamProperty: cdef class BPDetectorWorkspace(DetectorWorkspace): cdef readonly: - BaseSimulation sim Py_ssize_t node_index Plane plane @@ -51,14 +50,8 @@ cdef class BPDetectorWorkspace(DetectorWorkspace): double nr bint q_as_bp - def __init__(self, owner, sims): - super().__init__(owner, sims) - - for sim in sims: - if sim.is_audio: - continue - - self.sim = sim + def __init__(self, owner, sim): + super().__init__(owner, sim) self.node_index = self.sim.node_id(owner.node) self.plane = X if owner.direction == "x" else Y @@ -124,7 +117,6 @@ cpdef enum CavityProperty: cdef class CPDetectorWorkspace(DetectorWorkspace): cdef readonly: - BaseSimulation sim Py_ssize_t node_index Plane plane @@ -136,17 +128,11 @@ cdef class CPDetectorWorkspace(DetectorWorkspace): double nr bint q_as_bp - def __init__(self, owner, sims): - super().__init__(owner, sims) - - for sim in sims: - if sim.is_audio: - continue - - self.sim = sim + def __init__(self, owner, sim): + super().__init__(owner, sim) self.cav_ws = self.sim.cavity_workspaces[owner.cavity] - self.node_index = self.sim.node_id(owner.cavity.source) + self.node_index = self.sim.carrier.node_id(owner.cavity.source) self.plane = X if owner.direction == "x" else Y self.detecting = owner.detecting @@ -217,19 +203,11 @@ cdef c_cp_detector_output(DetectorWorkspace dws): cdef class AstigmatismDetectorWorkspace(DetectorWorkspace): cdef readonly: - BaseSimulation sim Py_ssize_t node_index - def __init__(self, owner, sims): - super().__init__(owner, sims) - - for sim in sims: - if sim.is_audio: - continue - - self.sim = sim - - self.node_index = self.sim.node_id(owner.node) + def __init__(self, owner, sim): + super().__init__(owner, sim) + self.node_index = self.sim.carrier.node_id(owner.node) self.set_output_fn(astig_detector_output) @@ -249,18 +227,11 @@ cdef c_astig_detector_output(DetectorWorkspace dws): ### accumulated Gouy phase detector ### cdef class GouyDetectorWorkspace(DetectorWorkspace): - cdef readonly BaseSimulation sim cdef double** gouy_targets cdef Py_ssize_t N - def __init__(self, owner, sims): - super().__init__(owner, sims) - - for sim in sims: - if sim.is_audio: - continue - - self.sim = sim + def __init__(self, owner, sim): + super().__init__(owner, sim) cdef list spaces = owner.spaces self.N = len(spaces) @@ -275,7 +246,7 @@ cdef class GouyDetectorWorkspace(DetectorWorkspace): cdef Py_ssize_t i, j for i in range(self.N): space = spaces[i] - for ws in sim.workspaces: + for ws in sim.carrier_ws.workspaces: if ws.owner is space: sws = ws if plane == X: diff --git a/src/finesse/detectors/compute/power.pyx b/src/finesse/detectors/compute/power.pyx index 52414802b18fa574194b04cbc83a3bc02738a9e9..62ccc954845288fa90c9cac2392b778a5b114602 100644 --- a/src/finesse/detectors/compute/power.pyx +++ b/src/finesse/detectors/compute/power.pyx @@ -10,8 +10,8 @@ from finesse.cymath.complex cimport crotate, cnorm from finesse.cymath.math cimport sqrt, sin, cos, float_eq from finesse.cymath.homs cimport in_mask, field_index -from finesse.simulations cimport BaseSimulation -from finesse.simulations.base cimport frequency_info_t +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation +from finesse.frequency cimport frequency_info_t from finesse.detectors.workspace cimport ( DetectorWorkspace, MaskedDetectorWorkspace, @@ -47,14 +47,10 @@ cdef: cdef class PD0Workspace(MaskedDetectorWorkspace): cdef public: - BaseSimulation DC int dc_node_id Py_ssize_t rhs_index Py_ssize_t size - def __init__(self, owner, sims): - super().__init__(owner, sims) - pd0_DC_output = OutputFuncWrapper.make_from_ptr(c_pd0_DC_output) cdef c_pd0_DC_output(DetectorWorkspace dws): @@ -65,9 +61,9 @@ cdef c_pd0_DC_output(DetectorWorkspace dws): double tmp = 0 for i in range(ws.rhs_index, ws.rhs_index + ws.size): - tmp += cnorm(ws.DC.out_view[i]) + tmp += cnorm(ws.sim.carrier.out_view[i]) - return tmp * ws.DC.model_data.EPSILON0_C * 0.5 + return tmp * ws.sim.model_data.EPSILON0_C * 0.5 pd0_DC_output_masked = OutputFuncWrapper.make_from_ptr(c_pd0_DC_output_masked) cdef c_pd0_DC_output_masked(DetectorWorkspace dws): @@ -79,13 +75,13 @@ cdef c_pd0_DC_output_masked(DetectorWorkspace dws): Py_ssize_t freq_idx double tmp = 0 - for i in range(ws.DC.num_frequencies): + for i in range(ws.sim.carrier.num_frequencies): freq_idx = ws.rhs_index + i for j in range(ws.num_HOMs): k = freq_idx + ws.mode_indices[j] - tmp += cnorm(ws.DC.out_view[k]) + tmp += cnorm(ws.sim.carrier.out_view[k]) - return tmp * ws.DC.model_data.EPSILON0_C * 0.5 + return tmp * ws.sim.carrier.model_data.EPSILON0_C * 0.5 ### PD1 workspace & output funcs ### @@ -104,8 +100,6 @@ cdef class PD1Values(BaseCValues): cdef class PD1Workspace(MaskedDetectorWorkspace): cdef public: bint output_real - BaseSimulation DC - BaseSimulation AC int dc_node_id int ac_node_id int[:,::1] homs @@ -123,8 +117,8 @@ cdef class PD1Workspace(MaskedDetectorWorkspace): self.pre_set_beats[0] = NULL self.pre_set_beats[1] = NULL - def __init__(self, owner, sims): - super().__init__(owner, sims, PD1Values()) + def __init__(self, owner, sim): + super().__init__(owner, sim, PD1Values()) self.v = self.values cpdef update_beats(self): @@ -134,19 +128,21 @@ cdef class PD1Workspace(MaskedDetectorWorkspace): """ cdef: list beats = [] - frequency_info_t f1, f2 + frequency_info_t *f1 + frequency_info_t *f2 Py_ssize_t i, j, n, prev_num_beats - + Py_ssize_t Nf = self.sim.carrier.optical_frequencies.size + prev_num_beats = self.num_pre_set_beats - for i in range(self.DC.num_frequencies): - f1 = self.DC.frequency_info[i] - for j in range(self.DC.num_frequencies): - f2 = self.DC.frequency_info[j] + for i in range(Nf): + f1 = &self.sim.carrier.optical_frequencies.frequency_info[i] + for j in range(Nf): + f2 = &self.sim.carrier.optical_frequencies.frequency_info[j] if f1.f - f2.f == self.v.f: beats.append(( - self.DC.field_fast(self.dc_node_id, f1.index, 0), - self.DC.field_fast(self.dc_node_id, f2.index, 0), + self.sim.carrier.field_fast(self.dc_node_id, f1.index, 0), + self.sim.carrier.field_fast(self.dc_node_id, f2.index, 0), )) self.num_pre_set_beats = len(beats) @@ -193,26 +189,28 @@ cdef object c_pd1_AC_output(DetectorWorkspace dws): """This expects a `PD1Workspace` input""" cdef: PD1Workspace ws = dws - frequency_info_t f1, f2 + frequency_info_t *f1, + frequency_info_t *f2 Py_ssize_t i, j, n complex_t tmp + Py_ssize_t Nf = ws.sim.carrier.optical_frequencies.size ws.Aij = 0 - for i in range(ws.DC.num_frequencies): - f1 = ws.DC.frequency_info[i] - tmp = ws.DC.get_DC_out_fast(ws.dc_node_id, f1.index) - ws.Aij += conj(tmp) * ws.AC.get_DC_out_fast(ws.ac_node_id, f1.audio_upper_index) + for i in range(Nf): + f1 = &ws.sim.carrier.optical_frequencies.frequency_info[i] + tmp = ws.sim.carrier.get_out_fast(ws.dc_node_id, f1.index) + ws.Aij += conj(tmp) * ws.sim.signal.get_out_fast(ws.ac_node_id, f1.audio_upper_index) # lower sideband conjugate is modelled - ws.Aij += tmp * ws.AC.get_DC_out_fast(ws.ac_node_id, f1.audio_lower_index) + ws.Aij += tmp * ws.sim.signal.get_out_fast(ws.ac_node_id, f1.audio_lower_index) # TODO (sjr) Loop over HOMs needed here surely? # Factor of two because of signal scaling if ws.output_real: tmp = ws.Aij*cexp(-1j * ws.v.phase * finesse.constants._DEG2RAD) - return 2 * tmp.real * ws.AC.model_data.EPSILON0_C * 0.5 + return 2 * tmp.real * ws.sim.model_data.EPSILON0_C * 0.5 else: - return 2 * ws.Aij * ws.AC.model_data.EPSILON0_C * 0.5 + return 2 * ws.Aij * ws.sim.model_data.EPSILON0_C * 0.5 pd1_DC_output = OutputFuncWrapper.make_from_ptr(c_pd1_DC_output) @@ -223,10 +221,12 @@ cdef object c_pd1_DC_output(DetectorWorkspace dws): """This expects a `PD1Workspace` input""" cdef: PD1Workspace ws = dws - frequency_info_t f1, f2 + frequency_info_t *f1 + frequency_info_t *f2 Py_ssize_t i, j, k, n Py_ssize_t m # mode index complex_t tmp + Py_ssize_t Nf = ws.sim.carrier.optical_frequencies.size ws.Aij = 0 @@ -237,18 +237,18 @@ cdef object c_pd1_DC_output(DetectorWorkspace dws): j = ws.pre_set_beats[1][n] for k in range(ws.num_HOMs): m = ws.mode_indices[k] - ws.Aij += ws.DC.out_view[i+m] * conj(ws.DC.out_view[j+m]) + ws.Aij += ws.sim.carrier.out_view[i+m] * conj(ws.sim.carrier.out_view[j+m]) else: # Do dumb thing and check everything if we have/can not optimise - for i in range(ws.DC.num_frequencies): - f1 = ws.DC.frequency_info[i] - for j in range(ws.DC.num_frequencies): - f2 = ws.DC.frequency_info[j] + for i in range(ws.sim.carrier.num_frequencies): + f1 = &ws.sim.carrier.optical_frequencies.frequency_info[i] + for j in range(ws.sim.carrier.num_frequencies): + f2 = &ws.sim.carrier.optical_frequencies.frequency_info[j] if float_eq(f1.f - f2.f, ws.v.f): for k in range(ws.num_HOMs): m = ws.mode_indices[k] - ws.Aij += (ws.DC.get_DC_out_fast(ws.dc_node_id, f1.index, m) * - conj(ws.DC.get_DC_out_fast(ws.dc_node_id, f2.index, m))) + ws.Aij += (ws.sim.carrier.get_out_fast(ws.dc_node_id, f1.index, m) * + conj(ws.sim.carrier.get_out_fast(ws.dc_node_id, f2.index, m))) if ws.v.phase: tmp = ws.Aij*cexp(-1j * ws.v.phase * finesse.constants._DEG2RAD) @@ -256,9 +256,9 @@ cdef object c_pd1_DC_output(DetectorWorkspace dws): tmp = ws.Aij if ws.output_real: - return tmp.real * ws.DC.model_data.EPSILON0_C * 0.5 + return tmp.real * ws.sim.model_data.EPSILON0_C * 0.5 else: - return tmp * ws.DC.model_data.EPSILON0_C * 0.5 + return tmp * ws.sim.model_data.EPSILON0_C * 0.5 ### PD2 workspace & output funcs ### @@ -279,8 +279,6 @@ cdef class PD2Values(BaseCValues): cdef class PD2Workspace(MaskedDetectorWorkspace): cdef public: bint output_real - BaseSimulation DC - BaseSimulation AC int dc_node_id int ac_node_id int[:,::1] homs @@ -293,8 +291,8 @@ cdef class PD2Workspace(MaskedDetectorWorkspace): complex_t z2 PD2Values v - def __init__(self, owner, sims): - super().__init__(owner, sims, PD2Values()) + def __init__(self, owner, sim): + super().__init__(owner, sim, PD2Values()) self.v = self.values pd2_DC_output = OutputFuncWrapper.make_from_ptr(c_pd2_DC_output) @@ -305,31 +303,33 @@ cdef object c_pd2_DC_output(DetectorWorkspace dws): """This expects a `PD2Workspace` input""" cdef: PD2Workspace ws = dws - frequency_info_t f1, f2 + frequency_info_t *f1 + frequency_info_t *f2 Py_ssize_t i, j, k, n Py_ssize_t m # mode index complex_t tmp, phs double f_plus = ws.v.f1 + ws.v.f2 double f_minus = ws.v.f2 - ws.v.f1 + Py_ssize_t Nf = ws.sim.carrier.optical_frequencies.size ws.z1 = 0 ws.z2 = 0 - for i in range(ws.DC.num_frequencies): - f1 = ws.DC.frequency_info[i] - for j in range(ws.DC.num_frequencies): - f2 = ws.DC.frequency_info[j] + for i in range(Nf): + f1 = &ws.sim.carrier.optical_frequencies.frequency_info[i] + for j in range(Nf): + f2 = &ws.sim.carrier.optical_frequencies.frequency_info[j] if f1.f - f2.f == f_plus: for k in range(ws.num_HOMs): m = ws.mode_indices[k] - ws.z1 += (ws.DC.get_DC_out_fast(ws.dc_node_id, f1.index, m) * - conj(ws.DC.get_DC_out_fast(ws.dc_node_id, f2.index, m))) + ws.z1 += (ws.sim.carrier.get_out_fast(ws.dc_node_id, f1.index, m) * + conj(ws.sim.carrier.get_out_fast(ws.dc_node_id, f2.index, m))) if f1.f - f2.f == f_minus: for k in range(ws.num_HOMs): m = ws.mode_indices[k] - ws.z2 += (ws.DC.get_DC_out_fast(ws.dc_node_id, f1.index, m) * - conj(ws.DC.get_DC_out_fast(ws.dc_node_id, f2.index, m))) + ws.z2 += (ws.sim.carrier.get_out_fast(ws.dc_node_id, f1.index, m) * + conj(ws.sim.carrier.get_out_fast(ws.dc_node_id, f2.index, m))) if ws.v.phase1: phs = cexp(-1j * ws.v.phase1 * finesse.constants._DEG2RAD) @@ -343,9 +343,9 @@ cdef object c_pd2_DC_output(DetectorWorkspace dws): # Factor 1/2 from demod gain # And a mystery factor of two to match up with v2 if ws.output_real: - return tmp.real * ws.DC.model_data.EPSILON0_C * 0.5 * 0.5 + return tmp.real * ws.sim.model_data.EPSILON0_C * 0.5 * 0.5 else: - return tmp * ws.DC.model_data.EPSILON0_C * 0.5 * 0.5 + return tmp * ws.sim.model_data.EPSILON0_C * 0.5 * 0.5 pd2_AC_output = OutputFuncWrapper.make_from_ptr(c_pd2_AC_output) @@ -356,13 +356,15 @@ cdef object c_pd2_AC_output(DetectorWorkspace dws): """This expects a `PD2Workspace` input""" cdef: PD2Workspace ws = dws - frequency_info_t f1, f2 + frequency_info_t *f1 + frequency_info_t *f2 Py_ssize_t i, j, k Py_ssize_t m # mode index complex_t tmp, phs, car1, car2 double df double f_plus = ws.v.f1 + ws.v.f2 double f_minus = ws.v.f2 - ws.v.f1 + Py_ssize_t Nf = ws.sim.carrier.optical_frequencies.size ws.z1 = 0 ws.z2 = 0 @@ -370,28 +372,28 @@ cdef object c_pd2_AC_output(DetectorWorkspace dws): # Here we loop over each of the carrier beats and apply the relevant signal sideband # beats. This should be the equivalent code in signaldiodes but they fill the maxtrix # instead. - for i in range(ws.DC.num_frequencies): - f1 = ws.DC.frequency_info[i] - for j in range(ws.DC.num_frequencies): - f2 = ws.DC.frequency_info[j] + for i in range(Nf): + f1 = &ws.sim.carrier.optical_frequencies.frequency_info[i] + for j in range(Nf): + f2 = &ws.sim.carrier.optical_frequencies.frequency_info[j] df = f1.f - f2.f if float_eq(df, -ws.v.f1): for k in range(ws.num_HOMs): m = ws.mode_indices[k] - car1 = ws.DC.get_DC_out_fast(ws.dc_node_id, f1.index, m) - ws.z2 += car1 * ws.AC.get_DC_out_fast(ws.ac_node_id, f2.audio_lower_index, m) + car1 = ws.sim.carrier.get_out_fast(ws.dc_node_id, f1.index, m) + ws.z2 += car1 * ws.sim.signal.get_out_fast(ws.ac_node_id, f2.audio_lower_index, m) - car2 = ws.DC.get_DC_out_fast(ws.dc_node_id, f2.index, m) - ws.z2 += ws.AC.get_DC_out_fast(ws.ac_node_id, f1.audio_upper_index, m) * conj(car2) + car2 = ws.sim.carrier.get_out_fast(ws.dc_node_id, f2.index, m) + ws.z2 += ws.sim.signal.get_out_fast(ws.ac_node_id, f1.audio_upper_index, m) * conj(car2) elif float_eq(df, ws.v.f1): for k in range(ws.num_HOMs): m = ws.mode_indices[k] - car1 = ws.DC.get_DC_out_fast(ws.dc_node_id, f1.index, m) - ws.z1 += car1 * ws.AC.get_DC_out_fast(ws.ac_node_id, f2.audio_lower_index, m) + car1 = ws.sim.carrier.get_out_fast(ws.dc_node_id, f1.index, m) + ws.z1 += car1 * ws.sim.signal.get_out_fast(ws.ac_node_id, f2.audio_lower_index, m) - car2 = ws.DC.get_DC_out_fast(ws.dc_node_id, f2.index, m) - ws.z1 += ws.AC.get_DC_out_fast(ws.ac_node_id, f1.audio_upper_index, m) * conj(car2) + car2 = ws.sim.carrier.get_out_fast(ws.dc_node_id, f2.index, m) + ws.z1 += ws.sim.signal.get_out_fast(ws.ac_node_id, f1.audio_upper_index, m) * conj(car2) phs = cexp(-1j * ws.v.phase1 * finesse.constants._DEG2RAD) # 1/2 from cos(X)*cos(Y) = 1/2(cos(X+Y)+cos(X-Y)) expansion @@ -402,375 +404,377 @@ cdef object c_pd2_AC_output(DetectorWorkspace dws): # Factor of two because of signal scaling if ws.output_real: - return 2 * tmp.real * ws.DC.model_data.EPSILON0_C * 0.5 - else: - return 2 * tmp * ws.DC.model_data.EPSILON0_C * 0.5 - - -### Generic (slower) PD workspace & output funcs ### - -cdef class PDWorkspace(DetectorWorkspace): - cdef: - public BaseSimulation DC - public BaseSimulation AC - public Py_ssize_t dc_node_id - public Py_ssize_t ac_node_id - public int[:, ::1] homs - const int[:, ::1] mask - bint mode_is_network - double[:, :, :, ::1] beat_factors - Py_ssize_t num_demod - Py_ssize_t num_mixes - double[:, ::1] fmix_view - double EPSILON0_C - complex_t[::1] zview - - def __init__(self, - owner, - sims, - int N_demods, - double EPSILON0_C, - const int[:, ::1] mask, - bint mode_is_network, - beat_factors=None): - - super().__init__(owner, sims) - self.EPSILON0_C = EPSILON0_C - self.mask = mask - self.mode_is_network = mode_is_network - self.beat_factors = beat_factors - # Computing values and allocating arrays that will be used throughout - # the simulation output calculations - self.num_demod = N_demods - self.num_mixes = 1 + self.num_demod * (self.num_demod - 1) / 2 - self.fmix_view = np.ones((self.num_mixes, self.num_demod)) - self.zview = np.zeros(self.num_mixes, dtype=np.complex128) - # TODO ddb - I think the mix table only needs to be made once - # Might need to update if the demod frequencies change - _create_mix_table(self.num_demod, self.fmix_view) - -def mixer_pd_output(PDWorkspace ws, - double[::1] freqs, - double[::1] phases, - long long[::1] max_phase_indices -): - cdef: - Py_ssize_t mix_index - double f_ref - complex_t dm_amp - complex_t power - # Flag for whether this pd is computing purely a transfer function - # TODO: Is this really needed, or just an artifact from the past? - int transfer = 0 - - for mix_index in range(ws.num_mixes): - f_ref = _get_reference_freq(ws.num_demod, mix_index, ws.fmix_view, freqs) - if ws.beat_factors is not None: - ws.zview[mix_index] = compute_amplitude_beats(ws, &transfer, f_ref) - else: - ws.zview[mix_index] = compute_amplitude(ws, &transfer, f_ref) - - dm_amp = _set_demodulation_phase( - ws.num_demod, ws.zview, ws.fmix_view, ws.num_mixes, phases, max_phase_indices - ) - power = _get_demodulation_signal( - ws.num_demod, dm_amp, phases, max_phase_indices, ws.EPSILON0_C, ws.mode_is_network, transfer - ) - - return power - - -cdef _create_mix_table(Py_ssize_t num_demod, double[:, ::1] fmix_view): - cdef: - int num_mixes = 1 - - Py_ssize_t demod_index_outer, demod_index_inner, mix_permutation_index - - np.ndarray[double, ndim=1] fmix_tmp = np.ones(num_demod) - double[::1] fmix_tmp_view = fmix_tmp - - for demod_index_outer in range(1, num_demod): - for mix_permutation_index in range(demod_index_outer, 0, -1): - num_mixes += 1 - fmix_tmp_view[mix_permutation_index - 1] *= -1 - - for demod_index_inner in range(num_demod): - fmix_view[num_mixes - 1][demod_index_inner] = fmix_tmp_view[demod_index_inner] - - -def dc_pd_output(PDWorkspace ws): - cdef: - complex_t power = COMPLEX_0 - int transfer = 0 - - if ws.beat_factors is not None: - power = compute_amplitude_beats(ws, &transfer) + return 2 * tmp.real * ws.sim.model_data.EPSILON0_C * 0.5 else: - power = compute_amplitude(ws, &transfer) - - return power.real * ws.EPSILON0_C * 0.5 - - -cdef double _get_reference_freq( - Py_ssize_t num_demod, - Py_ssize_t mix_index, - double[:, ::1] fmix_view, - double[::1] freqs -) nogil: - cdef: - double f_ref = 0.0 - Py_ssize_t demod_index - - for demod_index in range(num_demod): - f_ref += fmix_view[mix_index][demod_index] * freqs[demod_index] - - return f_ref - - -cdef complex_t _set_demodulation_phase( - Py_ssize_t num_demod, - complex_t[::1] zview, - double[:, ::1] fmix_view, - Py_ssize_t num_mixes, - double[::1] phases, - long long[::1] max_phase_indices -) nogil: - cdef: - Py_ssize_t demod_index, mix_index - complex_t demod_amp = COMPLEX_0 - - complex_t z1, z2 - double phi_max - - double* phi = calloc(num_mixes, sizeof(double)) - - for demod_index in range(num_demod - 1): - z1 = COMPLEX_0 - z2 = COMPLEX_0 - # max phase - if in_index_array(max_phase_indices, demod_index): - for mix_index in range(num_mixes): - if fmix_view[mix_index][demod_index] < 0.0: - z1 += zview[mix_index] - else: - z2 += zview[mix_index] - - phi_max = 0.5 * (carg(z2) - carg(z1)) - phases[demod_index] = phi_max - - for mix_index in range(num_mixes): - phi[mix_index] -= phi_max * fmix_view[mix_index][demod_index] - # user defined phase - else: - for mix_index in range(num_mixes): - phi[mix_index] -= ( - phases[demod_index] * fmix_view[mix_index][demod_index] - ) - - for mix_index in range(num_mixes): - demod_amp += crotate(zview[mix_index], phi[mix_index]) - - free(phi) - - return demod_amp - - -cdef complex_t _get_demodulation_signal( - Py_ssize_t num_demod, - complex_t demod_amp, - double[::1] phases, - long long[::1] max_phase_indices, - double epsilon0_c, - bint mode_is_network, - int transfer -) nogil: - cdef: - complex_t z = demod_amp - - if not mode_is_network: - if in_index_array(max_phase_indices, num_demod - 1): - z = cabs(demod_amp) - else: - z = ( - creal(demod_amp) * cos(phases[num_demod - 1]) - + cimag(demod_amp) * sin(phases[num_demod - 1]) - ) - - z *= 0.5**(num_demod - 1) - - if transfer == 2: - z *= 2 - - return z * epsilon0_c * 0.5 - -cdef complex_t compute_amplitude( - PDWorkspace ws, - int *transfer, - double f_ref=NAN -): - cdef: - complex_t amplitude = COMPLEX_0 - - Py_ssize_t field_idx - Py_ssize_t N = ws.homs.shape[0] - int n, m - bint mixed = False - - if not isnan(f_ref): - mixed = True - - for field_idx in range(N): - n = ws.homs[field_idx][0] - m = ws.homs[field_idx][1] - - if in_mask(n, m, ws.mask): - continue - - amplitude += compute_mode_amplitude( - ws.DC, - ws.AC, - ws.dc_node_id, - ws.ac_node_id, - field_idx, - field_idx, - transfer, - f_ref, - mixed - ) - - return amplitude - - -cdef complex_t compute_amplitude_beats( - PDWorkspace ws, - int *transfer, - double f_ref=NAN -): - cdef: - complex_t amplitude = COMPLEX_0 - - Py_ssize_t N = ws.homs.shape[0] - Py_ssize_t field_idx_outer, field_idx_inner - int n1, m1, n2, m2 - double pd_factor - bint mixed = False - - if ~isnan(f_ref): - mixed = True - - for field_idx_outer in range(N): - n1 = ws.homs[field_idx_outer][0] - m1 = ws.homs[field_idx_outer][1] - - # TODO ddb : probably worth changing ws.mask to a list of included - # mode indicies rather than checking all the time - if in_mask(n1, m1, ws.mask): - continue - - for field_idx_inner in range(N): - n2 = ws.homs[field_idx_inner][0] - m2 = ws.homs[field_idx_inner][1] - - pd_factor = ws.beat_factors [n1][m1][n2][m2] - if pd_factor == 0.0: - continue - - amplitude += compute_mode_amplitude( - ws.DC, - ws.AC, - ws.dc_node_id, - ws.ac_node_id, - field_idx_outer, - field_idx_inner, - transfer, - f_ref, - mixed - ) * pd_factor - - return amplitude - - -cdef complex_t compute_mode_amplitude( - BaseSimulation DC, - BaseSimulation AC, - Py_ssize_t dc_node_id, - Py_ssize_t ac_node_id, - Py_ssize_t field_idx_outer, - Py_ssize_t field_idx_inner, - int *transfer, - double f_ref, - bint mixed -) nogil: - cdef: - Py_ssize_t i, j - - complex_t inner, outer - complex_t amplitude = COMPLEX_0 - - frequency_info_t f1, f2 - - for i in range(DC.num_frequencies): - f1 = DC.frequency_info[i] - outer = DC.get_DC_out_fast(dc_node_id, f1.index, field_idx_outer) - for j in range(DC.num_frequencies): - f2 = DC.frequency_info[j] - if mixed: - if not float_eq(f1.f - f2.f, f_ref): - continue - else: - if not float_eq(f1.f, f2.f): - continue - - transfer[0] |= 1 - inner = DC.get_DC_out_fast(dc_node_id, f2.index, field_idx_inner) - amplitude += outer * conj(inner) - - if not mixed or AC is None: - continue - - for j in range(AC.num_frequencies): - f2 = AC.frequency_info[j] - - if not float_eq(f1.f - f2.f, f_ref): - continue - - transfer[0] |= 2 - - inner = AC.get_DC_out_fast(ac_node_id, f2.index, field_idx_inner) - - if f2.audio_order < 0: - amplitude += outer * inner - else: - amplitude += outer * conj(inner) - - if AC is not None and mixed: - for i in range(AC.num_frequencies): - f1 = AC.frequency_info[i] - outer = AC.get_DC_out_fast(ac_node_id, f1.index, field_idx_outer) - if f1.audio_order < 0: - outer = conj(outer) - for j in range(DC.num_frequencies): - f2 = DC.frequency_info[j] - - if not float_eq(f1.f - f2.f, f_ref): - continue - - transfer[0] |= 2 - - inner = DC.get_DC_out_fast(dc_node_id, f2.index, field_idx_inner) - - amplitude += outer * conj(inner) - - return amplitude - - -cdef bint in_index_array(long long[::] indices, Py_ssize_t idx) nogil: - cdef: - Py_ssize_t i - Py_ssize_t N = indices.shape[0] - - for i in range(N): - if indices[i] == idx: - return True - - return False + return 2 * tmp * ws.sim.model_data.EPSILON0_C * 0.5 + + +# ### Generic (slower) PD workspace & output funcs ### +# TODO ddb this all needs changing over to use the CarrierSignalMatrixSimulation +# class, which as none of this is used anywhere really I can't be bothered doing +# right now, as I'm not sure if any of this have any valid test cases +# cdef class PDWorkspace(DetectorWorkspace): +# cdef: +# public BaseSimulation DC +# public BaseSimulation AC +# public Py_ssize_t dc_node_id +# public Py_ssize_t ac_node_id +# public int[:, ::1] homs +# const int[:, ::1] mask +# bint mode_is_network +# double[:, :, :, ::1] beat_factors +# Py_ssize_t num_demod +# Py_ssize_t num_mixes +# double[:, ::1] fmix_view +# double EPSILON0_C +# complex_t[::1] zview + +# def __init__(self, +# owner, +# sims, +# int N_demods, +# double EPSILON0_C, +# const int[:, ::1] mask, +# bint mode_is_network, +# beat_factors=None): + +# super().__init__(owner, sims) +# self.EPSILON0_C = EPSILON0_C +# self.mask = mask +# self.mode_is_network = mode_is_network +# self.beat_factors = beat_factors +# # Computing values and allocating arrays that will be used throughout +# # the simulation output calculations +# self.num_demod = N_demods +# self.num_mixes = 1 + self.num_demod * (self.num_demod - 1) / 2 +# self.fmix_view = np.ones((self.num_mixes, self.num_demod)) +# self.zview = np.zeros(self.num_mixes, dtype=np.complex128) +# # TODO ddb - I think the mix table only needs to be made once +# # Might need to update if the demod frequencies change +# _create_mix_table(self.num_demod, self.fmix_view) + +# def mixer_pd_output(PDWorkspace ws, +# double[::1] freqs, +# double[::1] phases, +# long long[::1] max_phase_indices +# ): +# cdef: +# Py_ssize_t mix_index +# double f_ref +# complex_t dm_amp +# complex_t power +# # Flag for whether this pd is computing purely a transfer function +# # TODO: Is this really needed, or just an artifact from the past? +# int transfer = 0 + +# for mix_index in range(ws.num_mixes): +# f_ref = _get_reference_freq(ws.num_demod, mix_index, ws.fmix_view, freqs) +# if ws.beat_factors is not None: +# ws.zview[mix_index] = compute_amplitude_beats(ws, &transfer, f_ref) +# else: +# ws.zview[mix_index] = compute_amplitude(ws, &transfer, f_ref) + +# dm_amp = _set_demodulation_phase( +# ws.num_demod, ws.zview, ws.fmix_view, ws.num_mixes, phases, max_phase_indices +# ) +# power = _get_demodulation_signal( +# ws.num_demod, dm_amp, phases, max_phase_indices, ws.EPSILON0_C, ws.mode_is_network, transfer +# ) + +# return power + + +# cdef _create_mix_table(Py_ssize_t num_demod, double[:, ::1] fmix_view): +# cdef: +# int num_mixes = 1 + +# Py_ssize_t demod_index_outer, demod_index_inner, mix_permutation_index + +# np.ndarray[double, ndim=1] fmix_tmp = np.ones(num_demod) +# double[::1] fmix_tmp_view = fmix_tmp + +# for demod_index_outer in range(1, num_demod): +# for mix_permutation_index in range(demod_index_outer, 0, -1): +# num_mixes += 1 +# fmix_tmp_view[mix_permutation_index - 1] *= -1 + +# for demod_index_inner in range(num_demod): +# fmix_view[num_mixes - 1][demod_index_inner] = fmix_tmp_view[demod_index_inner] + + +# def dc_pd_output(PDWorkspace ws): +# cdef: +# complex_t power = COMPLEX_0 +# int transfer = 0 + +# if ws.beat_factors is not None: +# power = compute_amplitude_beats(ws, &transfer) +# else: +# power = compute_amplitude(ws, &transfer) + +# return power.real * ws.EPSILON0_C * 0.5 + + +# cdef double _get_reference_freq( +# Py_ssize_t num_demod, +# Py_ssize_t mix_index, +# double[:, ::1] fmix_view, +# double[::1] freqs +# ) nogil: +# cdef: +# double f_ref = 0.0 +# Py_ssize_t demod_index + +# for demod_index in range(num_demod): +# f_ref += fmix_view[mix_index][demod_index] * freqs[demod_index] + +# return f_ref + + +# cdef complex_t _set_demodulation_phase( +# Py_ssize_t num_demod, +# complex_t[::1] zview, +# double[:, ::1] fmix_view, +# Py_ssize_t num_mixes, +# double[::1] phases, +# long long[::1] max_phase_indices +# ) nogil: +# cdef: +# Py_ssize_t demod_index, mix_index +# complex_t demod_amp = COMPLEX_0 + +# complex_t z1, z2 +# double phi_max + +# double* phi = calloc(num_mixes, sizeof(double)) + +# for demod_index in range(num_demod - 1): +# z1 = COMPLEX_0 +# z2 = COMPLEX_0 +# # max phase +# if in_index_array(max_phase_indices, demod_index): +# for mix_index in range(num_mixes): +# if fmix_view[mix_index][demod_index] < 0.0: +# z1 += zview[mix_index] +# else: +# z2 += zview[mix_index] + +# phi_max = 0.5 * (carg(z2) - carg(z1)) +# phases[demod_index] = phi_max + +# for mix_index in range(num_mixes): +# phi[mix_index] -= phi_max * fmix_view[mix_index][demod_index] +# # user defined phase +# else: +# for mix_index in range(num_mixes): +# phi[mix_index] -= ( +# phases[demod_index] * fmix_view[mix_index][demod_index] +# ) + +# for mix_index in range(num_mixes): +# demod_amp += crotate(zview[mix_index], phi[mix_index]) + +# free(phi) + +# return demod_amp + + +# cdef complex_t _get_demodulation_signal( +# Py_ssize_t num_demod, +# complex_t demod_amp, +# double[::1] phases, +# long long[::1] max_phase_indices, +# double epsilon0_c, +# bint mode_is_network, +# int transfer +# ) nogil: +# cdef: +# complex_t z = demod_amp + +# if not mode_is_network: +# if in_index_array(max_phase_indices, num_demod - 1): +# z = cabs(demod_amp) +# else: +# z = ( +# creal(demod_amp) * cos(phases[num_demod - 1]) +# + cimag(demod_amp) * sin(phases[num_demod - 1]) +# ) + +# z *= 0.5**(num_demod - 1) + +# if transfer == 2: +# z *= 2 + +# return z * epsilon0_c * 0.5 + +# cdef complex_t compute_amplitude( +# PDWorkspace ws, +# int *transfer, +# double f_ref=NAN +# ): +# cdef: +# complex_t amplitude = COMPLEX_0 + +# Py_ssize_t field_idx +# Py_ssize_t N = ws.homs.shape[0] +# int n, m +# bint mixed = False + +# if not isnan(f_ref): +# mixed = True + +# for field_idx in range(N): +# n = ws.homs[field_idx][0] +# m = ws.homs[field_idx][1] + +# if in_mask(n, m, ws.mask): +# continue + +# amplitude += compute_mode_amplitude( +# ws.sim.carrier, +# ws.sim.signal, +# ws.dc_node_id, +# ws.ac_node_id, +# field_idx, +# field_idx, +# transfer, +# f_ref, +# mixed +# ) + +# return amplitude + + +# cdef complex_t compute_amplitude_beats( +# PDWorkspace ws, +# int *transfer, +# double f_ref=NAN +# ): +# cdef: +# complex_t amplitude = COMPLEX_0 + +# Py_ssize_t N = ws.homs.shape[0] +# Py_ssize_t field_idx_outer, field_idx_inner +# int n1, m1, n2, m2 +# double pd_factor +# bint mixed = False + +# if ~isnan(f_ref): +# mixed = True + +# for field_idx_outer in range(N): +# n1 = ws.homs[field_idx_outer][0] +# m1 = ws.homs[field_idx_outer][1] + +# # TODO ddb : probably worth changing ws.mask to a list of included +# # mode indicies rather than checking all the time +# if in_mask(n1, m1, ws.mask): +# continue + +# for field_idx_inner in range(N): +# n2 = ws.homs[field_idx_inner][0] +# m2 = ws.homs[field_idx_inner][1] + +# pd_factor = ws.beat_factors [n1][m1][n2][m2] +# if pd_factor == 0.0: +# continue + +# amplitude += compute_mode_amplitude( +# ws.sim.carrier, +# ws.sim.signal, +# ws.dc_node_id, +# ws.ac_node_id, +# field_idx_outer, +# field_idx_inner, +# transfer, +# f_ref, +# mixed +# ) * pd_factor + +# return amplitude + + +# cdef complex_t compute_mode_amplitude( +# BaseSimulation DC, +# BaseSimulation AC, +# Py_ssize_t dc_node_id, +# Py_ssize_t ac_node_id, +# Py_ssize_t field_idx_outer, +# Py_ssize_t field_idx_inner, +# int *transfer, +# double f_ref, +# bint mixed +# ) nogil: +# cdef: +# Py_ssize_t i, j + +# complex_t inner, outer +# complex_t amplitude = COMPLEX_0 + +# frequency_info_t f1, f2 + +# for i in range(DC.num_frequencies): +# f1 = DC.frequency_info[i] +# outer = DC.get_out_fast(dc_node_id, f1.index, field_idx_outer) +# for j in range(DC.num_frequencies): +# f2 = DC.frequency_info[j] +# if mixed: +# if not float_eq(f1.f - f2.f, f_ref): +# continue +# else: +# if not float_eq(f1.f, f2.f): +# continue + +# transfer[0] |= 1 +# inner = DC.get_out_fast(dc_node_id, f2.index, field_idx_inner) +# amplitude += outer * conj(inner) + +# if not mixed or AC is None: +# continue + +# for j in range(AC.num_frequencies): +# f2 = AC.frequency_info[j] + +# if not float_eq(f1.f - f2.f, f_ref): +# continue + +# transfer[0] |= 2 + +# inner = AC.get_out_fast(ac_node_id, f2.index, field_idx_inner) + +# if f2.audio_order < 0: +# amplitude += outer * inner +# else: +# amplitude += outer * conj(inner) + +# if AC is not None and mixed: +# for i in range(AC.num_frequencies): +# f1 = AC.frequency_info[i] +# outer = AC.get_out_fast(ac_node_id, f1.index, field_idx_outer) +# if f1.audio_order < 0: +# outer = conj(outer) +# for j in range(DC.num_frequencies): +# f2 = DC.frequency_info[j] + +# if not float_eq(f1.f - f2.f, f_ref): +# continue + +# transfer[0] |= 2 + +# inner = DC.get_out_fast(dc_node_id, f2.index, field_idx_inner) + +# amplitude += outer * conj(inner) + +# return amplitude + + +# cdef bint in_index_array(long long[::] indices, Py_ssize_t idx) nogil: +# cdef: +# Py_ssize_t i +# Py_ssize_t N = indices.shape[0] + +# for i in range(N): +# if indices[i] == idx: +# return True + +# return False diff --git a/src/finesse/detectors/gouy.py b/src/finesse/detectors/gouy.py index d21db74b59f006c14ff22a38dec9b47f2dbc65a7..b168ad50ba9ae9279ec8a43140216e06b30a1174 100644 --- a/src/finesse/detectors/gouy.py +++ b/src/finesse/detectors/gouy.py @@ -111,6 +111,6 @@ class Gouy(Detector): path = self._model.path(self.__from_node, self.__to_node, self.__via_node) self.__spaces = list(path.spaces) - def _get_workspace(self, sims): - ws = GouyDetectorWorkspace(self, sims) + def _get_workspace(self, sim): + ws = GouyDetectorWorkspace(self, sim) return ws diff --git a/src/finesse/detectors/knmdetector.py b/src/finesse/detectors/knmdetector.py index 114a0f71de62d8720a04f2ccc185c331e9484888..380d43455780f43275a08fab1ea1a569aa01ba83 100644 --- a/src/finesse/detectors/knmdetector.py +++ b/src/finesse/detectors/knmdetector.py @@ -45,14 +45,14 @@ class KnmDetector(Detector): if any(k < 0 for k in (self.n1, self.m1, self.n2, self.m2)): raise ValueError("Mode indices cannot be negative.") - def _get_workspace(self, sims): - ws = KnmDetectorWorkspace(self, sims) + def _get_workspace(self, sim): + ws = KnmDetectorWorkspace(self, sim) ws.n1 = self.n1 ws.m1 = self.m1 ws.n2 = self.n2 ws.m2 = self.m2 - comp_ws = list(filter(lambda x: x.owner == self.__comp, sims[0].workspaces)) + comp_ws = list(filter(lambda x: x.owner == self.__comp, sim.workspaces)) if not comp_ws: raise RuntimeError( f"Could not find {self.__comp.name} in simulation workspace" diff --git a/src/finesse/detectors/motion_detector.py b/src/finesse/detectors/motion_detector.py index 818d84d5d8221682665d47e9f1e4ac0704747bac..2d95f7e78630d3923f711f6d0b69a4ddd3874dfd 100644 --- a/src/finesse/detectors/motion_detector.py +++ b/src/finesse/detectors/motion_detector.py @@ -13,8 +13,8 @@ LOGGER = logging.getLogger(__name__) class MotionDetectorWorkspace(DetectorWorkspace): - def __init__(self, owner, sims): - super().__init__(owner, sims) + def __init__(self, owner, sim): + super().__init__(owner, sim) class MotionDetector(Detector): @@ -32,12 +32,11 @@ class MotionDetector(Detector): def __init__(self, name, node): Detector.__init__(self, name, node, dtype=np.complex128, label="Motion") - def _get_workspace(self, sims): + def _get_workspace(self, sim): def output_fn(ws): - for sim in ws.sims: - if sim.is_audio: - return sim.get_DC_out(self._node, 0, 0) * sim.model._x_scale + if ws.sim.signal: + return ws.sim.signal.get_out(self._node, 0, 0) * ws.sim.model._x_scale - ws = MotionDetectorWorkspace(self, sims) + ws = MotionDetectorWorkspace(self, sim) ws.set_output_fn(output_fn) return ws diff --git a/src/finesse/detectors/powerdetector.py b/src/finesse/detectors/powerdetector.py index e8fa2c7e88a18f8d72ac8695ef252cda803b7278..1400d9a0be667039e830fc340793fe3521c9d78b 100644 --- a/src/finesse/detectors/powerdetector.py +++ b/src/finesse/detectors/powerdetector.py @@ -23,12 +23,12 @@ LOGGER = logging.getLogger(__name__) def check_is_audio(ws, f: Parameter): - if ws.AC is None: + if ws.sim.signal is None: return False elif f.state == ParameterState.Symbolic: - return f.value.owner is ws.AC.model.fsig + return f.value.owner is ws.sim.model.fsig else: - return f.value is ws.AC.model.fsig.f.value + return f.value is ws.sim.model.fsig.f.value @model_parameter("f", "Frequency", None, Rebuild.Frequencies) @@ -72,14 +72,8 @@ class PowerDetectorDemod1(Detector): self.f = f self.phase = phase - def _get_workspace(self, sims): - """ - All the sims should have been started now - When the simulation has started we can grab - references from all the simulations - that we will need for computing this output. - """ - ws = PD1Workspace(self, sims) + def _get_workspace(self, sim): + ws = PD1Workspace(self, sim) ws.is_f_changing = self.f.is_changing ws.is_phase_changing = self.phase.is_changing @@ -93,21 +87,15 @@ class PowerDetectorDemod1(Detector): ws.output_real = False self._set_dtype(np.complex128) - for sim in sims: - if sim.is_audio: - ws.AC = sim - ws.DC = sim.DC - ws.ac_node_id = sim.node_id(self._node) - ws.dc_node_id = sim.DC.node_id(self._node) - ws.is_audio_mixing = check_is_audio(ws, self.f) - else: - ws.DC = sim - ws.AC = None - ws.dc_node_id = sim.node_id(self._node) - - ws.setup_mask(ws.DC) + + ws.dc_node_id = sim.carrier.node_id(self._node) + if sim.signal: + ws.ac_node_id = sim.signal.node_id(self._node) + ws.is_audio_mixing = check_is_audio(ws, self.f) + + ws.setup_mask() # Would there be any weird situation where AC and DC homs are different? - ws.homs = ws.DC.model_data.homs_view + ws.homs = ws.sim.model_data.homs_view if ws.is_audio_mixing: ws.set_output_fn(pd1_AC_output) @@ -173,9 +161,9 @@ class PowerDetectorDemod2(Detector): self.f2 = f2 self.phase2 = phase2 - def _get_workspace(self, sims): - ws = PD2Workspace(self, sims) - + def _get_workspace(self, sim): + ws = PD2Workspace(self, sim) + ws.is_f1_changing = self.f1.is_changing ws.is_phase1_changing = self.phase1.is_changing ws.is_f2_changing = self.f2.is_changing @@ -190,27 +178,19 @@ class PowerDetectorDemod2(Detector): ws.output_real = False self._set_dtype(np.complex128) - for sim in sims: - if sim.is_audio: - ws.AC = sim - ws.DC = sim.DC - ws.ac_node_id = sim.node_id(self._node) - ws.dc_node_id = sim.DC.node_id(self._node) - - if check_is_audio(ws, self.f1): - raise Exception( - f"pd2 {self.name} f1 cannot be an audio frequency, use f2 for audio demodulation" - ) + ws.dc_node_id = sim.carrier.node_id(self._node) - ws.is_audio_mixing = check_is_audio(ws, self.f2) - else: - ws.DC = sim - ws.AC = None - ws.dc_node_id = sim.node_id(self._node) + if sim.signal: + ws.ac_node_id = sim.signal.node_id(self._node) + if check_is_audio(ws, self.f1): + raise Exception( + f"pd2 {self.name} f1 cannot be an audio frequency, use f2 for audio demodulation" + ) + ws.is_audio_mixing = check_is_audio(ws, self.f2) - ws.setup_mask(ws.DC) + ws.setup_mask() # Would there be any weird situation where AC and DC homs are different? - ws.homs = ws.DC.model_data.homs_view + ws.homs = ws.sim.model_data.homs_view if ws.is_audio_mixing: ws.set_output_fn(pd2_AC_output) @@ -243,19 +223,13 @@ class PowerDetector(Detector): def __init__(self, name, node): Detector.__init__(self, name, node, dtype=np.float64, unit="W", label="Power") - def _get_workspace(self, sims): - ws = PD0Workspace(self, sims) - - for sim in sims: - if not sim.is_audio: - ws.DC = sim - ni = ws.DC.get_node_info(self.node) - ws.rhs_index = ni["rhs_index"] - ws.size = ni["nfreqs"] * ni["nhoms"] - ws.setup_mask(ws.DC) + def _get_workspace(self, sim): + ws = PD0Workspace(self, sim) - if ws.DC is None: - raise Exception("Could not find a DC simulation") + ni = sim.carrier.get_node_info(self.node) + ws.rhs_index = ni["rhs_index"] + ws.size = ni["nfreqs"] * ni["nhoms"] + ws.setup_mask() if ws.has_mask: ws.set_output_fn(pd0_DC_output_masked) diff --git a/src/finesse/detectors/quantum_noise_detector.py b/src/finesse/detectors/quantum_noise_detector.py index c1495ea71a8905d0c7438b180663a9861edbd515..fec3ea208136e36e7747f6c6d00d6af78e252d7f 100644 --- a/src/finesse/detectors/quantum_noise_detector.py +++ b/src/finesse/detectors/quantum_noise_detector.py @@ -112,7 +112,7 @@ class QuantumNoiseDetector(Detector): # vacuum noise and demodulate that into our signal. for el in self.demod_vac_contri.values(): if len(el["c_idx"]) > 1: - for j in range(max(sim.nhoms, 1)): + for j in range(max(sim.model_data.num_HOMs, 1)): val = 0 for k in el["c_idx"]: val += ( @@ -126,7 +126,7 @@ class QuantumNoiseDetector(Detector): rtn += abs(val) * (1 + el["f"] / sim.model.f0) ** 2 else: val = 0 - for j in range(max(sim.nhoms, 1)): + for j in range(max(sim.model_data.num_HOMs, 1)): # val += abs(carrier.out[carrier.field(self._node, el["c_idx"][0], j)])**2 val += abs(carrier.get_DC_out(self._node, el["c_idx"][0], j)) ** 2 rtn += val * (1 + el["f"] / sim.model.f0) @@ -219,7 +219,7 @@ class QuantumNoiseDetector(Detector): # product between the j'th carrier and this. fs = sim.frequencies[self.demod_f_sig[j][k]] if fs.order > 0: - for i in range(max(sim.nhoms, 1)): + for i in range(max(sim.model_data.num_HOMs, 1)): c_idx = carrier.field(self._node, fc.index, i) s_idx = sim.field(self._node, fs.index, i) self.s[s_idx] += ( @@ -228,7 +228,7 @@ class QuantumNoiseDetector(Detector): * np.exp(-1j * np.deg2rad(self.demod_phi[k])) ) else: - for i in range(max(sim.nhoms, 1)): + for i in range(max(sim.model_data.num_HOMs, 1)): c_idx = carrier.field(self._node, fc.index, i) s_idx = sim.field(self._node, fs.index, i) self.s[s_idx] += np.conj( diff --git a/src/finesse/detectors/workspace.pxd b/src/finesse/detectors/workspace.pxd index ee2e79e153b85eedbbd2274d7640b96c2e9de077..96f4e8941c893a7f6821f8e8fee7e78497fe83e4 100644 --- a/src/finesse/detectors/workspace.pxd +++ b/src/finesse/detectors/workspace.pxd @@ -1,4 +1,4 @@ -from finesse.simulations.base cimport BaseSimulation +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation from finesse.element cimport ElementWorkspace import numpy as np @@ -16,7 +16,7 @@ cdef class DetectorWorkspace(ElementWorkspace): cdef: OutputFuncWrapper fn_c # C output function object fn_py # Python output function - readonly BaseSimulation[:] sims + readonly CarrierSignalMatrixSimulation sim np.flatiter flatiter Py_ssize_t block_size # Size of output (typically unity) @@ -29,6 +29,7 @@ cdef class MaskedDetectorWorkspace(DetectorWorkspace): Py_ssize_t* mode_indices readonly Py_ssize_t num_HOMs readonly bint has_mask + readonly int[:, ::1] homs_view - cpdef setup_mask(self, BaseSimulation sim) + cpdef setup_mask(self) cdef bint hom_in_modes(self, Py_ssize_t hom_idx) diff --git a/src/finesse/detectors/workspace.pyx b/src/finesse/detectors/workspace.pyx index 067396c65372f471687738a8ffacaabb37c7a440..1912c00f468f25674a38a54a197399ccfe55e326 100644 --- a/src/finesse/detectors/workspace.pyx +++ b/src/finesse/detectors/workspace.pyx @@ -38,10 +38,9 @@ cdef class OutputFuncWrapper: cdef class DetectorWorkspace(ElementWorkspace): - def __init__(self, object owner, object sims, object values=None): + def __init__(self, object owner, object sim, object values=None): super().__init__(owner, values) - self.sims = np.array(sims) - + self.sim = sim self.block_size = owner.dtype_size cpdef get_output(self): @@ -75,11 +74,12 @@ cdef class MaskedDetectorWorkspace(DetectorWorkspace): `mode_indices` will simply be an array from `[0, Nhoms]` where `Nhoms` is the total number of modes in the simulation. """ - def __init__(self, owner, sims, values=None): - super().__init__(owner, sims, values) + def __init__(self, owner, CarrierSignalMatrixSimulation sim, values=None): + super().__init__(owner, sim, values) self.mode_indices = NULL - self.num_HOMs = 0 + self.num_HOMs = sim.model_data.num_HOMs + self.homs_view = sim.model_data.homs_view self.has_mask = False def __dealloc__(self): @@ -90,7 +90,7 @@ cdef class MaskedDetectorWorkspace(DetectorWorkspace): @cython.boundscheck(False) @cython.wraparound(False) @cython.initializedcheck(False) - cpdef setup_mask(self, BaseSimulation sim): + cpdef setup_mask(self): cdef: int[:, ::1] mask Py_ssize_t N_mask @@ -101,25 +101,24 @@ cdef class MaskedDetectorWorkspace(DetectorWorkspace): self.has_mask = self.owner.has_mask if not self.has_mask: # no mask so just copy across all mode indices - self.num_HOMs = sim.nhoms self.mode_indices = calloc(self.num_HOMs, sizeof(Py_ssize_t)) - for i in range(sim.nhoms): + for i in range(self.num_HOMs): self.mode_indices[i] = i else: mask = self.owner.mask N_mask = mask.shape[0] - if N_mask >= sim.nhoms: + if N_mask >= self.num_HOMs: raise RuntimeError( f"Error in detector {self.owner.name}:\n" f" Length of mask array greater than or equal to number of modes" ) - self.num_HOMs = sim.nhoms - N_mask + self.num_HOMs = self.num_HOMs - N_mask self.mode_indices = calloc(self.num_HOMs, sizeof(Py_ssize_t)) - for i in range(sim.nhoms): - n = sim.homs_view[i][0] - m = sim.homs_view[i][1] + for i in range(self.num_HOMs): + n = self.homs_view[i][0] + m = self.homs_view[i][1] if not in_mask(n, m, mask): if count >= self.num_HOMs: diff --git a/src/finesse/frequency.pxd b/src/finesse/frequency.pxd index 9eecb8733a2e4ddbcefcbbfb9c6483a8d5f497be..d03d3e7b60d20e754b1b8361f52a1a81ecd28689 100644 --- a/src/finesse/frequency.pxd +++ b/src/finesse/frequency.pxd @@ -1,5 +1,3 @@ -from finesse.simulations cimport BaseSimulation - cdef class Frequency: cdef: str __name @@ -9,5 +7,35 @@ cdef class Frequency: int __index object __symbol object __lambdified - BaseSimulation __sim double __start_value + + +"""This struct is used to contain various properties of a frequency bin in +a simulation in a fast C struct. The python side should update this struct +and pass it to C functions, allowing GIL to be released, etc. +""" +cdef struct frequency_info: + double f # Current frequency bin value [Hz] + double *f_car # If a sideband, the current carrier frequency bin value [Hz] + Py_ssize_t index # Index of this frequency bin + # Optical carrier field members + Py_ssize_t audio_lower_index # index of lower sideband if signals being simulated + Py_ssize_t audio_upper_index # index of upper sideband if signals being simulated + # Optical signal field members + Py_ssize_t audio_order # audio order, either -1 or +1, 0 means a carrier + Py_ssize_t audio_carrier_index # index of carrier frequency bin + + +ctypedef frequency_info frequency_info_t + + +cdef class FrequencyContainer: + cdef readonly: + Py_ssize_t size + tuple frequencies + cdef: + frequency_info_t* frequency_info + frequency_info_t* carrier_frequency_info + + cdef update_frequency_info(self) + cdef initialise_frequency_info(self) \ No newline at end of file diff --git a/src/finesse/frequency.pyx b/src/finesse/frequency.pyx index f2549abc4d8961ae2c8fcb30361e13002a00a4b1..f44da0810555a73490e32ca17f732ccfdba54f68 100644 --- a/src/finesse/frequency.pyx +++ b/src/finesse/frequency.pyx @@ -8,8 +8,70 @@ from finesse.element import ModelElement from finesse.parameter cimport Rebuild from finesse.parameter import model_parameter from finesse.symbols import Symbol -from finesse.simulations cimport BaseSimulation +from libc.stdlib cimport free, malloc + +cdef class FrequencyContainer: + """Contains an array of frequency objects and their associated + faster access C struct information. + """ + def __cinit__(self, *args, **kwargs): + self.size = 0 + self.frequency_info = NULL + self.carrier_frequency_info = NULL + + def __init__(self, frequencies, FrequencyContainer carrier_cnt=None): + self.size = len(frequencies) + self.frequencies = tuple(frequencies) + self.frequency_info = malloc(self.size * sizeof(frequency_info_t)) + if not self.frequency_info: + raise MemoryError() + if carrier_cnt: + if not carrier_cnt.frequency_info: + raise MemoryError() + self.carrier_frequency_info = carrier_cnt.frequency_info + else: + self.carrier_frequency_info = NULL + + def __dealloc__(self): + if self.size: + free(self.frequency_info) + + cdef initialise_frequency_info(self): + cdef Py_ssize_t i, j, cidx + cdef FrequencyContainer fcnt + + for i in range(self.size): + self.frequency_info[i].f = self.frequencies[i].f + self.frequency_info[i].index = self.frequencies[i].index + self.frequency_info[i].audio_order = self.frequencies[i].audio_order + + if self.frequency_info[i].audio_order: + assert(self.carrier_frequency_info) + cidx = self.frequencies[i].audio_carrier_index + self.frequency_info[i].audio_carrier_index = cidx + self.frequency_info[i].f_car = &self.carrier_frequency_info[cidx].f + # Update carrier info so it knows about this sideband + if self.frequency_info[i].audio_order == 1: + self.carrier_frequency_info[cidx].audio_upper_index = self.frequencies[i].index + elif self.frequency_info[i].audio_order == -1: + self.carrier_frequency_info[cidx].audio_lower_index = self.frequencies[i].index + else: + raise Exception("Unexpected") + + # if self.is_audio: + # for i in range(len(self.unique_fcnt)): + # fcnt = self.unique_fcnt[i] + # for j in range(fcnt.size): + # fcnt.frequency_info[j].f = fcnt.frequencies[j].f + # fcnt.frequency_info[j].index = fcnt.frequencies[j].index + + cdef update_frequency_info(self): + cdef Py_ssize_t i, j, cidx + cdef FrequencyContainer fcnt + + for i in range(self.size): + self.frequency_info[i].f = self.frequencies[i].f def generate_frequency_list(model): """ @@ -146,7 +208,7 @@ cdef class Frequency: self.__symbol = symbol self.__index = index self.__is_audio = audio - self.__sim = sim + #self.__sim = sim self.__symbol_changing = symbol.is_changing self.__start_value = self.__symbol.eval() self.__lambdified = eval(f"lambda: {symbol}", sim.model.elements) @@ -178,9 +240,9 @@ cdef class Frequency: "Frequency objects cannot be deepcopied as they are associated with Simulations" ) - @property - def simulation(self): - return self.__sim + #@property + #def simulation(self): + # return self.__sim @property def symbol(self): diff --git a/src/finesse/model.py b/src/finesse/model.py index c5897627f8db41634c5ec78e8e485ecca60244cb..18e332f598b4d2f88e01819d4ab7157d899201aa 100644 --- a/src/finesse/model.py +++ b/src/finesse/model.py @@ -47,7 +47,6 @@ from finesse.gaussian import BeamParam from finesse.locks import Lock from finesse.paths import OpticalPath from finesse.tree import TreeNode -from finesse.simulations import KLUSimulation, AccessSimulation from finesse.solutions import BaseSolution, BeamTraceSolution import finesse.tracing.ctracer as ctracer import finesse.tracing.tools as tracetools @@ -124,9 +123,6 @@ class Model: "store": True, } self.__sim_trace_config = self.__default_sim_trace_config.copy() - - self.__simulations = [] # list of simulations this model is associated with - self.__analysis = None # the root action self.yaxis = None @@ -252,12 +248,6 @@ class Model: # key errors on the memo as the new nodes haven't been created yet cdict = {"_Model__cavities": self.__dict__["_Model__cavities"]} - if self.is_built: - sdict["_Model__carrier"] = None - sdict["_Model__signal"] = None - sdict["_Model__simulations"] = [] - sdict["_detector_workspaces"] = None - new.__dict__.update(deepcopy(sdict, memo)) new.__dict__.update(deepcopy(cdict, memo)) @@ -1941,9 +1931,6 @@ class Model: plot_nx_graph(self.get_network(network_type), **kwargs) - def _register_simulation(self, sim): - self.__simulations.append(weakref.ref(sim)) - def __toggle_param_locks(self, state): """ Togglin @@ -1988,16 +1975,6 @@ class Model: if not self.is_built: raise Exception("Model has not been built") - # determine if all simulations have finished before we unbuild - # the model - for sim in self.__simulations: - if sim().is_matrix_sim: - if sim() is not None and sim().active(): - raise Exception(f"Simulation object {hex(id(sim))} is still active") - - self.__simulations.clear() - del self.__carrier - del self.__signal # To unfreeze a graph you have to rebuild it self.__network = nx.DiGraph(self.__network) self.__toggle_param_locks(False) @@ -2026,9 +2003,7 @@ class Model: def _build( self, - frequencies=None, - carrier_sim_type=KLUSimulation, - signal_sim_type=KLUSimulation, + frequencies=None ): """ NOTE: Consider using :function:`finesse.model.Model.built()` @@ -2057,15 +2032,11 @@ class Model: frequencies : list of :class:`.Symbol` Custom list of carrier frequencies can be specfied to run the simulations with. - carrier_sim_type, signal_sim_type : object of instance :class:`.BaseSimulation` - The type of simulations that should be built using this model. - Returns ------- - tuple(Carrier, Signal) : :class:`.carrier_sim_type`, :class:`.signal_sim_type` - Two Simulation objects can be returned. The signal simulation - is only returned if the Model is modelling signal or noise - features. + sim : object + A single simulation object is returned that should be used to perform + simulations with this model. Raises ------ @@ -2079,105 +2050,39 @@ class Model: nx.freeze(self.network) self.__is_built = True self.__toggle_param_locks(True) - - self._unfreeze() + + + # TODO ddb I like this AccesSimulation idea but I think there's + # some more refactoring to do with the simulations. Maybe + # BaseSimulation > TraceSimulation > CarrierSignalMatrixSimulation needs_matrix = ( any(d.needs_fields for d in self.detectors) or self.fsig.f.value is not None ) - if not needs_matrix: - self.__carrier = AccessSimulation(self, "carrier", frequencies=frequencies) - else: - self.__carrier = carrier_sim_type(self, "carrier", frequencies=frequencies) - self.__signal = None - self._freeze() + # so far the only simulation we can build is a carrier+signal matrix type + # of simulation. Eventually here we need some logic to decide what to run + # and return the relevant simulation object. + from finesse.simulations.basematrix import CarrierSignalMatrixSimulation + return CarrierSignalMatrixSimulation(self, "sim", needs_matrix=needs_matrix) - if self.fsig.f.value is not None: - self.__signal = signal_sim_type( - self, "signal", frequencies=frequencies, is_audio=True - ) - self.__signal.DC = self.__carrier - sims = (self.__carrier, self.__signal) - else: - sims = (self.__carrier,) - - return sims + @contextmanager def built(self): if self.is_built: raise Exception("Model has already been built") - sims = self._build() + sim = self._build() try: - for sim in sims: - sim.__enter__() - - # TODO ddb - Temporary. I don't like this, but I don't know where to store - # workspaces that rely on multiple simulations. Need some higher level container - # that holds the simulations and outputs that rely on all of them - self._unfreeze() - self._detector_workspaces = [] - self._freeze() - - # Once the simulations are started we can tell all the detectors to - # prepare themselves - for det in self.detectors: - self._detector_workspaces.append(det._get_workspace(sims)) - + sim.__enter__() # Yield and let the caller do something with the model - yield sims + yield sim finally: - for sim in sims: - sim.__exit__(None, None, None) - if hasattr(self, "_detector_workspaces"): - del self._detector_workspaces + sim.__exit__(None, None, None) self.unbuild() - @property - def carrier_simulation(self): - """ - When the model has been built this property returns the carrier simulation - - Returns - ------- - sim : :class:`.Simulation` - - Raises - ------ - If the carrier simulation isn't available an AttributeError is raised - """ - if self.is_built: - try: - return self.__carrier - except AttributeError: - raise AttributeError("Model does not contain any carrier simulation") - else: - raise Exception("Model has not been built") - - @property - def signal_simulation(self): - """ - When the model has been built this property returns the signal simulation - - Returns - ------- - sim : :class:`.Simulation` - - Raises - ------ - If the signal simulation isn't available an AttributeError is raised - """ - if self.is_built: - try: - return self.__signal - except AttributeError: - raise AttributeError("Model does not contain any signal simulation") - else: - raise Exception("Model has not been built") - def __node_exists_check(self, node): if not self.network.has_node(node.full_name): raise NodeException("Node {} is not in the model".format(node), node) diff --git a/src/finesse/plotting/plot.py b/src/finesse/plotting/plot.py index 2aab853f4d517750d5f44e84cf07aeac4517c309..d2c177fb60743501d44d054945adeffe77ff7918 100644 --- a/src/finesse/plotting/plot.py +++ b/src/finesse/plotting/plot.py @@ -153,9 +153,6 @@ class Plotter: def __init__(self, solution): self.out = solution - - self._model = self.out._model - # figure size scaling and layout self.scale = 1 self.tight_layout = True @@ -168,7 +165,8 @@ class Plotter: def __get_detectors(self, detectors=None): if detectors is None: - detectors = self._model.detectors + detectors = self.out.detectors + assert(detectors is not None) elif isinstance(detectors, type) or ( hasattr(detectors, "__getitem__") @@ -180,7 +178,7 @@ class Plotter: det_types = [detectors] detectors = list( - filter(lambda x: type(x) in det_types, self._model.detectors) + filter(lambda x: type(x) in det_types, self.out.detectors) ) elif isinstance(detectors, str) or isinstance(detectors, fd.Detector): @@ -197,7 +195,11 @@ class Plotter: for i, detector in enumerate(detectors): if isinstance(detector, str): - element = self._model.elements.get(detector) + try: + b = filter(lambda x: x.name == detector, self.out.detectors) + element = tuple(b)[0] + except IndexError: + element = None msg = "" if element is None: diff --git a/src/finesse/simulations/KLU.pyx b/src/finesse/simulations/KLU.pyx index ffd880b99aba4a67140154a25e2dc3ba1e1cde95..e697c9fe49effe2535f5adb7efb923822d5e0948 100644 --- a/src/finesse/simulations/KLU.pyx +++ b/src/finesse/simulations/KLU.pyx @@ -12,8 +12,7 @@ import gc from finesse.config import FinesseConfig from finesse.cmatrix import KLUMatrix from finesse.cmatrix cimport KLUMatrix -from finesse.simulations.basematrix cimport BaseMatrixSimulation -from finesse.simulations.basematrix import BaseMatrixSimulation +from finesse.simulations.basematrix cimport MatrixSystemSolver LOGGER = logging.getLogger(__name__) @@ -37,13 +36,8 @@ class KLUStatsDetector(Detector): if stat.lower() not in ('rcond', 'growth', 'condest'): raise Exception("Matrix setting must be either 'rcond', 'growth', or 'condest'") - def _get_workspace(self, sims): - ws = KLUStatWorkspace(self, sims) - - for sim in sims: - if self.matrix == sim.name: - ws.sim = sim - break + def _get_workspace(self, sim): + ws = KLUStatWorkspace(self, sim) ws.fn = getattr(sim, self.stat) ws.set_output_fn(self.fill) return ws @@ -51,10 +45,14 @@ class KLUStatsDetector(Detector): def fill(self, ws): return ws.fn() -cdef class KLUSimulation(BaseMatrixSimulation): +cdef class KLUMatrixSystemSolver(MatrixSystemSolver): cdef double prev_rcond cdef double rcond_diff_lim + def __init__(self, *args, **kwargs): + super().__init__(KLUMatrix, *args, **kwargs) + self.rcond_diff_lim = float(FinesseConfig()['klu']['rcond_diff_lim']) + cdef solve(self): cdef double rcond = (self._M).rcond() cdef double diff = abs((self.prev_rcond - rcond)/self.prev_rcond) @@ -79,62 +77,44 @@ cdef class KLUSimulation(BaseMatrixSimulation): cpdef double rgrowth(self): return (self._M).rgrowth() - def __enter__(self): - """ - When entered the Simulation object will create the matrix to be used in - the simulation. - - This is where the matrix gets allocated and constructed. It will expect - that the model structure does not change after this until it has been - exited. - """ - # Set the matrix method we'll be using to run this simulation - self._M = KLUMatrix(self.name) - # Initialising the simulation expects there to be a self._M class that handles the - # matrix build/memory/etc. This must be set before initialising. - - self._initialise() - + cdef construct(self): self._M.construct() - # Point output to RHS view which klu replaces with solution after solving + # Point output of matrix to RHS view which klu replaces with solution after solving self.out_view = self._M.rhs_view + cdef initial_run(self): self.initial_fill() self._M.factor() self.prev_rcond = self._M.rcond() - self.rcond_diff_lim = float(FinesseConfig()['klu']['rcond_diff_lim']) self.fill_rhs() self.solve() - # TODO ddb: this should be chcking audio and that we have quantum calculations going on - # TODO ddb: create sparse noise source matrix - # TODO ddb: noise calculations probably more generic than just quantum - # if self.is_audio: - # self._Mq = np.zeros(self._M.num_equations) - - return self - - def __exit__(self, type_, value, traceback): - self._unbuild() - self._clear_workspace() - self._submatrices = None - - # Code below can be used in debug mode to determine if anyone is keeping any - # references to this matrix object, meaning it's memory can't be freed. - # This takes ~20ms to do so makes a difference for quick models. Maybe we need - # a debug mode - - #_ref = self._M - self._M = None - - # refs = gc.get_referrers(_ref) - # Nref = len(refs) - # if Nref > 0: - # LOGGER.warn( - # f"Something other than the Simulation object (N={Nref}) is keeping" - # f" a reference to the matrix object {repr(self._M)}." - # " Could lead to excessive memory usage if not released." - # ) - # for _ in refs: - # LOGGER.warn(f" - {repr(_)}") - #del _ref + # def __enter__(self): + # """ + # When entered the Simulation object will create the matrix to be used in + # the simulation. + + # This is where the matrix gets allocated and constructed. It will expect + # that the model structure does not change after this until it has been + # exited. + # """ + # # Set the matrix method we'll be using to run this simulation + # self._M = KLUMatrix(self.name) + # # Initialising the simulation expects there to be a self._M class that handles the + # # matrix build/memory/etc. This must be set before initialising. + + # self._initialise() + + # self._M.construct() + # # Point output to RHS view which klu replaces with solution after solving + # self.out_view = self._M.rhs_view + + # self.initial_fill() + # self._M.factor() + # self.prev_rcond = self._M.rcond() + # self.rcond_diff_lim = float(FinesseConfig()['klu']['rcond_diff_lim']) + # self.fill_rhs() + # self.solve() + + # return self + diff --git a/src/finesse/simulations/__init__.pxd b/src/finesse/simulations/__init__.pxd index 48bf26eb62f47462582d8af4eb94aaf68c36e231..249b2f98100958df5303d5c3151128cb9f21f958 100644 --- a/src/finesse/simulations/__init__.pxd +++ b/src/finesse/simulations/__init__.pxd @@ -1 +1 @@ -from finesse.simulations.base cimport BaseSimulation +from finesse.simulations.basematrix cimport CarrierSignalMatrixSimulation diff --git a/src/finesse/simulations/__init__.py b/src/finesse/simulations/__init__.py index ea436ddd20e6b6274c9630fe14027db6150a460a..ab4b9f7bceda8d1786a573425c0cbd5af34c39bd 100644 --- a/src/finesse/simulations/__init__.py +++ b/src/finesse/simulations/__init__.py @@ -6,20 +6,20 @@ Listed below are all the sub-modules of the ``simulations`` module with a brief description of the contents of each. """ -from finesse.simulations.access import AccessSimulation -from finesse.simulations.base import BaseSimulation -from finesse.simulations.KLU import KLUSimulation -from finesse.simulations.digraph import DigraphSimulation, DigraphSimulationBase -from finesse.simulations.debug import DebugSimulation -from finesse.simulations.dense import DenseSimulation +# from finesse.simulations.access import AccessSimulation +# from finesse.simulations.base import BaseSimulation +from finesse.simulations.basematrix import CarrierSignalMatrixSimulation +# from finesse.simulations.digraph import DigraphSimulation, DigraphSimulationBase +# from finesse.simulations.debug import DebugSimulation +# from finesse.simulations.dense import DenseSimulation -__all__ = ( - "AccessSimulation", - "BaseSimulation", - "KLUSimulation", - "DigraphSimulation", - "DigraphSimulationBase", - "DebugSimulation", - "DenseSimulation", -) +# __all__ = ( +# "AccessSimulation", +# "BaseSimulation", +# "CarrierSignalMatrixSimulation", +# "DigraphSimulation", +# "DigraphSimulationBase", +# "DebugSimulation", +# "DenseSimulation", +# ) diff --git a/src/finesse/simulations/access.py b/src/finesse/simulations/access.py index db7896ed820da02298745d0e682c4775145a53fa..9142ee6fdcc7ec8d89142960ef8524162dd154b4 100644 --- a/src/finesse/simulations/access.py +++ b/src/finesse/simulations/access.py @@ -1,32 +1,14 @@ -import logging +# import logging -from finesse.simulations.base import BaseSimulation +# from finesse.simulations.basematrix import CarrierSignalMatrixSimulation -LOGGER = logging.getLogger(__name__) +# LOGGER = logging.getLogger(__name__) +# # TODO (sjr) Don't really know what to call this +# class AccessSimulation(CarrierSignalMatrixSimulation): +# """Simulation for probing properties of the interferometer +# configuration which require no matrix solving. +# """ -# TODO (sjr) Don't really know what to call this -class AccessSimulation(BaseSimulation): - """Simulation for probing properties of the interferometer - configuration which require no matrix solving. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs, needs_matrix=False) - - def __enter__(self): - self._initialise() - - return self - - def __exit__(self, type_, value, traceback): - self._unbuild() - - def run(self, *args, **kwargs): - for ews in self.variable_workspaces: - ews.update_parameter_values() - - for ws in self.workspaces: - ws.update_parameter_values() - - return self.modal_update() +# def __init__(self, *args, **kwargs): +# super().__init__(*args, **kwargs, needs_matrix=False) diff --git a/src/finesse/simulations/base.pxd b/src/finesse/simulations/base.pxd index 695384ba5ad2a7f95002ec3155949d046ec6288d..2200462a5560cf06c4e585670745ed84231bcdd7 100644 --- a/src/finesse/simulations/base.pxd +++ b/src/finesse/simulations/base.pxd @@ -1,28 +1,9 @@ cimport numpy as np from finesse.cymath cimport complex_t -from finesse.components.workspace cimport ConnectorWorkspace from finesse.tracing.ctracer cimport TraceForest, TraceTree - +from finesse.frequency cimport frequency_info_t, FrequencyContainer from cpython.ref cimport PyObject -cdef struct frequency_info: - double f - double f_car - Py_ssize_t index - Py_ssize_t audio_order - Py_ssize_t audio_carrier_index - Py_ssize_t audio_lower_index - Py_ssize_t audio_upper_index - -ctypedef frequency_info frequency_info_t - - -cdef class FrequencyContainer: - cdef readonly: - Py_ssize_t size - tuple frequencies - frequency_info_t[::1] frequency_info - cdef struct NodeInfoEntry: Py_ssize_t index @@ -50,152 +31,3 @@ cdef class ModelData: double x_scale bint zero_K00 # should phase of k0000 coefficients be zeroed bint zero_tem00_gouy # should Gouy phase of TEM00 be zeroed - -cdef class BaseSimulation: - """ - Base Simulation class for executing a model. - - The `Simulation` class uses the :class:`.Model` to build - and fill the sparse matrix and is used as a reference for - detectors to compute outputs to return to the user. - - .. note:: - Instances of this class **cannot** be copied, they are generated - from and operate on a :class:`.Model` object in a particular - built state. - - .. note:: - The module :mod:`pickle` **cannot** be used on instances of this class. - """ - cdef: - readonly unicode name - readonly object model - readonly bint is_matrix_sim - - readonly bint is_audio - readonly bint is_modal - readonly bint any_frequencies_changing - - # Optical frequencies - # TODO ddb - refactor to optical_frequencies at some point - np.ndarray _input_frequencies - readonly frequency_info_t[:] frequency_info - readonly tuple frequencies - readonly int num_frequencies - readonly dict frequency_map - readonly tuple unique_fcnt # Unique frequency containers for mech/elec - - readonly dict mechanical_frequencies - readonly dict electrical_frequencies - - # mode indices only ever need to be indexed or iterated over - # so make it a readonly memoryview on the models' homs array - readonly int[:, ::1] homs_view - readonly int nhoms - - # the beam trace results - NodeBeamParam* trace - # forest of changing trace trees - readonly TraceForest trace_forest - # flag indicating whether to retrace (true means - # determined automatically by TraceForest code) - bint retrace - - tuple _components - tuple _detectors - - public dict _submatrices - type _submatrix_type - - Py_ssize_t num_nodes - readonly dict nodes - readonly dict nodes_idx - NodeInfoEntry* _c_node_info - - #public np.ndarray out - const complex_t[::1] out_view - - public list _edge_owners - list _fill_rhs - list _set_gouy_phases - - public list workspaces - public list variable_workspaces - list to_initial_fill - list to_refill - PyObject** ptr_to_refill - int num_to_refill - - list to_rhs_fill - list to_rhs_refill - PyObject** ptr_to_rhs_refill - int num_to_rhs_refill - - public dict cavity_workspaces - - # List of workspaces for components which scatter modes - list to_scatter_matrix_compute - - readonly ModelData model_data - public BaseSimulation DC - object __weakref__ - - cdef _initialise_submatrices(self) - - cpdef get_node_info(self, name) - - cpdef Py_ssize_t findex(self, object node, Py_ssize_t freq) - cdef Py_ssize_t findex_fast(self, Py_ssize_t node_id, Py_ssize_t freq) nogil - - cpdef Py_ssize_t field(self, object node, Py_ssize_t freq=?, Py_ssize_t hom=?) - cdef Py_ssize_t field_fast(self, Py_ssize_t node_id, Py_ssize_t freq=?, Py_ssize_t hom=?) nogil - cdef inline Py_ssize_t field_fast_2( - self, - Py_ssize_t node_rhs_idx, - Py_ssize_t num_hom, - Py_ssize_t freq, - Py_ssize_t hom) nogil - - cpdef complex_t get_DC_out(self, object node, Py_ssize_t freq=?, Py_ssize_t hom=?) - cdef complex_t get_DC_out_fast(self, Py_ssize_t node_id, Py_ssize_t freq=?, Py_ssize_t hom=?) nogil - - cpdef Py_ssize_t node_id(self, object node) - - cpdef int modal_update(self) except -1 - - cpdef component_edge_get(self, object comp, unicode edgestr, object f1, object f2) - - cdef solve(self) - cdef _clear_rhs(self) - cdef initial_fill(self) - cdef refill(self) - cdef fill_rhs(self) - cdef fill_qnoise_rhs(self) - - cdef void update_all_parameter_values(self) - - cdef void update_cavities(self) - cdef void compute_knm_matrices(self) - cdef int set_gouy_phases(self) except -1 - - cpdef int run(self, bint clear_fill_rhs) except -1 - - cpdef _initialise_frequencies(self) - - cdef void _determine_changing_beam_params(self) - # TODO (sjr) Would prefer to have the below methods in finesse.tracing.ctracer - # (see commented out blocks there) but circular import nonsense - # prevents that right now - cdef void _setup_trace_forest(self) - cdef void _setup_single_trace_tree(self, TraceTree tree) - cdef void _propagate_trace(self, TraceTree tree) - cdef bint trace_beam(self) - - cdef _fill_node_info(self) - cpdef _initialise_nodes(self) - cpdef _initialise_elements(self) - cdef _init_frequency_info(self) - cdef _update_frequency_info(self) - cpdef _clear_workspace(self) - cdef void set_source_fast(self, Py_ssize_t node_id, Py_ssize_t freq_idx, Py_ssize_t hom_idx, complex_t value) - cdef void set_source_fast_2(self, Py_ssize_t rhs_idx, complex_t value) diff --git a/src/finesse/simulations/base.pyx b/src/finesse/simulations/base.pyx index 9dfa68fb84a1e798ecdade79159c345f063e997a..7e2d0d1a6539e0cdff0cf2b82c9e1e3f2d016956 100644 --- a/src/finesse/simulations/base.pyx +++ b/src/finesse/simulations/base.pyx @@ -1,6 +1,7 @@ # cython: profile=True import contextlib + import logging cimport cython @@ -16,19 +17,11 @@ from finesse.cymath.math cimport float_eq from finesse.cymath.gaussbeam cimport transform_q, inv_transform_q from finesse.element cimport ElementWorkspace from finesse.frequency cimport Frequency -from finesse.components.workspace cimport ConnectorCallbacks +from finesse.components.workspace cimport ConnectorCallbacks, ConnectorWorkspace from finesse.components.node import NodeType from finesse.components.modal.workspace cimport KnmConnectorWorkspace from finesse.components.modal.cavity cimport CavityWorkspace -from finesse.components.modal.space cimport ( - SpaceWorkspace, - set_gouy_phase -) -from finesse.components.modal.laser cimport ( - LaserWorkspace, - set_tem_gouy_phases -) from cpython.ref cimport PyObject from libc.stdio cimport printf @@ -39,6 +32,7 @@ cdef extern from "stdlib.h": void* malloc(size_t size) void* calloc(size_t N, size_t size) + cdef extern from "constants.h": long double PI double C_LIGHT @@ -48,1222 +42,5 @@ cdef extern from "constants.h": LOGGER = logging.getLogger(__name__) -cdef class FrequencyContainer: - def __init__(self, frequencies): - self.size = len(frequencies) - self.frequencies = frequencies - cdef frequency_info_t* ptr = malloc(self.size * sizeof(frequency_info_t)) - if not ptr: - raise MemoryError() - self.frequency_info = ptr - - def __dealloc__(self): - if self.size: - free(&(self.frequency_info[0])) - - cdef class ModelData: - pass - - -cdef class BaseSimulation: - """ - Base Simulation class for executing a model. - - The `Simulation` class uses the :class:`.Model` to build - and fill the sparse matrix and is used as a reference for - detectors to compute outputs to return to the user. - - .. note:: - Instances of this class **cannot** be copied, they are generated - from and operate on a :class:`.Model` object in a particular - built state. - - .. note:: - The module :mod:`pickle` **cannot** be used on instances of this class. - """ - - def __init__(self, model, unicode name, frequencies=None, bint is_audio=False, bint needs_matrix=True): - self.model = model - self.name = name - self.is_matrix_sim = needs_matrix - self.is_audio = is_audio - self.is_modal = model.is_modal - self._input_frequencies = frequencies - self.frequency_map = None - self.nhoms = 1 - self.any_frequencies_changing = True - self.frequency_info = None - self._c_node_info = NULL - self.trace = NULL - self.ptr_to_refill = NULL - self.DC = None - self.mechanical_frequencies = {} - self.electrical_frequencies = {} - - # register that this simulation is dependant on this model - model._register_simulation(self) - - def __dealloc__(self): - if self._c_node_info != NULL: - free(self._c_node_info) - if self.trace != NULL and not self.is_audio: - free(self.trace) - if self.ptr_to_refill != NULL: - free(self.ptr_to_refill) - if self.frequency_info is not None and len(self.frequency_info): - free(&(self.frequency_info[0])) - - cdef _initialise_submatrices(self): - raise NotImplementedError() - - cpdef Py_ssize_t findex(self, object node, Py_ssize_t freq): - """ - Returns simulation unique index for a given frequency at this node. - Used to refer to submatrices of HOMs in the interferometer matrix. - - Parameters - ---------- - node : :class:`.Node` - Node object to get the index of. - freq : int - Frequency index. - - Returns - ------- - index : int - Index of the `node` for a given frequency. - """ - return self.findex_fast(self.node_id(node), freq) - - cdef Py_ssize_t findex_fast(self, Py_ssize_t node_id, Py_ssize_t freq) nogil: - cdef: - NodeInfoEntry ni = self._c_node_info[node_id] - Py_ssize_t freq_idx = ni.freq_index - - return freq_idx + freq - - cpdef Py_ssize_t field(self, object node, Py_ssize_t freq=0, Py_ssize_t hom=0): - """ - Returns simulation unique index of a field at a particular frequency - index at this node. - - Parameters - ---------- - node : :class:`.Node` - Node object to get the index of. - freq : int - Frequency index. - hom : int, optional - Higher Order Mode index, defaults to zero. - """ - return self.field_fast(self.node_id(node), freq, hom) - - cdef Py_ssize_t field_fast(self, Py_ssize_t node_id, Py_ssize_t freq=0, Py_ssize_t hom=0) nogil: - cdef: - NodeInfoEntry ni = self._c_node_info[node_id] - Py_ssize_t Nh = ni.nhoms - Py_ssize_t rhs_idx = ni.rhs_index - return rhs_idx + freq * Nh + hom - - cdef inline Py_ssize_t field_fast_2( - self, - Py_ssize_t node_rhs_idx, - Py_ssize_t num_hom, - Py_ssize_t freq, - Py_ssize_t hom) nogil: - """Inlined function to return field index fast.""" - return node_rhs_idx + freq * num_hom + hom - - cpdef complex_t get_DC_out(self, object node, Py_ssize_t freq=0, Py_ssize_t hom=0): - return self.get_DC_out_fast(self.node_id(node), freq, hom) - - @cython.boundscheck(False) - @cython.wraparound(False) - @cython.initializedcheck(False) - cdef complex_t get_DC_out_fast(self, Py_ssize_t node_id, Py_ssize_t freq=0, Py_ssize_t hom=0) nogil: - cdef Py_ssize_t field_idx = self.field_fast(node_id, freq, hom) - - if self.out_view is None: - return COMPLEX_0 - - return self.out_view[field_idx] - - cpdef Py_ssize_t node_id(self, object node): - if type(node) is str: - return self.nodes_idx[node] - else: - return self.nodes_idx[node.full_name] - - cpdef int modal_update(self) except -1: - """Updates HOM related dependencies / properties of the model. - - These updates are as follows: - - * Execute a beam trace on the changing trace trees - * Computes the changing scattering matrices - * Calculates the Gouy phase of Spaces and Laser power coefficients - - Returns - ------- - exit_code : int - A value of ``0`` if successful, a value of ``1`` if an error - was raised during the beam tracing. - """ - cdef bint beam_trace_valid = True - - if self.is_modal and not self.is_audio: - self.update_cavities() - - if self.retrace: - beam_trace_valid = self.trace_beam() - if not beam_trace_valid: - return 1 - - # compute the changing scattering matrices - if self.is_matrix_sim: - self.compute_knm_matrices() - - # update the changing Gouy phases at spaces - # and TEM Gouy phases at lasers - return self.set_gouy_phases() - - return 0 - - @property - def num_equations(self): - r""" - The number of equations the configuration consists of, given by, - - .. math:: - n_{\mathrm{eq}} = n_{\mathrm{nodes}} n_{\mathrm{freq}} n_{\mathrm{HOM}}, - - where :math:`n_{\mathrm{nodes}}` is the number of nodes in the network, - :math:`n_{\mathrm{freq}}` is the number of frequencies and - :math:`n_{\mathrm{HOM}}` is the number of higher-order modes. - - :getter: Returns the numbers of equations describing the system (read-only). - """ - return len(self.model.network.nodes) * len(self.frequencies) * self.homs_view.shape[0] - - @contextlib.contextmanager - def component_edge_fill(self, comp, edgestr, f1, f2, conjugate = False): - """ - Returns a matrix for the submatrix an element has requested - for different connections it needs. The key is:: - - (element, connection_name, ifreq, ofreq) - - This is a context manager, to be used like with sim.component_edge_fill(key) as mat:: - - mat[:] = computations - - Parameters - ---------- - element : finesse.component.Connector - The object reference that created the requests. - connection_name : str - String name given to the connection. - ifreq : finesse.Frequency - Incoming frequency. - ofreq : finesse.Frequency - Outgoing frequency. - - Returns - ------- - matrix - """ - #the two-stage nature of this will make some fill checks and hooks - #much more safe (and powerfull) - - #this will be helpful for on-demand filling and will also help improve - #make DC simulation of squeezers work (because post-fill transformations - #will be needed) - key = (comp, edgestr, f1.index, f2.index) - mat = self._submatrices[key] - yield mat - #check things, or transform things here - if conjugate: - mat[:].imag *= -1 - return - - @contextlib.contextmanager - def component_edge_fill3(self, owner_idx, conn_idx, f1_index, f2_index): - mat = self._submatrices[(owner_idx, conn_idx, f1_index, f2_index)] - yield mat - return - - cpdef component_edge_get(self, object comp, unicode edgestr, object f1, object f2): - """ - Returns a matrix for the submatrix an element has requested - for different connections it needs. The key is:: - - (element, connection_name, ifreq, ofreq) - - element - is the object reference that created the requests - connection_name - the string name given to the connection - ifreq - incoming frequency - ofreq - outgoing frequency - - this is a context manager, to be used like:: - - with sim.component_edge_fill(key) as mat: - mat[:] = computations - - Returns - ------- - matrix - """ - key = (comp, edgestr, f1.index, f2.index) - return self._submatrices[key] - - def iter_edges(self): - from finesse.components.node import NodeType - from finesse.frequency import FrequencyGenerator - - _done = {} - for owner in self._edge_owners: - if owner in _done: - continue - - couples_f = isinstance(owner, FrequencyGenerator) - - # For each connection this element wants... - for name in owner._registered_connections: - nio = tuple((owner.nodes[_] for _ in owner._registered_connections[name])) # convert weak ref (input, output) - # If we are a carrier matrix only compute optics, no AC - if not self.is_audio: - if (nio[0].type is not NodeType.OPTICAL or nio[1].type is not NodeType.OPTICAL): - continue - - # Loop over all the frequencies we can couple between and add - # submatrixes to the overall model - for i, ifreq in enumerate(self.frequencies): - for j, ofreq in enumerate(self.frequencies): - #for k in range(Nhom): - # For each input and output frequency check if our - # element wants to couple them at this - if couples_f and not owner._couples_frequency(self, name, ifreq, ofreq): - continue - elif not couples_f and ifreq != ofreq: - # If it doesn't couple frequencies and the - # frequencies are different then ignore - continue - - iodx_semantic = [] # submatrix indices - # Get simulation unique indices for submatrix - # position. How we get these depends on the type of - # the nodes involved - for freq, node in zip((ifreq, ofreq), nio): - if node.type is NodeType.OPTICAL: - iodx_semantic.append((node, freq)) - else: - # Mechanical and electrical don't have multiple - # freqs, so always use the zeroth frequency index - iodx_semantic.append((node, self.frequencies[0])) - - n_fr, n_to = iodx_semantic - yield (n_fr, n_to) - _done[owner] = True - - def get_changing_edges(self): - """ - Returns - ------- - set() of the node-obj, freq-obj pairs - (TODO should perhaps be node-name and freq-idx) - """ - edges_changing = set() - edges_phys, nodes = self.model.get_changing_edges_elements() - #TODO - for Mn_fr, Mn_to in self.iter_edges(): - n_fr, f_fr = Mn_fr - n_to, f_to = Mn_to - - if (n_fr.full_name, n_to.full_name) in edges_phys: - edges_changing.add((Mn_fr, Mn_to)) - return edges_changing - - cdef solve(self): - """ - Solves the current state of the simulation. `self.out` should be populated with the solution vector - once this has been called. - """ - raise NotImplementedError() - - cdef _clear_rhs(self): - raise NotImplementedError() - - cdef initial_fill(self): - cdef ConnectorWorkspace ws - - self._init_frequency_info() - - for ws in self.to_initial_fill: - ws.update_parameter_values() - if ws.fn_c is not None: - ws.fn_c.func(ws) - elif ws.fn_py is not None: - ws.fn_py(ws) - - cdef refill(self): - cdef int i - cdef ConnectorWorkspace ws - if self.any_frequencies_changing: - self._update_frequency_info() - - for i in range(self.num_to_refill): - ws = self.ptr_to_refill[i] - # TODO (sjr) Probably don't need this update call for now - # (see start of self.run method) - ws.update_parameter_values() - if ws.fn_c is not None: - ws.fn_c.func(ws) - elif ws.fn_py is not None: - ws.fn_py(ws) - - cdef fill_rhs(self): - cdef ConnectorWorkspace ws - for ws in self.to_rhs_fill: - ws.update_parameter_values() - if ws.fn_rhs_c is not None: - ws.fn_rhs_c.func(ws) - elif ws.fn_rhs_py is not None: - ws.fn_rhs_py(ws) - - cdef fill_qnoise_rhs(self): - raise NotImplementedError() - # from finesse.components.node import NodeType - - # if self.is_modal: - # Nhom = self.homs_view.shape[0] - # else: - # Nhom = 1 - - # for comp in self._components: - # # Fill in all open port vacuum contributions - # for port in comp.ports: - # if port.type != NodeType.OPTICAL or port.is_connected: - # continue - # for freq in self.frequencies: - # for hom in range(Nhom): - # idx = self.field(port.i, freq.index, hom) - # self._Mq[idx] = self.model._UNIT_VACUUM / 2 * (1 + freq.carrier.f / self.model.f0) - # # Then ask components for their noise contributions - # if hasattr(comp, "_fill_qnoise_rhs"): - # comp._fill_qnoise_rhs(self) - - cdef void update_all_parameter_values(self): - cdef: - ElementWorkspace ews - ConnectorWorkspace ws - - Py_ssize_t i - # TODO (sjr) Should probably cache these or move away from - # lists for best performance - Py_ssize_t Nvws = len(self.variable_workspaces) - Py_ssize_t Ncws = len(self.workspaces) - - for i in range(Nvws): - ews = self.variable_workspaces[i] - ews.update_parameter_values() - - for i in range(Ncws): - ws = self.workspaces[i] - ws.update_parameter_values() - - cdef void update_cavities(self): - cdef CavityWorkspace ws - for ws in self.cavity_workspaces.values(): - ws.update() - - cdef void compute_knm_matrices(self): - cdef KnmConnectorWorkspace ws - for ws in self.to_scatter_matrix_compute: - ws.update_changing_knm_workspaces() - ws.compute_scattering_matrices() - - cdef int set_gouy_phases(self) except -1: - cdef ConnectorWorkspace ws - for ws in self.workspaces: - if isinstance(ws, SpaceWorkspace): - if set_gouy_phase(ws) == -1: - return -1 - elif isinstance(ws, LaserWorkspace): - set_tem_gouy_phases(ws) - - return 0 - - cpdef int run(self, bint clear_fill_rhs) except -1: - """Executes the simulation for model in its current state. - - Takes the following steps to compute an output: - * Clears the RHS vector - * Fills the matrix - * Fills the RHS vector - * Solves - * Fills the detector output list - - Results are saved in `Simulation.out` property. - - Returns - ------- - integer - 0 if success - 1 if modal update failed - -1 if exception raised - """ - # NOTE (sjr) Just updating all parameter values on each call to run for - # now. This may not be the most optimal thing to do, but it - # avoids duplicating these parameter update calls in different - # places (e.g. refill, compute_knm_matrices, set_gouy_phases) and - # should be safe in that no parameters get accidentally missed at - # any data point. - self.update_all_parameter_values() - - # update HOM stuff - cdef int status - status = self.modal_update() - # immediately return if invalid beam trace region encountered - # no need to go ahead and fill or solve as they won't be used - if status: - return status - - # (re)-fill matrices, RHS vector, then solve the matrix - if clear_fill_rhs: - self._clear_rhs() - self.fill_rhs() - self.refill() - self.solve() - - # TODO ddb: if audio and quantum calcs needed - # if self.is_audio and False: - # self.fill_qnoise_rhs() - return 0 - - def get_frequency_object(self, frequency): - """Get the frequency object corresponding to the specified frequency. - - Parameters - ---------- - f : number or :class:`.Symbol` - Frequency to search for in this simulation. - - Returns - ------- - :class:`.Frequency` - The frequency object. - """ - from finesse.symbols import Symbol - - if isinstance(frequency, Symbol): - if frequency.is_changing: - # 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 self.frequencies: - if f.symbol == frequency: - return f - - f_value = float(frequency) - # otherwise do some value comparisons - for f in self.frequencies: - if float(f.f) == f_value: - return f - - return None - - def _initialise(self): - model_homs = self.model.homs - self.homs_view = self.model.homs - self._components = self.model.components - self._detectors = self.model.detectors - self._submatrices = {} - self.out_view = None - - self.model_data = ModelData() - self.model_data.EPSILON0_C = self.model._EPSILON0_C - self.model_data.lambda0 = self.model.lambda0 - self.model_data.f0 = C_LIGHT / self.model_data.lambda0 - self.model_data.k0 = 2.0 * PI / self.model_data.lambda0 - self.model_data.homs_view = model_homs - self.model_data.maxtem = np.max(model_homs[:, 0] + model_homs[:, 1]) - self.model_data.num_HOMs = len(model_homs) - self.model_data.x_scale = self.model._x_scale - if self.model.fsig.f.value is None: - self.model_data.fsig = 0 - else: - self.model_data.fsig = self.model.fsig.f.value - self.model_data.zero_K00 = self.model.phase_config.ZERO_K00 - self.model_data.zero_tem00_gouy = self.model.phase_config.ZERO_TEM00_GOUY - - network = self.model.network - - # Make sure optical nodes come first as signal simulation self.trace - # data is pointed to carrier sim trace, so need optical nodes to have - # same indices between both simulations (guaranteed if optical nodes - # are first) - self.nodes = {n: network.nodes[n]['weakref']() - for n in network.nodes - if 'optical' in network.nodes[n]} - if not self.is_audio: - LOGGER.info("Building DC simulation") - else: - LOGGER.info("Building AC simulation") - # For audio matrix we solve all types so add - # the other node types here now - self.nodes.update( - {n: network.nodes[n]['weakref']() - for n in network.nodes - if 'optical' not in network.nodes[n]} - ) - - # Faster look up for node indices - self.nodes_idx = {n.full_name : i for i, n in enumerate(self.nodes.values())} - - if self.is_modal: - self.nhoms = self.homs_view.shape[0] - else: - self.nhoms = 1 - - # NOTE (sjr) Need to ensure a beam trace has been performed before - # the next few calls as trace data is required for these - cdef TraceForest model_trace_forest = self.model._trace_forest - if self.is_modal and not self.is_audio: - # Make sure the model trace forest gets re-planted - # when building a new simulation - self.model._rebuild_trace_forest = True - LOGGER.info( - "Performing initial beam trace with configuration options: %s", - self.model.sim_trace_config, - ) - self.model.beam_trace(**self.model.sim_trace_config) - self.retrace = self.model.retrace - - if self.retrace and self.is_modal and not self.is_audio: - # Construct the forest of changing trace trees - need to do this - # here as the _get_workspace method of Connectors requires the - # simulation trace_forest field to be initialised when making - # the refill flags - self.trace_forest = model_trace_forest.make_changing_forest() - else: - # just make an empty TraceForest - self.trace_forest = TraceForest(self.model) - - self._initialise_nodes() - self._initialise_frequencies() - self._fill_node_info() - self._initialise_elements() - - self.update_all_parameter_values() - - if self.is_modal and not self.is_audio: - # compute all the initial: - # - scattering matrices - # - space Gouy phases - # - laser tem Gouy phases - self.compute_knm_matrices() - self.set_gouy_phases() - # ... then determine which beam parameters will be changing - # so that only the items from above which change get - # re-computed on subsequent calls to their respective functions - self._determine_changing_beam_params() - - if self.is_matrix_sim: - self._initialise_submatrices() - - cpdef _initialise_frequencies(self): - #------------------------------------------------------------------------------------------- - # Optical frequencies - #------------------------------------------------------------------------------------------- - from finesse.frequency import Frequency, generate_frequency_list - # If no frequencies were specified then generate a default set - if self._input_frequencies is None: - self._input_frequencies = generate_frequency_list(self.model) - - frequencies = [] - - LOGGER.info('Generating simulation with carrier frequencies %s', self._input_frequencies) - # Now loop over the requested frequencies for this simulation - # and generate the frequency bin objects - if not self.is_audio: - for i, f in enumerate(self._input_frequencies): - frequencies.append( - Frequency(str(f.eval(keep_changing_symbols=True)), self, f, index=i) - ) - else: - for i, f in enumerate(self._input_frequencies): - fp = f + self.model.fsig.f.ref - fm = f - self.model.fsig.f.ref - frequencies.append( - Frequency(str(fp.eval(keep_changing_symbols=True)), - self, fp, index=2*i, audio_order=1, - audio=True, audio_carrier_index=i) - ) - frequencies.append( - Frequency(str(fm.eval(keep_changing_symbols=True)), - self, fm, index=2*i+1, audio_order=-1, - audio=True, audio_carrier_index=i) - ) - self.frequencies = tuple(frequencies) - # We loop over frequencies a lot. Here we make a c-struct array - # that contains the important info needed for fast filling loops etc. - self.num_frequencies = len(frequencies) - cdef frequency_info_t* ptr = malloc(self.num_frequencies * sizeof(frequency_info_t )) - if not ptr: - raise MemoryError() - self.frequency_info = ptr - self.any_frequencies_changing = False - # Global flag for if any frequency is changing - for _f in self.frequencies: - if _f.symbol.is_changing: - self.any_frequencies_changing = True - break - - if self.is_audio: - # Audio matrix frequencies are more complicated as they can have multiple frequencies - # in mechanical and electrical, on a per-node basis... - fsig = FrequencyContainer( - (Frequency("fsig", self, self.model.fsig.f.ref, index=0), ) - ) - - for node in self.nodes.values(): - if node.type == NodeType.OPTICAL: - continue - - #----------------------------------------------------------------------------------- - # Mechanical frequencies - #----------------------------------------------------------------------------------- - # By default mechanical frequencies just have a single frequency at the Model.fsig.f - # However for more complicated systems we can have multiple frequencies. - elif node.type == NodeType.MECHANICAL: - fs = [] - freqs = node.frequencies - if len(freqs) == 1 and freqs[0] == self.model.fsig.f.ref: - # Most components will just use a single fsig so reuse same object - # for efficient filling later - self.mechanical_frequencies[node] = fsig - else: - for i, sym in enumerate(node.frequencies): - fs.append( - Frequency( - str(fm.eval(keep_changing_symbols=True)), - self, - sym, - index=i, - ) - ) - self.mechanical_frequencies[node] = FrequencyContainer(tuple(fs)) - - #----------------------------------------------------------------------------------- - # Electrical frequencies - #----------------------------------------------------------------------------------- - elif node.type == NodeType.ELECTRICAL: - self.electrical_frequencies[node] = fsig - else: - raise ValueError("Unexpected") - - # Get the unique FrequencyContainer objects for filling the info of. - # This could be more optimal and dive into the references and check if - # frequency containers are actually unique, but this mostly just stops - # there being 100s of fsig containers - # TODO - optimise this away from a tuple - self.unique_fcnt = tuple( - set(self.mechanical_frequencies.values()) | - set(self.electrical_frequencies.values()) - ) - - cdef void _determine_changing_beam_params(self): - cdef: - Py_ssize_t i - Py_ssize_t num_nodes = len(self.nodes) - ConnectorWorkspace ws - KnmConnectorWorkspace kws - - # re-set all beam parameter changing flags to false initially - for i in range(num_nodes): - self.trace[i].is_changing = False - - if self.retrace: - LOGGER.info("Flagging changing beam parameters.") - # Prepare the forest for simulation by setting all the node_id attributes - # and flag the corresponding self.trace entries as changing - self._setup_trace_forest() - - # now tell each knm workspace whether it is changing or not - # so that only changing scattering matrices get recomputed - # from here on - for kws in self.to_scatter_matrix_compute: - kws.flag_changing_knm_workspaces() - - cdef void _setup_trace_forest(self): - cdef: - Py_ssize_t tree_idx - TraceTree tree - - for tree_idx in range(self.trace_forest.N_trees): - tree = self.trace_forest.forest[tree_idx] - self._setup_single_trace_tree(tree) - - if self.trace_forest.N_trees: - LOGGER.info("Determined changing trace trees:%s", self.trace_forest.draw()) - - cdef void _setup_single_trace_tree(self, TraceTree tree): - cdef: - TraceTree ltree = tree.left - TraceTree rtree = tree.right - - tree.node_id = self.node_id(tree.node) - tree.opp_node_id = self.node_id(tree.node.opposite) - self.trace[tree.node_id].is_changing = True - self.trace[tree.opp_node_id].is_changing = True - - if ltree is not None: - self._setup_single_trace_tree(ltree) - if rtree is not None: - self._setup_single_trace_tree(rtree) - - @cython.initializedcheck(False) - cdef void _propagate_trace(self, TraceTree tree): - cdef: - TraceTree ltree = tree.left - TraceTree rtree = tree.right - - NodeBeamParam q1 = self.trace[tree.node_id] - complex_t qx1 = q1.qx - complex_t qy1 = q1.qy - complex_t qx2, qy2 - - if ltree is not None: - # For non-symmetric traces we have some special checks - # to do on trees which couldn't be reached from the - # other dependency trees. Note these are only performed - # on the left tree; see TraceForest._add_backwards_nonsymm_trees - # for details. - if self.trace_forest.symmetric or (not tree.do_nonsymm_reverse and not tree.do_inv_transform): - qx2 = transform_q(tree.left_abcd_x, qx1, tree.nr, ltree.nr) - qy2 = transform_q(tree.left_abcd_y, qy1, tree.nr, ltree.nr) - elif tree.do_inv_transform: - # Can't reach tree directly but there is a coupling from ltree.node - # to tree.node so apply the inverse abcd law to get correct q - qx2 = inv_transform_q(tree.left_abcd_x, qx1, tree.nr, ltree.nr) - qy2 = inv_transform_q(tree.left_abcd_y, qy1, tree.nr, ltree.nr) - else: - # Really is no way to get to the node (no coupling from ltree.node to - # tree.node) so only option now is to reverse q for ltree node entry - qx2 = -conj(qx1) - qy2 = -conj(qy1) - - self.trace[ltree.node_id].qx = qx2 - self.trace[ltree.node_id].qy = qy2 - if self.trace_forest.symmetric: - self.trace[ltree.opp_node_id].qx = -conj(qx2) - self.trace[ltree.opp_node_id].qy = -conj(qy2) - - self._propagate_trace(ltree) - - if rtree is not None: - qx2 = transform_q(tree.right_abcd_x, qx1, tree.nr, rtree.nr) - qy2 = transform_q(tree.right_abcd_y, qy1, tree.nr, rtree.nr) - - self.trace[rtree.node_id].qx = qx2 - self.trace[rtree.node_id].qy = qy2 - if self.trace_forest.symmetric: - self.trace[rtree.opp_node_id].qx = -conj(qx2) - self.trace[rtree.opp_node_id].qy = -conj(qy2) - - self._propagate_trace(rtree) - - cdef bint trace_beam(self): - cdef: - TraceTree tree - Py_ssize_t tree_idx - - CavityWorkspace cav_ws - bint source_is_cav - double gx, gy - - complex_t qx_src, qy_src - Py_ssize_t source_node_id, source_opp_node_id - - bint valid = not self.trace_forest.num_source_trees - - for tree_idx in range(self.trace_forest.N_trees): - tree = self.trace_forest.forest[tree_idx] - - if tree.is_source: - cav_ws = self.cavity_workspaces.get(tree.dependency) - source_is_cav = cav_ws is not None - if source_is_cav: # cavity source - if not cav_ws.is_stable: - gx = cav_ws.gx - gy = cav_ws.gy - if float_eq(gx, gy): - LOGGER.warning( - "Skipping beam traces from cavity %s as it is unstable " - "with g = %s", - tree.dependency.name, - gx, - ) - else: - LOGGER.warning( - "Skipping beam traces from cavity %s as it is unstable " - "with gx = %s, gy = %s", - tree.dependency.name, - gx, - gy, - ) - - continue - - # TODO (sjr) If the above statement is triggered then I need to - # notify the dependent sub-trees that their dependency - # now has an invalid beam parameter - # -> not too sure what will happen currently but I suspect - # that the previous data point trace data will just be - # used for these external cavity tree traces; at worst - # though we may have UB occurring - - # If tree is a source and beam parameter is not None then - # we have at least one valid dependency so the beam trace - # is ok - valid = True - - source_node_id = tree.node_id - source_opp_node_id = tree.opp_node_id - if not source_is_cav: # gauss command - qx_src = tree.dependency.qx - qy_src = tree.dependency.qy - else: - qx_src = cav_ws.qx - qy_src = cav_ws.qy - - self.trace[source_node_id].qx = qx_src - self.trace[source_node_id].qy = qy_src - if self.trace_forest.symmetric: - self.trace[source_opp_node_id].qx = -conj(qx_src) - self.trace[source_opp_node_id].qy = -conj(qy_src) - - self._propagate_trace(tree) - - return valid - - cpdef _initialise_nodes(self): - from finesse.components.node import NodeType - - self.num_nodes = len(self.nodes) - self._c_node_info = calloc(self.num_nodes, sizeof(NodeInfoEntry)) - if not self._c_node_info: - raise MemoryError() - - cdef BaseSimulation DC - if not self.is_audio: - self.trace = calloc(self.num_nodes, sizeof(NodeBeamParam)) - if not self.trace: - raise MemoryError() - else: - # Just point audio sim trace to DC trace so that - # carrier simulation beam trace data get used in - # any necessary AC calcs without needing repeated - # calculations - DC = self.DC - if DC.trace == NULL: - raise RuntimeError( - "Bug encountered! Trace data array of carrier " - "simulation is not in an initialised state." - ) - - self.trace = DC.trace - - cdef _fill_node_info(self): - cdef: - Py_ssize_t i, s_rhs_idx, s_f_idx, Nsm, Neq, Nhom, Nf - NodeInfoEntry node_info_entry - frequency_info_t *finfo_ptr = NULL - FrequencyContainer ficnt - - Nhom = self.nhoms - Nf = self.num_frequencies - s_rhs_idx = 0 - s_f_idx = 0 - - for i, n in enumerate(self.nodes.values()): - if n.type is NodeType.OPTICAL: - Neq = Nhom # Number of equations in each submatrix - Nsm = Nf # Each frequency is a submatrix of HOMs - finfo_ptr = &(self.frequency_info[0]) - elif n.type is NodeType.MECHANICAL: - Nsm = self.mechanical_frequencies[n].size # Number of mechanical frequencies - if Nsm < 1: - raise Exception(f"Number of mechanical frequencies at {n} must be > 0") - - # Higher order mechanical modes at a particular frequency. This should probably - # be kept as 1 mode per frequency, additional mechanical degrees of freedom should - # be defined as a separate node in a port. - ficnt = (self.mechanical_frequencies[n]) - Nsm = ficnt.size - finfo_ptr = &(ficnt.frequency_info[0]) - Neq = 1 - elif n.type is NodeType.ELECTRICAL: - ficnt = (self.electrical_frequencies[n]) - Nsm = ficnt.size - finfo_ptr = &(ficnt.frequency_info[0]) - Neq = 1 # no higher order modes of electronics as far as I'm aware... - else: - raise Exception("Not handled") - - assert(finfo_ptr != NULL) - #(index, RHS index, frequency index, num freqs, num HOM) - node_info_entry = NodeInfoEntry( - i, - s_rhs_idx, - s_f_idx, - Nsm, - Neq, - finfo_ptr - ) - self._c_node_info[i] = node_info_entry - finfo_ptr = NULL - - if not self.is_audio: - assert(n.type is NodeType.OPTICAL) - if self.is_modal: - qx, qy = self.model.last_trace[n] - # NOTE set is_changing to True for all node beam params initially - # so that initial scattering matrices get computed, these flags - # will then be updated in _determine_changing_beam_params - self.trace[i] = NodeBeamParam(qx.q, qy.q, True) - else: - self.trace[i] = NodeBeamParam(COMPLEX_0, COMPLEX_0, False) - - s_rhs_idx += Neq * Nsm # Track how many equations we are going through - s_f_idx += Nsm # keep track of how many frequencies*nodes - - cpdef _initialise_elements(self): - from finesse.components import Connector, Cavity, Variable - - # Store all the edge owners we'll need to loop over and call fill on - self._edge_owners = [] - # use set for unique edges - for el in set(nx.get_edge_attributes(self.model.network, "owner").values()): - self._edge_owners.append(el()) - - # Store all the elements that might fill the RHS - self._fill_rhs = [] - - # TODO ddb - probably need to move away from lists as they aren't that fast to iterate - # over. Maybe once we have all the lists filled we can covert them into some PyObject - # memoryview - self.workspaces = [] - self.variable_workspaces = [] - self.to_initial_fill = [] - self.to_refill = [] - self.to_rhs_fill = [] - self.to_rhs_refill = [] - self.cavity_workspaces = {} - self.to_scatter_matrix_compute = [] - - # Get any callbacks for the elements in the model - # tell the element that we have now built the model and it - # should do some initialisations for running simulations - for el in self.model.elements.values(): - if hasattr(el, "_fill_rhs") and self.is_matrix_sim: - self._fill_rhs.append(el._fill_rhs) - - el._setup_changing_params() - - if isinstance(el, Connector): - ws = el._get_workspace(self) - if ws is None: - continue - - if self.is_modal: - # if the connector scatters modes then initialise the - # knm workspaces here and store the connector workspace - # in to_scatter_matrix_compute for future use - if isinstance(ws, KnmConnectorWorkspace): - ( ws).initialise_knm_workspaces() - self.to_scatter_matrix_compute.append(ws) - - self.workspaces.append(ws) # store all workspaces here - - if isinstance(ws, ConnectorWorkspace): - if ws.callback_flag & ConnectorCallbacks.FILL_MATRIX: - self.to_initial_fill.append(ws) # Initial fill all - if ws.refill: - self.to_refill.append(ws) - - if ws.callback_flag & ConnectorCallbacks.FILL_RHS: - self.to_rhs_fill.append(ws) - if ws.refill: - self.to_rhs_refill.append(ws) - elif ws is not None: - # None means the component doesn't want anything - # to do with this simulation - raise Exception("Unexpected workspace type") - elif isinstance(el, Variable): - ws = el._get_workspace(self) - if ws is not None: - self.variable_workspaces.append(ws) - elif isinstance(el, Cavity): - self.cavity_workspaces[el] = el._get_workspace(self) - - # Compile cy_exprs for changing symbolics, these are stored - # in ElementWorkspace.chprm_expr which is used for fast evaluating - # of the changing symbolic expressions - for ws in self.workspaces: - ws.compile_cy_exprs() - # Also compile the changing ABCD matrix elements, these are - # stored in the relevant cy_expr** field of the associated - # workspace -> note that the cy_expr* element is NULL for non - # changing elements - if self.is_modal: - ws.compile_abcd_cy_exprs() - for vws in self.variable_workspaces: - ws.compile_cy_exprs() - - # refil gets hammered hard so lets stick it in a type array rather than a list - self.num_to_refill = len(self.to_refill) - self.ptr_to_refill = malloc(self.num_to_refill * sizeof(PyObject*)) - if not self.ptr_to_refill: - raise MemoryError() - cdef int i - for i in range(self.num_to_refill): - self.ptr_to_refill[i] = self.to_refill[i] - - self.num_to_rhs_refill = len(self.to_rhs_refill) - self.ptr_to_rhs_refill = malloc(self.num_to_rhs_refill * sizeof(PyObject*)) - if not self.ptr_to_rhs_refill: - raise MemoryError() - for i in range(self.num_to_rhs_refill): - self.ptr_to_rhs_refill[i] = self.to_rhs_refill[i] - - # print(self.name) - # print("MATRIX") - # print("to_refill", self.to_refill) - # print("to_rhs_refill", self.to_rhs_refill) - - cpdef get_node_info(self, node): - cdef int i - if type(node) is str: - i = self.nodes_idx[node] - else: - i = self.nodes_idx[node.full_name] - cdef NodeInfoEntry ni = self._c_node_info[i] - return { - "index": ni.index, - "rhs_index": ni.rhs_index, - "freq_index": ni.freq_index, - "nfreqs": ni.nfreqs, - "nhoms": ni.nhoms, - } - - def __enter__(self): - """ - When entered the Simulation object will create the matrix to be used in - the simulation. - - This is where the matrix gets allocated and constructed. It will expect - that the model structure does not change after this until it has been - exited. - """ - raise NotImplementedError() - - def _unbuild(self): - # let elements know that we have now finished this - # simulation and do any cleanup if they need to - for el in self.model.elements.values(): - if hasattr(el, "_on_unbuild"): - if self.is_matrix_sim: - el._on_unbuild(self) - el._clear_changing_params() - - def __exit__(self, type_, value, traceback): - raise NotImplementedError() - - cdef _init_frequency_info(self): - self.frequency_map = {float(f.f) : f for f in self.frequencies} - - cdef Py_ssize_t i, j, cidx - cdef FrequencyContainer fcnt - - for i in range(self.num_frequencies): - self.frequency_info[i].f = self.frequencies[i].f - self.frequency_info[i].index = self.frequencies[i].index - self.frequency_info[i].audio_order = self.frequencies[i].audio_order - - if self.frequency_info[i].audio_order: - cidx = self.frequencies[i].audio_carrier_index - self.frequency_info[i].audio_carrier_index = cidx - self.frequency_info[i].f_car = self.DC.frequencies[cidx].f - # Update carrier info so it knows about this sideband - if self.frequency_info[i].audio_order == 1: - self.DC.frequency_info[cidx].audio_upper_index = self.frequencies[i].index - elif self.frequency_info[i].audio_order == -1: - self.DC.frequency_info[cidx].audio_lower_index = self.frequencies[i].index - else: - raise Exception("Unexpected") - - if self.is_audio: - for i in range(len(self.unique_fcnt)): - fcnt = self.unique_fcnt[i] - for j in range(fcnt.size): - fcnt.frequency_info[j].f = fcnt.frequencies[j].f - fcnt.frequency_info[j].index = fcnt.frequencies[j].index - - - cdef _update_frequency_info(self): - cdef Py_ssize_t i, j, cidx - cdef FrequencyContainer fcnt - - for i in range(self.num_frequencies): - self.frequency_info[i].f = self.frequencies[i].f - - if self.frequency_info[i].audio_order and self.DC.any_frequencies_changing: - cidx = self.frequencies[i].audio_carrier_index - self.frequency_info[i].f_car = self.DC.frequencies[cidx].f - - if self.is_audio: - for i in range(len(self.unique_fcnt)): - fcnt = self.unique_fcnt[i] - for j in range(fcnt.size): - fcnt.frequency_info[j].f = fcnt.frequencies[j].f - - cpdef _clear_workspace(self): - if self.to_scatter_matrix_compute is not None: - for ws in self.to_scatter_matrix_compute: - (ws).free_knm_workspaces() - # empty out any workspaces left - self.to_scatter_matrix_compute.clear() - - if self.trace_forest: - self.trace_forest.clear() - if self.cavity_workspaces is not None: - self.cavity_workspaces.clear() - if self.workspaces is not None: - self.workspaces.clear() - if self.variable_workspaces is not None: - self.variable_workspaces.clear() - if self.to_initial_fill is not None: - self.to_initial_fill.clear() - if self.to_refill is not None: - self.to_refill.clear() - if self.to_rhs_fill is not None: - self.to_rhs_fill.clear() - if self.to_rhs_refill is not None: - self.to_rhs_refill.clear() - - cdef void set_source_fast(self, Py_ssize_t node_id, Py_ssize_t freq_idx, Py_ssize_t hom_idx, complex_t value): - raise NotImplementedError() - - cdef void set_source_fast_2(self, Py_ssize_t rhs_idx, complex_t value): - raise NotImplementedError() - - def __copy__(self): - raise Exception("Simulation objects can not be copied") - - def __deepcopy__(self, memo): - raise Exception("Simulation objects can not be deepcopied") + pass \ No newline at end of file diff --git a/src/finesse/simulations/basematrix.pxd b/src/finesse/simulations/basematrix.pxd index 046fe33f45a59439f8a9d555459fd2ad4feebf72..d9045355afff31ce9603aff6c46def779dac40fe 100644 --- a/src/finesse/simulations/basematrix.pxd +++ b/src/finesse/simulations/basematrix.pxd @@ -1,6 +1,142 @@ -from finesse.simulations.base cimport BaseSimulation from finesse.cmatrix cimport CCSMatrix +from finesse.cymath cimport complex_t +from finesse.tracing.ctracer cimport TraceForest, TraceTree +from finesse.simulations.base cimport ModelData, NodeBeamParam, NodeInfoEntry +from finesse.frequency cimport frequency_info_t, FrequencyContainer, Frequency +from cpython.ref cimport PyObject +from finesse.components.workspace cimport ConnectorWorkspace + + +cdef extern from "constants.h": + long double PI + double C_LIGHT + double complex COMPLEX_0 + + +cdef class MatrixSystemWorkspaces: + cdef readonly: + list to_initial_fill + list to_refill + list to_rhs_refill + int num_to_refill + int num_to_rhs_refill -cdef class BaseMatrixSimulation(BaseSimulation): + cdef: + PyObject** ptr_to_refill + PyObject** ptr_to_rhs_refill + + +cdef class MatrixSystemSolver: cdef: CCSMatrix _M + readonly dict _submatrices + Py_ssize_t num_nodes + readonly dict nodes + readonly dict nodes_idx + NodeInfoEntry* _c_node_info + readonly FrequencyContainer optical_frequencies + readonly dict mechanical_frequencies + readonly dict electrical_frequencies + readonly tuple unique_elec_mech_fcnts # Unique frequency containers for mech/elec + readonly int nhoms + const complex_t[::1] out_view + readonly bint any_frequencies_changing + bint is_signal_matrix + public bint manual_rhs + readonly MatrixSystemWorkspaces workspaces + + cpdef setup_nodes(self, list nodes) + cdef clear_rhs(self) + cdef initial_fill(self) + cdef refill(self) + cdef fill_rhs(self) + cdef solve(self) + cdef construct(self) + cdef initial_run(self) + cpdef run(self) + + cdef get_node_matrix_params(self, node, Py_ssize_t *Ns, Py_ssize_t *Nf, frequency_info_t** fptr) + cdef tuple get_node_frequencies(self, node) + + cdef update_frequency_info(self) + cdef assign_submatrices(self, workspaces) + + cpdef Py_ssize_t findex(self, object node, Py_ssize_t freq) + cdef Py_ssize_t findex_fast(self, Py_ssize_t node_id, Py_ssize_t freq) nogil + + cpdef Py_ssize_t field(self, object node, Py_ssize_t freq=?, Py_ssize_t hom=?) + cdef Py_ssize_t field_fast(self, Py_ssize_t node_id, Py_ssize_t freq=?, Py_ssize_t hom=?) nogil + cdef inline Py_ssize_t field_fast_2( + self, + Py_ssize_t node_rhs_idx, + Py_ssize_t num_hom, + Py_ssize_t freq, + Py_ssize_t hom + ) nogil + + cpdef complex_t get_out(self, object node, Py_ssize_t freq=?, Py_ssize_t hom=?) + cdef complex_t get_out_fast(self, Py_ssize_t node_id, Py_ssize_t freq=?, Py_ssize_t hom=?) nogil + + cdef void set_source_fast(self, Py_ssize_t node_id, Py_ssize_t freq_idx, Py_ssize_t hom_idx, complex_t value) + cdef void set_source_fast_2(self, Py_ssize_t rhs_idx, complex_t value) + + cpdef Py_ssize_t node_id(self, object node) + cpdef get_node_info(self, name) + + + +cdef class CarrierSignalMatrixSimulation: + cdef: + readonly MatrixSystemSolver carrier + readonly MatrixSystemSolver signal + readonly bint compute_signals + + readonly unicode name + readonly object model + readonly bint is_modal + readonly bint do_matrix_solving + + readonly MatrixSystemWorkspaces carrier_ws + readonly MatrixSystemWorkspaces signal_ws + readonly list detector_workspaces + + # mode indices only ever need to be indexed or iterated over + # so make it a readonly memoryview on the models' homs array + readonly int[:, ::1] homs_view + + public list workspaces + public list variable_workspaces + readonly list gouy_phase_workspaces + + # Tracing stuff + public dict cavity_workspaces + NodeBeamParam* trace + readonly TraceForest trace_forest + bint retrace + + # List of workspaces for components which scatter modes + list to_scatter_matrix_compute + readonly ModelData model_data + object __weakref__ + + cdef initialise_model_data(self) + cpdef initialise_workspaces(self) + cdef initialise_trace_forest(self, optical_nodes) + cdef void update_all_parameter_values(self) + + cdef void update_cavities(self) + cdef void compute_knm_matrices(self) + cdef int set_gouy_phases(self) except -1 + cpdef int modal_update(self) except -1 + + cpdef run_carrier(self) + cpdef run_signal(self) + + cdef void _determine_changing_beam_params(self) + # TODO (sjr) Would prefer to have the below methods in finesse.tracing.ctracer + # (see commented out blocks there) but circular import nonsense + # prevents that right now + cdef void _setup_trace_forest(self) + cdef void _setup_single_trace_tree(self, TraceTree tree) + cdef void _propagate_trace(self, TraceTree tree) + cdef bint trace_beam(self) \ No newline at end of file diff --git a/src/finesse/simulations/basematrix.pyx b/src/finesse/simulations/basematrix.pyx index a2b28be7a32b5316a5f51cf8e9fb5af4bab5474a..a0c1b247ea04bbbf6c6d035965b56f1b72a54cc1 100644 --- a/src/finesse/simulations/basematrix.pyx +++ b/src/finesse/simulations/basematrix.pyx @@ -2,87 +2,262 @@ Sub-module consisting of the :class:`.Simulation` for performing specific executions of a :class:`.Model`. """ - +import contextlib import logging import weakref import re import numpy as np cimport numpy as np +from libc.stdlib cimport free, malloc, calloc + +cimport cython + from finesse.cmatrix cimport CCSMatrix from finesse.components.node import NodeType -from finesse.simulations.base cimport BaseSimulation, NodeInfoEntry -from finesse.frequency cimport Frequency +from finesse.simulations.base cimport NodeInfoEntry +from finesse.frequency cimport Frequency, frequency_info_t, FrequencyContainer from finesse.components.workspace cimport ConnectionSetting from finesse.cymath cimport complex_t +from finesse.cymath.math cimport float_eq +from finesse.cymath.complex cimport conj +from finesse.cymath.gaussbeam cimport transform_q, inv_transform_q + +from finesse.element cimport ElementWorkspace +from finesse.components.workspace cimport ConnectorWorkspace, FillFuncWrapper, ConnectorMatrixSimulationInfo +from finesse.components.modal.workspace cimport KnmConnectorWorkspace +from finesse.components.workspace cimport ConnectorCallbacks +from finesse.components.modal.cavity cimport CavityWorkspace LOGGER = logging.getLogger(__name__) -cdef class BaseMatrixSimulation(BaseSimulation): - def active(self): - return self._M is not None - def print_matrix(self): - self._M.print_matrix() +cdef class MatrixSystemSolver: + """A linear set of systems can be represented as a matrix, each equation + in this system is a particular state which we want to compute. The system + is solved by applying some inputs into various states, or the right hand + side (RHS) vector, and solving the system. - cdef _clear_rhs(self): - self._M.clear_rhs() + The underlying matrix can be either a sparse or dense matrix. This class + should not assume either, but merely call upon a standard matrix interface. + Therefore the algorithm used for solving can varying significantly. + The overall matrix is sectioned into submatricies which connect various + states together. - def set_source(self, object node, int freq_idx, int hom_idx, complex value): - self._M.set_rhs(self.field_fast(self.node_id(node), freq_idx, hom_idx), value) + Nodes represent a physical location in the model in which some state of + the system must be computed. Some nodes can have multiple states, such + as multiple optical modes. + """ + + def __init__(self, + matrix_type, + str name, + list nodes, + FrequencyContainer optical_frequencies, + dict mechanical_frequencies, + dict electrical_frequencies, + int num_optical_homs, + bint is_signal_matrix + ): + if is_signal_matrix: + if (mechanical_frequencies is None or electrical_frequencies is None): + raise Exception("Mechanical and electrical frequency containers not provided") + else: + if mechanical_frequencies is not None or electrical_frequencies is not None: + raise Exception("Mechanical and electrical frequency containers incorrectly provided for carrier simulation") + assert(num_optical_homs >= 1) - cdef void set_source_fast(self, Py_ssize_t node_id, Py_ssize_t freq_idx, Py_ssize_t hom_idx, complex_t value): - self._M.set_rhs(self.field_fast(node_id, freq_idx, hom_idx), value) + self.is_signal_matrix = is_signal_matrix + self.manual_rhs = False + self.workspaces = MatrixSystemWorkspaces() + self._M = matrix_type(name) + self.out_view = None + self.nhoms = num_optical_homs + self.optical_frequencies = optical_frequencies + self.mechanical_frequencies = mechanical_frequencies + self.electrical_frequencies = electrical_frequencies - cdef void set_source_fast_2(self, Py_ssize_t rhs_idx, complex_t value): - self._M.set_rhs(rhs_idx, value) + if is_signal_matrix: + # Get the unique FrequencyContainer objects for filling the info of. + # This could be more optimal and dive into the references and check if + # frequency containers are actually unique, but this mostly just stops + # there being 100s of fsig containers from each node + # TODO - optimise this away from a tuple + self.unique_elec_mech_fcnts = tuple( + set(mechanical_frequencies.values()) | + set(electrical_frequencies.values()) + ) + self.any_frequencies_changing = False + # Global flag for if any frequency is changing + for _f in self.optical_frequencies.frequencies: + if _f.symbol.is_changing: + self.any_frequencies_changing = True + break - @property - def M(self): + self.setup_nodes(nodes) + + def __dealloc__(self): + if self._c_node_info != NULL: + free(self._c_node_info) + + cdef get_node_matrix_params(self, node, Py_ssize_t *Ns, Py_ssize_t *Nf, frequency_info_t** fptr): + """For a given node in the simulation this should set the provided pointers + regarding the number of states and submatricies that are required in the matrix: + - Ns : Number of unique states at the node per frequency + - Nf : Number of frequencies at the node + - fptr : Pointer to frequency_info_t for details on the number of frequencies """ - A weak reference to the underlying Matrix object. + assert(Ns) + assert(Nf) + assert(fptr) - .. note:: + cdef FrequencyContainer ficnt - References to the Matrix should not be kept. + if node.type is NodeType.OPTICAL: + Ns[0] = self.nhoms + Nf[0] = self.optical_frequencies.size + fptr[0] = &self.optical_frequencies.frequency_info[0] + elif node.type is NodeType.MECHANICAL: + # Higher order mechanical modes at a particular frequency. This should probably + # be kept as 1 mode per frequency, additional mechanical degrees of freedom should + # be defined as a separate node in a port. + ficnt = (self.mechanical_frequencies[node]) + Ns[0] = 1 + Nf[0] = ficnt.size + fptr[0] = &(ficnt.frequency_info[0]) + elif node.type is NodeType.ELECTRICAL: + ficnt = (self.electrical_frequencies[node]) + Ns[0] = 1 # no higher order modes of electronics as far as I'm aware... + Nf[0] = ficnt.size + fptr[0] = &(ficnt.frequency_info[0]) + else: + raise Exception("Node type not handled") - :getter: Returns a weak reference to the underlying matrix (read-only). + cdef tuple get_node_frequencies(self, node): + if node.type == NodeType.OPTICAL: + return self.optical_frequencies.frequencies + elif node.type == NodeType.MECHANICAL: + return self.mechanical_frequencies[node].frequencies + elif node.type == NodeType.ELECTRICAL: + return self.electrical_frequencies[node].frequencies + else: + raise ValueError() + + cpdef setup_nodes(self, list nodes): + cdef: + Py_ssize_t i, s_rhs_idx, s_f_idx, Nsm, Neq, Nhom, Nf + NodeInfoEntry *info + frequency_info_t *finfo_ptr = NULL + FrequencyContainer ficnt + + self.nodes = {n.full_name : n for n in nodes} + self.nodes_idx = {n.full_name : i for i, n in enumerate(nodes)} + self.num_nodes = len(self.nodes) + + self._c_node_info = calloc(self.num_nodes, sizeof(NodeInfoEntry)) + if not self._c_node_info: + raise MemoryError() + + Nhom = self.nhoms + s_rhs_idx = 0 # total number of states in the matrix so far + s_f_idx = 0 # Number of frequency submatrices so far + + for i, n in enumerate(self.nodes.values()): + self.get_node_matrix_params(n, &Neq, &Nsm, &finfo_ptr) + info = &self._c_node_info[i] + info.index = i + info.rhs_index = s_rhs_idx + info.freq_index = s_f_idx + info.nfreqs = Nsm + info.nhoms = Neq + info.frequencies = finfo_ptr + + s_rhs_idx += Neq * Nsm # Track how many equations we are going through + s_f_idx += Nsm # keep track of how many frequencies*nodes + + @contextlib.contextmanager + def component_edge_fill(self, comp, edgestr, f1, f2, conjugate = False): """ - return weakref.ref(self._M) + Returns a matrix for the submatrix an element has requested + for different connections it needs. The key is:: - @property - def Mq(self): + (element, connection_name, ifreq, ofreq) + + This is a context manager, to be used like with sim.component_edge_fill(key) as mat:: + + mat[:] = computations + + Parameters + ---------- + element : finesse.component.Connector + The object reference that created the requests. + connection_name : str + String name given to the connection. + ifreq : finesse.Frequency + Incoming frequency. + ofreq : finesse.Frequency + Outgoing frequency. + + Returns + ------- + matrix """ - A reference to the underlying quantum Matrix object. + #the two-stage nature of this will make some fill checks and hooks + #much more safe (and powerfull) + + #this will be helpful for on-demand filling and will also help improve + #make DC simulation of squeezers work (because post-fill transformations + #will be needed) + key = (comp, edgestr, f1.index, f2.index) + mat = self._submatrices[key] + yield mat + #check things, or transform things here + if conjugate: + mat[:].imag *= -1 + return + + @contextlib.contextmanager + def component_edge_fill3(self, owner_idx, conn_idx, f1_index, f2_index): + mat = self._submatrices[(owner_idx, conn_idx, f1_index, f2_index)] + yield mat + return - :getter: Returns a reference to the quantum matrix (read-only). + cdef assign_submatrices(self, connector_workspaces): + """An important function. This takes all the connector workspaces - i.e. model elements + that have requested some type of connection in the model - and ensures they have the + correct submatrix allocated to them in for this solver. """ - raise NotImplementedError() - #return self._Mq + cdef: + NodeInfoEntry node_inf + Frequency ifreq, ofreq + bint couples_f, is_freq_gen + ConnectorWorkspace ws + + if self._submatrices: + raise Exception("Submatricies already assigned") - cdef _initialise_submatrices(self): - cdef NodeInfoEntry node_inf - cdef Frequency ifreq, ofreq - cdef bint couples_f, is_freq_gen + self._submatrices = {} + #print(f"Is signal {self.is_signal_matrix}") + from finesse.components import FrequencyGenerator, Space # Add in the diagonal elements of the matrix for n, node_info_idx in self.nodes_idx.items(): + #print(n) node_inf = self._c_node_info[node_info_idx] Nsm = node_inf.nfreqs Neq = node_inf.nhoms - #print("^^^", n, "Nsm", Nsm, "Neq", Neq) + for freq in range(Nsm): fidx = self.findex(n, freq) # Get index for this submatrix self._M.add_diagonal_elements( Neq, fidx, f"I,node={n},f={freq},fidx={fidx},Neq={Neq}" ) - - unique_owners = set(self._edge_owners) + id_owner = -1 - - for ws in self.to_initial_fill: + # for everything that needs to fill the matrix... + for ws in connector_workspaces: owner = ws.owner id_owner += 1 ws.owner_id = id_owner # store the owner index @@ -93,6 +268,7 @@ cdef class BaseMatrixSimulation(BaseSimulation): # For each connection this element wants... for name in owner._registered_connections: + #print(name) id_connection += 1 # convert weak ref (input, output) @@ -103,9 +279,10 @@ cdef class BaseMatrixSimulation(BaseSimulation): nio = tuple(nio) # If we are a carrier matrix only compute optics, no AC electronics or mechanics - if not self.is_audio: + if not self.is_signal_matrix: if (nio[0].type is not NodeType.OPTICAL or nio[1].type is not NodeType.OPTICAL): + #print("excluded", name) continue dim = 0 # Dimension of the frequency coupling matrix @@ -115,25 +292,8 @@ cdef class BaseMatrixSimulation(BaseSimulation): Nfo = self._c_node_info[self.node_id(nio[1])].nfreqs #print("!!!", owner, nio, Nfi, Nfo) - - if nio[0].type == NodeType.OPTICAL: - ifreqs = self.frequencies - elif nio[0].type == NodeType.MECHANICAL: - ifreqs = self.mechanical_frequencies[nio[0]].frequencies - elif nio[0].type == NodeType.ELECTRICAL: - ifreqs = self.electrical_frequencies[nio[0]].frequencies - else: - raise ValueError() - - if nio[1].type == NodeType.OPTICAL: - ofreqs = self.frequencies - elif nio[1].type == NodeType.MECHANICAL: - ofreqs = self.mechanical_frequencies[nio[1]].frequencies - elif nio[1].type == NodeType.ELECTRICAL: - ofreqs = self.electrical_frequencies[nio[1]].frequencies - else: - raise ValueError() - + ifreqs = self.get_node_frequencies(nio[0]) + ofreqs = self.get_node_frequencies(nio[1]) #print(" in", nio[0], ifreqs, "\n out", nio[1], ofreqs) # Frequency generators might couple fields, they might not. @@ -151,17 +311,27 @@ cdef class BaseMatrixSimulation(BaseSimulation): couples_f = is_freq_gen or (nio[0].type != nio[1].type) or does_f_couple is not None #print(f" is_freq_gen={is_freq_gen} couples_f={couples_f}") - if not hasattr(ws.connections, name): + if self.is_signal_matrix: + ws_conn = ws.signal.connections + conn_settings = ws.signal.connection_settings + else: + ws_conn = ws.carrier.connections + conn_settings = ws.carrier.connection_settings + + if not hasattr(ws_conn, name): if couples_f: - setattr(ws.connections, name, np.empty((Nfi, Nfo) , dtype=object)) + # We will need a 2D array of submatrices to describe how multiple + # elements will couple together + setattr(ws_conn, name, np.empty((Nfi, Nfo) , dtype=object)) dim = 2 else: - setattr(ws.connections, name, np.empty(Nfi, dtype=object)) + # if not, just a diagonal + setattr(ws_conn, name, np.empty(Nfi, dtype=object)) dim = 1 else: # If a specialist object already exists lets probe it's shape # as that will describe what can actually be coupled or not - dim = getattr(ws.connections, name).dimensions + dim = getattr(ws_conn, name).dimensions # Loop over all the frequencies we can couple between and add # submatrixes to the overall model @@ -200,7 +370,7 @@ cdef class BaseMatrixSimulation(BaseSimulation): # Here we determined whether to conjugate fill a submatrix view or not conjugate_fill = False - if self.is_audio: + if self.is_signal_matrix: if nio[0].type == nio[1].type == NodeType.OPTICAL: # Opt-2-Opt lower sideband is conjugated if ifreq.audio_order < 0 and ofreq.audio_order < 0: @@ -211,41 +381,45 @@ cdef class BaseMatrixSimulation(BaseSimulation): elif nio[1].type == NodeType.OPTICAL and ofreq.audio_order < 0: # ?-2-Opt lower sideband is conjugated conjugate_fill = True - + if tuple(key) not in self._submatrices: smname = "{}__{}__{}".format(name, *tags) - # print("Requesting:", *iodx, self.name, smname, i, j, nio) + #print("Requesting:", key) # Then we get a view of the underlying matrix which we set the values # with. Store one for each frequency. By requesting this view we are # telling the matrix that these elements should be non-zero in the # model. - setting = ws.connection_settings.get(name) + setting = conn_settings.get(name) if setting is None: # default to using full matrix if nothing set setting = ConnectionSetting.MATRIX + if setting == ConnectionSetting.DIAGONAL: + #print("!!!D", owner, name, self.is_signal_matrix) SM = self._M.get_sub_diagonal_view(*iodx, smname, conjugate_fill) elif setting == ConnectionSetting.MATRIX: + #print("!!!M", owner, name, self.is_signal_matrix) SM = self._M.get_sub_matrix_view(*iodx, smname, conjugate_fill) elif setting == ConnectionSetting.DISABLED: + #print("!!!DIS", owner, name, self.is_signal_matrix) SM = None else: raise Exception(f"Unhandled setting {setting}") - if dim == 1: - getattr(ws.connections, name)[ifreq.index] = SM - elif dim == 2: - # print(getattr(ws.connections, name)) - # print(getattr(ws.connections, name).shape[0]) - # print(getattr(ws.connections, name).shape[1]) - # print(ifreq, ofreq) - # print(ifreq.index, ofreq.index) - getattr(ws.connections, name)[ifreq.index, ofreq.index] = SM - else: - raise Exception(f"Unhandled dimension size {dim}") + try: + if dim == 1: + getattr(ws_conn, name)[ifreq.index] = SM + elif dim == 2: + getattr(ws_conn, name)[ifreq.index, ofreq.index] = SM + else: + raise Exception(f"Unhandled dimension size {dim}") + except IndexError: + raise IndexError(f"Error setting submatrix to connection {name} in {owner}. " + "Size of array of submatricies wrong, number of frequencies " + "assumed probably incorrect.") - setattr(ws.connections, name + "_idx", id_connection) + setattr(ws_conn, name + "_idx", id_connection) self._submatrices[tuple(key)] = SM SMs.append(SM) else: @@ -258,3 +432,949 @@ cdef class BaseMatrixSimulation(BaseSimulation): ) else: continue + #print("done") + + cdef update_frequency_info(self): + self.optical_frequencies.update_frequency_info() + if self.is_signal_matrix: + for i in range(len(self.unique_elec_mech_fcnts)): + fcnt = self.s[i].update_frequency_info() + + cdef solve(self): + raise NotImplementedError() + + cdef initial_fill(self): + cdef ConnectorWorkspace ws + self.optical_frequencies.initialise_frequency_info() + + for ws in self.workspaces.to_initial_fill: + ws.update_parameter_values() + if self.is_signal_matrix: + info = ws.signal + else: + info = ws.carrier + + if info.fn_c is not None: + info.fn_c.func(ws) + elif info.fn_py is not None: + info.fn_py(ws) + + cdef refill(self): + cdef int i + cdef ConnectorWorkspace ws + cdef ConnectorMatrixSimulationInfo sys_ws + if self.any_frequencies_changing: + self.update_frequency_info() + + for i in range(self.workspaces.num_to_refill): + ws = self.workspaces.ptr_to_refill[i] + # TODO (sjr) Probably don't need this update call for now + # (see start of self.run method) + if self.is_signal_matrix: + sys_ws = ws.signal + else: + sys_ws = ws.carrier + + ws.update_parameter_values() + if sys_ws.fn_c is not None: + sys_ws.fn_c.func(ws) + elif sys_ws.fn_py is not None: + sys_ws.fn_py(ws) + + cdef fill_rhs(self): + cdef ConnectorWorkspace ws + cdef ConnectorMatrixSimulationInfo sys_ws + for ws in self.workspaces.to_rhs_refill: + ws.update_parameter_values() + + if self.is_signal_matrix: + sys_ws = ws.signal + else: + sys_ws = ws.carrier + + if sys_ws.fn_rhs_c is not None: + sys_ws.fn_rhs_c.func(ws) + elif sys_ws.fn_rhs_py is not None: + sys_ws.fn_rhs_py(ws) + + cdef construct(self): + """This is called when workspaces and submatrices have been setup. Calling + construct should now go and allocate the memory for the matrix and RHS. + + This method should be overwritten by an inheriting solver class with + specfics of the solving technique. + """ + raise NotImplementedError() + + cdef initial_run(self): + """Once a solver has been constructed it will most likely need to be initially + filled and ran. Some sparse solvers for example must do a full factor first, then + can perform faster refactors. + + This method should be overwritten by an inheriting solver class with + specfics of the solving technique. + """ + raise NotImplementedError() + + cpdef run(self): + """Executes the simulation for model in its current state. + + Takes the following steps to compute an output: + * If self.manual_rhs: + * Clears the RHS vector + * Fills the RHS vector + * Fills the matrix + * Solves + """ + if not self.manual_rhs: + self.clear_rhs() + self.fill_rhs() + self.refill() + self.solve() + + @property + def M(self): + """ + A weak reference to the underlying Matrix object. + + .. note:: + + References to the Matrix should not be kept. + + :getter: Returns a weak reference to the underlying matrix (read-only). + """ + return weakref.ref(self._M) + + def print_matrix(self): + self._M.print_matrix() + + cdef clear_rhs(self): + self._M.clear_rhs() + + def set_source(self, object node, int freq_idx, int hom_idx, complex value): + self._M.set_rhs(self.field_fast(self.node_id(node), freq_idx, hom_idx), value) + + cdef void set_source_fast(self, Py_ssize_t node_id, Py_ssize_t freq_idx, Py_ssize_t hom_idx, complex_t value): + self._M.set_rhs(self.field_fast(node_id, freq_idx, hom_idx), value) + + cdef void set_source_fast_2(self, Py_ssize_t rhs_idx, complex_t value): + self._M.set_rhs(rhs_idx, value) + + cpdef Py_ssize_t findex(self, object node, Py_ssize_t freq): + """ + Returns simulation unique index for a given frequency at this node. + Used to refer to submatrices of HOMs in the interferometer matrix. + + Parameters + ---------- + node : :class:`.Node` + Node object to get the index of. + freq : int + Frequency index. + + Returns + ------- + index : int + Index of the `node` for a given frequency. + """ + return self.findex_fast(self.node_id(node), freq) + + cdef Py_ssize_t findex_fast(self, Py_ssize_t node_id, Py_ssize_t freq) nogil: + cdef: + NodeInfoEntry ni = self._c_node_info[node_id] + Py_ssize_t freq_idx = ni.freq_index + + return freq_idx + freq + + cpdef Py_ssize_t field(self, object node, Py_ssize_t freq=0, Py_ssize_t hom=0): + """ + Returns simulation unique index of a field at a particular frequency + index at this node. + + Parameters + ---------- + node : :class:`.Node` + Node object to get the index of. + freq : int + Frequency index. + hom : int, optional + Higher Order Mode index, defaults to zero. + """ + return self.field_fast(self.node_id(node), freq, hom) + + cdef Py_ssize_t field_fast(self, Py_ssize_t node_id, Py_ssize_t freq=0, Py_ssize_t hom=0) nogil: + cdef: + NodeInfoEntry ni = self._c_node_info[node_id] + Py_ssize_t Nh = ni.nhoms + Py_ssize_t rhs_idx = ni.rhs_index + return rhs_idx + freq * Nh + hom + + cdef inline Py_ssize_t field_fast_2( + self, + Py_ssize_t node_rhs_idx, + Py_ssize_t num_hom, + Py_ssize_t freq, + Py_ssize_t hom) nogil: + """Inlined function to return field index fast.""" + return node_rhs_idx + freq * num_hom + hom + + cpdef complex_t get_out(self, object node, Py_ssize_t freq=0, Py_ssize_t hom=0): + return self.get_out_fast(self.node_id(node), freq, hom) + + @cython.boundscheck(False) + @cython.wraparound(False) + @cython.initializedcheck(False) + cdef complex_t get_out_fast(self, Py_ssize_t node_id, Py_ssize_t freq=0, Py_ssize_t hom=0) nogil: + cdef Py_ssize_t field_idx = self.field_fast(node_id, freq, hom) + + if self.out_view is None: + return COMPLEX_0 + + return self.out_view[field_idx] + + cpdef Py_ssize_t node_id(self, object node): + if type(node) is str: + return self.nodes_idx[node] + else: + return self.nodes_idx[node.full_name] + + cpdef get_node_info(self, node): + """For a given node (object or name) the key parameters + for where this node is represented in the matrix of linear + equations is returned in a NodeInfoEntry object. + """ + cdef int i + if type(node) is str: + i = self.nodes_idx[node] + else: + i = self.nodes_idx[node.full_name] + + cdef NodeInfoEntry ni = self._c_node_info[i] + return { + "index": ni.index, + "rhs_index": ni.rhs_index, + "freq_index": ni.freq_index, + "nfreqs": ni.nfreqs, + "nhoms": ni.nhoms, + } + + def get_frequency_object(self, frequency, node): + """Get a :class:`.Frequency` object corresponding to a numerical or symbolic value. + Returns none if nothing has been found. + + Parameters + ---------- + f : number or :class:`.Symbol` + Frequency to search for in this simulation. + + Returns + ------- + :class:`.Frequency` + The frequency object. + """ + from finesse.symbols import Symbol + + if node.type == NodeType.OPTICAL: + frequencies = self.optical_frequencies.frequencies + elif node.type == NodeType.MECHANICAL: + if not self.is_signal_matrix: + return None + frequencies = self.mechanical_frequencies[node].frequencies + elif node.type == NodeType.ELECTRICAL: + if not self.is_signal_matrix: + return None + frequencies = self.electrical_frequencies[node].frequencies + + if isinstance(frequency, Symbol): + if frequency.is_changing: + # 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 frequencies: + if f.symbol == frequency: + return f + + f_value = float(frequency) + # otherwise do some value comparisons + for f in frequencies: + if np.isclose(float(f.f), f_value, atol=1e-15, rtol=1e-15): + return f + + return None + + +cdef class CarrierSignalMatrixSimulation: + """A matrix based simulation which models the DC build of optical + fields (carriers) and optionally a signal simulation, which models + small AC oscillations throughout the system around the DC operating + point. + + This simualations class contains two solvers, one for the carrier + optical fields, and another for the signal simulations consisting of + optical, electrical, and mechanical. + """ + def __init__(self, model, unicode name, bint needs_matrix=True): + self.model = model + self.name = name + self.trace = NULL + self.do_matrix_solving = needs_matrix + + def build(self): + cdef: + Py_ssize_t i = 0 + + from finesse.simulations.KLU import KLUMatrixSystemSolver + + self.compute_signals = self.model.fsig.f.value is not None + self.is_modal = self.model.is_modal + self.initialise_model_data() + + # First we sort out the two matrix simulations as required by the model + cf = self.generate_carrier_frequencies() + onodes = self.model.optical_nodes + self.carrier = KLUMatrixSystemSolver("carrier", onodes, cf, None, None, self.model_data.num_HOMs, False) + + if self.compute_signals: + nodes = onodes.copy() + nodes.extend(self.model.mechanical_nodes) + nodes.extend(self.model.electrical_nodes) + sf, mf, ef = self.generate_signal_frequencies(nodes, cf) + self.signal = KLUMatrixSystemSolver("signal", nodes, sf, mf, ef, self.model_data.num_HOMs, True) + + self.initialise_trace_forest(onodes) + self.initialise_workspaces() + self.update_all_parameter_values() + + if self.is_modal: + # compute all the initial: + # - scattering matrices + # - space Gouy phases + # - laser tem Gouy phases + if self.do_matrix_solving: + self.compute_knm_matrices() + self.set_gouy_phases() + # ... then determine which beam parameters will be changing + # so that only the items from above which change get + # re-computed on subsequent calls to their respective functions + self._determine_changing_beam_params() + + if self.do_matrix_solving: + self.carrier.assign_submatrices(self.workspaces) + self.carrier.construct() + self.carrier.initial_run() + + if self.signal: + self.signal.assign_submatrices(self.workspaces) + self.signal.construct() + self.signal.initial_run() + + self.setup_detector_workspaces() + + def unbuild(self): + pass + #self._unbuild() + #self._clear_workspace() + + # Code below can be used in debug mode to determine if anyone is keeping any + # references to this matrix object, meaning its memory can't be freed. + # This takes ~20ms to do so makes a difference for quick models. Maybe we need + # a debug mode + + #_ref = self._M + #self._M = None + + # refs = gc.get_referrers(_ref) + # Nref = len(refs) + # if Nref > 0: + # LOGGER.warn( + # f"Something other than the Simulation object (N={Nref}) is keeping" + # f" a reference to the matrix object {repr(self._M)}." + # " Could lead to excessive memory usage if not released." + # ) + # for _ in refs: + # LOGGER.warn(f" - {repr(_)}") + #del _ref + + + cdef void update_cavities(self): + cdef CavityWorkspace ws + for ws in self.cavity_workspaces.values(): + ws.update() + + cdef void compute_knm_matrices(self): + cdef KnmConnectorWorkspace ws + for ws in self.to_scatter_matrix_compute: + ws.update_changing_knm_workspaces() + ws.compute_scattering_matrices() + + cdef int set_gouy_phases(self) except -1: + cdef ConnectorWorkspace ws + cdef int rtn + + for ws in self.gouy_phase_workspaces: + if ws.fn_gouy_c is not None: + rtn = ws.fn_gouy_c.func(ws) + elif ws.fn_gouy_py is not None: + rtn = ws.fn_gouy_py(ws) + if rtn: + return rtn + + return 0 + + cpdef int modal_update(self) except -1: + """Updates HOM related dependencies / properties of the model. + + These updates are as follows: + + * Execute a beam trace on the changing trace trees + * Computes the changing scattering matrices + * Calculates the Gouy phase of Spaces and Laser power coefficients + + Returns + ------- + exit_code : int + A value of ``0`` if successful, a value of ``1`` if an error + was raised during the beam tracing. + """ + cdef bint beam_trace_valid = True + + + self.update_cavities() + + if self.retrace: + beam_trace_valid = self.trace_beam() + if not beam_trace_valid: + return 1 + + # compute the changing scattering matrices + if self.do_matrix_solving: + self.compute_knm_matrices() + + # update the changing Gouy phases at spaces + # and TEM Gouy phases at lasers + return self.set_gouy_phases() + + cdef void _determine_changing_beam_params(self): + cdef: + Py_ssize_t i + Py_ssize_t num_nodes = self.carrier.num_nodes + ConnectorWorkspace ws + KnmConnectorWorkspace kws + + # re-set all beam parameter changing flags to false initially + for i in range(num_nodes): + self.trace[i].is_changing = False + + if self.retrace: + LOGGER.info("Flagging changing beam parameters.") + # Prepare the forest for simulation by setting all the node_id attributes + # and flag the corresponding self.trace entries as changing + self._setup_trace_forest() + + # now tell each knm workspace whether it is changing or not + # so that only changing scattering matrices get recomputed + # from here on + for kws in self.to_scatter_matrix_compute: + kws.flag_changing_knm_workspaces() + + cdef void _setup_trace_forest(self): + cdef: + Py_ssize_t tree_idx + TraceTree tree + + for tree_idx in range(self.trace_forest.N_trees): + tree = self.trace_forest.forest[tree_idx] + self._setup_single_trace_tree(tree) + + # # Also need to set the parent node_id as this will be used + # # in the beam tracing propagation routine + # if tree.parent is not None: + # self.trace_forest.forest[tree_idx] = tree.parent + # tree.parent.node_id = self.node_id(tree.parent.node) + # tree.parent.opp_node_id = self.node_id(tree.parent.node.opposite) + + if self.trace_forest.N_trees: + LOGGER.info("Determined changing trace trees:%s", self.trace_forest.draw()) + + cdef void _setup_single_trace_tree(self, TraceTree tree): + cdef: + TraceTree ltree = tree.left + TraceTree rtree = tree.right + + tree.node_id = self.node_id(tree.node) + tree.opp_node_id = self.node_id(tree.node.opposite) + self.trace[tree.node_id].is_changing = True + self.trace[tree.opp_node_id].is_changing = True + + if ltree is not None: + self._setup_single_trace_tree(ltree) + if rtree is not None: + self._setup_single_trace_tree(rtree) + + @cython.initializedcheck(False) + cdef void _propagate_trace(self, TraceTree tree): + cdef: + TraceTree ltree = tree.left + TraceTree rtree = tree.right + + NodeBeamParam q1 = self.trace[tree.node_id] + complex_t qx1 = q1.qx + complex_t qy1 = q1.qy + complex_t qx2, qy2 + + if ltree is not None: + # For non-symmetric traces we have some special checks + # to do on trees which couldn't be reached from the + # other dependency trees. Note these are only performed + # on the left tree; see TraceForest._add_backwards_nonsymm_trees + # for details. + if self.trace_forest.symmetric or (not tree.do_nonsymm_reverse and not tree.do_inv_transform): + qx2 = transform_q(tree.left_abcd_x, qx1, tree.nr, ltree.nr) + qy2 = transform_q(tree.left_abcd_y, qy1, tree.nr, ltree.nr) + elif tree.do_inv_transform: + # Can't reach tree directly but there is a coupling from ltree.node + # to tree.node so apply the inverse abcd law to get correct q + qx2 = inv_transform_q(tree.left_abcd_x, qx1, tree.nr, ltree.nr) + qy2 = inv_transform_q(tree.left_abcd_y, qy1, tree.nr, ltree.nr) + else: + # Really is no way to get to the node (no coupling from ltree.node to + # tree.node) so only option now is to reverse q for ltree node entry + qx2 = -conj(qx1) + qy2 = -conj(qy1) + + self.trace[ltree.node_id].qx = qx2 + self.trace[ltree.node_id].qy = qy2 + if self.trace_forest.symmetric: + self.trace[ltree.opp_node_id].qx = -conj(qx2) + self.trace[ltree.opp_node_id].qy = -conj(qy2) + + self._propagate_trace(ltree) + + if rtree is not None: + qx2 = transform_q(tree.right_abcd_x, qx1, tree.nr, rtree.nr) + qy2 = transform_q(tree.right_abcd_y, qy1, tree.nr, rtree.nr) + + self.trace[rtree.node_id].qx = qx2 + self.trace[rtree.node_id].qy = qy2 + if self.trace_forest.symmetric: + self.trace[rtree.opp_node_id].qx = -conj(qx2) + self.trace[rtree.opp_node_id].qy = -conj(qy2) + + self._propagate_trace(rtree) + + cdef bint trace_beam(self): + cdef: + TraceTree tree + Py_ssize_t tree_idx + + CavityWorkspace cav_ws + bint source_is_cav + double gx, gy + + complex_t qx_src, qy_src + Py_ssize_t source_node_id, source_opp_node_id + + bint valid = not self.trace_forest.num_source_trees + + for tree_idx in range(self.trace_forest.N_trees): + tree = self.trace_forest.forest[tree_idx] + + if tree.is_source: + cav_ws = self.cavity_workspaces.get(tree.dependency) + source_is_cav = cav_ws is not None + if source_is_cav: # cavity source + if not cav_ws.is_stable: + gx = cav_ws.gx + gy = cav_ws.gy + if float_eq(gx, gy): + LOGGER.warning( + "Skipping beam traces from cavity %s as it is unstable " + "with g = %s", + tree.dependency.name, + gx, + ) + else: + LOGGER.warning( + "Skipping beam traces from cavity %s as it is unstable " + "with gx = %s, gy = %s", + tree.dependency.name, + gx, + gy, + ) + + continue + + # TODO (sjr) If the above statement is triggered then I need to + # notify the dependent sub-trees that their dependency + # now has an invalid beam parameter + # -> not too sure what will happen currently but I suspect + # that the previous data point trace data will just be + # used for these external cavity tree traces; at worst + # though we may have UB occurring + + # If tree is a source and beam parameter is not None then + # we have at least one valid dependency so the beam trace + # is ok + valid = True + + source_node_id = tree.node_id + source_opp_node_id = tree.opp_node_id + if not source_is_cav: # gauss command + qx_src = tree.dependency.qx + qy_src = tree.dependency.qy + else: + qx_src = cav_ws.qx + qy_src = cav_ws.qy + + self.trace[source_node_id].qx = qx_src + self.trace[source_node_id].qy = qy_src + if self.trace_forest.symmetric: + self.trace[source_opp_node_id].qx = -conj(qx_src) + self.trace[source_opp_node_id].qy = -conj(qy_src) + + self._propagate_trace(tree) + + return valid + + cpdef run_carrier(self): + """Runs the carrier matrix solver for the current state of the model. + This will update all the C based structs with the current model state so + that filling and calculations can be performed. + + Returns + ------- + exit_code : int + ``0`` if this was a valid run and the output can be used, ``-1`` otherwise. + """ + # NOTE (sjr) Just updating all parameter values on each call to run for + # now. This may not be the most optimal thing to do, but it + # avoids duplicating these parameter update calls in different + # places (e.g. refill, compute_knm_matrices, set_gouy_phases) and + # should be safe in that no parameters get accidentally missed at + # any data point. + # ddb - this just updates everything, even things that are not changing as + # it acts on all the workspaces, probably not the best idea + self.update_all_parameter_values() + + # update HOM stuff + cdef int status + + if self.is_modal: + status = self.modal_update() + # immediately return if invalid beam trace region encountered + # no need to go ahead and fill or solve as they won't be used + if status: + return status + + if self.do_matrix_solving: + self.carrier.run() + return 0 + + cpdef run_signal(self): + """Runs the signal matrix solver for the current state. This function should assume that + a call to the `run_carrier` method has preceeded it. Many modal and parameter updates + should happen in there already, so do not need to be repeated here. + """ + self.model_data.fsig = self.model.fsig.f.value + # Probably some other preparatory stuff needs to go here in the future + if self.do_matrix_solving: + self.signal.run() + + def setup_detector_workspaces(self): + # Once the simulations are started we can tell all the detectors to + # prepare themselves + self.detector_workspaces = [] + for det in self.model.detectors: + self.detector_workspaces.append(det._get_workspace(self)) + + def __enter__(self): + self.build() + + def __exit__(self, type_, value, traceback): + self.unbuild() + + cdef void update_all_parameter_values(self): + """Loops through all workspaces to update the C structs so they + represent the current model element parameter values. + """ + cdef: + ElementWorkspace ews + ConnectorWorkspace ws + + Py_ssize_t i + # TODO (sjr) Should probably cache these or move away from + # lists for best performance + Py_ssize_t Nvws = len(self.variable_workspaces) + Py_ssize_t Ncws = len(self.workspaces) + + for i in range(Nvws): + ews = self.variable_workspaces[i] + ews.update_parameter_values() + + for i in range(Ncws): + ws = self.workspaces[i] + ws.update_parameter_values() + + cdef initialise_trace_forest(self, optical_nodes): + cdef TraceForest model_trace_forest = self.model._trace_forest + # Before we setup the workspaces some initial beam trace must be done + # so that workspaces can initialise themselves + if self.is_modal: + # Make sure the model trace forest gets re-planted + # when building a new simulation + self.model._rebuild_trace_forest = True + LOGGER.info( + "Performing initial beam trace with configuration options: %s", + self.model.sim_trace_config, + ) + self.model.beam_trace(**self.model.sim_trace_config) + self.retrace = self.model.retrace + self.trace = calloc(len(optical_nodes), sizeof(NodeBeamParam)) + if not self.trace: + raise MemoryError() + + for i, n in enumerate(optical_nodes): + qx, qy = self.model.last_trace[n] + # NOTE set is_changing to True for all node beam params initially + # so that initial scattering matrices get computed, these flags + # will then be updated in _determine_changing_beam_params + self.trace[i] = NodeBeamParam(qx.q, qy.q, True) + # else: # ddb - nothing should be accessing trace if it isn't a modal simulation? + # self.trace[i] = NodeBeamParam(COMPLEX_0, COMPLEX_0, False) + + if self.retrace: + # construct the forest of changing trace trees - need to do this + # here as the _get_workspace method of Connectors requires the + # simulation trace_forest field to be initialised when making + # the refill flags + self.trace_forest = model_trace_forest.make_changing_forest() + else: + # just make an empty TraceForest + self.trace_forest = TraceForest(self.model) + + cdef initialise_model_data(self): + self.model_data = ModelData() + self.model_data.EPSILON0_C = self.model._EPSILON0_C + self.model_data.lambda0 = self.model.lambda0 + self.model_data.f0 = C_LIGHT / self.model_data.lambda0 + self.model_data.k0 = 2.0 * PI / self.model_data.lambda0 + self.model_data.homs_view = self.model.homs + self.model_data.maxtem = np.max(self.model.homs[:, 0] + self.model.homs[:, 1]) + self.model_data.num_HOMs = len(self.model.homs) + self.model_data.x_scale = self.model._x_scale + if self.model.fsig.f.value is None: + self.model_data.fsig = 0 + else: + self.model_data.fsig = self.model.fsig.f.value + self.model_data.zero_K00 = self.model.phase_config.ZERO_K00 + self.model_data.zero_tem00_gouy = self.model.phase_config.ZERO_TEM00_GOUY + + def generate_carrier_frequencies(self): + """Returns a list of Frequency objects that the model has requested""" + from finesse.frequency import Frequency, generate_frequency_list + frequencies_to_use = generate_frequency_list(self.model) + + carrier_frequencies = [] + + LOGGER.info('Generating simulation with carrier frequencies %s', frequencies_to_use) + for i, f in enumerate(frequencies_to_use): + carrier_frequencies.append( + Frequency(str(f.eval(keep_changing_symbols=True)), self, f, index=i) + ) + + fcnt = FrequencyContainer(carrier_frequencies) + return fcnt + + def generate_signal_frequencies(self, nodes, FrequencyContainer carrier_optical_frequencies): + """Generates the optical, mechanical, and electrical frequencies that should be + modelled by the signal simulation. + """ + signal_frequencies = [] # All optical frequencies are present at all nodes + # elec and mech can have different frequencies on a per node basis + mechanical_frequencies = {} + electrical_frequencies = {} + + for i, f in enumerate(carrier_optical_frequencies.frequencies): + fp = f.f + self.model.fsig.f.ref + fm = f.f - self.model.fsig.f.ref + signal_frequencies.append( + Frequency(str(fp.eval(keep_changing_symbols=True)), + self, fp, index=2*i, audio_order=1, + audio=True, audio_carrier_index=i) + ) + signal_frequencies.append( + Frequency(str(fm.eval(keep_changing_symbols=True)), + self, fm, index=2*i+1, audio_order=-1, + audio=True, audio_carrier_index=i) + ) + + fcnt = FrequencyContainer(signal_frequencies, carrier_cnt=carrier_optical_frequencies) + + # Audio matrix frequencies are more complicated as they can have multiple frequencies + # in mechanical and electrical, on a per-node basis... + fsig = FrequencyContainer( + (Frequency("fsig", self, self.model.fsig.f.ref, index=0), ) + ) + + + for node in nodes: + if node.type == NodeType.OPTICAL: + continue + + #----------------------------------------------------------------------------------- + # Mechanical frequencies + #----------------------------------------------------------------------------------- + # By default mechanical frequencies just have a single frequency at the Model.fsig.f + # However for more complicated systems we can have multiple frequencies. + elif node.type == NodeType.MECHANICAL: + fs = [] + freqs = node.frequencies + if len(freqs) == 1 and freqs[0] == self.model.fsig.f.ref: + # Most components will just use a single fsig so reuse same object + # for efficient filling later + mechanical_frequencies[node] = fsig + else: + for i, sym in enumerate(node.frequencies): + fs.append( + Frequency( + str(fm.eval(keep_changing_symbols=True)), + self, + sym, + index=i, + ) + ) + mechanical_frequencies[node] = FrequencyContainer(tuple(fs)) + + #----------------------------------------------------------------------------------- + # Electrical frequencies + #----------------------------------------------------------------------------------- + elif node.type == NodeType.ELECTRICAL: + electrical_frequencies[node] = fsig + else: + raise ValueError("Unexpected") + + return fcnt, mechanical_frequencies, electrical_frequencies + + cpdef initialise_workspaces(self): + from finesse.components import Connector, Cavity, Variable + # TODO ddb - probably need to move away from lists as they aren't that fast to iterate + # over. Maybe once we have all the lists filled we can covert them into some PyObject + # memoryview + + self.workspaces = [] + self.cavity_workspaces = {} + self.to_scatter_matrix_compute = [] + self.variable_workspaces = [] + self.gouy_phase_workspaces = [] + + if self.is_modal and self.trace == NULL: + raise Exception("Beam trace has not been set before workspaces are initialised") + + # Get any callbacks for the elements in the model + # tell the element that we have now built the model and it + # should do some initialisations for running simulations + for el in self.model.elements.values(): + el._setup_changing_params() + + if isinstance(el, Connector): + ws = el._get_workspace(self) + if ws is None: + continue + + self.workspaces.append(ws) # store all workspaces here + + # if the connector scatters modes then initialise the + # knm workspaces here and store the connector workspace + # in to_scatter_matrix_compute for future use + if self.is_modal and isinstance(ws, KnmConnectorWorkspace): + ( ws).initialise_knm_workspaces() + self.to_scatter_matrix_compute.append(ws) + + if isinstance(ws, ConnectorWorkspace): + # Determine if we should be adding this workspace to any + # todo list for looping over later + if self.signal: + x = ( + (ws.carrier, self.carrier.workspaces), + (ws.signal, self.signal.workspaces) + ) + else: + x = ((ws.carrier, self.carrier.workspaces),) + + for info, ws_store in x: + if info.callback_flag & ConnectorCallbacks.FILL_MATRIX: + ws_store.to_initial_fill.append(ws) # Initial fill all + if ws.carrier.refill: + ws_store.to_refill.append(ws) + + if info.callback_flag & ConnectorCallbacks.FILL_RHS: + ws_store.to_rhs_refill.append(ws) + + if ws.fn_gouy_c is not None or ws.fn_gouy_py is not None: + self.gouy_phase_workspaces.append(ws) + + elif ws is not None: + # None means the component doesn't want anything + # to do with this simulation + raise Exception("Unexpected workspace type") + elif isinstance(el, Variable): + ws = el._get_workspace(self) + if ws is not None: + self.variable_workspaces.append(ws) + elif isinstance(el, Cavity): + self.cavity_workspaces[el] = el._get_workspace(self) + + # Compile cy_exprs for changing symbolics, these are stored + # in ElementWorkspace.chprm_expr which is used for fast evaluating + # of the changing symbolic expressions + for ws in self.workspaces: + ws.compile_cy_exprs() + # Also compile the changing ABCD matrix elements, these are + # stored in the relevant cy_expr** field of the associated + # workspace -> note that the cy_expr* element is NULL for non + # changing elements + if self.is_modal: + ws.compile_abcd_cy_exprs() + + for vws in self.variable_workspaces: + ws.compile_cy_exprs() + + self.carrier.workspaces.list_to_C() + if self.signal: + self.signal.workspaces.list_to_C() + # print(self.name) + # print("MATRIX") + # print("to_refill", self.to_refill) + # print("to_rhs_refill", self.to_rhs_refill) + + +cdef class MatrixSystemWorkspaces: + def __init__(self): + self.to_initial_fill = [] + self.to_refill = [] + self.to_rhs_refill = [] + + def list_to_C(self): + """Converts the python lists of workspaces into C Pyobject arrays for + fast loop access. + """ + self.num_to_refill = len(self.to_refill) + self.ptr_to_refill = malloc(self.num_to_refill * sizeof(PyObject*)) + if not self.ptr_to_refill: + raise MemoryError() + cdef int i + for i in range(self.num_to_refill): + self.ptr_to_refill[i] = self.to_refill[i] + + self.num_to_rhs_refill = len(self.to_rhs_refill) + self.ptr_to_rhs_refill = malloc(self.num_to_rhs_refill * sizeof(PyObject*)) + if not self.ptr_to_rhs_refill: + raise MemoryError() + for i in range(self.num_to_rhs_refill): + self.ptr_to_rhs_refill[i] = self.to_rhs_refill[i] + + def __dealloc__(self): + if self.ptr_to_refill: + free(self.ptr_to_refill) + + if self.ptr_to_rhs_refill: + free(self.ptr_to_rhs_refill) diff --git a/src/finesse/simulations/debug.py b/src/finesse/simulations/debug.py index a4c53c30873337a11d1f71869ddcb06ba6199a7c..178579148c666b7ed9d3db6cf0f00fa40566bd93 100644 --- a/src/finesse/simulations/debug.py +++ b/src/finesse/simulations/debug.py @@ -1,152 +1,152 @@ -""" -Sub-module consisting of the :class:`.Simulation` for -performing specific executions of a :class:`.Model`. -""" - -import logging -import numpy as np -import re - -from finesse.components.node import NodeType -from finesse.simulations.base import BaseSimulation - -LOGGER = logging.getLogger(__name__) - - -class DebugSimulation(BaseSimulation): - def active(self): - return self._M is not None - - def print_matrix(self): - self._M.print_matrix() - - def solve(self): - self.out = None - return - - def __enter__(self): - """ - When entered the Simulation object will create the matrix to be used in - the simulation. - - This is where the matrix gets allocated and constructed. It will expect - that the model structure does not change after this until it has been - exited. - """ - # Initialising the simulation expects there to be a self._M class that handles the - # matrix build/memory/etc. This must be set before initialising. - - self._initialise() - self.initial_fill() - return self - - def _initialise_submatrices(self): - from finesse.components import FrequencyGenerator - - # Add in the diagonal elements of the matrix - # for n, node_inf in self._node_info.items(): - # Nsm = node_inf["nfreqs"] - # Neq = node_inf["nhoms"] - # for freq in range(Nsm): - # fidx = self.findex(n, freq) # Get index for this submatrix - # comment = f"I,node={n.full_name},f={freq},fidx={fidx},Neq={Neq}" - # self.M().add_diagonal_elements(Neq, fidx, comment) - # Not building the diagonal currently - - _done = {} - # Loop over every edge in the network which represents a bunch of - # connections between frequencies and HOMs between two nodes - for owner in self._edge_owners: - if owner in _done: - continue - - couples_f = isinstance(owner, FrequencyGenerator) - - # For each connection this element wants... - for name in owner._registered_connections: - # convert weak ref (input, output) - nio = tuple( - (owner.nodes[_] for _ in owner._registered_connections[name]) - ) - - # If we are a carrier matrix only compute optics, no AC electronics or mechanics - if not self.is_audio: - if ( - nio[0].type is not NodeType.OPTICAL - or nio[1].type is not NodeType.OPTICAL - ): - continue - - # Loop over all the frequencies we can couple between and add - # submatrixes to the overall model - for ifreq in self.frequencies: - for ofreq in self.frequencies: - # For each input and output frequency check if our - # element wants to couple them at this - if couples_f and not owner._couples_frequency( - self, name, ifreq, ofreq - ): - continue - elif not couples_f and ifreq != ofreq: - # If it doesn't couple frequencies and the - # frequencies are different then ignore - continue - - iodx = [] # submatrix indices - tags = [] # descriptive naming tags for submatrix key - key_name = re.sub(r"^[^.]*\.", "", name) - key_name = re.sub(r">[^.]*\.", ">", key_name) - key = [owner, key_name] - - # Get simulation unique indices for submatrix - # position. How we get these depends on the type of - # the nodes involved - for freq, node in zip((ifreq, ofreq), nio): - if node.type is NodeType.OPTICAL: - iodx.append(self.findex(node, freq.index)) - tags.append(freq.name) - key.append(freq) - else: - # Mechanical and electrical don't have multiple - # freqs, so always use the zeroth frequency index - iodx.append(self.findex(node, 0)) - tags.append("AC") - key.append(None) - - assert len(iodx) == 2 - assert len(key) == 4 - - if tuple(key) not in self._submatrices: - # smname = "{}__{}__{}".format(name, *tags) - - # print("Requesting:", *iodx, self.name, smname, i, j, nio) - # Then we get a view of the underlying matrix which we set the values - # with. Store one for each frequency. By requesting this view we are - # telling the matrix that these elements should be non-zero in the - # model. - n1size = self._node_info[node]["nhoms"] - n2size = self._node_info[node]["nhoms"] - self._submatrices[tuple(key)] = np.zeros( - (n1size, n2size), dtype=complex - ) - else: - # Check if we've just requested the same submatrix. - sm = self._submatrices[tuple(key)] - if sm.from_idx != iodx[0] or sm.to_idx != iodx[1]: - raise Exception( - "Requested submatrix has already been requested," - "but new one has different indices" - ) - else: - continue - - _done[owner] = True - - def __exit__(self, type_, value, traceback): - return - - def _clear_rhs(self): - return None - - def set_source(self, field_idx, vector): - return None +# """ +# Sub-module consisting of the :class:`.Simulation` for +# performing specific executions of a :class:`.Model`. +# """ + +# import logging +# import numpy as np +# import re + +# from finesse.components.node import NodeType +# from finesse.simulations.base import BaseSimulation + +# LOGGER = logging.getLogger(__name__) + + +# class DebugSimulation(BaseSimulation): +# def active(self): +# return self._M is not None + +# def print_matrix(self): +# self._M.print_matrix() + +# def solve(self): +# self.out = None +# return + +# def __enter__(self): +# """ +# When entered the Simulation object will create the matrix to be used in +# the simulation. + +# This is where the matrix gets allocated and constructed. It will expect +# that the model structure does not change after this until it has been +# exited. +# """ +# # Initialising the simulation expects there to be a self._M class that handles the +# # matrix build/memory/etc. This must be set before initialising. + +# self._initialise() +# self.initial_fill() +# return self + +# def _initialise_submatrices(self): +# from finesse.components import FrequencyGenerator + +# # Add in the diagonal elements of the matrix +# # for n, node_inf in self._node_info.items(): +# # Nsm = node_inf["nfreqs"] +# # Neq = node_inf["nhoms"] +# # for freq in range(Nsm): +# # fidx = self.findex(n, freq) # Get index for this submatrix +# # comment = f"I,node={n.full_name},f={freq},fidx={fidx},Neq={Neq}" +# # self.M().add_diagonal_elements(Neq, fidx, comment) +# # Not building the diagonal currently + +# _done = {} +# # Loop over every edge in the network which represents a bunch of +# # connections between frequencies and HOMs between two nodes +# for owner in self._edge_owners: +# if owner in _done: +# continue + +# couples_f = isinstance(owner, FrequencyGenerator) + +# # For each connection this element wants... +# for name in owner._registered_connections: +# # convert weak ref (input, output) +# nio = tuple( +# (owner.nodes[_] for _ in owner._registered_connections[name]) +# ) + +# # If we are a carrier matrix only compute optics, no AC electronics or mechanics +# if not self.is_audio: +# if ( +# nio[0].type is not NodeType.OPTICAL +# or nio[1].type is not NodeType.OPTICAL +# ): +# continue + +# # Loop over all the frequencies we can couple between and add +# # submatrixes to the overall model +# for ifreq in self.frequencies: +# for ofreq in self.frequencies: +# # For each input and output frequency check if our +# # element wants to couple them at this +# if couples_f and not owner._couples_frequency( +# self, name, ifreq, ofreq +# ): +# continue +# elif not couples_f and ifreq != ofreq: +# # If it doesn't couple frequencies and the +# # frequencies are different then ignore +# continue + +# iodx = [] # submatrix indices +# tags = [] # descriptive naming tags for submatrix key +# key_name = re.sub(r"^[^.]*\.", "", name) +# key_name = re.sub(r">[^.]*\.", ">", key_name) +# key = [owner, key_name] + +# # Get simulation unique indices for submatrix +# # position. How we get these depends on the type of +# # the nodes involved +# for freq, node in zip((ifreq, ofreq), nio): +# if node.type is NodeType.OPTICAL: +# iodx.append(self.findex(node, freq.index)) +# tags.append(freq.name) +# key.append(freq) +# else: +# # Mechanical and electrical don't have multiple +# # freqs, so always use the zeroth frequency index +# iodx.append(self.findex(node, 0)) +# tags.append("AC") +# key.append(None) + +# assert len(iodx) == 2 +# assert len(key) == 4 + +# if tuple(key) not in self._submatrices: +# # smname = "{}__{}__{}".format(name, *tags) + +# # print("Requesting:", *iodx, self.name, smname, i, j, nio) +# # Then we get a view of the underlying matrix which we set the values +# # with. Store one for each frequency. By requesting this view we are +# # telling the matrix that these elements should be non-zero in the +# # model. +# n1size = self._node_info[node]["nhoms"] +# n2size = self._node_info[node]["nhoms"] +# self._submatrices[tuple(key)] = np.zeros( +# (n1size, n2size), dtype=complex +# ) +# else: +# # Check if we've just requested the same submatrix. +# sm = self._submatrices[tuple(key)] +# if sm.from_idx != iodx[0] or sm.to_idx != iodx[1]: +# raise Exception( +# "Requested submatrix has already been requested," +# "but new one has different indices" +# ) +# else: +# continue + +# _done[owner] = True + +# def __exit__(self, type_, value, traceback): +# return + +# def _clear_rhs(self): +# return None + +# def set_source(self, field_idx, vector): +# return None diff --git a/src/finesse/simulations/dense.py b/src/finesse/simulations/dense.py index 87270650d74ff792999544bac9b36a50cc1ee848..e8d896e7a2b15e661a0b9aefd866e41df0e430f1 100644 --- a/src/finesse/simulations/dense.py +++ b/src/finesse/simulations/dense.py @@ -6,50 +6,50 @@ performing specific executions of a :class:`.Model`. import logging from finesse.densematrix import DenseMatrix -from finesse.simulations.basematrix import BaseMatrixSimulation +#from finesse.simulations.basematrix import BaseMatrixSimulation LOGGER = logging.getLogger(__name__) -class DenseSimulation(BaseMatrixSimulation): - def solve(self): - if not self.model.is_built: - raise Exception("Model has not been built") - - # As we put -1 on diagonals we actually solve the negative - # of the system, so inverse that here... - # -1 here because we compute the negative of the interferometer - # matrix. Why? Because the -1 sign only gets applied once along the - # diagonal rather than for every non-diagonal element. Which means - # equations are written as they are in math. - self.out = -1 * self._M.solve() - return self.out - - def __enter__(self): - """ - When entered the Simulation object will create the matrix to be used in - the simulation. - - This is where the matrix gets allocated and constructed. It will expect - that the model structure does not change after this until it has been - exited. - """ - # Set the matrix method we'll be using to run this simulation - self._M = DenseMatrix(self.name) - # Initialising the simulation expects there to be a self._M class that handles the - # matrix build/memory/etc. This must be set before initialising. - self._initialise() - - self._M.construct() - self.initial_fill() - - return self - - def __exit__(self, type_, value, traceback): - self._unbuild() - self.ws.clear() - - self.ws = None - self._M = None - self._Mq = None - self._submatrices = None +# class DenseSimulation(BaseMatrixSimulation): +# def solve(self): +# if not self.model.is_built: +# raise Exception("Model has not been built") + +# # As we put -1 on diagonals we actually solve the negative +# # of the system, so inverse that here... +# # -1 here because we compute the negative of the interferometer +# # matrix. Why? Because the -1 sign only gets applied once along the +# # diagonal rather than for every non-diagonal element. Which means +# # equations are written as they are in math. +# self.out = -1 * self._M.solve() +# return self.out + +# def __enter__(self): +# """ +# When entered the Simulation object will create the matrix to be used in +# the simulation. + +# This is where the matrix gets allocated and constructed. It will expect +# that the model structure does not change after this until it has been +# exited. +# """ +# # Set the matrix method we'll be using to run this simulation +# self._M = DenseMatrix(self.name) +# # Initialising the simulation expects there to be a self._M class that handles the +# # matrix build/memory/etc. This must be set before initialising. +# self._initialise() + +# self._M.construct() +# self.initial_fill() + +# return self + +# def __exit__(self, type_, value, traceback): +# self._unbuild() +# self.ws.clear() + +# self.ws = None +# self._M = None +# self._Mq = None +# self._submatrices = None diff --git a/src/finesse/simulations/digraph.py b/src/finesse/simulations/digraph.py index b5a1081d91634ca086d300dcadb799f14c4ac78a..9ad2946cada5369b5af53cf68a2bfc481fef1d82 100644 --- a/src/finesse/simulations/digraph.py +++ b/src/finesse/simulations/digraph.py @@ -1,636 +1,636 @@ -""" -Sub-module consisting of the :class:`.Simulation` for -performing specific executions of a :class:`.Model`. -""" - -import logging -import numpy as np -import re -import networkx as nx -import copy -import warnings -import contextlib - -from finesse.components.node import NodeType -from collections import defaultdict, OrderedDict - -# from . import base -from finesse.simulations.base import BaseSimulation -from .matrix_digraph import ( - SRE_copy, - SREdense_numeric_inverse, - SREDenseInverter, - SREPresolvedCSC, - DeferredMatrixStore, - MatrixStoreTypes, -) - -LOGGER = logging.getLogger(__name__) - - -def abs_sq(arr): - return arr.real ** 2 + arr.imag ** 2 - - -# @canFreeze -class DigraphSimulationBase(BaseSimulation): - """ - Simulation class for executing configuration models. - - The `Simulation` class uses the :class:`.Model` to build - and fill the sparse matrix and is used as a reference for - detectors to compute outputs to return to the user. - - .. note:: - Instances of this class **cannot** be copied, they are generated - from and operate on a :class:`.Model` object in a particular - built state. - - .. note:: - The module :mod:`pickle` **cannot** be used on instances of this class. - """ - - _submatrix_semantic_map = None - - def print_matrix(self): - raise NotImplementedError() - - def active(self): - return self._submatrix_semantic_map is not None - - def set_source(self, field_idx, vector): - if len(field_idx) == 3: - node, freq_idx, subidx = field_idx - elif len(field_idx) == 2: - node, freq_idx = field_idx - subidx = None - - freq = self.frequencies[freq_idx] - Mnode = (node, freq) - - if subidx is None: - assert vector.shape == (self._M_sizes[Mnode],) - self._M_seq["rhs"].add(Mnode) - self._M_req[Mnode].add("rhs") - # put it into matrix form to multiple with the size-1 'rhs' node - self._M_edges["rhs", Mnode] = DeferredMatrixStore( - ms_type=MatrixStoreTypes.MATRIX, - matrix=vector.reshape(-1, 1), - needs_build=False, - ) - else: - try: - source_vect = self._M_edges["rhs", Mnode] - except KeyError: - source_vect = DeferredMatrixStore( - ms_type=MatrixStoreTypes.MATRIX, - matrix=np.zeros((self._M_sizes[Mnode], 1), dtype=complex), - needs_build=False, - ) - self._M_seq["rhs"].add(Mnode) - self._M_req[Mnode].add("rhs") - self._M_edges["rhs", Mnode] = source_vect - source_vect.matrix[subidx] = vector - return - - @contextlib.contextmanager - def component_edge_fill(self, comp, edgestr, f1, f2, conjugate=False): - """ - Returns a dictionary of all the submatrices an element has requested - for different connections it needs. The key is:: - - (element, connection_name, ifreq, ofreq) - - Parameters - ---------- - element - The object reference that created the requests. - connection_name - The string name given to the connection. - ifreq - Iincoming frequency. - ofreq - Outgoing frequency. - - Returns - ------- - dict - """ - key = (comp, edgestr, f1, f2) - semantic_dict = self._submatrix_semantic_map[key] - Mnode_fr, Mnode_to = semantic_dict["iodx_semantic"] - is_diagonal = semantic_dict["is_diagonal"] - if Mnode_to in self._M_seq[Mnode_fr]: - matrix = self._M_edges[Mnode_fr, Mnode_to] - else: - self._M_seq[Mnode_fr].add(Mnode_to) - self._M_req[Mnode_to].add(Mnode_fr) - if not is_diagonal: - shape = (self._M_sizes[Mnode_to], self._M_sizes[Mnode_fr]) - matrix = DeferredMatrixStore( - ms_type=MatrixStoreTypes.MATRIX, - matrix=np.empty(shape, dtype=complex), - needs_build=False, - ) - else: - shape = (self._M_sizes[Mnode_to],) - assert self._M_sizes[Mnode_to] == self._M_sizes[Mnode_fr] - # builds only the diagonal. The matrix simplifier is responsible - # for applying the correct matrix mul rules between - # "diagonal matrices" (which are 1d arrays), scalars, and 2d matrices - matrix = DeferredMatrixStore( - ms_type=MatrixStoreTypes.DIAGONAL, - matrix=np.empty(shape, dtype=complex), - needs_build=False, - ) - self._M_edges[Mnode_fr, Mnode_to] = matrix - # assumes that the getter will be filling this matrix - yield matrix - # do some transformations or tagging here - if conjugate: - matrix[:].imag *= -1 - return - - def component_edge_get(self, comp, edgestr, f1, f2): - """ - Returns a matrix for the submatrix an element has requested - for different connections it needs. The key is:: - - (element, connection_name, ifreq, ofreq) - - Parameters - ---------- - element : finesse.component.Connector - Is the object reference that created the requests - connection_name : str - String name given to the connection - ifreq : finesse.Frequency - Incoming frequency - ofreq : finesse.Frequency - Outgoing frequency - - this is a context manager, to be used like - with sim.component_edge_fill(key) as mat: - mat[:] = computations - - Returns - ------- - matrix - """ - key = (comp, edgestr, f1, f2) - semantic_dict = self._submatrix_semantic_map[key] - Mnode_fr, Mnode_to = semantic_dict["iodx_semantic"] - return self._M_edges[Mnode_fr, Mnode_to] - - def __enter__(self): - """ - When entered the Simulation object will create the matrix to be used in - the simulation. - - This is where the matrix gets allocated and constructed. It will expect - that the model structure does not change after this until it has been - exited. - """ - - self._M_sizes = dict() - self._M_seq = defaultdict(set) - self._M_req = defaultdict(set) - self._M_edges = dict() - self._submatrix_semantic_map = OrderedDict() - - # add in some edges for the RHS sources - self._M_sizes["rhs"] = 1 - self._M_seq["rhs"].add("rhs") - self._M_req["rhs"].add("rhs") - self._M_edges["rhs", "rhs"] = DeferredMatrixStore( - ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False - ) - - self._initialise() - - self.initial_fill() - - # TODO ddb: this should be chcking audio and that we have quantum calculations going on - # TODO ddb: create sparse noise source matrix - # TODO ddb: noise calculations probably more generic than just quantum - return self - - def _initialise_submatrices(self): - from finesse.components import FrequencyGenerator, Space - - for n, node_inf in self._node_info.items(): - # Optical nodes have HOM information, mech/elec don't, all have frequencies though - if n.type is NodeType.OPTICAL: - Nsm = node_inf["nfreqs"] - # Neq = node_inf.nhoms - for fidx in range(Nsm): - freq = self.frequencies[fidx] - Mnode_key = (n, freq) - self._M_seq[Mnode_key].add(Mnode_key) - self._M_req[Mnode_key].add(Mnode_key) - self._M_sizes[Mnode_key] = node_inf["nhoms"] - self._M_edges[Mnode_key, Mnode_key] = DeferredMatrixStore( - ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False - ) - - elif n.type is NodeType.MECHANICAL or n.type is NodeType.ELECTRICAL: - freq = self.frequencies[0] - Mnode_key = (n, freq) - self._M_seq[Mnode_key].add(Mnode_key) - self._M_req[Mnode_key].add(Mnode_key) - self._M_sizes[Mnode_key] = 1 - self._M_edges[Mnode_key, Mnode_key] = DeferredMatrixStore( - ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False - ) - else: - raise Exception("Not handled") - - # Loop over every edge in the network which represents a bunch of - # connections between frequencies and HOMs between two nodes - _done = {} - for owner in self._edge_owners: - if owner in _done: - continue - - couples_f = isinstance(owner, FrequencyGenerator) - - # For each connection this element wants... - for name in owner._registered_connections: - nio = tuple( - (owner.nodes[_] for _ in owner._registered_connections[name]) - ) # convert weak ref (input, output) - # If we are a carrier matrix only compute optics, no AC - if not self.is_audio: - if ( - nio[0].type is not NodeType.OPTICAL - or nio[1].type is not NodeType.OPTICAL - ): - continue - - # Loop over all the frequencies we can couple between and add - # submatrixes to the overall model - for ifreq in self.frequencies: - for ofreq in self.frequencies: - # for k in range(Nhom): - # For each input and output frequency check if our - # element wants to couple them at this - if couples_f and not owner._couples_frequency( - self, name, ifreq, ofreq - ): - continue - elif not couples_f and ifreq != ofreq: - # If it doesn't couple frequencies and the - # frequencies are different then ignore - continue - - iodx_semantic = [] # submatrix indices - iodx = [] # submatrix indices - tags = [] # descriptive naming tags for submatrix key - key_name = re.sub(r"^[^.]*\.", "", name) - key_name = re.sub(r">[^.]*\.", ">", key_name) - key = [owner, key_name] - - # Get simulation unique indices for submatrix - # position. How we get these depends on the type of - # the nodes involved - for freq, node in zip((ifreq, ofreq), nio): - if node.type is NodeType.OPTICAL: - iodx_semantic.append((node, freq)) - iodx.append(self.findex(node, freq.index)) - tags.append(freq.name) - key.append(freq) - else: - # Mechanical and electrical don't have multiple - # freqs, so always use the zeroth frequency index - iodx_semantic.append((node, self.frequencies[0])) - iodx.append(self.findex(node, self.frequencies[0])) - tags.append("audio") - key.append(None) - - assert len(iodx) == 2 - assert len(key) == 4 - - # TODO, there should be a better registry for - # diagonals support - if isinstance(owner, Space): - is_diagonal = True - else: - is_diagonal = False - - if tuple(key) not in self._submatrix_semantic_map: - self._submatrix_semantic_map[tuple(key)] = dict( - iodx_semantic=iodx_semantic, is_diagonal=is_diagonal, - ) - _done[owner] = True - return - - def __exit__(self, type_, value, traceback): - self._unbuild() - self._submatrix_semantic_map = None - - _first_solve = True - - def solve(self): - if not nx.is_frozen(self.model.network): - raise Exception("Model has not been built") - - SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) - seq, req, edges = SRE - - # if self._first_solve: - # from .matrix.SRE_graphs import SRE_graph - # SRE_graph('test_network.pdf', SRE) - # solve into out_semantic, but also create the usual "out" indexed vector - # so that output requests elsewhere in the code can work - - SREinv = SREdense_numeric_inverse( - seq, req, edges, inputs=["rhs"], outputs=seq.keys(), - ) - - seq_i, req_i, edges_i = SREinv - out_semantic = dict() - for Mnode in seq_i["rhs"]: - edges_i["rhs", Mnode] - out_semantic[Mnode] = edges_i["rhs", Mnode] - - self._first_solve = False - self.out_semantic = out_semantic - - # TODO, return the correct output type - self.out = out_semantic - return - - def _clear_rhs(self): - for Mnode_to in self._M_seq["rhs"]: - self._M_req[Mnode_to].remove("rhs") - del self._M_edges["rhs", Mnode_to] - self._M_seq["rhs"].clear() - - # add the self-edge back in - self._M_seq["rhs"].add("rhs") - self._M_req["rhs"].add("rhs") - self._M_edges["rhs", "rhs"] = DeferredMatrixStore( - ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False - ) - return - - def get_DC_out(self, node, freq=0, hom=0): - Mnode = (node, self.frequencies[freq]) - outvec = self.out_semantic[Mnode].matrix - return outvec[hom] - - -class DigraphSimulation(DigraphSimulationBase): - _first_solve = True - - def solve(self): - if not nx.is_frozen(self.model.network): - raise Exception("Model has not been built") - - SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) - seq, req, edges = SRE - - SREinv = SREDenseInverter( - seq, req, edges, sizes=self._M_sizes, inputs=["rhs"], outputs=seq.keys(), - ) - # if self._first_solve: - # SREinv.plot_graph('test_network_0.pdf') - # solve into out_semantic, but also create the usual "out" indexed vector - # so that output requests elsewhere in the code can work - N = 0 - while SREinv.simplify_trivial(): - N += 1 - # if self._first_solve: - # SREinv.plot_graph('test_network_{}.pdf'.format(N)) - N += 1 - # if self._first_solve: - # SREinv.plot_graph('test_network_{}.pdf'.format(N)) - seq_i, req_i, edges_i = SREinv.numeric_finish() - out_semantic = dict() - for Mnode in seq_i["rhs"]: - edges_i["rhs", Mnode] - out_semantic[Mnode] = edges_i["rhs", Mnode] - - self._first_solve = False - self.out_semantic = out_semantic - - # TODO, return the correct output type - self.out = out_semantic - - -class DigraphSimulationReducing(DigraphSimulationBase): - _first_solve = True - _last_edges = None - _presolve = None - - def solve(self): - if not nx.is_frozen(self.model.network): - raise Exception("Model has not been built") - - SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) - seq, req, edges = SRE - - # fill has just happened, so determine changing edges - edges_changed = dict() - if self._last_edges is not None: - for ekey, edge in edges.items(): - edge_last = self._last_edges[ekey] - if np.any(abs_sq(edge - edge_last) > 1e-12): - edges_changed[ekey] = edge - - # because the fill is in-place, one must copy all of the matrices, - # slowslow - last_edges = dict() - for ekey, Medge in edges.items(): - last_edges[ekey] = np.copy(Medge) - - self._last_edges = last_edges - - if self._presolve is not None: - if set(self._presolve.edges_changing.keys()) != set(edges_changed.keys()): - # clear the presolved node - self._presolve = None - - # print(edges_changed.keys()) - - if self._presolve is None: - # if self._first_solve: - # from .matrix.SRE_graphs import SRE_graph - # SRE_graph('test_network.pdf', SRE) - # solve into out_semantic, but also create the usual "out" indexed vector - # so that output requests elsewhere in the code can work - - SREinv = SREDenseInverter( - seq, - req, - edges, - sizes=self._M_sizes, - inputs=["rhs"], - outputs=seq.keys(), - ) - SREinv.set_changing_edges(edges_changed) - - while SREinv.simplify_trivial(): - pass - - self._presolve = SREinv - - SREinv = copy.copy(self._presolve) - SREinv.reinject_changed_edges(edges_changed) - while SREinv.simplify_trivial(): - pass - - seq_i, req_i, edges_i = SREinv.numeric_finish() - out_semantic = dict() - for Mnode in seq_i["rhs"]: - out_semantic[Mnode] = edges_i["rhs", Mnode] - - self._first_solve = False - self.out_semantic = out_semantic - - # TODO, return the correct output type - self.out = out_semantic - return - - -class DigraphSimulationReducingCSC(DigraphSimulationBase): - _first_solve = True - _last_edges = None - _presolve = None - _use_KLU_offsets = False - _check_edge_changes = False - - def solve(self): - if not nx.is_frozen(self.model.network): - raise Exception("Model has not been built") - - SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) - seq, req, edges = SRE - - if self._check_edge_changes: - # fill has just happened, so determine changing edges - edges_changed_set = set() - if self._last_edges is not None: - for ekey, edge in edges.items(): - edge_last = self._last_edges[ekey] - if np.any(abs_sq(edge - edge_last) > 1e-12): - edges_changed_set.add(ekey) - else: - edges_changed_set = None - - if edges_changed_set is not None: - edges_changed_big = self.get_changing_edges() - if set(edges_changed_big) - set(edges_changed_set): - warnings.warn("Edge changed lists are inefficient") - if set(edges_changed_set) - set(edges_changed_big): - warnings.warn("Edges changed lists are inconsistent!") - else: - edges_changed_set = self.get_changing_edges() - - edges_changed = {} - for ekey in edges_changed_set: - edges_changed[ekey] = edges[ekey] - - # because the fill is in-place, one must copy all of the matrices, - # slowslow - last_edges = dict() - for ekey, Medge in edges.items(): - last_edges[ekey] = np.copy(Medge) - - self._last_edges = last_edges - - if self._presolve is not None: - if self._presolve.edges != set(edges_changed.keys()): - # clear the presolved node - # self._presolve = None - pass - - if edges_changed: - # if self._first_solve: - # from .matrix.SRE_graphs import SRE_graph - # SRE_graph('test_network.pdf', SRE) - # solve into out_semantic, but also create the usual "out" indexed vector - # so that output requests elsewhere in the code can work - - if self._presolve is None: - SREinv = SREDenseInverter( - seq, - req, - edges, - sizes=self._M_sizes, - inputs=["rhs"], - outputs=set(seq.keys()), - ) - SREinv.set_changing_edges(edges_changed) - - while SREinv.simplify_trivial(): - pass - if self._use_KLU_offsets: - offsets = {} - idx_max = 0 - for n, node_inf in self._node_info.items(): - Nsm = node_inf["nfreqs"] - Neq = node_inf["nhoms"] - for fidx in range(Nsm): - freq = self.frequencies[fidx] - Mnode = (n, freq) - idx = node_inf["rhs_index"] + fidx * Neq - offsets[Mnode] = idx - idx_max = max(idx, node_inf["rhs_index"] + (fidx + 1) * Neq) - offsets["rhs"] = idx_max - else: - offsets = None - - self._presolve = SREPresolvedCSC.from_inverter( - SREinv, - # inputs_offsets = dict(rhs = 0), - outputs_offsets=offsets, - # internal_offsets = dict(offsets), - ) - - # TODO, actually do the reduce here - self._presolve.update_solve(edges_changed) - self.out_semantic = None - else: - SREinv = SREDenseInverter( - seq, - req, - edges, - sizes=self._M_sizes, - inputs=["rhs"], - outputs=set(seq.keys()), - ) - # if self._first_solve: - # SREinv.plot_graph('test_network_0.pdf') - # solve into out_semantic, but also create the usual "out" indexed vector - # so that output requests elsewhere in the code can work - N = 0 - while SREinv.simplify_trivial(): - N += 1 - # if self._first_solve: - # SREinv.plot_graph('test_network_{}.pdf'.format(N)) - N += 1 - # if self._first_solve: - # SREinv.plot_graph('test_network_{}.pdf'.format(N)) - seq_i, req_i, edges_i = SREinv.numeric_finish() - out_semantic = dict() - for Mnode in seq_i["rhs"]: - edges_i["rhs", Mnode] - out_semantic[Mnode] = edges_i["rhs", Mnode] - - self.out_semantic = out_semantic - - self._first_solve = False - # TODO, return the correct output type - self.out = None - return - - def get_DC_out(self, node, freq=0, hom=0): - if self._presolve is None: - Mnode = (node, self.frequencies[freq]) - outvec = self.out_semantic[Mnode].matrix - return outvec[hom] - else: - Mnode = (node, self.frequencies[freq]) - Midx = self._presolve.offsets_out[Mnode] - return self._presolve.out[Midx + hom] +# """ +# Sub-module consisting of the :class:`.Simulation` for +# performing specific executions of a :class:`.Model`. +# """ + +# import logging +# import numpy as np +# import re +# import networkx as nx +# import copy +# import warnings +# import contextlib + +# from finesse.components.node import NodeType +# from collections import defaultdict, OrderedDict + +# # from . import base +# from finesse.simulations.base import BaseSimulation +# from .matrix_digraph import ( +# SRE_copy, +# SREdense_numeric_inverse, +# SREDenseInverter, +# SREPresolvedCSC, +# DeferredMatrixStore, +# MatrixStoreTypes, +# ) + +# LOGGER = logging.getLogger(__name__) + + +# def abs_sq(arr): +# return arr.real ** 2 + arr.imag ** 2 + + +# # @canFreeze +# class DigraphSimulationBase(BaseSimulation): +# """ +# Simulation class for executing configuration models. + +# The `Simulation` class uses the :class:`.Model` to build +# and fill the sparse matrix and is used as a reference for +# detectors to compute outputs to return to the user. + +# .. note:: +# Instances of this class **cannot** be copied, they are generated +# from and operate on a :class:`.Model` object in a particular +# built state. + +# .. note:: +# The module :mod:`pickle` **cannot** be used on instances of this class. +# """ + +# _submatrix_semantic_map = None + +# def print_matrix(self): +# raise NotImplementedError() + +# def active(self): +# return self._submatrix_semantic_map is not None + +# def set_source(self, field_idx, vector): +# if len(field_idx) == 3: +# node, freq_idx, subidx = field_idx +# elif len(field_idx) == 2: +# node, freq_idx = field_idx +# subidx = None + +# freq = self.frequencies[freq_idx] +# Mnode = (node, freq) + +# if subidx is None: +# assert vector.shape == (self._M_sizes[Mnode],) +# self._M_seq["rhs"].add(Mnode) +# self._M_req[Mnode].add("rhs") +# # put it into matrix form to multiple with the size-1 'rhs' node +# self._M_edges["rhs", Mnode] = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.MATRIX, +# matrix=vector.reshape(-1, 1), +# needs_build=False, +# ) +# else: +# try: +# source_vect = self._M_edges["rhs", Mnode] +# except KeyError: +# source_vect = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.MATRIX, +# matrix=np.zeros((self._M_sizes[Mnode], 1), dtype=complex), +# needs_build=False, +# ) +# self._M_seq["rhs"].add(Mnode) +# self._M_req[Mnode].add("rhs") +# self._M_edges["rhs", Mnode] = source_vect +# source_vect.matrix[subidx] = vector +# return + +# @contextlib.contextmanager +# def component_edge_fill(self, comp, edgestr, f1, f2, conjugate=False): +# """ +# Returns a dictionary of all the submatrices an element has requested +# for different connections it needs. The key is:: + +# (element, connection_name, ifreq, ofreq) + +# Parameters +# ---------- +# element +# The object reference that created the requests. +# connection_name +# The string name given to the connection. +# ifreq +# Iincoming frequency. +# ofreq +# Outgoing frequency. + +# Returns +# ------- +# dict +# """ +# key = (comp, edgestr, f1, f2) +# semantic_dict = self._submatrix_semantic_map[key] +# Mnode_fr, Mnode_to = semantic_dict["iodx_semantic"] +# is_diagonal = semantic_dict["is_diagonal"] +# if Mnode_to in self._M_seq[Mnode_fr]: +# matrix = self._M_edges[Mnode_fr, Mnode_to] +# else: +# self._M_seq[Mnode_fr].add(Mnode_to) +# self._M_req[Mnode_to].add(Mnode_fr) +# if not is_diagonal: +# shape = (self._M_sizes[Mnode_to], self._M_sizes[Mnode_fr]) +# matrix = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.MATRIX, +# matrix=np.empty(shape, dtype=complex), +# needs_build=False, +# ) +# else: +# shape = (self._M_sizes[Mnode_to],) +# assert self._M_sizes[Mnode_to] == self._M_sizes[Mnode_fr] +# # builds only the diagonal. The matrix simplifier is responsible +# # for applying the correct matrix mul rules between +# # "diagonal matrices" (which are 1d arrays), scalars, and 2d matrices +# matrix = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.DIAGONAL, +# matrix=np.empty(shape, dtype=complex), +# needs_build=False, +# ) +# self._M_edges[Mnode_fr, Mnode_to] = matrix +# # assumes that the getter will be filling this matrix +# yield matrix +# # do some transformations or tagging here +# if conjugate: +# matrix[:].imag *= -1 +# return + +# def component_edge_get(self, comp, edgestr, f1, f2): +# """ +# Returns a matrix for the submatrix an element has requested +# for different connections it needs. The key is:: + +# (element, connection_name, ifreq, ofreq) + +# Parameters +# ---------- +# element : finesse.component.Connector +# Is the object reference that created the requests +# connection_name : str +# String name given to the connection +# ifreq : finesse.Frequency +# Incoming frequency +# ofreq : finesse.Frequency +# Outgoing frequency + +# this is a context manager, to be used like +# with sim.component_edge_fill(key) as mat: +# mat[:] = computations + +# Returns +# ------- +# matrix +# """ +# key = (comp, edgestr, f1, f2) +# semantic_dict = self._submatrix_semantic_map[key] +# Mnode_fr, Mnode_to = semantic_dict["iodx_semantic"] +# return self._M_edges[Mnode_fr, Mnode_to] + +# def __enter__(self): +# """ +# When entered the Simulation object will create the matrix to be used in +# the simulation. + +# This is where the matrix gets allocated and constructed. It will expect +# that the model structure does not change after this until it has been +# exited. +# """ + +# self._M_sizes = dict() +# self._M_seq = defaultdict(set) +# self._M_req = defaultdict(set) +# self._M_edges = dict() +# self._submatrix_semantic_map = OrderedDict() + +# # add in some edges for the RHS sources +# self._M_sizes["rhs"] = 1 +# self._M_seq["rhs"].add("rhs") +# self._M_req["rhs"].add("rhs") +# self._M_edges["rhs", "rhs"] = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False +# ) + +# self._initialise() + +# self.initial_fill() + +# # TODO ddb: this should be chcking audio and that we have quantum calculations going on +# # TODO ddb: create sparse noise source matrix +# # TODO ddb: noise calculations probably more generic than just quantum +# return self + +# def _initialise_submatrices(self): +# from finesse.components import FrequencyGenerator, Space + +# for n, node_inf in self._node_info.items(): +# # Optical nodes have HOM information, mech/elec don't, all have frequencies though +# if n.type is NodeType.OPTICAL: +# Nsm = node_inf["nfreqs"] +# # Neq = node_inf.nhoms +# for fidx in range(Nsm): +# freq = self.frequencies[fidx] +# Mnode_key = (n, freq) +# self._M_seq[Mnode_key].add(Mnode_key) +# self._M_req[Mnode_key].add(Mnode_key) +# self._M_sizes[Mnode_key] = node_inf["nhoms"] +# self._M_edges[Mnode_key, Mnode_key] = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False +# ) + +# elif n.type is NodeType.MECHANICAL or n.type is NodeType.ELECTRICAL: +# freq = self.frequencies[0] +# Mnode_key = (n, freq) +# self._M_seq[Mnode_key].add(Mnode_key) +# self._M_req[Mnode_key].add(Mnode_key) +# self._M_sizes[Mnode_key] = 1 +# self._M_edges[Mnode_key, Mnode_key] = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False +# ) +# else: +# raise Exception("Not handled") + +# # Loop over every edge in the network which represents a bunch of +# # connections between frequencies and HOMs between two nodes +# _done = {} +# for owner in self._edge_owners: +# if owner in _done: +# continue + +# couples_f = isinstance(owner, FrequencyGenerator) + +# # For each connection this element wants... +# for name in owner._registered_connections: +# nio = tuple( +# (owner.nodes[_] for _ in owner._registered_connections[name]) +# ) # convert weak ref (input, output) +# # If we are a carrier matrix only compute optics, no AC +# if not self.is_audio: +# if ( +# nio[0].type is not NodeType.OPTICAL +# or nio[1].type is not NodeType.OPTICAL +# ): +# continue + +# # Loop over all the frequencies we can couple between and add +# # submatrixes to the overall model +# for ifreq in self.frequencies: +# for ofreq in self.frequencies: +# # for k in range(Nhom): +# # For each input and output frequency check if our +# # element wants to couple them at this +# if couples_f and not owner._couples_frequency( +# self, name, ifreq, ofreq +# ): +# continue +# elif not couples_f and ifreq != ofreq: +# # If it doesn't couple frequencies and the +# # frequencies are different then ignore +# continue + +# iodx_semantic = [] # submatrix indices +# iodx = [] # submatrix indices +# tags = [] # descriptive naming tags for submatrix key +# key_name = re.sub(r"^[^.]*\.", "", name) +# key_name = re.sub(r">[^.]*\.", ">", key_name) +# key = [owner, key_name] + +# # Get simulation unique indices for submatrix +# # position. How we get these depends on the type of +# # the nodes involved +# for freq, node in zip((ifreq, ofreq), nio): +# if node.type is NodeType.OPTICAL: +# iodx_semantic.append((node, freq)) +# iodx.append(self.findex(node, freq.index)) +# tags.append(freq.name) +# key.append(freq) +# else: +# # Mechanical and electrical don't have multiple +# # freqs, so always use the zeroth frequency index +# iodx_semantic.append((node, self.frequencies[0])) +# iodx.append(self.findex(node, self.frequencies[0])) +# tags.append("audio") +# key.append(None) + +# assert len(iodx) == 2 +# assert len(key) == 4 + +# # TODO, there should be a better registry for +# # diagonals support +# if isinstance(owner, Space): +# is_diagonal = True +# else: +# is_diagonal = False + +# if tuple(key) not in self._submatrix_semantic_map: +# self._submatrix_semantic_map[tuple(key)] = dict( +# iodx_semantic=iodx_semantic, is_diagonal=is_diagonal, +# ) +# _done[owner] = True +# return + +# def __exit__(self, type_, value, traceback): +# self._unbuild() +# self._submatrix_semantic_map = None + +# _first_solve = True + +# def solve(self): +# if not nx.is_frozen(self.model.network): +# raise Exception("Model has not been built") + +# SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) +# seq, req, edges = SRE + +# # if self._first_solve: +# # from .matrix.SRE_graphs import SRE_graph +# # SRE_graph('test_network.pdf', SRE) +# # solve into out_semantic, but also create the usual "out" indexed vector +# # so that output requests elsewhere in the code can work + +# SREinv = SREdense_numeric_inverse( +# seq, req, edges, inputs=["rhs"], outputs=seq.keys(), +# ) + +# seq_i, req_i, edges_i = SREinv +# out_semantic = dict() +# for Mnode in seq_i["rhs"]: +# edges_i["rhs", Mnode] +# out_semantic[Mnode] = edges_i["rhs", Mnode] + +# self._first_solve = False +# self.out_semantic = out_semantic + +# # TODO, return the correct output type +# self.out = out_semantic +# return + +# def _clear_rhs(self): +# for Mnode_to in self._M_seq["rhs"]: +# self._M_req[Mnode_to].remove("rhs") +# del self._M_edges["rhs", Mnode_to] +# self._M_seq["rhs"].clear() + +# # add the self-edge back in +# self._M_seq["rhs"].add("rhs") +# self._M_req["rhs"].add("rhs") +# self._M_edges["rhs", "rhs"] = DeferredMatrixStore( +# ms_type=MatrixStoreTypes.SCALAR, matrix=-1, needs_build=False +# ) +# return + +# def get_DC_out(self, node, freq=0, hom=0): +# Mnode = (node, self.frequencies[freq]) +# outvec = self.out_semantic[Mnode].matrix +# return outvec[hom] + + +# class DigraphSimulation(DigraphSimulationBase): +# _first_solve = True + +# def solve(self): +# if not nx.is_frozen(self.model.network): +# raise Exception("Model has not been built") + +# SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) +# seq, req, edges = SRE + +# SREinv = SREDenseInverter( +# seq, req, edges, sizes=self._M_sizes, inputs=["rhs"], outputs=seq.keys(), +# ) +# # if self._first_solve: +# # SREinv.plot_graph('test_network_0.pdf') +# # solve into out_semantic, but also create the usual "out" indexed vector +# # so that output requests elsewhere in the code can work +# N = 0 +# while SREinv.simplify_trivial(): +# N += 1 +# # if self._first_solve: +# # SREinv.plot_graph('test_network_{}.pdf'.format(N)) +# N += 1 +# # if self._first_solve: +# # SREinv.plot_graph('test_network_{}.pdf'.format(N)) +# seq_i, req_i, edges_i = SREinv.numeric_finish() +# out_semantic = dict() +# for Mnode in seq_i["rhs"]: +# edges_i["rhs", Mnode] +# out_semantic[Mnode] = edges_i["rhs", Mnode] + +# self._first_solve = False +# self.out_semantic = out_semantic + +# # TODO, return the correct output type +# self.out = out_semantic + + +# class DigraphSimulationReducing(DigraphSimulationBase): +# _first_solve = True +# _last_edges = None +# _presolve = None + +# def solve(self): +# if not nx.is_frozen(self.model.network): +# raise Exception("Model has not been built") + +# SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) +# seq, req, edges = SRE + +# # fill has just happened, so determine changing edges +# edges_changed = dict() +# if self._last_edges is not None: +# for ekey, edge in edges.items(): +# edge_last = self._last_edges[ekey] +# if np.any(abs_sq(edge - edge_last) > 1e-12): +# edges_changed[ekey] = edge + +# # because the fill is in-place, one must copy all of the matrices, +# # slowslow +# last_edges = dict() +# for ekey, Medge in edges.items(): +# last_edges[ekey] = np.copy(Medge) + +# self._last_edges = last_edges + +# if self._presolve is not None: +# if set(self._presolve.edges_changing.keys()) != set(edges_changed.keys()): +# # clear the presolved node +# self._presolve = None + +# # print(edges_changed.keys()) + +# if self._presolve is None: +# # if self._first_solve: +# # from .matrix.SRE_graphs import SRE_graph +# # SRE_graph('test_network.pdf', SRE) +# # solve into out_semantic, but also create the usual "out" indexed vector +# # so that output requests elsewhere in the code can work + +# SREinv = SREDenseInverter( +# seq, +# req, +# edges, +# sizes=self._M_sizes, +# inputs=["rhs"], +# outputs=seq.keys(), +# ) +# SREinv.set_changing_edges(edges_changed) + +# while SREinv.simplify_trivial(): +# pass + +# self._presolve = SREinv + +# SREinv = copy.copy(self._presolve) +# SREinv.reinject_changed_edges(edges_changed) +# while SREinv.simplify_trivial(): +# pass + +# seq_i, req_i, edges_i = SREinv.numeric_finish() +# out_semantic = dict() +# for Mnode in seq_i["rhs"]: +# out_semantic[Mnode] = edges_i["rhs", Mnode] + +# self._first_solve = False +# self.out_semantic = out_semantic + +# # TODO, return the correct output type +# self.out = out_semantic +# return + + +# class DigraphSimulationReducingCSC(DigraphSimulationBase): +# _first_solve = True +# _last_edges = None +# _presolve = None +# _use_KLU_offsets = False +# _check_edge_changes = False + +# def solve(self): +# if not nx.is_frozen(self.model.network): +# raise Exception("Model has not been built") + +# SRE = SRE_copy((self._M_seq, self._M_req, self._M_edges,)) +# seq, req, edges = SRE + +# if self._check_edge_changes: +# # fill has just happened, so determine changing edges +# edges_changed_set = set() +# if self._last_edges is not None: +# for ekey, edge in edges.items(): +# edge_last = self._last_edges[ekey] +# if np.any(abs_sq(edge - edge_last) > 1e-12): +# edges_changed_set.add(ekey) +# else: +# edges_changed_set = None + +# if edges_changed_set is not None: +# edges_changed_big = self.get_changing_edges() +# if set(edges_changed_big) - set(edges_changed_set): +# warnings.warn("Edge changed lists are inefficient") +# if set(edges_changed_set) - set(edges_changed_big): +# warnings.warn("Edges changed lists are inconsistent!") +# else: +# edges_changed_set = self.get_changing_edges() + +# edges_changed = {} +# for ekey in edges_changed_set: +# edges_changed[ekey] = edges[ekey] + +# # because the fill is in-place, one must copy all of the matrices, +# # slowslow +# last_edges = dict() +# for ekey, Medge in edges.items(): +# last_edges[ekey] = np.copy(Medge) + +# self._last_edges = last_edges + +# if self._presolve is not None: +# if self._presolve.edges != set(edges_changed.keys()): +# # clear the presolved node +# # self._presolve = None +# pass + +# if edges_changed: +# # if self._first_solve: +# # from .matrix.SRE_graphs import SRE_graph +# # SRE_graph('test_network.pdf', SRE) +# # solve into out_semantic, but also create the usual "out" indexed vector +# # so that output requests elsewhere in the code can work + +# if self._presolve is None: +# SREinv = SREDenseInverter( +# seq, +# req, +# edges, +# sizes=self._M_sizes, +# inputs=["rhs"], +# outputs=set(seq.keys()), +# ) +# SREinv.set_changing_edges(edges_changed) + +# while SREinv.simplify_trivial(): +# pass +# if self._use_KLU_offsets: +# offsets = {} +# idx_max = 0 +# for n, node_inf in self._node_info.items(): +# Nsm = node_inf["nfreqs"] +# Neq = node_inf["nhoms"] +# for fidx in range(Nsm): +# freq = self.frequencies[fidx] +# Mnode = (n, freq) +# idx = node_inf["rhs_index"] + fidx * Neq +# offsets[Mnode] = idx +# idx_max = max(idx, node_inf["rhs_index"] + (fidx + 1) * Neq) +# offsets["rhs"] = idx_max +# else: +# offsets = None + +# self._presolve = SREPresolvedCSC.from_inverter( +# SREinv, +# # inputs_offsets = dict(rhs = 0), +# outputs_offsets=offsets, +# # internal_offsets = dict(offsets), +# ) + +# # TODO, actually do the reduce here +# self._presolve.update_solve(edges_changed) +# self.out_semantic = None +# else: +# SREinv = SREDenseInverter( +# seq, +# req, +# edges, +# sizes=self._M_sizes, +# inputs=["rhs"], +# outputs=set(seq.keys()), +# ) +# # if self._first_solve: +# # SREinv.plot_graph('test_network_0.pdf') +# # solve into out_semantic, but also create the usual "out" indexed vector +# # so that output requests elsewhere in the code can work +# N = 0 +# while SREinv.simplify_trivial(): +# N += 1 +# # if self._first_solve: +# # SREinv.plot_graph('test_network_{}.pdf'.format(N)) +# N += 1 +# # if self._first_solve: +# # SREinv.plot_graph('test_network_{}.pdf'.format(N)) +# seq_i, req_i, edges_i = SREinv.numeric_finish() +# out_semantic = dict() +# for Mnode in seq_i["rhs"]: +# edges_i["rhs", Mnode] +# out_semantic[Mnode] = edges_i["rhs", Mnode] + +# self.out_semantic = out_semantic + +# self._first_solve = False +# # TODO, return the correct output type +# self.out = None +# return + +# def get_DC_out(self, node, freq=0, hom=0): +# if self._presolve is None: +# Mnode = (node, self.frequencies[freq]) +# outvec = self.out_semantic[Mnode].matrix +# return outvec[hom] +# else: +# Mnode = (node, self.frequencies[freq]) +# Midx = self._presolve.offsets_out[Mnode] +# return self._presolve.out[Midx + hom] diff --git a/src/finesse/solutions/array.pxd b/src/finesse/solutions/array.pxd index 0bc17e5578cdb9a99c9f1b8934354a9e3a21c017..48e599adaaabd9aed986282ffdb191d1519e1683 100644 --- a/src/finesse/solutions/array.pxd +++ b/src/finesse/solutions/array.pxd @@ -14,9 +14,9 @@ cdef class ArraySolution(BaseSolution): readonly int _axes readonly int _num public list _units - readonly object _model readonly tuple x readonly tuple params + readonly tuple detectors readonly bint masked PyObject** workspaces Py_ssize_t num_workspaces diff --git a/src/finesse/solutions/array.pyx b/src/finesse/solutions/array.pyx index 95b9a4c145263102f80a897ddce9f5212a7cc6a6..c58ec2fd5b841e39b88331adbfce352ebac4ac27 100644 --- a/src/finesse/solutions/array.pyx +++ b/src/finesse/solutions/array.pyx @@ -108,11 +108,10 @@ cdef class ArraySolution(BaseSolution): free(self.workspaces) self.workspaces = NULL - def __init__(self, name, parents, model, shape, xs, params): + def __init__(self, name, parents, detector_workspaces, shape, xs, params): cdef Py_ssize_t i super().__init__(name, parents) - self._model = model self._num = -1 self._units = [] self.empty = False # tree drawing fill circle @@ -125,8 +124,10 @@ cdef class ArraySolution(BaseSolution): self._axes = 1 dtypes = [] + self.detectors = tuple(ws.owner for ws in detector_workspaces) - for det in model.detectors: + for ws in detector_workspaces: + det = ws.owner dtypes.append((det.name, det.dtype, det.dtype_shape)) LOGGER.info("Outputting shape=%s dtype=%s", shape, dtypes) @@ -135,7 +136,7 @@ cdef class ArraySolution(BaseSolution): self._outputs = np.zeros(shape, dtype=self._dtype) # Setup the workspace pointers - self.num_workspaces = len(model._detector_workspaces) + self.num_workspaces = len(detector_workspaces) if self.num_workspaces > 0: self.workspaces = calloc(self.num_workspaces, sizeof(PyObject*)) @@ -143,8 +144,8 @@ cdef class ArraySolution(BaseSolution): raise MemoryError() for i in range(self.num_workspaces): - owner = model._detector_workspaces[i].owner - self.workspaces[i] = model._detector_workspaces[i] + owner = detector_workspaces[i].owner + self.workspaces[i] = detector_workspaces[i] Py_XINCREF(self.workspaces[i]) (self.workspaces[i]).flatiter = self._outputs[owner.name].flat diff --git a/tests/validation/cavity_scan.ipynb b/tests/validation/cavity_scan.ipynb index 839dc8646278bc6efdc4be4b1e5aa8db91a549cc..a06c5c7af99376a920ad7379f69ed0f3b95f4736 100644 Binary files a/tests/validation/cavity_scan.ipynb and b/tests/validation/cavity_scan.ipynb differ