def __init__(
        self,
        n_c: int,
        cost_hamiltonian: IsingOperator,
        ansatz: Ansatz,
        inner_optimizer: Optimizer,
        recorder: RecorderFactory = _recorder,
    ) -> None:
        """This is an implementation of recursive QAOA (RQAOA) from https://arxiv.org/abs/1910.08980 page 4.

        The main idea is that we call QAOA recursively and reduce the size of the cost hamiltonian by 1
        on each recursion, until we hit a threshold number of qubits `n_c`. Then, we use brute force to
        solve the reduced QAOA problem, mapping the reduced solution to the original solution.

        Args:
            n_c: The threshold number of qubits at which recursion stops, as described in the original paper.
                Cannot be greater than number of qubits.
            cost_hamiltonian: Hamiltonian representing the cost function.
            ansatz: an Ansatz object with all params (ex. `n_layers`) initialized
            inner_optimizer: optimizer used for optimization of parameters at each recursion of RQAOA.
            recorder: recorder object which defines how to store the optimization history.

        """
        n_qubits = count_qubits(change_operator_type(cost_hamiltonian, QubitOperator))

        if n_c >= n_qubits or n_c <= 0:
            raise ValueError(
                "n_c needs to be a value less than number of qubits and greater than 0."
            )

        self._n_c = n_c
        self._ansatz = ansatz
        self._cost_hamiltonian = cost_hamiltonian
        self._inner_optimizer = inner_optimizer
        self._recorder = recorder
    def _generate_circuit(self,
                          params: Optional[np.ndarray] = None) -> Circuit:
        """Returns a parametrizable circuit represention of the ansatz.
        Args:
            params: parameters of the circuit.
        """
        if params is not None:
            Warning(
                "This method retuns a parametrizable circuit, params will be ignored."
            )
        circuit = Circuit()

        # Prepare initial state
        circuit += create_layer_of_gates(self.number_of_qubits, RY,
                                         self._thetas)

        # Add time evolution layers
        cost_circuit = time_evolution(
            change_operator_type(self._cost_hamiltonian, QubitOperator),
            sympy.Symbol(f"gamma"),
        )
        for i in range(self.number_of_layers):
            circuit += cost_circuit.bind(
                {sympy.Symbol(f"gamma"): sympy.Symbol(f"gamma_{i}")})
            circuit += create_layer_of_gates(self.number_of_qubits, RY,
                                             -self._thetas)
            circuit += create_layer_of_gates(
                self.number_of_qubits,
                RZ,
                [-2 * sympy.Symbol(f"beta_{i}")] * self.number_of_qubits,
            )
            circuit += create_layer_of_gates(self.number_of_qubits, RY,
                                             self._thetas)

        return circuit
Exemplo n.º 3
0
    def _generate_circuit(self,
                          params: Optional[np.ndarray] = None) -> Circuit:
        """Returns a parametrizable circuit represention of the ansatz.
        By convention the initial state is taken to be the |+..+> state and is
        evolved first under the cost Hamiltonian and then the mixer Hamiltonian.
        Args:
            params: parameters of the circuit.
        """
        if params is not None:
            Warning(
                "This method retuns a parametrizable circuit, params will be ignored."
            )
        circuit = Circuit()
        qubits = [
            Qubit(qubit_index) for qubit_index in range(self.number_of_qubits)
        ]
        circuit.qubits = qubits

        # Prepare initial state
        circuit += create_layer_of_gates(self.number_of_qubits, "H")

        # Add time evolution layers
        pyquil_cost_hamiltonian = qubitop_to_pyquilpauli(
            change_operator_type(self._cost_hamiltonian, QubitOperator))
        pyquil_mixer_hamiltonian = qubitop_to_pyquilpauli(
            self._mixer_hamiltonian)

        for i in range(self.number_of_layers):
            circuit += time_evolution(pyquil_cost_hamiltonian,
                                      sympy.Symbol(f"gamma_{i}"))
            circuit += time_evolution(pyquil_mixer_hamiltonian,
                                      sympy.Symbol(f"beta_{i}"))

        return circuit
