Skip to content

Commit

Permalink
Merge branch 'main' into ryanhill1-patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
pavoljuhas authored Jun 11, 2024
2 parents 63d3b6a + f87fd4c commit f55d42e
Show file tree
Hide file tree
Showing 21 changed files with 847 additions and 396 deletions.
24 changes: 13 additions & 11 deletions cirq-core/cirq/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,29 +145,31 @@ class AbstractCircuit(abc.ABC):
"""

@classmethod
def from_moments(cls: Type[CIRCUIT_TYPE], *moments: 'cirq.OP_TREE') -> CIRCUIT_TYPE:
def from_moments(cls: Type[CIRCUIT_TYPE], *moments: Optional['cirq.OP_TREE']) -> CIRCUIT_TYPE:
"""Create a circuit from moment op trees.
Args:
*moments: Op tree for each moment. If an op tree is a moment, it
will be included directly in the new circuit. If an op tree is
a circuit, it will be frozen, wrapped in a CircuitOperation, and
included in its own moment in the new circuit. Otherwise, the
op tree will be passed to `cirq.Moment` to create a new moment
which is then included in the new circuit. Note that in the
latter case we have the normal restriction that operations in a
moment must be applied to disjoint sets of qubits.
*moments: Op trees for each moment, which can be one of the following:
- Moment: will be included directly in the new circuit.
- AbstractCircuit: will be frozen, wrapped in a CircuitOperation,
and included in its own moment in the new circuit.
- None: will be skipped and omitted from the circuit. This can be
used to include or skip a moment based on a conditional, for example.
- Other OP_TREE: will be passed to `cirq.Moment` to create a new moment
which is then included in the new circuit. Note that in this
case we have the normal restriction that operations in a
moment must be applied to disjoint sets of qubits.
"""
return cls._from_moments(cls._make_moments(moments))

@staticmethod
def _make_moments(moments: Iterable['cirq.OP_TREE']) -> Iterator['cirq.Moment']:
def _make_moments(moments: Iterable[Optional['cirq.OP_TREE']]) -> Iterator['cirq.Moment']:
for m in moments:
if isinstance(m, Moment):
yield m
elif isinstance(m, AbstractCircuit):
yield Moment(m.freeze().to_op())
else:
elif m is not None:
yield Moment(m)

@classmethod
Expand Down
1 change: 1 addition & 0 deletions cirq-core/cirq/circuits/circuit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def test_from_moments():
[cirq.X(c)],
[],
cirq.Z(d),
None,
[cirq.measure(a, b, key='ab'), cirq.measure(c, d, key='cd')],
)
assert circuit == cirq.Circuit(
Expand Down
22 changes: 9 additions & 13 deletions cirq-core/cirq/experiments/xeb_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Estimation of fidelity associated with experimental circuit executions."""
import concurrent
import os
import time
import uuid
from concurrent.futures.thread import ThreadPoolExecutor
from dataclasses import dataclass
from typing import (
Callable,
Expand Down Expand Up @@ -276,18 +274,16 @@ def _execute_sample_2q_xeb_tasks_in_batches(
run_batch = _SampleInBatches(
sampler=sampler, repetitions=repetitions, combinations_by_layer=combinations_by_layer
)
with ThreadPoolExecutor(max_workers=2) as pool:
futures = [pool.submit(run_batch, task_batch) for task_batch in batched_tasks]

records = []
with progress_bar(total=len(batched_tasks) * batch_size) as progress:
for future in concurrent.futures.as_completed(futures):
new_records = future.result()
if dataset_directory is not None:
os.makedirs(f'{dataset_directory}', exist_ok=True)
protocols.to_json(new_records, f'{dataset_directory}/xeb.{uuid.uuid4()}.json')
records.extend(new_records)
progress.update(batch_size)
records = []
with progress_bar(total=len(batched_tasks) * batch_size) as progress:
for task in batched_tasks:
new_records = run_batch(task)
if dataset_directory is not None:
os.makedirs(f'{dataset_directory}', exist_ok=True)
protocols.to_json(new_records, f'{dataset_directory}/xeb.{uuid.uuid4()}.json')
records.extend(new_records)
progress.update(batch_size)
return records


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials
from google.oauth2 import service_account # type: ignore
from google.oauth2 import service_account

try:
OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault]
Expand Down Expand Up @@ -172,7 +172,7 @@ def transport(self) -> QuantumEngineServiceTransport:
def __init__(
self,
*,
credentials: ga_credentials.Credentials = None,
credentials: Optional[ga_credentials.Credentials] = None,
transport: Union[str, QuantumEngineServiceTransport] = "grpc_asyncio",
client_options: Optional[ClientOptions] = None,
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from google.auth.transport import mtls
from google.auth.transport.grpc import SslCredentials
from google.auth.exceptions import MutualTLSChannelError
from google.oauth2 import service_account # type: ignore
from google.oauth2 import service_account

try:
OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials
from google.oauth2 import service_account # type: ignore
from google.oauth2 import service_account

from cirq_google.cloud.quantum_v1alpha1.types import engine
from cirq_google.cloud.quantum_v1alpha1.types import quantum
Expand All @@ -48,7 +48,7 @@ def __init__(
self,
*,
host: str = DEFAULT_HOST,
credentials: ga_credentials.Credentials = None,
credentials: Optional[ga_credentials.Credentials] = None,
credentials_file: Optional[str] = None,
scopes: Optional[Sequence[str]] = None,
quota_project_id: Optional[str] = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def __init__(

if channel:
# Ignore credentials if a channel was passed.
credentials = False
credentials = None
# If a channel was explicitly provided, set it.
self._grpc_channel = channel
self._ssl_channel_credentials = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class QuantumEngineServiceGrpcAsyncIOTransport(QuantumEngineServiceTransport):
def create_channel(
cls,
host: str = 'quantum.googleapis.com',
credentials: ga_credentials.Credentials = None,
credentials: Optional[ga_credentials.Credentials] = None,
credentials_file: Optional[str] = None,
scopes: Optional[Sequence[str]] = None,
quota_project_id: Optional[str] = None,
Expand Down Expand Up @@ -94,7 +94,7 @@ def __init__(
self,
*,
host: str = 'quantum.googleapis.com',
credentials: ga_credentials.Credentials = None,
credentials: Optional[ga_credentials.Credentials] = None,
credentials_file: Optional[str] = None,
scopes: Optional[Sequence[str]] = None,
channel: Optional[aio.Channel] = None,
Expand Down Expand Up @@ -166,7 +166,7 @@ def __init__(

if channel:
# Ignore credentials if a channel was passed.
credentials = False
credentials = None
# If a channel was explicitly provided, set it.
self._grpc_channel = channel
self._ssl_channel_credentials = None
Expand Down
43 changes: 0 additions & 43 deletions cirq-rigetti/cirq_rigetti/_qcs_api_client_decorator.py

This file was deleted.

31 changes: 13 additions & 18 deletions cirq-rigetti/cirq_rigetti/aspen_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,16 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, cast, Optional, Union, Dict, Any
from typing import List, Optional, Union, Dict, Any
import functools
from math import sqrt
import httpx
import json
import numpy as np
import networkx as nx
import cirq
from pyquil.quantum_processor import QCSQuantumProcessor
from qcs_api_client.models import InstructionSetArchitecture
from qcs_api_client.operations.sync import get_instruction_set_architecture
from cirq_rigetti._qcs_api_client_decorator import _provide_default_client
from qcs_sdk.client import QCSClient
from qcs_sdk.qpu.isa import get_instruction_set_architecture, InstructionSetArchitecture, Family


class UnsupportedQubit(ValueError):
Expand Down Expand Up @@ -50,6 +49,8 @@ class UnsupportedRigettiQCSQuantumProcessor(ValueError):
class RigettiQCSAspenDevice(cirq.devices.Device):
"""A cirq.Qid supporting Rigetti QCS Aspen device topology."""

isa: InstructionSetArchitecture

def __init__(self, isa: Union[InstructionSetArchitecture, Dict[str, Any]]) -> None:
"""Initializes a RigettiQCSAspenDevice with its Rigetti QCS `InstructionSetArchitecture`.
Expand All @@ -63,9 +64,9 @@ def __init__(self, isa: Union[InstructionSetArchitecture, Dict[str, Any]]) -> No
if isinstance(isa, InstructionSetArchitecture):
self.isa = isa
else:
self.isa = InstructionSetArchitecture.from_dict(isa)
self.isa = InstructionSetArchitecture.from_raw(json.dumps(isa))

if self.isa.architecture.family.lower() != 'aspen':
if self.isa.architecture.family != Family.Aspen:
raise UnsupportedRigettiQCSQuantumProcessor(
'this integration currently only supports Aspen devices, '
f'but client provided a {self.isa.architecture.family} device'
Expand Down Expand Up @@ -224,23 +225,22 @@ def __repr__(self):
return f'cirq_rigetti.RigettiQCSAspenDevice(isa={self.isa!r})'

def _json_dict_(self):
return {'isa': self.isa.to_dict()}
return {'isa': json.loads(self.isa.json())}

@classmethod
def _from_json_dict_(cls, isa, **kwargs):
return cls(isa=InstructionSetArchitecture.from_dict(isa))
return cls(isa=InstructionSetArchitecture.from_raw(json.dumps(isa)))


@_provide_default_client # pragma: no cover
def get_rigetti_qcs_aspen_device(
quantum_processor_id: str, client: Optional[httpx.Client]
quantum_processor_id: str, client: Optional[QCSClient] = None
) -> RigettiQCSAspenDevice:
"""Retrieves a `qcs_api_client.models.InstructionSetArchitecture` from the Rigetti
QCS API and uses it to initialize a RigettiQCSAspenDevice.
Args:
quantum_processor_id: The identifier of the Rigetti QCS quantum processor.
client: Optional; A `httpx.Client` initialized with Rigetti QCS credentials
client: Optional; A `QCSClient` initialized with Rigetti QCS credentials
and configuration. If not provided, `qcs_api_client` will initialize a
configured client based on configured values in the current user's
`~/.qcs` directory or default values.
Expand All @@ -250,12 +250,7 @@ def get_rigetti_qcs_aspen_device(
set and architecture.
"""
isa = cast(
InstructionSetArchitecture,
get_instruction_set_architecture(
client=client, quantum_processor_id=quantum_processor_id
).parsed,
)
isa = get_instruction_set_architecture(client=client, quantum_processor_id=quantum_processor_id)
return RigettiQCSAspenDevice(isa=isa)


Expand Down
36 changes: 17 additions & 19 deletions cirq-rigetti/cirq_rigetti/aspen_device_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from unittest.mock import patch, PropertyMock
from math import sqrt
import pathlib
import json
import pytest
import cirq
from cirq_rigetti import (
Expand All @@ -12,9 +11,8 @@
RigettiQCSAspenDevice,
UnsupportedQubit,
UnsupportedRigettiQCSOperation,
UnsupportedRigettiQCSQuantumProcessor,
)
from qcs_api_client.models import InstructionSetArchitecture, Node
from qcs_sdk.qpu.isa import InstructionSetArchitecture, Family
import numpy as np

dir_path = pathlib.Path(os.path.dirname(os.path.realpath(__file__)))
Expand All @@ -24,7 +22,7 @@
@pytest.fixture
def qcs_aspen8_isa() -> InstructionSetArchitecture:
with open(fixture_path / 'QCS-Aspen-8-ISA.json', 'r') as f:
return InstructionSetArchitecture.from_dict(json.load(f))
return InstructionSetArchitecture.from_raw(f.read())


def test_octagonal_qubit_index():
Expand Down Expand Up @@ -204,17 +202,6 @@ def test_rigetti_qcs_aspen_device_invalid_qubit(
device.validate_operation(cirq.I(qubit))


def test_rigetti_qcs_aspen_device_non_existent_qubit(qcs_aspen8_isa: InstructionSetArchitecture):
"""test RigettiQCSAspenDevice throws error when qubit does not exist on device"""
# test device may only be initialized with Aspen ISA.
device_with_limited_nodes = RigettiQCSAspenDevice(
isa=InstructionSetArchitecture.from_dict(qcs_aspen8_isa.to_dict())
)
device_with_limited_nodes.isa.architecture.nodes = [Node(node_id=10)]
with pytest.raises(UnsupportedQubit):
device_with_limited_nodes.validate_qubit(cirq.GridQubit(0, 0))


@pytest.mark.parametrize(
'operation',
[
Expand Down Expand Up @@ -265,7 +252,18 @@ def test_rigetti_qcs_aspen_device_repr(qcs_aspen8_isa: InstructionSetArchitectur

def test_rigetti_qcs_aspen_device_family_validation(qcs_aspen8_isa: InstructionSetArchitecture):
"""test RigettiQCSAspenDevice validates architecture family on initialization"""
non_aspen_isa = InstructionSetArchitecture.from_dict(qcs_aspen8_isa.to_dict())
non_aspen_isa.architecture.family = "not-aspen" # type: ignore
with pytest.raises(UnsupportedRigettiQCSQuantumProcessor):
RigettiQCSAspenDevice(isa=non_aspen_isa)
non_aspen_isa = InstructionSetArchitecture.from_raw(qcs_aspen8_isa.json())
non_aspen_isa.architecture.family = Family.NONE

assert (
non_aspen_isa.architecture.family == Family.Aspen
), 'ISA family is read-only and should still be Aspen'


def test_get_rigetti_qcs_aspen_device(qcs_aspen8_isa: InstructionSetArchitecture):
with patch('cirq_rigetti.aspen_device.get_instruction_set_architecture') as mock:
mock.return_value = qcs_aspen8_isa

from cirq_rigetti.aspen_device import get_rigetti_qcs_aspen_device

assert get_rigetti_qcs_aspen_device('Aspen-8') == RigettiQCSAspenDevice(isa=qcs_aspen8_isa)
Loading

0 comments on commit f55d42e

Please sign in to comment.