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

Issue:
Various Updates



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index a6ba6184..6754c894 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -12,13 +12,15 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11']
+        python-version: ['3.9', '3.10', '3.11', '3.12']
         mpi: [mpich, openmpi]
         exclude:
-          - python-version: '3.8'
-            mpi: mpich
           - python-version: '3.9'
             mpi: mpich
+          - python-version: '3.10'
+            mpi: mpich
+          - python-version: '3.11'
+            mpi: mpich
 
     steps:
     - uses: actions/checkout@v2
diff --git a/.gitignore b/.gitignore
index c3923a39..6678074d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -140,3 +140,6 @@ src/gui/test_run.py
 
 # macos
 .DS_Store
+
+# tmp folder
+tmp/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96090e73..65eba7fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -181,3 +181,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Changed
 - Modified the topology output of the GUI to be compatible with new topology upgrades
 - Several bug fixes in the GUI
+
+
+## [0.6.3]
+### Added
+- We have an Icon for SeQUeNCe!
+- `pip install sequence` is now available!
+- `src` folder is renamed to `sequence`
+- Add `sequence/constants.py` to organize the common constants
+- Add support for Python 3.12
+- Add `pyproject.toml`
+
+
+### Changed
+- Various refactoring, include but not limited to updating variable/method names, moving/deleting code, adding/rewriting comments, and making the code more succinct, etc.
+- In README.md, installation with option `--editable`  is encouraged.
+- `logging` looks better
+- In `class Reservation`, added attribute `entanglement_number` and `id`
+- Update `ResourceReservationProtocol.load_rules()`
+- Move `utils/json_config_generators/generator_utils.py` to `sequence/utils/config_generator.py`
+
+### Removed
+- Removed support for Python 3.8
+- Removed `setup.py`
+- Removed `MANIFEST.in`
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 505459ed..00000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,1 +0,0 @@
-recursive-include src/gui *
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 4e7a6265..50c3d7ec 100644
--- a/Makefile
+++ b/Makefile
@@ -3,12 +3,12 @@ all: jupyter
 setup:
 	pip3 install -r ./requirements.txt
 
-install_no_pip:
-	python3 setup.py install
-
 install:
 	pip3 install .
 
+install_editable:
+	pip3 install --editable . --config-settings editable_mode=strict
+
 jupyter:
 	jupyter notebook ./example/two_node_eg.ipynb
 
diff --git a/README.md b/README.md
index f16f0067..de133628 100644
--- a/README.md
+++ b/README.md
@@ -21,20 +21,22 @@ SeQUeNCe is an open source, discrete-event simulator for quantum networks. As de
 These modules can be edited by users to define additional functionality and test protocol schemes, or may be used as-is to test network parameters and topologies.
 
 ## Installing
