def build_farhi_qaoa_circuit_template(hamiltonian): """Constructs a circuit template for a QAOA ansatz. Args: hamiltonians (list): a list of zquantum.core.qubitoperator.QubitOperator objects Returns: circuit_template (dict): dictionary describing the ansatz """ n_qubits = count_qubits(hamiltonian) diffusion_op = QubitOperator() for i in range(n_qubits): diffusion_op += QubitOperator((i, 'X')) ansatz = { 'ansatz_type': 'singlet UCCSD', 'ansatz_module': 'zquantum.qaoa.ansatz', 'ansatz_func': 'build_qaoa_circuit', 'ansatz_grad_func': 'build_qaoa_circuit_grads', 'supports_simple_shift_rule': False, 'ansatz_kwargs': { 'hamiltonians': [ convert_qubitop_to_dict(hamiltonian), convert_qubitop_to_dict(diffusion_op) ] }, 'n_params': [2] } return (ansatz)
def _build_hamiltonian(self, graph: nx.Graph) -> QubitOperator: """Construct a qubit operator with Hamiltonian for the graph partition problem. The returned Hamiltonian is consistent with the definitions from "Ising formulations of many NP problems" by A. Lucas, page 6 (https://arxiv.org/pdf/1302.5843.pdf). The operator's terms contain Pauli Z matrices applied to qubits. The qubit indices are based on graph node indices in the graph definition, not on the node names. Args: graph: undirected weighted graph defining the problem scale_factor: constant by which all the coefficients in the Hamiltonian will be multiplied offset: coefficient of the constant term added to the Hamiltonian to shift its energy levels Returns: operator describing the Hamiltonian """ ham_a = QubitOperator() for i in graph.nodes: ham_a += QubitOperator(f"Z{i}") ham_a = ham_a**2 ham_b = QubitOperator() for i, j in graph.edges: ham_b += 1 - QubitOperator(f"Z{i} Z{j}") ham_b /= 2 return ham_a + ham_b
def test_x_y_z_mode_projection(self): """Find the projection of a wavefunction generated from a linear combination of qubits. python2 and python3 iterate through Qubit operators differently. Consequently, the coeffcients must be set based on that iteration at test time. """ n_qubits = 2 qubits = cirq.LineQubit.range(n_qubits) test_wfn = numpy.array( [0.92377985 + 0.j, 0. - 0.20947377j, 0.32054904 + 0.j, 0. + 0.j], dtype=numpy.complex128) self.assertAlmostEqual(numpy.vdot(test_wfn, test_wfn), 1.0 + 0.0j, places=6) ops = QubitOperator('X0', 1.0) + QubitOperator('Y1', 1.0) \ + QubitOperator('Z0', 1.0) test_cof = numpy.zeros((3, 1), dtype=numpy.complex128) for indx, cluster in enumerate(ops.terms): if cluster[0][1] == 'X': test_cof[indx] = 0.32054904 if cluster[0][1] == 'Y': test_cof[indx] = -0.20947377 if cluster[0][1] == 'Z': test_cof[indx] = 0.92377985 cof = numpy.zeros((3, 1), dtype=numpy.complex128) cirq_utils.qubit_projection(ops, qubits, test_wfn, cof) self.assertTrue(numpy.allclose(cof, test_cof))
def _build_hamiltonian( self, graph: nx.Graph, ) -> QubitOperator: """Construct a qubit operator with Hamiltonian for the vertex cover problem. From https://arxiv.org/pdf/1302.5843.pdf, see equations 33 and 34 and https://quantumcomputing.stackexchange.com/questions/16082/vertex-cover-mappings-from-qubo-to-ising-and-vice-versa for corrective translation shifts The operator's terms contain Pauli Z matrices applied to qubits. The qubit indices are based on graph node indices in the graph definition, not on the node names. Args: graph: undirected weighted graph defining the problem scale_factor: constant by which all the coefficients in the Hamiltonian will be multiplied offset: coefficient of the constant term added to the Hamiltonian to shift its energy levels Returns: operator describing the Hamiltonian """ ham_a = QubitOperator() for i, j in graph.edges: ham_a += (1 - QubitOperator(f"Z{i}")) * (1 - QubitOperator(f"Z{j}")) ham_a *= self._hamiltonian_factor / 4 ham_b = QubitOperator() for i in graph.nodes: ham_b += QubitOperator(f"Z{i}") ham_b /= 2 return ham_a + ham_b + len(graph.nodes) / 2
def get_maxcut_hamiltonian(graph): """Converts a MAXCUT instance, as described by a weighted graph, to an Ising Hamiltonian. Args: graph (networkx.Graph): undirected weighted graph describing the MAXCUT instance. Returns: zquantum.core.qubitoperator.QubitOperator object describing the Hamiltonian. """ output = QubitOperator() nodes_dict = generate_graph_node_dict(graph) for edge in graph.edges: coeff = graph.edges[edge[0], edge[1]]['weight'] node_index1 = nodes_dict[edge[0]] node_index2 = nodes_dict[edge[1]] ZZ_term_str = 'Z' + str(node_index1) + ' Z' + str(node_index2) output += QubitOperator(ZZ_term_str, coeff) return output
def _build_hamiltonian(self, graph: nx.Graph) -> QubitOperator: """Construct a qubit operator with Hamiltonian for the stable set problem. Based on "Efficient Combinatorial Optimization Using Quantum Annealing" p. 8 (https://arxiv.org/pdf/1801.08653.pdf) and also mentioned briefly in "Ising formulations of many NP problems" by A. Lucas, page 11 section 4.2 (https://arxiv.org/pdf/1302.5843.pdf). The operator's terms contain Pauli Z matrices applied to qubits. The qubit indices are based on graph node indices in the graph definition, not on the node names. Args: graph: undirected weighted graph defining the problem scale_factor: constant by which all the coefficients in the Hamiltonian will be multiplied offset: coefficient of the constant term added to the Hamiltonian to shift its energy levels Returns: operator describing the Hamiltonian """ ham_a = QubitOperator() for i, j in graph.edges: ham_a += (1 - QubitOperator(f"Z{i}")) * (1 - QubitOperator(f"Z{j}")) ham_b = QubitOperator() for i in graph.nodes: ham_b += QubitOperator(f"Z{i}") return ham_a / 2 + ham_b / 2 - len(graph.nodes) / 2
def reverse_qubit_order(qubit_operator: QubitOperator, n_qubits: Optional[int] = None): """Reverse the order of qubit indices in a qubit operator. Args: qubit_operator (openfermion.QubitOperator): the operator n_qubits (int): total number of qubits. Needs to be provided when the size of the system of interest is greater than the size of qubit operator (optional) Returns: reversed_op (openfermion.ops.QubitOperator) """ reversed_op = QubitOperator() if n_qubits is None: n_qubits = count_qubits(qubit_operator) if n_qubits < count_qubits(qubit_operator): raise ValueError("Invalid number of qubits specified.") for term in qubit_operator.terms: new_term = [] for factor in term: new_factor = list(factor) new_factor[0] = n_qubits - 1 - new_factor[0] new_term.append(tuple(new_factor)) reversed_op += QubitOperator(tuple(new_term), qubit_operator.terms[term]) return reversed_op
def test_create_circuits_from_qubit_operator(self): # Initialize target qubits = [Qubit(i) for i in range(0, 2)] gate_Z0 = Gate("Z", [qubits[0]]) gate_X1 = Gate("X", [qubits[1]]) gate_Y0 = Gate("Y", [qubits[0]]) gate_Z1 = Gate("Z", [qubits[1]]) circuit1 = Circuit() circuit1.qubits = qubits circuit1.gates = [gate_Z0, gate_X1] circuit2 = Circuit() circuit2.qubits = qubits circuit2.gates = [gate_Y0, gate_Z1] target_circuits_list = [circuit1, circuit2] # Given qubit_op = QubitOperator("Z0 X1") + QubitOperator("Y0 Z1") # When pauli_circuits = create_circuits_from_qubit_operator(qubit_op) # Then self.assertEqual(pauli_circuits[0].gates, target_circuits_list[0].gates) self.assertEqual(pauli_circuits[1].gates, target_circuits_list[1].gates) self.assertEqual( str(pauli_circuits[0].qubits), str(target_circuits_list[0].qubits) ) self.assertEqual( str(pauli_circuits[1].qubits), str(target_circuits_list[1].qubits) )
def test_one_qubit_parametric_gates_using_expectation_values( self, backend_for_gates_test, initial_gate, tested_gate, params, target_values): n_samples = 1000 # Given gate_1 = builtin_gate_by_name(initial_gate)(0) gate_2 = builtin_gate_by_name(tested_gate)(*params)(0) circuit = Circuit([gate_1, gate_2]) operators = [ QubitOperator("[]"), QubitOperator("[X0]"), QubitOperator("[Y0]"), QubitOperator("[Z0]"), ] sigma = 1 / np.sqrt(n_samples) for i, operator in enumerate(operators): # When estimation_tasks = [EstimationTask(operator, circuit, n_samples)] expectation_values = estimate_expectation_values_by_averaging( backend_for_gates_test, estimation_tasks) calculated_value = expectation_values.values[0] # Then assert calculated_value == pytest.approx(target_values[i], abs=sigma * 3)
def test_qubitop_to_paulisum_more_terms(self): # Given qubit_operator = ( QubitOperator("Z0 Z1 Z2", -1.5) + QubitOperator("X0", 2.5) + QubitOperator("Y1", 3.5) ) expected_qubits = (LineQubit(0), LineQubit(5), LineQubit(8)) expected_paulisum = ( PauliSum() + ( PauliString(Z.on(expected_qubits[0])) * PauliString(Z.on(expected_qubits[1])) * PauliString(Z.on(expected_qubits[2])) * -1.5 ) + (PauliString(X.on(expected_qubits[0]) * 2.5)) + (PauliString(Y.on(expected_qubits[1]) * 3.5)) ) # When paulisum = qubitop_to_paulisum(qubit_operator, qubits=expected_qubits) # Then self.assertEqual(paulisum.qubits, expected_qubits) self.assertEqual(paulisum, expected_paulisum)
def qiskitpauli_to_qubitop( qiskit_pauli: WeightedPauliOperator) -> QubitOperator: """ Convert a qiskit's qiskit.aqua.operators.WeightedPauliOperator to a OpenFermion QubitOperator. Args: qiskit_pauli: WeightedPauliOperator to convert to an OpenFermion QubitOperator Returns: QubitOperator representing the WeightedPauliOperator """ if not isinstance(qiskit_pauli, WeightedPauliOperator): raise TypeError("qiskit_pauli must be a qiskit WeightedPauliOperator") transformed_term = QubitOperator() for weight, qiskit_term in qiskit_pauli.paulis: openfermion_term = QubitOperator() for (term_qubit, term_pauli) in enumerate(str(qiskit_term)): if term_pauli != "I": if openfermion_term == QubitOperator(): openfermion_term = QubitOperator( f"[{term_pauli}{term_qubit}]") else: openfermion_term *= QubitOperator( f"[{term_pauli}{term_qubit}]") transformed_term += openfermion_term * weight return transformed_term
def test_get_pauli_strings(self): qubit_operator = (QubitOperator( ((0, "X"), (1, "Y"))) - 0.5 * QubitOperator( ((1, "Y"), )) + 0.5 * QubitOperator(())) constructed_list = get_pauli_strings(qubit_operator) target_list = ["X0Y1", "Y1", ""] self.assertListEqual(constructed_list, target_list)
def test_group_individually(self): target_operator = 10.0 * QubitOperator("Z0") target_operator += 5.0 * QubitOperator("Z1") target_operator -= 3.0 * QubitOperator("Y0") target_operator += 1.0 * QubitOperator("X0") target_operator += 20.0 * QubitOperator("") expected_operator_terms_per_frame = [ (10.0 * QubitOperator("Z0")).terms, (5.0 * QubitOperator("Z1")).terms, (-3.0 * QubitOperator("Y0")).terms, (1.0 * QubitOperator("X0")).terms, (20.0 * QubitOperator("")).terms, ] circuit = Circuit(Program(X(0))) estimation_tasks = [EstimationTask(target_operator, circuit, None)] grouped_tasks = group_individually(estimation_tasks) assert len(grouped_tasks) == 5 for task in grouped_tasks: assert task.operator.terms in expected_operator_terms_per_frame
def ansatz(self, thetas, number_of_layers): cost_hamiltonian = QubitOperator((0, "Z")) + QubitOperator((1, "Z")) return WarmStartQAOAAnsatz( number_of_layers=number_of_layers, cost_hamiltonian=cost_hamiltonian, thetas=thetas, )
def ansatz(self): cost_hamiltonian = QubitOperator((0, "Z")) + QubitOperator((1, "Z")) mixer_hamiltonian = QubitOperator((0, "X")) + QubitOperator((1, "X")) return QAOAFarhiAnsatz( number_of_layers=1, cost_hamiltonian=cost_hamiltonian, mixer_hamiltonian=mixer_hamiltonian, )
def create_one_qubit_operator(x_coeff: float, y_coeff: float, z_coeff: float, constant: float) -> None: qubit_operator = (x_coeff * QubitOperator("X0") + y_coeff * QubitOperator("Y0") + z_coeff * QubitOperator("Z0") + constant * QubitOperator( ())) save_qubit_operator(qubit_operator, "qubit_operator.json")
def binary_string_rep_inv(binary_string): n_qubit = len(binary_string) // 2 qop = QubitOperator(()) for i, k in enumerate(binary_string): if k == 0: continue if i < n_qubit: qop *= QubitOperator((i, 'X')) else: qop *= QubitOperator((i - n_qubit, 'Z')) return qop
def test_set_cost_hamiltonian(self, ansatz): # Given new_cost_hamiltonian = QubitOperator((0, "Z")) - QubitOperator( (1, "Z")) # When ansatz.cost_hamiltonian = new_cost_hamiltonian # Then assert ansatz._cost_hamiltonian == new_cost_hamiltonian
def test_set_mixer_hamiltonian(self, ansatz): # Given new_mixer_hamiltonian = QubitOperator((0, "Z")) - QubitOperator( (1, "Z")) # When ansatz.mixer_hamiltonian = new_mixer_hamiltonian # Then ansatz._mixer_hamiltonian == new_mixer_hamiltonian
def test_set_mixer_hamiltonian_invalidates_circuit(self, ansatz): # Given new_mixer_hamiltonian = QubitOperator((0, "Z")) - QubitOperator( (1, "Z")) # When ansatz.mixer_hamiltonian = new_mixer_hamiltonian # Then assert ansatz._parametrized_circuit is None
def test_create_all_x_mixer_hamiltonian(): # Given number_of_qubits = 4 target_operator = (QubitOperator("X0") + QubitOperator("X1") + QubitOperator("X2") + QubitOperator("X3")) # When operator = create_all_x_mixer_hamiltonian(number_of_qubits) # Then assert operator == target_operator
def test_get_number_of_qubits(self, ansatz): # Given new_cost_hamiltonian = (QubitOperator((0, "Z")) + QubitOperator( (1, "Z")) + QubitOperator((2, "Z"))) target_number_of_qubits = 3 # When ansatz.cost_hamiltonian = new_cost_hamiltonian # Then assert ansatz.number_of_qubits == target_number_of_qubits
def test_exception_convert_observable(): r"""Test that an error is raised if the QubitOperator contains complex coefficients. Currently the vqe.Hamiltonian class does not support complex coefficients. """ qubit_op = (QubitOperator("Y0 Y1", 1 + 0j) + QubitOperator("Z0 X1", 4.5 + 1.5j) + QubitOperator("Y0 X1", 2)) with pytest.raises( TypeError, match="The coefficients entering the QubitOperator must be real"): qchem.convert_observable(qubit_op)
def test_create_farhi_qaoa_circuits_fails_when_length_of_inputs_is_not_equal(): # Given hamiltonians = [ QubitOperator("Z0 Z1"), QubitOperator("Z0") + QubitOperator("Z1"), ] number_of_layers = [2] # When with pytest.raises(AssertionError): create_farhi_qaoa_circuits(hamiltonians, number_of_layers)
def test_evaluate_qubit_operator_list(self): # Given qubit_op_list = [ QubitOperator("0.5 [] + 0.5 [Z1]"), QubitOperator("0.3 [X1] + 0.2[Y2]"), ] expectation_values = ExpectationValues([0.5, 0.5, 0.4, 0.6]) # When value_estimate = evaluate_qubit_operator_list(qubit_op_list, expectation_values) # Then self.assertAlmostEqual(value_estimate.value, 0.74)
def test_get_maxcut_hamiltonian(self): # Given graph = nx.Graph() graph.add_edge(1, 2, weight=0.4) graph.add_edge(2, 3, weight=-0.1) graph.add_edge(1, 3, weight=0.2) target_hamiltonian = 0.4*QubitOperator('Z0 Z1') - 0.1*QubitOperator('Z1 Z2') + 0.2*QubitOperator('Z0 Z2') # When hamiltonian = get_maxcut_hamiltonian(graph) # Then self.assertEqual(hamiltonian, target_hamiltonian)
def test_get_number_of_qubits_with_ising_hamiltonian(self, ansatz): # Given new_cost_hamiltonian = (QubitOperator((0, "Z")) + QubitOperator( (1, "Z")) + QubitOperator((2, "Z"))) new_cost_hamiltonian = change_operator_type(new_cost_hamiltonian, IsingOperator) target_number_of_qubits = 3 # When ansatz.cost_hamiltonian = new_cost_hamiltonian # Then assert ansatz.number_of_qubits == target_number_of_qubits
def test_get_expectation_value(self): """Check <Z0> and <Z1> for the state |100>""" # Given wf = pyquil.wavefunction.Wavefunction([0, 1, 0, 0, 0, 0, 0, 0]) op1 = QubitOperator("Z0") op2 = QubitOperator("Z1") # When exp_op1 = get_expectation_value(op1, wf) exp_op2 = get_expectation_value(op2, wf) # Then self.assertAlmostEqual(-1, exp_op1) self.assertAlmostEqual(1, exp_op2)
def test_get_context_selection_circuit_for_group(self): group = QubitOperator("X0 Y1") - 0.5 * QubitOperator((1, "Y")) circuit, ising_operator = get_context_selection_circuit_for_group( group) # Need to convert to QubitOperator in order to get matrix representation qubit_operator = change_operator_type(ising_operator, QubitOperator) target_unitary = qubit_operator_sparse(group) transformed_unitary = ( circuit.to_unitary().conj().T @ qubit_operator_sparse(qubit_operator) @ circuit.to_unitary()) assert np.allclose(target_unitary.todense(), transformed_unitary)
def test_group_greedily_all_different_groups(self): target_operator = 10.0 * QubitOperator("Z0") target_operator -= 3.0 * QubitOperator("Y0") target_operator += 1.0 * QubitOperator("X0") target_operator += 20.0 * QubitOperator("") expected_operators = [ 10.0 * QubitOperator("Z0"), -3.0 * QubitOperator("Y0"), 1.0 * QubitOperator("X0"), 20.0 * QubitOperator(""), ] circuit = Circuit(Program(X(0))) estimation_tasks = [EstimationTask(target_operator, circuit, None)] grouped_tasks = group_greedily(estimation_tasks) for task, operator in zip(grouped_tasks, expected_operators): assert task.operator == operator for initial_task, modified_task in zip(estimation_tasks, grouped_tasks): assert modified_task.circuit == initial_task.circuit assert modified_task.number_of_shots == initial_task.number_of_shots