From 107172d830556729ac1c56031bca27f4cb9bfdd5 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 20:51:35 -0600 Subject: [PATCH 01/40] Move everything in the module to ease imports --- pyproject.toml | 3 + sequence/config_generators/__init__.py | 0 .../config_generator_as_memo_num.py | 187 ++++++++++++++++++ .../config_generator_caveman.py | 0 .../config_generator_line.py | 86 ++++++++ .../config_generator_mesh.py | 0 .../config_generator_random.py | 0 .../config_generator_ring.py | 0 .../config_generator_star.py | 0 .../config_generator_tree.py | 0 .../config_generator_as_memo_num.py | 186 ----------------- .../config_generator_line.py | 87 -------- 12 files changed, 276 insertions(+), 273 deletions(-) create mode 100644 sequence/config_generators/__init__.py create mode 100644 sequence/config_generators/config_generator_as_memo_num.py rename {utils/json_config_generators => sequence/config_generators}/config_generator_caveman.py (100%) create mode 100644 sequence/config_generators/config_generator_line.py rename {utils/json_config_generators => sequence/config_generators}/config_generator_mesh.py (100%) rename {utils/json_config_generators => sequence/config_generators}/config_generator_random.py (100%) rename {utils/json_config_generators => sequence/config_generators}/config_generator_ring.py (100%) rename {utils/json_config_generators => sequence/config_generators}/config_generator_star.py (100%) rename {utils/json_config_generators => sequence/config_generators}/config_generator_tree.py (100%) delete mode 100644 utils/json_config_generators/config_generator_as_memo_num.py delete mode 100644 utils/json_config_generators/config_generator_line.py diff --git a/pyproject.toml b/pyproject.toml index 99119078b..a3855fd98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,9 @@ dependencies = [ "tqdm>=4.67.1", ] +[project.scripts] +generate-line = "sequence.config_generators.config_generator_line:main" + [build-system] requires = ["uv_build>=0.9.17,<0.10.0"] build-backend = "uv_build" diff --git a/sequence/config_generators/__init__.py b/sequence/config_generators/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sequence/config_generators/config_generator_as_memo_num.py b/sequence/config_generators/config_generator_as_memo_num.py new file mode 100644 index 000000000..1b54aa29a --- /dev/null +++ b/sequence/config_generators/config_generator_as_memo_num.py @@ -0,0 +1,187 @@ +"""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 ..utils.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 = 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/sequence/config_generators/config_generator_caveman.py similarity index 100% rename from utils/json_config_generators/config_generator_caveman.py rename to sequence/config_generators/config_generator_caveman.py diff --git a/sequence/config_generators/config_generator_line.py b/sequence/config_generators/config_generator_line.py new file mode 100644 index 000000000..c3526e41b --- /dev/null +++ b/sequence/config_generators/config_generator_line.py @@ -0,0 +1,86 @@ +"""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 ..utils.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) \ No newline at end of file diff --git a/utils/json_config_generators/config_generator_mesh.py b/sequence/config_generators/config_generator_mesh.py similarity index 100% rename from utils/json_config_generators/config_generator_mesh.py rename to sequence/config_generators/config_generator_mesh.py diff --git a/utils/json_config_generators/config_generator_random.py b/sequence/config_generators/config_generator_random.py similarity index 100% rename from utils/json_config_generators/config_generator_random.py rename to sequence/config_generators/config_generator_random.py diff --git a/utils/json_config_generators/config_generator_ring.py b/sequence/config_generators/config_generator_ring.py similarity index 100% rename from utils/json_config_generators/config_generator_ring.py rename to sequence/config_generators/config_generator_ring.py diff --git a/utils/json_config_generators/config_generator_star.py b/sequence/config_generators/config_generator_star.py similarity index 100% rename from utils/json_config_generators/config_generator_star.py rename to sequence/config_generators/config_generator_star.py diff --git a/utils/json_config_generators/config_generator_tree.py b/sequence/config_generators/config_generator_tree.py similarity index 100% rename from utils/json_config_generators/config_generator_tree.py rename to sequence/config_generators/config_generator_tree.py 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_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) - From 40716815ead038c492c8f1c6410b51f55baa66cc Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 20:57:05 -0600 Subject: [PATCH 02/40] Add entry points for cli to each. Create project script for each --- pyproject.toml | 9 +- .../config_generator_caveman.py | 69 +++++---- .../config_generator_mesh.py | 120 +++++++-------- .../config_generator_ring.py | 128 ++++++++-------- .../config_generator_star.py | 140 +++++++++--------- .../config_generator_tree.py | 64 ++++---- 6 files changed, 268 insertions(+), 262 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a3855fd98..d0e91c90d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,14 @@ dependencies = [ ] [project.scripts] -generate-line = "sequence.config_generators.config_generator_line:main" +generate-internet-autonomous = "sequence.config_generators.config_generator_as_memo_num:main" +generate-caveman = "sequence.config_generators.config_generator_caveman:main" +generate-linear = "sequence.config_generators.config_generator_line:main" +generate-mesh = "sequence.config_generators.config_generator_mesh:main" +generate-random = "sequence.config_generators.config_generator_random:main" +generate-ring = "sequence.config_generators.config_generator_ring:main" +generate-star = "sequence.config_generators.config_generator_star:main" +generate-tree = "sequence.config_generators.config_generator_tree:main" [build-system] requires = ["uv_build>=0.9.17,<0.10.0"] diff --git a/sequence/config_generators/config_generator_caveman.py b/sequence/config_generators/config_generator_caveman.py index 44d5c22d3..66da86c5b 100644 --- a/sequence/config_generators/config_generator_caveman.py +++ b/sequence/config_generators/config_generator_caveman.py @@ -24,38 +24,37 @@ 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) +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) diff --git a/sequence/config_generators/config_generator_mesh.py b/sequence/config_generators/config_generator_mesh.py index 911807993..24532606d 100644 --- a/sequence/config_generators/config_generator_mesh.py +++ b/sequence/config_generators/config_generator_mesh.py @@ -24,70 +24,70 @@ from sequence.topology.router_net_topo import RouterNetTopo from sequence.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() -# 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 = {} -output_dict = {} + router_names = [router_name_func(i) for i in range(args.net_size)] + nodes = generate_nodes(router_names, args.memo_size) -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 -# 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 -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 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) + # 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/sequence/config_generators/config_generator_ring.py b/sequence/config_generators/config_generator_ring.py index ea4890669..6867a3789 100644 --- a/sequence/config_generators/config_generator_ring.py +++ b/sequence/config_generators/config_generator_ring.py @@ -24,68 +24,68 @@ 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) +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) diff --git a/sequence/config_generators/config_generator_star.py b/sequence/config_generators/config_generator_star.py index 2a68b9ea2..a315c66c8 100644 --- a/sequence/config_generators/config_generator_star.py +++ b/sequence/config_generators/config_generator_star.py @@ -25,74 +25,74 @@ 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) +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) diff --git a/sequence/config_generators/config_generator_tree.py b/sequence/config_generators/config_generator_tree.py index b52a2981f..493dc6ab7 100644 --- a/sequence/config_generators/config_generator_tree.py +++ b/sequence/config_generators/config_generator_tree.py @@ -78,36 +78,36 @@ def add_branches(node_names, nodes, index, bsm_names, bsm_nodes, qchannels, ccha 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(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) From e5eedd162a6eb2c05a2bf2cf1981ee28ca5ed811 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:00:55 -0600 Subject: [PATCH 03/40] Fix imports to relative. --- .../config_generator_as_memo_num.py | 19 ++++--------------- .../config_generator_caveman.py | 4 ++-- .../config_generator_mesh.py | 8 ++++---- .../config_generator_random.py | 8 ++++---- .../config_generator_ring.py | 8 ++++---- .../config_generator_star.py | 8 ++++---- .../config_generator_tree.py | 8 ++++---- 7 files changed, 26 insertions(+), 37 deletions(-) diff --git a/sequence/config_generators/config_generator_as_memo_num.py b/sequence/config_generators/config_generator_as_memo_num.py index 1b54aa29a..c3f2cac1f 100644 --- a/sequence/config_generators/config_generator_as_memo_num.py +++ b/sequence/config_generators/config_generator_as_memo_num.py @@ -1,4 +1,5 @@ -"""This module generates JSON config files for Internet Autonomous System Network +""" +This module generates JSON config files for Internet Autonomous System Network """ from collections import defaultdict @@ -28,7 +29,7 @@ def main(): 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_argument('alpha', type=int, help="Alph for exponential distribution of flows") parser = add_default_args(parser) args = parser.parse_args() @@ -172,16 +173,4 @@ def main(): 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() + table[path[-1]] = path[i + 1] \ No newline at end of file diff --git a/sequence/config_generators/config_generator_caveman.py b/sequence/config_generators/config_generator_caveman.py index 66da86c5b..1750da55b 100644 --- a/sequence/config_generators/config_generator_caveman.py +++ b/sequence/config_generators/config_generator_caveman.py @@ -21,8 +21,8 @@ import json import os -from sequence.utils.config_generator import * -from sequence.topology.topology import Topology +from ..utils.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() diff --git a/sequence/config_generators/config_generator_mesh.py b/sequence/config_generators/config_generator_mesh.py index 24532606d..1a3cb82ee 100644 --- a/sequence/config_generators/config_generator_mesh.py +++ b/sequence/config_generators/config_generator_mesh.py @@ -19,10 +19,10 @@ 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 +from ..utils.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 diff --git a/sequence/config_generators/config_generator_random.py b/sequence/config_generators/config_generator_random.py index 2dcc6401e..e65f5073a 100644 --- a/sequence/config_generators/config_generator_random.py +++ b/sequence/config_generators/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 ..utils.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/config_generators/config_generator_ring.py b/sequence/config_generators/config_generator_ring.py index 6867a3789..fec076f50 100644 --- a/sequence/config_generators/config_generator_ring.py +++ b/sequence/config_generators/config_generator_ring.py @@ -19,10 +19,10 @@ 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 ..utils.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 diff --git a/sequence/config_generators/config_generator_star.py b/sequence/config_generators/config_generator_star.py index a315c66c8..696195454 100644 --- a/sequence/config_generators/config_generator_star.py +++ b/sequence/config_generators/config_generator_star.py @@ -20,10 +20,10 @@ 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 ..utils.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 diff --git a/sequence/config_generators/config_generator_tree.py b/sequence/config_generators/config_generator_tree.py index 493dc6ab7..7085810d8 100644 --- a/sequence/config_generators/config_generator_tree.py +++ b/sequence/config_generators/config_generator_tree.py @@ -27,10 +27,10 @@ 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 ..utils.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 From da15315566f1fad4f0ce68d82c97876ffa540dd7 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:01:54 -0600 Subject: [PATCH 04/40] Fix args --- sequence/config_generators/config_generator_tree.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sequence/config_generators/config_generator_tree.py b/sequence/config_generators/config_generator_tree.py index 7085810d8..7ff7e9b2b 100644 --- a/sequence/config_generators/config_generator_tree.py +++ b/sequence/config_generators/config_generator_tree.py @@ -34,7 +34,7 @@ -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,7 +74,7 @@ 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 @@ -93,7 +93,7 @@ def main(): # generate quantum links and bsm connections bsm_names, bsm_nodes, qchannels, cchannels = \ - add_branches(router_names, nodes, 0, [], [], [], []) + add_branches(args, router_names, nodes, 0, [], [], [], []) nodes += bsm_nodes output_dict[Topology.ALL_NODE] = nodes output_dict[Topology.ALL_Q_CHANNEL] = qchannels From b71a1c9af0284e6a9840658b4612dca7ab6d6cf2 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:11:43 -0600 Subject: [PATCH 05/40] Update changelog and version --- CHANGELOG.md | 11 +++++++++++ pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b796349..357dc3ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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). +## [0.8.6] - 2026-3-02 +### Added +- Created project scripts for each config generator. + +### Changed +- Migrated `utils/json_config_generators/` to `sequence/config_generators/`. + +### Removed + + + ## [0.8.5] - 2026-2-27 ### Added - `NetworkManager` ABC with a factory pattern for selection and future implementation of new network managers. diff --git a/pyproject.toml b/pyproject.toml index d0e91c90d..c814fab06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sequence" -version = "0.8.5" +version = "0.8.6" 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" } ] From ce7b4fe9a2ab83632860938c058fbe4039ce9366 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:24:57 -0600 Subject: [PATCH 06/40] Fix typo, preserve backward compat --- sequence/config_generators/config_generator_as_memo_num.py | 7 +++++-- sequence/config_generators/config_generator_caveman.py | 3 +++ sequence/config_generators/config_generator_line.py | 5 ++++- sequence/config_generators/config_generator_mesh.py | 2 ++ sequence/config_generators/config_generator_ring.py | 2 ++ sequence/config_generators/config_generator_star.py | 2 ++ sequence/config_generators/config_generator_tree.py | 2 ++ 7 files changed, 20 insertions(+), 3 deletions(-) diff --git a/sequence/config_generators/config_generator_as_memo_num.py b/sequence/config_generators/config_generator_as_memo_num.py index c3f2cac1f..b991b8fa7 100644 --- a/sequence/config_generators/config_generator_as_memo_num.py +++ b/sequence/config_generators/config_generator_as_memo_num.py @@ -29,7 +29,7 @@ def main(): 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="Alph for exponential distribution of flows") + parser.add_argument('alpha', type=int, help="Alpha for exponential distribution of flows") parser = add_default_args(parser) args = parser.parse_args() @@ -173,4 +173,7 @@ def main(): if path[-1] in table: assert table[path[-1]] == path[i + 1] else: - table[path[-1]] = path[i + 1] \ No newline at end of file + table[path[-1]] = path[i + 1] + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sequence/config_generators/config_generator_caveman.py b/sequence/config_generators/config_generator_caveman.py index 1750da55b..ff108d867 100644 --- a/sequence/config_generators/config_generator_caveman.py +++ b/sequence/config_generators/config_generator_caveman.py @@ -58,3 +58,6 @@ def main(): 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/config_generators/config_generator_line.py b/sequence/config_generators/config_generator_line.py index c3526e41b..58194b7a6 100644 --- a/sequence/config_generators/config_generator_line.py +++ b/sequence/config_generators/config_generator_line.py @@ -83,4 +83,7 @@ def main(): # write final json path = os.path.join(args.directory, args.output) output_file = open(path, 'w') - json.dump(output_dict, output_file, indent=4) \ No newline at end of file + json.dump(output_dict, output_file, indent=4) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sequence/config_generators/config_generator_mesh.py b/sequence/config_generators/config_generator_mesh.py index 1a3cb82ee..d0c6a1e5d 100644 --- a/sequence/config_generators/config_generator_mesh.py +++ b/sequence/config_generators/config_generator_mesh.py @@ -91,3 +91,5 @@ def main(): 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/config_generators/config_generator_ring.py b/sequence/config_generators/config_generator_ring.py index fec076f50..69a5cc3b5 100644 --- a/sequence/config_generators/config_generator_ring.py +++ b/sequence/config_generators/config_generator_ring.py @@ -89,3 +89,5 @@ def main(): 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/config_generators/config_generator_star.py b/sequence/config_generators/config_generator_star.py index 696195454..98ab45dd7 100644 --- a/sequence/config_generators/config_generator_star.py +++ b/sequence/config_generators/config_generator_star.py @@ -96,3 +96,5 @@ def main(): output_file = open(path, 'w') json.dump(output_dict, output_file, indent=4) +if __name__ == "__main__": + main() diff --git a/sequence/config_generators/config_generator_tree.py b/sequence/config_generators/config_generator_tree.py index 7ff7e9b2b..798caf1b1 100644 --- a/sequence/config_generators/config_generator_tree.py +++ b/sequence/config_generators/config_generator_tree.py @@ -111,3 +111,5 @@ def main(): output_file = open(path, 'w') json.dump(output_dict, output_file, indent=4) +if __name__ == "__main__": + main() \ No newline at end of file From 5daa8cfef9b291f46ea89580f98ab32f7a06ca43 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:30:48 -0600 Subject: [PATCH 07/40] fix random.choice to use correct method --- sequence/{utils => config_generators}/config_generator.py | 0 sequence/config_generators/config_generator_as_memo_num.py | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename sequence/{utils => config_generators}/config_generator.py (100%) diff --git a/sequence/utils/config_generator.py b/sequence/config_generators/config_generator.py similarity index 100% rename from sequence/utils/config_generator.py rename to sequence/config_generators/config_generator.py diff --git a/sequence/config_generators/config_generator_as_memo_num.py b/sequence/config_generators/config_generator_as_memo_num.py index b991b8fa7..2230ddf78 100644 --- a/sequence/config_generators/config_generator_as_memo_num.py +++ b/sequence/config_generators/config_generator_as_memo_num.py @@ -10,7 +10,7 @@ import numpy as np import random -from ..utils.config_generator import add_default_args, generate_bsm_links, generate_classical, final_config, router_name_func, bsm_name_func +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 @@ -94,7 +94,7 @@ def main(): if unused_nodes: n2 = unused_nodes.pop() else: - samples = random.choice(list(range(NET_SIZE)), 2) + samples = np.random.choice(list(range(NET_SIZE)), 2) if samples[0] != n1: n2 = samples[0] else: From 6d0825d06eb167114fa660436380fec56869f302 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:31:34 -0600 Subject: [PATCH 08/40] Update the lock file --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 50a11056e..e839894e1 100644 --- a/uv.lock +++ b/uv.lock @@ -2508,7 +2508,7 @@ wheels = [ [[package]] name = "sequence" -version = "0.8.5" +version = "0.8.6" source = { editable = "." } dependencies = [ { name = "dash" }, From 314a13a466a79720490e39860c102a630bb3ad89 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Mon, 2 Mar 2026 21:34:31 -0600 Subject: [PATCH 09/40] Fix imports --- sequence/config_generators/config_generator.py | 8 ++++---- sequence/config_generators/config_generator_caveman.py | 2 +- sequence/config_generators/config_generator_line.py | 2 +- sequence/config_generators/config_generator_mesh.py | 2 +- sequence/config_generators/config_generator_random.py | 2 +- sequence/config_generators/config_generator_ring.py | 2 +- sequence/config_generators/config_generator_star.py | 2 +- sequence/config_generators/config_generator_tree.py | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sequence/config_generators/config_generator.py b/sequence/config_generators/config_generator.py index d23091e81..4598fc482 100644 --- a/sequence/config_generators/config_generator.py +++ b/sequence/config_generators/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/config_generators/config_generator_caveman.py b/sequence/config_generators/config_generator_caveman.py index ff108d867..995d129ef 100644 --- a/sequence/config_generators/config_generator_caveman.py +++ b/sequence/config_generators/config_generator_caveman.py @@ -21,7 +21,7 @@ import json import os -from ..utils.config_generator import add_default_args, router_name_func, generate_nodes, generate_classical, generate_bsm_links, bsm_name_func, final_config +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(): diff --git a/sequence/config_generators/config_generator_line.py b/sequence/config_generators/config_generator_line.py index 58194b7a6..5a4c517ee 100644 --- a/sequence/config_generators/config_generator_line.py +++ b/sequence/config_generators/config_generator_line.py @@ -19,7 +19,7 @@ import json import os -from ..utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +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 diff --git a/sequence/config_generators/config_generator_mesh.py b/sequence/config_generators/config_generator_mesh.py index d0c6a1e5d..43578e13b 100644 --- a/sequence/config_generators/config_generator_mesh.py +++ b/sequence/config_generators/config_generator_mesh.py @@ -19,7 +19,7 @@ import argparse import json -from ..utils.config_generator import add_default_args, generate_nodes, final_config, router_name_func +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 diff --git a/sequence/config_generators/config_generator_random.py b/sequence/config_generators/config_generator_random.py index e65f5073a..71d1ae562 100644 --- a/sequence/config_generators/config_generator_random.py +++ b/sequence/config_generators/config_generator_random.py @@ -22,7 +22,7 @@ import random from networkx.generators.geometric import waxman_graph -from ..utils.config_generator import add_default_args, generate_nodes, final_config, router_name_func +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 diff --git a/sequence/config_generators/config_generator_ring.py b/sequence/config_generators/config_generator_ring.py index 69a5cc3b5..4a727d193 100644 --- a/sequence/config_generators/config_generator_ring.py +++ b/sequence/config_generators/config_generator_ring.py @@ -19,7 +19,7 @@ import json import os -from ..utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +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 diff --git a/sequence/config_generators/config_generator_star.py b/sequence/config_generators/config_generator_star.py index 98ab45dd7..2d7f5871b 100644 --- a/sequence/config_generators/config_generator_star.py +++ b/sequence/config_generators/config_generator_star.py @@ -20,7 +20,7 @@ import json import os -from ..utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +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 diff --git a/sequence/config_generators/config_generator_tree.py b/sequence/config_generators/config_generator_tree.py index 798caf1b1..49b8fb0f5 100644 --- a/sequence/config_generators/config_generator_tree.py +++ b/sequence/config_generators/config_generator_tree.py @@ -27,7 +27,7 @@ import json import os -from ..utils.config_generator import add_default_args, generate_nodes, generate_classical, final_config, router_name_func +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 From 7dd6d5effe4f4bceb04176c92c04649ea7b45cd0 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 3 Mar 2026 18:39:53 -0600 Subject: [PATCH 10/40] [major] added the BDS formalism of the entanglement swapping protocol; reorganized the swapping module by using the registry decorator --- sequence/components/bsm.py | 8 +- sequence/constants.py | 2 +- sequence/entanglement_management/__init__.py | 2 +- .../entanglement_protocol.py | 5 +- .../generation/generation_base.py | 3 +- .../purification/bbpssw_circuit.py | 4 +- .../purification/bbpssw_protocol.py | 13 +- .../swapping/__init__.py | 9 + .../swapping/swapping_base.py | 379 ++++++++++++++++++ .../swapping/swapping_bds.py | 215 ++++++++++ .../swapping_circuit.py} | 204 ++-------- sequence/kernel/quantum_manager.py | 8 +- sequence/protocol.py | 11 +- .../action_condition_set.py | 4 +- sequence/topology/router_net_topo.py | 4 +- .../entanglement_management/test_swapping.py | 12 +- tests/topology/test_node.py | 2 + 17 files changed, 671 insertions(+), 214 deletions(-) create mode 100644 sequence/entanglement_management/swapping/__init__.py create mode 100644 sequence/entanglement_management/swapping/swapping_base.py create mode 100644 sequence/entanglement_management/swapping/swapping_bds.py rename sequence/entanglement_management/{swapping.py => swapping/swapping_circuit.py} (51%) 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/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/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..bd87e0a32 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() @@ -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.""" 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..26c9869e2 --- /dev/null +++ b/sequence/entanglement_management/swapping/swapping_base.py @@ -0,0 +1,379 @@ +"""The base class for entanglement swapping protocol. +""" +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 ...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, "RAW") + else: + self.update_resource_manager(memo, "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, "RAW") + + def release(self) -> None: + self.update_resource_manager(self.memory, "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..276531fc7 --- /dev/null +++ b/sequence/entanglement_management/swapping/swapping_bds.py @@ -0,0 +1,215 @@ +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 + +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.debug(f'swapping successed!') + 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.debug(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, "RAW") + self.update_resource_manager(self.right_memo, "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.debug(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, "ENTANGLED") + else: + self.update_resource_manager(self.memory, "RAW") diff --git a/sequence/entanglement_management/swapping.py b/sequence/entanglement_management/swapping/swapping_circuit.py similarity index 51% rename from sequence/entanglement_management/swapping.py rename to sequence/entanglement_management/swapping/swapping_circuit.py index bbe9cc863..b0335c68c 100644 --- a/sequence/entanglement_management/swapping.py +++ b/sequence/entanglement_management/swapping/swapping_circuit.py @@ -11,69 +11,36 @@ Also defined in this module is the message type used by these protocols. """ -from enum import Enum, auto + +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 ..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() + 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 -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): +@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 should be instantiated on the middle node, where it measures a memory from each pair to be swapped. + 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 that does swapping operations. + EntanglementSwappingA_Circuit.circuit (Circuit): circuit that does swapping operations. Attributes: - own (Node): node that protocol instance is attached to. + 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. @@ -94,7 +61,7 @@ class EntanglementSwappingA(EntanglementProtocol): 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): + 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: @@ -105,43 +72,9 @@ def __init__(self, owner: "Node", name: str, left_memo: "Memory", right_memo: "M 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 + super().__init__(owner, name, left_memo, right_memo, 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. @@ -195,12 +128,6 @@ def start(self) -> None: 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. @@ -215,58 +142,19 @@ def updated_fidelity(self, f1: float, f2: float) -> float: 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): +@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 should be instantiated on the end nodes, where it waits for swapping results from the middle node. + EntanglementSwappingB_Circuit 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. + 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. @@ -290,38 +178,18 @@ 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. + 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. """ - - 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 + 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 (EntanglementSwappingMesssage): message sent. + msg (EntanglementSwappingMessage): message sent. Side Effects: Will invoke `update_resource_manager` method. @@ -346,21 +214,3 @@ def received_message(self, src: str, msg: "EntanglementSwappingMessage") -> None 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/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/protocol.py b/sequence/protocol.py index ff3d99f97..372596978 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"): @@ -49,7 +50,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 +60,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..6c88279d5 100644 --- a/sequence/resource_management/action_condition_set.py +++ b/sequence/resource_management/action_condition_set.py @@ -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] diff --git a/sequence/topology/router_net_topo.py b/sequence/topology/router_net_topo.py index 7a6a552a7..a8582fcb8 100644 --- a/sequence/topology/router_net_topo.py +++ b/sequence/topology/router_net_topo.py @@ -6,7 +6,7 @@ from ..network_management.routing_static import 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 +56,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) 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/topology/test_node.py b/tests/topology/test_node.py index ef5245a51..5c36cad3a 100644 --- a/tests/topology/test_node.py +++ b/tests/topology/test_node.py @@ -141,3 +141,5 @@ def receive_message(self, src, msg): expect_node2_log = [(CC_DELAY + i, "node1", str(i)) for i in range(MSG_NUM)] for actual, expect in zip(node2.log, expect_node2_log): assert actual == expect + +test_Node_send_message() \ No newline at end of file From 207848e46a907503fc6df6e8bfe67d7d3bda8ca6 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Wed, 4 Mar 2026 14:16:00 -0600 Subject: [PATCH 11/40] [minor] updating docstrings and minor refactor --- sequence/app/request_app.py | 4 +++- .../entanglement_management/swapping/swapping_base.py | 9 +++++---- .../entanglement_management/swapping/swapping_bds.py | 11 ++++++----- tests/topology/test_node.py | 2 -- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/sequence/app/request_app.py b/sequence/app/request_app.py index 0d56b0cbd..5e213c094 100644 --- a/sequence/app/request_app.py +++ b/sequence/app/request_app.py @@ -146,7 +146,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/entanglement_management/swapping/swapping_base.py b/sequence/entanglement_management/swapping/swapping_base.py index 26c9869e2..9153579a1 100644 --- a/sequence/entanglement_management/swapping/swapping_base.py +++ b/sequence/entanglement_management/swapping/swapping_base.py @@ -9,6 +9,7 @@ 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: @@ -239,9 +240,9 @@ def memory_expire(self, memory: "Memory") -> None: for memo in self.memories: if memo == memory: - self.update_resource_manager(memo, "RAW") + self.update_resource_manager(memo, MemoryInfo.RAW) else: - self.update_resource_manager(memo, "ENTANGLED") + self.update_resource_manager(memo, MemoryInfo.ENTANGLED) def release_remote_protocol(self, remote_node: str): self.owner.resource_manager.release_remote_protocol(remote_node, self) @@ -362,10 +363,10 @@ def memory_expire(self, memory: Memory) -> None: Will update memory in attached resource manager. """ - self.update_resource_manager(self.memory, "RAW") + self.update_resource_manager(self.memory, MemoryInfo.RAW) def release(self) -> None: - self.update_resource_manager(self.memory, "ENTANGLED") + self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED) @abstractmethod def received_message(self, src: str, msg: EntanglementSwappingMessage) -> None: diff --git a/sequence/entanglement_management/swapping/swapping_bds.py b/sequence/entanglement_management/swapping/swapping_bds.py index 276531fc7..95391905a 100644 --- a/sequence/entanglement_management/swapping/swapping_bds.py +++ b/sequence/entanglement_management/swapping/swapping_bds.py @@ -4,6 +4,7 @@ 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 @@ -110,8 +111,8 @@ def start(self) -> None: 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, "RAW") - self.update_resource_manager(self.right_memo, "RAW") + 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. @@ -202,7 +203,7 @@ def received_message(self, src: str, msg: EntanglementSwappingMessage) -> None: 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, + # 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) @@ -210,6 +211,6 @@ def received_message(self, src: str, msg: EntanglementSwappingMessage) -> None: 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, "ENTANGLED") + self.update_resource_manager(self.memory, MemoryInfo.ENTANGLED) else: - self.update_resource_manager(self.memory, "RAW") + self.update_resource_manager(self.memory, MemoryInfo.RAW) diff --git a/tests/topology/test_node.py b/tests/topology/test_node.py index 5c36cad3a..ef5245a51 100644 --- a/tests/topology/test_node.py +++ b/tests/topology/test_node.py @@ -141,5 +141,3 @@ def receive_message(self, src, msg): expect_node2_log = [(CC_DELAY + i, "node1", str(i)) for i in range(MSG_NUM)] for actual, expect in zip(node2.log, expect_node2_log): assert actual == expect - -test_Node_send_message() \ No newline at end of file From f23632e5d241cb62a107b82bf24f7102a7632f1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:23:03 +0000 Subject: [PATCH 12/40] Bump urllib3 from 2.6.1 to 2.6.3 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.1 to 2.6.3. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.6.1...2.6.3) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 50a11056e..6cfd2750e 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'", @@ -2744,11 +2744,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]] From 61c166e89faeb1718294f96b2669d9f32c09c429 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 21:39:29 +0000 Subject: [PATCH 13/40] Bump pillow from 12.0.0 to 12.1.1 Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.0.0 to 12.1.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/12.0.0...12.1.1) --- updated-dependencies: - dependency-name: pillow dependency-version: 12.1.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 168 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/uv.lock b/uv.lock index 50a11056e..673160ac6 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'", @@ -1771,89 +1771,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]] From 53cbdcd4f31b51b730177137b8d3be55d85e1983 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Wed, 4 Mar 2026 16:50:39 -0600 Subject: [PATCH 14/40] [minor] update docstring and README; remove the quotaiton markin typing --- CHANGELOG.md | 11 ++++++++++ pyproject.toml | 2 +- .../swapping/swapping_base.py | 22 ++++++++++++++----- .../swapping/swapping_bds.py | 4 +++- .../swapping/swapping_circuit.py | 15 ++----------- uv.lock | 4 ++-- 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b796349..805bae958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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] +### Added +- Created 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` + +### 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. diff --git a/pyproject.toml b/pyproject.toml index 99119078b..db8071970 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sequence" -version = "0.8.5" +version = "0.8.6" 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" } ] diff --git a/sequence/entanglement_management/swapping/swapping_base.py b/sequence/entanglement_management/swapping/swapping_base.py index 9153579a1..56b5e3ee7 100644 --- a/sequence/entanglement_management/swapping/swapping_base.py +++ b/sequence/entanglement_management/swapping/swapping_base.py @@ -1,5 +1,17 @@ """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 @@ -62,7 +74,7 @@ class EntanglementSwappingA(EntanglementProtocol, ABC): _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): + def __init__(self, owner: Node, name: str, left_memo: Memory, right_memo: Memory, success_prob: float = 1): """Constructor for Entanglement Swapping A protocol. Args: @@ -138,7 +150,7 @@ def decorator(protocol_class: type['EntanglementSwappingA']) -> type['Entangleme return decorator @classmethod - def create(cls, owner: "Node", name: str, left_memo: "Memory", right_memo: "Memory", + 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. @@ -215,7 +227,7 @@ def received_message(self, src: str, msg: Message) -> None: """ raise Exception("EntanglementSwappingA protocol '{}' should not receive messages.".format(self.name)) - def memory_expire(self, memory: "Memory") -> None: + def memory_expire(self, memory: Memory) -> None: """Method to receive memory expiration events. Releases held memories on current node. @@ -259,7 +271,7 @@ class EntanglementSwappingB(EntanglementProtocol, ABC): _registry: dict[str, type['EntanglementSwappingB']] = {} _global_formalism: str = KET_VECTOR_FORMALISM - def __init__(self, owner: "Node", name: str, hold_memo: "Memory"): + def __init__(self, owner: Node, name: str, hold_memo: Memory): """Constructor for entanglement swapping B protocol. Args: @@ -326,7 +338,7 @@ def decorator(protocol_class: type['EntanglementSwappingB']) -> type['Entangleme return decorator @classmethod - def create(cls, owner: "Node", name: str, hold_memo: "Memory", **kwargs) -> 'EntanglementSwappingB': + 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() diff --git a/sequence/entanglement_management/swapping/swapping_bds.py b/sequence/entanglement_management/swapping/swapping_bds.py index 95391905a..ff5e1cbe3 100644 --- a/sequence/entanglement_management/swapping/swapping_bds.py +++ b/sequence/entanglement_management/swapping/swapping_bds.py @@ -1,3 +1,5 @@ +"""The entanglement swapping protocol for Bell Diagonal State (BDS) formalism. +""" from __future__ import annotations from typing import TYPE_CHECKING @@ -39,7 +41,7 @@ class EntanglementSwappingA_BDS(EntanglementSwappingA): 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): + 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: diff --git a/sequence/entanglement_management/swapping/swapping_circuit.py b/sequence/entanglement_management/swapping/swapping_circuit.py index b0335c68c..018655a5c 100644 --- a/sequence/entanglement_management/swapping/swapping_circuit.py +++ b/sequence/entanglement_management/swapping/swapping_circuit.py @@ -1,17 +1,6 @@ -"""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. +"""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 @@ -174,7 +163,7 @@ class EntanglementSwappingB_Circuit(EntanglementSwappingB): x_z_cir.x(0) x_z_cir.z(0) - def __init__(self, owner: "Node", name: str, hold_memo: "Memory"): + def __init__(self, owner: Node, name: str, hold_memo: Memory): """Constructor for entanglement swapping B protocol. Args: diff --git a/uv.lock b/uv.lock index 50a11056e..54bb28e90 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'", @@ -2508,7 +2508,7 @@ wheels = [ [[package]] name = "sequence" -version = "0.8.5" +version = "0.8.6" source = { editable = "." } dependencies = [ { name = "dash" }, From c7e21530852c61b42e2024c716814dc5d91a4b35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:04:17 +0000 Subject: [PATCH 15/40] Bump nbconvert from 7.16.6 to 7.17.0 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.6 to 7.17.0. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.16.6...v7.17.0) --- updated-dependencies: - dependency-name: nbconvert dependency-version: 7.17.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 50a11056e..01d8485d7 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'", @@ -1475,7 +1475,7 @@ wheels = [ [[package]] name = "nbconvert" -version = "7.16.6" +version = "7.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beautifulsoup4" }, @@ -1493,9 +1493,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]] From 114953d44275fbd0bd1f03c821621281cfac652c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:17:59 +0000 Subject: [PATCH 16/40] Bump flask from 3.1.2 to 3.1.3 Bumps [flask](https://github.com/pallets/flask) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/pallets/flask/releases) - [Changelog](https://github.com/pallets/flask/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/flask/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: flask dependency-version: 3.1.3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 50a11056e..56671b928 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]] From 567de5aa85f6f7c3176050817f8c6b44169806eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 23:22:07 +0000 Subject: [PATCH 17/40] Bump werkzeug from 3.1.4 to 3.1.6 Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.4 to 3.1.6. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/werkzeug/compare/3.1.4...3.1.6) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/uv.lock b/uv.lock index 50a11056e..a858725e9 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'", @@ -2789,14 +2789,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]] From 0d8180dea261abd70796e831a1c836b10fc77545 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Wed, 4 Mar 2026 20:19:04 -0600 Subject: [PATCH 18/40] Bump version --- CHANGELOG.md | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 357dc3ce7..d2e6f60c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## [0.8.6] - 2026-3-02 +## [1.0.0] - 2026-3-02 ### Added - Created project scripts for each config generator. diff --git a/pyproject.toml b/pyproject.toml index c814fab06..ef92d98ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "sequence" -version = "0.8.6" +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" } ] From 462edd003bbf21ecae302805005b48930c13ede6 Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Thu, 5 Mar 2026 16:15:16 -0600 Subject: [PATCH 19/40] Update the lock --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 048a430ea..2aa59239e 100644 --- a/uv.lock +++ b/uv.lock @@ -2508,7 +2508,7 @@ wheels = [ [[package]] name = "sequence" -version = "0.8.6" +version = "1.0.0" source = { editable = "." } dependencies = [ { name = "dash" }, From 1a7c1c758916b754f970f1a8f6655dff30c3cc9d Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 00:37:44 -0500 Subject: [PATCH 20/40] [minor] cosmetic updates --- sequence/components/memory.py | 2 +- .../generation/single_heralded.py | 4 +--- .../entanglement_management/purification/bbpssw_bds.py | 3 +-- .../purification/bbpssw_protocol.py | 2 +- .../entanglement_management/swapping/swapping_bds.py | 6 +++--- sequence/resource_management/action_condition_set.py | 9 ++++++--- sequence/topology/dqc_net_topo.py | 1 - sequence/utils/noise.py | 1 - 8 files changed, 13 insertions(+), 15 deletions(-) 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/entanglement_management/generation/single_heralded.py b/sequence/entanglement_management/generation/single_heralded.py index ae3e6d3fc..60c6f4f07 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): """ diff --git a/sequence/entanglement_management/purification/bbpssw_bds.py b/sequence/entanglement_management/purification/bbpssw_bds.py index a1079a17a..57852d78e 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) """ diff --git a/sequence/entanglement_management/purification/bbpssw_protocol.py b/sequence/entanglement_management/purification/bbpssw_protocol.py index bd87e0a32..30b2d8095 100644 --- a/sequence/entanglement_management/purification/bbpssw_protocol.py +++ b/sequence/entanglement_management/purification/bbpssw_protocol.py @@ -171,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/swapping_bds.py b/sequence/entanglement_management/swapping/swapping_bds.py index ff5e1cbe3..9e573b66e 100644 --- a/sequence/entanglement_management/swapping/swapping_bds.py +++ b/sequence/entanglement_management/swapping/swapping_bds.py @@ -73,7 +73,7 @@ def start(self) -> None: assert self.right_memo.entangled_memory["node_id"] == self.right_node if self.owner.get_generator().random() < self.success_probability(): - log.logger.debug(f'swapping successed!') + log.logger.info(f'swapping successed!') 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) @@ -106,7 +106,7 @@ def start(self) -> None: expire_time=expire_time, meas_res=[]) else: - log.logger.debug(f'swapping failed!') + 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) @@ -154,7 +154,7 @@ def swapping_res(self) -> list[float]: else: bds_elems = [new_elem_1, new_elem_2, new_elem_3, new_elem_4] - log.logger.debug(f'before swapping, f = {left_state.state[0]:.6f}, {right_state.state[0]:.6f}; after swapping, f = {bds_elems[0]:.6f}') + 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 diff --git a/sequence/resource_management/action_condition_set.py b/sequence/resource_management/action_condition_set.py index 6c88279d5..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] @@ -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/topology/dqc_net_topo.py b/sequence/topology/dqc_net_topo.py index 9cbbba072..dbb0b7f60 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 diff --git a/sequence/utils/noise.py b/sequence/utils/noise.py index aaf71d72b..6b8d7989b 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: """ From e82b40cf59100446adfc8265744b884936850aeb Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 11:22:30 -0500 Subject: [PATCH 21/40] [minor] add a new message type EARLY_EXPIRE for resource manager --- .../resource_management/resource_manager.py | 139 +++++++++--------- sequence/resource_management/rule_manager.py | 1 - 2 files changed, 73 insertions(+), 67 deletions(-) diff --git a/sequence/resource_management/resource_manager.py b/sequence/resource_management/resource_manager.py index 17d22ab73..d94a04567 100644 --- a/sequence/resource_management/resource_manager.py +++ b/sequence/resource_management/resource_manager.py @@ -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 @@ -393,70 +397,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 + 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) + 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_PROTOCOL: - for p in self.owner.protocols: - if p.name == msg.protocol: - p.release() - - 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.""" diff --git a/sequence/resource_management/rule_manager.py b/sequence/resource_management/rule_manager.py index f191ae179..66e635c73 100644 --- a/sequence/resource_management/rule_manager.py +++ b/sequence/resource_management/rule_manager.py @@ -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 From d9dbbd5b7b8b232ed38ec8dd809b491aa55d59ab Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Tue, 10 Mar 2026 11:33:22 -0500 Subject: [PATCH 22/40] Delete util/. Move config to sequence/utils/ Keep draw_topo.py --- pyproject.toml | 17 ++++--- sequence/config_generators/__init__.py | 0 .../config_generator.py | 0 .../config_generator_as_memo_num.py | 0 .../config_generator_caveman.py | 0 .../config_generator_line.py | 0 .../config_generator_mesh.py | 0 .../config_generator_random.py | 0 .../config_generator_ring.py | 0 .../config_generator_star.py | 0 .../config_generator_tree.py | 0 {utils => sequence/utils}/draw_topo.py | 2 +- utils/cavity_vs_fidelity.py | 12 ----- utils/dict_timing.py | 28 ----------- utils/lock_test.py | 20 -------- utils/qmanager_timing.py | 50 ------------------- utils/timing_test.py | 46 ----------------- uv.lock | 11 ++++ 18 files changed, 21 insertions(+), 165 deletions(-) delete mode 100644 sequence/config_generators/__init__.py rename sequence/{config_generators => utils}/config_generator.py (100%) rename sequence/{config_generators => utils}/config_generator_as_memo_num.py (100%) rename sequence/{config_generators => utils}/config_generator_caveman.py (100%) rename sequence/{config_generators => utils}/config_generator_line.py (100%) rename sequence/{config_generators => utils}/config_generator_mesh.py (100%) rename sequence/{config_generators => utils}/config_generator_random.py (100%) rename sequence/{config_generators => utils}/config_generator_ring.py (100%) rename sequence/{config_generators => utils}/config_generator_star.py (100%) rename sequence/{config_generators => utils}/config_generator_tree.py (100%) rename {utils => sequence/utils}/draw_topo.py (98%) delete mode 100644 utils/cavity_vs_fidelity.py delete mode 100644 utils/dict_timing.py delete mode 100644 utils/lock_test.py delete mode 100644 utils/qmanager_timing.py delete mode 100644 utils/timing_test.py diff --git a/pyproject.toml b/pyproject.toml index ef92d98ba..6fba551bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,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", @@ -44,14 +45,14 @@ dependencies = [ ] [project.scripts] -generate-internet-autonomous = "sequence.config_generators.config_generator_as_memo_num:main" -generate-caveman = "sequence.config_generators.config_generator_caveman:main" -generate-linear = "sequence.config_generators.config_generator_line:main" -generate-mesh = "sequence.config_generators.config_generator_mesh:main" -generate-random = "sequence.config_generators.config_generator_random:main" -generate-ring = "sequence.config_generators.config_generator_ring:main" -generate-star = "sequence.config_generators.config_generator_star:main" -generate-tree = "sequence.config_generators.config_generator_tree:main" +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"] diff --git a/sequence/config_generators/__init__.py b/sequence/config_generators/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/sequence/config_generators/config_generator.py b/sequence/utils/config_generator.py similarity index 100% rename from sequence/config_generators/config_generator.py rename to sequence/utils/config_generator.py diff --git a/sequence/config_generators/config_generator_as_memo_num.py b/sequence/utils/config_generator_as_memo_num.py similarity index 100% rename from sequence/config_generators/config_generator_as_memo_num.py rename to sequence/utils/config_generator_as_memo_num.py diff --git a/sequence/config_generators/config_generator_caveman.py b/sequence/utils/config_generator_caveman.py similarity index 100% rename from sequence/config_generators/config_generator_caveman.py rename to sequence/utils/config_generator_caveman.py diff --git a/sequence/config_generators/config_generator_line.py b/sequence/utils/config_generator_line.py similarity index 100% rename from sequence/config_generators/config_generator_line.py rename to sequence/utils/config_generator_line.py diff --git a/sequence/config_generators/config_generator_mesh.py b/sequence/utils/config_generator_mesh.py similarity index 100% rename from sequence/config_generators/config_generator_mesh.py rename to sequence/utils/config_generator_mesh.py diff --git a/sequence/config_generators/config_generator_random.py b/sequence/utils/config_generator_random.py similarity index 100% rename from sequence/config_generators/config_generator_random.py rename to sequence/utils/config_generator_random.py diff --git a/sequence/config_generators/config_generator_ring.py b/sequence/utils/config_generator_ring.py similarity index 100% rename from sequence/config_generators/config_generator_ring.py rename to sequence/utils/config_generator_ring.py diff --git a/sequence/config_generators/config_generator_star.py b/sequence/utils/config_generator_star.py similarity index 100% rename from sequence/config_generators/config_generator_star.py rename to sequence/utils/config_generator_star.py diff --git a/sequence/config_generators/config_generator_tree.py b/sequence/utils/config_generator_tree.py similarity index 100% rename from sequence/config_generators/config_generator_tree.py rename to sequence/utils/config_generator_tree.py 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/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/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 2aa59239e..66c00e1e3 100644 --- a/uv.lock +++ b/uv.lock @@ -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" @@ -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" }, From 9455606e0abdcf90898e945619b9030fca3a1c6c Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Tue, 10 Mar 2026 13:45:22 -0500 Subject: [PATCH 23/40] Switch to np import --- sequence/kernel/quantum_state.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/sequence/kernel/quantum_state.py b/sequence/kernel/quantum_state.py index a5abcf8c8..3cd219142 100644 --- a/sequence/kernel/quantum_state.py +++ b/sequence/kernel/quantum_state.py @@ -5,15 +5,17 @@ 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 + +from numpy import pi, cos, sin, arange, log, log2, array, outer, trace, kron +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 +44,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 +56,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) @@ -112,7 +113,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,12 +143,12 @@ 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" @@ -197,7 +198,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: @@ -398,5 +399,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 From 46b7cf3fd0bef154335c1b6491802a68ce9d50cd Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 15:16:21 -0500 Subject: [PATCH 24/40] [minor] fix comments, added ResourceManager.expire_remote_rules() --- .../chapter1/discrete-event-simulation.md | 2 +- .../swapping/swapping_bds.py | 2 +- .../resource_management/resource_manager.py | 23 +++++++++++++++++-- sequence/utils/noise.py | 4 ++-- 4 files changed, 25 insertions(+), 6 deletions(-) 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/sequence/entanglement_management/swapping/swapping_bds.py b/sequence/entanglement_management/swapping/swapping_bds.py index 9e573b66e..ce9de0a50 100644 --- a/sequence/entanglement_management/swapping/swapping_bds.py +++ b/sequence/entanglement_management/swapping/swapping_bds.py @@ -73,7 +73,7 @@ def start(self) -> None: 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 successed!') + 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) diff --git a/sequence/resource_management/resource_manager.py b/sequence/resource_management/resource_manager.py index d94a04567..20d8493d3 100644 --- a/sequence/resource_management/resource_manager.py +++ b/sequence/resource_management/resource_manager.py @@ -389,7 +389,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. @@ -420,7 +420,7 @@ def received_message(self, src: str, msg: ResourceManagerMessage) -> None: case ResourceManagerMsgType.RESPONSE: protocol_name = msg.ini_protocol_name - protocol: EntanglementProtocol = None + protocol: EntanglementProtocol | None = None for p in self.pending_protocols: if p.name == protocol_name: protocol = p @@ -502,6 +502,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/utils/noise.py b/sequence/utils/noise.py index 6b8d7989b..a63940f21 100644 --- a/sequence/utils/noise.py +++ b/sequence/utils/noise.py @@ -79,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, From 7bc31919602628cac8cc7cf3d3fdb31fdd7f8c38 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 15:20:23 -0500 Subject: [PATCH 25/40] [minor] use np. prefix --- sequence/kernel/quantum_state.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/sequence/kernel/quantum_state.py b/sequence/kernel/quantum_state.py index 3cd219142..8f382d1cf 100644 --- a/sequence/kernel/quantum_state.py +++ b/sequence/kernel/quantum_state.py @@ -11,7 +11,6 @@ import math from abc import ABC -from numpy import pi, cos, sin, arange, log, log2, array, outer, trace, kron import numpy as np from numpy.random import Generator @@ -102,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. " \ @@ -152,7 +151,7 @@ def __init__(self, state: list[list[complex]], keys: list[int], truncation: int 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. " @@ -215,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]): @@ -234,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}") @@ -359,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 From 0d197d439b9a16fd3b7ff239490eb38a8872cda4 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 17:37:40 -0500 Subject: [PATCH 26/40] [minor] update the tutorial to catch up the recent updates; add NOTEs saying refer to chapter 6 Application module for standard usage --- docs/source/tutorial/chapter3/entangle.md | 8 +- docs/source/tutorial/chapter3/example3.py | 6 +- .../tutorial/chapter4/resource_management.md | 73 +++++++++---------- docs/source/tutorial/chapter4/two_flow.py | 16 ++-- .../tutorial/chapter5/network_manager.md | 7 +- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/docs/source/tutorial/chapter3/entangle.md b/docs/source/tutorial/chapter3/entangle.md index 530a4b29e..feea15ce1 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 using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at the entangement 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,10 @@ 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, 1, 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..3c9ec1055 100644 --- a/docs/source/tutorial/chapter3/example3.py +++ b/docs/source/tutorial/chapter3/example3.py @@ -24,10 +24,10 @@ 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, 1, 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 +59,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..477d9f8ac 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 using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at 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 @@ -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_request, eg_rule_condition, action_args, condition_args) node.resource_manager.load(rule) if index < (len(path) - 1): @@ -301,8 +304,8 @@ def add_eg_rules(index: int, path: List[RouterNode], middles: List[BSMNode]): 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) + protocol = EntanglementGenerationA.create(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) @@ -320,10 +323,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 +348,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 +363,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 +373,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 +381,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 +394,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 +436,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 +479,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 +489,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 +507,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..d1d139c75 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 using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at 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) From 906ab78d884037d9c6904e1c1feb4a1d56d7f019 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 18:37:50 -0500 Subject: [PATCH 27/40] [minor] fix comments --- docs/source/tutorial/chapter3/entangle.md | 5 +++-- docs/source/tutorial/chapter3/example3.py | 3 ++- .../tutorial/chapter4/resource_management.md | 16 +++++----------- docs/source/tutorial/chapter5/network_manager.md | 2 +- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/source/tutorial/chapter3/entangle.md b/docs/source/tutorial/chapter3/entangle.md index feea15ce1..b25e4829a 100644 --- a/docs/source/tutorial/chapter3/entangle.md +++ b/docs/source/tutorial/chapter3/entangle.md @@ -1,6 +1,6 @@ # Chapter 3: Entanglement Management -**NOTE**: The standard way to generate entanglement is using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at the entangement management module. +**NOTE**: The standard way to generate entanglement is using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at the entanglement management module. --- @@ -519,7 +519,8 @@ 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.create(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.create(self.owner, '%s.ESB' % self.owner.name, memo)] diff --git a/docs/source/tutorial/chapter3/example3.py b/docs/source/tutorial/chapter3/example3.py index 3c9ec1055..5460bb893 100644 --- a/docs/source/tutorial/chapter3/example3.py +++ b/docs/source/tutorial/chapter3/example3.py @@ -24,7 +24,8 @@ 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.create(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.create(self.owner, '%s.ESB' % self.owner.name, memo)] diff --git a/docs/source/tutorial/chapter4/resource_management.md b/docs/source/tutorial/chapter4/resource_management.md index 477d9f8ac..1a4fb5ab6 100644 --- a/docs/source/tutorial/chapter4/resource_management.md +++ b/docs/source/tutorial/chapter4/resource_management.md @@ -235,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() @@ -285,7 +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): @@ -302,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.create(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 diff --git a/docs/source/tutorial/chapter5/network_manager.md b/docs/source/tutorial/chapter5/network_manager.md index d1d139c75..58357ad22 100644 --- a/docs/source/tutorial/chapter5/network_manager.md +++ b/docs/source/tutorial/chapter5/network_manager.md @@ -2,7 +2,7 @@ **NOTE**: The standard way to generate entanglement is using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at 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 From 4584239ac93d9ac3072eea83a77a62cfe6e9efaa Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 18:47:45 -0500 Subject: [PATCH 28/40] [minor] --- docs/source/tutorial/chapter0/prerequisite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 309a59c6965b49a5d83c328f1ca37723aac4bb91 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Tue, 10 Mar 2026 19:01:05 -0500 Subject: [PATCH 29/40] [minor] resolve comments --- docs/source/tutorial/chapter3/entangle.md | 4 ++-- docs/source/tutorial/chapter3/example3.py | 2 +- docs/source/tutorial/chapter4/resource_management.md | 2 +- docs/source/tutorial/chapter5/network_manager.md | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/tutorial/chapter3/entangle.md b/docs/source/tutorial/chapter3/entangle.md index b25e4829a..a5bf67c37 100644 --- a/docs/source/tutorial/chapter3/entangle.md +++ b/docs/source/tutorial/chapter3/entangle.md @@ -1,6 +1,6 @@ # Chapter 3: Entanglement Management -**NOTE**: The standard way to generate entanglement is using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at the entanglement management module. +**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. --- @@ -520,7 +520,7 @@ class SimpleManager: left_memo = self.owner.components[self.memo_names[0]] right_memo = self.owner.components[self.memo_names[1]] self.owner.protocols = [EntanglementSwappingA.create(self.owner, 'ESA', left_memo, right_memo, - success_prob = 1, degradation=0.99)] + success_prob=1, degradation=0.99)] else: memo = self.owner.components[self.memo_names[0]] self.owner.protocols = [EntanglementSwappingB.create(self.owner, '%s.ESB' % self.owner.name, memo)] diff --git a/docs/source/tutorial/chapter3/example3.py b/docs/source/tutorial/chapter3/example3.py index 5460bb893..5f1e0b9d0 100644 --- a/docs/source/tutorial/chapter3/example3.py +++ b/docs/source/tutorial/chapter3/example3.py @@ -25,7 +25,7 @@ def create_protocol(self): left_memo = self.owner.components[self.memo_names[0]] right_memo = self.owner.components[self.memo_names[1]] self.owner.protocols = [EntanglementSwappingA.create(self.owner, 'ESA', left_memo, right_memo, - success_prob = 1, degradation=0.99)] + success_prob=1, degradation=0.99)] else: memo = self.owner.components[self.memo_names[0]] self.owner.protocols = [EntanglementSwappingB.create(self.owner, '%s.ESB' % self.owner.name, memo)] diff --git a/docs/source/tutorial/chapter4/resource_management.md b/docs/source/tutorial/chapter4/resource_management.md index 1a4fb5ab6..57d93738d 100644 --- a/docs/source/tutorial/chapter4/resource_management.md +++ b/docs/source/tutorial/chapter4/resource_management.md @@ -1,6 +1,6 @@ # Chapter 4: Resource Management -**NOTE**: The standard way to generate entanglement is using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at the resource management module. +**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. diff --git a/docs/source/tutorial/chapter5/network_manager.md b/docs/source/tutorial/chapter5/network_manager.md index 58357ad22..e21e17240 100644 --- a/docs/source/tutorial/chapter5/network_manager.md +++ b/docs/source/tutorial/chapter5/network_manager.md @@ -1,6 +1,6 @@ # Chapter 5: Network Manager -**NOTE**: The standard way to generate entanglement is using the `app` module, which is discussed in chapter 6. The purpose of this chapter is explaining what is happening under the hood at the network management module. +**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. --- From d975066c52074b74a95e073b22cb40d6293c845b Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Wed, 11 Mar 2026 20:01:39 -0500 Subject: [PATCH 30/40] Add passing init to nw manager and rs manager. Create missing inits in parents. --- sequence/network_management/network_manager.py | 10 ++++++++++ sequence/protocol.py | 3 +++ sequence/resource_management/resource_manager.py | 3 +++ sequence/topology/node.py | 7 ++++--- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/sequence/network_management/network_manager.py b/sequence/network_management/network_manager.py index aa0f1840f..5d3d183f4 100644 --- a/sequence/network_management/network_manager.py +++ b/sequence/network_management/network_manager.py @@ -105,6 +105,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 +119,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) @@ -157,6 +164,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. diff --git a/sequence/protocol.py b/sequence/protocol.py index 372596978..92c8243e9 100644 --- a/sequence/protocol.py +++ b/sequence/protocol.py @@ -40,6 +40,9 @@ def received_message(self, src: str, msg: "Message"): pass + def init(self): + pass + def __str__(self) -> str: return self.name diff --git a/sequence/resource_management/resource_manager.py b/sequence/resource_management/resource_manager.py index 17d22ab73..2759e595c 100644 --- a/sequence/resource_management/resource_manager.py +++ b/sequence/resource_management/resource_manager.py @@ -145,6 +145,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. diff --git a/sequence/topology/node.py b/sequence/topology/node.py index d81b4b091..44a0c4567 100644 --- a/sequence/topology/node.py +++ b/sequence/topology/node.py @@ -390,10 +390,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 From 3ebcc1900a4f3ac2cbc6b7be9bd9051b01971abe Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Wed, 11 Mar 2026 20:04:27 -0500 Subject: [PATCH 31/40] Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 426758ec9..6ac21ccf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0] - 2026-3-02 ### Added +- Add empty init() to Protocol to satisfy class hierarchy. - Created project scripts for each config generator. - Created the `swapping` module that has the following classes. The subclasses use the registry decorator (factory pattern). - Base class: `EntanglementSwappingA` & `EntanglementSwappingB` @@ -14,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Subclass for Bell diagonal state: `EntanglementSwappingA_BDS` & `EntanglementSwappingB_BDS` ### Changed +- 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 From 23c9fab45c38f3e12a2932be5fa7b9c10544600e Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Thu, 12 Mar 2026 12:09:11 -0500 Subject: [PATCH 32/40] [minor] Docstring, comment, logging, and various cosmetic updates --- CHANGELOG.md | 5 +++++ sequence/app/request_app.py | 4 +++- sequence/network_management/reservation.py | 25 ++++++++++++---------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 805bae958..38195647b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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. ### Removed - The old `swapping.py` is removed. diff --git a/sequence/app/request_app.py b/sequence/app/request_app.py index 5e213c094..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 diff --git a/sequence/network_management/reservation.py b/sequence/network_management/reservation.py index eddc3c72b..a8a154d87 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 From 35a6cda01c550dc9d2e66e9c3bbb407ad313031a Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Thu, 12 Mar 2026 19:51:17 -0500 Subject: [PATCH 33/40] [major] Create a parent class RoutingProtocol for the current classes StaticRoutingProtocol and DistributedRoutingProtocol. The register decorator is used to make it easy to plug in new routing protocols in the future. --- CHANGELOG.md | 2 +- .../three_node_eg_ep_es.ipynb | 12 +- sequence/gui/simulator_bindings.py | 2 +- sequence/network_management/__init__.py | 2 +- .../network_management/network_manager.py | 23 +-- .../network_management/routing/__init__.py | 8 ++ .../routing/routing_base.py | 134 ++++++++++++++++++ .../{ => routing}/routing_distributed.py | 47 +++--- .../routing/routing_static.py | 41 ++++++ sequence/network_management/routing_static.py | 79 ----------- sequence/topology/dqc_net_topo.py | 2 +- sequence/topology/router_net_topo.py | 5 +- tests/network_management/line_topo.json | 2 +- tests/network_management/ring_topo.json | 2 +- .../test_network_manager.py | 12 +- tests/network_management/test_routing.py | 18 +-- tests/topology/dqc_node_net_topo_simple.json | 2 +- tests/topology/test_dqc_node_net_topo.py | 1 + 18 files changed, 236 insertions(+), 158 deletions(-) create mode 100644 sequence/network_management/routing/__init__.py create mode 100644 sequence/network_management/routing/routing_base.py rename sequence/network_management/{ => routing}/routing_distributed.py (95%) create mode 100644 sequence/network_management/routing/routing_static.py delete mode 100644 sequence/network_management/routing_static.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac21ccf4..d0e60e915 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### 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/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/sequence/gui/simulator_bindings.py b/sequence/gui/simulator_bindings.py index 0a570df7a..84aaf049e 100644 --- a/sequence/gui/simulator_bindings.py +++ b/sequence/gui/simulator_bindings.py @@ -112,7 +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( + node.network_manager.protocol_stack[0].update_forwarding_rule( dst, next_node) 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 5d3d183f4..dc854da56 100644 --- a/sequence/network_management/network_manager.py +++ b/sequence/network_management/network_manager.py @@ -17,13 +17,11 @@ from ..topology.node import QuantumRouter 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 @@ -151,7 +149,9 @@ def __init__(self, owner: "QuantumRouter", memory_array_name: str, component_tem 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) + RoutingProtocol.set_global_type(routing_type) + self.routing_protocol = RoutingProtocol.create(owner, name=routing_type) # Create and load the stack to protocol_stack protocols: list = self.create_stack() self.load_stack(protocols) @@ -187,21 +187,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/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..3eeac702c --- /dev/null +++ b/sequence/network_management/routing/routing_base.py @@ -0,0 +1,134 @@ +""" +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]] = {} + _global_type: str = ROUTING_STATIC + + 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 set_global_type(cls, protocol_type: str): + """Set the global routing protocol type. + + Args: + protocol_type (str): type of the routing protocol. + """ + if protocol_type not in cls._registry: + raise ValueError(f"Routing protocol type {protocol_type} not registered.") + cls._global_type = protocol_type + + @classmethod + def get_global_type(cls) -> str: + """Get the global routing protocol type. + + Returns: + str: global routing protocol type. + """ + return cls._global_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) -> RoutingProtocol: + """Factory method to create a routing protocol instance. + + Args: + owner (QuantumRouter): node protocol is attached to. + name (str): name of protocol instance. + protocol_type (str): type of the routing protocol. If None, use global type. + + Returns: + RoutingProtocol: instance of the routing protocol. + """ + protocol_type = cls._global_type + 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..874462844 100644 --- a/sequence/network_management/routing_distributed.py +++ b/sequence/network_management/routing/routing_distributed.py @@ -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: @@ -291,8 +292,7 @@ class DistributedRoutingProtocol(Protocol): 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' + 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/topology/dqc_net_topo.py b/sequence/topology/dqc_net_topo.py index 9cbbba072..9db87a3db 100644 --- a/sequence/topology/dqc_net_topo.py +++ b/sequence/topology/dqc_net_topo.py @@ -191,7 +191,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/router_net_topo.py b/sequence/topology/router_net_topo.py index a8582fcb8..1a30fe010 100644 --- a/sequence/topology/router_net_topo.py +++ b/sequence/topology/router_net_topo.py @@ -2,8 +2,7 @@ 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_VECTOR_FORMALISM, QuantumManager @@ -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/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_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..5774ce0ac 100644 --- a/tests/topology/test_dqc_node_net_topo.py +++ b/tests/topology/test_dqc_node_net_topo.py @@ -57,3 +57,4 @@ def test_sequential_simulation_dqc_node_net_topo_simple(): assert other in routing.forwarding_table +test_sequential_simulation_dqc_node_net_topo_simple() \ No newline at end of file From dbce95f28513a3d8f41f61a0363b3a83786ec781 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Thu, 12 Mar 2026 19:54:51 -0500 Subject: [PATCH 34/40] [minor] update CHANGELOG --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0e60e915..8c0538b8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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. -- Created the `swapping` module that has the following classes. The subclasses use the registry decorator (factory pattern). +- 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` From e00b3855a845012c98f35ecd93812143ef77b078 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Thu, 12 Mar 2026 20:06:46 -0500 Subject: [PATCH 35/40] [minor] cosmetic updates --- docs/source/tutorial/chapter6/custom_app.py | 4 ++-- sequence/network_management/network_manager.py | 10 ++++------ .../network_management/routing/routing_distributed.py | 4 ++-- sequence/network_management/rsvp.py | 7 ++++--- 4 files changed, 12 insertions(+), 13 deletions(-) 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/sequence/network_management/network_manager.py b/sequence/network_management/network_manager.py index dc854da56..215b3afef 100644 --- a/sequence/network_management/network_manager.py +++ b/sequence/network_management/network_manager.py @@ -3,19 +3,16 @@ 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 ..utils import log from .forwarding import ForwardingProtocol @@ -63,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 @@ -134,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. @@ -143,7 +141,7 @@ 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 = {} diff --git a/sequence/network_management/routing/routing_distributed.py b/sequence/network_management/routing/routing_distributed.py index 874462844..1fb2e70b4 100644 --- a/sequence/network_management/routing/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 @@ -291,7 +291,7 @@ class DistributedRoutingProtocol(RoutingProtocol): 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): + def __init__(self, owner: QuantumRouter, name: str): super().__init__(owner, name, protocol_type=ROUTING_DISTRIBUTED) self.owner.protocols.append(self) self.lsdb = LinkStateDB() 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: From 9e683bf99442ecbce15e7cd8f3acf218370b8db6 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Thu, 12 Mar 2026 20:11:44 -0500 Subject: [PATCH 36/40] [minor] resolve comments --- sequence/network_management/routing/routing_base.py | 3 +-- tests/topology/test_dqc_node_net_topo.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/sequence/network_management/routing/routing_base.py b/sequence/network_management/routing/routing_base.py index 3eeac702c..fe00ee756 100644 --- a/sequence/network_management/routing/routing_base.py +++ b/sequence/network_management/routing/routing_base.py @@ -76,12 +76,11 @@ def decorator(protocol_class: type[RoutingProtocol]): @classmethod def create(cls, owner: QuantumRouter, name: str) -> RoutingProtocol: - """Factory method to create a routing protocol instance. + """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. - protocol_type (str): type of the routing protocol. If None, use global type. Returns: RoutingProtocol: instance of the routing protocol. diff --git a/tests/topology/test_dqc_node_net_topo.py b/tests/topology/test_dqc_node_net_topo.py index 5774ce0ac..fe314d4b1 100644 --- a/tests/topology/test_dqc_node_net_topo.py +++ b/tests/topology/test_dqc_node_net_topo.py @@ -55,6 +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 - - -test_sequential_simulation_dqc_node_net_topo_simple() \ No newline at end of file From 0296107a280d028e60876cf7dbe5567d5c52a879 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Thu, 12 Mar 2026 20:12:00 -0500 Subject: [PATCH 37/40] [minor] resolve comments --- sequence/gui/simulator_bindings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sequence/gui/simulator_bindings.py b/sequence/gui/simulator_bindings.py index 84aaf049e..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].update_forwarding_rule( - dst, - next_node) + node.network_manager.routing_protocol.update_forwarding_rule(dst,next_node) def init_logging(self): set_logger( From 3cb617e6f89b78b03366a4eeec312020319a38ff Mon Sep 17 00:00:00 2001 From: Robert Hayek Date: Fri, 13 Mar 2026 14:11:34 -0500 Subject: [PATCH 38/40] Fix `__str__` format in `Reservation` class and update related unit test. --- sequence/network_management/reservation.py | 2 +- tests/network_management/test_reservation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sequence/network_management/reservation.py b/sequence/network_management/reservation.py index a8a154d87..b741be9ec 100644 --- a/sequence/network_management/reservation.py +++ b/sequence/network_management/reservation.py @@ -53,7 +53,7 @@ def __init__(self, initiator: str, responder: str, start_time: int, assert self.memory_size > 0 def __str__(self) -> str: - return (f'|identity={self.identity}, initiator={self.initiator}; responder={self.responder}; path={self.path}; ' + 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}|') 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 From b51866a9c443139552987b6c9c458778b4afd0c3 Mon Sep 17 00:00:00 2001 From: Caitao Zhan Date: Mon, 16 Mar 2026 14:09:17 -0500 Subject: [PATCH 39/40] [minor] deleted the global_type for RoutingProtocol --- .../network_management/network_manager.py | 3 +-- .../routing/routing_base.py | 24 +------------------ 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/sequence/network_management/network_manager.py b/sequence/network_management/network_manager.py index 215b3afef..9c5d38c2d 100644 --- a/sequence/network_management/network_manager.py +++ b/sequence/network_management/network_manager.py @@ -148,8 +148,7 @@ def __init__(self, owner: QuantumRouter, memory_array_name: str, component_templ self.protocol_stack = [] self.forwarding_table = {} routing_type = component_templates.get('routing', ROUTING_STATIC) - RoutingProtocol.set_global_type(routing_type) - self.routing_protocol = RoutingProtocol.create(owner, name=routing_type) + 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) diff --git a/sequence/network_management/routing/routing_base.py b/sequence/network_management/routing/routing_base.py index fe00ee756..fc933481a 100644 --- a/sequence/network_management/routing/routing_base.py +++ b/sequence/network_management/routing/routing_base.py @@ -24,7 +24,6 @@ class RoutingProtocol(Protocol, ABC): """Abstract base class for routing protocols. """ _registry: dict[str, type[RoutingProtocol]] = {} - _global_type: str = ROUTING_STATIC def __init__(self, owner: QuantumRouter, name: str, protocol_type: str): """Constructor for routing protocol. @@ -36,26 +35,6 @@ def __init__(self, owner: QuantumRouter, name: str, protocol_type: str): """ super().__init__(owner, name, protocol_type) - @classmethod - def set_global_type(cls, protocol_type: str): - """Set the global routing protocol type. - - Args: - protocol_type (str): type of the routing protocol. - """ - if protocol_type not in cls._registry: - raise ValueError(f"Routing protocol type {protocol_type} not registered.") - cls._global_type = protocol_type - - @classmethod - def get_global_type(cls) -> str: - """Get the global routing protocol type. - - Returns: - str: global routing protocol type. - """ - return cls._global_type - @classmethod def register(cls, protocol_type: str, protocol_class: type[RoutingProtocol] = None): """Register a routing protocol class. @@ -75,7 +54,7 @@ def decorator(protocol_class: type[RoutingProtocol]): return decorator @classmethod - def create(cls, owner: QuantumRouter, name: str) -> RoutingProtocol: + def create(cls, owner: QuantumRouter, name: str, protocol_type) -> RoutingProtocol: """Factory method to create a routing protocol instance using the global routing type. Args: @@ -85,7 +64,6 @@ def create(cls, owner: QuantumRouter, name: str) -> RoutingProtocol: Returns: RoutingProtocol: instance of the routing protocol. """ - protocol_type = cls._global_type try: protocol_class = cls._registry[protocol_type] return protocol_class(owner, name) From 7840817a567ba7762fc135d70acaf53171041773 Mon Sep 17 00:00:00 2001 From: "roman.gordienko" Date: Thu, 2 Apr 2026 07:42:52 +0300 Subject: [PATCH 40/40] Deprecate Python 3.11 (#287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove 3.11 from CI matrix and pyproject.toml classifiers - Update requires-python to >=3.12 - Update README minimum version - Modernize typing: Optional→X|None, List→list, Dict→dict, Tuple→tuple, Type→type (PEP 585/604) --- .github/workflows/validation.yml | 1 - README.md | 2 +- pyproject.toml | 3 +-- sequence/components/circuit.py | 1 - .../generation/generation_message.py | 1 - .../entanglement_management/generation/single_heralded.py | 2 +- .../entanglement_management/purification/bbpssw_bds.py | 2 +- .../purification/bbpssw_protocol.py | 2 +- sequence/resource_management/resource_manager.py | 4 ++-- sequence/resource_management/rule_manager.py | 6 +++--- sequence/topology/node.py | 8 +++++--- sequence/topology/topology.py | 2 +- 12 files changed, 16 insertions(+), 18 deletions(-) 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/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/pyproject.toml b/pyproject.toml index 6fba551bf..30ebf73c4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" 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/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 60c6f4f07..e25067da4 100644 --- a/sequence/entanglement_management/generation/single_heralded.py +++ b/sequence/entanglement_management/generation/single_heralded.py @@ -248,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 57852d78e..1796abae6 100644 --- a/sequence/entanglement_management/purification/bbpssw_bds.py +++ b/sequence/entanglement_management/purification/bbpssw_bds.py @@ -151,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_protocol.py b/sequence/entanglement_management/purification/bbpssw_protocol.py index 30b2d8095..de2760969 100644 --- a/sequence/entanglement_management/purification/bbpssw_protocol.py +++ b/sequence/entanglement_management/purification/bbpssw_protocol.py @@ -96,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. diff --git a/sequence/resource_management/resource_manager.py b/sequence/resource_management/resource_manager.py index a04da7b67..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 ( @@ -317,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: diff --git a/sequence/resource_management/rule_manager.py b/sequence/resource_management/rule_manager.py index 66e635c73..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] @@ -134,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/node.py b/sequence/topology/node.py index 44a0c4567..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) 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