unitaryfund / mitiq

Mitiq is an open source toolkit for implementing error mitigation techniques on most current intermediate-scale quantum computers.
https://mitiq.readthedocs.io
GNU General Public License v3.0
357 stars 157 forks source link

[Question] Using mitiq in QAOA/VQE-Class from Qiskit #752

Closed lasys closed 2 years ago

lasys commented 3 years ago

Hello,

i would like to integrate mitiq into the QAOA-Class from Qiskit.

My idea was to change the lines in the VQE-Class as follows:

...

self._eval_count = 0
energy_evaluation, expectation = self.get_energy_evaluation(
    operator, return_expectation=True
)

# insert mitiq
# Choose a noise scaling method
scaling_method = mitiq.zne.scaling.fold_global

# Initialize an inference method (i.e. a Mitiq Factory)
inference_factory = mitiq.zne.inference.LinearFactory(scale_factors=[1.0, 3.0])

noisy_executor = partial(energy_evaluation)
mitigated_executor = mitigate_executor(noisy_executor, inference_factory, scaling_method)
mitigated_executor = mitigated_executor(self.ansatz)

start_time = time()
opt_params, opt_value, nfev = self.optimizer.optimize(
    num_vars=len(initial_point),
    #objective_function=energy_evaluation,
    # use wrapped energy_evaluation with mitiq
    objective_function=mitigated_executor,
    gradient_function=gradient,
    variable_bounds=bounds,
    initial_point=initial_point,
)
eval_time = time() - start_time

...

When i run the code with:

qaoa = MY_QAOA(optimizer=spsa, quantum_instance=qi_noise_model_ibmq, reps=2)
algorithm = MinimumEigenOptimizer(qaoa)
result = algorithm.solve(problem)

Unfortunately i get this error:


---------------------------------------------------------------------------
QasmError                                 Traceback (most recent call last)
/usr/local/lib/python3.7/dist-packages/mitiq/interface/conversions.py in convert_to_mitiq(circuit)
     83     try:
---> 84         mitiq_circuit = conversion_function(circuit)
     85     except Exception:

13 frames
QasmError: 'Cannot represent circuits with unbound parameters in OpenQASM 2.'

During handling of the above exception, another exception occurred:

CircuitConversionError                    Traceback (most recent call last)
/usr/local/lib/python3.7/dist-packages/mitiq/interface/conversions.py in convert_to_mitiq(circuit)
     85     except Exception:
     86         raise CircuitConversionError(
---> 87             "Circuit could not be converted to an internal Mitiq circuit. "
     88             "This may be because the circuit contains custom gates or Pragmas "
     89             "(pyQuil). If you think this is a bug, you can open an issue at "

CircuitConversionError: Circuit could not be converted to an internal Mitiq circuit. This may be because the circuit contains custom gates or Pragmas (pyQuil). If you think this is a bug, you can open an issue at https://github.com/unitaryfund/mitiq.

Does anyone have a suggestion on how to fix this or have another suggestion on how to integrate mitiq? Thanks a lot for your help!

EDIT:

Here ist the code: Colab

github-actions[bot] commented 3 years ago

Hello @lasys, thank you for your interest in Mitiq! If this is a bug report, please provide screenshots and/or minimum viable code to reproduce your issue, so we can do our best to help get it fixed. If you have any questions in the meantime, you can also ask us on the Unitary Fund Discord.

andreamari commented 3 years ago

Hello @lasys,

The error is related to a conversion problem from Qiskit to Cirq (which is the internal representation used by Mitiq). Probably the problem is due to parameterized (symbolic) gates.

However I think the main problem is another one. The object that you define as noisy_executor must be a function which input a circuit and returns an expectation value in order to be compatible with Mitiq. In your case instead it is indirectly defined by using energy_evaluation which is a function from the set of variational parameters to an expectation value (the input circuit is not an argument). I think this is the main problem. I am not sure what could be a simple solution, but I would probably try to add Mitiq somewhere in self.get_energy_evaluation() instead of changing self.compute_minimum_eigenvalue().

Good luck!

rmlarose commented 3 years ago

The only relevant portion from the colab is below.

energy_evaluation, expectation = self.get_energy_evaluation(
    operator, return_expectation=True
)

# insert mitiq
# Choose a noise scaling method
scaling_method = mitiq.zne.scaling.fold_global

# Initialize an inference method (i.e. a Mitiq Factory)
inference_factory = mitiq.zne.inference.LinearFactory(scale_factors=[1.0, 3.0])

noisy_executor = partial(energy_evaluation)
mitigated_executor = mitigate_executor(noisy_executor, inference_factory, scaling_method)
mitigated_executor = mitigated_executor(self.ansatz)

Problems:

There's a line in energy_evaluation which says:

sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings)

