From b4d2c72208b3db1daa8fefd5d8d4751d464dd111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Sekav=C4=8Dnik?= Date: Fri, 23 Feb 2024 14:21:12 +0100 Subject: [PATCH] Partial integration of the backends --- .gitignore | 3 + examples/beam_splitter_example.py | 98 +++---------------- quasi/devices/__init__.py | 1 - quasi/devices/beam_splitter.py | 88 ----------------- .../beam_splitters/ideal_beam_splitter.py | 1 - quasi/devices/control/simple_trigger.py | 6 +- quasi/devices/fiber/__init__.py | 1 + .../fiber/documentation/ideal_fiber.md | 0 quasi/devices/fiber/generic_fiber.py | 65 ++++++++++++ quasi/devices/fiber/ideal_fiber.py | 72 ++++++++++++++ quasi/devices/generic_device.py | 15 ++- .../devices/sources/ideal_coherent_source.py | 3 +- .../devices/sources/ideal_n_photon_source.py | 41 +++++--- .../sources/ideal_single_photon_source.py | 3 +- .../sources/ideal_squeezed_photon_source.py | 1 - quasi/devices/variables/float_variable.py | 4 +- quasi/experiment/experiment_manager.py | 36 +++++-- quasi/gui/panels/side_panel.py | 3 + quasi/kernel/__init__.py | 1 + quasi/kernel/fock_kernel/__init__.py | 1 + quasi/kernel/fock_kernel/fock_kernel.py | 55 +++++++++++ quasi/kernel/generic_kernel.py | 30 ++++++ quasi/signals/__init__.py | 2 +- quasi/signals/fock_signal.py | 22 +++++ quasi/signals/generic_float_signal.py | 5 +- quasi/signals/generic_quantum_signal.py | 11 +-- quasi/simulation/__init__.py | 2 +- quasi/simulation/mode_manager.py | 9 +- quasi/simulation/simulation.py | 68 ++++++++++--- 29 files changed, 405 insertions(+), 242 deletions(-) delete mode 100644 quasi/devices/beam_splitter.py create mode 100644 quasi/devices/fiber/__init__.py create mode 100644 quasi/devices/fiber/documentation/ideal_fiber.md create mode 100644 quasi/devices/fiber/generic_fiber.py create mode 100644 quasi/devices/fiber/ideal_fiber.py create mode 100644 quasi/kernel/fock_kernel/__init__.py create mode 100644 quasi/kernel/fock_kernel/fock_kernel.py create mode 100644 quasi/kernel/generic_kernel.py create mode 100644 quasi/signals/fock_signal.py diff --git a/.gitignore b/.gitignore index 5f3c9ee..55a988d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.local_dir.el +.*.el + .DS_Store .idea *.log diff --git a/examples/beam_splitter_example.py b/examples/beam_splitter_example.py index 29fab85..fdd2549 100644 --- a/examples/beam_splitter_example.py +++ b/examples/beam_splitter_example.py @@ -2,92 +2,24 @@ Ideal Beam Splitter experiment example """ -from quasi.simulation import Simulation -from quasi.devices.ideal import BeamSplitter -from quasi.devices.ideal import SinglePhotonSource -from quasi.devices.ideal import SinglePhotonDetector -from quasi.devices.ideal import Fiber -from quasi.devices import connect_ports -from quasi.signals import GenericQuantumSignal +from quasi.simulation import Simulation, SimulationType +from quasi.devices.sources import IdealNPhotonSource +from quasi.devices.control import SimpleTrigger +from quasi.signals import GenericBoolSignal +from quasi.experiment import Experiment -Sim = Simulation() +sim = Simulation() +sim.set_simulation_type(SimulationType.FOCK) -""" ╭───────────╮ - │ DEVICES │ - ╰───────────╯ -""" - -# Single Photon Sources -sps1 = SinglePhotonSource(name="SPS1") -sps2 = SinglePhotonSource(name="SPS2") - - -# Beam Splitter -bs = BeamSplitter(name="BS") - -# Detectors -spd1 = SinglePhotonDetector(name="SPD1") -spd2 = SinglePhotonDetector(name="SPD2") - -# Fibers -fib1 = Fiber(name="FIB1", length=10) -fib2 = Fiber(name="FIB2", length=10) -fib3 = Fiber(name="FIB3", length=10) -fib4 = Fiber(name="FIB4", length=10) - -# Prints a table of devices, registered with the simulation -# Each object instance registers itself with simulation with -# Initiation method -Sim.list_devices() - -""" ╭────────────────────╮ - │ EXPERIMENT SETUP │ - ╰────────────────────╯ -We should create a utility where the devices and connections -can be set up using yaml language -""" - -# Connect the devices, through signals -sig1 = GenericQuantumSignal() -sps1.register_signal(signal=sig1, port_label="OUT") -fib1.register_signal(signal=sig1, port_label="IN") - -sig2 = GenericQuantumSignal() -sps2.register_signal(signal=sig2, port_label="OUT") -fib2.register_signal(signal=sig2, port_label="IN") - -sig3 = GenericQuantumSignal() -fib1.register_signal(signal=sig3, port_label="OUT") -bs.register_signal(signal=sig3, port_label="A") +s1 = IdealNPhotonSource() +s1.set_photon_num(2) -sig4 = GenericQuantumSignal() -fib2.register_signal(signal=sig4, port_label="OUT") -bs.register_signal(signal=sig4, port_label="B") +st = SimpleTrigger() +sig_trigger = GenericBoolSignal() +st.register_signal(signal=sig_trigger, port_label="trigger") -sig5 = GenericQuantumSignal() -bs.register_signal(signal=sig5, port_label="C") -fib3.register_signal(signal=sig5, port_label="IN") +sim.run() -sig6 = GenericQuantumSignal() -bs.register_signal(signal=sig6, port_label="D") -fib4.register_signal(signal=sig6, port_label="IN") - -sig7 = GenericQuantumSignal() -fib3.register_signal(signal=sig7, port_label="OUT") -spd1.register_signal(signal=sig7, port_label="IN") - -sig8 = GenericQuantumSignal() -fib4.register_signal(signal=sig8, port_label="OUT") -spd2.register_signal(signal=sig8, port_label="IN") - -# Registers which devices should be triggered when -# simulation starts -Sim.register_triggers(sps1,sps2) -Sim.list_triggered_devices() -""" ╭────────────────────╮ - │ RUN SIMULATION │ - ╰────────────────────╯ -""" -Sim.set_dimensions(50) -Sim.run() +exp = Experiment.get_instance() +print(exp.state.all_fock_probs()) diff --git a/quasi/devices/__init__.py b/quasi/devices/__init__.py index 273c4b2..bf46eec 100644 --- a/quasi/devices/__init__.py +++ b/quasi/devices/__init__.py @@ -7,5 +7,4 @@ from .generic_device import coordinate_gui #from .port import connect_ports from .port import Port -from .beam_splitter import BeamSplitter from .generic_device import DeviceInformation diff --git a/quasi/devices/beam_splitter.py b/quasi/devices/beam_splitter.py deleted file mode 100644 index a6a81a5..0000000 --- a/quasi/devices/beam_splitter.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Beam Splitter -""" -from quasi.devices.generic_device import GenericDevice -from quasi.devices.generic_device import wait_input_compute -from quasi.devices.generic_device import ensure_output_compute -from quasi.devices.port import Port -from quasi.signals import GenericQuantumSignal -from quasi.extra import Reference - - -_BEAM_SPLITTER_BIB = { - "author": "Chunyu Deng and Mengjia Lu and Yu Sun and Lei Huang and" - "Dongyu Wang and Guohua Hu and Ruohu Zhang and Binfeng Yun and Yiping Cui", - "journal": "Opt. Express", - "keywords": "Coherent communications; Effective refractive index;" - " Extinction ratios; Numerical simulation; Phase shift; Polarization splitters", - "number": 8, - "pages": "11627--11634", - "publisher": "Optica Publishing Group", - "title": "Broadband and compact polarization beam splitter in LNOI;" - " hetero-anisotropic metamaterials", - "volume": 29, - "month": "Apr", - "year": 2021, - "url": "https://opg.optica.org/oe/abstract.cfm?URI=oe-29-8-11627", - "doi": "10.1364/OE.421262", -} -_BEAM_SPLITTER_DOI = "10.1364/OE.421262" - - -class BeamSplitter(GenericDevice): - """ - Simple Beam Splitter Device - """ - - power_average = 0 - power_peak = 0 - reference = Reference(doi=_BEAM_SPLITTER_DOI, bib_dict=_BEAM_SPLITTER_BIB) - - # bandwidth = 100 MHz # how do we add physical units?? - # We could implement some logic to store the phisical parameters for devices - # However I propose that for example in this case we should specify the - # values in the Signal class - # In this case we could account for devices of varying complexity - - ports = { - "A": Port( - label="A", - direction="input", - signal=None, - signal_type=GenericQuantumSignal, - device=None, - ), - "B": Port( - label="B", - direction="input", - signal=None, - signal_type=GenericQuantumSignal, - device=None, - ), - "C": Port( - label="C", - direction="output", - signal=None, - signal_type=GenericQuantumSignal, - device=None, - ), - "D": Port( - label="D", - direction="output", - signal=None, - signal_type=GenericQuantumSignal, - device=None, - ), - } - - @ensure_output_compute - @wait_input_compute - def compute_outpus(self): - """ - Here the output signals should be computed. - --- - @wait_input_compute guarantees, that the signals at input ports - are calculated. - @ensure_output_compute raises an exception if compute has finished, - but output signals compute flag was not set. - """ diff --git a/quasi/devices/beam_splitters/ideal_beam_splitter.py b/quasi/devices/beam_splitters/ideal_beam_splitter.py index 4896588..ea338d8 100644 --- a/quasi/devices/beam_splitters/ideal_beam_splitter.py +++ b/quasi/devices/beam_splitters/ideal_beam_splitter.py @@ -7,7 +7,6 @@ ensure_output_compute) from quasi.devices.port import Port from quasi.signals import (GenericSignal, - QuantumContentType, GenericQuantumSignal) from quasi.gui.icons import icon_list diff --git a/quasi/devices/control/simple_trigger.py b/quasi/devices/control/simple_trigger.py index 97a7c3a..b5f7f3d 100644 --- a/quasi/devices/control/simple_trigger.py +++ b/quasi/devices/control/simple_trigger.py @@ -19,7 +19,7 @@ class SimpleTrigger(GenericDevice): Trigger turns on at the start of simulation. """ ports = { - "T": Port(label="T", + "trigger": Port(label="trigger", direction="output", signal=None, signal_type=GenericBoolSignal, @@ -39,6 +39,6 @@ class SimpleTrigger(GenericDevice): @coordinate_gui @wait_input_compute def compute_outputs(self, *args, **kwargs): - self.ports["T"].signal.set_bool(True) - self.ports["T"].signal.set_computed() + self.ports["trigger"].signal.set_bool(True) + self.ports["trigger"].signal.set_computed() diff --git a/quasi/devices/fiber/__init__.py b/quasi/devices/fiber/__init__.py new file mode 100644 index 0000000..3aa930a --- /dev/null +++ b/quasi/devices/fiber/__init__.py @@ -0,0 +1 @@ +from .ideal_fiber import IdealFiber diff --git a/quasi/devices/fiber/documentation/ideal_fiber.md b/quasi/devices/fiber/documentation/ideal_fiber.md new file mode 100644 index 0000000..e69de29 diff --git a/quasi/devices/fiber/generic_fiber.py b/quasi/devices/fiber/generic_fiber.py new file mode 100644 index 0000000..3ba8e9d --- /dev/null +++ b/quasi/devices/fiber/generic_fiber.py @@ -0,0 +1,65 @@ +""" +This module implements a generic fiber +all fibers must extend this fiber abstraction +""" + +from quasi.devices.generic_device import GenericDevice +from quasi.signals.generic_float_signal import GenericFloatSignal +from quasi.devices import Port +from quasi.signals.generic_quantum_signal import GenericQuantumSignal + +from abc import ABCMeta, ABC + +class EnforcePortsMeta(ABCMeta): + def __init__(cls, name, bases, namespace, **kwargs): + super().__init__(name, bases, namespace) + # Only enforce ports structure on subclasses, not on GenericDevice itself + if bases != (ABC,) and 'GenericDevice' in [base.__name__ for base in bases]: + # Ensure the class has a 'ports' class variable and it's a dictionary + if not hasattr(cls, 'ports') or not isinstance(cls.ports, dict): + raise TypeError(f"{cls.__name__} must have a 'ports' class variable of type dict") + + # Specify the required keys + required_keys = {'length', 'input', 'output'} + # Check if the 'ports' dictionary has all the required keys + if not required_keys.issubset(cls.ports.keys()): + missing_keys = required_keys - cls.ports.keys() + raise TypeError(f"{cls.__name__}'s 'ports' dictionary is missing keys: {missing_keys}") + + +class GenericFiber(GenericDevice, metaclass=EnforcePortsMeta): + """ + Generic Fiber specifies the format for all Fiber Devices + Every Fiber device must extend this abstract base class + """ + + ports = { + "length": Port( + label="control", + direction="length", + signal=None, + signal_type=GenericFloatSignal, + device=None), + "input": Port( + label="input", + direction="input", + signal=None, + signal_type=GenericQuantumSignal, + device=None), + "output": Port( + label="output", + direction="output", + signal=None, + signal_type=GenericQuantumSignal, + device=None), + } + + + def set_length(self, length: float): + """ + Sets the length of the fiber + """ + length_signal = GenericFloatSignal() + length_signal.set_float(length) + self.register_signal(signal=length_signal, port_label="length") + length_signal.set_computed() diff --git a/quasi/devices/fiber/ideal_fiber.py b/quasi/devices/fiber/ideal_fiber.py new file mode 100644 index 0000000..176cdc0 --- /dev/null +++ b/quasi/devices/fiber/ideal_fiber.py @@ -0,0 +1,72 @@ +""" +Ideal Fiber Implementation + ideal fiber has no attenuation, it just adds a delay to the signal + the delay is proportional to the length of the fiber +""" +from typing import Union +from quasi.devices import (GenericDevice, + wait_input_compute, + coordinate_gui, + ensure_output_compute) + +from quasi.devices.fiber.generic_fiber import GenericFiber +from quasi.devices.port import Port +from quasi.signals.generic_float_signal import GenericFloatSignal +from quasi.signals.generic_int_signal import GenericIntSignal +from quasi.signals.generic_quantum_signal import GenericQuantumSignal +from quasi.gui.icons import icon_list +from quasi.simulation import ModeManager + +class IdealFiber(GenericFiber): + """ + Ideal Fiber + - no attenuation + - only time delay + """ + + ports = { + "length": Port( + label="length", + direction="input", # Implement control + signal=None, + signal_type=GenericFloatSignal, + device=None), + "input": Port( + label="input", + direction="input", + signal=None, + signal_type=GenericQuantumSignal, + device=None), + "output": Port( + label="output", + direction="output", + signal=None, + signal_type=GenericQuantumSignal, + device=None), + } + + # Gui Configuration + gui_icon = icon_list.FIBER + gui_tags = ["ideal"] + gui_name = "Ideal Fiber" + gui_documentation = "ideal_fiber.md" + + power_peak = 0 + power_average = 0 + + reference = None + + + @ensure_output_compute + @coordinate_gui + @wait_input_compute + def compute_outputs(self, *args, **kwargs): + """ + Ideal fiber only adds delay to the signal, + without any distortions + """ + self.ports["output"].signal.set_contents( + timestamp=0, + mode_id=self.ports["input"].signal.mode_id + ) + self.ports["output"].signal.set_computed() diff --git a/quasi/devices/generic_device.py b/quasi/devices/generic_device.py index 9a52d9b..f03dccb 100644 --- a/quasi/devices/generic_device.py +++ b/quasi/devices/generic_device.py @@ -7,6 +7,9 @@ from copy import deepcopy from quasi.simulation import Simulation, DeviceInformation +from quasi.signals.generic_signal import GenericSignal +from quasi.devices.port import Port +from quasi.simulation import ModeManager def wait_input_compute(method): @@ -36,6 +39,7 @@ def wrapper(self, *args, **kwargs): return method(self, *args, **kwargs) return wrapper + def coordinate_gui(method): """ Wrapper funciton, informs the gui about the @@ -51,8 +55,6 @@ def wrapper(self, *args, **kwargs): return wrapper - - class GenericDevice(ABC): # pylint: disable=too-few-public-methods """ Generic Device class used to implement every device @@ -69,15 +71,12 @@ def __init__(self, name=None, uid=None): for port in self.ports.keys(): self.ports[port].device = self - - # Regitering the device to the simulation - print(f"NEW DEVICE {uid}") + # Registering the device to the simulation simulation = Simulation.get_instance() ref = DeviceInformation(name=name, obj_ref=self, uid=uid) self.ref = ref simulation.register_device(ref) self.coordinator = None - def register_signal( self, signal: GenericSignal, port_label: str, override: bool = False @@ -93,7 +92,7 @@ def register_signal( f"Port with label {port_label} does not exist." ) from exc - if not port.signal is None: + if port.signal is not None: if not override: raise PortConnectedException( "Signal was already registered for the port\n" @@ -118,7 +117,6 @@ def compute_outputs(self): Output is computed when all of the input signal (COMPUTED is set) """ - @property @abstractmethod def ports(self) -> Dict[str, Type["Port"]]: @@ -152,7 +150,6 @@ def set_coordinator(self, coordinator): this is required to have feedback in the gui """ self.coordinator = coordinator - class NoPortException(Exception): diff --git a/quasi/devices/sources/ideal_coherent_source.py b/quasi/devices/sources/ideal_coherent_source.py index 8fb921a..fff9454 100644 --- a/quasi/devices/sources/ideal_coherent_source.py +++ b/quasi/devices/sources/ideal_coherent_source.py @@ -10,7 +10,6 @@ ensure_output_compute) from quasi.devices.port import Port from quasi.signals import (GenericSignal, - QuantumContentType, GenericBoolSignal, GenericQuantumSignal) @@ -75,6 +74,6 @@ def compute_outputs(self, *args, **kwargs): print(mm.modes[m_id]) self.ports["output"].signal.set_contents( - content_type=QuantumContentType.FOCK, + timestamp=0, mode_id=m_id) self.ports["output"].signal.set_computed() diff --git a/quasi/devices/sources/ideal_n_photon_source.py b/quasi/devices/sources/ideal_n_photon_source.py index 2a24690..430bbcf 100644 --- a/quasi/devices/sources/ideal_n_photon_source.py +++ b/quasi/devices/sources/ideal_n_photon_source.py @@ -9,15 +9,16 @@ ensure_output_compute) from quasi.devices.port import Port from quasi.signals import (GenericSignal, - QuantumContentType, GenericBoolSignal, GenericIntSignal, GenericQuantumSignal) from quasi.gui.icons import icon_list -from quasi.simulation import ModeManager +from quasi.simulation import Simulation, SimulationType, ModeManager + +from quasi.experiment import Experiment +from quasi._math.fock.ops import adagger -from quasi._math.fock.ops import adagger, a class IdealNPhotonSource(GenericDevice): @@ -53,25 +54,37 @@ class IdealNPhotonSource(GenericDevice): power_peak = 0 power_average = 0 - reference = None + def set_photon_num(self, photon_num:int): + photon_num_sig = GenericIntSignal() + photon_num_sig.set_int(photon_num) + self.register_signal(signal=photon_num_sig, port_label="photon_num") + photon_num_sig.set_computed() + + @ensure_output_compute @coordinate_gui @wait_input_compute def compute_outputs(self, *args, **kwargs): + simulation = Simulation.get_instance() + if simulation.simulation_type is SimulationType.FOCK: + self.simulate_fock() + def simulate_fock(self): + """ + Fock Simulation + """ + # Get the Experiment object reference + exp = Experiment.get_instance() + # Get the mode manager mm = ModeManager() - m_id = mm.create_new_mode() - AD = adagger(mm.simulation.dimensions) - A = a(mm.simulation.dimensions) - mode = mm.get_mode(m_id) + mode = mm.create_new_mode() - photon_num = self.ports["photon_num"].signal.contents - for i in range(photon_num): - mode=np.matmul(AD, np.matmul(mode, A)) - mm.modes[m_id]=mode + # Generate the creation operator + ad = adagger(exp.cutoff) + exp.add_operation(ad, [mm.get_mode_index(mode)]) self.ports["output"].signal.set_contents( - content_type=QuantumContentType.FOCK, - mode_id=m_id) + timestamp=0, + mode_id=mode) self.ports["output"].signal.set_computed() diff --git a/quasi/devices/sources/ideal_single_photon_source.py b/quasi/devices/sources/ideal_single_photon_source.py index 7790a75..a9d78e5 100644 --- a/quasi/devices/sources/ideal_single_photon_source.py +++ b/quasi/devices/sources/ideal_single_photon_source.py @@ -9,7 +9,6 @@ ensure_output_compute) from quasi.devices.port import Port from quasi.signals import (GenericSignal, - QuantumContentType, GenericBoolSignal, GenericQuantumSignal) @@ -62,7 +61,7 @@ def compute_outputs(self, *args, **kwargs): mode=np.matmul(AD, np.matmul(mode, A)) mm.modes[m_id]=np.matmul(AD, np.matmul(mode, A)) self.ports["output"].signal.set_contents( - content_type=QuantumContentType.FOCK, + timestamp=0, mode_id=m_id) self.ports["output"].signal.set_computed() diff --git a/quasi/devices/sources/ideal_squeezed_photon_source.py b/quasi/devices/sources/ideal_squeezed_photon_source.py index bac59ff..5e809ba 100644 --- a/quasi/devices/sources/ideal_squeezed_photon_source.py +++ b/quasi/devices/sources/ideal_squeezed_photon_source.py @@ -12,7 +12,6 @@ from quasi.devices.port import Port from quasi.signals import (GenericSignal, GenericFloatSignal, - QuantumContentType, GenericBoolSignal, GenericQuantumSignal) diff --git a/quasi/devices/variables/float_variable.py b/quasi/devices/variables/float_variable.py index b9eb2dd..7b01130 100644 --- a/quasi/devices/variables/float_variable.py +++ b/quasi/devices/variables/float_variable.py @@ -41,8 +41,8 @@ class FloatVariable(GenericDevice): @coordinate_gui @wait_input_compute def compute_outputs(self, *args, **kwargs): - self.ports["int"].signal.set_int(self.values["value"]) - self.ports["int"].signal.set_computed() + self.ports["float"].signal.set_float(self.values["value"]) + self.ports["float"].signal.set_computed() def set_value(self, value:str): diff --git a/quasi/experiment/experiment_manager.py b/quasi/experiment/experiment_manager.py index 4756c3d..75b913b 100644 --- a/quasi/experiment/experiment_manager.py +++ b/quasi/experiment/experiment_manager.py @@ -6,19 +6,36 @@ class Experiment: """Singleton object""" + __instance = None + + def __new__(cls, *args, **kwargs): + if not cls.__instance: + cls.__instance = super(Experiment, cls).__new__(cls) + return cls.__instance def __init__(self, num_modes, hbar=2, cutoff=10): - """ - Initialization method - """ + # Prevent reinitialization if the instance already exists + if hasattr(self, 'initialized'): + return + self.data = None self.init_modes = 1 self.cutoff = cutoff self.num_modes = num_modes self.hbar = hbar - self.state_preparations: list = [] - self.operations: list = [] - self.channels: list = [] + self.state_preparations = [] + self.operations = [] + self.channels = [] + self.initialized = True # Mark the instance as initialized + + @staticmethod + def get_instance(): + """ + Returns the singleton Experiment object. Raises an exception if the instance hasn't been created yet. + """ + if not Experiment.__instance: + raise Exception("Experiment instance not created yet") + return Experiment.__instance def add_operation(self, operator, modes): self.operations.append((operator, modes)) @@ -126,3 +143,10 @@ def execute(self): num_modes=self.num_modes, cutoff_dim=self.cutoff, ) + + +class ExperimentInitializedException(Exception): + """ + Exception for the case, when Experiment is attempted to be + initialized more than once. + """ diff --git a/quasi/gui/panels/side_panel.py b/quasi/gui/panels/side_panel.py index 215c03b..ce84150 100644 --- a/quasi/gui/panels/side_panel.py +++ b/quasi/gui/panels/side_panel.py @@ -15,6 +15,7 @@ import quasi.devices.control as quasi_control import quasi.devices.variables as quasi_variables import quasi.devices.extra as quasi_extra +import quasi.devices.fiber as quasi_fiber class DraggableDevice(ft.Draggable): """ @@ -96,6 +97,8 @@ def _get_all_devices(self): quasi_control) devices_dict["Investigation"] = self._get_device_class( quasi_investigation) + devices_dict["Fiber"] = self._get_device_class( + quasi_fiber) devices_dict["Extra"] = self._get_device_class( quasi_extra) return devices_dict diff --git a/quasi/kernel/__init__.py b/quasi/kernel/__init__.py index e69de29..4182d53 100644 --- a/quasi/kernel/__init__.py +++ b/quasi/kernel/__init__.py @@ -0,0 +1 @@ +from .fock_kernel.fock_kernel import FockKernel diff --git a/quasi/kernel/fock_kernel/__init__.py b/quasi/kernel/fock_kernel/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/quasi/kernel/fock_kernel/__init__.py @@ -0,0 +1 @@ + diff --git a/quasi/kernel/fock_kernel/fock_kernel.py b/quasi/kernel/fock_kernel/fock_kernel.py new file mode 100644 index 0000000..a46f133 --- /dev/null +++ b/quasi/kernel/fock_kernel/fock_kernel.py @@ -0,0 +1,55 @@ +""" +This module implements a Fock Kernel +""" +from typing import List +from quasi.kernel.generic_kernel import GenericKernel + + +class FockMode(): + """ + Just holds information about fock mode + """ + def __init__(self, truncation): + self.truncation = truncation + +class FockState(): + """ + This class keeps track of the modes + """ + def __init__(self, fock_mode: FockMode): + self.modified = True + pass + + def cleanup(self): + if self.modified: + pass + +class FockKernel(GenericKernel): + """ + This class implements Fock Kernel, + Fock Kernel allows modes to have varied truncations. + """ + def __init__(self): + """ + According to the special issue + """ + self.state = [] + self.modes = [] + + def add_mode(self, truncation: int): # pylint: disable=arguments-differ + """ + Creates a new mode in vaccum state and returns the index of the mode + """ + fm = FockMode(truncation) + self.modes.append(fm) + self.state.append(FockState(fm)) + + + def _cleanup(self): + """ + Determines if the modes could be split + """ + + def remove_mode(self, mode_index:int): # pylint: disable=arguments-differ + pass + diff --git a/quasi/kernel/generic_kernel.py b/quasi/kernel/generic_kernel.py new file mode 100644 index 0000000..266a015 --- /dev/null +++ b/quasi/kernel/generic_kernel.py @@ -0,0 +1,30 @@ +""" +This module implements a Generic Kernel. +Generic Kernel enforces structure in the kernels. +""" + +from abc import ABCMeta, abstractmethod + +class SingletonMeta(ABCMeta): + """ + All kernels must be singletons + """ + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] + +class GenericKernel(metaclass=SingletonMeta): + """ + Enforcing Structure for the Kernels. + All kernels must extend this class + """ + + @abstractmethod + def add_mode(self, *args, **kwargs): + pass + + @abstractmethod + def remove_mode(self, *args, **kwargs): + pass diff --git a/quasi/signals/__init__.py b/quasi/signals/__init__.py index e14814c..72b6b13 100644 --- a/quasi/signals/__init__.py +++ b/quasi/signals/__init__.py @@ -3,6 +3,6 @@ """ from .generic_bool_signal import GenericBoolSignal from .generic_signal import GenericSignal -from .generic_quantum_signal import GenericQuantumSignal, QuantumContentType +from .generic_quantum_signal import GenericQuantumSignal from .generic_int_signal import GenericIntSignal from .generic_float_signal import GenericFloatSignal diff --git a/quasi/signals/fock_signal.py b/quasi/signals/fock_signal.py new file mode 100644 index 0000000..4f54b45 --- /dev/null +++ b/quasi/signals/fock_signal.py @@ -0,0 +1,22 @@ +""" +Quantum Signal with Fock Modes +""" + +from quasi.signals.generic_quantum_signal import ( + GenericQuantumSignal +) + + +class FockSignal(GenericQuantumSignal): + """ + Signal Carrying a reference to the Fock Mode + """ + def __init__(self): + super().__init__() + self.contents = None + self.mode_id = None + self.timestamp = None + + def set_contents(self, timestamp, mode_id=None, content=None): + self.mode_id = mode_id + self.timestamp = timestamp diff --git a/quasi/signals/generic_float_signal.py b/quasi/signals/generic_float_signal.py index 08e75ca..39a4664 100644 --- a/quasi/signals/generic_float_signal.py +++ b/quasi/signals/generic_float_signal.py @@ -12,5 +12,8 @@ def __init__(self): super().__init__() self.contents = None - def set_int(self, x: float): + def set_float(self, x: float): + """ + Sets the content to the specified float + """ self.contents = float(x) diff --git a/quasi/signals/generic_quantum_signal.py b/quasi/signals/generic_quantum_signal.py index e442f67..202fb05 100644 --- a/quasi/signals/generic_quantum_signal.py +++ b/quasi/signals/generic_quantum_signal.py @@ -6,11 +6,6 @@ from quasi.signals.generic_signal import GenericSignal -class QuantumContentType(Enum): - NONE = -1 - FOCK = auto() - QOPS = auto() - class GenericQuantumSignal(GenericSignal): """ @@ -20,13 +15,13 @@ class GenericQuantumSignal(GenericSignal): def __init__(self): super().__init__() self.contents = None - self.content_type = QuantumContentType.NONE self.mode_id = None + self.timestamp = None def set_contents(self, - content_type: QuantumContentType, + timestamp, mode_id=None, content=None): - self.content_type = content_type + self.timestamp = timestamp self.mode_id = mode_id self.contents = content diff --git a/quasi/simulation/__init__.py b/quasi/simulation/__init__.py index d02d296..46a4ec4 100644 --- a/quasi/simulation/__init__.py +++ b/quasi/simulation/__init__.py @@ -1,7 +1,7 @@ """ Module __init__ file """ - +# pylint: disable=unused-import from .simulation import Simulation from .simulation import DeviceInformation from .simulation import SimulationType diff --git a/quasi/simulation/mode_manager.py b/quasi/simulation/mode_manager.py index f365389..a26de5d 100644 --- a/quasi/simulation/mode_manager.py +++ b/quasi/simulation/mode_manager.py @@ -33,13 +33,12 @@ def create_new_mode(self) -> str: Creates a new mode in vacuum state and returns its id. """ new_mode_id = uuid.uuid4() - mode = vacuum_state( - 2, - Simulation.dimensions - ) - self.modes[new_mode_id] = mode + self.modes[new_mode_id] = len(self.modes.keys()) return new_mode_id + def get_mode_index(self, key: str): + return self.modes[key] + def clear_modes(self) -> None: self.modes = {} diff --git a/quasi/simulation/simulation.py b/quasi/simulation/simulation.py index 3b98f16..3f2c8c7 100644 --- a/quasi/simulation/simulation.py +++ b/quasi/simulation/simulation.py @@ -8,6 +8,12 @@ from dataclasses import dataclass from quasi.signals.generic_bool_signal import GenericBoolSignal +from quasi.signals.generic_quantum_signal import GenericQuantumSignal +from quasi.experiment.experiment_manager import Experiment +from typing import Type, TYPE_CHECKING + +if TYPE_CHECKING: + from quasi.devices import GenericDevice @dataclass class DeviceInformation: @@ -15,14 +21,13 @@ class DeviceInformation: Device representation, used for registering the device with the Simulation singleton class """ - uuid : str - name : str - obj_ref : object - + uuid: str + name: str + obj_ref: object - def __init__(self, name:str, obj_ref:object, uid=None): + def __init__(self, name: str, obj_ref: Type['GenericDevice'], uid=None): self.name = name - self.obj_ref = obj_ref + self.obj_ref = obj_ref if uid is not None: self.uuid = uid else: @@ -32,10 +37,36 @@ def __init__(self, name:str, obj_ref:object, uid=None): def device_type(self): return self.obj_ref.__class__.__name__ - -class SimulationType: + @property + def new_modes(self) -> int: + """ + Computes number of new modes, the simulation + would require + """ + modes = 0 + quantum_outputs = [port for port in self.obj_ref.ports.items() if + (port[1].direction == "output" and + port[1].signal_type is GenericQuantumSignal)] + quantum_inputs = [port for port in self.obj_ref.ports.items() if + (port[1].direction == "input" and + port[1].signal_type is GenericQuantumSignal)] + + # We create the modes for the outputs, + # that can't be mapped to inputs + if len(quantum_outputs) > len(quantum_inputs): + modes += len(quantum_outputs)-len(quantum_inputs) + + # We create the modes for the empty inputs + modes += len( + [port for port in quantum_inputs if + port.signal is None] + ) + return modes + + +class SimulationType(Enum): FOCK = auto() - MIXED = auto() + GAUSSIAN = auto() class Simulation: @@ -80,7 +111,6 @@ def register_device(self, device_information: DeviceInformation): def set_simulation_type(self, simulation_type: SimulationType): self.simulation_type = simulation_type - @classmethod def set_dimensions(cls, dimensions): cls.dimensions = dimensions @@ -90,6 +120,16 @@ def get_dimensions(cls): return cls.dimensions def run(self): + """ + Executes the experiment + """ + # Determine number of modes + modes = sum([d.new_modes for d in self.devices]) + + # REVIEW THIS + if self.simulation_type == SimulationType.FOCK: + Experiment(num_modes=modes, cutoff=Simulation.get_dimensions()) + for d in self.initial_trigger_devices: d = d.obj_ref sig = d.ports["TRIGGER"].signal @@ -104,7 +144,9 @@ def run(self): for p in processes: p.join() - + if self.simulation_type == SimulationType.FOCK: + exp = Experiment.get_instance() + exp.execute() def register_triggers(self, *devices): """ @@ -115,10 +157,9 @@ def register_triggers(self, *devices): sig = GenericBoolSignal() d.register_signal(signal=sig, port_label="TRIGGER") d = [x for x in self.devices if x.obj_ref == d][0] - if not d in self.initial_trigger_devices: + if d not in self.initial_trigger_devices: self.initial_trigger_devices.append(d) - def list_devices(self): self._list_devices(self.devices, "DEVICES") @@ -142,7 +183,6 @@ def _list_devices(self, devices, title): print(f"├─ {str(d.name).ljust(n)} : {str(d.device_type).ljust(t)} : {str(d.uuid).ljust(u)} │") print("╰─"+"─"*total_bot+"─╯") - def clear_all(self): for d in self.devices: del d