Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transpile ansatz and pauli-lists separately in VQE #6454

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f4aec15
transpile only ansatz with default
hhorii May 24, 2021
b065320
Merge remote-tracking branch 'upstream/main' into transpile_only_ansatz
hhorii Jun 4, 2021
e02a061
split-transpile with considration of layout in vqe
hhorii Jun 7, 2021
6106e83
remove common_circuit as params
ikkoham Jun 9, 2021
f00d82d
correct type information in CircuitSampler
hhorii Jun 10, 2021
8948a8f
refactor and lint
ikkoham Jun 10, 2021
bab18b5
remove unused qubits from layout when split-transpilation is enable i…
hhorii Jun 11, 2021
ce72422
move transpile from sample_circuits to convert
ikkoham Jun 16, 2021
a698c92
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 16, 2021
ee73ba9
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 16, 2021
d3b80d1
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 17, 2021
eba2bd4
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 17, 2021
16cf929
Merge branch 'transpile_only_ansatz' of https://github.com/hhorii/qis…
ikkoham Jun 17, 2021
7b13a91
fix lint
ikkoham Jun 17, 2021
c65902a
remove unnecessary params
ikkoham Jun 18, 2021
d25c5c7
remove param stateless vqe doesn't use
ikkoham Jun 18, 2021
853d64d
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 18, 2021
3a160b3
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 21, 2021
535e0ee
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 22, 2021
951fd54
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 24, 2021
5e002a0
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 28, 2021
30b34c6
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 28, 2021
7418ac2
final layout hack
ikkoham Jun 29, 2021
63dc784
Merge branch 'main' into transpile_only_ansatz
ikkoham Jun 29, 2021
410538e
fix black
ikkoham Jun 29, 2021
6990e36
add releasenote
ikkoham Jun 29, 2021
08b51c8
fix the bug
ikkoham Jun 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions qiskit/algorithms/minimum_eigen_solvers/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,41 @@
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""The Variational Quantum Eigensolver algorithm.

