Skip to content

Commit

Permalink
update parallel XEB methods to support circuits instead of single gates
Browse files Browse the repository at this point in the history
  • Loading branch information
NoureldinYosri committed Jan 10, 2025
1 parent 5d317ba commit f8dc0c6
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 25 deletions.
99 changes: 82 additions & 17 deletions cirq-core/cirq/experiments/random_quantum_circuit_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,36 @@ def random_rotations_between_two_qubit_circuit(
return circuit


def _generate_library_of_2q_circuits(
n_library_circuits: int,
two_qubit_op_factory: Callable[
['cirq.Qid', 'cirq.Qid', 'np.random.RandomState'], 'cirq.OP_TREE'
] = lambda a, b, _: ops.CZPowGate()(a, b),
*,
max_cycle_depth: int = 100,
q0: 'cirq.Qid' = devices.LineQubit(0),
q1: 'cirq.Qid' = devices.LineQubit(1),
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
):
rs = value.parse_random_state(random_state)
exponents = np.linspace(0, 7 / 4, 8)
single_qubit_gates = [
ops.PhasedXZGate(x_exponent=0.5, z_exponent=z, axis_phase_exponent=a)
for a, z in itertools.product(exponents, repeat=2)
]
return [
random_rotations_between_two_qubit_circuit(
q0,
q1,
depth=max_cycle_depth,
two_qubit_op_factory=two_qubit_op_factory,
single_qubit_gates=single_qubit_gates,
seed=rs,
)
for _ in range(n_library_circuits)
]


def generate_library_of_2q_circuits(
n_library_circuits: int,
two_qubit_gate: 'cirq.Gate',
Expand Down Expand Up @@ -269,23 +299,58 @@ def generate_library_of_2q_circuits(
random_state: A random state or seed used to deterministically sample the random circuits.
tags: Tags to add to the two qubit operations.
"""
rs = value.parse_random_state(random_state)
exponents = np.linspace(0, 7 / 4, 8)
single_qubit_gates = [
ops.PhasedXZGate(x_exponent=0.5, z_exponent=z, axis_phase_exponent=a)
for a, z in itertools.product(exponents, repeat=2)
]
return [
random_rotations_between_two_qubit_circuit(
q0,
q1,
depth=max_cycle_depth,
two_qubit_op_factory=lambda a, b, _: two_qubit_gate(a, b).with_tags(*tags),
single_qubit_gates=single_qubit_gates,
seed=rs,
)
for _ in range(n_library_circuits)
]
two_qubit_op_factory = lambda a, b, _: two_qubit_gate(a, b).with_tags(*tags)
return _generate_library_of_2q_circuits(
n_library_circuits=n_library_circuits,
two_qubit_op_factory=two_qubit_op_factory,
max_cycle_depth=max_cycle_depth,
q0=q0,
q1=q1,
random_state=random_state,
)


def generate_library_of_2q_circuits_for_circuit_op(
n_library_circuits: int,
circuit_or_op: Union[circuits.AbstractCircuit, ops.Operation],
*,
max_cycle_depth: int = 100,
q0: 'cirq.Qid' = devices.LineQubit(0),
q1: 'cirq.Qid' = devices.LineQubit(1),
random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
tags: Sequence[Any] = (),
):
"""Generate a library of two-qubit Circuits.
For single-qubit gates, this uses PhasedXZGates where the axis-in-XY-plane is one
of eight eighth turns and the Z rotation angle is one of eight eighth turns. This
provides 8*8=64 total choices, each implementable with one PhasedXZGate. This is
appropriate for architectures with microwave single-qubit control.
Args:
n_library_circuits: The number of circuits to generate.
circuit_or_op: A circuit on two qubits or a two qubit operation.
max_cycle_depth: The maximum cycle_depth in the circuits to generate. If you are using XEB,
this must be greater than or equal to the maximum value in `cycle_depths`.
q0: The first qubit to use when constructing the circuits.
q1: The second qubit to use when constructing the circuits
random_state: A random state or seed used to deterministically sample the random circuits.
tags: Tags to add to the two qubit operations.
"""
if isinstance(circuit_or_op, ops.Operation):
op = circuit_or_op.with_tags(*tags)
else:
op = circuits.CircuitOperation(circuit_or_op.freeze()).with_tags(*tags)

two_qubit_op_factory = lambda a, b, _: op.with_qubits(a, b)
return _generate_library_of_2q_circuits(
n_library_circuits=n_library_circuits,
two_qubit_op_factory=two_qubit_op_factory,
max_cycle_depth=max_cycle_depth,
q0=q0,
q1=q1,
random_state=random_state,
)


def _get_active_pairs(graph: nx.Graph, grid_layer: GridInteractionLayer):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
get_random_combinations_for_pairs,
get_random_combinations_for_layer_circuit,
get_grid_interaction_layer_circuit,
generate_library_of_2q_circuits_for_circuit_op,
)

SINGLE_QUBIT_LAYER = Dict[cirq.GridQubit, Optional[cirq.Gate]]
Expand Down Expand Up @@ -86,6 +87,40 @@ def test_generate_library_of_2q_circuits():
assert m2.operations[0].gate == cirq.CNOT


def test_generate_library_of_2q_circuits_for_circuit_op_with_operation():
circuits = generate_library_of_2q_circuits_for_circuit_op(
n_library_circuits=5,
circuit_or_op=cirq.CNOT(cirq.q(0), cirq.q(1)),
max_cycle_depth=13,
random_state=9,
)
assert len(circuits) == 5
for circuit in circuits:
assert len(circuit.all_qubits()) == 2
assert sorted(circuit.all_qubits()) == cirq.LineQubit.range(2)
for m1, m2 in zip(circuit.moments[::2], circuit.moments[1::2]):
assert len(m1.operations) == 2 # single qubit layer
assert len(m2.operations) == 1
assert m2.operations[0].gate == cirq.CNOT


def test_generate_library_of_2q_circuits_for_circuit_op_with_circuit():
circuits = generate_library_of_2q_circuits_for_circuit_op(
n_library_circuits=5,
circuit_or_op=cirq.Circuit(cirq.CNOT(cirq.q(0), cirq.q(1))),
max_cycle_depth=13,
random_state=9,
)
assert len(circuits) == 5
for circuit in circuits:
assert len(circuit.all_qubits()) == 2
assert sorted(circuit.all_qubits()) == cirq.LineQubit.range(2)
for m1, m2 in zip(circuit.moments[::2], circuit.moments[1::2]):
assert len(m1.operations) == 2 # single qubit layer
assert len(m2.operations) == 1
assert isinstance(m2.operations[0], cirq.CircuitOperation)


def test_generate_library_of_2q_circuits_custom_qubits():
circuits = generate_library_of_2q_circuits(
n_library_circuits=5,
Expand Down
36 changes: 28 additions & 8 deletions cirq-core/cirq/experiments/two_qubit_xeb.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

"""Provides functions for running and analyzing two-qubit XEB experiments."""
from typing import Sequence, TYPE_CHECKING, Optional, Tuple, Dict, cast, Mapping, Any
from typing import Sequence, TYPE_CHECKING, Optional, Tuple, Dict, cast, Mapping, Any, Union

from dataclasses import dataclass
from types import MappingProxyType
Expand Down Expand Up @@ -403,6 +403,7 @@ def parallel_xeb_workflow(
pool: Optional['multiprocessing.pool.Pool'] = None,
batch_size: int = 9,
tags: Sequence[Any] = (),
entangling_circuit_or_op: Optional[Union['cirq.AbstractCircuit', 'cirq.Operation']] = None,
**plot_kwargs,
) -> Tuple[pd.DataFrame, Sequence['cirq.Circuit'], pd.DataFrame]:
"""A utility method that runs the full XEB workflow.
Expand All @@ -424,6 +425,8 @@ def parallel_xeb_workflow(
environments. The number of (circuit, cycle_depth) tasks to be run in each batch
is given by this number.
tags: Tags to add to two qubit operations.
entangling_circuit_or_op: Optional operation or circuit for XEB.
When provided it overrides `entangling_gate`.
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
Returns:
Expand All @@ -447,13 +450,22 @@ def parallel_xeb_workflow(
ax.set_title('device layout')
ax.plot(**plot_kwargs)

circuit_library = rqcg.generate_library_of_2q_circuits(
n_library_circuits=n_circuits,
two_qubit_gate=entangling_gate,
random_state=rs,
max_cycle_depth=max(cycle_depths),
tags=tags,
)
if entangling_circuit_or_op is not None:
circuit_library = rqcg.generate_library_of_2q_circuits_for_circuit_op(
n_library_circuits=n_circuits,
circuit_or_op=entangling_circuit_or_op,
random_state=rs,
max_cycle_depth=max(cycle_depths),
tags=tags,
)
else:
circuit_library = rqcg.generate_library_of_2q_circuits(
n_library_circuits=n_circuits,
two_qubit_gate=entangling_gate,
random_state=rs,
max_cycle_depth=max(cycle_depths),
tags=tags,
)

combs_by_layer = rqcg.get_random_combinations_for_device(
n_library_circuits=len(circuit_library),
Expand Down Expand Up @@ -492,6 +504,7 @@ def parallel_two_qubit_xeb(
pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None,
batch_size: int = 9,
tags: Sequence[Any] = (),
entangling_circuit_or_op: Optional[Union['cirq.AbstractCircuit', 'cirq.Operation']] = None,
**plot_kwargs,
) -> TwoQubitXEBResult:
"""A convenience method that runs the full XEB workflow.
Expand All @@ -512,6 +525,8 @@ def parallel_two_qubit_xeb(
environments. The number of (circuit, cycle_depth) tasks to be run in each batch
is given by this number.
tags: Tags to add to two qubit operations.
entangling_circuit_or_op: Optional operation or circuit for XEB.
When provided it overrides `entangling_gate`.
**plot_kwargs: Arguments to be passed to 'plt.Axes.plot'.
Returns:
A TwoQubitXEBResult object representing the results of the experiment.
Expand All @@ -531,6 +546,7 @@ def parallel_two_qubit_xeb(
ax=ax,
batch_size=batch_size,
tags=tags,
entangling_circuit_or_op=entangling_circuit_or_op,
**plot_kwargs,
)
return TwoQubitXEBResult(fit_exponential_decays(fids))
Expand All @@ -551,6 +567,7 @@ def run_rb_and_xeb(
pairs: Optional[Sequence[tuple['cirq.GridQubit', 'cirq.GridQubit']]] = None,
batch_size: int = 9,
tags: Sequence[Any] = (),
entangling_circuit_or_op: Optional[Union['cirq.AbstractCircuit', 'cirq.Operation']] = None,
) -> InferredXEBResult:
"""A convenience method that runs both RB and XEB workflows.
Expand All @@ -569,6 +586,8 @@ def run_rb_and_xeb(
environments. The number of (circuit, cycle_depth) tasks to be run in each batch
is given by this number.
tags: Tags to add to two qubit operations.
entangling_circuit_or_op: Optional operation or circuit for XEB.
When provided it overrides `entangling_gate`.
Returns:
An InferredXEBResult object representing the results of the experiment.
Expand Down Expand Up @@ -599,6 +618,7 @@ def run_rb_and_xeb(
random_state=random_state,
batch_size=batch_size,
tags=tags,
entangling_circuit_or_op=entangling_circuit_or_op,
)

return InferredXEBResult(rb, xeb)
51 changes: 51 additions & 0 deletions cirq-core/cirq/experiments/two_qubit_xeb_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,57 @@ def test_run_rb_and_xeb(
)


@pytest.mark.parametrize(
'sampler,qubits,pairs',
[
(
cirq.DensityMatrixSimulator(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
cirq.GridQubit.rect(3, 2, 4, 3),
None,
),
(
cirq.DensityMatrixSimulator(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
None,
[
(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)),
(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)),
],
),
(
DensityMatrixSimulatorWithProcessor(
seed=0, noise=cirq.ConstantQubitNoiseModel(cirq.amplitude_damp(0.1))
),
None,
None,
),
],
)
def test_run_rb_and_xeb_with_circuit(
sampler: cirq.Sampler,
qubits: Optional[Sequence[cirq.GridQubit]],
pairs: Optional[Sequence[tuple[cirq.GridQubit, cirq.GridQubit]]],
):
res = cirq.experiments.run_rb_and_xeb(
sampler=sampler,
qubits=qubits,
pairs=pairs,
repetitions=100,
num_clifford_range=tuple(np.arange(3, 10, 1)),
xeb_combinations=1,
num_circuits=1,
depths_xeb=(3, 4, 5),
random_state=0,
entangling_circuit_or_op=cirq.Circuit(cirq.CZ(cirq.q(0), cirq.q(1))),
)
np.testing.assert_allclose(
[res.xeb_result.xeb_error(*pair) for pair in res.all_qubit_pairs], 0.1, atol=1e-1
)


def test_run_rb_and_xeb_without_processor_fails():
sampler = (
cirq.DensityMatrixSimulator(
Expand Down

0 comments on commit f8dc0c6

Please sign in to comment.