To use Mitiq in this example, you probably need to apply mitigate_executor to something in self._circuit_sampler.

My main takeaway from this is we should print out the circuit in CircuitConversionErrors.

rmlarose commented 3 years ago

Totally missed @andreamari's comment somehow 🙃 at least we had more-or-less the same conclusions.

github-actions[bot] commented 3 years ago

This issue had no activity for 2 months, and will be closed in one week unless there is new activity. Cheers!

lasys commented 3 years ago

Sorry for the late reply. I have found a solution and implemented it as follows.

def get_energy_evaluation(
        self,
        operator: OperatorBase,
        return_expectation: bool = False,
    ) -> Callable[[np.ndarray], Union[float, List[float]]]:
        """Returns a function handle to evaluates the energy at given parameters for the ansatz.

        This is the objective function to be passed to the optimizer that is used for evaluation.

        Args:
            operator: The operator whose energy to evaluate.
            return_expectation: If True, return the ``ExpectationBase`` expectation converter used
                in the construction of the expectation value. Useful e.g. to evaluate other
                operators with the same expectation value converter.

        Returns:
            Energy of the hamiltonian of each parameter, and, optionally, the expectation
            converter.

        Raises:
            RuntimeError: If the circuit is not parameterized (i.e. has 0 free parameters).

        """
        num_parameters = self.ansatz.num_parameters
        if num_parameters == 0:
            raise RuntimeError("The ansatz must be parameterized, but has 0 free parameters.")

        expect_op, expectation = self.construct_expectation(
            self._ansatz_params, operator, return_expectation=True
        )

        # create circuit for ZNE 
        param_dict = dict(zip(self._ansatz_params, self._initial_point))  # type: Dict
        circuit = self.ansatz.assign_parameters(param_dict)

        def energy_evaluation(parameters):

            if circuit != None:

                # Initialize an inference method (i.e. a Mitiq Factory)
                scaling_method = mitiq.zne.scaling.fold_global
                inference_factory = mitiq.zne.inference.LinearFactory(scale_factors=[1.0, 3.0])

                # See mitiq maxcut-example
                noisy_executor = partial(real_energy_evaluation, parameters=parameters)
                mitigated_executor = mitigate_executor(noisy_executor, inference_factory, scaling_method)
                mitigated_executor = mitigated_executor(circuit)

                return mitigated_executor

            print("Don't use zne")
            return real_energy_evaluation(None, parameters)

        def real_energy_evaluation(cc, parameters):
            parameter_sets = np.reshape(parameters, (-1, num_parameters))
            # Create dict associating each parameter with the lists of parameterization values for it
            param_bindings = dict(zip(self._ansatz_params, parameter_sets.transpose().tolist()))       
            start_time = time()
            sampled_expect_op = self._circuit_sampler.convert(expect_op, params=param_bindings)
            means = np.real(sampled_expect_op.eval())
            if self._callback is not None:
                variance = np.real(expectation.compute_variance(sampled_expect_op))
                estimator_error = np.sqrt(variance / self.quantum_instance.run_config.shots)
                for i, param_set in enumerate(parameter_sets):
                    self._eval_count += 1
                    self._callback(self._eval_count, param_set, means[i], estimator_error[i])
            else:
                self._eval_count += len(means)

            end_time = time()
            logger.info(
                "Energy evaluation returned %s - %.5f (ms), eval count: %s",
                means,
                (end_time - start_time) * 1000,
                self._eval_count,
            )

            return means if len(means) > 1 else means[0]

        if return_expectation:
            return energy_evaluation, expectation

        return energy_evaluation

I also added Clifford Data Regression in the same function energy_evaluation in another project. If you have nothing to add, the issue can be closed. Thank you very much for your help and work!

rmlarose commented 3 years ago

Thanks @lasys. I think there could be value in adding a "Mitiq with variational ansatze in Qiskit" example to the documentation. If you'd be interested in writing this using your example as a foundation, please let me know! I'll close this issue and open a new issue for the documentation.