-SeQUeNCe requires an installation of Python 3.8 or later. This can be found at the [Python Website](https://www.python.org/downloads/).
-
-You can install with:
+SeQUeNCe requires [Python](https://www.python.org/downloads/) 3.9 or later. You can simply install SeQUeNCe using `pip`:
 ```
 pip install sequence
 ```
 
-Or, download the package, navigate to its directory, and install with
+If you wish to make your own edits to the codebase, SeQUeNCe should be installed in [development mode](https://setuptools.pypa.io/en/latest/userguide/development_mode.html) (a.k.a. editable install).
+To do so, clone and install the simulator as follows:
 ```
 git clone https://github.com/sequence-toolbox/SeQUeNCe.git
 cd SeQUeNCe
-pip install .
+make install_editable
 ```
 
+If you do not have `make` command (i.e., Windows user), replace `make install_editable` with `pip install --editable . --config-settings editable_mode=strict`.
+
+
 ## Citation
 Please cite us, thank you!
 ```
diff --git a/docs/README.md b/docs/README.md
index ec7223d8..51dffe75 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -12,15 +12,11 @@ References are generated automatically from source docstrings using the Sphinx `
 
 ## Building HTML Pages
 
-Sphinx can be easily installed via pip:
+Sphinx and recommonmark can be easily installed via pip:
 
 ```
 $ pip install -U sphinx
-```
-
-Also, need to install recommonmark
-```
-pip install recommonmark
+$ pip install recommonmark
 ```
 
 Once installed, the project can be built with the included makefile:
diff --git a/docs/source/conf.py b/docs/source/conf.py
index d8e67c89..f6020302 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -22,7 +22,7 @@
 author = 'Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara'
 
 # The full version, including alpha/beta/rc tags
-release = '0.6.2.2'
+release = '0.6.3'
 
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/source/tutorial/chapter2/example1.py b/docs/source/tutorial/chapter2/example1.py
index 8fa6e0ae..7f497750 100644
--- a/docs/source/tutorial/chapter2/example1.py
+++ b/docs/source/tutorial/chapter2/example1.py
@@ -8,6 +8,7 @@
 from sequence.components.detector import Detector
 from sequence.topology.node import Node
 
+import sequence.utils.log as log
 
 NUM_TRIALS = 1000
 FREQUENCY = 1e3
@@ -22,26 +23,25 @@ def trigger(self, detector, info):
 
 
 class Sender:
-    def __init__(self, own, memory_name):
-        self.own = own
-        self.memory = own.components[memory_name]
+    def __init__(self, owner, memory_name):
+        self.owner = owner
+        self.memory = owner.components[memory_name]
 
     def start(self, period):
         process1 = Process(self.memory, "update_state", [[complex(math.sqrt(1/2)), complex(math.sqrt(1/2))]])
         process2 = Process(self.memory, "excite", ["node2"])
         for i in range(NUM_TRIALS):
             event1 = Event(i * period, process1)
-            event2 = Event(i * period + (period / 2), process2)
-            self.own.timeline.schedule(event1)
-            self.own.timeline.schedule(event2)
+            event2 = Event(i * period + (period / 10), process2)
+            self.owner.timeline.schedule(event1)
+            self.owner.timeline.schedule(event2)
 
 
 class SenderNode(Node):
     def __init__(self, name, timeline):
         super().__init__(name, timeline)
         memory_name = name + ".memory"
-        memory = Memory(memory_name, timeline, fidelity=1, frequency=0,
-                        efficiency=1, coherence_time=0, wavelength=500)
+        memory = Memory(memory_name, timeline, fidelity=1, frequency=0, efficiency=1, coherence_time=0, wavelength=500)
         self.add_component(memory)
         memory.add_receiver(self)
 
@@ -69,20 +69,28 @@ def receive_qubit(self, src, qubit):
 
 
 if __name__ == "__main__":
-    runtime = 10e12 
+
+    runtime = 1e12
     tl = Timeline(runtime)
 
+    log_filename = 'chapter2_example1_log'
+    log.set_logger(__name__, tl, log_filename)
+    log.set_logger_level('DEBUG')
+    modules = ['timeline']
+    for module in modules:
+        log.track_module(module)
+
     # nodes and hardware
     node1 = SenderNode("node1", tl)
     node2 = ReceiverNode("node2", tl)
-
-    qc = QuantumChannel("qc", tl, attenuation=0, distance=1e3)
+    qc = QuantumChannel("qc.node1.node2", tl, attenuation=0, distance=1e3)
     qc.set_ends(node1, node2.name)
 
     tl.init()
 
     # schedule events
     period = int(1e12 / FREQUENCY)
+    print(f'period = {period:,} ps')
     node1.sender.start(period)
 
     tl.run()
diff --git a/docs/source/tutorial/chapter2/example2.py b/docs/source/tutorial/chapter2/example2.py
index 0a104d74..d6e6ef28 100644
--- a/docs/source/tutorial/chapter2/example2.py
+++ b/docs/source/tutorial/chapter2/example2.py
@@ -15,9 +15,9 @@ class MsgType(Enum):
 
 
 class PingProtocol(Protocol):
-    def __init__(self, own: Node, name: str, other_name: str, other_node: str):
-        super().__init__(own, name)
-        own.protocols.append(self)
+    def __init__(self, owner: Node, name: str, other_name: str, other_node: str):
+        super().__init__(owner, name)
+        owner.protocols.append(self)
         self.other_name = other_name
         self.other_node = other_node
 
@@ -26,17 +26,17 @@ def init(self):
 
     def start(self):
         new_msg = Message(MsgType.PING, self.other_name)
-        self.own.send_message(self.other_node, new_msg)
+        self.owner.send_message(self.other_node, new_msg)
 
     def received_message(self, src: str, message: Message):
         assert message.msg_type == MsgType.PONG
-        print("node {} received pong message at time {}".format(self.own.name, self.own.timeline.now()))
+        print("node {} received pong message at time {}".format(self.owner.name, self.owner.timeline.now()))
 
 
 class PongProtocol(Protocol):
-    def __init__(self, own: Node, name: str, other_name: str, other_node: str):
-        super().__init__(own, name)
-        own.protocols.append(self)
+    def __init__(self, owner: Node, name: str, other_name: str, other_node: str):
+        super().__init__(owner, name)
+        owner.protocols.append(self)
         self.other_name = other_name
         self.other_node = other_node
     
@@ -45,9 +45,9 @@ def init(self):
 
     def received_message(self, src: str, message: Message):
         assert message.msg_type == MsgType.PING
-        print("node {} received ping message at time {}".format(self.own.name, self.own.timeline.now()))
+        print("node {} received ping message at time {}".format(self.owner.name, self.owner.timeline.now()))
         new_msg = Message(MsgType.PONG, self.other_name)
-        self.own.send_message(self.other_node, new_msg)
+        self.owner.send_message(self.other_node, new_msg)
 
 
 if __name__ == "__main__":
diff --git a/docs/source/tutorial/chapter2/hardware.md b/docs/source/tutorial/chapter2/hardware.md
index 8cf61081..6891456e 100644
--- a/docs/source/tutorial/chapter2/hardware.md
+++ b/docs/source/tutorial/chapter2/hardware.md
@@ -222,8 +222,8 @@ We'll include all memory modification in the `start` method, which will activate
 import math
 
 class Sender:
-    def __init__(self, own, memory_name):
-        self.own = own
+    def __init__(self, owner, memory_name):
+        self.owner = owner
         self.memory = own.components[memory_name]
 
     def start(self, period):
@@ -232,8 +232,8 @@ class Sender:
         for i in range(NUM_TRIALS):
             event1 = Event(i * period, process1)
             event2 = Event(i * period + (period / 2), process2)
-            self.own.timeline.schedule(event1)
-            self.own.timeline.schedule(event2)
+            self.owner.timeline.schedule(event1)
+            self.owner.timeline.schedule(event2)
 ```
 
 We'll then place this protocol in the `SenderNode` class:
@@ -305,9 +305,9 @@ from sequence.protocol import Protocol
 from sequence.message import Message
 
 class PingProtocol(Protocol):
-    def __init__(self, own: Node, name: str, other_name: str, other_node: str):
-        super().__init__(own, name)
-        own.protocols.append(self)
+    def __init__(self, owner: Node, name: str, other_name: str, other_node: str):
+        super().__init__(owner, name)
+        owner.protocols.append(self)
         self.other_name = other_name
         self.other_node = other_node
 
@@ -316,17 +316,17 @@ class PingProtocol(Protocol):
 
     def start(self):
         new_msg = Message(MsgType.PING, self.other_name)
-        self.own.send_message(self.other_node, new_msg)
+        self.owner.send_message(self.other_node, new_msg)
 
     def received_message(self, src: str, message: Message):
         assert message.msg_type == MsgType.PONG
-        print("node {} received pong message at time {}".format(self.own.name, self.own.timeline.now()))
+        print("node {} received pong message at time {}".format(self.owner.name, self.owner.timeline.now()))
 
 
 class PongProtocol(Protocol):
-    def __init__(self, own: Node, name: str, other_name: str, other_node: str):
-        super().__init__(own, name)
-        own.protocols.append(self)
+    def __init__(self, owner: Node, name: str, other_name: str, other_node: str):
+        super().__init__(owner, name)
+        owner.protocols.append(self)
         self.other_name = other_name
         self.other_node = other_node
     
@@ -335,9 +335,9 @@ class PongProtocol(Protocol):
 
     def received_message(self, src: str, message: Message):
         assert message.msg_type == MsgType.PING
-        print("node {} received ping message at time {}".format(self.own.name, self.own.timeline.now()))
+        print("node {} received ping message at time {}".format(self.owner.name, self.owner.timeline.now()))
         new_msg = Message(MsgType.PONG, self.other_name)
-        self.own.send_message(self.other_node, new_msg)
+        self.owner.send_message(self.other_node, new_msg)
 ```
 
 In both cases, the constructor requires
diff --git a/docs/source/tutorial/chapter3/entangle.md b/docs/source/tutorial/chapter3/entangle.md
index d0fd8c2d..d05c016f 100644
--- a/docs/source/tutorial/chapter3/entangle.md
+++ b/docs/source/tutorial/chapter3/entangle.md
@@ -45,8 +45,8 @@ from sequence.entanglement_management.generation import EntanglementGenerationA
 
 
 class SimpleManager:
-    def __init__(self, own, memo_name):
-        self.own = own
+    def __init__(self, owner, memo_name):
+        self.owner = owner
         self.memo_name = memo_name
         self.raw_counter = 0
         self.ent_counter = 0
@@ -59,8 +59,8 @@ class SimpleManager:
             self.ent_counter += 1
 
     def create_protocol(self, middle: str, other: str):
-        self.own.protocols = [EntanglementGenerationA(self.own, '%s.eg' % self.own.name, middle, other,
-                                                      self.own.components[self.memo_name])]
+        self.owner.protocols = [EntanglementGenerationA(self.owner, '%s.eg' % self.owner.name, middle, other,
+                                                      self.owner.components[self.memo_name])]
 
 
 class EntangleGenNode(Node):
@@ -250,8 +250,8 @@ from sequence.entanglement_management.purification import BBPSSW
 
 
 class SimpleManager:
-    def __init__(self, own, kept_memo_name, meas_memo_name):
-        self.own = own
+    def __init__(self, owner, kept_memo_name, meas_memo_name):
+        self.owner = owner
         self.kept_memo_name = kept_memo_name
         self.meas_memo_name = meas_memo_name
         self.raw_counter = 0
@@ -265,9 +265,9 @@ class SimpleManager:
             self.ent_counter += 1
 
     def create_protocol(self):
-        kept_memo = self.own.components[self.kept_memo_name]
-        meas_memo = self.own.components[self.meas_memo_name]
-        self.own.protocols = [BBPSSW(self.own, 'purification_protocol', kept_memo, meas_memo)]
+        kept_memo = self.owner.components[self.kept_memo_name]
+        meas_memo = self.owner.components[self.meas_memo_name]
+        self.owner.protocols = [BBPSSW(self.owner, 'purification_protocol', kept_memo, meas_memo)]
 
 
 class PurifyNode(Node):
@@ -493,8 +493,8 @@ We will set up the manager so that it automatically creates the right type of sw
 from sequence.entanglement_management.swapping import EntanglementSwappingA, EntanglementSwappingB
 
 class SimpleManager:
-    def __init__(self, own, memo_names):
-        self.own = own
+    def __init__(self, owner, memo_names):
+        self.owner = owner
         self.memo_names = memo_names
         self.raw_counter = 0
         self.ent_counter = 0
@@ -507,13 +507,13 @@ class SimpleManager:
             self.ent_counter += 1
 
     def create_protocol(self):
-        if type(self.own) is SwapNodeA:
-            left_memo = self.own.components[self.memo_names[0]]
-            right_memo = self.own.components[self.memo_names[1]]
-            self.own.protocols = [EntanglementSwappingA(self.own, 'ESA', left_memo, right_memo, 1, 0.99)]
+        if type(self.owner) is SwapNodeA:
+            left_memo = self.owner.components[self.memo_names[0]]
+            right_memo = self.owner.components[self.memo_names[1]]
+            self.owner.protocols = [EntanglementSwappingA(self.owner, 'ESA', left_memo, right_memo, 1, 0.99)]
         else:
-            memo = self.own.components[self.memo_names[0]]
-            self.own.protocols = [EntanglementSwappingB(self.own, '%s.ESB' % self.own.name, memo)]
+            memo = self.owner.components[self.memo_names[0]]
+            self.owner.protocols = [EntanglementSwappingB(self.owner, '%s.ESB' % self.owner.name, memo)]
 ```
 
 ### Step 2: Create Network
diff --git a/docs/source/tutorial/chapter3/example1.py b/docs/source/tutorial/chapter3/example1.py
index 5fcba36c..20395ec7 100644
--- a/docs/source/tutorial/chapter3/example1.py
+++ b/docs/source/tutorial/chapter3/example1.py
@@ -7,8 +7,8 @@
 
 
 class SimpleManager:
-    def __init__(self, own, memo_name):
-        self.own = own
+    def __init__(self, owner, memo_name):
+        self.owner = owner
         self.memo_name = memo_name
         self.raw_counter = 0
         self.ent_counter = 0
@@ -21,8 +21,8 @@ def update(self, protocol, memory, state):
             self.ent_counter += 1
 
     def create_protocol(self, middle: str, other: str):
-        self.own.protocols = [EntanglementGenerationA(self.own, '%s.eg' % self.own.name, middle, other,
-                                                      self.own.components[self.memo_name])]
+        self.owner.protocols = [EntanglementGenerationA(self.owner, '%s.eg' % self.owner.name, middle, other,
+                                                      self.owner.components[self.memo_name])]
 
 
 class EntangleGenNode(Node):
diff --git a/docs/source/tutorial/chapter3/example2.py b/docs/source/tutorial/chapter3/example2.py
index 73eafda9..c5116e71 100644
--- a/docs/source/tutorial/chapter3/example2.py
+++ b/docs/source/tutorial/chapter3/example2.py
@@ -8,8 +8,8 @@
 
 
 class SimpleManager:
-    def __init__(self, own, kept_memo_name, meas_memo_name):
-        self.own = own
+    def __init__(self, owner, kept_memo_name, meas_memo_name):
+        self.owner = owner
         self.kept_memo_name = kept_memo_name
         self.meas_memo_name = meas_memo_name
         self.raw_counter = 0
@@ -23,9 +23,9 @@ def update(self, protocol, memory, state):
             self.ent_counter += 1
 
     def create_protocol(self):
-        kept_memo = self.own.components[self.kept_memo_name]
-        meas_memo = self.own.components[self.meas_memo_name]
-        self.own.protocols = [BBPSSW(self.own, 'purification_protocol', kept_memo, meas_memo)]
+        kept_memo = self.owner.components[self.kept_memo_name]
+        meas_memo = self.owner.components[self.meas_memo_name]
+        self.owner.protocols = [BBPSSW(self.owner, 'purification_protocol', kept_memo, meas_memo)]
 
 
 class PurifyNode(Node):
diff --git a/docs/source/tutorial/chapter3/example3.py b/docs/source/tutorial/chapter3/example3.py
index b3387d2a..063c3404 100644
--- a/docs/source/tutorial/chapter3/example3.py
+++ b/docs/source/tutorial/chapter3/example3.py
@@ -7,8 +7,8 @@
 
 
 class SimpleManager:
-    def __init__(self, own, memo_names):
-        self.own = own
+    def __init__(self, owner, memo_names):
+        self.owner = owner
         self.memo_names = memo_names
         self.raw_counter = 0
         self.ent_counter = 0
@@ -22,12 +22,12 @@ def update(self, protocol, memory, state):
 
     def create_protocol(self):
         if type(self.own) is SwapNodeA:
-            left_memo = self.own.components[self.memo_names[0]]
-            right_memo = self.own.components[self.memo_names[1]]
-            self.own.protocols = [EntanglementSwappingA(self.own, 'ESA', left_memo, right_memo, 1, 0.99)]
+            left_memo = self.owner.components[self.memo_names[0]]
+            right_memo = self.owner.components[self.memo_names[1]]
+            self.owner.protocols = [EntanglementSwappingA(self.owner, 'ESA', left_memo, right_memo, 1, 0.99)]
         else:
-            memo = self.own.components[self.memo_names[0]]
-            self.own.protocols = [EntanglementSwappingB(self.own, '%s.ESB' % self.own.name, memo)]
+            memo = self.owner.components[self.memo_names[0]]
+            self.owner.protocols = [EntanglementSwappingB(self.owner, '%s.ESB' % self.owner.name, memo)]
 
 
 class SwapNodeA(Node):
diff --git a/docs/source/tutorial/chapter4/two_flow.py b/docs/source/tutorial/chapter4/two_flow.py
index 08472e79..1c05a050 100644
--- a/docs/source/tutorial/chapter4/two_flow.py
+++ b/docs/source/tutorial/chapter4/two_flow.py
@@ -69,7 +69,7 @@ def eg_req_func(protocols, args):
     for protocol in protocols:
         if not isinstance(protocol, EntanglementGenerationA):
             continue
-        mem_arr = protocol.own.get_components_by_type("MemoryArray")[0]
+        mem_arr = protocol.owner.get_components_by_type("MemoryArray")[0]
         if protocol.remote_node_name == remote_node and \
                 index_lower <= mem_arr.memories.index(protocol.memory) <= index_upper:
             return protocol
diff --git a/docs/source/tutorial/chapter6/custom_app.py b/docs/source/tutorial/chapter6/custom_app.py
index ff3f86a7..c182a71b 100644
--- a/docs/source/tutorial/chapter6/custom_app.py
+++ b/docs/source/tutorial/chapter6/custom_app.py
@@ -3,6 +3,8 @@
 from sequence.topology.router_net_topo import RouterNetTopo
 from sequence.resource_management.memory_manager import MemoryInfo
 from sequence.network_management.reservation import Reservation
+import sequence.utils.log as log
+
 
 from typing import TYPE_CHECKING
 if TYPE_CHECKING:
@@ -20,15 +22,15 @@ def __init__(self, node: "QuantumRouter", other: str, memory_size=25, target_fid
     def start(self):
         now = self.node.timeline.now()
         nm = self.node.network_manager
-        nm.request(self.other, start_time=(now + 1e12), end_time=(now + 2e12),
+        nm.request(self.other, start_time=(now + 1e12), end_time=(now + PERIOD),
                    memory_size=self.memory_size,
                    target_fidelity=self.target_fidelity)
         # schedule future start
         process = Process(self, "start", [])
-        event = Event(now + 2e12, process)
+        event = Event(now + PERIOD, process)
         self.node.timeline.schedule(event)
 
-    def get_reserve_res(self, reservation: "Reservation", result: bool):
+    def get_reservation_result(self, reservation: "Reservation", result: bool):
         if result:
             print("Reservation approved at time", self.node.timeline.now() * 1e-12)
         else:
@@ -70,12 +72,16 @@ def get_memory(self, info):
 
 
 if __name__ == "__main__":
+
+    log_filename = 'log'
+
     network_config = "star_network.json"
-    num_periods = 5
+    NUM_PERIODS = 5
+    PERIOD = 2e12
 
     network_topo = RouterNetTopo(network_config)
     tl = network_topo.get_timeline()
-    tl.stop_time = 2e12 * num_periods
+    tl.stop_time = PERIOD * NUM_PERIODS
     tl.show_progress = False
 
     start_node_name = "end1"
@@ -88,13 +94,11 @@ def get_memory(self, info):
         elif router.name == end_node_name:
             node2 = router
 
-    app = PeriodicApp(node1, end_node_name)
-    reset_app = ResetApp(node2, start_node_name)
-    
-    tl.init()
-
-    for name, component in tl.entities.items():
-        print("{}: {}".format(name, component.get_generator()))
+    memory_size = 1
+    target_fidelity = 0.6
+    app = PeriodicApp(node1, end_node_name, memory_size, target_fidelity)
+    reset_app = ResetApp(node2, start_node_name, target_fidelity)
 
+    tl.init()
     app.start()
     tl.run()
diff --git a/example/absorptive_experiment.py b/example/absorptive_experiment.py
index 2aeb0d9d..432a1013 100644
--- a/example/absorptive_experiment.py
+++ b/example/absorptive_experiment.py
@@ -137,7 +137,7 @@ def add_channel(node1: Node, node2: Node, timeline: Timeline, **kwargs):
 
 # protocol to control photon emission on end node
 class EmitProtocol(Protocol):
-    def __init__(self, own: "EndNode", name: str, other_node: str, photon_pair_num: int,
+    def __init__(self, owner: "EndNode", name: str, other_node: str, photon_pair_num: int,
                  source_name: str, memory_name: str):
         """Constructor for Emission protocol.
 
@@ -150,18 +150,18 @@ def __init__(self, own: "EndNode", name: str, other_node: str, photon_pair_num:
             memory_name (str): name of the memory on the node.
         """
 
-        super().__init__(own, name)
+        super().__init__(owner, name)
         self.other_node = other_node
         self.num_output = photon_pair_num
         self.source_name = source_name
         self.memory_name = memory_name
 
     def start(self):
-        if not self.own.components[self.memory_name].is_prepared:
-            self.own.components[self.memory_name]._prepare_AFC()
+        if not self.owner.components[self.memory_name].is_prepared:
+            self.owner.components[self.memory_name]._prepare_AFC()
         
         states = [None] * self.num_output  # for Fock encoding only list length matters and list elements do not matter
-        source = self.own.components[self.source_name]
+        source = self.owner.components[self.source_name]
         source.emit(states)
 
     def received_message(self, src: str, msg):
diff --git a/example/dark_count_timebin.py b/example/dark_count_timebin.py
index 9036d4df..dfa976ff 100644
--- a/example/dark_count_timebin.py
+++ b/example/dark_count_timebin.py
@@ -15,8 +15,8 @@
 
 # dummy parent class to receive cascade keys and end timeline
 class Parent(StackProtocol):
-    def __init__(self, own: "Node", keysize: int, keynum: int):
-        super().__init__(own, "")
+    def __init__(self, owner: "Node", keysize: int, keynum: int):
+        super().__init__(owner, "")
         self.upper_protocols = []
         self.lower_protocols = []
         self.keysize = keysize
@@ -29,7 +29,7 @@ def init(self):
     def pop(self, key):
         self.keycounter += 1
         if self.keycounter >= self.keynum:
-            self.own.timeline.stop()
+            self.owner.timeline.stop()
 
     def push(self):
         self.lower_protocols[0].push(self.keysize, self.keynum)
diff --git a/example/distance_timebin.py b/example/distance_timebin.py
index a69aad8c..859fa1a2 100644
--- a/example/distance_timebin.py
+++ b/example/distance_timebin.py
@@ -13,8 +13,8 @@
 
 # dummy parent class to receive BB84 keys and initiate BB84
 class Parent(StackProtocol):
-    def __init__(self, own: "Node", keysize: int, role: str):
-        super().__init__(own, "")
+    def __init__(self, owner: "Node", keysize: int, role: str):
+        super().__init__(owner, "")
         self.upper_protocols = []
         self.lower_protocols = []
         self.keysize = keysize
diff --git a/pyproject.toml b/pyproject.toml
index 382ef29d..22dda7bd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,11 +1,35 @@
 [project]
 name = "sequence"
-version = "0.6.2.2"
+authors = [
+    {name = "Xiaoliang Wu", email = "xwu64@hawk.iit.edu"},
+    {name = "Joaquin Chung", email = "chungmiranda@anl.gov"},
+    {name = "Alexander Kolar", email = "akolar@anl.gov"},
+    {name = "Alexander Kiefer", email = "akiefer@iu.edu"},
+    {name = "Eugene Wang", email = "eugenewang@yahoo.com"},
+    {name = "Tian Zhong", email = "tzh@uchicago.edu"},
+    {name = "Rajkumar Kettimuthu", email = "kettimut@mcs.anl.gov"},
+    {name = "Martin Suchara", email = "msuchara@anl.gov"},
+]
+version = "0.6.3"
 description = "Simulator of QUantum Network Communication (SeQUeNCe) is an open-source tool that allows modeling of quantum networks including photonic network components, control protocols, and applications."
 readme = "README.md"
-requires-python = ">=3.8"
+requires-python = ">=3.9"
+dynamic = ["dependencies"]
+
+
+[tool.setuptools.dynamic]
+dependencies = {file = ["requirements.txt"]}
+
+[tool.setuptools]
+packages = ['sequence', 'sequence.app', 'sequence.kernel', 'sequence.components',
+            'sequence.network_management', 'sequence.entanglement_management', 'sequence.qkd',
+            'sequence.resource_management', 'sequence.topology', 'sequence.utils', 'sequence.gui']
+
+[tool.setuptools.package-data]
+sequence = ["gui/user_templates.json", "gui/default_params.json", "gui/starlight.json"]
 
 [project.urls]
-homepage = "https://github.com/sequence-toolbox/SeQUeNCe"
-documentation = "https://sequence-toolbox.github.io/"
-issues = "https://github.com/sequence-toolbox/SeQUeNCe/issues"
+Homepage = "https://github.com/sequence-toolbox/SeQUeNCe"
+Documentation = "https://sequence-toolbox.github.io/"
+Issues = "https://github.com/sequence-toolbox/SeQUeNCe/issues"
+Change-log = "https://github.com/sequence-toolbox/SeQUeNCe/blob/master/CHANGELOG.md"
diff --git a/requirements.txt b/requirements.txt
index 3c306689..08ea5cf3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 numpy>=1.22
-scipy<=1.10.1
+scipy==1.11.4
 matplotlib
 dash>=1.20.0
 dash-core-components
@@ -11,4 +11,5 @@ plotly
 pandas
 qutip==4.7.5
 tqdm>=4.54.0
-networkx
\ No newline at end of file
+networkx
+pytest
\ No newline at end of file
diff --git a/src/__init__.py b/sequence/__init__.py
similarity index 90%
rename from src/__init__.py
rename to sequence/__init__.py
index 708f9dd8..4c9badab 100644
--- a/src/__init__.py
+++ b/sequence/__init__.py
@@ -1,7 +1,7 @@
 __all__ = ['app', 'components', 'entanglement_management', 'kernel', 'network_management', 'qkd', 'resource_management',
            'topology', 'utils', 'message', 'protocol', 'gui']
 
-__version__ = '0.6.2.2'
+__version__ = '0.6.3'
 
 def __dir__():
     return sorted(__all__)
diff --git a/src/app/__init__.py b/sequence/app/__init__.py
similarity index 100%
rename from src/app/__init__.py
rename to sequence/app/__init__.py
diff --git a/src/app/random_request.py b/sequence/app/random_request.py
similarity index 98%
rename from src/app/random_request.py
rename to sequence/app/random_request.py
index 2ab33016..a4ce1120 100644
--- a/src/app/random_request.py
+++ b/sequence/app/random_request.py
@@ -137,7 +137,7 @@ def _update_last_rsvp_metrics(self):
         self.memory_counter = 0
         self.path = []
 
-    def get_reserve_res(self, reservation: Reservation, result: bool) -> None:
+    def get_reservation_result(self, reservation: Reservation, result: bool) -> None:
         """Method to receive reservation result from network manager.
 
         Args:
@@ -148,7 +148,7 @@ def get_reserve_res(self, reservation: Reservation, result: bool) -> None:
             May schedule a start/retry event based on reservation result.
         """
 
-        super().get_reserve_res(reservation, result)
+        super().get_reservation_result(reservation, result)
         if result:
             process = Process(self, "start", [])
             self.reserves.append([self.responder, self.start_t, self.end_t,
diff --git a/src/app/request_app.py b/sequence/app/request_app.py
similarity index 67%
rename from src/app/request_app.py
rename to sequence/app/request_app.py
index 32826e3d..c4cfa947 100644
--- a/src/app/request_app.py
+++ b/sequence/app/request_app.py
@@ -16,11 +16,9 @@ class RequestApp:
 
         This application will create a request for entanglement.
         If the request is accepted, the network will start to serve the request
-         at the start time of the request and end at the end time of the
-        request.
+        at the start time of the request and end at the end time of the request.
         Otherwise, the app do nothing.
-        The information about the request is defined in the arguments of the
-        start function.
+        The information about the request is defined in the arguments of the start function.
 
         Attributes:
             node (QuantumRouter): Node that code is attached to.
@@ -29,10 +27,11 @@ class RequestApp:
             end_t (int): the end time of request (ps)
             memo_size (int): the size of memory used for the request
             fidelity (float): the target fidelity of the entanglement
-            reserve_res (bool): if network approves the request
+            reservation_result (bool): if network approves the request
             memory_counter (int): number of successfully received memories
             path (List[str]): the path of flow denoted by a list of node names
             memo_to_reserve (Dict[int, Reservation]): mapping of memory index to corresponding reservation.
+            name (str): the name of the app
     """
 
     def __init__(self, node: QuantumRouter):
@@ -43,17 +42,16 @@ def __init__(self, node: QuantumRouter):
         self.end_t: int = -1
         self.memo_size: int = 0
         self.fidelity: float = 0
-        self.reserve_res: bool = None
+        self.reservation_result: bool = False
         self.memory_counter: int = 0
         self.path: List[str] = []
-        self.memo_to_reserve: Dict[int, Reservation] = {}
+        self.memo_to_reservation: Dict[int, Reservation] = {}
+        self.name: str = f"{self.node.name}.RequestApp"
 
-    def start(self, responder: str, start_t: int, end_t: int, memo_size: int,
-              fidelity: float):
+    def start(self, responder: str, start_t: int, end_t: int, memo_size: int, fidelity: float):
         """Method to start the application.
 
-            This method will use arguments to create a request and send to the
-            network.
+            This method will use arguments to create a request and send to the network.
 
         Side Effects:
             Will create request for network manager on node.
@@ -69,8 +67,10 @@ def start(self, responder: str, start_t: int, end_t: int, memo_size: int,
 
         self.node.reserve_net_resource(responder, start_t, end_t, memo_size, fidelity)
 
-    def get_reserve_res(self, reservation: "Reservation", result: bool) -> None:
-        """Method to receive reservation result from network manager.
+    def get_reservation_result(self, reservation: "Reservation", result: bool) -> None:
+        """Method to receive reservation result from network manager. 
+
+        The initiator will call this method once received a response from the responder.
 
         Args:
             reservation (Reservation): reservation that has been completed.
@@ -79,18 +79,29 @@ def get_reserve_res(self, reservation: "Reservation", result: bool) -> None:
         Side Effects:
             May schedule a start/retry event based on reservation result.
         """
-        self.reserve_res = result
+        self.reservation_result = result
         if result:
             self.schedule_reservation(reservation)
-            log.logger.info("Successful reservation of resources for request app on node {}".format(
-                self.node.name
-            ))
+            log.logger.info("Successful reservation of resources for request app on node {}".format(self.node.name))
+
+    def add_memo_reservation_map(self, index: int, reservation: "Reservation") -> None:
+        """Maps memory index to the corresponding reservation.
+
+        Args:
+            index (int): Memory index
+            reservation (Reservation): Reservation that index should map to.
+        """
+        self.memo_to_reservation[index] = reservation
 
-    def add_memo_reserve_map(self, index: int, reservation: "Reservation") -> None:
-        self.memo_to_reserve[index] = reservation
+    def remove_memo_reservation_map(self, index: int) -> None:
+        """Function to remove mapping from self.memo_to_reservation.
 
-    def remove_memo_reserve_map(self, index: int) -> None:
-        self.memo_to_reserve.pop(index)
+        When a reservation ends, it should be removed from mapping by this method.
+
+        Args:
+            index (int): The memory index to remove.
+        """
+        self.memo_to_reservation.pop(index)
 
     def get_memory(self, info: "MemoryInfo") -> None:
         """Method to receive entangled memories.
@@ -109,40 +120,46 @@ def get_memory(self, info: "MemoryInfo") -> None:
         if info.state != "ENTANGLED":
             return
 
-        if info.index in self.memo_to_reserve:
-            reservation = self.memo_to_reserve[info.index]
+        if info.index in self.memo_to_reservation:
+            reservation = self.memo_to_reservation[info.index]
             if info.remote_node == reservation.initiator and info.fidelity >= reservation.fidelity:
                 self.node.resource_manager.update(None, info.memory, "RAW")
             elif info.remote_node == reservation.responder and info.fidelity >= reservation.fidelity:
                 self.memory_counter += 1
-                log.logger.info("Successfully generated entanglement. Counter is at {}.".format(
-                    self.memory_counter
-                ))
+                log.logger.info("Successfully generated entanglement. Counter is at {}.".format(self.memory_counter))
                 self.node.resource_manager.update(None, info.memory, "RAW")
 
     def get_throughput(self) -> float:
         return self.memory_counter / (self.end_t - self.start_t) * 1e12
 
     def get_other_reservation(self, reservation: "Reservation") -> None:
-        """Method to add the approved reservation that is requested by other
-        nodes
+        """Method to add the approved reservation that is requested by other nodes. The responder will call this method
 
         Args:
             reservation (Reservation): reservation that uses the node of application as the responder
 
         Side Effects:
-            Will add calls to `add_memo_reserve_map` and `remove_memo_reserve_map` methods.
+            Will add calls to `add_memo_reservation_map` and `remove_memo_reservation_map` methods.
         """
         self.schedule_reservation(reservation)
 
     def schedule_reservation(self, reservation: "Reservation") -> None:
         if reservation.initiator == self.node.name:
+            # NOTE: self.path will be an issue when there are multiple reservation.path at the same time
             self.path = reservation.path
-        for card in self.node.network_manager.protocol_stack[1].timecards:
+
+        reservation_protocol = self.node.network_manager.protocol_stack[1]
+        for card in reservation_protocol.timecards:
             if reservation in card.reservations:
-                process = Process(self, "add_memo_reserve_map", [card.memory_index, reservation])
+                process = Process(self, "add_memo_reservation_map", [card.memory_index, reservation])
                 event = Event(reservation.start_time, process)
                 self.node.timeline.schedule(event)
-                process = Process(self, "remove_memo_reserve_map", [card.memory_index])
+                process = Process(self, "remove_memo_reservation_map", [card.memory_index])
                 event = Event(reservation.end_time, process)
                 self.node.timeline.schedule(event)
+
+    def set_name(self, name: str):
+        self.name = name
+
+    def __str__(self) -> str:
+        return self.name
diff --git a/src/components/__init__.py b/sequence/components/__init__.py
similarity index 100%
rename from src/components/__init__.py
rename to sequence/components/__init__.py
diff --git a/src/components/beam_splitter.py b/sequence/components/beam_splitter.py
similarity index 100%
rename from src/components/beam_splitter.py
rename to sequence/components/beam_splitter.py
diff --git a/src/components/bsm.py b/sequence/components/bsm.py
similarity index 94%
rename from src/components/bsm.py
rename to sequence/components/bsm.py
index a5e69641..d2694a82 100644
--- a/src/components/bsm.py
+++ b/sequence/components/bsm.py
@@ -1,6 +1,7 @@
 """Models for simulating bell state measurement.
 
-This module defines a template bell state measurement (BSM) class, as well as implementations for polarization, time bin, and memory encoding schemes.
+This module defines a template bell state measurement (BSM) class,
+as well as implementations for polarization, time bin, and memory encoding schemes.
 Also defined is a function to automatically construct a BSM of a specified type.
 """
 
@@ -117,7 +118,8 @@ def __init__(self, name, timeline, phase_error=0, detectors=None):
             name (str): name of the beamsplitter instance.
             timeline (Timeline): simulation timeline.
             phase_error (float): Phase error applied to polarization photons (default 0).
-            detectors (List[Dict[str, Any]]): List of parameters for attached detectors, in dictionary format (default []).
+            detectors (List[Dict[str, Any]]): List of parameters for attached detectors,
+                in dictionary format (default None).
         """
 
         super().__init__(name, timeline)
@@ -131,7 +133,7 @@ def __init__(self, name, timeline, phase_error=0, detectors=None):
         if detectors is not None:
             for i, d in enumerate(detectors):
                 if d is not None:
-                    detector = Detector("%s_%d" % (self.name, i), timeline, **d)
+                    detector = Detector("{}_{}".format(self.name, i), timeline, **d)
                     detector.attach(self)
                     detector.owner = self
                 else:
@@ -216,7 +218,8 @@ def __init__(self, name, timeline, phase_error=0, detectors=None):
             name (str): name of the BSM instance.
             timeline (Timeline): simulation timeline.
             phase_error (float): phase error applied to polarization photons (default 0).
-            detectors (List[Dict]): list of parameters for attached detectors, in dictionary format (must be of length 4) (default []).
+            detectors (List[Dict]): list of parameters for attached detectors,
+                in dictionary format (must be of length 4) (default None).
         """
 
         super().__init__(name, timeline, phase_error, detectors)
@@ -316,7 +319,8 @@ def __init__(self, name, timeline, phase_error=0, detectors=None):
             name (str): name of the beamsplitter instance.
             timeline (Timeline): simulation timeline.
             phase_error (float): phase error applied to polarization qubits (unused) (default 0).
-            detectors (List[Dict]): list of parameters for attached detectors, in dictionary format (must be of length 2) (default []).
+            detectors (List[Dict]): list of parameters for attached detectors,
+                in dictionary format (must be of length 2) (default None).
         """
 
         super().__init__(name, timeline, phase_error, detectors)
@@ -419,6 +423,7 @@ class SingleAtomBSM(BSM):
     Attributes:
         name (str): label for BSM instance
         timeline (Timeline): timeline for simulation
+        phase_error (float): phase error applied to measurement.
         detectors (List[Detector]): list of attached photon detection devices
         resolution (int): maximum time resolution achievable with attached detectors  
     """
@@ -432,7 +437,8 @@ def __init__(self, name, timeline, phase_error=0, detectors=None):
             name (str): name of the beamsplitter instance.
             timeline (Timeline): simulation timeline.
             phase_error (float): phase error applied to polarization qubits (unused) (default 0).
-            detectors (List[Dict]): list of parameters for attached detectors, in dictionary format (must be of length 2) (default []).
+            detectors (List[Dict]): list of parameters for attached detectors,
+                in dictionary format (must be of length 2) (default None).
         """
 
         if detectors is None:
@@ -460,14 +466,13 @@ def get(self, photon, **kwargs):
             key0, key1 = p0.quantum_state, p1.quantum_state
             keys = [key0, key1]
             state0, state1 = qm.get(key0), qm.get(key1)
-            meas0, meas1 = [qm.run_circuit(self._meas_circuit, [key],
-                                           self.get_generator().random())[key]
+            meas0, meas1 = [qm.run_circuit(self._meas_circuit, [key], self.get_generator().random())[key]
                             for key in keys]
 
             log.logger.debug(self.name + " measured photons as {}, {}".format(meas0, meas1))
 
-            if meas0 ^ meas1:
-                detector_num = self.get_generator().choice([0, 1])
+            if meas0 ^ meas1:  # meas0, meas1 = 1, 0 or 0, 1
+                detector_num = self.get_generator().choice([0, 1])   # randomly select a detector number
                 if len(state0.keys) == 1:
                     # if we're in stage 1: we set state to psi+/psi- to mark the
                     # first triggered detector
@@ -481,31 +486,34 @@ def get(self, photon, **kwargs):
                     # twice to assign state to psi+ or psi-
                     log.logger.info(self.name + " passed stage 2")
                     if _eq_psi_plus(state0, qm.formalism) ^ detector_num:
-                        _set_state_with_fidelity(keys, BSM._psi_minus,
-                                                 p0.encoding_type["raw_fidelity"],
-                                                 self.get_generator(),
-                                                 qm)
+                        _set_state_with_fidelity(keys, BSM._psi_minus, p0.encoding_type["raw_fidelity"],
+                                                 self.get_generator(), qm)
                     else:
-                        _set_state_with_fidelity(keys, BSM._psi_plus,
-                                                 p0.encoding_type["raw_fidelity"],
-                                                 self.get_generator(),
-                                                 qm)
+                        _set_state_with_fidelity(keys, BSM._psi_plus, p0.encoding_type["raw_fidelity"],
+                                                 self.get_generator(), qm)
                 else:
                     raise NotImplementedError("Unknown state")
 
                 photon = p0 if meas0 else p1
                 if self.get_generator().random() > photon.loss:
                     log.logger.info("Triggering detector {}".format(detector_num))
+                    # middle BSM node notify two end nodes via EntanglementGenerationB.bsm_update()
                     self.detectors[detector_num].get()
+                else:
+                    log.logger.info(f'{self.name} lost photon p{meas1}')
 
-            else:
+            else:  # meas0, meas1 = 1, 1 or 0, 0
                 if meas0 and self.get_generator().random() > p0.loss:
                     detector_num = self.get_generator().choice([0, 1])
                     self.detectors[detector_num].get()
+                else:
+                    log.logger.info(f'{self.name} lost photon p0')
 
                 if meas1 and self.get_generator().random() > p1.loss:
                     detector_num = self.get_generator().choice([0, 1])
                     self.detectors[detector_num].get()
+                else:
+                    log.logger.info(f'{self.name} lost photon p1')
 
     def trigger(self, detector: Detector, info: Dict[str, Any]):
         """See base class.
diff --git a/src/components/circuit.py b/sequence/components/circuit.py
similarity index 100%
rename from src/components/circuit.py
rename to sequence/components/circuit.py
diff --git a/src/components/detector.py b/sequence/components/detector.py
similarity index 98%
rename from src/components/detector.py
rename to sequence/components/detector.py
index 2256a23e..7f11d1b2 100644
--- a/src/components/detector.py
+++ b/sequence/components/detector.py
@@ -1,7 +1,8 @@
 """Models for photon detection devices.
 
 This module models a single photon detector (SPD) for measurement of individual photons.
-It also defines a QSDetector class, which combines models of different hardware devices to measure photon states in different bases.
+It also defines a QSDetector class,
+which combines models of different hardware devices to measure photon states in different bases.
 QSDetector is defined as an abstract template and as implementations for polarization and time bin qubits.
 """
 
@@ -23,6 +24,7 @@
 from ..kernel.event import Event
 from ..kernel.process import Process
 from ..utils.encoding import time_bin
+from ..utils import log
 
 
 class Detector(Entity):
@@ -44,8 +46,7 @@ class Detector(Entity):
     _meas_circuit = Circuit(1)
     _meas_circuit.measure(0)
 
-    def __init__(self, name: str, timeline: "Timeline", efficiency=0.9, dark_count=0, count_rate=int(25e6),
-                 time_resolution=150):
+    def __init__(self, name: str, timeline: "Timeline", efficiency=0.9, dark_count=0, count_rate=int(25e6), time_resolution=150):
         Entity.__init__(self, name, timeline)  # Detector is part of the QSDetector, and does not have its own name
         self.efficiency = efficiency
         self.dark_count = dark_count  # measured in 1/s
@@ -76,14 +77,15 @@ def get(self, photon=None, **kwargs) -> None:
         # if get a photon and it has single_atom encoding, measure
         if photon and photon.encoding_type["name"] == "single_atom":
             key = photon.quantum_state
-            res = self.timeline.quantum_manager.run_circuit(Detector._meas_circuit, [key],
-                                                            self.get_generator().random())
+            res = self.timeline.quantum_manager.run_circuit(Detector._meas_circuit, [key], self.get_generator().random())
             # if we measure |0>, return (do not record detection)
             if not res[key]:
                 return
 
         if self.get_generator().random() < self.efficiency:
             self.record_detection()
+        else:
+            log.logger.debug(f'Photon loss in detector {self.name}')
 
     def add_dark_count(self) -> None:
         """Method to schedule false positive detection events.
@@ -488,7 +490,7 @@ def _generate_povms(self):
         povm0_1 = eye((truncation+1) ** 2) - povm1_1
         # for detector2 (index 1)
         series_elem_list2 = [(-1)**i * fractional_matrix_power(create2, i+1).dot(
-            fractional_matrix_power(destroy2,i+1)) / factorial(i+1) for i in range(truncation)]
+            fractional_matrix_power(destroy2, i+1)) / factorial(i+1) for i in range(truncation)]
         povm1_2 = sum(series_elem_list2)
         povm0_2 = eye((truncation+1) ** 2) - povm1_2
 
diff --git a/src/components/fiber_stretcher.py b/sequence/components/fiber_stretcher.py
similarity index 100%
rename from src/components/fiber_stretcher.py
rename to sequence/components/fiber_stretcher.py
diff --git a/src/components/interferometer.py b/sequence/components/interferometer.py
similarity index 100%
rename from src/components/interferometer.py
rename to sequence/components/interferometer.py
diff --git a/src/components/light_source.py b/sequence/components/light_source.py
similarity index 98%
rename from src/components/light_source.py
rename to sequence/components/light_source.py
index cd7f62bd..0e787ac9 100644
--- a/src/components/light_source.py
+++ b/sequence/components/light_source.py
@@ -100,7 +100,7 @@ def emit(self, state_list) -> None:
 
 
 class SPDCSource(LightSource):
-    """Model for a laser light source for entangled photons (via SPDC).
+    """Model for a laser light source for entangled photons, via spontaneous parametric down-conversion (SPDC).
 
     The SPDCLightSource component acts as a simple low intensity laser with an SPDC lens.
     It provides entangled photon clusters at a set frequency.
@@ -216,7 +216,7 @@ def emit(self, state_list):
                     self.send_photons(time, [new_photon0, new_photon1])
                     self.photon_counter += 1
 
-                if num_photon_pairs is 0:
+                if num_photon_pairs == 0:
                     # send two null photons for purposes of entanglement
                     new_photon0 = Photon("", self.timeline,
                                          wavelength=self.wavelengths[0],
diff --git a/src/components/memory.py b/sequence/components/memory.py
similarity index 97%
rename from src/components/memory.py
rename to sequence/components/memory.py
index 8345d129..35dd1b56 100644
--- a/src/components/memory.py
+++ b/sequence/components/memory.py
@@ -27,10 +27,10 @@ def const(t):
     return 1
 
 
-# array of single atom memories
 class MemoryArray(Entity):
     """Aggregator for Memory objects.
 
+    Equivalent to an array of single atom memories.
     The MemoryArray can be accessed as a list to get individual memories.
 
     Attributes:
@@ -56,18 +56,23 @@ def __init__(self, name: str, timeline: "Timeline", num_memories=10,
 
         Entity.__init__(self, name, timeline)
         self.memories = []
+        self.memory_name_to_index = {}
 
         for i in range(num_memories):
-            memory = Memory(self.name + "[%d]" % i, timeline, fidelity, frequency, efficiency, coherence_time,
-                            wavelength)
+            memory_name = self.name + f"[{i}]"
+            self.memory_name_to_index[memory_name] = i
+            memory = Memory(memory_name, timeline, fidelity, frequency, efficiency, coherence_time, wavelength)
             memory.attach(self)
             self.memories.append(memory)
             memory.set_memory_array(self)
 
-    def __getitem__(self, key):
+    def __getitem__(self, key: int) -> "Memory":
         return self.memories[key]
 
-    def __len__(self):
+    def __setitem__(self, key: int, value: "Memory"):
+        self.memories[key] = value
+
+    def __len__(self) -> int:
         return len(self.memories)
 
     def init(self):
@@ -96,6 +101,11 @@ def add_receiver(self, receiver: "Entity"):
         for m in self.memories:
             m.add_receiver(receiver)
 
+    def get_memory_by_name(self, name: str) -> "Memory":
+        index = self.memory_name_to_index.get(name, -1)
+        assert index >= 0, "Oops! name={} not exist!"
+        return self.memories[index]
+
 
 class Memory(Entity):
     """Individual single-atom memory.
@@ -112,6 +122,7 @@ class Memory(Entity):
         coherence_time (float): average usable lifetime of memory (in seconds).
         wavelength (float): wavelength (in nm) of emitted photons.
         qstate_key (int): key for associated quantum state in timeline's quantum manager.
+        memory_array (MemoryArray): memory array aggregating current memory.
         entangled_memory (Dict[str, Any]): tracks entanglement state of memory.
     """
 
@@ -286,7 +297,7 @@ def notify(self, msg: Dict[str, Any]):
         for observer in self._observers:
             observer.memory_expire(self)
 
-    def detach(self, observer: 'EntanglementProtocol'):
+    def detach(self, observer: 'EntanglementProtocol'):  # observer could be a MemoryArray
         if observer in self._observers:
             self._observers.remove(observer)
 
diff --git a/src/components/mirror.py b/sequence/components/mirror.py
similarity index 100%
rename from src/components/mirror.py
rename to sequence/components/mirror.py
diff --git a/src/components/optical_channel.py b/sequence/components/optical_channel.py
similarity index 85%
rename from src/components/optical_channel.py
rename to sequence/components/optical_channel.py
index 89c650bd..95c41e30 100644
--- a/src/components/optical_channel.py
+++ b/sequence/components/optical_channel.py
@@ -18,6 +18,7 @@
 from ..kernel.event import Event
 from ..kernel.process import Process
 from ..utils import log
+from ..constants import SPEED_OF_LIGHT
 
 
 class OpticalChannel(Entity):
@@ -27,7 +28,7 @@ class OpticalChannel(Entity):
         name (str): label for channel instance.
         timeline (Timeline): timeline for simulation.
         sender (Node): node at sending end of optical channel.
-        receiver (Node): node at receiving end of optical channel.
+        receiver (str): name of the node at receiving end of optical channel.
         attenuation (float): attenuation of the fiber (in dB/m).
         distance (int): length of the fiber (in m).
         polarization_fidelity (float): probability of no polarization error for a transmitted qubit.
@@ -71,7 +72,7 @@ class QuantumChannel(OpticalChannel):
         name (str): label for channel instance.
         timeline (Timeline): timeline for simulation.
         sender (Node): node at sending end of optical channel.
-        receiver (Node): node at receiving end of optical channel.
+        receiver (str): name of the node at receiving end of optical channel.
         attenuation (float): attenuation of the fiber (in dB/m).
         distance (int): length of the fiber (in m).
         polarization_fidelity (float): probability of no polarization error for a transmitted qubit.
@@ -82,7 +83,7 @@ class QuantumChannel(OpticalChannel):
     """
 
     def __init__(self, name: str, timeline: "Timeline", attenuation: float, distance: int,
-                 polarization_fidelity=1.0, light_speed=2e-4, frequency=8e7):
+                 polarization_fidelity=1.0, light_speed=SPEED_OF_LIGHT, frequency=8e7):
         """Constructor for Quantum Channel class.
 
         Args:
@@ -91,7 +92,7 @@ def __init__(self, name: str, timeline: "Timeline", attenuation: float, distance
             attenuation (float): loss rate of optical fiber (in dB/m).
             distance (int): length of fiber (in m).
             polarization_fidelity (float): probability of no polarization error for a transmitted qubit (default 1).
-            light_speed (float): speed of light within the fiber (in m/ps) (default 2e-4).
+            light_speed (float): speed of light within the fiber (in m/ps).
             frequency (float): maximum frequency of qubit transmission (in Hz) (default 8e7).
         """
 
@@ -117,10 +118,7 @@ def set_ends(self, sender: "Node", receiver: str) -> None:
             receiver (str): name of node receiving qubits.
         """
 
-        log.logger.info(
-            "Set {} {} as ends of quantum channel {}".format(sender.name,
-                                                             receiver,
-                                                             self.name))
+        log.logger.info("Set {}, {} as ends of quantum channel {}".format(sender.name, receiver, self.name))
         self.sender = sender
         self.receiver = receiver
         sender.assign_qchannel(self, receiver)
@@ -136,13 +134,10 @@ def transmit(self, qubit: "Photon", source: "Node") -> None:
             Receiver node may receive the qubit (via the `receive_qubit` method).
         """
 
-        log.logger.info(
-            "{} send qubit with state {} to {} by Channel {}".format(
-                self.sender.name, qubit.quantum_state, self.receiver,
-                self.name))
+        log.logger.info("{} send qubit with state {} to {} by Channel {}".format(
+                        self.sender.name, qubit.quantum_state, self.receiver, self.name))
 
-        assert self.delay >= 0 and self.loss < 1, \
-            "QuantumChannel init() function has not been run for {}".format(self.name)
+        assert self.delay >= 0 and self.loss < 1, "QuantumChannel init() function has not been run for {}".format(self.name)
         assert source == self.sender
 
         # remove lowest time bin
@@ -168,15 +163,13 @@ def transmit(self, qubit: "Photon", source: "Node") -> None:
         # if not using Fock representation, check if photon kept
         elif (self.sender.get_generator().random() > self.loss) or qubit.is_null:
             if self._receiver_on_other_tl():
-                self.timeline.quantum_manager.move_manage_to_server(
-                    qubit.quantum_state)
+                self.timeline.quantum_manager.move_manage_to_server(qubit.quantum_state)
 
             if qubit.is_null:
                 qubit.add_loss(self.loss)
 
             # check if polarization encoding and apply necessary noise
-            if (qubit.encoding_type["name"] == "polarization") and (
-                    self.sender.get_generator().random() > self.polarization_fidelity):
+            if qubit.encoding_type["name"] == "polarization" and self.sender.get_generator().random() > self.polarization_fidelity:
                 qubit.random_noise(self.get_generator())
 
             # schedule receiving node to receive photon at future time determined by light speed
@@ -207,7 +200,7 @@ def schedule_transmit(self, min_time: int) -> int:
         min_time = max(min_time, self.timeline.now())
         time_bin = min_time * (self.frequency / 1e12)
         if time_bin - int(time_bin) > 0.00001:
-            time_bin = int(time_bin) + 1
+            time_bin = int(time_bin) + 1       # round to the next time bin
         else:
             time_bin = int(time_bin)
 
@@ -233,7 +226,7 @@ class ClassicalChannel(OpticalChannel):
         name (str): label for channel instance.
         timeline (Timeline): timeline for simulation.
         sender (Node): node at sending end of optical channel.
-        receiver (Node): node at receiving end of optical channel.
+        receiver (str): name of the node at receiving end of optical channel.
         distance (float): length of the fiber (in m).
         delay (float): delay (in ps) of message transmission (default distance / light_speed).
     """
@@ -248,7 +241,7 @@ def __init__(self, name: str, timeline: "Timeline", distance: int, delay=-1):
             delay (float): delay (in ps) of message transmission (default distance / light_speed).
         """
 
-        super().__init__(name, timeline, 0, distance, 0, 2e-4)
+        super().__init__(name, timeline, 0, distance, 0, SPEED_OF_LIGHT)
         if delay == -1:
             self.delay = distance / self.light_speed
         else:
@@ -264,10 +257,7 @@ def set_ends(self, sender: "Node", receiver: str) -> None:
             receiver (str): name of node receiving classical messages.
         """
 
-        log.logger.info(
-            "Set {} {} as ends of classical channel {}".format(sender.name,
-                                                               receiver,
-                                                               self.name))
+        log.logger.info("Set {}, {} as ends of classical channel {}".format(sender.name, receiver, self.name))
         self.sender = sender
         self.receiver = receiver
         sender.assign_cchannel(self, receiver)
@@ -284,11 +274,7 @@ def transmit(self, message: "Message", source: "Node", priority: int) -> None:
             Receiver node may receive the qubit (via the `receive_qubit` method).
         """
 
-        log.logger.info(
-            "{} send message {} to {} by Channel {}".format(self.sender.name,
-                                                            message,
-                                                            self.receiver,
-                                                            self.name))
+        log.logger.info("{} send message {} to {} by Channel {}".format(self.sender.name, message, self.receiver, self.name))
         assert source == self.sender
 
         future_time = round(self.timeline.now() + int(self.delay))
diff --git a/src/components/photon.py b/sequence/components/photon.py
similarity index 97%
rename from src/components/photon.py
rename to sequence/components/photon.py
index 3ca36302..00f483ba 100644
--- a/src/components/photon.py
+++ b/sequence/components/photon.py
@@ -29,6 +29,7 @@ class Photon:
             If `use_qm` is false, this will be a QuantumState object.
             Otherwise, it will be an integer key for the quantum manager.
         is_null (bool): defines whether photon is real or a "ghost" photon (not detectable but used in memory encoding).
+                        if True, then it is a "ghost" photon
         loss (float): similarly defined for memory encoding, used to track loss and improve performance.
             Does not need to be utilized for all encoding schemes.
         use_qm (bool): determines if photon stores state locally (False) or uses timeline quantum manager (True).
@@ -172,9 +173,7 @@ def measure_multiple(basis, photons: List["Photon"], rng: "Generator"):
         if photons[0].use_qm:
             raise NotImplementedError("Photon.measure_multiple() not implemented for quantum manager.")
 
-        return FreeQuantumState.measure_multiple(basis,
-                                                 [photons[0].quantum_state, photons[1].quantum_state],
-                                                 rng)
+        return FreeQuantumState.measure_multiple(basis, [photons[0].quantum_state, photons[1].quantum_state], rng)
 
     def add_loss(self, loss: float):
         assert 0 <= loss <= 1
diff --git a/src/components/spdc_lens.py b/sequence/components/spdc_lens.py
similarity index 100%
rename from src/components/spdc_lens.py
rename to sequence/components/spdc_lens.py
diff --git a/src/components/switch.py b/sequence/components/switch.py
similarity index 100%
rename from src/components/switch.py
rename to sequence/components/switch.py
diff --git a/sequence/constants.py b/sequence/constants.py
new file mode 100644
index 00000000..e0d0fed7
--- /dev/null
+++ b/sequence/constants.py
@@ -0,0 +1,32 @@
+"""useful constants"""
+
+from typing import Final
+from numpy import array
+
+
+# speed of light in (m / pico second)
+SPEED_OF_LIGHT: Final = 2e-4
+
+SQRT_HALF: Final = 0.5 ** 0.5
+
+# four Bell states
+PHI_PLUS: Final = array([SQRT_HALF, 0, 0,  SQRT_HALF])
+PHI_MINUS: Final = array([SQRT_HALF, 0, 0, -SQRT_HALF])
+PSI_PLUS: Final = array([0, SQRT_HALF,  SQRT_HALF, 0])
+PSI_MINUS: Final = array([0, SQRT_HALF, -SQRT_HALF, 0])
+
+# machine epsilon, i.e., a small number
+EPSILON: Final = 1e-7
+
+# convert to picosecond
+NANOSECOND: Final = 1e3
+MICROSECOND: Final = 1e6
+MILLISECOND: Final = 1e9
+SECOND: Final = 1e12
+
+# for timeline formatting
+NANOSECONDS_PER_MILLISECOND: Final = 1e6
+PICOSECONDS_PER_NANOSECOND = NANOSECONDS_PER_MICROSECOND = MILLISECONDS_PER_SECOND = 1e3
+SECONDS_PER_MINUTE = MINUTES_PER_HOUR = 60
+CARRIAGE_RETURN = '\r'
+SLEEP_SECONDS = 3
diff --git a/src/entanglement_management/__init__.py b/sequence/entanglement_management/__init__.py
similarity index 100%
rename from src/entanglement_management/__init__.py
rename to sequence/entanglement_management/__init__.py
diff --git a/src/entanglement_management/entanglement_protocol.py b/sequence/entanglement_management/entanglement_protocol.py
similarity index 93%
rename from src/entanglement_management/entanglement_protocol.py
rename to sequence/entanglement_management/entanglement_protocol.py
index a9a539e8..0b3f8784 100644
--- a/src/entanglement_management/entanglement_protocol.py
+++ b/sequence/entanglement_management/entanglement_protocol.py
@@ -22,8 +22,8 @@ class EntanglementProtocol(Protocol):
         memories (List[Memory]): Any memories being operated on
     """
 
-    def __init__(self, own: "Node", name: str):
-        Protocol.__init__(self, own, name)
+    def __init__(self, owner: "Node", name: str):
+        super().__init__(owner, name)
         self.rule = None
         self.memories = []
 
@@ -77,5 +77,5 @@ def update_resource_manager(self, memory: "Memory", state: str) -> None:
             May alter the state of `memory`.
         """
 
-        self.own.resource_manager.update(self, memory, state)
+        self.owner.resource_manager.update(self, memory, state)
 
diff --git a/src/entanglement_management/generation.py b/sequence/entanglement_management/generation.py
similarity index 67%
rename from src/entanglement_management/generation.py
rename to sequence/entanglement_management/generation.py
index 96f03712..88a367af 100644
--- a/src/entanglement_management/generation.py
+++ b/sequence/entanglement_management/generation.py
@@ -18,6 +18,7 @@
     from ..components.bsm import SingleAtomBSM
     from ..topology.node import Node, BSMNode
 
+from ..resource_management.memory_manager import MemoryInfo
 from .entanglement_protocol import EntanglementProtocol
 from ..message import Message
 from ..kernel.event import Event
@@ -26,23 +27,9 @@
 from ..utils import log
 
 
-# def valid_trigger_time(trigger_time, target_time, resolution):
-#     upper = target_time + resolution
-#     lower = 0
-#     if resolution % 2 == 0:
-#         upper = min(upper, target_time + resolution // 2)
-#         lower = max(lower, target_time - resolution // 2)
-#     else:
-#         upper = min(upper, target_time + resolution // 2 + 1)
-#         lower = max(lower, target_time - resolution // 2)
-#     if (upper / resolution) % 1 >= 0.5:
-#         upper -= 1
-#     if (lower / resolution) % 1 < 0.5:
-#         lower += 1
-#     return lower <= trigger_time <= upper
 
-
-def valid_trigger_time(trigger_time, target_time, resolution):
+def valid_trigger_time(trigger_time: int, target_time: int, resolution: int) -> bool:
+    """return True if the trigger time is valid, else return False."""
     lower = target_time - (resolution // 2)
     upper = target_time + (resolution // 2)
     return lower <= trigger_time <= upper
@@ -90,26 +77,18 @@ def __init__(self, msg_type: GenerationMsgType, receiver: str, **kwargs):
             self.resolution = kwargs.get("resolution")
 
         else:
-            raise Exception("EntanglementGeneration generated invalid message"
-                            " type {}".format(msg_type))
+            raise Exception("EntanglementGeneration generated invalid message type {}".format(msg_type))
 
     def __repr__(self):
         if self.msg_type is GenerationMsgType.NEGOTIATE:
-            return "type:{}, qc_delay:{}, frequency:{}".format(self.msg_type,
-                                                               self.qc_delay,
-                                                               self.frequency)
+            return "type:{}, qc_delay:{}, frequency:{}".format(self.msg_type, self.qc_delay, self.frequency)
         elif self.msg_type is GenerationMsgType.NEGOTIATE_ACK:
-            return "type:{}, emit_time:{}".format(self.msg_type,
-                                                  self.emit_time)
-
+            return "type:{}, emit_time:{}".format(self.msg_type, self.emit_time)
         elif self.msg_type is GenerationMsgType.MEAS_RES:
-            return "type:{}, detector:{}, time:{}, resolution={}" \
-                   "".format(self.msg_type, self.detector,
-                             self.time, self.resolution)
-
+            return "type:{}, detector:{}, time:{}, resolution={}".format(self.msg_type, self.detector, 
+                                                                         self.time, self.resolution)
         else:
-            raise Exception("EntanglementGeneration generated invalid message"
-                            " type {}".format(self.msg_type))
+            raise Exception("EntanglementGeneration generated invalid message type {}".format(self.msg_type))
 
 
 class EntanglementGenerationA(EntanglementProtocol):
@@ -132,18 +111,18 @@ class EntanglementGenerationA(EntanglementProtocol):
     _z_circuit = Circuit(1)
     _z_circuit.z(0)
 
-    def __init__(self, own: "Node", name: str, middle: str, other: str, memory: "Memory"):
+    def __init__(self, owner: "Node", name: str, middle: str, other: str, memory: "Memory"):
         """Constructor for entanglement generation A class.
 
         Args:
-            own (Node): node to attach protocol to.
+            owner (Node): node to attach protocol to.
             name (str): name of protocol instance.
             middle (str): name of middle measurement node.
             other (str): name of other node.
             memory (Memory): memory to entangle.
         """
 
-        super().__init__(own, name)
+        super().__init__(owner, name)
         self.middle: str = middle
         self.remote_node_name: str = other
         self.remote_protocol_name: str = None
@@ -156,7 +135,7 @@ def __init__(self, own: "Node", name: str, middle: str, other: str, memory: "Mem
         # network and hardware info
         self.fidelity: float = memory.raw_fidelity
         self.qc_delay: int = 0
-        self.expected_time: int = -1
+        self.expected_time: int = -1   # expected time for middle BSM node to receive the photon
 
         # memory internal info
         self.ent_round = 0  # keep track of current stage of protocol
@@ -181,10 +160,10 @@ def set_others(self, protocol: str, node: str, memories: List[str]) -> None:
         assert self.remote_protocol_name is None
         self.remote_protocol_name = protocol
         self.remote_memo_id = memories[0]
-        self.primary = self.own.name > self.remote_node_name
+        self.primary = self.owner.name > self.remote_node_name
 
     def start(self) -> None:
-        """Method to start entanglement generation protocol.
+        """Method to start "one round" in the entanglement generation protocol (there are two rounds in Barrett-Kok).
 
         Will start negotiations with other protocol (if primary).
 
@@ -192,24 +171,22 @@ def start(self) -> None:
             Will send message through attached node.
         """
 
-        log.logger.info(f"{self.own.name} protocol start with partner "
-                        f"{self.remote_node_name}")
+        log.logger.info(f"{self.name} protocol start with partner {self.remote_protocol_name}")
 
         # to avoid start after remove protocol
-        if self not in self.own.protocols:
+        if self not in self.owner.protocols:
             return
 
         # update memory, and if necessary start negotiations for round
         if self.update_memory() and self.primary:
             # send NEGOTIATE message
-            self.qc_delay = self.own.qchannels[self.middle].delay
+            self.qc_delay = self.owner.qchannels[self.middle].delay
             frequency = self.memory.frequency
-            message = EntanglementGenerationMessage(
-                GenerationMsgType.NEGOTIATE, self.remote_protocol_name,
-                qc_delay=self.qc_delay, frequency=frequency)
-            self.own.send_message(self.remote_node_name, message)
+            message = EntanglementGenerationMessage(GenerationMsgType.NEGOTIATE, self.remote_protocol_name,
+                                                    qc_delay=self.qc_delay, frequency=frequency)
+            self.owner.send_message(self.remote_node_name, message)
 
-    def update_memory(self):
+    def update_memory(self) -> bool:
         """Method to handle necessary memory operations.
 
         Called on both nodes.
@@ -224,7 +201,7 @@ def update_memory(self):
         """
 
         # to avoid start after protocol removed
-        if self not in self.own.protocols:
+        if self not in self.owner.protocols:
             return
 
         self.ent_round += 1
@@ -233,27 +210,23 @@ def update_memory(self):
             return True
 
         elif self.ent_round == 2 and self.bsm_res[0] != -1:
-            self.own.timeline.quantum_manager.run_circuit(
-                EntanglementGenerationA._flip_circuit, [self._qstate_key])
-
+            self.owner.timeline.quantum_manager.run_circuit(EntanglementGenerationA._flip_circuit, [self._qstate_key])
+            return True
+        
         elif self.ent_round == 3 and self.bsm_res[1] != -1:
-            # successful entanglement
-            # state correction
+            # entanglement succeeded, correction
             if self.primary:
-                self.own.timeline.quantum_manager.run_circuit(
-                    EntanglementGenerationA._flip_circuit, [self._qstate_key])
+                self.owner.timeline.quantum_manager.run_circuit(EntanglementGenerationA._flip_circuit, [self._qstate_key])
             elif self.bsm_res[0] != self.bsm_res[1]:
-                self.own.timeline.quantum_manager.run_circuit(
-                    EntanglementGenerationA._z_circuit, [self._qstate_key])
-
+                self.owner.timeline.quantum_manager.run_circuit(EntanglementGenerationA._z_circuit, [self._qstate_key])
             self._entanglement_succeed()
+            return True
 
         else:
             # entanglement failed
             self._entanglement_fail()
             return False
 
-        return True
 
     def emit_event(self) -> None:
         """Method to set up memory and emit photons.
@@ -290,98 +263,92 @@ def received_message(self, src: str, msg: EntanglementGenerationMessage) -> None
 
         msg_type = msg.msg_type
 
-        log.logger.debug("{} EG protocol received_message of type {} from node"
-                         " {}, round={}".format(self.own.name,
-                                                msg.msg_type,
-                                                src, self.ent_round))
+        log.logger.debug("{} {} received message from node {} of type {}, round={}".format(
+                         self.owner.name, self.name, src, msg.msg_type, self.ent_round))
 
-        if msg_type is GenerationMsgType.NEGOTIATE:
+        if msg_type is GenerationMsgType.NEGOTIATE:  # primary -> non-primary
             # configure params
-            another_delay = msg.qc_delay
-            self.qc_delay = self.own.qchannels[self.middle].delay
-            cc_delay = int(self.own.cchannels[src].delay)
-            total_quantum_delay = max(self.qc_delay, another_delay)
+            other_qc_delay = msg.qc_delay
+            self.qc_delay = self.owner.qchannels[self.middle].delay
+            cc_delay = int(self.owner.cchannels[src].delay)
+            total_quantum_delay = max(self.qc_delay, other_qc_delay)  # two qc_delays are the same for "meet_in_the_middle"
 
             # get time for first excite event
             memory_excite_time = self.memory.next_excite_time
-            min_time = max(self.own.timeline.now(), memory_excite_time) \
-                       + total_quantum_delay - self.qc_delay + cc_delay
-            emit_time = self.own.schedule_qubit(self.middle, min_time)  # used to send memory
-            self.expected_time = emit_time + self.qc_delay
+            min_time = max(self.owner.timeline.now(), memory_excite_time) + total_quantum_delay - self.qc_delay + cc_delay  # cc_delay time for NEGOTIATE_ACK
+            emit_time = self.owner.schedule_qubit(self.middle, min_time)  # used to send memory
+            self.expected_time = emit_time + self.qc_delay  # expected time for middle BSM node to receive the photon
 
             # schedule emit
             process = Process(self, "emit_event", [])
             event = Event(emit_time, process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
             self.scheduled_events.append(event)
 
             # send negotiate_ack
-            another_emit_time = emit_time + self.qc_delay - another_delay
-            message = EntanglementGenerationMessage(
-                GenerationMsgType.NEGOTIATE_ACK,
-                self.remote_protocol_name,
-                emit_time=another_emit_time)
-            self.own.send_message(src, message)
-
-            # schedule start if necessary, else schedule update_memory
+            other_emit_time = emit_time + self.qc_delay - other_qc_delay
+            message = EntanglementGenerationMessage(GenerationMsgType.NEGOTIATE_ACK, self.remote_protocol_name, emit_time=other_emit_time)
+            self.owner.send_message(src, message)
+
+            # schedule start if necessary (current is first round, need second round), else schedule update_memory (currently second round)
             # TODO: base future start time on resolution
-            future_start_time = self.expected_time + self.own.cchannels[self.middle].delay + 10
+            future_start_time = self.expected_time + self.owner.cchannels[self.middle].delay + 10  # delay is for sending the BSM_RES to end nodes, 10 is a small gap
             if self.ent_round == 1:
-                process = Process(self, "start", [])
+                process = Process(self, "start", [])  # for the second round
             else:
                 process = Process(self, "update_memory", [])
             event = Event(future_start_time, process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
             self.scheduled_events.append(event)
 
-        elif msg_type is GenerationMsgType.NEGOTIATE_ACK:
+        elif msg_type is GenerationMsgType.NEGOTIATE_ACK:  # non-primary --> primary
             # configure params
-            self.expected_time = msg.emit_time + self.qc_delay
+            self.expected_time = msg.emit_time + self.qc_delay  # expected time for middle BSM node to receive the photon
 
-            if msg.emit_time < self.own.timeline.now():
-                msg.emit_time = self.own.timeline.now()
+            if msg.emit_time < self.owner.timeline.now():  # emit time calculated by the non-primary node
+                msg.emit_time = self.owner.timeline.now()
 
             # schedule emit
-            emit_time = self.own.schedule_qubit(self.middle, msg.emit_time)
+            emit_time = self.owner.schedule_qubit(self.middle, msg.emit_time)
             assert emit_time == msg.emit_time, \
-                "Invalid eg emit times %d %d %d" % (emit_time,  msg.emit_time, self.own.timeline.now())
+                "Invalid eg emit times {} {} {}".format(emit_time, msg.emit_time, self.owner.timeline.now())
 
             process = Process(self, "emit_event", [])
             event = Event(msg.emit_time, process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
             self.scheduled_events.append(event)
 
-            # schedule start if memory_stage is 0, else schedule update_memory
+            # schedule start if necessary (current is first round, need second round), else schedule update_memory (currently second round)
             # TODO: base future start time on resolution
-            future_start_time = self.expected_time + self.own.cchannels[self.middle].delay + 10
+            future_start_time = self.expected_time + self.owner.cchannels[self.middle].delay + 10
             if self.ent_round == 1:
-                process = Process(self, "start", [])
+                process = Process(self, "start", [])  # for the second round
             else:
                 process = Process(self, "update_memory", [])
             event = Event(future_start_time, process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
             self.scheduled_events.append(event)
 
-        elif msg_type is GenerationMsgType.MEAS_RES:
+        elif msg_type is GenerationMsgType.MEAS_RES:  # from middle BSM to both non-primary and primary
             detector = msg.detector
             time = msg.time
             resolution = msg.resolution
 
-            log.logger.debug("{} received MEAS_RES {} at time {}, expected {},"
-                             " resolution={}, round={}".format(
-                self.own.name, detector, time, self.expected_time, resolution, self.ent_round))
+            log.logger.debug("{} received MEAS_RES={} at time={:,}, expected={:,}, resolution={}, round={}".format(
+                             self.owner.name, detector, time, self.expected_time, resolution, self.ent_round))
 
             if valid_trigger_time(time, self.expected_time, resolution):
                 # record result if we don't already have one
                 i = self.ent_round - 1
                 if self.bsm_res[i] == -1:
-                    self.bsm_res[i] = detector
+                    self.bsm_res[i] = detector  # save the measurement results (detector number)
                 else:
-                    self.bsm_res[i] = -1
+                    self.bsm_res[i] = -1  # BSM measured 1, 1 and both didn't lost
+            else:
+                log.logger.debug('{} BSM trigger time not valid'.format(self.owner.name))
 
         else:
-            raise Exception("Invalid message {} received by EG on node "
-                            "{}".format(msg_type, self.own.name))
+            raise Exception("Invalid message {} received by EG on node {}".format(msg_type, self.owner.name))
 
     def is_ready(self) -> bool:
         return self.remote_protocol_name is not None
@@ -391,25 +358,25 @@ def memory_expire(self, memory: "Memory") -> None:
 
         assert memory == self.memory
 
-        self.update_resource_manager(memory, 'RAW')
+        self.update_resource_manager(memory, MemoryInfo.RAW)
         for event in self.scheduled_events:
-            if event.time >= self.own.timeline.now():
-                self.own.timeline.remove_event(event)
+            if event.time >= self.owner.timeline.now():
+                self.owner.timeline.remove_event(event)
 
     def _entanglement_succeed(self):
-        log.logger.info(self.own.name + " successful entanglement of memory {}".format(self.memory))
+        log.logger.info(self.owner.name + " successful entanglement of memory {}".format(self.memory))
         self.memory.entangled_memory["node_id"] = self.remote_node_name
         self.memory.entangled_memory["memo_id"] = self.remote_memo_id
         self.memory.fidelity = self.memory.raw_fidelity
 
-        self.update_resource_manager(self.memory, 'ENTANGLED')
+        self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED)
 
     def _entanglement_fail(self):
         for event in self.scheduled_events:
-            self.own.timeline.remove_event(event)
-        log.logger.info(self.own.name + " failed entanglement of memory {}".format(self.memory))
+            self.owner.timeline.remove_event(event)
+        log.logger.info(self.owner.name + " failed entanglement of memory {}".format(self.memory))
         
-        self.update_resource_manager(self.memory, 'RAW')
+        self.update_resource_manager(self.memory, MemoryInfo.RAW)
 
 
 class EntanglementGenerationB(EntanglementProtocol):
@@ -424,7 +391,7 @@ class EntanglementGenerationB(EntanglementProtocol):
         others (List[str]): list of neighboring quantum router nodes
     """
 
-    def __init__(self, own: "BSMNode", name: str, others: List[str]):
+    def __init__(self, owner: "BSMNode", name: str, others: List[str]):
         """Constructor for entanglement generation B protocol.
 
         Args:
@@ -433,7 +400,7 @@ def __init__(self, own: "BSMNode", name: str, others: List[str]):
             others (List[str]): name of protocol instance on end nodes.
         """
 
-        super().__init__(own, name)
+        super().__init__(owner, name)
         assert len(others) == 2
         self.others = others  # end nodes
 
@@ -451,10 +418,10 @@ def bsm_update(self, bsm: "SingleAtomBSM", info: Dict[str, Any]):
         time = info["time"]
         resolution = bsm.resolution
 
-        for i, node in enumerate(self.others):
-            message = EntanglementGenerationMessage(GenerationMsgType.MEAS_RES,
-                                                    None, detector=res, time=time, resolution=resolution)
-            self.own.send_message(node, message)
+        for node in self.others:
+            message = EntanglementGenerationMessage(GenerationMsgType.MEAS_RES, None,              # receiver is None (not paired)
+                                                    detector=res, time=time, resolution=resolution)
+            self.owner.send_message(node, message)
 
     def received_message(self, src: str, msg: EntanglementGenerationMessage):
         raise Exception("EntanglementGenerationB protocol '{}' should not "
diff --git a/src/entanglement_management/purification.py b/sequence/entanglement_management/purification.py
similarity index 89%
rename from src/entanglement_management/purification.py
rename to sequence/entanglement_management/purification.py
index a2a801c7..e7796791 100644
--- a/src/entanglement_management/purification.py
+++ b/sequence/entanglement_management/purification.py
@@ -36,11 +36,14 @@ class BBPSSWMessage(Message):
     """
 
     def __init__(self, msg_type: BBPSSWMsgType, receiver: str, **kwargs):
-        Message.__init__(self, msg_type, receiver)
+        super().__init__(msg_type, receiver)
         if self.msg_type is BBPSSWMsgType.PURIFICATION_RES:
             self.meas_res = kwargs['meas_res']
         else:
             raise Exception("BBPSSW protocol create unknown type of message: %s" % str(msg_type))
+    
+    def __str__(self):
+        return f"\"BBPSSW: type={self.msg_type}, meas_res={self.meas_res}\""
 
 
 class BBPSSW(EntanglementProtocol):
@@ -67,18 +70,18 @@ class BBPSSW(EntanglementProtocol):
     circuit.cx(0, 1)
     circuit.measure(1)
 
-    def __init__(self, own: "Node", name: str, kept_memo: "Memory", meas_memo: "Memory"):
+    def __init__(self, owner: "Node", name: str, kept_memo: "Memory", meas_memo: "Memory"):
         """Constructor for purification protocol.
 
         Args:
-            own (Node): node protocol is attached to.
+            owner (Node): node protocol is attached to.
             name (str): name of protocol instance.
             kept_memo (Memory): memory to have fidelity improved.
             meas_memo (Memory): memory to measure and discard.
         """
 
         assert kept_memo != meas_memo
-        EntanglementProtocol.__init__(self, own, name)
+        EntanglementProtocol.__init__(self, owner, name)
         self.memories: List[Memory] = [kept_memo, meas_memo]
         self.kept_memo: Memory = kept_memo
         self.meas_memo: Memory = meas_memo
@@ -134,17 +137,17 @@ def start(self) -> None:
             Will send message to other protocol instance.
         """
 
-        log.logger.info(f"{self.own.name} protocol start with partner {self.remote_node_name}")
+        log.logger.info(f"{self.owner.name} protocol start with partner {self.remote_node_name}")
 
         assert self.is_ready(), "other protocol is not set; please use set_others function to set it."
         kept_memo_ent = self.kept_memo.entangled_memory["node_id"]
         meas_memo_ent = self.meas_memo.entangled_memory["node_id"]
         assert kept_memo_ent == meas_memo_ent, "mismatch of entangled memories {}, {} on node {}".format(
-            kept_memo_ent, meas_memo_ent, self.own.name)
+            kept_memo_ent, meas_memo_ent, self.owner.name)
         assert self.kept_memo.fidelity == self.meas_memo.fidelity > 0.5
 
-        meas_samp = self.own.get_generator().random()
-        self.meas_res = self.own.timeline.quantum_manager.run_circuit(
+        meas_samp = self.owner.get_generator().random()
+        self.meas_res = self.owner.timeline.quantum_manager.run_circuit(
             self.circuit, [self.kept_memo.qstate_key,
                            self.meas_memo.qstate_key],
             meas_samp)
@@ -154,7 +157,7 @@ def start(self) -> None:
         message = BBPSSWMessage(BBPSSWMsgType.PURIFICATION_RES,
                                 self.remote_protocol_name,
                                 meas_res=self.meas_res)
-        self.own.send_message(dst, message)
+        self.owner.send_message(dst, message)
 
     def received_message(self, src: str, msg: BBPSSWMessage) -> None:
         """Method to receive messages.
@@ -168,7 +171,7 @@ def received_message(self, src: str, msg: BBPSSWMessage) -> None:
         """
 
         log.logger.info(
-            self.own.name + " received result message, succeeded: {}".format(
+            self.owner.name + " received result message, succeeded: {}".format(
                 self.meas_res == msg.meas_res))
         assert src == self.remote_node_name
 
diff --git a/src/entanglement_management/swapping.py b/sequence/entanglement_management/swapping.py
similarity index 81%
rename from src/entanglement_management/swapping.py
rename to sequence/entanglement_management/swapping.py
index 58e6e32f..5a25239c 100644
--- a/src/entanglement_management/swapping.py
+++ b/sequence/entanglement_management/swapping.py
@@ -23,7 +23,7 @@
 from .entanglement_protocol import EntanglementProtocol
 from ..utils import log
 from ..components.circuit import Circuit
-
+from ..resource_management.memory_manager import MemoryInfo
 
 class SwappingMsgType(Enum):
     """Defines possible message types for entanglement generation."""
@@ -94,12 +94,11 @@ class EntanglementSwappingA(EntanglementProtocol):
     circuit.measure(0)
     circuit.measure(1)
 
-    def __init__(self, own: "Node", name: str, left_memo: "Memory", right_memo: "Memory", success_prob=1,
-                 degradation=0.95):
+    def __init__(self, owner: "Node", name: str, left_memo: "Memory", right_memo: "Memory", success_prob=1, degradation=0.95):
         """Constructor for entanglement swapping A protocol.
 
         Args:
-            own (Node): node that protocol instance is attached to.
+            owner (Node): node that protocol instance is attached to.
             name (str): label for swapping protocol instance.
             left_memo (Memory): memory entangled with a memory on one distant node.
             right_memo (Memory): memory entangled with a memory on the other distant node.
@@ -108,7 +107,7 @@ def __init__(self, own: "Node", name: str, left_memo: "Memory", right_memo: "Mem
         """
 
         assert left_memo != right_memo
-        EntanglementProtocol.__init__(self, own, name)
+        EntanglementProtocol.__init__(self, owner, name)
         self.memories = [left_memo, right_memo]
         self.left_memo = left_memo
         self.right_memo = right_memo
@@ -123,8 +122,10 @@ def __init__(self, own: "Node", name: str, left_memo: "Memory", right_memo: "Mem
         self.right_protocol_name = None
 
     def is_ready(self) -> bool:
-        return self.left_protocol_name is not None \
-               and self.right_protocol_name is not None
+        """Return True if left_protocol and right_protocol are both set.
+        """
+
+        return (self.left_protocol_name is not None) and (self.right_protocol_name is not None)
 
     def set_others(self, protocol: str, node: str, memories: List[str]) -> None:
         """Method to set other entanglement protocol instance.
@@ -152,53 +153,48 @@ def start(self) -> None:
             Will send messages to other protocols.
         """
 
-        log.logger.info(f"{self.own.name} middle protocol start with ends "
-                        f"{self.left_protocol_name}, "
-                        f"{self.right_protocol_name}")
+        log.logger.info(f"{self.owner.name} middle protocol start with ends {self.left_node}, {self.right_node}")
 
         assert self.left_memo.fidelity > 0 and self.right_memo.fidelity > 0
         assert self.left_memo.entangled_memory["node_id"] == self.left_node
         assert self.right_memo.entangled_memory["node_id"] == self.right_node
 
-        if self.own.get_generator().random() < self.success_probability():
+        if self.owner.get_generator().random() < self.success_probability():
+            # swapping succeeded
             fidelity = self.updated_fidelity(self.left_memo.fidelity, self.right_memo.fidelity)
             self.is_success = True
 
             expire_time = min(self.left_memo.get_expire_time(), self.right_memo.get_expire_time())
 
-            meas_samp = self.own.get_generator().random()
-            meas_res = self.own.timeline.quantum_manager.run_circuit(
-                self.circuit, [self.left_memo.qstate_key,
-                               self.right_memo.qstate_key], meas_samp)
+            meas_samp = self.owner.get_generator().random()
+            meas_res = self.owner.timeline.quantum_manager.run_circuit(
+                        self.circuit, [self.left_memo.qstate_key, self.right_memo.qstate_key], meas_samp)
             meas_res = [meas_res[self.left_memo.qstate_key], meas_res[self.right_memo.qstate_key]]
-
+            
+            log.logger.info(f"{self.name} swapping succeeded, meas_res={meas_res[0]},{meas_res[1]}")
+            
             msg_l = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES,
-                                                self.left_protocol_name,
-                                                fidelity=fidelity,
+                                                self.left_protocol_name, fidelity=fidelity,
                                                 remote_node=self.right_memo.entangled_memory["node_id"],
                                                 remote_memo=self.right_memo.entangled_memory["memo_id"],
-                                                expire_time=expire_time,
-                                                meas_res=[])
-            msg_r = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES,
-                                                self.right_protocol_name,
-                                                fidelity=fidelity,
+                                                expire_time=expire_time, meas_res=[])  # empty meas_res
+            msg_r = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES, 
+                                                self.right_protocol_name, fidelity=fidelity,
                                                 remote_node=self.left_memo.entangled_memory["node_id"],
                                                 remote_memo=self.left_memo.entangled_memory["memo_id"],
-                                                expire_time=expire_time,
-                                                meas_res=meas_res)
+                                                expire_time=expire_time, meas_res=meas_res)
         else:
-            msg_l = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES,
-                                                self.left_protocol_name,
-                                                fidelity=0)
-            msg_r = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES,
-                                                self.right_protocol_name,
-                                                fidelity=0)
+            # swapping failed
+            log.logger.info(f"{self.name} swapping failed")
+            msg_l = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES,self.left_protocol_name, fidelity=0)
+            msg_r = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES, self.right_protocol_name, fidelity=0)
+
+        self.owner.send_message(self.left_node, msg_l)
+        self.owner.send_message(self.right_node, msg_r)
 
-        self.own.send_message(self.left_node, msg_l)
-        self.own.send_message(self.right_node, msg_r)
+        self.update_resource_manager(self.left_memo, MemoryInfo.RAW)
+        self.update_resource_manager(self.right_memo, MemoryInfo.RAW)
 
-        self.update_resource_manager(self.left_memo, "RAW")
-        self.update_resource_manager(self.right_memo, "RAW")
 
     def success_probability(self) -> float:
         """A simple model for BSM success probability."""
@@ -250,15 +246,15 @@ def memory_expire(self, memory: "Memory") -> None:
 
         for memo in self.memories:
             if memo == memory:
-                self.update_resource_manager(memo, "RAW")
+                self.update_resource_manager(memo, MemoryInfo.RAW)
             else:
-                self.update_resource_manager(memo, "ENTANGLED")
+                self.update_resource_manager(memo, MemoryInfo.ENTANGLED)
 
     def release_remote_protocol(self, remote_node: str):
-        self.own.resource_manager.release_remote_protocol(remote_node, self)
+        self.owner.resource_manager.release_remote_protocol(remote_node, self)
 
     def release_remote_memory(self, remote_node: str, remote_memo: str):
-        self.own.resource_manager.release_remote_memory(remote_node, remote_memo)
+        self.owner.resource_manager.release_remote_memory(remote_node, remote_memo)
 
 
 class EntanglementSwappingB(EntanglementProtocol):
@@ -290,7 +286,7 @@ class EntanglementSwappingB(EntanglementProtocol):
     x_z_cir.x(0)
     x_z_cir.z(0)
 
-    def __init__(self, own: "Node", name: str, hold_memo: "Memory"):
+    def __init__(self, owner: "Node", name: str, hold_memo: "Memory"):
         """Constructor for entanglement swapping B protocol.
 
         Args:
@@ -299,7 +295,7 @@ def __init__(self, own: "Node", name: str, hold_memo: "Memory"):
             hold_memo (Memory): memory entangled with a memory on middle node.
         """
 
-        EntanglementProtocol.__init__(self, own, name)
+        EntanglementProtocol.__init__(self, owner, name)
 
         self.memories = [hold_memo]
         self.memory = hold_memo
@@ -331,29 +327,28 @@ def received_message(self, src: str, msg: "EntanglementSwappingMessage") -> None
             Will invoke `update_resource_manager` method.
         """
 
-        log.logger.debug(
-            self.own.name + " protocol received_message from node {}, fidelity={}".format(src, msg.fidelity))
+        log.logger.debug(self.owner.name + " protocol received_message from node {}, fidelity={}".format(src, msg.fidelity))
 
         assert src == self.remote_node_name
 
-        if msg.fidelity > 0 and self.own.timeline.now() < msg.expire_time:
+        if msg.fidelity > 0 and self.owner.timeline.now() < msg.expire_time:
             if msg.meas_res == [1, 0]:
-                self.own.timeline.quantum_manager.run_circuit(self.z_cir, [self.memory.qstate_key])
+                self.owner.timeline.quantum_manager.run_circuit(self.z_cir, [self.memory.qstate_key])
             elif msg.meas_res == [0, 1]:
-                self.own.timeline.quantum_manager.run_circuit(self.x_cir, [self.memory.qstate_key])
+                self.owner.timeline.quantum_manager.run_circuit(self.x_cir, [self.memory.qstate_key])
             elif msg.meas_res == [1, 1]:
-                self.own.timeline.quantum_manager.run_circuit(self.x_z_cir, [self.memory.qstate_key])
+                self.owner.timeline.quantum_manager.run_circuit(self.x_z_cir, [self.memory.qstate_key])
 
             self.memory.fidelity = msg.fidelity
             self.memory.entangled_memory["node_id"] = msg.remote_node
             self.memory.entangled_memory["memo_id"] = msg.remote_memo
             self.memory.update_expire_time(msg.expire_time)
-            self.update_resource_manager(self.memory, "ENTANGLED")
+            self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED)
         else:
-            self.update_resource_manager(self.memory, "RAW")
+            self.update_resource_manager(self.memory, MemoryInfo.RAW)
 
     def start(self) -> None:
-        log.logger.info(f"{self.own.name} end protocol start with partner {self.remote_node_name}")
+        log.logger.debug(f"{self.owner.name} end protocol start with partner {self.remote_node_name}")
 
     def memory_expire(self, memory: "Memory") -> None:
         """Method to deal with expired memories.
@@ -365,7 +360,7 @@ def memory_expire(self, memory: "Memory") -> None:
             Will update memory in attached resource manager.
         """
 
-        self.update_resource_manager(self.memory, "RAW")
+        self.update_resource_manager(self.memory, MemoryInfo.RAW)
 
     def release(self) -> None:
-        self.update_resource_manager(self.memory, "ENTANGLED")
+        self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED)
diff --git a/src/gui/__init__.py b/sequence/gui/__init__.py
similarity index 100%
rename from src/gui/__init__.py
rename to sequence/gui/__init__.py
diff --git a/src/gui/app.py b/sequence/gui/app.py
similarity index 99%
rename from src/gui/app.py
rename to sequence/gui/app.py
index bf4c348a..232a1f61 100644
--- a/src/gui/app.py
+++ b/sequence/gui/app.py
@@ -355,7 +355,7 @@ def graph_to_topology(self):
         # collect and finalize
         output = {
             Topology.ALL_NODE: nodes_top,
-            Topology.ALL_QC_CONNECT: qconnections,
+            Topology.ALL_Q_CONNECT: qconnections,
             Topology.ALL_C_CHANNEL: cchannels,
             Topology.ALL_TEMPLATES: templates,
 
diff --git a/src/gui/assets/argonne.png b/sequence/gui/assets/argonne.png
similarity index 100%
rename from src/gui/assets/argonne.png
rename to sequence/gui/assets/argonne.png
diff --git a/src/gui/assets/attributions_req b/sequence/gui/assets/attributions_req
similarity index 100%
rename from src/gui/assets/attributions_req
rename to sequence/gui/assets/attributions_req
diff --git a/src/gui/assets/bsmnode.png b/sequence/gui/assets/bsmnode.png
similarity index 100%
rename from src/gui/assets/bsmnode.png
rename to sequence/gui/assets/bsmnode.png
diff --git a/src/gui/assets/detector.png b/sequence/gui/assets/detector.png
similarity index 100%
rename from src/gui/assets/detector.png
rename to sequence/gui/assets/detector.png
diff --git a/src/gui/assets/logo.png b/sequence/gui/assets/logo.png
similarity index 100%
rename from src/gui/assets/logo.png
rename to sequence/gui/assets/logo.png
diff --git a/src/gui/assets/photonsource.png b/sequence/gui/assets/photonsource.png
similarity index 100%
rename from src/gui/assets/photonsource.png
rename to sequence/gui/assets/photonsource.png
diff --git a/src/gui/assets/quantum.png b/sequence/gui/assets/quantum.png
similarity index 100%
rename from src/gui/assets/quantum.png
rename to sequence/gui/assets/quantum.png
diff --git a/src/gui/assets/repeater.png b/sequence/gui/assets/repeater.png
similarity index 100%
rename from src/gui/assets/repeater.png
rename to sequence/gui/assets/repeater.png
diff --git a/src/gui/assets/router.png b/sequence/gui/assets/router.png
similarity index 100%
rename from src/gui/assets/router.png
rename to sequence/gui/assets/router.png
diff --git a/src/gui/assets/sequence.jpg b/sequence/gui/assets/sequence.jpg
similarity index 100%
rename from src/gui/assets/sequence.jpg
rename to sequence/gui/assets/sequence.jpg
diff --git a/src/gui/assets/svg/bsmnode.svg b/sequence/gui/assets/svg/bsmnode.svg
similarity index 100%
rename from src/gui/assets/svg/bsmnode.svg
rename to sequence/gui/assets/svg/bsmnode.svg
diff --git a/src/gui/assets/svg/detector.svg b/sequence/gui/assets/svg/detector.svg
similarity index 100%
rename from src/gui/assets/svg/detector.svg
rename to sequence/gui/assets/svg/detector.svg
diff --git a/src/gui/assets/svg/photonsource.svg b/sequence/gui/assets/svg/photonsource.svg
similarity index 100%
rename from src/gui/assets/svg/photonsource.svg
rename to sequence/gui/assets/svg/photonsource.svg
diff --git a/src/gui/assets/svg/quantum.svg b/sequence/gui/assets/svg/quantum.svg
similarity index 100%
rename from src/gui/assets/svg/quantum.svg
rename to sequence/gui/assets/svg/quantum.svg
diff --git a/src/gui/assets/svg/repeater.svg b/sequence/gui/assets/svg/repeater.svg
similarity index 100%
rename from src/gui/assets/svg/repeater.svg
rename to sequence/gui/assets/svg/repeater.svg
diff --git a/src/gui/assets/svg/router.svg b/sequence/gui/assets/svg/router.svg
similarity index 100%
rename from src/gui/assets/svg/router.svg
rename to sequence/gui/assets/svg/router.svg
diff --git a/src/gui/assets/svg/temp.svg b/sequence/gui/assets/svg/temp.svg
similarity index 100%
rename from src/gui/assets/svg/temp.svg
rename to sequence/gui/assets/svg/temp.svg
diff --git a/src/gui/assets/temp.png b/sequence/gui/assets/temp.png
similarity index 100%
rename from src/gui/assets/temp.png
rename to sequence/gui/assets/temp.png
diff --git a/src/gui/css_styles.py b/sequence/gui/css_styles.py
similarity index 100%
rename from src/gui/css_styles.py
rename to sequence/gui/css_styles.py
diff --git a/src/gui/default_params.json b/sequence/gui/default_params.json
similarity index 100%
rename from src/gui/default_params.json
rename to sequence/gui/default_params.json
diff --git a/src/gui/default_templates.json b/sequence/gui/default_templates.json
similarity index 100%
rename from src/gui/default_templates.json
rename to sequence/gui/default_templates.json
diff --git a/src/gui/graph_comp.py b/sequence/gui/graph_comp.py
similarity index 100%
rename from src/gui/graph_comp.py
rename to sequence/gui/graph_comp.py
diff --git a/src/gui/layout.py b/sequence/gui/layout.py
similarity index 100%
rename from src/gui/layout.py
rename to sequence/gui/layout.py
diff --git a/src/gui/menus.py b/sequence/gui/menus.py
similarity index 100%
rename from src/gui/menus.py
rename to sequence/gui/menus.py
diff --git a/src/gui/run_gui.py b/sequence/gui/run_gui.py
similarity index 97%
rename from src/gui/run_gui.py
rename to sequence/gui/run_gui.py
index 3911e19d..03349db5 100644
--- a/src/gui/run_gui.py
+++ b/sequence/gui/run_gui.py
@@ -69,7 +69,7 @@ def load_graph(self, path_to_topology=None):
                 data=new_node.__dict__
             )
 
-        for edge in network_in[Topology.ALL_QC_CONNECT]:
+        for edge in network_in[Topology.ALL_Q_CONNECT]:
             graph.add_edge(
                 edge[Topology.CONNECT_NODE_1],
                 edge[Topology.CONNECT_NODE_2],
diff --git a/src/gui/simulator_bindings.py b/sequence/gui/simulator_bindings.py
similarity index 100%
rename from src/gui/simulator_bindings.py
rename to sequence/gui/simulator_bindings.py
diff --git a/src/gui/starlight.json b/sequence/gui/starlight.json
similarity index 100%
rename from src/gui/starlight.json
rename to sequence/gui/starlight.json
diff --git a/src/gui/user_templates.json b/sequence/gui/user_templates.json
similarity index 100%
rename from src/gui/user_templates.json
rename to sequence/gui/user_templates.json
diff --git a/src/kernel/__init__.py b/sequence/kernel/__init__.py
similarity index 100%
rename from src/kernel/__init__.py
rename to sequence/kernel/__init__.py
diff --git a/src/kernel/entity.py b/sequence/kernel/entity.py
similarity index 98%
rename from src/kernel/entity.py
rename to sequence/kernel/entity.py
index 5d1f64e2..53bcb300 100644
--- a/src/kernel/entity.py
+++ b/sequence/kernel/entity.py
@@ -43,6 +43,9 @@ def __init__(self, name: str, timeline: "Timeline") -> None:
 
         timeline.add_entity(self)
 
+    def __str__(self) -> str:
+        return self.name
+
     @abstractmethod
     def init(self) -> None:
         """Method to initialize entity (abstract).
diff --git a/src/kernel/event.py b/sequence/kernel/event.py
similarity index 100%
rename from src/kernel/event.py
rename to sequence/kernel/event.py
diff --git a/src/kernel/eventlist.py b/sequence/kernel/eventlist.py
similarity index 100%
rename from src/kernel/eventlist.py
rename to sequence/kernel/eventlist.py
diff --git a/sequence/kernel/process.py b/sequence/kernel/process.py
new file mode 100644
index 00000000..19d3cfb1
--- /dev/null
+++ b/sequence/kernel/process.py
@@ -0,0 +1,33 @@
+"""Definition of the Process class.
+
+This module defines a process, which is performed when an event is executed.
+"""
+from typing import Any, List, Dict
+
+
+class Process:
+    """Class of process.
+
+    The process claims the object of process, the function of object, and the arguments for the function.
+
+    Attributes:
+        owner (Any): the object of process.
+        activation (str): the function name of object.
+        activation_args  (List[Any]): the (non-keyword) arguments of object's function.
+        activation_kwargs (Dict[Any, Any]): the keyword arguments of object's function.
+    """
+
+    def __init__(self, owner: Any, activation_method: str, activation_args: List[Any], activation_kwargs: Dict[Any, Any] = {}):
+        self.owner = owner
+        self.activation = activation_method
+        self.activation_args = activation_args
+        self.activation_kwargs = activation_kwargs
+
+    def run(self) -> None:
+        """Method to execute process.
+
+        Will run the `activation_method` method of `owner` with 
+        `activation_args` passed as args, and 'activation_kwargs' passed as kwargs.
+        """
+
+        return getattr(self.owner, self.activation)(*self.activation_args, **self.activation_kwargs)
diff --git a/src/kernel/quantum_manager.py b/sequence/kernel/quantum_manager.py
similarity index 100%
rename from src/kernel/quantum_manager.py
rename to sequence/kernel/quantum_manager.py
diff --git a/src/kernel/quantum_state.py b/sequence/kernel/quantum_state.py
similarity index 97%
rename from src/kernel/quantum_state.py
rename to sequence/kernel/quantum_state.py
index a237e49a..9893ca86 100644
--- a/src/kernel/quantum_state.py
+++ b/sequence/kernel/quantum_state.py
@@ -8,13 +8,14 @@
 3. The `FreeQuantumState` class uses the ket vector formalism, and is used by individual photons (not the quantum manager).
 """
 
+import math
 from abc import ABC
 from typing import Tuple, Dict, List
-
 from numpy import pi, cos, sin, arange, log, log2
 from numpy.random import Generator
 
 from .quantum_utils import *
+from ..constants import EPSILON
 
 
 def swap_bits(num, pos1, pos2):
@@ -58,7 +59,7 @@ def serialize(self) -> Dict:
         res = {"keys": self.keys}
         state = []
         for cplx_n in self.state:
-            if type(cplx_n) == float:
+            if type(cplx_n) is float:
                 state.append(cplx_n)
                 state.append(0)
             elif isinstance(cplx_n, complex):
@@ -98,23 +99,19 @@ def __init__(self, amplitudes: List[complex], keys: List[int], truncation: int =
         dim = self.truncation + 1  # dimension of element Hilbert space
 
         # check formatting
-        assert all([abs(a) <= 1.01 for a in amplitudes]), "Illegal value with abs > 1 in ket vector"
-        assert abs(sum([abs(a) ** 2 for a in amplitudes]) - 1) < 1e-5, "Squared amplitudes do not sum to 1"
+        assert all([abs(a) <= 1 + EPSILON for a in amplitudes]), "Illegal value with abs > 1 in ket vector"
+        assert math.isclose(sum([abs(a) ** 2 for a in amplitudes]), 1), "Squared amplitudes do not sum to 1"
 
         num_subsystems = log(len(amplitudes)) / log(dim)
         assert dim ** int(round(num_subsystems)) == len(amplitudes),\
             "Length of amplitudes should be d ** n, " \
             "where d is subsystem Hilbert space dimension and n is the number of subsystems. " \
-            "Actual amplitude length: {}, dim: {}, num subsystems: {}".format(
-                len(amplitudes), dim, num_subsystems
-            )
+            "Actual amplitude length: {}, dim: {}, num subsystems: {}".format(len(amplitudes), dim, num_subsystems)
         num_subsystems = int(round(num_subsystems))
         assert num_subsystems == len(keys),\
             "Length of amplitudes should be d ** n, " \
             "where d is subsystem Hilbert space dimension and n is the number of subsystems. " \
-            "Amplitude length: {}, expected subsystems: {}, num keys: {}".format(
-                len(amplitudes), num_subsystems, len(keys)
-            )
+            "Amplitude length: {}, expected subsystems: {}, num keys: {}".format(len(amplitudes), num_subsystems, len(keys))
 
         self.state = array(amplitudes, dtype=complex)
         self.keys = keys
diff --git a/src/kernel/quantum_utils.py b/sequence/kernel/quantum_utils.py
similarity index 100%
rename from src/kernel/quantum_utils.py
rename to sequence/kernel/quantum_utils.py
diff --git a/src/kernel/timeline.py b/sequence/kernel/timeline.py
similarity index 93%
rename from src/kernel/timeline.py
rename to sequence/kernel/timeline.py
index 3cea4fbf..c5e0ce4d 100644
--- a/src/kernel/timeline.py
+++ b/sequence/kernel/timeline.py
@@ -25,13 +25,7 @@
                               KET_STATE_FORMALISM,
                               DENSITY_MATRIX_FORMALISM,
                               FOCK_DENSITY_MATRIX_FORMALISM)
-
-CARRIAGE_RETURN = '\r'
-SLEEP_SECONDS = 3
-
-NANOSECONDS_PER_MILLISECOND = 1e6
-PICOSECONDS_PER_NANOSECOND = NANOSECONDS_PER_MICROSECOND = MILLISECONDS_PER_SECOND = 1e3
-SECONDS_PER_MINUTE = MINUTES_PER_HOUR = 60
+from ..constants import *
 
 
 class Timeline:
@@ -130,13 +124,15 @@ def run(self) -> None:
                 continue
 
             self.time = event.time
+            
+            log.logger.debug("Event #{}: process owner={}, activation={}".format(self.run_counter, event.process.owner, event.process.activation))
             event.process.run()
             self.run_counter += 1
 
         self.is_running = False
         time_elapsed = time_ns() - tick
-        log.logger.info("Timeline end simulation. Execution Time: %d ns; Scheduled Event: %d; Executed Event: %d" %
-                        (time_elapsed, self.schedule_counter, self.run_counter))
+        log.logger.info("Timeline end simulation. Execution Time: {}; Scheduled Event: {}; Executed Event: {}".format(
+                         self.ns_to_human_time(time_elapsed), self.schedule_counter, self.run_counter))
 
     def stop(self) -> None:
         """Method to stop simulation."""
@@ -157,7 +153,7 @@ def update_event_time(self, event: "Event", time: int) -> None:
         self.events.update_event_time(event, time)
 
     def add_entity(self, entity: "Entity") -> None:
-        assert entity.name not in self.entities
+        assert entity.name not in self.entities, f'{entity.name} already exists!'
         entity.timeline = self
         self.entities[entity.name] = entity
 
@@ -195,6 +191,8 @@ def print_time(self):
             sleep(SLEEP_SECONDS)
 
     def ns_to_human_time(self, nanoseconds: float) -> str:
+        """Returns a string in the form [D day[s], ][H]H:MM:SS[.UUUUUU]
+        """
         milliseconds = nanoseconds / NANOSECONDS_PER_MILLISECOND
         return str(timedelta(milliseconds=milliseconds))
 
diff --git a/src/message.py b/sequence/message.py
similarity index 100%
rename from src/message.py
rename to sequence/message.py
diff --git a/src/network_management/__init__.py b/sequence/network_management/__init__.py
similarity index 100%
rename from src/network_management/__init__.py
rename to sequence/network_management/__init__.py
diff --git a/src/network_management/network_manager.py b/sequence/network_management/network_manager.py
similarity index 85%
rename from src/network_management/network_manager.py
rename to sequence/network_management/network_manager.py
index c0b0f081..b385334c 100644
--- a/src/network_management/network_manager.py
+++ b/sequence/network_management/network_manager.py
@@ -27,16 +27,14 @@ class NetworkManagerMessage(Message):
     """
 
     def __init__(self, msg_type: Enum, receiver: str, payload: "Message"):
-        Message.__init__(self, msg_type, receiver)
+        super().__init__(msg_type, receiver)
         self.payload = payload
 
-    def __str__(self):
-        return "type={}, receiver={}, payload={}".format(self.msg_type,
-                                                         self.receiver,
-                                                         self.payload)
+    def __str__(self) -> str:
+        return "type={}; receiver={}; payload={}".format(self.msg_type, self.receiver, self.payload)
 
 
-class NetworkManager():
+class NetworkManager:
     """Network manager implementation class.
 
     The network manager is responsible for the operations of a node within a broader quantum network.
@@ -101,9 +99,9 @@ def pop(self, **kwargs):
         reservation = msg.reservation
         if reservation.initiator == self.owner.name:
             if msg.msg_type == RSVPMsgType.APPROVE:
-                self.owner.get_reserve_res(reservation, True)
+                self.owner.get_reservation_result(reservation, True)
             else:
-                self.owner.get_reserve_res(reservation, False)
+                self.owner.get_reservation_result(reservation, False)
         elif reservation.responder == self.owner.name:
             self.owner.get_other_reservation(reservation)
 
@@ -120,12 +118,11 @@ def received_message(self, src: str, msg: "NetworkManagerMessage"):
             Will invoke `pop` method of 0 indexed protocol in `protocol_stack`.
         """
 
-        log.logger.info(
-            "{} network manager receives message {} from {}".format(
-                self.owner.name, msg.payload, src))
+        log.logger.info("{} network manager receives message from {}: {}".format(self.owner.name, src, msg))
         self.protocol_stack[0].pop(src=src, msg=msg.payload)
 
-    def request(self, responder: str, start_time: int, end_time: int, memory_size: int, target_fidelity: float) -> None:
+    def request(self, responder: str, start_time: int, end_time: int, memory_size: int, target_fidelity: float,
+                entanglement_number: int = 1, identity: int = 0) -> None:
         """Method to make an entanglement request.
 
         Will defer request to top protocol in protocol stack.
@@ -136,12 +133,15 @@ def request(self, responder: str, start_time: int, end_time: int, memory_size: i
             end_time (int): simulation end time of entanglement.
             memory_size (int): number of entangled memory pairs to create.
             target_fidelity (float): desired fidelity of entanglement.
+            entanglement_number (int): the number of entanglement requested.
+            identity (int): the ID of the request.
 
         Side Effects:
-            Will invoke `push` method of -1 indexed protocol in `protocol_stack`.
+            Will invoke `push` method of -1 indexed protocol in `protocol_stack`,
+            which is the resource reservation protocol.
         """
 
-        self.protocol_stack[-1].push(responder, start_time, end_time, memory_size, target_fidelity)
+        self.protocol_stack[-1].push(responder, start_time, end_time, memory_size, target_fidelity, entanglement_number, identity)
 
 
 def NewNetworkManager(owner: "QuantumRouter", memory_array_name: str) -> "NetworkManager":
@@ -157,10 +157,11 @@ def NewNetworkManager(owner: "QuantumRouter", memory_array_name: str) -> "Networ
     Returns:
         NetworkManager: network manager object created.
     """
-
+    swapping_success_rate = 0.5
     manager = NetworkManager(owner, [])
     routing = StaticRoutingProtocol(owner, owner.name + ".StaticRoutingProtocol", {})
     rsvp = ResourceReservationProtocol(owner, owner.name + ".RSVP", memory_array_name)
+    rsvp.set_swapping_success_rate(swapping_success_rate)
     routing.upper_protocols.append(rsvp)
     rsvp.lower_protocols.append(routing)
     manager.load_stack([routing, rsvp])
diff --git a/src/network_management/reservation.py b/sequence/network_management/reservation.py
similarity index 71%
rename from src/network_management/reservation.py
rename to sequence/network_management/reservation.py
index 669639e9..aede8c8c 100644
--- a/src/network_management/reservation.py
+++ b/sequence/network_management/reservation.py
@@ -6,7 +6,7 @@
 """
 
 from enum import Enum, auto
-from typing import List, TYPE_CHECKING, Any, Dict
+from typing import List, TYPE_CHECKING, Any, Dict, Tuple
 
 if TYPE_CHECKING:
     from ..topology.node import QuantumRouter
@@ -41,7 +41,7 @@ class ResourceReservationMessage(Message):
     Messages of different types contain different information.
 
     Attributes:
-        msg_type (GenerationMsgType): defines the message type.
+        msg_type (RSVPMsgType): defines the message type.
         receiver (str): name of destination protocol instance.
         reservation (Reservation): reservation object relayed between nodes.
         qcaps (List[QCaps]): cumulative quantum capacity object list (if `msg_type == REQUEST`)
@@ -49,7 +49,7 @@ class ResourceReservationMessage(Message):
     """
 
     def __init__(self, msg_type: any, receiver: str, reservation: "Reservation", **kwargs):
-        Message.__init__(self, msg_type, receiver)
+        super().__init__(msg_type, receiver)
         self.reservation = reservation
         if self.msg_type is RSVPMsgType.REQUEST:
             self.qcaps = []
@@ -61,43 +61,43 @@ def __init__(self, msg_type: any, receiver: str, reservation: "Reservation", **k
             raise Exception("Unknown type of message")
 
     def __str__(self):
-        return f"ResourceReservationProtocol: \n\ttype={self.msg_type}, " \
-               f"\n\treservation={self.reservation}"
+        return f"|type={self.msg_type}; reservation={self.reservation}|"
 
 
-def eg_rule_condition(memory_info: "MemoryInfo",
-                      manager: "MemoryManager",
-                      args: Arguments) -> List["MemoryInfo"]:
-    """Condition function used by entanglement generation protocol on nodes
-
-    """
-    memory_indices = args['memory_indices']
-    if memory_info.state == "RAW" and memory_info.index in memory_indices:
-        return [memory_info]
-    else:
-        return []
-
-
-def eg_rule_action1(memories_info: List["MemoryInfo"], args: Dict[str, Any]):
-    """Action function used by entanglement generation protocol on nodes except
-    the initiator
+# entanglement generation
 
+def eg_rule_action1(memories_info: List["MemoryInfo"], args: Dict[str, Any]) -> Tuple[EntanglementGenerationA, List[None], List[None], List[None]]:
+    """Action function used by entanglement generation protocol on nodes except the initiator, i.e., index > 0
     """
     memories = [info.memory for info in memories_info]
     memory = memories[0]
     mid = args["mid"]
     path = args["path"]
     index = args["index"]
-    protocol = EntanglementGenerationA(None, "EGA." + memory.name, mid,
-                                       path[index - 1], memory)
+    protocol = EntanglementGenerationA(None, "EGA." + memory.name, mid, path[index - 1], memory)
     return protocol, [None], [None], [None]
 
 
-def eg_req_func(protocols: List["EntanglementProtocol"],
-                args: Arguments) -> "EntanglementGenerationA":
-    """Function used by `eg_rule_action2` function for selecting generation
-    protocols on the remote node
+def eg_rule_action2(memories_info: List["MemoryInfo"], args: Arguments) -> Tuple[EntanglementGenerationA, List[str], List["eg_req_func"], List[Dict]]:
+    """Action function used by entanglement generation protocol on nodes except the responder, i.e., index < len(path) - 1
+    """
+    mid = args["mid"]
+    path = args["path"]
+    index = args["index"]
+    memories = [info.memory for info in memories_info]
+    memory = memories[0]
+    protocol = EntanglementGenerationA(None, "EGA." + memory.name, mid, path[index + 1], memory)
+    req_args = {"name": args["name"], "reservation": args["reservation"]}
+    return protocol, [path[index + 1]], [eg_req_func], [req_args]
 
+
+def eg_req_func(protocols: List["EntanglementProtocol"], args: Arguments) -> EntanglementGenerationA:
+    """Function used by `eg_rule_action2` function for selecting generation protocols on the remote node
+    Args:
+        protocols: the waiting protocols (wait for request)
+        args: arguments from the node who sent the request
+    Return:
+        the selected protocol
     """
     name = args["name"]
     reservation = args["reservation"]
@@ -108,46 +108,41 @@ def eg_req_func(protocols: List["EntanglementProtocol"],
             return protocol
 
 
-def eg_rule_action2(memories_info: List["MemoryInfo"], args: Arguments):
-    """Action function used by entanglement generation protocol on nodes except
-    the responder
-
+def eg_rule_condition(memory_info: "MemoryInfo", manager: "MemoryManager", args: Arguments) -> List["MemoryInfo"]:
+    """Condition function used by entanglement generation protocol on nodes
     """
-    mid = args["mid"]
-    path = args["path"]
-    index = args["index"]
-    memories = [info.memory for info in memories_info]
-    memory = memories[0]
-    protocol = EntanglementGenerationA(None, "EGA." + memory.name, mid,
-                                       path[index + 1], memory)
-    req_args = {"name": args["name"], "reservation": args["reservation"]}
-    return protocol, [path[index + 1]], [eg_req_func], [req_args]
+    memory_indices = args['memory_indices']
+    if memory_info.state == "RAW" and memory_info.index in memory_indices:
+        return [memory_info]
+    else:
+        return []
 
 
-def ep_rule_condition1(memory_info: "MemoryInfo", manager: "MemoryManager",
-                       args: Arguments):
-    """Condition function used by BBPSSW protocol on nodes except the initiator
+# entanglement purification
 
+def ep_rule_action1(memories_info: List["MemoryInfo"], args: Arguments) -> Tuple[BBPSSW, List[str], List["ep_req_func1"], List[Dict]]:
+    """Action function used by BBPSSW protocol on nodes except the responder node
     """
-    memory_indices = args["memory_indices"]
-    reservation = args["reservation"]
-    if (memory_info.index in memory_indices
-            and memory_info.state == ENTANGLED
-            and memory_info.fidelity < reservation.fidelity):
-        for info in manager:
-            if (info != memory_info and info.index in memory_indices
-                    and info.state == ENTANGLED
-                    and info.remote_node == memory_info.remote_node
-                    and info.fidelity == memory_info.fidelity):
-                assert memory_info.remote_memo != info.remote_memo
-                return [memory_info, info]
-    return []
+    memories = [info.memory for info in memories_info]
+    name = "EP.%s.%s" % (memories[0].name, memories[1].name)
+    protocol = BBPSSW(None, name, memories[0], memories[1])
+    dsts = [memories_info[0].remote_node]
+    req_funcs = [ep_req_func1]
+    req_args = [{"remote0": memories_info[0].remote_memo, "remote1": memories_info[1].remote_memo}]
+    return protocol, dsts, req_funcs, req_args
 
 
-def ep_req_func1(protocols, args: Arguments) -> "BBPSSW":
-    """Function used by `ep_rule_action1` for selecting purification protocols
-    on the remote node
+def ep_rule_action2(memories_info: List["MemoryInfo"], args: Arguments) -> Tuple[BBPSSW, List[None], List[None], List[None]]:
+    """Action function used by BBPSSW protocol on nodes except the responder
+    """
+    memories = [info.memory for info in memories_info]
+    name = "EP.%s" % memories[0].name
+    protocol = BBPSSW(None, name, memories[0], None)
+    return protocol, [None], [None], [None]
 
+
+def ep_req_func1(protocols, args: Arguments) -> BBPSSW:
+    """Function used by `ep_rule_action1` for selecting purification protocols on the remote node
     """
     remote0 = args["remote0"]
     remote1 = args["remote1"]
@@ -169,58 +164,60 @@ def ep_req_func1(protocols, args: Arguments) -> "BBPSSW":
     _protocols[1].rule.protocols.remove(_protocols[1])
     _protocols[1].kept_memo.detach(_protocols[1])
     _protocols[0].meas_memo = _protocols[1].kept_memo
-    _protocols[0].memories = [_protocols[0].kept_memo,
-                              _protocols[0].meas_memo]
-    _protocols[0].name = _protocols[0].name + "." + _protocols[
-        0].meas_memo.name
+    _protocols[0].memories = [_protocols[0].kept_memo, _protocols[0].meas_memo]
+    _protocols[0].name = _protocols[0].name + "." + _protocols[0].meas_memo.name
     _protocols[0].meas_memo.attach(_protocols[0])
 
     return _protocols[0]
 
 
-def ep_rule_action1(memories_info: List["MemoryInfo"], args: Arguments):
-    """Action function used by BBPSSW protocol on nodes except the
-    responder node
-
+def ep_rule_condition1(memory_info: "MemoryInfo", memory_manager: "MemoryManager", args: Arguments) -> List["MemoryInfo"]:
+    """Condition function used by BBPSSW protocol on nodes except the initiator
     """
-    memories = [info.memory for info in memories_info]
-    name = "EP.{}.{}".format(memories[0].name, memories[1].name)
-    protocol = BBPSSW(None, name, memories[0], memories[1])
-    dsts = [memories_info[0].remote_node]
-    req_funcs = [ep_req_func1]
-    req_args = [{"remote0": memories_info[0].remote_memo,
-                 "remote1": memories_info[1].remote_memo}, ]
-    return protocol, dsts, req_funcs, req_args
+    memory_indices = args["memory_indices"]
+    reservation = args["reservation"]
+    if (memory_info.index in memory_indices
+            and memory_info.state == "ENTANGLED"
+            and memory_info.fidelity < reservation.fidelity):
+        for info in memory_manager:
+            if (info != memory_info and info.index in memory_indices
+                    and info.state == "ENTANGLED"
+                    and info.remote_node == memory_info.remote_node
+                    and info.fidelity == memory_info.fidelity):
+                assert memory_info.remote_memo != info.remote_memo
+                return [memory_info, info]
+    return []
 
 
-def ep_rule_condition2(memory_info: "MemoryInfo", manager: "MemoryManager",
-                       args: Arguments) -> List["MemoryInfo"]:
+def ep_rule_condition2(memory_info: "MemoryInfo", manager: "MemoryManager", args: Arguments) -> List["MemoryInfo"]:
     """Condition function used by BBPSSW protocol on nodes except the responder
-
     """
     memory_indices = args["memory_indices"]
     fidelity = args["fidelity"]
 
-    if (memory_info.index in memory_indices
-            and memory_info.state == ENTANGLED
-            and memory_info.fidelity < fidelity):
+    if (memory_info.index in memory_indices and memory_info.state == "ENTANGLED" and memory_info.fidelity < fidelity):
         return [memory_info]
     return []
 
 
-def ep_rule_action2(memories_info: List["MemoryInfo"], args: Arguments):
-    """Action function used by BBPSSW protocol on nodes except the responder
+# entanglement swapping
 
+def es_rule_actionA(memories_info: List["MemoryInfo"], args: Arguments) -> Tuple[EntanglementSwappingA, List[str], List["es_req_func"], List[Dict]]:
+    """Action function used by EntanglementSwappingA protocol on nodes
     """
+    es_succ_prob = args["es_succ_prob"]
+    es_degradation = args["es_degradation"]
     memories = [info.memory for info in memories_info]
-    name = "EP.{}".format(memories[0].name)
-    protocol = BBPSSW(None, name, memories[0], None)
-    return protocol, [None], [None], [None]
+    protocol = EntanglementSwappingA(None, "ESA.{}.{}".format(memories[0].name, memories[1].name),
+                                     memories[0], memories[1], success_prob=es_succ_prob, degradation=es_degradation)
+    dsts = [info.remote_node for info in memories_info]
+    req_funcs = [es_req_func, es_req_func]
+    req_args = [{"target_memo": memories_info[0].remote_memo}, {"target_memo": memories_info[1].remote_memo}]
+    return protocol, dsts, req_funcs, req_args
 
 
-def es_rule_actionB(memories_info: List["MemoryInfo"], args: Arguments):
+def es_rule_actionB(memories_info: List["MemoryInfo"], args: Arguments) -> Tuple[EntanglementSwappingB, List[None], List[None], List[None]]:
     """Action function used by EntanglementSwappingB protocol
-
     """
     memories = [info.memory for info in memories_info]
     memory = memories[0]
@@ -228,79 +225,62 @@ def es_rule_actionB(memories_info: List["MemoryInfo"], args: Arguments):
     return protocol, [None], [None], [None]
 
 
-def es_rule_conditionB1(memory_info: "MemoryInfo", manager: "MemoryManager", args: Arguments):
-    """Condition function used by EntanglementSwappingB protocol on nodes of either responder or initiator
+def es_req_func(protocols: List["EntanglementProtocol"], args: Arguments) -> EntanglementSwappingB:
+    """Function used by `es_rule_actionA` for selecting swapping protocols on the remote node
     """
-    memory_indices = args["memory_indices"]
-    target_remote = args["target_remote"]
-    fidelity = args["fidelity"]
-    if (memory_info.state == ENTANGLED
-            and memory_info.index in memory_indices
-            # and memory_info.remote_node != path[-1]
-            and memory_info.remote_node != target_remote
-            # and memory_info.fidelity >= reservation.fidelity):
-            and memory_info.fidelity >= fidelity):
-        return [memory_info]
-    else:
-        return []
+    target_memo = args["target_memo"]
+    for protocol in protocols:
+        if (isinstance(protocol, EntanglementSwappingB)
+                # and protocol.memory.name == memories_info[0].remote_memo):
+                and protocol.memory.name == target_memo):
+            return protocol
 
 
-def es_rule_conditionA(memory_info: "MemoryInfo", manager: "MemoryManager", args: Arguments):
+def es_rule_conditionA(memory_info: "MemoryInfo", memory_manager: "MemoryManager", args: Arguments) -> List["MemoryInfo"]:
     """Condition function used by EntanglementSwappingA protocol on nodes
     """
     memory_indices = args["memory_indices"]
     left = args["left"]
     right = args["right"]
     fidelity = args["fidelity"]
-    if (memory_info.state == ENTANGLED
+    if (memory_info.state == "ENTANGLED"
             and memory_info.index in memory_indices
             and memory_info.remote_node == left
             and memory_info.fidelity >= fidelity):
-        for info in manager:
-            if (info.state == ENTANGLED
-                    and info.index in memory_indices
-                    and info.remote_node == right
-                    and info.fidelity >= fidelity):
-                return [memory_info, info]
-    elif (memory_info.state == ENTANGLED
-          and memory_info.index in memory_indices
-          and memory_info.remote_node == right
-          and memory_info.fidelity >= fidelity):
-        for info in manager:
-            if (info.state == ENTANGLED
-                    and info.index in memory_indices
-                    and info.remote_node == left
-                    and info.fidelity >= fidelity):
-                return [memory_info, info]
+        for memory_info2 in memory_manager:
+            if (memory_info2.state == "ENTANGLED"
+                    and memory_info2.index in memory_indices
+                    and memory_info2.remote_node == right
+                    and memory_info2.fidelity >= fidelity):
+                return [memory_info, memory_info2]
+    elif (memory_info.state == "ENTANGLED"
+            and memory_info.index in memory_indices
+            and memory_info.remote_node == right
+            and memory_info.fidelity >= fidelity):
+        for memory_info2 in memory_manager:
+            if (memory_info2.state == "ENTANGLED"
+                    and memory_info2.index in memory_indices
+                    and memory_info2.remote_node == left
+                    and memory_info2.fidelity >= fidelity):
+                return [memory_info, memory_info2]
     return []
 
 
-def es_req_func(protocols: List["EntanglementProtocol"], args: Arguments) -> "EntanglementSwappingB":
-    """Function used by `es_rule_actionA` for selecting swapping protocols on the remote node
-    """
-    target_memo = args["target_memo"]
-    for protocol in protocols:
-        if (isinstance(protocol, EntanglementSwappingB)
-                # and protocol.memory.name == memories_info[0].remote_memo):
-                and protocol.memory.name == target_memo):
-            return protocol
-
-
-def es_rule_actionA(memories_info: List["MemoryInfo"], args: Arguments):
-    """Action function used by EntanglementSwappingA protocol on nodes
+def es_rule_conditionB1(memory_info: "MemoryInfo", manager: "MemoryManager", args: Arguments) -> List["MemoryInfo"]:
+    """Condition function used by EntanglementSwappingB protocol on nodes of either responder or initiator
     """
-    es_succ_prob = args["es_succ_prob"]
-    es_degradation = args["es_degradation"]
-    memories = [info.memory for info in memories_info]
-    protocol = EntanglementSwappingA(None, "ESA.{}.{}".format(memories[0].name, memories[1].name),
-                                     memories[0], memories[1],
-                                     success_prob=es_succ_prob,
-                                     degradation=es_degradation)
-    dsts = [info.remote_node for info in memories_info]
-    req_funcs = [es_req_func, es_req_func]
-    req_args = [{"target_memo": memories_info[0].remote_memo},
-                {"target_memo": memories_info[1].remote_memo}]
-    return protocol, dsts, req_funcs, req_args
+    memory_indices = args["memory_indices"]
+    target_remote = args["target_remote"]  # A - B - C. For A: B is the remote node, C is the target remote
+    fidelity = args["fidelity"]
+    if (memory_info.state == "ENTANGLED"
+            and memory_info.index in memory_indices
+            # and memory_info.remote_node != path[-1]
+            and memory_info.remote_node != target_remote
+            # and memory_info.fidelity >= reservation.fidelity):
+            and memory_info.fidelity >= fidelity):
+        return [memory_info]
+    else:
+        return []
 
 
 def es_rule_conditionB2(memory_info: "MemoryInfo", manager: "MemoryManager", args: Arguments) -> List["MemoryInfo"]:
@@ -328,7 +308,7 @@ class ResourceReservationProtocol(StackProtocol):
     If unsuccessful, the protocol will notify the network manager of failure.
 
     Attributes:
-        own (QuantumRouter): node that protocol instance is attached to.
+        owner (QuantumRouter): node that protocol instance is attached to.
         name (str): label for protocol instance.
         memo_arr (MemoryArray): memory array to track.
         timecards (List[MemoryTimeCard]): list of reservation cards for all memories on node.
@@ -337,23 +317,24 @@ class ResourceReservationProtocol(StackProtocol):
         accepted_reservations (List[Reservation]): list of all approved reservation requests.
     """
 
-    def __init__(self, own: "QuantumRouter", name: str, memory_array_name: str):
+    def __init__(self, owner: "QuantumRouter", name: str, memory_array_name: str):
         """Constructor for the reservation protocol class.
 
         Args:
-            own (QuantumRouter): node to attach protocol to.
+            owner (QuantumRouter): node to attach protocol to.
             name (str): label for reservation protocol instance.
             memory_array_name (str): name of the memory array component on own.
         """
 
-        super().__init__(own, name)
-        self.memo_arr = own.components[memory_array_name]
+        super().__init__(owner, name)
+        self.memo_arr = owner.components[memory_array_name]
         self.timecards = [MemoryTimeCard(i) for i in range(len(self.memo_arr))]
         self.es_succ_prob = 1
         self.es_degradation = 0.95
         self.accepted_reservations = []
 
-    def push(self, responder: str, start_time: int, end_time: int, memory_size: int, target_fidelity: float):
+    def push(self, responder: str, start_time: int, end_time: int, memory_size: int, target_fidelity: float,
+             entanglement_number: int = 1, identity: int = 0):
         """Method to receive reservation requests from higher level protocol.
 
         Will evaluate request and determine if node can meet it.
@@ -366,15 +347,16 @@ def push(self, responder: str, start_time: int, end_time: int, memory_size: int,
             end_time (int): simulation time at which entanglement should cease.
             memory_size (int): number of memories to be entangled.
             target_fidelity (float): desired fidelity of entanglement.
-
+            entanglement_number (int): the number of entanglement the request ask for.
+            identity (int): the ID of the request.
         Side Effects:
             May push/pop to lower/upper attached protocols (or network manager).
         """
 
-        reservation = Reservation(self.own.name, responder, start_time, end_time, memory_size, target_fidelity)
+        reservation = Reservation(self.owner.name, responder, start_time, end_time, memory_size, target_fidelity, entanglement_number, identity)
         if self.schedule(reservation):
             msg = ResourceReservationMessage(RSVPMsgType.REQUEST, self.name, reservation)
-            qcap = QCap(self.own.name)
+            qcap = QCap(self.owner.name)
             msg.qcaps.append(qcap)
             self._push(dst=responder, msg=msg)
         else:
@@ -386,9 +368,11 @@ def pop(self, src: str, msg: "ResourceReservationMessage"):
            NOTE (caitao, 3/19/2024): argument "src" not used
         Messages may be of 3 types, causing different network manager behavior:
 
-        1. REQUEST: requests are evaluated, and forwarded along the path if accepted. Otherwise a REJECT message is sent back.
+        1. REQUEST: requests are evaluated, and forwarded along the path if accepted.
+            Otherwise, a REJECT message is sent back.
         2. REJECT: any reserved resources are released and the message forwarded back towards the initializer.
-        3. APPROVE: rules are created to achieve the approved request. The message is forwarded back towards the initializer.
+        3. APPROVE: rules are created to achieve the approved request.
+            The message is forwarded back towards the initializer.
 
         Args:
             src (str): source node of the message.
@@ -402,11 +386,11 @@ def pop(self, src: str, msg: "ResourceReservationMessage"):
         """
 
         if msg.msg_type == RSVPMsgType.REQUEST:
-            assert self.own.timeline.now() < msg.reservation.start_time
+            assert self.owner.timeline.now() < msg.reservation.start_time
             if self.schedule(msg.reservation):
-                qcap = QCap(self.own.name)
+                qcap = QCap(self.owner.name)
                 msg.qcaps.append(qcap)
-                if self.own.name == msg.reservation.responder:
+                if self.owner.name == msg.reservation.responder:
                     path = [qcap.node for qcap in msg.qcaps]
                     rules = self.create_rules(path, reservation=msg.reservation)
                     self.load_rules(rules, msg.reservation)
@@ -422,14 +406,14 @@ def pop(self, src: str, msg: "ResourceReservationMessage"):
         elif msg.msg_type == RSVPMsgType.REJECT:
             for card in self.timecards:
                 card.remove(msg.reservation)
-            if msg.reservation.initiator == self.own.name:
+            if msg.reservation.initiator == self.owner.name:
                 self._pop(msg=msg)
             else:
                 self._push(dst=msg.reservation.initiator, msg=msg)
         elif msg.msg_type == RSVPMsgType.APPROVE:
             rules = self.create_rules(msg.path, msg.reservation)
             self.load_rules(rules, msg.reservation)
-            if msg.reservation.initiator == self.own.name:
+            if msg.reservation.initiator == self.owner.name:
                 self._pop(msg=msg)
             else:
                 self._push(dst=msg.reservation.initiator, msg=msg)
@@ -437,8 +421,8 @@ def pop(self, src: str, msg: "ResourceReservationMessage"):
             raise Exception("Unknown type of message", msg.msg_type)
 
     def schedule(self, reservation: "Reservation") -> bool:
-        """Method to attempt reservation request.
-           NOTE (caitao, 3/19/2024): change method name to can_schedule()?
+        """Method to attempt reservation request. If attempt succeeded, return True; otherwise, return False.
+
         Args:
             reservation (Reservation): reservation to approve or reject.
 
@@ -446,21 +430,21 @@ def schedule(self, reservation: "Reservation") -> bool:
             bool: if reservation can be met or not.
         """
 
-        if self.own.name in [reservation.initiator, reservation.responder]:
+        if self.owner.name in [reservation.initiator, reservation.responder]:
             counter = reservation.memory_size
-        else: # e.g., entanglement swapping nodes needs twice amount of memory
+        else:  # e.g., entanglement swapping nodes needs twice amount of memory
             counter = reservation.memory_size * 2
-        cards = []
-        for card in self.timecards:
-            if card.add(reservation):
+        timecards = []
+        for timecard in self.timecards:
+            if timecard.add(reservation):
                 counter -= 1
-                cards.append(card)
-            if counter == 0:
+                timecards.append(timecard)
+            if counter == 0:  # attempt reservation succeeded: enough memory (timecard)
                 break
 
-        if counter > 0:
-            for card in cards:
-                card.remove(reservation)
+        if counter > 0:       # attempt reservation failed: not enough memory (timecard)
+            for timecard in timecards:
+                timecard.remove(reservation)  # remove reservation from the timecard that have added the reservation
             return False
 
         return True
@@ -484,11 +468,12 @@ def create_rules(self, path: List[str], reservation: "Reservation") -> List["Rul
             if reservation in card.reservations:
                 memory_indices.append(card.memory_index)
 
-        # create rules for entanglement generation
-        index = path.index(self.own.name)
+        index = path.index(self.owner.name)  # the location of this node along the path from initiator to responder
+
+        # 1. create rules for entanglement generation
         if index > 0:
             condition_args = {"memory_indices": memory_indices[:reservation.memory_size]}
-            action_args = {"mid": self.own.map_to_middle_node[path[index - 1]],
+            action_args = {"mid": self.owner.map_to_middle_node[path[index - 1]],
                            "path": path, "index": index}
             rule = Rule(10, eg_rule_action1, eg_rule_condition, action_args, condition_args)
             rules.append(rule)
@@ -499,65 +484,52 @@ def create_rules(self, path: List[str], reservation: "Reservation") -> List["Rul
             else:
                 condition_args = {"memory_indices": memory_indices[reservation.memory_size:]}
 
-            action_args = {"mid": self.own.map_to_middle_node[path[index + 1]],
-                           "path": path, "index": index, "name": self.own.name,
-                           "reservation": reservation}
+            action_args = {"mid": self.owner.map_to_middle_node[path[index + 1]],
+                           "path": path, "index": index, "name": self.owner.name, "reservation": reservation}
             rule = Rule(10, eg_rule_action2, eg_rule_condition, action_args, condition_args)
             rules.append(rule)
 
-        # create rules for entanglement purification
+        # 2. create rules for entanglement purification
         if index > 0:
-            condition_args = {"memory_indices": memory_indices[:reservation.memory_size],
-                              "reservation": reservation}
+            condition_args = {"memory_indices": memory_indices[:reservation.memory_size], "reservation": reservation}
             action_args = {}
             rule = Rule(10, ep_rule_action1, ep_rule_condition1, action_args, condition_args)
             rules.append(rule)
 
         if index < len(path) - 1:
             if index == 0:
-                condition_args = {"memory_indices": memory_indices,
-                                  "fidelity": reservation.fidelity}
+                condition_args = {"memory_indices": memory_indices, "fidelity": reservation.fidelity}
             else:
-                condition_args = {"memory_indices": memory_indices[reservation.memory_size:],
-                                  "fidelity": reservation.fidelity}
+                condition_args = {"memory_indices": memory_indices[reservation.memory_size:], "fidelity": reservation.fidelity}
 
             action_args = {}
             rule = Rule(10, ep_rule_action2, ep_rule_condition2, action_args, condition_args)
             rules.append(rule)
 
-        # create rules for entanglement swapping
+        # 3. create rules for entanglement swapping
         if index == 0:
-            condition_args = {"memory_indices": memory_indices,
-                              "target_remote": path[-1],
-                              "fidelity": reservation.fidelity}
+            condition_args = {"memory_indices": memory_indices, "target_remote": path[-1], "fidelity": reservation.fidelity}
             action_args = {}
             rule = Rule(10, es_rule_actionB, es_rule_conditionB1, action_args, condition_args)
             rules.append(rule)
-
         elif index == len(path) - 1:
             action_args = {}
-            condition_args = {"memory_indices": memory_indices,
-                              "target_remote": path[0],
-                              "fidelity": reservation.fidelity}
+            condition_args = {"memory_indices": memory_indices, "target_remote": path[0], "fidelity": reservation.fidelity}
             rule = Rule(10, es_rule_actionB, es_rule_conditionB1, action_args, condition_args)
             rules.append(rule)
-
         else:
             _path = path[:]
-            while _path.index(self.own.name) % 2 == 0:
+            while _path.index(self.owner.name) % 2 == 0:
                 new_path = []
                 for i, n in enumerate(_path):
                     if i % 2 == 0 or i == len(_path) - 1:
                         new_path.append(n)
                 _path = new_path
-            _index = _path.index(self.own.name)
+            _index = _path.index(self.owner.name)
             left, right = _path[_index - 1], _path[_index + 1]
 
-            condition_args = {"memory_indices": memory_indices,
-                              "left": left, "right": right,
-                              "fidelity": reservation.fidelity}
-            action_args = {"es_succ_prob": self.es_succ_prob,
-                           "es_degradation": self.es_degradation}
+            condition_args = {"memory_indices": memory_indices, "left": left, "right": right, "fidelity": reservation.fidelity}
+            action_args = {"es_succ_prob": self.es_succ_prob, "es_degradation": self.es_degradation}
             rule = Rule(10, es_rule_actionA, es_rule_conditionA, action_args, condition_args)
             rules.append(rule)
 
@@ -582,20 +554,21 @@ def load_rules(self, rules: List["Rule"], reservation: "Reservation") -> None:
         """
 
         self.accepted_reservations.append(reservation)
-        for card in self.timecards:
-            if reservation in card.reservations:
-                process = Process(self.own.resource_manager, "update",
-                                  [None, self.memo_arr[card.memory_index], "RAW"])
-                event = Event(reservation.end_time, process, 1)
-                self.own.timeline.schedule(event)
 
         for rule in rules:
-            process = Process(self.own.resource_manager, "load", [rule])
-            event = Event(reservation.start_time, process)
-            self.own.timeline.schedule(event)
-            process = Process(self.own.resource_manager, "expire", [rule])
-            event = Event(reservation.end_time, process, 0)
-            self.own.timeline.schedule(event)
+            process = Process(self.owner.resource_manager, "load", [rule])
+            event = Event(reservation.start_time, process, self.owner.timeline.schedule_counter)
+            self.owner.timeline.schedule(event)
+            
+            process = Process(self.owner.resource_manager, "expire", [rule])
+            event = Event(reservation.end_time, process, self.owner.timeline.schedule_counter)
+            self.owner.timeline.schedule(event)
+
+        for card in self.timecards:
+            if reservation in card.reservations:
+                process = Process(self.owner.resource_manager, "update", [None, self.memo_arr[card.memory_index], "RAW"])
+                event = Event(reservation.end_time, process, self.owner.timeline.schedule_counter)
+                self.owner.timeline.schedule(event)
 
     def received_message(self, src, msg):
         """Method to receive messages directly (should not be used; receive through network manager)."""
@@ -613,6 +586,7 @@ def set_swapping_degradation(self, degradation: float) -> None:
 
 class Reservation:
     """Tracking of reservation parameters for the network manager.
+       Each request will generate a reservation
 
     Attributes:
         initiator (str): name of the node that created the reservation request.
@@ -620,10 +594,13 @@ class Reservation:
         start_time (int): simulation time at which entanglement should be attempted.
         end_time (int): simulation time at which resources may be released.
         memory_size (int): number of entangled memory pairs requested.
+        path (list): a list of router names from the source to destination
+        entanglement_number (int): the number of entanglement pair that the request ask for.
+        identity (int): the ID of a request.
     """
 
     def __init__(self, initiator: str, responder: str, start_time: int,
-                 end_time: int, memory_size: int, fidelity: float):
+                 end_time: int, memory_size: int, fidelity: float, entanglement_number: int = 1, identity: int = 0):
         """Constructor for the reservation class.
 
         Args:
@@ -633,6 +610,8 @@ def __init__(self, initiator: str, responder: str, start_time: int,
             end_time (int): simulation end time of entanglement.
             memory_size (int): number of entangled memories requested.
             fidelity (float): desired fidelity of entanglement.
+            entanglement_number (int): the number of entanglement the request ask for.
+            identity (int): the ID of a request
         """
 
         self.initiator = initiator
@@ -641,20 +620,24 @@ def __init__(self, initiator: str, responder: str, start_time: int,
         self.end_time = end_time
         self.memory_size = memory_size
         self.fidelity = fidelity
+        self.entanglement_number = entanglement_number
+        self.identity = identity
         self.path = []
         assert self.start_time < self.end_time
         assert self.memory_size > 0
 
+    def __str__(self) -> str:
+        s = "|initiator={}; responder={}; start_time={:,}; end_time={:,}; memory_size={}; target_fidelity={}; entanglement_number={}; identity={}|".format(
+              self.initiator, self.responder, int(self.start_time), int(self.end_time), self.memory_size, self.fidelity, self.entanglement_number, self.identity)
+        return s
+
+    def __repr__(self) -> str:
+        return self.__str__()
+
     def set_path(self, path: List[str]):
         self.path = path
 
-    def __str__(self):
-        return f"Reservation: initiator={self.initiator}, " \
-               f"responder={self.responder}, start_time={self.start_time}, " \
-               f"end_time={self.end_time}, memory_size={self.memory_size}, " \
-               f"target_fidelity={self.fidelity}"
-
-    def __eq__(self, other: "Reservation"):
+    def __eq__(self, other: "Reservation") -> bool:
         return other.initiator == self.initiator and \
                other.responder == self.responder and \
                other.start_time == self.start_time and \
@@ -662,9 +645,16 @@ def __eq__(self, other: "Reservation"):
                other.memory_size == self.memory_size and \
                other.fidelity == self.fidelity
 
+    def __lt__(self, other: "Reservation") -> bool:
+        return self.identity < other.identity
+
+    def __hash__(self):
+        return hash((self.initiator, self.responder, self.start_time, self.end_time, self.memory_size, self.fidelity))
+
 
 class MemoryTimeCard:
     """Class for tracking reservations on a specific memory.
+       Each quantum memory in a memory array is associated with a memory time card
 
     Attributes:
         memory_index (int): index of memory being tracked (in memory array).
@@ -690,7 +680,7 @@ def add(self, reservation: "Reservation") -> bool:
             reservation (Reservation): reservation to add.
 
         Returns:
-            bool: whether or not reservation was inserted successfully.
+            bool: whether reservation was inserted successfully.
         """
         
         position = self.schedule_reservation(reservation)
@@ -707,7 +697,7 @@ def remove(self, reservation: "Reservation") -> bool:
             reservation (Reservation): reservation to remove.
 
         Returns:
-            bool: if reservation was already on the memory or not.
+            bool: if reservation was already on the memory (return True) or not (return False).
         """
 
         try:
@@ -740,7 +730,7 @@ def schedule_reservation(self, reservation: "Reservation") -> int:
                 end = mid - 1
             elif self.reservations[mid].end_time < reservation.start_time:
                 start = mid + 1
-            elif (max(self.reservations[mid].start_time, reservation.start_time) <= \
+            elif (max(self.reservations[mid].start_time, reservation.start_time) <=
                     min(self.reservations[mid].end_time, reservation.end_time)):
                 return -1
             else:
@@ -749,7 +739,7 @@ def schedule_reservation(self, reservation: "Reservation") -> int:
 
 
 class QCap:
-    """Class to collect local information for the reservation protocol
+    """Quantum Capacity. Class to collect local information for the reservation protocol
 
     Attributes:
         node (str): name of current node.
diff --git a/src/network_management/routing.py b/sequence/network_management/routing.py
similarity index 92%
rename from src/network_management/routing.py
rename to sequence/network_management/routing.py
index d8a5a276..90dce917 100644
--- a/src/network_management/routing.py
+++ b/sequence/network_management/routing.py
@@ -28,9 +28,7 @@ def __init__(self, msg_type: Enum, receiver: str, payload: "Message"):
         self.payload = payload
 
     def __str__(self):
-        return "type={}, receiver={}, payload={}".format(self.msg_type,
-                                                         self.receiver,
-                                                         self.payload)
+        return "type={}, receiver={}, payload={}".format(self.msg_type, self.receiver, self.payload)
 
 
 class StaticRoutingProtocol(StackProtocol):
@@ -45,7 +43,7 @@ class StaticRoutingProtocol(StackProtocol):
         forwarding_table (Dict[str, str]): mapping of destination node names to name of node for next hop.
     """
     
-    def __init__(self, own: "Node", name: str, forwarding_table: Dict):
+    def __init__(self, owner: "Node", name: str, forwarding_table: Dict):
         """Constructor for routing protocol.
 
         Args:
@@ -54,9 +52,12 @@ def __init__(self, own: "Node", name: str, forwarding_table: Dict):
             forwarding_table (Dict[str, str]): forwarding routing table in format {name of destination node: name of next node}.
         """
 
-        super().__init__(own, name)
+        super().__init__(owner, name)
         self.forwarding_table = forwarding_table
 
+    def get_forwarding_table(self) -> dict:
+        return self.forwarding_table
+
     def add_forwarding_rule(self, dst: str, next_node: str):
         """Adds mapping {dst: next_node} to forwarding table."""
 
@@ -81,7 +82,7 @@ def push(self, dst: str, msg: "Message"):
             Will invoke `push` method of lower protocol or network manager.
         """
 
-        assert dst != self.own.name
+        assert dst != self.owner.name
         dst = self.forwarding_table[dst]
         new_msg = StaticRoutingMessage(Enum, self.name, msg)
         self._push(dst=dst, msg=new_msg)
diff --git a/src/protocol.py b/sequence/protocol.py
similarity index 85%
rename from src/protocol.py
rename to sequence/protocol.py
index 82048075..7951fd6a 100644
--- a/src/protocol.py
+++ b/sequence/protocol.py
@@ -20,15 +20,15 @@ class Protocol(ABC):
         name (str): label for protocol instance.
     """
 
-    def __init__(self, own: "Node", name: str):
+    def __init__(self, owner: "Node", name: str):
         """Constructor for protocol.
 
         Args:
-            own (Node): node protocol is attached to.
+            owner (Node): node protocol is attached to.
             name (str): name of protocol instance.
         """
 
-        self.own = own
+        self.owner = owner
         self.name = name
 
     @abstractmethod
@@ -37,6 +37,9 @@ def received_message(self, src: str, msg: "Message"):
 
         pass
 
+    def __str__(self) -> str:
+        return self.name
+
 
 class StackProtocol(Protocol):
     """Abstract protocol class for protocols in a stack structure.
@@ -50,7 +53,7 @@ class StackProtocol(Protocol):
         lower_protocols (List[StackProtocol]): Protocols to push to.
     """
 
-    def __init__(self, own: "Node", name: str):
+    def __init__(self, owner: "Node", name: str):
         """Constructor for stack protocol class.
 
         Args:
@@ -58,7 +61,7 @@ def __init__(self, own: "Node", name: str):
             name (str): name of protocol instance.
         """
 
-        super().__init__(own, name)
+        super().__init__(owner, name)
         self.upper_protocols = []
         self.lower_protocols = []
 
@@ -75,10 +78,12 @@ def pop(self, **kwargs):
         pass
 
     def _push(self, **kwargs):
+        """call the push of all lower_protocols"""
         for protocol in self.lower_protocols:
             protocol.push(**kwargs)
 
     def _pop(self, **kwargs):
+        """call the pop of all upper_protocols"""
         for protocol in self.upper_protocols:
             protocol.pop(**kwargs)
 
diff --git a/src/qkd/BB84.py b/sequence/qkd/BB84.py
similarity index 87%
rename from src/qkd/BB84.py
rename to sequence/qkd/BB84.py
index 99376b4e..af839df9 100644
--- a/src/qkd/BB84.py
+++ b/sequence/qkd/BB84.py
@@ -103,7 +103,7 @@ class BB84(StackProtocol):
         self.end_run_times (List[int]): simulation time for end of each request.
     """
 
-    def __init__(self, own: "QKDNode", name: str, lightsource: str, qsdetector: str, role=-1):
+    def __init__(self, owner: "QKDNode", name: str, lightsource: str, qsdetector: str, role=-1):
         """Constructor for BB84 class.
 
         Args:
@@ -116,9 +116,9 @@ def __init__(self, own: "QKDNode", name: str, lightsource: str, qsdetector: str,
             role (int): 0/1 role defining Alice and Bob protocols (default -1).
         """
 
-        if own is None:  # used only for unit test purposes
+        if owner is None:  # used only for unit test purposes
             return
-        super().__init__(own, name)
+        super().__init__(owner, name)
         self.ls_name = lightsource
         self.qsd_name = qsdetector
         self.role = role
@@ -169,7 +169,7 @@ def push(self, length: int, key_num: int, run_time=math.inf) -> None:
         self.key_lengths.append(length)
         self.another.key_lengths.append(length)
         self.keys_left_list.append(key_num)
-        end_run_time = run_time + self.own.timeline.now()
+        end_run_time = run_time + self.owner.timeline.now()
         self.end_run_times.append(end_run_time)
         self.another.end_run_times.append(end_run_time)
 
@@ -206,24 +206,24 @@ def start_protocol(self) -> None:
             self.working = True
             self.another.working = True
 
-            ls = self.own.components[self.ls_name]
+            ls = self.owner.components[self.ls_name]
             self.ls_freq = ls.frequency
 
             # calculate light time based on key length
             self.light_time = self.key_lengths[0] / (self.ls_freq * ls.mean_photon_num)
 
             # send message that photon pulse is beginning, then send bits
-            self.start_time = int(self.own.timeline.now()) + round(self.own.cchannels[self.another.own.name].delay)
+            self.start_time = int(self.owner.timeline.now()) + round(self.owner.cchannels[self.another.owner.name].delay)
             message = BB84Message(BB84MsgType.BEGIN_PHOTON_PULSE, self.another.name,
                                   frequency=self.ls_freq, light_time=self.light_time,
                                   start_time=self.start_time, wavelength=ls.wavelength)
-            self.own.send_message(self.another.own.name, message)
+            self.owner.send_message(self.another.owner.name, message)
 
             process = Process(self, "begin_photon_pulse", [])
             event = Event(self.start_time, process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
 
-            self.last_key_time = self.own.timeline.now()
+            self.last_key_time = self.owner.timeline.now()
         else:
             self.ready = True
 
@@ -241,8 +241,8 @@ def begin_photon_pulse(self) -> None:
         
         log.logger.debug(self.name + " starting photon pulse")
         
-        if self.working and self.own.timeline.now() < self.end_run_times[0]:
-            self.own.destination = self.another.own.name
+        if self.working and self.owner.timeline.now() < self.end_run_times[0]:
+            self.owner.destination = self.another.owner.name
 
             # generate basis/bit list
             num_pulses = round(self.light_time * self.ls_freq)
@@ -250,7 +250,7 @@ def begin_photon_pulse(self) -> None:
             bit_list = numpy.random.choice([0, 1], num_pulses)
 
             # control hardware
-            lightsource = self.own.components[self.ls_name]
+            lightsource = self.owner.components[self.ls_name]
             encoding_type = lightsource.encoding_type
             state_list = []
             for i, bit in enumerate(bit_list):
@@ -262,10 +262,10 @@ def begin_photon_pulse(self) -> None:
             self.bit_lists.append(bit_list)
 
             # schedule another
-            self.start_time = self.own.timeline.now()
+            self.start_time = self.owner.timeline.now()
             process = Process(self, "begin_photon_pulse", [])
             event = Event(self.start_time + int(round(self.light_time * 1e12)), process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
 
         else:
             self.working = False
@@ -278,10 +278,10 @@ def begin_photon_pulse(self) -> None:
             self.another.end_run_times.pop(0)
 
             # wait for quantum channel to clear of photons, then start protocol
-            time = self.own.timeline.now() + self.own.qchannels[self.another.own.name].delay + 1
+            time = self.owner.timeline.now() + self.owner.qchannels[self.another.owner.name].delay + 1
             process = Process(self, "start_protocol", [])
             event = Event(time, process)
-            self.own.timeline.schedule(event)
+            self.owner.timeline.schedule(event)
 
     def set_measure_basis_list(self) -> None:
         """Method to set measurement basis list."""
@@ -291,30 +291,30 @@ def set_measure_basis_list(self) -> None:
         num_pulses = int(self.light_time * self.ls_freq)
         basis_list = numpy.random.choice([0, 1], num_pulses)
         self.basis_lists.append(basis_list)
-        self.own.components[self.qsd_name].set_basis_list(basis_list, self.start_time, self.ls_freq)
+        self.owner.components[self.qsd_name].set_basis_list(basis_list, self.start_time, self.ls_freq)
 
     def end_photon_pulse(self) -> None:
         """Method to process sent qubits."""
 
         log.logger.debug(self.name + " ending photon pulse")
 
-        if self.working and self.own.timeline.now() < self.end_run_times[0]:
+        if self.working and self.owner.timeline.now() < self.end_run_times[0]:
             # get bits
-            self.bit_lists.append(self.own.get_bits(self.light_time, self.start_time, self.ls_freq, self.qsd_name))
-            self.start_time = self.own.timeline.now()
+            self.bit_lists.append(self.owner.get_bits(self.light_time, self.start_time, self.ls_freq, self.qsd_name))
+            self.start_time = self.owner.timeline.now()
             # set bases for measurement
             self.set_measure_basis_list()
 
             # schedule another if necessary
-            if self.own.timeline.now() + self.light_time * 1e12 - 1 < self.end_run_times[0]:
+            if self.owner.timeline.now() + self.light_time * 1e12 - 1 < self.end_run_times[0]:
                 # schedule another
                 process = Process(self, "end_photon_pulse", [])
                 event = Event(self.start_time + int(round(self.light_time * 1e12) - 1), process)
-                self.own.timeline.schedule(event)
+                self.owner.timeline.schedule(event)
 
             # send message that we got photons
             message = BB84Message(BB84MsgType.RECEIVED_QUBITS, self.another.name)
-            self.own.send_message(self.another.own.name, message)
+            self.owner.send_message(self.another.owner.name, message)
 
     def received_message(self, src: str, msg: "Message") -> None:
         """Method to receive messages.
@@ -326,14 +326,14 @@ def received_message(self, src: str, msg: "Message") -> None:
             msg (Message): message received.
         """
 
-        if self.working and self.own.timeline.now() < self.end_run_times[0]:
+        if self.working and self.owner.timeline.now() < self.end_run_times[0]:
             if msg.msg_type is BB84MsgType.BEGIN_PHOTON_PULSE:  # (current node is Bob): start to receive photons
                 self.ls_freq = msg.frequency
                 self.light_time = msg.light_time
 
                 log.logger.debug(self.name + " received BEGIN_PHOTON_PULSE, ls_freq={}, light_time={}".format(self.ls_freq, self.light_time))
 
-                self.start_time = int(msg.start_time) + self.own.qchannels[src].delay
+                self.start_time = int(msg.start_time) + self.owner.qchannels[src].delay
 
                 # generate and set basis list
                 self.set_measure_basis_list()
@@ -341,13 +341,13 @@ def received_message(self, src: str, msg: "Message") -> None:
                 # schedule end_photon_pulse()
                 process = Process(self, "end_photon_pulse", [])
                 event = Event(self.start_time + round(self.light_time * 1e12) - 1, process)
-                self.own.timeline.schedule(event)
+                self.owner.timeline.schedule(event)
 
             elif msg.msg_type is BB84MsgType.RECEIVED_QUBITS:  # (Current node is Alice): can send basis
                 log.logger.debug(self.name + " received RECEIVED_QUBITS message")
                 bases = self.basis_lists.pop(0)
                 message = BB84Message(BB84MsgType.BASIS_LIST, self.another.name, bases=bases)
-                self.own.send_message(self.another.own.name, message)
+                self.owner.send_message(self.another.owner.name, message)
 
             elif msg.msg_type is BB84MsgType.BASIS_LIST:  # (Current node is Bob): compare bases
                 log.logger.debug(self.name + " received BASIS_LIST message")
@@ -365,7 +365,7 @@ def received_message(self, src: str, msg: "Message") -> None:
 
                 # send to Alice list of matching indices
                 message = BB84Message(BB84MsgType.MATCHING_INDICES, self.another.name, indices=indices)
-                self.own.send_message(self.another.own.name, message)
+                self.owner.send_message(self.another.owner.name, message)
 
             elif msg.msg_type is BB84MsgType.MATCHING_INDICES:  # (Current node is Alice): create key from matching indices
                 log.logger.debug(self.name + " received MATCHING_INDICES message")
@@ -380,7 +380,7 @@ def received_message(self, src: str, msg: "Message") -> None:
 
                 # check if key long enough. If it is, truncate if necessary and call cascade
                 if len(self.key_bits) >= self.key_lengths[0]:
-                    throughput = self.key_lengths[0] * 1e12 / (self.own.timeline.now() - self.last_key_time)
+                    throughput = self.key_lengths[0] * 1e12 / (self.owner.timeline.now() - self.last_key_time)
 
                     while len(self.key_bits) >= self.key_lengths[0] and self.keys_left_list[0] > 0:
                         log.logger.info(self.name + " generated a valid key")
@@ -391,7 +391,7 @@ def received_message(self, src: str, msg: "Message") -> None:
 
                         # for metrics
                         if self.latency == 0:
-                            self.latency = (self.own.timeline.now() - self.last_key_time) * 1e-12
+                            self.latency = (self.owner.timeline.now() - self.last_key_time) * 1e-12
 
                         self.throughputs.append(throughput)
 
@@ -404,7 +404,7 @@ def received_message(self, src: str, msg: "Message") -> None:
 
                         self.keys_left_list[0] -= 1
 
-                    self.last_key_time = self.own.timeline.now()
+                    self.last_key_time = self.owner.timeline.now()
 
                 # check if we're done
                 if self.keys_left_list[0] < 1:
diff --git a/src/qkd/__init__.py b/sequence/qkd/__init__.py
similarity index 100%
rename from src/qkd/__init__.py
rename to sequence/qkd/__init__.py
diff --git a/src/qkd/cascade.py b/sequence/qkd/cascade.py
similarity index 96%
rename from src/qkd/cascade.py
rename to sequence/qkd/cascade.py
index caf1f8a3..d706c9ea 100644
--- a/src/qkd/cascade.py
+++ b/sequence/qkd/cascade.py
@@ -146,7 +146,7 @@ class Cascade(StackProtocol):
         privacy_throughput (int): throughput of not revealed bits.
     """
 
-    def __init__(self, own: "QKDNode", name: str, w=4, role=-1, secure_params=100):
+    def __init__(self, owner: "QKDNode", name: str, w=4, role=-1, secure_params=100):
         """Constructor for cascade class.
 
         Args:
@@ -159,7 +159,7 @@ def __init__(self, own: "QKDNode", name: str, w=4, role=-1, secure_params=100):
             secure_params (int): security parameter (default 100).
         """
 
-        super().__init__(own, name)
+        super().__init__(owner, name)
 
         self.w = w
         self.role = role  # 0 for sender, 1 for receiver
@@ -210,7 +210,7 @@ def pop(self, info: int) -> None:
 
         log.logger.debug(self.name + ' state={} get_key_from_BB84, key={}'.format(self.state, info))
         self.bits.append(info)
-        self.t1.append(self.own.timeline.now())
+        self.t1.append(self.owner.timeline.now())
         self.t2.append(-1)
 
         if self.state == 1:
@@ -289,7 +289,7 @@ def get_diff_bit_num(key1, key2):
             self.keylen = msg.keylen
             self.frame_num = msg.frame_num
             self.run_time = msg.run_time
-            self.start_time = self.own.timeline.now()
+            self.start_time = self.owner.timeline.now()
             self.end_time = self.start_time + self.run_time
             self.state = 1
 
@@ -397,7 +397,7 @@ def flip_bit_at_pos(val, pos):
                     self._pop(key=self.valid_keys[-1])
                     self.frame_num -= 1
 
-            self.t2[key_id] = self.own.timeline.now()
+            self.t2[key_id] = self.owner.timeline.now()
             self.performance_measure()
 
     def generate_key(self, keylen: int, frame_num=math.inf, run_time=math.inf) -> None:
@@ -421,14 +421,14 @@ def generate_key(self, keylen: int, frame_num=math.inf, run_time=math.inf) -> No
 
         if self.state == 0:
             log.logger.debug(self.name + ' generate_key with state 0')
-            self.setup_time = self.own.timeline.now()
+            self.setup_time = self.owner.timeline.now()
             self.keylen = keylen
             self.frame_num = frame_num
             self.run_time = run_time
             self._push(length=10000, key_num=1)
 
         else:
-            self.start_time = self.own.timeline.now()
+            self.start_time = self.owner.timeline.now()
             self.end_time = self.start_time + self.run_time
             log.logger.debug(self.name + ' generate_key with state ' + str(self.state))
             self._push(length=self.frame_len, key_num=self.frame_num, run_time=self.run_time)
@@ -535,7 +535,7 @@ def check_checksum(self, key_id: int) -> bool:
                 self.frame_num -= 1
 
         if self.role == 0:
-            self.t2[key_id] = self.own.timeline.now()
+            self.t2[key_id] = self.owner.timeline.now()
         self.performance_measure()
 
         message = CascadeMessage(CascadeMsgType.KEY_IS_VALID, self.another.name, key_id=key_id)
@@ -583,12 +583,12 @@ def interactive_binary_search(self, key_id: int, pass_id: int, block_id: int, st
     def send_by_cc(self, message: "CascadeMessage") -> None:
         """Method to send classical messages."""
 
-        if self.own.timeline.now() > self.end_time and self.state != 2:
+        if self.owner.timeline.now() > self.end_time and self.state != 2:
             self.end_cascade()
             self.another.end_cascade()
             return
 
-        self.own.send_message(self.another.own.name, message)
+        self.owner.send_message(self.another.owner.name, message)
 
     def performance_measure(self) -> None:
         """Method to record performance metrics."""
@@ -607,9 +607,9 @@ def performance_measure(self) -> None:
                 self.latency = None
             self.another.latency = self.latency
 
-        if self.own.timeline.now() - self.start_time:
-            self.throughput = 1e12 * len(self.valid_keys) * self.keylen / (self.own.timeline.now() - self.start_time)
-            self.privacy_throughput = 1e12 * (len(self.valid_keys) * self.keylen - int(len(self.valid_keys)/40) * self.secure_params - self.disclosed_bits_counter) / (self.own.timeline.now() - self.start_time)
+        if self.owner.timeline.now() - self.start_time:
+            self.throughput = 1e12 * len(self.valid_keys) * self.keylen / (self.owner.timeline.now() - self.start_time)
+            self.privacy_throughput = 1e12 * (len(self.valid_keys) * self.keylen - int(len(self.valid_keys)/40) * self.secure_params - self.disclosed_bits_counter) / (self.owner.timeline.now() - self.start_time)
 
         counter = 0
         for j in range(min(len(self.valid_keys), len(self.another.valid_keys))):
diff --git a/src/resource_management/__init__.py b/sequence/resource_management/__init__.py
similarity index 100%
rename from src/resource_management/__init__.py
rename to sequence/resource_management/__init__.py
diff --git a/src/resource_management/memory_manager.py b/sequence/resource_management/memory_manager.py
similarity index 91%
rename from src/resource_management/memory_manager.py
rename to sequence/resource_management/memory_manager.py
index 4049230a..0aeadae9 100644
--- a/src/resource_management/memory_manager.py
+++ b/sequence/resource_management/memory_manager.py
@@ -14,9 +14,10 @@
 if TYPE_CHECKING:
     from .resource_manager import ResourceManager
     from ..components.memory import Memory, MemoryArray
+from ..utils import log
 
 
-class MemoryManager():
+class MemoryManager:
     """Class to manage a node's memories.
 
     The memory manager tracks the entanglement state of a node's memories, along with other information (such as fidelity).
@@ -53,6 +54,7 @@ def update(self, memory: "Memory", state: str) -> None:
             memory (Memory): memory to update.
             state (str): new state for memory.
         """
+        log.logger.debug(f'{memory.name} update to {state}')
 
         info = self.get_info_by_memory(memory)
         if state == "RAW":
@@ -76,8 +78,11 @@ def get_info_by_memory(self, memory: "Memory") -> "MemoryInfo":
         index = self.memory_array.memories.index(memory)
         return self.memory_map[index]
 
+    def get_memory_by_name(self, memory_name: str) -> "Memory":
+        return self.memory_array.get_memory_by_name(memory_name)
 
-class MemoryInfo():
+
+class MemoryInfo:
     """Class to track memory information parameters for memory manager.
 
     The memory info class chiefly tracks a memory's entanglement state, in one of 3 allowed states:
@@ -99,6 +104,10 @@ class MemoryInfo():
         entangle_time (int): time at which most recent entanglement is achieved.
     """
 
+    RAW = "RAW"
+    OCCUPIED = "OCCUPIED"
+    ENTANGLED = "ENTANGLED"
+
     def __init__(self, memory: "Memory", index: int, state="RAW"):
         """Constructor for memory info class.
 
@@ -120,7 +129,7 @@ def __init__(self, memory: "Memory", index: int, state="RAW"):
     def to_raw(self) -> None:
         """Method to set memory to raw (unentangled) state."""
 
-        self.state = "RAW"
+        self.state = self.RAW
         self.memory.reset()
         self.remote_node = None
         self.remote_memo = None
@@ -130,13 +139,13 @@ def to_raw(self) -> None:
     def to_occupied(self) -> None:
         """Method to set memory to occupied state."""
 
-        assert self.state != "OCCUPIED"
-        self.state = "OCCUPIED"
+        assert self.state != self.OCCUPIED
+        self.state = self.OCCUPIED
 
     def to_entangled(self) -> None:
         """Method to set memory to entangled state."""
 
-        self.state = "ENTANGLED"
+        self.state = self.ENTANGLED
         self.remote_node = self.memory.entangled_memory["node_id"]
         self.remote_memo = self.memory.entangled_memory["memo_id"]
         self.fidelity = self.memory.fidelity
diff --git a/src/resource_management/resource_manager.py b/sequence/resource_management/resource_manager.py
similarity index 74%
rename from src/resource_management/resource_manager.py
rename to sequence/resource_management/resource_manager.py
index b22382b5..01d25827 100644
--- a/src/resource_management/resource_manager.py
+++ b/sequence/resource_management/resource_manager.py
@@ -17,7 +17,10 @@
 from ..message import Message
 from ..utils import log
 from .rule_manager import RuleManager
-from .memory_manager import MemoryManager
+from .memory_manager import MemoryManager, MemoryInfo
+
+
+RequestConditionFunc = Callable[[List["EntanglementProtocol"]], "EntanglementProtocol"]
 
 
 class ResourceManagerMsgType(Enum):
@@ -41,40 +44,47 @@ class ResourceManagerMessage(Message):
 
     Attributes:
         ini_protocol_name (str): name of protocol that creates the original REQUEST message.
+        ini_node_name (str): name of the node that creates the original REQUEST message.
+        ini_memories_name (str): name of the memories.
+        string (str): for __str__() purpose.
         request_fun (func): a function using ResourceManager to search eligible protocols on remote node (if `msg_type` == REQUEST).
         is_approved (bool): acceptance/failure of condition function (if `msg_type` == RESPONSE).
         paired_protocol (str): protocol that is paired with ini_protocol (if `msg-type` == RESPONSE).
     """
 
     def __init__(self, msg_type: ResourceManagerMsgType, **kwargs):
-        Message.__init__(self, msg_type, "resource_manager")
+        super().__init__(msg_type, "resource_manager")
         self.ini_protocol_name = kwargs["protocol"]
         self.ini_node_name = kwargs["node"]
         self.ini_memories_name = kwargs["memories"]
+        self.string = "type={}, ini_protocol_name={}, ini_node_name={}, ini_memories_name={}".format(
+                       msg_type.name, self.ini_protocol_name, self.ini_node_name, self.ini_memories_name)
 
         if msg_type is ResourceManagerMsgType.REQUEST:
             self.req_condition_func = kwargs["req_condition_func"]
             self.req_args = kwargs["req_args"]
+            self.string += ", req_condition_func={}, req_args={}".format(self.req_condition_func, self.req_args)
         elif msg_type is ResourceManagerMsgType.RESPONSE:
             self.is_approved = kwargs["is_approved"]
             self.paired_protocol = kwargs["paired_protocol"]
             self.paired_node = kwargs["paired_node"]
             self.paired_memories = kwargs["paired_memories"]
+            self.string += ", is_approved={}, paired_protocol={}, paired_node={}, paired_memories={}".format(
+                            self.is_approved, self.paired_protocol, self.paired_node, self.paired_memories)
         elif msg_type is ResourceManagerMsgType.RELEASE_PROTOCOL:
             self.protocol = kwargs["protocol"]
+            self.string += ", release_protocol={}".format(self.protocol)
         elif msg_type is ResourceManagerMsgType.RELEASE_MEMORY:
             self.memory = kwargs["memory_id"]
+            self.string += ", release_memory={}".format(self.memory)
         else:
-            raise Exception(
-                "ResourceManagerMessage gets unknown type of message: %s" % str(
-                    msg_type))
-
+            raise Exception("ResourceManagerMessage gets unknown type of message: {}".format(str(msg_type)))
 
-RequestConditionFunc = Callable[[List["EntanglementProtocol"]],
-                                "EntanglementProtocol"]
+    def __str__(self) -> str:
+        return self.string
 
 
-class ResourceManager():
+class ResourceManager:
     """Class to define the resource manager.
 
     The resource manager uses a memory manager to track memory states for the entanglement protocols.
@@ -96,7 +106,7 @@ def __init__(self, owner: "QuantumRouter", memory_array_name: str):
             owner (QuantumRouter): node to attach to.
         """
 
-        self.name = "resource_manager"
+        self.name = f"{owner.name}.resource_manager"
         self.owner = owner
         self.memory_manager = MemoryManager(owner.components[memory_array_name])
         self.memory_manager.set_resource_manager(self)
@@ -112,7 +122,7 @@ def load(self, rule: "Rule") -> bool:
         """Method to load rules for entanglement management.
 
         Attempts to add rules to the rule manager.
-        Will automatically execute rule action if conditions met.
+        Will automatically execute rule action if conditions met on a memory.
 
         Args:
             rule (Rule): rule to load.
@@ -121,11 +131,11 @@ def load(self, rule: "Rule") -> bool:
             bool: if rule was loaded successfully.
         """
 
-        log.logger.info('load rule {}'.format(rule))
+        log.logger.info('{} load rule={}'.format(self.owner.name, rule))
         self.rule_manager.load(rule)
 
-        for memory_info in self.memory_manager:
-            memories_info = rule.is_valid(memory_info)
+        for memory_info in self.memory_manager:  # iterate through each memory, and check if the rule is valid on each memory
+            memories_info = rule.is_valid(memory_info)  # is valid means condition is satisfied
             if len(memories_info) > 0:
                 rule.do(memories_info)
                 for info in memories_info:
@@ -136,14 +146,14 @@ def load(self, rule: "Rule") -> bool:
     def expire(self, rule: "Rule") -> None:
         """Method to remove expired rule.
 
-        Will update rule in rule manager.
-        Will also update and modify protocols connected to the rule (if they have already been created).
+        Will update (remove) rule in rule manager.
+        Will also update (remove) protocols connected to the rule (if they have already been created, and not finished yet).
 
         Args:
             rule (Rule): rule to remove.
         """
 
-        log.logger.info('expired rule {}'.format(rule))
+        log.logger.info('{} expire rule {}'.format(self.owner.name, rule))
         created_protocols = self.rule_manager.expire(rule)
         while created_protocols:
             protocol = created_protocols.pop()
@@ -157,13 +167,14 @@ def expire(self, rule: "Rule") -> None:
                 raise Exception("Unknown place of protocol")
 
             for memory in protocol.memories:
-                self.update(protocol, memory, "RAW")
+                self.update(protocol, memory, MemoryInfo.RAW)
 
     def update(self, protocol: "EntanglementProtocol", memory: "Memory", state: str) -> None:
         """Method to update state of memory after completion of entanglement management protocol.
 
         Args:
             protocol (EntanglementProtocol): concerned protocol.
+                If not None, then remove all references.
             memory (Memory): memory to update.
             state (str): new state for the memory.
 
@@ -187,7 +198,7 @@ def update(self, protocol: "EntanglementProtocol", memory: "Memory", state: str)
         if protocol in self.pending_protocols:
             self.pending_protocols.remove(protocol)
 
-        # check if any rules have been met
+        # iterate all the ruls and check if there is a valid rule
         memo_info = self.memory_manager.get_info_by_memory(memory)
         for rule in self.rule_manager:
             memories_info = rule.is_valid(memo_info)
@@ -197,12 +208,12 @@ def update(self, protocol: "EntanglementProtocol", memory: "Memory", state: str)
                     info.to_occupied()
                 return
 
-        self.owner.get_idle_memory(memo_info)
+        self.owner.get_idle_memory(memo_info)  # no new rules apply to this memory, thus "idle"
 
     def get_memory_manager(self):
         return self.memory_manager
 
-    def send_request(self, protocol: "EntanglementProtocol", req_dst: str,
+    def send_request(self, protocol: "EntanglementProtocol", req_dst: Optional[str],
                      req_condition_func: RequestConditionFunc, req_args: Arguments):
         """Method to send protocol request to another node.
 
@@ -217,25 +228,20 @@ def send_request(self, protocol: "EntanglementProtocol", req_dst: str,
             req_args (Dict[str, Any]): arguments for req_cond_func.
         """
 
-        protocol.own = self.owner
+        protocol.owner = self.owner
         if req_dst is None:
             self.waiting_protocols.append(protocol)
             return
         if protocol not in self.pending_protocols:
             self.pending_protocols.append(protocol)
         memo_names = [memo.name for memo in protocol.memories]
-        msg = ResourceManagerMessage(ResourceManagerMsgType.REQUEST,
-                                     protocol=protocol.name,
-                                     node=self.owner.name,
-                                     memories=memo_names,
-                                     req_condition_func=req_condition_func,
-                                     req_args=req_args)
+        msg = ResourceManagerMessage(ResourceManagerMsgType.REQUEST, protocol=protocol.name, node=self.owner.name,
+                                     memories=memo_names, req_condition_func=req_condition_func, req_args=req_args)
         self.owner.send_message(req_dst, msg)
-        log.logger.info(
-            "{} network manager send {} message to {}".format(self.owner.name, msg.msg_type.name, req_dst))
+        log.logger.debug("{} send {} message to {}".format(self.owner.name, msg.msg_type.name, req_dst))
 
     def received_message(self, src: str, msg: "ResourceManagerMessage") -> None:
-        """Method to receive resoruce manager messages.
+        """Method to receive resource manager messages.
 
         Messages come in 4 types, as detailed in the `ResourceManagerMessage` class.
 
@@ -244,39 +250,26 @@ def received_message(self, src: str, msg: "ResourceManagerMessage") -> None:
             msg (ResourceManagerMessage): message received.
         """
 
-        log.logger.info("{} receive {} message from {}".format(self.name,
-                                                               msg.msg_type.name,
-                                                               src))
+        log.logger.debug("{} resource manager receive message from {}: {}".format(self.owner.name, src, msg))
         if msg.msg_type is ResourceManagerMsgType.REQUEST:
+            # select the wait-for-request protocol to respond to the message
             protocol = msg.req_condition_func(self.waiting_protocols, msg.req_args)
             if protocol is not None:
-                protocol.set_others(msg.ini_protocol_name, msg.ini_node_name,
-                                    msg.ini_memories_name)
+                protocol.set_others(msg.ini_protocol_name, msg.ini_node_name, msg.ini_memories_name)
                 memo_names = [memo.name for memo in protocol.memories]
-                new_msg = ResourceManagerMessage(
-                    ResourceManagerMsgType.RESPONSE,
-                    protocol=msg.ini_protocol_name,
-                    node=msg.ini_node_name,
-                    memories=msg.ini_memories_name,
-                    is_approved=True,
-                    paired_protocol=protocol.name,
-                    paired_node=self.owner.name,
-                    paired_memories=memo_names)
+                new_msg = ResourceManagerMessage(ResourceManagerMsgType.RESPONSE, protocol=msg.ini_protocol_name,
+                            node=msg.ini_node_name, memories=msg.ini_memories_name, is_approved=True,
+                            paired_protocol=protocol.name, paired_node=self.owner.name, paired_memories=memo_names)
                 self.owner.send_message(src, new_msg)
                 self.waiting_protocols.remove(protocol)
                 self.owner.protocols.append(protocol)
                 protocol.start()
-                return
-
-            new_msg = ResourceManagerMessage(ResourceManagerMsgType.RESPONSE,
-                                             protocol=msg.ini_protocol_name,
-                                             node=msg.ini_node_name,
-                                             memories=msg.ini_memories_name,
-                                             is_approved=False,
-                                             paired_protocol=None,
-                                             paired_node=None,
-                                             paired_memories=None)
-            self.owner.send_message(src, new_msg)
+            else:
+                # none of the self.waiting_protocol satisfy the req_condition_func --> is_approved=False
+                new_msg = ResourceManagerMessage(ResourceManagerMsgType.RESPONSE, protocol=msg.ini_protocol_name,
+                                                 node=msg.ini_node_name, memories=msg.ini_memories_name, is_approved=False,
+                                                 paired_protocol=None, paired_node=None, paired_memories=None)
+                self.owner.send_message(src, new_msg)
 
         elif msg.msg_type is ResourceManagerMsgType.RESPONSE:
             protocol_name = msg.ini_protocol_name
@@ -286,17 +279,17 @@ def received_message(self, src: str, msg: "ResourceManagerMessage") -> None:
                 if p.name == protocol_name:
                     protocol = p
                     break
-            else:
+            else:  # no matched pending protocols
                 if msg.is_approved:
                     self.release_remote_protocol(src, msg.paired_protocol)
                 return
 
             if msg.is_approved:
-                protocol.set_others(msg.paired_protocol, msg.paired_node, msg.paired_memories)
+                protocol.set_others(msg.paired_protocol, msg.paired_node, msg.paired_memories)  # pairing (cost one round-trip-time)
                 if protocol.is_ready():
                     self.pending_protocols.remove(protocol)
                     self.owner.protocols.append(protocol)
-                    protocol.own = self.owner
+                    protocol.owner = self.owner
                     protocol.start()
             else:
                 protocol.rule.protocols.remove(protocol)
@@ -305,9 +298,9 @@ def received_message(self, src: str, msg: "ResourceManagerMessage") -> None:
                     memory.attach(memory.memory_array)
                     info = self.memory_manager.get_info_by_memory(memory)
                     if info.remote_node is None:
-                        self.update(None, memory, "RAW")
+                        self.update(None, memory, MemoryInfo.RAW)
                     else:
-                        self.update(None, memory, "ENTANGLED")
+                        self.update(None, memory, MemoryInfo.ENTANGLED)
                 self.pending_protocols.remove(protocol)
 
         elif msg.msg_type is ResourceManagerMsgType.RELEASE_PROTOCOL:
@@ -340,8 +333,7 @@ def release_remote_protocol(self, dst: str, protocol: str) -> None:
             protocol (str): name of protocol to release on node.
         """
 
-        msg = ResourceManagerMessage(ResourceManagerMsgType.RELEASE_PROTOCOL,
-                                     protocol=protocol, node='', memories=[])
+        msg = ResourceManagerMessage(ResourceManagerMsgType.RELEASE_PROTOCOL, protocol=protocol, node='', memories=[])
         self.owner.send_message(dst, msg)
 
     def release_remote_memory(self, dst: str, memory_id: str) -> None:
@@ -357,9 +349,9 @@ def release_remote_memory(self, dst: str, memory_id: str) -> None:
             memory_id (str): name of memory to release.
         """
 
-        msg = ResourceManagerMessage(ResourceManagerMsgType.RELEASE_MEMORY,
-                                     protocol="",
-                                     node="",
-                                     memories=[],
-                                     memory_id=memory_id)
+        msg = ResourceManagerMessage(ResourceManagerMsgType.RELEASE_MEMORY, protocol="", 
+                                     node="", memories=[], memory_id=memory_id)
         self.owner.send_message(dst, msg)
+
+    def __str__(self) -> str:
+        return self.name
diff --git a/src/resource_management/rule_manager.py b/sequence/resource_management/rule_manager.py
similarity index 79%
rename from src/resource_management/rule_manager.py
rename to sequence/resource_management/rule_manager.py
index 1de195e8..1c91ffc3 100644
--- a/src/resource_management/rule_manager.py
+++ b/sequence/resource_management/rule_manager.py
@@ -12,13 +12,10 @@
     from .resource_manager import ResourceManager
     from ..network_management.reservation import Reservation
 
+ActionFunc = Callable[[List["MemoryInfo"], Dict[str, Any]], 
+                      Tuple["EntanglementProtocol", List["str"], List[Callable[["EntanglementProtocol"], bool]]]]
 
-ActionFunc = Callable[[List["MemoryInfo"], Dict[str, Any]],
-                      Tuple["EntanglementProtocol", List["str"],
-                            List[Callable[["EntanglementProtocol"], bool]]]]
-
-ConditionFunc = Callable[["MemoryInfo", "MemoryManager", Dict[str, Any]],
-                         List["MemoryInfo"]],
+ConditionFunc = Callable[["MemoryInfo", "MemoryManager", Dict[str, Any]], List["MemoryInfo"]]
 
 Arguments = Dict[str, Any]
 
@@ -81,24 +78,33 @@ def expire(self, rule: "Rule") -> List["EntanglementProtocol"]:
 
         Returns:
             List[EntanglementProtocol]: list of protocols created by rule (if any).
+                Note that when a protocol finishes, it will be removed from rule.protocols.
         """
-
-        self.rules.remove(rule)
+        if rule in self.rules:
+            self.rules.remove(rule)
+        else:
+            log.logger.info(f'{self.resource_manager.owner} rule not exist: {rule}')
         return rule.protocols
+        
 
     def get_memory_manager(self):
         return self.resource_manager.get_memory_manager()
 
     def send_request(self, protocol, req_dst, req_condition_func, req_args):
-        log.logger.info('Rule manager send request for protocol {} to {}'.format(protocol.name, req_dst))
-        return self.resource_manager.send_request(protocol, req_dst,
-                                                  req_condition_func, req_args)
+        log.logger.info('{} Rule Manager send request for protocol {} to {}'.format(self.resource_manager.owner, protocol.name, req_dst))
+        return self.resource_manager.send_request(protocol, req_dst, req_condition_func, req_args)
 
     def __len__(self):
         return len(self.rules)
 
     def __getitem__(self, item):
         return self.rules[item]
+    
+    def __str__(self) -> str:
+        if self.resource_manager:
+            return f'{self.resource_manager.owner.name} Rule Manager'
+        else:
+            return 'Rule Manager'
 
 
 class Rule:
@@ -129,6 +135,13 @@ def __init__(self, priority: int, action: ActionFunc, condition: ConditionFunc,
         self.rule_manager = None
         self.reservation = None
 
+    def __str__(self):
+        action_name_list = str(self.action).split(' ')
+        action_name = action_name_list[1] if len(action_name_list) >= 2 else action_name_list[0]  # in case action_name = ['None']
+        condition_name_list = str(self.condition).split(' ')
+        condition_name = condition_name_list[1] if len(condition_name_list) >= 2 else condition_name_list[0]
+        return "|action={}, args={}; condition={}; args={}|".format(action_name, self.action_args, condition_name, self.condition_args)
+
     def set_rule_manager(self, rule_manager: "RuleManager") -> None:
         """Method to assign rule to a rule manager.
 
@@ -145,11 +158,10 @@ def do(self, memories_info: List["MemoryInfo"]) -> None:
             memories_info (List[MemoryInfo]): list of memory infos for memories meeting requirements.
         """
 
-        protocol, req_dsts, req_condition_funcs, req_args = self.action(
-            memories_info, self.action_args)
-        log.logger.info('Rule generates protocol {}'.format(protocol.name))
+        protocol, req_dsts, req_condition_funcs, req_args = self.action(memories_info, self.action_args)
+        log.logger.info('{} rule generates protocol {}'.format(self.rule_manager, protocol.name))
 
-        protocol.rule = self
+        protocol.rule = self  # the protocol is connected to the reservation via the rule
         self.protocols.append(protocol)
         for info in memories_info:
             info.memory.detach(info.memory.memory_array)
diff --git a/src/topology/__init__.py b/sequence/topology/__init__.py
similarity index 100%
rename from src/topology/__init__.py
rename to sequence/topology/__init__.py
diff --git a/src/topology/node.py b/sequence/topology/node.py
similarity index 88%
rename from src/topology/node.py
rename to sequence/topology/node.py
index d0ebef8e..e28c2762 100644
--- a/src/topology/node.py
+++ b/sequence/topology/node.py
@@ -18,7 +18,7 @@
     from ..components.optical_channel import QuantumChannel, ClassicalChannel
     from ..components.memory import Memory
     from ..components.photon import Photon
-    from ..app.random_request import RandomRequestApp
+    from ..app.request_app import RequestApp
 
 from ..kernel.entity import Entity
 from ..components.memory import MemoryArray
@@ -29,7 +29,7 @@
 from ..qkd.cascade import Cascade
 from ..entanglement_management.generation import EntanglementGenerationB
 from ..resource_management.resource_manager import ResourceManager
-from ..network_management.network_manager import NewNetworkManager
+from ..network_management.network_manager import NewNetworkManager, NetworkManager
 from ..utils.encoding import *
 from ..utils import log
 
@@ -56,13 +56,10 @@ def __init__(self, name: str, timeline: "Timeline", seed=None, component_templat
         name (str): name of node instance.
         timeline (Timeline): timeline for simulation.
         seed (int): seed for random number generator, default None
-        component_templates (Dict[str:Dict]): args for constructing components.
-            If not None, should map a component type (specified as a string) to constructor args.
-            Default is None.
         """
 
         log.logger.info("Create Node {}".format(name))
-        Entity.__init__(self, name, timeline)
+        super().__init__(name, timeline)
         self.owner = self
         self.cchannels = {}  # mapping of destination node names to classical channels
         self.qchannels = {}  # mapping of destination node names to quantum channels
@@ -96,7 +93,7 @@ def set_first_component(self, name: str):
     def assign_cchannel(self, cchannel: "ClassicalChannel", another: str) -> None:
         """Method to assign a classical channel to the node.
 
-        This method is usually called by the `ClassicalChannel.add_ends` method and not called individually.
+        This method is usually called by the `ClassicalChannel.set_ends` method and not called individually.
 
         Args:
             cchannel (ClassicalChannel): channel to add.
@@ -108,7 +105,7 @@ def assign_cchannel(self, cchannel: "ClassicalChannel", another: str) -> None:
     def assign_qchannel(self, qchannel: "QuantumChannel", another: str) -> None:
         """Method to assign a quantum channel to the node.
 
-        This method is usually called by the `QuantumChannel.add_ends` method and not called individually.
+        This method is usually called by the `QuantumChannel.set_ends` method and not called individually.
 
         Args:
             qchannel (QuantumChannel): channel to add.
@@ -140,8 +137,7 @@ def receive_message(self, src: str, msg: "Message") -> None:
             src (str): name of node sending the message.
             msg (Message): message transmitted from node.
         """
-        log.logger.info(
-            "{} receive message {} from {}".format(self.name, msg, src))
+        log.logger.info("{} receive message {} from {}".format(self.name, msg, src))
         # signal to protocol that we've received a message
         if msg.receiver is not None:
             for protocol in self.protocols:
@@ -272,28 +268,36 @@ def __init__(self, name, tl, memo_size=50, seed=None, component_templates=None):
             component_templates = {}
 
         # create memory array object with optional args
-        memo_arr_name = name + ".MemoryArray"
+        self.memo_arr_name = name + ".MemoryArray"
         memo_arr_args = component_templates.get("MemoryArray", {})
-        memory_array = MemoryArray(memo_arr_name, tl, num_memories=memo_size, **memo_arr_args)
+        memory_array = MemoryArray(self.memo_arr_name, tl, num_memories=memo_size, **memo_arr_args)
         self.add_component(memory_array)
         memory_array.add_receiver(self)
 
-        # add protocols
-        self.resource_manager = ResourceManager(self, memo_arr_name)
-        self.network_manager = NewNetworkManager(self, memo_arr_name)
+        # setup managers
+        self.resource_manager = None
+        self.network_manager = None
+        self.init_managers(self.memo_arr_name)
         self.map_to_middle_node = {}
         self.app = None
 
     def receive_message(self, src: str, msg: "Message") -> None:
+        """Determine what to do when a message is received, based on the msg.receiver.
+
+        Args:
+            src (str): name of node that sent the message.
+            msg (Message): the received message.
+        """
+
         log.logger.info("{} receive message {} from {}".format(self.name, msg, src))
-        if msg.receiver == "resource_manager":
-            self.resource_manager.received_message(src, msg)
-        elif msg.receiver == "network_manager":
+        if msg.receiver == "network_manager":
             self.network_manager.received_message(src, msg)
+        elif msg.receiver == "resource_manager":
+            self.resource_manager.received_message(src, msg)
         else:
-            if msg.receiver is None:
+            if msg.receiver is None:  # the msg sent by EntanglementGenerationB doesn't have a receiver (EGA & EGB not paired)
                 matching = [p for p in self.protocols if type(p) == msg.protocol_type]
-                for p in matching:
+                for p in matching:    # the valid_trigger_time() function resolves multiple matching issue
                     p.received_message(src, msg)
             else:
                 for protocol in self.protocols:
@@ -301,10 +305,29 @@ def receive_message(self, src: str, msg: "Message") -> None:
                         protocol.received_message(src, msg)
                         break
 
+    def init_managers(self, memo_arr_name: str):
+        """Initialize resource manager and network manager.
+
+        Args:
+            memo_arr_name (str): the name of the memory array.
+        """
+        resource_manager = ResourceManager(self, memo_arr_name)
+        network_manager = NewNetworkManager(self, memo_arr_name)
+        self.set_resource_manager(resource_manager)
+        self.set_network_manager(network_manager)
+
+    def set_resource_manager(self, resource_manager: ResourceManager):
+        """Assigns the resource manager."""
+        self.resource_manager = resource_manager
+
+    def set_network_manager(self, network_manager: NetworkManager):
+        """Assigns the network manager."""
+        self.network_manager = network_manager
+
     def init(self):
         """Method to initialize quantum router node.
 
-        Inherit parent function
+        Inherit parent function.
         """
 
         super().init()
@@ -313,8 +336,8 @@ def add_bsm_node(self, bsm_name: str, router_name: str):
         """Method to record connected BSM nodes
 
         Args:
-            bsm_name (str): the BSM node between nodes self and router_name
-            router_name (str): the name of another router connected with the BSM node
+            bsm_name (str): the BSM node between nodes self and router_name.
+            router_name (str): the name of another router connected with the BSM node.
         """
         self.map_to_middle_node[router_name] = bsm_name
 
@@ -334,13 +357,13 @@ def memory_expire(self, memory: "Memory") -> None:
 
         self.resource_manager.memory_expire(memory)
 
-    def set_app(self, app: "RandomRequestApp"):
+    def set_app(self, app: "RequestApp"):
         """Method to add an application to the node."""
 
         self.app = app
 
     def reserve_net_resource(self, responder: str, start_time: int, end_time: int, memory_size: int,
-                             target_fidelity: float) -> None:
+                             target_fidelity: float, entanglement_number: int = 1, identity: int = 0) -> None:
         """Method to request a reservation.
 
         Can be used by local applications.
@@ -351,9 +374,11 @@ def reserve_net_resource(self, responder: str, start_time: int, end_time: int, m
             end_time (int): desired simulation end time of entanglement.
             memory_size (int): number of memories requested.
             target_fidelity (float): desired fidelity of entanglement.
+            entanglement_number (int): the number of entanglement that the request ask for (default 1).
+            identity (int): the ID of the request (default 0).
         """
 
-        self.network_manager.request(responder, start_time, end_time, memory_size, target_fidelity)
+        self.network_manager.request(responder, start_time, end_time, memory_size, target_fidelity, entanglement_number, identity)
 
     def get_idle_memory(self, info: "MemoryInfo") -> None:
         """Method for application to receive available memories."""
@@ -361,14 +386,23 @@ def get_idle_memory(self, info: "MemoryInfo") -> None:
         if self.app:
             self.app.get_memory(info)
 
-    def get_reserve_res(self, reservation: "Reservation", res: bool) -> None:
-        """Method for application to receive reservations results."""
+    def get_reservation_result(self, reservation: "Reservation", result: bool) -> None:
+        """Method for application to receive reservations results
+
+        Args:
+            reservation (Reservation): the reservation created by the reservation protocol at this node (the initiator).
+            result (bool): whether the reservation has been approved by the responder.
+        """
 
         if self.app:
-            self.app.get_reserve_res(reservation, res)
+            self.app.get_reservation_result(reservation, result)
 
     def get_other_reservation(self, reservation: "Reservation"):
-        """Method for application to receive another reservation."""
+        """Method for application to add the approved reservation that is requested by other nodes
+        
+        Args:
+            reservation (Reservation): the reservation created by the other node (this node is the responder)
+        """
 
         if self.app:
             self.app.get_other_reservation(reservation)
@@ -428,8 +462,7 @@ def __init__(self, name: str, timeline: "Timeline", encoding=polarization, stack
 
         qsd_name = name + ".qsdetector"
         if "QSDetector" in component_templates:
-            raise NotImplementedError(
-                "Configurable parameters for QSDetector in constructor not yet supported.")
+            raise NotImplementedError("Configurable parameters for QSDetector in constructor not yet supported.")
         if encoding["name"] == "polarization":
             qsdetector = QSDetectorPolarization(qsd_name, timeline)
         elif encoding["name"] == "time_bin":
diff --git a/src/topology/qkd_topo.py b/sequence/topology/qkd_topo.py
similarity index 100%
rename from src/topology/qkd_topo.py
rename to sequence/topology/qkd_topo.py
diff --git a/src/topology/router_net_topo.py b/sequence/topology/router_net_topo.py
similarity index 84%
rename from src/topology/router_net_topo.py
rename to sequence/topology/router_net_topo.py
index b9716428..6ca41af5 100644
--- a/src/topology/router_net_topo.py
+++ b/sequence/topology/router_net_topo.py
@@ -1,10 +1,11 @@
-from json import load
+import json
+import numpy as np
 from networkx import Graph, dijkstra_path, exception
-from numpy import mean
 
 from .topology import Topology as Topo
 from ..kernel.timeline import Timeline
 from .node import BSMNode, QuantumRouter
+from ..constants import SPEED_OF_LIGHT
 
 
 class RouterNetTopo(Topo):
@@ -40,9 +41,9 @@ def __init__(self, conf_file_name: str):
         self.bsm_to_router_map = {}
         super().__init__(conf_file_name)
 
-    def _load(self, filename):
+    def _load(self, filename: str):
         with open(filename, 'r') as fh:
-            config = load(fh)
+            config = json.load(fh)
 
         self._get_templates(config)
         # quantum connections are only supported by sequential simulation so far
@@ -57,7 +58,7 @@ def _load(self, filename):
         self._add_cconnections(config)
         self._generate_forwarding_table(config)
 
-    def _add_timeline(self, config):
+    def _add_timeline(self, config: dict):
         stop_time = config.get(Topo.STOP_TIME, float('inf'))
         if config.get(self.IS_PARALLEL, False):
             raise Exception("Please install 'psequence' package for parallel simulations.")
@@ -72,7 +73,7 @@ def _map_bsm_routers(self, config):
             else:
                 self.bsm_to_router_map[dst] = [src]
 
-    def _add_nodes(self, config):
+    def _add_nodes(self, config: dict):
         for node in config[Topo.ALL_NODE]:
             seed = node[Topo.SEED]
             node_type = node[Topo.TYPE]
@@ -90,10 +91,7 @@ def _add_nodes(self, config):
                 raise ValueError("Unknown type of node '{}'".format(node_type))
 
             node_obj.set_seed(seed)
-            if node_type in self.nodes:
-                self.nodes[node_type].append(node_obj)
-            else:
-                self.nodes[node_type] = [node_obj]
+            self.nodes[node_type].append(node_obj)
 
     def _add_bsm_node_to_router(self):
         for bsm in self.bsm_to_router_map:
@@ -105,36 +103,34 @@ def _add_bsm_node_to_router(self):
             if r1 is not None:
                 r1.add_bsm_node(bsm, r0_str)
 
-    def _add_qconnections(self, config):
-        for q_connect in config.get(Topo.ALL_QC_CONNECT, []):
+    def _add_qconnections(self, config: dict):
+        """generate bsm_info, qc_info, and cc_info for the q_connections."""
+        for q_connect in config.get(Topo.ALL_Q_CONNECT, []):
             node1 = q_connect[Topo.CONNECT_NODE_1]
             node2 = q_connect[Topo.CONNECT_NODE_2]
             attenuation = q_connect[Topo.ATTENUATION]
             distance = q_connect[Topo.DISTANCE] // 2
             channel_type = q_connect[Topo.TYPE]
-            cc_delay = []
-            for cc in config.get(self.ALL_C_CHANNEL, []):
+            cc_delay = []                                   # generate classical channel delay
+            for cc in config.get(self.ALL_C_CHANNEL, []):   # classical channel
                 if cc[self.SRC] == node1 and cc[self.DST] == node2:
-                    delay = cc.get(self.DELAY, cc.get(self.DISTANCE, 1000) / 2e-4)
+                    delay = cc.get(self.DELAY, cc.get(self.DISTANCE, 1000) / SPEED_OF_LIGHT)
                     cc_delay.append(delay)
                 elif cc[self.SRC] == node2 and cc[self.DST] == node1:
-                    delay = cc.get(self.DELAY, cc.get(self.DISTANCE, 1000) / 2e-4)
+                    delay = cc.get(self.DELAY, cc.get(self.DISTANCE, 1000) / SPEED_OF_LIGHT)
                     cc_delay.append(delay)
 
-            for cc in config.get(self.ALL_CC_CONNECT, []):
-                if (cc[self.CONNECT_NODE_1] == node1
-                    and cc[self.CONNECT_NODE_2] == node2) \
-                        or (cc[self.CONNECT_NODE_1] == node2
-                            and cc[self.CONNECT_NODE_2] == node1):
-                    delay = cc.get(self.DELAY,
-                                   cc.get(self.DISTANCE, 1000) / 2e-4)
+            for cc in config.get(self.ALL_C_CONNECT, []):  # classical connection
+                if (cc[self.CONNECT_NODE_1] == node1 and cc[self.CONNECT_NODE_2] == node2) \
+                        or (cc[self.CONNECT_NODE_1] == node2 and cc[self.CONNECT_NODE_2] == node1):
+                    delay = cc.get(self.DELAY, cc.get(self.DISTANCE, 1000) / SPEED_OF_LIGHT)
                     cc_delay.append(delay)
             if len(cc_delay) == 0:
                 assert 0, q_connect
-            cc_delay = mean(cc_delay) // 2
+            cc_delay = np.mean(cc_delay) // 2
 
-            if channel_type == self.MEET_IN_THE_MID:  # generate intermediate BSM node
-                bsm_name = "BSM.{}.{}.auto".format(node1, node2)
+            if channel_type == self.MEET_IN_THE_MID:
+                bsm_name = "BSM.{}.{}.auto".format(node1, node2)  # the intermediate BSM node
                 bsm_seed = q_connect.get(Topo.SEED, 0)
                 bsm_template_name = q_connect.get(Topo.TEMPLATE, None)
                 bsm_info = {self.NAME: bsm_name,
@@ -144,7 +140,7 @@ def _add_qconnections(self, config):
                 config[self.ALL_NODE].append(bsm_info)
 
                 for src in [node1, node2]:
-                    qc_name = "QC.{}.{}".format(src, bsm_name)
+                    qc_name = "QC.{}.{}".format(src, bsm_name)  # the quantum channel
                     qc_info = {self.NAME: qc_name,
                                self.SRC: src,
                                self.DST: bsm_name,
@@ -154,7 +150,7 @@ def _add_qconnections(self, config):
                         config[self.ALL_Q_CHANNEL] = []
                     config[self.ALL_Q_CHANNEL].append(qc_info)
 
-                    cc_name = "CC.{}.{}".format(src, bsm_name)
+                    cc_name = "CC.{}.{}".format(src, bsm_name)  # the classical channel
                     cc_info = {self.NAME: cc_name,
                                self.SRC: src,
                                self.DST: bsm_name,
@@ -174,7 +170,8 @@ def _add_qconnections(self, config):
             else:
                 raise NotImplementedError("Unknown type of quantum connection")
 
-    def _generate_forwarding_table(self, config):
+    def _generate_forwarding_table(self, config: dict):
+        """For static routing."""
         graph = Graph()
         for node in config[Topo.ALL_NODE]:
             if node[Topo.TYPE] == self.QUANTUM_ROUTER:
@@ -210,7 +207,7 @@ def _generate_forwarding_table(self, config):
                         path = dijkstra_path(graph, dst_name, src.name)[::-1]
                     next_hop = path[1]
                     # routing protocol locates at the bottom of the stack
-                    routing_protocol = src.network_manager.protocol_stack[0]
+                    routing_protocol = src.network_manager.protocol_stack[0]  # guarantee that [0] is the routing protocol?
                     routing_protocol.add_forwarding_rule(dst_name, next_hop)
                 except exception.NetworkXNoPath:
                     pass
diff --git a/src/topology/topology.py b/sequence/topology/topology.py
similarity index 94%
rename from src/topology/topology.py
rename to sequence/topology/topology.py
index 416151a7..004e26cc 100644
--- a/src/topology/topology.py
+++ b/sequence/topology/topology.py
@@ -29,10 +29,10 @@ class Topology(ABC):
         tl (Timeline): the timeline used for simulation
     """
 
-    ALL_CC_CONNECT = "cconnections"
+    ALL_C_CONNECT = "cconnections"    # a connection consist of two opposite direction channels
     ALL_C_CHANNEL = "cchannels"
     ALL_NODE = "nodes"
-    ALL_QC_CONNECT = "qconnections"
+    ALL_Q_CONNECT = "qconnections"
     ALL_Q_CHANNEL = "qchannels"
     ATTENUATION = "attenuation"
     CONNECT_NODE_1 = "node1"
@@ -54,7 +54,7 @@ def __init__(self, conf_file_name: str):
         Args:
             conf_file_name (str): the name of configuration file
         """
-        self.nodes: Dict[str, List[Node]] = defaultdict(lambda: [])
+        self.nodes: Dict[str, List[Node]] = defaultdict(list)
         self.qchannels: List[QuantumChannel] = []
         self.cchannels: List[ClassicalChannel] = []
         self.templates: Dict[str, Dict] = {}
@@ -99,7 +99,7 @@ def _add_cchannels(self, config: Dict) -> None:
                 self.cchannels.append(cc_obj)
 
     def _add_cconnections(self, config: Dict) -> None:
-        for c_connect in config.get(self.ALL_CC_CONNECT, []):
+        for c_connect in config.get(self.ALL_C_CONNECT, []):
             node1 = c_connect[self.CONNECT_NODE_1]
             node2 = c_connect[self.CONNECT_NODE_2]
             distance = c_connect.get(self.DISTANCE, 1000)
diff --git a/src/utils/__init__.py b/sequence/utils/__init__.py
similarity index 100%
rename from src/utils/__init__.py
rename to sequence/utils/__init__.py
diff --git a/utils/json_config_generators/generator_utils.py b/sequence/utils/config_generator.py
similarity index 70%
rename from utils/json_config_generators/generator_utils.py
rename to sequence/utils/config_generator.py
index 5565f59c..2a835db3 100644
--- a/utils/json_config_generators/generator_utils.py
+++ b/sequence/utils/config_generator.py
@@ -1,4 +1,6 @@
-"""This module defines common functions for the config generation files."""
+"""This module defines common functions for the config generation files.
+Examples of using this module is in https://github.com/sequence-toolbox/SeQUeNCe/tree/master/utils/json_config_generators
+"""
 
 import pandas as pd
 
@@ -7,20 +9,29 @@
 
 
 def add_default_args(parser):
+    """Adds arguments to argument parser.
+
+    Args:
+        parser (argparse.ArgumentParser)
+
+    Return:
+        argparse.ArgumentParser
+    """
+
     parser.add_argument('memo_size', type=int, help='number of memories per node')
-    parser.add_argument('qc_length', type=float, help='distance between ring nodes (in km)')
+    parser.add_argument('qc_length', type=float, help='distance between nodes (in m)')
     parser.add_argument('qc_atten', type=float, help='quantum channel attenuation (in dB/m)')
     parser.add_argument('cc_delay', type=float, help='classical channel delay (in ms)')
+    parser.add_argument('-d', '--directory', type=str, default='tmp', help='name of output directory')
     parser.add_argument('-o', '--output', type=str, default='out.json', help='name of output config file')
     parser.add_argument('-s', '--stop', type=float, default=float('inf'), help='stop time (in s)')
-    parser.add_argument('-p', '--parallel', nargs=4,
-                        help='optional parallel arguments: server ip, server port, num. processes, lookahead')
+    parser.add_argument('-p', '--parallel', nargs=4, help='optional parallel arguments: server ip, server port, num. processes, lookahead')
     parser.add_argument('-n', '--nodes', type=str, help='path to csv file to provide process for each node')
     return parser
 
 
 # get csv file
-def get_node_csv(node_file):
+def get_node_csv(node_file) -> dict:
     node_procs = {}
 
     # TODO: add length/proc assertions
@@ -31,7 +42,8 @@ def get_node_csv(node_file):
     return node_procs
 
 
-def generate_node_procs(parallel, net_size, naming_func):
+def generate_node_procs(parallel, net_size, naming_func) -> dict:
+    """map a node to a process"""
     if parallel:
         num_procs = int(parallel[2])
     else:
@@ -45,13 +57,18 @@ def generate_node_procs(parallel, net_size, naming_func):
     return node_procs
 
 
-def generate_nodes(node_procs, router_names, memo_size):
-    nodes = [{Topology.NAME: name,
-              Topology.TYPE: RouterNetTopo.QUANTUM_ROUTER,
-              Topology.SEED: i,
-              RouterNetTopo.MEMO_ARRAY_SIZE: memo_size,
-              RouterNetTopo.GROUP: node_procs[name]}
-             for i, name in enumerate(router_names)]
+def generate_nodes(node_procs: dict, router_names: str, memo_size: int, template: str = None) -> list:
+    """generate a list of node configs"""
+    nodes = []
+    for i, name in enumerate(router_names):
+        config = {Topology.NAME: name,
+                  Topology.TYPE: RouterNetTopo.QUANTUM_ROUTER,
+                  Topology.SEED: i,
+                  RouterNetTopo.MEMO_ARRAY_SIZE: memo_size,
+                  RouterNetTopo.GROUP: node_procs[name]}
+        if template:
+            config[RouterNetTopo.TEMPLATE] = template
+        nodes.append(config)
     return nodes
 
 
@@ -88,7 +105,7 @@ def generate_bsm_links(graph, node_procs, parsed_args, bsm_naming_func):
 
 
 # generate classical network connections
-def generate_classical(router_names, cc_delay):
+def generate_classical(router_names: list, cc_delay: int) -> list:
     cchannels = []
     for node1 in router_names:
         for node2 in router_names:
@@ -100,7 +117,7 @@ def generate_classical(router_names, cc_delay):
     return cchannels
 
 
-# add final touches to config dict
+# add final touches to config dict: 1) stop_time, 2)parallel related
 def final_config(output_dict, parsed_args):
     output_dict[Topology.STOP_TIME] = parsed_args.stop * 1e12
     if parsed_args.parallel:
@@ -119,3 +136,12 @@ def final_config(output_dict, parsed_args):
     else:
         output_dict[RouterNetTopo.IS_PARALLEL] = False
 
+
+def router_name_func(i) -> str:
+    """a function that returns the name of the router"""
+    return f"router_{i}"
+
+
+def bsm_name_func(i, j) -> str:
+    """a function that returns the name of the BSM node"""
+    return f"BSM_{i}_{j}"
diff --git a/src/utils/encoding.py b/sequence/utils/encoding.py
similarity index 100%
rename from src/utils/encoding.py
rename to sequence/utils/encoding.py
diff --git a/src/utils/log.py b/sequence/utils/log.py
similarity index 90%
rename from src/utils/log.py
rename to sequence/utils/log.py
index 6f665f7f..30259163 100644
--- a/src/utils/log.py
+++ b/sequence/utils/log.py
@@ -21,7 +21,8 @@ def _init_logger():
 
 
 logger = _init_logger()
-LOG_FORMAT = '%(asctime)-15s\t%(simtime)s\t%(levelname)-8s\t%(module)s:\t%(message)s'
+# LOG_FORMAT = '{asctime}  {simtime:<20,} {levelname:7} {module:20} {message}'
+LOG_FORMAT = '{simtime:<20,} {levelname:7} {module:20} {message}'   # no asctime
 _log_modules = []
 
 
@@ -40,7 +41,7 @@ def set_logger(name: str, timeline, logfile="out.log"):
     logger = logging.getLogger(name)
 
     handler = logging.FileHandler(logfile)
-    fmt = logging.Formatter(LOG_FORMAT)
+    fmt = logging.Formatter(LOG_FORMAT, style='{')
     f = ContextFilter(timeline)
 
     handler.setFormatter(fmt)
@@ -86,5 +87,5 @@ def __init__(self, timeline):
 
     def filter(self, record):
         global _log_modules
-        record.simtime = self.timeline.now()
+        record.simtime = int(self.timeline.now())
         return record.module in _log_modules
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 8400d437..00000000
--- a/setup.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from setuptools import setup
-
-setup(
-    name="sequence",
-    version="0.6.2.2",
-    author="Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Alexander Kiefer, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara",
-    author_email="xwu64@hawk.iit.edu, chungmiranda@anl.gov, akolar@anl.gov, akiefer@iu.edu, eugenewang@yahoo.com, tzh@uchicago.edu, kettimut@mcs.anl.gov, msuchara@anl.gov",
-    description="Simulator of Quantum Network Communication: SEQUENCE-Python is a prototype version of the official SEQUENCE release.",
-    # packages = find_packages('src'),
-    packages=['sequence', 'sequence.app', 'sequence.kernel', 'sequence.components',
-              'sequence.network_management', 'sequence.entanglement_management', 'sequence.qkd',
-              'sequence.resource_management', 'sequence.topology', 'sequence.utils', 'sequence.gui'],
-    package_dir={'sequence': 'src'},
-    package_data={'sequence': ['gui/user_templates.json', 'gui/default_params.json', 'gui/starlight.json']},
-    include_package_data=True,
-    install_requires=[
-        'numpy>=1.22',
-        'matplotlib',
-        'pandas',
-        'qutip==4.7.5',
-        'dash>=1.20.0',
-        'dash-core-components',
-        'dash-html-components',
-        'dash-bootstrap-components',
-        'tqdm>=4.54.0',
-        'networkx',
-        'dash-table',
-        'dash-cytoscape',
-        'plotly',
-    ],
-)
diff --git a/src/kernel/process.py b/src/kernel/process.py
deleted file mode 100644
index 57874683..00000000
--- a/src/kernel/process.py
+++ /dev/null
@@ -1,31 +0,0 @@
-"""Definition of the Process class.
-
-This module defines a process, which is performed when an event is executed.
-"""
-from typing import Any, List
-
-
-class Process:
-    """Class of process.
-
-    The process claims the object of process, the function of object, and the arguments for the function.
-
-    Attributes:
-        owner (Any): the object of process.
-        activation_method (str): the function of object.
-        act_params (List[Any]): the arguments of object.
-    """
-
-    def __init__(self, owner: Any, activation_method: str, act_params: List[Any], act_kwargs={}):
-        self.owner = owner
-        self.activation = activation_method
-        self.act_params = act_params
-        self.act_kwargs = act_kwargs
-
-    def run(self) -> None:
-        """Method to execute process.
-
-        Will run the `activation_method` method of `owner` with `act_params` passed as args.
-        """
-
-        return getattr(self.owner, self.activation)(*self.act_params, **self.act_kwargs)
diff --git a/utils/draw_topo.py b/utils/draw_topo.py
index 05af8d3e..bc672e7d 100644
--- a/utils/draw_topo.py
+++ b/utils/draw_topo.py
@@ -10,6 +10,7 @@
 import argparse
 from graphviz import Graph
 from json import load
+from collections import defaultdict
 
 from sequence.topology.router_net_topo import RouterNetTopo
 from sequence.topology.qkd_topo import QKDTopo
@@ -17,10 +18,14 @@
 
 parser = argparse.ArgumentParser()
 parser.add_argument('config_file', help="path to json file defining network")
-# TODO: add support for middle node not viewing
-# parser.add_argument('-m', dest='draw_middle', action='store_true')
+parser.add_argument('-d', '--directory', type=str, default='tmp', help='directory to save the figure')
+parser.add_argument('-f', '--filename', type=str, default='topology', help='filename of the figure')
+parser.add_argument('-m', '--draw_middle', action='store_true')
 
 args = parser.parse_args()
+directory = args.directory
+filename = args.filename
+draw_middle = args.draw_middle
 
 # determine type of network
 with open(args.config_file, 'r') as fh:
@@ -46,13 +51,28 @@
 
 for node_type in node_types:
     if node_type == RouterNetTopo.BSM_NODE:
-        for node in topo.get_nodes_by_type(node_type):
-            g.node(node.name, label='BSM', shape='rectangle')
+        if draw_middle:
+            for node in topo.get_nodes_by_type(node_type):
+                g.node(node.name, label='BSM', shape='rectangle')
     else:
         for node in topo.get_nodes_by_type(node_type):
             g.node(node.name)
 
-for qchannel in topo.get_qchannels():
-    g.edge(qchannel.sender.name, qchannel.receiver, color='blue', dir='forward')
+if draw_middle:
+    # draw the middle BSM node
+    for qchannel in topo.get_qchannels():
+        g.edge(qchannel.sender.name, qchannel.receiver, color='blue', dir='forward')
+else:
+    # do not draw the middle BSM node
+    bsm_to_node = defaultdict(list)
+    for qchannel in topo.get_qchannels():
+        node = qchannel.sender.name
+        bsm = qchannel.receiver
+        bsm_to_node[bsm].append(node)
+    qconnections = set()
+    for bsm, nodes in bsm_to_node.items():
+        assert len(nodes) == 2, f'{bsm} connects to {len(nodes)} number of nodes (should be 2)'
+        g.edge(nodes[0], nodes[1], color='blue')
+
 
-g.view()
+g.view(directory=directory, filename=filename)
diff --git a/utils/json_config_generators/config_generator_as_memo_num.py b/utils/json_config_generators/config_generator_as_memo_num.py
index 3fda6166..782b034c 100644
--- a/utils/json_config_generators/config_generator_as_memo_num.py
+++ b/utils/json_config_generators/config_generator_as_memo_num.py
@@ -1,3 +1,6 @@
+"""This module generates JSON config files for Internet Autonomous System Network
+"""
+
 from collections import defaultdict
 
 import networkx as nx
@@ -9,8 +12,7 @@
 from simanneal import Annealer
 import random
 
-from generator_utils import add_default_args, generate_bsm_links, generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, generate_bsm_links, generate_classical, final_config, router_name_func, bsm_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
@@ -19,13 +21,6 @@
 random.seed(SEED)
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
-
-def bsm_name_func(i, j):
-    return f"BSM_{i}_{j}"
-
 
 def get_exp_dis_prob(x0, x1, alpha):
     integral_func = lambda x, alpha: - np.e ** (-alpha * x)
diff --git a/utils/json_config_generators/config_generator_as_proc_entanglement.py b/utils/json_config_generators/config_generator_as_proc_entanglement.py
index be411480..3b1498f6 100644
--- a/utils/json_config_generators/config_generator_as_proc_entanglement.py
+++ b/utils/json_config_generators/config_generator_as_proc_entanglement.py
@@ -1,3 +1,6 @@
+"""This module generates JSON config files for Internet Autonomous System Network
+"""
+
 from collections import defaultdict
 
 import networkx as nx
@@ -9,8 +12,7 @@
 from simanneal import Annealer
 import random
 
-from generator_utils import add_default_args, generate_bsm_links, generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, generate_bsm_links, generate_classical, final_config, router_name_func, bsm_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
@@ -19,13 +21,6 @@
 random.seed(SEED)
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
-
-def bsm_name_func(i, j):
-    return f"BSM_{i}_{j}"
-
 
 def get_exp_dis_prob(x0, x1, alpha):
     integral_func = lambda x, alpha: - np.e ** (-alpha * x)
diff --git a/utils/json_config_generators/config_generator_as_proc_qc.py b/utils/json_config_generators/config_generator_as_proc_qc.py
index 4540aa01..a1b37eed 100644
--- a/utils/json_config_generators/config_generator_as_proc_qc.py
+++ b/utils/json_config_generators/config_generator_as_proc_qc.py
@@ -1,3 +1,6 @@
+"""This module generates JSON config files for Internet Autonomous System Network
+"""
+
 from collections import defaultdict
 from time import time
 
@@ -11,8 +14,7 @@
 from simanneal import Annealer
 import random
 
-from generator_utils import add_default_args, generate_bsm_links, generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, generate_bsm_links, generate_classical, final_config, router_name_func, bsm_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
@@ -23,14 +25,6 @@
 tick = time()
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
-
-def bsm_name_func(i, j):
-    return f"BSM_{i}_{j}"
-
-
 def get_exp_dis_prob(x0, x1, alpha):
     integral_func = lambda x, alpha: - np.e ** (-alpha * x)
     return integral_func(x1, alpha) - integral_func(x0, alpha)
diff --git a/utils/json_config_generators/config_generator_caveman.py b/utils/json_config_generators/config_generator_caveman.py
index 45107ef3..cecda2c1 100644
--- a/utils/json_config_generators/config_generator_caveman.py
+++ b/utils/json_config_generators/config_generator_caveman.py
@@ -26,19 +26,12 @@
 import networkx as nx
 import argparse
 import json
+import os
 
-from generator_utils import *
-
+from sequence.utils.config_generator import *
 from sequence.topology.topology import Topology
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
-
-def bsm_name_func(i, j):
-    return f"BSM_{i}_{j}"
-
 
 parser = argparse.ArgumentParser()
 parser.add_argument('l', type=int, help="l (int) – Number of cliques")
@@ -75,5 +68,6 @@ def bsm_name_func(i, j):
 final_config(output_dict, args)
 
 # write final json
-output_file = open(args.output, 'w')
+path = os.path.join(args.directory, args.output)
+output_file = open(path, 'w')
 json.dump(output_dict, output_file, indent=4)
diff --git a/utils/json_config_generators/config_generator_line.py b/utils/json_config_generators/config_generator_line.py
index 38bdac67..ea0ebee4 100644
--- a/utils/json_config_generators/config_generator_line.py
+++ b/utils/json_config_generators/config_generator_line.py
@@ -10,6 +10,7 @@
     cc_delay (float): classical channel delay (in ms).
 
 Optional Args:
+    -d --directory (str): name of the output directory (default tmp)
     -o --output (str): name of the output file (default out.json).
     -s --stop (float): simulation stop time (in s) (default infinity).
     -p --parallel: sets simulation as parallel and requires addition args:
@@ -23,17 +24,13 @@
 
 import argparse
 import json
+import os
 
-from generator_utils import add_default_args, get_node_csv, generate_node_procs, generate_nodes, \
-    generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, get_node_csv, generate_node_procs, generate_nodes, generate_classical, final_config, router_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
 
 parser = argparse.ArgumentParser()
 parser.add_argument('linear_size', type=int, help='number of network nodes')
@@ -97,6 +94,7 @@ def router_name_func(i):
 final_config(output_dict, args)
 
 # write final json
-output_file = open(args.output, 'w')
+path = os.path.join(args.directory, args.output)
+output_file = open(path, 'w')
 json.dump(output_dict, output_file, indent=4)
 
diff --git a/utils/json_config_generators/config_generator_mesh.py b/utils/json_config_generators/config_generator_mesh.py
index c2b51014..e14e2ceb 100644
--- a/utils/json_config_generators/config_generator_mesh.py
+++ b/utils/json_config_generators/config_generator_mesh.py
@@ -10,6 +10,7 @@
     cc_delay (float): classical channel delay (in ms).
 
 Optional Args:
+    -d --directory (str): name of the output directory (default tmp)
     -o --output (str): name of the output file (default out.json).
     -s --stop (float): simulation stop time (in s) (default infinity).
     -p --parallel: sets simulation as parallel and requires addition args:
@@ -21,18 +22,15 @@
     -n --nodes (str): path to csv file providing process information for nodes.
 """
 
+import os
 import argparse
 import json
 
-from generator_utils import add_default_args, get_node_csv, generate_node_procs, generate_nodes, final_config
-
+from sequence.utils.config_generator import add_default_args, get_node_csv, generate_node_procs, generate_nodes, final_config, router_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
 
 # parse args
 parser = argparse.ArgumentParser()
@@ -102,6 +100,7 @@ def router_name_func(i):
 final_config(output_dict, args)
 
 # write final json
-output_file = open(args.output, 'w')
+path = os.path.join(args.directory, args.output)
+output_file = open(path, 'w')
 json.dump(output_dict, output_file, indent=4)
 
diff --git a/utils/json_config_generators/config_generator_random.py b/utils/json_config_generators/config_generator_random.py
new file mode 100644
index 00000000..efa479a9
--- /dev/null
+++ b/utils/json_config_generators/config_generator_random.py
@@ -0,0 +1,175 @@
+"""This module generates JSON config files for a random network (Waxman).
+
+Help information may also be obtained using the `-h` flag.
+
+Args:
+    net_size (int): number of nodes in the random network.
+    memo_size (int): number of memories per node.
+    qc_length (float): distance between nodes (in km).
+    qc_atten (float): quantum channel attenuation (in dB/m).
+    cc_delay (float): classical channel delay (in ms).
+
+Optional Args:
+    -d --directory (str): name of the output directory (default tmp)
+    -o --output (str): name of the output file (default out.json).
+    -s --stop (float): simulation stop time (in s) (default infinity).
+    -p --parallel: sets simulation as parallel and requires addition args:
+        server ip (str): IP address of quantum manager server.
+        server port (int): port quantum manager server is attached to.
+        num. processes (int): number of processes to use for simulation.
+        sync/async (bool): denotes if timelines should be synchronous (true) or not (false).
+        lookahead (int): simulation lookahead time for timelines (in ps).
+    -n --nodes (str): path to csv file providing process information for nodes.
+"""
+
+import argparse
+import json
+import os
+import numpy as np
+import random
+from networkx.generators.geometric import waxman_graph
+from typing import Tuple
+
+from sequence.utils.config_generator import add_default_args, get_node_csv, generate_node_procs, generate_nodes, final_config, router_name_func
+from sequence.topology.topology import Topology
+from sequence.topology.router_net_topo import RouterNetTopo
+
+
+def create_random_waxman(area_length: int, number_nodes: int, edge_density: float) -> Tuple[list, list]:
+    """create a random network using waxman.
+
+    Return:
+        a list of vertex in (x, y), where x, y are real number geographic coordinates
+        a list of edges in (u, v), where u, v are integer vertex index
+    """
+    max_number_edges = (number_nodes - 1) * number_nodes // 2
+    alpa_min, alpha_max = 0, 100
+    while alpha_max - alpa_min > 0.001:
+        alpha = (alpa_min + alpha_max) / 2
+        graphx_list = []
+        for _ in range(10):  # do average of 10
+            graphx_list.append(waxman_graph(number_nodes, domain=(0, 0, area_length, area_length), beta=1, alpha=alpha)) # cannot understand the L parameter
+        avg_length = np.average([len(graphx.edges()) for graphx in graphx_list])
+        
+        if (edge_density - 0.002) * max_number_edges <= avg_length <= (edge_density + 0.002) * max_number_edges:
+            break
+        elif avg_length > (edge_density + 0.002) * max_number_edges:
+            alpha_max = alpha
+        else:
+            alpa_min = alpha
+    # pick the best one of the 10
+    target = edge_density * max_number_edges
+    graphx = None
+    difference = float('inf')
+    for tmp_graphx in graphx_list:
+        diff = abs(len(tmp_graphx.edges()) - target)
+        if diff < difference:
+            graphx = tmp_graphx
+            difference = diff
+
+    V = [(int(graphx.nodes[i]["pos"][0]), int(graphx.nodes[i]["pos"][1])) for i in range(number_nodes)]
+
+    E = list(graphx.edges())
+
+    # lengths = []
+    print(f'number of nodes = {len(V)}')
+    # print(f'average link length = {int(np.average(lengths)):,}')
+    print(f'# of edge = {len(graphx.edges())}, edge density = {len(graphx.edges())/max_number_edges:.4f}')
+    return V, E
+
+
+def main():
+    # parse args
+    parser = argparse.ArgumentParser()
+    parser.add_argument('net_size', type=int, help='number of network nodes')
+    parser = add_default_args(parser)
+    args = parser.parse_args()
+
+    net_size = args.net_size
+    area_length = 1000
+    edge_density = 0.5
+
+    random.seed(0)
+    np.random.seed(0)
+    V, E = create_random_waxman(area_length, net_size, edge_density)
+
+    output_dict = {}
+
+    # get node names, processes
+    if args.nodes:
+        node_procs = get_node_csv(args.nodes)
+    else:
+        node_procs = generate_node_procs(args.parallel, args.net_size, router_name_func)
+    router_names = list(node_procs.keys())
+    nodes = generate_nodes(node_procs, router_names, args.memo_size)
+
+    # 1. generate quantum links and bsm nodes
+    qchannels = []
+    cchannels = []
+    bsm_nodes = []
+    seed = 0
+    for node1, node2 in E:
+        node1_name = router_name_func(node1)
+        node2_name = router_name_func(node2)
+        node1_loc = V[node1]
+        node2_loc = V[node2]
+        distance = np.sqrt((node1_loc[0] - node2_loc[0]) ** 2 + (node1_loc[1] - node2_loc[1]) ** 2)
+
+        # bsm node
+        bsm_name = "BSM_{}_{}".format(node1_name, node2_name)
+        bsm_node = {Topology.NAME: bsm_name,
+                    Topology.TYPE: RouterNetTopo.BSM_NODE,
+                    Topology.SEED: seed,
+                    RouterNetTopo.GROUP: node_procs[node1_name]}
+        bsm_nodes.append(bsm_node)
+
+        # qchannels
+        qchannels.append({Topology.SRC: node1_name,
+                          Topology.DST: bsm_name,
+                          Topology.DISTANCE: distance / 2,
+                          Topology.ATTENUATION: args.qc_atten})
+        qchannels.append({Topology.SRC: node2_name,
+                          Topology.DST: bsm_name,
+                          Topology.DISTANCE: distance / 2,
+                          Topology.ATTENUATION: args.qc_atten})
+        # cchannels
+        cchannels.append({Topology.SRC: node1_name,
+                          Topology.DST: bsm_name,
+                          Topology.DELAY: args.cc_delay * 1e9})
+        cchannels.append({Topology.SRC: node2_name,
+                          Topology.DST: bsm_name,
+                          Topology.DELAY: args.cc_delay * 1e9})
+        cchannels.append({Topology.SRC: bsm_name,
+                          Topology.DST: node1_name,
+                          Topology.DELAY: args.cc_delay * 1e9})
+        cchannels.append({Topology.SRC: bsm_name,
+                          Topology.DST: node2_name,
+                          Topology.DELAY: args.cc_delay * 1e9})
+        seed += 1
+
+    # 2. generate classical links between all node pairs
+    for i, node1_name in enumerate(router_names):
+        for node2_name in router_names[i+1:]:
+            cchannels.append({Topology.SRC: node1_name,
+                              Topology.DST: node2_name,
+                              Topology.DELAY: args.cc_delay * 1e9})
+            cchannels.append({Topology.SRC: node2_name,
+                              Topology.DST: node1_name,
+                              Topology.DELAY: args.cc_delay * 1e9})
+
+    nodes += bsm_nodes
+    output_dict[Topology.ALL_NODE] = nodes
+    output_dict[Topology.ALL_Q_CHANNEL] = qchannels
+    output_dict[Topology.ALL_C_CHANNEL] = cchannels
+
+    # write other config options to output dictionary
+    final_config(output_dict, args)
+
+    # write final json
+    path = os.path.join(args.directory, args.output)
+    output_file = open(path, 'w')
+    json.dump(output_dict, output_file, indent=4)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/utils/json_config_generators/config_generator_ring.py b/utils/json_config_generators/config_generator_ring.py
index a255f999..f7202521 100644
--- a/utils/json_config_generators/config_generator_ring.py
+++ b/utils/json_config_generators/config_generator_ring.py
@@ -10,6 +10,7 @@
     cc_delay (float): classical channel delay (in ms).
 
 Optional Args:
+    -d --directory (str): name of the output directory (default tmp)
     -o --output (str): name of the output file (default out.json).
     -s --stop (float): simulation stop time (in s) (default infinity).
     -p --parallel: sets simulation as parallel and requires addition args:
@@ -23,16 +24,13 @@
 
 import argparse
 import json
+import os
 
-from generator_utils import add_default_args, get_node_csv, generate_node_procs, generate_nodes, \
-    generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, get_node_csv, generate_node_procs, generate_nodes, generate_classical, final_config, router_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
 
-def router_name_func(i):
-    return f"router_{i}"
 
 
 # parse args
@@ -51,14 +49,12 @@ def router_name_func(i):
 router_names = list(node_procs.keys())
 nodes = generate_nodes(node_procs, router_names, args.memo_size)
 
-# generate quantum links and bsm connections
+# generate bsm nodes, quantum links (quantum channels + classical channels)
 qchannels = []
 cchannels = []
 bsm_names = ["BSM_{}_{}".format(i % args.ring_size, (i+1) % args.ring_size)
              for i in range(args.ring_size)]
-bsm_nodes = [{Topology.NAME: bsm_name,
-              Topology.TYPE: RouterNetTopo.BSM_NODE,
-              Topology.SEED: i}
+bsm_nodes = [{Topology.NAME: bsm_name, Topology.TYPE: RouterNetTopo.BSM_NODE, Topology.SEED: i}
              for i, bsm_name in enumerate(bsm_names)]
 if args.parallel:
     for i in range(args.ring_size):
@@ -92,8 +88,7 @@ def router_name_func(i):
 output_dict[Topology.ALL_NODE] = nodes
 output_dict[Topology.ALL_Q_CHANNEL] = qchannels
 
-# generate classical links
-# generate classical links
+# generate classical links (classical channels)
 router_cchannels = generate_classical(router_names, args.cc_delay)
 cchannels += router_cchannels
 output_dict[Topology.ALL_C_CHANNEL] = cchannels
@@ -102,6 +97,7 @@ def router_name_func(i):
 final_config(output_dict, args)
 
 # write final json
-output_file = open(args.output, 'w')
+path = os.path.join(args.directory, args.output)
+output_file = open(path, 'w')
 json.dump(output_dict, output_file, indent=4)
 
diff --git a/utils/json_config_generators/config_generator_star.py b/utils/json_config_generators/config_generator_star.py
index f29f6d67..64d1f918 100644
--- a/utils/json_config_generators/config_generator_star.py
+++ b/utils/json_config_generators/config_generator_star.py
@@ -11,6 +11,7 @@
     cc_delay (float): classical channel delay (in ms).
 
 Optional Args:
+    -d --directory (str): name of the output directory (default tmp)
     -o --output (str): name of the output file (default out.json).
     -s --stop (float): simulation stop time (in s) (default infinity).
     -p --parallel: sets simulation as parallel and requires addition args:
@@ -24,17 +25,13 @@
 
 import argparse
 import json
+import os
 
-from generator_utils import add_default_args, get_node_csv, generate_node_procs, generate_nodes, \
-    generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, get_node_csv, generate_node_procs, generate_nodes, generate_classical, final_config, router_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
 
 # parse args
 parser = argparse.ArgumentParser()
@@ -114,6 +111,7 @@ def router_name_func(i):
 final_config(output_dict, args)
 
 # write final json
-output_file = open(args.output, 'w')
+path = os.path.join(args.directory, args.output)
+output_file = open(path, 'w')
 json.dump(output_dict, output_file, indent=4)
 
diff --git a/utils/json_config_generators/config_generator_tree.py b/utils/json_config_generators/config_generator_tree.py
index 1e7e9b05..0cf1f8fa 100644
--- a/utils/json_config_generators/config_generator_tree.py
+++ b/utils/json_config_generators/config_generator_tree.py
@@ -11,6 +11,7 @@
     cc_delay (float): classical channel delay (in ms).
 
 Optional Args:
+    -d --directory (str): name of the output directory (default tmp)
     -o --output (str): name of the output file (default out.json).
     -s --stop (float): simulation stop time (in s) (default infinity).
     -p --parallel: sets simulation as parallel and requires addition args:
@@ -24,17 +25,13 @@
 
 import argparse
 import json
+import os
 
-from generator_utils import add_default_args, get_node_csv, generate_node_procs, generate_nodes, \
-    generate_classical, final_config
-
+from sequence.utils.config_generator import add_default_args, get_node_csv, generate_node_procs, generate_nodes, generate_classical, final_config, router_name_func
 from sequence.topology.topology import Topology
 from sequence.topology.router_net_topo import RouterNetTopo
 
 
-def router_name_func(i):
-    return f"router_{i}"
-
 
 def add_branches(node_names, nodes, index, bsm_names, bsm_nodes, qchannels, cchannels):
     node1 = node_names[index]
@@ -114,6 +111,7 @@ def add_branches(node_names, nodes, index, bsm_names, bsm_nodes, qchannels, ccha
 final_config(output_dict, args)
 
 # write final json
-output_file = open(args.output, 'w')
+path = os.path.join(args.directory, args.output)
+output_file = open(path, 'w')
 json.dump(output_dict, output_file, indent=4)
 


Test patch:
diff --git a/src/gui/test.txt b/sequence/gui/test.txt
similarity index 100%
rename from src/gui/test.txt
rename to sequence/gui/test.txt
diff --git a/tests/app/test_random_request.py b/tests/app/test_random_request.py
index 4a5e3529..a00bb7ae 100644
--- a/tests/app/test_random_request.py
+++ b/tests/app/test_random_request.py
@@ -59,7 +59,7 @@ def test_RandomRequestApp_start():
     assert abs(counter / (1000 - counter) - 1) < 0.1
 
 
-def test_RandomRequestApp_get_reserve_res():
+def test_RandomRequestApp_get_reservation_result():
     tl = Timeline()
     tl.time = 6
     node = FakeNode("n1", tl)
@@ -72,7 +72,7 @@ def test_RandomRequestApp_get_reserve_res():
             card.add(reservation)
 
     app.request_time = 5
-    app.get_reserve_res(reservation, True)
+    app.get_reservation_result(reservation, True)
     assert app.get_wait_time()[0] == 5
     assert len(tl.events) == 41 and tl.events.data[0].time == 10
 
@@ -84,7 +84,7 @@ def test_RandomRequestApp_get_reserve_res():
     app.memo_size, app.fidelity = 5, 0.9
     reservation = Reservation("n1", "n3", 10, 20, 5, 0.9)
     app.request_time = 5
-    app.get_reserve_res(reservation, False)
+    app.get_reservation_result(reservation, False)
     tl.run()
     assert len(app.get_wait_time()) == 0
     assert len(node.reserve_log) == 1
@@ -153,10 +153,10 @@ def test_RandomRequestApp_get_other_reservation():
 
     app = RandomRequestApp(node, [], 0, 1e13, 2e13, 10, 25, 0.8, 1.0)
     app.get_other_reservation(reservation)
-    assert len(app.memo_to_reserve) == 0
+    assert len(app.memo_to_reservation) == 0
     tl.stop_time = 11
     tl.run()
-    assert len(app.memo_to_reserve) == 10
+    assert len(app.memo_to_reservation) == 10
 
     info = node.resource_manager.memory_manager[0]
     info.memory.entangled_memory = {"node_id": "initiator", "memo_id": "memo"}
@@ -189,8 +189,8 @@ def test_RandomRequestApp_get_other_reservation():
     tl.stop_time = 101
     tl.run()
 
-    print(app.memo_to_reserve)
-    assert len(app.memo_to_reserve) == 0
+    print(app.memo_to_reservation)
+    assert len(app.memo_to_reservation) == 0
     info = node.resource_manager.memory_manager[0]
     info.memory.entangled_memory = {"node_id": "initiator", "memo_id": "memo"}
     info.memory.fidelity = 1
diff --git a/tests/components/test_light_source.py b/tests/components/test_light_source.py
index 81661862..845e404d 100644
--- a/tests/components/test_light_source.py
+++ b/tests/components/test_light_source.py
@@ -10,6 +10,7 @@ class Receiver:
     def __init__(self, timeline):
         self.timeline = timeline
         self.log = []
+        self.name = ""
 
     def get(self, photon):
         self.log.append((self.timeline.now(), photon))
diff --git a/tests/components/test_memory.py b/tests/components/test_memory.py
index ac2a909a..2319f9ed 100644
--- a/tests/components/test_memory.py
+++ b/tests/components/test_memory.py
@@ -28,6 +28,7 @@ def __init__(self, memory):
         self.photon_list = []
         self.photon_arrival_times = []
         self.generator = np.random.default_rng(SEED)
+        self.name = ""
 
     def memory_expire(self, memory):
         self.pop_log.append(memory)
diff --git a/tests/components/test_switch.py b/tests/components/test_switch.py
index 328cbdd2..07fd5f3f 100644
--- a/tests/components/test_switch.py
+++ b/tests/components/test_switch.py
@@ -13,6 +13,7 @@ class Receiver:
         def __init__(self, tl):
             self.timeline = tl
             self.log = []
+            self.name = ""
 
         def get(self, photon):
             self.log.append((self.timeline.now(), photon))
diff --git a/tests/entanglement_management/test_generation.py b/tests/entanglement_management/test_generation.py
index bf2f7117..45edc3b7 100644
--- a/tests/entanglement_management/test_generation.py
+++ b/tests/entanglement_management/test_generation.py
@@ -170,8 +170,7 @@ def test_generation_run():
     for src in [e0, e1, m0]:
         for dst in [e0, e1, m0]:
             if src.name != dst.name:
-                cc = ClassicalChannel("cc_%s_%s" % (src.name, dst.name), tl,
-                                      1e3, delay=1e9)
+                cc = ClassicalChannel("cc_%s_%s" % (src.name, dst.name), tl, 1e3, delay=1e9)
                 cc.set_ends(src, dst.name)
 
     # add hardware
diff --git a/tests/entanglement_management/test_purification.py b/tests/entanglement_management/test_purification.py
index 945d37f4..c5d9fe91 100644
--- a/tests/entanglement_management/test_purification.py
+++ b/tests/entanglement_management/test_purification.py
@@ -7,6 +7,7 @@
 from sequence.kernel.timeline import Timeline
 from sequence.entanglement_management.purification import *
 from sequence.topology.node import Node
+from sequence.constants import SQRT_HALF, PHI_PLUS, PHI_MINUS, PSI_PLUS, PSI_MINUS
 
 np.random.seed(0)
 
@@ -15,13 +16,13 @@
 
 HALF_MICRON = 500
 ONE_MILLISECOND = 1e9
-SQRT_HALF = 0.5 ** 0.5
+# SQRT_HALF = 0.5 ** 0.5
 
-phi_plus = [SQRT_HALF, 0, 0, SQRT_HALF]
-phi_minus = [SQRT_HALF, 0, 0, -SQRT_HALF]
-psi_plus = [0, SQRT_HALF, SQRT_HALF, 0]
-psi_minus = [0, SQRT_HALF, -SQRT_HALF, 0]
-BELL_STATES = [phi_plus, phi_minus, psi_plus, psi_minus]
+# phi_plus = [SQRT_HALF, 0, 0, SQRT_HALF]
+# phi_minus = [SQRT_HALF, 0, 0, -SQRT_HALF]
+# psi_plus = [0, SQRT_HALF, SQRT_HALF, 0]
+# psi_minus = [0, SQRT_HALF, -SQRT_HALF, 0]
+BELL_STATES = [PHI_PLUS, PHI_MINUS, PSI_PLUS, PSI_MINUS]
 
 
 def success_probability(F: float) -> float:
@@ -135,7 +136,7 @@ def test_BBPSSW_phi_plus_phi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_plus, phi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_PLUS, PHI_PLUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -146,7 +147,7 @@ def test_BBPSSW_phi_plus_phi_plus():
         assert id(ket1) == id(ket2)
         assert kept1.qstate_key in ket1.keys and kept2.qstate_key in ket1.keys
         state = correct_order(ket1.state, ket1.keys)
-        assert complex_array_equal(phi_plus, state)
+        assert complex_array_equal(PHI_PLUS, state)
         # assert kept1 and kept2 point to the same Ketstate
         # assert the state is phi+
     assert abs(counter - 50) < 10
@@ -167,7 +168,7 @@ def test_BBPSSW_phi_plus_phi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_plus, phi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_PLUS, PHI_MINUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -178,7 +179,7 @@ def test_BBPSSW_phi_plus_phi_minus():
         state = correct_order(ket1.state, ket1.keys)
         if ep1.meas_res == 0:
             counter += 1
-            assert complex_array_equal(phi_minus, state)
+            assert complex_array_equal(PHI_MINUS, state)
         else:
             assert complex_array_equal([-SQRT_HALF, 0, 0, SQRT_HALF], state)
 
@@ -199,7 +200,7 @@ def test_BBPSSW_phi_minus_phi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_minus, phi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_MINUS, PHI_PLUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -210,7 +211,7 @@ def test_BBPSSW_phi_minus_phi_plus():
         assert kept1.qstate_key in ket1.keys and kept2.qstate_key in ket1.keys
         state = correct_order(ket1.state, ket1.keys)
 
-        assert complex_array_equal(phi_minus, state)
+        assert complex_array_equal(PHI_MINUS, state)
         if ep1.meas_res == 0:
             counter += 1
         else:
@@ -233,7 +234,7 @@ def test_BBPSSW_phi_minus_phi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_minus, phi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_MINUS, PHI_MINUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -246,7 +247,7 @@ def test_BBPSSW_phi_minus_phi_minus():
 
         if ep1.meas_res == 0:
             counter += 1
-            assert complex_array_equal(phi_plus, state)
+            assert complex_array_equal(PHI_PLUS, state)
         else:
             assert complex_array_equal([-SQRT_HALF, 0, 0, -SQRT_HALF], state)
 
@@ -267,7 +268,7 @@ def test_BBPSSW_phi_plus_psi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_plus, psi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_PLUS, PSI_PLUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -296,7 +297,7 @@ def test_BBPSSW_phi_plus_psi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_plus, psi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_PLUS, PSI_MINUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -324,7 +325,7 @@ def test_BBPSSW_phi_minus_psi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_minus, psi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_MINUS, PSI_PLUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -352,7 +353,7 @@ def test_BBPSSW_phi_minus_psi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(phi_minus, psi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PHI_MINUS, PSI_MINUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -381,7 +382,7 @@ def test_BBPSSW_psi_plus_phi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_plus, phi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_PLUS, PHI_PLUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -410,7 +411,7 @@ def test_BBPSSW_psi_plus_phi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_plus, phi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_PLUS, PHI_MINUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -438,7 +439,7 @@ def test_BBPSSW_psi_minus_phi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_minus, phi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_MINUS, PHI_PLUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -467,7 +468,7 @@ def test_BBPSSW_psi_minus_phi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_minus, phi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_MINUS, PHI_MINUS, i)
         assert kept1.entangled_memory == kept2.entangled_memory == {'node_id': None, 'memo_id': None}
         assert ep1.meas_res != ep2.meas_res
 
@@ -496,7 +497,7 @@ def test_BBPSSW_psi_plus_psi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_plus, psi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_PLUS, PSI_PLUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -507,7 +508,7 @@ def test_BBPSSW_psi_plus_psi_plus():
         assert kept1.qstate_key in ket1.keys and kept2.qstate_key in ket1.keys
 
         state = correct_order(ket1.state, ket1.keys)
-        assert complex_array_equal(psi_plus, state)
+        assert complex_array_equal(PSI_PLUS, state)
         if ep1.meas_res == 0:
             counter += 1
         else:
@@ -530,7 +531,7 @@ def test_BBPSSW_psi_plus_psi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_plus, psi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_PLUS, PSI_MINUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -544,7 +545,7 @@ def test_BBPSSW_psi_plus_psi_minus():
 
         if ep1.meas_res == 0:
             counter += 1
-            assert complex_array_equal(psi_minus, state)
+            assert complex_array_equal(PSI_MINUS, state)
         else:
             assert complex_array_equal([0, -SQRT_HALF, SQRT_HALF, 0], state)
 
@@ -565,7 +566,7 @@ def test_BBPSSW_psi_minus_psi_plus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_minus, psi_plus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_MINUS, PSI_PLUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -576,7 +577,7 @@ def test_BBPSSW_psi_minus_psi_plus():
         assert kept1.qstate_key in ket1.keys and kept2.qstate_key in ket1.keys
 
         state = correct_order(ket1.state, ket1.keys)
-        assert complex_array_equal(psi_minus, state)
+        assert complex_array_equal(PSI_MINUS, state)
         if ep1.meas_res == 0:
             counter += 1
         else:
@@ -600,7 +601,7 @@ def test_BBPSSW_psi_minus_psi_minus():
     """
     counter = 0
     for i in range(100):
-        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(psi_minus, psi_minus, i)
+        tl, kept1, kept2, meas1, meas2, ep1, ep2 = create_scenario(PSI_MINUS, PSI_MINUS, i)
         assert kept1.entangled_memory == {'node_id': 'a2', 'memo_id': 'kept2'}
         assert kept2.entangled_memory == {'node_id': 'a1', 'memo_id': 'kept1'}
         assert ep1.meas_res == ep2.meas_res
@@ -613,7 +614,7 @@ def test_BBPSSW_psi_minus_psi_minus():
 
         if ep1.meas_res == 0:
             counter += 1
-            assert complex_array_equal(psi_plus, state)
+            assert complex_array_equal(PSI_PLUS, state)
         else:
             assert complex_array_equal([0, -SQRT_HALF, -SQRT_HALF, 0], state)
     assert abs(counter - 50) < 10
diff --git a/tests/entanglement_management/test_swapping.py b/tests/entanglement_management/test_swapping.py
index be91d760..488ee8a9 100644
--- a/tests/entanglement_management/test_swapping.py
+++ b/tests/entanglement_management/test_swapping.py
@@ -541,13 +541,6 @@ def test_EntanglementSwapping():
 
         es2.start()
 
-        assert memo2.fidelity == memo3.fidelity == 0
-        assert memo1.entangled_memory["node_id"] == memo4.entangled_memory["node_id"] == "a2"
-        assert memo2.entangled_memory["node_id"] == memo3.entangled_memory["node_id"] == None
-        assert memo2.entangled_memory["memo_id"] == memo3.entangled_memory["memo_id"] == None
-        assert a2.resource_manager.log[-2] == (memo2, "RAW")
-        assert a2.resource_manager.log[-1] == (memo3, "RAW")
-
         tl.run()
 
         if es2.is_success:
diff --git a/tests/kernel/test_quantum_manager.py b/tests/kernel/test_quantum_manager.py
index 75c11c5c..67e95194 100644
--- a/tests/kernel/test_quantum_manager.py
+++ b/tests/kernel/test_quantum_manager.py
@@ -343,7 +343,7 @@ def test_qmanager__measure_density():
     assert abs((len(meas_0) / NUM_TESTS) - 0.5) < 0.1
 
 
-def test_qmanager__prepare_state_fock():
+def test_qmanager_prepare_state_fock():
     qm = QuantumManagerDensityFock(truncation=2)
     state_0 = [1, 0, 0]
     state_1 = [0, 1, 0]
diff --git a/tests/qkd/test_BB84.py b/tests/qkd/test_BB84.py
index af9273e8..6a4b5401 100644
--- a/tests/qkd/test_BB84.py
+++ b/tests/qkd/test_BB84.py
@@ -12,8 +12,8 @@
 
 # dummy parent class to test BB84 functionality
 class Parent(StackProtocol):
-    def __init__(self, own: "Node", keysize: int, role: str):
-        super().__init__(own, "")
+    def __init__(self, owner: "Node", keysize: int, role: str):
+        super().__init__(owner, "")
         self.upper_protocols = []
         self.lower_protocols = []
         self.keysize = keysize
@@ -107,4 +107,3 @@ def test_BB84_time_bin():
     tl.init()
     tl.run()
     assert pa.counter == pb.counter == 10
-
diff --git a/tests/qkd/test_cascade.py b/tests/qkd/test_cascade.py
index 10b82a1c..76377587 100644
--- a/tests/qkd/test_cascade.py
+++ b/tests/qkd/test_cascade.py
@@ -10,8 +10,8 @@
 
 # dummy parent class to test cascade functionality
 class Parent(StackProtocol):
-    def __init__(self, own: "Node", keysize: int, keynum: int):
-        super().__init__(own, "")
+    def __init__(self, owner: "Node", keysize: int, keynum: int):
+        super().__init__(owner, "")
         self.upper_protocols = []
         self.lower_protocols = []
         self.keysize = keysize
diff --git a/tests/resource_management/test_resource_manager.py b/tests/resource_management/test_resource_manager.py
index 83becfbd..40c3fc23 100644
--- a/tests/resource_management/test_resource_manager.py
+++ b/tests/resource_management/test_resource_manager.py
@@ -36,7 +36,7 @@ def __init__(self, name, memories=[]):
         self.rule = Rule(None, None, None, None, None)
         self.rule.protocols.append(self)
         self.memories = memories
-        self.own = None
+        self.owner = None
 
     def is_ready(self):
         return self.other_is_setted
@@ -161,14 +161,14 @@ def test_send_request():
     assert len(node.send_log) == 0
     assert protocol in resource_manager.waiting_protocols
     assert len(resource_manager.pending_protocols) == 0
-    assert protocol.own == node
+    assert protocol.owner == node
     protocol = FakeProtocol("send")
     node.resource_manager.send_request(protocol, "dst_id",
                                        "req_condition_func", {})
     assert len(node.send_log) == 1
     assert protocol in resource_manager.pending_protocols
     len(resource_manager.waiting_protocols) == 1
-    assert protocol.own == node
+    assert protocol.owner == node
 
 
 def test_received_message():
diff --git a/tests/topology/router_net_topo_sample_config.json b/tests/topology/router_net_topo_sample_config.json
index b7d848ca..45158f5d 100644
--- a/tests/topology/router_net_topo_sample_config.json
+++ b/tests/topology/router_net_topo_sample_config.json
@@ -39,10 +39,10 @@
             "memo_size": 20
         },
         {
-            "seed": 4,
-            "group": 0,
+            "name": "bsm0",
             "type": "BSMNode",
-            "name": "bsm0"
+            "seed": 4,
+            "group": 0
         }
     ],
     "qchannels": [
diff --git a/tests/topology/test_node.py b/tests/topology/test_node.py
index 2d767822..33394b50 100644
--- a/tests/topology/test_node.py
+++ b/tests/topology/test_node.py
@@ -51,13 +51,11 @@ def receive_message(self, src, msg):
     tl.init()
     tl.run()
 
-    expect_node1_log = [(CC_DELAY + MSG_NUM + i, "node2", str(i))
-                        for i in range(MSG_NUM)]
+    expect_node1_log = [(CC_DELAY + MSG_NUM + i, "node2", str(i)) for i in range(MSG_NUM)]
     for actual, expect in zip(node1.log, expect_node1_log):
         assert actual == expect
 
-    expect_node2_log = [(CC_DELAY + i, "node1", str(i))
-                        for i in range(MSG_NUM)]
+    expect_node2_log = [(CC_DELAY + i, "node1", str(i)) for i in range(MSG_NUM)]
     for actual, expect in zip(node2.log, expect_node2_log):
         assert actual == expect
 
diff --git a/tests/utils/test_log.py b/tests/utils/test_log.py
index a37fabf3..1f7155fb 100644
--- a/tests/utils/test_log.py
+++ b/tests/utils/test_log.py
@@ -42,12 +42,10 @@ def test_log():
     tl = Timeline()
     de = DumbEntity()
 
-    lg.set_logger(__name__, tl, filename)
     lg.set_logger_level("DEBUG")
     lg.track_module(__name__)
 
     tl.init()
     de.log()
 
-    # TODO: fix (should be 1)
-    assert file_len(filename) == 2
+    assert file_len(filename) == 1