def test_mutation_free_estimation(): """ Make sure the estimation routines do not mutate the programs the user sends This is accomplished by a deep copy in `estimate_pauli_sum'. """ prog = Program().inst(I(0)) pauli_sum = sX(0) # measure in the X-basis # set up fake QVM fakeQVM = Mock(spec=QVMConnection()) fakeQVM.run = Mock(return_value=[[0], [1]]) expected_value, estimator_variance, total_shots = \ estimate_locally_commuting_operator(prog, PauliSum([pauli_sum]), 1.0E-3, quantum_resource=fakeQVM) # make sure RY(-pi/2) 0\nMEASURE 0 [0] was not added to the program the # user sees assert prog.out() == 'I 0\n'
def test_QAOACostFunctionOnWFSim_get_wavefunction(): sim = WavefunctionSimulator() # ham = PauliSum.from_compact_str("0.7*Z0*Z1 + 1.2*Z0*Z2") term1 = PauliTerm("Z", 0, 0.7) * PauliTerm("Z", 1) term2 = PauliTerm("Z", 0, 1.2) * PauliTerm("Z", 2) ham = PauliSum([term1, term2]) timesteps = 2 params = StandardWithBiasParams\ .linear_ramp_from_hamiltonian(ham, timesteps) cost_function = QAOACostFunctionOnWFSim(ham, params=params, sim=sim, scalar_cost_function=True, nshots=100) wf = cost_function.get_wavefunction(params.raw()) assert np.allclose(wf.probabilities(), np.array([0.01, 0.308, 0.053, 0.13, 0.13, 0.053, 0.308, 0.01]), rtol=1e-2, atol=0.005)
def get_operator_expectation_value( self, state_circuit: Circuit, operator: QubitPauliOperator) -> complex: """Calculates the expectation value of the given circuit with respect to the operator using the built-in QVM functionality :param state_circuit: Circuit that generates the desired state :math:`\\left|\\psi\\right>`. :type state_circuit: Circuit :param operator: Operator :math:`H`. :type operator: QubitPauliOperator :return: :math:`\\left<\\psi | H | \\psi \\right>` :rtype: complex """ prog = tk_to_pyquil(state_circuit) pauli_sum = PauliSum([ self._gen_PauliTerm(term, coeff) for term, coeff in operator._dict.items() ]) return complex(self._sim.expectation(prog, pauli_sum))
def create_cost_hamiltonian(weights): """ Translating the distances between cities into the cost hamiltonian. """ cost_operators = [] number_of_cities = weights.shape[0] for t in range(number_of_cities - 1): for city_1 in range(number_of_cities): for city_2 in range(number_of_cities): # If these aren't the same city and they have an edge connecting them distance = weights[city_1, city_2] if city_1 != city_2 and distance != 0.0: qubit_1 = t * number_of_cities + city_1 qubit_2 = (t + 1) * number_of_cities + city_2 cost_operators.append( PauliTerm("Z", qubit_1, distance) * PauliTerm("Z", qubit_2)) cost_hamiltonian = [PauliSum(cost_operators)] return cost_hamiltonian
def test_pauli_sum_from_str(): # this also tests PauliTerm.from_compact_str() since it gets called Sum = (1.5 + .5j) * sX(0) * sZ(2) + 0.7 * sZ(1) another_str = "(1.5 + 0.5j)*X0*Z2+.7*Z1" assert PauliSum.from_compact_str(str(Sum)) == Sum assert PauliSum.from_compact_str(Sum.compact_str()) == Sum assert PauliSum.from_compact_str(another_str) == Sum # test sums of length one Sum = PauliSum([1 * sY(0) * sY(1)]) the_str = "1*Y0*Y1" assert PauliSum.from_compact_str(the_str) == Sum # test sums containing the identity Sum = (1.5 + .5j) * sX(0) * sZ(2) + 0.7 * sI(1) the_str = "(1.5 + 0.5j)*X0*Z2+.7*I" assert PauliSum.from_compact_str(the_str) == Sum # test the simplification (both in sums and products) Sum = PauliSum([2 * sY(1)]) the_str = "1*Y0*X0 + (0+1j)*Z0 + 2*Y1" assert PauliSum.from_compact_str(the_str) == Sum
def lifted_pauli(pauli_sum: Union[PauliSum, PauliTerm], qubits: List[int]) -> np.ndarray: """ Takes a PauliSum object along with a list of qubits and returns a matrix corresponding the tensor representation of the object. Useful for generating the full Hamiltonian after a particular fermion to pauli transformation. For example: Converting a PauliSum X0Y1 + Y1X0 into the matrix .. code-block:: python [[ 0.+0.j, 0.+0.j, 0.+0.j, 0.-2.j], [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+2.j, 0.+0.j, 0.+0.j, 0.+0.j]] Developer note: Quil and the QVM like qubits to be ordered such that qubit 0 is on the right. Therefore, in ``qubit_adjacent_lifted_gate``, ``lifted_pauli``, and ``lifted_state_operator``, we build up the lifted matrix by performing the kronecker product from right to left. :param pauli_sum: Pauli representation of an operator :param qubits: list of qubits in the order they will be represented in the resultant matrix. :return: matrix representation of the pauli_sum operator """ if isinstance(pauli_sum, PauliTerm): pauli_sum = PauliSum([pauli_sum]) n_qubits = len(qubits) result_hilbert = np.zeros((2**n_qubits, 2**n_qubits), dtype=np.complex128) # left kronecker product corresponds to the correct basis ordering for term in pauli_sum.terms: term_hilbert = np.array([1]) for qubit in qubits: term_hilbert = np.kron(QUANTUM_GATES[term[qubit]], term_hilbert) result_hilbert += term_hilbert * term.coefficient return result_hilbert
def test_qaoa_on_qvm(): # ham = PauliSum.from_compact_str("(-1.0)*Z0*Z1 + 0.8*Z0 + (-0.5)*Z1") term1 = PauliTerm("Z", 0, -1) * PauliTerm("Z", 1) term2 = PauliTerm("Z", 0, 0.8) term3 = PauliTerm("Z", 1, -0.5) ham = PauliSum([term1, term2, term3]) params = FourierParams.linear_ramp_from_hamiltonian(ham, n_steps=10, q=2) p0 = params.raw() cost_fun = QAOACostFunctionOnQVM(ham, params, "2q-qvm", scalar_cost_function=True, nshots=4, base_numshots=50) out = minimize(cost_fun, p0, tol=2e-1, method="Cobyla", options={"maxiter": 100}) assert np.allclose(out["fun"], -1.3, rtol=1.1) assert out["success"]
def test_qaoa_on_wfsim(): # ham = PauliSum.from_compact_str("(-1.0)*Z0*Z1 + 0.8*Z0 + (-0.5)*Z1") term1 = PauliTerm("Z", 0, -1) * PauliTerm("Z", 1) term2 = PauliTerm("Z", 0, 0.8) term3 = PauliTerm("Z", 1, -0.5) ham = PauliSum([term1, term2, term3]) params = FourierWithBiasParams.linear_ramp_from_hamiltonian(ham, n_steps=10, q=2) p0 = params.raw() cost_fun = QAOACostFunctionOnWFSim(ham, params, scalar_cost_function=True) out = minimize(cost_fun, p0, tol=1e-3, method="Cobyla", options={"maxiter": 500}) wf = cost_fun.get_wavefunction(params.raw()) assert np.allclose(out["fun"], -1.3, rtol=1.1) assert out["success"] assert np.allclose(wf.probabilities(), [0, 0, 0, 1], rtol=1.5, atol=0.05)
def create_penalty_operators_for_qubit_range(self, range_of_qubits): cost_operators = [] tsp_matrix = TSP_utilities.get_tsp_matrix(self.nodes_array) weight = -100 * np.max(tsp_matrix) # weight = -0.5 for i in range_of_qubits: if i == range_of_qubits[0]: z_term = PauliTerm("Z", i, weight) all_ones_term = PauliTerm("I", 0, 0.5 * weight) - PauliTerm( "Z", i, 0.5 * weight) else: z_term = z_term * PauliTerm("Z", i) all_ones_term = all_ones_term * (PauliTerm("I", 0, 0.5) - PauliTerm("Z", i, 0.5)) z_term = PauliSum([z_term]) cost_operators.append( PauliTerm("I", 0, weight) - z_term + self.all_ones_coefficient * all_ones_term) return cost_operators
def test_imaginary_removal(): """ remove terms with imaginary coefficients from a pauli sum """ test_term = 0.25 * sX(1) * sZ(2) * sX(3) + 0.25j * sX(1) * sZ(2) * sY(3) test_term += -0.25j * sY(1) * sZ(2) * sX(3) + 0.25 * sY(1) * sZ(2) * sY(3) true_term = 0.25 * sX(1) * sZ(2) * sX(3) + 0.25 * sY(1) * sZ(2) * sY(3) assert remove_imaginary_terms(test_term) == true_term test_term = (0.25 + 1j) * sX(0) * sZ(2) + 1j * sZ(2) # is_identity in pyquil apparently thinks zero is identity assert remove_imaginary_terms(test_term) == 0.25 * sX(0) * sZ(2) test_term = 0.25 * sX(0) * sZ(2) + 1j * sZ(2) assert remove_imaginary_terms(test_term) == PauliSum([0.25 * sX(0) * sZ(2)]) with pytest.raises(TypeError): remove_imaginary_terms(5) with pytest.raises(TypeError): remove_imaginary_terms(sX(0))
def test_measure_observables_many_progs(forest): expts = [ ExperimentSetting(sI(), o1 * o2) for o1, o2 in itertools.product([sI(0), sX(0), sY(0), sZ(0)], [sI(1), sX(1), sY(1), sZ(1)]) ] qc = get_qc('2q-qvm') qc.qam.random_seed = 51 for prog in _random_2q_programs(): suite = TomographyExperiment(expts, program=prog, qubits=[0, 1]) assert len(suite) == 4 * 4 gsuite = group_experiments(suite) assert len(gsuite) == 3 * 3 # can get all the terms with I for free in this case wfn = WavefunctionSimulator() wfn_exps = {} for expt in expts: wfn_exps[expt] = wfn.expectation(gsuite.program, PauliSum([expt.out_operator])) for res in measure_observables(qc, gsuite, n_shots=1_000): np.testing.assert_allclose(wfn_exps[res.setting], res.expectation, atol=0.1)
def random_hamiltonian(reg: List[Union[int, QubitPlaceholder]]) -> PauliSum: """ Creates a random cost hamiltonian, diagonal in the computational basis: - Randomly selects which qubits that will have a bias term, then assigns them a bias coefficient. - Randomly selects which qubit pairs will have a coupling term, then assigns them a coupling coefficient. In both cases, the random coefficient is drawn from the uniform distribution on the interval [0,1). Parameters ---------- reg: register to build the hamiltonian on. Returns ------- PauliSum: A hamiltonian with random couplings and biases, as a PauliSum object. """ hamiltonian = [] n_biases = np.random.randint(len(reg)) bias_qubits = random.sample(reg, n_biases) bias_coeffs = np.random.rand(n_biases) for qubit, coeff in zip(bias_qubits, bias_coeffs): hamiltonian.append(PauliTerm("Z", qubit, coeff)) for q1, q2 in itertools.combinations(reg, 2): are_coupled = np.random.randint(2) if are_coupled: couple_coeff = np.random.rand() hamiltonian.append( PauliTerm("Z", q1, couple_coeff) * PauliTerm("Z", q2)) return PauliSum(hamiltonian)
def hamiltonian_from_distance_matrix(dist, biases=None) -> PauliSum: """ Generates a Hamiltonian from a distance matrix and a numpy array of single qubit bias terms where the i'th indexed value of in biases is applied to the i'th qubit. Parameters ---------- dist: A 2-dimensional square matrix where entries in row i, column j represent the distance between node i and node j. biases: A dictionary of floats, with keys indicating the qubits with bias terms, and corresponding values being the bias coefficients. Returns ------- hamiltonian: A PauliSum object modelling the Hamiltonian of the system """ pauli_list = list() m, n = dist.shape #allows tolerance for both matrices and dataframes if isinstance(dist, pd.DataFrame): dist = dist.values if biases: if not isinstance(biases, type(dict())): raise ValueError('biases must be of type dict()') for key in biases: term = PauliTerm('Z', key, biases[key]) pauli_list.append(term) # pairwise interactions for i in range(m): for j in range(n): if i < j: term = PauliTerm('Z', i, dist[i][j]) * PauliTerm('Z', j) pauli_list.append(term) return PauliSum(pauli_list)
def maxcut_qaoa(graph, steps=1, rand_seed=None, connection=None, samples=None, initial_beta=None, initial_gamma=None, minimizer_kwargs=None, vqe_option=None): if not isinstance(graph, nx.Graph) and isinstance(graph, list): maxcut_graph = nx.Graph() for edge in graph: maxcut_graph.add_edge(*edge) graph = maxcut_graph.copy() cost_operators = [] driver_operators = [] for i, j in graph.edges(): weight = graph.get_edge_data(i,j)['weight']/largest_weight print(weight) cost_operators.append(PauliTerm("Z", i, weight)*PauliTerm("Z", j) + PauliTerm("I", 0, -weight)) for i in graph.nodes(): driver_operators.append(PauliSum([PauliTerm("X", i, -1.0)])) if connection is None: connection = get_qc(f"{len(graph.nodes)}q-qvm") if minimizer_kwargs is None: minimizer_kwargs = {'method': 'Nelder-Mead', 'options': {'ftol': 1.0e-2, 'xtol': 1.0e-2, 'disp': False}} if vqe_option is None: vqe_option = {'disp': print, 'return_all': True, 'samples': samples} qaoa_inst = QAOA(connection, list(graph.nodes()), steps=steps, cost_ham=cost_operators, ref_ham=driver_operators, store_basis=True, rand_seed=rand_seed, init_betas=initial_beta, init_gammas=initial_gamma, minimizer=minimize, minimizer_kwargs=minimizer_kwargs, vqe_options=vqe_option) return qaoa_inst
def test_extended_get_constraints(): weights = [0.1, 0.3, 0.5, -0.7] term1 = PauliTerm.from_list([("Z", 0), ("Z", 1)], 0.1) term2 = PauliTerm.from_list([("Z", 1), ("Z", 2)], 0.3) term3 = PauliTerm.from_list([("Z", 2), ("Z", 3)], 0.5) term4 = PauliTerm.from_list([("Z", 3), ("Z", 0)], -0.7) ham = PauliSum([term1, term2, term3, term4]) p = 2 params = ExtendedParams.linear_ramp_from_hamiltonian(ham, p) actual_constraints = params.get_constraints() expected_constraints = [(0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi), (0, 2 * np.pi / weights[0]), (0, 2 * np.pi / weights[1]), (0, 2 * np.pi / weights[2]), (0, 2 * np.pi / weights[3]), (0, 2 * np.pi / weights[0]), (0, 2 * np.pi / weights[1]), (0, 2 * np.pi / weights[2]), (0, 2 * np.pi / weights[3])] assert (np.allclose(expected_constraints, actual_constraints)) cost_function = QAOACostFunctionOnWFSim(ham, params) np.random.seed(0) random_angles = np.random.uniform(-100, 100, size=len(params.raw())) value = cost_function(random_angles) normalised_angles = [ random_angles[i] % actual_constraints[i][1] for i in range(len(params.raw())) ] normalised_value = cost_function(normalised_angles) assert (np.allclose(value, normalised_value))
def test_pauli_sum(): q_plus = 0.5 * PauliTerm('X', 0) + 0.5j * PauliTerm('Y', 0) the_sum = q_plus * PauliSum([PauliTerm('X', 0)]) term_strings = map(lambda x: str(x), the_sum.terms) assert '0.5*I' in term_strings assert '(0.5+0j)*Z0' in term_strings assert len(term_strings) == 2 assert len(the_sum.terms) == 2 the_sum = q_plus * PauliTerm('X', 0) term_strings = map(lambda x: str(x), the_sum.terms) assert '0.5*I' in term_strings assert '(0.5+0j)*Z0' in term_strings assert len(term_strings) == 2 assert len(the_sum.terms) == 2 the_sum = PauliTerm('X', 0) * q_plus term_strings = map(lambda x: str(x), the_sum.terms) assert '0.5*I' in term_strings assert '(-0.5+0j)*Z0' in term_strings assert len(term_strings) == 2 assert len(the_sum.terms) == 2
def test_mutation_free_estimation(): """ Make sure the estimation routines do not mutate the programs the user sends. This is accomplished by a deep copy in `estimate_pauli_sum'. """ prog = Program().inst(I(0)) pauli_sum = sX(0) # measure in the X-basis # set up fake QVM with patch("pyquil.api.QuantumComputer") as qc: # Mock the response qc.run.return_value = [[0], [1]] _, _, _ = estimate_locally_commuting_operator(prog, pauli_sum=PauliSum( [pauli_sum]), variance_bound=1.0E-3, quantum_resource=qc) # make sure RY(-pi/2) 0\nMEASURE 0 [0] was not added to the program the user sees assert prog.out() == 'I 0\n'
def create_phase_separator(self): """ Creates phase-separation operators, which depend on the objective function. """ cost_operators = [] reduced_distance_matrix = np.delete(self.distance_matrix, self.starting_node, axis=0) reduced_distance_matrix = np.delete(reduced_distance_matrix, self.starting_node, axis=1) for t in range(self.reduced_number_of_nodes - 1): for city_1 in range(self.reduced_number_of_nodes): for city_2 in range(self.reduced_number_of_nodes): if city_1 != city_2: distance = reduced_distance_matrix[city_1, city_2] qubit_1 = t * (self.reduced_number_of_nodes) + city_1 qubit_2 = (t + 1) * (self.reduced_number_of_nodes) + city_2 cost_operators.append( PauliTerm("Z", qubit_1, distance) * PauliTerm("Z", qubit_2)) costs_to_starting_node = np.delete( self.distance_matrix[:, self.starting_node], self.starting_node) for city in range(self.reduced_number_of_nodes): distance_from_0 = -costs_to_starting_node[city] qubit = city cost_operators.append(PauliTerm("Z", qubit, distance_from_0)) for city in range(self.reduced_number_of_nodes): distance_from_0 = -costs_to_starting_node[city] qubit = self.number_of_qubits - ( self.reduced_number_of_nodes) + city cost_operators.append(PauliTerm("Z", qubit, distance_from_0)) phase_separator = [PauliSum(cost_operators)] return phase_separator
def test_time_evolution_derivatives(self): # Given hamiltonian = PauliSum([ PauliTerm('X', 0) * PauliTerm('X', 1), PauliTerm('Y', 0, 0.5) * PauliTerm('Y', 1), PauliTerm('Z', 0, 0.3) * PauliTerm('Z', 1) ]) time = 0.4 order = 3 reference_factors_1 = [1.0 / order, 0.5 / order, 0.3 / order] * 3 reference_factors_2 = [-1.0 * x for x in reference_factors_1] # When derivatives, factors = time_evolution_derivatives(hamiltonian, time, trotter_order=order) # Then self.assertEqual(len(derivatives), order * 2 * len(hamiltonian.terms)) self.assertEqual(len(factors), order * 2 * len(hamiltonian.terms)) self.assertListEqual(reference_factors_1, factors[0:18:2]) self.assertListEqual(reference_factors_2, factors[1:18:2])
def create_phase_separator(self): cost_operators = [] for t in range(len(self.nodes_array) - 1): for city_1 in range(len(self.nodes_array)): for city_2 in range(len(self.nodes_array)): if city_1 != city_2: tsp_matrix = TSP_utilities.get_tsp_matrix( self.nodes_array) distance = tsp_matrix[city_1, city_2] qubit_1 = t * len(self.nodes_array) + city_1 qubit_2 = (t + 1) * len(self.nodes_array) + city_2 cost_operators.append( PauliTerm("Z", qubit_1, distance) * PauliTerm("Z", qubit_2)) for city in range(len(self.costs_to_starting_node)): distance_from_0 = -self.costs_to_starting_node[city] qubit = city cost_operators.append(PauliTerm("Z", qubit, distance_from_0)) phase_separator = [PauliSum(cost_operators)] return phase_separator
def qubitop_to_pyquilpauli(qubit_operator): """ Convert an OpenFermion QubitOperator to a PauliSum :param QubitOperator qubit_operator: OpenFermion QubitOperator to convert to a pyquil.PauliSum :return: PauliSum representing the qubit operator :rtype: PauliSum """ if not isinstance(qubit_operator, QubitOperator): raise TypeError("qubit_operator must be a OpenFermion " "QubitOperator object") transformed_term = PauliSum([PauliTerm("I", 0, 0.0)]) for qubit_terms, coefficient in qubit_operator.terms.items(): base_term = PauliTerm('I', 0) for tensor_term in qubit_terms: base_term *= PauliTerm(tensor_term[1], tensor_term[0]) transformed_term += base_term * coefficient return transformed_term
def estimate_general_psum_symmeterized(program, pauli_sum, variance_bound, quantum_resource, confusion_mat_dict=None, sequential=False): """ Estimate the expected value of a Pauli sum to fixed precision. :param program: state preparation program :param pauli_sum: pauli sum of operators to estimate expected value :param variance_bound: variance bound on the estimator :param quantum_resource: quantum abstract machine object :return: expected value, estimator variance, total number of experiments """ if sequential: expected_value = 0 estimator_variance = 0 total_shots = 0 variance_bound_per_term = variance_bound / len(pauli_sum) for term in pauli_sum: exp_v, exp_var, exp_shots = \ estimate_locally_commuting_operator_symmeterized( program, PauliSum([term]), variance_bound_per_term, quantum_resource, confusion_mat_dict=confusion_mat_dict) expected_value += exp_v estimator_variance += exp_var total_shots += exp_shots return expected_value, estimator_variance, total_shots else: return estimate_locally_commuting_operator_symmeterized( program, pauli_sum, variance_bound, quantum_resource, confusion_mat_dict=confusion_mat_dict)
def ring_of_disagrees(n: int) -> PauliSum: """ Builds the cost Hamiltonian for the "Ring of Disagrees" described in the original QAOA paper (https://arxiv.org/abs/1411.4028), for the specified number of vertices n. Parameters ---------- n: Number of vertices in the ring Returns ------- hamiltonian: The cost Hamiltonian representing the ring, as a PauliSum object. """ hamiltonian = [] for i in range(n - 1): hamiltonian.append(PauliTerm("Z", i, 0.5) * PauliTerm("Z", i + 1)) hamiltonian.append(PauliTerm("Z", n - 1, 0.5) * PauliTerm("Z", 0)) return PauliSum(hamiltonian)
def test_expectation(forest: ForestConnection): # The forest fixture (argument) to this test is to ensure this is # skipped when a forest web api key is unavailable. You could also # pass it to the constructor of WavefunctionSimulator() but it is not # necessary. wfnsim = WavefunctionSimulator() bell = Program( H(0), CNOT(0, 1), ) expects = wfnsim.expectation(bell, [ sZ(0) * sZ(1), sZ(0), sZ(1), sX(0) * sX(1), ]) assert expects.size == 4 np.testing.assert_allclose(expects, [1, 0, 0, 1]) pauli_sum = PauliSum([sZ(0) * sZ(1)]) expects = wfnsim.expectation(bell, pauli_sum) assert expects.size == 1 np.testing.assert_allclose(expects, [1])
def random_hamiltonian(nqubits: int) -> PauliSum: """ Creates a random cost hamiltonian, diagonal in the computational basis: - Randomly selects which qubits that will have a bias term, then assigns them a bias coefficient. - Randomly selects which qubit pairs will have a coupling term, then assigns them a coupling coefficient. In both cases, the random coefficient is drawn from the uniform distribution on the interval [0,1). Parameters ---------- nqubits: The desired number of qubits. Returns ------- hamiltonian: A hamiltonian with random couplings and biases, as a PauliSum object. """ hamiltonian = [] numb_biases = np.random.randint(nqubits) bias_qubits = np.random.choice(nqubits, numb_biases, replace=False) bias_coeffs = np.random.rand(numb_biases) for i in range(numb_biases): hamiltonian.append(PauliTerm("Z", int(bias_qubits[i]), bias_coeffs[i])) for i in range(nqubits): for j in range(i + 1, nqubits): are_coupled = np.random.randint(2) if are_coupled: couple_coeff = np.random.rand() hamiltonian.append( PauliTerm("Z", i, couple_coeff) * PauliTerm("Z", j)) return PauliSum(hamiltonian)
def sampling_expectation_z_base(hamiltonian: PauliSum, bitstrings: np.array) -> Tuple[float, float]: """Calculates the energy expectation value of ``bitstrings`` w.r.t ``ham``. Warning ------- This function assumes, that all terms in ``hamiltonian`` commute trivially _and_ that the ``bitstrings`` were measured in their basis. Parameters ---------- param hamiltonian: The hamiltonian param bitstrings: 2D arry or list The measurement outcomes. One column per qubit. Returns ------- tuple (expectation_value, variance/(n-1)) """ # this dictionary maps from qubit indices to indices of the bitstrings # This is neccesary, because hamiltonian might not act on all qubits. # E.g. if hamiltonian = X0 + 1.0*Z2 bitstrings is a 2 x numshots array index_lut = {q: i for (i, q) in enumerate(hamiltonian.get_qubits())} if bitstrings.ndim == 2: energies = np.zeros(bitstrings.shape[0]) else: energies = np.array([0]) for term in hamiltonian: sign = np.zeros_like(energies) for factor in term: sign += bitstrings[:, index_lut[factor[0]]] energies += term.coefficient.real * (-1)**sign return (np.mean(energies), np.var(energies) / (bitstrings.shape[0] - 1))
def vqe_parametric(vqe_strategy, vqe_tomography, vqe_method): """ Initialize a VQE experiment with a custom hamiltonian given as constant input """ _vqe = None if vqe_strategy == "custom_program": custom_ham = PauliSum([PauliTerm(*x) for x in HAMILTONIAN]) _vqe = VQEexperiment(hamiltonian=custom_ham, method=vqe_method, strategy=vqe_strategy, parametric=True, tomography=vqe_tomography, shotN=NSHOTS_FLOAT) elif vqe_strategy == "HF": cwd = os.path.abspath(os.path.dirname(__file__)) fname = os.path.join(cwd, "resources", "H.hdf5") molecule = MolecularData(filename=fname) _vqe = VQEexperiment(molecule=molecule, method=vqe_method, strategy=vqe_strategy, parametric=True, tomography=vqe_tomography, shotN=NSHOTS_FLOAT) elif vqe_strategy == "UCCSD": cwd = os.path.abspath(os.path.dirname(__file__)) fname = os.path.join(cwd, "resources", "H2.hdf5") molecule = MolecularData(filename=fname) _vqe = VQEexperiment(molecule=molecule, method=vqe_method, strategy=vqe_strategy, parametric=True, tomography=vqe_tomography, shotN=NSHOTS_FLOAT) return _vqe
def lifted_pauli(pauli_sum: Union[PauliSum, PauliTerm], qubits: List[int]): """ Takes a PauliSum object along with a list of qubits and returns a matrix corresponding the tensor representation of the object. Useful for generating the full Hamiltonian after a particular fermion to pauli transformation. For example: Converting a PauliSum X0Y1 + Y1X0 into the matrix .. code-block:: python [[ 0.+0.j, 0.+0.j, 0.+0.j, 0.-2.j], [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j], [ 0.+2.j, 0.+0.j, 0.+0.j, 0.+0.j]] :param pauli_sum: Pauli representation of an operator :param qubits: list of qubits in the order they will be represented in the resultant matrix. :returns: matrix representation of the pauli_sum operator """ if isinstance(pauli_sum, PauliTerm): pauli_sum = PauliSum([pauli_sum]) n_qubits = len(qubits) result_hilbert = np.zeros((2 ** n_qubits, 2 ** n_qubits), dtype=np.complex128) # left kronecker product corresponds to the correct basis ordering for term in pauli_sum.terms: term_hilbert = np.array([1]) for qubit in qubits: term_hilbert = np.kron(QUANTUM_GATES[term[qubit]], term_hilbert) result_hilbert += term_hilbert * term.coefficient return result_hilbert
def pauli_matrix(pauli_sum: PauliSum, qubit_mapping: Dict = {}) -> np.array: """Create the matrix representation of pauli_sum. Parameters ---------- qubit_mapping: A dictionary-like object that maps from :py:class`QubitPlaceholder` to :py:class:`int` Returns ------- np.matrix: A matrix representing the PauliSum """ # get unmapped Qubits and check that all QubitPlaceholders are mapped unmapped_qubits = {*pauli_sum.get_qubits()} - qubit_mapping.keys() if not all(isinstance(q, int) for q in unmapped_qubits): raise ValueError("Not all QubitPlaceholders are mapped") # invert qubit_mapping and assert its injectivity inv_mapping = dict([v, k] for k, v in qubit_mapping.items()) if len(inv_mapping) is not len(qubit_mapping): raise ValueError("qubit_mapping must be injective") # add unmapped qubits to the inverse mapping, ensuring we don't have # a list entry twice for q in unmapped_qubits: if q not in inv_mapping.keys(): inv_mapping[q] = q else: raise ValueError("qubit_mapping maps to qubit already in use") qubit_list = [inv_mapping[k] for k in sorted(inv_mapping.keys())] matrix = lifted_pauli(pauli_sum, qubit_list) return matrix
class TestTimeEvolutionDerivatives: @pytest.fixture( params=[ PauliSum( [ PauliTerm("X", 0) * PauliTerm("X", 1), PauliTerm("Y", 0, 0.5) * PauliTerm("Y", 1), PauliTerm("Z", 0, 0.3) * PauliTerm("Z", 1), ] ), QubitOperator("[X0 X1] + 0.5[Y0 Y1] + 0.3[Z0 Z1]"), ] ) def hamiltonian(self, request): return request.param @pytest.mark.parametrize("time", [0.4, sympy.Symbol("t")]) def test_time_evolution_derivatives_gives_correct_number_of_derivatives_and_factors( self, time, hamiltonian ): order = 3 reference_factors_1 = [1.0 / order, 0.5 / order, 0.3 / order] * 3 reference_factors_2 = [-1.0 * x for x in reference_factors_1] derivatives, factors = time_evolution_derivatives( hamiltonian, time, trotter_order=order ) if isinstance(hamiltonian, QubitOperator): terms = list(hamiltonian.get_operators()) elif isinstance(hamiltonian, PauliSum): terms = hamiltonian.terms assert len(derivatives) == order * 2 * len(terms) assert len(factors) == order * 2 * len(terms) assert factors[0:18:2] == reference_factors_1 assert factors[1:18:2] == reference_factors_2