Repository: sequence-toolbox/SeQUeNCe (language: python)

Issue:
[Proposal] Mirror component
Hi! 
I have been using SeQUeNCe for the past few months and have created a mirror component for my own use and simulation of certain experimental setups. The mirror component affects the fidelity of the output as per the input. It effectively detects a photon input, "loses" photons as defined per fidelity (defined over 1), and re-emits a copy of any "non-lost" photons.
I think it could be a useful component for users that use the tool as a lab experiment simulator.

      class Mirror(Entity):

          def __init__(self, name: str, timeline: "Timeline", fidelity=0.98, time_resolution=150, frequency=8e7, wavelength=1550, bandwidth=0, mean_photon_num=0.1,
                        encoding_type=polarization, phase_error=0):

                Entity.__init__(self, name, timeline)  # Splitter is part of the QSDetector, and does not have its own name
                self.fidelity = fidelity
                self.receivers = []
                # for BB84
                self.start_time = 0
                self.basis_list = []
                self.photon_counter = 0
                self.time_resolution = time_resolution  # measured in ps

                self.frequency = frequency  # measured in Hz
                self.wavelength = wavelength  # measured in nm
                self.linewidth = bandwidth  # st. dev. in photon wavelength (nm)
                self.mean_photon_num = mean_photon_num
                self.encoding_type = encoding_type
                self.phase_error = phase_error

          def init(self):

                pass

          def get(self, dark_get=False) -> None:

                self.photon_counter += 1
                now = self.timeline.now()
                time = round(now / self.time_resolution) * self.time_resolution

          def emit(self, state_list, dst: str) -> None:

                time = self.timeline.now()
                period = int(round(1e12 / self.frequency))

                for i, state in enumerate(state_list):

                    num_photons = 1

                    if random.random_sample() < self.phase_error:
                        state = multiply([1, -1], state)

                    for _ in range(num_photons):
                        wavelength = self.linewidth * random.randn() + self.wavelength
                        new_photon = Photon(str(i),
                                            wavelength=wavelength,
                                            location=self.owner,
                                            encoding_type=self.encoding_type,
                                            quantum_state=state)
                        
                        process = Process(self.owner, "send_qubit", [dst, new_photon])
                        
                        event = Event(time, process)
                        self.owner.timeline.schedule(event)
                        self.photon_counter += 1
                    time += period
Documentation
Add module and class level docstrings to RnD, along with automatic documentation.


Discussion / hints:
Thanks for your work. Please create a pull request. We'll merge your code.


Fix patch:
diff --git a/src/components/mirror.py b/src/components/mirror.py
new file mode 100644
index 00000000..c630acc2
--- /dev/null
+++ b/src/components/mirror.py
@@ -0,0 +1,89 @@
+from numpy import multiply
+
+from .photon import Photon
+from ..kernel.entity import Entity
+from ..kernel.event import Event
+from ..kernel.process import Process
+from ..utils.encoding import polarization
+
+from typing import TYPE_CHECKING, Any, Dict
+if TYPE_CHECKING:
+    from ..kernel.timeline import Timeline
+    from ..components.photon import Photon
+    from typing import List
+
+class Mirror(Entity):
+    
+    """Single photon reflecting device.
+    This class models the reflection of a single photon, in the fashion of an experimental mirror.
+    Can be attached to many different devices to enable different measurement options.
+    Attributes:
+        name (str): label for mirror instance.
+        timeline (Timeline): timeline for simulation.
+        fidelity (float): fraction of lost qubits on the reflective surface
+        frequency (float): frequency (in Hz) of photon creation.
+        wavelength (float): wavelength (in nm) of emitted photons.
+        linewidth (float): st. dev. in photon wavelength (in nm).
+        mean_photon_num (float): mean number of photons emitted each period.
+        encoding_type (Dict[str, Any]): encoding scheme of emitted photons (as defined in the encoding module).
+        phase_error (float): phase error applied to qubits.
+
+    """
+
+    def __init__(self, name: str, timeline: "Timeline", fidelity=0.98,
+                 time_resolution=150, frequency=8e7, wavelength=1550,
+                 bandwidth=0, mean_photon_num=0.1, encoding_type=polarization,
+                 phase_error=0):
+
+        Entity.__init__(self, name, timeline)
+        self.fidelity = fidelity
+        self.receivers = []
+        # for BB84
+        self.start_time = 0
+        self.basis_list = []
+        self.photon_counter = 0
+        self.time_resolution = time_resolution  # measured in ps
+        self.frequency = frequency  # measured in Hz
+        self.wavelength = wavelength  # measured in nm
+        self.linewidth = bandwidth  # st. dev. in photon wavelength (nm)
+        self.mean_photon_num = mean_photon_num
+        self.encoding_type = encoding_type
+        self.phase_error = phase_error
+
+    def init(self):
+
+        pass
+
+    def get(self, dark_get=False) -> None:
+
+        self.photon_counter += 1
+        now = self.timeline.now()
+        time = round(now / self.time_resolution) * self.time_resolution
+
+    def emit(self, state_list, dst: str) -> None:
+
+        time = self.timeline.now()
+        period = int(round(1e12 / self.frequency))
+
+        for i, state in enumerate(state_list):
+
+            num_photons = 1
+            
+            rng = self.get_generator()
+            if rng.random_sample() < self.phase_error:
+                state = multiply([1, -1], state)
+
+            for _ in range(num_photons):
+                wavelength = self.linewidth * rng.random_sample() + self.wavelength
+                new_photon = Photon(str(i),
+                                    wavelength=wavelength,
+                                    location=self.owner,
+                                    encoding_type=self.encoding_type,
+                                    quantum_state=state)
+
+                process = Process(self.owner, "send_qubit", [dst, new_photon])
+
+                event = Event(time, process)
+                self.owner.timeline.schedule(event)
+                self.photon_counter += 1
+                time += period


