def eval_contraction_cost(filename): """ Loads circuit from file, evaluates contraction cost with and without optimization """ # Prepare graphical model n_qubits, circuit = ops.read_circuit_file(filename) buckets, data_dict, bra_vars, ket_vars = opt.circ2buckets( n_qubits, circuit) graph_raw = gm.buckets2graph( buckets, ignore_variables=bra_vars+ket_vars) # estimate cost mem_raw, flop_raw = gm.get_contraction_costs(graph_raw) mem_raw_tot = sum(mem_raw) # optimize node order peo, treewidth = gm.get_peo(graph_raw) # get cost for reordered graph graph, label_dict = gm.relabel_graph_nodes( graph_raw, dict(zip(peo, sorted(graph_raw.nodes(), key=int))) ) mem_opt, flop_opt = gm.get_contraction_costs(graph) mem_opt_tot = sum(mem_opt) # split graph and relabel in optimized way n_var_parallel = 3 _, reduced_graph = gm.split_graph_by_metric( graph_raw, n_var_parallel) # peo, treewidth = gm.get_peo(reduced_graph) peo, treewidth = gm.get_peo(reduced_graph) graph_parallel, label_dict = gm.relabel_graph_nodes( reduced_graph, dict(zip( peo, sorted(reduced_graph.nodes(), key=int))) ) mem_par, flop_par = gm.get_contraction_costs(graph_parallel) mem_par_tot = sum(mem_par) print('Memory (in doubles):\n raw: {} optimized: {}'.format( mem_raw_tot, mem_opt_tot)) print(' parallel:\n node: {} total: {} n_tasks: {}'.format( mem_par_tot, mem_par_tot*2**(n_var_parallel), 2**(n_var_parallel) ))
def get_optimal_graphical_model( filename): """ Builds a graphical model to contract a circuit in ``filename`` and finds its tree decomposition """ n_qubits, circuit = ops.read_circuit_file(filename) buckets, data_dict, bra_vars, ket_vars = opt.circ2buckets( n_qubits, circuit) graph = gm.buckets2graph(buckets, ignore_variables=bra_vars+ket_vars) peo, tw = gm.get_peo(graph) graph_optimal, label_dict = gm.relabel_graph_nodes( graph, dict(zip(peo, range(1, len(peo) + 1))) ) return graph_optimal
def einsum_sequential_np(subscripts, *operands): """ This function implements a sequential einsum using graphical models. Parameters ---------- subsrcipts : str, set of indices in Einstein notation, i.e. 'ij,jk,klm,lm->i' operands: list of array_like, tensor Returns ------- result: array_like Result of the contraction """ graph_initial, free_variables, data_dict = einsum2graph( subscripts, *operands) # Calculate elimination order graph = gm.make_clique_on(graph_initial, free_variables) peo_initial, treewidth = gm.get_peo(graph) # transform peo so free_variables are at the end peo = gm.get_equivalent_peo(graph, peo_initial, free_variables) # reorder graph and get buckets graph_optimal, _ = gm.relabel_graph_nodes( graph_initial, dict(zip(peo, range(len(peo))))) buckets = opt.graph2buckets(graph_optimal) # populate buckets with actual arrays and perform elimination np_buckets = npfr.get_np_buckets(buckets, data_dict) result = opt.bucket_elimination( np_buckets, npfr.process_bucket_np, n_var_nosum=len(free_variables)) return result.data
def eval_circuit(n_qubits, circuit, final_state, initial_state=0, measured_final=None, measured_initial=None, pdict={}): """ Evaluate a circuit with specified initial and final states. Parameters ---------- n_qubits: int Number of qubits in the circuit circuit: list of lists List of lists of gates final_state: int Values of measured qubits at the end of the circuit (bra). Bitwise coded with the length of min(n_qubits, len(measured_final) initial_state: int Values of the measured qubits at the beginning of the circuit (ket). Bitwise coded with the length of min(n_qubits, len(measured_initial) measured_final: list, default None Iterable with the positions of qubits which are measured. If not all qubits are measured, then a subset of amplitudes will be evaluated measured_initial: list, default None Iterable with the positions of qubits which are measured initially. If not all are measured, then the resulting amplitudes will be evaluated for a subset of initial states. pdict: dict, default {} Returns ------- amplitudes: numpy.array """ # Check which qubits are measured all_qubits = set(range(n_qubits)) if measured_final is None: measured_final = tuple(range(n_qubits)) else: if not set(measured_final).issubset(all_qubits): raise ValueError(f'measured_final qubits outside allowed' f' range: {measured_final}') if measured_initial is None: measured_initial = tuple(range(n_qubits)) else: if not set(measured_initial).issubset(all_qubits): raise ValueError(f'measured_initial qubits outside allowed' f' range: {measured_initial}') # Prepare graphical model buckets, data_dict, bra_vars, ket_vars = opt.circ2buckets( n_qubits, circuit, pdict=pdict) # Collect free qubit variables free_final = sorted(all_qubits - set(measured_final)) free_bra_vars = [bra_vars[idx] for idx in free_final] bra_vars = [bra_vars[idx] for idx in measured_final] free_initial = sorted(all_qubits - set(measured_initial)) free_ket_vars = [ket_vars[idx] for idx in free_initial] ket_vars = [ket_vars[idx] for idx in measured_initial] if len(free_bra_vars) > 0: log.info('Evaluate amplitudes over all final states of qubits:') log.info(f'{free_final}') if len(free_ket_vars) > 0: log.info('Evaluate amplitudes over all initial states of qubits:') log.info(f'{free_initial}') graph_initial = gm.buckets2graph( buckets, ignore_variables=bra_vars+ket_vars) graph = gm.make_clique_on(graph_initial, free_bra_vars+free_ket_vars) # Get PEO peo_initial, treewidth = gm.get_peo(graph) # transform peo so free_bra_vars and free_ket_vars are at the end # this fixes the layout of the tensor peo = gm.get_equivalent_peo(graph, peo_initial, free_bra_vars+free_ket_vars) # place bra and ket variables to beginning, so these variables # will be contracted first perm_buckets, perm_dict = opt.reorder_buckets( buckets, bra_vars + ket_vars + peo) perm_graph, _ = gm.relabel_graph_nodes( graph, perm_dict) # extract bra and ket variables from variable list and sort according # to qubit order ket_vars = sorted([perm_dict[idx] for idx in ket_vars], key=str) bra_vars = sorted([perm_dict[idx] for idx in bra_vars], key=str) # make proper slice dictionaries. We choose ket = |0>, # bra = |0> on fixed entries slice_dict = utils.slice_from_bits(initial_state, ket_vars) slice_dict.update(utils.slice_from_bits(final_state, bra_vars)) slice_dict.update({var: slice(None) for var in free_bra_vars}) slice_dict.update({var: slice(None) for var in free_ket_vars}) # Finally make numpy buckets and calculate sliced_buckets = npfr.get_sliced_np_buckets( perm_buckets, data_dict, slice_dict) result = opt.bucket_elimination( sliced_buckets, npfr.process_bucket_np, n_var_nosum=len(free_bra_vars+free_ket_vars)) return result.data
def eval_with_multiamp_np(filename, initial_state=0): """ Loads circuit from file and evaluates multiple amplitudes at once using np framework """ # Values of the fixed bra qubits. # this can be changed to your taste target_state = 0 # Prepare graphical model n_qubits, circuit = ops.read_circuit_file(filename) buckets, data_dict, bra_vars, ket_vars = opt.circ2buckets( n_qubits, circuit) # Collect free qubit variables free_final_qubits = [1, 3] free_bra_vars = [] for ii in free_final_qubits: try: free_bra_vars.append(bra_vars[ii]) except IndexError: pass bra_vars = [var for var in bra_vars if var not in free_bra_vars] if len(free_bra_vars) > 0: print('Evaluate all amplitudes over final qubits:') print(free_final_qubits) print('Free variables in the resulting expression:') print(free_bra_vars) graph_initial = gm.buckets2graph( buckets, ignore_variables=bra_vars+ket_vars) graph = gm.make_clique_on(graph_initial, free_bra_vars) # Run quickbb peo_initial, treewidth = gm.get_peo(graph) # transform peo so free_bra_vars are at the end peo = gm.get_equivalent_peo(graph, peo_initial, free_bra_vars) # place bra and ket variables to beginning, so these variables # will be contracted first perm_buckets, perm_dict = opt.reorder_buckets( buckets, bra_vars + ket_vars + peo) perm_graph, _ = gm.relabel_graph_nodes( graph, perm_dict) # extract bra and ket variables from variable list and sort according # to qubit order ket_vars = sorted([perm_dict[idx] for idx in ket_vars], key=str) bra_vars = sorted([perm_dict[idx] for idx in bra_vars], key=str) # make proper slice dictionaries. We choose ket = |0>, # bra = |0> on fixed entries slice_dict = utils.slice_from_bits(initial_state, ket_vars) slice_dict.update(utils.slice_from_bits(target_state, bra_vars)) slice_dict.update({var: slice(None) for var in free_bra_vars}) # Finally make numpy buckets and calculate sliced_buckets = npfr.get_sliced_np_buckets( perm_buckets, data_dict, slice_dict) result = opt.bucket_elimination( sliced_buckets, npfr.process_bucket_np, n_var_nosum=len(free_bra_vars)) amplitudes = result.data.flatten() # Now calculate the reference amplitudes_reference = get_amplitudes_from_cirq(filename) # Get a slice as we do not need full amplitude bra_slices = {var: slice_dict[var] for var in slice_dict if var.name.startswith('o')} # sort slice in the big endian order for Cirq computed_subtensor = [slice_dict[var] for var in sorted(bra_slices, key=str)] slice_of_amplitudes = amplitudes_reference.reshape( [2]*n_qubits)[tuple(computed_subtensor)] slice_of_amplitudes = slice_of_amplitudes.flatten() print('Result:') print(np.round(amplitudes, 3)) print('Reference:') print(np.round(slice_of_amplitudes, 3)) print('Max difference:') print(np.max(np.abs(amplitudes - slice_of_amplitudes)))