Exemplo n.º 4
0
    def get_expectation_values(self, circuit, qubit_operator, **kwargs):
        """Run a circuit and measure the expectation values with respect to a
        given operator. Note: the number of bitstrings measured is derived
        from self.n_samples - if self.n_samples = None, then this will use
        self.get_exact_expectation_values

        Args:
            circuit (zquantum.core.circuit.Circuit): the circuit to prepare the state
            qubit_operator (openfermion.ops.QubitOperator): the operator to measure
        Returns:
            zquantum.core.measurement.ExpectationValues: the expectation values
                of each term in the operator
        """
        self.num_circuits_run += 1
        self.num_jobs_run += 1
        if self.n_samples == None:
            return self.get_exact_expectation_values(circuit, qubit_operator,
                                                     **kwargs)
        else:
            operator = change_operator_type(qubit_operator, IsingOperator)
            measurements = self.run_circuit_and_measure(circuit)
            expectation_values = measurements.get_expectation_values(operator)

            expectation_values = expectation_values_to_real(expectation_values)
            return expectation_values
    def _generate_circuit(self,
                          params: Optional[np.ndarray] = None) -> Circuit:
        """Returns a parametrizable circuit represention of the ansatz.
        By convention the initial state is taken to be the |+..+> state and is
        evolved first under the cost Hamiltonian and then the mixer Hamiltonian.
        Args:
            params: parameters of the circuit.
        """
        if params is not None:
            Warning(
                "This method retuns a parametrizable circuit, params will be ignored."
            )
        circuit = Circuit()

        # Prepare initial state
        circuit += create_layer_of_gates(self.number_of_qubits, H)

        # Add time evolution layers
        cost_circuit = time_evolution(
            change_operator_type(self._cost_hamiltonian, QubitOperator),
            sympy.Symbol(f"gamma"),
        )
        mixer_circuit = time_evolution(self._mixer_hamiltonian,
                                       sympy.Symbol(f"beta"))
        for i in range(self.number_of_layers):
            circuit += cost_circuit.bind(
                {sympy.Symbol(f"gamma"): sympy.Symbol(f"gamma_{i}")})
            circuit += mixer_circuit.bind(
                {sympy.Symbol(f"beta"): sympy.Symbol(f"beta_{i}")})

        return circuit
    def test_generate_circuit_with_ising_operator(self, ansatz, symbols_map,
                                                  target_unitary):
        # When
        ansatz.cost_hamiltonian = change_operator_type(ansatz.cost_hamiltonian,
                                                       IsingOperator)

        parametrized_circuit = ansatz._generate_circuit()
        evaluated_circuit = parametrized_circuit.bind(symbols_map)
        final_unitary = evaluated_circuit.to_unitary()

        # Then
        assert compare_unitary(final_unitary, target_unitary, tol=1e-10)
Exemplo n.º 7
0
def get_summed_expectation_values(
        operator: str,
        measurements: str,
        use_bessel_correction: Optional[bool] = True):
    if isinstance(operator, str):
        operator = load_qubit_operator(operator)
        operator = change_operator_type(operator, openfermion.IsingOperator)
    if isinstance(measurements, str):
        measurements = Measurements.load_from_file(measurements)
    expectation_values = measurements.get_expectation_values(
        operator, use_bessel_correction=use_bessel_correction)
    value_estimate = sum_expectation_values(expectation_values)
    save_value_estimate(value_estimate, "value-estimate.json")
    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_generate_circuit_with_ising_operator(self, ansatz,
                                                  number_of_layers, thetas):
        # When
        ansatz.cost_hamiltonian = change_operator_type(ansatz.cost_hamiltonian,
                                                       IsingOperator)

        parametrized_circuit = ansatz._generate_circuit()
        symbols_map = create_symbols_map(number_of_layers)
        target_unitary = create_target_unitary(thetas, number_of_layers)
        evaluated_circuit = parametrized_circuit.evaluate(symbols_map)
        final_unitary = evaluated_circuit.to_unitary()

        # Then
        assert compare_unitary(final_unitary, target_unitary, tol=1e-10)