See https://arxiv.org/abs/1304.3061
"""

from typing import Optional, List, Callable, Union, Dict, Tuple
import logging
import warnings
from time import time
from typing import Callable, Dict, List, Optional, Tuple, Union

import numpy as np

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.circuit.library import RealAmplitudes
from qiskit.providers import BaseBackend
from qiskit.providers import Backend
from qiskit.opflow import (
OperatorBase,
CircuitSampler,
CircuitStateFn,
ExpectationBase,
ExpectationFactory,
StateFn,
CircuitStateFn,
ListOp,
I,
CircuitSampler,
ListOp,
OperatorBase,
StateFn,
)
from qiskit.opflow.gradients import GradientBase
from qiskit.utils.validation import validate_min
from qiskit.providers import Backend, BaseBackend
from qiskit.utils import QuantumInstance, algorithm_globals
from qiskit.utils.backend_utils import is_aer_provider
from qiskit.utils.deprecation import deprecate_function
from qiskit.utils import QuantumInstance, algorithm_globals
from ..optimizers import Optimizer, SLSQP
from qiskit.utils.validation import validate_min

from ..exceptions import AlgorithmError
from ..optimizers import SLSQP, Optimizer
from ..variational_algorithm import VariationalAlgorithm, VariationalResult
from .minimum_eigen_solver import MinimumEigensolver, MinimumEigensolverResult
from ..exceptions import AlgorithmError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -97,6 +97,7 @@ def __init__(
max_evals_grouped: int = 1,
callback: Optional[Callable[[int, np.ndarray, float, float], None]] = None,
quantum_instance: Optional[Union[QuantumInstance, BaseBackend, Backend]] = None,
split_transpile: bool = True,
sort_parameters_by_name: Optional[bool] = None,
) -> None:
"""
Expand Down Expand Up @@ -133,6 +134,8 @@ def __init__(
These are: the evaluation count, the optimizer parameters for the
ansatz, the evaluated mean and the evaluated standard deviation.`
quantum_instance: Quantum Instance or Backend
split_transpile: ``True`` allows transpiling circuits for ansatz and expectation
separately.
sort_parameters_by_name: Deprecated. If True, the initial point is bound to the ansatz
parameters strictly sorted by name instead of the default circuit order. That means
that the ansatz parameters are e.g. sorted as ``x[0] x[1] x[10] x[2] ...`` instead
Expand Down Expand Up @@ -166,6 +169,7 @@ def __init__(
self._circuit_sampler = None # type: Optional[CircuitSampler]
self._expectation = expectation
self._include_custom = include_custom
self._split_transpile = split_transpile

# set ansatz -- still supporting pre 0.18.0 sorting
self._sort_parameters_by_name = sort_parameters_by_name
Expand Down Expand Up @@ -235,7 +239,9 @@ def quantum_instance(
quantum_instance = QuantumInstance(quantum_instance)
self._quantum_instance = quantum_instance
self._circuit_sampler = CircuitSampler(
quantum_instance, param_qobj=is_aer_provider(quantum_instance.backend)
quantum_instance,
param_qobj=is_aer_provider(quantum_instance.backend),
split_transpile=self._split_transpile,
)

@property
Expand Down Expand Up @@ -367,7 +373,7 @@ def construct_expectation(

observable_meas = expectation.convert(StateFn(operator, is_measurement=True))
ansatz_circuit_op = CircuitStateFn(wave_function)
expect_op = observable_meas.compose(ansatz_circuit_op).reduce()
expect_op = observable_meas.compose(ansatz_circuit_op)

if return_expectation:
return expect_op, expectation
Expand Down Expand Up @@ -416,7 +422,7 @@ def _eval_aux_ops(
threshold: float = 1e-12,
) -> np.ndarray:
# Create new CircuitSampler to avoid breaking existing one's caches.
sampler = CircuitSampler(self.quantum_instance)
sampler = CircuitSampler(self.quantum_instance, split_transpile=self._split_transpile)

aux_op_meas = expectation.convert(StateFn(ListOp(aux_operators), is_measurement=True))
aux_op_expect = aux_op_meas.compose(CircuitStateFn(self.ansatz.bind_parameters(parameters)))
Expand Down
116 changes: 102 additions & 14 deletions qiskit/opflow/converters/circuit_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
from qiskit.circuit import Parameter, ParameterExpression, QuantumCircuit
from qiskit.opflow.converters.converter_base import ConverterBase
from qiskit.opflow.exceptions import OpflowError
from qiskit.opflow.list_ops.composed_op import ComposedOp
from qiskit.opflow.list_ops.list_op import ListOp
from qiskit.opflow.operator_base import OperatorBase
from qiskit.opflow.state_fns.circuit_state_fn import CircuitStateFn
from qiskit.opflow.state_fns.dict_state_fn import DictStateFn
from qiskit.opflow.state_fns.state_fn import StateFn
from qiskit.providers import Backend, BaseBackend
from qiskit.transpiler.layout import Layout
from qiskit.utils.backend_utils import is_aer_provider, is_statevector_backend
from qiskit.utils.quantum_instance import QuantumInstance

Expand Down Expand Up @@ -58,6 +60,7 @@ def __init__(
param_qobj: bool = False,
attach_results: bool = False,
caching: str = "last",
split_transpile: bool = True,
) -> None:
"""
Args:
Expand All @@ -72,6 +75,8 @@ def __init__(
the circuits.
caching: The caching strategy. Can be `'last'` (default) to store the last operator
that was converted, set to `'all'` to cache all processed operators.
split_transpile: ``True`` allows transpiling circuits for ansatz and expectation
separately.

Raises:
ValueError: Set statevector or param_qobj True when not supported by backend.
Expand Down Expand Up @@ -99,6 +104,7 @@ def __init__(
self._transpiled_circ_cache: Optional[List[Any]] = None
self._transpiled_circ_templates: Optional[List[Any]] = None
self._transpile_before_bind = True
self._split_transpile = split_transpile

def _check_quantum_instance_and_modes_consistent(self) -> None:
"""Checks whether the statevector and param_qobj settings are compatible with the
Expand Down Expand Up @@ -172,20 +178,101 @@ def convert(
if self._caching == "last":
self.clear_cache()

# convert to circuit and reduce
operator_dicts_replaced = operator.to_circuit_op()
self._reduced_op_cache = operator_dicts_replaced.reduce()

# extract circuits
self._circuit_ops_cache = {}
self._extract_circuitstatefns(self._reduced_op_cache)
if not self._circuit_ops_cache:
raise OpflowError(
"Circuits are empty. "
"Check that the operator is an instance of CircuitStateFn or its ListOp."
)
self._transpiled_circ_cache = None
self._transpile_before_bind = True
if (
self._split_transpile
and isinstance(operator, ComposedOp)
and len(operator) == 2
and isinstance(operator[1], CircuitStateFn)
):
self._reduced_op_cache = operator.to_circuit_op().reduce()
self._circuit_ops_cache = {}
self._extract_circuitstatefns(self._reduced_op_cache)
if not self._circuit_ops_cache:
raise OpflowError(
"Circuits are empty. "
"Check that the operator is an instance of CircuitStateFn or its ListOp."
)
circuits = [
op_c.to_circuit(meas=not self._statevector)
for op_c in list(self._circuit_ops_cache.values())
]

common_circuit = operator[1].to_circuit_op().reduce().to_circuit().copy()
len_common_circuit = len(common_circuit)
try:
# 1. transpile a common circuit
common_circuit.measure_all()
transpiled_common_circuit = self.quantum_instance.transpile(common_circuit)[0]
layout = Layout(
{
i: qr[0]
for i, (_, qr, _) in enumerate(
transpiled_common_circuit[-common_circuit.num_qubits :]
)
}
)
transpiled_common_circuit.remove_final_measurements()

# 2. transpile diff circuits
diff_circuits = []
for circuit in circuits:
diff_circuit = circuit.copy()
del diff_circuit.data[0:len_common_circuit]
diff_circuits.append(diff_circuit)

used_qubits = set(
used_qubit
for diff_circuit in diff_circuits
for used_qubit in diff_circuit.qubits
)
for q in list(layout.get_virtual_bits().keys()):
if q not in used_qubits:
del layout[q]
orig_layout = self.quantum_instance.compile_config["initial_layout"]

self.quantum_instance.compile_config["initial_layout"] = layout
diff_circuits = self.quantum_instance.transpile(diff_circuits)
self.quantum_instance.compile_config["initial_layout"] = orig_layout

# 3. combine
transpiled_circuits = []
for diff_circuit in diff_circuits:
transpiled_circuit = transpiled_common_circuit.copy()
for creg in diff_circuit.cregs:
if creg not in transpiled_circuit.cregs:
transpiled_circuit.add_register(creg)
for inst, qargs, cargs in diff_circuit.data:
transpiled_circuit.append(inst, qargs, cargs)
transpiled_circuits.append(transpiled_circuit)

# 4. set transpiled circuit cache
self._transpiled_circ_cache = transpiled_circuits
self._transpile_before_bind = True

except QiskitError:
logger.debug(
r"CircuitSampler failed to transpile circuits with unbound "
r"parameters. Attempting to transpile only when circuits are bound "
r"now, but this can hurt performance due to repeated transpilation."
)
self._transpile_before_bind = False
self._transpiled_circ_cache = circuits
else:
# convert to circuit and reduce
operator_dicts_replaced = operator.to_circuit_op()
self._reduced_op_cache = operator_dicts_replaced.reduce()

# extract circuits
self._circuit_ops_cache = {}
self._extract_circuitstatefns(self._reduced_op_cache)
if not self._circuit_ops_cache:
raise OpflowError(
"Circuits are empty. "
"Check that the operator is an instance of CircuitStateFn or its ListOp."
)

self._transpiled_circ_cache = None
self._transpile_before_bind = True
else:
# load the cached circuits
self._reduced_op_cache = self._cached_ops[op_id].reduced_op_cache
Expand Down Expand Up @@ -297,6 +384,7 @@ def sample_circuits(

try:
self._transpiled_circ_cache = self.quantum_instance.transpile(circuits)

except QiskitError:
logger.debug(
r"CircuitSampler failed to transpile circuits with unbound "
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ def _set_type_checked_item(self, virtual, physical):

def __delitem__(self, key):
if isinstance(key, int):
del self._p2v[key]
del self._v2p[self._p2v[key]]
del self._p2v[key]
elif isinstance(key, Qubit):
del self._v2p[key]
del self._p2v[self._v2p[key]]
del self._v2p[key]
Comment on lines -126 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should probably not be touched

Copy link
Contributor

@ikkoham ikkoham Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? This is clearly a bug, @hhorii found. This is necessary to execute our code. Should we separate the PR?

else:
raise LayoutError(
"The key to remove should be of the form"
Expand Down
6 changes: 6 additions & 0 deletions releasenotes/notes/transpile-separately-f4fee95cd8868c43.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
features:
- |
Add the `split_transpile` option to :class:`~qiskit.opflow.CircuitSampler`.
This reduce the transpilation time because a parameterized circuit of ansatz is transpiled only
once.
12 changes: 3 additions & 9 deletions test/python/opflow/test_pauli_expectation.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,19 +198,13 @@ def test_grouped_pauli_expectation(self):
)
wf = CX @ (H ^ I) @ Zero
expect_op = PauliExpectation(group_paulis=False).convert(~StateFn(two_qubit_H2) @ wf)
self.sampler._extract_circuitstatefns(expect_op)
self.sampler.convert(expect_op)
num_circuits_ungrouped = len(self.sampler._circuit_ops_cache)
self.assertEqual(num_circuits_ungrouped, 5)

expect_op_grouped = PauliExpectation(group_paulis=True).convert(~StateFn(two_qubit_H2) @ wf)
q_instance = QuantumInstance(
BasicAer.get_backend("statevector_simulator"),
seed_simulator=self.seed,
seed_transpiler=self.seed,
)
sampler = CircuitSampler(q_instance)
sampler._extract_circuitstatefns(expect_op_grouped)
num_circuits_grouped = len(sampler._circuit_ops_cache)
self.sampler.convert(expect_op_grouped)
num_circuits_grouped = len(self.sampler._circuit_ops_cache)
self.assertEqual(num_circuits_grouped, 2)

@unittest.skip(reason="IBMQ testing not available in general.")
Expand Down
58 changes: 53 additions & 5 deletions test/python/opflow/test_state_op_meas_evals.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,29 @@

import unittest
from test.python.opflow import QiskitOpflowTestCase
from ddt import ddt, data
import numpy

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.utils import QuantumInstance
from qiskit.opflow import StateFn, Zero, One, H, X, I, Z, Plus, Minus, CircuitSampler, ListOp
import numpy
from ddt import data, ddt

from qiskit import BasicAer
from qiskit.circuit import Parameter, QuantumCircuit
from qiskit.circuit.library import RealAmplitudes
from qiskit.opflow import (
CircuitSampler,
H,
I,
ListOp,
Minus,
One,
PauliExpectation,
Plus,
StateFn,
X,
Z,
Zero,
)
from qiskit.opflow.exceptions import OpflowError
from qiskit.utils import QuantumInstance


@ddt
Expand Down Expand Up @@ -217,6 +233,38 @@ def test_quantum_instance_with_backend_shots(self):
res = sampler.convert(~Plus @ Plus).eval()
self.assertAlmostEqual(res, 1 + 0j, places=2)

def test_split_transpile(self):
"""Test split transpile for CircuitSampler"""
ansatz = StateFn(RealAmplitudes(num_qubits=3, reps=1, entanglement="linear"))
observable = StateFn(X ^ I ^ I, is_measurement=True)
expectation = PauliExpectation(False).convert(observable @ ansatz)
param_bindings = dict(zip(ansatz.parameters, [1] * len(ansatz.parameters)))
sampled = CircuitSampler(
QuantumInstance(
BasicAer.get_backend("qasm_simulator"),
seed_simulator=15,
seed_transpiler=15,
initial_layout=[0, 1, 2],
coupling_map=[[0, 1], [0, 2]],
),
attach_results=True,
split_transpile=False,
).convert(expectation, param_bindings)
counts_expected = sampled[1].execution_results["counts"]
sampled = CircuitSampler(
QuantumInstance(
BasicAer.get_backend("qasm_simulator"),
seed_simulator=15,
seed_transpiler=15,
initial_layout=[0, 1, 2],
coupling_map=[[0, 1], [0, 2]],
),
attach_results=True,
split_transpile=True,
).convert(expectation, param_bindings)
counts_target = sampled[1].execution_results["counts"]
self.assertDictEqual(counts_target, counts_expected)


if __name__ == "__main__":
unittest.main()