Test patch:
diff --git a/tests/components/test_mirror.py b/tests/components/test_mirror.py
new file mode 100644
index 00000000..33db9b9e
--- /dev/null
+++ b/tests/components/test_mirror.py
@@ -0,0 +1,104 @@
+from numpy import random
+from random import randrange
+
+from typing import TYPE_CHECKING
+if TYPE_CHECKING:
+    from sequence.kernel.timeline import Timeline
+
+from sequence.kernel.event import Event
+from sequence.kernel.process import Process
+from sequence.components.light_source import polarization
+from sequence.components.optical_channel import QuantumChannel
+from sequence.topology.node import Node
+
+from sequence.utils.encoding import *
+from sequence.components.light_source import LightSource
+
+from sequence.components.mirror import Mirror
+
+NUM_TRIALS = 1000
+FREQUENCY = 1e3
+
+
+class Counter():
+    def __init__(self):
+        self.count = 0
+
+    def trigger(self, detector, info):
+        self.count += 1
+
+
+class EmittingNode(Node):
+    def __init__(self, name, timeline):
+        super().__init__(name, timeline)
+        self.light_source = LightSource(
+            name, timeline, frequency=80000000, mean_photon_num=1)
+        self.light_source.owner = self
+
+
+class MiddleNode(Node):
+
+    def __init__(self, name, timeline):
+        super().__init__(name, timeline)
+        self.mirror = Mirror(name, timeline)
+        self.mirror.owner = self
+    #src = node1
+
+    def receive_qubit(self, src, qubit):
+        #print("received something")
+        if not qubit.is_null:
+            self.mirror.get()
+
+            y = randrange(100)
+            if not (self.mirror.fidelity * 100) < y:
+                process_photon = Process(self.mirror, "emit", [
+                                         [qubit.quantum_state.state], "node3"])
+
+                time = self.timeline.now()
+                period = int(round(1e12 / self.mirror.frequency))
+                event = Event(time, process_photon)
+                self.owner.timeline.schedule(event)
+                time += period
+                #print("receiving mirror")
+
+def test_mirror():
+    class Receiver(Node):
+        def __init__(self, name, tl):
+            Node.__init__(self, name, tl)
+            self.log = []
+
+        def receive_qubit(self, src: str, qubit) -> None:
+            self.log.append((self.timeline.now(), src, qubit))
+
+    tl = Timeline()
+    FID, FREQ, MEAN = 0.98, 8e7, 0.1
+    mr = Mirror("mr", tl, fidelity=FID, frequency=8e7, mean_photon_num=MEAN)
+    sender = MiddleNode("sender", tl, ls)
+    sender.set_seed(YOUR_SEED)
+
+
+    assert sender.Mirror.fidelity == FID  
+    assert sender.Mirror.frequency == FREQ  
+    assert sender.Mirror.mean_photon_num == MEAN
+
+    receiver = Receiver("receiver", tl)
+    qc = QuantumChannel("qc", tl, distance=1e5, attenuation=0)
+    qc.set_ends(sender, receiver)
+    state_list = []
+    STATE_LEN = 1000
+    for _ in range(STATE_LEN):
+        rng = sender.get_generator()
+        basis = rng.randint(2)
+        bit = random.randint(2)
+        state_list.append(polarization["bases"][basis][bit])
+
+    tl.init()
+    mr.emit(state_list, "receiver")
+    tl.run()
+
+    assert (len(receiver.log) / STATE_LEN) - MEAN < 0.1
+    for time, src, qubit in receiver.log:
+        index = int(qubit.name)
+        assert state_list[index] == qubit.quantum_state.state
+        assert time == index * (1e12 / FREQ) + qc.delay
+        assert src == "sender"