diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index f23371988..80ee693aa 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -15,7 +15,6 @@ jobs: strategy: matrix: python-version: - - "3.11" - "3.12" - "3.13" - "3.14" diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b796349..88ec6fa25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.0] - 2026-3-02 +### Added +- Added the `routing` module: a new parent class `RoutingProtocol` for the subclasses `StaticRoutingProtocol` and `DistributedRoutingProtocol`. The register decorator is used to make it easy to plug in new routing protocols in the future. +- Add empty init() to Protocol to satisfy class hierarchy. +- Created project scripts for each config generator. +- Added the `swapping` module that has the following classes. The subclasses use the registry decorator (factory pattern). + - Base class: `EntanglementSwappingA` & `EntanglementSwappingB` + - Subclass for ket vector and density matrix: `EntanglementSwappingA_Circuit` & `EntanglementSwappingB_Circuit` + - Subclass for Bell diagonal state: `EntanglementSwappingA_BDS` & `EntanglementSwappingB_BDS` +- Update the tutorial to catch up on the recent updates. Add notes saying refer to chapter 6 Application module for standard usage +- Introducing `EARLY_EXPIRE` message type. A request has a `start_time` and `end_time`. When a request is finished before the `end_time`, then the reservation (and the associated rules) should expire early. If not, the quantum network will keep generating entanglement pairs because the rules still exist. + +### Changed +- Docstring, comment, logging, and various cosmetic updates. +- Moved routing_protocol init() to network manager. +- Trigger empty inits for resource and network manager to enable future bootstrapping. +- Migrated `utils/json_config_generators/` to `sequence/config_generators/`. + +### Removed +- The old `swapping.py` is removed. + + ## [0.8.5] - 2026-2-27 ### Added - `NetworkManager` ABC with a factory pattern for selection and future implementation of new network managers. - File `action_condition_set.py` is added. This contains the action, condition, and request functions for entanglement generation, swapping, and purification. -- Class `DistributedRoutingProtocol` is added, among many other supporting classes in `routing_distributed.py`. This module does distributed entanglement routing. +- Class `DistributedRoutingProtocol` is added, among many other supporting classes in `routing_distributed`. This module does distributed entanglement routing. - Class `NetworkManager` has two new attributes: `forwarding_table` and `routing_protocol`. - Add a cutoff flag to memory to allow disable the expiration of memories. - Add a template in config json for network manager to select routing protocols (distributed vs. static) diff --git a/README.md b/README.md index 22d31d1d0..1915a2511 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ These modules can be edited by users to define additional functionality and test ## Installation ### For Users -SeQUeNCe requires [Python](https://www.python.org/downloads/) 3.11 or later. You can install SeQUeNCe using `pip`: +SeQUeNCe requires [Python](https://www.python.org/downloads/) 3.12 or later. You can install SeQUeNCe using `pip`: ``` pip install sequence ``` diff --git a/docs/source/tutorial/chapter0/prerequisite.md b/docs/source/tutorial/chapter0/prerequisite.md index 3628dcdf7..21d43b36f 100644 --- a/docs/source/tutorial/chapter0/prerequisite.md +++ b/docs/source/tutorial/chapter0/prerequisite.md @@ -6,7 +6,7 @@ Thank you for downloading SeQUeNCe and welcome to the tutorial section of the do ### Python Version -The simulator requires at least version **3.10** of Python. This can be found at the [Python Website](https://www.python.org/downloads/). +The simulator requires at least version **3.11** of Python. This can be found at the [Python Website](https://www.python.org/downloads/). ### Dependencies diff --git a/docs/source/tutorial/chapter1/discrete-event-simulation.md b/docs/source/tutorial/chapter1/discrete-event-simulation.md index 65c94b51a..2bba5efff 100644 --- a/docs/source/tutorial/chapter1/discrete-event-simulation.md +++ b/docs/source/tutorial/chapter1/discrete-event-simulation.md @@ -65,7 +65,7 @@ tl.show_progress = False # turn of progress bar, we will address this in later t store = Store(tl) # create store # open store at 7:00 -open_proc = Process(store, 'open', []) # Process(object, function name: string, arguments of function: List[]) +open_proc = Process(store, 'open', []) # Process(object, function name: string, arguments of function: list[]) open_event = Event(7, open_proc) # Event(occurring time: int, process: Process) tl.schedule(open_event) # Timeline.schedule(Event) ``` diff --git a/docs/source/tutorial/chapter3/entangle.md b/docs/source/tutorial/chapter3/entangle.md index 530a4b29e..a5bf67c37 100644 --- a/docs/source/tutorial/chapter3/entangle.md +++ b/docs/source/tutorial/chapter3/entangle.md @@ -1,5 +1,9 @@ # Chapter 3: Entanglement Management +**NOTE**: The standard way to generate entanglement is to use the `app` module, which is discussed in chapter 6. The purpose of this chapter is to explain what is happening under the hood in the entanglement management module. + +--- + In previous chapters, we introduced the usage of hardware models. In this chapter, we will use protocols in the entanglement management module to control these hardware devices and change the entanglement state of quantum memories. We will also use a simple manager protocol to control the entanglement generation protocols. @@ -515,10 +519,11 @@ class SimpleManager: 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)] + self.owner.protocols = [EntanglementSwappingA.create(self.owner, 'ESA', left_memo, right_memo, + success_prob=1, degradation=0.99)] else: memo = self.owner.components[self.memo_names[0]] - self.owner.protocols = [EntanglementSwappingB(self.owner, '%s.ESB' % self.owner.name, memo)] + self.owner.protocols = [EntanglementSwappingB.create(self.owner, '%s.ESB' % self.owner.name, memo)] ``` ### Step 2: Create Network diff --git a/docs/source/tutorial/chapter3/example3.py b/docs/source/tutorial/chapter3/example3.py index 6ae75f9cc..5f1e0b9d0 100644 --- a/docs/source/tutorial/chapter3/example3.py +++ b/docs/source/tutorial/chapter3/example3.py @@ -24,10 +24,11 @@ def create_protocol(self): 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)] + self.owner.protocols = [EntanglementSwappingA.create(self.owner, 'ESA', left_memo, right_memo, + success_prob=1, degradation=0.99)] else: memo = self.owner.components[self.memo_names[0]] - self.owner.protocols = [EntanglementSwappingB(self.owner, '%s.ESB' % self.owner.name, memo)] + self.owner.protocols = [EntanglementSwappingB.create(self.owner, '%s.ESB' % self.owner.name, memo)] class SwapNodeA(Node): @@ -59,7 +60,7 @@ def receive_message(self, src: str, msg: "Message") -> None: self.protocols[0].received_message(src, msg) def create_protocol(self): - self.protocols = [EntanglementSwappingB(self, '%s.ESB'%self.name, self.memo)] + self.protocols = [EntanglementSwappingB.create(self, '%s.ESB'%self.name, self.memo)] def entangle_memory(tl: Timeline, memo1: Memory, memo2: Memory, fidelity: float): diff --git a/docs/source/tutorial/chapter4/resource_management.md b/docs/source/tutorial/chapter4/resource_management.md index bfa5b990f..57d93738d 100644 --- a/docs/source/tutorial/chapter4/resource_management.md +++ b/docs/source/tutorial/chapter4/resource_management.md @@ -1,5 +1,11 @@ # Chapter 4: Resource Management +**NOTE**: The standard way to generate entanglement is to use the `app` module, which is discussed in chapter 6. The purpose of this chapter is to explain what is happening under the hood in the resource management module. + +See `sequence.resource_management.action_condition_set` for detailed docstrings. + +--- + In this tutorial, we will show the usual operation of the Resource Management module included in SeQUeNCe. We’re going to build a linear, three-node network and create two entanglement flows. The network, including flows, is shown below: ![topo](figures/two_flow_linear_topo.png) @@ -145,26 +151,25 @@ def eg_match_func(protocols, args): def eg_rule_action_await(memories_info: List["MemoryInfo"], args): mid_name = args["mid_name"] other_name = args["other_name"] - memories = [info.memory for info in memories_info] memory = memories[0] - protocol = EntanglementGenerationA(None, "EGA." + memory.name, mid_name, - other_name, - memory) - req_args = {"remote_node": args["node_name"], - "index_upper": args["index_upper"], - "index_lower": args["index_lower"]} - return [protocol, [other_name], [eg_match_func], [req_args]] + protocol = EntanglementGenerationA.create(None, "EGA." + memory.name, + mid_name, other_name, memory) + return [protocol, [None], [None], [None]] def eg_rule_action_request(memories_info: List["MemoryInfo"], args): mid_name = args["mid_name"] other_name = args["other_name"] + memories = [info.memory for info in memories_info] memory = memories[0] - protocol = EntanglementGenerationA(None, "EGA." + memory.name, - mid_name, other_name, memory) - return [protocol, [None], [None], [None]] + protocol = EntanglementGenerationA.create(None, "EGA." + memory.name, + mid_name, other_name, memory) + req_args = {"remote_node": args["node_name"], + "index_upper": args["index_upper"], + "index_lower": args["index_lower"]} + return [protocol, [other_name], [eg_match_func], [req_args]] ``` ### Step 4: Build the Network @@ -230,10 +235,10 @@ tl.init() action_args = {"mid_name": "m12", "other_name": "r2", "node_name": "r1", "index_upper": 9, "index_lower": 0} condition_args = {"index_lower": 0, "index_upper": 9} -rule1 = Rule(10, eg_rule_action_await, eg_rule_condition, action_args, condition_args) +rule1 = Rule(10, eg_rule_action_request, eg_rule_condition, action_args, condition_args) r1.resource_manager.load(rule1) action_args2 = {"mid_name": "m12", "other_name": "r1"} -rule2 = Rule(10, eg_rule_action_request, eg_rule_condition, action_args2, condition_args) +rule2 = Rule(10, eg_rule_action_await, eg_rule_condition, action_args2, condition_args) r2.resource_manager.load(rule2) tl.run() @@ -262,8 +267,7 @@ To do this, we'll create a function `add_eg_rules` that takes as arguments - `path`, a list of router nodes that make up the path of the entanglement flow, and - `middles`, a list of bsm nodes along the path. -The conditions and actions of the rule will be very similar to before, but with variable memory indices, nodes, and -arguments. +The conditions and actions of the rule will be very similar to before, but with variable memory indices, nodes, and arguments. ```python def add_eg_rules(index: int, path: List[RouterNode], middles: List[BSMNode]): @@ -281,8 +285,7 @@ def add_eg_rules(index: int, path: List[RouterNode], middles: List[BSMNode]): condition_args = {"index_lower": mem_range[0], "index_upper": mem_range[0] + 9} - rule = Rule(10, eg_rule_action_request, eg_rule_condition, action_args, - condition_args) + rule = Rule(10, eg_rule_action_await, eg_rule_condition, action_args, condition_args) node.resource_manager.load(rule) if index < (len(path) - 1): @@ -299,14 +302,8 @@ def add_eg_rules(index: int, path: List[RouterNode], middles: List[BSMNode]): "index_upper": node_mems[index + 1][1] - 1, "index_lower": node_mems[index + 1][0]} - memories = [info.memory for info in memories_info] - memory = memories[0] - protocol = EntanglementGenerationA(None, "EGA." + memory.name, middle_names[index], node_names[index + 1], - memory) - return [protocol, [node_names[index + 1]], [req_func]] - - rule = Rule(10, eg_rule_action, eg_rule_condition) - node.resource_manager.load(rule) + rule = Rule(10, eg_rule_action_request, eg_rule_condition, action_args, condition_args) + node.resource_manager.load(rule) ``` ### Step 7: Flow 2 Entanglement Purification @@ -320,10 +317,9 @@ The arguments for our `add_ep_rules` function will be similar to our previous fu - `target_fidelity`, the fidelity of entanglement we wish to achieve. ```python -from sequence.entanglement_management.purification import BBPSSW +from sequence.entanglement_management.purification import BBPSSWProtocol -def ep_rule_condition_request(memory_info: "MemoryInfo", manager: "MemoryManager", - args): +def ep_rule_condition_request(memory_info: "MemoryInfo", manager: "MemoryManager", args): index_upper = args["index_upper"] index_lower = args["index_lower"] target_fidelity = args["target_fidelity"] @@ -346,7 +342,7 @@ def ep_match_func(protocols, args): _protocols = [] for protocol in protocols: - if not isinstance(protocol, BBPSSW): + if not isinstance(protocol, BBPSSWProtocol): continue if protocol.kept_memo.name == remote1: @@ -361,10 +357,8 @@ def ep_match_func(protocols, args): _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] @@ -373,7 +367,7 @@ def ep_match_func(protocols, args): def ep_rule_action_request(memories_info: List["MemoryInfo"], args): 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]) + protocol = BBPSSWProtocol.create(None, name, memories[0], memories[1]) dsts = [memories_info[0].remote_node] req_funcs = [ep_match_func] req_args = {"remote1": memories_info[0].remote_memo, @@ -381,8 +375,7 @@ def ep_rule_action_request(memories_info: List["MemoryInfo"], args): return [protocol, dsts, req_funcs, [req_args]] -def ep_rule_condition_await(memory_info: "MemoryInfo", manager: "MemoryManager", - args): +def ep_rule_condition_await(memory_info: "MemoryInfo", manager: "MemoryManager", args): index_upper = args["index_upper"] index_lower = args["index_lower"] target_fidelity = args["target_fidelity"] @@ -395,7 +388,7 @@ def ep_rule_condition_await(memory_info: "MemoryInfo", manager: "MemoryManager", def ep_rule_action_await(memories_info: List["MemoryInfo"], args): memories = [info.memory for info in memories_info] name = "EP.%s" % (memories[0].name) - protocol = BBPSSW(None, name, memories[0], None) + protocol = BBPSSWProtocol.create(None, name, memories[0], None) return [protocol, [None], [None], [None]] @@ -437,8 +430,7 @@ For now, we will only define the actions and conditions and leave the rule creat ```python from sequence.entanglement_management.swapping import EntanglementSwappingA, EntanglementSwappingB -def es_rule_condition_A(memory_info: "MemoryInfo", manager: "MemoryManager", - args): +def es_rule_condition_A(memory_info: "MemoryInfo", manager: "MemoryManager", args): index_lower = args["index_lower"] index_upper = args["index_upper"] target_fidelity = args["target_fidelity"] @@ -481,11 +473,9 @@ def es_rule_action_A(memories_info: List["MemoryInfo"], args): memories = [info.memory for info in memories_info] - protocol = EntanglementSwappingA(None, "ESA.%s.%s" % ( - memories[0].name, memories[1].name), - memories[0], memories[1], - success_prob=succ_prob, - degradation=degradation) + protocol_name = "ESA.%s.%s" % (memories[0].name, memories[1].name) + protocol = EntanglementSwappingA.create(None, protocol_name, memories[0], memories[1], + success_prob=succ_prob, degradation=degradation) dsts = [info.remote_node for info in memories_info] req_funcs = [es_match_func, es_match_func] req_args = [{"target_memo": memories_info[0].remote_memo}, @@ -493,8 +483,7 @@ def es_rule_action_A(memories_info: List["MemoryInfo"], args): return [protocol, dsts, req_funcs, req_args] -def es_rule_condition_B(memory_info: "MemoryInfo", manager: "MemoryManager", - args): +def es_rule_condition_B(memory_info: "MemoryInfo", manager: "MemoryManager", args): index_lower = args["index_lower"] index_upper = args["index_upper"] target_node = args["target_node"] @@ -512,7 +501,7 @@ def es_rule_condition_B(memory_info: "MemoryInfo", manager: "MemoryManager", def es_rule_action_B(memories_info: List["MemoryInfo"], args): memories = [info.memory for info in memories_info] memory = memories[0] - protocol = EntanglementSwappingB(None, "ESB." + memory.name, memory) + protocol = EntanglementSwappingB.create(None, "ESB." + memory.name, memory) return [protocol, [None], [None], [None]] ``` diff --git a/docs/source/tutorial/chapter4/two_flow.py b/docs/source/tutorial/chapter4/two_flow.py index 3e2845c3d..d34f6f882 100644 --- a/docs/source/tutorial/chapter4/two_flow.py +++ b/docs/source/tutorial/chapter4/two_flow.py @@ -177,10 +177,8 @@ def ep_req_func(protocols, args): _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] @@ -288,11 +286,9 @@ def es_rule_actionA(memories_info: list["MemoryInfo"], args): memories = [info.memory for info in memories_info] - protocol = EntanglementSwappingA(None, "ESA.%s.%s" % ( - memories[0].name, memories[1].name), - memories[0], memories[1], - success_prob=succ_prob, - degradation=degradation) + protocol_name = "ESA.%s.%s" % (memories[0].name, memories[1].name) + protocol = EntanglementSwappingA.create(None, protocol_name, memories[0], memories[1], + success_prob=succ_prob, degradation=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}, @@ -318,7 +314,7 @@ def es_rule_conditionB(memory_info: "MemoryInfo", manager: "MemoryManager", args def es_rule_actionB(memories_info: list["MemoryInfo"], args): memories = [info.memory for info in memories_info] memory = memories[0] - protocol = EntanglementSwappingB(None, "ESB." + memory.name, memory) + protocol = EntanglementSwappingB.create(None, "ESB." + memory.name, memory) return [protocol, [None], [None], [None]] diff --git a/docs/source/tutorial/chapter5/network_manager.md b/docs/source/tutorial/chapter5/network_manager.md index eea7cff96..e21e17240 100644 --- a/docs/source/tutorial/chapter5/network_manager.md +++ b/docs/source/tutorial/chapter5/network_manager.md @@ -1,10 +1,15 @@ # Chapter 5: Network Manager -In this tutorial, we will showcase the Network Management module of SeQUeNCe and show its operation on a typical network. The goal of this tutorial is to +**NOTE**: The standard way to generate entanglement is to use the `app` module, which is discussed in chapter 6. The purpose of this chapter is to explain what is happening under the hood in the network management module. + +--- + +In this tutorial, we will showcase the (default and distributed) Network Management module of SeQUeNCe and show its operation on a typical network. The goal of this tutorial is to - Gain familiarity with the `network_management` module - Gain familiarity with the `topology.topology` module - Using external files to build networks + To achieve this, we will be using an example json file to build our network and will use the network manager on `QuantumRouter` nodes to request entanglement pairs. The json file will create the network topology shown below: ![topo](figures/star_network.png) diff --git a/docs/source/tutorial/chapter6/custom_app.py b/docs/source/tutorial/chapter6/custom_app.py index c182a71b2..bcc778a72 100644 --- a/docs/source/tutorial/chapter6/custom_app.py +++ b/docs/source/tutorial/chapter6/custom_app.py @@ -1,9 +1,9 @@ +from __future__ import annotations from sequence.kernel.process import Process from sequence.kernel.event import Event 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 @@ -12,7 +12,7 @@ class PeriodicApp: - def __init__(self, node: "QuantumRouter", other: str, memory_size=25, target_fidelity=0.9): + def __init__(self, node: QuantumRouter, other: str, memory_size=25, target_fidelity=0.9): self.node = node self.node.set_app(self) self.other = other diff --git a/example/demo_for_beginners/three_node_eg_ep_es.ipynb b/example/demo_for_beginners/three_node_eg_ep_es.ipynb index aa114f4fa..cf43769dd 100644 --- a/example/demo_for_beginners/three_node_eg_ep_es.ipynb +++ b/example/demo_for_beginners/three_node_eg_ep_es.ipynb @@ -160,12 +160,12 @@ " # create routing table manually\n", " # note that the routing table is based on quantum links, not classical\n", " # the arguments are the names of the destination node and the next node in the best path towards the destination\n", - " r1.network_manager.routing_protocol.add_forwarding_rule(\"r2\", \"r2\")\n", - " r1.network_manager.routing_protocol.add_forwarding_rule(\"r3\", \"r2\")\n", - " r2.network_manager.routing_protocol.add_forwarding_rule(\"r1\", \"r1\")\n", - " r2.network_manager.routing_protocol.add_forwarding_rule(\"r3\", \"r3\")\n", - " r3.network_manager.routing_protocol.add_forwarding_rule(\"r1\", \"r2\")\n", - " r3.network_manager.routing_protocol.add_forwarding_rule(\"r2\", \"r2\")\n", + " r1.network_manager.routing_protocol.update_forwarding_rule(\"r2\", \"r2\")\n", + " r1.network_manager.routing_protocol.update_forwarding_rule(\"r3\", \"r2\")\n", + " r2.network_manager.routing_protocol.update_forwarding_rule(\"r1\", \"r1\")\n", + " r2.network_manager.routing_protocol.update_forwarding_rule(\"r3\", \"r3\")\n", + " r3.network_manager.routing_protocol.update_forwarding_rule(\"r1\", \"r2\")\n", + " r3.network_manager.routing_protocol.update_forwarding_rule(\"r2\", \"r2\")\n", " \n", " ## run our simulation\n", " \n", diff --git a/pyproject.toml b/pyproject.toml index 99119078b..30ebf73c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sequence" -version = "0.8.5" +version = "1.0.0" authors = [ { name = "Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Alexander Kiefer, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara, Robert Hayek, Ansh Singal, Caitao Zhan", email = "czhan@anl.gov" } ] @@ -10,9 +10,8 @@ maintainers = [ ] 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.11, <3.15" +requires-python = ">=3.12, <3.15" classifiers = [ - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14" @@ -26,6 +25,7 @@ dependencies = [ "dash-html-components>=2.0.0", "dash-table>=5.0.0", "gmpy2>=2.2.2", + "graphviz>=0.21", "ipywidgets>=8.1.8", "jupyterlab>=4.5.0", "matplotlib>=3.10.7", @@ -43,6 +43,16 @@ dependencies = [ "tqdm>=4.67.1", ] +[project.scripts] +generate-internet-autonomous = "utils.config_generators.config_generator_as_memo_num:main" +generate-caveman = "sequence.utils.config_generator_caveman:main" +generate-linear = "sequence.utils.config_generator_line:main" +generate-mesh = "sequence.utils.config_generator_mesh:main" +generate-random = "sequence.utils.config_generator_random:main" +generate-ring = "sequence.utils.config_generator_ring:main" +generate-star = "sequence.utils.config_generator_star:main" +generate-tree = "sequence.utils.config_generator_tree:main" + [build-system] requires = ["uv_build>=0.9.17,<0.10.0"] build-backend = "uv_build" diff --git a/sequence/app/request_app.py b/sequence/app/request_app.py index 0d56b0cbd..e8d0bc04d 100644 --- a/sequence/app/request_app.py +++ b/sequence/app/request_app.py @@ -82,7 +82,9 @@ def get_reservation_result(self, reservation: Reservation, result: bool) -> None self.reservation_result = result if result: self.schedule_reservation(reservation) - log.logger.info(f"Successful reservation of resources for request app on node {self.node.name}") + log.logger.info(f"{self.node.name}, reservation successful: {reservation}") + else: + log.logger.info(f"{self.node.name}, reservation failed: {reservation}") def get_other_reservation(self, reservation: Reservation) -> None: """Method to add the approved reservation that is requested by other nodes. The responder will call this method @@ -146,7 +148,9 @@ def get_throughput(self) -> float: def schedule_reservation(self, reservation: Reservation) -> None: """Calling the `add_memo_reservation_map` and `remove_memo_reservation_map` methods at the - reservation's start_time and end_time for all timecards (memory) involved in the reservation. + reservation's start_time and end_time for all timecards (memory) involved in the reservation. + + Called by the initiator and the responder when reservation is approved. Args: reservation (Reservation): reservation to schedule diff --git a/sequence/components/bsm.py b/sequence/components/bsm.py index 3843554da..48e29eb4f 100644 --- a/sequence/components/bsm.py +++ b/sequence/components/bsm.py @@ -21,7 +21,7 @@ from ..kernel.entity import Entity from ..kernel.event import Event from ..kernel.process import Process -from ..constants import KET_STATE_FORMALISM, DENSITY_MATRIX_FORMALISM +from ..constants import KET_VECTOR_FORMALISM, DENSITY_MATRIX_FORMALISM from ..utils.encoding import * from ..utils import log @@ -54,7 +54,7 @@ def _set_state_with_fidelity(keys: list[int], desired_state: list[complex], fide BSM._psi_plus, BSM._psi_minus] assert desired_state in possible_states - if qm.get_active_formalism() == KET_STATE_FORMALISM: + if qm.get_active_formalism() == KET_VECTOR_FORMALISM: probabilities = [(1 - fidelity) / 3] * 4 probabilities[possible_states.index(desired_state)] = fidelity state_ind = rng.choice(4, p=probabilities) @@ -73,7 +73,7 @@ def _set_state_with_fidelity(keys: list[int], desired_state: list[complex], fide def _set_pure_state(keys: list[int], ket_state: list[complex], qm: "QuantumManager"): - if qm.get_active_formalism() == KET_STATE_FORMALISM: + if qm.get_active_formalism() == KET_VECTOR_FORMALISM: qm.set(keys, ket_state) elif qm.get_active_formalism() == DENSITY_MATRIX_FORMALISM: state = outer(ket_state, ket_state) @@ -85,7 +85,7 @@ def _set_pure_state(keys: list[int], ket_state: list[complex], qm: "QuantumManag def _eq_psi_plus(state: "State", formalism: str): - if formalism == KET_STATE_FORMALISM: + if formalism == KET_VECTOR_FORMALISM: return array_equal(state.state, BSM._psi_plus) elif formalism == DENSITY_MATRIX_FORMALISM: d_state = outer(BSM._phi_plus, BSM._psi_plus) diff --git a/sequence/components/circuit.py b/sequence/components/circuit.py index 1facae560..0442252e5 100644 --- a/sequence/components/circuit.py +++ b/sequence/components/circuit.py @@ -4,7 +4,6 @@ """ from math import e, pi, sqrt -from typing import Optional import numpy as np from qutip_qip.circuit import QubitCircuit diff --git a/sequence/components/memory.py b/sequence/components/memory.py index 3efc2063d..f1db1bb72 100644 --- a/sequence/components/memory.py +++ b/sequence/components/memory.py @@ -471,7 +471,7 @@ def get_bds_fidelity(self) -> float: """ state_obj = self.timeline.quantum_manager.get(self.qstate_key) state = state_obj.state - return state[0] + return float(state[0]) class AbsorptiveMemory(Entity): diff --git a/sequence/constants.py b/sequence/constants.py index b6ea19608..56073f6b9 100644 --- a/sequence/constants.py +++ b/sequence/constants.py @@ -44,7 +44,7 @@ SECOND: Final = 10**12 #: Built-in ket-vector formalism identifier. -KET_STATE_FORMALISM: Final = "ket_vector" +KET_VECTOR_FORMALISM: Final = "ket_vector" #: Built-in density-matrix formalism identifier. DENSITY_MATRIX_FORMALISM: Final = "density_matrix" #: Built-in Fock density-matrix formalism identifier. diff --git a/sequence/entanglement_management/__init__.py b/sequence/entanglement_management/__init__.py index 5bb4304f5..4d5819904 100644 --- a/sequence/entanglement_management/__init__.py +++ b/sequence/entanglement_management/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['entanglement_protocol', 'generation', 'swapping', 'purification'] +__all__ = ['entanglement_protocol', 'generation', 'swapping', 'purification', 'teleportation'] def __dir__(): return sorted(__all__) diff --git a/sequence/entanglement_management/entanglement_protocol.py b/sequence/entanglement_management/entanglement_protocol.py index a1f7049a2..cce14da3d 100644 --- a/sequence/entanglement_management/entanglement_protocol.py +++ b/sequence/entanglement_management/entanglement_protocol.py @@ -18,12 +18,13 @@ class EntanglementProtocol(Protocol): Attributes: owner (Node): Node object to attach to name (str): Name of the protocol instance + protocol_type (str): Type of the protocol instance rule (Rule): Rule which created this protocol instance (from the rule manager). memories (list[Memory]): Any memories being operated on """ - def __init__(self, owner: "Node", name: str): - super().__init__(owner, name) + def __init__(self, owner: "Node", name: str, protocol_type: str = ""): + super().__init__(owner, name, protocol_type) self.rule = None self.memories = [] diff --git a/sequence/entanglement_management/generation/generation_base.py b/sequence/entanglement_management/generation/generation_base.py index c56335371..a560ac2e2 100644 --- a/sequence/entanglement_management/generation/generation_base.py +++ b/sequence/entanglement_management/generation/generation_base.py @@ -54,8 +54,7 @@ class EntanglementGenerationA(EntanglementProtocol, ABC): _global_type: str = BARRET_KOK def __init__(self, owner: "Node", name: str, middle: str, other: str, memory: "Memory", **kwargs): - super().__init__(owner, name) - self.protocol_type = BARRET_KOK + super().__init__(owner, name, BARRET_KOK) self.middle: str = middle self.remote_node_name: str = other self.remote_protocol_name: str = '' diff --git a/sequence/entanglement_management/generation/generation_message.py b/sequence/entanglement_management/generation/generation_message.py index 182fc29c8..657cd5dc2 100644 --- a/sequence/entanglement_management/generation/generation_message.py +++ b/sequence/entanglement_management/generation/generation_message.py @@ -1,7 +1,6 @@ from __future__ import annotations from enum import auto, Enum -from typing import Optional from ...message import Message diff --git a/sequence/entanglement_management/generation/single_heralded.py b/sequence/entanglement_management/generation/single_heralded.py index ae3e6d3fc..e25067da4 100644 --- a/sequence/entanglement_management/generation/single_heralded.py +++ b/sequence/entanglement_management/generation/single_heralded.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Dict, Any +from typing import TYPE_CHECKING, Any from .generation_base import EntanglementGenerationA, EntanglementGenerationB, QuantumCircuitMixin from .generation_message import EntanglementGenerationMessage, GenerationMsgType, valid_trigger_time @@ -18,8 +18,6 @@ from ...topology.node import Node, BSMNode - - @EntanglementGenerationA.register(SINGLE_HERALDED) class SingleHeraldedA(EntanglementGenerationA, QuantumCircuitMixin): """ @@ -250,7 +248,7 @@ def bsm_update(self, bsm: SingleHeraldedBSM, info: dict[str, Any]) -> None: Args: bsm (SingleAtomBSM or SingleHeraldedBSM): bsm object calling method. - info (Dict[str, any]): information passed from bsm. + info (dict[str, any]): information passed from bsm. """ assert bsm.encoding == SINGLE_HERALDED, "SingleHeraldedB should only be used with SingleHeraldedBSM." assert info['info_type'] == 'BSM_res' diff --git a/sequence/entanglement_management/purification/bbpssw_bds.py b/sequence/entanglement_management/purification/bbpssw_bds.py index a1079a17a..1796abae6 100644 --- a/sequence/entanglement_management/purification/bbpssw_bds.py +++ b/sequence/entanglement_management/purification/bbpssw_bds.py @@ -6,7 +6,6 @@ """ from __future__ import annotations -from typing import List, Tuple from typing import TYPE_CHECKING import numpy as np @@ -36,7 +35,7 @@ class BBPSSW_BDS(BBPSSWProtocol): meas_res (int): measurement result from circuit. remote_node_name (str): name of other node. remote_protocol_name (str): name of other protocol. - remote_memories (List[str]): name of remote memories. + remote_memories (list[str]): name of remote memories. is_twirled (bool): whether we twirl the input and output BDS. True: BBPSSW, False: DEJMPS. (default True) """ @@ -152,7 +151,7 @@ def purification_res(self) -> tuple[float, npt.NDArray]: The four BDS density matrix elements of kept entangled pair conditioned on successful purification. Returns: - Tuple[float, np.array]: success probability and BDS density matrix elements of kept entangled pair. + tuple[float, np.array]: success probability and BDS density matrix elements of kept entangled pair. """ assert self.owner.timeline.quantum_manager.get_active_formalism() == BELL_DIAGONAL_STATE_FORMALISM, ( diff --git a/sequence/entanglement_management/purification/bbpssw_circuit.py b/sequence/entanglement_management/purification/bbpssw_circuit.py index 15e0b181f..c51bb3213 100644 --- a/sequence/entanglement_management/purification/bbpssw_circuit.py +++ b/sequence/entanglement_management/purification/bbpssw_circuit.py @@ -15,10 +15,10 @@ from ...topology.node import Node from ...utils import log -from ...constants import KET_STATE_FORMALISM, DENSITY_MATRIX_FORMALISM +from ...constants import KET_VECTOR_FORMALISM, DENSITY_MATRIX_FORMALISM from .bbpssw_protocol import BBPSSWProtocol, BBPSSWMessage, BBPSSWMsgType -@BBPSSWProtocol.register(KET_STATE_FORMALISM) +@BBPSSWProtocol.register(KET_VECTOR_FORMALISM) @BBPSSWProtocol.register(DENSITY_MATRIX_FORMALISM) class BBPSSWCircuit(BBPSSWProtocol): """Purification protocol instance. diff --git a/sequence/entanglement_management/purification/bbpssw_protocol.py b/sequence/entanglement_management/purification/bbpssw_protocol.py index e2c56097c..de2760969 100644 --- a/sequence/entanglement_management/purification/bbpssw_protocol.py +++ b/sequence/entanglement_management/purification/bbpssw_protocol.py @@ -1,12 +1,14 @@ +"""Base class for BBPSSW entanglement purification protocol. +""" from __future__ import annotations from abc import ABC, abstractmethod from enum import Enum, auto -from typing import TYPE_CHECKING, List, Dict, Type, Optional +from typing import TYPE_CHECKING from collections.abc import Callable from sequence.entanglement_management.entanglement_protocol import EntanglementProtocol from sequence.utils.log import logger -from ...constants import KET_STATE_FORMALISM +from ...constants import KET_VECTOR_FORMALISM from ...message import Message if TYPE_CHECKING: @@ -41,7 +43,7 @@ def __str__(self): class BBPSSWProtocol(EntanglementProtocol, ABC): _registry: dict[str, type['BBPSSWProtocol']] = {} - _global_formalism: str = KET_STATE_FORMALISM + _global_formalism: str = KET_VECTOR_FORMALISM def __init__(self, owner: Node, name: str, kept_memo: Memory, meas_memo: Memory, **kwargs): """Constructor for purification protocol. @@ -53,14 +55,13 @@ def __init__(self, owner: Node, name: str, kept_memo: Memory, meas_memo: Memory, meas_memo (Memory): Memory to measure and discard. """ assert kept_memo != meas_memo - super().__init__(owner, name) + super().__init__(owner, name, 'bbpssw') self.memories: list[Memory] = [kept_memo, meas_memo] self.kept_memo: Memory = kept_memo self.meas_memo: Memory = meas_memo self.remote_node_name: str = '' self.remote_protocol_name: str = '' self.remote_memories: list[str] = [] - self.protocol_type = 'bbpssw' self.meas_res = None if self.meas_memo is None: self.memories.pop() @@ -95,7 +96,7 @@ def register(cls, name: str, protocol_class: type['BBPSSWProtocol'] | None = Non Args: name (str): Name of the protocol to register. - protocol_class (Type[BBPSSWProtocol], optional): The protocol class to register + protocol_class (type[BBPSSWProtocol], optional): The protocol class to register Returns: If used as a decorator, returns the decorator function. @@ -158,7 +159,7 @@ def list_protocols(cls) -> list[str]: @classmethod def clear_global_formalism(cls) -> None: """Resets the global formalism to default""" - cls._global_formalism = KET_STATE_FORMALISM + cls._global_formalism = KET_VECTOR_FORMALISM def is_ready(self) -> bool: """Check if the protocol is ready to start.""" @@ -170,7 +171,7 @@ def set_others(self, protocol: str, node: str, memories: list[str]) -> None: args: protocol (str): Other protocol name. node (str): Other node name. - memories (List[str]): The list of memory names used on other node. + memories (list[str]): The list of memory names used on other node. """ self.remote_node_name = node self.remote_protocol_name = protocol diff --git a/sequence/entanglement_management/swapping.py b/sequence/entanglement_management/swapping.py deleted file mode 100644 index bbe9cc863..000000000 --- a/sequence/entanglement_management/swapping.py +++ /dev/null @@ -1,366 +0,0 @@ -"""Code for entanglement swapping. - -This module defines code for entanglement swapping. -Success is pre-determined based on network parameters. -The entanglement swapping protocol is an asymmetric protocol: - -* The EntanglementSwappingA instance initiates the protocol and performs the swapping operation. -* The EntanglementSwappingB instance waits for the swapping result from EntanglementSwappingA. - -The swapping results decides the following operations of EntanglementSwappingB. -Also defined in this module is the message type used by these protocols. -""" - -from enum import Enum, auto -from typing import TYPE_CHECKING -from functools import lru_cache - -if TYPE_CHECKING: - from ..components.memory import Memory - from ..topology.node import Node - -from ..message import Message -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.""" - - SWAP_RES = auto() - - -class EntanglementSwappingMessage(Message): - """Message used by entanglement swapping protocols. - - This message contains all information passed between swapping protocol instances. - - Attributes: - msg_type (SwappingMsgType): defines the message type. - receiver (str): name of destination protocol instance. - fidelity (float): fidelity of the newly swapped memory pair. - remote_node (str): name of the distant node holding the entangled memory of the new pair. - remote_memo (int): index of the entangled memory on the remote node. - expire_time (int): expiration time of the new memory pair. - """ - - def __init__(self, msg_type: SwappingMsgType, receiver: str, **kwargs): - Message.__init__(self, msg_type, receiver) - if self.msg_type is SwappingMsgType.SWAP_RES: - self.fidelity = kwargs.get("fidelity") - self.remote_node = kwargs.get("remote_node") - self.remote_memo = kwargs.get("remote_memo") - self.expire_time = kwargs.get("expire_time") - self.meas_res = kwargs.get("meas_res") - else: - raise Exception("Entanglement swapping protocol create unkown type of message: %s" % str(msg_type)) - - def __str__(self): - if self.msg_type == SwappingMsgType.SWAP_RES: - return "EntanglementSwappingMessage: msg_type: {}; fidelity: {:.2f}; remote_node: {}; remote_memo: {}; ".format( - self.msg_type, self.fidelity, self.remote_node, self.remote_memo) - - -class EntanglementSwappingA(EntanglementProtocol): - """Entanglement swapping protocol for middle router. - - The entanglement swapping protocol is an asymmetric protocol. - EntanglementSwappingA should be instantiated on the middle node, where it measures a memory from each pair to be swapped. - Results of measurement and swapping are sent to the end routers. - - Variables: - EntanglementSwappingA.circuit (Circuit): circuit that does swapping operations. - - Attributes: - own (Node): node that protocol instance is attached to. - name (str): label for protocol instance. - left_memo (Memory): a memory from one pair to be swapped. - right_memo (Memory): a memory from the other pair to be swapped. - left_node (str): name of node that contains memory entangling with left_memo. - left_remote_memo (str): name of memory that entangles with left_memo. - right_node (str): name of node that contains memory entangling with right_memo. - right_remote_memo (str): name of memory that entangles with right_memo. - success_prob (float): probability of a successful swapping operation. - degradation (float): degradation factor of memory fidelity after swapping. - is_success (bool): flag to show the result of swapping - left_protocol_name (str): name of left protocol. - right_protocol_name (str): name of right protocol. - """ - - circuit = Circuit(2) - circuit.cx(0, 1) - circuit.h(0) - circuit.measure(0) - circuit.measure(1) - - 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: - 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. - success_prob (float): probability of a successful swapping operation (default 1). - degradation (float): degradation factor of memory fidelity after swapping (default 0.95). - """ - - assert left_memo != right_memo - EntanglementProtocol.__init__(self, owner, name) - self.memories = [left_memo, right_memo] - self.left_memo = left_memo - self.right_memo = right_memo - self.left_node = left_memo.entangled_memory['node_id'] - self.left_remote_memo = left_memo.entangled_memory['memo_id'] - self.right_node = right_memo.entangled_memory['node_id'] - self.right_remote_memo = right_memo.entangled_memory['memo_id'] - self.success_prob = success_prob - self.degradation = degradation - self.is_success = False - self.left_protocol_name = None - self.right_protocol_name = None - - def is_ready(self) -> bool: - """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. - - Args: - protocol (str): other protocol name. - node (str): other node name. - memories (list[str]): the list of memories name used on other node. - """ - - if node == self.left_memo.entangled_memory["node_id"]: - self.left_protocol_name = protocol - elif node == self.right_memo.entangled_memory["node_id"]: - self.right_protocol_name = protocol - else: - raise Exception(f"Cannot pair protocol {self.name} with {protocol}") - - def start(self) -> None: - """Method to start entanglement swapping protocol. - - Will run circuit and send measurement results to other protocols. - - Side Effects: - Will call `update_resource_manager` method. - Will send messages to other protocols. - """ - - 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.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.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, - remote_node=self.right_memo.entangled_memory["node_id"], - remote_memo=self.right_memo.entangled_memory["memo_id"], - 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) - else: - # 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.update_resource_manager(self.left_memo, MemoryInfo.RAW) - self.update_resource_manager(self.right_memo, MemoryInfo.RAW) - - - def success_probability(self) -> float: - """A simple model for BSM success probability.""" - - return self.success_prob - - @lru_cache(maxsize=128) - def updated_fidelity(self, f1: float, f2: float) -> float: - """A simple model updating fidelity of entanglement. - - Args: - f1 (float): fidelity 1. - f2 (float): fidelity 2. - - Returns: - float: fidelity of swapped entanglement. - """ - - return f1 * f2 * self.degradation - - def received_message(self, src: str, msg: "Message") -> None: - """Method to receive messages (should not be used on A protocol).""" - - raise Exception(f"EntanglementSwappingA protocol '{self.name}' should not receive messages.") - - def memory_expire(self, memory: "Memory") -> None: - """Method to receive memory expiration events. - - Releases held memories on current node. - Memories at the remote node are released as well. - - Args: - memory (Memory): memory that expired. - - Side Effects: - Will invoke `update` method of attached resource manager. - Will invoke `release_remote_protocol` or `release_remote_memory` method of resource manager. - """ - - assert self.is_ready() is False - if self.left_protocol_name: - self.release_remote_protocol(self.left_node) - else: - self.release_remote_memory(self.left_node, self.left_remote_memo) - if self.right_protocol_name: - self.release_remote_protocol(self.right_node) - else: - self.release_remote_memory(self.right_node, self.right_remote_memo) - - for memo in self.memories: - if memo == memory: - self.update_resource_manager(memo, MemoryInfo.RAW) - else: - self.update_resource_manager(memo, MemoryInfo.ENTANGLED) - - def release_remote_protocol(self, remote_node: str): - self.owner.resource_manager.release_remote_protocol(remote_node, self) - - def release_remote_memory(self, remote_node: str, remote_memo: str): - self.owner.resource_manager.release_remote_memory(remote_node, remote_memo) - - -class EntanglementSwappingB(EntanglementProtocol): - """Entanglement swapping protocol for end router. - - The entanglement swapping protocol is an asymmetric protocol. - EntanglementSwappingB should be instantiated on the end nodes, where it waits for swapping results from the middle node. - - Variables: - EntanglementSwappingB.x_cir (Circuit): circuit that corrects state with an x gate. - EntanglementSwappingB.z_cir (Circuit): circuit that corrects state with z gate. - EntanglementSwappingB.x_z_cir (Circuit): circuit that corrects state with an x and z gate. - - Attributes: - own (QuantumRouter): node that protocol instance is attached to. - name (str): name of protocol instance. - memory (Memory): memory to swap. - remote_protocol_name (str): name of another protocol to communicate with for swapping. - remote_node_name (str): name of node hosting the other protocol. - """ - - x_cir = Circuit(1) - x_cir.x(0) - - z_cir = Circuit(1) - z_cir.z(0) - - x_z_cir = Circuit(1) - x_z_cir.x(0) - x_z_cir.z(0) - - def __init__(self, owner: "Node", name: str, hold_memo: "Memory"): - """Constructor for entanglement swapping B protocol. - - Args: - own (Node): node protocol instance is attached to. - name (str): name of protocol instance. - hold_memo (Memory): memory entangled with a memory on middle node. - """ - - EntanglementProtocol.__init__(self, owner, name) - - self.memories = [hold_memo] - self.memory = hold_memo - self.remote_protocol_name = None - self.remote_node_name = None - - def is_ready(self) -> bool: - return self.remote_protocol_name is not None - - def set_others(self, protocol: str, node: str, memories: list[str]) -> None: - """Method to set other entanglement protocol instance. - - Args: - protocol (str): other protocol name. - node (str): other node name. - memories (list[str]): the list of memory names used on other node. - """ - self.remote_node_name = node - self.remote_protocol_name = protocol - - def received_message(self, src: str, msg: "EntanglementSwappingMessage") -> None: - """Method to receive messages from EntanglementSwappingA. - - Args: - src (str): name of node sending message. - msg (EntanglementSwappingMesssage): message sent. - - Side Effects: - Will invoke `update_resource_manager` method. - """ - - log.logger.debug(self.owner.name + f" protocol received_message from node {src}, fidelity={msg.fidelity}") - - assert src == self.remote_node_name - - if msg.fidelity > 0 and self.owner.timeline.now() < msg.expire_time: - if msg.meas_res == [1, 0]: - self.owner.timeline.quantum_manager.run_circuit(self.z_cir, [self.memory.qstate_key]) - elif msg.meas_res == [0, 1]: - self.owner.timeline.quantum_manager.run_circuit(self.x_cir, [self.memory.qstate_key]) - elif msg.meas_res == [1, 1]: - 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, MemoryInfo.ENTANGLED) - else: - self.update_resource_manager(self.memory, MemoryInfo.RAW) - - def start(self) -> None: - 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. - - Args: - memory (Memory): memory that expired. - - Side Effects: - Will update memory in attached resource manager. - """ - - self.update_resource_manager(self.memory, MemoryInfo.RAW) - - def release(self) -> None: - self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED) diff --git a/sequence/entanglement_management/swapping/__init__.py b/sequence/entanglement_management/swapping/__init__.py new file mode 100644 index 000000000..f97d0bb10 --- /dev/null +++ b/sequence/entanglement_management/swapping/__init__.py @@ -0,0 +1,9 @@ +from .swapping_base import EntanglementSwappingA, EntanglementSwappingB, SwappingMsgType, EntanglementSwappingMessage +from .swapping_circuit import EntanglementSwappingA_Circuit, EntanglementSwappingB_Circuit +from .swapping_bds import EntanglementSwappingA_BDS, EntanglementSwappingB_BDS + +__all__ = ['EntanglementSwappingA', 'EntanglementSwappingB', 'SwappingMsgType', 'EntanglementSwappingMessage', 'EntanglementSwappingA_Circuit', + 'EntanglementSwappingB_Circuit', 'EntanglementSwappingA_BDS', 'EntanglementSwappingB_BDS'] + +def __dir__(): + return sorted(__all__) diff --git a/sequence/entanglement_management/swapping/swapping_base.py b/sequence/entanglement_management/swapping/swapping_base.py new file mode 100644 index 000000000..56b5e3ee7 --- /dev/null +++ b/sequence/entanglement_management/swapping/swapping_base.py @@ -0,0 +1,392 @@ +"""The base class for entanglement swapping protocol. + +This module defines code for entanglement swapping. +Success rate is pre-determined based on network parameters. +The entanglement swapping protocol is an asymmetric protocol: + +* The EntanglementSwappingA instance initiates the protocol and performs the swapping operation. +* The EntanglementSwappingB instance waits for the swapping result from EntanglementSwappingA. + +The swapping results decide the following operations of EntanglementSwappingB. +Also defined in this module is the message type used by these protocols. + +""" + +from __future__ import annotations +from abc import ABC, abstractmethod +from collections.abc import Callable +from enum import Enum, auto +from typing import TYPE_CHECKING + +from ..entanglement_protocol import EntanglementProtocol +from ...constants import KET_VECTOR_FORMALISM +from ...message import Message +from ...resource_management.memory_manager import MemoryInfo +from ...utils import log + +if TYPE_CHECKING: + from ...components.memory import Memory + from ...topology.node import Node + + +class SwappingMsgType(Enum): + """Defines possible message types for entanglement generation. + """ + SWAP_RES = auto() + + +class EntanglementSwappingMessage(Message): + """Message used by entanglement swapping protocols. + + This message contains all information passed between swapping protocol instances. + + Attributes: + msg_type (SwappingMsgType): defines the message type. + receiver (str): name of destination protocol instance. + fidelity (float): fidelity of the newly swapped memory pair. + remote_node (str): name of the distant node holding the entangled memory of the new pair. + remote_memo (int): index of the entangled memory on the remote node. + expire_time (int): expiration time of the new memory pair. + """ + + def __init__(self, msg_type: SwappingMsgType, receiver: str, **kwargs): + Message.__init__(self, msg_type, receiver) + if self.msg_type is SwappingMsgType.SWAP_RES: + self.fidelity = kwargs.get("fidelity") + self.remote_node = kwargs.get("remote_node") + self.remote_memo = kwargs.get("remote_memo") + self.expire_time = kwargs.get("expire_time") + self.meas_res = kwargs.get("meas_res") + else: + raise Exception("Entanglement swapping protocol create unkown type of message: %s" % str(msg_type)) + + def __str__(self): + if self.msg_type == SwappingMsgType.SWAP_RES: + return "EntanglementSwappingMessage: msg_type: {}; fidelity: {:.2f}; remote_node: {}; remote_memo: {}; ".format( + self.msg_type, self.fidelity, self.remote_node, self.remote_memo) + + +class EntanglementSwappingA(EntanglementProtocol, ABC): + """Base class for Entanglement Swapping A protocol. + + The default formalism is KET_VECTOR_FORMALISM. + """ + _registry: dict[str, type['EntanglementSwappingA']] = {} + _global_formalism: str = KET_VECTOR_FORMALISM + + def __init__(self, owner: Node, name: str, left_memo: Memory, right_memo: Memory, success_prob: float = 1): + """Constructor for Entanglement Swapping A protocol. + + Args: + owner (Node): node that protocol instance is attached to. + name (str): label for 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. + success_prob (float): probability of a successful swapping operation (default 1). + """ + assert left_memo != right_memo + super().__init__(owner, name, 'EntanglementSwappingA') + self.memories = [left_memo, right_memo] + self.left_memo = left_memo + self.right_memo = right_memo + self.left_node = left_memo.entangled_memory['node_id'] + self.left_remote_memo = left_memo.entangled_memory['memo_id'] + self.right_node = right_memo.entangled_memory['node_id'] + self.right_remote_memo = right_memo.entangled_memory['memo_id'] + self.success_prob = success_prob + assert 1 >= self.success_prob >= 0, "Entanglement swapping success probability must be between 0 and 1." + self.is_success = False + self.left_protocol_name = None + self.right_protocol_name = None + + @classmethod + def set_formalism(cls, formalism: str) -> None: + """Set the global formalism for all Entanglement Swapping A protocol instances. + + Args: + formalism (str): global formalism for all Entanglement Swapping A protocol instances. + """ + if formalism not in cls._registry: + raise ValueError(f"Protocol type {formalism} not found in registry.") + cls._global_formalism = formalism + + @classmethod + def get_formalism(cls) -> str: + """Get the global formalism for all Entanglement Swapping A protocol instances. + + Returns: + The global formalism for all Entanglement Swapping A protocol instances. + """ + return cls._global_formalism + + @classmethod + def register(cls, name: str, protocol_class: type['EntanglementSwappingA'] = None + ) -> Callable[[type['EntanglementSwappingA']], type['EntanglementSwappingA']] | None: + """Register a specific type of Entanglement Swapping A protocol. + + This method should be used as a decorator to register different types of Entanglement Swapping A protocols. + The registered protocol can then be set as the global formalism for all Entanglement Swapping A protocol instances. + + Args: + name (str): name of the specific type of Entanglement Swapping A protocol. + protocol_class (type[EntanglementSwappingA] | None): the class of the specific type of Entanglement Swapping A protocol. If None, the decorated class will be registered. + + Returns: + If protocol_class is None, returns a decorator function. Otherwise, returns None. + """ + if name in cls._registry: + raise ValueError(f"Protocol type {name} already registered.") + + if protocol_class is not None: + cls._registry[name] = protocol_class + return None + + def decorator(protocol_class: type['EntanglementSwappingA']) -> type['EntanglementSwappingA']: + if name in cls._registry: + raise ValueError(f"Protocol type {name} already registered.") + cls._registry[name] = protocol_class + return protocol_class + + return decorator + + @classmethod + def create(cls, owner: Node, name: str, left_memo: Memory, right_memo: Memory, + success_prob: float = 1, **kwargs) -> 'EntanglementSwappingA': + """Factory method to create an Entanglement Swapping A protocol instance of the global formalism. + + Args: + owner (Node): node that protocol instance is attached to. + name (str): label for 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. + success_prob (float): probability of a successful swapping operation (default 1). + + Returns: + An instance of the Entanglement Swapping A protocol of the global formalism. + """ + protocol_name = EntanglementSwappingA.get_formalism() + try: + protocol_class = cls._registry[protocol_name] + return protocol_class(owner, name, left_memo, right_memo, success_prob, **kwargs) + except KeyError: + raise ValueError(f"Protocol class '{protocol_name}' is not registered.") + + @classmethod + def list_protocols(cls) -> list[str]: + """List all registered Entanglement Swapping A protocols. + + Returns: + A list of names of all registered Entanglement Swapping A protocols. + """ + return list(cls._registry.keys()) + + @classmethod + def clear_global_formalism(cls) -> None: + """Resets the global formalism to default""" + cls._global_formalism = KET_VECTOR_FORMALISM + + def is_ready(self) -> bool: + """Check if the protocol is ready. + + Returns: + bool: True if the protocol is ready to start, False otherwise. + """ + 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. + + Args: + protocol (str): other protocol name. + node (str): other node name. + memories (list[str]): the list of memories name used on other node. + """ + if node == self.left_memo.entangled_memory["node_id"]: + self.left_protocol_name = protocol + elif node == self.right_memo.entangled_memory["node_id"]: + self.right_protocol_name = protocol + else: + raise Exception("Cannot pair protocol %s with %s" % (self.name, protocol)) + + def success_probability(self) -> float: + """A simple model for BSM success probability. + + Returns: + float: the probability of a successful swapping operation. + """ + return self.success_prob + + @abstractmethod + def start(self) -> None: + """Method to start entanglement swapping process (abstract). + """ + pass + + def received_message(self, src: str, msg: Message) -> None: + """Method to receive messages (should not be used on A protocol). + """ + raise Exception("EntanglementSwappingA protocol '{}' should not receive messages.".format(self.name)) + + def memory_expire(self, memory: Memory) -> None: + """Method to receive memory expiration events. + + Releases held memories on current node. + Memories at the remote node are released as well. + + Args: + memory (Memory): memory that expired. + + Side Effects: + Will invoke `update` method of attached resource manager. + Will invoke `release_remote_protocol` or `release_remote_memory` method of resource manager. + """ + assert self.is_ready() is False + if self.left_protocol_name: + self.release_remote_protocol(self.left_node) + else: + self.release_remote_memory(self.left_node, self.left_remote_memo) + if self.right_protocol_name: + self.release_remote_protocol(self.right_node) + else: + self.release_remote_memory(self.right_node, self.right_remote_memo) + + for memo in self.memories: + if memo == memory: + self.update_resource_manager(memo, MemoryInfo.RAW) + else: + self.update_resource_manager(memo, MemoryInfo.ENTANGLED) + + def release_remote_protocol(self, remote_node: str): + self.owner.resource_manager.release_remote_protocol(remote_node, self) + + def release_remote_memory(self, remote_node: str, remote_memo: str): + self.owner.resource_manager.release_remote_memory(remote_node, remote_memo) + + +class EntanglementSwappingB(EntanglementProtocol, ABC): + """Base class for Entanglement Swapping B protocol. + + The default formalism is KET_VECTOR_FORMALISM. + """ + _registry: dict[str, type['EntanglementSwappingB']] = {} + _global_formalism: str = KET_VECTOR_FORMALISM + + def __init__(self, owner: Node, name: str, hold_memo: Memory): + """Constructor for entanglement swapping B protocol. + + Args: + owner (Node): node protocol instance is attached to. + name (str): name of protocol instance. + hold_memo (Memory): memory entangled with a memory on middle node. + """ + super().__init__(owner, name, 'EntanglementSwappingB') + self.memories = [hold_memo] + self.memory = hold_memo + self.remote_protocol_name = None + self.remote_node_name = None + + @classmethod + def set_formalism(cls, formalism: str) -> None: + """Set the global formalism for all Entanglement Swapping B protocol instances. + + Valid Built-formalisms: + 1. Bell Diagonal -> bds + 2. Circuit -> circuit (DEFAULT) + + Args: + formalism (str): global formalism for all Entanglement Swapping B protocol instances. + """ + if formalism not in cls._registry: + raise ValueError(f"Protocol type {formalism} not found in registry.") + cls._global_formalism = formalism + + @classmethod + def get_formalism(cls) -> str: + """Get the global formalism for all Entanglement Swapping B protocol instances. + + Returns: + The global formalism for all Entanglement Swapping B protocol instances. + """ + return cls._global_formalism + + @classmethod + def register(cls, name: str, protocol_class: type['EntanglementSwappingB'] = None + ) -> Callable[[type['EntanglementSwappingB']], type['EntanglementSwappingB']] | None: + """Register a specific type of Entanglement Swapping B protocol. + + This method should be used as a decorator to register different types of Entanglement Swapping B protocols. + + Args: + name (str): name of the specific type of Entanglement Swapping B protocol. + protocol_class (type[EntanglementSwappingB] | None): the class of the specific type of Entanglement Swapping B protocol. If None, the decorated class will be registered. + + Returns: + If protocol_class is None, returns a decorator function. Otherwise, returns None. + """ + if name in cls._registry: + raise ValueError(f"{name} already registered.") + + if protocol_class is not None: + cls._registry[name] = protocol_class + + def decorator(protocol_class: type['EntanglementSwappingB']) -> type['EntanglementSwappingB']: + if name in cls._registry: + raise ValueError(f"{name} already registered.") + cls._registry[name] = protocol_class + return protocol_class + + return decorator + + @classmethod + def create(cls, owner: Node, name: str, hold_memo: Memory, **kwargs) -> 'EntanglementSwappingB': + """Factory method to create an Entanglement Swapping B protocol instance of the global formalism. + """ + protocol_name = EntanglementSwappingB.get_formalism() + try: + protocol_class = cls._registry[protocol_name] + return protocol_class(owner, name, hold_memo, **kwargs) + except KeyError: + raise ValueError(f"Protocol class '{protocol_name}' is not registered.") + + def is_ready(self) -> bool: + return self.remote_protocol_name is not None + + def set_others(self, protocol: str, node: str, memories: list[str]) -> None: + """Method to set other entanglement protocol instance. + + Args: + protocol (str): other protocol name. + node (str): other node name. + memories (list[str]): the list of memory names used on other node. + """ + self.remote_node_name = node + self.remote_protocol_name = protocol + + def start(self) -> None: + log.logger.info(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. + + Args: + memory (Memory): memory that expired. + + Side Effects: + Will update memory in attached resource manager. + """ + + self.update_resource_manager(self.memory, MemoryInfo.RAW) + + def release(self) -> None: + self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED) + + @abstractmethod + def received_message(self, src: str, msg: EntanglementSwappingMessage) -> None: + """Method to receive messages from EntanglementSwappingA. + + Args: + src (str): name of node sending message. + msg (EntanglementSwappingMessage): message sent. + """ + pass + diff --git a/sequence/entanglement_management/swapping/swapping_bds.py b/sequence/entanglement_management/swapping/swapping_bds.py new file mode 100644 index 000000000..ce9de0a50 --- /dev/null +++ b/sequence/entanglement_management/swapping/swapping_bds.py @@ -0,0 +1,218 @@ +"""The entanglement swapping protocol for Bell Diagonal State (BDS) formalism. +""" +from __future__ import annotations +from typing import TYPE_CHECKING + +from .swapping_base import EntanglementSwappingA, EntanglementSwappingB, SwappingMsgType, EntanglementSwappingMessage +from ...utils import log +from ...kernel.quantum_manager import BELL_DIAGONAL_STATE_FORMALISM +from ...resource_management.memory_manager import MemoryInfo + +if TYPE_CHECKING: + from ...components.memory import Memory + from ...topology.node import Node + + +@EntanglementSwappingA.register(BELL_DIAGONAL_STATE_FORMALISM) +class EntanglementSwappingA_BDS(EntanglementSwappingA): + """Entanglement swapping protocol for middle node/router. + + The entanglement swapping protocol is an asymmetric protocol. + EntanglementSwappingA should be instantiated on the middle node, + where it measures a memory from each pair to be swapped. + Results of measurement and swapping are sent to the end nodes. + + Variables: + EntanglementSwappingA.circuit (Circuit): circuit that does swapping operations. + + Attributes: + owner (Node): node that protocol instance is attached to. + name (str): label for protocol instance. + left_memo (Memory): a memory from one pair to be swapped. + right_memo (Memory): a memory from the other pair to be swapped. + left_node (str): name of node that contains memory entangling with left_memo. + left_remote_memo (str): name of memory that entangles with left_memo. + right_node (str): name of node that contains memory entangling with right_memo. + right_remote_memo (str): name of memory that entangles with right_memo. + success_prob (float): probability of a successful swapping operation. + is_success (bool): flag to show the result of swapping. + left_protocol_name (str): name of left protocol. + right_protocol_name (str): name of right protocol. + is_twirled (bool): whether the input and output states are twirled into Werner form (default True). + """ + + def __init__(self, owner: Node, name: str, left_memo: Memory, right_memo: Memory, success_prob=1, is_twirled=True): + """Constructor for entanglement swapping A protocol. + + Args: + 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. + success_prob (float): probability of a successful swapping operation (default 1). + is_twirled (bool): whether the input and output states are twirled into Werner form (default True). + """ + assert left_memo != right_memo + super().__init__(owner, name, left_memo, right_memo, success_prob) + self.is_twirled = is_twirled + + def start(self) -> None: + """Method to start entanglement swapping protocol. + + Will run circuit and send measurement results to other protocols. + + Side Effects: + Will call `update_resource_manager` method. + Will send messages to other protocols. + """ + + log.logger.info(f"{self.owner.name} middle protocol start with ends {self.left_protocol_name}, {self.right_protocol_name}") + + 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.owner.get_generator().random() < self.success_probability(): + log.logger.info(f'swapping succeeded!') + self.is_success = True + expire_time = min(self.left_memo.get_expire_time(), self.right_memo.get_expire_time()) + # first invoke single-memory decoherence channels on each involved quantum memory (in total 4) + # note that bds_decohere() has changed the last_update_time to now, + # thus we don't need to change it for the udpated state from swapping + left_remote_memory: Memory = self.owner.timeline.get_entity_by_name(self.left_remote_memo) + right_remote_memory: Memory = self.owner.timeline.get_entity_by_name(self.right_remote_memo) + self.left_memo.bds_decohere() + left_remote_memory.bds_decohere() + self.right_memo.bds_decohere() + right_remote_memory.bds_decohere() + # get BDS conditioned on success, fidelity is the first diagonal element + new_bds = self.swapping_res() + fidelity = new_bds[0] + keys = [left_remote_memory.qstate_key, right_remote_memory.qstate_key] + self.owner.timeline.quantum_manager.set(keys, new_bds) + + msg_l = EntanglementSwappingMessage(SwappingMsgType.SWAP_RES, + 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, + remote_node=self.left_memo.entangled_memory["node_id"], + remote_memo=self.left_memo.entangled_memory["memo_id"], + expire_time=expire_time, + meas_res=[]) + else: + log.logger.info(f'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.update_resource_manager(self.left_memo, MemoryInfo.RAW) + self.update_resource_manager(self.right_memo, MemoryInfo.RAW) + + def swapping_res(self) -> list[float]: + """Method to calculate the resulting entangled state conditioned on successful swapping, for BDS formalism. + + Returns: + list[float]: resultant bell diagonal state entries. + """ + assert self.owner.timeline.quantum_manager.get_active_formalism() == BELL_DIAGONAL_STATE_FORMALISM, ( + "Input states should be Bell diagonal states.") + + left_state = self.owner.timeline.quantum_manager.get(self.left_memo.qstate_key) + right_state = self.owner.timeline.quantum_manager.get(self.right_memo.qstate_key) + + left_elem_1, left_elem_2, left_elem_3, left_elem_4 = left_state.state # BDS diagonal elements of left pair + right_elem_1, right_elem_2, right_elem_3, right_elem_4 = right_state.state # BDS diagonal elements of right pair + + if self.is_twirled: + left_elem_2, left_elem_3, left_elem_4 = [(1-left_elem_1)/3] * 3 + right_elem_2, right_elem_3, right_elem_4 = [(1-right_elem_1)/3] * 3 + + # assert 1. >= left_elem_1 >= 0.5 and 1. >= right_elem_1 >= 0.5, "Input states should have fidelity above 1/2." + # gate and measurment fidelities on swapping node, assuming two single-qubit measurements have equal fidelity + gate_fid, meas_fid = self.owner.gate_fid, self.owner.meas_fid + # calculate the BDS elements + c_I = left_elem_1 * right_elem_1 + left_elem_2 * right_elem_2 + left_elem_3 * right_elem_3 + left_elem_4 * right_elem_4 + c_X = left_elem_1 * right_elem_2 + left_elem_2 * right_elem_1 + left_elem_3 * right_elem_4 + left_elem_4 * right_elem_3 + c_Y = left_elem_1 * right_elem_4 + left_elem_4 * right_elem_1 + left_elem_2 * right_elem_3 + left_elem_3 * right_elem_2 + c_Z = left_elem_1 * right_elem_3 + left_elem_3 * right_elem_1 + left_elem_2 * right_elem_4 + left_elem_4 * right_elem_2 + + new_elem_1 = gate_fid * (meas_fid**2 * c_I + meas_fid*(1-meas_fid)*(c_X+c_Z) + (1-meas_fid)**2*c_Y) + (1-gate_fid)/4 + new_elem_2 = gate_fid * (meas_fid**2 * c_X + meas_fid*(1-meas_fid)*(c_I+c_Y) + (1-meas_fid)**2*c_Z) + (1-gate_fid)/4 + new_elem_3 = gate_fid * (meas_fid**2 * c_Z + meas_fid*(1-meas_fid)*(c_I+c_Y) + (1-meas_fid)**2*c_X) + (1-gate_fid)/4 + new_elem_4 = gate_fid * (meas_fid**2 * c_Y + meas_fid*(1-meas_fid)*(c_X+c_Z) + (1-meas_fid)**2*c_I) + (1-gate_fid)/4 + + if self.is_twirled: + bds_elems = [new_elem_1, (1-new_elem_1)/3, (1-new_elem_1)/3, (1-new_elem_1)/3] + else: + bds_elems = [new_elem_1, new_elem_2, new_elem_3, new_elem_4] + + log.logger.info(f'before swapping, f = {left_state.state[0]:.6f}, {right_state.state[0]:.6f}; after swapping, f = {bds_elems[0]:.6f}') + + return bds_elems + + +@EntanglementSwappingB.register(BELL_DIAGONAL_STATE_FORMALISM) +class EntanglementSwappingB_BDS(EntanglementSwappingB): + """Entanglement swapping protocol for end node/router. + + The entanglement swapping protocol is an asymmetric protocol. + EntanglementSwappingB should be instantiated on the end nodes, where it waits for swapping results from the middle node. + + Variables: + EntanglementSwappingB.x_cir (Circuit): circuit that corrects state with an x gate. + EntanglementSwappingB.z_cir (Circuit): circuit that corrects state with z gate. + EntanglementSwappingB.x_z_cir (Circuit): circuit that corrects state with an x and z gate. + + Attributes: + own (QuantumRouter): node that protocol instance is attached to. + name (str): name of protocol instance. + memory (Memory): memory to swap. + remote_protocol_name (str): name of another protocol to communicate with for swapping. + remote_node_name (str): name of node hosting the other protocol. + """ + + def __init__(self, own: Node, name: str, hold_memo: Memory): + """Constructor for entanglement swapping B protocol. + + Args: + owner (Node): node protocol instance is attached to. + name (str): name of protocol instance. + hold_memo (Memory): memory entangled with a memory on middle node. + """ + super().__init__(own, name, hold_memo) + + def received_message(self, src: str, msg: EntanglementSwappingMessage) -> None: + """Method to receive messages from EntanglementSwappingA. + + Args: + src (str): name of node sending message. + msg (EntanglementSwappingMessage): message sent. + + Side Effects: + Will invoke `update_resource_manager` method. + """ + + log.logger.debug(self.owner.name + " protocol received_message from node {}, fidelity={:.6f}".format(src, msg.fidelity)) + + assert src == self.remote_node_name + + if msg.fidelity > 0 and self.owner.timeline.now() < msg.expire_time: + # if using BDS formalism, updated BDS has been determined analytically taking into account local correction + self.memory.entangled_memory["node_id"] = msg.remote_node + self.memory.entangled_memory["memo_id"] = msg.remote_memo + remote_memory: Memory = self.owner.timeline.get_entity_by_name(msg.remote_memo) + self.memory.bds_decohere() + remote_memory.bds_decohere() + self.memory.fidelity = self.memory.get_bds_fidelity() + self.memory.update_expire_time(msg.expire_time) + self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED) + else: + self.update_resource_manager(self.memory, MemoryInfo.RAW) diff --git a/sequence/entanglement_management/swapping/swapping_circuit.py b/sequence/entanglement_management/swapping/swapping_circuit.py new file mode 100644 index 000000000..018655a5c --- /dev/null +++ b/sequence/entanglement_management/swapping/swapping_circuit.py @@ -0,0 +1,205 @@ +"""The entanglement swapping protocol for circuit-based (ket vector, density matrix) formalism. +""" + +from __future__ import annotations +from typing import TYPE_CHECKING +from functools import lru_cache + +if TYPE_CHECKING: + from ...components.memory import Memory + from ...topology.node import Node + +from .swapping_base import EntanglementSwappingA, EntanglementSwappingB, SwappingMsgType, EntanglementSwappingMessage +from ...components.circuit import Circuit +from ...constants import KET_VECTOR_FORMALISM, DENSITY_MATRIX_FORMALISM +from ...resource_management.memory_manager import MemoryInfo +from ...utils import log + + +@EntanglementSwappingA.register(KET_VECTOR_FORMALISM) +@EntanglementSwappingA.register(DENSITY_MATRIX_FORMALISM) +class EntanglementSwappingA_Circuit(EntanglementSwappingA): + """Entanglement swapping protocol for middle router. + + The entanglement swapping protocol is an asymmetric protocol. + EntanglementSwappingA_Circuit should be instantiated on the middle node, where it measures a memory from each pair to be swapped. + Results of measurement and swapping are sent to the end routers. + + Variables: + EntanglementSwappingA_Circuit.circuit (Circuit): circuit that does swapping operations. + + Attributes: + owner (Node): node that protocol instance is attached to. + name (str): label for protocol instance. + left_memo (Memory): a memory from one pair to be swapped. + right_memo (Memory): a memory from the other pair to be swapped. + left_node (str): name of node that contains memory entangling with left_memo. + left_remote_memo (str): name of memory that entangles with left_memo. + right_node (str): name of node that contains memory entangling with right_memo. + right_remote_memo (str): name of memory that entangles with right_memo. + success_prob (float): probability of a successful swapping operation. + degradation (float): degradation factor of memory fidelity after swapping. + is_success (bool): flag to show the result of swapping + left_protocol_name (str): name of left protocol. + right_protocol_name (str): name of right protocol. + """ + + circuit = Circuit(2) + circuit.cx(0, 1) + circuit.h(0) + circuit.measure(0) + circuit.measure(1) + + 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: + 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. + success_prob (float): probability of a successful swapping operation (default 1). + degradation (float): degradation factor of memory fidelity after swapping (default 0.95). + """ + assert left_memo != right_memo + super().__init__(owner, name, left_memo, right_memo, success_prob) + self.degradation = degradation + + def start(self) -> None: + """Method to start entanglement swapping protocol. + + Will run circuit and send measurement results to other protocols. + + Side Effects: + Will call `update_resource_manager` method. + Will send messages to other protocols. + """ + + 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.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.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, + remote_node=self.right_memo.entangled_memory["node_id"], + remote_memo=self.right_memo.entangled_memory["memo_id"], + 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) + else: + # 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.update_resource_manager(self.left_memo, MemoryInfo.RAW) + self.update_resource_manager(self.right_memo, MemoryInfo.RAW) + + @lru_cache(maxsize=128) + def updated_fidelity(self, f1: float, f2: float) -> float: + """A simple model updating fidelity of entanglement. + + Args: + f1 (float): fidelity 1. + f2 (float): fidelity 2. + + Returns: + float: fidelity of swapped entanglement. + """ + + return f1 * f2 * self.degradation + + +@EntanglementSwappingB.register(KET_VECTOR_FORMALISM) +@EntanglementSwappingB.register(DENSITY_MATRIX_FORMALISM) +class EntanglementSwappingB_Circuit(EntanglementSwappingB): + """Entanglement swapping protocol for end router. + + The entanglement swapping protocol is an asymmetric protocol. + EntanglementSwappingB_Circuit should be instantiated on the end nodes, where it waits for swapping results from the middle node. + + Variables: + EntanglementSwappingB_Circuit.x_cir (Circuit): circuit that corrects state with an x gate. + EntanglementSwappingB_Circuit.z_cir (Circuit): circuit that corrects state with z gate. + EntanglementSwappingB_Circuit.x_z_cir (Circuit): circuit that corrects state with an x and z gate. + + Attributes: + own (QuantumRouter): node that protocol instance is attached to. + name (str): name of protocol instance. + memory (Memory): memory to swap. + remote_protocol_name (str): name of another protocol to communicate with for swapping. + remote_node_name (str): name of node hosting the other protocol. + """ + + x_cir = Circuit(1) + x_cir.x(0) + + z_cir = Circuit(1) + z_cir.z(0) + + x_z_cir = Circuit(1) + x_z_cir.x(0) + x_z_cir.z(0) + + def __init__(self, owner: Node, name: str, hold_memo: Memory): + """Constructor for entanglement swapping B protocol. + + Args: + owner (Node): node protocol instance is attached to. + name (str): name of protocol instance. + hold_memo (Memory): memory entangled with a memory on middle node. + """ + super().__init__(owner, name, hold_memo) + + def received_message(self, src: str, msg: "EntanglementSwappingMessage") -> None: + """Method to receive messages from EntanglementSwappingA. + + Args: + src (str): name of node sending message. + msg (EntanglementSwappingMessage): message sent. + + Side Effects: + Will invoke `update_resource_manager` method. + """ + + log.logger.debug(self.owner.name + f" protocol received_message from node {src}, fidelity={msg.fidelity}") + + assert src == self.remote_node_name + + if msg.fidelity > 0 and self.owner.timeline.now() < msg.expire_time: + if msg.meas_res == [1, 0]: + self.owner.timeline.quantum_manager.run_circuit(self.z_cir, [self.memory.qstate_key]) + elif msg.meas_res == [0, 1]: + self.owner.timeline.quantum_manager.run_circuit(self.x_cir, [self.memory.qstate_key]) + elif msg.meas_res == [1, 1]: + 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, MemoryInfo.ENTANGLED) + else: + self.update_resource_manager(self.memory, MemoryInfo.RAW) diff --git a/sequence/gui/simulator_bindings.py b/sequence/gui/simulator_bindings.py index 0a570df7a..7c748ae71 100644 --- a/sequence/gui/simulator_bindings.py +++ b/sequence/gui/simulator_bindings.py @@ -112,9 +112,7 @@ def __init__(self, sim_time: int, time_scale: int, logging: str, sim_name, confi for node in self.topology.get_nodes_by_type("QuantumRouter"): table = self.topology.generate_forwarding_table(node.name) for dst, next_node in table.items(): - node.network_manager.protocol_stack[0].add_forwarding_rule( - dst, - next_node) + node.network_manager.routing_protocol.update_forwarding_rule(dst,next_node) def init_logging(self): set_logger( diff --git a/sequence/kernel/quantum_manager.py b/sequence/kernel/quantum_manager.py index e4bafc171..f60344381 100644 --- a/sequence/kernel/quantum_manager.py +++ b/sequence/kernel/quantum_manager.py @@ -25,7 +25,7 @@ from .quantum_state import KetState, DensityState, BellDiagonalState from .quantum_utils import * -from ..constants import KET_STATE_FORMALISM, DENSITY_MATRIX_FORMALISM, FOCK_DENSITY_MATRIX_FORMALISM, BELL_DIAGONAL_STATE_FORMALISM +from ..constants import KET_VECTOR_FORMALISM, DENSITY_MATRIX_FORMALISM, FOCK_DENSITY_MATRIX_FORMALISM, BELL_DIAGONAL_STATE_FORMALISM class QuantumManager(ABC): @@ -46,7 +46,7 @@ class QuantumManager(ABC): """ _registry: dict = {} _global_formalism_lock = Lock() - _global_formalism: str = KET_STATE_FORMALISM + _global_formalism: str = KET_VECTOR_FORMALISM def __init__(self, truncation: int = 1): self.states: dict[int, "State"] = {} @@ -74,7 +74,7 @@ def get_active_formalism(cls): @classmethod def clear_active_formalism(cls): with cls._global_formalism_lock: - cls._global_formalism = KET_STATE_FORMALISM + cls._global_formalism = KET_VECTOR_FORMALISM @classmethod def register(cls, name: str, manager_class=None): @@ -233,7 +233,7 @@ def set_states(self, states: dict): self.states = states -@QuantumManager.register(KET_STATE_FORMALISM) +@QuantumManager.register(KET_VECTOR_FORMALISM) class QuantumManagerKet(QuantumManager): """Class to track and manage quantum states with the ket vector formalism.""" diff --git a/sequence/kernel/quantum_state.py b/sequence/kernel/quantum_state.py index a5abcf8c8..8f382d1cf 100644 --- a/sequence/kernel/quantum_state.py +++ b/sequence/kernel/quantum_state.py @@ -5,15 +5,16 @@ 1. The `KetState` class represents the ket vector formalism and is used by a quantum manager. 2. The `DensityState` class represents the density matrix formalism and is also used by a quantum manager. -3. The `FreeQuantumState` class uses the ket vector formalism, and is used by individual photons (not the quantum manager). +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 numpy import pi, cos, sin, arange, log, log2 + +import numpy as np from numpy.random import Generator -from .quantum_utils import * +from .quantum_utils import measure_state_with_cache, measure_entangled_state_with_cache, measure_multiple_with_cache from ..constants import EPSILON @@ -42,9 +43,8 @@ def __init__(self, **kwargs): # potential key word arguments for derived classes, e.g. truncation = d-1 for qudit super().__init__() - self.state = None - self.keys = [] + self.keys: list = [] def deserialize(self, json_data) -> None: self.keys = json_data["keys"] @@ -55,8 +55,8 @@ def deserialize(self, json_data) -> None: self.state.append(complex_val) def serialize(self) -> dict: - res = {"keys": self.keys} - state = [] + res: dict[str, list] = {"keys": self.keys} + state: list = [] for cplx_n in self.state: if type(cplx_n) is float: state.append(cplx_n) @@ -101,7 +101,7 @@ def __init__(self, amplitudes: list[complex], keys: list[int], truncation: int = 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) + num_subsystems = np.log(len(amplitudes)) / np.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. " \ @@ -112,7 +112,7 @@ def __init__(self, amplitudes: list[complex], keys: list[int], truncation: int = "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)) - self.state = array(amplitudes, dtype=complex) + self.state = np.array(amplitudes, dtype=complex) self.keys = keys @@ -142,16 +142,16 @@ def __init__(self, state: list[list[complex]], keys: list[int], truncation: int self.truncation = truncation dim = self.truncation + 1 # dimension of element Hilbert space - state = array(state, dtype=complex) + state = np.array(state, dtype=complex) if state.ndim == 1: - state = outer(state, state.conj()) + state = np.outer(state, state.conj()) # check formatting - assert abs(trace(array(state)) - 1) < 0.01, "density matrix trace must be 1" + assert abs(np.trace(np.array(state)) - 1) < 0.01, "density matrix trace must be 1" for row in state: assert len(state) == len(row), "density matrix must be square" - num_subsystems = log(len(state)) / log(dim) + num_subsystems = np.log(len(state)) / np.log(dim) assert dim ** int(round(num_subsystems)) == len(state), ( "Length of amplitudes should be d ** n, " "where d is subsystem Hilbert space dimension and n is the number of subsystems. " @@ -197,7 +197,7 @@ def combine_state(self, another_state: "FreeQuantumState"): """ entangled_states = self.entangled_states + another_state.entangled_states - new_state = kron(self.state, another_state.state) + new_state = np.kron(self.state, another_state.state) new_state = tuple(new_state) for quantum_state in entangled_states: @@ -214,8 +214,8 @@ def random_noise(self, rng: Generator): """ # TODO: rewrite for entangled states - angle = rng.random() * 2 * pi - self.state = (complex(cos(angle)), complex(sin(angle))) + angle = rng.random() * 2 * np.pi + self.state = (complex(np.cos(angle)), complex(np.sin(angle))) # only for use with entangled state def set_state(self, state: tuple[complex]): @@ -233,7 +233,7 @@ def set_state(self, state: tuple[complex]): assert all([abs(a) <= 1.01 for a in state]), "Illegal value with abs > 1 in quantum state" assert abs(sum([abs(a) ** 2 for a in state]) - 1) < 1e-5, "Squared amplitudes do not sum to 1" - num_qubits = log2(len(state)) + num_qubits = np.log2(len(state)) assert 2 ** int(round(num_qubits)) == len(state), ( "Length of amplitudes should be 2 ** n, where n is the number of qubits. " f"Actual amplitude length: {len(state)}, num qubits: {num_qubits}") @@ -358,7 +358,7 @@ def measure_multiple(basis, states, rng: Generator): new_states, probabilities = measure_multiple_with_cache(state, basis, length_diff) - possible_results = arange(0, basis_dimension, 1) + possible_results = np.arange(0, basis_dimension, 1) # result gives index of the basis vector that will be projected to res = rng.choice(possible_results, p=probabilities) # project to new state, then reassign quantum state and entangled photons @@ -398,5 +398,5 @@ def __init__(self, diag_elems: list[float], keys: list[int]): assert len(keys) == 2, "BellDiagonalState density matrix are only supported for 2-qubit entangled states." # note: density matrix diagonal elements are guaranteed to be real from Hermiticity - self.state = array(diag_elems, dtype=float) + self.state: list[float] = np.array(diag_elems, dtype=float) self.keys = keys diff --git a/sequence/network_management/__init__.py b/sequence/network_management/__init__.py index 4f8ce5c3e..3d61e0d3b 100644 --- a/sequence/network_management/__init__.py +++ b/sequence/network_management/__init__.py @@ -1,4 +1,4 @@ -__all__ = ['routing_static', 'routing_distributed', 'forwarding', 'reservation', 'network_manager'] +__all__ = ['routing', 'forwarding', 'memory_timecard', 'network_manager', 'reservation', 'rsvp'] def __dir__(): return sorted(__all__) diff --git a/sequence/network_management/network_manager.py b/sequence/network_management/network_manager.py index aa0f1840f..9c5d38c2d 100644 --- a/sequence/network_management/network_manager.py +++ b/sequence/network_management/network_manager.py @@ -3,27 +3,22 @@ This module defines the NetworkManager ABC and the default NetworkManager, DistributedNetworkManager. """ from __future__ import annotations - from abc import ABC, abstractmethod from enum import Enum, auto from typing import TYPE_CHECKING, Any -from sequence.utils.log import logger - -from ..components.memory import MemoryArray if TYPE_CHECKING: from ..protocol import StackProtocol from ..topology.node import QuantumRouter +from ..components.memory import MemoryArray from ..message import Message -from ..protocol import Protocol from ..utils import log from .forwarding import ForwardingProtocol from .memory_timecard import MemoryTimeCard from .reservation import Reservation -from .routing_distributed import DistributedRoutingProtocol -from .routing_static import StaticRoutingProtocol +from .routing import RoutingProtocol, ROUTING_STATIC from .rsvp import RSVPMessage, RSVPMsgType, RSVPProtocol @@ -65,7 +60,7 @@ class NetworkManager(ABC): def __init__(self, owner: QuantumRouter, memory_array_name: str, **kwargs): if kwargs: - logger.warning(f'Network Manager ABC received kwargs: {list(kwargs.keys())}, ignoring.') + log.logger.warning(f'Network Manager ABC received kwargs: {list(kwargs.keys())}, ignoring.') self.name: str = 'network_manager' self.owner = owner self.memory_array_name = memory_array_name @@ -105,6 +100,10 @@ def set_global_type(cls, network_manager_type: str) -> None: else: cls._global_type = network_manager_type + @classmethod + def get_global_type(cls): + return cls._global_type + @abstractmethod def received_message(self, src: str, msg: NetworkManagerMessage): """Handle Message received into the NetworkManager.""" @@ -115,6 +114,9 @@ def request(self, responder, start_time, end_time, memory_size, target_fidelity, """Handle Requests from the Application.""" pass + def init(self): + pass + def push(self, dst: str, msg: NetworkManagerMessage): self.owner.send_message(dst, msg) @@ -129,6 +131,7 @@ def generate_rules(self, reservation: Reservation): """ self.owner.resource_manager.generate_load_rules(reservation.path, reservation, self.timecards, self.memory_array_name) + @NetworkManager.register('distributed') class DistributedNetworkManager(NetworkManager): """The default Network Manager implementation. @@ -138,13 +141,14 @@ class DistributedNetworkManager(NetworkManager): forwarding_table (dict[str, str]): mapping of destination node to next hop for forwarding. routing_protocol (Protocol): protocol used for updating forwarding table. """ - def __init__(self, owner: "QuantumRouter", memory_array_name: str, component_templates=None): + def __init__(self, owner: QuantumRouter, memory_array_name: str, component_templates=None): super().__init__(owner, memory_array_name) if component_templates is None: component_templates = {} self.protocol_stack = [] self.forwarding_table = {} - self.routing_protocol = self._create_routing_protocol(component_templates.get('routing', 'static')) + routing_type = component_templates.get('routing', ROUTING_STATIC) + self.routing_protocol = RoutingProtocol.create(owner, name=routing_type, protocol_type=routing_type) # Create and load the stack to protocol_stack protocols: list = self.create_stack() self.load_stack(protocols) @@ -157,6 +161,9 @@ def rsvp(self): def forward(self): return self.protocol_stack[0] + def init(self): + self.routing_protocol.init() + def get_forwarding_table(self) -> dict[str, str]: """Returns the forwarding table. @@ -177,21 +184,6 @@ def set_forwarding_table(self, forwarding_table: dict): def get_routing_protocol(self): return self.routing_protocol - def _create_routing_protocol(self, routing_type) -> Protocol: - """Helper function to create routing protocol based on routing type. - - Args: - routing_type (str): type of routing protocol to create, i.e., 'static' or 'distributed'. - """ - match routing_type: - case 'static': - routing_protocol_cls = StaticRoutingProtocol - case 'distributed': - routing_protocol_cls = DistributedRoutingProtocol - case _: - raise NotImplementedError(f'Routing protocol {routing_type} is not implemented.') - return routing_protocol_cls(self.owner, f'{routing_protocol_cls.__name__}') - def create_stack(self) -> list[StackProtocol]: """Helper function to stand up the protocols diff --git a/sequence/network_management/reservation.py b/sequence/network_management/reservation.py index eddc3c72b..b741be9ec 100644 --- a/sequence/network_management/reservation.py +++ b/sequence/network_management/reservation.py @@ -35,8 +35,8 @@ def __init__(self, initiator: str, responder: str, start_time: int, fidelity (float): desired fidelity of entanglement. entanglement_number (int): the number of entanglement the request ask for. identity (int): the ID of a request - path - purification_mode + path (list[str]): a list of router names from the source to destination + purification_mode (str): the mode of purification, 'until_target' or 'once' """ self.initiator = initiator @@ -53,7 +53,9 @@ def __init__(self, initiator: str, responder: str, start_time: int, assert self.memory_size > 0 def __str__(self) -> str: - return f'|initiator={self.initiator}; responder={self.responder}; start_time={self.start_time:,}; end_time={self.end_time:,}; memory_size={self.memory_size}; target_fidelity={self.fidelity}; entanglement_number={self.entanglement_number}; identity={self.identity}|' + return (f'|identity={self.identity}; initiator={self.initiator}; responder={self.responder}; path={self.path}; ' + f'start_time={self.start_time:,}; end_time={self.end_time:,}; target_fidelity={self.fidelity}; ' + f'entanglement_number={self.entanglement_number}; memory_size={self.memory_size}|') def __repr__(self) -> str: return self.__str__() @@ -61,19 +63,20 @@ def __repr__(self) -> str: def __eq__(self, other: object) -> bool: if not isinstance(other, Reservation): return False - return other.initiator == self.initiator and \ - other.responder == self.responder and \ - other.start_time == self.start_time and \ - other.end_time == self.end_time and \ - other.memory_size == self.memory_size and \ - other.fidelity == self.fidelity + return (other.initiator == self.initiator + and other.responder == self.responder + and other.start_time == self.start_time + and other.end_time == self.end_time + and other.memory_size == self.memory_size + and other.fidelity == self.fidelity + and other.entanglement_number == self.entanglement_number) 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)) + return hash((self.initiator, self.responder, self.start_time, self.end_time, self.memory_size, + self.fidelity, self.entanglement_number)) def set_path(self, path: list[str]): self.path = path - \ No newline at end of file diff --git a/sequence/network_management/routing/__init__.py b/sequence/network_management/routing/__init__.py new file mode 100644 index 000000000..d23f07d37 --- /dev/null +++ b/sequence/network_management/routing/__init__.py @@ -0,0 +1,8 @@ +from .routing_base import RoutingProtocol, ROUTING_STATIC, ROUTING_DISTRIBUTED +from .routing_distributed import DistributedRoutingProtocol +from .routing_static import StaticRoutingProtocol + +__all__ = ['RoutingProtocol', 'DistributedRoutingProtocol', 'StaticRoutingProtocol'] + +def __dir__(): + return sorted(__all__) \ No newline at end of file diff --git a/sequence/network_management/routing/routing_base.py b/sequence/network_management/routing/routing_base.py new file mode 100644 index 000000000..fc933481a --- /dev/null +++ b/sequence/network_management/routing/routing_base.py @@ -0,0 +1,111 @@ +""" +The base class for routing protocols. +All routing protocols should inherit from this class and implement the required methods. +""" + +from __future__ import annotations +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...topology.node import QuantumRouter + +from ...message import Message +from ...protocol import Protocol +from ...utils import log + + +# type labels for routing protocols: +ROUTING_STATIC = 'routing_static' +ROUTING_DISTRIBUTED = 'routing_distributed' + + +class RoutingProtocol(Protocol, ABC): + """Abstract base class for routing protocols. + """ + _registry: dict[str, type[RoutingProtocol]] = {} + + def __init__(self, owner: QuantumRouter, name: str, protocol_type: str): + """Constructor for routing protocol. + + Args: + owner (QuantumRouter): node protocol is attached to. + name (str): name of protocol instance. + protocol_type (str): type of the routing protocol. + """ + super().__init__(owner, name, protocol_type) + + @classmethod + def register(cls, protocol_type: str, protocol_class: type[RoutingProtocol] = None): + """Register a routing protocol class. + + Args: + protocol_type (str): type of the routing protocol. + protocol_class (type['RoutingProtocol']): class of the routing protocol. Defaults to None. + """ + if protocol_class is not None: + cls._registry[protocol_type] = protocol_class + return None + + def decorator(protocol_class: type[RoutingProtocol]): + cls._registry[protocol_type] = protocol_class + return protocol_class + + return decorator + + @classmethod + def create(cls, owner: QuantumRouter, name: str, protocol_type) -> RoutingProtocol: + """Factory method to create a routing protocol instance using the global routing type. + + Args: + owner (QuantumRouter): node protocol is attached to. + name (str): name of protocol instance. + + Returns: + RoutingProtocol: instance of the routing protocol. + """ + try: + protocol_class = cls._registry[protocol_type] + return protocol_class(owner, name) + except KeyError: + raise ValueError(f"Routing protocol type {protocol_type} not registered.") + + @abstractmethod + def init(self): + """Initialize routing protocol. Must be implemented by subclasses.""" + pass + + @abstractmethod + def received_message(self, src: str, msg: Message): + """Method to handle received messages. Must be implemented by subclasses.""" + pass + + @property + def forwarding_table(self) -> dict[str, str]: + """Returns the forwarding table. + + Returns: + dict[str, str]: forwarding table in format {name of destination node: name of next node}. + """ + return self.owner.network_manager.get_forwarding_table() + + def update_forwarding_rule(self, dst: str, next_node: str): + """Updates dst to map to next_node in forwarding table. + If dst not in forwarding table, effectively adds a new rule to the forwarding table. + + Args: + dst (str): name of destination node. + next_node (str): name of next hop node. + """ + forwarding_table = self.forwarding_table + forwarding_table[dst] = next_node + log.logger.debug(f'Updated forwarding rule at node {self.owner.name}: {dst} -> {next_node}') + + def set_forwarding_table(self, forwarding_table: dict[str, str]): + """Sets the whole forwarding table. + + Args: + forwarding_table (dict[str, str]): forwarding table in format {name of destination node: name of next node}. + """ + self.owner.network_manager.set_forwarding_table(forwarding_table) + log.logger.debug(f'Set forwarding table at node {self.owner.name} to: {forwarding_table}') diff --git a/sequence/network_management/routing_distributed.py b/sequence/network_management/routing/routing_distributed.py similarity index 95% rename from sequence/network_management/routing_distributed.py rename to sequence/network_management/routing/routing_distributed.py index b2625c6db..1fb2e70b4 100644 --- a/sequence/network_management/routing_distributed.py +++ b/sequence/network_management/routing/routing_distributed.py @@ -3,7 +3,7 @@ This module defines the DistributedRoutingProtocol, which is an OSPF-like routing protocol for quantum networks. Also included are the message types, packets, FSM, LSDB used by the routing protocol """ - +from __future__ import annotations from collections import defaultdict from dataclasses import dataclass, field from enum import Enum, auto @@ -13,12 +13,12 @@ if TYPE_CHECKING: from sequence.topology.node import QuantumRouter -from ..constants import EPSILON, SECOND -from ..kernel.event import Event -from ..kernel.process import Process -from ..message import Message -from ..protocol import Protocol -from ..utils import log +from .routing_base import RoutingProtocol, ROUTING_DISTRIBUTED +from ...constants import EPSILON, SECOND +from ...kernel.event import Event +from ...kernel.process import Process +from ...message import Message +from ...utils import log class DistRoutingMsgType(Enum): @@ -270,7 +270,8 @@ def purge_withdrawn(self, now: int) -> list[str]: return to_purge -class DistributedRoutingProtocol(Protocol): +@RoutingProtocol.register(ROUTING_DISTRIBUTED) +class DistributedRoutingProtocol(RoutingProtocol): """Class to implement distributed routing protocol (OSPF-like protocol). Attributes: @@ -290,9 +291,8 @@ class DistributedRoutingProtocol(Protocol): DBD_TIMEOUT: int = SECOND // 2 # time to wait before retransmitting DBD MAX_AGE: int = 1000 * SECOND # maximum age of LSA - def __init__(self, owner: "QuantumRouter", name: str): - super().__init__(owner, name) - self.protocol_type = 'routing_distributed' + def __init__(self, owner: QuantumRouter, name: str): + super().__init__(owner, name, protocol_type=ROUTING_DISTRIBUTED) self.owner.protocols.append(self) self.lsdb = LinkStateDB() self.fsm: dict[str, NeighborFSM] = {} @@ -367,7 +367,7 @@ def send_hello(self, delay: int): for neighbor in self.link_cost.keys(): # send hello to all neighbors seen_neighbors = {n for n, fsm in self.fsm.items() if fsm.state != "Down"} hello_payload = HelloPayload(sender=self.owner.name, seen_neighbors=seen_neighbors) - hello_msg = DistRoutingMessage(DistRoutingMsgType.HELLO, "DistributedRoutingProtocol", hello_payload) + hello_msg = DistRoutingMessage(DistRoutingMsgType.HELLO, ROUTING_DISTRIBUTED, hello_payload) self.owner.send_message(neighbor, hello_msg) process = Process(self, "send_hello", [self.HELLO_INTERVAL]) @@ -410,7 +410,7 @@ def expire_lsa(self, last_originated_time: int): updated = self.lsdb.install(withdrawal, now) if updated: forwarding_table = self.run_spf() - self.owner.network_manager.set_forwarding_table(forwarding_table) + self.set_forwarding_table(forwarding_table) self.flood_to_all_neighbors(withdrawal) self.lsdb.purge_withdrawn(now) @@ -531,7 +531,7 @@ def send_dbd(self, neighbor: str) -> DistRoutingMessage: if self.get_age(lsa) < self.MAX_AGE: summaries.append(lsa.header) dbd_payload = DBDPayload(sender=self.owner.name, summaries=summaries) - dbd_msg = DistRoutingMessage(DistRoutingMsgType.DBD, "DistributedRoutingProtocol", dbd_payload) + dbd_msg = DistRoutingMessage(DistRoutingMsgType.DBD, ROUTING_DISTRIBUTED, dbd_payload) self.owner.send_message(neighbor, dbd_msg) return dbd_msg @@ -541,7 +541,7 @@ def originate_and_flood(self): # install into own LSDB, always newer for self-originated LSA self.lsdb.install(lsa, self.owner.timeline.now()) forwarding_table = self.run_spf() # recompute routes - self.owner.network_manager.set_forwarding_table(forwarding_table) + self.set_forwarding_table(forwarding_table) self.flood_to_all_neighbors(lsa) def originate(self) -> LSA: @@ -653,17 +653,6 @@ def get_age(self, item: LSA | LSAHeader) -> int: else: raise ValueError("item must be LSA or LSAHeader") - def update_forwarding_rule(self, dst: str, next_node: str): - """Updates dst to map to next_node in forwarding table. - If dst not in forwarding table, add new rule. - - Args: - dst (str): name of destination node. - next_node (str): name of next hop node. - """ - forwarding_table = self.owner.network_manager.get_forwarding_table() - forwarding_table[dst] = next_node - def flood_to_all_neighbors(self, lsa: LSA, exclude_neighbor: str | None = None): """Flood LSA to all neighbors with 2-way adjacency. An LSU message containing the LSA is sent to each neighbor, except the excluded neighbor if specified. @@ -677,7 +666,7 @@ def flood_to_all_neighbors(self, lsa: LSA, exclude_neighbor: str | None = None): if neighbor == exclude_neighbor: continue lsu_payload = LSUPayload(sender=self.owner.name, lsas=[lsa]) - lsu_msg = DistRoutingMessage(DistRoutingMsgType.LSU, "DistributedRoutingProtocol", lsu_payload) + lsu_msg = DistRoutingMessage(DistRoutingMsgType.LSU, ROUTING_DISTRIBUTED, lsu_payload) self.owner.send_message(neighbor, lsu_msg) def handle_dbd(self, src: str, payload: DBDPayload): @@ -721,7 +710,7 @@ def handle_dbd(self, src: str, payload: DBDPayload): fsm.pending_requested = set(requested) self.set_state(src, "Loading") lsr_payload = LSRPayload(sender=self.owner.name, requested=requested) - lsr_msg = DistRoutingMessage(DistRoutingMsgType.LSR, "DistributedRoutingProtocol", lsr_payload) + lsr_msg = DistRoutingMessage(DistRoutingMsgType.LSR, ROUTING_DISTRIBUTED, lsr_payload) self.owner.send_message(src, lsr_msg) else: # clear the pending requested LSAs advance the neighbor to Full state because DBs are in sync @@ -748,7 +737,7 @@ def handle_lsr(self, src: str, payload: LSRPayload): lsas_to_send.append(lsa) if lsas_to_send: lsu_payload = LSUPayload(sender=self.owner.name, lsas=lsas_to_send) - lsu_msg = DistRoutingMessage(DistRoutingMsgType.LSU, "DistributedRoutingProtocol", lsu_payload) + lsu_msg = DistRoutingMessage(DistRoutingMsgType.LSU, ROUTING_DISTRIBUTED, lsu_payload) self.owner.send_message(src, lsu_msg) def handle_lsu(self, src: str, payload: LSUPayload): @@ -785,10 +774,10 @@ def handle_lsu(self, src: str, payload: LSUPayload): self.set_state(neighbor, "Full") if lsdb_updated: # recompute routes if LSDB is updated forwarding_table = self.run_spf() - self.owner.network_manager.set_forwarding_table(forwarding_table) + self.set_forwarding_table(forwarding_table) # send LSAck back to sender, even if no LSAs are updated (empty acks list) lsack_payload = LSAckPayload(sender=self.owner.name, acks=acks) - lsack_msg = DistRoutingMessage(DistRoutingMsgType.LSAck, "DistributedRoutingProtocol", lsack_payload) + lsack_msg = DistRoutingMessage(DistRoutingMsgType.LSAck, ROUTING_DISTRIBUTED, lsack_payload) self.owner.send_message(src, lsack_msg) def handle_lsack(self, src: str, payload: LSAckPayload): diff --git a/sequence/network_management/routing/routing_static.py b/sequence/network_management/routing/routing_static.py new file mode 100644 index 000000000..031580964 --- /dev/null +++ b/sequence/network_management/routing/routing_static.py @@ -0,0 +1,41 @@ +"""Definition of Static Routing protocol. + +This module defines the StaticRouting protocol, which uses a pre-generated static routing table to direct reservation hops. +Routing tables may be created manually, or generated and installed automatically by the `Topology` class. +""" + +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ...topology.node import QuantumRouter + +from .routing_base import RoutingProtocol, ROUTING_STATIC +from ...message import Message + + +@RoutingProtocol.register(ROUTING_STATIC) +class StaticRoutingProtocol(RoutingProtocol): + """Class to update forwarding table manually. + + The `StaticRoutingProtocol` class writes to the forwarding table (from the `NetworkManager`). + Static in this context means that the forwarding table is manually configured (by a network administrator), + not automatically updated via a routing protocol (i.e. computer program). + """ + + def __init__(self, owner: QuantumRouter, name: str): + """Constructor for routing protocol. + + Args: + owner (QuantumRouter): node protocol is attached to. + name (str): name of protocol instance. + """ + super().__init__(owner, name, protocol_type=ROUTING_STATIC) + + def init(self): + pass + + def received_message(self, src: str, msg: "Message"): + """Method to directly receive messages from node (should not be used).""" + + raise Exception("StaticRouting protocol should not call this function") diff --git a/sequence/network_management/routing_static.py b/sequence/network_management/routing_static.py deleted file mode 100644 index b6ae2fd04..000000000 --- a/sequence/network_management/routing_static.py +++ /dev/null @@ -1,79 +0,0 @@ -"""Definition of Static Routing protocol. - -This module defines the StaticRouting protocol, which uses a pre-generated static routing table to direct reservation hops. -Routing tables may be created manually, or generated and installed automatically by the `Topology` class. -""" - - -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from ..topology.node import Node - -from ..message import Message -from ..protocol import Protocol -from ..utils import log - - -class StaticRoutingProtocol(Protocol): - """Class to update forwarding table manually. - - The `StaticRoutingProtocol` class writes to the forwarding table (from the `NetworkManager`). - Static in this context means that the forwarding table is manually configured (by a network administrator), - not automatically updated via a routing protocol (i.e. computer program). - - Attributes: - owner (Node): node that protocol instance is attached to. - name (str): label for protocol instance. - """ - - def __init__(self, owner: "Node", name: str): - """Constructor for routing protocol. - - Args: - owner (Node): node protocol is attached to. - name (str): name of protocol instance. - """ - super().__init__(owner, name) - self.protocol_type = 'routing_static' - - @property - def forwarding_table(self) -> dict[str, str]: - """Returns the forwarding table. - - Returns: - dict[str, str]: forwarding table in format {name of destination node: name of next node}. - """ - return self.owner.network_manager.get_forwarding_table() - - def add_forwarding_rule(self, dst: str, next_node: str): - """Adds mapping {dst: next_node} to forwarding table. - - Args: - dst (str): name of destination node. - next_node (str): name of next hop node. - """ - forwarding_table = self.forwarding_table - if dst not in forwarding_table: - forwarding_table[dst] = next_node - log.logger.info(f'Added forwarding rule at node {self.owner.name}: {dst} -> {next_node}') - - def update_forwarding_rule(self, dst: str, next_node: str): - """updates dst to map to next_node in forwarding table. - - Args: - dst (str): name of destination node. - next_node (str): name of next hop node. - """ - forwarding_table = self.forwarding_table - if dst in forwarding_table: - forwarding_table[dst] = next_node - log.logger.info(f'Updated forwarding rule at node {self.owner.name}: {dst} -> {next_node}') - - def received_message(self, src: str, msg: "Message"): - """Method to directly receive messages from node (should not be used).""" - - raise Exception("StaticRouting protocol should not call this function") - - def init(self): - pass diff --git a/sequence/network_management/rsvp.py b/sequence/network_management/rsvp.py index 9d474b56e..d0614084a 100644 --- a/sequence/network_management/rsvp.py +++ b/sequence/network_management/rsvp.py @@ -1,12 +1,13 @@ -from sequence.network_management.memory_timecard import MemoryTimeCard +from __future__ import annotations from enum import Enum, auto from typing import TYPE_CHECKING -from .reservation import Reservation if TYPE_CHECKING: from ..topology.node import QuantumRouter +from .memory_timecard import MemoryTimeCard +from .reservation import Reservation from ..message import Message from ..protocol import StackProtocol @@ -71,7 +72,7 @@ class RSVPProtocol(StackProtocol): accepted_reservations (list[Reservation]): list of all approved reservation requests. """ - def __init__(self, owner: "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: diff --git a/sequence/protocol.py b/sequence/protocol.py index ff3d99f97..92c8243e9 100644 --- a/sequence/protocol.py +++ b/sequence/protocol.py @@ -16,22 +16,23 @@ class Protocol(ABC): """Abstract protocol class for code running on network nodes. Attributes: - own (Node): node protocol is attached to. + owner (Node): node protocol is attached to. name (str): label for protocol instance. protocol_type (str): type of protocol instance (e.g. 'single_heralded'). """ - def __init__(self, owner: "Node", name: str): + def __init__(self, owner: "Node", name: str, protocol_type: str = ""): """Constructor for protocol. Args: owner (Node): node protocol is attached to. name (str): name of protocol instance. + protocol_type (str): type of protocol instance. """ self.owner = owner self.name = name - self.protocol_type = None + self.protocol_type = protocol_type @abstractmethod def received_message(self, src: str, msg: "Message"): @@ -39,6 +40,9 @@ def received_message(self, src: str, msg: "Message"): pass + def init(self): + pass + def __str__(self) -> str: return self.name @@ -49,7 +53,7 @@ class StackProtocol(Protocol): Adds interfaces for push and pop functions. Attributes: - own (Node): node protocol is attached to. + owner (Node): node protocol is attached to. name (str): label for protocol instance. upper_protocols (list[StackProtocol]): Protocols to pop to. lower_protocols (list[StackProtocol]): Protocols to push to. @@ -59,7 +63,7 @@ def __init__(self, owner: "Node", name: str): """Constructor for stack protocol class. Args: - own (Node): node protocol is attached to. + owner (Node): node protocol is attached to. name (str): name of protocol instance. """ diff --git a/sequence/resource_management/action_condition_set.py b/sequence/resource_management/action_condition_set.py index e9bd3ae5f..13b8d2f00 100644 --- a/sequence/resource_management/action_condition_set.py +++ b/sequence/resource_management/action_condition_set.py @@ -265,7 +265,7 @@ def ep_rule_condition_await(memory_info: MemoryInfo, _manager: MemoryManager, ar purification_mode = args["purification_mode"] if purification_mode == "until_target": - if (memory_info.index in memory_indices + if (memory_info.index in memory_indices and memory_info.state in ["ENTANGLED", "PURIFIED"] and memory_info.fidelity < fidelity): return [memory_info] @@ -344,7 +344,7 @@ def es_rule_action_A(memories_info: list[MemoryInfo], _args: Arguments) -> Actio # es_succ_prob = args["es_succ_prob"] # es_degradation = args["es_degradation"] memories = [info.memory for info in memories_info] - protocol = EntanglementSwappingA(TempNode, f"ESA.{memories[0].name}.{memories[1].name}", memories[0], memories[1]) + protocol = EntanglementSwappingA.create(TempNode, f"ESA.{memories[0].name}.{memories[1].name}", memories[0], memories[1]) dsts = [info.remote_node for info in memories_info] req_funcs: list[RequestFunction | None] = [es_match_func, es_match_func] req_args = [{"target_memo": memories_info[0].remote_memo}, {"target_memo": memories_info[1].remote_memo}] @@ -366,7 +366,7 @@ def es_rule_action_B(memories_info: list[MemoryInfo], _args: Arguments) -> Actio """ memories = [info.memory for info in memories_info] memory = memories[0] - protocol = EntanglementSwappingB(TempNode, "ESB." + memory.name, memory) + protocol = EntanglementSwappingB.create(TempNode, "ESB." + memory.name, memory) return protocol, [None], [None], [None] @@ -419,8 +419,11 @@ def es_rule_condition_A(memory_info: MemoryInfo, memory_manager: MemoryManager, def es_rule_condition_B_end(memory_info: MemoryInfo, _manager: MemoryManager, args: Arguments) -> list[MemoryInfo]: - """Condition function used by the EntanglementSwappingB protocol on either the responder or initiator nodes. + """Condition function used by the EntanglementSwappingB protocol on either the responder or initiator nodes, aka end nodes. + Example: A - B --- Z + For end node A, the remote node is B, and the target node is Z (also an end node). + Args: memory_info: the memory info to be checked _manager: the memory manager to get other memory info (not used in this condition function) @@ -431,7 +434,7 @@ def es_rule_condition_B_end(memory_info: MemoryInfo, _manager: MemoryManager, ar list[MemoryInfo]: the list of memory info that satisfy the condition """ 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 + target_remote = args["target_remote"] fidelity = args["fidelity"] if (memory_info.state in ["ENTANGLED", "PURIFIED"] and memory_info.index in memory_indices diff --git a/sequence/resource_management/resource_manager.py b/sequence/resource_management/resource_manager.py index 17d22ab73..7965bc426 100644 --- a/sequence/resource_management/resource_manager.py +++ b/sequence/resource_management/resource_manager.py @@ -7,7 +7,7 @@ from __future__ import annotations from enum import Enum, auto -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from collections.abc import Callable from .action_condition_set import ( @@ -51,22 +51,24 @@ class ResourceManagerMsgType(Enum): RESPONSE = auto() RELEASE_PROTOCOL = auto() RELEASE_MEMORY = auto() + EARLY_EXPIRE = auto() class ResourceManagerMessage(Message): """Message for resource manager communication. - There are four types of ResourceManagerMessage: + There are five types of ResourceManagerMessage: * REQUEST: request eligible protocols from remote resource manager to pair entanglement protocols. * RESPONSE: approve or reject received request. * RELEASE_PROTOCOL: release the protocol on the remote node * RELEASE_MEMORY: release the memory on the remote node + * EARLY_EXPIRE: expire rules before the reservation's end_time, which is used when the reservation is finished before end_time. 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. + ini_memories_name (list[str]): a list of names of the memories. 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). @@ -76,23 +78,23 @@ def __init__(self, msg_type: ResourceManagerMsgType, **kwargs): super().__init__(msg_type, "resource_manager") self.ini_protocol_name: str = kwargs["protocol"] self.ini_node_name: str = kwargs["node"] - self.ini_memories_name: str = kwargs["memories"] + self.ini_memories_name: list[str] = kwargs["memories"] match self.msg_type: case ResourceManagerMsgType.REQUEST: self.req_condition_func = kwargs["req_condition_func"] self.req_args = kwargs["req_args"] - case 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"] - case ResourceManagerMsgType.RELEASE_PROTOCOL: self.protocol = kwargs["protocol"] case ResourceManagerMsgType.RELEASE_MEMORY: self.memory = kwargs["memory_id"] + case ResourceManagerMsgType.EARLY_EXPIRE: + self.reservation = kwargs["reservation"] case _: raise Exception(f"ResourceManagerMessage gets unknown type of message: {str(self.msg_type)}") @@ -108,6 +110,8 @@ def __str__(self) -> str: base += f', release_protocol={self.protocol}' case ResourceManagerMsgType.RELEASE_MEMORY: base += f', release_memory={self.memory}' + case ResourceManagerMsgType.EARLY_EXPIRE: + base += f', reservation={self.reservation}' case _: raise Exception(f'ResourceManagerMessage got an unknown type of message: {str(self.msg_type)}') return base @@ -145,6 +149,9 @@ def __init__(self, owner: QuantumRouter, memory_array_name: str): self.waiting_protocols = [] # Protocols that are waiting request from remote resource self.memory_to_protocol_map = {} + def init(self): + pass + def generate_load_rules(self, path: list[str], reservation: Reservation, timecards: list[MemoryTimeCard], memory_array_name: str): """Generate and load rules for a given reservation. @@ -310,7 +317,7 @@ def expire(self, rule: Rule) -> None: for memory in protocol.memories: self.update(protocol, memory, MemoryInfo.RAW) - def update(self, protocol: Optional[EntanglementProtocol], memory: Memory, state: str) -> None: + def update(self, protocol: EntanglementProtocol | None, memory: Memory, state: str) -> None: """Method to update state of memory after completion of entanglement management protocol. Args: @@ -385,7 +392,7 @@ def send_request(self, protocol: EntanglementProtocol, req_dst: str | None, def received_message(self, src: str, msg: ResourceManagerMessage) -> None: """Method to receive resource manager messages. - Messages come in 4 types, as detailed in the `ResourceManagerMessage` class. + Messages come in 5 types, as detailed in the `ResourceManagerMessage` class. Args: src (str): name of the node that sent the message. @@ -393,70 +400,73 @@ def received_message(self, src: str, msg: ResourceManagerMessage) -> None: """ log.logger.debug(f"{self.owner.name} resource manager receive message from {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) - 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) - self.owner.send_message(src, new_msg) - self.waiting_protocols.remove(protocol) - self.owner.protocols.append(protocol) - protocol.start() - 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 - - protocol: EntanglementProtocol | None = None - for p in self.pending_protocols: - if p.name == protocol_name: - protocol = p - break - 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) # pairing (cost one round-trip-time) - if protocol.is_ready(): - self.pending_protocols.remove(protocol) + match msg.msg_type: + case 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) + 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) + self.owner.send_message(src, new_msg) + self.waiting_protocols.remove(protocol) self.owner.protocols.append(protocol) - protocol.owner = self.owner protocol.start() - else: - protocol.rule.protocols.remove(protocol) - for memory in protocol.memories: - memory.detach(protocol) - memory.attach(memory.memory_array) - info = self.memory_manager.get_info_by_memory(memory) - if info.remote_node is None: - self.update(None, memory, MemoryInfo.RAW) - else: - self.update(None, memory, MemoryInfo.ENTANGLED) - self.pending_protocols.remove(protocol) + 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) + + case ResourceManagerMsgType.RESPONSE: + protocol_name = msg.ini_protocol_name + protocol: EntanglementProtocol | None = None + for p in self.pending_protocols: + if p.name == protocol_name: + protocol = p + break + else: # no matched pending protocols + if msg.is_approved: + self.release_remote_protocol(src, msg.paired_protocol) + return - elif msg.msg_type is ResourceManagerMsgType.RELEASE_PROTOCOL: - for p in self.owner.protocols: - if p.name == msg.protocol: - p.release() + if msg.is_approved: + 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.owner = self.owner + protocol.start() + else: + protocol.rule.protocols.remove(protocol) + for memory in protocol.memories: + memory.detach(protocol) + memory.attach(memory.memory_array) + info = self.memory_manager.get_info_by_memory(memory) + if info.remote_node is None: + self.update(None, memory, MemoryInfo.RAW) + else: + self.update(None, memory, MemoryInfo.ENTANGLED) + self.pending_protocols.remove(protocol) - elif msg.msg_type is ResourceManagerMsgType.RELEASE_MEMORY: - target_id = msg.memory - for protocol in self.owner.protocols: - for memory in protocol.memories: - if memory.name == target_id: - protocol.release() - return + case ResourceManagerMsgType.RELEASE_PROTOCOL: + for p in self.owner.protocols: + if p.name == msg.protocol: + p.release() + + case ResourceManagerMsgType.RELEASE_MEMORY: + target_id = msg.memory + for protocol in self.owner.protocols: + for memory in protocol.memories: + if memory.name == target_id: + protocol.release() + return + + case ResourceManagerMsgType.EARLY_EXPIRE: + self.expire_rules_by_reservation(msg.reservation) def memory_expire(self, memory: Memory): """Method to receive memory expiration events.""" @@ -495,6 +505,25 @@ def release_remote_memory(self, dst: str, memory_id: str) -> None: node="", memories=[], memory_id=memory_id) self.owner.send_message(dst, msg) + def expire_remote_rules(self, dst: str, reservation: Reservation) -> None: + """Expire rules (associated with the reservation) on distant nodes. + + This is used when the request is finished before end_time. + The rules associated with the request's reservation should be expired when the request is finished. + Otherwise the quantum network keeps generating entanglement till the end_time, which is not desired. + + Typically, the initiator will call this method to expire the rules on the intermediate nodes. + Meanwhile, the initiator and responder will directly call self.expire_rules_by_reservation() + to expire the rules on their own node, since they are aware of the reservation is finished. + + Args: + dst (str): name of destination node. + reservation (Reservation): the rules created by this reservation will expire + """ + msg = ResourceManagerMessage(ResourceManagerMsgType.EARLY_EXPIRE, reservation=reservation, + protocol="", node="", memories=[]) + self.owner.send_message(dst, msg) + def __str__(self) -> str: return self.name diff --git a/sequence/resource_management/rule_manager.py b/sequence/resource_management/rule_manager.py index f191ae179..a1522af4c 100644 --- a/sequence/resource_management/rule_manager.py +++ b/sequence/resource_management/rule_manager.py @@ -4,7 +4,7 @@ This is achieved through rules (also defined in this module), which if met define a set of actions to take. """ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from collections.abc import Callable from ..utils import log if TYPE_CHECKING: @@ -13,7 +13,7 @@ from .resource_manager import ResourceManager from ..network_management.reservation import Reservation -ActionReturn = tuple["EntanglementProtocol", list[Optional[str]], list[Optional[Callable[[list["EntanglementProtocol"], dict[str, Any]], Optional["EntanglementProtocol"]]]], list[Optional[dict[str, Any]]]] +ActionReturn = tuple["EntanglementProtocol", list[str | None], list[Callable[[list["EntanglementProtocol"], dict[str, Any]], "EntanglementProtocol" | None] | None], list[dict[str, Any] | None]] ActionFunc = Callable[[list["MemoryInfo"], dict[str, Any]], ActionReturn] @@ -87,7 +87,6 @@ def expire(self, rule: "Rule") -> list["EntanglementProtocol"]: else: log.logger.info(f'{self.resource_manager.owner} rule not exist: {rule}') return rule.protocols - def get_memory_manager(self) -> MemoryManager: assert self.resource_manager is not None @@ -135,7 +134,7 @@ def __init__(self, priority: int, action: ActionFunc, condition: ConditionFunc, self.condition_args: Arguments = condition_args self.protocols: list[EntanglementProtocol] = [] self.rule_manager = None - self.reservation: Optional["Reservation"] = None + self.reservation: "Reservation" | None = None def __str__(self): action_name_list = str(self.action).split(' ') diff --git a/sequence/topology/dqc_net_topo.py b/sequence/topology/dqc_net_topo.py index 9cbbba072..7bbeae781 100644 --- a/sequence/topology/dqc_net_topo.py +++ b/sequence/topology/dqc_net_topo.py @@ -6,7 +6,6 @@ from ..kernel.timeline import Timeline from .node import BSMNode from ..constants import SPEED_OF_LIGHT -from typing import Dict, List, Type from .node import Node, DQCNode @@ -191,7 +190,7 @@ def _generate_forwarding_table(self, config: dict): next_hop = path[1] # routing protocol locates at the bottom of the stack routing_protocol = src.network_manager.get_routing_protocol() - routing_protocol.add_forwarding_rule(dst_name, next_hop) + routing_protocol.update_forwarding_rule(dst_name, next_hop) except exception.NetworkXNoPath: pass diff --git a/sequence/topology/node.py b/sequence/topology/node.py index d81b4b091..2bad3a5d8 100644 --- a/sequence/topology/node.py +++ b/sequence/topology/node.py @@ -4,8 +4,10 @@ All node types inherit from the base Node type, which inherits from Entity. Node types can be used to collect all the necessary hardware and software for a network usage scenario. """ +from __future__ import annotations + from math import inf -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any import numpy as np @@ -197,14 +199,14 @@ def get_components_by_type(self, component_type: str | type) -> list: return [comp for comp in self.components.values() if isinstance(comp, component_type)] return [] - def get_component_by_name(self, name: str) -> Optional["Entity"]: + def get_component_by_name(self, name: str) -> Entity | None: """Method to return the component with the given name. Args: name (str): The name of the component to retrieve. Returns: - Optional[Entity]: The component with the given name, or None if not found. + Entity | None: The component with the given name, or None if not found. """ return self.timeline.get_entity_by_name(name) @@ -390,10 +392,11 @@ def set_down(self, down: bool): self.down = down def init(self): - """Method to initialize quantum router node. """ - if self.network_manager.routing_protocol is not None: - self.network_manager.routing_protocol.init() + Method to initialize the managers. + """ + self.resource_manager.init() + self.network_manager.init() def add_bsm_node(self, bsm_name: str, router_name: str): """Method to record connected BSM nodes diff --git a/sequence/topology/router_net_topo.py b/sequence/topology/router_net_topo.py index 7a6a552a7..1a30fe010 100644 --- a/sequence/topology/router_net_topo.py +++ b/sequence/topology/router_net_topo.py @@ -2,11 +2,10 @@ import numpy as np from networkx import Graph, dijkstra_path, exception -from ..network_management.routing_distributed import DistributedRoutingProtocol -from ..network_management.routing_static import StaticRoutingProtocol +from ..network_management.routing import DistributedRoutingProtocol, StaticRoutingProtocol from .topology import Topology as Topo from ..kernel.timeline import Timeline -from ..kernel.quantum_manager import KET_STATE_FORMALISM, QuantumManager +from ..kernel.quantum_manager import KET_VECTOR_FORMALISM, QuantumManager from .node import BSMNode, QuantumRouter from ..constants import SPEED_OF_LIGHT @@ -56,7 +55,7 @@ def _load(self, filename: str): def _add_timeline(self, config: dict): stop_time = config.get(Topo.STOP_TIME, 10 ** 23) - formalism = config.get(Topo.FORMALISM, KET_STATE_FORMALISM) + formalism = config.get(Topo.FORMALISM, KET_VECTOR_FORMALISM) truncation = config.get(Topo.TRUNC, 1) QuantumManager.set_global_manager_formalism(formalism) self.tl = Timeline(stop_time=stop_time, truncation=truncation) @@ -209,7 +208,7 @@ def _generate_forwarding_table(self, config: dict): next_hop = path[1] # routing protocol locates at the bottom of the stack routing_protocol = src.network_manager.get_routing_protocol() - routing_protocol.add_forwarding_rule(dst_name, next_hop) + routing_protocol.update_forwarding_rule(dst_name, next_hop) except exception.NetworkXNoPath: pass diff --git a/sequence/topology/topology.py b/sequence/topology/topology.py index 4e4417e86..492f507e5 100644 --- a/sequence/topology/topology.py +++ b/sequence/topology/topology.py @@ -6,7 +6,7 @@ """ from abc import ABC, abstractmethod from collections import defaultdict -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING if TYPE_CHECKING: from ..kernel.timeline import Timeline diff --git a/sequence/utils/config_generator.py b/sequence/utils/config_generator.py index d23091e81..4598fc482 100644 --- a/sequence/utils/config_generator.py +++ b/sequence/utils/config_generator.py @@ -2,10 +2,10 @@ Examples of using this module is in https://github.com/sequence-toolbox/SeQUeNCe/tree/master/utils/json_config_generators """ -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.topology.dqc_net_topo import DQCNetTopo -from sequence.constants import SECOND, MILLISECOND +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..topology.dqc_net_topo import DQCNetTopo +from ..constants import SECOND, MILLISECOND def add_default_args(parser): diff --git a/sequence/utils/config_generator_as_memo_num.py b/sequence/utils/config_generator_as_memo_num.py new file mode 100644 index 000000000..2230ddf78 --- /dev/null +++ b/sequence/utils/config_generator_as_memo_num.py @@ -0,0 +1,179 @@ +""" +This module generates JSON config files for Internet Autonomous System Network +""" + +from collections import defaultdict +import networkx as nx +from networkx import dijkstra_path +import argparse +import json +import numpy as np +import random + +from .config_generator import add_default_args, generate_bsm_links, generate_classical, final_config, router_name_func, bsm_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo + + +SEED = 1 +random.seed(SEED) + + +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) + +def main(): +# example: python config_generator_as_memo_num.py 20 0 1 10 1 0.0002 1 -o as_20.json -s 10 + + parser = argparse.ArgumentParser() + parser.add_argument('net_size', type=int, help="net_size (int) - Number of routers") + parser.add_argument('seed', type=int, help="seed (int) - Indicator of random number generation state.") + parser.add_argument('alpha', type=int, help="Alpha for exponential distribution of flows") + parser = add_default_args(parser) + args = parser.parse_args() + + NET_SIZE = args.net_size + NET_SEED = args.seed + ALPHA = args.alpha + FLOW_MEMO_SIZE = args.memo_size + QC_LEN = args.qc_length + QC_ATT = args.qc_atten + CC_DELAY = args.cc_delay + + graph = nx.random_internet_as_graph(NET_SIZE, NET_SEED) + paths = [] + for src in graph.nodes: + for dst in graph.nodes: + if dst >= src: + continue + path = dijkstra_path(graph, src, dst) + hop_num = len(path) - 2 + while len(paths) <= hop_num: + paths.append([]) + paths[hop_num].append(tuple(path)) + paths[hop_num].append(tuple(path[::-1])) + + MAX_HOP = len(paths) + TOTAL_FLOW_NUM = NET_SIZE + FLOW_NUMS = [int(get_exp_dis_prob(i, i + 1, ALPHA) * TOTAL_FLOW_NUM) for i in range(MAX_HOP)] + FLOW_NUMS[-1] = TOTAL_FLOW_NUM - sum(FLOW_NUMS[:-1]) + + selected_paths = {} + hops_counter = [0 for i in range(MAX_HOP)] + nodes_caps = [0 for i in range(NET_SIZE)] + + for hop_num in range(MAX_HOP - 1, -1, -1): + flow_num = FLOW_NUMS[hop_num] + for f_index in range(flow_num): + while len(paths[hop_num]) > 0: + sample_index = random.choice(list(range(len(paths[hop_num])))) + sample_path = paths[hop_num][sample_index] + paths[hop_num].remove(sample_path) + + if sample_path[0] in selected_paths: + continue + + selected_paths[sample_path[0]] = sample_path + for i, node in enumerate(sample_path): + if i == 0 or i == len(sample_path) - 1: + nodes_caps[node] += 1 * FLOW_MEMO_SIZE + else: + nodes_caps[node] += 2 * FLOW_MEMO_SIZE + + hops_counter[hop_num] += 1 + break + + unused_nodes = [] + for i, c in enumerate(nodes_caps): + if c == 0: + unused_nodes.append(i) + + while unused_nodes: + n1 = unused_nodes.pop() + if unused_nodes: + n2 = unused_nodes.pop() + else: + samples = np.random.choice(list(range(NET_SIZE)), 2) + if samples[0] != n1: + n2 = samples[0] + else: + n2 = samples[1] + + if n2 > n1: + n1, n2 = n2, n1 + + path = dijkstra_path(graph, n1, n2) + hops_counter[len(path) - 2] += 1 + + for i, node in enumerate(path): + if i == 0 or i == len(path) - 1: + nodes_caps[node] += 1 * FLOW_MEMO_SIZE + else: + nodes_caps[node] += 2 * FLOW_MEMO_SIZE + + if n1 not in selected_paths: + selected_paths[n1] = path + else: + selected_paths[n2] = path[::-1] + + mapping = {} + node_memo_size = {} + + for i in range(NET_SIZE): + mapping[i] = router_name_func(i) + node_memo_size[router_name_func(i)] = nodes_caps[i] + nx.relabel_nodes(graph, mapping, copy=False) + + output_dict = {} + + router_names = [router_name_func(i) for i in range(NET_SIZE)] + nodes = [{Topology.NAME: name, + Topology.TYPE: RouterNetTopo.QUANTUM_ROUTER, + Topology.SEED: i, + RouterNetTopo.MEMO_ARRAY_SIZE: node_memo_size[name]} + for i, name in enumerate(router_names)] + + # add bsm links + cchannels, qchannels, bsm_nodes = generate_bsm_links(graph, args, bsm_name_func) + nodes += bsm_nodes + output_dict[Topology.ALL_NODE] = nodes + output_dict[Topology.ALL_Q_CHANNEL] = qchannels + + # add router-to-router classical channels + router_cchannels = generate_classical(router_names, args.cc_delay) + cchannels += router_cchannels + output_dict[Topology.ALL_C_CHANNEL] = cchannels + + # write other config options + final_config(output_dict, args) + + # write final json + output_file = open(args.output, 'w') + json.dump(output_dict, output_file, indent=4) + + flows = {} + for src in selected_paths: + path = selected_paths[src] + src_name = router_name_func(src) + new_path = [router_name_func(n) for n in path] + flows[src_name] = new_path + + flow_fh = open("flow_" + args.output, 'w') + flow_info = {"flows": flows, "memo_size": FLOW_MEMO_SIZE} + json.dump(flow_info, flow_fh) + + fow_tables = defaultdict(lambda: {}) + for src in selected_paths: + path = selected_paths[src] + for i, node in enumerate(path): + if i == len(path) - 1: + break + table = fow_tables[node] + if path[-1] in table: + assert table[path[-1]] == path[i + 1] + else: + table[path[-1]] = path[i + 1] + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sequence/utils/config_generator_caveman.py b/sequence/utils/config_generator_caveman.py new file mode 100644 index 000000000..995d129ef --- /dev/null +++ b/sequence/utils/config_generator_caveman.py @@ -0,0 +1,63 @@ +"""This module generates JSON config files for networks in a caveman graph configuration. + +More information may be found here: https://mathworld.wolfram.com/CavemanGraph.html +Help information may also be obtained using the `-h` flag. + +Args: + l (int): number of cliques (clusters) in the graph. + k (int): number of nodes per clique. + 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: + -o --output (str): name of the output file (default out.json). + -s --stop (float): simulation stop time (in s) (default infinity). +""" + +import networkx as nx +import argparse +import json +import os + +from .config_generator import add_default_args, router_name_func, generate_nodes, generate_classical, generate_bsm_links, bsm_name_func, final_config +from ..topology.topology import Topology + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('l', type=int, help="l (int) - Number of cliques") + parser.add_argument('k', type=int, help="k (int) - Size of cliques") + add_default_args(parser) + args = parser.parse_args() + + graph = nx.connected_caveman_graph(args.l, args.k) + mapping = {} + NODE_NUM = args.l * args.k + for i in range(NODE_NUM): + mapping[i] = router_name_func(i) + nx.relabel_nodes(graph, mapping, copy=False) + + output_dict = {} + + router_names = [router_name_func(i) for i in range(NODE_NUM)] + nodes = generate_nodes(router_names, args.memo_size) + + cchannels, qchannels, bsm_nodes = generate_bsm_links(graph, args, bsm_name_func) + nodes += bsm_nodes + output_dict[Topology.ALL_NODE] = nodes + output_dict[Topology.ALL_Q_CHANNEL] = qchannels + + router_cchannels = generate_classical(router_names, args.cc_delay) + cchannels += router_cchannels + output_dict[Topology.ALL_C_CHANNEL] = cchannels + + 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() \ No newline at end of file diff --git a/sequence/utils/config_generator_line.py b/sequence/utils/config_generator_line.py new file mode 100644 index 000000000..5a4c517ee --- /dev/null +++ b/sequence/utils/config_generator_line.py @@ -0,0 +1,89 @@ +"""This module generates JSON config files for networks in a linear configuration. + +Help information may also be obtained using the `-h` flag. + +Args: + linear_size (int): number of nodes in the graph. + 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). +""" + +import argparse +import json +import os + +from .config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..constants import MILLISECOND + +# example: python config_generator_line.py 2 10 10 0.0002 1 -d config -o line_2.json -s 10 -gf 0.99 -mf 0.99 +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('linear_size', type=int, help='number of network nodes') + parser = add_default_args(parser) + args = parser.parse_args() + + output_dict = {} + + + router_names = [router_name_func(i) for i in range(args.linear_size)] + nodes = generate_nodes(router_names, args.memo_size) + + # generate bsm nodes + bsm_names = ["BSM_{}_{}".format(i, i + 1) + for i in range(args.linear_size - 1)] + bsm_nodes = [{Topology.NAME: bsm_name, + Topology.TYPE: RouterNetTopo.BSM_NODE, + Topology.SEED: i} + for i, bsm_name in enumerate(bsm_names)] + + nodes += bsm_nodes + output_dict[Topology.ALL_NODE] = nodes + + # generate quantum links, classical with bsm nodes + qchannels = [] + cchannels = [] + for i, bsm_name in enumerate(bsm_names): + # qchannels + qchannels.append({Topology.SRC: router_names[i], + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + qchannels.append({Topology.SRC: router_names[i + 1], + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + # cchannels + for node in [router_names[i], router_names[i + 1]]: + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: node, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + + cchannels.append({Topology.SRC: node, + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + output_dict[Topology.ALL_Q_CHANNEL] = qchannels + + # generate classical links + router_cchannels = generate_classical(router_names, args.cc_delay) + cchannels += router_cchannels + 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() \ No newline at end of file diff --git a/sequence/utils/config_generator_mesh.py b/sequence/utils/config_generator_mesh.py new file mode 100644 index 000000000..43578e13b --- /dev/null +++ b/sequence/utils/config_generator_mesh.py @@ -0,0 +1,95 @@ +"""This module generates JSON config files for a fully-connected mesh network. + +Help information may also be obtained using the `-h` flag. + +Args: + net_size (int): number of nodes in the mesh. + 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). +""" + +import os +import argparse +import json + +from .config_generator import add_default_args, generate_nodes, final_config, router_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..constants import MILLISECOND + +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() + + output_dict = {} + + router_names = [router_name_func(i) for i in range(args.net_size)] + nodes = generate_nodes(router_names, args.memo_size) + + # generate quantum links, classical links, and bsm nodes + qchannels = [] + cchannels = [] + bsm_nodes = [] + seed = 0 + for i, node1 in enumerate(router_names): + for node2 in router_names[i+1:]: + bsm_name = "BSM_{}_{}".format(node1, node2) + bsm_node = {Topology.NAME: bsm_name, + Topology.TYPE: RouterNetTopo.BSM_NODE, + Topology.SEED: seed} + bsm_nodes.append(bsm_node) + # qchannels + qchannels.append({Topology.SRC: node1, + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + qchannels.append({Topology.SRC: node2, + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + # cchannels + cchannels.append({Topology.SRC: node1, + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: node2, + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: node1, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: node2, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: node1, + Topology.DST: node2, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: node2, + Topology.DST: node1, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + seed += 1 + + 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() \ No newline at end of file diff --git a/utils/json_config_generators/config_generator_random.py b/sequence/utils/config_generator_random.py similarity index 95% rename from utils/json_config_generators/config_generator_random.py rename to sequence/utils/config_generator_random.py index 2dcc6401e..71d1ae562 100644 --- a/utils/json_config_generators/config_generator_random.py +++ b/sequence/utils/config_generator_random.py @@ -22,10 +22,10 @@ import random from networkx.generators.geometric import waxman_graph -from sequence.utils.config_generator import add_default_args, generate_nodes, final_config, router_name_func -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.constants import MILLISECOND +from .config_generator import add_default_args, generate_nodes, final_config, router_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..constants import MILLISECOND def create_random_waxman(area_length: int, number_nodes: int, edge_density: float) -> tuple[list, list]: diff --git a/sequence/utils/config_generator_ring.py b/sequence/utils/config_generator_ring.py new file mode 100644 index 000000000..4a727d193 --- /dev/null +++ b/sequence/utils/config_generator_ring.py @@ -0,0 +1,93 @@ +"""This module generates JSON config files for networks in a ring configuration. + +Help information may also be obtained using the `-h` flag. + +Args: + ring_size (int): number of nodes in the network ring. + 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). +""" + +import argparse +import json +import os + +from .config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..constants import MILLISECOND + +def main(): + # python config_generator_ring.py 4 10 1 0.0002 1 -o ring_topo.json -s 100 + + # parse args + parser = argparse.ArgumentParser() + parser.add_argument('ring_size', type=int, help='number of network nodes') + parser = add_default_args(parser) + args = parser.parse_args() + + output_dict = {} + + # get node names, processes + router_names = [router_name_func(i) for i in range(args.ring_size)] + nodes = generate_nodes(router_names, args.memo_size) + + # 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} + for i, bsm_name in enumerate(bsm_names)] + + + for i, bsm_name in enumerate(bsm_names): + # qchannels + qchannels.append({Topology.SRC: router_names[i % args.ring_size], + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + qchannels.append({Topology.SRC: router_names[(i + 1) % args.ring_size], + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + # cchannels + cchannels.append({Topology.SRC: router_names[i % args.ring_size], + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: router_names[(i + 1) % args.ring_size], + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: router_names[i % args.ring_size], + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: router_names[(i + 1) % args.ring_size], + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + + nodes += bsm_nodes + output_dict[Topology.ALL_NODE] = nodes + output_dict[Topology.ALL_Q_CHANNEL] = qchannels + + # generate classical links (classical channels) + router_cchannels = generate_classical(router_names, args.cc_delay) + cchannels += router_cchannels + 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() \ No newline at end of file diff --git a/sequence/utils/config_generator_star.py b/sequence/utils/config_generator_star.py new file mode 100644 index 000000000..2d7f5871b --- /dev/null +++ b/sequence/utils/config_generator_star.py @@ -0,0 +1,100 @@ +"""This module generates JSON config files for networks in a star configuration. + +Help information may also be obtained using the `-h` flag. + +Args: + star_size (int): number of non-center network nodes (network size is star_size+1). + memo_size (int): number of memories per end node. + memo_size_center (int): number of memories on the center 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). +""" + +import argparse +import json +import os + +from .config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..constants import MILLISECOND + +def main(): + # parse args + parser = argparse.ArgumentParser() + parser.add_argument('star_size', type=int, help='number of non-center network nodes') + parser.add_argument('memo_size_center', type=int, help='number of memories on center node') + parser = add_default_args(parser) + args = parser.parse_args() + + output_dict = {} + + + # generate nodes, with middle having different num + center_name = "router_center" + router_names = [router_name_func(i) for i in range(args.star_size + 1)] + router_names[-1] = center_name + + nodes = generate_nodes(router_names, args.memo_size) + for node in nodes: + if node[Topology.NAME] == center_name: + node[RouterNetTopo.MEMO_ARRAY_SIZE] = args.memo_size_center + break + + # generate quantum links + qchannels = [] + cchannels = [] + bsm_names = ["BSM_" + str(i) for i in range(args.star_size)] + bsm_nodes = [{Topology.NAME: bsm_name, + Topology.TYPE: RouterNetTopo.BSM_NODE, + Topology.SEED: i} + for i, bsm_name in enumerate(bsm_names)] + + for node_name, bsm_name in zip(router_names, bsm_names): + # qchannels + qchannels.append({Topology.SRC: node_name, + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + qchannels.append({Topology.SRC: center_name, + Topology.DST: bsm_name, + Topology.DISTANCE: args.qc_length * 500, + Topology.ATTENUATION: args.qc_atten}) + # cchannels + cchannels.append({Topology.SRC: node_name, + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: center_name, + Topology.DST: bsm_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: node_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + cchannels.append({Topology.SRC: bsm_name, + Topology.DST: center_name, + Topology.DELAY: int(args.cc_delay * MILLISECOND)}) + + output_dict[Topology.ALL_NODE] = nodes + bsm_nodes + output_dict[Topology.ALL_Q_CHANNEL] = qchannels + + # generate classical links + router_cchannels = generate_classical(router_names, args.cc_delay) + cchannels += router_cchannels + 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_tree.py b/sequence/utils/config_generator_tree.py similarity index 67% rename from utils/json_config_generators/config_generator_tree.py rename to sequence/utils/config_generator_tree.py index b52a2981f..49b8fb0f5 100644 --- a/utils/json_config_generators/config_generator_tree.py +++ b/sequence/utils/config_generator_tree.py @@ -27,14 +27,14 @@ import json import os -from sequence.utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.constants import MILLISECOND +from .config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +from ..topology.topology import Topology +from ..topology.router_net_topo import RouterNetTopo +from ..constants import MILLISECOND -def add_branches(node_names, nodes, index, bsm_names, bsm_nodes, qchannels, cchannels): +def add_branches(args, node_names, nodes, index, bsm_names, bsm_nodes, qchannels, cchannels): node1 = node_names[index] branch_indices = [args.branches*index+i for i in range(1, args.branches+1)] @@ -74,40 +74,42 @@ def add_branches(node_names, nodes, index, bsm_names, bsm_nodes, qchannels, ccha Topology.DELAY: int(args.cc_delay * MILLISECOND)}) bsm_names, bsm_nodes, qchannels, cchannels = \ - add_branches(node_names, nodes, i, bsm_names, bsm_nodes, + add_branches(args, node_names, nodes, i, bsm_names, bsm_nodes, qchannels, cchannels) return bsm_names, bsm_nodes, qchannels, cchannels - -# parse args -parser = argparse.ArgumentParser() -parser.add_argument('tree_size', type=int, help='number of nodes in the tree') -parser.add_argument('branches', type=int, help='number of branches per node') -parser = add_default_args(parser) -args = parser.parse_args() - -output_dict = {} - -router_names = [router_name_func(i) for i in range(args.tree_size)] -nodes = generate_nodes(router_names, args.memo_size) - -# generate quantum links and bsm connections -bsm_names, bsm_nodes, qchannels, cchannels = \ - add_branches(router_names, nodes, 0, [], [], [], []) -nodes += bsm_nodes -output_dict[Topology.ALL_NODE] = nodes -output_dict[Topology.ALL_Q_CHANNEL] = qchannels - -# generate classical links -router_cchannels = generate_classical(router_names, args.cc_delay) -cchannels += router_cchannels -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) - +def main(): + # parse args + parser = argparse.ArgumentParser() + parser.add_argument('tree_size', type=int, help='number of nodes in the tree') + parser.add_argument('branches', type=int, help='number of branches per node') + parser = add_default_args(parser) + args = parser.parse_args() + + output_dict = {} + + router_names = [router_name_func(i) for i in range(args.tree_size)] + nodes = generate_nodes(router_names, args.memo_size) + + # generate quantum links and bsm connections + bsm_names, bsm_nodes, qchannels, cchannels = \ + add_branches(args, router_names, nodes, 0, [], [], [], []) + nodes += bsm_nodes + output_dict[Topology.ALL_NODE] = nodes + output_dict[Topology.ALL_Q_CHANNEL] = qchannels + + # generate classical links + router_cchannels = generate_classical(router_names, args.cc_delay) + cchannels += router_cchannels + 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() \ No newline at end of file diff --git a/utils/draw_topo.py b/sequence/utils/draw_topo.py similarity index 98% rename from utils/draw_topo.py rename to sequence/utils/draw_topo.py index 14b32b842..c68db7eac 100644 --- a/utils/draw_topo.py +++ b/sequence/utils/draw_topo.py @@ -27,7 +27,7 @@ filename = args.filename draw_middle = args.draw_middle -# determine type of network +# Determine type of network with open(args.config_file, 'r') as fh: config = load(fh) nodes = config["nodes"] diff --git a/sequence/utils/noise.py b/sequence/utils/noise.py index aaf71d72b..a63940f21 100644 --- a/sequence/utils/noise.py +++ b/sequence/utils/noise.py @@ -1,6 +1,5 @@ import numpy as np from sequence.components.circuit import Circuit -from typing import List class Noise: """ @@ -80,11 +79,11 @@ def apply_depolarizing_noise(rho: np.ndarray, p: float, qubits: list[int], keys: - p = 0: state remains unchanged, - p = 1: selected qubits become maximally mixed. - qubits : List[int] + qubits : list[int] One or two qubit indices to apply noise to. These must appear in the `keys` list. - keys : List[int] + keys : list[int] A permutation of [0, 1, ..., n-1] defining the tensor product order of qubits in `rho`. Each key corresponds to a qubit managed by QuantumManagerDensity. For example, diff --git a/tests/entanglement_management/test_swapping.py b/tests/entanglement_management/test_swapping.py index 488ee8a90..8771496c7 100644 --- a/tests/entanglement_management/test_swapping.py +++ b/tests/entanglement_management/test_swapping.py @@ -81,11 +81,11 @@ def create_scenario(state1, state2, seed_index): a1, a2, a3 = nodes memo1, memo2, memo3, memo4 = memories - es1 = EntanglementSwappingB(a1, "a1.ESb0", memo1) + es1 = EntanglementSwappingB.create(a1, "a1.ESb0", memo1) a1.protocols.append(es1) - es2 = EntanglementSwappingA(a2, "a2.ESa0", memo2, memo3) + es2 = EntanglementSwappingA.create(a2, "a2.ESa0", memo2, memo3) a2.protocols.append(es2) - es3 = EntanglementSwappingB(a3, "a3.ESb1", memo4) + es3 = EntanglementSwappingB.create(a3, "a3.ESb1", memo4) a3.protocols.append(es3) es1.set_others(es2.name, a2.name, [memo2.name, memo3.name]) @@ -527,11 +527,11 @@ def test_EntanglementSwapping(): a1, a2, a3 = nodes memo1, memo2, memo3, memo4 = memories - es1 = EntanglementSwappingB(a1, "a1.ESb%d" % i, memo1) + es1 = EntanglementSwappingB.create(a1, "a1.ESb%d" % i, memo1) a1.protocols.append(es1) - es2 = EntanglementSwappingA(a2, "a2.ESa%d" % i, memo2, memo3, success_prob=0.2) + es2 = EntanglementSwappingA.create(a2, "a2.ESa%d" % i, memo2, memo3, success_prob=0.2) a2.protocols.append(es2) - es3 = EntanglementSwappingB(a3, "a3.ESb%d" % i, memo4) + es3 = EntanglementSwappingB.create(a3, "a3.ESb%d" % i, memo4) a3.protocols.append(es3) es1.set_others(es2.name, a2.name, [memo2.name, memo3.name]) diff --git a/tests/network_management/line_topo.json b/tests/network_management/line_topo.json index 8039fff07..c829a89a4 100644 --- a/tests/network_management/line_topo.json +++ b/tests/network_management/line_topo.json @@ -1,7 +1,7 @@ { "templates": { "network_manager": { - "routing": "distributed", + "routing": "routing_distributed", "MemoryArray": { "fidelity": 1.0 } diff --git a/tests/network_management/ring_topo.json b/tests/network_management/ring_topo.json index 087c38323..62141bebb 100644 --- a/tests/network_management/ring_topo.json +++ b/tests/network_management/ring_topo.json @@ -1,7 +1,7 @@ { "templates": { "network_manager": { - "routing": "distributed", + "routing": "routing_distributed", "MemoryArray": { "fidelity": 1.0 } diff --git a/tests/network_management/test_network_manager.py b/tests/network_management/test_network_manager.py index e0646766b..e10941795 100644 --- a/tests/network_management/test_network_manager.py +++ b/tests/network_management/test_network_manager.py @@ -212,12 +212,12 @@ def test_NetworkManager(self): qc = QuantumChannel("qc_n3_m2", tl, 0, 10) qc.set_ends(n3, m2.name) - n1.network_manager.routing_protocol.add_forwarding_rule("n2", "n2") - n1.network_manager.routing_protocol.add_forwarding_rule("n3", "n2") - n2.network_manager.routing_protocol.add_forwarding_rule("n1", "n1") - n2.network_manager.routing_protocol.add_forwarding_rule("n3", "n3") - n3.network_manager.routing_protocol.add_forwarding_rule("n1", "n2") - n3.network_manager.routing_protocol.add_forwarding_rule("n2", "n2") + n1.network_manager.routing_protocol.update_forwarding_rule("n2", "n2") + n1.network_manager.routing_protocol.update_forwarding_rule("n3", "n2") + n2.network_manager.routing_protocol.update_forwarding_rule("n1", "n1") + n2.network_manager.routing_protocol.update_forwarding_rule("n3", "n3") + n3.network_manager.routing_protocol.update_forwarding_rule("n1", "n2") + n3.network_manager.routing_protocol.update_forwarding_rule("n2", "n2") tl.init() diff --git a/tests/network_management/test_reservation.py b/tests/network_management/test_reservation.py index dde0c6980..f748109dc 100644 --- a/tests/network_management/test_reservation.py +++ b/tests/network_management/test_reservation.py @@ -99,7 +99,7 @@ def test_invalid_init(self): @pytest.mark.unit def test_string_repr(self, std_reservation): - assert str(std_reservation) == '|initiator=a; responder=b; start_time=10; end_time=20; memory_size=5; target_fidelity=0.9; entanglement_number=1; identity=0|' + assert str(std_reservation) == '|identity=0; initiator=a; responder=b; path=[]; start_time=10; end_time=20; target_fidelity=0.9; entanglement_number=1; memory_size=5|' assert repr(std_reservation) == str(std_reservation) @pytest.mark.unit diff --git a/tests/network_management/test_routing.py b/tests/network_management/test_routing.py index de38e2721..e2d8510e0 100644 --- a/tests/network_management/test_routing.py +++ b/tests/network_management/test_routing.py @@ -1,6 +1,6 @@ from sequence.kernel.event import Event from sequence.kernel.process import Process -from sequence.network_management.routing_distributed import DistributedRoutingProtocol +from sequence.network_management.routing import DistributedRoutingProtocol from sequence.topology import router_net_topo import sequence.utils.log as log from sequence.constants import SECOND @@ -21,7 +21,7 @@ def test_distributed_routing_protocol_1(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('DEBUG') - # modules = ["routing_distributed"] + # modules = ["distributed"] # for module in modules: # log.track_module(module) @@ -64,7 +64,7 @@ def test_distributed_routing_protocol_2(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('INFO') - # modules = ["routing_distributed"] + # modules = ["distributed"] # for module in modules: # log.track_module(module) @@ -152,7 +152,7 @@ def test_distributed_routing_protocol_3(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('DEBUG') - # modules = ["routing_distributed", "node"] + # modules = ["distributed", "node"] # for module in modules: # log.track_module(module) @@ -200,7 +200,7 @@ def test_distributed_routing_protocol_4(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('INFO') - # modules = ["routing_distributed"] + # modules = ["distributed"] # for module in modules: # log.track_module(module) @@ -291,7 +291,7 @@ def test_distributed_routing_protocol_5(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('INFO') - # modules = ["routing_distributed", "node"] + # modules = ["distributed", "node"] # for module in modules: # log.track_module(module) @@ -389,7 +389,7 @@ def test_distributed_routing_protocol_6(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('DEBUG') - # modules = ["routing_distributed"] + # modules = ["distributed"] # for module in modules: # log.track_module(module) @@ -436,7 +436,7 @@ def test_distributed_routing_protocol_7(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('DEBUG') - # modules = ["routing_distributed"] + # modules = ["distributed"] # for module in modules: # log.track_module(module) @@ -483,7 +483,7 @@ def test_distributed_routing_protocol_8(): # log_filename = "tests/network_management/test_distributed_routing_protocol.log" # log.set_logger(__name__, tl, log_filename) # log.set_logger_level('DEBUG') - # modules = ["routing_distributed"] + # modules = ["distributed"] # for module in modules: # log.track_module(module) diff --git a/tests/topology/dqc_node_net_topo_simple.json b/tests/topology/dqc_node_net_topo_simple.json index 0a7034126..1adc3dd7a 100644 --- a/tests/topology/dqc_node_net_topo_simple.json +++ b/tests/topology/dqc_node_net_topo_simple.json @@ -1,7 +1,7 @@ { "templates": { "network_manager": { - "routing": "static" + "routing": "routing_static" } }, diff --git a/tests/topology/test_dqc_node_net_topo.py b/tests/topology/test_dqc_node_net_topo.py index 4597173c4..fe314d4b1 100644 --- a/tests/topology/test_dqc_node_net_topo.py +++ b/tests/topology/test_dqc_node_net_topo.py @@ -55,5 +55,3 @@ def test_sequential_simulation_dqc_node_net_topo_simple(): # ensure it knows how to reach the other node other = "q2" if qn.name == "q1" else "q1" assert other in routing.forwarding_table - - diff --git a/utils/cavity_vs_fidelity.py b/utils/cavity_vs_fidelity.py deleted file mode 100644 index 5d18c93b0..000000000 --- a/utils/cavity_vs_fidelity.py +++ /dev/null @@ -1,12 +0,0 @@ -def get_fidelity_by_cavity(C: int): - """ - C: the cavity cooperativity - 50 <= C <= 500 - """ - gama = 14 - gama_star = 32 - delta_omega = 0 - gama_prime = (C + 1) * gama - tau = gama_prime + 2 * gama_star - F_e = 0.5 * (1 + gama_prime ** 2 / (tau ** 2 + delta_omega ** 2)) - return F_e diff --git a/utils/dict_timing.py b/utils/dict_timing.py deleted file mode 100644 index cf74b7ac3..000000000 --- a/utils/dict_timing.py +++ /dev/null @@ -1,28 +0,0 @@ -import time -import multiprocessing -import numpy as np - - -NUM_KEYS = 10000 - -test_dict = {} -for i in range(NUM_KEYS): - test_dict[i] = np.random.random() - -mgr = multiprocessing.Manager() -mgr_dict = mgr.dict() -mgr_dict.update(test_dict) - -start1 = time.time() -for i in range(NUM_KEYS): - val = test_dict[i] -end1 = time.time() -print("Normal read:", end1 - start1) - -start2 = time.time() -for i in range(NUM_KEYS): - val = mgr_dict[i] -end2 = time.time() -print("Manager read:", end2 - start2) - -print("Ratio:", (end2 - start2) / (end1 - start1)) diff --git a/utils/json_config_generators/config_generator_as_memo_num.py b/utils/json_config_generators/config_generator_as_memo_num.py deleted file mode 100644 index a27f91ff1..000000000 --- a/utils/json_config_generators/config_generator_as_memo_num.py +++ /dev/null @@ -1,186 +0,0 @@ -"""This module generates JSON config files for Internet Autonomous System Network -""" - -from collections import defaultdict -import networkx as nx -from networkx import dijkstra_path -import argparse -import json -import numpy as np -import random - -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 - - -SEED = 1 -random.seed(SEED) - - -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) - -# example: python config_generator_as_memo_num.py 20 0 1 10 1 0.0002 1 -o as_20.json -s 10 - -parser = argparse.ArgumentParser() -parser.add_argument('net_size', type=int, help="net_size (int) - Number of routers") -parser.add_argument('seed', type=int, help="seed (int) - Indicator of random number generation state.") -parser.add_argument('alpha', type=int, help="alpha for exponential distribution of flows") -parser = add_default_args(parser) -args = parser.parse_args() - -NET_SIZE = args.net_size -NET_SEED = args.seed -ALPHA = args.alpha -FLOW_MEMO_SIZE = args.memo_size -QC_LEN = args.qc_length -QC_ATT = args.qc_atten -CC_DELAY = args.cc_delay - -graph = nx.random_internet_as_graph(NET_SIZE, NET_SEED) -paths = [] -for src in graph.nodes: - for dst in graph.nodes: - if dst >= src: - continue - path = dijkstra_path(graph, src, dst) - hop_num = len(path) - 2 - while len(paths) <= hop_num: - paths.append([]) - paths[hop_num].append(tuple(path)) - paths[hop_num].append(tuple(path[::-1])) - -MAX_HOP = len(paths) -TOTAL_FLOW_NUM = NET_SIZE -FLOW_NUMS = [int(get_exp_dis_prob(i, i + 1, ALPHA) * TOTAL_FLOW_NUM) for i in range(MAX_HOP)] -FLOW_NUMS[-1] = TOTAL_FLOW_NUM - sum(FLOW_NUMS[:-1]) - -selected_paths = {} -hops_counter = [0 for i in range(MAX_HOP)] -nodes_caps = [0 for i in range(NET_SIZE)] - -for hop_num in range(MAX_HOP - 1, -1, -1): - flow_num = FLOW_NUMS[hop_num] - for f_index in range(flow_num): - while len(paths[hop_num]) > 0: - sample_index = random.choice(list(range(len(paths[hop_num])))) - sample_path = paths[hop_num][sample_index] - paths[hop_num].remove(sample_path) - - if sample_path[0] in selected_paths: - continue - - selected_paths[sample_path[0]] = sample_path - for i, node in enumerate(sample_path): - if i == 0 or i == len(sample_path) - 1: - nodes_caps[node] += 1 * FLOW_MEMO_SIZE - else: - nodes_caps[node] += 2 * FLOW_MEMO_SIZE - - hops_counter[hop_num] += 1 - break - -unused_nodes = [] -for i, c in enumerate(nodes_caps): - if c == 0: - unused_nodes.append(i) - -while unused_nodes: - n1 = unused_nodes.pop() - if unused_nodes: - n2 = unused_nodes.pop() - else: - samples = random.choice(list(range(NET_SIZE)), 2) - if samples[0] != n1: - n2 = samples[0] - else: - n2 = samples[1] - - if n2 > n1: - n1, n2 = n2, n1 - - path = dijkstra_path(graph, n1, n2) - hops_counter[len(path) - 2] += 1 - - for i, node in enumerate(path): - if i == 0 or i == len(path) - 1: - nodes_caps[node] += 1 * FLOW_MEMO_SIZE - else: - nodes_caps[node] += 2 * FLOW_MEMO_SIZE - - if n1 not in selected_paths: - selected_paths[n1] = path - else: - selected_paths[n2] = path[::-1] - -mapping = {} -node_memo_size = {} - -for i in range(NET_SIZE): - mapping[i] = router_name_func(i) - node_memo_size[router_name_func(i)] = nodes_caps[i] -nx.relabel_nodes(graph, mapping, copy=False) - -output_dict = {} - -router_names = [router_name_func(i) for i in range(NET_SIZE)] -nodes = [{Topology.NAME: name, - Topology.TYPE: RouterNetTopo.QUANTUM_ROUTER, - Topology.SEED: i, - RouterNetTopo.MEMO_ARRAY_SIZE: node_memo_size[name]} - for i, name in enumerate(router_names)] - -# add bsm links -cchannels, qchannels, bsm_nodes = generate_bsm_links(graph, args, bsm_name_func) -nodes += bsm_nodes -output_dict[Topology.ALL_NODE] = nodes -output_dict[Topology.ALL_Q_CHANNEL] = qchannels - -# add router-to-router classical channels -router_cchannels = generate_classical(router_names, args.cc_delay) -cchannels += router_cchannels -output_dict[Topology.ALL_C_CHANNEL] = cchannels - -# write other config options -final_config(output_dict, args) - -# write final json -output_file = open(args.output, 'w') -json.dump(output_dict, output_file, indent=4) - -flows = {} -for src in selected_paths: - path = selected_paths[src] - src_name = router_name_func(src) - new_path = [router_name_func(n) for n in path] - flows[src_name] = new_path - -flow_fh = open("flow_" + args.output, 'w') -flow_info = {"flows": flows, "memo_size": FLOW_MEMO_SIZE} -json.dump(flow_info, flow_fh) - -fow_tables = defaultdict(lambda: {}) -for src in selected_paths: - path = selected_paths[src] - for i, node in enumerate(path): - if i == len(path) - 1: - break - table = fow_tables[node] - if path[-1] in table: - assert table[path[-1]] == path[i + 1] - else: - table[path[-1]] = path[i + 1] - -# visualization -# r_f = lambda: random.randint(0,255) -# colors = ['#%02X%02X%02X' % (r_f(),r_f(),r_f()) for _ in range(NET_SIZE)] -# r_group = node_procs -# color_map = [None] * NET_SIZE -# -# for n in r_group: -# index = int(n.replace("router_", "")) -# color_map[index] = colors[r_group[n]] -# nx.draw(graph, node_color=color_map) -# plt.show() diff --git a/utils/json_config_generators/config_generator_caveman.py b/utils/json_config_generators/config_generator_caveman.py deleted file mode 100644 index 44d5c22d3..000000000 --- a/utils/json_config_generators/config_generator_caveman.py +++ /dev/null @@ -1,61 +0,0 @@ -"""This module generates JSON config files for networks in a caveman graph configuration. - -More information may be found here: https://mathworld.wolfram.com/CavemanGraph.html -Help information may also be obtained using the `-h` flag. - -Args: - l (int): number of cliques (clusters) in the graph. - k (int): number of nodes per clique. - 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: - -o --output (str): name of the output file (default out.json). - -s --stop (float): simulation stop time (in s) (default infinity). -""" - -import networkx as nx -import argparse -import json -import os - -from sequence.utils.config_generator import * -from sequence.topology.topology import Topology - - - -parser = argparse.ArgumentParser() -parser.add_argument('l', type=int, help="l (int) - Number of cliques") -parser.add_argument('k', type=int, help="k (int) - Size of cliques") -add_default_args(parser) -args = parser.parse_args() - -graph = nx.connected_caveman_graph(args.l, args.k) -mapping = {} -NODE_NUM = args.l * args.k -for i in range(NODE_NUM): - mapping[i] = router_name_func(i) -nx.relabel_nodes(graph, mapping, copy=False) - -output_dict = {} - -router_names = [router_name_func(i) for i in range(NODE_NUM)] -nodes = generate_nodes(router_names, args.memo_size) - -cchannels, qchannels, bsm_nodes = generate_bsm_links(graph, args, bsm_name_func) -nodes += bsm_nodes -output_dict[Topology.ALL_NODE] = nodes -output_dict[Topology.ALL_Q_CHANNEL] = qchannels - -router_cchannels = generate_classical(router_names, args.cc_delay) -cchannels += router_cchannels -output_dict[Topology.ALL_C_CHANNEL] = cchannels - -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) diff --git a/utils/json_config_generators/config_generator_line.py b/utils/json_config_generators/config_generator_line.py deleted file mode 100644 index bc7c8e528..000000000 --- a/utils/json_config_generators/config_generator_line.py +++ /dev/null @@ -1,87 +0,0 @@ -"""This module generates JSON config files for networks in a linear configuration. - -Help information may also be obtained using the `-h` flag. - -Args: - linear_size (int): number of nodes in the graph. - 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). -""" - -import argparse -import json -import os - -from sequence.utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.constants import MILLISECOND - -# example: python config_generator_line.py 2 10 10 0.0002 1 -d config -o line_2.json -s 10 -gf 0.99 -mf 0.99 - -parser = argparse.ArgumentParser() -parser.add_argument('linear_size', type=int, help='number of network nodes') -parser = add_default_args(parser) -args = parser.parse_args() - -output_dict = {} - - -router_names = [router_name_func(i) for i in range(args.linear_size)] -nodes = generate_nodes(router_names, args.memo_size) - -# generate bsm nodes -bsm_names = ["BSM_{}_{}".format(i, i + 1) - for i in range(args.linear_size - 1)] -bsm_nodes = [{Topology.NAME: bsm_name, - Topology.TYPE: RouterNetTopo.BSM_NODE, - Topology.SEED: i} - for i, bsm_name in enumerate(bsm_names)] - -nodes += bsm_nodes -output_dict[Topology.ALL_NODE] = nodes - -# generate quantum links, classical with bsm nodes -qchannels = [] -cchannels = [] -for i, bsm_name in enumerate(bsm_names): - # qchannels - qchannels.append({Topology.SRC: router_names[i], - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - qchannels.append({Topology.SRC: router_names[i + 1], - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - # cchannels - for node in [router_names[i], router_names[i + 1]]: - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: node, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - - cchannels.append({Topology.SRC: node, - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) -output_dict[Topology.ALL_Q_CHANNEL] = qchannels - -# generate classical links -router_cchannels = generate_classical(router_names, args.cc_delay) -cchannels += router_cchannels -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) - diff --git a/utils/json_config_generators/config_generator_mesh.py b/utils/json_config_generators/config_generator_mesh.py deleted file mode 100644 index 911807993..000000000 --- a/utils/json_config_generators/config_generator_mesh.py +++ /dev/null @@ -1,93 +0,0 @@ -"""This module generates JSON config files for a fully-connected mesh network. - -Help information may also be obtained using the `-h` flag. - -Args: - net_size (int): number of nodes in the mesh. - 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). -""" - -import os -import argparse -import json - -from sequence.utils.config_generator import add_default_args, generate_nodes, final_config, router_name_func -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.constants import MILLISECOND - - -# 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() - -output_dict = {} - -router_names = [router_name_func(i) for i in range(args.net_size)] -nodes = generate_nodes(router_names, args.memo_size) - -# generate quantum links, classical links, and bsm nodes -qchannels = [] -cchannels = [] -bsm_nodes = [] -seed = 0 -for i, node1 in enumerate(router_names): - for node2 in router_names[i+1:]: - bsm_name = "BSM_{}_{}".format(node1, node2) - bsm_node = {Topology.NAME: bsm_name, - Topology.TYPE: RouterNetTopo.BSM_NODE, - Topology.SEED: seed} - bsm_nodes.append(bsm_node) - # qchannels - qchannels.append({Topology.SRC: node1, - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - qchannels.append({Topology.SRC: node2, - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - # cchannels - cchannels.append({Topology.SRC: node1, - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: node2, - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: node1, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: node2, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: node1, - Topology.DST: node2, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: node2, - Topology.DST: node1, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - seed += 1 - -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) - diff --git a/utils/json_config_generators/config_generator_ring.py b/utils/json_config_generators/config_generator_ring.py deleted file mode 100644 index ea4890669..000000000 --- a/utils/json_config_generators/config_generator_ring.py +++ /dev/null @@ -1,91 +0,0 @@ -"""This module generates JSON config files for networks in a ring configuration. - -Help information may also be obtained using the `-h` flag. - -Args: - ring_size (int): number of nodes in the network ring. - 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). -""" - -import argparse -import json -import os - -from sequence.utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.constants import MILLISECOND - - -# python config_generator_ring.py 4 10 1 0.0002 1 -o ring_topo.json -s 100 - -# parse args -parser = argparse.ArgumentParser() -parser.add_argument('ring_size', type=int, help='number of network nodes') -parser = add_default_args(parser) -args = parser.parse_args() - -output_dict = {} - -# get node names, processes -router_names = [router_name_func(i) for i in range(args.ring_size)] -nodes = generate_nodes(router_names, args.memo_size) - -# 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} - for i, bsm_name in enumerate(bsm_names)] - - -for i, bsm_name in enumerate(bsm_names): - # qchannels - qchannels.append({Topology.SRC: router_names[i % args.ring_size], - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - qchannels.append({Topology.SRC: router_names[(i + 1) % args.ring_size], - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - # cchannels - cchannels.append({Topology.SRC: router_names[i % args.ring_size], - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: router_names[(i + 1) % args.ring_size], - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: router_names[i % args.ring_size], - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: router_names[(i + 1) % args.ring_size], - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - -nodes += bsm_nodes -output_dict[Topology.ALL_NODE] = nodes -output_dict[Topology.ALL_Q_CHANNEL] = qchannels - -# generate classical links (classical channels) -router_cchannels = generate_classical(router_names, args.cc_delay) -cchannels += router_cchannels -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) - diff --git a/utils/json_config_generators/config_generator_star.py b/utils/json_config_generators/config_generator_star.py deleted file mode 100644 index 2a68b9ea2..000000000 --- a/utils/json_config_generators/config_generator_star.py +++ /dev/null @@ -1,98 +0,0 @@ -"""This module generates JSON config files for networks in a star configuration. - -Help information may also be obtained using the `-h` flag. - -Args: - star_size (int): number of non-center network nodes (network size is star_size+1). - memo_size (int): number of memories per end node. - memo_size_center (int): number of memories on the center 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). -""" - -import argparse -import json -import os - -from sequence.utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func -from sequence.topology.topology import Topology -from sequence.topology.router_net_topo import RouterNetTopo -from sequence.constants import MILLISECOND - - -# parse args -parser = argparse.ArgumentParser() -parser.add_argument('star_size', type=int, help='number of non-center network nodes') -parser.add_argument('memo_size_center', type=int, help='number of memories on center node') -parser = add_default_args(parser) -args = parser.parse_args() - -output_dict = {} - - -# generate nodes, with middle having different num -center_name = "router_center" -router_names = [router_name_func(i) for i in range(args.star_size + 1)] -router_names[-1] = center_name - -nodes = generate_nodes(router_names, args.memo_size) -for node in nodes: - if node[Topology.NAME] == center_name: - node[RouterNetTopo.MEMO_ARRAY_SIZE] = args.memo_size_center - break - -# generate quantum links -qchannels = [] -cchannels = [] -bsm_names = ["BSM_" + str(i) for i in range(args.star_size)] -bsm_nodes = [{Topology.NAME: bsm_name, - Topology.TYPE: RouterNetTopo.BSM_NODE, - Topology.SEED: i} - for i, bsm_name in enumerate(bsm_names)] - -for node_name, bsm_name in zip(router_names, bsm_names): - # qchannels - qchannels.append({Topology.SRC: node_name, - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - qchannels.append({Topology.SRC: center_name, - Topology.DST: bsm_name, - Topology.DISTANCE: args.qc_length * 500, - Topology.ATTENUATION: args.qc_atten}) - # cchannels - cchannels.append({Topology.SRC: node_name, - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: center_name, - Topology.DST: bsm_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: node_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - cchannels.append({Topology.SRC: bsm_name, - Topology.DST: center_name, - Topology.DELAY: int(args.cc_delay * MILLISECOND)}) - -output_dict[Topology.ALL_NODE] = nodes + bsm_nodes -output_dict[Topology.ALL_Q_CHANNEL] = qchannels - -# generate classical links -router_cchannels = generate_classical(router_names, args.cc_delay) -cchannels += router_cchannels -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) - diff --git a/utils/lock_test.py b/utils/lock_test.py deleted file mode 100644 index e9c79c26a..000000000 --- a/utils/lock_test.py +++ /dev/null @@ -1,20 +0,0 @@ -import multiprocessing -from itertools import product - - -NUM_PROCS = 5 - -def test_func(num, lock): - print(lock) - if num >= NUM_PROCS / 2: - lock.acquire() - -manager = multiprocessing.Manager() -lock = manager.Lock() -nums = list(range(NUM_PROCS)) - -p = multiprocessing.Pool(NUM_PROCS) -p.starmap(test_func, product(nums, [lock])) -p.close() -p.terminate() - diff --git a/utils/qmanager_timing.py b/utils/qmanager_timing.py deleted file mode 100644 index 3abde2f8c..000000000 --- a/utils/qmanager_timing.py +++ /dev/null @@ -1,50 +0,0 @@ -import time -import multiprocessing -import numpy as np - -from psequence.quantum_manager_server import generate_arg_parser, start_server -from sequence.kernel.quantum_manager_client import QuantumManagerClient -from sequence.components.circuit import Circuit - - -def client_function(ip, port): - client = QuantumManagerClient("KET", ip, port) - - # send request for new state - key = client.new() - - # send request to get state - _ = client.get(key) - - # run Hadamard gate - circ = Circuit(1) - circ.h(0) - client.run_circuit(circ, [key]) - - # get state again to verify - _ = client.get(key) - - -NUM_TRIALS = 10 -NUM_CLIENTS = 10 - -parser = generate_arg_parser() -args = parser.parse_args() - -p = multiprocessing.Process(target=start_server, args=(args.ip, args.port)) -p.start() - -times = [] -for _ in range(NUM_TRIALS): - start = time.time() - pool = multiprocessing.Pool(NUM_CLIENTS, client_function, [args.ip, args.port]) - pool.close() - pool.join() - end = time.time() - print("\ttime:", end - start) - times.append(end - start) - -p.kill() - -print("average time:", np.mean(times)) - diff --git a/utils/timing_test.py b/utils/timing_test.py deleted file mode 100644 index 494ffe0da..000000000 --- a/utils/timing_test.py +++ /dev/null @@ -1,46 +0,0 @@ -import subprocess -import statistics as stats -import sys -import time - -runtimes = [] - - -def timeit_wrapper(func): - def wrapper(*args, **kwargs): - start = time.perf_counter() - return_val = func(*args, **kwargs) - end = time.perf_counter() - runtimes.append(end - start) - return return_val - return wrapper - - -if __name__ == "__main__": - ''' - Program for timing a script, returns average of several runs - input: relative path to script - ''' - - script = sys.argv[1] - try: - num_trials = int(sys.argv[2]) - except IndexError: - num_trials = 10 - - @timeit_wrapper - def run(): - cmd = "python3 " + script - subprocess.call(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - - print("running timing test for {} with {} trials".format(script, num_trials)) - - for i in range(num_trials): - print("\trunning trial number {} ... ".format(i + 1), end='', flush=True) - run() - print("ran in {}s".format(runtimes[-1])) - - print("mean time: {}".format(stats.mean(runtimes))) - print("min time: {}".format(min(runtimes))) - print("max time: {}".format(max(runtimes))) - print("standard deviation: {}".format(stats.stdev(runtimes))) diff --git a/uv.lock b/uv.lock index 50a11056e..66c00e1e3 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.11, <3.15" resolution-markers = [ "python_full_version >= '3.14'", @@ -663,7 +663,7 @@ wheels = [ [[package]] name = "flask" -version = "3.1.2" +version = "3.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "blinker" }, @@ -673,9 +673,9 @@ dependencies = [ { name = "markupsafe" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, ] [[package]] @@ -784,6 +784,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/65/b7/8c5007180dce911b509587339f0b44b4f8dc1e1904440c02a652abab6a23/gmpy2-2.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:0ef9501b8d6168d5bd4e951adaa2cf1902cf94aa93fe6e4d7e85a59288fcd6ef", size = 870558, upload-time = "2025-11-27T04:16:00.514Z" }, ] +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -1475,7 +1484,7 @@ wheels = [ [[package]] name = "nbconvert" -version = "7.16.6" +version = "7.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -1493,9 +1502,9 @@ dependencies = [ { name = "pygments" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, ] [[package]] @@ -1771,89 +1780,89 @@ wheels = [ [[package]] name = "pillow" -version = "12.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, - { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, - { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, - { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, - { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, - { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, - { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, - { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, - { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, - { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, - { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, - { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, - { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, - { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, - { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, - { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, - { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, - { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, - { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, - { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, - { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, - { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, - { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, - { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, - { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, - { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, - { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, - { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, - { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, - { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, - { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, - { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, - { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, - { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, - { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, - { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, - { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, - { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] @@ -2508,7 +2517,7 @@ wheels = [ [[package]] name = "sequence" -version = "0.8.5" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "dash" }, @@ -2518,6 +2527,7 @@ dependencies = [ { name = "dash-html-components" }, { name = "dash-table" }, { name = "gmpy2" }, + { name = "graphviz" }, { name = "ipywidgets" }, { name = "jupyterlab" }, { name = "matplotlib" }, @@ -2551,6 +2561,7 @@ requires-dist = [ { name = "dash-html-components", specifier = ">=2.0.0" }, { name = "dash-table", specifier = ">=5.0.0" }, { name = "gmpy2", specifier = ">=2.2.2" }, + { name = "graphviz", specifier = ">=0.21" }, { name = "ipywidgets", specifier = ">=8.1.8" }, { name = "jupyterlab", specifier = ">=4.5.0" }, { name = "matplotlib", specifier = ">=3.10.7" }, @@ -2744,11 +2755,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.1" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/1d/0f3a93cca1ac5e8287842ed4eebbd0f7a991315089b1a0b01c7788aa7b63/urllib3-2.6.1.tar.gz", hash = "sha256:5379eb6e1aba4088bae84f8242960017ec8d8e3decf30480b3a1abdaa9671a3f", size = 432678, upload-time = "2025-12-08T15:25:26.773Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/56/190ceb8cb10511b730b564fb1e0293fa468363dbad26145c34928a60cb0c/urllib3-2.6.1-py3-none-any.whl", hash = "sha256:e67d06fe947c36a7ca39f4994b08d73922d40e6cca949907be05efa6fd75110b", size = 131138, upload-time = "2025-12-08T15:25:25.51Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] @@ -2789,14 +2800,14 @@ wheels = [ [[package]] name = "werkzeug" -version = "3.1.4" +version = "3.1.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/ea/b0f8eeb287f8df9066e56e831c7824ac6bab645dd6c7a8f4b2d767944f9b/werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e", size = 864687, upload-time = "2025-11-29T02:15:22.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/f9/9e082990c2585c744734f85bec79b5dae5df9c974ffee58fe421652c8e91/werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905", size = 224960, upload-time = "2025-11-29T02:15:21.13Z" }, + { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" }, ] [[package]]