From a1d4c659da9d6a9c417dc29b87169dc1a323ad5b Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Tue, 19 Sep 2023 15:18:06 -0700 Subject: [PATCH 01/12] Bug fixes for GUI --- src/gui/app.py | 2 +- src/gui/menus.py | 15 ++++++++------- src/gui/simulator_bindings.py | 2 +- src/gui/user_templates.json | 4 ++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index f23de8d7..c3985b39 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -1018,7 +1018,7 @@ def template_menu(edgeType, save_click, temp, temp_type, temp_name): parsed = {temp_name: self.parse_node(temp)} new_templates[temp_type].update(parsed) self.templates = new_templates - return [dash.no_update, 'Template Saved', '', ''] + return [dash.no_update, 'Template Saved', dash.no_update, ''] else: opts = list(self.templates['Memory'].keys()) return [router_template, '', opts, ''] diff --git a/src/gui/menus.py b/src/gui/menus.py index 99a16390..852112d8 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -276,7 +276,8 @@ def getTimeUnits(id_extra): dbc.Input( id='mem_size', className='memo_size', - placeholder='Memory Array Size' + placeholder='Memory Array Size', + type='number' ), dbc.Label('Memory Type'), dcc.Dropdown( @@ -382,7 +383,7 @@ def getTimeUnits(id_extra): place='Enter Node ID' ), getDropdownField( - 'Quantum_Router', + 'QuantumRouter', OPTIONS, 'Type:', 'type_menu', @@ -497,7 +498,7 @@ def getTimeUnits(id_extra): place='Enter ID' ), getDropdownField( - 'Quantum_Router', + 'QuantumRouter', OPTIONS, 'Type:', 'template_type_menu', @@ -847,7 +848,7 @@ def makeLegend(values): style={ 'position': 'relative', 'top': '0px', - 'left': '0px' + 'left': '10px' } ), dbc.Row( @@ -900,12 +901,12 @@ def makeLegend(values): href='https://github.com/sequence-toolbox/SeQUeNCe/issues', # nopep8 ), ], - nav=True, + label="More", group=True, size='sm', + nav=True, in_navbar=True, - label="More", - right=True, + # right=True, toggle_style={ 'color': 'white' } diff --git a/src/gui/simulator_bindings.py b/src/gui/simulator_bindings.py index 8830dfdd..0a570df7 100644 --- a/src/gui/simulator_bindings.py +++ b/src/gui/simulator_bindings.py @@ -43,7 +43,7 @@ def __init__(self, sim_time: int, time_scale: int, logging: str, sim_name, confi self.timeline, **node_temp ) - elif node_type == "Quantum_Router": + elif node_type == "QuantumRouter": mem = node_temp.pop('mem_type') mem_config = self.sim_templates[mem].copy() node_in = QuantumRouter( diff --git a/src/gui/user_templates.json b/src/gui/user_templates.json index 3c08eebd..534bbdef 100644 --- a/src/gui/user_templates.json +++ b/src/gui/user_templates.json @@ -35,8 +35,8 @@ } }, "QuantumErrorCorrection": {}, - "Quantum_Repeater": {}, - "Quantum_Router": { + "QuantumRepeater": {}, + "QuantumRouter": { "default_router": { "mem_type": "default_memory", "memo_size": 50 From 892d3ac3fe68d712238aa0ead34e97cc6459ab18 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Tue, 19 Sep 2023 21:29:34 -0700 Subject: [PATCH 02/12] Dynamic memory array size for quantum router --- src/gui/app.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index c3985b39..f0d5fbac 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -294,12 +294,17 @@ def graph_to_topology(self): nodes_top = [] for node in nodes: + node_type = node[1]['node_type'] + node_template_name = node[1]['data']['template'] + node_template = self.templates[node_type][node_template_name] + nodes_top.append( { Topology.NAME: node[1]['label'], - Topology.TYPE: node[1]['node_type'], + Topology.TYPE: node_type, Topology.SEED: None, - RouterNetTopo.MEMO_ARRAY_SIZE: 50 + RouterNetTopo.MEMO_ARRAY_SIZE: node_template['memo_size'], + Topology.TEMPLATE: node_template_name } ) From effaed4e65a2542393e0a52ea1a2265fd8c095f8 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Tue, 19 Sep 2023 22:26:11 -0700 Subject: [PATCH 03/12] Fix some GUI typing and bugs to templates when adding nodes --- src/gui/app.py | 44 +++++++++++++++++----------------- src/gui/default_templates.json | 43 +++++++++++++++++++++++++++++++++ src/gui/layout.py | 10 ++++---- src/gui/menus.py | 31 ++++++++++++++---------- 4 files changed, 87 insertions(+), 41 deletions(-) create mode 100644 src/gui/default_templates.json diff --git a/src/gui/app.py b/src/gui/app.py index f0d5fbac..11480b92 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -54,7 +54,7 @@ ] DIRECTORY, _ = os.path.split(__file__) -TEMPLATES = '/user_templates.json' +TEMPLATES = '/default_templates.json' class QuantumGUI: @@ -101,31 +101,28 @@ def __init__(self, graph_in, templates=None, delays=None, tdm=None): self.data = graph_in self.cc_delays = delays self.qc_tdm = tdm - self.defaults = {} - with open(DIRECTORY + '/default_params.json', 'r') as json_file: - self.defaults = json.load(json_file) - json_file.close() + # self.defaults = {} + # with open(DIRECTORY + '/default_params.json', 'r') as json_file: + # self.defaults = json.load(json_file) + # json_file.close() if templates is None: - if os.path.exists(DIRECTORY + TEMPLATES): with open(DIRECTORY + TEMPLATES, 'r') as json_file: self.templates = json.load(json_file) - json_file.close() else: user_defaults = {} for x in TYPES: user_defaults[x] = {} self.templates = user_defaults - with open(DIRECTORY + '/user_templates.json', 'w') as outfile: + with open(DIRECTORY + TEMPLATES, 'w') as outfile: json.dump( user_defaults, outfile, sort_keys=True, indent=4 ) - outfile.close() # TODO: re-add simulation # self.simulation = GUI_Sim(0, 0, 'NOTSET', 'init', self) @@ -298,15 +295,16 @@ def graph_to_topology(self): node_template_name = node[1]['data']['template'] node_template = self.templates[node_type][node_template_name] - nodes_top.append( - { - Topology.NAME: node[1]['label'], - Topology.TYPE: node_type, - Topology.SEED: None, - RouterNetTopo.MEMO_ARRAY_SIZE: node_template['memo_size'], - Topology.TEMPLATE: node_template_name - } - ) + node_dict = { + Topology.NAME: node[1]['label'], + Topology.TYPE: node_type, + Topology.SEED: None, + Topology.TEMPLATE: node_template_name + } + if node_type == RouterNetTopo.QUANTUM_ROUTER: + node_dict[RouterNetTopo.MEMO_ARRAY_SIZE] = node_template['memo_size'] + + nodes_top.append(node_dict) qconnections = [] @@ -348,7 +346,7 @@ def graph_to_topology(self): return output - def _callback_add_node(self, add_node_name, add_node_type): + def _callback_add_node(self, add_node_name, add_node_type, add_node_template): """Function which adds a node with the given name and type to the current graph. Args: @@ -372,7 +370,7 @@ def _callback_add_node(self, add_node_name, add_node_type): if data == add_node_name: return [dash.no_update, 'Node already exists'] - new_node = GraphNode(add_node_name, add_node_type, 'test') + new_node = GraphNode(add_node_name, add_node_type, add_node_template) new_graph.add_node( add_node_name, label=add_node_name, @@ -563,7 +561,7 @@ def save_all(self, path): if not os.path.exists(DIRECTORY+'/data'): os.mkdir(DIRECTORY+'/data') - self.save_templates(new_path) + # self.save_templates(new_path) self.save_simulation(new_path) self.save_topology(new_path) return new_path @@ -695,6 +693,7 @@ def get_app(self, name): State('node_to_add_name', 'value'), State('type_menu', 'value'), + State('add_template_menu', 'value'), State('edge_properties', 'children'), State('from_node', 'value'), State('to_node', 'value'), @@ -712,6 +711,7 @@ def edit_graph( update_delay, node_name, node_to_add_type, + node_to_add_template, properties, from_node, to_node, @@ -740,7 +740,7 @@ def edit_graph( input_id = ctx.triggered[0]['prop_id'].split('.')[0] # print(input_id) if input_id == 'add_node': - info = self._callback_add_node(node_name, node_to_add_type) + info = self._callback_add_node(node_name, node_to_add_type, node_to_add_template) graph_data = info[0] err_msg = info[1] diff --git a/src/gui/default_templates.json b/src/gui/default_templates.json new file mode 100644 index 00000000..eaadf1d8 --- /dev/null +++ b/src/gui/default_templates.json @@ -0,0 +1,43 @@ +{ + "BSMNode": { + "default_BSM": { + "detector_1": "default_detector", + "detector_2": "default_detector" + } + }, + "Detector": { + "default_detector": { + "count_rate": 50000000.0, + "dark_count": 0, + "efficiency": 0.8, + "resolution": 100.0 + } + }, + "Entanglement": { + "default_entanglement": { + "degredation": 0.99, + "succ_prob": 0.9 + } + }, + "Memory": { + "default_memory": { + "coherence_time": 1300000000000.0, + "efficiency": 0.75, + "frequency": 2000.0, + "raw_fidelity": 0.85 + } + }, + "PhotonSource": {}, + "QKDNode": { + "default_QKD": { + "encoding": "polarization", + "stack_size": 5 + } + }, + "QuantumRouter": { + "default_router": { + "mem_type": "default_memory", + "memo_size": 50 + } + } +} \ No newline at end of file diff --git a/src/gui/layout.py b/src/gui/layout.py index b31e2c95..c3543001 100644 --- a/src/gui/layout.py +++ b/src/gui/layout.py @@ -175,15 +175,13 @@ def graph_element(graph, name): Constant containing all available class in the GUI """ TYPES = [ - 'Quantum_Repeater', 'QuantumRouter', - 'Photon_Source', + 'PhotonSource', 'Detector', - # 'QuantumErrorCorrection', 'BSM_node', - # 'Temp', 'Memory', - # 'Protocol' + 'QKD', + 'Entanglement' ] @@ -196,7 +194,7 @@ def graph_element(graph, name): def genImages(): images = { - 'Quantum_Repeater': 'repeater.png', + 'QuantumRepeater': 'repeater.png', 'QuantumRouter': 'router.png', 'Photon_Source': 'photonsource.png', 'Detector': 'detector.png', diff --git a/src/gui/menus.py b/src/gui/menus.py index 852112d8..d84e8188 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -18,16 +18,17 @@ Mapping of all types in the GUI to their representative colors """ TYPE_COLORS = { - 'Quantum_Repeater': '#4D9DE0', - 'QuantumRouter': '#E15554', - 'Photon_Source': '#E1BC29', - 'Detector': '#3BB273', - 'QuantumErrorCorrection': '#7768AE ', - 'BSM_node': '#FFC857', + 'QuantumRepeater': '#4d9de0', + 'QuantumRouter': '#e15554', + 'PhotonSource': '#e1bc29', + 'Detector': '#3bb273', + 'QuantumErrorCorrection': '#7768ae', + 'BSMNode': '#ffc857', 'Quantum': '#8634eb', 'Classical': '#345feb', 'Memory': '#8a34ab', - 'Temp': '#084C61', + 'Temp': '#084c61', + 'QKDNode': '#cc99ff' } @@ -54,24 +55,28 @@ # }, { 'label': 'BSM Node', - 'value': 'BSM_node' - }, - { - 'label': 'Quantum Repeater', - 'value': 'Quantum_Repeater' + 'value': 'BSMNode' }, # { + # 'label': 'Quantum Repeater', + # 'value': 'Quantum_Repeater' + # }, + # { # 'label': 'Quantum Error Correction', # 'value': 'QuantumErrorCorrection' # }, { 'label': 'Photon Source', - 'value': 'Photon_Source' + 'value': 'PhotonSource' }, # { # 'label': 'Temp', # 'value': 'Temp' # } + { + 'label': 'QKD Node', + 'value': 'QKDNode' + } ] DIRECTORY, _ = os.path.split(__file__) From a0b8e6cae9de625cc41c0666afe193066a6f92af Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Wed, 20 Sep 2023 10:52:02 -0700 Subject: [PATCH 04/12] Fix BSM node bugs for GUI --- src/gui/app.py | 12 ++++++++++-- src/gui/menus.py | 11 +++++++++-- tests/kernel/test_quantum_state.py | 5 +++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index 11480b92..50d34633 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -1012,7 +1012,7 @@ def template_menu(edgeType, save_click, temp, temp_type, temp_name): return [quantum_memory_template, '', '', ''] elif edgeType == 'Detector': return [detector_template, '', '', ''] - elif edgeType == 'BSM_node': + elif edgeType == 'BSMNode': opts = list(self.templates['Detector'].keys()) return [bsm_template, '', '', opts] elif input_id == 'save_template': @@ -1202,7 +1202,15 @@ def updateTypeMenu(data): return [makeDropdownOptions(data), data[0]] @app.callback( - Output("detec_type", "options"), + Output("detec_type_1", "options"), + Input('detec_opts', 'data'), + prevent_initial_call=True, + ) + def updateTypeMenu(data): + return makeDropdownOptions(data) + + @app.callback( + Output("detec_type_2", "options"), Input('detec_opts', 'data'), prevent_initial_call=True, ) diff --git a/src/gui/menus.py b/src/gui/menus.py index d84e8188..f58ce77c 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -357,9 +357,16 @@ def getTimeUnits(id_extra): ] bsm_template = [ - dbc.Label('Detector Type'), + dbc.Label('Detector 1 Type'), dcc.Dropdown( - id='detec_type', + id='detec_type_1', + className='detector_type', + value='', + options=[] + ), + dbc.Label('Detector 2 Type'), + dcc.Dropdown( + id='detec_type_2', className='detector_type', value='', options=[] diff --git a/tests/kernel/test_quantum_state.py b/tests/kernel/test_quantum_state.py index d38267c4..12fb9b9d 100644 --- a/tests/kernel/test_quantum_state.py +++ b/tests/kernel/test_quantum_state.py @@ -123,3 +123,8 @@ def test_measure_entangled(): counter += 1 assert abs(0.5 - counter / 1000) < 0.1 + +def test_polarization_noise(): + qs = FreeQuantumState() + pass + From c01e17d5a4ebdd0fe6b3c1959ecac4faa0161ec2 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Wed, 20 Sep 2023 11:10:37 -0700 Subject: [PATCH 05/12] Fix edge bug in GUI --- src/gui/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/app.py b/src/gui/app.py index 50d34633..298fc893 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -819,11 +819,13 @@ def edit_graph( elif input_id == 'submit_edit': # print(selected) edited = self.parse_edit(selected) + + # for an edge if 'source' in edited: src = last_clicked['source'] trgt = last_clicked['target'] new_data = {k: edited[k] for k in EDGE_DICT_ORDER} - self.data.edges[src][trgt]['data'] = new_data + self.data.edges[src, trgt]['data'] = new_data gd = nx.readwrite.cytoscape_data(self.data)['elements'] err_msg = '' @@ -840,6 +842,7 @@ def edit_graph( delay_columns ] + # for a node else: old_name = last_clicked['name'] self.data.nodes[old_name]['data'] = edited From b739db906e27cfcaa9c140f0c67efd89d835d520 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Wed, 20 Sep 2023 11:46:17 -0700 Subject: [PATCH 06/12] Fix tab formatting and add units --- src/gui/layout.py | 4 ++++ src/gui/menus.py | 12 ++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/gui/layout.py b/src/gui/layout.py index c3543001..b617a68b 100644 --- a/src/gui/layout.py +++ b/src/gui/layout.py @@ -26,6 +26,10 @@ def make_item(menu, label, num, icon): 'font-size': '18px', 'padding-right': '10px', 'overflowX': 'hidden' + }, + selected_style={ + 'text-align': 'left', + 'font-size': '18px' } ) diff --git a/src/gui/menus.py b/src/gui/menus.py index f58ce77c..99e8bf71 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -206,13 +206,13 @@ def getSelectedEdgeMenu(values, nodes, link_types): ), getInputField( values['attenuation'], - 'Attenuation:', + 'Attenuation (dB/m):', 'selected_attenuation', 'attenuation' ), getInputField( values['distance'], - 'Distance:', + 'Distance (m):', 'selected_distance', 'distance' ) @@ -249,13 +249,13 @@ def getTimeUnits(id_extra): classic_edge = [ getInputField( '', - 'Distance:', + 'Distance (m):', 'distance_input', '' ), getInputField( '', - 'Attenuation:', + 'Attenuation (dB/m):', 'attenuation_input', '' ) @@ -264,13 +264,13 @@ def getTimeUnits(id_extra): quantum_edge = [ getInputField( '', - 'Distance:', + 'Distance (m):', 'distance_input', '' ), getInputField( '', - 'Attenuation:', + 'Attenuation (dB/m):', 'attenuation_input', '' ) From ee7b372273ec96cc6de00773a5b856ad687e0712 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Wed, 20 Sep 2023 17:57:02 -0700 Subject: [PATCH 07/12] Restrict options for nodes to add --- src/gui/menus.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/gui/menus.py b/src/gui/menus.py index 99e8bf71..f12bf5de 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -36,7 +36,22 @@ Default node type options for dropdown menus """ -OPTIONS = [ +OPTIONS_NODE = [ + { + 'label': 'Quantum Router', + 'value': 'QuantumRouter' + }, + { + 'label': 'BSM Node', + 'value': 'BSMNode' + }, + { + 'label': 'QKD Node', + 'value': 'QKDNode' + } +] + +OPTIONS_TEMPLATE = [ { 'label': 'Quantum Router', 'value': 'QuantumRouter' @@ -161,7 +176,7 @@ def getSelectedNodeMenu(values, templates): out.append( getDropdownField( values['type'], - OPTIONS, + OPTIONS_NODE, 'Node Type:', 'selected_node_type', 'type' @@ -396,7 +411,7 @@ def getTimeUnits(id_extra): ), getDropdownField( 'QuantumRouter', - OPTIONS, + OPTIONS_NODE, 'Type:', 'type_menu', '' @@ -511,7 +526,7 @@ def getTimeUnits(id_extra): ), getDropdownField( 'QuantumRouter', - OPTIONS, + OPTIONS_TEMPLATE, 'Type:', 'template_type_menu', '' From aa053713825aefc7f0cf250447eed2a5669aebfb Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Thu, 21 Sep 2023 09:47:33 -0700 Subject: [PATCH 08/12] Fix default fidelity in GUI --- src/gui/menus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/menus.py b/src/gui/menus.py index f12bf5de..3970e049 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -341,7 +341,7 @@ def getTimeUnits(id_extra): dbc.Input(id='mem_eff_in', className='efficiency', placeholder='0.75'), dbc.Label('Fidelity'), - dbc.Input(id='fidelity_in', className='fidelity', placeholder='500'), + dbc.Input(id='fidelity_in', className='fidelity', placeholder='0.85'), ] detector_template = [ From 1f2ecb05a4c59be94f509902aec7ee5bf606815c Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Thu, 21 Sep 2023 11:33:38 -0700 Subject: [PATCH 09/12] Add router and BSM templates to the topology file --- src/gui/app.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index 298fc893..03592348 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -288,8 +288,8 @@ def graph_to_topology(self): # q_delay = self.qc_tdm.copy() c_delay = self.cc_delays.copy() + # add node dict nodes_top = [] - for node in nodes: node_type = node[1]['node_type'] node_template_name = node[1]['data']['template'] @@ -306,8 +306,8 @@ def graph_to_topology(self): nodes_top.append(node_dict) + # add quantum connections dict qconnections = [] - for edge in edges: qconnections.append( { @@ -319,6 +319,7 @@ def graph_to_topology(self): } ) + # add classical connections dict cchannels = [] table = c_delay.to_numpy() labels = list(c_delay.columns) @@ -335,10 +336,28 @@ def graph_to_topology(self): } ) + # add templates dict + templates = {} + for temp_name, temp_vals in self.templates['QuantumRouter'].items(): + templates[temp_name] = { + 'MemoryArray': self.templates['Memory'][temp_vals['mem_type']] + } + for temp_name, temp_vals in self.templates['BSMNode'].items(): + templates[temp_name] = { + 'SingleAtomBSM': { + 'detectors': [ + self.templates['Detector'][temp_vals['detector_1']], + self.templates['Detector'][temp_vals['detector_2']] + ] + } + } + + # collect and finalize output = { Topology.ALL_NODE: nodes_top, Topology.ALL_QC_CONNECT: qconnections, Topology.ALL_C_CHANNEL: cchannels, + Topology.ALL_TEMPLATES: templates, RouterNetTopo.IS_PARALLEL: False, Topology.STOP_TIME: int(1e12) From 40f081be91d8c4f860414e5707a4375480256f50 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Thu, 21 Sep 2023 11:33:50 -0700 Subject: [PATCH 10/12] Fix bugs with QKD templates --- src/gui/app.py | 4 ++-- src/gui/menus.py | 34 +++++++++++++++------------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/gui/app.py b/src/gui/app.py index 03592348..bf4c348a 100644 --- a/src/gui/app.py +++ b/src/gui/app.py @@ -1028,8 +1028,8 @@ def template_menu(edgeType, save_click, temp, temp_type, temp_name): if edgeType == 'QuantumRouter': opts = list(self.templates['Memory'].keys()) return [router_template, '', opts, ''] - elif edgeType == 'Protocol': - return [protocol_template, '', '', ''] + elif edgeType == 'QKDNode': + return [qkd_template, '', '', ''] elif edgeType == 'Memory': return [quantum_memory_template, '', '', ''] elif edgeType == 'Detector': diff --git a/src/gui/menus.py b/src/gui/menus.py index 3970e049..462d2572 100644 --- a/src/gui/menus.py +++ b/src/gui/menus.py @@ -64,30 +64,14 @@ 'label': 'Detector', 'value': 'Detector' }, - # { - # 'label': 'Protocol', - # 'value': 'Protocol' - # }, { 'label': 'BSM Node', 'value': 'BSMNode' }, - # { - # 'label': 'Quantum Repeater', - # 'value': 'Quantum_Repeater' - # }, - # { - # 'label': 'Quantum Error Correction', - # 'value': 'QuantumErrorCorrection' - # }, { 'label': 'Photon Source', 'value': 'PhotonSource' }, - # { - # 'label': 'Temp', - # 'value': 'Temp' - # } { 'label': 'QKD Node', 'value': 'QKDNode' @@ -388,9 +372,21 @@ def getTimeUnits(id_extra): ), ] - -protocol_template = [ - +qkd_template = [ + dbc.Label('Photon Encoding'), + dcc.Dropdown( + id='encoding_in', + className='encoding', + value='', + options=["polarization", "time_bin"] + ), + dbc.Label('Protocol Stack Size'), + dcc.Dropdown( + id='stack_size_in', + className='stack_size', + value='', + options=[1, 2, 3, 4, 5] + ), ] # New # From 464bddc65e13686dc0fd51152fc6a2a6eea05f21 Mon Sep 17 00:00:00 2001 From: Alex-Kolar Date: Fri, 22 Sep 2023 07:56:27 -0700 Subject: [PATCH 11/12] Move around example files for QCE 2023 --- .../GUI_and_code_demo.ipynb | 0 .../Teleport_demo.ipynb | 0 .../Teleport_demo_filled.ipynb | 0 .../images/star_network.png | Bin .../modify_script_method_function.ipynb | 0 .../star_network.json | 0 .../star_network_modified.json | 0 .../QCE_demos_2023/GUI_and_code_demo.ipynb | 360 +++++++++++++++++ .../GUI_and_code_demo_filled.ipynb | 361 ++++++++++++++++++ .../QCE_demos_2023/images/star_network.png | Bin 0 -> 45417 bytes .../modify_script_method_function.ipynb | 158 ++++++++ example/QCE_demos_2023/star_network.json | 118 ++++++ 12 files changed, 997 insertions(+) rename example/{QCE_demos => QCE_demos_2022}/GUI_and_code_demo.ipynb (100%) rename example/{QCE_demos => QCE_demos_2022}/Teleport_demo.ipynb (100%) rename example/{QCE_demos => QCE_demos_2022}/Teleport_demo_filled.ipynb (100%) rename example/{QCE_demos => QCE_demos_2022}/images/star_network.png (100%) rename example/{QCE_demos => QCE_demos_2022}/modify_script_method_function.ipynb (100%) rename example/{QCE_demos => QCE_demos_2022}/star_network.json (100%) rename example/{QCE_demos => QCE_demos_2022}/star_network_modified.json (100%) create mode 100644 example/QCE_demos_2023/GUI_and_code_demo.ipynb create mode 100644 example/QCE_demos_2023/GUI_and_code_demo_filled.ipynb create mode 100644 example/QCE_demos_2023/images/star_network.png create mode 100644 example/QCE_demos_2023/modify_script_method_function.ipynb create mode 100644 example/QCE_demos_2023/star_network.json diff --git a/example/QCE_demos/GUI_and_code_demo.ipynb b/example/QCE_demos_2022/GUI_and_code_demo.ipynb similarity index 100% rename from example/QCE_demos/GUI_and_code_demo.ipynb rename to example/QCE_demos_2022/GUI_and_code_demo.ipynb diff --git a/example/QCE_demos/Teleport_demo.ipynb b/example/QCE_demos_2022/Teleport_demo.ipynb similarity index 100% rename from example/QCE_demos/Teleport_demo.ipynb rename to example/QCE_demos_2022/Teleport_demo.ipynb diff --git a/example/QCE_demos/Teleport_demo_filled.ipynb b/example/QCE_demos_2022/Teleport_demo_filled.ipynb similarity index 100% rename from example/QCE_demos/Teleport_demo_filled.ipynb rename to example/QCE_demos_2022/Teleport_demo_filled.ipynb diff --git a/example/QCE_demos/images/star_network.png b/example/QCE_demos_2022/images/star_network.png similarity index 100% rename from example/QCE_demos/images/star_network.png rename to example/QCE_demos_2022/images/star_network.png diff --git a/example/QCE_demos/modify_script_method_function.ipynb b/example/QCE_demos_2022/modify_script_method_function.ipynb similarity index 100% rename from example/QCE_demos/modify_script_method_function.ipynb rename to example/QCE_demos_2022/modify_script_method_function.ipynb diff --git a/example/QCE_demos/star_network.json b/example/QCE_demos_2022/star_network.json similarity index 100% rename from example/QCE_demos/star_network.json rename to example/QCE_demos_2022/star_network.json diff --git a/example/QCE_demos/star_network_modified.json b/example/QCE_demos_2022/star_network_modified.json similarity index 100% rename from example/QCE_demos/star_network_modified.json rename to example/QCE_demos_2022/star_network_modified.json diff --git a/example/QCE_demos_2023/GUI_and_code_demo.ipynb b/example/QCE_demos_2023/GUI_and_code_demo.ipynb new file mode 100644 index 00000000..b1cc1794 --- /dev/null +++ b/example/QCE_demos_2023/GUI_and_code_demo.ipynb @@ -0,0 +1,360 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Network with Applications\n", + "\n", + "In this file, we'll demonstrate the simulation of a more complicated network topology with applications. These applications will act on each node, requesting memories to be entangled and then recording metrics. The network topology, including hardware components, is shown below:\n", + "\n", + "\n", + "\n", + "In this file, we construct the network described above and add a custom app to two nodes. Most of the code has been written, but some will need to be filled in. We'll be building the topology from an external json file constructed using the GUI.\n", + "\n", + "## Imports\n", + "We must first import the necessary tools from SeQUeNCe. For building our specific request, we will import:\n", + "\n", + "- `RequestApp` is an example application included with SeQUeNCe. We will investigate its behavior through a class that inherits from this.\n", + "- `Message` is a wrapper for classical messages to exchange between nodes.\n", + "\n", + "Finally, for creating the network itself, we will import:\n", + "\n", + "- `Topology` is a powerful class for creating and managing complex network topologies. We'll be using the `RouterNetTopo` subclass to build our network and intefrace with specific nodes and node types." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import interact\n", + "import time\n", + "\n", + "# for building application\n", + "from sequence.app.request_app import RequestApp\n", + "from sequence.message import Message\n", + "\n", + "# for building network\n", + "from sequence.topology.router_net_topo import RouterNetTopo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Custom Application\n", + "\n", + "We may now define our custom application. When receiving an entangled memory, we will announce its reception; we will then use the new `AppMessage` class to communicate results to the other node. The receiving node will wait when receiving an entangled memory. Once the classical message is received from the originator, it will release its local memory to be reused. This mimics the behavior of a teleportation operation." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "START_TIME = 1e12\n", + "\n", + "class TeleportMessage(Message):\n", + " def __init__(self, msg_type, receiver, memo_name: str):\n", + " super().__init__(msg_type, receiver)\n", + " self.memo_name = memo_name\n", + "\n", + "class TeleportApp(RequestApp):\n", + " def __init__(self, node, name, other_name):\n", + " super().__init__(node)\n", + " self.name = name\n", + " self.other_name = other_name\n", + " node.protocols.append(self)\n", + " self.memos_to_measure = {}\n", + " \n", + " # collect metrics:\n", + " self.latency = 0\n", + " self.count = 0\n", + " \n", + " def get_memory(self, info: \"MemoryInfo\") -> None:\n", + " \"\"\"Method to receive entangled memories.\n", + "\n", + " Will check if the received memory is qualified.\n", + " If it's a qualified memory, the application sets memory to RAW state\n", + " and release back to resource manager.\n", + " The counter of entanglement memories, 'memory_counter', is added.\n", + " Otherwise, the application does not modify the state of memory and\n", + " release back to the resource manager.\n", + "\n", + " Args:\n", + " info (MemoryInfo): info on the qualified entangled memory.\n", + " \"\"\"\n", + "\n", + " if info.state != \"ENTANGLED\":\n", + " return\n", + "\n", + " if info.index in self.memo_to_reserve:\n", + " reservation = self.memo_to_reserve[info.index]\n", + " \n", + " if info.remote_node == reservation.responder and info.fidelity >= reservation.fidelity:\n", + " # we are initiator, and want to teleport qubit\n", + " print(\"node {} memory {} entangled with other memory {}\".format(\n", + " self.node.name, info.index, info.remote_memo))\n", + " \n", + " # record metrics\n", + " if self.count == 0:\n", + " # record latency\n", + " pass\n", + " \n", + " # send message to other node\n", + " message = TeleportMessage(None, self.other_name, info.remote_memo)\n", + " \n", + " # reset local memory\n", + " \n", + " elif info.remote_node == reservation.initiator and info.fidelity >= reservation.fidelity:\n", + " # we are responder, and want to receive qubit\n", + " # need to wait on message from sender to correct entanled memory\n", + " self.memos_to_measure[info.memory.name] = info.memory\n", + " \n", + " def received_message(self, src, message):\n", + " memo_name = message.memo_name\n", + " \n", + " print(\"node {} received teleportation message for memory {}\".format(\n", + " self.node.name, memo_name))\n", + " \n", + " # reset local memory\n", + " memory = self.memos_to_measure.pop(memo_name)\n", + " self.node.resource_manager.update(None, memory, \"RAW\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Building the Simulation\n", + "\n", + "We'll now construct the network and add our applications. This example follows the usual process to ensure that all tools function properly:\n", + "1. Create the topology instance for the simulation to manage our network\n", + " - This class will create a simulation timeline\n", + " - This instantiates the Simulation Kernel (see below)\n", + "2. Create the simulated network topology. In this case, we are using an external JSON file to specify nodes and their connectivity.\n", + " - This includes specifying hardware parameters in the `set_parameters` function, defined later\n", + " - This instantiates the Hardware, Entanglement Management, Resource Management, and Network Management modules\n", + "3. Install custom protocols/applications and ensure all are set up properly\n", + " - This instantiates the Application module\n", + "4. Initialize and run the simulation\n", + "5. Collect and display the desired metrics\n", + "\n", + "The JSON file specifies that network nodes should be of type `QuantumRouter`, a node type defined by SeQUeNCe. This will automatically create all necessary hardware and protocol instances on the nodes, and the `Topology` class will automatically generate `BSMNode` instances on the quantum channels between such nodes.\n", + "\n", + "To construct an application, we need:\n", + "- The node to attach the application to\n", + "- The name of the application instance\n", + "- The name of the node to teleport qubits to.\n", + "\n", + "We can get a list of all desired application nodes, in this case routers, from the `Topology` class with the `get_nodes_by_type` method. We then set an application on each one." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "def test(sim_time=1.5, qc_atten=1e-5):\n", + " \"\"\"\n", + " sim_time: duration of simulation time (s)\n", + " qc_atten: quantum channel attenuation (dB/km)\n", + " \"\"\"\n", + " \n", + " network_config = \"star_network.json\"\n", + " \n", + " # here, we make a new topology using the configuration JSON file.\n", + " # we then modify some of the simulation parameters of the network.\n", + " network_topo = RouterNetTopo(network_config)\n", + " set_parameters(network_topo, sim_time, qc_atten)\n", + " \n", + " # get two end nodes\n", + " start_node_name = \"router1\"\n", + " end_node_name = \"router2\"\n", + " node1 = node2 = None\n", + "\n", + " for router in network_topo.get_nodes_by_type(RouterNetTopo.QUANTUM_ROUTER):\n", + " if router.name == start_node_name:\n", + " node1 = router\n", + " elif router.name == end_node_name:\n", + " node2 = router\n", + " \n", + " start_app_name = \"start_app\"\n", + " end_app_name = \"end_app\"\n", + "\n", + " # create applications\n", + " start_app = None\n", + " end_app = None\n", + " \n", + " # run the simulation\n", + " tl = network_topo.get_timeline()\n", + " tl.show_progress = False\n", + " tl.init()\n", + " \n", + " start_app.start(end_node_name, START_TIME, 2e12, 10, 0.9)\n", + " tick = time.time()\n", + " tl.run()\n", + " \n", + " print(\"\\n\")\n", + " print(\"Execution time: {:.3f} seconds\".format(time.time() - tick))\n", + " \n", + " print(\"\\n\")\n", + " print(\"Latency: {:.3f} s\".format(start_app.latency))\n", + " print(\"Number of entangled memories:\", start_app.count)\n", + " print(\"Average throughput: {:.3f} pairs/s\".format(\n", + " start_app.count / (sim_time - (START_TIME * 1e-12))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting parameters\n", + "\n", + "Here we define the `set_parameters` function we used earlier. This function will take a `Topology` as input and change many parameters to desired values. This will be covered in greater detail in workshop 3.\n", + "\n", + "The simulation time limit will be set using the `get_timeline` method.\n", + "\n", + "Quantum memories and detectors are hardware elements, and so parameters are changed by accessing the hardware included with the `QuantumRouter` and `BSMNode` node types. Many complex hardware elements, such as bsm devices or memory arrays, have methods to update parameters for all included hardware elements. This includes `update_memory_params` to change all memories in an array or `update_detector_params` to change all detectors.\n", + "\n", + "We will also set the success probability and swapping degradation of the entanglement swapping protocol. This will be set in the Network management Module (specifically the reservation protocol), as this information is necessary to create and manage the rules for the Resource Management module.\n", + "\n", + "Lastly, we'll update some parameters of the quantum channels. Quantum channels (and, similarly, classical channels) can be accessed from the `Topology` class as the `qchannels` field. Since these are individual hardware elements, we will set the parameters directly." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def set_parameters(topology, simulation_time, attenuation):\n", + " \"\"\"\n", + " simulation_time: duration of simulation time (s)\n", + " attenuation: attenuation on quantum channels (db/m)\n", + " \"\"\"\n", + " \n", + " PS_PER_S = 1e12\n", + " \n", + " # set timeline stop time\n", + " topology.get_timeline().stop_time = (simulation_time * PS_PER_S)\n", + " \n", + " # set memory parameters\n", + " MEMO_FREQ = 2e3\n", + " MEMO_EXPIRE = 0\n", + " MEMO_EFFICIENCY = 1\n", + " MEMO_FIDELITY = 0.9349367588934053\n", + " for node in topology.get_nodes_by_type(RouterNetTopo.QUANTUM_ROUTER):\n", + " memory_array = node.get_components_by_type(\"MemoryArray\")[0]\n", + " memory_array.update_memory_params(\"frequency\", MEMO_FREQ)\n", + " memory_array.update_memory_params(\"coherence_time\", MEMO_EXPIRE)\n", + " memory_array.update_memory_params(\"efficiency\", MEMO_EFFICIENCY)\n", + " memory_array.update_memory_params(\"raw_fidelity\", MEMO_FIDELITY)\n", + "\n", + " # set detector parameters\n", + " DETECTOR_EFFICIENCY = 0.9\n", + " DETECTOR_COUNT_RATE = 5e7\n", + " DETECTOR_RESOLUTION = 100\n", + " for node in topology.get_nodes_by_type(RouterNetTopo.BSM_NODE):\n", + " bsm = node.get_components_by_type(\"SingleAtomBSM\")[0]\n", + " bsm.update_detectors_params(\"efficiency\", DETECTOR_EFFICIENCY)\n", + " bsm.update_detectors_params(\"count_rate\", DETECTOR_COUNT_RATE)\n", + " bsm.update_detectors_params(\"time_resolution\", DETECTOR_RESOLUTION)\n", + " \n", + " # set entanglement swapping parameters\n", + " SWAP_SUCC_PROB = 0.90\n", + " SWAP_DEGRADATION = 0.99\n", + " for node in topology.get_nodes_by_type(RouterNetTopo.QUANTUM_ROUTER):\n", + " node.network_manager.protocol_stack[1].set_swapping_success_rate(SWAP_SUCC_PROB)\n", + " node.network_manager.protocol_stack[1].set_swapping_degradation(SWAP_DEGRADATION)\n", + " \n", + " # set quantum channel parameters\n", + " ATTENUATION = attenuation\n", + " QC_FREQ = 1e11\n", + " for qc in topology.qchannels:\n", + " qc.attenuation = ATTENUATION\n", + " qc.frequency = QC_FREQ" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running the Simulation\n", + "\n", + "All that is left is to run the simulation with user input. We'll specify:\n", + "\n", + " sim_time: duration of simulation time (s)\n", + " qc_atten: attenuation on quantum channels (dB/m)\n", + "\n", + "Note that different hardware parameters or network topologies may cause the simulation to run for a very long time." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3015b584b71c4bcd9eaa6db134a806de", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=1.5, description='sim_time', max=2.0, min=1.0), Dropdown(description='…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interact(test, sim_time=(1, 2, 0.1), qc_atten=[0, 1e-5, 2e-5])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "venv" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/example/QCE_demos_2023/GUI_and_code_demo_filled.ipynb b/example/QCE_demos_2023/GUI_and_code_demo_filled.ipynb new file mode 100644 index 00000000..cf460475 --- /dev/null +++ b/example/QCE_demos_2023/GUI_and_code_demo_filled.ipynb @@ -0,0 +1,361 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Network with Applications\n", + "\n", + "In this file, we'll demonstrate the simulation of a more complicated network topology with applications. These applications will act on each node, requesting memories to be entangled and then recording metrics. The network topology, including hardware components, is shown below:\n", + "\n", + "\n", + "\n", + "In this file, we construct the network described above and add a custom app to two nodes. Most of the code has been written, but some will need to be filled in. We'll be building the topology from an external json file constructed using the GUI.\n", + "\n", + "## Imports\n", + "We must first import the necessary tools from SeQUeNCe. For building our specific request, we will import:\n", + "\n", + "- `RequestApp` is an example application included with SeQUeNCe. We will investigate its behavior through a class that inherits from this.\n", + "- `Message` is a wrapper for classical messages to exchange between nodes.\n", + "\n", + "Finally, for creating the network itself, we will import:\n", + "\n", + "- `Topology` is a powerful class for creating and managing complex network topologies. We'll be using the `RouterNetTopo` subclass to build our network and intefrace with specific nodes and node types." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import interact\n", + "import time\n", + "\n", + "# for building application\n", + "from sequence.app.request_app import RequestApp\n", + "from sequence.message import Message\n", + "\n", + "# for building network\n", + "from sequence.topology.router_net_topo import RouterNetTopo" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Custom Application\n", + "\n", + "We may now define our custom application. When receiving an entangled memory, we will announce its reception; we will then use the new `AppMessage` class to communicate results to the other node. The receiving node will wait when receiving an entangled memory. Once the classical message is received from the originator, it will release its local memory to be reused. This mimics the behavior of a teleportation operation." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "START_TIME = 1e12\n", + "\n", + "class TeleportMessage(Message):\n", + " def __init__(self, msg_type, receiver, memo_name: str):\n", + " super().__init__(msg_type, receiver)\n", + " self.memo_name = memo_name\n", + "\n", + "class TeleportApp(RequestApp):\n", + " def __init__(self, node, name, other_name):\n", + " super().__init__(node)\n", + " self.name = name\n", + " self.other_name = other_name\n", + " node.protocols.append(self)\n", + " self.memos_to_measure = {}\n", + " \n", + " # collect metrics:\n", + " self.latency = 0\n", + " self.count = 0\n", + " \n", + " def get_memory(self, info: \"MemoryInfo\") -> None:\n", + " \"\"\"Method to receive entangled memories.\n", + "\n", + " Will check if the received memory is qualified.\n", + " If it's a qualified memory, the application sets memory to RAW state\n", + " and release back to resource manager.\n", + " The counter of entanglement memories, 'memory_counter', is added.\n", + " Otherwise, the application does not modify the state of memory and\n", + " release back to the resource manager.\n", + "\n", + " Args:\n", + " info (MemoryInfo): info on the qualified entangled memory.\n", + " \"\"\"\n", + "\n", + " if info.state != \"ENTANGLED\":\n", + " return\n", + "\n", + " if info.index in self.memo_to_reserve:\n", + " reservation = self.memo_to_reserve[info.index]\n", + " \n", + " if info.remote_node == reservation.responder and info.fidelity >= reservation.fidelity:\n", + " # we are initiator, and want to teleport qubit\n", + " print(\"node {} memory {} entangled with other memory {}\".format(\n", + " self.node.name, info.index, info.remote_memo))\n", + " \n", + " # record metrics\n", + " if self.count == 0:\n", + " self.latency = (self.node.timeline.now() - START_TIME) * 1e-12\n", + " self.count += 1\n", + " \n", + " # send message to other node\n", + " message = TeleportMessage(None, self.other_name, info.remote_memo)\n", + " self.node.send_message(self.responder, message)\n", + " \n", + " # reset local memory\n", + " self.node.resource_manager.update(None, info.memory, \"RAW\")\n", + " \n", + " elif info.remote_node == reservation.initiator and info.fidelity >= reservation.fidelity:\n", + " # we are responder, and want to receive qubit\n", + " # need to wait on message from sender to correct entanled memory\n", + " self.memos_to_measure[info.memory.name] = info.memory\n", + " \n", + " def received_message(self, src, message):\n", + " memo_name = message.memo_name\n", + " \n", + " print(\"node {} received teleportation message for memory {}\".format(\n", + " self.node.name, memo_name))\n", + " \n", + " # reset local memory\n", + " memory = self.memos_to_measure.pop(memo_name)\n", + " self.node.resource_manager.update(None, memory, \"RAW\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Building the Simulation\n", + "\n", + "We'll now construct the network and add our applications. This example follows the usual process to ensure that all tools function properly:\n", + "1. Create the topology instance for the simulation to manage our network\n", + " - This class will create a simulation timeline\n", + " - This instantiates the Simulation Kernel (see below)\n", + "2. Create the simulated network topology. In this case, we are using an external JSON file to specify nodes and their connectivity.\n", + " - This includes specifying hardware parameters in the `set_parameters` function, defined later\n", + " - This instantiates the Hardware, Entanglement Management, Resource Management, and Network Management modules\n", + "3. Install custom protocols/applications and ensure all are set up properly\n", + " - This instantiates the Application module\n", + "4. Initialize and run the simulation\n", + "5. Collect and display the desired metrics\n", + "\n", + "The JSON file specifies that network nodes should be of type `QuantumRouter`, a node type defined by SeQUeNCe. This will automatically create all necessary hardware and protocol instances on the nodes, and the `Topology` class will automatically generate `BSMNode` instances on the quantum channels between such nodes.\n", + "\n", + "To construct an application, we need:\n", + "- The node to attach the application to\n", + "- The name of the application instance\n", + "- The name of the node to teleport qubits to.\n", + "\n", + "We can get a list of all desired application nodes, in this case routers, from the `Topology` class with the `get_nodes_by_type` method. We then set an application on each one." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [], + "source": [ + "def test(sim_time=1.5, qc_atten=1e-5):\n", + " \"\"\"\n", + " sim_time: duration of simulation time (s)\n", + " qc_atten: quantum channel attenuation (dB/km)\n", + " \"\"\"\n", + " \n", + " network_config = \"star_network.json\"\n", + " \n", + " # here, we make a new topology using the configuration JSON file.\n", + " # we then modify some of the simulation parameters of the network.\n", + " network_topo = RouterNetTopo(network_config)\n", + " set_parameters(network_topo, sim_time, qc_atten)\n", + " \n", + " # get two end nodes and create application\n", + " start_node_name = \"router1\"\n", + " end_node_name = \"router2\"\n", + " node1 = node2 = None\n", + "\n", + " for router in network_topo.get_nodes_by_type(RouterNetTopo.QUANTUM_ROUTER):\n", + " if router.name == start_node_name:\n", + " node1 = router\n", + " elif router.name == end_node_name:\n", + " node2 = router\n", + " \n", + " start_app_name = \"start_app\"\n", + " end_app_name = \"end_app\"\n", + " \n", + " start_app = TeleportApp(node1, start_app_name, end_app_name)\n", + " end_app = TeleportApp(node2, end_app_name, start_app_name)\n", + " \n", + " # run the simulation\n", + " tl = network_topo.get_timeline()\n", + " tl.show_progress = False\n", + " tl.init()\n", + " \n", + " start_app.start(end_node_name, START_TIME, 2e12, 10, 0.9)\n", + " tick = time.time()\n", + " tl.run()\n", + " \n", + " print(\"\\n\")\n", + " print(\"Execution time: {:.3f} seconds\".format(time.time() - tick))\n", + " \n", + " print(\"\\n\")\n", + " print(\"Latency: {:.3f} s\".format(start_app.latency))\n", + " print(\"Number of entangled memories:\", start_app.count)\n", + " print(\"Average throughput: {:.3f} pairs/s\".format(\n", + " start_app.count / (sim_time - (START_TIME * 1e-12))))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting parameters\n", + "\n", + "Here we define the `set_parameters` function we used earlier. This function will take a `Topology` as input and change many parameters to desired values. This will be covered in greater detail in workshop 3.\n", + "\n", + "The simulation time limit will be set using the `get_timeline` method.\n", + "\n", + "Quantum memories and detectors are hardware elements, and so parameters are changed by accessing the hardware included with the `QuantumRouter` and `BSMNode` node types. Many complex hardware elements, such as bsm devices or memory arrays, have methods to update parameters for all included hardware elements. This includes `update_memory_params` to change all memories in an array or `update_detector_params` to change all detectors.\n", + "\n", + "We will also set the success probability and swapping degradation of the entanglement swapping protocol. This will be set in the Network management Module (specifically the reservation protocol), as this information is necessary to create and manage the rules for the Resource Management module.\n", + "\n", + "Lastly, we'll update some parameters of the quantum channels. Quantum channels (and, similarly, classical channels) can be accessed from the `Topology` class as the `qchannels` field. Since these are individual hardware elements, we will set the parameters directly." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def set_parameters(topology, simulation_time, attenuation):\n", + " \"\"\"\n", + " simulation_time: duration of simulation time (s)\n", + " attenuation: attenuation on quantum channels (db/m)\n", + " \"\"\"\n", + " \n", + " PS_PER_S = 1e12\n", + " \n", + " # set timeline stop time\n", + " topology.get_timeline().stop_time = (simulation_time * PS_PER_S)\n", + " \n", + " # set memory parameters\n", + " MEMO_FREQ = 2e3\n", + " MEMO_EXPIRE = 0\n", + " MEMO_EFFICIENCY = 1\n", + " MEMO_FIDELITY = 0.9349367588934053\n", + " for node in topology.get_nodes_by_type(RouterNetTopo.QUANTUM_ROUTER):\n", + " memory_array = node.get_components_by_type(\"MemoryArray\")[0]\n", + " memory_array.update_memory_params(\"frequency\", MEMO_FREQ)\n", + " memory_array.update_memory_params(\"coherence_time\", MEMO_EXPIRE)\n", + " memory_array.update_memory_params(\"efficiency\", MEMO_EFFICIENCY)\n", + " memory_array.update_memory_params(\"raw_fidelity\", MEMO_FIDELITY)\n", + "\n", + " # set detector parameters\n", + " DETECTOR_EFFICIENCY = 0.9\n", + " DETECTOR_COUNT_RATE = 5e7\n", + " DETECTOR_RESOLUTION = 100\n", + " for node in topology.get_nodes_by_type(RouterNetTopo.BSM_NODE):\n", + " bsm = node.get_components_by_type(\"SingleAtomBSM\")[0]\n", + " bsm.update_detectors_params(\"efficiency\", DETECTOR_EFFICIENCY)\n", + " bsm.update_detectors_params(\"count_rate\", DETECTOR_COUNT_RATE)\n", + " bsm.update_detectors_params(\"time_resolution\", DETECTOR_RESOLUTION)\n", + " \n", + " # set entanglement swapping parameters\n", + " SWAP_SUCC_PROB = 0.90\n", + " SWAP_DEGRADATION = 0.99\n", + " for node in topology.get_nodes_by_type(RouterNetTopo.QUANTUM_ROUTER):\n", + " node.network_manager.protocol_stack[1].set_swapping_success_rate(SWAP_SUCC_PROB)\n", + " node.network_manager.protocol_stack[1].set_swapping_degradation(SWAP_DEGRADATION)\n", + " \n", + " # set quantum channel parameters\n", + " ATTENUATION = attenuation\n", + " QC_FREQ = 1e11\n", + " for qc in topology.qchannels:\n", + " qc.attenuation = ATTENUATION\n", + " qc.frequency = QC_FREQ" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running the Simulation\n", + "\n", + "All that is left is to run the simulation with user input. We'll specify:\n", + "\n", + " sim_time: duration of simulation time (s)\n", + " qc_atten: attenuation on quantum channels (dB/m)\n", + "\n", + "Note that different hardware parameters or network topologies may cause the simulation to run for a very long time." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "3015b584b71c4bcd9eaa6db134a806de", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=1.5, description='sim_time', max=2.0, min=1.0), Dropdown(description='…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 25, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "interact(test, sim_time=(1, 2, 0.1), qc_atten=[0, 1e-5, 2e-5])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "venv" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/example/QCE_demos_2023/images/star_network.png b/example/QCE_demos_2023/images/star_network.png new file mode 100644 index 0000000000000000000000000000000000000000..10b048e1912528e2b4a50eb3d02995cb2ce7ffba GIT binary patch literal 45417 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

-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 Date: Fri, 22 Sep 2023 07:59:49 -0700 Subject: [PATCH 12/12] Upgrade to version 0.6.2 --- CHANGELOG.md | 9 +++++++++ docs/source/conf.py | 2 +- setup.py | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9273e205..787b21f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -172,3 +172,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Removed support for scipy version 1.11. This is currently causing some issues with qutip. + +## [0.6.2] +### Added +- Moved around and added a few files in the `examples` folder + - These are primarily for the IEEE QCE 2023 conference + +### Changed +- Modified the topology output of the GUI to be compatible with new topology upgrades +- Several bug fixes in the GUI diff --git a/docs/source/conf.py b/docs/source/conf.py index 808060f0..bdee7c97 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,7 +22,7 @@ author = 'Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara' # The full version, including alpha/beta/rc tags -release = '0.6.1' +release = '0.6.2' # -- General configuration --------------------------------------------------- diff --git a/setup.py b/setup.py index b3a4f86b..96af5754 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name="sequence", - version="0.6.1", + version="0.6.2", author="Xiaoliang Wu, Joaquin Chung, Alexander Kolar, Alexander Kiefer, Eugene Wang, Tian Zhong, Rajkumar Kettimuthu, Martin Suchara", author_email="xwu64@hawk.iit.edu, chungmiranda@anl.gov, akolar@anl.gov, akiefer@iu.edu, eugenewang@yahoo.com, tzh@uchicago.edu, kettimut@mcs.anl.gov, msuchara@anl.gov", description="Simulator of Quantum Network Communication: SEQUENCE-Python is a prototype version of the official SEQUENCE release.",