def _evaluate_solution_for_hamiltonian(solution: Tuple[int],
                                       hamiltonian: QubitOperator) -> float:
    """Evaluates a solution of a hamiltonian by its calculating expectation value.

    Args:
        solution: solution to a problem as a tuple of bits
        hamiltonian: a Hamiltonian representing a problem.

    Returns:
        float: value of a solution.
    """
    hamiltonian = change_operator_type(hamiltonian, IsingOperator)
    expectation_values = expectation_values_to_real(
        Measurements([solution]).get_expectation_values(hamiltonian))
    return sum(expectation_values.values)
Exemplo n.º 11
0
    def _generate_circuit(self,
                          params: Optional[np.ndarray] = None) -> Circuit:
        """Returns a parametrizable circuit represention of the ansatz.
        Args:
            params: parameters of the circuit.
        """
        if params is not None:
            Warning(
                "This method retuns a parametrizable circuit, params will be ignored."
            )
        circuit = Circuit()
        qubits = [
            Qubit(qubit_index) for qubit_index in range(self.number_of_qubits)
        ]
        circuit.qubits = qubits

        # Prepare initial state
        circuit += create_layer_of_gates(self.number_of_qubits, "Ry",
                                         self._thetas)

        pyquil_cost_hamiltonian = qubitop_to_pyquilpauli(
            change_operator_type(self._cost_hamiltonian, QubitOperator))

        # Add time evolution layers
        for i in range(self.number_of_layers):
            circuit += time_evolution(pyquil_cost_hamiltonian,
                                      sympy.Symbol(f"gamma_{i}"))
            circuit += create_layer_of_gates(self.number_of_qubits, "Ry",
                                             -self._thetas)
            circuit += create_layer_of_gates(
                self.number_of_qubits,
                "Rz",
                [-2 * sympy.Symbol(f"beta_{i}")] * self.number_of_qubits,
            )
            circuit += create_layer_of_gates(self.number_of_qubits, "Ry",
                                             self._thetas)

        return circuit
    def _minimize(
        self,
        cost_function_factory: Callable[[IsingOperator, Ansatz], CostFunction],
        initial_params: np.ndarray,
        keep_history: bool = False,
    ) -> OptimizeResult:
        """Args:
            cost_function_factory: function that generates CostFunction objects given the provided ansatz
                and cost_hamiltonian.
            initial_params: initial parameters used for optimization
            keep_history: flag indicating whether history of cost function
                evaluations should be recorded.

        Returns:
            OptimizeResult with the added entry of:
                opt_solutions (List[Tuple[int, ...]]): The solution(s) to recursive QAOA as a list of tuples;
                    each tuple is a tuple of bits.
        """

        n_qubits = count_qubits(
            change_operator_type(self._cost_hamiltonian, QubitOperator)
        )
        qubit_map = _create_default_qubit_map(n_qubits)

        histories: Dict[str, List[HistoryEntry]] = defaultdict(list)
        histories["history"] = []

        return self._recursive_minimize(
            cost_function_factory,
            initial_params,
            keep_history,
            cost_hamiltonian=self._cost_hamiltonian,
            qubit_map=qubit_map,
            nit=0,
            nfev=0,
            histories=histories,
        )
