From b38ffa62aaea143b1b563451e1b921883d257947 Mon Sep 17 00:00:00 2001 From: Thomas OBrien Date: Wed, 24 Oct 2018 23:13:12 +0200 Subject: [PATCH 1/5] Added option to auto-shrink waiting gates to only include the time taken by actual gates, this way circuits can be tetrised. Also stopped builder from splitting setup into component pieces upon initialisation --- qsoverlay/DiCarlo_setup.py | 9 +- qsoverlay/circuit_builder.py | 158 +++++++++++++++++++-------------- qsoverlay/experiment_setup.py | 8 +- qsoverlay/test/test_builder.py | 60 ++++++++++--- 4 files changed, 155 insertions(+), 80 deletions(-) diff --git a/qsoverlay/DiCarlo_setup.py b/qsoverlay/DiCarlo_setup.py index 526c5b9..7dff27e 100644 --- a/qsoverlay/DiCarlo_setup.py +++ b/qsoverlay/DiCarlo_setup.py @@ -134,7 +134,14 @@ def quick_setup(qubit_list, connectivity_dic=None, rng=None, *, seed=None, setup['gate_set'] = make_1q2q_gateset(qubit_dic=setup['qubit_dic'], gate_dic=setup['gate_dic'], connectivity_dic=connectivity_dic) - return Setup(**setup) + + system_params = { + 'uses_waiting_gates': True + } + + setup = Setup(**setup, system_params=system_params) + + return setup def asymmetric_setup(qubit_parameters=None, diff --git a/qsoverlay/circuit_builder.py b/qsoverlay/circuit_builder.py index a02f534..74ae258 100644 --- a/qsoverlay/circuit_builder.py +++ b/qsoverlay/circuit_builder.py @@ -16,45 +16,17 @@ class Builder: - def __init__(self, - setup=None, - qubit_dic=None, - gate_dic=None, - gate_set=None, - update_rules=None, - **kwargs): + def __init__(self, setup, **kwargs): ''' - qubit_dic: list of the qubits in the system. - Each qubit should have a set of parameters, - which is called whenever required by the gates. - gate_dic: a dictionary of allowed gates. - Each 'allowed gate' consists of: - - the function to be called - - a set of 'qubit_args' that will be found in - the qubit dictionary and passed to the gate. - - a 'time' - the length of the gate - Note that 'Measure' should be in the gate_set - gate_set: a dictionary of allowed gate instances. - An allowed gate instance is a list of the gate - along with the qubits it is performed between. - update_rules: a set of rules for updating the system. - (i.e. between experiments). + Args: + ------- + setup: an experiment_setup.Setup containing details of + the experiment. kwargs: Can add t1 and t2 via the kwargs instead of passing them with the qubit_dic. ''' - if setup is not None: - self.qubit_dic = setup.qubit_dic - self.gate_dic = setup.gate_dic - self.gate_set = setup.gate_set - self.update_rules = setup.update_rules - else: - self.qubit_dic = qubit_dic or {} - self.gate_dic = gate_dic or {} - self.gate_set = gate_set or {} - self.update_rules = update_rules or [] - - self.save_flag = True + self.setup = setup self.new_circuit(**kwargs) def new_circuit(self, circuit_title='New Circuit', **kwargs): @@ -70,9 +42,14 @@ def new_circuit(self, circuit_title='New Circuit', **kwargs): # Times stores the current time of every qubit (beginning at 0) self.times = {} + self.tmins = {} + + # save_flag is used to keep track of which gates have been + # saved during a circuit, and needs to be reset here. + self.save_flag = True # Make qubits - for qubit, qubit_args in sorted(self.qubit_dic.items()): + for qubit, qubit_args in sorted(self.setup.qubit_dic.items()): if 'classical' in qubit_args.keys() and\ qubit_args['classical'] is True: @@ -95,6 +72,7 @@ def new_circuit(self, circuit_title='New Circuit', **kwargs): # Initialise the time of the latest gate on each qubit to 0 self.times[qubit] = 0 + self.tmins[qubit] = None def make_reverse_circuit(self, title='reversed', finalize=True): @@ -111,8 +89,8 @@ def make_reverse_circuit(self, title='reversed', for n, gate_desc in enumerate(reversed_circuit_list): gate_name = gate_desc[0] - num_qubits = self.gate_dic[gate_name]['num_qubits'] - user_kws = self.gate_dic[gate_name]['user_kws'] + num_qubits = self.setup.gate_dic[gate_name]['num_qubits'] + user_kws = self.setup.gate_dic[gate_name]['user_kws'] if 'angle' in user_kws: gate_desc = list(gate_desc) @@ -120,10 +98,7 @@ def make_reverse_circuit(self, title='reversed', gate_desc[num_qubits + 1 + angle_index] *= -1 reversed_circuit_list[n] = tuple(gate_desc) - reversed_circuit_builder = Builder(qubit_dic=self.qubit_dic, - gate_dic=self.gate_dic, - gate_set=self.gate_set, - update_rules=self.update_rules) + reversed_circuit_builder = Builder(setup=self.setup) reversed_circuit_builder.add_circuit_list(reversed_circuit_list) if finalize: reversed_circuit_builder.finalize() @@ -153,8 +128,8 @@ def add_qasm(self, qasm_generator, qubits_first=True, **params): # Get the gate name gate_name = line[:spaces[0]] - num_qubits = self.gate_dic[gate_name]['num_qubits'] - user_kws = self.gate_dic[gate_name]['user_kws'] + num_qubits = self.setup.gate_dic[gate_name]['num_qubits'] + user_kws = self.setup.gate_dic[gate_name]['user_kws'] if gate_name == 'measure': # line looks like 'measure q -> c;' @@ -224,8 +199,8 @@ def add_circuit_list(self, circuit_list): def __lt__(self, gate_desc): gate_name = gate_desc[0] - num_qubits = self.gate_dic[gate_name]['num_qubits'] - user_kws = self.gate_dic[gate_name]['user_kws'] + num_qubits = self.setup.gate_dic[gate_name]['num_qubits'] + user_kws = self.setup.gate_dic[gate_name]['user_kws'] if len(gate_desc) == len(user_kws) + num_qubits + 2: return_flag = gate_desc[-1] @@ -264,7 +239,7 @@ def add_gate(self, gate_name, # the same for every qubit/pair of qubits). gate_tuple = (gate_name, *qubit_list) - circuit_args, builder_args = self.gate_set[gate_tuple] + circuit_args, builder_args = self.setup.gate_set[gate_tuple] # kwargs is the list of arguments that gets passed to the gate # itself. We initiate with the set of additional arguments passed @@ -302,7 +277,7 @@ def add_gate(self, gate_name, # data. if self.save_flag: user_data = [kwargs[kw] - for kw in self.gate_dic[gate_name]['user_kws']] + for kw in self.setup.gate_dic[gate_name]['user_kws']] if return_flag is not False: self.circuit_list.append((gate_name, *qubit_list, *user_data, return_flag)) @@ -311,7 +286,7 @@ def add_gate(self, gate_name, *user_data)) # Get the gate to add to quantumsim. - gate = self.gate_dic[gate_name]['function'] + gate = self.setup.gate_dic[gate_name]['function'] # The save flag prevents saving multiple gate # definitions when using recursive gates (i.e. @@ -344,6 +319,11 @@ def add_gate(self, gate_name, for qubit in qubit_list: self.times[qubit] = max(self.times[qubit], time + gate_time) + # If this qubit has not been used before, store the start + # of this gate as the first time it is activated. + if self.tmins[qubit] is None: + self.tmins[qubit] = time + # My current best idea for adjustable gates - return the # gate that could be adjusted to the user. if return_flag is not False: @@ -353,22 +333,75 @@ def update(self, **kwargs): for rule in self.update_rules: update_function_dic[rule](self, **kwargs) - def finalize(self, topo_order=False, t_add=0): + def finalize(self, topo_order=False, dtmax=0, dtmin=0, shrink=False): + """ + Script to run to finalize gates. Currently adds waiting gates + as required and sorts gates. + + Args: + -------- + topo_order : bool + whether to toposort gates or simply order them by time. + dtmax : float or dict of floats + time to pad qubits by (i.e. dead time after the gates + in the circuit are executed). If float, applies same + padding to all qubits. + dtmin : float or dict of floats + time to pad qubits by on the left (i.e. dead time before + any gates are executed). If float, applies same padding + to all qubits. + shrink : bool + whether to shrink resting gates around qubits. When """ - Adds resting gates to all systems as required. - quantumsim currently assumes fixed values for photon - numbers, so we take them from a random qubit - Photons in quantumsim are currently broken, so - they're not in here right now. + if self.setup.system_params['uses_waiting_gates'] is True: + self.add_waiting_gates( + dtmin=dtmin, dtmax=dtmax, shrink=shrink) + + if topo_order is True: + self.circuit.order() + else: + self.circuit.gates = sorted(self.circuit.gates, + key=lambda x: x.time) + + def add_waiting_gates(self, dtmin=0, dtmax=0, shrink=False): """ + Function to add waiting gates to system + + Args: + -------- + dtmax : float or dict of floats + time to pad qubits by (i.e. dead time after the gates + in the circuit are executed). If float, applies same + padding to all qubits. + dtmin : float or dict of floats + time to pad qubits by on the left (i.e. dead time before + any gates are executed). If float, applies same padding + to all qubits. + shrink : bool + whether to shrink resting gates around qubits. When + """ + if shrink: + tmin = self.tmins + tmax = self.times + else: + circuit_time = max(self.times.values()) + tmax = {key: circuit_time for key in self.times.keys()} + tmin = {key: 0 for key in self.times.keys()} + + if type(dtmax) == dict: + for key,val in dtmax.items(): + tmax[key] += val + else: + for key in tmax.keys(): + tmax[key] += dtmax - circuit_time = max(self.times.values()) - if type(t_add) == dict: - circuit_time = {key: val + circuit_time - for key, val in t_add.items()} + if type(dtmin) == dict: + for key,val in dtmin.items(): + tmin[key] -= val else: - circuit_time += t_add + for key in tmin.keys(): + tmin[key] -= dtmin # args = list(self.qubit_dic.values())[0] @@ -380,9 +413,4 @@ def finalize(self, topo_order=False, t_add=0): # chi=args['chi']) # else: - self.circuit.add_waiting_gates(tmin=0, tmax=circuit_time) - if topo_order is True: - self.circuit.order() - else: - self.circuit.gates = sorted(self.circuit.gates, - key=lambda x: x.time) + self.circuit.add_waiting_gates(tmin=tmin, tmax=tmax) diff --git a/qsoverlay/experiment_setup.py b/qsoverlay/experiment_setup.py index 8adbe37..27e07a4 100644 --- a/qsoverlay/experiment_setup.py +++ b/qsoverlay/experiment_setup.py @@ -15,7 +15,8 @@ def __init__( self, filename=None, seed=None, state=None, gate_dic=None, update_rules=None, - qubit_dic=None, gate_set=None): + qubit_dic=None, gate_set=None, + system_params=None): if filename is not None: self.load(filename, seed, state) @@ -24,6 +25,7 @@ def __init__( self.update_rules = update_rules or [] self.qubit_dic = qubit_dic or {} self.gate_set = gate_set or {} + self.system_params = system_params or {} def load(self, filename, seed=None, state=None): with open(filename, 'r') as infile: @@ -31,6 +33,7 @@ def load(self, filename, seed=None, state=None): self.update_rules = setup_load_format['update_rules'] self.qubit_dic = setup_load_format['qubit_dic'] + self.system_params = setup_load_format['system_params'] # Currently assumes each qubit uses the same # uniform_noisy_sampler - this needs fixing @@ -90,7 +93,8 @@ def save(self, filename): 'gate_dic': gate_dic_save_format, 'update_rules': self.update_rules, 'qubit_dic': qubit_dic_save_format, - 'gate_set': gate_set_save_format + 'gate_set': gate_set_save_format, + 'system_params': system_params } with open(filename, 'w') as outfile: json.dump(setup_save_format, outfile) diff --git a/qsoverlay/test/test_builder.py b/qsoverlay/test/test_builder.py index a2520ca..3dc552d 100644 --- a/qsoverlay/test/test_builder.py +++ b/qsoverlay/test/test_builder.py @@ -1,4 +1,5 @@ from qsoverlay.circuit_builder import Builder +from qsoverlay.experiment_setup import Setup from qsoverlay.DiCarlo_setup import quick_setup from quantumsim.sparsedm import SparseDM import pytest @@ -12,13 +13,14 @@ def test_init(self): qubit_dic = {'q_test': {}} gate_dic = {} gate_set = {} - b = Builder(qubit_dic=qubit_dic, - gate_dic=gate_dic, - gate_set=gate_set, + setup = Setup(qubit_dic=qubit_dic, + gate_dic=gate_dic, + gate_set=gate_set) + b = Builder(setup=setup, circuit_title='Test') - assert len(b.qubit_dic.keys()) == 1 - assert b.gate_dic == {} - assert b.gate_set == {} + assert len(b.setup.qubit_dic.keys()) == 1 + assert b.setup.gate_dic == {} + assert b.setup.gate_set == {} assert len(b.times.keys()) == 1 assert b.circuit.title == 'Test' assert len(b.circuit.qubits) == 1 @@ -31,9 +33,10 @@ def test_T1T2(self): qubit_dic = {'q_test': {'t1': 10, 't2': 20}} gate_dic = {} gate_set = {} - b = Builder(qubit_dic=qubit_dic, - gate_dic=gate_dic, - gate_set=gate_set, + setup = Setup(qubit_dic=qubit_dic, + gate_dic=gate_dic, + gate_set=gate_set) + b = Builder(setup=setup, circuit_title='Test') assert b.circuit.qubits[0].t1 == 10 assert b.circuit.qubits[0].t2 == 20 @@ -42,9 +45,10 @@ def test_T1T2_kwarginit(self): qubit_dic = {'q_test': {}} gate_dic = {} gate_set = {} - b = Builder(qubit_dic=qubit_dic, - gate_dic=gate_dic, - gate_set=gate_set, + setup = Setup(qubit_dic=qubit_dic, + gate_dic=gate_dic, + gate_set=gate_set) + b = Builder(setup=setup, circuit_title='Test', t1=20, t2=30) @@ -140,3 +144,35 @@ def test_make_imperfect_bell(self): assert np.abs(diag[3]-0.5) < 1e-2 assert np.abs(diag[1]) < 3e-2 assert np.abs(diag[2]) < 3e-2 + + def test_shrink(self): + qubit_list = ['q0','q1'] + with pytest.warns(UserWarning): + setup = quick_setup(qubit_list) + sq_gate_time = setup.qubit_dic['q0']['oneq_gate_time'] + cp_gate_time = setup.qubit_dic['q0']['CZ_gate_time'] + b = Builder(setup) + b.add_gate('RotateY', ['q0'], angle=np.pi/2) + b.add_gate('CZ', ['q0', 'q1']) + b.add_gate('RotateY', ['q1'], angle=-np.pi/2) + b.finalize(shrink=True) + assert min([gate.time for gate in b.circuit.gates + if 'q1' in gate.involved_qubits]) == sq_gate_time + cp_gate_time/4 + assert max([gate.time for gate in b.circuit.gates + if 'q0' in gate.involved_qubits]) == sq_gate_time + 3*cp_gate_time/4 + + def test_noshrink(self): + qubit_list = ['q0','q1'] + with pytest.warns(UserWarning): + setup = quick_setup(qubit_list) + sq_gate_time = setup.qubit_dic['q0']['oneq_gate_time'] + cp_gate_time = setup.qubit_dic['q0']['CZ_gate_time'] + b = Builder(setup) + b.add_gate('RotateY', ['q0'], angle=np.pi/2) + b.add_gate('CZ', ['q0', 'q1']) + b.add_gate('RotateY', ['q1'], angle=-np.pi/2) + b.finalize(shrink=False) + assert min([gate.time for gate in b.circuit.gates + if 'q1' in gate.involved_qubits]) == sq_gate_time/2 + cp_gate_time/4 + assert max([gate.time for gate in b.circuit.gates + if 'q0' in gate.involved_qubits]) == sq_gate_time*3/2 + cp_gate_time*3/4 From 9dcb180ae0333fef0efb3ea311b703638de511f3 Mon Sep 17 00:00:00 2001 From: Thomas OBrien Date: Thu, 1 Nov 2018 16:01:20 +0100 Subject: [PATCH 2/5] Changed times slightly --- qsoverlay/circuit_builder.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/qsoverlay/circuit_builder.py b/qsoverlay/circuit_builder.py index f86214e..63344f8 100644 --- a/qsoverlay/circuit_builder.py +++ b/qsoverlay/circuit_builder.py @@ -226,10 +226,10 @@ def add_gates_simultaneous(self, gate_descriptions): starting_time = max([ self.times[gate_desc[j]] for gate_desc in gate_descriptions - for j in range(1, self.gate_dic[gate_desc[0]]['num_qubits']+1)]) + for j in range(1, self.setup.gate_dic[gate_desc[0]]['num_qubits']+1)]) for gate_desc in gate_descriptions: - num_qubits = self.gate_dic[gate_desc[0]]['num_qubits'] + num_qubits = self.setup.gate_dic[gate_desc[0]]['num_qubits'] qubit_list = gate_desc[1:num_qubits + 1] for qubit in qubit_list: self.times[qubit] = starting_time @@ -361,7 +361,8 @@ def update(self, **kwargs): for rule in self.update_rules: update_function_dic[rule](self, **kwargs) - def finalize(self, topo_order=False, dtmax=0, dtmin=0, shrink=False): + def finalize(self, topo_order=False, tmin=None, tmax=None, + dtmax=0, dtmin=0, shrink=False): """ Script to run to finalize gates. Currently adds waiting gates as required and sorts gates. @@ -370,6 +371,12 @@ def finalize(self, topo_order=False, dtmax=0, dtmin=0, shrink=False): -------- topo_order : bool whether to toposort gates or simply order them by time. + tmin : float or dict of floats + earliest time to add decay on qubits. If set, overrides + shrink. + tmax : float of dict of floats + latest time to add decay on qubits. If set, overrides + shrink. dtmax : float or dict of floats time to pad qubits by (i.e. dead time after the gates in the circuit are executed). If float, applies same @@ -384,6 +391,7 @@ def finalize(self, topo_order=False, dtmax=0, dtmin=0, shrink=False): if self.setup.system_params['uses_waiting_gates'] is True: self.add_waiting_gates( + tmin=tmin, tmax=tmax, dtmin=dtmin, dtmax=dtmax, shrink=shrink) if topo_order is True: @@ -392,7 +400,8 @@ def finalize(self, topo_order=False, dtmax=0, dtmin=0, shrink=False): self.circuit.gates = sorted(self.circuit.gates, key=lambda x: x.time) - def add_waiting_gates(self, dtmin=0, dtmax=0, shrink=False): + def add_waiting_gates(self, tmin=None, tmax=None, + dtmin=0, dtmax=0, shrink=False): """ Function to add waiting gates to system @@ -409,13 +418,17 @@ def add_waiting_gates(self, dtmin=0, dtmax=0, shrink=False): shrink : bool whether to shrink resting gates around qubits. When """ - if shrink: - tmin = self.tmins - tmax = self.times - else: - circuit_time = max(self.times.values()) - tmax = {key: circuit_time for key in self.times.keys()} - tmin = {key: 0 for key in self.times.keys()} + if tmin is None: + if shrink: + tmin = self.tmins + else: + tmin = {key: 0 for key in self.times.keys()} + if tmax is None: + if shrink: + tmax = self.times + else: + circuit_time = max(self.times.values()) + tmax = {key: circuit_time for key in self.times.keys()} if type(dtmax) == dict: for key,val in dtmax.items(): From 8e6d8f66e64665fc1313b08bc4e5d431189c96aa Mon Sep 17 00:00:00 2001 From: Thomas OBrien Date: Tue, 6 Nov 2018 14:15:11 +0100 Subject: [PATCH 3/5] Adjusted circuit_builder to use simple order --- qsoverlay/circuit_builder.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/qsoverlay/circuit_builder.py b/qsoverlay/circuit_builder.py index 63344f8..ed3578d 100644 --- a/qsoverlay/circuit_builder.py +++ b/qsoverlay/circuit_builder.py @@ -361,16 +361,17 @@ def update(self, **kwargs): for rule in self.update_rules: update_function_dic[rule](self, **kwargs) - def finalize(self, topo_order=False, tmin=None, tmax=None, - dtmax=0, dtmin=0, shrink=False): + def finalize(self, toposort=False, tmin=None, tmax=None, + dtmax=0, dtmin=0, shrink=False): """ Script to run to finalize gates. Currently adds waiting gates as required and sorts gates. Args: -------- - topo_order : bool + toposort : bool or "simple" whether to toposort gates or simply order them by time. + Setting toposort="simple" uses the simple toposort algorithm. tmin : float or dict of floats earliest time to add decay on qubits. If set, overrides shrink. @@ -394,11 +395,7 @@ def finalize(self, topo_order=False, tmin=None, tmax=None, tmin=tmin, tmax=tmax, dtmin=dtmin, dtmax=dtmax, shrink=shrink) - if topo_order is True: - self.circuit.order() - else: - self.circuit.gates = sorted(self.circuit.gates, - key=lambda x: x.time) + self.circuit.order(toposort) def add_waiting_gates(self, tmin=None, tmax=None, dtmin=0, dtmax=0, shrink=False): From 258079ef0a24c6040e58f3fab7b7ceae5ea6c3cd Mon Sep 17 00:00:00 2001 From: Thomas OBrien Date: Thu, 31 Jan 2019 15:12:37 +0100 Subject: [PATCH 4/5] Added system_params to asymmetric setup --- qsoverlay/DiCarlo_setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/qsoverlay/DiCarlo_setup.py b/qsoverlay/DiCarlo_setup.py index 7dff27e..e81f4d7 100644 --- a/qsoverlay/DiCarlo_setup.py +++ b/qsoverlay/DiCarlo_setup.py @@ -191,7 +191,12 @@ def asymmetric_setup(qubit_parameters=None, qubit_dic=asym_setup['qubit_dic'], gate_dic=asym_setup['gate_dic'], connectivity_dic=connectivity_dic) - return Setup(**asym_setup) + + system_params = { + 'uses_waiting_gates': True + } + + return Setup(**asym_setup, system_params=system_params) def get_gate_dic(): From 6dc3bbf65101b621a387fbecb096b256ade33d2a Mon Sep 17 00:00:00 2001 From: Thomas OBrien Date: Thu, 27 Jun 2019 16:02:23 +0200 Subject: [PATCH 5/5] Two minor fixes --- qsoverlay/circuit_builder.py | 9 +++++---- qsoverlay/experiment_setup.py | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/qsoverlay/circuit_builder.py b/qsoverlay/circuit_builder.py index ed3578d..83d761f 100644 --- a/qsoverlay/circuit_builder.py +++ b/qsoverlay/circuit_builder.py @@ -390,10 +390,11 @@ def finalize(self, toposort=False, tmin=None, tmax=None, whether to shrink resting gates around qubits. When """ - if self.setup.system_params['uses_waiting_gates'] is True: - self.add_waiting_gates( - tmin=tmin, tmax=tmax, - dtmin=dtmin, dtmax=dtmax, shrink=shrink) + if ('uses_waiting_gates' in self.setup.system_params and + self.setup.system_params['uses_waiting_gates'] is True): + self.add_waiting_gates( + tmin=tmin, tmax=tmax, + dtmin=dtmin, dtmax=dtmax, shrink=shrink) self.circuit.order(toposort) diff --git a/qsoverlay/experiment_setup.py b/qsoverlay/experiment_setup.py index 27e07a4..08892d9 100644 --- a/qsoverlay/experiment_setup.py +++ b/qsoverlay/experiment_setup.py @@ -33,7 +33,10 @@ def load(self, filename, seed=None, state=None): self.update_rules = setup_load_format['update_rules'] self.qubit_dic = setup_load_format['qubit_dic'] - self.system_params = setup_load_format['system_params'] + try: + self.system_params = setup_load_format['system_params'] + except: + self.system_params = {} # Currently assumes each qubit uses the same # uniform_noisy_sampler - this needs fixing