Exemplo n.º 13
0
    def get_expectation_values_for_circuitset(self, circuitset, operator,
                                              **kwargs):
        """Run a set of circuits and measure the expectation values with respect to a
        given operator.

        Args:
            circuitset (list of zquantum.core.circuit.Circuit objects): the circuits to prepare the states
            operator (openfermion.ops.IsingOperator or openfermion.ops.QubitOperator): the operator to measure
        Returns:
            list of zquantum.core.measurement.ExpectationValues objects: a list of the expectation values of each
                term in the operator with respect to the various state preparation circuits
        """
        self.num_circuits_run += len(circuitset)
        self.num_jobs_run += 1
        operator = change_operator_type(operator, IsingOperator)
        measurements_set = self.run_circuitset_and_measure(circuitset)

        expectation_values_set = []
        for measurements in measurements_set:
            expectation_values = measurements.get_expectation_values(operator)
            expectation_values = expectation_values_to_real(expectation_values)
            expectation_values_set.append(expectation_values)

        return expectation_values_set
    def _recursive_minimize(
        self,
        cost_function_factory,
        initial_params,
        keep_history,
        cost_hamiltonian,
        qubit_map,
        nit,
        nfev,
        histories,
    ):
        """A method that recursively calls itself with each recursion reducing 1 term
        of the cost hamiltonian
        """

        # Set up QAOA circuit
        ansatz = copy(self._ansatz)

        ansatz.cost_hamiltonian = cost_hamiltonian

        cost_function = cost_function_factory(
            cost_hamiltonian,
            ansatz,
        )

        if keep_history:
            cost_function = self.recorder(cost_function)

        # Run & optimize QAOA
        opt_results = self.inner_optimizer.minimize(cost_function, initial_params)
        nit += opt_results.nit
        nfev += opt_results.nfev
        if keep_history:
            histories = extend_histories(cost_function, histories)

        # Reduce the cost hamiltonian
        (
            term_with_largest_expval,
            largest_expval,
        ) = _find_term_with_strongest_correlation(
            cost_hamiltonian,
            ansatz,
            opt_results.opt_params,
            cost_function_factory,
        )

        new_qubit_map = _update_qubit_map(
            qubit_map, term_with_largest_expval, largest_expval
        )

        reduced_cost_hamiltonian = _create_reduced_hamiltonian(
            cost_hamiltonian,
            term_with_largest_expval,
            largest_expval,
        )

        # Check new cost hamiltonian has correct amount of qubits
        assert (
            count_qubits(change_operator_type(reduced_cost_hamiltonian, QubitOperator))
            == count_qubits(change_operator_type(cost_hamiltonian, QubitOperator)) - 1
            # If we have 1 qubit, the reduced cost hamiltonian would be empty and say it has
            # 0 qubits.
            or count_qubits(
                change_operator_type(reduced_cost_hamiltonian, QubitOperator)
            )
            == 0
            and count_qubits(change_operator_type(cost_hamiltonian, QubitOperator)) == 2
            and self._n_c == 1
        )

        # Check qubit map has correct amount of qubits
        assert (
            count_qubits(change_operator_type(cost_hamiltonian, QubitOperator)) - 1
            == max([l[0] for l in new_qubit_map.values()]) + 1
        )

        if (
            count_qubits(change_operator_type(reduced_cost_hamiltonian, QubitOperator))
            > self._n_c
        ):
            # If we didn't reach threshold `n_c`, we repeat the the above with the reduced
            # cost hamiltonian.
            return self._recursive_minimize(
                cost_function_factory,
                initial_params,
                keep_history,
                cost_hamiltonian=reduced_cost_hamiltonian,
                qubit_map=new_qubit_map,
                nit=nit,
                nfev=nfev,
                histories=histories,
            )

        else:
            best_value, reduced_solutions = solve_problem_by_exhaustive_search(
                change_operator_type(reduced_cost_hamiltonian, QubitOperator)
            )

            solutions = _map_reduced_solutions_to_original_solutions(
                reduced_solutions, new_qubit_map
            )

            opt_result = optimization_result(
                opt_solutions=solutions,
                opt_value=best_value,
                opt_params=None,
                nit=nit,
                nfev=nfev,
                **histories,
            )

            return opt_result
 def number_of_qubits(self):
     """Returns number of qubits used for the ansatz circuit."""
     return count_qubits(
         change_operator_type(self._cost_hamiltonian, QubitOperator))