diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5cb38c69b2d..c36d9612598 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -27,4 +27,4 @@ P1 - I need this no later than the next release (end of quarter) P2 - we should do it in the next couple of quarters P3 - I'm not really blocked by it, it is an idea I'd like to discuss / suggestion based on principle - \ No newline at end of file + diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md index 0c82289bfae..ff471b796bf 100644 --- a/.github/ISSUE_TEMPLATE/task.md +++ b/.github/ISSUE_TEMPLATE/task.md @@ -1,6 +1,6 @@ --- name: Project task -about: Task to track larger efforts +about: Task to track larger efforts title: '' labels: 'kind/task' assignees: '' @@ -16,4 +16,4 @@ assignees: '' **Related** -Related issues: #abc, #xyz, ... \ No newline at end of file +Related issues: #abc, #xyz, ... diff --git a/.github/workflows/ci-daily.yml b/.github/workflows/ci-daily.yml index 4748c3d90c2..c607d820b07 100644 --- a/.github/workflows/ci-daily.yml +++ b/.github/workflows/ci-daily.yml @@ -14,7 +14,7 @@ jobs: name: Pytest Ubuntu strategy: matrix: - python-version: ['3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 @@ -44,7 +44,7 @@ jobs: name: Pytest Windows strategy: matrix: - python-version: ['3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] runs-on: windows-2019 steps: - uses: actions/checkout@v4 @@ -70,7 +70,7 @@ jobs: name: Pytest MacOS strategy: matrix: - python-version: ['3.10', '3.11'] + python-version: ['3.10', '3.11', '3.12'] # TODO(#6577): upgrade to macos-latest when it runs Python 3.10 runs-on: macos-13 steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e780b733e6b..f3b41499a9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,7 @@ jobs: - name: Install requirements run: pip install -r dev_tools/requirements/dev.env.txt - name: RST check - run: find . -type f -name "*.rst" | xargs rstcheck --report warning --ignore-directives autoclass,automodule + run: find . -type f -name "*.rst" | xargs rstcheck - name: Doc check run: check/doctest -q nbformat: @@ -144,7 +144,7 @@ jobs: - name: Install dependencies run: pip install -r dev_tools/requirements/isolated-base.env.txt - name: Test each module in isolation - run: pytest -n auto -m slow dev_tools/packaging/isolated_packages_test.py + run: pytest -n auto --enable-slow-tests dev_tools/packaging/isolated_packages_test.py pytest: name: Pytest Ubuntu strategy: diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 6e59559f076..285cfc30e6a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -89,4 +89,4 @@ harassment or threats to anyone's safety, we may take action without notice. This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at -https://www.contributor-covenant.org/version/1/4/code-of-conduct.html \ No newline at end of file +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/benchmarks/README.md b/benchmarks/README.md index 0e9ef527d5f..81620a7f5f1 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -19,4 +19,4 @@ You can also pass arguments to the script, which would be forwarded to the `asv Please refer [Running Benchmarks guide by ASV](https://asv.readthedocs.io/en/stable/using.html#running-benchmarks) for more information. ## Results Database -TODO([#3838](https://github.com/quantumlib/Cirq/issues/3838)): Add details regarding GCP setup. \ No newline at end of file +TODO([#3838](https://github.com/quantumlib/Cirq/issues/3838)): Add details regarding GCP setup. diff --git a/check/all b/check/all index 81f0689a736..66e3bfad0ee 100755 --- a/check/all +++ b/check/all @@ -38,20 +38,20 @@ cd "${topdir}" || exit $? errors=() # Parse arguments. -apply_arg="" +apply_arg=( ) only_changed=0 -rev="" +rev=( ) for arg in "$@"; do if [[ "${arg}" == "--only-changed-files" ]]; then only_changed=1 elif [[ "${arg}" == "--apply-format-changes" ]]; then - apply_arg="--apply" - elif [ -z "${rev}" ]; then + apply_arg=( "--apply" ) + elif [[ "${#rev[@]}" == 0 ]]; then if [ "$(git cat-file -t "${arg}" 2> /dev/null)" != "commit" ]; then echo -e "\033[31mNo revision '${arg}'.\033[0m" >&2 exit 1 fi - rev="${arg}" + rev=( "${arg}" ) else echo -e "\033[31mInvalid arguments. Expected [BASE_REVISION] [--only-changed-files] [--apply-format].\033[0m" >&2 exit 1 @@ -73,15 +73,16 @@ echo "Running mypy" check/mypy || errors+=( "check/mypy failed" ) echo "Running incremental format" -check/format-incremental "${rev}" "${apply_arg}" || errors+=( "check/format-incremental failed" ) +check/format-incremental "${rev[@]}" "${apply_arg[@]}" || + errors+=( "check/format-incremental failed" ) if [ ${only_changed} -ne 0 ]; then echo "Running pytest and incremental coverage on changed files" - check/pytest-changed-files-and-incremental-coverage "${rev}" || + check/pytest-changed-files-and-incremental-coverage "${rev[@]}" || errors+=( "check/pytest-changed-files-and-incremental-coverage failed" ) else echo "Running pytest and incremental coverage" - check/pytest-and-incremental-coverage "${rev}" || + check/pytest-and-incremental-coverage "${rev[@]}" || errors+=( "check/pytest-and-incremental-coverage failed" ) fi diff --git a/check/build-changed-protos b/check/build-changed-protos index c4453373285..df58599498b 100755 --- a/check/build-changed-protos +++ b/check/build-changed-protos @@ -32,7 +32,7 @@ cd "${topdir}" || exit $? # Figure out which revision to compare against. if [ -n "$1" ] && [[ $1 != -* ]]; then - if [ "$(git cat-file -t "$1" 2> /dev/null)" != "commit" ]; then + if ! git rev-parse --verify --quiet --no-revs "$1^{commit}"; then echo -e "\033[31mNo revision '$1'.\033[0m" >&2 exit 1 fi @@ -62,7 +62,7 @@ dev_tools/build-protos.sh # Filenames with spaces will be ugly (each part will be listed separately) # but the error logic will still work. -uncommitted=$(git status --porcelain 2>/dev/null | grep -E "^?? cirq-google" | cut -d " " -f 3) +uncommitted=$(git status --porcelain 2>/dev/null | grep -E "^[?][?] cirq-google" | cut -d " " -f 3) if [[ -n "$uncommitted" ]]; then echo -e "\033[31mERROR: Uncommitted generated files found! Please generate and commit these files using dev_tools/build-protos.sh:\033[0m" diff --git a/check/format-incremental b/check/format-incremental index 74a253efeea..2f7a8dab078 100755 --- a/check/format-incremental +++ b/check/format-incremental @@ -46,7 +46,7 @@ for arg in "$@"; do elif [[ "${arg}" == "--all" ]]; then only_changed=0 elif [ -z "${rev}" ]; then - if [ "$(git cat-file -t "${arg}" 2> /dev/null)" != "commit" ]; then + if ! git rev-parse --verify --quiet --no-revs "${arg}^{commit}"; then echo -e "\033[31mNo revision '${arg}'.\033[0m" >&2 exit 1 fi diff --git a/check/pylint-changed-files b/check/pylint-changed-files index b335e61d1aa..1e097006992 100755 --- a/check/pylint-changed-files +++ b/check/pylint-changed-files @@ -31,7 +31,7 @@ cd "${topdir}" || exit $? # Figure out which revision to compare against. if [ -n "$1" ] && [[ $1 != -* ]]; then - if [ "$(git cat-file -t "$1" 2> /dev/null)" != "commit" ]; then + if ! git rev-parse --verify --quiet --no-revs "$1^{commit}"; then echo -e "\033[31mNo revision '$1'.\033[0m" >&2 exit 1 fi diff --git a/check/pytest-and-incremental-coverage b/check/pytest-and-incremental-coverage index e088f4d3864..ba2890ddd88 100755 --- a/check/pytest-and-incremental-coverage +++ b/check/pytest-and-incremental-coverage @@ -45,7 +45,7 @@ done # Figure out which revision to compare against. if [ -n "${BASEREV}" ]; then - if [ "$(git cat-file -t "${BASEREV}" 2> /dev/null)" != "commit" ]; then + if ! git rev-parse --verify --quiet --no-revs "${BASEREV}^{commit}"; then echo -e "\033[31mNo revision '${BASEREV}'.\033[0m" >&2 exit 1 fi diff --git a/check/pytest-changed-files b/check/pytest-changed-files index 8de45dc590d..175dba97321 100755 --- a/check/pytest-changed-files +++ b/check/pytest-changed-files @@ -35,7 +35,7 @@ cd "${topdir}" || exit $? # Figure out which branch to compare against. rest=( "$@" ) if [ -n "$1" ] && [[ $1 != -* ]]; then - if [ "$(git cat-file -t "$1" 2> /dev/null)" != "commit" ]; then + if ! git rev-parse --verify --quiet --no-revs "$1^{commit}"; then echo -e "\033[31mNo revision '$1'.\033[0m" >&2 exit 1 fi diff --git a/check/pytest-changed-files-and-incremental-coverage b/check/pytest-changed-files-and-incremental-coverage index d5b4a38cc40..a4c120caea4 100755 --- a/check/pytest-changed-files-and-incremental-coverage +++ b/check/pytest-changed-files-and-incremental-coverage @@ -34,7 +34,7 @@ cd "$(git rev-parse --show-toplevel)" || exit 1 # Figure out which revision to compare against. if [ -n "$1" ] && [[ $1 != -* ]]; then - if [ "$(git cat-file -t "$1" 2> /dev/null)" != "commit" ]; then + if ! git rev-parse --verify --quiet --no-revs "$1^{commit}"; then echo -e "\033[31mNo revision '$1'.\033[0m" >&2 exit 1 fi diff --git a/cirq-aqt/cirq_aqt/_version.py b/cirq-aqt/cirq_aqt/_version.py index 9f43ba84951..016426c9d4f 100644 --- a/cirq-aqt/cirq_aqt/_version.py +++ b/cirq-aqt/cirq_aqt/_version.py @@ -14,4 +14,4 @@ """Define version number here, read it from setup.py automatically""" -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-aqt/cirq_aqt/_version_test.py b/cirq-aqt/cirq_aqt/_version_test.py index de298e1c71c..64c4ad1c7c8 100644 --- a/cirq-aqt/cirq_aqt/_version_test.py +++ b/cirq-aqt/cirq_aqt/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq_aqt.__version__ == "1.4.0.dev" + assert cirq_aqt.__version__ == "1.5.0.dev" diff --git a/cirq-aqt/requirements.txt b/cirq-aqt/requirements.txt index 3d368bb39ab..fde7fb282cf 100644 --- a/cirq-aqt/requirements.txt +++ b/cirq-aqt/requirements.txt @@ -1 +1 @@ -requests~=2.18 \ No newline at end of file +requests~=2.18 diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index b2236ac9fc4..c70c328a26c 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -332,6 +332,7 @@ ZPowGate, ZZ, ZZPowGate, + UniformSuperpositionGate, ) from cirq.transformers import ( diff --git a/cirq-core/cirq/_version.py b/cirq-core/cirq/_version.py index 4190ae6fef5..4e5d0f3872f 100644 --- a/cirq-core/cirq/_version.py +++ b/cirq-core/cirq/_version.py @@ -28,4 +28,4 @@ 'of cirq (e.g. "python -m pip install cirq==1.1.*")' ) -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-core/cirq/_version_test.py b/cirq-core/cirq/_version_test.py index aa7be8a0ce4..eb0fe0a712a 100644 --- a/cirq-core/cirq/_version_test.py +++ b/cirq-core/cirq/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq.__version__ == "1.4.0.dev" + assert cirq.__version__ == "1.5.0.dev" diff --git a/cirq-core/cirq/circuits/circuit.py b/cirq-core/cirq/circuits/circuit.py index 0f6895991d5..b09495cd256 100644 --- a/cirq-core/cirq/circuits/circuit.py +++ b/cirq-core/cirq/circuits/circuit.py @@ -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 diff --git a/cirq-core/cirq/circuits/circuit_operation_test.py b/cirq-core/cirq/circuits/circuit_operation_test.py index f3ba30dd62a..f2840e1e102 100644 --- a/cirq-core/cirq/circuits/circuit_operation_test.py +++ b/cirq-core/cirq/circuits/circuit_operation_test.py @@ -327,9 +327,9 @@ def test_repeat(add_measurements: bool, use_default_ids_for_initial_rep: bool) - _ = op_base.repeat() with pytest.raises(TypeError, match='Only integer or sympy repetitions are allowed'): - _ = op_base.repeat(1.3) # type: ignore[arg-type] - assert op_base.repeat(3.00000000001).repetitions == 3 # type: ignore[arg-type] - assert op_base.repeat(2.99999999999).repetitions == 3 # type: ignore[arg-type] + _ = op_base.repeat(1.3) + assert op_base.repeat(3.00000000001).repetitions == 3 + assert op_base.repeat(2.99999999999).repetitions == 3 @pytest.mark.parametrize('add_measurements', [True, False]) diff --git a/cirq-core/cirq/circuits/circuit_test.py b/cirq-core/cirq/circuits/circuit_test.py index 4494aaf9ae3..a6e055ea2ee 100644 --- a/cirq-core/cirq/circuits/circuit_test.py +++ b/cirq-core/cirq/circuits/circuit_test.py @@ -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( diff --git a/cirq-core/cirq/experiments/qubit_characterizations.py b/cirq-core/cirq/experiments/qubit_characterizations.py index 9ba7928b81c..916c662e468 100644 --- a/cirq-core/cirq/experiments/qubit_characterizations.py +++ b/cirq-core/cirq/experiments/qubit_characterizations.py @@ -17,8 +17,8 @@ import functools from typing import ( - Any, cast, + Any, Iterator, List, Optional, @@ -107,7 +107,6 @@ def plot(self, ax: Optional[plt.Axes] = None, **plot_kwargs: Any) -> plt.Axes: show_plot = not ax if not ax: fig, ax = plt.subplots(1, 1, figsize=(8, 8)) # pragma: no cover - ax = cast(plt.Axes, ax) # pragma: no cover ax.set_ylim((0.0, 1.0)) # pragma: no cover ax.plot(self._num_cfds_seq, self._gnd_state_probs, 'ro', label='data', **plot_kwargs) x = np.linspace(self._num_cfds_seq[0], self._num_cfds_seq[-1], 100) @@ -304,7 +303,9 @@ def plot(self, axes: Optional[List[plt.Axes]] = None, **plot_kwargs: Any) -> Lis """ show_plot = axes is None if axes is None: - fig, axes = plt.subplots(1, 2, figsize=(12.0, 5.0), subplot_kw={'projection': '3d'}) + fig, axes_v = plt.subplots(1, 2, figsize=(12.0, 5.0), subplot_kw={'projection': '3d'}) + axes_v = cast(np.ndarray, axes_v) + axes = list(axes_v) elif len(axes) != 2: raise ValueError('A TomographyResult needs 2 axes to plot.') mat = self._density_matrix diff --git a/cirq-core/cirq/experiments/readout_confusion_matrix.py b/cirq-core/cirq/experiments/readout_confusion_matrix.py index d99925aad14..a11efee1f03 100644 --- a/cirq-core/cirq/experiments/readout_confusion_matrix.py +++ b/cirq-core/cirq/experiments/readout_confusion_matrix.py @@ -26,12 +26,26 @@ import cirq +def _mitigate_single_bitstring(z_rinv_all: list[np.ndarray], bitstring: np.ndarray) -> float: + """Return the mitigated Pauli expectation value for a single observed bitstring. + + Args: + z_rinv_all: A list of single-qubit pauli-Z (as a vector) contracted with the single-qubit + inverse response matrix, for each qubit. + bitstring: The measured bitstring. + + Returns: + The corrected expectation value of ZZZZZ... given the single measured bitstring. + """ + return np.prod([z_rinv[bit] for z_rinv, bit in zip(z_rinv_all, bitstring)]) + + class TensoredConfusionMatrices: """Store and use confusion matrices for readout error mitigation on sets of qubits. The confusion matrix (CM) for one qubit is: - [ Pr(0|0) Pr(1|0) ] + [ Pr(0|0) Pr(0|1) ] [ Pr(1|0) Pr(1|1) ] where Pr(i | j) = Probability of observing state "i" given state "j" was prepared. @@ -71,7 +85,7 @@ def __init__( confusion_matrices: Sequence of confusion matrices, computed for qubit patterns present in `measure_qubits`. A single confusion matrix is also accepted. measure_qubits: Sequence of smaller qubit patterns, for which the confusion matrices - were computed. A single qubit pattern is also accepted. Note that the + were computed. A single qubit pattern is also accepted. Note that each qubit pattern is a sequence of qubits used to label the axes of the corresponding confusion matrix. repetitions: The number of repetitions that were used to estimate the confusion @@ -298,6 +312,72 @@ def func(x): ) # pragma: no cover return res.x + def readout_mitigation_pauli_uncorrelated( + self, qubits: Sequence['cirq.Qid'], measured_bitstrings: np.ndarray + ) -> tuple[float, float]: + r"""Uncorrelated readout error mitigation for a multi-qubit Pauli operator. + + This function scalably performs readout error mitigation on an arbitrary-length Pauli + operator. It is a reimplementation of https://github.com/eliottrosenberg/correlated_SPAM + but specialized to the case in which readout is uncorrelated. We require that the confusion + matrix is a tensor product of single-qubit confusion matrices. We then invert the confusion + matrix by inverting each of the $C^{(q)}$ Then, in a bit-by-bit fashion, we apply the + inverses of the single-site confusion matrices to the bits of the measured bitstring, + contract them with the single-site Pauli operator, and take the product over all of the + bits. This could be generalized to tensor product spaces that are larger than single qubits, + but the essential simplification is that each tensor product space is small, so that none of + the response matrices is exponentially large. + + This can result in mitigated Pauli operators that are not in the range [-1, 1], but if + the readout error is indeed uncorrelated and well-characterized, then it should converge + to being within this range. Results are improved both by a more precise characterization + of the response matrices (whose statistical uncertainty is not accounted for in the error + propagation here) and by increasing the number of measured bitstrings. + + Args: + qubits: The qubits on which the Pauli operator acts. + measured_bitstrings: The experimentally measured bitstrings in the eigenbasis of the + Pauli operator. measured_bitstrings[i,j] is the ith bitstring, qubit j. + + Returns: + The error-mitigated expectation value of the Pauli operator and its statistical + uncertainty (not including the uncertainty in the confusion matrices for now). + + Raises: + NotImplementedError: If the confusion matrix is not a tensor product of single-qubit + confusion matrices for all of `qubits`. + """ + + # first, get all of the confusion matrices + cm_all = [] + for qubit in qubits: + try: + idx = self.measure_qubits.index((qubit,)) + except: # pragma: no cover + raise NotImplementedError( # pragma: no cover + "The response matrix must be a tensor product of single-qu" # pragma: no cover + + f"bit response matrices, including that of qubit {qubit}." # pragma: no cover + ) # pragma: no cover + cm_all.append(self.confusion_matrices[idx]) + + # get the correction matrices, assuming uncorrelated readout: + cminv_all = [np.linalg.inv(cm) for cm in cm_all] + + # next, contract them with the single-qubit Pauli operators: + z = np.array([1, -1]) + z_cminv_all = [z @ cminv for cminv in cminv_all] + + # finally, mitigate each bitstring: + z_mit_all_shots = np.array( + [ + _mitigate_single_bitstring(z_cminv_all, bitstring) + for bitstring in measured_bitstrings + ] + ) + + # return mean and statistical uncertainty: + return np.mean(z_mit_all_shots), np.std(z_mit_all_shots) / np.sqrt(len(measured_bitstrings)) + def __repr__(self) -> str: return ( f"cirq.TensoredConfusionMatrices(" diff --git a/cirq-core/cirq/experiments/readout_confusion_matrix_test.py b/cirq-core/cirq/experiments/readout_confusion_matrix_test.py index bfd13e30cb6..48c0ce889d1 100644 --- a/cirq-core/cirq/experiments/readout_confusion_matrix_test.py +++ b/cirq-core/cirq/experiments/readout_confusion_matrix_test.py @@ -17,6 +17,34 @@ import pytest from cirq.experiments.single_qubit_readout_calibration_test import NoisySingleQubitReadoutSampler +from cirq.experiments.readout_confusion_matrix import TensoredConfusionMatrices + + +def add_readout_error( + measurements: np.ndarray, + zero_errors: np.ndarray, + one_errors: np.ndarray, + rng: np.random.Generator, +) -> np.ndarray: + """Add readout errors to measured (or simulated) bitstrings. + + Args: + measurements: The bitstrings to which we will add readout errors. measurements[i,j] is the + ith bitstring, qubit j. + zero_errors: zero_errors[i] is the probability of a 0->1 readout error on qubit i. + one_errors: one_errors[i] is the probability of a 1->0 readout error on qubit i. + rng: The pseudorandom number generator to use. + + Returns: + New measurements but with readout errors added. + """ + num_bitstrs, n = measurements.shape + assert len(zero_errors) == len(one_errors) == n + # compute the probability that each bit is 1 after adding readout errors: + p1 = measurements * (1 - one_errors) + (1 - measurements) * zero_errors + r = rng.random((num_bitstrs, n)) + noisy_measurements = r < p1 + return noisy_measurements.astype(int) def get_expected_cm(num_qubits: int, p0: float, p1: float): @@ -159,3 +187,78 @@ def test_readout_confusion_matrix_repr_and_equality(): eq.add_equality_group(a, a) eq.add_equality_group(b, b) eq.add_equality_group(c, c) + + +def _sample_ghz(n: int, repetitions: int, rng: np.random.Generator) -> np.ndarray: + """Sample a GHZ state in the z basis. + Args: + n: The number of qubits. + repetitions: The number of repetitions. + rng: The pseudorandom number generator to use. + Returns: + An array of the measurement outcomes. + """ + return np.tile(rng.integers(0, 2, size=repetitions), (n, 1)).T + + +def _add_noise_and_mitigate_ghz( + n: int, + repetitions: int, + zero_errors: np.ndarray, + one_errors: np.ndarray, + rng: np.random.Generator | None = None, +) -> tuple[float, float, float, float]: + """Add readout error to GHZ-like bitstrings and measure with and + without readout error mitigation. + Args: + n: The number of qubits. + repetitions: The number of repetitions. + zero_errors: zero_errors[i] is the probability of a 0->1 readout error on qubit i. + one_errors: one_errors[i] is the probability of a 1->0 readout error on qubit i. + rng: The pseudorandom number generator to use. + Returns: + A tuple of: + - The mitigated expectation value of + - The statistical uncertainty of the previous output + - The unmitigated expectation value of + - The statstical uncertainty of the previous output + """ + if rng is None: + rng = np.random.default_rng(0) + confusion_matrices = [ + np.array([[1 - e0, e1], [e0, 1 - e1]]) for e0, e1 in zip(zero_errors, one_errors) + ] + qubits = cirq.LineQubit.range(n) + tcm = TensoredConfusionMatrices( + confusion_matrices, [[q] for q in qubits], repetitions=0, timestamp=0.0 + ) + + measurements = _sample_ghz(n, repetitions, rng) + noisy_measurements = add_readout_error(measurements, zero_errors, one_errors, rng) + # unmitigated: + p1 = np.mean(np.sum(noisy_measurements, axis=1) % 2) + z = 1 - 2 * np.mean(p1) + dz = 2 * np.sqrt(p1 * (1 - p1) / repetitions) + # return mitigated and unmitigated: + return (*tcm.readout_mitigation_pauli_uncorrelated(qubits, noisy_measurements), z, dz) + + +def test_uncorrelated_readout_mitigation_pauli(): + n_all = np.arange(2, 35) + z_all_mit = [] + dz_all_mit = [] + z_all_raw = [] + dz_all_raw = [] + repetitions = 10_000 + for n in n_all: + e0 = np.ones(n) * 0.005 + e1 = np.ones(n) * 0.03 + z_mit, dz_mit, z_raw, dz_raw = _add_noise_and_mitigate_ghz(n, repetitions, e0, e1) + z_all_mit.append(z_mit) + dz_all_mit.append(dz_mit) + z_all_raw.append(z_raw) + dz_all_raw.append(dz_raw) + + for n, z, dz in zip(n_all, z_all_mit, dz_all_mit): + ideal = 1.0 if n % 2 == 0 else 0.0 + assert np.isclose(z, ideal, atol=4 * dz) diff --git a/cirq-core/cirq/experiments/single_qubit_readout_calibration.py b/cirq-core/cirq/experiments/single_qubit_readout_calibration.py index cad1c27a36c..76891d57065 100644 --- a/cirq-core/cirq/experiments/single_qubit_readout_calibration.py +++ b/cirq-core/cirq/experiments/single_qubit_readout_calibration.py @@ -14,7 +14,7 @@ """Single qubit readout experiments using parallel or isolated statistics.""" import dataclasses import time -from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING +from typing import cast, Any, Dict, Iterable, List, Optional, TYPE_CHECKING import sympy import numpy as np @@ -77,8 +77,9 @@ def plot_heatmap( """ if axs is None: - _, axs = plt.subplots(1, 2, dpi=200, facecolor='white', figsize=(12, 4)) - + _, axs_v = plt.subplots(1, 2, dpi=200, facecolor='white', figsize=(12, 4)) + axs_v = cast(np.ndarray, axs_v) + axs = cast(tuple[plt.Axes, plt.Axes], (axs_v[0], axs_v[1])) else: if ( not isinstance(axs, (tuple, list, np.ndarray)) diff --git a/cirq-core/cirq/experiments/two_qubit_xeb.py b/cirq-core/cirq/experiments/two_qubit_xeb.py index ef609301c7f..2ad477cf2df 100644 --- a/cirq-core/cirq/experiments/two_qubit_xeb.py +++ b/cirq-core/cirq/experiments/two_qubit_xeb.py @@ -347,7 +347,7 @@ def plot_histogram( return ax -def parallel_two_qubit_xeb( +def parallel_xeb_workflow( sampler: 'cirq.Sampler', qubits: Optional[Sequence['cirq.GridQubit']] = None, entangling_gate: 'cirq.Gate' = ops.CZ, @@ -358,8 +358,8 @@ def parallel_two_qubit_xeb( random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, ax: Optional[plt.Axes] = None, **plot_kwargs, -) -> TwoQubitXEBResult: - """A convenience method that runs the full XEB workflow. +) -> Tuple[pd.DataFrame, Sequence['cirq.Circuit'], pd.DataFrame]: + """A utility method that runs the full XEB workflow. Args: sampler: The quantum engine or simulator to run the circuits. @@ -375,7 +375,12 @@ def parallel_two_qubit_xeb( **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. Returns: - A TwoQubitXEBResult object representing the results of the experiment. + - A DataFrame with columns 'cycle_depth' and 'fidelity'. + - The circuits used to perform XEB. + - A pandas dataframe with index given by ['circuit_i', 'cycle_depth']. + Columns always include "sampled_probs". If `combinations_by_layer` is + not `None` and you are doing parallel XEB, additional metadata columns + will be attached to the returned DataFrame. Raises: ValueError: If qubits are not specified and the sampler has no device. @@ -420,6 +425,52 @@ def parallel_two_qubit_xeb( sampled_df=sampled_df, circuits=circuit_library, cycle_depths=cycle_depths ) + return fids, circuit_library, sampled_df + + +def parallel_two_qubit_xeb( + sampler: 'cirq.Sampler', + qubits: Optional[Sequence['cirq.GridQubit']] = None, + entangling_gate: 'cirq.Gate' = ops.CZ, + n_repetitions: int = 10**4, + n_combinations: int = 10, + n_circuits: int = 20, + cycle_depths: Sequence[int] = tuple(np.arange(3, 100, 20)), + random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, + ax: Optional[plt.Axes] = None, + **plot_kwargs, +) -> TwoQubitXEBResult: + """A convenience method that runs the full XEB workflow. + + Args: + sampler: The quantum engine or simulator to run the circuits. + qubits: Qubits under test. If none, uses all qubits on the sampler's device. + entangling_gate: The entangling gate to use. + n_repetitions: The number of repetitions to use. + n_combinations: The number of combinations to generate. + n_circuits: The number of circuits to generate. + cycle_depths: The cycle depths to use. + random_state: The random state to use. + ax: the plt.Axes to plot the device layout on. If not given, + no plot is created. + **plot_kwargs: Arguments to be passed to 'plt.Axes.plot'. + Returns: + A TwoQubitXEBResult object representing the results of the experiment. + Raises: + ValueError: If qubits are not specified and the sampler has no device. + """ + fids, *_ = parallel_xeb_workflow( + sampler=sampler, + qubits=qubits, + entangling_gate=entangling_gate, + n_repetitions=n_repetitions, + n_combinations=n_combinations, + n_circuits=n_circuits, + cycle_depths=cycle_depths, + random_state=random_state, + ax=ax, + **plot_kwargs, + ) return TwoQubitXEBResult(fit_exponential_decays(fids)) diff --git a/cirq-core/cirq/experiments/xeb_fitting.py b/cirq-core/cirq/experiments/xeb_fitting.py index bbce3300b61..7f46d2d7f92 100644 --- a/cirq-core/cirq/experiments/xeb_fitting.py +++ b/cirq-core/cirq/experiments/xeb_fitting.py @@ -146,6 +146,69 @@ def get_initial_simplex_and_names( """Return an initial Nelder-Mead simplex and the names for each parameter.""" +def _try_defaults_from_unitary(gate: 'cirq.Gate') -> Optional[Dict[str, 'cirq.TParamVal']]: + r"""Try to figure out the PhasedFSim angles from the unitary of the gate. + + The unitary of a PhasedFSimGate has the form: + $$ + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & e^{-i \gamma - i \zeta} \cos(\theta) & -i e^{-i \gamma + i\chi} \sin(\theta) & 0 \\ + 0 & -i e^{-i \gamma - i \chi} \sin(\theta) & e^{-i \gamma + i \zeta} \cos(\theta) & 0 \\ + 0 & 0 & 0 & e^{-2i \gamma - i \phi} + \end{bmatrix} + $$ + That's the information about the five angles $\theta, \phi, \gamma, \zeta, \chi$ is encoded in + the submatrix unitary[1:3, 1:3] and the element u[3][3]. With some algebra, we can isolate each + of the angles as an argument of a combination of those elements (and potentially other angles). + + Args: + A cirq gate. + + Returns: + A dictionary mapping angles to values or None if the gate doesn't have a unitary or if it + can't be represented by a PhasedFSimGate. + """ + u = protocols.unitary(gate, default=None) + if u is None: + return None + + gamma = np.angle(u[1, 1] * u[2, 2] - u[1, 2] * u[2, 1]) / -2 + phi = -np.angle(u[3, 3]) - 2 * gamma + phased_cos_theta_2 = u[1, 1] * u[2, 2] + if phased_cos_theta_2 == 0: + # The zeta phase is multiplied with cos(theta), + # so if cos(theta) is zero then any value is possible. + zeta = 0 + else: + zeta = np.angle(u[2, 2] / u[1, 1]) / 2 + + phased_sin_theta_2 = u[1, 2] * u[2, 1] + if phased_sin_theta_2 == 0: + # The chi phase is multiplied with sin(theta), + # so if sin(theta) is zero then any value is possible. + chi = 0 + else: + chi = np.angle(u[1, 2] / u[2, 1]) / 2 + + theta = np.angle(np.exp(1j * (gamma + zeta)) * u[1, 1] - np.exp(1j * (gamma - chi)) * u[1, 2]) + + if np.allclose( + u, + protocols.unitary( + ops.PhasedFSimGate(theta=theta, phi=phi, chi=chi, zeta=zeta, gamma=gamma) + ), + ): + return { + 'theta_default': theta, + 'phi_default': phi, + 'gamma_default': gamma, + 'zeta_default': zeta, + 'chi_default': chi, + } + return None + + def phased_fsim_angles_from_gate(gate: 'cirq.Gate') -> Dict[str, 'cirq.TParamVal']: """For a given gate, return a dictionary mapping '{angle}_default' to its noiseless value for the five PhasedFSim angles.""" @@ -175,6 +238,11 @@ def phased_fsim_angles_from_gate(gate: 'cirq.Gate') -> Dict[str, 'cirq.TParamVal 'phi_default': gate.phi, } + # Handle all gates that can be represented using an FSimGate. + from_unitary = _try_defaults_from_unitary(gate) + if from_unitary is not None: + return from_unitary + raise ValueError(f"Unknown default angles for {gate}.") @@ -580,15 +648,6 @@ def _fit_exponential_decay( return a, layer_fid, a_std, layer_fid_std -def _one_unique(df, name, default): - """Helper function to assert that there's one unique value in a column and return it.""" - if name not in df.columns: - return default - vals = df[name].unique() - assert len(vals) == 1, name - return vals[0] - - def fit_exponential_decays(fidelities_df: pd.DataFrame) -> pd.DataFrame: """Fit exponential decay curves to a fidelities DataFrame. diff --git a/cirq-core/cirq/experiments/xeb_fitting_test.py b/cirq-core/cirq/experiments/xeb_fitting_test.py index 6fc5f48ccff..8f6ccdd4478 100644 --- a/cirq-core/cirq/experiments/xeb_fitting_test.py +++ b/cirq-core/cirq/experiments/xeb_fitting_test.py @@ -32,6 +32,7 @@ fit_exponential_decays, before_and_after_characterization, XEBPhasedFSimCharacterizationOptions, + phased_fsim_angles_from_gate, ) from cirq.experiments.xeb_sampling import sample_2q_xeb_circuits @@ -354,7 +355,7 @@ def test_options_with_defaults_from_gate(): assert options.zeta_default == 0.0 with pytest.raises(ValueError): - _ = XEBPhasedFSimCharacterizationOptions().with_defaults_from_gate(cirq.CZ) + _ = XEBPhasedFSimCharacterizationOptions().with_defaults_from_gate(cirq.XX) def test_options_defaults_set(): @@ -395,3 +396,34 @@ def test_options_defaults_set(): phi_default=0.0, ) assert o3.defaults_set() is True + + +def _random_angles(n, seed): + rng = np.random.default_rng(seed) + r = 2 * rng.random((n, 5)) - 1 + return np.pi * r + + +@pytest.mark.parametrize( + 'gate', + [ + cirq.CZ, + cirq.SQRT_ISWAP, + cirq.SQRT_ISWAP_INV, + cirq.ISWAP, + cirq.ISWAP_INV, + cirq.cphase(0.1), + cirq.CZ**0.2, + ] + + [cirq.PhasedFSimGate(*r) for r in _random_angles(10, 0)], +) +def test_phased_fsim_angles_from_gate(gate): + angles = phased_fsim_angles_from_gate(gate) + angles = {k.removesuffix('_default'): v for k, v in angles.items()} + phasedfsim = cirq.PhasedFSimGate(**angles) + np.testing.assert_allclose(cirq.unitary(phasedfsim), cirq.unitary(gate), atol=1e-9) + + +def test_phased_fsim_angles_from_gate_unsupporet_gate(): + with pytest.raises(ValueError, match='Unknown default angles'): + _ = phased_fsim_angles_from_gate(cirq.testing.TwoQubitGate()) diff --git a/cirq-core/cirq/experiments/xeb_sampling.py b/cirq-core/cirq/experiments/xeb_sampling.py index 8679ffcdc51..44dc31197f5 100644 --- a/cirq-core/cirq/experiments/xeb_sampling.py +++ b/cirq-core/cirq/experiments/xeb_sampling.py @@ -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, @@ -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 diff --git a/cirq-core/cirq/interop/quirk/cells/parse.py b/cirq-core/cirq/interop/quirk/cells/parse.py index e09722b3e87..8d680ef15fb 100644 --- a/cirq-core/cirq/interop/quirk/cells/parse.py +++ b/cirq-core/cirq/interop/quirk/cells/parse.py @@ -154,7 +154,7 @@ def apply(op: Union[str, _HangingNode]) -> None: a = vals.pop() # Note: vals seems to be _HangingToken # func operates on _ResolvedTokens. Ignoring type issues for now. - vals.append(op.func(a, b)) # type: ignore[arg-type] + vals.append(op.func(a, b)) def close_paren() -> None: while True: diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 4880046618a..65dea9c7587 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -247,6 +247,7 @@ def _symmetricalqidpair(qids): 'ZipLongest': cirq.ZipLongest, 'ZPowGate': cirq.ZPowGate, 'ZZPowGate': cirq.ZZPowGate, + 'UniformSuperpositionGate': cirq.UniformSuperpositionGate, # Old types, only supported for backwards-compatibility 'BooleanHamiltonian': _boolean_hamiltonian_gate_op, # Removed in v0.15 'CrossEntropyResult': _cross_entropy_result, # Removed in v0.16 diff --git a/cirq-core/cirq/linalg/decompositions.py b/cirq-core/cirq/linalg/decompositions.py index 82bc5e3b876..5c51cd79698 100644 --- a/cirq-core/cirq/linalg/decompositions.py +++ b/cirq-core/cirq/linalg/decompositions.py @@ -222,8 +222,9 @@ def kron_factor_4x4_to_2x2s(matrix: np.ndarray) -> Tuple[complex, np.ndarray, np f2[(a & 1) ^ i, (b & 1) ^ j] = matrix[a ^ i, b ^ j] # Rescale factors to have unit determinants. - f1 /= np.sqrt(np.linalg.det(f1)) or 1 - f2 /= np.sqrt(np.linalg.det(f2)) or 1 + with np.errstate(divide="ignore", invalid="ignore"): + f1 /= np.sqrt(np.linalg.det(f1)) or 1 + f2 /= np.sqrt(np.linalg.det(f2)) or 1 # Determine global phase. g = matrix[a, b] / (f1[a >> 1, b >> 1] * f2[a & 1, b & 1]) @@ -965,7 +966,8 @@ def kak_vector( # The algorithm in the appendix mentioned above is slightly incorrect in # that it only works for elements of SU(4). A phase correction must be # added to deal with U(4). - phases = np.log(-1j * np.linalg.det(unitary)).imag + np.pi / 2 + with np.errstate(divide="ignore", invalid="ignore"): + phases = np.log(-1j * np.linalg.det(unitary)).imag + np.pi / 2 evals *= np.exp(-1j * phases / 2)[..., np.newaxis] # The following steps follow the appendix exactly. diff --git a/cirq-core/cirq/linalg/diagonalize.py b/cirq-core/cirq/linalg/diagonalize.py index 96bf52fe860..959e92b3edc 100644 --- a/cirq-core/cirq/linalg/diagonalize.py +++ b/cirq-core/cirq/linalg/diagonalize.py @@ -255,10 +255,11 @@ def bidiagonalize_unitary_with_special_orthogonals( ) # Convert to special orthogonal w/o breaking diagonalization. - if np.linalg.det(left) < 0: - left[0, :] *= -1 - if np.linalg.det(right) < 0: - right[:, 0] *= -1 + with np.errstate(divide="ignore", invalid="ignore"): + if np.linalg.det(left) < 0: + left[0, :] *= -1 + if np.linalg.det(right) < 0: + right[:, 0] *= -1 diag = combinators.dot(left, mat, right) diff --git a/cirq-core/cirq/linalg/predicates.py b/cirq-core/cirq/linalg/predicates.py index bbfba50eddf..f0a25f5958a 100644 --- a/cirq-core/cirq/linalg/predicates.py +++ b/cirq-core/cirq/linalg/predicates.py @@ -91,9 +91,10 @@ def is_special_orthogonal(matrix: np.ndarray, *, rtol: float = 1e-5, atol: float Returns: Whether the matrix is special orthogonal within the given tolerance. """ - return is_orthogonal(matrix, rtol=rtol, atol=atol) and ( - matrix.shape[0] == 0 or np.allclose(np.linalg.det(matrix), 1, rtol=rtol, atol=atol) - ) + with np.errstate(divide="ignore", invalid="ignore"): + return is_orthogonal(matrix, rtol=rtol, atol=atol) and ( + matrix.shape[0] == 0 or np.allclose(np.linalg.det(matrix), 1, rtol=rtol, atol=atol) + ) def is_unitary(matrix: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8) -> bool: @@ -128,9 +129,10 @@ def is_special_unitary(matrix: np.ndarray, *, rtol: float = 1e-5, atol: float = Whether the matrix is unitary with unit determinant within the given tolerance. """ - return is_unitary(matrix, rtol=rtol, atol=atol) and ( - matrix.shape[0] == 0 or np.allclose(np.linalg.det(matrix), 1, rtol=rtol, atol=atol) - ) + with np.errstate(divide="ignore", invalid="ignore"): + return is_unitary(matrix, rtol=rtol, atol=atol) and ( + matrix.shape[0] == 0 or np.allclose(np.linalg.det(matrix), 1, rtol=rtol, atol=atol) + ) def is_normal(matrix: np.ndarray, *, rtol: float = 1e-5, atol: float = 1e-8) -> bool: diff --git a/cirq-core/cirq/linalg/transformations.py b/cirq-core/cirq/linalg/transformations.py index 9a9f8942fdc..2ef730aaea2 100644 --- a/cirq-core/cirq/linalg/transformations.py +++ b/cirq-core/cirq/linalg/transformations.py @@ -592,7 +592,8 @@ def to_special(u: np.ndarray) -> np.ndarray: Returns: the special unitary matrix """ - return u * (np.linalg.det(u) ** (-1 / len(u))) + with np.errstate(divide="ignore", invalid="ignore"): + return u * (np.linalg.det(u) ** (-1 / len(u))) def state_vector_kronecker_product(t1: np.ndarray, t2: np.ndarray) -> np.ndarray: diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index 5cadb6ad9af..25db2fc710a 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -217,3 +217,5 @@ from cirq.ops.state_preparation_channel import StatePreparationChannel from cirq.ops.control_values import AbstractControlValues, ProductOfSums, SumOfProducts + +from cirq.ops.uniform_superposition_gate import UniformSuperpositionGate diff --git a/cirq-core/cirq/ops/common_gates_test.py b/cirq-core/cirq/ops/common_gates_test.py index fb878d5e508..c676ca4f68f 100644 --- a/cirq-core/cirq/ops/common_gates_test.py +++ b/cirq-core/cirq/ops/common_gates_test.py @@ -899,6 +899,8 @@ def test_cphase_unitary(angle_rads, expected_unitary): np.testing.assert_allclose(cirq.unitary(cirq.cphase(angle_rads)), expected_unitary) +# TODO(#6663): fix this use case. +@pytest.mark.xfail def test_parameterized_cphase(): assert cirq.cphase(sympy.pi) == cirq.CZ assert cirq.cphase(sympy.pi / 2) == cirq.CZ**0.5 diff --git a/cirq-core/cirq/ops/global_phase_op.py b/cirq-core/cirq/ops/global_phase_op.py index e1a66272244..6a64a634aa6 100644 --- a/cirq-core/cirq/ops/global_phase_op.py +++ b/cirq-core/cirq/ops/global_phase_op.py @@ -28,7 +28,7 @@ class GlobalPhaseGate(raw_types.Gate): def __init__(self, coefficient: 'cirq.TParamValComplex', atol: float = 1e-8) -> None: if not isinstance(coefficient, sympy.Basic): - if abs(1 - abs(coefficient)) > atol: # type: ignore[operator] + if abs(1 - abs(coefficient)) > atol: raise ValueError(f'Coefficient is not unitary: {coefficient!r}') self._coefficient = coefficient diff --git a/cirq-core/cirq/ops/pauli_string.py b/cirq-core/cirq/ops/pauli_string.py index f9e6896bffe..1978b6ba031 100644 --- a/cirq-core/cirq/ops/pauli_string.py +++ b/cirq-core/cirq/ops/pauli_string.py @@ -1120,6 +1120,23 @@ def _validate_qubit_mapping( ) +def _try_interpret_as_pauli_string(op: Any): + """Return a reprepresentation of an operation as a pauli string, if it is possible.""" + if isinstance(op, gate_operation.GateOperation): + gates = { + common_gates.XPowGate: pauli_gates.X, + common_gates.YPowGate: pauli_gates.Y, + common_gates.ZPowGate: pauli_gates.Z, + } + if (pauli := gates.get(type(op.gate), None)) is not None: + exponent = op.gate.exponent # type: ignore + if exponent % 2 == 0: + return cirq.PauliString() + if exponent % 2 == 1: + return pauli.on(op.qubits[0]) + return None + + # Ignoring type because mypy believes `with_qubits` methods are incompatible. class SingleQubitPauliStringGateOperation( # type: ignore gate_operation.GateOperation, PauliString @@ -1159,11 +1176,15 @@ def __mul__(self, other): return self._as_pauli_string() * other._as_pauli_string() if isinstance(other, (PauliString, complex, float, int)): return self._as_pauli_string() * other + if (as_pauli_string := _try_interpret_as_pauli_string(other)) is not None: + return self * as_pauli_string return NotImplemented def __rmul__(self, other): if isinstance(other, (PauliString, complex, float, int)): return other * self._as_pauli_string() + if (as_pauli_string := _try_interpret_as_pauli_string(other)) is not None: + return as_pauli_string * self return NotImplemented def __neg__(self): diff --git a/cirq-core/cirq/ops/pauli_string_test.py b/cirq-core/cirq/ops/pauli_string_test.py index 49a21980dc0..48ea085f311 100644 --- a/cirq-core/cirq/ops/pauli_string_test.py +++ b/cirq-core/cirq/ops/pauli_string_test.py @@ -211,6 +211,29 @@ def test_list_op_constructor_matches_mapping(pauli): assert cirq.PauliString([op]) == cirq.PauliString({q0: pauli}) +@pytest.mark.parametrize('pauli1', (cirq.X, cirq.Y, cirq.Z)) +@pytest.mark.parametrize('pauli2', (cirq.X, cirq.Y, cirq.Z)) +def test_exponent_mul_consistency(pauli1, pauli2): + a, b = cirq.LineQubit.range(2) + op_a, op_b = pauli1(a), pauli2(b) + + assert op_a * op_a * op_a == op_a + assert op_a * op_a**2 == op_a + assert op_a**2 * op_a == op_a + assert op_b * op_a * op_a == op_b + assert op_b * op_a**2 == op_b + assert op_a**2 * op_b == op_b + assert op_a * op_a * op_a * op_a == cirq.PauliString() + assert op_a * op_a**3 == cirq.PauliString() + assert op_b * op_a * op_a * op_a == op_b * op_a + assert op_b * op_a**3 == op_b * op_a + + op_a, op_b = pauli1(a), pauli2(a) + + assert op_a * op_b**3 == op_a * op_b * op_b * op_b + assert op_b**3 * op_a == op_b * op_b * op_b * op_a + + def test_constructor_flexibility(): a, b = cirq.LineQubit.range(2) with pytest.raises(TypeError, match='cirq.PAULI_STRING_LIKE'): diff --git a/cirq-core/cirq/ops/permutation_gate.py b/cirq-core/cirq/ops/permutation_gate.py index e91e21534a5..e8eddcc0381 100644 --- a/cirq-core/cirq/ops/permutation_gate.py +++ b/cirq-core/cirq/ops/permutation_gate.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, Iterable, Sequence, Tuple, TYPE_CHECKING +from typing import Any, Dict, Sequence, Tuple, TYPE_CHECKING from cirq import protocols, value from cirq.ops import raw_types, swap_gates @@ -74,23 +74,21 @@ def _has_unitary_(self): return True def _decompose_(self, qubits: Sequence['cirq.Qid']) -> 'cirq.OP_TREE': - n = len(qubits) - qubit_ids = [*range(n)] - is_sorted = False - - def _swap_if_out_of_order(idx: int) -> Iterable['cirq.Operation']: - nonlocal is_sorted - if self._permutation[qubit_ids[idx]] > self._permutation[qubit_ids[idx + 1]]: - yield swap_gates.SWAP(qubits[idx], qubits[idx + 1]) - qubit_ids[idx + 1], qubit_ids[idx] = qubit_ids[idx], qubit_ids[idx + 1] - is_sorted = False - - while not is_sorted: - is_sorted = True - for i in range(0, n - 1, 2): - yield from _swap_if_out_of_order(i) - for i in range(1, n - 1, 2): - yield from _swap_if_out_of_order(i) + permutation = [p for p in self.permutation] + + for i in range(len(permutation)): + + if permutation[i] == -1: + continue + cycle = [i] + while permutation[cycle[-1]] != i: + cycle.append(permutation[cycle[-1]]) + + for j in cycle: + permutation[j] = -1 + + for idx in cycle[1:]: + yield swap_gates.SWAP(qubits[cycle[0]], qubits[idx]) def _apply_unitary_(self, args: 'cirq.ApplyUnitaryArgs'): # Compute the permutation index list. diff --git a/cirq-core/cirq/ops/phased_x_gate.py b/cirq-core/cirq/ops/phased_x_gate.py index 90e0cdbd4f9..26548a64b47 100644 --- a/cirq-core/cirq/ops/phased_x_gate.py +++ b/cirq-core/cirq/ops/phased_x_gate.py @@ -28,7 +28,7 @@ @value.value_equality(manual_cls=True, approximate=True) class PhasedXPowGate(raw_types.Gate): - r"""A gate equivalent to $Z^{p} X^t Z^{-p}$. + r"""A gate equivalent to $Z^{-p} X^t Z^{p}$ (in time order). The unitary matrix of `cirq.PhasedXPowGate(exponent=t, phase_exponent=p)` is: $$ diff --git a/cirq-core/cirq/ops/phased_x_z_gate.py b/cirq-core/cirq/ops/phased_x_z_gate.py index 4a511025a21..20711939fd3 100644 --- a/cirq-core/cirq/ops/phased_x_z_gate.py +++ b/cirq-core/cirq/ops/phased_x_z_gate.py @@ -27,7 +27,7 @@ @value.value_equality(approximate=True) class PhasedXZGate(raw_types.Gate): - r"""A single qubit gate equivalent to the circuit $Z^z Z^{a} X^x Z^{-a}$. + r"""A single qubit gate equivalent to the circuit $Z^{-a} X^x Z^{a} Z^z$ (in time order). The unitary matrix of `cirq.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a)` is: $$ @@ -67,6 +67,22 @@ def __init__( self._z_exponent = z_exponent self._axis_phase_exponent = axis_phase_exponent + @classmethod + def from_zyz_angles(cls, z0_rad: float, y_rad: float, z1_rad: float) -> 'cirq.PhasedXZGate': + """Create a PhasedXZGate from ZYZ angles. + + The returned gate is equivalent to $Rz(z0_rad) Ry(y_rad) Rz(z1_rad)$ (in time order). + """ + return cls.from_zyz_exponents(z0=z0_rad / np.pi, y=y_rad / np.pi, z1=z1_rad / np.pi) + + @classmethod + def from_zyz_exponents(cls, z0: float, y: float, z1: float) -> 'cirq.PhasedXZGate': + """Create a PhasedXZGate from ZYZ exponents. + + The returned gate is equivalent to $Z^z0 Y^y Z^z1$ (in time order). + """ + return PhasedXZGate(axis_phase_exponent=-z0 + 0.5, x_exponent=y, z_exponent=z0 + z1) + def _canonical(self) -> 'cirq.PhasedXZGate': x = self.x_exponent z = self.z_exponent diff --git a/cirq-core/cirq/ops/phased_x_z_gate_test.py b/cirq-core/cirq/ops/phased_x_z_gate_test.py index c91af8a3b1d..037c56fcc7b 100644 --- a/cirq-core/cirq/ops/phased_x_z_gate_test.py +++ b/cirq-core/cirq/ops/phased_x_z_gate_test.py @@ -34,6 +34,30 @@ def test_eq(): eq.add_equality_group(cirq.PhasedXZGate(x_exponent=1, z_exponent=0, axis_phase_exponent=0)) +@pytest.mark.parametrize('z0_rad', [-np.pi / 5, 0, np.pi / 5, np.pi / 4, np.pi / 2, np.pi]) +@pytest.mark.parametrize('y_rad', [0, np.pi / 5, np.pi / 4, np.pi / 2, np.pi]) +@pytest.mark.parametrize('z1_rad', [-np.pi / 5, 0, np.pi / 5, np.pi / 4, np.pi / 2, np.pi]) +def test_from_zyz_angles(z0_rad: float, y_rad: float, z1_rad: float) -> None: + q = cirq.q(0) + phxz = cirq.PhasedXZGate.from_zyz_angles(z0_rad, y_rad, z1_rad) + zyz = cirq.Circuit(cirq.rz(z0_rad).on(q), cirq.ry(y_rad).on(q), cirq.rz(z1_rad).on(q)) + cirq.testing.assert_allclose_up_to_global_phase( + cirq.unitary(phxz), cirq.unitary(zyz), atol=1e-8 + ) + + +@pytest.mark.parametrize('z0', [-0.2, 0, 0.2, 0.25, 0.5, 1]) +@pytest.mark.parametrize('y', [0, 0.2, 0.25, 0.5, 1]) +@pytest.mark.parametrize('z1', [-0.2, 0, 0.2, 0.25, 0.5, 1]) +def test_from_zyz_exponents(z0: float, y: float, z1: float) -> None: + q = cirq.q(0) + phxz = cirq.PhasedXZGate.from_zyz_exponents(z0, y, z1) + zyz = cirq.Circuit(cirq.Z(q) ** z0, cirq.Y(q) ** y, cirq.Z(q) ** z1) + cirq.testing.assert_allclose_up_to_global_phase( + cirq.unitary(phxz), cirq.unitary(zyz), atol=1e-8 + ) + + def test_canonicalization(): def f(x, z, a): return cirq.PhasedXZGate(x_exponent=x, z_exponent=z, axis_phase_exponent=a) diff --git a/cirq-core/cirq/ops/raw_types.py b/cirq-core/cirq/ops/raw_types.py index 64bf87ae5bb..d15cfd0dcbb 100644 --- a/cirq-core/cirq/ops/raw_types.py +++ b/cirq-core/cirq/ops/raw_types.py @@ -1032,6 +1032,9 @@ def _circuit_diagram_info_(self, args: 'cirq.CircuitDiagramInfoArgs'): def __repr__(self) -> str: return f'({self._original!r}**-1)' + def __str__(self) -> str: + return f'{self._original!s}†' + def _validate_qid_shape(val: Any, qubits: Sequence['cirq.Qid']) -> None: """Helper function to validate qubits for gates and operations. diff --git a/cirq-core/cirq/ops/raw_types_test.py b/cirq-core/cirq/ops/raw_types_test.py index a8b464f58ea..34804c95fdf 100644 --- a/cirq-core/cirq/ops/raw_types_test.py +++ b/cirq-core/cirq/ops/raw_types_test.py @@ -759,6 +759,7 @@ def __repr__(self): assert cirq.parameter_names(g) == {'a'} assert cirq.resolve_parameters(g, {a: 0}) == Gate(0) ** -1 cirq.testing.assert_implements_consistent_protocols(g, global_vals={'C': Gate, 'a': a}) + assert str(g) == 'C(a)†' def test_tagged_act_on(): diff --git a/cirq-core/cirq/ops/uniform_superposition_gate.py b/cirq-core/cirq/ops/uniform_superposition_gate.py new file mode 100644 index 00000000000..87349482704 --- /dev/null +++ b/cirq-core/cirq/ops/uniform_superposition_gate.py @@ -0,0 +1,123 @@ +# Copyright 2024 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 Sequence, Any, Dict, TYPE_CHECKING + +import numpy as np +from cirq.ops.common_gates import H, ry +from cirq.ops.pauli_gates import X +from cirq.ops import raw_types + + +if TYPE_CHECKING: + import cirq + + +class UniformSuperpositionGate(raw_types.Gate): + r"""Creates a uniform superposition state on the states $[0, M)$ + The gate creates the state $\frac{1}{\sqrt{M}}\sum_{j=0}^{M-1}\ket{j}$ + (where $1\leq M \leq 2^n$), using n qubits, according to the Shukla-Vedula algorithm [SV24]. + References: + [SV24] + [An efficient quantum algorithm for preparation of uniform quantum superposition + states](https://arxiv.org/abs/2306.11747) + """ + + def __init__(self, m_value: int, num_qubits: int) -> None: + """Initializes UniformSuperpositionGate. + + Args: + m_value: The number of computational basis states. + num_qubits: The number of qubits used. + + Raises: + ValueError: If `m_value` is not a positive integer, or + if `num_qubits` is not an integer greater than or equal to log2(m_value). + """ + if not (isinstance(m_value, int) and (m_value > 0)): + raise ValueError("m_value must be a positive integer.") + log_two_m_value = m_value.bit_length() + + if (m_value & (m_value - 1)) == 0: + log_two_m_value = log_two_m_value - 1 + if not (isinstance(num_qubits, int) and (num_qubits >= log_two_m_value)): + raise ValueError( + "num_qubits must be an integer greater than or equal to log2(m_value)." + ) + self._m_value = m_value + self._num_qubits = num_qubits + + def _decompose_(self, qubits: Sequence["cirq.Qid"]) -> "cirq.OP_TREE": + """Decomposes the gate into a sequence of standard gates. + Implements the construction from https://arxiv.org/pdf/2306.11747. + """ + qreg = list(qubits) + qreg.reverse() + + if self._m_value == 1: # if m_value is 1, do nothing + return + if (self._m_value & (self._m_value - 1)) == 0: # if m_value is an integer power of 2 + m = self._m_value.bit_length() - 1 + yield H.on_each(qreg[:m]) + return + k = self._m_value.bit_length() + l_value = [] + for i in range(self._m_value.bit_length()): + if (self._m_value >> i) & 1: + l_value.append(i) # Locations of '1's + + yield X.on_each(qreg[q_bit] for q_bit in l_value[1:k]) + m_current = 2 ** (l_value[0]) + theta = -2 * np.arccos(np.sqrt(m_current / self._m_value)) + if l_value[0] > 0: # if m_value is even + yield H.on_each(qreg[: l_value[0]]) + + yield ry(theta).on(qreg[l_value[1]]) + + for i in range(l_value[0], l_value[1]): + yield H(qreg[i]).controlled_by(qreg[l_value[1]], control_values=[False]) + + for m in range(1, len(l_value) - 1): + theta = -2 * np.arccos(np.sqrt(2 ** l_value[m] / (self._m_value - m_current))) + yield ry(theta).on(qreg[l_value[m + 1]]).controlled_by( + qreg[l_value[m]], control_values=[0] + ) + for i in range(l_value[m], l_value[m + 1]): + yield H.on(qreg[i]).controlled_by(qreg[l_value[m + 1]], control_values=[0]) + + m_current = m_current + 2 ** (l_value[m]) + + def num_qubits(self) -> int: + return self._num_qubits + + @property + def m_value(self) -> int: + return self._m_value + + def __eq__(self, other): + if isinstance(other, UniformSuperpositionGate): + return (self._m_value == other._m_value) and (self._num_qubits == other._num_qubits) + return False + + def __repr__(self) -> str: + return f'UniformSuperpositionGate(m_value={self._m_value}, num_qubits={self._num_qubits})' + + def _json_dict_(self) -> Dict[str, Any]: + d = {} + d['m_value'] = self._m_value + d['num_qubits'] = self._num_qubits + return d + + def __str__(self) -> str: + return f'UniformSuperpositionGate(m_value={self._m_value}, num_qubits={self._num_qubits})' diff --git a/cirq-core/cirq/ops/uniform_superposition_gate_test.py b/cirq-core/cirq/ops/uniform_superposition_gate_test.py new file mode 100644 index 00000000000..6f3e472d19a --- /dev/null +++ b/cirq-core/cirq/ops/uniform_superposition_gate_test.py @@ -0,0 +1,94 @@ +# Copyright 2024 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +import numpy as np +import pytest +import cirq + + +@pytest.mark.parametrize( + ["m", "n"], + [[int(m), n] for n in range(3, 7) for m in np.random.randint(1, 1 << n, size=3)] + + [(1, 2), (4, 2), (6, 3), (7, 3)], +) +def test_generated_unitary_is_uniform(m: int, n: int) -> None: + r"""The code checks that the unitary matrix corresponds to the generated uniform superposition + states (see uniform_superposition_gate.py). It is enough to check that the + first colum of the unitary matrix (which corresponds to the action of the gate on + $\ket{0}^n$ is $\frac{1}{\sqrt{M}} [1 1 \cdots 1 0 \cdots 0]^T$, where the first $M$ + entries are all "1"s (excluding the normalization factor of $\frac{1}{\sqrt{M}}$ and the + remaining $2^n-M$ entries are all "0"s. + """ + gate = cirq.UniformSuperpositionGate(m, n) + matrix = np.array(cirq.unitary(gate)) + np.testing.assert_allclose( + matrix[:, 0], (1 / np.sqrt(m)) * np.array([1] * m + [0] * (2**n - m)), atol=1e-8 + ) + + +@pytest.mark.parametrize(["m", "n"], [(1, 1), (-2, 1), (-3.1, 2), (6, -4), (5, 6.1)]) +def test_incompatible_m_value_and_qubit_args(m: int, n: int) -> None: + r"""The code checks that test errors are raised if the arguments m (number of + superposition states and n (number of qubits) are positive integers and are compatible + (i.e., n >= log2(m)). + """ + + if not (isinstance(m, int)): + with pytest.raises(ValueError, match="m_value must be a positive integer."): + cirq.UniformSuperpositionGate(m, n) + elif not (isinstance(n, int)): + with pytest.raises( + ValueError, + match="num_qubits must be an integer greater than or equal to log2\\(m_value\\).", + ): + cirq.UniformSuperpositionGate(m, n) + elif m < 1: + with pytest.raises(ValueError, match="m_value must be a positive integer."): + cirq.UniformSuperpositionGate(int(m), int(n)) + elif n < np.log2(m): + with pytest.raises( + ValueError, + match="num_qubits must be an integer greater than or equal to log2\\(m_value\\).", + ): + cirq.UniformSuperpositionGate(m, n) + + +def test_repr(): + assert ( + repr(cirq.UniformSuperpositionGate(7, 3)) + == 'UniformSuperpositionGate(m_value=7, num_qubits=3)' + ) + + +def test_uniform_superposition_gate_json_dict(): + assert cirq.UniformSuperpositionGate(7, 3)._json_dict_() == {'m_value': 7, 'num_qubits': 3} + + +def test_str(): + assert ( + str(cirq.UniformSuperpositionGate(7, 3)) + == 'UniformSuperpositionGate(m_value=7, num_qubits=3)' + ) + + +@pytest.mark.parametrize(["m", "n"], [(5, 3), (10, 4)]) +def test_eq(m: int, n: int) -> None: + a = cirq.UniformSuperpositionGate(m, n) + b = cirq.UniformSuperpositionGate(m, n) + c = cirq.UniformSuperpositionGate(m + 1, n) + d = cirq.X + assert a.m_value == b.m_value + assert a.__eq__(b) + assert not (a.__eq__(c)) + assert not (a.__eq__(d)) diff --git a/cirq-core/cirq/protocols/json_test_data/UniformSuperpositionGate.json b/cirq-core/cirq/protocols/json_test_data/UniformSuperpositionGate.json new file mode 100644 index 00000000000..52203d8538e --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/UniformSuperpositionGate.json @@ -0,0 +1,5 @@ +{ + "cirq_type": "UniformSuperpositionGate", + "m_value": 7, + "num_qubits": 3 +} diff --git a/cirq-core/cirq/protocols/json_test_data/UniformSuperpositionGate.repr b/cirq-core/cirq/protocols/json_test_data/UniformSuperpositionGate.repr new file mode 100644 index 00000000000..62b2bdac0f2 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/UniformSuperpositionGate.repr @@ -0,0 +1 @@ + cirq.UniformSuperpositionGate(m_value=7, num_qubits=3) diff --git a/cirq-core/cirq/sim/classical_simulator.py b/cirq-core/cirq/sim/classical_simulator.py index a5287637bfc..02879e518a1 100644 --- a/cirq-core/cirq/sim/classical_simulator.py +++ b/cirq-core/cirq/sim/classical_simulator.py @@ -117,12 +117,25 @@ def _act_on_fallback_(self, action, qubits: Sequence['cirq.Qid'], allow_decompos Raises: ValueError: If initial_state shape for type np.ndarray is not equal to 1. - If gate is not one of X, CNOT, SWAP, CCNOT, or a measurement. + If gate is not one of X, SWAP, a controlled version of X or SWAP, + or a measurement. """ if isinstance(self._state.basis, np.ndarray) and len(self._state.basis.shape) != 1: raise ValueError('initial_state shape for type np.ndarray is not equal to 1') gate = action.gate if isinstance(action, ops.Operation) else action mapped_qubits = [self.qubit_map[i] for i in qubits] + + if isinstance(gate, ops.ControlledGate): + control_qubits = mapped_qubits[: gate.num_controls()] + mapped_qubits = mapped_qubits[gate.num_controls() :] + + controls_state = tuple(self._state.basis[c] for c in control_qubits) + if controls_state not in gate.control_values.expand(): + # gate has no effect; controls were off + return True + + gate = gate.sub_gate + if _is_identity(gate): pass elif gate == ops.X: @@ -138,7 +151,10 @@ def _act_on_fallback_(self, action, qubits: Sequence['cirq.Qid'], allow_decompos c1, c2, q = mapped_qubits self._state.basis[q] ^= self._state.basis[c1] & self._state.basis[c2] else: - raise ValueError(f'{gate} is not one of X, CNOT, SWAP, CCNOT, or a measurement') + raise ValueError( + f'{gate} is not one of X, SWAP; a controlled version ' + 'of X or SWAP; or a measurement' + ) return True diff --git a/cirq-core/cirq/sim/classical_simulator_test.py b/cirq-core/cirq/sim/classical_simulator_test.py index 3cf8c170bd8..96c8a4afdc0 100644 --- a/cirq-core/cirq/sim/classical_simulator_test.py +++ b/cirq-core/cirq/sim/classical_simulator_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from itertools import product import numpy as np import pytest import cirq @@ -78,6 +79,43 @@ def test_CCNOT(): np.testing.assert_equal(results, expected_results) +@pytest.mark.parametrize(['initial_state'], [(list(x),) for x in product([0, 1], repeat=4)]) +def test_CCCX(initial_state): + CCCX = cirq.CCNOT.controlled() + qubits = cirq.LineQubit.range(4) + + circuit = cirq.Circuit() + circuit.append(CCCX(*qubits)) + circuit.append(cirq.measure(qubits, key='key')) + + final_state = initial_state.copy() + final_state[-1] ^= all(final_state[:-1]) + + sim = cirq.ClassicalStateSimulator() + results = sim.simulate(circuit, initial_state=initial_state).measurements['key'] + np.testing.assert_equal(results, final_state) + + +@pytest.mark.parametrize(['initial_state'], [(list(x),) for x in product([0, 1], repeat=3)]) +def test_CSWAP(initial_state): + CSWAP = cirq.SWAP.controlled() + qubits = cirq.LineQubit.range(3) + circuit = cirq.Circuit() + + circuit = cirq.Circuit() + circuit.append(CSWAP(*qubits)) + circuit.append(cirq.measure(qubits, key='key')) + + a, b, c = initial_state + if a: + b, c = c, b + final_state = [a, b, c] + + sim = cirq.ClassicalStateSimulator() + results = sim.simulate(circuit, initial_state=initial_state).measurements['key'] + np.testing.assert_equal(results, final_state) + + def test_measurement_gate(): q0, q1 = cirq.LineQubit.range(2) circuit = cirq.Circuit() diff --git a/cirq-core/cirq/sim/density_matrix_simulator_test.py b/cirq-core/cirq/sim/density_matrix_simulator_test.py index 119bc3f1830..d0272f33c38 100644 --- a/cirq-core/cirq/sim/density_matrix_simulator_test.py +++ b/cirq-core/cirq/sim/density_matrix_simulator_test.py @@ -402,10 +402,10 @@ def test_run_param_resolver(dtype: Type[np.complexfloating], split: bool): cirq.measure(q1), ) param_resolver = {'b0': b0, 'b1': b1} - result = simulator.run(circuit, param_resolver=param_resolver) # type: ignore + result = simulator.run(circuit, param_resolver=param_resolver) np.testing.assert_equal(result.measurements, {'q(0)': [[b0]], 'q(1)': [[b1]]}) # pylint: disable=line-too-long - np.testing.assert_equal(result.params, cirq.ParamResolver(param_resolver)) # type: ignore + np.testing.assert_equal(result.params, cirq.ParamResolver(param_resolver)) @pytest.mark.parametrize('dtype', [np.complex64, np.complex128]) diff --git a/cirq-core/cirq/sim/sparse_simulator_test.py b/cirq-core/cirq/sim/sparse_simulator_test.py index a769be3028d..d07e95ecd86 100644 --- a/cirq-core/cirq/sim/sparse_simulator_test.py +++ b/cirq-core/cirq/sim/sparse_simulator_test.py @@ -498,11 +498,11 @@ def test_simulate_param_resolver(dtype: Type[np.complexfloating], split: bool): (cirq.X ** sympy.Symbol('b0'))(q0), (cirq.X ** sympy.Symbol('b1'))(q1) ) resolver = {'b0': b0, 'b1': b1} - result = simulator.simulate(circuit, param_resolver=resolver) # type: ignore + result = simulator.simulate(circuit, param_resolver=resolver) expected_state = np.zeros(shape=(2, 2)) expected_state[b0][b1] = 1.0 np.testing.assert_equal(result.final_state_vector, np.reshape(expected_state, 4)) - assert result.params == cirq.ParamResolver(resolver) # type: ignore + assert result.params == cirq.ParamResolver(resolver) assert len(result.measurements) == 0 diff --git a/cirq-core/cirq/study/flatten_expressions.py b/cirq-core/cirq/study/flatten_expressions.py index d532b4bcf09..5000880e72a 100644 --- a/cirq-core/cirq/study/flatten_expressions.py +++ b/cirq-core/cirq/study/flatten_expressions.py @@ -225,8 +225,7 @@ def __init__( params = param_dict if param_dict else {} # TODO: Support complex values for typing below. symbol_params: resolver.ParamDictType = { - _ensure_not_str(param): _ensure_not_str(val) # type: ignore[misc] - for param, val in params.items() + _ensure_not_str(param): _ensure_not_str(val) for param, val in params.items() } super().__init__(symbol_params) if get_param_name is None: diff --git a/cirq-core/cirq/study/resolver.py b/cirq-core/cirq/study/resolver.py index b66c31ac884..f9bd4e52b41 100644 --- a/cirq-core/cirq/study/resolver.py +++ b/cirq-core/cirq/study/resolver.py @@ -139,10 +139,10 @@ def value_of( if isinstance(param_value, str): param_value = sympy.Symbol(param_value) elif not isinstance(param_value, sympy.Basic): - return value # type: ignore[return-value] + return value if recursive: param_value = self._value_of_recursive(value) - return param_value # type: ignore[return-value] + return param_value if not isinstance(value, sympy.Basic): # No known way to resolve this variable, return unchanged. @@ -207,7 +207,7 @@ def _value_of_recursive(self, value: 'cirq.TParamKey') -> 'cirq.TParamValComplex # There isn't a full evaluation for 'value' yet. Until it's ready, # map value to None to identify loops in component evaluation. - self._deep_eval_map[value] = _RECURSION_FLAG # type: ignore + self._deep_eval_map[value] = _RECURSION_FLAG v = self.value_of(value, recursive=False) if v == value: @@ -220,10 +220,8 @@ def _resolve_parameters_(self, resolver: 'ParamResolver', recursive: bool) -> 'P new_dict: Dict['cirq.TParamKey', Union[float, str, sympy.Symbol, sympy.Expr]] = { k: k for k in resolver } - new_dict.update({k: self.value_of(k, recursive) for k in self}) # type: ignore[misc] - new_dict.update( - {k: resolver.value_of(v, recursive) for k, v in new_dict.items()} # type: ignore[misc] - ) + new_dict.update({k: self.value_of(k, recursive) for k in self}) + new_dict.update({k: resolver.value_of(v, recursive) for k, v in new_dict.items()}) if recursive and self._param_dict: new_resolver = ParamResolver(cast(ParamDictType, new_dict)) # Resolve down to single-step mappings. diff --git a/cirq-core/cirq/study/sweepable.py b/cirq-core/cirq/study/sweepable.py index dd3356b212d..6ae9dcb681f 100644 --- a/cirq-core/cirq/study/sweepable.py +++ b/cirq-core/cirq/study/sweepable.py @@ -14,7 +14,7 @@ """Defines which types are Sweepable.""" -from typing import Iterable, Iterator, List, Sequence, Union, cast +from typing import Iterable, Iterator, List, Optional, Sequence, Union, cast import warnings from typing_extensions import Protocol @@ -44,12 +44,12 @@ def to_resolvers(sweepable: Sweepable) -> Iterator[ParamResolver]: yield from sweep -def to_sweeps(sweepable: Sweepable) -> List[Sweep]: +def to_sweeps(sweepable: Sweepable, metadata: Optional[dict] = None) -> List[Sweep]: """Converts a Sweepable to a list of Sweeps.""" if sweepable is None: return [UnitSweep] if isinstance(sweepable, ParamResolver): - return [_resolver_to_sweep(sweepable)] + return [_resolver_to_sweep(sweepable, metadata)] if isinstance(sweepable, Sweep): return [sweepable] if isinstance(sweepable, dict): @@ -63,9 +63,9 @@ def to_sweeps(sweepable: Sweepable) -> List[Sweep]: stacklevel=2, ) product_sweep = dict_to_product_sweep(sweepable) - return [_resolver_to_sweep(resolver) for resolver in product_sweep] + return [_resolver_to_sweep(resolver, metadata) for resolver in product_sweep] if isinstance(sweepable, Iterable) and not isinstance(sweepable, str): - return [sweep for item in sweepable for sweep in to_sweeps(item)] # type: ignore[arg-type] + return [sweep for item in sweepable for sweep in to_sweeps(item, metadata)] raise TypeError(f'Unrecognized sweepable type: {type(sweepable)}.\nsweepable: {sweepable}') @@ -98,8 +98,13 @@ def to_sweep( raise TypeError(f'Unexpected sweep-like value: {sweep_or_resolver_list}') -def _resolver_to_sweep(resolver: ParamResolver) -> Sweep: +def _resolver_to_sweep(resolver: ParamResolver, metadata: Optional[dict]) -> Sweep: params = resolver.param_dict if not params: return UnitSweep - return Zip(*[Points(key, [cast(float, value)]) for key, value in params.items()]) + return Zip( + *[ + Points(key, [cast(float, value)], metadata=metadata.get(key) if metadata else None) + for key, value in params.items() + ] + ) diff --git a/cirq-core/cirq/study/sweepable_test.py b/cirq-core/cirq/study/sweepable_test.py index c09ef35c7e1..1aadcb65b34 100644 --- a/cirq-core/cirq/study/sweepable_test.py +++ b/cirq-core/cirq/study/sweepable_test.py @@ -147,3 +147,30 @@ def test_to_sweep_resolver_list(r_list_gen): def test_to_sweep_type_error(): with pytest.raises(TypeError, match='Unexpected sweep'): cirq.to_sweep(5) + + +def test_to_sweeps_with_param_dict_appends_metadata(): + params = {'a': 1, 'b': 2, 'c': 3} + unit_map = {'a': 'ns', 'b': 'ns'} + + sweep = cirq.to_sweeps(params, unit_map) + + assert sweep == [ + cirq.Zip( + cirq.Points('a', [1], metadata='ns'), + cirq.Points('b', [2], metadata='ns'), + cirq.Points('c', [3]), + ) + ] + + +def test_to_sweeps_with_param_list_appends_metadata(): + resolvers = [cirq.ParamResolver({'a': 2}), cirq.ParamResolver({'a': 1})] + unit_map = {'a': 'ns'} + + sweeps = cirq.study.to_sweeps(resolvers, unit_map) + + assert sweeps == [ + cirq.Zip(cirq.Points('a', [2], metadata='ns')), + cirq.Zip(cirq.Points('a', [1], metadata='ns')), + ] diff --git a/cirq-core/cirq/study/sweeps.py b/cirq-core/cirq/study/sweeps.py index 3f98798f602..dc67bb02721 100644 --- a/cirq-core/cirq/study/sweeps.py +++ b/cirq-core/cirq/study/sweeps.py @@ -589,10 +589,7 @@ def dict_to_product_sweep(factor_dict: ProductOrZipSweepLike) -> Product: Cartesian product of the sweeps. """ return Product( - *( - Points(k, v if isinstance(v, Sequence) else [v]) # type: ignore - for k, v in factor_dict.items() - ) + *(Points(k, v if isinstance(v, Sequence) else [v]) for k, v in factor_dict.items()) ) diff --git a/cirq-core/cirq/testing/lin_alg_utils.py b/cirq-core/cirq/testing/lin_alg_utils.py index f1228ecd011..ad005fd0e6d 100644 --- a/cirq-core/cirq/testing/lin_alg_utils.py +++ b/cirq-core/cirq/testing/lin_alg_utils.py @@ -133,7 +133,8 @@ def random_special_unitary( The sampled special unitary. """ r = random_unitary(dim, random_state=random_state) - r[0, :] /= np.linalg.det(r) + with np.errstate(divide="ignore", invalid="ignore"): + r[0, :] /= np.linalg.det(r) return r @@ -152,8 +153,9 @@ def random_special_orthogonal( The sampled special orthogonal matrix. """ m = random_orthogonal(dim, random_state=random_state) - if np.linalg.det(m) < 0: - m[0, :] *= -1 + with np.errstate(divide="ignore", invalid="ignore"): + if np.linalg.det(m) < 0: + m[0, :] *= -1 return m diff --git a/cirq-core/cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py b/cirq-core/cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py index af01e926499..64ec15dafe3 100644 --- a/cirq-core/cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py +++ b/cirq-core/cirq/transformers/analytical_decompositions/controlled_gate_decomposition.py @@ -47,7 +47,9 @@ def _decompose_abc(matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarr See [1], chapter 4. """ assert matrix.shape == (2, 2) - delta = np.angle(np.linalg.det(matrix)) * 0.5 + with np.errstate(divide="ignore", invalid="ignore"): + # On MacOS, np.linalg.det emits superflous warnings + delta = np.angle(np.linalg.det(matrix)) * 0.5 alpha = np.angle(matrix[0, 0]) + np.angle(matrix[0, 1]) - 2 * delta beta = np.angle(matrix[0, 0]) - np.angle(matrix[0, 1]) diff --git a/cirq-core/cirq/transformers/target_gatesets/compilation_target_gateset.py b/cirq-core/cirq/transformers/target_gatesets/compilation_target_gateset.py index 2396e8b0e02..59c3f11aca4 100644 --- a/cirq-core/cirq/transformers/target_gatesets/compilation_target_gateset.py +++ b/cirq-core/cirq/transformers/target_gatesets/compilation_target_gateset.py @@ -76,7 +76,7 @@ def transformer_with_kwargs( class CompilationTargetGateset(ops.Gateset, metaclass=abc.ABCMeta): """Abstract base class to create gatesets that can be used as targets for compilation. - An instance of this type can be passed to transformers like `cirq.convert_to_target_gateset`, + An instance of this type can be passed to transformers like `cirq.optimize_for_target_gateset`, which can transform any given circuit to contain gates accepted by this gateset. """ diff --git a/cirq-core/cirq/vis/heatmap.py b/cirq-core/cirq/vis/heatmap.py index e672a2b8c27..e496bacc014 100644 --- a/cirq-core/cirq/vis/heatmap.py +++ b/cirq-core/cirq/vis/heatmap.py @@ -111,6 +111,7 @@ def __init__( applying format(value, annotation_format) for each key in value_map. This is ignored if annotation_map is explicitly specified. annotation_text_kwargs: Matplotlib Text **kwargs, + highlighted_qubits: An iterable of qubits to highlight. colorbar_position: {'right', 'left', 'top', 'bottom'}, default = 'right' colorbar_size: str, default = '5%' @@ -157,6 +158,7 @@ def _validate_kwargs(self, kwargs) -> None: "annotation_map", "annotation_text_kwargs", "annotation_format", + "highlighted_qubits", ] valid_kwargs = ( valid_colorbar_kwargs @@ -296,9 +298,39 @@ def plot( show_plot = not ax if not ax: fig, ax = plt.subplots(figsize=(8, 8)) - ax = cast(plt.Axes, ax) original_config = copy.deepcopy(self._config) self.update_config(**kwargs) + + highlighted_qubits = frozenset(kwargs.get("highlighted_qubits", ())) + if highlighted_qubits: + edgecolors = tuple( + ( + "red" + if not highlighted_qubits.isdisjoint(qubits) + else self._config["collection_options"].get("edgecolors", "grey") + ) + for qubits in sorted(self._value_map.keys()) + ) + linestyles = tuple( + ( + "solid" + if not highlighted_qubits.isdisjoint(qubits) + else self._config["collection_options"].get("linestyles", "dashed") + ) + for qubits in sorted(self._value_map.keys()) + ) + linewidths = tuple( + ( + 4 + if not highlighted_qubits.isdisjoint(qubits) + else self._config["collection_options"].get("linewidths", 2) + ) + for qubits in sorted(self._value_map.keys()) + ) + self._config["collection_options"].update( + {"edgecolors": edgecolors, "linestyles": linestyles, "linewidths": linewidths} + ) + collection = self._plot_on_axis(ax) if show_plot: fig.show() @@ -383,20 +415,21 @@ def plot( show_plot = not ax if not ax: fig, ax = plt.subplots(figsize=(8, 8)) - ax = cast(plt.Axes, ax) original_config = copy.deepcopy(self._config) self.update_config(**kwargs) qubits = set([q for qubits in self._value_map.keys() for q in qubits]) + collection_options: Dict[str, Any] = {"cmap": "binary"} + highlighted_qubits = frozenset(kwargs.get("highlighted_qubits", ())) + if not highlighted_qubits: + collection_options.update( + {"linewidths": 2, "edgecolors": "lightgrey", "linestyles": "dashed"} + ) Heatmap({q: 0.0 for q in qubits}).plot( ax=ax, - collection_options={ - 'cmap': 'binary', - 'linewidths': 2, - 'edgecolor': 'lightgrey', - 'linestyle': 'dashed', - }, + collection_options=collection_options, plot_colorbar=False, annotation_format=None, + highlighted_qubits=highlighted_qubits, ) collection = self._plot_on_axis(ax) if show_plot: diff --git a/cirq-core/cirq/vis/heatmap_test.py b/cirq-core/cirq/vis/heatmap_test.py index dceb00cff1c..396af208e37 100644 --- a/cirq-core/cirq/vis/heatmap_test.py +++ b/cirq-core/cirq/vis/heatmap_test.py @@ -23,6 +23,7 @@ import matplotlib as mpl import matplotlib.pyplot as plt +from matplotlib.colors import to_rgba_array from cirq.devices import grid_qubit from cirq.vis import heatmap @@ -34,6 +35,11 @@ def ax(): return figure.add_subplot(111) +def _to_linestyle_tuple(linestyles, linewidths=None): + collection = mpl.collections.Collection(linestyles=linestyles, linewidths=linewidths) + return collection.get_linestyles()[0] + + def test_default_ax(): row_col_list = ((0, 5), (8, 1), (7, 0), (13, 5), (1, 6), (3, 2), (2, 8)) test_value_map = { @@ -343,3 +349,162 @@ def test_plot_updates_local_config(): _, ax = plt.subplots() random_heatmap.plot(ax) assert ax.get_title() == original_title + + +@pytest.mark.usefixtures('closefigures') +def test_heatmap_plot_highlighted_qubits(): + value_map = { + (grid_qubit.GridQubit(0, 0),): 0.1, + (grid_qubit.GridQubit(0, 1),): 0.2, + (grid_qubit.GridQubit(0, 2),): 0.3, + (grid_qubit.GridQubit(1, 0),): 0.4, + } + single_qubit_heatmap = heatmap.Heatmap(value_map) + + highlighted_qubits = [grid_qubit.GridQubit(0, 1), grid_qubit.GridQubit(1, 0)] + + expected_linewidths = [2, 4, 2, 4] + expected_edgecolors = np.vstack( + (to_rgba_array("grey"), to_rgba_array("red"), to_rgba_array("grey"), to_rgba_array("red")) + ) + # list of tuples: (offset, onoffseq), onoffseq = None for solid line. + expected_linestyles = [ + _to_linestyle_tuple("dashed", linewidths=2), + _to_linestyle_tuple("solid"), + _to_linestyle_tuple("dashed", linewidths=2), + _to_linestyle_tuple("solid"), + ] + + _, ax = plt.subplots() + _ = single_qubit_heatmap.plot(ax, highlighted_qubits=highlighted_qubits) + + for artist in ax.get_children(): + if isinstance(artist, mpl.collections.PolyCollection): + assert np.all(artist.get_linewidths() == expected_linewidths) + assert np.array_equal(artist.get_edgecolors(), expected_edgecolors) + assert artist.get_linestyles() == expected_linestyles + + +@pytest.mark.usefixtures('closefigures') +def test_heatmap_plot_highlighted_qubits_two_qubit(): + value_map = { + (grid_qubit.GridQubit(0, 0), grid_qubit.GridQubit(0, 1)): 0.1, + (grid_qubit.GridQubit(0, 1), grid_qubit.GridQubit(0, 2)): 0.2, + (grid_qubit.GridQubit(1, 0), grid_qubit.GridQubit(0, 0)): 0.3, + (grid_qubit.GridQubit(3, 3), grid_qubit.GridQubit(3, 2)): 0.9, + } + two_qubit_interaction_heatmap = heatmap.TwoQubitInteractionHeatmap(value_map) + + highlighted_qubits = [ + grid_qubit.GridQubit(0, 1), + grid_qubit.GridQubit(0, 0), + grid_qubit.GridQubit(3, 3), + ] + + expected_linewidths = [4, 4, 2, 2, 2, 4] + expected_edgecolors = np.vstack( + ( + to_rgba_array("red"), + to_rgba_array("red"), + to_rgba_array("grey"), + to_rgba_array("grey"), + to_rgba_array("grey"), + to_rgba_array("red"), + ) + ) + # list of tuples: (offset, onoffseq), onoffseq = None for solid line. + expected_linestyles = [ + _to_linestyle_tuple("solid"), + _to_linestyle_tuple("solid"), + _to_linestyle_tuple("dashed", linewidths=2), + _to_linestyle_tuple("dashed", linewidths=2), + _to_linestyle_tuple("dashed", linewidths=2), + _to_linestyle_tuple("solid"), + ] + + _, ax = plt.subplots() + _ = two_qubit_interaction_heatmap.plot(ax, highlighted_qubits=highlighted_qubits) + + for artist in ax.get_children(): + if isinstance(artist, mpl.collections.PolyCollection): + # Since for two qubit interactions, there are two collections: + # one to highlight individual qubits and one showing their interaction. + # Here, the former is required, so the latter is excluded. + if artist.get_cmap().name != 'viridis': # assuming 'viridis' is the default cmap used. + assert np.all(artist.get_linewidths() == expected_linewidths) + assert np.array_equal(artist.get_edgecolors(), expected_edgecolors) + assert artist.get_linestyles() == expected_linestyles + + +@pytest.mark.usefixtures('closefigures') +def test_heatmap_highlighted_repeat_qubits(): + value_map = { + (grid_qubit.GridQubit(0, 0), grid_qubit.GridQubit(0, 1)): 0.1, + (grid_qubit.GridQubit(0, 1), grid_qubit.GridQubit(0, 2)): 0.2, + (grid_qubit.GridQubit(1, 0), grid_qubit.GridQubit(0, 0)): 0.3, + (grid_qubit.GridQubit(3, 3), grid_qubit.GridQubit(3, 2)): 0.9, + } + two_qubit_interaction_heatmap = heatmap.TwoQubitInteractionHeatmap(value_map) + + highlighted_qubits_1 = [ + grid_qubit.GridQubit(0, 1), + grid_qubit.GridQubit(0, 0), + grid_qubit.GridQubit(3, 3), + ] + highlighted_qubits_2 = highlighted_qubits_1 + [grid_qubit.GridQubit(0, 0)] * 5 + + _, ax1 = plt.subplots() + _ = two_qubit_interaction_heatmap.plot(ax1, highlighted_qubits=highlighted_qubits_1) + _, ax2 = plt.subplots() + _ = two_qubit_interaction_heatmap.plot(ax2, highlighted_qubits=highlighted_qubits_2) + + for artist_1, artist_2 in zip(ax1.get_children(), ax2.get_children()): + if isinstance(artist_1, mpl.collections.PolyCollection) and isinstance( + artist_2, mpl.collections.PolyCollection + ): + # Since for two qubit interactions, there are two collections: + # one to highlight individual qubits and one showing their interaction. + # Here, the former is required, so the latter is excluded. + if ( + artist_1.get_cmap().name != 'viridis' and artist_2.get_cmap().name != 'viridis' + ): # assuming 'viridis' is the default cmap used. + assert np.all(artist_1.get_linewidths() == artist_1.get_linewidths()) + assert np.array_equal(artist_1.get_edgecolors(), artist_2.get_edgecolors()) + assert artist_1.get_linestyles() == artist_2.get_linestyles() + + +@pytest.mark.usefixtures('closefigures') +def test_heatmap_highlighted_init_collection_options_used(): + value_map = { + (grid_qubit.GridQubit(0, 0),): 0.1, + (grid_qubit.GridQubit(0, 1),): 0.2, + (grid_qubit.GridQubit(0, 2),): 0.3, + (grid_qubit.GridQubit(1, 0),): 0.4, + } + single_qubit_heatmap = heatmap.Heatmap( + value_map, + collection_options={"edgecolors": "blue", "linewidths": 6, "linestyles": "dashed"}, + ) + + highlighted_qubits = [grid_qubit.GridQubit(0, 1), grid_qubit.GridQubit(1, 0)] + + expected_linewidths = [6, 4, 6, 4] + expected_edgecolors = np.vstack( + (to_rgba_array("blue"), to_rgba_array("red"), to_rgba_array("blue"), to_rgba_array("red")) + ) + # list of tuples: (offset, onoffseq), onoffseq = None for solid line. + expected_linestyles = [ + _to_linestyle_tuple("dashed", linewidths=6), + _to_linestyle_tuple("solid"), + _to_linestyle_tuple("dashed", linewidths=6), + _to_linestyle_tuple("solid"), + ] + + _, ax = plt.subplots() + _ = single_qubit_heatmap.plot(ax, highlighted_qubits=highlighted_qubits) + + for artist in ax.get_children(): + if isinstance(artist, mpl.collections.PolyCollection): + assert np.all(artist.get_linewidths() == expected_linewidths) + assert np.array_equal(artist.get_edgecolors(), expected_edgecolors) + assert artist.get_linestyles() == expected_linestyles diff --git a/cirq-core/cirq/vis/state_histogram.py b/cirq-core/cirq/vis/state_histogram.py index 3a3706cf04f..d2525c06687 100644 --- a/cirq-core/cirq/vis/state_histogram.py +++ b/cirq-core/cirq/vis/state_histogram.py @@ -14,7 +14,7 @@ """Tool to visualize the results of a study.""" -from typing import cast, Optional, Sequence, SupportsFloat, Union +from typing import Optional, Sequence, SupportsFloat, Union import collections import numpy as np import matplotlib.pyplot as plt @@ -87,7 +87,6 @@ def plot_state_histogram( show_fig = not ax if not ax: fig, ax = plt.subplots(1, 1) - ax = cast(plt.Axes, ax) if isinstance(data, result.Result): values = get_state_histogram(data) elif isinstance(data, collections.Counter): diff --git a/cirq-core/requirements.txt b/cirq-core/requirements.txt index ccba6c10b72..b526c835455 100644 --- a/cirq-core/requirements.txt +++ b/cirq-core/requirements.txt @@ -1,13 +1,13 @@ # Runtime requirements for the python 3 version of cirq. -attrs +attrs>=21.3.0 duet>=0.2.8 matplotlib~=3.0 networkx>=2.4 numpy~=1.22 pandas sortedcontainers~=2.0 -scipy<1.13.0 +scipy~=1.0 sympy typing_extensions>=4.2 tqdm diff --git a/cirq-ft/LICENSE b/cirq-ft/LICENSE deleted file mode 100644 index 8dada3edaf5..00000000000 --- a/cirq-ft/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - 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. diff --git a/cirq-ft/README.rst b/cirq-ft/README.rst deleted file mode 100644 index 0c9edcf8be0..00000000000 --- a/cirq-ft/README.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. image:: https://raw.githubusercontent.com/quantumlib/Cirq/main/docs/images/Cirq_logo_color.png - :target: https://github.com/quantumlib/cirq - :alt: cirq-ft - :width: 500px - -Cirq-FT: Cirq for Fault-Tolerant algorithms -------------------------------------------- - -Cirq-FT is a Python library for rapid prototyping and resource estimation of fault tolerant -algorithms that extends the quantum computing SDK **Cirq**. - -Installation ------------- -Cirq-FT is currently in beta mode and only available as a pre-release. -To install the pre-release version of **cirq-ft**, use - -.. code-block:: bash - - pip install cirq-ft~=1.0.dev - - - -Note, that this will install both cirq-ft and cirq-core as well. - -To get all the optional **Cirq** modules installed as well, use `pip install cirq` or -`pip install cirq~=1.0.dev` for the pre-release version. diff --git a/cirq-ft/cirq_ft/__init__.py b/cirq-ft/cirq_ft/__init__.py deleted file mode 100644 index 938720ba693..00000000000 --- a/cirq-ft/cirq_ft/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 cirq_ft._version import __version__ -from cirq_ft.algos import ( - QROM, - AdditionGate, - AddMod, - And, - ApplyGateToLthQubit, - ContiguousRegisterGate, - GenericSelect, - LessThanEqualGate, - LessThanGate, - MultiControlPauli, - MultiTargetCNOT, - MultiTargetCSwap, - MultiTargetCSwapApprox, - PrepareHubbard, - PrepareOracle, - PrepareUniformSuperposition, - ProgrammableRotationGateArray, - ProgrammableRotationGateArrayBase, - QubitizationWalkOperator, - ReflectionUsingPrepare, - SelectedMajoranaFermionGate, - SelectHubbard, - SelectOracle, - SelectSwapQROM, - StatePreparationAliasSampling, - SwapWithZeroGate, - UnaryIterationGate, - unary_iteration, -) -from cirq_ft.infra import ( - GateWithRegisters, - Register, - Signature, - SelectionRegister, - TComplexity, - map_clean_and_borrowable_qubits, - t_complexity, - testing, -) diff --git a/cirq-ft/cirq_ft/_version.py b/cirq-ft/cirq_ft/_version.py deleted file mode 100644 index de17f28f3f7..00000000000 --- a/cirq-ft/cirq_ft/_version.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -"""Define version number here, read it from setup.py automatically""" - -__version__ = "1.4.0.dev" diff --git a/cirq-ft/cirq_ft/_version_test.py b/cirq-ft/cirq_ft/_version_test.py deleted file mode 100644 index f245fae33ee..00000000000 --- a/cirq-ft/cirq_ft/_version_test.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq_ft - - -def test_version(): - assert cirq_ft.__version__ == "1.4.0.dev" diff --git a/cirq-ft/cirq_ft/algos/__init__.py b/cirq-ft/cirq_ft/algos/__init__.py deleted file mode 100644 index b0cc284434d..00000000000 --- a/cirq-ft/cirq_ft/algos/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 cirq_ft.algos.and_gate import And -from cirq_ft.algos.apply_gate_to_lth_target import ApplyGateToLthQubit -from cirq_ft.algos.arithmetic_gates import ( - AdditionGate, - AddMod, - ContiguousRegisterGate, - LessThanEqualGate, - LessThanGate, - SingleQubitCompare, - BiQubitsMixer, -) -from cirq_ft.algos.generic_select import GenericSelect -from cirq_ft.algos.hubbard_model import PrepareHubbard, SelectHubbard -from cirq_ft.algos.multi_control_multi_target_pauli import MultiControlPauli, MultiTargetCNOT -from cirq_ft.algos.prepare_uniform_superposition import PrepareUniformSuperposition -from cirq_ft.algos.programmable_rotation_gate_array import ( - ProgrammableRotationGateArray, - ProgrammableRotationGateArrayBase, -) -from cirq_ft.algos.qrom import QROM -from cirq_ft.algos.qubitization_walk_operator import QubitizationWalkOperator -from cirq_ft.algos.reflection_using_prepare import ReflectionUsingPrepare -from cirq_ft.algos.select_and_prepare import PrepareOracle, SelectOracle -from cirq_ft.algos.select_swap_qrom import SelectSwapQROM -from cirq_ft.algos.selected_majorana_fermion import SelectedMajoranaFermionGate -from cirq_ft.algos.state_preparation import StatePreparationAliasSampling -from cirq_ft.algos.swap_network import MultiTargetCSwap, MultiTargetCSwapApprox, SwapWithZeroGate -from cirq_ft.algos.unary_iteration_gate import UnaryIterationGate, unary_iteration diff --git a/cirq-ft/cirq_ft/algos/and_gate.ipynb b/cirq-ft/cirq_ft/algos/and_gate.ipynb deleted file mode 100644 index 1c47eec56ab..00000000000 --- a/cirq-ft/cirq_ft/algos/and_gate.ipynb +++ /dev/null @@ -1,226 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "97385158", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "93353f4e", - "metadata": {}, - "source": [ - "# And\n", - "\n", - "To do classical logic with a reversible circuit (a pre-requisite for a quantum circuit), we use a three (qu)bit operation called a Toffoli gate that takes `[a, b, c]` to `[a, b, c ^ (a & b)]`. If we take `c` to be zero, this is an And gate taking `[a, b]` to `[a, b, a & b]`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a95dab52", - "metadata": {}, - "outputs": [], - "source": [ - "import itertools\n", - "for a, b, in itertools.product([0, 1], repeat=2):\n", - " print(a, b, '->', a & b)" - ] - }, - { - "cell_type": "markdown", - "id": "37a44523", - "metadata": {}, - "source": [ - "## Quantum operation\n", - "\n", - "We provide a quantum operation for performing quantum And. Specifically, it assumes the third qubit (i.e. the target) is initialized to the `|0>` state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c456e50", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "from cirq.contrib.svg import SVGCircuit\n", - "from cirq_ft import And, infra\n", - "\n", - "gate = And()\n", - "r = gate.signature\n", - "quregs = infra.get_named_qubits(r)\n", - "operation = gate.on_registers(**quregs)\n", - "circuit = cirq.Circuit(operation)\n", - "SVGCircuit(circuit)" - ] - }, - { - "cell_type": "markdown", - "id": "8d4e16dc", - "metadata": {}, - "source": [ - "## Efficient decomposition\n", - "\n", - "This specialization of the Toffoli gate permits a specialized decomposition that minimizes the `T`-gate count." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fefe2934", - "metadata": {}, - "outputs": [], - "source": [ - "c2 = cirq.Circuit(cirq.decompose_once(operation))\n", - "SVGCircuit(c2)" - ] - }, - { - "cell_type": "markdown", - "id": "b91cc9d9", - "metadata": {}, - "source": [ - "## Test behavior\n", - "\n", - "We can test the behavior of the And gate on computational basis states using a Quantum simulator." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ec57c0e", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "input_states = [(a, b, 0) for a, b in itertools.product([0, 1], repeat=2)]\n", - "output_states = [(a, b, a & b) for a, b, _ in input_states]\n", - "\n", - "\n", - "for inp, out in zip(input_states, output_states):\n", - " result = cirq.Simulator(dtype=np.complex128).simulate(c2, initial_state=inp)\n", - " print(inp, '->', result.dirac_notation())\n", - " assert result.dirac_notation()[1:-1] == \"\".join(str(x) for x in out)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e5832e4", - "metadata": {}, - "outputs": [], - "source": [ - "inds, = np.where(abs(result.final_state_vector) > 1e-8)\n", - "assert len(inds) == 1\n", - "ind, = inds\n", - "f'{ind:3b}'" - ] - }, - { - "cell_type": "markdown", - "id": "fa451755", - "metadata": {}, - "source": [ - "## Uncompute\n", - "\n", - "We can save even more `T` gates when \"uncomputing\" an And operation, i.e. performing the adjoint operation by using classical control." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a2878f83", - "metadata": {}, - "outputs": [], - "source": [ - "inv_operation = operation ** -1\n", - "inv_circuit = cirq.Circuit(inv_operation)\n", - "SVGCircuit(inv_circuit)" - ] - }, - { - "cell_type": "markdown", - "id": "3a2dcf07", - "metadata": {}, - "source": [ - "We reset our target using measurement and fix up phases depending on the result of that measurement:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd123481", - "metadata": {}, - "outputs": [], - "source": [ - "inv_c2 = cirq.Circuit(cirq.decompose_once(inv_operation))\n", - "inv_c2" - ] - }, - { - "cell_type": "markdown", - "id": "a99e3bf5", - "metadata": {}, - "source": [ - "## Test Adjoint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3cacf727", - "metadata": {}, - "outputs": [], - "source": [ - "input_states = [(a, b, a & b) for a, b in itertools.product([0, 1], repeat=2)]\n", - "output_states = [(a, b, 0) for a, b, _ in input_states]\n", - "\n", - "for inp, out in zip(input_states, output_states):\n", - " result = cirq.Simulator().simulate(inv_circuit, initial_state=inp)\n", - " print(inp, '->', result.dirac_notation())\n", - " assert result.dirac_notation()[1:-1] == \"\".join(str(x) for x in out)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/algos/and_gate.py b/cirq-ft/cirq_ft/algos/and_gate.py deleted file mode 100644 index c4210d8ccad..00000000000 --- a/cirq-ft/cirq_ft/algos/and_gate.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Sequence, Tuple - -import numpy as np -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra - - -@attr.frozen -class And(infra.GateWithRegisters): - r"""And gate optimized for T-count. - - Assumptions: - * And(cv).on(c1, c2, target) assumes that target is initially in the |0> state. - * And(cv, adjoint=True).on(c1, c2, target) will always leave the target in |0> state. - - Multi-controlled AND version decomposes into an AND ladder with `#controls - 2` ancillas. - - Args: - cv: A tuple of integers representing 1 control bit per control qubit. - adjoint: If True, the $And^\dagger$ is implemented using measurement based un-computation. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662) - Babbush et. al. 2018. Section III.A. and Fig. 4. - - [Verifying Measurement Based Uncomputation](https://algassert.com/post/1903). - Gidney, C. 2019. - - Raises: - ValueError: If number of control values (i.e. `len(self.cv)`) is less than 2. - """ - - cv: Tuple[int, ...] = attr.field( - default=(1, 1), converter=lambda v: (v,) if isinstance(v, int) else tuple(v) - ) - adjoint: bool = False - - @cv.validator - def _validate_cv(self, attribute, value): - if len(value) < 2: - raise ValueError(f"And gate needs at-least 2 control values, supplied {value} instead.") - - @cached_property - def signature(self) -> infra.Signature: - one_side = infra.Side.RIGHT if not self.adjoint else infra.Side.LEFT - n_cv = len(self.cv) - junk_reg = [infra.Register('junk', 1, shape=n_cv - 2, side=one_side)] if n_cv > 2 else [] - return infra.Signature( - [ - infra.Register('ctrl', 1, shape=n_cv), - *junk_reg, - infra.Register('target', 1, side=one_side), - ] - ) - - def __pow__(self, power: int) -> "And": - if power == 1: - return self - if power == -1: - return And(self.cv, adjoint=self.adjoint ^ True) - return NotImplemented # pragma: no cover - - def __str__(self) -> str: - suffix = "" if self.cv == (1,) * len(self.cv) else str(self.cv) - return f"And†{suffix}" if self.adjoint else f"And{suffix}" - - def __repr__(self) -> str: - return f"cirq_ft.And({self.cv}, adjoint={self.adjoint})" - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - controls = ["(0)", "@"] - target = "And†" if self.adjoint else "And" - wire_symbols = [controls[c] for c in self.cv] - wire_symbols += ["Anc"] * (len(self.cv) - 2) - wire_symbols += [target] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _has_unitary_(self) -> bool: - return not self.adjoint - - def _decompose_single_and( - self, cv1: int, cv2: int, c1: cirq.Qid, c2: cirq.Qid, target: cirq.Qid - ) -> cirq.ops.op_tree.OpTree: - """Decomposes a single `And` gate on 2 controls and 1 target in terms of Clifford+T gates. - - * And(cv).on(c1, c2, target) uses 4 T-gates and assumes target is in |0> state. - * And(cv, adjoint=True).on(c1, c2, target) uses measurement based un-computation - (0 T-gates) and will always leave the target in |0> state. - """ - pre_post_ops = [cirq.X(q) for (q, v) in zip([c1, c2], [cv1, cv2]) if v == 0] - yield pre_post_ops - if self.adjoint: - yield cirq.H(target) - yield cirq.measure(target, key=f"{target}") - yield cirq.CZ(c1, c2).with_classical_controls(f"{target}") - yield cirq.reset(target) - else: - yield [cirq.H(target), cirq.T(target)] - yield [cirq.CNOT(c1, target), cirq.CNOT(c2, target)] - yield [cirq.CNOT(target, c1), cirq.CNOT(target, c2)] - yield [cirq.T(c1) ** -1, cirq.T(c2) ** -1, cirq.T(target)] - yield [cirq.CNOT(target, c1), cirq.CNOT(target, c2)] - yield [cirq.H(target), cirq.S(target)] - yield pre_post_ops - - def _decompose_via_tree( - self, - controls: NDArray[cirq.Qid], # type:ignore[type-var] - control_values: Sequence[int], - ancillas: NDArray[cirq.Qid], - target: cirq.Qid, - ) -> cirq.ops.op_tree.OpTree: - """Decomposes multi-controlled `And` in-terms of an `And` ladder of size #controls- 2.""" - if len(controls) == 2: - yield And(control_values, adjoint=self.adjoint).on(*controls, target) - return - new_controls = np.concatenate([ancillas[0:1], controls[2:]]) - new_control_values = (1, *control_values[2:]) - and_op = And(control_values[:2], adjoint=self.adjoint).on(*controls[:2], ancillas[0]) - if self.adjoint: - yield from self._decompose_via_tree( - new_controls, new_control_values, ancillas[1:], target - ) - yield and_op - else: - yield and_op - yield from self._decompose_via_tree( - new_controls, new_control_values, ancillas[1:], target - ) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - control, ancilla, target = ( - quregs['ctrl'].flatten(), - quregs.get('junk', np.array([])).flatten(), - quregs['target'].flatten(), - ) - if len(self.cv) == 2: - yield self._decompose_single_and( - self.cv[0], self.cv[1], control[0], control[1], *target - ) - else: - yield self._decompose_via_tree(control, self.cv, ancilla, *target) - - def _t_complexity_(self) -> infra.TComplexity: - pre_post_cliffords = len(self.cv) - sum(self.cv) # number of zeros in self.cv - num_single_and = len(self.cv) - 1 - if self.adjoint: - return infra.TComplexity(clifford=4 * num_single_and + 2 * pre_post_cliffords) - else: - return infra.TComplexity( - t=4 * num_single_and, clifford=9 * num_single_and + 2 * pre_post_cliffords - ) diff --git a/cirq-ft/cirq_ft/algos/and_gate_test.py b/cirq-ft/cirq_ft/algos/and_gate_test.py deleted file mode 100644 index 907e45bb71f..00000000000 --- a/cirq-ft/cirq_ft/algos/and_gate_test.py +++ /dev/null @@ -1,239 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import itertools -import random -from typing import List, Tuple - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - -random.seed(12345) - - -@pytest.mark.parametrize("cv", [(0, 0), (0, 1), (1, 0), (1, 1)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate(cv: Tuple[int, int]): - c1, c2, t = cirq.LineQubit.range(3) - input_states = [(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0)] - output_states = [inp[:2] + (1 if inp[:2] == cv else 0,) for inp in input_states] - - and_gate = cirq_ft.And(cv) - circuit = cirq.Circuit(and_gate.on(c1, c2, t)) - for inp, out in zip(input_states, output_states): - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, [c1, c2, t], inp, out) - - -def random_cv(n: int) -> List[int]: - return [random.randint(0, 1) for _ in range(n)] - - -@pytest.mark.parametrize("cv", [[1] * 3, random_cv(5), random_cv(6), random_cv(7)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_controlled_and_gate(cv: List[int]): - gate = cirq_ft.And(cv) - r = gate.signature - assert r.get_right('junk').total_bits() == r.get_left('ctrl').total_bits() - 2 - quregs = infra.get_named_qubits(r) - and_op = gate.on_registers(**quregs) - circuit = cirq.Circuit(and_op) - - input_controls = [cv] + [random_cv(len(cv)) for _ in range(10)] - qubit_order = infra.merge_qubits(gate.signature, **quregs) - - for input_control in input_controls: - initial_state = input_control + [0] * (r.get_right('junk').total_bits() + 1) - result = cirq.Simulator(dtype=np.complex128).simulate( - circuit, initial_state=initial_state, qubit_order=qubit_order - ) - expected_output = np.asarray([0, 1] if input_control == cv else [1, 0]) - assert cirq.equal_up_to_global_phase( - cirq.sub_state_vector( - result.final_state_vector, keep_indices=[cirq.num_qubits(gate) - 1] - ), - expected_output, - ) - - # Test adjoint. - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit + cirq.Circuit(cirq.inverse(and_op)), - qubit_order=qubit_order, - inputs=initial_state, - outputs=initial_state, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_diagram(): - gate = cirq_ft.And((1, 0, 1, 0, 1, 0)) - qubit_regs = infra.get_named_qubits(gate.signature) - op = gate.on_registers(**qubit_regs) - ctrl, junk, target = ( - qubit_regs["ctrl"].flatten(), - qubit_regs["junk"].flatten(), - qubit_regs['target'].flatten(), - ) - # Qubit order should be alternating (control, ancilla) pairs. - c_and_a = sum(zip(ctrl[1:], junk), ()) + (ctrl[-1],) - qubit_order = np.concatenate([ctrl[0:1], c_and_a, target]) - # Test diagrams. - cirq.testing.assert_has_diagram( - cirq.Circuit(op), - """ -ctrl[0]: ───@───── - │ -ctrl[1]: ───(0)─── - │ -junk[0]: ───Anc─── - │ -ctrl[2]: ───@───── - │ -junk[1]: ───Anc─── - │ -ctrl[3]: ───(0)─── - │ -junk[2]: ───Anc─── - │ -ctrl[4]: ───@───── - │ -junk[3]: ───Anc─── - │ -ctrl[5]: ───(0)─── - │ -target: ────And─── -""", - qubit_order=qubit_order, - ) - cirq.testing.assert_has_diagram( - cirq.Circuit(op**-1), - """ -ctrl[0]: ───@────── - │ -ctrl[1]: ───(0)──── - │ -junk[0]: ───Anc──── - │ -ctrl[2]: ───@────── - │ -junk[1]: ───Anc──── - │ -ctrl[3]: ───(0)──── - │ -junk[2]: ───Anc──── - │ -ctrl[4]: ───@────── - │ -junk[3]: ───Anc──── - │ -ctrl[5]: ───(0)──── - │ -target: ────And†─── -""", - qubit_order=qubit_order, - ) - # Test diagram of decomposed 3-qubit and ladder. - decomposed_circuit = cirq.Circuit(cirq.decompose_once(op)) + cirq.Circuit( - cirq.decompose_once(op**-1) - ) - cirq.testing.assert_has_diagram( - decomposed_circuit, - """ -ctrl[0]: ───@─────────────────────────────────────────────────────────@────── - │ │ -ctrl[1]: ───(0)───────────────────────────────────────────────────────(0)──── - │ │ -junk[0]: ───And───@────────────────────────────────────────────@──────And†─── - │ │ -ctrl[2]: ─────────@────────────────────────────────────────────@───────────── - │ │ -junk[1]: ─────────And───@───────────────────────────────@──────And†────────── - │ │ -ctrl[3]: ───────────────(0)─────────────────────────────(0)────────────────── - │ │ -junk[2]: ───────────────And───@──────────────────@──────And†───────────────── - │ │ -ctrl[4]: ─────────────────────@──────────────────@─────────────────────────── - │ │ -junk[3]: ─────────────────────And───@─────@──────And†──────────────────────── - │ │ -ctrl[5]: ───────────────────────────(0)───(0)──────────────────────────────── - │ │ -target: ────────────────────────────And───And†─────────────────────────────── -""", - qubit_order=qubit_order, - ) - - -@pytest.mark.parametrize( - "cv, adjoint, str_output", - [ - ((1, 1, 1), False, "And"), - ((1, 0, 1), False, "And(1, 0, 1)"), - ((1, 1, 1), True, "And†"), - ((1, 0, 1), True, "And†(1, 0, 1)"), - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_str_and_repr(cv, adjoint, str_output): - gate = cirq_ft.And(cv, adjoint=adjoint) - assert str(gate) == str_output - cirq.testing.assert_equivalent_repr(gate, setup_code="import cirq_ft\n") - - -@pytest.mark.parametrize("cv", [(0, 0), (0, 1), (1, 0), (1, 1)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_adjoint(cv: Tuple[int, int]): - c1, c2, t = cirq.LineQubit.range(3) - all_cvs = [(0, 0), (0, 1), (1, 0), (1, 1)] - input_states = [inp + (1 if inp == cv else 0,) for inp in all_cvs] - output_states = [inp + (0,) for inp in all_cvs] - - circuit = cirq.Circuit(cirq_ft.And(cv, adjoint=True).on(c1, c2, t)) - for inp, out in zip(input_states, output_states): - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, [c1, c2, t], inp, out) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('and_gate') - - -@pytest.mark.parametrize( - "cv", [*itertools.chain(*[itertools.product(range(2), repeat=n) for n in range(2, 7 + 1)])] -) -@pytest.mark.parametrize("adjoint", [*range(2)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(cv, adjoint): - gate = cirq_ft.And(cv=cv, adjoint=adjoint) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(gate) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_raises(): - with pytest.raises(ValueError, match="at-least 2 control values"): - _ = cirq_ft.And(cv=(1,)) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_power(): - cv = (1, 0) - and_gate = cirq_ft.And(cv) - assert and_gate**1 is and_gate - assert and_gate**-1 == cirq_ft.And(cv, adjoint=True) - assert (and_gate**-1) ** -1 == cirq_ft.And(cv) diff --git a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.ipynb b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.ipynb deleted file mode 100644 index 236832f691c..00000000000 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.ipynb +++ /dev/null @@ -1,126 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "60432dd0", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "ac3bfb05", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Apply to L-th Target" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e214a27", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "249829b0", - "metadata": { - "cq.autogen": "_make_ApplyGateToLthQubit.md" - }, - "source": [ - "## `ApplyGateToLthQubit`\n", - "A controlled SELECT operation for single-qubit gates.\n", - "\n", - "$$\n", - "\\mathrm{SELECT} = \\sum_{l}|l \\rangle \\langle l| \\otimes [G(l)]_l\n", - "$$\n", - "\n", - "Where $G$ is a function that maps an index to a single-qubit gate.\n", - "\n", - "This gate uses the unary iteration scheme to apply `nth_gate(selection)` to the\n", - "`selection`-th qubit of `target` all controlled by the `control` register.\n", - "\n", - "#### Parameters\n", - " - `selection_regs`: Indexing `select` signature of type Tuple[`SelectionRegister`, ...]. It also contains information about the iteration length of each selection register.\n", - " - `nth_gate`: A function mapping the composite selection index to a single-qubit gate.\n", - " - `control_regs`: Control signature for constructing a controlled version of the gate.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8d2a7bf", - "metadata": { - "cq.autogen": "_make_ApplyGateToLthQubit.py" - }, - "outputs": [], - "source": [ - "def _z_to_odd(n: int):\n", - " if n % 2 == 1:\n", - " return cirq.Z\n", - " return cirq.I\n", - "\n", - "apply_z_to_odd = cirq_ft.ApplyGateToLthQubit(\n", - " cirq_ft.SelectionRegister('selection', 3, 4),\n", - " nth_gate=_z_to_odd,\n", - " control_regs=cirq_ft.Signature.build(control=2),\n", - ")\n", - "\n", - "g = cq_testing.GateHelper(\n", - " apply_z_to_odd\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.py b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.py deleted file mode 100644 index 86ce2b3f680..00000000000 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -import itertools -from typing import Callable, Sequence, Tuple - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import unary_iteration_gate - - -@attr.frozen -class ApplyGateToLthQubit(unary_iteration_gate.UnaryIterationGate): - r"""A controlled SELECT operation for single-qubit gates. - - $$ - \mathrm{SELECT} = \sum_{l}|l \rangle \langle l| \otimes [G(l)]_l - $$ - - Where $G$ is a function that maps an index to a single-qubit gate. - - This gate uses the unary iteration scheme to apply `nth_gate(selection)` to the - `selection`-th qubit of `target` all controlled by the `control` register. - - Args: - selection_regs: Indexing `select` signature of type Tuple[`SelectionRegisters`, ...]. - It also contains information about the iteration length of each selection register. - nth_gate: A function mapping the composite selection index to a single-qubit gate. - control_regs: Control signature for constructing a controlled version of the gate. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.A. and Figure 7. - """ - - selection_regs: Tuple[infra.SelectionRegister, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.SelectionRegister) else tuple(v) - ) - nth_gate: Callable[..., cirq.Gate] - control_regs: Tuple[infra.Register, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.Register) else tuple(v) - ) - - @control_regs.default - def control_regs_default(self): - return (infra.Register('control', 1),) - - @classmethod - def make_on( - cls, *, nth_gate: Callable[..., cirq.Gate], **quregs: Sequence[cirq.Qid] - ) -> cirq.Operation: - """Helper constructor to automatically deduce bitsize attributes.""" - return ApplyGateToLthQubit( - infra.SelectionRegister('selection', len(quregs['selection']), len(quregs['target'])), - nth_gate=nth_gate, - control_regs=infra.Register('control', len(quregs['control'])), - ).on_registers(**quregs) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.control_regs - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.selection_regs - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - total_iteration_size = np.prod( - tuple(reg.iteration_length for reg in self.selection_registers) - ) - return (infra.Register('target', int(total_iteration_size)),) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@"] * infra.total_bits(self.control_registers) - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - for it in itertools.product(*[range(reg.iteration_length) for reg in self.selection_regs]): - wire_symbols += [str(self.nth_gate(*it))] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - target: Sequence[cirq.Qid], - **selection_indices: int, - ) -> cirq.OP_TREE: - selection_shape = tuple(reg.iteration_length for reg in self.selection_regs) - selection_idx = tuple(selection_indices[reg.name] for reg in self.selection_regs) - target_idx = int(np.ravel_multi_index(selection_idx, selection_shape)) - return self.nth_gate(*selection_idx).on(target[target_idx]).controlled_by(control) diff --git a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py deleted file mode 100644 index 1bf4cb64157..00000000000 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize("selection_bitsize,target_bitsize", [[3, 5], [3, 7], [4, 5]]) -@allow_deprecated_cirq_ft_use_in_tests -def test_apply_gate_to_lth_qubit(selection_bitsize, target_bitsize): - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - gate = cirq_ft.ApplyGateToLthQubit( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), lambda _: cirq.X - ) - g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) - # Upper bounded because not all ancillas may be used as part of unary iteration. - assert ( - len(g.all_qubits) - <= target_bitsize + 2 * (selection_bitsize + infra.total_bits(gate.control_registers)) - 1 - ) - - for n in range(target_bitsize): - # Initial qubit values - qubit_vals = {q: 0 for q in g.all_qubits} - # All controls 'on' to activate circuit - qubit_vals.update({c: 1 for c in g.quregs['control']}) - # Set selection according to `n` - qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in g.all_qubits] - qubit_vals[g.quregs['target'][n]] = 1 - final_state = [qubit_vals[x] for x in g.all_qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - g.decomposed_circuit, g.all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_apply_gate_to_lth_qubit_diagram(): - # Apply Z gate to all odd targets and Identity to even targets. - gate = cirq_ft.ApplyGateToLthQubit( - cirq_ft.SelectionRegister('selection', 3, 5), - lambda n: cirq.Z if n & 1 else cirq.I, - control_regs=cirq_ft.Signature.build(control=2), - ) - circuit = cirq.Circuit(gate.on_registers(**infra.get_named_qubits(gate.signature))) - qubits = list(q for v in infra.get_named_qubits(gate.signature).values() for q in v) - cirq.testing.assert_has_diagram( - circuit, - """ -control0: ─────@──── - │ -control1: ─────@──── - │ -selection0: ───In─── - │ -selection1: ───In─── - │ -selection2: ───In─── - │ -target0: ──────I──── - │ -target1: ──────Z──── - │ -target2: ──────I──── - │ -target3: ──────Z──── - │ -target4: ──────I──── -""", - qubit_order=qubits, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_apply_gate_to_lth_qubit_make_on(): - gate = cirq_ft.ApplyGateToLthQubit( - cirq_ft.SelectionRegister('selection', 3, 5), - lambda n: cirq.Z if n & 1 else cirq.I, - control_regs=cirq_ft.Signature.build(control=2), - ) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - op2 = cirq_ft.ApplyGateToLthQubit.make_on( - nth_gate=lambda n: cirq.Z if n & 1 else cirq.I, **infra.get_named_qubits(gate.signature) - ) - # Note: ApplyGateToLthQubit doesn't support value equality. - assert op.qubits == op2.qubits - assert op.gate.selection_regs == op2.gate.selection_regs - assert op.gate.control_regs == op2.gate.control_regs - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('apply_gate_to_lth_target') diff --git a/cirq-ft/cirq_ft/algos/arithmetic_gates.py b/cirq-ft/cirq_ft/algos/arithmetic_gates.py deleted file mode 100644 index 4cc34974f95..00000000000 --- a/cirq-ft/cirq_ft/algos/arithmetic_gates.py +++ /dev/null @@ -1,682 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Iterable, Iterator, List, Optional, Sequence, Tuple, Union - -import attr -import cirq -from numpy.typing import NDArray - -from cirq_ft import infra -from cirq_ft.algos import and_gate -from cirq_ft.deprecation import deprecated_cirq_ft_class - - -@deprecated_cirq_ft_class() -@attr.frozen -class LessThanGate(cirq.ArithmeticGate): - """Applies U_a|x>|z> = |x> |z ^ (x < a)>""" - - bitsize: int - less_than_val: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.bitsize, self.less_than_val, [2] - - def with_registers(self, *new_registers) -> "LessThanGate": - return LessThanGate(len(new_registers[0]), new_registers[1]) - - def apply(self, *register_vals: int) -> Union[int, Iterable[int]]: - input_val, less_than_val, target_register_val = register_vals - return input_val, less_than_val, target_register_val ^ (input_val < less_than_val) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.bitsize - wire_symbols += [f'+(x < {self.less_than_val})'] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - def __repr__(self) -> str: - return f'cirq_ft.LessThanGate({self.bitsize}, {self.less_than_val})' - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - """Decomposes the gate into 4N And and And† operations for a T complexity of 4N. - - The decomposition proceeds from the most significant qubit -bit 0- to the least significant - qubit while maintaining whether the qubit sequence is equal to the current prefix of the - `_val` or not. - - The bare-bone logic is: - 1. if ith bit of `_val` is 1 then: - - qubit sequence < `_val` iff they are equal so far and the current qubit is 0. - 2. update `are_equal`: `are_equal := are_equal and (ith bit == ith qubit).` - - This logic is implemented using $n$ `And` & `And†` operations and n+1 clean ancilla where - - one ancilla `are_equal` contains the equality informaiton - - ancilla[i] contain whether the qubits[:i+1] != (i+1)th prefix of `_val` - """ - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - - qubits, target = qubits[:-1], qubits[-1] - # Trivial case, self._val is larger than any value the registers could represent - if self.less_than_val >= 2**self.bitsize: - yield cirq.X(target) - return - adjoint = [] - - (are_equal,) = context.qubit_manager.qalloc(1) - - # Initially our belief is that the numbers are equal. - yield cirq.X(are_equal) - adjoint.append(cirq.X(are_equal)) - - # Scan from left to right. - # `are_equal` contains whether the numbers are equal so far. - ancilla = context.qubit_manager.qalloc(self.bitsize) - for b, q, a in zip( - infra.bit_tools.iter_bits(self.less_than_val, self.bitsize), qubits, ancilla - ): - if b: - yield cirq.X(q) - adjoint.append(cirq.X(q)) - - # ancilla[i] = are_equal so far and (q_i != _val[i]). - # = equivalent to: Is the current prefix of qubits < prefix of `_val`? - yield and_gate.And().on(q, are_equal, a) - adjoint.append(and_gate.And(adjoint=True).on(q, are_equal, a)) - - # target ^= is the current prefix of the qubit sequence < current prefix of `_val` - yield cirq.CNOT(a, target) - - # If `a=1` (i.e. the current prefixes aren't equal) this means that - # `are_equal` is currently = 1 and q[i] != _val[i] so we need to flip `are_equal`. - yield cirq.CNOT(a, are_equal) - adjoint.append(cirq.CNOT(a, are_equal)) - else: - # ancilla[i] = are_equal so far and (q = 1). - yield and_gate.And().on(q, are_equal, a) - adjoint.append(and_gate.And(adjoint=True).on(q, are_equal, a)) - - # if `a=1` then we need to flip `are_equal` since this means that are_equal=1, - # b_i=0, q_i=1 => current prefixes are not equal so we need to flip `are_equal`. - yield cirq.CNOT(a, are_equal) - adjoint.append(cirq.CNOT(a, are_equal)) - - yield from reversed(adjoint) - - def _has_unitary_(self): - return True - - def _t_complexity_(self) -> infra.TComplexity: - n = self.bitsize - if self.less_than_val >= 2**n: - return infra.TComplexity(clifford=1) - return infra.TComplexity( - t=4 * n, clifford=15 * n + 3 * bin(self.less_than_val).count("1") + 2 - ) - - -@attr.frozen -class BiQubitsMixer(infra.GateWithRegisters): - """Implements the COMPARE2 (Fig. 1) https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - - This gates mixes the values in a way that preserves the result of comparison. - The signature being compared are 2-qubit signature where - x = 2*x_msb + x_lsb - y = 2*y_msb + y_lsb - The Gate mixes the 4 qubits so that sign(x - y) = sign(x_lsb' - y_lsb') where x_lsb' and y_lsb' - are the final values of x_lsb' and y_lsb'. - - Note that the ancilla qubits are used to reduce the T-count and the user - should clean the qubits at a later point in time with the adjoint gate. - See: https://github.com/quantumlib/Cirq/pull/6313 and - https://github.com/quantumlib/Qualtran/issues/389 - """ # pylint: disable=line-too-long - - adjoint: bool = False - - @cached_property - def signature(self) -> infra.Signature: - one_side = infra.Side.RIGHT if not self.adjoint else infra.Side.LEFT - return infra.Signature( - [ - infra.Register('x', 2), - infra.Register('y', 2), - infra.Register('ancilla', 3, side=one_side), - ] - ) - - def __repr__(self) -> str: - return f'cirq_ft.algos.BiQubitsMixer({self.adjoint})' - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - x, y, ancilla = quregs['x'], quregs['y'], quregs['ancilla'] - x_msb, x_lsb = x - y_msb, y_lsb = y - - def _cswap(control: cirq.Qid, a: cirq.Qid, b: cirq.Qid, aux: cirq.Qid) -> cirq.OP_TREE: - """A CSWAP with 4T complexity and whose adjoint has 0T complexity. - - A controlled SWAP that swaps `a` and `b` based on `control`. - It uses an extra qubit `aux` so that its adjoint would have - a T complexity of zero. - """ - yield cirq.CNOT(a, b) - yield and_gate.And(adjoint=self.adjoint).on(control, b, aux) - yield cirq.CNOT(aux, a) - yield cirq.CNOT(a, b) - - def _decomposition(): - # computes the difference of x - y where - # x = 2*x_msb + x_lsb - # y = 2*y_msb + y_lsb - # And stores the result in x_lsb and y_lsb such that - # sign(x - y) = sign(x_lsb - y_lsb) - # This decomposition uses 3 ancilla qubits in order to have a - # T complexity of 8. - yield cirq.X(ancilla[0]) - yield cirq.CNOT(y_msb, x_msb) - yield cirq.CNOT(y_lsb, x_lsb) - yield from _cswap(x_msb, x_lsb, ancilla[0], ancilla[1]) - yield from _cswap(x_msb, y_msb, y_lsb, ancilla[2]) - yield cirq.CNOT(y_lsb, x_lsb) - - if self.adjoint: - yield from reversed(tuple(cirq.flatten_to_ops(_decomposition()))) - else: - yield from _decomposition() - - def __pow__(self, power: int) -> cirq.Gate: - if power == 1: - return self - if power == -1: - return BiQubitsMixer(adjoint=not self.adjoint) - return NotImplemented # pragma: no cover - - def _t_complexity_(self) -> infra.TComplexity: - if self.adjoint: - return infra.TComplexity(clifford=18) - return infra.TComplexity(t=8, clifford=28) - - def _has_unitary_(self): - return not self.adjoint - - -@attr.frozen -class SingleQubitCompare(infra.GateWithRegisters): - """Applies U|a>|b>|0>|0> = |a> |a=b> |(a |(a>b)> - - Source: (FIG. 3) in https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - """ # pylint: disable=line-too-long - - adjoint: bool = False - - @cached_property - def signature(self) -> infra.Signature: - one_side = infra.Side.RIGHT if not self.adjoint else infra.Side.LEFT - return infra.Signature( - [ - infra.Register('a', 1), - infra.Register('b', 1), - infra.Register('less_than', 1, side=one_side), - infra.Register('greater_than', 1, side=one_side), - ] - ) - - def __repr__(self) -> str: - return f'cirq_ft.algos.SingleQubitCompare({self.adjoint})' - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - a = quregs['a'] - b = quregs['b'] - less_than = quregs['less_than'] - greater_than = quregs['greater_than'] - - def _decomposition() -> Iterator[cirq.Operation]: - yield and_gate.And((0, 1), adjoint=self.adjoint).on(*a, *b, *less_than) - yield cirq.CNOT(*less_than, *greater_than) - yield cirq.CNOT(*b, *greater_than) - yield cirq.CNOT(*a, *b) - yield cirq.CNOT(*a, *greater_than) - yield cirq.X(*b) - - if self.adjoint: - yield from reversed(tuple(_decomposition())) - else: - yield from _decomposition() - - def __pow__(self, power: int) -> cirq.Gate: - if not isinstance(power, int): - raise ValueError('SingleQubitCompare is only defined for integer powers.') - if power % 2 == 0: - return cirq.IdentityGate(4) - if power < 0: - return SingleQubitCompare(adjoint=not self.adjoint) - return self - - def _t_complexity_(self) -> infra.TComplexity: - if self.adjoint: - return infra.TComplexity(clifford=11) - return infra.TComplexity(t=4, clifford=16) - - -def _equality_with_zero( - context: cirq.DecompositionContext, qubits: Sequence[cirq.Qid], z: cirq.Qid -) -> cirq.OP_TREE: - if len(qubits) == 1: - (q,) = qubits - yield cirq.X(q) - yield cirq.CNOT(q, z) - return - - ancilla = context.qubit_manager.qalloc(len(qubits) - 2) - yield and_gate.And(cv=[0] * len(qubits)).on(*qubits, *ancilla, z) - - -@deprecated_cirq_ft_class() -@attr.frozen -class LessThanEqualGate(cirq.ArithmeticGate): - """Applies U|x>|y>|z> = |x>|y> |z ^ (x <= y)>""" - - x_bitsize: int - y_bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.x_bitsize, [2] * self.y_bitsize, [2] - - def with_registers(self, *new_registers) -> "LessThanEqualGate": - return LessThanEqualGate(len(new_registers[0]), len(new_registers[1])) - - def apply(self, *register_vals: int) -> Union[int, int, Iterable[int]]: - x_val, y_val, target_val = register_vals - return x_val, y_val, target_val ^ (x_val <= y_val) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.x_bitsize - wire_symbols += ["In(y)"] * self.y_bitsize - wire_symbols += ['+(x <= y)'] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - def __repr__(self) -> str: - return f'cirq_ft.LessThanEqualGate({self.x_bitsize}, {self.y_bitsize})' - - def _decompose_via_tree( - self, context: cirq.DecompositionContext, X: Sequence[cirq.Qid], Y: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - """Returns comparison oracle from https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - - This decomposition follows the tree structure of (FIG. 2) - """ # pylint: disable=line-too-long - if len(X) == 1: - return - if len(X) == 2: - yield BiQubitsMixer().on_registers(x=X, y=Y, ancilla=context.qubit_manager.qalloc(3)) - return - - m = len(X) // 2 - yield self._decompose_via_tree(context, X[:m], Y[:m]) - yield self._decompose_via_tree(context, X[m:], Y[m:]) - yield BiQubitsMixer().on_registers( - x=(X[m - 1], X[-1]), y=(Y[m - 1], Y[-1]), ancilla=context.qubit_manager.qalloc(3) - ) - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - """Decomposes the gate in a T-complexity optimal way. - - The construction can be broken in 4 parts: - 1. In case of differing bitsizes then a multicontrol And Gate - - Section III.A. https://arxiv.org/abs/1805.03662) is used to check whether - the extra prefix is equal to zero: - - result stored in: `prefix_equality` qubit. - 2. The tree structure (FIG. 2) https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - followed by a SingleQubitCompare to compute the result of comparison of - the suffixes of equal length: - - result stored in: `less_than` and `greater_than` with equality in qubits[-2] - 3. The results from the previous two steps are combined to update the target qubit. - 4. The adjoint of the previous operations is added to restore the input qubits - to their original state and clean the ancilla qubits. - """ # pylint: disable=line-too-long - - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - - lhs, rhs, target = qubits[: self.x_bitsize], qubits[self.x_bitsize : -1], qubits[-1] - - n = min(len(lhs), len(rhs)) - - prefix_equality = None - adjoint: List[cirq.Operation] = [] - - # if one of the registers is longer than the other store equality with |0--0> - # into `prefix_equality` using d = |len(P) - len(Q)| And operations => 4d T. - if len(lhs) != len(rhs): - (prefix_equality,) = context.qubit_manager.qalloc(1) - if len(lhs) > len(rhs): - for op in cirq.flatten_to_ops( - _equality_with_zero(context, lhs[:-n], prefix_equality) - ): - yield op - adjoint.append(cirq.inverse(op)) - else: - for op in cirq.flatten_to_ops( - _equality_with_zero(context, rhs[:-n], prefix_equality) - ): - yield op - adjoint.append(cirq.inverse(op)) - - yield cirq.X(target), cirq.CNOT(prefix_equality, target) - - # compare the remaing suffix of P and Q - lhs = lhs[-n:] - rhs = rhs[-n:] - for op in cirq.flatten_to_ops(self._decompose_via_tree(context, lhs, rhs)): - yield op - adjoint.append(cirq.inverse(op)) - - less_than, greater_than = context.qubit_manager.qalloc(2) - yield SingleQubitCompare().on_registers( - a=lhs[-1], b=rhs[-1], less_than=less_than, greater_than=greater_than - ) - adjoint.append( - SingleQubitCompare(adjoint=True).on_registers( - a=lhs[-1], b=rhs[-1], less_than=less_than, greater_than=greater_than - ) - ) - - if prefix_equality is None: - yield cirq.X(target) - yield cirq.CNOT(greater_than, target) - else: - (less_than_or_equal,) = context.qubit_manager.qalloc(1) - yield and_gate.And([1, 0]).on(prefix_equality, greater_than, less_than_or_equal) - adjoint.append( - and_gate.And([1, 0], adjoint=True).on( - prefix_equality, greater_than, less_than_or_equal - ) - ) - - yield cirq.CNOT(less_than_or_equal, target) - - yield from reversed(adjoint) - - def _t_complexity_(self) -> infra.TComplexity: - n = min(self.x_bitsize, self.y_bitsize) - d = max(self.x_bitsize, self.y_bitsize) - n - is_second_longer = self.y_bitsize > self.x_bitsize - if d == 0: - # When both registers are of the same size the T complexity is - # 8n - 4 same as in https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf. pylint: disable=line-too-long - return infra.TComplexity(t=8 * n - 4, clifford=46 * n - 17) - else: - # When the registers differ in size and `n` is the size of the smaller one and - # `d` is the difference in size. The T complexity is the sum of the tree - # decomposition as before giving 8n + O(1) and the T complexity of an `And` gate - # over `d` registers giving 4d + O(1) totaling 8n + 4d + O(1). - # From the decomposition we get that the constant is -4 as well as the clifford counts. - if d == 1: - return infra.TComplexity(t=8 * n, clifford=46 * n + 3 + 2 * is_second_longer) - else: - return infra.TComplexity( - t=8 * n + 4 * d - 4, clifford=46 * n + 17 * d - 14 + 2 * is_second_longer - ) - - def _has_unitary_(self): - return True - - -@deprecated_cirq_ft_class() -@attr.frozen -class ContiguousRegisterGate(cirq.ArithmeticGate): - """Applies U|x>|y>|0> -> |x>|y>|x(x-1)/2 + y> - - This is useful in the case when $|x>$ and $|y>$ represent two selection signature such that - $y < x$. For example, imagine a classical for-loop over two variables $x$ and $y$: - - >>> N = 10 - >>> data = [[1000 * x + 10 * y for y in range(x)] for x in range(N)] - >>> for x in range(N): - ... for y in range(x): - ... # Iterates over a total of (N * (N - 1)) / 2 elements. - ... assert data[x][y] == 1000 * x + 10 * y - - We can rewrite the above using a single for-loop that uses a "contiguous" variable `i` s.t. - - >>> import numpy as np - >>> N = 10 - >>> data = [[1000 * x + 10 * y for y in range(x)] for x in range(N)] - >>> for i in range((N * (N - 1)) // 2): - ... x = int(np.floor((1 + np.sqrt(1 + 8 * i)) / 2)) - ... y = i - (x * (x - 1)) // 2 - ... assert data[x][y] == 1000 * x + 10 * y - - Note that both the for-loops iterate over the same ranges and in the same order. The only - difference is that the second loop is a "flattened" version of the first one. - - Such a flattening of selection signature is useful when we want to load multi dimensional - data to a target register which is indexed on selection signature $x$ and $y$ such that - $0<= y <= x < N$ and we want to use a `SelectSwapQROM` to laod this data; which gives a - sqrt-speedup over a traditional QROM at the cost of using more memory and loading chunks - of size `sqrt(N)` in a single iteration. See the reference for more details. - - References: - [Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction] - (https://arxiv.org/abs/2011.03494) - Lee et. al. (2020). Appendix F, Page 67. - """ - - bitsize: int - target_bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.bitsize, [2] * self.bitsize, [2] * self.target_bitsize - - def with_registers(self, *new_registers) -> 'ContiguousRegisterGate': - x_bitsize, y_bitsize, target_bitsize = [len(reg) for reg in new_registers] - assert ( - x_bitsize == y_bitsize - ), f'x_bitsize={x_bitsize} should be same as y_bitsize={y_bitsize}' - return ContiguousRegisterGate(x_bitsize, target_bitsize) - - def apply(self, *register_vals: int) -> Union[int, Iterable[int]]: - x, y, target = register_vals - return x, y, target ^ ((x * (x - 1)) // 2 + y) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.bitsize - wire_symbols += ["In(y)"] * self.bitsize - wire_symbols += ['+(x(x-1)/2 + y)'] * self.target_bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _t_complexity_(self) -> infra.TComplexity: - # See the linked reference for explanation of the Toffoli complexity. - toffoli_complexity = infra.t_complexity(cirq.CCNOT) - return (self.bitsize**2 + self.bitsize - 1) * toffoli_complexity - - def __repr__(self) -> str: - return f'cirq_ft.ContiguousRegisterGate({self.bitsize}, {self.target_bitsize})' - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - -@deprecated_cirq_ft_class() -@attr.frozen -class AdditionGate(cirq.ArithmeticGate): - """Applies U|p>|q> -> |p>|p+q>. - - Args: - bitsize: The number of bits used to represent each integer p and q. - Note that this adder does not detect overflow if bitsize is not - large enough to hold p + q and simply drops the most significant bits. - - References: - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648) - """ - - bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.bitsize, [2] * self.bitsize - - def with_registers(self, *new_registers) -> 'AdditionGate': - return AdditionGate(len(new_registers[0])) - - def apply(self, *register_values: int) -> Union[int, Iterable[int]]: - p, q = register_values - return p, p + q - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.bitsize - wire_symbols += ["In(y)/Out(x+y)"] * self.bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _has_unitary_(self): - return True - - def _left_building_block(self, inp, out, anc, depth): - if depth == self.bitsize - 1: - return - else: - yield cirq.CX(anc[depth - 1], inp[depth]) - yield cirq.CX(anc[depth - 1], out[depth]) - yield and_gate.And().on(inp[depth], out[depth], anc[depth]) - yield cirq.CX(anc[depth - 1], anc[depth]) - yield from self._left_building_block(inp, out, anc, depth + 1) - - def _right_building_block(self, inp, out, anc, depth): - if depth == 0: - return - else: - yield cirq.CX(anc[depth - 1], anc[depth]) - yield and_gate.And(adjoint=True).on(inp[depth], out[depth], anc[depth]) - yield cirq.CX(anc[depth - 1], inp[depth]) - yield cirq.CX(inp[depth], out[depth]) - yield from self._right_building_block(inp, out, anc, depth - 1) - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - input_bits = qubits[: self.bitsize] - output_bits = qubits[self.bitsize :] - ancillas = context.qubit_manager.qalloc(self.bitsize - 1) - # Start off the addition by anding into the ancilla - yield and_gate.And().on(input_bits[0], output_bits[0], ancillas[0]) - # Left part of Fig.2 - yield from self._left_building_block(input_bits, output_bits, ancillas, 1) - yield cirq.CX(ancillas[-1], output_bits[-1]) - yield cirq.CX(input_bits[-1], output_bits[-1]) - # right part of Fig.2 - yield from self._right_building_block(input_bits, output_bits, ancillas, self.bitsize - 2) - yield and_gate.And(adjoint=True).on(input_bits[0], output_bits[0], ancillas[0]) - yield cirq.CX(input_bits[0], output_bits[0]) - context.qubit_manager.qfree(ancillas) - - def _t_complexity_(self) -> infra.TComplexity: - # There are N - 2 building blocks each with one And/And^dag contributing 13 cliffords and - # 6 CXs. In addition there is one additional And/And^dag pair and 3 CXs. - num_clifford = (self.bitsize - 2) * 19 + 16 - return infra.TComplexity(t=4 * self.bitsize - 4, clifford=num_clifford) - - def __repr__(self) -> str: - return f'cirq_ft.AdditionGate({self.bitsize})' - - -@deprecated_cirq_ft_class() -@attr.frozen(auto_attribs=True) -class AddMod(cirq.ArithmeticGate): - """Applies U_{M}_{add}|x> = |(x + add) % M> if x < M else |x>. - - Applies modular addition to input register `|x>` given parameters `mod` and `add_val` s.t. - 1) If integer `x` < `mod`: output is `|(x + add) % M>` - 2) If integer `x` >= `mod`: output is `|x>`. - - This condition is needed to ensure that the mapping of all input basis states (i.e. input - states |0>, |1>, ..., |2 ** bitsize - 1) to corresponding output states is bijective and thus - the gate is reversible. - - Also supports controlled version of the gate by specifying a per qubit control value as a tuple - of integers passed as `cv`. - """ - - bitsize: int - mod: int = attr.field() - add_val: int = 1 - cv: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - - @mod.validator - def _validate_mod(self, attribute, value): - if not 1 <= value <= 2**self.bitsize: - raise ValueError(f"mod: {value} must be between [1, {2 ** self.bitsize}].") - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - add_reg = (2,) * self.bitsize - control_reg = (2,) * len(self.cv) - return (control_reg, add_reg) if control_reg else (add_reg,) - - def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> "AddMod": - raise NotImplementedError() - - def apply(self, *args) -> Union[int, Iterable[int]]: - target_val = args[-1] - if target_val < self.mod: - new_target_val = (target_val + self.add_val) % self.mod - else: - new_target_val = target_val - if self.cv and args[0] != int(''.join(str(x) for x in self.cv), 2): - new_target_val = target_val - ret = (args[0], new_target_val) if self.cv else (new_target_val,) - return ret - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if b else '@(0)' for b in self.cv] - wire_symbols += [f"Add_{self.add_val}_Mod_{self.mod}"] * self.bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int) -> 'AddMod': - return AddMod(self.bitsize, self.mod, add_val=self.add_val * power, cv=self.cv) - - def __repr__(self) -> str: - return f'cirq_ft.AddMod({self.bitsize}, {self.mod}, {self.add_val}, {self.cv})' - - def _t_complexity_(self) -> infra.TComplexity: - # Rough cost as given in https://arxiv.org/abs/1905.09749 - return 5 * infra.t_complexity(AdditionGate(self.bitsize)) diff --git a/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py b/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py deleted file mode 100644 index 57515fe4a87..00000000000 --- a/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py +++ /dev/null @@ -1,441 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import itertools - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.infra import bit_tools -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def identity_map(n: int): - """Returns a dict of size `2**n` mapping each integer in range [0, 2**n) to itself.""" - return {i: i for i in range(2**n)} - - -@allow_deprecated_cirq_ft_use_in_tests -def test_less_than_gate(): - qubits = cirq.LineQubit.range(4) - gate = cirq_ft.LessThanGate(3, 5) - op = gate.on(*qubits) - circuit = cirq.Circuit(op) - basis_map = { - 0b_000_0: 0b_000_1, - 0b_000_1: 0b_000_0, - 0b_001_0: 0b_001_1, - 0b_001_1: 0b_001_0, - 0b_010_0: 0b_010_1, - 0b_010_1: 0b_010_0, - 0b_011_0: 0b_011_1, - 0b_011_1: 0b_011_0, - 0b_100_0: 0b_100_1, - 0b_100_1: 0b_100_0, - 0b_101_0: 0b_101_0, - 0b_101_1: 0b_101_1, - 0b_110_0: 0b_110_0, - 0b_110_1: 0b_110_1, - 0b_111_0: 0b_111_0, - 0b_111_1: 0b_111_1, - } - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - circuit += op**-1 - cirq.testing.assert_equivalent_computational_basis_map(identity_map(len(qubits)), circuit) - gate2 = cirq_ft.LessThanGate(4, 10) - assert gate.with_registers(*gate2.registers()) == gate2 - assert cirq.circuit_diagram_info(gate).wire_symbols == ("In(x)",) * 3 + ("+(x < 5)",) - assert (gate**1 is gate) and (gate**-1 is gate) - assert gate.__pow__(2) is NotImplemented - - -@pytest.mark.parametrize("bits", [*range(8)]) -@pytest.mark.parametrize("val", [3, 5, 7, 8, 9]) -@allow_deprecated_cirq_ft_use_in_tests -def test_decompose_less_than_gate(bits: int, val: int): - qubit_states = list(bit_tools.iter_bits(bits, 3)) - circuit = cirq.Circuit( - cirq.decompose_once(cirq_ft.LessThanGate(3, val).on(*cirq.LineQubit.range(4))) - ) - if val < 8: - initial_state = [0] * 4 + qubit_states + [0] - output_state = [0] * 4 + qubit_states + [int(bits < val)] - else: - # When val >= 2**number_qubits the decomposition doesn't create any ancilla since the - # answer is always 1. - initial_state = [0] - output_state = [1] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, sorted(circuit.all_qubits()), initial_state, output_state - ) - - -@pytest.mark.parametrize("n", [*range(2, 5)]) -@pytest.mark.parametrize("val", [3, 4, 5, 7, 8, 9]) -@allow_deprecated_cirq_ft_use_in_tests -def test_less_than_consistent_protocols(n: int, val: int): - g = cirq_ft.LessThanGate(n, val) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - # Test the unitary is self-inverse - u = cirq.unitary(g) - np.testing.assert_allclose(u @ u, np.eye(2 ** (n + 1))) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_in_less_equal_than_gate(): - qubits = cirq.LineQubit.range(7) - op = cirq_ft.LessThanEqualGate(3, 3).on(*qubits) - circuit = cirq.Circuit(op) - basis_map = {} - for in1, in2 in itertools.product(range(2**3), repeat=2): - for target_reg_val in range(2): - target_bin = bin(target_reg_val)[2:] - in1_bin = format(in1, '03b') - in2_bin = format(in2, '03b') - out_bin = bin(target_reg_val ^ (in1 <= in2))[2:] - true_out_int = target_reg_val ^ (in1 <= in2) - input_int = int(in1_bin + in2_bin + target_bin, 2) - output_int = int(in1_bin + in2_bin + out_bin, 2) - assert true_out_int == int(out_bin, 2) - basis_map[input_int] = output_int - - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - circuit += op**-1 - cirq.testing.assert_equivalent_computational_basis_map(identity_map(len(qubits)), circuit) - - -@pytest.mark.parametrize("x_bitsize", [*range(1, 5)]) -@pytest.mark.parametrize("y_bitsize", [*range(1, 5)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_less_than_equal_consistent_protocols(x_bitsize: int, y_bitsize: int): - g = cirq_ft.LessThanEqualGate(x_bitsize, y_bitsize) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - - # Decomposition works even when context is None. - qubits = cirq.LineQid.range(x_bitsize + y_bitsize + 1, dimension=2) - assert cirq.Circuit(g._decompose_with_context_(qubits=qubits)) == cirq.Circuit( - cirq.decompose_once( - g.on(*qubits), context=cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - ) - ) - - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - # Test the unitary is self-inverse - assert g**-1 is g - u = cirq.unitary(g) - np.testing.assert_allclose(u @ u, np.eye(2 ** (x_bitsize + y_bitsize + 1))) - # Test diagrams - expected_wire_symbols = ("In(x)",) * x_bitsize + ("In(y)",) * y_bitsize + ("+(x <= y)",) - assert cirq.circuit_diagram_info(g).wire_symbols == expected_wire_symbols - # Test with_registers - assert g.with_registers([2] * 4, [2] * 5, [2]) == cirq_ft.LessThanEqualGate(4, 5) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_contiguous_register_gate(): - gate = cirq_ft.ContiguousRegisterGate(3, 6) - circuit = cirq.Circuit(gate.on(*cirq.LineQubit.range(12))) - basis_map = {} - for p in range(2**3): - for q in range(p): - inp = f'0b_{p:03b}_{q:03b}_{0:06b}' - out = f'0b_{p:03b}_{q:03b}_{(p * (p - 1))//2 + q:06b}' - basis_map[int(inp, 2)] = int(out, 2) - - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - # Test the unitary is self-inverse - gate = cirq_ft.ContiguousRegisterGate(2, 4) - assert gate**-1 is gate - u = cirq.unitary(gate) - np.testing.assert_allclose(u @ u, np.eye(2 ** cirq.num_qubits(gate))) - # Test diagrams - expected_wire_symbols = ("In(x)",) * 2 + ("In(y)",) * 2 + ("+(x(x-1)/2 + y)",) * 4 - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_wire_symbols - # Test with_registers - assert gate.with_registers([2] * 3, [2] * 3, [2] * 6) == cirq_ft.ContiguousRegisterGate(3, 6) - - -@pytest.mark.parametrize('n', [*range(1, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_contiguous_register_gate_t_complexity(n): - gate = cirq_ft.ContiguousRegisterGate(n, 2 * n) - toffoli_complexity = cirq_ft.t_complexity(cirq.CCNOT) - assert cirq_ft.t_complexity(gate) == (n**2 + n - 1) * toffoli_complexity - - -@pytest.mark.parametrize('a,b,num_bits', itertools.product(range(4), range(4), range(3, 5))) -@allow_deprecated_cirq_ft_use_in_tests -def test_add(a: int, b: int, num_bits: int): - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits())[-num_anc:] - initial_state = [0] * (2 * num_bits + num_anc) - initial_state[:num_bits] = list(bit_tools.iter_bits(a, num_bits))[::-1] - initial_state[num_bits : 2 * num_bits] = list(bit_tools.iter_bits(b, num_bits))[::-1] - final_state = [0] * (2 * num_bits + num_bits - 1) - final_state[:num_bits] = list(bit_tools.iter_bits(a, num_bits))[::-1] - final_state[num_bits : 2 * num_bits] = list(bit_tools.iter_bits(a + b, num_bits))[::-1] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, qubits + ancillas, initial_state, final_state - ) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - # Test diagrams - expected_wire_symbols = ("In(x)",) * num_bits + ("In(y)/Out(x+y)",) * num_bits - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_wire_symbols - # Test with_registers - assert gate.with_registers([2] * 6, [2] * 6) == cirq_ft.AdditionGate(6) - - -@pytest.mark.parametrize('bitsize', [3]) -@pytest.mark.parametrize('mod', [5, 8]) -@pytest.mark.parametrize('add_val', [1, 2]) -@pytest.mark.parametrize('cv', [[], [0, 1], [1, 0], [1, 1]]) -@allow_deprecated_cirq_ft_use_in_tests -def test_add_mod_n(bitsize, mod, add_val, cv): - gate = cirq_ft.AddMod(bitsize, mod, add_val=add_val, cv=cv) - basis_map = {} - num_cvs = len(cv) - for x in range(2**bitsize): - y = (x + add_val) % mod if x < mod else x - if not num_cvs: - basis_map[x] = y - continue - for cb in range(2**num_cvs): - inp = f'0b_{cb:0{num_cvs}b}_{x:0{bitsize}b}' - if tuple(int(x) for x in f'{cb:0{num_cvs}b}') == tuple(cv): - out = f'0b_{cb:0{num_cvs}b}_{y:0{bitsize}b}' - basis_map[int(inp, 2)] = int(out, 2) - else: - basis_map[int(inp, 2)] = int(inp, 2) - - num_qubits = gate.num_qubits() - op = gate.on(*cirq.LineQubit.range(num_qubits)) - circuit = cirq.Circuit(op) - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - circuit += op**-1 - cirq.testing.assert_equivalent_computational_basis_map(identity_map(num_qubits), circuit) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - - -@allow_deprecated_cirq_ft_use_in_tests -def test_add_mod_n_protocols(): - with pytest.raises(ValueError, match="must be between"): - _ = cirq_ft.AddMod(3, 10) - add_one = cirq_ft.AddMod(3, 5, 1) - add_two = cirq_ft.AddMod(3, 5, 2, cv=[1, 0]) - - assert add_one == cirq_ft.AddMod(3, 5, 1) - assert add_one != add_two - assert hash(add_one) != hash(add_two) - assert add_two.cv == (1, 0) - assert cirq.circuit_diagram_info(add_two).wire_symbols == ('@', '@(0)') + ('Add_2_Mod_5',) * 3 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_add_truncated(): - num_bits = 3 - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits))) - ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) - assert len(ancillas) == num_anc - all_qubits = qubits + ancillas - # Corresponds to 2^2 + 2^2 (4 + 4 = 8 = 2^3 (needs num_bits = 4 to work properly)) - initial_state = [0, 0, 1, 0, 0, 1, 0, 0] - # Should be 1000 (or 0001 below) but bit falls off the end - final_state = [0, 0, 1, 0, 0, 0, 0, 0] - # increasing number of bits yields correct value - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - num_bits = 4 - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) - assert len(ancillas) == num_anc - all_qubits = qubits + ancillas - initial_state = [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0] - final_state = [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0] - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - num_bits = 3 - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) - assert len(ancillas) == num_anc - all_qubits = qubits + ancillas - # Corresponds to 2^2 + (2^2 + 2^1 + 2^0) (4 + 7 = 11 = 1011 (need num_bits=4 to work properly)) - initial_state = [0, 0, 1, 1, 1, 1, 0, 0] - # Should be 1011 (or 1101 below) but last two bits are lost - final_state = [0, 0, 1, 1, 1, 0, 0, 0] - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - -@pytest.mark.parametrize('a,b,num_bits', itertools.product(range(4), range(4), range(3, 5))) -@allow_deprecated_cirq_ft_use_in_tests -def test_subtract(a, b, num_bits): - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits())[-num_anc:] - initial_state = [0] * (2 * num_bits + num_anc) - initial_state[:num_bits] = list(bit_tools.iter_bits_twos_complement(a, num_bits))[::-1] - initial_state[num_bits : 2 * num_bits] = list( - bit_tools.iter_bits_twos_complement(-b, num_bits) - )[::-1] - final_state = [0] * (2 * num_bits + num_bits - 1) - final_state[:num_bits] = list(bit_tools.iter_bits_twos_complement(a, num_bits))[::-1] - final_state[num_bits : 2 * num_bits] = list( - bit_tools.iter_bits_twos_complement(a - b, num_bits) - )[::-1] - all_qubits = qubits + ancillas - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - -@pytest.mark.parametrize("n", [*range(3, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_addition_gate_t_complexity(n: int): - g = cirq_ft.AdditionGate(n) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - - -@pytest.mark.parametrize('a,b', itertools.product(range(2**3), repeat=2)) -@allow_deprecated_cirq_ft_use_in_tests -def test_add_no_decompose(a, b): - num_bits = 5 - qubits = cirq.LineQubit.range(2 * num_bits) - op = cirq_ft.AdditionGate(num_bits).on(*qubits) - circuit = cirq.Circuit(op) - basis_map = {} - a_bin = format(a, f'0{num_bits}b') - b_bin = format(b, f'0{num_bits}b') - out_bin = format(a + b, f'0{num_bits}b') - true_out_int = a + b - input_int = int(a_bin + b_bin, 2) - output_int = int(a_bin + out_bin, 2) - assert true_out_int == int(out_bin, 2) - basis_map[input_int] = output_int - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - - -@pytest.mark.parametrize("P,n", [(v, n) for n in range(1, 4) for v in range(1 << n)]) -@pytest.mark.parametrize("Q,m", [(v, n) for n in range(1, 4) for v in range(1 << n)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_decompose_less_than_equal_gate(P: int, n: int, Q: int, m: int): - qubit_states = list(bit_tools.iter_bits(P, n)) + list(bit_tools.iter_bits(Q, m)) - circuit = cirq.Circuit( - cirq.decompose_once( - cirq_ft.LessThanEqualGate(n, m).on(*cirq.LineQubit.range(n + m + 1)), - context=cirq.DecompositionContext(cirq.GreedyQubitManager(prefix='_c')), - ) - ) - qubit_order = tuple(sorted(circuit.all_qubits())) - num_ancillas = len(circuit.all_qubits()) - n - m - 1 - initial_state = qubit_states + [0] + [0] * num_ancillas - output_state = qubit_states + [int(P <= Q)] + [0] * num_ancillas - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, qubit_order, initial_state, output_state - ) - - -@pytest.mark.parametrize("adjoint", [False, True]) -@allow_deprecated_cirq_ft_use_in_tests -def test_single_qubit_compare_protocols(adjoint: bool): - g = cirq_ft.algos.SingleQubitCompare(adjoint=adjoint) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - expected_side = cirq_ft.infra.Side.LEFT if adjoint else cirq_ft.infra.Side.RIGHT - assert g.signature[2] == cirq_ft.Register('less_than', 1, side=expected_side) - assert g.signature[3] == cirq_ft.Register('greater_than', 1, side=expected_side) - - with pytest.raises(ValueError): - _ = g**0.5 # type: ignore - - assert g**2 == cirq.IdentityGate(4) - assert g**1 is g - assert g**-1 == cirq_ft.algos.SingleQubitCompare(adjoint=adjoint ^ True) - - -@pytest.mark.parametrize("v1,v2", [(v1, v2) for v1 in range(2) for v2 in range(2)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_single_qubit_compare(v1: int, v2: int): - g = cirq_ft.algos.SingleQubitCompare() - qubits = cirq.LineQid.range(4, dimension=2) - c = cirq.Circuit(g.on(*qubits)) - initial_state = [v1, v2, 0, 0] - output_state = [v1, int(v1 == v2), int(v1 < v2), int(v1 > v2)] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - c, sorted(c.all_qubits()), initial_state, output_state - ) - - # Check that g**-1 restores the qubits to their original state - c = cirq.Circuit(g.on(*qubits), (g**-1).on(*qubits)) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - c, sorted(c.all_qubits()), initial_state, initial_state - ) - - -@pytest.mark.parametrize("adjoint", [False, True]) -@allow_deprecated_cirq_ft_use_in_tests -def test_bi_qubits_mixer_protocols(adjoint: bool): - g = cirq_ft.algos.BiQubitsMixer(adjoint=adjoint) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - - assert g**1 is g - assert g**-1 == cirq_ft.algos.BiQubitsMixer(adjoint=adjoint ^ True) - - -@pytest.mark.parametrize("x", [*range(4)]) -@pytest.mark.parametrize("y", [*range(4)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_bi_qubits_mixer(x: int, y: int): - g = cirq_ft.algos.BiQubitsMixer() - qubits = cirq.LineQid.range(7, dimension=2) - c = cirq.Circuit(g.on(*qubits)) - x_1, x_0 = (x >> 1) & 1, x & 1 - y_1, y_0 = (y >> 1) & 1, y & 1 - initial_state = [x_1, x_0, y_1, y_0, 0, 0, 0] - result = ( - cirq.Simulator() - .simulate(c, initial_state=initial_state, qubit_order=qubits) - .dirac_notation()[1:-1] - ) - x_0, y_0 = int(result[1]), int(result[3]) - assert np.sign(x - y) == np.sign(x_0 - y_0) - - # Check that g**-1 restores the qubits to their original state - c = cirq.Circuit(g.on(*qubits), (g**-1).on(*qubits)) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - c, sorted(c.all_qubits()), initial_state, initial_state - ) diff --git a/cirq-ft/cirq_ft/algos/generic_select.ipynb b/cirq-ft/cirq_ft/algos/generic_select.ipynb deleted file mode 100644 index c84180565b4..00000000000 --- a/cirq-ft/cirq_ft/algos/generic_select.ipynb +++ /dev/null @@ -1,131 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "d852231f", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "ae4c107e", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Generic Select\n", - "\n", - "Gates for applying generic selected unitaries." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c5e21402", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "f217ffaf", - "metadata": { - "cq.autogen": "_make_GenericSelect.md" - }, - "source": [ - "## `GenericSelect`\n", - "A SELECT gate for selecting and applying operators from an array of `PauliString`s.\n", - "\n", - "$$\n", - "\\mathrm{SELECT} = \\sum_{l}|l \\rangle \\langle l| \\otimes U_l\n", - "$$\n", - "\n", - "Where $U_l$ is a member of the Pauli group.\n", - "\n", - "This gate uses the unary iteration scheme to apply `select_unitaries[selection]` to `target`\n", - "controlled on the single-bit `control` register.\n", - "\n", - "#### Parameters\n", - " - `selection_bitsize`: The size of the indexing `select` register. This should be at least `log2(len(select_unitaries))`\n", - " - `target_bitsize`: The size of the `target` register.\n", - " - `select_unitaries`: List of `DensePauliString`s to apply to the `target` register. Each dense pauli string must contain `target_bitsize` terms.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "488c76f9", - "metadata": { - "cq.autogen": "_make_GenericSelect.py" - }, - "outputs": [], - "source": [ - "from cirq_ft import GenericSelect\n", - "\n", - "target_bitsize = 4\n", - "us = ['XIXI', 'YIYI', 'ZZZZ', 'ZXYZ']\n", - "us = [cirq.DensePauliString(u) for u in us]\n", - "selection_bitsize = int(np.ceil(np.log2(len(us))))\n", - "g = cq_testing.GateHelper(\n", - " GenericSelect(selection_bitsize, target_bitsize, select_unitaries=us)\n", - ")\n", - "\n", - "display_gate_and_compilation(g, vertical=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "efb6062a", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/generic_select.py b/cirq-ft/cirq_ft/algos/generic_select.py deleted file mode 100644 index 6e86636960d..00000000000 --- a/cirq-ft/cirq_ft/algos/generic_select.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -"""Gates for applying generic selected unitaries.""" - -from functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import select_and_prepare, unary_iteration_gate - - -def _to_tuple(dps: Sequence[cirq.DensePauliString]) -> Tuple[cirq.DensePauliString, ...]: - return tuple(dps) - - -@attr.frozen -class GenericSelect(select_and_prepare.SelectOracle, unary_iteration_gate.UnaryIterationGate): - r"""A SELECT gate for selecting and applying operators from an array of `PauliString`s. - - $$ - \mathrm{SELECT} = \sum_{l}|l \rangle \langle l| \otimes U_l - $$ - - Where $U_l$ is a member of the Pauli group. - - This gate uses the unary iteration scheme to apply `select_unitaries[selection]` to `target` - controlled on the single-bit `control` register. - - Args: - selection_bitsize: The size of the indexing `select` register. This should be at least - `log2(len(select_unitaries))` - target_bitsize: The size of the `target` register. - select_unitaries: List of `DensePauliString`s to apply to the `target` register. Each - dense pauli string must contain `target_bitsize` terms. - control_val: Optional control value. If specified, a singly controlled gate is constructed. - """ - - selection_bitsize: int - target_bitsize: int - select_unitaries: Tuple[cirq.DensePauliString, ...] = attr.field(converter=_to_tuple) - control_val: Optional[int] = None - - def __attrs_post_init__(self): - if any(len(dps) != self.target_bitsize for dps in self.select_unitaries): - raise ValueError( - f"Each dense pauli string in {self.select_unitaries} should contain " - f"{self.target_bitsize} terms." - ) - min_bitsize = (len(self.select_unitaries) - 1).bit_length() - if self.selection_bitsize < min_bitsize: - raise ValueError( - f"selection_bitsize={self.selection_bitsize} should be at-least {min_bitsize}" - ) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if self.control_val is None else (infra.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister( - 'selection', self.selection_bitsize, len(self.select_unitaries) - ), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('target', self.target_bitsize),) - - def decompose_from_registers( - self, context, **quregs: NDArray[cirq.Qid] # type:ignore[type-var] - ) -> cirq.OP_TREE: - if self.control_val == 0: - yield cirq.X(*quregs['control']) - yield super(GenericSelect, self).decompose_from_registers(context=context, **quregs) - if self.control_val == 0: - yield cirq.X(*quregs['control']) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - selection: int, - control: cirq.Qid, - target: Sequence[cirq.Qid], - ) -> cirq.OP_TREE: - """Applies `self.select_unitaries[selection]`. - - Args: - context: `cirq.DecompositionContext` stores options for decomposing gates (eg: - cirq.QubitManager). - selection: takes on values [0, self.iteration_lengths[0]) - control: Qid that is the control qubit or qubits - target: Target register qubits - """ - ps = self.select_unitaries[selection].on(*target) - return ps.with_coefficient(np.sign(complex(ps.coefficient).real)).controlled_by(control) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'GenericSelect': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - return GenericSelect( - self.selection_bitsize, - self.target_bitsize, - self.select_unitaries, - control_val=control_values[0], - ) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def __repr__(self) -> str: - return ( - f'cirq_ft.GenericSelect(' - f'{self.selection_bitsize},' - f'{self.target_bitsize}, ' - f'{self.select_unitaries}, ' - f'{self.control_val})' - ) diff --git a/cirq-ft/cirq_ft/algos/generic_select_test.py b/cirq-ft/cirq_ft/algos/generic_select_test.py deleted file mode 100644 index 9b086c2cded..00000000000 --- a/cirq-ft/cirq_ft/algos/generic_select_test.py +++ /dev/null @@ -1,288 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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, Sequence - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def get_1d_Ising_hamiltonian( - qubits: Sequence[cirq.Qid], j_zz_strength: float = 1.0, gamma_x_strength: float = -1 -) -> cirq.PauliSum: - r"""A one dimensional ising model with periodic boundaries. - - $$ - H = -J\sum_{k=0}^{L-1}\sigma_{k}^{Z}\sigma_{(k+1)\%L}^{Z} - \Gamma\sum_{k=0}^{L-1}\sigma_{k}^{X} - $$ - - Args: - qubits: One qubit for each spin site. - j_zz_strength: The two-body ZZ potential strength, $J$. - gamma_x_strength: The one-body X potential strength, $\Gamma$. - - Returns: - cirq.PauliSum representing the Hamiltonian - """ - n_sites = len(qubits) - terms: List[cirq.PauliString] = [] - for k in range(n_sites): - terms.append( - cirq.PauliString( - {qubits[k]: cirq.Z, qubits[(k + 1) % n_sites]: cirq.Z}, coefficient=j_zz_strength - ) - ) - terms.append(cirq.PauliString({qubits[k]: cirq.X}, coefficient=gamma_x_strength)) - return cirq.PauliSum.from_pauli_strings(terms) - - -def get_1d_Ising_lcu_coeffs( - n_spins: int, j_zz_strength: float = np.pi / 3, gamma_x_strength: float = np.pi / 7 -) -> np.ndarray: - """Get LCU coefficients for a 1d ising Hamiltonian. - - The order of the terms is according to `get_1d_Ising_hamiltonian`, namely: ZZ's and X's - interleaved. - """ - spins = cirq.LineQubit.range(n_spins) - ham = get_1d_Ising_hamiltonian(spins, j_zz_strength, gamma_x_strength) - coeffs = np.array([term.coefficient.real for term in ham]) - lcu_coeffs = coeffs / np.sum(coeffs) - return lcu_coeffs - - -@pytest.mark.parametrize('control_val', [0, 1]) -@allow_deprecated_cirq_ft_use_in_tests -def test_ising_zero_bitflip_select(control_val): - num_sites = 4 - target_bitsize = num_sites - num_select_unitaries = 2 * num_sites - # PBC Ising in 1-D has num_sites ZZ operations and num_sites X operations. - # Thus 2 * num_sites Pauli ops - selection_bitsize = int(np.ceil(np.log2(num_select_unitaries))) - all_qubits = cirq.LineQubit.range(2 * selection_bitsize + target_bitsize + 1) - control, selection, target = ( - all_qubits[0], - all_qubits[1 : 2 * selection_bitsize : 2], - all_qubits[2 * selection_bitsize + 1 :], - ) - - # Get dense PauliString Hamiltonian terms - # right now we only handle positive interaction term values - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham] - # built select with unary iteration gate - op = cirq_ft.GenericSelect( - selection_bitsize=selection_bitsize, - target_bitsize=target_bitsize, - select_unitaries=dense_pauli_string_hamiltonian, - control_val=control_val, - ).on(control, *selection, *target) - circuit = cirq.Circuit(cirq.decompose(op)) - all_qubits = circuit.all_qubits() - - # now we need to have a superposition w.r.t all operators to act on target. - # Normally this would be generated by a PREPARE circuit but we will - # build it directly here. - for selection_integer in range(num_select_unitaries): - # turn on control bit to activate circuit - qubit_vals = {x: int(control_val) if x == control else 0 for x in all_qubits} - # Initialize selection bits appropriately - qubit_vals.update(zip(selection, iter_bits(selection_integer, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in all_qubits] - for i, pauli_val in enumerate(dense_pauli_string_hamiltonian[selection_integer]): - if pauli_val == cirq.X: - # Hamiltonian already defined on correct qubits so just take qid - qubit_vals[target[i]] = 1 - final_state = [qubit_vals[x] for x in all_qubits] - - cirq_ft.infra.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_ising_one_bitflip_select(): - num_sites = 4 - target_bitsize = num_sites - num_select_unitaries = 2 * num_sites - # PBC Ising in 1-D has num_sites ZZ operations and num_sites X operations. - # Thus 2 * num_sites Pauli ops - selection_bitsize = int(np.ceil(np.log2(num_select_unitaries))) - all_qubits = cirq.LineQubit.range(2 * selection_bitsize + target_bitsize + 1) - control, selection, target = ( - all_qubits[0], - all_qubits[1 : 2 * selection_bitsize : 2], - all_qubits[2 * selection_bitsize + 1 :], - ) - - # Get dense PauliString Hamiltonian terms - # right now we only handle positive interaction term values - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham] - # built select with unary iteration gate - op = cirq_ft.GenericSelect( - selection_bitsize=selection_bitsize, - target_bitsize=target_bitsize, - select_unitaries=dense_pauli_string_hamiltonian, - control_val=1, - ).on(control, *selection, *target) - circuit = cirq.Circuit(cirq.decompose(op)) - all_qubits = sorted(circuit.all_qubits()) - - # now we need to have a superposition w.r.t all operators to act on target. - # Normally this would be generated by a PREPARE circuit, but we will - # build it directly here. - for selection_integer in range(num_select_unitaries): - # turn on control bit to activate circuit - qubit_vals = {x: int(x == control) for x in all_qubits} - # Initialize selection bits appropriately - qubit_vals.update(zip(selection, iter_bits(selection_integer, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in all_qubits] - for i, pauli_val in enumerate(dense_pauli_string_hamiltonian[selection_integer]): - if pauli_val == cirq.X: - # Hamiltonian already defined on correct qubits so just take qid - qubit_vals[target[i]] = 1 - final_state = [qubit_vals[x] for x in all_qubits] - - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -def _fake_prepare( - positive_coefficients: np.ndarray, selection_register: List[cirq.Qid] -) -> cirq.OP_TREE: - pos_coeffs = positive_coefficients.flatten() - size_hilbert_of_reg = 2 ** len(selection_register) - assert len(pos_coeffs) <= size_hilbert_of_reg - # pad to 2**(len(selection_bitsize)) size - if len(pos_coeffs) < size_hilbert_of_reg: - pos_coeffs = np.hstack( - (pos_coeffs, np.array([0] * (size_hilbert_of_reg - len(pos_coeffs)))) - ) - - assert np.isclose(pos_coeffs.conj().T @ pos_coeffs, 1.0) - circuit = cirq.Circuit() - circuit.append(cirq.StatePreparationChannel(pos_coeffs).on(*selection_register)) - return circuit - - -@allow_deprecated_cirq_ft_use_in_tests -def test_select_application_to_eigenstates(): - # To validate the unary iteration correctly applies the Hamiltonian to a state we - # compare to directly applying Hamiltonian to the initial state. - # - # The target register starts in an eigenstate so = eig / lambda - sim = cirq.Simulator(dtype=np.complex128) - num_sites = 3 - target_bitsize = num_sites - num_select_unitaries = 2 * num_sites - # PBC Ising in 1-D has num_sites ZZ operations and num_sites X operations. - # Thus 2 * num_sites Pauli ops - selection_bitsize = int(np.ceil(np.log2(num_select_unitaries))) - all_qubits = cirq.LineQubit.range(2 * selection_bitsize + target_bitsize + 1) - control, selection, target = ( - all_qubits[0], - all_qubits[1 : 2 * selection_bitsize : 2], - all_qubits[2 * selection_bitsize + 1 :], - ) - - # Get dense PauliString Hamiltonian terms - # right now we only handle positive interaction term values - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham] - # built select with unary iteration gate - op = cirq_ft.GenericSelect( - selection_bitsize=selection_bitsize, - target_bitsize=target_bitsize, - select_unitaries=dense_pauli_string_hamiltonian, - control_val=1, - ).on(control, *selection, *target) - select_circuit = cirq.Circuit(cirq.decompose(op)) - all_qubits = select_circuit.all_qubits() - - coeffs = get_1d_Ising_lcu_coeffs(num_sites, 1, 1) - prep_circuit = _fake_prepare(np.sqrt(coeffs), selection) - turn_on_control = cirq.Circuit(cirq.X.on(control)) - - ising_eigs, ising_wfns = np.linalg.eigh(ham.matrix()) - qubitization_lambda = sum(xx.coefficient.real for xx in dense_pauli_string_hamiltonian) - for iw_idx, ie in enumerate(ising_eigs): - eigenstate_prep = cirq.Circuit() - eigenstate_prep.append( - cirq.StatePreparationChannel(ising_wfns[:, iw_idx].flatten()).on(*target) - ) - - input_circuit = turn_on_control + prep_circuit + eigenstate_prep - input_vec = sim.simulate(input_circuit, qubit_order=all_qubits).final_state_vector - final_circuit = input_circuit + select_circuit - out_vec = sim.simulate(final_circuit, qubit_order=all_qubits).final_state_vector - - # Overlap of inital_state and SELECT initial_state should be like applying H/lambda - # which should give (E / lambda) * initial_state - np.testing.assert_allclose(np.vdot(input_vec, out_vec), ie / qubitization_lambda, atol=1e-8) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_generic_select_raises(): - with pytest.raises(ValueError, match='should contain 3'): - _ = cirq_ft.GenericSelect(2, 3, [cirq.DensePauliString('Y')]) - - with pytest.raises(ValueError, match='should be at-least 3'): - _ = cirq_ft.GenericSelect(1, 2, [cirq.DensePauliString('XX')] * 5) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_generic_select_consistent_protocols_and_controlled(): - select_bitsize, num_select, num_sites = 3, 6, 3 - # Get Ising Hamiltonian - target = cirq.LineQubit.range(num_sites) - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dps_hamiltonian = [tt.dense(target) for tt in ham] - assert len(dps_hamiltonian) == num_select - - # Build GenericSelect gate. - gate = cirq_ft.GenericSelect(select_bitsize, num_sites, dps_hamiltonian) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq\nimport cirq_ft') - - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = gate.controlled(num_controls=2) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('generic_select') diff --git a/cirq-ft/cirq_ft/algos/hubbard_model.ipynb b/cirq-ft/cirq_ft/algos/hubbard_model.ipynb deleted file mode 100644 index 99f17654978..00000000000 --- a/cirq-ft/cirq_ft/algos/hubbard_model.ipynb +++ /dev/null @@ -1,292 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "16d9d3ca", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "1117ec28", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Hubbard Model\n", - "\n", - "Gates for Qubitizing the Hubbard Model.\n", - "\n", - "This follows section V. of the [Linear T Paper](https://arxiv.org/abs/1805.03662).\n", - "\n", - "The Hubbard model is a special case of the electronic structure Hamiltonian\n", - "restricted to spins on a planar grid.\n", - "\n", - "$$\n", - "H = -t \\sum_{\\langle p,q \\rangle, \\sigma} a_{p,\\sigma}^\\dagger a_{q,\\sigma}\n", - " + \\frac{u}{2} \\sum_{p,\\alpha\\ne\\beta} n_{p, \\alpha} n_{p, \\beta}\n", - "$$\n", - "\n", - "Under the Jordan-Wigner transformation this is\n", - "\n", - "$$\n", - "\\def\\Zvec{\\overrightarrow{Z}}\n", - "\\def\\hop#1{#1_{p,\\sigma} \\Zvec #1_{q,\\sigma}}\n", - "H = -\\frac{t}{2} \\sum_{\\langle p,q \\rangle, \\sigma} (\\hop{X} + \\hop{Y})\n", - " + \\frac{u}{8} \\sum_{p,\\alpha\\ne\\beta} Z_{p,\\alpha}Z_{p,\\beta}\n", - " - \\frac{u}{4} \\sum_{p,\\sigma} Z_{p,\\sigma} + \\frac{uN}{4}\\mathbb{1}\n", - "$$\n", - "\n", - "\n", - "This model consists of a PREPARE and SELECT operation where our selection operation has indices\n", - "for $p$, $\\alpha$, $q$, and $\\beta$ as well as two indicator bits $U$ and $V$. There are four cases\n", - "considered in both the PREPARE and SELECT operations corresponding to the terms in the Hamiltonian:\n", - "\n", - " - $U=1$, single-body Z\n", - " - $V=1$, spin-spin ZZ term\n", - " - $pq$, YZY term.\n", - "\n", - "See the documentation for `PrepareHubbard` and `SelectHubbard` for details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "079e71f9", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "1156cf4f", - "metadata": { - "cq.autogen": "_make_SelectHubbard.md" - }, - "source": [ - "## `SelectHubbard`\n", - "The SELECT operation optimized for the 2D Hubbard model.\n", - "\n", - "In contrast to the arbitrary chemistry Hamiltonian, we:\n", - " - explicitly consider the two dimensions of indices to permit optimization of the circuits.\n", - " - dispense with the `theta` parameter.\n", - "\n", - "If neither $U$ nor $V$ is set we apply the kinetic terms of the Hamiltonian:\n", - "\n", - "$$\n", - "-\\hop{X} \\quad p < q \\\\\n", - "-\\hop{Y} \\quad p > q\n", - "$$\n", - "\n", - "If $U$ is set we know $(p,\\alpha)=(q,\\beta)$ and apply the single-body term: $-Z_{p,\\alpha}$.\n", - "If $V$ is set we know $p=q, \\alpha=0$, and $\\beta=1$ and apply the spin term:\n", - "$Z_{p,\\alpha}Z_{p,\\beta}$\n", - "\n", - "The circuit for implementing $\\textit{C-SELECT}_{Hubbard}$ has a T-cost of $10 * N + log(N)$\n", - "and $0$ rotations.\n", - "\n", - "#### Parameters\n", - "- `x_dim`: the number of sites along the x axis.\n", - "- `y_dim`: the number of sites along the y axis.\n", - "- `control_val`: Optional bit specifying the control value for constructing a controlled\n", - " version of this gate. Defaults to None, which means no control.\n", - "\n", - "\n", - "#### Registers\n", - " - `control`: A control bit for the entire gate.\n", - " - `U`: Whether we're applying the single-site part of the potential.\n", - " - `V`: Whether we're applying the pairwise part of the potential.\n", - " - `p_x`: First set of site indices, x component.\n", - " - `p_y`: First set of site indices, y component.\n", - " - `alpha`: First set of sites' spin indicator.\n", - " - `q_x`: Second set of site indices, x component.\n", - " - `q_y`: Second set of site indices, y component.\n", - " - `beta`: Second set of sites' spin indicator.\n", - " - `target`: The system register to apply the select operation.\n", - "\n", - "#### References\n", - "Section V. and Fig. 19 of https://arxiv.org/abs/1805.03662.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6bfb9288", - "metadata": { - "cq.autogen": "_make_SelectHubbard.py" - }, - "outputs": [], - "source": [ - "from cirq_ft.algos.hubbard_model import *\n", - "\n", - "x_dim, y_dim, t = 2, 2, 5\n", - "mu = 4 * t\n", - "\n", - "select = cq_testing.GateHelper(\n", - " SelectHubbard(x_dim=x_dim, y_dim=y_dim, control_val=1)\n", - ")\n", - "display_gate_and_compilation(select, include_costs=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "09b0da48", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "x, y = [], []\n", - "for dim in range(2, 21):\n", - " select = SelectHubbard(x_dim=dim, y_dim=dim, control_val=1)\n", - " cost = cirq_ft.t_complexity(select)\n", - " N = 2 * dim * dim\n", - " logN = (2 * (dim - 1).bit_length() + 1)\n", - " # 2 * (4 * (N - 1)) : From 2 SelectMajoranaFermion gates.\n", - " # 4 * (N/2) : From 1 mulit-controlled ApplyToLthQubit gate on N / 2 targets.\n", - " # 2 * 7 * logN : From 2 CSWAPS on logN qubits corresponding to (p, q) select signature.\n", - " assert cost.t == 10 * N + 14 * logN - 8\n", - " assert cost.rotations == 0\n", - " x.append(N)\n", - " y.append(cost.t)\n", - " \n", - "plt.xlabel('N')\n", - "plt.ylabel('T-costs')\n", - "plt.title('$\\mathrm{SELECT}_\\mathrm{Hubbard}$')\n", - "plt.plot(x, y, label='$10 * N + 14 * logN - 8$', marker=\"o\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "fa794187", - "metadata": {}, - "source": [ - "## `PrepareHubbard`\n", - "The PREPARE operation optimized for the 2D Hubbard model.\n", - "\n", - "In contrast to the arbitrary chemistry Hamiltonian, we:\n", - " - explicitly consider the two dimensions of indices to permit optimization of the circuits.\n", - " - dispense with the `theta` parameter.\n", - "\n", - "The circuit for implementing $\\textit{PREPARE}_{Hubbard}$ has a T-cost of $O(log(N)$ and uses $O(1)$\n", - "single qubit rotations.\n", - "\n", - "\n", - "#### Parameters\n", - "- `x_dim`: the number of sites along the x axis.\n", - "- `y_dim`: the number of sites along the y axis.\n", - "- `t`: coefficient for hopping terms in the Hubbard model hamiltonian.\n", - "- `mu`: coefficient for single body Z term and two-body ZZ terms in the Hubbard model hamiltonian.\n", - "\n", - "#### Registers\n", - " - `control`: A control bit for the entire gate.\n", - " - `U`: Whether we're applying the single-site part of the potential.\n", - " - `V`: Whether we're applying the pairwise part of the potential.\n", - " - `p_x`: First set of site indices, x component.\n", - " - `p_y`: First set of site indices, y component.\n", - " - `alpha`: First set of sites' spin indicator.\n", - " - `q_x`: Second set of site indices, x component.\n", - " - `q_y`: Second set of site indices, y component.\n", - " - `beta`: Second set of sites' spin indicator.\n", - " - `target`: The system register to apply the select operation.\n", - " - `junk`: Temporary Work space.\n", - "\n", - "#### References\n", - "Section V. and Fig. 20 of https://arxiv.org/abs/1805.03662." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6464ad4f", - "metadata": {}, - "outputs": [], - "source": [ - "prepare = cq_testing.GateHelper(\n", - " PrepareHubbard(x_dim=x_dim, y_dim=x_dim, t=t, mu=mu)\n", - ")\n", - "display_gate_and_compilation(prepare, include_costs=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "54a4259c", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "x, y_t, y_max = [], [], []\n", - "for dim in range(2, 21):\n", - " prepare = PrepareHubbard(x_dim=dim, y_dim=dim, t=t, mu=mu)\n", - " cost = cirq_ft.t_complexity(prepare)\n", - " N = 2 * dim * dim\n", - " logN = 2 * (dim - 1).bit_length() + 1\n", - " assert cost.t <= 32 * logN\n", - " assert cost.rotations <= 2 * logN + 9\n", - " x.append(N)\n", - " y_t.append(cost.t)\n", - " y_max.append(32 * logN)\n", - " \n", - "plt.xlabel('N')\n", - "plt.ylabel('T-costs')\n", - "plt.title('$\\mathrm{PREPARE}_\\mathrm{Hubbard}$')\n", - "plt.plot(x, y_t, label='$O(logN)$ T-cost', marker=\"o\")\n", - "plt.plot(x, y_max, label='$32 * logN$')\n", - "plt.legend()\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/hubbard_model.py b/cirq-ft/cirq_ft/algos/hubbard_model.py deleted file mode 100644 index 18435bf265d..00000000000 --- a/cirq-ft/cirq_ft/algos/hubbard_model.py +++ /dev/null @@ -1,357 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -r"""Gates for Qubitizing the Hubbard Model. - -This follows section V. of the [Linear T Paper](https://arxiv.org/abs/1805.03662). - -The 2D Hubbard model is a special case of the electronic structure Hamiltonian -restricted to spins on a planar grid. - -$$ -H = -t \sum_{\langle p,q \rangle, \sigma} a_{p,\sigma}^\dagger a_{q,\sigma} - + \frac{u}{2} \sum_{p,\alpha\ne\beta} n_{p, \alpha} n_{p, \beta} -$$ - -Under the Jordan-Wigner transformation, this is - -$$ -\def\Zvec{\overrightarrow{Z}} -\def\hop#1{#1_{p,\sigma} \Zvec #1_{q,\sigma}} -H = -\frac{t}{2} \sum_{\langle p,q \rangle, \sigma} (\hop{X} + \hop{Y}) - + \frac{u}{8} \sum_{p,\alpha\ne\beta} Z_{p,\alpha}Z_{p,\beta} - - \frac{u}{4} \sum_{p,\sigma} Z_{p,\sigma} + \frac{uN}{4}\mathbb{1} -$$ - - -This model consists of a PREPARE and SELECT operation where our selection operation has indices -for $p$, $\alpha$, $q$, and $\beta$ as well as two indicator bits $U$ and $V$. There are four cases -considered in both the PREPARE and SELECT operations corresponding to the terms in the Hamiltonian: - - - $U=1$, single-body Z - - $V=1$, spin-spin ZZ term - - $pq$, YZY term. - -See the documentation for `PrepareHubbard` and `SelectHubbard` for details. -""" -from functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate, apply_gate_to_lth_target, arithmetic_gates -from cirq_ft.algos import prepare_uniform_superposition as prep_u -from cirq_ft.algos import ( - qubitization_walk_operator, - select_and_prepare, - selected_majorana_fermion, - swap_network, -) - - -@attr.frozen -class SelectHubbard(select_and_prepare.SelectOracle): - r"""The SELECT operation optimized for the 2D Hubbard model. - - In contrast to the arbitrary chemistry Hamiltonian, we: - - explicitly consider the two dimensions of indices to permit optimization of the circuits. - - dispense with the `theta` parameter. - - If neither $U$ nor $V$ is set we apply the kinetic terms of the Hamiltonian: - - $$ - -\hop{X} \quad p < q \\ - -\hop{Y} \quad p > q - $$ - - If $U$ is set we know $(p,\alpha)=(q,\beta)$ and apply the single-body term: $-Z_{p,\alpha}$. - If $V$ is set we know $p=q, \alpha=0$, and $\beta=1$ and apply the spin term: - $Z_{p,\alpha}Z_{p,\beta}$ - - The circuit for implementing $\textit{C-SELECT}_{Hubbard}$ has a T-cost of $10 * N + log(N)$ - and $0$ rotations. - - - Args: - x_dim: the number of sites along the x axis. - y_dim: the number of sites along the y axis. - control_val: Optional bit specifying the control value for constructing a controlled - version of this gate. Defaults to None, which means no control. - - Signature: - control: A control bit for the entire gate. - U: Whether we're applying the single-site part of the potential. - V: Whether we're applying the pairwise part of the potential. - p_x: First set of site indices, x component. - p_y: First set of site indices, y component. - alpha: First set of sites' spin indicator. - q_x: Second set of site indices, x component. - q_y: Second set of site indices, y component. - beta: Second set of sites' spin indicator. - target: The system register to apply the select operation. - - References: - Section V. and Fig. 19 of https://arxiv.org/abs/1805.03662. - """ - - x_dim: int - y_dim: int - control_val: Optional[int] = None - - def __attrs_post_init__(self): - if self.x_dim != self.y_dim: - raise NotImplementedError("Currently only supports the case where x_dim=y_dim.") - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if self.control_val is None else (infra.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister('U', 1, 2), - infra.SelectionRegister('V', 1, 2), - infra.SelectionRegister('p_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('p_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('alpha', 1, 2), - infra.SelectionRegister('q_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('q_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('beta', 1, 2), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('target', self.x_dim * self.y_dim * 2),) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - p_x, p_y, q_x, q_y = quregs['p_x'], quregs['p_y'], quregs['q_x'], quregs['q_y'] - U, V, alpha, beta = quregs['U'], quregs['V'], quregs['alpha'], quregs['beta'] - control, target = quregs.get('control', ()), quregs['target'] - - yield selected_majorana_fermion.SelectedMajoranaFermionGate( - selection_regs=( - infra.SelectionRegister('alpha', 1, 2), - infra.SelectionRegister( - 'p_y', self.signature.get_left('p_y').total_bits(), self.y_dim - ), - infra.SelectionRegister( - 'p_x', self.signature.get_left('p_x').total_bits(), self.x_dim - ), - ), - control_regs=self.control_registers, - target_gate=cirq.Y, - ).on_registers(control=control, p_x=p_x, p_y=p_y, alpha=alpha, target=target) - - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_x, target_y=q_x) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_y, target_y=q_y) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=alpha, target_y=beta) - - q_selection_regs = ( - infra.SelectionRegister('beta', 1, 2), - infra.SelectionRegister('q_y', self.signature.get_left('q_y').total_bits(), self.y_dim), - infra.SelectionRegister('q_x', self.signature.get_left('q_x').total_bits(), self.x_dim), - ) - yield selected_majorana_fermion.SelectedMajoranaFermionGate( - selection_regs=q_selection_regs, control_regs=self.control_registers, target_gate=cirq.X - ).on_registers(control=control, q_x=q_x, q_y=q_y, beta=beta, target=target) - - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=alpha, target_y=beta) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_y, target_y=q_y) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_x, target_y=q_x) - - yield ( - cirq.S(*control) ** -1 if control else cirq.global_phase_operation(-1j) - ) # Fix errant i from XY=iZ - yield cirq.Z(*U).controlled_by(*control) # Fix errant -1 from multiple pauli applications - - target_qubits_for_apply_to_lth_gate = [ - target[np.ravel_multi_index((1, qy, qx), (2, self.y_dim, self.x_dim))] - for qx in range(self.x_dim) - for qy in range(self.y_dim) - ] - - yield apply_gate_to_lth_target.ApplyGateToLthQubit( - selection_regs=( - infra.SelectionRegister( - 'q_y', self.signature.get_left('q_y').total_bits(), self.y_dim - ), - infra.SelectionRegister( - 'q_x', self.signature.get_left('q_x').total_bits(), self.x_dim - ), - ), - nth_gate=lambda *_: cirq.Z, - control_regs=infra.Register('control', 1 + infra.total_bits(self.control_registers)), - ).on_registers( - q_x=q_x, q_y=q_y, control=[*V, *control], target=target_qubits_for_apply_to_lth_gate - ) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'SelectHubbard': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - return SelectHubbard(self.x_dim, self.y_dim, control_val=control_values[0]) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - info = super(SelectHubbard, self)._circuit_diagram_info_(args) - if self.control_val is None: - return info - ctrl = ('@' if self.control_val else '@(0)',) - return info.with_wire_symbols(ctrl + info.wire_symbols[0:1] + info.wire_symbols[2:]) - - def __repr__(self) -> str: - return f'cirq_ft.SelectHubbard({self.x_dim}, {self.y_dim}, {self.control_val})' - - -@attr.frozen -class PrepareHubbard(select_and_prepare.PrepareOracle): - r"""The PREPARE operation optimized for the 2D Hubbard model. - - In contrast to the arbitrary chemistry Hamiltonian, we: - - explicitly consider the two dimensions of indices to permit optimization of the circuits. - - dispense with the `theta` parameter. - - The circuit for implementing $\textit{PREPARE}_{Hubbard}$ has a T-cost of $O(log(N)$ - and uses $O(1)$ single qubit rotations. - - Args: - x_dim: the number of sites along the x axis. - y_dim: the number of sites along the y axis. - t: coefficient for hopping terms in the Hubbard model hamiltonian. - mu: coefficient for single body Z term and two-body ZZ terms in the Hubbard model - hamiltonian. - - Signature: - control: A control bit for the entire gate. - U: Whether we're applying the single-site part of the potential. - V: Whether we're applying the pairwise part of the potential. - p_x: First set of site indices, x component. - p_y: First set of site indices, y component. - alpha: First set of sites' spin indicator. - q_x: Second set of site indices, x component. - q_y: Second set of site indices, y component. - beta: Second set of sites' spin indicator. - target: The system register to apply the select operation. - junk: Temporary Work space. - - References: - Section V. and Fig. 20 of https://arxiv.org/abs/1805.03662. - """ - - x_dim: int - y_dim: int - t: int - mu: int - - def __attrs_post_init__(self): - if self.x_dim != self.y_dim: - raise NotImplementedError("Currently only supports the case where x_dim=y_dim.") - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister('U', 1, 2), - infra.SelectionRegister('V', 1, 2), - infra.SelectionRegister('p_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('p_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('alpha', 1, 2), - infra.SelectionRegister('q_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('q_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('beta', 1, 2), - ) - - @cached_property - def junk_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('temp', 2),) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.junk_registers]) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - p_x, p_y, q_x, q_y = quregs['p_x'], quregs['p_y'], quregs['q_x'], quregs['q_y'] - U, V, alpha, beta = quregs['U'], quregs['V'], quregs['alpha'], quregs['beta'] - temp = quregs['temp'] - - N = self.x_dim * self.y_dim * 2 - qlambda = 2 * N * self.t + (N * self.mu) // 2 - yield cirq.Ry(rads=2 * np.arccos(np.sqrt(self.t * N / qlambda))).on(*V) - yield cirq.Ry(rads=2 * np.arccos(np.sqrt(1 / 5))).on(*U).controlled_by(*V) - yield prep_u.PrepareUniformSuperposition(self.x_dim).on_registers(controls=[], target=p_x) - yield prep_u.PrepareUniformSuperposition(self.y_dim).on_registers(controls=[], target=p_y) - yield cirq.H.on_each(*temp) - yield cirq.CNOT(*U, *V) - yield cirq.X(*beta) - yield from [cirq.X(*V), cirq.H(*alpha).controlled_by(*V), cirq.CX(*V, *beta), cirq.X(*V)] - yield cirq.Circuit(cirq.CNOT.on_each([*zip([*p_x, *p_y, *alpha], [*q_x, *q_y, *beta])])) - yield swap_network.MultiTargetCSwap.make_on(control=temp[:1], target_x=q_x, target_y=q_y) - yield arithmetic_gates.AddMod(len(q_x), self.x_dim, add_val=1, cv=[0, 0]).on(*U, *V, *q_x) - yield swap_network.MultiTargetCSwap.make_on(control=temp[:1], target_x=q_x, target_y=q_y) - - and_target = context.qubit_manager.qalloc(1) - and_anc = context.qubit_manager.qalloc(1) - yield and_gate.And(cv=(0, 0, 1)).on_registers( - ctrl=np.array([U, V, temp[-1:]]), junk=np.array([and_anc]), target=and_target - ) - yield swap_network.MultiTargetCSwap.make_on( - control=and_target, target_x=[*p_x, *p_y, *alpha], target_y=[*q_x, *q_y, *beta] - ) - yield and_gate.And(cv=(0, 0, 1), adjoint=True).on_registers( - ctrl=np.array([U, V, temp[-1:]]), junk=np.array([and_anc]), target=and_target - ) - context.qubit_manager.qfree([*and_anc, *and_target]) - - def __repr__(self) -> str: - return f'cirq_ft.PrepareHubbard({self.x_dim}, {self.y_dim}, {self.t}, {self.mu})' - - -def get_walk_operator_for_hubbard_model( - x_dim: int, y_dim: int, t: int, mu: int -) -> 'qubitization_walk_operator.QubitizationWalkOperator': - select = SelectHubbard(x_dim, y_dim) - prepare = PrepareHubbard(x_dim, y_dim, t, mu) - return qubitization_walk_operator.QubitizationWalkOperator(select=select, prepare=prepare) diff --git a/cirq-ft/cirq_ft/algos/hubbard_model_test.py b/cirq-ft/cirq_ft/algos/hubbard_model_test.py deleted file mode 100644 index f1545c1b281..00000000000 --- a/cirq-ft/cirq_ft/algos/hubbard_model_test.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize('dim', [*range(2, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_select_t_complexity(dim): - select = cirq_ft.SelectHubbard(x_dim=dim, y_dim=dim, control_val=1) - cost = cirq_ft.t_complexity(select) - N = 2 * dim * dim - logN = 2 * (dim - 1).bit_length() + 1 - assert cost.t == 10 * N + 14 * logN - 8 - assert cost.rotations == 0 - - -@pytest.mark.parametrize('dim', [*range(2, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_t_complexity(dim): - prepare = cirq_ft.PrepareHubbard(x_dim=dim, y_dim=dim, t=2, mu=8) - cost = cirq_ft.t_complexity(prepare) - logN = 2 * (dim - 1).bit_length() + 1 - assert cost.t <= 32 * logN - # TODO(#233): The rotation count should reduce to a constant once cost for Controlled-H - # gates is recognized as $2$ T-gates instead of $2$ rotations. - assert cost.rotations <= 2 * logN + 9 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_hubbard_model_consistent_protocols(): - select_gate = cirq_ft.SelectHubbard(x_dim=2, y_dim=2) - prepare_gate = cirq_ft.PrepareHubbard(x_dim=2, y_dim=2, t=1, mu=2) - - # Test equivalent repr - cirq.testing.assert_equivalent_repr(select_gate, setup_code='import cirq_ft') - cirq.testing.assert_equivalent_repr(prepare_gate, setup_code='import cirq_ft') - - # Build controlled SELECT gate - select_op = select_gate.on_registers(**infra.get_named_qubits(select_gate.signature)) - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - select_gate.controlled(), - select_gate.controlled(num_controls=1), - select_gate.controlled(control_values=(1,)), - select_op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - select_gate.controlled(control_values=(0,)), - select_gate.controlled(num_controls=1, control_values=(0,)), - select_op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = select_gate.controlled(num_controls=2) - - # Test diagrams - expected_symbols = ['U', 'V', 'p_x', 'p_y', 'alpha', 'q_x', 'q_y', 'beta'] - expected_symbols += ['target'] * 8 - expected_symbols[0] = 'SelectHubbard' - assert cirq.circuit_diagram_info(select_gate).wire_symbols == tuple(expected_symbols) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('hubbard_model') diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/__init__.py b/cirq-ft/cirq_ft/algos/mean_estimation/__init__.py deleted file mode 100644 index 4bf33c45db6..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 cirq_ft.algos.mean_estimation.arctan import ArcTan -from cirq_ft.algos.mean_estimation.complex_phase_oracle import ComplexPhaseOracle -from cirq_ft.algos.mean_estimation.mean_estimation_operator import ( - CodeForRandomVariable, - MeanEstimationOperator, -) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/arctan.py b/cirq-ft/cirq_ft/algos/mean_estimation/arctan.py deleted file mode 100644 index 08678a1fdd4..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/arctan.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 Iterable, Sequence, Union - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.deprecation import deprecated_cirq_ft_class - - -@deprecated_cirq_ft_class() -@attr.frozen -class ArcTan(cirq.ArithmeticGate): - r"""Applies U|x>|0>|0000...0> = |x>|sign>|abs(-2 arctan(x) / pi)>. - - Args: - selection_bitsize: The bitsize of input register |x>. - target_bitsize: The bitsize of output register. The computed quantity, - $\abs(-2 * \arctan(x) / \pi)$ is stored as a fixed-length binary approximation - in the output register of size `target_bitsize`. - """ - - selection_bitsize: int - target_bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return (2,) * self.selection_bitsize, (2,), (2,) * self.target_bitsize - - def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> "ArcTan": - raise NotImplementedError() - - def apply(self, *register_values: int) -> Union[int, Iterable[int]]: - input_val, target_sign, target_val = register_values - output_val = -2 * np.arctan(input_val, dtype=np.double) / np.pi - assert -1 <= output_val <= 1 - output_sign, output_bin = infra.bit_tools.float_as_fixed_width_int( - output_val, 1 + self.target_bitsize - ) - return input_val, target_sign ^ output_sign, target_val ^ output_bin - - def _t_complexity_(self) -> infra.TComplexity: - # Approximate T-complexity of O(target_bitsize) - return infra.TComplexity(t=self.target_bitsize) - - def __pow__(self, power) -> 'ArcTan': - if power in [+1, -1]: - return self - raise NotImplementedError("__pow__ is only implemented for +1/-1.") # pragma: no cover diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/arctan_test.py b/cirq-ft/cirq_ft/algos/mean_estimation/arctan_test.py deleted file mode 100644 index 3f716a1f206..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/arctan_test.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.algos.mean_estimation.arctan import ArcTan -from cirq_ft.infra.bit_tools import iter_bits_fixed_point -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize('selection_bitsize', [3, 4]) -@pytest.mark.parametrize('target_bitsize', [3, 5, 6]) -@allow_deprecated_cirq_ft_use_in_tests -def test_arctan(selection_bitsize, target_bitsize): - gate = ArcTan(selection_bitsize, target_bitsize) - maps = {} - for x in range(2**selection_bitsize): - inp = f'0b_{x:0{selection_bitsize}b}_0_{0:0{target_bitsize}b}' - y = -2 * np.arctan(x) / np.pi - bits = [*iter_bits_fixed_point(y, target_bitsize + 1, signed=True)] - sign, y_bin = bits[0], bits[1:] - y_bin_str = ''.join(str(b) for b in y_bin) - out = f'0b_{x:0{selection_bitsize}b}_{sign}_{y_bin_str}' - maps[int(inp, 2)] = int(out, 2) - num_qubits = gate.num_qubits() - op = gate.on(*cirq.LineQubit.range(num_qubits)) - circuit = cirq.Circuit(op) - cirq.testing.assert_equivalent_computational_basis_map(maps, circuit) - circuit += op**-1 - cirq.testing.assert_allclose_up_to_global_phase( - circuit.unitary(), np.diag([1] * 2**num_qubits), atol=1e-8 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_arctan_t_complexity(): - gate = ArcTan(4, 5) - assert cirq_ft.t_complexity(gate) == cirq_ft.TComplexity(t=5) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle.py b/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle.py deleted file mode 100644 index 7f482293c95..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import select_and_prepare -from cirq_ft.algos.mean_estimation import arctan - - -@attr.frozen -class ComplexPhaseOracle(infra.GateWithRegisters): - r"""Applies $ROT_{y}|l>|garbage_{l}> = exp(i * -2arctan{y_{l}})|l>|garbage_{l}>$. - - TODO(#6142): This currently assumes that the random variable `y_{l}` only takes integer - values. This constraint can be removed by using a standardized floating point to - binary encoding, like IEEE 754, to encode arbitrary floats in the binary target - register and use them to compute the more accurate $-2arctan{y_{l}}$ for any arbitrary - $y_{l}$. - """ - - encoder: select_and_prepare.SelectOracle - arctan_bitsize: int = 32 - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.encoder.control_registers - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.encoder.selection_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.control_registers, *self.selection_registers]) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - qm = context.qubit_manager - target_reg = { - reg.name: qm.qalloc(reg.total_bits()) for reg in self.encoder.target_registers - } - target_qubits = infra.merge_qubits(self.encoder.target_registers, **target_reg) - encoder_op = self.encoder.on_registers(**quregs, **target_reg) - - arctan_sign, arctan_target = qm.qalloc(1), qm.qalloc(self.arctan_bitsize) - arctan_op = arctan.ArcTan(len(target_qubits), self.arctan_bitsize).on( - *target_qubits, *arctan_sign, *arctan_target - ) - - yield encoder_op - yield arctan_op - for i, q in enumerate(arctan_target): - yield (cirq.Z(q) ** (1 / 2 ** (1 + i))).controlled_by(*arctan_sign, control_values=[0]) - yield (cirq.Z(q) ** (-1 / 2 ** (1 + i))).controlled_by(*arctan_sign, control_values=[1]) - - yield cirq.inverse(arctan_op) - yield cirq.inverse(encoder_op) - - qm.qfree([*arctan_sign, *arctan_target, *target_qubits]) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@'] * infra.total_bits(self.control_registers) - wire_symbols += ['ROTy'] * infra.total_bits(self.selection_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle_test.py b/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle_test.py deleted file mode 100644 index 49dd34bbbe6..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle_test.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -import math -from typing import Optional, Tuple - -import cirq -import cirq_ft -import numpy as np -import pytest -from attr import frozen -from cirq_ft.algos.mean_estimation.complex_phase_oracle import ComplexPhaseOracle -from cirq_ft.infra import bit_tools -from cirq_ft.infra import testing as cq_testing -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@frozen -class ExampleSelect(cirq_ft.SelectOracle): - bitsize: int - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () if self.control_val is None else (cirq_ft.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('selection', self.bitsize),) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('target', self.bitsize),) - - def decompose_from_registers(self, context, selection, target): - yield [cirq.CNOT(s, t) for s, t in zip(selection, target)] - - -@pytest.mark.parametrize('bitsize', [2, 3, 4, 5]) -@pytest.mark.parametrize('arctan_bitsize', [5, 6, 7]) -@allow_deprecated_cirq_ft_use_in_tests -def test_phase_oracle(bitsize: int, arctan_bitsize: int): - phase_oracle = ComplexPhaseOracle(ExampleSelect(bitsize), arctan_bitsize) - g = cq_testing.GateHelper(phase_oracle) - - # Prepare uniform superposition state on selection register and apply phase oracle. - circuit = cirq.Circuit(cirq.H.on_each(*g.quregs['selection'])) - circuit += cirq.Circuit(cirq.decompose_once(g.operation)) - - # Simulate the circut and test output. - qubit_order = cirq.QubitOrder.explicit(g.quregs['selection'], fallback=cirq.QubitOrder.DEFAULT) - result = cirq.Simulator(dtype=np.complex128).simulate(circuit, qubit_order=qubit_order) - state_vector = result.final_state_vector - state_vector = state_vector.reshape(2**bitsize, len(state_vector) // 2**bitsize) - prepared_state = state_vector.sum(axis=1) - for x in range(2**bitsize): - output_val = -2 * np.arctan(x, dtype=np.double) / np.pi - output_bits = [*bit_tools.iter_bits_fixed_point(np.abs(output_val), arctan_bitsize)] - approx_val = np.sign(output_val) * math.fsum( - [b * (1 / 2 ** (1 + i)) for i, b in enumerate(output_bits)] - ) - - assert math.isclose(output_val, approx_val, abs_tol=1 / 2**bitsize), output_bits - - y = np.exp(1j * approx_val * np.pi) / np.sqrt(2**bitsize) - assert np.isclose(prepared_state[x], y) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_phase_oracle_consistent_protocols(): - bitsize, arctan_bitsize = 3, 5 - gate = ComplexPhaseOracle(ExampleSelect(bitsize, 1), arctan_bitsize) - expected_symbols = ('@',) + ('ROTy',) * bitsize - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_symbols diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator.py b/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator.py deleted file mode 100644 index 8d905958e76..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import reflection_using_prepare as rup -from cirq_ft.algos import select_and_prepare as sp -from cirq_ft.algos.mean_estimation import complex_phase_oracle - - -@attr.frozen -class CodeForRandomVariable: - r"""A collection of `encoder` and `synthesizer` for a random variable y. - - We say we have "the code" for a random variable $y$ defined on a probability space - $(W, p)$ if we have both, a synthesizer and an encoder defined as follows: - - The synthesizer is responsible to "prepare" the state - $\sum_{w \in W} \sqrt{p(w)} |w> |garbage_{w}>$ on the "selection register" $w$ and potentially - using a "junk register" corresponding to $|garbage_{w}>$. Thus, for convenience, the synthesizer - follows the LCU PREPARE Oracle API. - $$ - synthesizer|0> = \sum_{w \in W} \sqrt{p(w)} |w> |garbage_{w}> - $$ - - - The encoder is responsible to encode the value of random variable $y(w)$ in a "target register" - when the corresponding "selection register" stores integer $w$. Thus, for convenience, the - encoder follows the LCU SELECT Oracle API. - $$ - encoder|w>|0^b> = |w>|y(w)> - $$ - where b is the number of bits required to encode the real range of random variable y. - - References: - https://arxiv.org/abs/2208.07544, Definition 2.2 for synthesizer (P) and - Definition 2.10 for encoder (Y). - """ - - synthesizer: sp.PrepareOracle - encoder: sp.SelectOracle - - def __attrs_post_init__(self): - assert self.synthesizer.selection_registers == self.encoder.selection_registers - - -@attr.frozen -class MeanEstimationOperator(infra.GateWithRegisters): - r"""Mean estimation operator $U=REFL_{p} ROT_{y}$ as per Sec 3.1 of arxiv.org:2208.07544. - - The MeanEstimationOperator (aka KO Operator) expects `CodeForRandomVariable` to specify the - synthesizer and encoder, that follows LCU SELECT/PREPARE API for convenience. It is composed - of two unitaries: - - - REFL_{p}: Reflection around the state prepared by synthesizer $P$. It applies the unitary - $P^{\dagger}(2|0><0| - I)P$. - - ROT_{y}: Applies a complex phase $\exp(i * -2\arctan{y_{w}})$ when the selection register - stores $w$. This is achieved by using the encoder to encode $y(w)$ in a temporary target - register. - - Note that both $REFL_{p}$ and $ROT_{y}$ only act upon a selection register, thus mean estimation - operator expects only a selection register (and a control register, for a controlled version for - phase estimation). - """ - - code: CodeForRandomVariable - cv: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - power: int = 1 - arctan_bitsize: int = 32 - - @cv.validator - def _validate_cv(self, attribute, value): - assert value in [(), (0,), (1,)] - - @cached_property - def reflect(self) -> rup.ReflectionUsingPrepare: - return rup.ReflectionUsingPrepare( - self.code.synthesizer, control_val=None if self.cv == () else self.cv[0] - ) - - @cached_property - def select(self) -> complex_phase_oracle.ComplexPhaseOracle: - return complex_phase_oracle.ComplexPhaseOracle(self.code.encoder, self.arctan_bitsize) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.code.encoder.control_registers - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.code.encoder.selection_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.control_registers, *self.selection_registers]) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - select_reg = {reg.name: quregs[reg.name] for reg in self.select.signature} - reflect_reg = {reg.name: quregs[reg.name] for reg in self.reflect.signature} - select_op = self.select.on_registers(**select_reg) - reflect_op = self.reflect.on_registers(**reflect_reg) - for _ in range(self.power): - yield select_op - # Add a -1 global phase since `ReflectUsingPrepare` applies $R_{s} = I - 2|s> cirq.CircuitDiagramInfo: - wire_symbols = [] if self.cv == () else [["@(0)", "@"][self.cv[0]]] - wire_symbols += ['U_ko'] * ( - infra.total_bits(self.signature) - infra.total_bits(self.control_registers) - ) - if self.power != 1: - wire_symbols[-1] = f'U_ko^{self.power}' - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'MeanEstimationOperator': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and len(control_values) == 1 - and isinstance(control_values[0], int) - and not self.cv - ): - c_select = self.code.encoder.controlled(control_values=control_values) - assert isinstance(c_select, sp.SelectOracle) - return MeanEstimationOperator( - CodeForRandomVariable(encoder=c_select, synthesizer=self.code.synthesizer), - cv=self.cv + (control_values[0],), - power=self.power, - arctan_bitsize=self.arctan_bitsize, - ) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def with_power(self, new_power: int) -> 'MeanEstimationOperator': - return MeanEstimationOperator( - self.code, cv=self.cv, power=new_power, arctan_bitsize=self.arctan_bitsize - ) - - def __pow__(self, power: int): - return self.with_power(self.power * power) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator_test.py b/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator_test.py deleted file mode 100644 index 19864608bf2..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator_test.py +++ /dev/null @@ -1,295 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Optional, Sequence, Tuple - -import cirq -import cirq_ft -import numpy as np -import pytest -from attr import frozen -from cirq_ft import infra -from cirq_ft.algos.mean_estimation import CodeForRandomVariable, MeanEstimationOperator -from cirq_ft.infra import bit_tools -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@frozen -class BernoulliSynthesizer(cirq_ft.PrepareOracle): - r"""Synthesizes the state $sqrt(1 - p)|00..00> + sqrt(p)|11..11>$""" - - p: float - nqubits: int - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('q', self.nqubits, 2),) - - def decompose_from_registers( # type:ignore[override] - self, context, q: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - theta = np.arccos(np.sqrt(1 - self.p)) - yield cirq.ry(2 * theta).on(q[0]) - yield [cirq.CNOT(q[0], q[i]) for i in range(1, len(q))] - - -@frozen -class BernoulliEncoder(cirq_ft.SelectOracle): - r"""Encodes Bernoulli random variable y0/y1 as $Enc|ii..i>|0> = |ii..i>|y_{i}>$ where i=0/1.""" - - p: float - y: Tuple[int, int] - selection_bitsize: int - target_bitsize: int - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () if self.control_val is None else (cirq_ft.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('q', self.selection_bitsize, 2),) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('t', self.target_bitsize),) - - def decompose_from_registers( # type:ignore[override] - self, context, q: Sequence[cirq.Qid], t: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - y0_bin = bit_tools.iter_bits(self.y[0], self.target_bitsize) - y1_bin = bit_tools.iter_bits(self.y[1], self.target_bitsize) - - for y0, y1, tq in zip(y0_bin, y1_bin, t): - if y0: - yield cirq.X(tq).controlled_by( # pragma: no cover - *q, control_values=[0] * self.selection_bitsize # pragma: no cover - ) # pragma: no cover - if y1: - yield cirq.X(tq).controlled_by(*q, control_values=[1] * self.selection_bitsize) - - def controlled(self, *args, **kwargs): - cv = kwargs['control_values'][0] - return BernoulliEncoder(self.p, self.y, self.selection_bitsize, self.target_bitsize, cv) - - @cached_property - def mu(self) -> float: - return self.p * self.y[1] + (1 - self.p) * self.y[0] - - @cached_property - def s_square(self) -> float: - return self.p * (self.y[1] ** 2) + (1 - self.p) * (self.y[0] ** 2) - - -def overlap(v1: np.ndarray, v2: np.ndarray) -> float: - return np.abs(np.vdot(v1, v2)) ** 2 - - -def satisfies_theorem_321( - synthesizer: cirq_ft.PrepareOracle, - encoder: cirq_ft.SelectOracle, - c: float, - s: float, - mu: float, - arctan_bitsize: int, -): - r"""Verifies Theorem 3.21 of https://arxiv.org/abs/2208.07544 - - Pr[∣sin(θ/2)∣ ∈ ∣µ∣ / √(1 + s ** 2) . [1 / (1 + cs), 1 / (1 - cs)]] >= (1 - 2 / c**2) - """ - code = CodeForRandomVariable(synthesizer=synthesizer, encoder=encoder) - mean_gate = MeanEstimationOperator(code, arctan_bitsize=arctan_bitsize) - - # Compute a reduced unitary for mean_op. - u = cirq.unitary(mean_gate) - assert cirq.is_unitary(u) - - # Compute the final state vector obtained using the synthesizer `Prep |0>` - prep_op = synthesizer.on_registers(**infra.get_named_qubits(synthesizer.signature)) - prep_state = cirq.Circuit(prep_op).final_state_vector() - - expected_hav = abs(mu) * np.sqrt(1 / (1 + s**2)) - expected_hav_low = expected_hav / (1 + c * s) - expected_hav_high = expected_hav / (1 - c * s) - - overlap_sum = 0.0 - eigvals, eigvects = cirq.linalg.unitary_eig(u) - for eig_val, eig_vect in zip(eigvals, eigvects.T): - theta = np.abs(np.angle(eig_val)) - hav_theta = np.sin(theta / 2) - overlap_prob = overlap(prep_state, eig_vect) - if expected_hav_low <= hav_theta <= expected_hav_high: - overlap_sum += overlap_prob - return overlap_sum >= 1 - 2 / (c**2) > 0 - - -@pytest.mark.parametrize('selection_bitsize', [1, 2]) -@pytest.mark.parametrize( - 'p, y_1, target_bitsize, c', - [ - (1 / 100 * 1 / 100, 3, 2, 100 / 7), - (1 / 50 * 1 / 50, 2, 2, 50 / 4), - (1 / 50 * 1 / 50, 1, 1, 50 / 10), - (1 / 4 * 1 / 4, 1, 1, 1.5), - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_mean_estimation_bernoulli( - p: int, y_1: int, selection_bitsize: int, target_bitsize: int, c: float, arctan_bitsize: int = 5 -): - synthesizer = BernoulliSynthesizer(p, selection_bitsize) - encoder = BernoulliEncoder(p, (0, y_1), selection_bitsize, target_bitsize) - s = np.sqrt(encoder.s_square) - # For hav_theta interval to be reasonably wide, 1/(1-cs) term should be <=2; thus cs <= 0.5. - # The theorem assumes that C >= 1 and s <= 1 / c. - assert c * s <= 0.5 and c >= 1 >= s - - assert satisfies_theorem_321( - synthesizer=synthesizer, - encoder=encoder, - c=c, - s=s, - mu=encoder.mu, - arctan_bitsize=arctan_bitsize, - ) - - -@frozen -class GroverSynthesizer(cirq_ft.PrepareOracle): - r"""Prepare a uniform superposition over the first $2^n$ elements.""" - - n: int - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('selection', self.n),) - - def decompose_from_registers( # type:ignore[override] - self, *, context, selection: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - yield cirq.H.on_each(*selection) - - def __pow__(self, power): - if power in [+1, -1]: - return self - return NotImplemented # pragma: no cover - - -@frozen -class GroverEncoder(cirq_ft.SelectOracle): - """Enc|marked_item>|0> --> |marked_item>|marked_val>""" - - n: int - marked_item: int - marked_val: int - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('selection', self.n),) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('target', self.marked_val.bit_length()),) - - def decompose_from_registers( # type:ignore[override] - self, context, *, selection: Sequence[cirq.Qid], target: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - selection_cv = [ - *bit_tools.iter_bits(self.marked_item, infra.total_bits(self.selection_registers)) - ] - yval_bin = [*bit_tools.iter_bits(self.marked_val, infra.total_bits(self.target_registers))] - - for b, q in zip(yval_bin, target): - if b: - yield cirq.X(q).controlled_by(*selection, control_values=selection_cv) - - @cached_property - def mu(self) -> float: - return self.marked_val / 2**self.n - - @cached_property - def s_square(self) -> float: - return (self.marked_val**2) / 2**self.n - - -@pytest.mark.parametrize('n, marked_val, c', [(5, 1, 4), (4, 1, 2), (2, 1, np.sqrt(2))]) -@allow_deprecated_cirq_ft_use_in_tests -def test_mean_estimation_grover( - n: int, marked_val: int, c: float, marked_item: int = 1, arctan_bitsize: int = 5 -): - synthesizer = GroverSynthesizer(n) - encoder = GroverEncoder(n, marked_item=marked_item, marked_val=marked_val) - s = np.sqrt(encoder.s_square) - assert c * s < 1 and c >= 1 >= s - - assert satisfies_theorem_321( - synthesizer=synthesizer, - encoder=encoder, - c=c, - s=s, - mu=encoder.mu, - arctan_bitsize=arctan_bitsize, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_mean_estimation_operator_consistent_protocols(): - p, selection_bitsize, y_1, target_bitsize, arctan_bitsize = 0.1, 2, 1, 1, 4 - synthesizer = BernoulliSynthesizer(p, selection_bitsize) - encoder = BernoulliEncoder(p, (0, y_1), selection_bitsize, target_bitsize) - code = CodeForRandomVariable(synthesizer=synthesizer, encoder=encoder) - mean_gate = MeanEstimationOperator(code, arctan_bitsize=arctan_bitsize) - op = mean_gate.on_registers(**infra.get_named_qubits(mean_gate.signature)) - - # Test controlled gate. - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - mean_gate.controlled(), - mean_gate.controlled(num_controls=1), - mean_gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - mean_gate.controlled(control_values=(0,)), - mean_gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = mean_gate.controlled(num_controls=2) - - # Test with_power - assert mean_gate.with_power(5) ** 2 == MeanEstimationOperator( - code, arctan_bitsize=arctan_bitsize, power=10 - ) - # Test diagrams - expected_symbols = ['U_ko'] * cirq.num_qubits(mean_gate) - assert cirq.circuit_diagram_info(mean_gate).wire_symbols == tuple(expected_symbols) - control_symbols = ['@'] - assert cirq.circuit_diagram_info(mean_gate.controlled()).wire_symbols == tuple( - control_symbols + expected_symbols - ) - control_symbols = ['@(0)'] - assert cirq.circuit_diagram_info( - mean_gate.controlled(control_values=(0,)) - ).wire_symbols == tuple(control_symbols + expected_symbols) - expected_symbols[-1] = 'U_ko^2' - assert cirq.circuit_diagram_info( - mean_gate.with_power(2).controlled(control_values=(0,)) - ).wire_symbols == tuple(control_symbols + expected_symbols) diff --git a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli.py b/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli.py deleted file mode 100644 index 7d6142fb175..00000000000 --- a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate - - -class MultiTargetCNOT(infra.GateWithRegisters): - """Implements single control, multi-target CNOT_{n} gate in 2*log(n) + 1 CNOT depth. - - Implements CNOT_{n} = |0><0| I + |1><1| X^{n} using a circuit of depth 2*log(n) + 1 - containing only CNOT gates. See Appendix B.1 of https://arxiv.org/abs/1812.00954 for - reference. - """ - - def __init__(self, num_targets: int): - self._num_targets = num_targets - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(control=1, targets=self._num_targets) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ): - control, targets = quregs['control'], quregs['targets'] - - def cnots_for_depth_i(i: int, q: NDArray[cirq.Qid]) -> cirq.OP_TREE: - for c, t in zip(q[: 2**i], q[2**i : min(len(q), 2 ** (i + 1))]): - yield cirq.CNOT(c, t) - - depth = len(targets).bit_length() - for i in range(depth): - yield cirq.Moment(cnots_for_depth_i(depth - i - 1, targets)) - yield cirq.CNOT(*control, targets[0]) - for i in range(depth): - yield cirq.Moment(cnots_for_depth_i(i, targets)) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - return cirq.CircuitDiagramInfo(wire_symbols=["@"] + ["X"] * self._num_targets) - - -@attr.frozen -class MultiControlPauli(infra.GateWithRegisters): - """Implements multi-control, single-target C^{n}P gate. - - Implements $C^{n}P = (1 - |1^{n}><1^{n}|) I + |1^{n}><1^{n}| P^{n}$ using $n-1$ - clean ancillas using a multi-controlled `AND` gate. - - References: - [Constructing Large Controlled Nots] - (https://algassert.com/circuits/2015/06/05/Constructing-Large-Controlled-Nots.html) - """ - - cvs: Tuple[int, ...] = attr.field(converter=lambda v: (v,) if isinstance(v, int) else tuple(v)) - target_gate: cirq.Pauli = cirq.X - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(controls=len(self.cvs), target=1) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray['cirq.Qid'] - ) -> cirq.OP_TREE: - controls, target = quregs['controls'], quregs['target'] - qm = context.qubit_manager - and_ancilla, and_target = np.array(qm.qalloc(len(self.cvs) - 2)), qm.qalloc(1) - yield and_gate.And(self.cvs).on_registers( - ctrl=controls[:, np.newaxis], junk=and_ancilla[:, np.newaxis], target=and_target - ) - yield self.target_gate.on(*target).controlled_by(*and_target) - yield and_gate.And(self.cvs, adjoint=True).on_registers( - ctrl=controls[:, np.newaxis], junk=and_ancilla[:, np.newaxis], target=and_target - ) - qm.qfree([*and_ancilla, *and_target]) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@" if b else "@(0)" for b in self.cvs] - wire_symbols += [str(self.target_gate)] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _t_complexity_(self) -> infra.TComplexity: - and_cost = infra.t_complexity(and_gate.And(self.cvs)) - controlled_pauli_cost = infra.t_complexity(self.target_gate.controlled(1)) - and_inv_cost = infra.t_complexity(and_gate.And(self.cvs, adjoint=True)) - return and_cost + controlled_pauli_cost + and_inv_cost - - def _apply_unitary_(self, args: 'cirq.ApplyUnitaryArgs') -> np.ndarray: - return cirq.apply_unitary(self.target_gate.controlled(control_values=self.cvs), args) - - def _has_unitary_(self) -> bool: - return True diff --git a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli_test.py b/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli_test.py deleted file mode 100644 index 158ae31299f..00000000000 --- a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli_test.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize("num_targets", [3, 4, 6, 8, 10]) -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_target_cnot(num_targets): - qubits = cirq.LineQubit.range(num_targets + 1) - naive_circuit = cirq.Circuit(cirq.CNOT(qubits[0], q) for q in qubits[1:]) - op = cirq_ft.MultiTargetCNOT(num_targets).on(*qubits) - cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( - cirq.Circuit(op), naive_circuit, atol=1e-6 - ) - optimal_circuit = cirq.Circuit(cirq.decompose_once(op)) - assert len(optimal_circuit) == 2 * np.ceil(np.log2(num_targets)) + 1 - - -@pytest.mark.parametrize("num_controls", [*range(7, 17)]) -@pytest.mark.parametrize("pauli", [cirq.X, cirq.Y, cirq.Z]) -@pytest.mark.parametrize('cv', [0, 1]) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity_mcp(num_controls: int, pauli: cirq.Pauli, cv: int): - gate = cirq_ft.MultiControlPauli([cv] * num_controls, target_gate=pauli) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(gate) diff --git a/cirq-ft/cirq_ft/algos/phase_estimation_of_quantum_walk.ipynb b/cirq-ft/cirq_ft/algos/phase_estimation_of_quantum_walk.ipynb deleted file mode 100644 index 99ca0bed34e..00000000000 --- a/cirq-ft/cirq_ft/algos/phase_estimation_of_quantum_walk.ipynb +++ /dev/null @@ -1,199 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Heisenberg limited phase estimation\n", - "Implements Heisenberg-Limited Phase Estimation of the Qubitized Quantum Walks as described in Section-II B. of [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "\n", - "import cirq_ft\n", - "from cirq_ft import infra\n", - "\n", - "from cirq_ft.algos.qubitization_walk_operator_test import get_walk_operator_for_1d_Ising_model\n", - "from cirq_ft.algos.hubbard_model import get_walk_operator_for_hubbard_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_resource_state(m: int):\n", - " r\"\"\"Returns a state vector representing the resource state on m qubits from Eq.17 of Ref-1.\n", - " \n", - " Returns a numpy array of size 2^{m} representing the state vector corresponding to the state\n", - " $$\n", - " \\sqrt{\\frac{2}{2^m + 1}} \\sum_{n=0}^{2^{m}-1} \\sin{\\frac{\\pi(n + 1)}{2^{m}+1}}\\ket{n}\n", - " $$\n", - " \n", - " Args:\n", - " m: Number of qubits to prepare the resource state on.\n", - " \n", - " Ref:\n", - " 1) [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]\n", - " (https://arxiv.org/abs/1805.03662)\n", - " Eq. 17\n", - " \"\"\"\n", - " den = 1 + 2 ** m\n", - " norm = np.sqrt(2 / den)\n", - " return norm * np.sin(np.pi * (1 + np.arange(2**m)) / den) \n", - " \n", - "def phase_estimation(walk: cirq_ft.QubitizationWalkOperator, m: int) -> cirq.OP_TREE:\n", - " \"\"\"Heisenberg limited phase estimation circuit for learning eigenphase of `walk`.\n", - " \n", - " The method yields an OPTREE to construct Heisenberg limited phase estimation circuit \n", - " for learning eigenphases of the `walk` operator with `m` bits of accuracy. The \n", - " circuit is implemented as given in Fig.2 of Ref-1.\n", - " \n", - " Args:\n", - " walk: Qubitization walk operator.\n", - " m: Number of bits of accuracy for phase estimation. \n", - " \n", - " Ref:\n", - " 1) [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]\n", - " (https://arxiv.org/abs/1805.03662)\n", - " Fig. 2\n", - " \"\"\"\n", - " reflect = walk.reflect\n", - " walk_regs = infra.get_named_qubits(walk.signature)\n", - " reflect_regs = {reg.name: walk_regs[reg.name] for reg in reflect.signature}\n", - " \n", - " reflect_controlled = reflect.controlled(control_values=[0])\n", - " walk_controlled = walk.controlled(control_values=[1])\n", - " reflect_op = reflect.on_registers(**reflect_regs)\n", - "\n", - " m_qubits = [cirq.q(f'm_{i}') for i in range(m)]\n", - " state_prep = cirq.StatePreparationChannel(get_resource_state(m), name='chi_m')\n", - "\n", - " yield state_prep.on(*m_qubits)\n", - " yield walk_controlled.on_registers(**walk_regs, control=m_qubits[0])\n", - " for i in range(1, m):\n", - " yield reflect_controlled.on_registers(control=m_qubits[i], **reflect_regs)\n", - " yield walk.on_registers(**walk_regs)\n", - " walk = walk ** 2\n", - " yield reflect_controlled.on_registers(control=m_qubits[i], **reflect_regs)\n", - " \n", - " yield cirq.qft(*m_qubits, inverse=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "num_sites: int = 6\n", - "eps: float = 1e-2\n", - "m_bits: int = 4\n", - "\n", - "circuit = cirq.Circuit(phase_estimation(get_walk_operator_for_1d_Ising_model(num_sites, eps), m=m_bits))\n", - "print(circuit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resource estimates for 1D Ising model using generic SELECT / PREPARE " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "num_sites: int = 200\n", - "eps: float = 1e-5\n", - "m_bits: int = 14\n", - "\n", - "walk = get_walk_operator_for_1d_Ising_model(num_sites, eps)\n", - "\n", - "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = cirq_ft.t_complexity(circuit[1:-1])\n", - "print(result)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resource estimates for 2D Hubbard model using specialized SELECT / PREPARE \n", - "Phase estimation of walk operator for 2D Hubbard Model using SELECT and PREPARE circuits from Section V of https://arxiv.org/abs/1805.03662" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x_dim, y_dim = 20, 20\n", - "t = 20\n", - "mu = 4 * t\n", - "N = x_dim * y_dim * 2\n", - "qlambda = 2 * N * t + (N * mu) // 2\n", - "delta_E = t / 100\n", - "m_bits = int(np.log2(qlambda * np.pi * np.sqrt(2) / delta_E))\n", - "walk = get_walk_operator_for_hubbard_model(x_dim, y_dim, t, mu)\n", - "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = cirq_ft.t_complexity(circuit[1:-1])\n", - "print(result)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition.py b/cirq-ft/cirq_ft/algos/prepare_uniform_superposition.py deleted file mode 100644 index 3564e654037..00000000000 --- a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate, arithmetic_gates - - -@attr.frozen -class PrepareUniformSuperposition(infra.GateWithRegisters): - r"""Prepares a uniform superposition over first $n$ basis states using $O(log(n))$ T-gates. - - Performs a single round of amplitude amplification and prepares a uniform superposition over - the first $n$ basis states $|0>, |1>, ..., |n - 1>$. The expected T-complexity should be - $10 * log(L) + 2 * K$ T-gates and $2$ single qubit rotation gates, where $n = L * 2^K$. - - However, the current T-complexity is $12 * log(L)$ T-gates and $2 + 2 * (K + log(L))$ rotations - because of two open issues: - - https://github.com/quantumlib/cirq-qubitization/issues/233 and - - https://github.com/quantumlib/cirq-qubitization/issues/235 - - Args: - n: The gate prepares a uniform superposition over first $n$ basis states. - cv: Control values for each control qubit. If specified, a controlled version - of the gate is constructed. - - References: - See Fig 12 of https://arxiv.org/abs/1805.03662 for more details. - """ - - n: int - cv: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(controls=len(self.cv), target=(self.n - 1).bit_length()) - - def __repr__(self) -> str: - return f"cirq_ft.PrepareUniformSuperposition({self.n}, cv={self.cv})" - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - control_symbols = ["@" if cv else "@(0)" for cv in self.cv] - target_symbols = ['target'] * self.signature.get_left('target').total_bits() - target_symbols[0] = f"UNIFORM({self.n})" - return cirq.CircuitDiagramInfo(wire_symbols=control_symbols + target_symbols) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - controls, target = quregs.get('controls', ()), quregs['target'] - # Find K and L as per https://arxiv.org/abs/1805.03662 Fig 12. - n, k = self.n, 0 - while n > 1 and n % 2 == 0: - k += 1 - n = n // 2 - l, logL = int(n), self.signature.get_left('target').total_bits() - k - logL_qubits = target[:logL] - - yield [ - op.controlled_by(*controls, control_values=self.cv) for op in cirq.H.on_each(*target) - ] - if not len(logL_qubits): - return - - ancilla = context.qubit_manager.qalloc(1) - theta = np.arccos(1 - (2 ** np.floor(np.log2(l))) / l) - yield arithmetic_gates.LessThanGate(logL, l).on(*logL_qubits, *ancilla) - yield cirq.Rz(rads=theta)(*ancilla) - yield arithmetic_gates.LessThanGate(logL, l).on(*logL_qubits, *ancilla) - - yield cirq.H.on_each(*logL_qubits) - - and_ancilla = context.qubit_manager.qalloc(len(self.cv) + logL - 2) - and_op = and_gate.And((0,) * logL + self.cv).on_registers( - ctrl=np.asarray([*logL_qubits, *controls])[:, np.newaxis], - junk=np.asarray(and_ancilla)[:, np.newaxis], - target=ancilla, - ) - yield and_op - yield cirq.Rz(rads=theta)(*ancilla) - yield cirq.inverse(and_op) - - yield cirq.H.on_each(*logL_qubits) - context.qubit_manager.qfree([*ancilla, *and_ancilla]) diff --git a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition_test.py b/cirq-ft/cirq_ft/algos/prepare_uniform_superposition_test.py deleted file mode 100644 index 5cdb25b8875..00000000000 --- a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition_test.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -from cirq_ft import infra -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize("n", [*range(3, 20), 25, 41]) -@pytest.mark.parametrize("num_controls", [0, 1]) -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_uniform_superposition(n, num_controls): - gate = cirq_ft.PrepareUniformSuperposition(n, cv=[1] * num_controls) - all_qubits = cirq.LineQubit.range(cirq.num_qubits(gate)) - control, target = (all_qubits[:num_controls], all_qubits[num_controls:]) - turn_on_controls = [cirq.X(c) for c in control] - prepare_uniform_op = gate.on(*control, *target) - circuit = cirq.Circuit(turn_on_controls, prepare_uniform_op) - result = cirq.Simulator(dtype=np.complex128).simulate(circuit, qubit_order=all_qubits) - final_target_state = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=list(range(num_controls, num_controls + len(target))), - ) - expected_target_state = np.asarray([np.sqrt(1.0 / n)] * n + [0] * (2 ** len(target) - n)) - cirq.testing.assert_allclose_up_to_global_phase( - expected_target_state, final_target_state, atol=1e-6 - ) - - -@pytest.mark.parametrize("n", [*range(3, 41, 3)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_uniform_superposition_t_complexity(n: int): - gate = cirq_ft.PrepareUniformSuperposition(n) - result = cirq_ft.t_complexity(gate) - assert result.rotations <= 2 - # TODO(#235): Uncomputing `LessThanGate` should take 0 T-gates instead of 4 * n - # and therefore the total complexity should come down to `8 * logN` - assert result.t <= 12 * (n - 1).bit_length() - - gate = cirq_ft.PrepareUniformSuperposition(n, cv=(1,)) - result = cirq_ft.t_complexity(gate) - # TODO(#233): Controlled-H is currently counted as a separate rotation, but it can be - # implemented using 2 T-gates. - assert result.rotations <= 2 + 2 * infra.total_bits(gate.signature) - assert result.t <= 12 * (n - 1).bit_length() - - -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_uniform_superposition_consistent_protocols(): - gate = cirq_ft.PrepareUniformSuperposition(5, cv=(1, 0)) - # Repr - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - # Diagrams - expected_symbols = ('@', '@(0)', 'UNIFORM(5)', 'target', 'target') - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_symbols - # Equality - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - cirq_ft.PrepareUniformSuperposition(5, cv=(1, 0)), - cirq_ft.PrepareUniformSuperposition(5, cv=[1, 0]), - ) - equals_tester.add_equality_group( - cirq_ft.PrepareUniformSuperposition(5, cv=(0, 1)), - cirq_ft.PrepareUniformSuperposition(5, cv=[0, 1]), - ) - equals_tester.add_equality_group( - cirq_ft.PrepareUniformSuperposition(5), - cirq_ft.PrepareUniformSuperposition(5, cv=()), - cirq_ft.PrepareUniformSuperposition(5, cv=[]), - ) diff --git a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array.py b/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array.py deleted file mode 100644 index 977111221c7..00000000000 --- a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import abc -from functools import cached_property -from typing import Sequence, Tuple -from numpy.typing import NDArray - -import cirq -import numpy as np -from cirq._compat import cached_method -from cirq_ft import infra -from cirq_ft.algos import qrom -from cirq_ft.infra.bit_tools import iter_bits - - -class ProgrammableRotationGateArrayBase(infra.GateWithRegisters): - """Base class for a composite gate to apply multiplexed rotations on target register. - - Programmable rotation gate array is used to apply `M` rotations on a target register, - potentially interleaved with `M - 1` arbitrary unitaries, via the following steps: - - - Represent each rotation angle `theta` as a an integer approximation of `B` bits. - - Use an ancilla register of size `kappa` (a configurable parameter) and load multiplexed - bits of rotations angles, in batches of size at-most `kappa`, using `QROM` reads. - - Thus, a total of `⌈M * B / kappa⌉ + 1` QROM reads are required. - - After every QROM read, apply `kappa` (singly) controlled rotations on the target - register, each controlled on a different qubit from the `kappa` ancilla register. - - Thus, a total of `M * B` controlled rotations are applied on the target register. - - Note that naively applying multiplexed controlled rotations on target register using a unary - iteration loop would require us to apply `O(iteration_length)` controlled rotations for a - single multiplexed rotations array. On the contrary, this approach requires us to apply only - `B` controlled rotations on the target register; which is usually much smaller than the - iteration length. - - Users should derive from this base class and override the `interleaved_unitary` and - `interleaved_unitary_target` abstract methods to specify the information regarding - the unitaries that should be interleaved between `M` rotations. - - For more details, see the reference below: - - References: - Page 45; Section VII.B.1 - [Quantum computing enhanced computational catalysis] - (https://arxiv.org/abs/2007.14460). - Burg, Low et. al. 2021. - """ - - def __init__(self, *angles: Sequence[int], kappa: int, rotation_gate: cirq.Gate): - """Initializes ProgrammableRotationGateArrayBase - - Args: - angles: Sequence of integer-approximated rotation angles s.t. - `rotation_gate ** float_from_integer_approximation(angles[i][k])` should be applied - to the target register when the selection register of ith multiplexed rotation array - stores integer `k`. - kappa: Length of temporary data register to use for storing integer approximated bits - of multiplexed rotation angles. - rotation_gate: Exponential of this gate, depending on `angles`, shall be applied on the - target register. - - Raises: - ValueError: If all multiplexed `angles` sequences are not of the same length. - """ - if len(set(len(thetas) for thetas in angles)) != 1: - raise ValueError("All multiplexed angles sequences to apply must be of same length.") - self._angles = tuple(tuple(thetas) for thetas in angles) - self._selection_bitsize = (len(self._angles[0]) - 1).bit_length() - self._target_bitsize = cirq.num_qubits(rotation_gate) - self._kappa = kappa - self._rotation_gate = rotation_gate - - @property - def kappa(self) -> int: - return self._kappa - - @property - def angles(self) -> Tuple[Tuple[int, ...], ...]: - return self._angles - - @cached_method - def rotation_gate(self, exponent: int = -1) -> cirq.Gate: - """Returns `self._rotation_gate` ** 1 / (2 ** (1 + power))`""" - power = 1 / 2 ** (1 + exponent) - return cirq.pow(self._rotation_gate, power) - - @abc.abstractmethod - def interleaved_unitary( - self, index: int, **qubit_regs: NDArray[cirq.Qid] # type:ignore[type-var] - ) -> cirq.Operation: - pass - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return (infra.SelectionRegister('selection', self._selection_bitsize, len(self.angles[0])),) - - @cached_property - def kappa_load_target(self) -> Tuple[infra.Register, ...]: - return (infra.Register('kappa_load_target', self.kappa),) - - @cached_property - def rotations_target(self) -> Tuple[infra.Register, ...]: - return (infra.Register('rotations_target', self._target_bitsize),) - - @property - @abc.abstractmethod - def interleaved_unitary_target(self) -> Tuple[infra.Register, ...]: - pass - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [ - *self.selection_registers, - *self.kappa_load_target, - *self.rotations_target, - *self.interleaved_unitary_target, - ] - ) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - selection, kappa_load_target = quregs.pop('selection'), quregs.pop('kappa_load_target') - rotations_target = quregs.pop('rotations_target') - interleaved_unitary_target = quregs - - # 1. Find a convenient way to process batches of size kappa. - num_bits = sum(max(thetas).bit_length() for thetas in self.angles) - iteration_length = self.selection_registers[0].iteration_length - selection_bitsizes = [s.total_bits() for s in self.selection_registers] - angles_bits = np.zeros(shape=(iteration_length, num_bits), dtype=int) - angles_bit_pow = np.zeros(shape=(num_bits,), dtype=int) - angles_idx = np.zeros(shape=(num_bits,), dtype=int) - st, en = 0, 0 - for i, thetas in enumerate(self.angles): - bit_width = max(thetas).bit_length() - st, en = en, en + bit_width - angles_bits[:, st:en] = [[*iter_bits(t, bit_width)] for t in thetas] - angles_bit_pow[st:en] = [*range(bit_width)][::-1] - angles_idx[st:en] = i - assert en == num_bits - # 2. Process batches of size kappa. - power_of_2s = 2 ** np.arange(self.kappa)[::-1] - last_id = 0 - data = np.zeros(iteration_length, dtype=int) - for st in range(0, num_bits, self.kappa): - en = min(st + self.kappa, num_bits) - data ^= angles_bits[:, st:en].dot(power_of_2s[: en - st]) - yield qrom.QROM( - [data], selection_bitsizes=tuple(selection_bitsizes), target_bitsizes=(self.kappa,) - ).on_registers(selection=selection, target0=kappa_load_target) - data = angles_bits[:, st:en].dot(power_of_2s[: en - st]) - for cqid, bpow, idx in zip(kappa_load_target, angles_bit_pow[st:en], angles_idx[st:en]): - if idx != last_id: - yield self.interleaved_unitary( - last_id, rotations_target=rotations_target, **interleaved_unitary_target - ) - last_id = idx - yield self.rotation_gate(bpow).on(*rotations_target).controlled_by(cqid) - yield qrom.QROM( - [data], selection_bitsizes=tuple(selection_bitsizes), target_bitsizes=(self.kappa,) - ).on_registers(selection=selection, target0=kappa_load_target) - - -class ProgrammableRotationGateArray(ProgrammableRotationGateArrayBase): - """An implementation of `ProgrammableRotationGateArrayBase` base class - - - This implementation of the `ProgrammableRotationGateArray` base class expects - all interleaved_unitaries to act on the `rotations_target` register. - - See docstring of `ProgrammableRotationGateArrayBase` for more details. - """ - - def __init__( - self, - *angles: Sequence[int], - kappa: int, - rotation_gate: cirq.Gate, - interleaved_unitaries: Sequence[cirq.Gate] = (), - ): - super().__init__(*angles, kappa=kappa, rotation_gate=rotation_gate) - if not interleaved_unitaries: - identity_gate = cirq.IdentityGate(infra.total_bits(self.rotations_target)) - interleaved_unitaries = (identity_gate,) * (len(angles) - 1) - assert len(interleaved_unitaries) == len(angles) - 1 - assert all(cirq.num_qubits(u) == self._target_bitsize for u in interleaved_unitaries) - self._interleaved_unitaries = tuple(interleaved_unitaries) - - def interleaved_unitary(self, index: int, **qubit_regs: NDArray[cirq.Qid]) -> cirq.Operation: - return self._interleaved_unitaries[index].on(*qubit_regs['rotations_target']) - - @cached_property - def interleaved_unitary_target(self) -> Tuple[infra.Register, ...]: - return () diff --git a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py b/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py deleted file mode 100644 index 267dabfbe5e..00000000000 --- a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -class CustomProgrammableRotationGateArray(cirq_ft.ProgrammableRotationGateArrayBase): - def interleaved_unitary( - self, index: int, **qubit_regs: NDArray[cirq.Qid] # type:ignore[type-var] - ) -> cirq.Operation: - two_qubit_ops_factory = [ - cirq.X(*qubit_regs['unrelated_target']).controlled_by(*qubit_regs['rotations_target']), - cirq.Z(*qubit_regs['unrelated_target']).controlled_by(*qubit_regs['rotations_target']), - ] - return two_qubit_ops_factory[index % 2] - - @cached_property - def interleaved_unitary_target(self) -> Tuple[cirq_ft.Register, ...]: - return tuple(cirq_ft.Signature.build(unrelated_target=1)) - - -def construct_custom_prga(*args, **kwargs) -> cirq_ft.ProgrammableRotationGateArrayBase: - return CustomProgrammableRotationGateArray(*args, **kwargs) - - -def construct_prga_with_phase(*args, **kwargs) -> cirq_ft.ProgrammableRotationGateArrayBase: - return cirq_ft.ProgrammableRotationGateArray( - *args, **kwargs, interleaved_unitaries=[cirq.Z] * (len(args) - 1) - ) - - -def construct_prga_with_identity(*args, **kwargs) -> cirq_ft.ProgrammableRotationGateArrayBase: - return cirq_ft.ProgrammableRotationGateArray(*args, **kwargs) - - -@pytest.mark.parametrize( - "angles", [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], [[3, 4, 5], [10, 11, 12]]] -) -@pytest.mark.parametrize("kappa", [*range(1, 12)]) -@pytest.mark.parametrize( - "constructor", [construct_custom_prga, construct_prga_with_phase, construct_prga_with_identity] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_programmable_rotation_gate_array(angles, kappa, constructor): - rotation_gate = cirq.X - programmable_rotation_gate = constructor(*angles, kappa=kappa, rotation_gate=rotation_gate) - greedy_mm = cirq.GreedyQubitManager(prefix="_a") - g = cirq_ft.testing.GateHelper( - programmable_rotation_gate, context=cirq.DecompositionContext(greedy_mm) - ) - decomposed_circuit = cirq.Circuit(cirq.I.on_each(*g.all_qubits)) + g.decomposed_circuit - # Get interleaved unitaries. - interleaved_unitaries = [ - programmable_rotation_gate.interleaved_unitary(i, **g.quregs) - for i in range(len(angles) - 1) - ] - # Get qubits on which rotations + unitaries act. - rotations_and_unitary_registers = cirq_ft.Signature( - [ - *programmable_rotation_gate.rotations_target, - *programmable_rotation_gate.interleaved_unitary_target, - ] - ) - rotations_and_unitary_qubits = infra.merge_qubits(rotations_and_unitary_registers, **g.quregs) - - # Build circuit. - simulator = cirq.Simulator(dtype=np.complex128) - - def rotation_ops(theta: int) -> cirq.OP_TREE: - # OP-TREE to apply rotation, by integer-approximated angle `theta`, on the target register. - for i, b in enumerate(bin(theta)[2:][::-1]): - if b == '1': - yield cirq.pow(rotation_gate.on(*g.quregs['rotations_target']), (1 / 2 ** (1 + i))) - - for selection_integer in range(len(angles[0])): - # Set bits in initial_state s.t. selection register stores `selection_integer`. - qubit_vals = {x: 0 for x in g.all_qubits} - qubit_vals.update( - zip( - g.quregs['selection'], - iter_bits(selection_integer, g.r.get_left('selection').total_bits()), - ) - ) - initial_state = [qubit_vals[x] for x in g.all_qubits] - # Actual circuit simulation. - result = simulator.simulate( - decomposed_circuit, initial_state=initial_state, qubit_order=g.all_qubits - ) - ru_state_vector = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=[g.all_qubits.index(q) for q in rotations_and_unitary_qubits], - ) - # Expected circuit simulation by applying rotations directly. - expected_circuit = cirq.Circuit( - [ - [rotation_ops(angles[i][selection_integer]), u] - for i, u in enumerate(interleaved_unitaries) - ], - rotation_ops(angles[-1][selection_integer]), - ) - expected_ru_state_vector = simulator.simulate( - expected_circuit, qubit_order=rotations_and_unitary_qubits - ).final_state_vector - # Assert that actual and expected match. - cirq.testing.assert_allclose_up_to_global_phase( - ru_state_vector, expected_ru_state_vector, atol=1e-8 - ) - # Assert that all other qubits are returned to their original state. - ancilla_indices = [ - g.all_qubits.index(q) for q in g.all_qubits if q not in rotations_and_unitary_qubits - ] - ancilla_state_vector = cirq.sub_state_vector( - result.final_state_vector, keep_indices=ancilla_indices - ) - expected_ancilla_state_vector = cirq.quantum_state( - [initial_state[x] for x in ancilla_indices], - qid_shape=(2,) * len(ancilla_indices), - dtype=np.complex128, - ).state_vector() - cirq.testing.assert_allclose_up_to_global_phase( - ancilla_state_vector, expected_ancilla_state_vector, atol=1e-8 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_programmable_rotation_gate_array_consistent(): - with pytest.raises(ValueError, match='must be of same length'): - _ = CustomProgrammableRotationGateArray([1, 2], [1], kappa=1, rotation_gate=cirq.X) diff --git a/cirq-ft/cirq_ft/algos/qrom.ipynb b/cirq-ft/cirq_ft/algos/qrom.ipynb deleted file mode 100644 index 2f37c079d54..00000000000 --- a/cirq-ft/cirq_ft/algos/qrom.ipynb +++ /dev/null @@ -1,110 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "773cf7e2", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "ffe91e97", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# QROM" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2737c79f", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "5d6a18ce", - "metadata": { - "cq.autogen": "_make_QROM.md" - }, - "source": [ - "## `QROM`\n", - "Gate to load data[l] in the target register when the selection stores an index l.\n", - "\n", - "In the case of multi-dimensional data[p,q,r,...] we use of multple name\n", - "selection signature [p, q, r, ...] to index and load the data.\n", - "\n", - "#### Parameters\n", - " - `data`: List of numpy ndarrays specifying the data to load. If the length of this list is greater than one then we use the same selection indices to load each dataset (for example, to load alt and keep data for state preparation). Each data set is required to have the same shape and to be of integer type.\n", - " - `selection_bitsizes`: The number of bits used to represent each selection register corresponding to the size of each dimension of the array. Should be the same length as the shape of each of the datasets.\n", - " - `target_bitsizes`: The number of bits used to represent the data signature. This can be deduced from the maximum element of each of the datasets. Should be of length len(data), i.e. the number of datasets.\n", - " - `num_controls`: The number of control signature.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a91a2db", - "metadata": { - "cq.autogen": "_make_QROM.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.QROM([np.array([1, 2, 3, 4, 5])], selection_bitsizes=(3,), target_bitsizes=(3,))\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/qrom.py b/cirq-ft/cirq_ft/algos/qrom.py deleted file mode 100644 index 86f04b44b9d..00000000000 --- a/cirq-ft/cirq_ft/algos/qrom.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Callable, Sequence, Tuple - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate, unary_iteration_gate -from numpy.typing import ArrayLike, NDArray - - -@cirq.value_equality() -@attr.frozen -class QROM(unary_iteration_gate.UnaryIterationGate): - """Gate to load data[l] in the target register when the selection stores an index l. - - In the case of multi-dimensional data[p,q,r,...] we use multiple named - selection signature [p, q, r, ...] to index and load the data. Here `p, q, r, ...` - correspond to signature named `selection0`, `selection1`, `selection2`, ... etc. - - When the input data elements contain consecutive entries of identical data elements to - load, the QROM also implements the "variable-spaced" QROM optimization described in Ref[2]. - - Args: - data: List of numpy ndarrays specifying the data to load. If the length - of this list is greater than one then we use the same selection indices - to load each dataset (for example, to load alt and keep data for - state preparation). Each data set is required to have the same - shape and to be of integer type. - selection_bitsizes: The number of bits used to represent each selection register - corresponding to the size of each dimension of the array. Should be - the same length as the shape of each of the datasets. - target_bitsizes: The number of bits used to represent the data - signature. This can be deduced from the maximum element of each of the - datasets. Should be of length len(data), i.e. the number of datasets. - num_controls: The number of control signature. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. - - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] - (https://arxiv.org/abs/2007.07391). - Babbush et. al. (2020). Figure 3. - """ - - data: Sequence[NDArray] - selection_bitsizes: Tuple[int, ...] - target_bitsizes: Tuple[int, ...] - num_controls: int = 0 - - @classmethod - def build(cls, *data: ArrayLike, num_controls: int = 0) -> 'QROM': - _data = [np.array(d, dtype=int) for d in data] - selection_bitsizes = tuple((s - 1).bit_length() for s in _data[0].shape) - target_bitsizes = tuple(max(int(np.max(d)).bit_length(), 1) for d in data) - return QROM( - data=_data, - selection_bitsizes=selection_bitsizes, - target_bitsizes=target_bitsizes, - num_controls=num_controls, - ) - - def __attrs_post_init__(self): - shapes = [d.shape for d in self.data] - assert all([isinstance(s, int) for s in self.selection_bitsizes]) - assert all([isinstance(t, int) for t in self.target_bitsizes]) - assert len(set(shapes)) == 1, f"Data must all have the same size: {shapes}" - assert len(self.target_bitsizes) == len(self.data), ( - f"len(self.target_bitsizes)={len(self.target_bitsizes)} should be same as " - f"len(self.data)={len(self.data)}" - ) - assert all( - t >= int(np.max(d)).bit_length() for t, d in zip(self.target_bitsizes, self.data) - ) - assert isinstance(self.selection_bitsizes, tuple) - assert isinstance(self.target_bitsizes, tuple) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if not self.num_controls else (infra.Register('control', self.num_controls),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - if len(self.data[0].shape) == 1: - return ( - infra.SelectionRegister( - 'selection', self.selection_bitsizes[0], self.data[0].shape[0] - ), - ) - else: - return tuple( - infra.SelectionRegister(f'selection{i}', sb, l) - for i, (l, sb) in enumerate(zip(self.data[0].shape, self.selection_bitsizes)) - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return tuple( - infra.Register(f'target{i}', l) for i, l in enumerate(self.target_bitsizes) if l - ) - - def __repr__(self) -> str: - data_repr = f"({','.join(cirq._compat.proper_repr(d) for d in self.data)})" - selection_repr = repr(self.selection_bitsizes) - target_repr = repr(self.target_bitsizes) - return ( - f"cirq_ft.QROM({data_repr}, selection_bitsizes={selection_repr}, " - f"target_bitsizes={target_repr}, num_controls={self.num_controls})" - ) - - def _load_nth_data( - self, - selection_idx: Tuple[int, ...], - gate: Callable[[cirq.Qid], cirq.Operation], - **target_regs: NDArray[cirq.Qid], # type: ignore[type-var] - ) -> cirq.OP_TREE: - for i, d in enumerate(self.data): - target = target_regs.get(f'target{i}', ()) - for q, bit in zip(target, f'{int(d[selection_idx]):0{len(target)}b}'): - if int(bit): - yield gate(q) - - def decompose_zero_selection( - self, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - controls = infra.merge_qubits(self.control_registers, **quregs) - target_regs = {reg.name: quregs[reg.name] for reg in self.target_registers} - zero_indx = (0,) * len(self.data[0].shape) - if self.num_controls == 0: - yield self._load_nth_data(zero_indx, cirq.X, **target_regs) - elif self.num_controls == 1: - yield self._load_nth_data(zero_indx, lambda q: cirq.CNOT(controls[0], q), **target_regs) - else: - and_ancilla = context.qubit_manager.qalloc(len(controls) - 2) - and_target = context.qubit_manager.qalloc(1)[0] - multi_controlled_and = and_gate.And((1,) * len(controls)).on_registers( - ctrl=np.array(controls)[:, np.newaxis], - junk=np.array(and_ancilla)[:, np.newaxis], - target=and_target, - ) - yield multi_controlled_and - yield self._load_nth_data(zero_indx, lambda q: cirq.CNOT(and_target, q), **target_regs) - yield cirq.inverse(multi_controlled_and) - context.qubit_manager.qfree(and_ancilla + [and_target]) - - def _break_early(self, selection_index_prefix: Tuple[int, ...], l: int, r: int): - for data in self.data: - unique_element = np.unique(data[selection_index_prefix][l:r]) - if len(unique_element) > 1: - return False - return True - - def nth_operation( - self, context: cirq.DecompositionContext, control: cirq.Qid, **kwargs - ) -> cirq.OP_TREE: - selection_idx = tuple(kwargs[reg.name] for reg in self.selection_registers) - target_regs = {reg.name: kwargs[reg.name] for reg in self.target_registers} - yield self._load_nth_data(selection_idx, lambda q: cirq.CNOT(control, q), **target_regs) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@"] * self.num_controls - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - for i, target in enumerate(self.target_registers): - wire_symbols += [f"QROM_{i}"] * target.total_bits() - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - def _value_equality_values_(self): - data_tuple = tuple(tuple(d.flatten()) for d in self.data) - return (self.selection_registers, self.target_registers, self.control_registers, data_tuple) diff --git a/cirq-ft/cirq_ft/algos/qrom_test.py b/cirq-ft/cirq_ft/algos/qrom_test.py deleted file mode 100644 index d0a960370e2..00000000000 --- a/cirq-ft/cirq_ft/algos/qrom_test.py +++ /dev/null @@ -1,292 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import itertools - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "data,num_controls", - [ - pytest.param( - data, - num_controls, - id=f"{num_controls}-data{idx}", - marks=pytest.mark.slow if num_controls == 2 and idx == 2 else (), - ) - for idx, data in enumerate( - [[[1, 2, 3, 4, 5]], [[1, 2, 3], [4, 5, 10]], [[1], [2], [3], [4], [5], [6]]] - ) - for num_controls in [0, 1, 2] - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_1d(data, num_controls): - qrom = cirq_ft.QROM.build(*data, num_controls=num_controls) - greedy_mm = cirq.GreedyQubitManager('a', maximize_reuse=True) - g = cirq_ft.testing.GateHelper(qrom, context=cirq.DecompositionContext(greedy_mm)) - decomposed_circuit = cirq.Circuit(cirq.decompose(g.operation, context=g.context)) - inverse = cirq.Circuit(cirq.decompose(g.operation**-1, context=g.context)) - - assert ( - len(inverse.all_qubits()) - <= infra.total_bits(g.r) + g.r.get_left('selection').total_bits() + num_controls - ) - assert inverse.all_qubits() == decomposed_circuit.all_qubits() - - for selection_integer in range(len(data[0])): - for cval in range(2): - qubit_vals = {x: 0 for x in g.all_qubits} - qubit_vals.update( - zip( - g.quregs['selection'], - iter_bits(selection_integer, g.r.get_left('selection').total_bits()), - ) - ) - if num_controls: - qubit_vals.update(zip(g.quregs['control'], [cval] * num_controls)) - - initial_state = [qubit_vals[x] for x in g.all_qubits] - if cval or not num_controls: - for ti, d in enumerate(data): - target = g.quregs[f"target{ti}"] - qubit_vals.update(zip(target, iter_bits(d[selection_integer], len(target)))) - final_state = [qubit_vals[x] for x in g.all_qubits] - - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit, g.all_qubits, initial_state, final_state - ) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit + inverse, g.all_qubits, initial_state, initial_state - ) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit + inverse, g.all_qubits, final_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_diagram(): - d0 = np.array([1, 2, 3]) - d1 = np.array([4, 5, 6]) - qrom = cirq_ft.QROM.build(d0, d1) - q = cirq.LineQubit.range(cirq.num_qubits(qrom)) - circuit = cirq.Circuit(qrom.on_registers(**infra.split_qubits(qrom.signature, q))) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───In─────── - │ -1: ───In─────── - │ -2: ───QROM_0─── - │ -3: ───QROM_0─── - │ -4: ───QROM_1─── - │ -5: ───QROM_1─── - │ -6: ───QROM_1───""", - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_repr(): - data = [np.array([1, 2]), np.array([3, 5])] - selection_bitsizes = tuple((s - 1).bit_length() for s in data[0].shape) - target_bitsizes = tuple(int(np.max(d)).bit_length() for d in data) - qrom = cirq_ft.QROM(data, selection_bitsizes, target_bitsizes) - cirq.testing.assert_equivalent_repr(qrom, setup_code="import cirq_ft\nimport numpy as np") - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('qrom') - - -@pytest.mark.parametrize( - "data", [[[1, 2, 3, 4, 5]], [[1, 2, 3], [4, 5, 10]], [[1], [2], [3], [4], [5], [6]]] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(data): - qrom = cirq_ft.QROM.build(*data) - g = cirq_ft.testing.GateHelper(qrom) - n = np.prod(qrom.data[0].shape) - assert cirq_ft.t_complexity(g.gate) == cirq_ft.t_complexity(g.operation) - assert cirq_ft.t_complexity(g.gate).t == max(0, 4 * n - 8), n - - -def _assert_qrom_has_diagram(qrom: cirq_ft.QROM, expected_diagram: str): - gh = cirq_ft.testing.GateHelper(qrom) - op = gh.operation - context = cirq.DecompositionContext(qubit_manager=cirq.GreedyQubitManager(prefix="anc")) - circuit = cirq.Circuit(cirq.decompose_once(op, context=context)) - selection = [ - *itertools.chain.from_iterable(gh.quregs[reg.name] for reg in qrom.selection_registers) - ] - selection = [q for q in selection if q in circuit.all_qubits()] - anc = sorted(set(circuit.all_qubits()) - set(op.qubits)) - selection_and_anc = (selection[0],) + sum(zip(selection[1:], anc), ()) - qubit_order = cirq.QubitOrder.explicit(selection_and_anc, fallback=cirq.QubitOrder.DEFAULT) - cirq.testing.assert_has_diagram(circuit, expected_diagram, qubit_order=qubit_order) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_variable_spacing(): - # Tests for variable spacing optimization applied from https://arxiv.org/abs/2007.07391 - data = [1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8] # Figure 3a. - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data)).t == (8 - 2) * 4 - data = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5] # Figure 3b. - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data)).t == (5 - 2) * 4 - data = [1, 2, 3, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7] # Negative test: t count is not (g-2)*4 - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data)).t == (8 - 2) * 4 - # Works as expected when multiple data arrays are to be loaded. - data = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5] - # (a) Both data sequences are identical - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data, data)).t == (5 - 2) * 4 - # (b) Both data sequences have identical structure, even though the elements are not same. - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data, 2 * np.array(data))).t == (5 - 2) * 4 - # Works as expected when multidimensional input data is to be loaded - qrom = cirq_ft.QROM.build( - np.array( - [ - [1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1], - [2, 2, 2, 2, 2, 2, 2, 2], - [2, 2, 2, 2, 2, 2, 2, 2], - ] - ) - ) - # Value to be loaded depends only the on the first bit of outer loop. - _assert_qrom_has_diagram( - qrom, - r''' -selection00: ───X───@───X───@─── - │ │ -target00: ──────────┼───────X─── - │ -target01: ──────────X─────────── - ''', - ) - # When inner loop range is not a power of 2, the inner segment tree cannot be skipped. - qrom = cirq_ft.QROM.build( - np.array( - [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2], [2, 2, 2, 2, 2, 2]], - dtype=int, - ) - ) - _assert_qrom_has_diagram( - qrom, - r''' -selection00: ───X───@─────────@───────@──────X───@─────────@───────@────── - │ │ │ │ │ │ -selection10: ───────(0)───────┼───────@──────────(0)───────┼───────@────── - │ │ │ │ │ │ -anc_1: ─────────────And───@───X───@───And†───────And───@───X───@───And†─── - │ │ │ │ -target00: ────────────────┼───────┼────────────────────X───────X────────── - │ │ -target01: ────────────────X───────X─────────────────────────────────────── - ''', - ) - # No T-gates needed if all elements to load are identical. - assert cirq_ft.t_complexity(cirq_ft.QROM.build([3, 3, 3, 3])).t == 0 - - -@pytest.mark.parametrize( - "data,num_controls", - [ - pytest.param( - data, - num_controls, - id=f"{num_controls}-data{idx}", - marks=pytest.mark.slow if num_controls == 2 and idx == 0 else (), - ) - for idx, data in enumerate( - [ - [np.arange(6).reshape(2, 3), 4 * np.arange(6).reshape(2, 3)], - [np.arange(8).reshape(2, 2, 2)], - ] - ) - for num_controls in [0, 1, 2] - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_multi_dim(data, num_controls): - selection_bitsizes = tuple((s - 1).bit_length() for s in data[0].shape) - target_bitsizes = tuple(int(np.max(d)).bit_length() for d in data) - qrom = cirq_ft.QROM( - data, - selection_bitsizes=selection_bitsizes, - target_bitsizes=target_bitsizes, - num_controls=num_controls, - ) - greedy_mm = cirq.GreedyQubitManager('a', maximize_reuse=True) - g = cirq_ft.testing.GateHelper(qrom, context=cirq.DecompositionContext(greedy_mm)) - decomposed_circuit = cirq.Circuit(cirq.decompose(g.operation, context=g.context)) - inverse = cirq.Circuit(cirq.decompose(g.operation**-1, context=g.context)) - - assert ( - len(inverse.all_qubits()) - <= infra.total_bits(g.r) + infra.total_bits(qrom.selection_registers) + num_controls - ) - assert inverse.all_qubits() == decomposed_circuit.all_qubits() - - lens = tuple(reg.total_bits() for reg in qrom.selection_registers) - for idxs in itertools.product(*[range(dim) for dim in data[0].shape]): - qubit_vals = {x: 0 for x in g.all_qubits} - for cval in range(2): - if num_controls: - qubit_vals.update(zip(g.quregs['control'], [cval] * num_controls)) - for isel in range(len(idxs)): - qubit_vals.update( - zip(g.quregs[f'selection{isel}'], iter_bits(idxs[isel], lens[isel])) - ) - initial_state = [qubit_vals[x] for x in g.all_qubits] - if cval or not num_controls: - for ti, d in enumerate(data): - target = g.quregs[f"target{ti}"] - qubit_vals.update(zip(target, iter_bits(int(d[idxs]), len(target)))) - final_state = [qubit_vals[x] for x in g.all_qubits] - qubit_vals = {x: 0 for x in g.all_qubits} - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit, g.all_qubits, initial_state, final_state - ) - - -@pytest.mark.parametrize( - "data", - [ - [np.arange(6, dtype=int).reshape(2, 3), 4 * np.arange(6, dtype=int).reshape(2, 3)], - [np.arange(8, dtype=int).reshape(2, 2, 2)], - ], -) -@pytest.mark.parametrize("num_controls", [0, 1, 2]) -@allow_deprecated_cirq_ft_use_in_tests -def test_ndim_t_complexity(data, num_controls): - selection_bitsizes = tuple((s - 1).bit_length() for s in data[0].shape) - target_bitsizes = tuple(int(np.max(d)).bit_length() for d in data) - qrom = cirq_ft.QROM(data, selection_bitsizes, target_bitsizes, num_controls=num_controls) - g = cirq_ft.testing.GateHelper(qrom) - n = data[0].size - assert cirq_ft.t_complexity(g.gate) == cirq_ft.t_complexity(g.operation) - assert cirq_ft.t_complexity(g.gate).t == max(0, 4 * n - 8 + 4 * num_controls) diff --git a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.ipynb b/cirq-ft/cirq_ft/algos/qubitization_walk_operator.ipynb deleted file mode 100644 index f76d30aa8ff..00000000000 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.ipynb +++ /dev/null @@ -1,124 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "13836333", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "79886234", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14d0f193", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "b6fe1c8e", - "metadata": { - "cq.autogen": "_make_QubitizationWalkOperator.md" - }, - "source": [ - "## `QubitizationWalkOperator`\n", - "Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE.\n", - "\n", - "Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of\n", - "two reflections $R_{L} = (2|L>$ of $H$ with eigenvalue $E_k$, $|\\ell>|k>$ and\n", - "an orthogonal state $\\phi_{k}$ span the irreducible two-dimensional space that $|\\ell>|k>$ is\n", - "in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of\n", - "$-2arccos(E_{k} / \\lambda)$ s.t. $W = e^{i arccos(E_k / \\lambda) Y}$.\n", - "\n", - "Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$\n", - "s.t. $spectrum(H) = \\lambda cos(arg(spectrum(W)))$ where $arg(e^{i\\phi}) = \\phi$.\n", - "\n", - "#### Parameters\n", - " - `select`: The SELECT lcu gate implementing $SELECT=\\sum_{l}|l> = \\sum_{l=0}^{L - 1}\\sqrt{\\frac{w_{l}}{\\lambda}} |l> = |\\ell>$\n", - " - `control_val`: If 0/1, a controlled version of the walk operator is constructed. Defaults to None, in which case the resulting walk operator is not controlled.\n", - " - `power`: Constructs $W^{power}$ by repeatedly decomposing into `power` copies of $W$. Defaults to 1. \n", - "\n", - "#### References\n", - "[Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Figure 1.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1847c98d", - "metadata": { - "cq.autogen": "_make_QubitizationWalkOperator.py" - }, - "outputs": [], - "source": [ - "from cirq_ft.algos.qubitization_walk_operator_test import get_walk_operator_for_1d_Ising_model\n", - "\n", - "g = cq_testing.GateHelper(\n", - " get_walk_operator_for_1d_Ising_model(4, 2e-1)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.py b/cirq-ft/cirq_ft/algos/qubitization_walk_operator.py deleted file mode 100644 index 2c59b238c2d..00000000000 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import reflection_using_prepare, select_and_prepare - - -@attr.frozen(cache_hash=True) -class QubitizationWalkOperator(infra.GateWithRegisters): - r"""Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE. - - Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of - two reflections $R_{L} = (2|L>$ of $H$ with eigenvalue $E_k$, $|\ell>|k>$ and - an orthogonal state $\phi_{k}$ span the irreducible two-dimensional space that $|\ell>|k>$ is - in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of - $-2arccos(E_{k} / \lambda)$ s.t. $W = e^{i arccos(E_k / \lambda) Y}$. - - Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$ - s.t. $spectrum(H) = \lambda cos(arg(spectrum(W)))$ where $arg(e^{i\phi}) = \phi$. - - Args: - select: The SELECT lcu gate implementing $SELECT=\sum_{l}|l> = \sum_{l=0}^{L - 1}\sqrt{\frac{w_{l}}{\lambda}} |l> = |\ell>$ - control_val: If 0/1, a controlled version of the walk operator is constructed. Defaults to - None, in which case the resulting walk operator is not controlled. - power: Constructs $W^{power}$ by repeatedly decomposing into `power` copies of $W$. - Defaults to 1. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. - """ - - select: select_and_prepare.SelectOracle - prepare: select_and_prepare.PrepareOracle - control_val: Optional[int] = None - power: int = 1 - - def __attrs_post_init__(self): - assert self.select.control_registers == self.reflect.control_registers - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.select.control_registers - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.prepare.selection_registers - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return self.select.target_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - @cached_property - def reflect(self) -> reflection_using_prepare.ReflectionUsingPrepare: - return reflection_using_prepare.ReflectionUsingPrepare( - self.prepare, control_val=self.control_val - ) - - def decompose_from_registers( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - select_reg = {reg.name: quregs[reg.name] for reg in self.select.signature} - select_op = self.select.on_registers(**select_reg) - - reflect_reg = {reg.name: quregs[reg.name] for reg in self.reflect.signature} - reflect_op = self.reflect.on_registers(**reflect_reg) - for _ in range(self.power): - yield select_op - yield reflect_op - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if self.control_val else '@(0)'] * infra.total_bits( - self.control_registers - ) - wire_symbols += ['W'] * ( - infra.total_bits(self.signature) - infra.total_bits(self.control_registers) - ) - wire_symbols[-1] = f'W^{self.power}' if self.power != 1 else 'W' - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'QubitizationWalkOperator': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - c_select = self.select.controlled(control_values=control_values) - assert isinstance(c_select, select_and_prepare.SelectOracle) - return QubitizationWalkOperator( - c_select, self.prepare, control_val=control_values[0], power=self.power - ) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def with_power(self, new_power: int) -> 'QubitizationWalkOperator': - return QubitizationWalkOperator( - self.select, self.prepare, control_val=self.control_val, power=new_power - ) - - def __repr__(self) -> str: - return ( - f'cirq_ft.QubitizationWalkOperator(' - f'{self.select}, ' - f'{self.prepare}, ' - f'{self.control_val}, ' - f'{self.power})' - ) - - def __pow__(self, power: int): - return self.with_power(self.power * power) - - def _t_complexity_(self): - if self.power > 1: - return self.power * infra.t_complexity(self.with_power(1)) - return NotImplemented diff --git a/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py b/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py deleted file mode 100644 index 5506029ea62..00000000000 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.algos.generic_select_test import get_1d_Ising_hamiltonian -from cirq_ft.algos.reflection_using_prepare_test import greedily_allocate_ancilla, keep -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def walk_operator_for_pauli_hamiltonian( - ham: cirq.PauliSum, eps: float -) -> cirq_ft.QubitizationWalkOperator: - q = sorted(ham.qubits) - ham_dps = [ps.dense(q) for ps in ham] - ham_coeff = [abs(ps.coefficient.real) for ps in ham] - prepare = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - ham_coeff, probability_epsilon=eps - ) - select = cirq_ft.GenericSelect( - infra.total_bits(prepare.selection_registers), - select_unitaries=ham_dps, - target_bitsize=len(q), - ) - return cirq_ft.QubitizationWalkOperator(select=select, prepare=prepare) - - -def get_walk_operator_for_1d_Ising_model( - num_sites: int, eps: float -) -> cirq_ft.QubitizationWalkOperator: - ham = get_1d_Ising_hamiltonian(cirq.LineQubit.range(num_sites)) - return walk_operator_for_pauli_hamiltonian(ham, eps) - - -@pytest.mark.slow -@pytest.mark.parametrize('num_sites,eps', [(4, 2e-1), (3, 1e-1)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_qubitization_walk_operator(num_sites: int, eps: float): - ham = get_1d_Ising_hamiltonian(cirq.LineQubit.range(num_sites)) - ham_coeff = [abs(ps.coefficient.real) for ps in ham] - qubitization_lambda = np.sum(ham_coeff) - - walk = walk_operator_for_pauli_hamiltonian(ham, eps) - - g = cirq_ft.testing.GateHelper(walk) - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - walk_circuit = cirq.Circuit( - cirq.decompose(g.operation, keep=keep, on_stuck_raise=None, context=context) - ) - - L_state = np.zeros(2 ** len(g.quregs['selection'])) - L_state[: len(ham_coeff)] = np.sqrt(ham_coeff / qubitization_lambda) - - greedy_mm = cirq.GreedyQubitManager('ancilla', maximize_reuse=True) - walk_circuit = cirq.map_clean_and_borrowable_qubits(walk_circuit, qm=greedy_mm) - assert len(walk_circuit.all_qubits()) < 23 - qubit_order = cirq.QubitOrder.explicit( - [*g.quregs['selection'], *g.quregs['target']], fallback=cirq.QubitOrder.DEFAULT - ) - - sim = cirq.Simulator(dtype=np.complex128) - - eigen_values, eigen_vectors = np.linalg.eigh(ham.matrix()) - for eig_idx, eig_val in enumerate(eigen_values): - # Applying the walk operator W on an initial state |L>|k> - K_state = eigen_vectors[:, eig_idx].flatten() - prep_L_K = cirq.Circuit( - cirq.StatePreparationChannel(L_state, name="PREP_L").on(*g.quregs['selection']), - cirq.StatePreparationChannel(K_state, name="PREP_K").on(*g.quregs['target']), - ) - # Initial state: |L>|k> - L_K = sim.simulate(prep_L_K, qubit_order=qubit_order).final_state_vector - - prep_walk_circuit = prep_L_K + walk_circuit - # Final state: W|L>|k>|temp> with |temp> register traced out. - final_state = sim.simulate(prep_walk_circuit, qubit_order=qubit_order).final_state_vector - final_state = final_state.reshape(len(L_K), -1).sum(axis=1) - - # Overlap: = E_{k} / lambda - overlap = np.vdot(L_K, final_state) - cirq.testing.assert_allclose_up_to_global_phase( - overlap, eig_val / qubitization_lambda, atol=1e-6 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qubitization_walk_operator_diagrams(): - num_sites, eps = 4, 1e-1 - walk = get_walk_operator_for_1d_Ising_model(num_sites, eps) - # 1. Diagram for $W = SELECT.R_{L}$ - qu_regs = infra.get_named_qubits(walk.signature) - walk_op = walk.on_registers(**qu_regs) - circuit = cirq.Circuit(cirq.decompose_once(walk_op)) - cirq.testing.assert_has_diagram( - circuit, - ''' -selection0: ───In──────────────R_L─── - │ │ -selection1: ───In──────────────R_L─── - │ │ -selection2: ───In──────────────R_L─── - │ -target0: ──────GenericSelect───────── - │ -target1: ──────GenericSelect───────── - │ -target2: ──────GenericSelect───────── - │ -target3: ──────GenericSelect───────── -''', - ) - # 2. Diagram for $W^{2} = SELECT.R_{L}.SELCT.R_{L}$ - walk_squared_op = walk.with_power(2).on_registers(**qu_regs) - circuit = cirq.Circuit(cirq.decompose_once(walk_squared_op)) - cirq.testing.assert_has_diagram( - circuit, - ''' -selection0: ───In──────────────R_L───In──────────────R_L─── - │ │ │ │ -selection1: ───In──────────────R_L───In──────────────R_L─── - │ │ │ │ -selection2: ───In──────────────R_L───In──────────────R_L─── - │ │ -target0: ──────GenericSelect─────────GenericSelect───────── - │ │ -target1: ──────GenericSelect─────────GenericSelect───────── - │ │ -target2: ──────GenericSelect─────────GenericSelect───────── - │ │ -target3: ──────GenericSelect─────────GenericSelect───────── -''', - ) - # 3. Diagram for $Ctrl-W = Ctrl-SELECT.Ctrl-R_{L}$ - controlled_walk_op = walk.controlled().on_registers(**qu_regs, control=cirq.q('control')) - circuit = cirq.Circuit(cirq.decompose_once(controlled_walk_op)) - cirq.testing.assert_has_diagram( - circuit, - ''' -control: ──────@───────────────@───── - │ │ -selection0: ───In──────────────R_L─── - │ │ -selection1: ───In──────────────R_L─── - │ │ -selection2: ───In──────────────R_L─── - │ -target0: ──────GenericSelect───────── - │ -target1: ──────GenericSelect───────── - │ -target2: ──────GenericSelect───────── - │ -target3: ──────GenericSelect───────── -''', - ) - # 4. Diagram for $Ctrl-W = Ctrl-SELECT.Ctrl-R_{L}$ in terms of $Ctrl-SELECT$ and $PREPARE$. - gateset_to_keep = cirq.Gateset( - cirq_ft.GenericSelect, - cirq_ft.StatePreparationAliasSampling, - cirq_ft.MultiControlPauli, - cirq.X, - ) - - def keep(op): - ret = op in gateset_to_keep - if op.gate is not None and isinstance(op.gate, cirq.ops.raw_types._InverseCompositeGate): - ret |= op.gate._original in gateset_to_keep - return ret - - circuit = cirq.Circuit(cirq.decompose(controlled_walk_op, keep=keep, on_stuck_raise=None)) - circuit = greedily_allocate_ancilla(circuit) - # pylint: disable=line-too-long - cirq.testing.assert_has_diagram( - circuit, - ''' -ancilla_0: ────────────────────sigma_mu───────────────────────────────sigma_mu──────────────────────── - │ │ -ancilla_1: ────────────────────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_2: ────────────────────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_3: ────────────────────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_4: ────────────────────keep───────────────────────────────────keep──────────────────────────── - │ │ -ancilla_5: ────────────────────less_than_equal────────────────────────less_than_equal───────────────── - │ │ -control: ──────@───────────────┼───────────────────────────────Z──────┼─────────────────────────────── - │ │ │ │ -selection0: ───In──────────────StatePreparationAliasSampling───@(0)───StatePreparationAliasSampling─── - │ │ │ │ -selection1: ───In──────────────selection───────────────────────@(0)───selection─────────────────────── - │ │ │ │ -selection2: ───In──────────────selection^-1────────────────────@(0)───selection─────────────────────── - │ -target0: ──────GenericSelect────────────────────────────────────────────────────────────────────────── - │ -target1: ──────GenericSelect────────────────────────────────────────────────────────────────────────── - │ -target2: ──────GenericSelect────────────────────────────────────────────────────────────────────────── - │ -target3: ──────GenericSelect──────────────────────────────────────────────────────────────────────────''', - ) - # pylint: enable=line-too-long - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qubitization_walk_operator_consistent_protocols_and_controlled(): - gate = get_walk_operator_for_1d_Ising_model(4, 1e-1) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - # Test consistent repr - cirq.testing.assert_equivalent_repr( - gate, setup_code='import cirq\nimport cirq_ft\nimport numpy as np' - ) - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = gate.controlled(num_controls=2) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('qubitization_walk_operator') - execute_notebook('phase_estimation_of_quantum_walk') diff --git a/cirq-ft/cirq_ft/algos/reflection_using_prepare.py b/cirq-ft/cirq_ft/algos/reflection_using_prepare.py deleted file mode 100644 index 4a084cee4a6..00000000000 --- a/cirq-ft/cirq_ft/algos/reflection_using_prepare.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import multi_control_multi_target_pauli as mcmt -from cirq_ft.algos import select_and_prepare - - -@attr.frozen(cache_hash=True) -class ReflectionUsingPrepare(infra.GateWithRegisters): - """Applies reflection around a state prepared by `prepare_gate` - - Applies $R_{s} = I - 2|s><0|)P$ s.t. $P|0> = |s>$. - Here - $|s>$: The state along which we want to reflect. - $P$: Unitary that prepares that state $|s>$ from the zero state $|0>$ - $R_{s}$: Reflection operator that adds a `-1` phase to all states in the subspace - spanned by $|s>$. - - The composite gate corresponds to implementing the following circuit: - - |control> ------------------ Z ------------------- - | - |L> ---- PREPARE^† --- o --- PREPARE ------- - - - Args: - prepare_gate: An instance of `cq.StatePreparationAliasSampling` gate the corresponds to - `PREPARE`. - control_val: If 0/1, a controlled version of the reflection operator is constructed. - Defaults to None, in which case the resulting reflection operator is not controlled. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. - """ - - prepare_gate: select_and_prepare.PrepareOracle - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if self.control_val is None else (infra.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.prepare_gate.selection_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.control_registers, *self.selection_registers]) - - def decompose_from_registers( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - qm = context.qubit_manager - # 0. Allocate new ancillas, if needed. - phase_target = qm.qalloc(1)[0] if self.control_val is None else quregs.pop('control')[0] - state_prep_ancilla = { - reg.name: qm.qalloc(reg.total_bits()) for reg in self.prepare_gate.junk_registers - } - state_prep_selection_regs = quregs - prepare_op = self.prepare_gate.on_registers( - **state_prep_selection_regs, **state_prep_ancilla - ) - # 1. PREPARE† - yield cirq.inverse(prepare_op) - # 2. MultiControlled Z, controlled on |000..00> state. - phase_control = infra.merge_qubits(self.selection_registers, **state_prep_selection_regs) - yield cirq.X(phase_target) if not self.control_val else [] - yield mcmt.MultiControlPauli([0] * len(phase_control), target_gate=cirq.Z).on_registers( - controls=phase_control, target=phase_target - ) - yield cirq.X(phase_target) if not self.control_val else [] - # 3. PREPARE - yield prepare_op - - # 4. Deallocate ancilla. - qm.qfree([q for anc in state_prep_ancilla.values() for q in anc]) - if self.control_val is None: - qm.qfree([phase_target]) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if self.control_val else '@(0)'] * infra.total_bits( - self.control_registers - ) - wire_symbols += ['R_L'] * infra.total_bits(self.selection_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __repr__(self): - return f'cirq_ft.ReflectionUsingPrepare({self.prepare_gate}, {self.control_val})' - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'ReflectionUsingPrepare': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - return ReflectionUsingPrepare(self.prepare_gate, control_val=control_values[0]) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) diff --git a/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py b/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py deleted file mode 100644 index 58618728563..00000000000 --- a/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import itertools - -import cirq -import cirq_ft -from cirq_ft import infra -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - -gateset_to_keep = cirq.Gateset( - cirq_ft.And, - cirq_ft.LessThanGate, - cirq_ft.LessThanEqualGate, - cirq_ft.MultiTargetCSwap, - cirq_ft.MultiTargetCNOT, - cirq_ft.MultiControlPauli, - cirq.H, - cirq.CCNOT, - cirq.CNOT, - cirq.FREDKIN, - cirq.ControlledGate, - cirq.AnyUnitaryGateFamily(1), -) - - -def keep(op: cirq.Operation): - ret = op in gateset_to_keep - if op.gate is not None and isinstance(op.gate, cirq.ops.raw_types._InverseCompositeGate): - ret |= op.gate._original in gateset_to_keep - return ret - - -def greedily_allocate_ancilla(circuit: cirq.AbstractCircuit) -> cirq.Circuit: - greedy_mm = cirq.GreedyQubitManager(prefix="ancilla", maximize_reuse=True) - circuit = cirq.map_clean_and_borrowable_qubits(circuit, qm=greedy_mm) - assert len(circuit.all_qubits()) < 30 - return circuit.unfreeze() - - -def construct_gate_helper_and_qubit_order(gate): - g = cirq_ft.testing.GateHelper(gate) - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - circuit = cirq.Circuit( - cirq.decompose(g.operation, keep=keep, on_stuck_raise=None, context=context) - ) - ordered_input = list(itertools.chain(*g.quregs.values())) - qubit_order = cirq.QubitOrder.explicit(ordered_input, fallback=cirq.QubitOrder.DEFAULT) - return g, qubit_order, circuit - - -def get_3q_uniform_dirac_notation(signs): - terms = [ - '0.35|000⟩', - '0.35|001⟩', - '0.35|010⟩', - '0.35|011⟩', - '0.35|100⟩', - '0.35|101⟩', - '0.35|110⟩', - '0.35|111⟩', - ] - ret = terms[0] if signs[0] == '+' else f'-{terms[0]}' - for c, term in zip(signs[1:], terms[1:]): - ret = ret + f' {c} {term}' - return ret - - -@pytest.mark.slow -@pytest.mark.parametrize('num_ones', [*range(5, 9)]) -@pytest.mark.parametrize('eps', [0.01]) -@allow_deprecated_cirq_ft_use_in_tests -def test_reflection_using_prepare(num_ones, eps): - data = [1] * num_ones - prepare_gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - data, probability_epsilon=eps - ) - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate) - g, qubit_order, decomposed_circuit = construct_gate_helper_and_qubit_order(gate) - decomposed_circuit = greedily_allocate_ancilla(decomposed_circuit) - - initial_state_prep = cirq.Circuit(cirq.H.on_each(*g.quregs['selection'])) - initial_state = cirq.dirac_notation(initial_state_prep.final_state_vector()) - assert initial_state == get_3q_uniform_dirac_notation('++++++++') - result = cirq.Simulator(dtype=np.complex128).simulate( - initial_state_prep + decomposed_circuit, qubit_order=qubit_order - ) - selection = g.quregs['selection'] - prepared_state = result.final_state_vector.reshape(2 ** len(selection), -1).sum(axis=1) - signs = '-' * num_ones + '+' * (9 - num_ones) - assert cirq.dirac_notation(prepared_state) == get_3q_uniform_dirac_notation(signs) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_reflection_using_prepare_diagram(): - data = [1, 2, 3, 4, 5, 6] - eps = 0.1 - prepare_gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - data, probability_epsilon=eps - ) - # No control - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=None) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - circuit = greedily_allocate_ancilla(cirq.Circuit(cirq.decompose_once(op))) - cirq.testing.assert_has_diagram( - circuit, - ''' - ┌──────────────────────────────┐ ┌──────────────────────────────┐ -ancilla_0: ─────sigma_mu───────────────────────────────────sigma_mu───────────────────────── - │ │ -ancilla_1: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_2: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_3: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_4: ─────keep───────────────────────────────────────keep───────────────────────────── - │ │ -ancilla_5: ─────less_than_equal────────────────────────────less_than_equal────────────────── - │ │ -ancilla_6: ─────┼────────────────────────────X────Z───────X┼──────────────────────────────── - │ │ │ -selection0: ────StatePreparationAliasSampling─────@(0)─────StatePreparationAliasSampling──── - │ │ │ -selection1: ────selection─────────────────────────@(0)─────selection──────────────────────── - │ │ │ -selection2: ────selection^-1──────────────────────@(0)─────selection──────────────────────── - └──────────────────────────────┘ └──────────────────────────────┘''', - ) - - # Control on `|1>` state - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=1) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - circuit = greedily_allocate_ancilla(cirq.Circuit(cirq.decompose_once(op))) - cirq.testing.assert_has_diagram( - circuit, - ''' -ancilla_0: ────sigma_mu───────────────────────────────sigma_mu──────────────────────── - │ │ -ancilla_1: ────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_2: ────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_3: ────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_4: ────keep───────────────────────────────────keep──────────────────────────── - │ │ -ancilla_5: ────less_than_equal────────────────────────less_than_equal───────────────── - │ │ -control: ──────┼───────────────────────────────Z──────┼─────────────────────────────── - │ │ │ -selection0: ───StatePreparationAliasSampling───@(0)───StatePreparationAliasSampling─── - │ │ │ -selection1: ───selection───────────────────────@(0)───selection─────────────────────── - │ │ │ -selection2: ───selection^-1────────────────────@(0)───selection─────────────────────── -''', - ) - - # Control on `|0>` state - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=0) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - circuit = greedily_allocate_ancilla(cirq.Circuit(cirq.decompose_once(op))) - cirq.testing.assert_has_diagram( - circuit, - ''' - ┌──────────────────────────────┐ ┌──────────────────────────────┐ -ancilla_0: ─────sigma_mu───────────────────────────────────sigma_mu───────────────────────── - │ │ -ancilla_1: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_2: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_3: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_4: ─────keep───────────────────────────────────────keep───────────────────────────── - │ │ -ancilla_5: ─────less_than_equal────────────────────────────less_than_equal────────────────── - │ │ -control: ───────┼────────────────────────────X────Z───────X┼──────────────────────────────── - │ │ │ -selection0: ────StatePreparationAliasSampling─────@(0)─────StatePreparationAliasSampling──── - │ │ │ -selection1: ────selection─────────────────────────@(0)─────selection──────────────────────── - │ │ │ -selection2: ────selection^-1──────────────────────@(0)─────selection──────────────────────── - └──────────────────────────────┘ └──────────────────────────────┘ -''', - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_reflection_using_prepare_consistent_protocols_and_controlled(): - prepare_gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - [1, 2, 3, 4], probability_epsilon=0.1 - ) - # No control - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=None) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - # Test consistent repr - cirq.testing.assert_equivalent_repr( - gate, setup_code='import cirq\nimport cirq_ft\nimport numpy as np' - ) - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = gate.controlled(num_controls=2) diff --git a/cirq-ft/cirq_ft/algos/select_and_prepare.py b/cirq-ft/cirq_ft/algos/select_and_prepare.py deleted file mode 100644 index 3338aef2750..00000000000 --- a/cirq-ft/cirq_ft/algos/select_and_prepare.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import abc -from functools import cached_property -from typing import Tuple - -from cirq_ft import infra - - -class SelectOracle(infra.GateWithRegisters): - r"""Abstract base class that defines the API for a SELECT Oracle. - - The action of a SELECT oracle on a selection register $|l\rangle$ and target register - $|\Psi\rangle$ can be defined as: - - $$ - \mathrm{SELECT} = \sum_{l}|l \rangle \langle l| \otimes U_l - $$ - - In other words, the `SELECT` oracle applies $l$'th unitary $U_{l}$ on the target register - $|\Psi\rangle$ when the selection register stores integer $l$. - - $$ - \mathrm{SELECT}|l\rangle |\Psi\rangle = |l\rangle U_{l}|\Psi\rangle - $$ - """ - - @property - @abc.abstractmethod - def control_registers(self) -> Tuple[infra.Register, ...]: ... - - @property - @abc.abstractmethod - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: ... - - @property - @abc.abstractmethod - def target_registers(self) -> Tuple[infra.Register, ...]: ... - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - -class PrepareOracle(infra.GateWithRegisters): - r"""Abstract base class that defines the API for a PREPARE Oracle. - - Given a set of coefficients $\{c_0, c_1, ..., c_{N - 1}\}, the PREPARE oracle is used to encode - the coefficients as amplitudes of a state $|\Psi\rangle = \sum_{i=0}^{N-1}c_{i}|i\rangle$ using - a selection register $|i\rangle$. In order to prepare such a state, the PREPARE circuit is also - allowed to use a junk register that is entangled with selection register. - - Thus, the action of a PREPARE circuit on an input state $|0\rangle$ can be defined as: - - $$ - PREPARE |0\rangle = \sum_{i=0}^{N-1}c_{i}|i\rangle |junk_{i}\rangle - $$ - """ - - @property - @abc.abstractmethod - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: ... - - @cached_property - def junk_registers(self) -> Tuple[infra.Register, ...]: - return () - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.junk_registers]) diff --git a/cirq-ft/cirq_ft/algos/select_swap_qrom.py b/cirq-ft/cirq_ft/algos/select_swap_qrom.py deleted file mode 100644 index 169be003e8c..00000000000 --- a/cirq-ft/cirq_ft/algos/select_swap_qrom.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import List, Optional, Sequence, Tuple -from numpy.typing import NDArray - -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import qrom, swap_network - - -def find_optimal_log_block_size(iteration_length: int, target_bitsize: int) -> int: - """Find optimal block size, which is a power of 2, for SelectSwapQROM. - - This functions returns the optimal `k` s.t. - * k is in an integer and k >= 0. - * iteration_length/2^k + target_bitsize*(2^k - 1) is minimized. - The corresponding block size for SelectSwapQROM would be 2^k. - """ - k = 0.5 * np.log2(iteration_length / target_bitsize) - if k < 0: - return 1 - - def value(kk: List[int]): - return iteration_length / np.power(2, kk) + target_bitsize * (np.power(2, kk) - 1) - - k_int = [np.floor(k), np.ceil(k)] # restrict optimal k to integers - return int(k_int[np.argmin(value(k_int))]) # obtain optimal k - - -@cirq.value_equality() -class SelectSwapQROM(infra.GateWithRegisters): - """Gate to load data[l] in the target register when the selection register stores integer l. - - Let - N:= Number of data elements to load. - b:= Bit-length of the target register in which data elements should be loaded. - - The `SelectSwapQROM` is a hybrid of the following two existing primitives: - - * Unary Iteration based `cirq_ft.QROM` requires O(N) T-gates to load `N` data - elements into a b-bit target register. Note that the T-complexity is independent of `b`. - * `cirq_ft.SwapWithZeroGate` can swap a `b` bit register indexed `x` with a `b` - bit register at index `0` using O(b) T-gates, if the selection register stores integer `x`. - Note that the swap complexity is independent of the iteration length `N`. - - The `SelectSwapQROM` uses square root decomposition by combining the above two approaches to - further optimize the T-gate complexity of loading `N` data elements, each into a `b` bit - target register as follows: - - * Divide the `N` data elements into batches of size `B` (a variable) and - load each batch simultaneously into `B` distinct target signature using the conventional - QROM. This has T-complexity `O(N / B)`. - * Use `SwapWithZeroGate` to swap the `i % B`'th target register in batch number `i / B` - to load `data[i]` in the 0'th target register. This has T-complexity `O(B * b)`. - - This, the final T-complexity of `SelectSwapQROM` is `O(B * b + N / B)` T-gates; where `B` is - the block-size with an optimal value of `O(sqrt(N / b))`. - - This improvement in T-complexity is achieved at the cost of using an additional `O(B * b)` - ancilla qubits, with a nice property that these additional ancillas can be `dirty`; i.e. - they don't need to start in the |0> state and thus can be borrowed from other parts of the - algorithm. The state of these dirty ancillas would be unaffected after the operation has - finished. - - For more details, see the reference below: - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low, Kliuchnikov, Schaeffer. 2018. - """ - - def __init__( - self, - *data: Sequence[int], - target_bitsizes: Optional[Sequence[int]] = None, - block_size: Optional[int] = None, - ): - """Initializes SelectSwapQROM - - For a single data sequence of length `N`, maximum target bitsize `b` and block size `B`; - SelectSwapQROM requires: - - Selection register & ancilla of size `logN` for QROM data load. - - 1 clean target register of size `b`. - - `B` dirty target signature, each of size `b`. - - Similarly, to load `M` such data sequences, `SelectSwapQROM` requires: - - Selection register & ancilla of size `logN` for QROM data load. - - 1 clean target register of size `sum(target_bitsizes)`. - - `B` dirty target signature, each of size `sum(target_bitsizes)`. - - Args: - data: Sequence of integers to load in the target register. If more than one sequence - is provided, each sequence must be of the same length. - target_bitsizes: Sequence of integers describing the size of target register for each - data sequence to load. Defaults to `max(data[i]).bit_length()` for each i. - block_size(B): Load batches of `B` data elements in each iteration of traditional QROM - (N/B iterations required). Complexity of SelectSwap QROAM scales as - `O(B * b + N / B)`, where `B` is the block_size. Defaults to optimal value of - `O(sqrt(N / b))`. - - Raises: - ValueError: If all target data sequences to load do not have the same length. - """ - # Validate input. - if len(set(len(d) for d in data)) != 1: - raise ValueError("All data sequences to load must be of equal length.") - if target_bitsizes is None: - target_bitsizes = [max(d).bit_length() for d in data] - assert len(target_bitsizes) == len(data) - assert all(t >= max(d).bit_length() for t, d in zip(target_bitsizes, data)) - self._num_sequences = len(data) - self._target_bitsizes = tuple(target_bitsizes) - self._iteration_length = len(data[0]) - if block_size is None: - # Figure out optimal value of block_size - block_size = 2 ** find_optimal_log_block_size(len(data[0]), sum(target_bitsizes)) - assert 0 < block_size <= self._iteration_length - self._block_size = block_size - self._num_blocks = int(np.ceil(self._iteration_length / self.block_size)) - self.selection_q, self.selection_r = tuple( - (L - 1).bit_length() for L in [self.num_blocks, self.block_size] - ) - self._data = tuple(tuple(d) for d in data) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister( - 'selection', self.selection_q + self.selection_r, self._iteration_length - ), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return tuple( - infra.Register(f'target{sequence_id}', self._target_bitsizes[sequence_id]) - for sequence_id in range(self._num_sequences) - ) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.target_registers]) - - @property - def data(self) -> Tuple[Tuple[int, ...], ...]: - return self._data - - @property - def block_size(self) -> int: - return self._block_size - - @property - def num_blocks(self) -> int: - return self._num_blocks - - def __repr__(self) -> str: - data_repr = ','.join(repr(d) for d in self.data) - target_repr = repr(self._target_bitsizes) - return ( - f"cirq_ft.SelectSwapQROM(" - f"{data_repr}, " - f"target_bitsizes={target_repr}, " - f"block_size={self.block_size})" - ) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - # Divide each data sequence and corresponding target registers into - # `self.num_blocks` batches of size `self.block_size`. - selection, targets = quregs.pop('selection'), quregs - qrom_data: List[NDArray] = [] - qrom_target_bitsizes: List[int] = [] - ordered_target_qubits: List[cirq.Qid] = [] - for block_id in range(self.block_size): - for sequence_id in range(self._num_sequences): - data = self.data[sequence_id] - target_bitsize = self._target_bitsizes[sequence_id] - ordered_target_qubits.extend(context.qubit_manager.qborrow(target_bitsize)) - data_for_current_block = data[block_id :: self.block_size] - if len(data_for_current_block) < self.num_blocks: - zero_pad = (0,) * (self.num_blocks - len(data_for_current_block)) - data_for_current_block = data_for_current_block + zero_pad - qrom_data.append(np.array(data_for_current_block)) - qrom_target_bitsizes.append(target_bitsize) - # Construct QROM, SwapWithZero and CX operations using the batched data and qubits. - k = (self.block_size - 1).bit_length() - q, r = selection[: self.selection_q], selection[self.selection_q :] - qrom_gate = qrom.QROM( - qrom_data, - selection_bitsizes=(self.selection_q,), - target_bitsizes=tuple(qrom_target_bitsizes), - ) - qrom_op = qrom_gate.on_registers( - selection=q, **infra.split_qubits(qrom_gate.target_registers, ordered_target_qubits) - ) - swap_with_zero_gate = swap_network.SwapWithZeroGate( - k, infra.total_bits(self.target_registers), self.block_size - ) - swap_with_zero_op = swap_with_zero_gate.on_registers( - selection=r, - **infra.split_qubits(swap_with_zero_gate.target_registers, ordered_target_qubits), - ) - clean_targets = infra.merge_qubits(self.target_registers, **targets) - cnot_op = cirq.Moment(cirq.CNOT(s, t) for s, t in zip(ordered_target_qubits, clean_targets)) - # Yield the operations in correct order. - yield qrom_op - yield swap_with_zero_op - yield cnot_op - yield cirq.inverse(swap_with_zero_op) - yield cirq.inverse(qrom_op) - yield swap_with_zero_op - yield cnot_op - yield cirq.inverse(swap_with_zero_op) - - context.qubit_manager.qfree(ordered_target_qubits) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In_q"] * self.selection_q - wire_symbols += ["In_r"] * self.selection_r - for i, target in enumerate(self.target_registers): - wire_symbols += [f"QROAM_{i}"] * target.total_bits() - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _value_equality_values_(self): - return self.block_size, self._target_bitsizes, self.data diff --git a/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py b/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py deleted file mode 100644 index 5982cd350b5..00000000000 --- a/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "data,block_size", - [ - pytest.param( - data, - block_size, - id=f"{block_size}-data{didx}", - marks=pytest.mark.slow if block_size == 3 and didx == 1 else (), - ) - for didx, data in enumerate([[[1, 2, 3, 4, 5]], [[1, 2, 3], [3, 2, 1]]]) - for block_size in [None, 1, 2, 3] - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_select_swap_qrom(data, block_size): - qrom = cirq_ft.SelectSwapQROM(*data, block_size=block_size) - qubit_regs = infra.get_named_qubits(qrom.signature) - selection = qubit_regs["selection"] - selection_q, selection_r = selection[: qrom.selection_q], selection[qrom.selection_q :] - targets = [qubit_regs[f"target{i}"] for i in range(len(data))] - - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - qrom_circuit = cirq.Circuit(cirq.decompose(qrom.on_registers(**qubit_regs), context=context)) - - dirty_target_ancilla = [ - q for q in qrom_circuit.all_qubits() if isinstance(q, cirq.ops.BorrowableQubit) - ] - - circuit = cirq.Circuit( - # Prepare dirty ancillas in an arbitrary state. - cirq.H.on_each(*dirty_target_ancilla), - cirq.T.on_each(*dirty_target_ancilla), - # The dirty ancillas should remain unaffected by qroam. - *qrom_circuit, - # Bring back the dirty ancillas to their original state. - (cirq.T**-1).on_each(*dirty_target_ancilla), - cirq.H.on_each(*dirty_target_ancilla), - ) - all_qubits = sorted(circuit.all_qubits()) - for selection_integer in range(qrom.selection_registers[0].iteration_length): - svals_q = list(iter_bits(selection_integer // qrom.block_size, len(selection_q))) - svals_r = list(iter_bits(selection_integer % qrom.block_size, len(selection_r))) - qubit_vals = {x: 0 for x in all_qubits} - qubit_vals.update({s: sval for s, sval in zip(selection_q, svals_q)}) - qubit_vals.update({s: sval for s, sval in zip(selection_r, svals_r)}) - - dvals = np.random.randint(2, size=len(dirty_target_ancilla)) - qubit_vals.update({d: dval for d, dval in zip(dirty_target_ancilla, dvals)}) - - initial_state = [qubit_vals[x] for x in all_qubits] - for target, d in zip(targets, data): - for q, b in zip(target, iter_bits(d[selection_integer], len(target))): - qubit_vals[q] = b - final_state = [qubit_vals[x] for x in all_qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_repr(): - qrom = cirq_ft.SelectSwapQROM([1, 2], [3, 5]) - cirq.testing.assert_equivalent_repr(qrom, setup_code="import cirq_ft\n") - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qroam_diagram(): - data = [[1, 2, 3], [4, 5, 6]] - blocksize = 2 - qrom = cirq_ft.SelectSwapQROM(*data, block_size=blocksize) - q = cirq.LineQubit.range(cirq.num_qubits(qrom)) - circuit = cirq.Circuit(qrom.on_registers(**infra.split_qubits(qrom.signature, q))) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───In_q────── - │ -1: ───In_r────── - │ -2: ───QROAM_0─── - │ -3: ───QROAM_0─── - │ -4: ───QROAM_1─── - │ -5: ───QROAM_1─── - │ -6: ───QROAM_1─── -""", - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qroam_raises(): - with pytest.raises(ValueError, match="must be of equal length"): - _ = cirq_ft.SelectSwapQROM([1, 2], [1, 2, 3]) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qroam_hashable(): - qrom = cirq_ft.SelectSwapQROM([1, 2, 5, 6, 7, 8]) - assert hash(qrom) is not None - assert cirq_ft.t_complexity(qrom) == cirq_ft.TComplexity(32, 160, 0) diff --git a/cirq-ft/cirq_ft/algos/selected_majorana_fermion.py b/cirq-ft/cirq_ft/algos/selected_majorana_fermion.py deleted file mode 100644 index 057b194d7db..00000000000 --- a/cirq-ft/cirq_ft/algos/selected_majorana_fermion.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Sequence, Union, Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np - -from cirq_ft import infra -from cirq_ft.algos import unary_iteration_gate - - -@attr.frozen -class SelectedMajoranaFermionGate(unary_iteration_gate.UnaryIterationGate): - """Implements U s.t. U|l>|Psi> -> |l> T_{l} . Z_{l - 1} ... Z_{0} |Psi> - - where T is a single qubit target gate that defaults to pauli Y. The gate is - implemented using an accumulator bit in the unary iteration circuit as explained - in the reference below. - - - Args: - selection_regs: Indexing `select` signature of type `SelectionRegister`. It also contains - information about the iteration length of each selection register. - control_regs: Control signature for constructing a controlled version of the gate. - target_gate: Single qubit gate to be applied to the target qubits. - - References: - See Fig 9 of https://arxiv.org/abs/1805.03662 for more details. - """ - - selection_regs: Tuple[infra.SelectionRegister, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.SelectionRegister) else tuple(v) - ) - control_regs: Tuple[infra.Register, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.Register) else tuple(v) - ) - target_gate: cirq.Gate = cirq.Y - - @control_regs.default - def control_regs_default(self): - return infra.Register('control', 1) - - @classmethod - def make_on( - cls, - *, - target_gate=cirq.Y, - **quregs: Union[Sequence[cirq.Qid], NDArray[cirq.Qid]], # type: ignore[type-var] - ) -> cirq.Operation: - """Helper constructor to automatically deduce selection_regs attribute.""" - return SelectedMajoranaFermionGate( - selection_regs=infra.SelectionRegister( - 'selection', len(quregs['selection']), len(quregs['target']) - ), - target_gate=target_gate, - ).on_registers(**quregs) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.control_regs - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.selection_regs - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - total_iteration_size = np.prod( - tuple(reg.iteration_length for reg in self.selection_registers) - ) - return (infra.Register('target', int(total_iteration_size)),) - - @cached_property - def extra_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('accumulator', 1),) - - def decompose_from_registers( - self, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - quregs['accumulator'] = np.array(context.qubit_manager.qalloc(1)) - control = ( - quregs[self.control_regs[0].name] if infra.total_bits(self.control_registers) else [] - ) - yield cirq.X(*quregs['accumulator']).controlled_by(*control) - yield super(SelectedMajoranaFermionGate, self).decompose_from_registers( - context=context, **quregs - ) - context.qubit_manager.qfree(quregs['accumulator']) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@"] * infra.total_bits(self.control_registers) - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - wire_symbols += [f"Z{self.target_gate}"] * infra.total_bits(self.target_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - target: Sequence[cirq.Qid], - accumulator: Sequence[cirq.Qid], - **selection_indices: int, - ) -> cirq.OP_TREE: - selection_shape = tuple(reg.iteration_length for reg in self.selection_regs) - selection_idx = tuple(selection_indices[reg.name] for reg in self.selection_regs) - target_idx = int(np.ravel_multi_index(selection_idx, selection_shape)) - yield cirq.CNOT(control, *accumulator) - yield self.target_gate(target[target_idx]).controlled_by(control) - yield cirq.CZ(*accumulator, target[target_idx]) diff --git a/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py b/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py deleted file mode 100644 index eae2999ff3d..00000000000 --- a/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize", - [ - (2, 4), - pytest.param(3, 8, marks=pytest.mark.slow), - pytest.param(4, 9, marks=pytest.mark.slow), - ], -) -@pytest.mark.parametrize("target_gate", [cirq.X, cirq.Y]) -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate(selection_bitsize, target_bitsize, target_gate): - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=target_gate, - ) - g = cirq_ft.testing.GateHelper(gate) - assert len(g.all_qubits) <= infra.total_bits(gate.signature) + selection_bitsize + 1 - - sim = cirq.Simulator(dtype=np.complex128) - for n in range(target_bitsize): - # Initial qubit values - qubit_vals = {q: 0 for q in g.operation.qubits} - # All controls 'on' to activate circuit - qubit_vals.update({c: 1 for c in g.quregs['control']}) - # Set selection according to `n` - qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in g.operation.qubits] - - result = sim.simulate( - g.circuit, initial_state=initial_state, qubit_order=g.operation.qubits - ) - - final_target_state = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=[g.operation.qubits.index(q) for q in g.quregs['target']], - ) - - expected_target_state = cirq.Circuit( - [cirq.Z(q) for q in g.quregs['target'][:n]], - target_gate(g.quregs['target'][n]), - [cirq.I(q) for q in g.quregs['target'][n + 1 :]], - ).final_state_vector(qubit_order=g.quregs['target']) - - cirq.testing.assert_allclose_up_to_global_phase( - expected_target_state, final_target_state, atol=1e-6 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate_diagram(): - selection_bitsize, target_bitsize = 3, 5 - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=cirq.X, - ) - circuit = cirq.Circuit(gate.on_registers(**infra.get_named_qubits(gate.signature))) - qubits = list(q for v in infra.get_named_qubits(gate.signature).values() for q in v) - cirq.testing.assert_has_diagram( - circuit, - """ -control: ──────@──── - │ -selection0: ───In─── - │ -selection1: ───In─── - │ -selection2: ───In─── - │ -target0: ──────ZX─── - │ -target1: ──────ZX─── - │ -target2: ──────ZX─── - │ -target3: ──────ZX─── - │ -target4: ──────ZX─── -""", - qubit_order=qubits, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate_decomposed_diagram(): - selection_bitsize, target_bitsize = 2, 3 - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=cirq.X, - ) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - g = cirq_ft.testing.GateHelper(gate) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(g.operation, context=context)) - ancillas = sorted(set(circuit.all_qubits()) - set(g.operation.qubits)) - qubits = np.concatenate( - [ - g.quregs['control'], - [q for qs in zip(g.quregs['selection'], ancillas[1:]) for q in qs], - ancillas[0:1], - g.quregs['target'], - ] - ) - cirq.testing.assert_has_diagram( - circuit, - """ -control: ──────@───@──────────────────────────────────────@───────────@────── - │ │ │ │ -selection0: ───┼───(0)────────────────────────────────────┼───────────@────── - │ │ │ │ -_a_1: ─────────┼───And───@─────────────@───────────@──────X───@───@───And†─── - │ │ │ │ │ │ -selection1: ───┼─────────(0)───────────┼───────────@──────────┼───┼────────── - │ │ │ │ │ │ -_a_2: ─────────┼─────────And───@───@───X───@───@───And†───────┼───┼────────── - │ │ │ │ │ │ │ -_a_0: ─────────X───────────────X───┼───@───X───┼───@──────────X───┼───@────── - │ │ │ │ │ │ -target0: ──────────────────────────X───@───────┼───┼──────────────┼───┼────── - │ │ │ │ -target1: ──────────────────────────────────────X───@──────────────┼───┼────── - │ │ -target2: ─────────────────────────────────────────────────────────X───@────── """, - qubit_order=qubits, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate_make_on(): - selection_bitsize, target_bitsize = 3, 5 - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=cirq.X, - ) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - op2 = cirq_ft.SelectedMajoranaFermionGate.make_on( - target_gate=cirq.X, **infra.get_named_qubits(gate.signature) - ) - assert op == op2 diff --git a/cirq-ft/cirq_ft/algos/state_preparation.ipynb b/cirq-ft/cirq_ft/algos/state_preparation.ipynb deleted file mode 100644 index e9cef3f0f42..00000000000 --- a/cirq-ft/cirq_ft/algos/state_preparation.ipynb +++ /dev/null @@ -1,164 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "ce48b2ae", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "e2dca938", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# State Preparation using Coherent Alias Sampling\n", - "\n", - "Gates for preparing coefficient states.\n", - "\n", - "In section III.D. of the [Linear T paper](https://arxiv.org/abs/1805.03662) the authors introduce\n", - "a technique for initializing a state with $L$ unique coefficients (provided by a classical\n", - "database) with a number of T gates scaling as 4L + O(log(1/eps)) where eps is the\n", - "largest absolute error that one can tolerate in the prepared amplitudes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1e7c27f6", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "bf85ebed", - "metadata": { - "cq.autogen": "_make_StatePreparationAliasSampling.md" - }, - "source": [ - "## `StatePreparationAliasSampling`\n", - "Initialize a state with $L$ unique coefficients using coherent alias sampling.\n", - "\n", - "In particular, we take the zero state to:\n", - "\n", - "$$\n", - "\\sum_{\\ell=0}^{L-1} \\sqrt{p_\\ell} |\\ell\\rangle |\\mathrm{temp}_\\ell\\rangle\n", - "$$\n", - "\n", - "where the probabilities $p_\\ell$ are $\\mu$-bit binary approximations to the true values and\n", - "where the temporary register must be treated with care, see the details in Section III.D. of\n", - "the reference.\n", - "\n", - "The preparation is equivalent to [classical alias sampling](https://en.wikipedia.org/wiki/Alias_method):\n", - "we sample `l` with probability `p[l]` by first selecting `l` uniformly at random and then\n", - "returning it with probability `keep[l] / 2**mu`; otherwise returning `alt[l]`.\n", - "\n", - "Registers:\n", - " selection: The input/output register $|\\ell\\rangle$ of size lg(L) where the desired\n", - " coefficient state is prepared.\n", - " temp: Work space comprised of sub signature:\n", - " - sigma: A mu-sized register containing uniform probabilities for comparison against\n", - " `keep`.\n", - " - alt: A lg(L)-sized register of alternate indices\n", - " - keep: a mu-sized register of probabilities of keeping the initially sampled index.\n", - " - one bit for the result of the comparison.\n", - "\n", - "This gate corresponds to the following operations:\n", - " - UNIFORM_L on the selection register\n", - " - H^mu on the sigma register\n", - " - QROM addressed by the selection register into the alt and keep signature.\n", - " - LessThanEqualGate comparing the keep and sigma signature.\n", - " - Coherent swap between the selection register and alt register if the comparison\n", - " returns True.\n", - "\n", - "Total space will be (2 * log(L) + 2 mu + 1) work qubits + log(L) ancillas for QROM.\n", - "The 1 ancilla in work qubits is for the `LessThanEqualGate` followed by coherent swap.\n", - "\n", - "#### Parameters\n", - " - `lcu_probabilities`: The LCU coefficients.\n", - " - `probability_epsilon`: The desired accuracy to represent each probability (which sets mu size and keep/alt integers). See `openfermion.circuits.lcu_util.preprocess_lcu_coefficients_for_reversible_sampling` for more information. \n", - "\n", - "#### References\n", - "[Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Section III.D. and Figure 11.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4f9f2b46", - "metadata": { - "cq.autogen": "_make_StatePreparationAliasSampling.py" - }, - "outputs": [], - "source": [ - "coeffs = np.array([1.0, 1, 3, 2])\n", - "mu = 3\n", - "\n", - "state_prep = cirq_ft.StatePreparationAliasSampling.from_lcu_probs(\n", - " coeffs, probability_epsilon=2**-mu / len(coeffs)\n", - ")\n", - "g = cq_testing.GateHelper(\n", - " state_prep\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26c46f4b", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/state_preparation.py b/cirq-ft/cirq_ft/algos/state_preparation.py deleted file mode 100644 index 6a0f5cec994..00000000000 --- a/cirq-ft/cirq_ft/algos/state_preparation.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -"""Gates for preparing coefficient states. - -In section III.D. of the [Linear T paper](https://arxiv.org/abs/1805.03662) the authors introduce -a technique for initializing a state with $L$ unique coefficients (provided by a classical -database) with a number of T gates scaling as 4L + O(log(1/eps)) where eps is the -largest absolute error that one can tolerate in the prepared amplitudes. -""" - -from functools import cached_property -from typing import List, Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra, linalg -from cirq_ft.algos import ( - arithmetic_gates, - prepare_uniform_superposition, - qrom, - select_and_prepare, - swap_network, -) - - -@cirq.value_equality() -@attr.frozen -class StatePreparationAliasSampling(select_and_prepare.PrepareOracle): - r"""Initialize a state with $L$ unique coefficients using coherent alias sampling. - - In particular, we take the zero state to: - - $$ - \sum_{\ell=0}^{L-1} \sqrt{p_\ell} |\ell\rangle |\mathrm{temp}_\ell\rangle - $$ - - where the probabilities $p_\ell$ are $\mu$-bit binary approximations to the true values and - where the temporary register must be treated with care, see the details in Section III.D. of - the reference. - - The preparation is equivalent to [classical alias sampling] - (https://en.wikipedia.org/wiki/Alias_method): we sample `l` with probability `p[l]` by first - selecting `l` uniformly at random and then returning it with probability `keep[l] / 2**mu`; - otherwise returning `alt[l]`. - - Signature: - selection: The input/output register $|\ell\rangle$ of size lg(L) where the desired - coefficient state is prepared. - temp: Work space comprised of sub signature: - - sigma: A mu-sized register containing uniform probabilities for comparison against - `keep`. - - alt: A lg(L)-sized register of alternate indices - - keep: a mu-sized register of probabilities of keeping the initially sampled index. - - one bit for the result of the comparison. - - This gate corresponds to the following operations: - - UNIFORM_L on the selection register - - H^mu on the sigma register - - QROM addressed by the selection register into the alt and keep signature. - - LessThanEqualGate comparing the keep and sigma signature. - - Coherent swap between the selection register and alt register if the comparison - returns True. - - Total space will be (2 * log(L) + 2 mu + 1) work qubits + log(L) ancillas for QROM. - The 1 ancilla in work qubits is for the `LessThanEqualGate` followed by coherent swap. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.D. and Figure 11. - """ - - selection_registers: Tuple[infra.SelectionRegister, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.SelectionRegister) else tuple(v) - ) - alt: NDArray[np.int_] - keep: NDArray[np.int_] - mu: int - - @classmethod - def from_lcu_probs( - cls, lcu_probabilities: List[float], *, probability_epsilon: float = 1.0e-5 - ) -> 'StatePreparationAliasSampling': - """Factory to construct the state preparation gate for a given set of LCU coefficients. - - Args: - lcu_probabilities: The LCU coefficients. - probability_epsilon: The desired accuracy to represent each probability - (which sets mu size and keep/alt integers). - See `cirq_ft.linalg.lcu_util.preprocess_lcu_coefficients_for_reversible_sampling` - for more information. - """ - alt, keep, mu = linalg.preprocess_lcu_coefficients_for_reversible_sampling( - lcu_coefficients=lcu_probabilities, epsilon=probability_epsilon - ) - N = len(lcu_probabilities) - return StatePreparationAliasSampling( - selection_registers=infra.SelectionRegister('selection', (N - 1).bit_length(), N), - alt=np.array(alt), - keep=np.array(keep), - mu=mu, - ) - - @cached_property - def sigma_mu_bitsize(self) -> int: - return self.mu - - @cached_property - def alternates_bitsize(self) -> int: - return infra.total_bits(self.selection_registers) - - @cached_property - def keep_bitsize(self) -> int: - return self.mu - - @cached_property - def selection_bitsize(self) -> int: - return infra.total_bits(self.selection_registers) - - @cached_property - def junk_registers(self) -> Tuple[infra.Register, ...]: - return tuple( - infra.Signature.build( - sigma_mu=self.sigma_mu_bitsize, - alt=self.alternates_bitsize, - keep=self.keep_bitsize, - less_than_equal=1, - ) - ) - - def _value_equality_values_(self): - return ( - self.selection_registers, - tuple(self.alt.ravel()), - tuple(self.keep.ravel()), - self.mu, - ) - - def __repr__(self) -> str: - alt_repr = cirq._compat.proper_repr(self.alt) - keep_repr = cirq._compat.proper_repr(self.keep) - return ( - f'cirq_ft.StatePreparationAliasSampling(' - f'{self.selection_registers}, ' - f'{alt_repr}, ' - f'{keep_repr}, ' - f'{self.mu})' - ) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - selection, less_than_equal = quregs['selection'], quregs['less_than_equal'] - sigma_mu, alt, keep = quregs.get('sigma_mu', ()), quregs['alt'], quregs.get('keep', ()) - N = self.selection_registers[0].iteration_length - yield prepare_uniform_superposition.PrepareUniformSuperposition(N).on(*selection) - yield cirq.H.on_each(*sigma_mu) - qrom_gate = qrom.QROM( - [self.alt, self.keep], - (self.selection_bitsize,), - (self.alternates_bitsize, self.keep_bitsize), - ) - yield qrom_gate.on_registers(selection=selection, target0=alt, target1=keep) - yield arithmetic_gates.LessThanEqualGate(self.mu, self.mu).on( - *keep, *sigma_mu, *less_than_equal - ) - yield swap_network.MultiTargetCSwap.make_on( - control=less_than_equal, target_x=alt, target_y=selection - ) diff --git a/cirq-ft/cirq_ft/algos/state_preparation_test.py b/cirq-ft/cirq_ft/algos/state_preparation_test.py deleted file mode 100644 index dba8159de19..00000000000 --- a/cirq-ft/cirq_ft/algos/state_preparation_test.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.algos.generic_select_test import get_1d_Ising_lcu_coeffs -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "num_sites, epsilon", - [ - (2, 3e-3), - pytest.param(3, 3.0e-3, marks=pytest.mark.slow), - pytest.param(4, 5.0e-3, marks=pytest.mark.slow), - pytest.param(7, 8.0e-3, marks=pytest.mark.slow), - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_state_preparation_via_coherent_alias_sampling(num_sites, epsilon): - lcu_coefficients = get_1d_Ising_lcu_coeffs(num_sites) - gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - lcu_probabilities=lcu_coefficients.tolist(), probability_epsilon=epsilon - ) - g = cirq_ft.testing.GateHelper(gate) - qubit_order = g.operation.qubits - - # Assertion to ensure that simulating the `decomposed_circuit` doesn't run out of memory. - assert len(g.circuit.all_qubits()) < 20 - result = cirq.Simulator(dtype=np.complex128).simulate(g.circuit, qubit_order=qubit_order) - state_vector = result.final_state_vector - # State vector is of the form |l>|temp_{l}>. We trace out the |temp_{l}> part to - # get the coefficients corresponding to |l>. - L, logL = len(lcu_coefficients), len(g.quregs['selection']) - state_vector = state_vector.reshape(2**logL, len(state_vector) // 2**logL) - num_non_zero = (abs(state_vector) > 1e-6).sum(axis=1) - prepared_state = state_vector.sum(axis=1) - assert all(num_non_zero[:L] > 0) and all(num_non_zero[L:] == 0) - assert all(np.abs(prepared_state[:L]) > 1e-6) and all(np.abs(prepared_state[L:]) <= 1e-6) - prepared_state = prepared_state[:L] / np.sqrt(num_non_zero[:L]) - # Assert that the absolute square of prepared state (probabilities instead of amplitudes) is - # same as `lcu_coefficients` up to `epsilon`. - np.testing.assert_allclose(lcu_coefficients, abs(prepared_state) ** 2, atol=epsilon) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_state_preparation_via_coherent_alias_sampling_diagram(): - data = np.asarray(range(1, 5)) / np.sum(range(1, 5)) - gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - lcu_probabilities=data.tolist(), probability_epsilon=0.05 - ) - g = cirq_ft.testing.GateHelper(gate) - qubit_order = g.operation.qubits - - circuit = cirq.Circuit(cirq.decompose_once(g.operation)) - cirq.testing.assert_has_diagram( - circuit, - ''' -selection0: ────────UNIFORM(4)───In───────────────────×(y)─── - │ │ │ -selection1: ────────target───────In───────────────────×(y)─── - │ │ -sigma_mu0: ─────────H────────────┼────────In(y)───────┼────── - │ │ │ -sigma_mu1: ─────────H────────────┼────────In(y)───────┼────── - │ │ │ -sigma_mu2: ─────────H────────────┼────────In(y)───────┼────── - │ │ │ -alt0: ───────────────────────────QROM_0───┼───────────×(x)─── - │ │ │ -alt1: ───────────────────────────QROM_0───┼───────────×(x)─── - │ │ │ -keep0: ──────────────────────────QROM_1───In(x)───────┼────── - │ │ │ -keep1: ──────────────────────────QROM_1───In(x)───────┼────── - │ │ │ -keep2: ──────────────────────────QROM_1───In(x)───────┼────── - │ │ -less_than_equal: ─────────────────────────+(x <= y)───@────── -''', - qubit_order=qubit_order, - ) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('state_preparation') diff --git a/cirq-ft/cirq_ft/algos/swap_network.ipynb b/cirq-ft/cirq_ft/algos/swap_network.ipynb deleted file mode 100644 index cf87b7c640f..00000000000 --- a/cirq-ft/cirq_ft/algos/swap_network.ipynb +++ /dev/null @@ -1,176 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "aaebd62d", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "f31db18a", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Swap Network" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9354e0b6", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "f0727311", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwap.md" - }, - "source": [ - "## `MultiTargetCSwap`\n", - "Implements a multi-target controlled swap unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$.\n", - "\n", - "This decomposes into a qubitwise SWAP on the two target signature, and takes 14*n T-gates.\n", - "\n", - "#### References\n", - "[Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low et. al. 2018. See Appendix B.2.c.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "253b1d6a", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwap.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.MultiTargetCSwap(3)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - }, - { - "cell_type": "markdown", - "id": "4a5975fd", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwapApprox.md" - }, - "source": [ - "## `MultiTargetCSwapApprox`\n", - "Approximately implements a multi-target controlled swap unitary using only 4 * n T-gates.\n", - "\n", - "Implements the unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$ such that the output state is\n", - "correct up to a global phase factor of +1 / -1.\n", - "\n", - "This is useful when the incorrect phase can be absorbed in a garbage state of an algorithm; and\n", - "thus ignored, see the reference for more details.\n", - "\n", - "#### References\n", - "[Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low et. al. 2018. See Appendix B.2.c.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "972e5895", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwapApprox.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.MultiTargetCSwapApprox(2)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - }, - { - "cell_type": "markdown", - "id": "5fae3dd8", - "metadata": { - "cq.autogen": "_make_SwapWithZeroGate.md" - }, - "source": [ - "## `SwapWithZeroGate`\n", - "Swaps |Psi_0> with |Psi_x> if selection register stores index `x`.\n", - "\n", - "Implements the unitary U |x> |Psi_0> |Psi_1> ... |Psi_{n-1}> --> |x> |Psi_x> |Rest of Psi>.\n", - "Note that the state of `|Rest of Psi>` is allowed to be anything and should not be depended\n", - "upon.\n", - "\n", - "#### References\n", - "[Trading T-gates for dirty qubits in state preparation and unitary synthesis] (https://arxiv.org/abs/1812.00954). Low, Kliuchnikov, Schaeffer. 2018.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad3849f0", - "metadata": { - "cq.autogen": "_make_SwapWithZeroGate.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.SwapWithZeroGate(selection_bitsize=2, target_bitsize=3, n_target_registers=4)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/swap_network.py b/cirq-ft/cirq_ft/algos/swap_network.py deleted file mode 100644 index 62512de2486..00000000000 --- a/cirq-ft/cirq_ft/algos/swap_network.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -from typing import Sequence, Union, Tuple -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import multi_control_multi_target_pauli as mcmtp - - -@attr.frozen -class MultiTargetCSwap(infra.GateWithRegisters): - """Implements a multi-target controlled swap unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$. - - This decomposes into a qubitwise SWAP on the two target signature, and takes 14*n T-gates. - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low et. al. 2018. See Appendix B.2.c. - """ - - bitsize: int - - @classmethod - def make_on( - cls, **quregs: Union[Sequence[cirq.Qid], NDArray[cirq.Qid]] # type: ignore[type-var] - ) -> cirq.Operation: - """Helper constructor to automatically deduce bitsize attributes.""" - return cls(bitsize=len(quregs['target_x'])).on_registers(**quregs) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(control=1, target_x=self.bitsize, target_y=self.bitsize) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - control, target_x, target_y = quregs['control'], quregs['target_x'], quregs['target_y'] - yield [cirq.CSWAP(*control, t_x, t_y) for t_x, t_y in zip(target_x, target_y)] - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - if not args.use_unicode_characters: - return cirq.CircuitDiagramInfo( - ("@",) + ("swap_x",) * self.bitsize + ("swap_y",) * self.bitsize - ) - return cirq.CircuitDiagramInfo(("@",) + ("×(x)",) * self.bitsize + ("×(y)",) * self.bitsize) - - def __repr__(self) -> str: - return f"cirq_ft.MultiTargetCSwap({self.bitsize})" - - def _t_complexity_(self) -> infra.TComplexity: - return infra.TComplexity(t=7 * self.bitsize, clifford=10 * self.bitsize) - - -@attr.frozen -class MultiTargetCSwapApprox(MultiTargetCSwap): - """Approximately implements a multi-target controlled swap unitary using only 4 * n T-gates. - - Implements the unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$ such that the output state is - correct up to a global phase factor of +1 / -1. - - This is useful when the incorrect phase can be absorbed in a garbage state of an algorithm; and - thus ignored, see the reference for more details. - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low et. al. 2018. See Appendix B.2.c. - """ - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - control, target_x, target_y = quregs['control'], quregs['target_x'], quregs['target_y'] - - def g(q: cirq.Qid, adjoint=False) -> cirq.ops.op_tree.OpTree: - yield [cirq.S(q), cirq.H(q)] - yield cirq.T(q) ** (1 - 2 * adjoint) - yield [cirq.H(q), cirq.S(q) ** -1] - - cnot_x_to_y = [cirq.CNOT(x, y) for x, y in zip(target_x, target_y)] - cnot_y_to_x = [cirq.CNOT(y, x) for x, y in zip(target_x, target_y)] - g_inv_on_y = [list(g(q, True)) for q in target_y] # Uses len(target_y) T-gates - g_on_y = [list(g(q)) for q in target_y] # Uses len(target_y) T-gates - - yield [cnot_y_to_x, g_inv_on_y, cnot_x_to_y, g_inv_on_y] - yield mcmtp.MultiTargetCNOT(len(target_y)).on(*control, *target_y) - yield [g_on_y, cnot_x_to_y, g_on_y, cnot_y_to_x] - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - if not args.use_unicode_characters: - return cirq.CircuitDiagramInfo( - ("@(approx)",) + ("swap_x",) * self.bitsize + ("swap_y",) * self.bitsize - ) - return cirq.CircuitDiagramInfo( - ("@(approx)",) + ("×(x)",) * self.bitsize + ("×(y)",) * self.bitsize - ) - - def __repr__(self) -> str: - return f"cirq_ft.MultiTargetCSwapApprox({self.bitsize})" - - def _t_complexity_(self) -> infra.TComplexity: - """TComplexity as explained in Appendix B.2.c of https://arxiv.org/abs/1812.00954""" - n = self.bitsize - # 4 * n: G gates, each wth 1 T and 4 cliffords - # 4 * n: CNOTs - # 2 * n - 1: CNOTs from 1 MultiTargetCNOT - return infra.TComplexity(t=4 * n, clifford=22 * n - 1) - - -@attr.frozen -class SwapWithZeroGate(infra.GateWithRegisters): - """Swaps |Psi_0> with |Psi_x> if selection register stores index `x`. - - Implements the unitary U |x> |Psi_0> |Psi_1> ... |Psi_{n-1}> --> |x> |Psi_x> |Rest of Psi>. - Note that the state of `|Rest of Psi>` is allowed to be anything and should not be depended - upon. - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low, Kliuchnikov, Schaeffer. 2018. - """ - - selection_bitsize: int - target_bitsize: int - n_target_registers: int - - def __attrs_post_init__(self): - assert self.n_target_registers <= 2**self.selection_bitsize - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister('selection', self.selection_bitsize, self.n_target_registers), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return ( - infra.Register('target', bitsize=self.target_bitsize, shape=self.n_target_registers), - ) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.target_registers]) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - selection, target = quregs['selection'], quregs['target'] - assert target.shape == (self.n_target_registers, self.target_bitsize) - cswap_n = MultiTargetCSwapApprox(self.target_bitsize) - # Imagine a complete binary tree of depth `logN` with `N` leaves, each denoting a target - # register. If the selection register stores index `r`, we want to bring the value stored - # in leaf indexed `r` to the leaf indexed `0`. At each node of the binary tree, the left - # subtree contains node with current bit 0 and right subtree contains nodes with current - # bit 1. Thus, leaf indexed `0` is the leftmost node in the tree. - # Start iterating from the root of the tree. If the j'th bit is set in the selection - # register (i.e. the control would be activated); we know that the value we are searching - # for is in the right subtree. In order to (eventually) bring the desired value to node - # 0; we swap all values in the right subtree with all values in the left subtree. This - # takes (N / (2 ** (j + 1)) swaps at level `j`. - # Therefore, in total, we need $\sum_{j=0}^{logN-1} \frac{N}{2 ^ {j + 1}}$ controlled swaps. - for j in range(len(selection)): - for i in range(0, self.n_target_registers - 2**j, 2 ** (j + 1)): - # The inner loop is executed at-most `N - 1` times, where `N:= len(target_regs)`. - yield cswap_n.on_registers( - control=selection[len(selection) - j - 1], - target_x=target[i], - target_y=target[i + 2**j], - ) - - def __repr__(self) -> str: - return ( - "cirq_ft.SwapWithZeroGate(" - f"{self.selection_bitsize}," - f"{self.target_bitsize}," - f"{self.n_target_registers}" - f")" - ) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@(r⇋0)"] * self.selection_bitsize - for i in range(self.n_target_registers): - wire_symbols += [f"swap_{i}"] * self.target_bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/algos/swap_network_test.py b/cirq-ft/cirq_ft/algos/swap_network_test.py deleted file mode 100644 index 8b388c9dde8..00000000000 --- a/cirq-ft/cirq_ft/algos/swap_network_test.py +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import random - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - -random.seed(12345) - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize, n_target_registers", - [[3, 5, 1], [2, 2, 3], [2, 3, 4], [3, 2, 5], [4, 1, 10]], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_swap_with_zero_gate(selection_bitsize, target_bitsize, n_target_registers): - # Construct the gate. - gate = cirq_ft.SwapWithZeroGate(selection_bitsize, target_bitsize, n_target_registers) - # Allocate selection and target qubits. - all_qubits = cirq.LineQubit.range(cirq.num_qubits(gate)) - selection = all_qubits[:selection_bitsize] - target = np.array(all_qubits[selection_bitsize:]).reshape((n_target_registers, target_bitsize)) - # Create a circuit. - circuit = cirq.Circuit(gate.on_registers(selection=selection, target=target)) - - # Load data[i] in i'th target register; where each register is of size target_bitsize - data = [random.randint(0, 2**target_bitsize - 1) for _ in range(n_target_registers)] - target_state = [int(x) for d in data for x in format(d, f"0{target_bitsize}b")] - - sim = cirq.Simulator(dtype=np.complex128) - expected_state_vector = np.zeros(2**target_bitsize) - # Iterate on every selection integer. - for selection_integer in range(len(data)): - # Load `selection_integer` in the selection register and construct initial state. - selection_state = [int(x) for x in format(selection_integer, f"0{selection_bitsize}b")] - initial_state = selection_state + target_state - # Simulate the circuit with the initial state. - result = sim.simulate(circuit, initial_state=initial_state) - # Get the sub_state_vector corresponding to qubit register `target[0]`. - result_state_vector = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=list(range(selection_bitsize, selection_bitsize + target_bitsize)), - ) - # Expected state vector should correspond to data[selection_integer] due to the swap. - expected_state_vector[data[selection_integer]] = 1 - # Assert that result and expected state vectors are equal; reset and continue. - assert cirq.equal_up_to_global_phase(result_state_vector, expected_state_vector) - expected_state_vector[data[selection_integer]] = 0 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_swap_with_zero_gate_diagram(): - gate = cirq_ft.SwapWithZeroGate(3, 2, 4) - q = cirq.LineQubit.range(cirq.num_qubits(gate)) - circuit = cirq.Circuit(gate.on_registers(**infra.split_qubits(gate.signature, q))) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ────@(r⇋0)─── - │ -1: ────@(r⇋0)─── - │ -2: ────@(r⇋0)─── - │ -3: ────swap_0─── - │ -4: ────swap_0─── - │ -5: ────swap_1─── - │ -6: ────swap_1─── - │ -7: ────swap_2─── - │ -8: ────swap_2─── - │ -9: ────swap_3─── - │ -10: ───swap_3─── -""", - ) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - - -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_target_cswap(): - qubits = cirq.LineQubit.range(5) - c, q_x, q_y = qubits[0], qubits[1:3], qubits[3:] - cswap = cirq_ft.MultiTargetCSwap(2).on_registers(control=c, target_x=q_x, target_y=q_y) - cswap_approx = cirq_ft.MultiTargetCSwapApprox(2).on_registers( - control=c, target_x=q_x, target_y=q_y - ) - setup_code = "import cirq\nimport cirq_ft" - cirq.testing.assert_implements_consistent_protocols(cswap, setup_code=setup_code) - cirq.testing.assert_implements_consistent_protocols(cswap_approx, setup_code=setup_code) - circuit = cirq.Circuit(cswap, cswap_approx) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───@──────@(approx)─── - │ │ -1: ───×(x)───×(x)──────── - │ │ -2: ───×(x)───×(x)──────── - │ │ -3: ───×(y)───×(y)──────── - │ │ -4: ───×(y)───×(y)──────── - """, - ) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ---@--------@(approx)--- - | | -1: ---swap_x---swap_x------ - | | -2: ---swap_x---swap_x------ - | | -3: ---swap_y---swap_y------ - | | -4: ---swap_y---swap_y------ - """, - use_unicode_characters=False, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_target_cswap_make_on(): - qubits = cirq.LineQubit.range(5) - c, q_x, q_y = qubits[:1], qubits[1:3], qubits[3:] - cswap1 = cirq_ft.MultiTargetCSwap(2).on_registers(control=c, target_x=q_x, target_y=q_y) - cswap2 = cirq_ft.MultiTargetCSwap.make_on(control=c, target_x=q_x, target_y=q_y) - assert cswap1 == cswap2 - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('swap_network') - - -@pytest.mark.parametrize("n", [*range(1, 6)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(n): - g = cirq_ft.MultiTargetCSwap(n) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - - g = cirq_ft.MultiTargetCSwapApprox(n) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize, n_target_registers, want", - [ - [3, 5, 1, (0, 0)], - [2, 2, 3, (16, 86)], - [2, 3, 4, (36, 195)], - [3, 2, 5, (32, 172)], - [4, 1, 10, (36, 189)], - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_swap_with_zero_t_complexity(selection_bitsize, target_bitsize, n_target_registers, want): - t_complexity = cirq_ft.TComplexity(t=want[0], clifford=want[1]) - gate = cirq_ft.SwapWithZeroGate(selection_bitsize, target_bitsize, n_target_registers) - assert t_complexity == cirq_ft.t_complexity(gate) diff --git a/cirq-ft/cirq_ft/algos/unary_iteration.ipynb b/cirq-ft/cirq_ft/algos/unary_iteration.ipynb deleted file mode 100644 index aa3bda1e117..00000000000 --- a/cirq-ft/cirq_ft/algos/unary_iteration.ipynb +++ /dev/null @@ -1,575 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e2fa907b", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "49b5e1e6", - "metadata": {}, - "source": [ - "# Unary Iteration" - ] - }, - { - "cell_type": "markdown", - "id": "fcdb39f2", - "metadata": {}, - "source": [ - "Given an array of potential operations, for example:\n", - "\n", - " ops = [X(i) for i in range(5)]\n", - " \n", - "we would like to select an operation to apply:\n", - "\n", - " n = 4 --> apply ops[4]\n", - " \n", - "If $n$ is a quantum integer, we need to apply the transformation\n", - "\n", - "$$\n", - " |n \\rangle |\\psi\\rangle \\rightarrow |n\\rangle \\, \\mathrm{ops}_n \\cdot |\\psi\\rangle\n", - "$$\n", - "\n", - "The simplest conceptual way to do this is to use a \"total control\" quantum circuit where you introduce a multi-controlled operation for each of the `len(ops)` possible `n` values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0148f529", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "from cirq.contrib.svg import SVGCircuit\n", - "import numpy as np\n", - "from typing import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32e90969", - "metadata": {}, - "outputs": [], - "source": [ - "import operator\n", - "import cirq._compat\n", - "import itertools" - ] - }, - { - "cell_type": "markdown", - "id": "a6d947da", - "metadata": {}, - "source": [ - "## Total Control\n", - "\n", - "Here, we'll use Sympy's boolean logic to show how total control works. We perform an `And( ... )` for each possible bit pattern. We use an `Xnor` on each selection bit to toggle whether it's a positive or negative control (filled or open circle in quantum circuit diagrams).\n", - "\n", - "In this example, we indeed consider $X_n$ as our potential operations and toggle bits in the `target` register according to the total control." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8e61bf03", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "import sympy as S\n", - "import sympy.logic.boolalg as slb\n", - "\n", - "def total_control(selection, target):\n", - " \"\"\"Toggle bits in `target` depending on `selection`.\"\"\"\n", - " print(f\"Selection is {selection}\")\n", - " \n", - " for n, trial in enumerate(itertools.product((0, 1), repeat=len(selection))):\n", - " print(f\"Step {n}, apply total control: {trial}\")\n", - " target[n] ^= slb.And(*[slb.Xnor(s, t) for s, t in zip(selection, trial)])\n", - " \n", - " if target[n] == S.true:\n", - " print(f\" -> At this stage, {n}= and our output bit is set\")\n", - "\n", - " \n", - "selection = [0, 0, 0]\n", - "target = [False]*8\n", - "total_control(selection, target) \n", - "print()\n", - "print(\"Target:\")\n", - "print(target)" - ] - }, - { - "cell_type": "markdown", - "id": "e572a31d", - "metadata": {}, - "source": [ - "Note that our target register shows we have indeed applied $X_\\mathrm{0b010}$. Try changing `selection` to other bit patterns and notice how it changes." - ] - }, - { - "cell_type": "markdown", - "id": "a4a75f61", - "metadata": {}, - "source": [ - "Of course, we don't know what state the selection register will be in. We can use sympy's support for symbolic boolean logic to verify our gadget for all possible selection inputs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5df67d45", - "metadata": {}, - "outputs": [], - "source": [ - "selection = [S.Symbol(f's{i}') for i in range(3)]\n", - "target = [S.false for i in range(2**len(selection)) ]\n", - "total_control(selection, target)\n", - "\n", - "print()\n", - "print(\"Target:\")\n", - "for n, t in enumerate(target):\n", - " print(f'{n}= {t}')\n", - " \n", - "tc_target = target.copy()" - ] - }, - { - "cell_type": "markdown", - "id": "deab0553", - "metadata": {}, - "source": [ - "As expected, the \"not pattern\" (where `~` is boolean not) matches the binary representations of `n`." - ] - }, - { - "cell_type": "markdown", - "id": "81b69e70", - "metadata": {}, - "source": [ - "## Unary Iteration with segment trees\n", - "\n", - "A [segment tree](https://en.wikipedia.org/wiki/Segment_tree) is a data structure that allows logrithmic-time querying of intervals. We use a segment tree where each interval is length 1 and comprises all the `n` integers we may select.\n", - "\n", - "It is defined recursively by dividing the input interval into two half-size intervals until the left limit meets the right limit." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ab998aa4", - "metadata": {}, - "outputs": [], - "source": [ - "def segtree(ctrl, selection, target, depth, left, right):\n", - " \"\"\"Toggle bits in `target` depending on `selection` using a recursive segment tree.\"\"\"\n", - " print(f'depth={depth} left={left} right={right}', end=' ')\n", - " \n", - " if left == (right - 1):\n", - " # Leaf of the recusion.\n", - " print(f'n={n} ctrl={ctrl}')\n", - " target[left] ^= ctrl\n", - " return \n", - " print()\n", - " \n", - " assert depth < len(selection)\n", - " mid = (left + right) >> 1\n", - " \n", - " # Recurse left interval\n", - " new_ctrl = slb.And(ctrl, slb.Not(selection[depth]))\n", - " segtree(ctrl=new_ctrl, selection=selection, target=target, depth=depth+1, left=left, right=mid)\n", - " \n", - " # Recurse right interval\n", - " new_ctrl = slb.And(ctrl, selection[depth])\n", - " segtree(ctrl=new_ctrl, selection=selection, target=target, depth=depth+1, left=mid, right=right)\n", - " \n", - " # Quantum note:\n", - " # instead of throwing away the first value of `new_ctrl` and re-anding\n", - " # with selection, we can just invert the first one (but only if `ctrl` is active)\n", - " # new_ctrl ^= ctrl" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6a514ee6", - "metadata": {}, - "outputs": [], - "source": [ - "selection = [S.Symbol(f's{i}') for i in range(3)]\n", - "target = [S.false for i in range(2**len(selection)) ]\n", - "segtree(S.true, selection, target, 0, 0, 2**len(selection))\n", - "\n", - "print()\n", - "print(\"Target:\")\n", - "for n, t in enumerate(target):\n", - " print(f'n={n} {slb.simplify_logic(t)}')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "23d91438", - "metadata": {}, - "outputs": [], - "source": [ - "print(f\"{'n':3s} | {'segtree':18s} | {'total control':18s} | same?\")\n", - "for n, (t1, t2) in enumerate(zip(target, tc_target)):\n", - " t1 = slb.simplify_logic(t1)\n", - " print(f'{n:3d} | {str(t1):18s} | {str(t2):18s} | {str(t1==t2)}')" - ] - }, - { - "cell_type": "markdown", - "id": "e39448e6", - "metadata": {}, - "source": [ - "## Quantum Circuit\n", - "\n", - "We can translate the boolean logic to reversible, quantum logic. It is instructive to start from the suboptimal total control quantum circuit for comparison purposes. We can build this as in the sympy boolean-logic case by adding controlled X operations to the target signature, with the controls on the selection signature toggled on or off according to the binary representation of the selection index.\n", - "\n", - "Let us first build a GateWithRegisters object to implement the circuit" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6b37d717", - "metadata": {}, - "outputs": [], - "source": [ - "from functools import cached_property\n", - "import cirq\n", - "from cirq_ft import Signature, GateWithRegisters\n", - "from cirq_ft.infra.bit_tools import iter_bits\n", - "\n", - "class TotallyControlledNot(GateWithRegisters):\n", - " \n", - " def __init__(self, selection_bitsize: int, target_bitsize: int, control_bitsize: int = 1):\n", - " self._selection_bitsize = selection_bitsize\n", - " self._target_bitsize = target_bitsize\n", - " self._control_bitsize = control_bitsize\n", - "\n", - " @cached_property\n", - " def signature(self) -> Signature:\n", - " return Signature(\n", - " [\n", - " *Signature.build(control=self._control_bitsize),\n", - " *Signature.build(selection=self._selection_bitsize),\n", - " *Signature.build(target=self._target_bitsize)\n", - " ]\n", - " )\n", - "\n", - " def decompose_from_registers(self, **qubit_regs: Sequence[cirq.Qid]) -> cirq.OP_TREE:\n", - " num_controls = self._control_bitsize + self._selection_bitsize\n", - " for target_bit in range(self._target_bitsize):\n", - " bit_pattern = iter_bits(target_bit, self._selection_bitsize)\n", - " control_values = [1]*self._control_bitsize + list(bit_pattern)\n", - " yield cirq.X.controlled(\n", - " num_controls=num_controls,\n", - " control_values=control_values\n", - " ).on(\n", - " *qubit_regs[\"control\"], \n", - " *qubit_regs[\"selection\"],\n", - " qubit_regs[\"target\"][-(target_bit+1)])\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1f7b6758", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq_ft.infra.testing as cq_testing\n", - "tc_not = TotallyControlledNot(3, 5)\n", - "tc = cq_testing.GateHelper(tc_not)\n", - "cirq.Circuit((cirq.decompose_once(tc.operation)))\n", - "SVGCircuit(cirq.Circuit(cirq.decompose_once(tc.operation)))" - ] - }, - { - "cell_type": "markdown", - "id": "7b28663a", - "metadata": {}, - "source": [ - "## Tests for Correctness\n", - "\n", - "We can use a full statevector simulation to compare the desired statevector to the one generated by the unary iteration circuit for each basis state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "574c5058", - "metadata": {}, - "outputs": [], - "source": [ - "selection_bitsize = 3\n", - "target_bitsize = 5\n", - "for n in range(target_bitsize):\n", - " # Initial qubit values\n", - " qubit_vals = {q: 0 for q in tc.all_qubits}\n", - " # All controls 'on' to activate circuit\n", - " qubit_vals.update({c: 1 for c in tc.quregs['control']})\n", - " # Set selection according to `n`\n", - " qubit_vals.update(zip(tc.quregs['selection'], iter_bits(n, selection_bitsize)))\n", - "\n", - " initial_state = [qubit_vals[x] for x in tc.all_qubits]\n", - " final_state = [qubit_vals[x] for x in tc.all_qubits]\n", - " final_state[-(n+1)] = 1\n", - " cq_testing.assert_circuit_inp_out_cirqsim(\n", - " tc.circuit, tc.all_qubits, initial_state, final_state\n", - " )\n", - " print(f'n={n} checked!')" - ] - }, - { - "cell_type": "markdown", - "id": "d76fcf8f", - "metadata": {}, - "source": [ - "## Towards a segment tree \n", - "\n", - "Next let's see how we can reduce the circuit to the observe the tree structure.\n", - "First let's recall what we are trying to do with the controlled not. Given a\n", - "selection integer (say 3 = 011), we want to toggle the bit in the target\n", - "register to on if the qubit 1 and 2 are set to on in the selection register." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3aca2666", - "metadata": {}, - "outputs": [], - "source": [ - "# The selection bits [1-3] are set according to binary representation of the number 3 (011)\n", - "initial_state = [1, 0, 1, 1, 0, 0, 0, 0, 0]\n", - "final_state = [1, 0, 1, 1, 0, 1, 0, 0, 0]\n", - "actual, should_be = cq_testing.get_circuit_inp_out_cirqsim(\n", - " tc.circuit, tc.all_qubits, initial_state, final_state\n", - " )\n", - "print(\"simulated: \", actual)\n", - "print(\"expected : \", should_be)\n" - ] - }, - { - "cell_type": "markdown", - "id": "4640eeed", - "metadata": {}, - "source": [ - "Now what is important to note is that we can remove many repeated controlled operations by using ancilla qubits to flag what part of the circuit we need to apply, this works because we know the bit pattern of nearby integers is very similar. \n", - "\n", - "A circuit demonstrating this for our example is given below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef853ae7", - "metadata": {}, - "outputs": [], - "source": [ - "from cirq_ft.algos.and_gate import And\n", - "\n", - "selection_bitsize = 2\n", - "target_bitsize = 4\n", - "qubits = cirq.LineQubit(0).range(1 + selection_bitsize * 2 + target_bitsize)\n", - "circuit = cirq.Circuit()\n", - "circuit.append(\n", - " [\n", - " And((1, 0)).on(qubits[0], qubits[1], qubits[2]),\n", - " And((1, 0)).on(qubits[2], qubits[3], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[8]),\n", - " cirq.CNOT(qubits[2], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[7]),\n", - " And(adjoint=True).on(qubits[2], qubits[3], qubits[4]),\n", - " cirq.CNOT(qubits[0], qubits[2]),\n", - " And((1, 0)).on(qubits[2], qubits[3], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[6]),\n", - " cirq.CNOT(qubits[2], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[5]),\n", - " And(adjoint=True).on(qubits[2], qubits[3], qubits[4]),\n", - " And(adjoint=True).on(qubits[0], qubits[1], qubits[2]),\n", - " ]\n", - ")\n", - "\n", - "SVGCircuit(circuit)" - ] - }, - { - "cell_type": "markdown", - "id": "b9d45d52", - "metadata": {}, - "source": [ - "Reading from left to right we first check the control is set to on and the selection qubit is off, if both these conditions are met then the ancilla qubit is now set to 1. The next control checks if the previous condition was met and likewise the second selection index is also off. At this point if both these conditions are met we must be indexing 0 as the first two qubits are set to off (00), otherwise we know that we want to apply X to qubit 1 so we perform a CNOT operation to flip the bit value in the second ancilla qubit, before returning back up the circuit. Now if the left half of the circuit was not applied (i.e. the first selection register was set to 1) then the CNOT between the control qubit and the first ancilla qubit causes the ancilla qubit to toggle on. This triggers the right side of the circuit, which now performs the previously described operations to figure out if the lowest bit is set. Combining these two then yields the expected controlled X operation. \n", - "\n", - "Below we check the circuit is giving the expected behaviour." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "83d1287d", - "metadata": {}, - "outputs": [], - "source": [ - "initial_state = [1, 0, 0, 0, 0, 0, 0, 0, 0]\n", - "target_indx = 3\n", - "sel_bits = list(iter_bits(target_indx, selection_bitsize))\n", - "sel_indices = [i for i in range(1, 2*selection_bitsize+1, 2)]\n", - "initial_state[sel_indices[0]] = sel_bits[0]\n", - "initial_state[sel_indices[1]] = sel_bits[1]\n", - "result = cirq.Simulator(dtype=np.complex128).simulate(\n", - " circuit, initial_state=initial_state\n", - ")\n", - "actual = result.dirac_notation(decimals=2)[1:-1]\n", - "print(\"simulated: {}, index set in string {}\".format(actual, len(qubits)-1-target_indx))" - ] - }, - { - "cell_type": "markdown", - "id": "a86e0d42", - "metadata": {}, - "source": [ - "Extending the above idea to larger ranges of integers is relatively straightforward. For example consider the next simplest case of $L=8 = 2^3$. The circuit above takes care of the last two bits and can be duplicated. For the extra bit we just need to add a additional `AND` operations, and a CNOT to switch between the original range `[0,3]` or the new range `[4,7]` depending on whether the new selection register is off or on respectively. This procedure can be repeated and we can begin to notice the recursive tree-like structure. \n", - "\n", - "This structure is just the segtree described previously for boolean logic and this gives is the basic idea of unary iteration, \n", - "which uses `L-1` `AND` operations. Below the `ApplyXToLthQubit` builds the controlled Not operation using the `UnaryIterationGate` as a base class which defines the `decompose_from_registers` method appropriately to recursively construct the unary iteration circuit.\n", - "\n", - "Note below a different ordering of ancilla and selection qubits is taken to what was used in the simpler `L=4` example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9cba52b1", - "metadata": {}, - "outputs": [], - "source": [ - "from functools import cached_property\n", - "from cirq_ft import Register, SelectionRegister, UnaryIterationGate\n", - "\n", - "class ApplyXToLthQubit(UnaryIterationGate):\n", - " def __init__(self, selection_bitsize: int, target_bitsize: int, control_bitsize: int = 1):\n", - " self._selection_bitsize = selection_bitsize\n", - " self._target_bitsize = target_bitsize\n", - " self._control_bitsize = control_bitsize\n", - "\n", - " @cached_property\n", - " def control_registers(self) -> Tuple[Register, ...]:\n", - " return Register('control', self._control_bitsize),\n", - "\n", - " @cached_property\n", - " def selection_registers(self) -> Tuple[SelectionRegister, ...]:\n", - " return SelectionRegister('selection', self._selection_bitsize, self._target_bitsize),\n", - "\n", - " @cached_property\n", - " def target_registers(self) -> Tuple[Register, ...]:\n", - " return Register('target', self._target_bitsize),\n", - "\n", - " def nth_operation(\n", - " self, context, control: cirq.Qid, selection: int, target: Sequence[cirq.Qid]\n", - " ) -> cirq.OP_TREE:\n", - " return cirq.CNOT(control, target[-(selection + 1)])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1e4bafa", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq_ft.infra.testing as cq_testing\n", - "selection_bitsize = 3\n", - "target_bitsize = 5\n", - "\n", - "g = cq_testing.GateHelper(\n", - " ApplyXToLthQubit(selection_bitsize, target_bitsize))\n", - "SVGCircuit(cirq.Circuit(cirq.decompose_once(g.operation)))" - ] - }, - { - "cell_type": "markdown", - "id": "13773620", - "metadata": {}, - "source": [ - "## Tests for Correctness\n", - "\n", - "We can use a full statevector simulation to check again that the optimized circuit produces the expected result." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32ae469b", - "metadata": {}, - "outputs": [], - "source": [ - "from cirq_ft.infra.bit_tools import iter_bits\n", - "\n", - "for n in range(target_bitsize):\n", - " # Initial qubit values\n", - " qubit_vals = {q: 0 for q in g.all_qubits}\n", - " # All controls 'on' to activate circuit\n", - " qubit_vals.update({c: 1 for c in g.quregs['control']})\n", - " # Set selection according to `n`\n", - " qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize)))\n", - "\n", - " initial_state = [qubit_vals[x] for x in g.all_qubits]\n", - " qubit_vals[g.quregs['target'][-(n + 1)]] = 1\n", - " final_state = [qubit_vals[x] for x in g.all_qubits]\n", - " cq_testing.assert_circuit_inp_out_cirqsim(\n", - " g.decomposed_circuit, g.all_qubits, initial_state, final_state\n", - " )\n", - " print(f'n={n} checked!')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/unary_iteration_gate.py b/cirq-ft/cirq_ft/algos/unary_iteration_gate.py deleted file mode 100644 index 6cd5d3413cb..00000000000 --- a/cirq-ft/cirq_ft/algos/unary_iteration_gate.py +++ /dev/null @@ -1,448 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import abc -from functools import cached_property -from typing import Callable, Dict, Iterator, List, Sequence, Tuple -from numpy.typing import NDArray - -import cirq -import numpy as np - -from cirq_ft import infra -from cirq_ft.algos import and_gate -from cirq_ft.deprecation import deprecated_cirq_ft_function - - -def _unary_iteration_segtree( - ops: List[cirq.Operation], - control: cirq.Qid, - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - sl: int, - l: int, - r: int, - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - """Constructs a unary iteration circuit by iterating over nodes of an implicit Segment Tree. - - Args: - ops: Operations accumulated so far while traversing the implicit segment tree. The - accumulated ops are yielded and cleared when we reach a leaf node. - control: The control qubit that controls the execution of the entire unary iteration - circuit represented by the current node of the segment tree. - selection: Sequence of selection qubits. The i'th qubit in the list corresponds to the i'th - level in the segment tree.Thus, a total of O(logN) selection qubits are required for a - tree on range `N = (r_iter - l_iter)`. - ancilla: Pre-allocated ancilla qubits to be used for constructing the unary iteration - circuit. - sl: Current depth of the tree. `selection[sl]` gives the selection qubit corresponding to - the current depth. - l: Left index of the range represented by current node of the segment tree. - r: Right index of the range represented by current node of the segment tree. - l_iter: Left index of iteration range over which the segment tree should be constructed. - r_iter: Right index of iteration range over which the segment tree should be constructed. - break_early: For each internal node of the segment tree, `break_early(l, r)` is called to - evaluate whether the unary iteration should terminate early and not recurse in the - subtree of the node representing range `[l, r)`. If True, the internal node is - considered equivalent to a leaf node and the method yields only one tuple - `(OP_TREE, control_qubit, l)` for all integers in the range `[l, r)`. - - Yields: - One `Tuple[cirq.OP_TREE, cirq.Qid, int]` for each leaf node in the segment tree. The i'th - yielded element corresponds to the i'th leaf node which represents the `l_iter + i`'th - integer. The tuple corresponds to: - - cirq.OP_TREE: Operations to be inserted in the circuit in between the last leaf node - (or beginning of iteration) to the current leaf node. - - cirq.Qid: The control qubit which can be controlled upon to execute the $U_{l}$ on a - target register when the selection register stores integer $l$. - - int: Integer $l$ which would be stored in the selection register if the control qubit - is set. - """ - if l >= r_iter or l_iter >= r: - # Range corresponding to this node is completely outside of iteration range. - return - if l_iter <= l < r <= r_iter and (l == (r - 1) or break_early(l, r)): - # Reached a leaf node or a "special" internal node; yield the operations. - yield tuple(ops), control, l - ops.clear() - return - assert sl < len(selection) - m = (l + r) >> 1 - if r_iter <= m: - # Yield only left sub-tree. - yield from _unary_iteration_segtree( - ops, control, selection, ancilla, sl + 1, l, m, l_iter, r_iter, break_early - ) - return - if l_iter >= m: - # Yield only right sub-tree - yield from _unary_iteration_segtree( - ops, control, selection, ancilla, sl + 1, m, r, l_iter, r_iter, break_early - ) - return - anc, sq = ancilla[sl], selection[sl] - ops.append(and_gate.And((1, 0)).on(control, sq, anc)) - yield from _unary_iteration_segtree( - ops, anc, selection, ancilla, sl + 1, l, m, l_iter, r_iter, break_early - ) - ops.append(cirq.CNOT(control, anc)) - yield from _unary_iteration_segtree( - ops, anc, selection, ancilla, sl + 1, m, r, l_iter, r_iter, break_early - ) - ops.append(and_gate.And(adjoint=True).on(control, sq, anc)) - - -def _unary_iteration_zero_control( - ops: List[cirq.Operation], - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - sl, l, r = 0, 0, 2 ** len(selection) - m = (l + r) >> 1 - ops.append(cirq.X(selection[0])) - yield from _unary_iteration_segtree( - ops, selection[0], selection[1:], ancilla, sl, l, m, l_iter, r_iter, break_early - ) - ops.append(cirq.X(selection[0])) - yield from _unary_iteration_segtree( - ops, selection[0], selection[1:], ancilla, sl, m, r, l_iter, r_iter, break_early - ) - - -def _unary_iteration_single_control( - ops: List[cirq.Operation], - control: cirq.Qid, - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - sl, l, r = 0, 0, 2 ** len(selection) - yield from _unary_iteration_segtree( - ops, control, selection, ancilla, sl, l, r, l_iter, r_iter, break_early - ) - - -def _unary_iteration_multi_controls( - ops: List[cirq.Operation], - controls: Sequence[cirq.Qid], - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - num_controls = len(controls) - and_ancilla = ancilla[: num_controls - 2] - and_target = ancilla[num_controls - 2] - multi_controlled_and = and_gate.And((1,) * len(controls)).on_registers( - ctrl=np.array(controls).reshape(len(controls), 1), - junk=np.array(and_ancilla).reshape(len(and_ancilla), 1), - target=and_target, - ) - ops.append(multi_controlled_and) - yield from _unary_iteration_single_control( - ops, and_target, selection, ancilla[num_controls - 1 :], l_iter, r_iter, break_early - ) - ops.append(cirq.inverse(multi_controlled_and)) - - -@deprecated_cirq_ft_function() -def unary_iteration( - l_iter: int, - r_iter: int, - flanking_ops: List[cirq.Operation], - controls: Sequence[cirq.Qid], - selection: Sequence[cirq.Qid], - qubit_manager: cirq.QubitManager, - break_early: Callable[[int, int], bool] = lambda l, r: False, -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - """The method performs unary iteration on `selection` integer in `range(l_iter, r_iter)`. - - Unary iteration is a coherent for loop that can be used to conditionally perform a different - operation on a target register for every integer in the `range(l_iter, r_iter)` stored in the - selection register. - - Users can write multi-dimensional coherent for loops as follows: - - >>> import cirq - >>> from cirq_ft import unary_iteration - >>> N, M = 5, 7 - >>> target = [[cirq.q(f't({i}, {j})') for j in range(M)] for i in range(N)] - >>> selection = [[cirq.q(f's({i}, {j})') for j in range(3)] for i in range(3)] - >>> circuit = cirq.Circuit() - >>> i_ops = [] - >>> qm = cirq.GreedyQubitManager("ancilla", maximize_reuse=True) - >>> for i_optree, i_ctrl, i in unary_iteration(0, N, i_ops, [], selection[0], qm): - ... circuit.append(i_optree) - ... j_ops = [] - ... for j_optree, j_ctrl, j in unary_iteration(0, M, j_ops, [i_ctrl], selection[1], qm): - ... circuit.append(j_optree) - ... # Conditionally perform operations on target register using `j_ctrl`, `i` & `j`. - ... circuit.append(cirq.CNOT(j_ctrl, target[i][j])) - ... circuit.append(j_ops) - >>> circuit.append(i_ops) - - Note: Unary iteration circuits assume that the selection register stores integers only in the - range `[l, r)` for which the corresponding unary iteration circuit should be built. - - Args: - l_iter: Starting index of the iteration range. - r_iter: Ending index of the iteration range. - flanking_ops: A list of `cirq.Operation`s that represents operations to be inserted in the - circuit before/after the first/last iteration of the unary iteration for loop. Note that - the list is mutated by the function, such that before calling the function, the list - represents operations to be inserted before the first iteration and after the last call - to the function, list represents operations to be inserted at the end of last iteration. - controls: Control register of qubits. - selection: Selection register of qubits. - qubit_manager: A `cirq.QubitManager` to allocate new qubits. - break_early: For each internal node of the segment tree, `break_early(l, r)` is called to - evaluate whether the unary iteration should terminate early and not recurse in the - subtree of the node representing range `[l, r)`. If True, the internal node is - considered equivalent to a leaf node and the method yields only one tuple - `(OP_TREE, control_qubit, l)` for all integers in the range `[l, r)`. - - Yields: - (r_iter - l_iter) different tuples, each corresponding to an integer in range - [l_iter, r_iter). - Each returned tuple also corresponds to a unique leaf in the unary iteration tree. - The values of yielded `Tuple[cirq.OP_TREE, cirq.Qid, int]` correspond to: - - cirq.OP_TREE: The op-tree to be inserted in the circuit to get to the current leaf. - - cirq.Qid: Control qubit used to conditionally apply operations on the target conditioned - on the returned integer. - - int: The current integer in the iteration `range(l_iter, r_iter)`. - """ - assert 2 ** len(selection) >= r_iter - l_iter - assert len(selection) > 0 - ancilla = qubit_manager.qalloc(max(0, len(controls) + len(selection) - 1)) - if len(controls) == 0: - yield from _unary_iteration_zero_control( - flanking_ops, selection, ancilla, l_iter, r_iter, break_early - ) - elif len(controls) == 1: - yield from _unary_iteration_single_control( - flanking_ops, controls[0], selection, ancilla, l_iter, r_iter, break_early - ) - else: - yield from _unary_iteration_multi_controls( - flanking_ops, controls, selection, ancilla, l_iter, r_iter, break_early - ) - qubit_manager.qfree(ancilla) - - -class UnaryIterationGate(infra.GateWithRegisters): - """Base class for defining multiplexed gates that can execute a coherent for-loop. - - Unary iteration is a coherent for loop that can be used to conditionally perform a different - operation on a target register for every integer in the `range(l_iter, r_iter)` stored in the - selection register. - - `cirq_ft.UnaryIterationGate` leverages the utility method `cirq_ft.unary_iteration` to provide - a convenient API for users to define a multi-dimensional multiplexed gate that can execute - indexed operations on a target register depending on the index value stored in a selection - register. - - Note: Unary iteration circuits assume that the selection register stores integers only in the - range `[l, r)` for which the corresponding unary iteration circuit should be built. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.A. - """ - - @cached_property - @abc.abstractmethod - def control_registers(self) -> Tuple[infra.Register, ...]: - pass - - @cached_property - @abc.abstractmethod - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - pass - - @cached_property - @abc.abstractmethod - def target_registers(self) -> Tuple[infra.Register, ...]: - pass - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - @cached_property - def extra_registers(self) -> Tuple[infra.Register, ...]: - return () - - @abc.abstractmethod - def nth_operation( - self, context: cirq.DecompositionContext, control: cirq.Qid, **kwargs - ) -> cirq.OP_TREE: - """Apply nth operation on the target signature when selection signature store `n`. - - The `UnaryIterationGate` class is a mixin that represents a coherent for-loop over - different indices (i.e. selection signature). This method denotes the "body" of the - for-loop, which is executed `self.selection_registers.total_iteration_size` times and each - iteration represents a unique combination of values stored in selection signature. For each - call, the method should return the operations that should be applied to the target - signature, given the values stored in selection signature. - - The derived classes should specify the following arguments as `**kwargs`: - 1) `control: cirq.Qid`: A qubit which can be used as a control to selectively - apply operations when selection register stores specific value. - 2) Register names in `self.selection_registers`: Each argument corresponds to - a selection register and represents the integer value stored in the register. - 3) Register names in `self.target_registers`: Each argument corresponds to a target - register and represents the sequence of qubits that represent the target register. - 4) Register names in `self.extra_regs`: Each argument corresponds to an extra - register and represents the sequence of qubits that represent the extra register. - """ - - def decompose_zero_selection( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type: ignore[type-var] - ) -> cirq.OP_TREE: - """Specify decomposition of the gate when selection register is empty - - By default, if the selection register is empty, the decomposition will raise a - `NotImplementedError`. The derived classes can override this method and specify - a custom decomposition that should be used if the selection register is empty, - i.e. `infra.total_bits(self.selection_registers) == 0`. - - The derived classes should specify the following arguments as `**kwargs`: - 1) Register names in `self.control_registers`: Each argument corresponds to a - control register and represents sequence of qubits that represent the control register. - 2) Register names in `self.target_registers`: Each argument corresponds to a target - register and represents the sequence of qubits that represent the target register. - 3) Register names in `self.extra_regs`: Each argument corresponds to an extra - register and represents the sequence of qubits that represent the extra register. - """ - raise NotImplementedError("Selection register must not be empty.") - - def _break_early(self, selection_index_prefix: Tuple[int, ...], l: int, r: int) -> bool: - """Derived classes should override this method to specify an early termination condition. - - For each internal node of the unary iteration segment tree, `break_early(l, r)` is called - to evaluate whether the unary iteration should not recurse in the subtree of the node - representing range `[l, r)`. If True, the internal node is considered equivalent to a leaf - node and thus, `self.nth_operation` will be called for only integer `l` in the range [l, r). - - When the `UnaryIteration` class is constructed using multiple selection signature, i.e. we - wish to perform nested coherent for-loops, a unary iteration segment tree is constructed - corresponding to each nested coherent for-loop. For every such unary iteration segment tree, - the `_break_early` condition is checked by passing the `selection_index_prefix` tuple. - - Args: - selection_index_prefix: To evaluate the early breaking condition for the i'th nested - for-loop, the `selection_index_prefix` contains `i-1` integers corresponding to - the loop variable values for the first `i-1` nested loops. - l: Beginning of range `[l, r)` for internal node of unary iteration segment tree. - r: End (exclusive) of range `[l, r)` for internal node of unary iteration segment tree. - - Returns: - True of the `len(selection_index_prefix)`'th unary iteration should terminate early for - the given parameters. - """ - return False - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - if infra.total_bits(self.selection_registers) == 0 or self._break_early( - (), 0, self.selection_registers[0].iteration_length - ): - return self.decompose_zero_selection(context=context, **quregs) - - num_loops = len(self.selection_registers) - target_regs = {reg.name: quregs[reg.name] for reg in self.target_registers} - extra_regs = {reg.name: quregs[reg.name] for reg in self.extra_registers} - - def unary_iteration_loops( - nested_depth: int, - selection_reg_name_to_val: Dict[str, int], - controls: Sequence[cirq.Qid], - ) -> Iterator[cirq.OP_TREE]: - """Recursively write any number of nested coherent for-loops using unary iteration. - - This helper method is useful to write `num_loops` number of nested coherent for-loops by - recursively calling this method `num_loops` times. The ith recursive call of this method - has `nested_depth=i` and represents the body of ith nested for-loop. - - Args: - nested_depth: Integer between `[0, num_loops]` representing the nest-level of - for-loop for which this method implements the body. - selection_reg_name_to_val: A dictionary containing `nested_depth` elements mapping - the selection integer names (i.e. loop variables) to corresponding values; - for each of the `nested_depth` parent for-loops written before. - controls: Control qubits that should be used to conditionally activate the body of - this for-loop. - - Returns: - `cirq.OP_TREE` implementing `num_loops` nested coherent for-loops, with operations - returned by `self.nth_operation` applied conditionally to the target register based - on values of selection signature. - """ - if nested_depth == num_loops: - yield self.nth_operation( - context=context, - control=controls[0], - **selection_reg_name_to_val, - **target_regs, - **extra_regs, - ) - return - # Use recursion to write `num_loops` nested loops using unary_iteration(). - ops: List[cirq.Operation] = [] - selection_index_prefix = tuple(selection_reg_name_to_val.values()) - ith_for_loop = unary_iteration( - l_iter=0, - r_iter=self.selection_registers[nested_depth].iteration_length, - flanking_ops=ops, - controls=controls, - selection=[*quregs[self.selection_registers[nested_depth].name]], - qubit_manager=context.qubit_manager, - break_early=lambda l, r: self._break_early(selection_index_prefix, l, r), - ) - for op_tree, control_qid, n in ith_for_loop: - yield op_tree - selection_reg_name_to_val[self.selection_registers[nested_depth].name] = n - yield from unary_iteration_loops( - nested_depth + 1, selection_reg_name_to_val, (control_qid,) - ) - selection_reg_name_to_val.pop(self.selection_registers[nested_depth].name) - yield ops - - return unary_iteration_loops(0, {}, infra.merge_qubits(self.control_registers, **quregs)) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - """Basic circuit diagram. - - Descendants are encouraged to override this with more descriptive - circuit diagram information. - """ - wire_symbols = ["@"] * infra.total_bits(self.control_registers) - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - wire_symbols += [self.__class__.__name__] * infra.total_bits(self.target_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py b/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py deleted file mode 100644 index d3aa68b93ee..00000000000 --- a/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 functools import cached_property -import itertools -from typing import Sequence, Tuple - -import cirq -import cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -class ApplyXToLthQubit(cirq_ft.UnaryIterationGate): - def __init__(self, selection_bitsize: int, target_bitsize: int, control_bitsize: int = 1): - self._selection_bitsize = selection_bitsize - self._target_bitsize = target_bitsize - self._control_bitsize = control_bitsize - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('control', self._control_bitsize),) - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return ( - cirq_ft.SelectionRegister('selection', self._selection_bitsize, self._target_bitsize), - ) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('target', self._target_bitsize),) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - selection: int, - target: Sequence[cirq.Qid], - ) -> cirq.OP_TREE: - return cirq.CNOT(control, target[-(selection + 1)]) - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize, control_bitsize", [(3, 5, 1), (2, 4, 2), (1, 2, 3)] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_unary_iteration_gate(selection_bitsize, target_bitsize, control_bitsize): - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - gate = ApplyXToLthQubit(selection_bitsize, target_bitsize, control_bitsize) - g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) - assert len(g.all_qubits) <= 2 * (selection_bitsize + control_bitsize) + target_bitsize - 1 - - for n in range(target_bitsize): - # Initial qubit values - qubit_vals = {q: 0 for q in g.operation.qubits} - # All controls 'on' to activate circuit - qubit_vals.update({c: 1 for c in g.quregs['control']}) - # Set selection according to `n` - qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in g.operation.qubits] - qubit_vals[g.quregs['target'][-(n + 1)]] = 1 - final_state = [qubit_vals[x] for x in g.operation.qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - g.circuit, g.operation.qubits, initial_state, final_state - ) - - -class ApplyXToIJKthQubit(cirq_ft.UnaryIterationGate): - def __init__(self, target_shape: Tuple[int, int, int]): - self._target_shape = target_shape - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return tuple( - cirq_ft.SelectionRegister( - 'ijk'[i], (self._target_shape[i] - 1).bit_length(), self._target_shape[i] - ) - for i in range(3) - ) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return tuple( - cirq_ft.Signature.build( - t1=self._target_shape[0], t2=self._target_shape[1], t3=self._target_shape[2] - ) - ) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - i: int, - j: int, - k: int, - t1: Sequence[cirq.Qid], - t2: Sequence[cirq.Qid], - t3: Sequence[cirq.Qid], - ) -> cirq.OP_TREE: - yield [cirq.CNOT(control, t1[i]), cirq.CNOT(control, t2[j]), cirq.CNOT(control, t3[k])] - - -@pytest.mark.parametrize( - "target_shape", [pytest.param((2, 3, 2), marks=pytest.mark.slow), (2, 2, 2)] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_dimensional_unary_iteration_gate(target_shape: Tuple[int, int, int]): - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - gate = ApplyXToIJKthQubit(target_shape) - g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) - assert ( - len(g.all_qubits) - <= infra.total_bits(gate.signature) + infra.total_bits(gate.selection_registers) - 1 - ) - - max_i, max_j, max_k = target_shape - i_len, j_len, k_len = tuple(reg.total_bits() for reg in gate.selection_registers) - for i, j, k in itertools.product(range(max_i), range(max_j), range(max_k)): - qubit_vals = {x: 0 for x in g.operation.qubits} - # Initialize selection bits appropriately: - qubit_vals.update(zip(g.quregs['i'], iter_bits(i, i_len))) - qubit_vals.update(zip(g.quregs['j'], iter_bits(j, j_len))) - qubit_vals.update(zip(g.quregs['k'], iter_bits(k, k_len))) - # Construct initial state - initial_state = [qubit_vals[x] for x in g.operation.qubits] - # Build correct statevector with selection_integer bit flipped in the target register: - for reg_name, idx in zip(['t1', 't2', 't3'], [i, j, k]): - qubit_vals[g.quregs[reg_name][idx]] = 1 - final_state = [qubit_vals[x] for x in g.operation.qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - g.circuit, g.operation.qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_unary_iteration_loop(): - n_range, m_range = (3, 5), (6, 8) - selection_registers = [ - cirq_ft.SelectionRegister('n', 3, 5), - cirq_ft.SelectionRegister('m', 3, 8), - ] - selection = infra.get_named_qubits(selection_registers) - target = {(n, m): cirq.q(f't({n}, {m})') for n in range(*n_range) for m in range(*m_range)} - qm = cirq.GreedyQubitManager("ancilla", maximize_reuse=True) - circuit = cirq.Circuit() - i_ops = [] - # Build the unary iteration circuit - for i_optree, i_ctrl, i in cirq_ft.unary_iteration( - n_range[0], n_range[1], i_ops, [], selection['n'], qm - ): - circuit.append(i_optree) - j_ops = [] - for j_optree, j_ctrl, j in cirq_ft.unary_iteration( - m_range[0], m_range[1], j_ops, [i_ctrl], selection['m'], qm - ): - circuit.append(j_optree) - # Conditionally perform operations on target register using `j_ctrl`, `i` & `j`. - circuit.append(cirq.CNOT(j_ctrl, target[(i, j)])) - circuit.append(j_ops) - circuit.append(i_ops) - all_qubits = sorted(circuit.all_qubits()) - - i_len, j_len = 3, 3 - for i, j in itertools.product(range(*n_range), range(*m_range)): - qubit_vals = {x: 0 for x in all_qubits} - # Initialize selection bits appropriately: - qubit_vals.update(zip(selection['n'], iter_bits(i, i_len))) - qubit_vals.update(zip(selection['m'], iter_bits(j, j_len))) - # Construct initial state - initial_state = [qubit_vals[x] for x in all_qubits] - # Build correct statevector with selection_integer bit flipped in the target register: - qubit_vals[target[(i, j)]] = 1 - final_state = [qubit_vals[x] for x in all_qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_unary_iteration_loop_empty_range(): - qm = cirq.ops.SimpleQubitManager() - assert list(cirq_ft.unary_iteration(4, 4, [], [], [cirq.q('s')], qm)) == [] - assert list(cirq_ft.unary_iteration(4, 3, [], [], [cirq.q('s')], qm)) == [] - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('unary_iteration') diff --git a/cirq-ft/cirq_ft/deprecation.py b/cirq-ft/cirq_ft/deprecation.py deleted file mode 100644 index 7dbf1ed584d..00000000000 --- a/cirq-ft/cirq_ft/deprecation.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. -import functools -import unittest.mock -import os -from typing import Callable, Type -from cirq._compat import deprecated, deprecated_class - -_DEPRECATION_DEADLINE = 'v1.4' -_DEPRECATION_FIX_MSG = "Cirq-FT is deprecated in favour of Qualtran. pip install qualtran instead." - - -def deprecated_cirq_ft_class() -> Callable[[Type], Type]: # coverage: ignore - """Decorator to mark a class in Cirq-FT deprecated.""" - return deprecated_class(deadline=_DEPRECATION_DEADLINE, fix=_DEPRECATION_FIX_MSG) - - -def deprecated_cirq_ft_function() -> Callable[[Callable], Callable]: # coverage: ignore - """Decorator to mark a function in Cirq-FT deprecated.""" - return deprecated(deadline=_DEPRECATION_DEADLINE, fix=_DEPRECATION_FIX_MSG) - - -def allow_deprecated_cirq_ft_use_in_tests(func): # coverage: ignore - """Decorator to allow using deprecated classes and functions in Tests and suppress warnings.""" - - @functools.wraps(func) - @unittest.mock.patch.dict(os.environ, ALLOW_DEPRECATION_IN_TEST="True") - def wrapper(*args, **kwargs): - from cirq.testing import assert_logs - import logging - - with assert_logs(min_level=logging.WARNING, max_level=logging.WARNING, count=None) as logs: - ret_val = func(*args, **kwargs) - for log in logs: - msg = log.getMessage() - if _DEPRECATION_FIX_MSG in msg: - assert _DEPRECATION_DEADLINE in msg - return ret_val - - return wrapper diff --git a/cirq-ft/cirq_ft/infra/__init__.py b/cirq-ft/cirq_ft/infra/__init__.py deleted file mode 100644 index 3159271f131..00000000000 --- a/cirq-ft/cirq_ft/infra/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 cirq_ft.infra.gate_with_registers import ( - GateWithRegisters, - Register, - Signature, - Side, - SelectionRegister, - total_bits, - split_qubits, - merge_qubits, - get_named_qubits, -) -from cirq_ft.infra.qubit_management_transformers import map_clean_and_borrowable_qubits -from cirq_ft.infra.t_complexity_protocol import TComplexity, t_complexity diff --git a/cirq-ft/cirq_ft/infra/bit_tools.py b/cirq-ft/cirq_ft/infra/bit_tools.py deleted file mode 100644 index ce54cdd5996..00000000000 --- a/cirq-ft/cirq_ft/infra/bit_tools.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 Iterator, Tuple - -import numpy as np - - -def iter_bits(val: int, width: int, *, signed: bool = False) -> Iterator[int]: - """Iterate over the bits in a binary representation of `val`. - - This uses a big-endian convention where the most significant bit - is yielded first. - - Args: - val: The integer value. Its bitsize must fit within `width` - width: The number of output bits. - signed: If True, the most significant bit represents the sign of - the number (ones complement) which is 1 if val < 0 else 0. - Raises: - ValueError: If `val` is negative or if `val.bit_length()` exceeds `width`. - """ - if val.bit_length() + int(val < 0) > width: - raise ValueError(f"{val} exceeds width {width}.") - if val < 0 and not signed: - raise ValueError(f"{val} is negative.") - if signed: - yield 1 if val < 0 else 0 - width -= 1 - for b in f'{abs(val):0{width}b}': - yield int(b) - - -def iter_bits_twos_complement(val: int, width: int) -> Iterator[int]: - """Iterate over the bits in a binary representation of `val`. - - This uses a big-endian convention where the most significant bit - is yielded first. Allows for negative values and represents these using twos - complement. - - Args: - val: The integer value. Its bitsize must fit within `width` - width: The number of output bits. - - Raises: - ValueError: If `val.bit_length()` exceeds `2 * width + 1`. - """ - if (val.bit_length() - 1) // 2 > width: - raise ValueError(f"{val} exceeds width {width}.") - mask = (1 << width) - 1 - for b in f'{val&mask:0{width}b}': - yield int(b) - - -def iter_bits_fixed_point(val: float, width: int, *, signed: bool = False) -> Iterator[int]: - r"""Represent the floating point number -1 <= val <= 1 using `width` bits. - - $$ - val = \sum_{b=0}^{width - 1} val[b] / 2^{1+b} - $$ - - Args: - val: Floating point number in [-1, 1] - width: The number of output bits in fixed point binary representation of `val`. - signed: If True, the most significant bit represents the sign of - the number (ones complement) which is 1 if val < 0 else 0. - - Raises: - ValueError: If val is not between [0, 1] (signed=False) / [-1, 1] (signed=True). - """ - lb = -1 if signed else 0 - assert lb <= val <= 1, f"{val} must be between [{lb}, 1]" - if signed: - yield 1 if val < 0 else 0 - width -= 1 - val = abs(val) - for _ in range(width): - val = val * 2 - out_bit = np.floor(val) - val = val - out_bit - yield int(out_bit) - - -def float_as_fixed_width_int(val: float, width: int) -> Tuple[int, int]: - """Returns a `width` length fixed point binary representation of `val` where -1 <= val <= 1.""" - bits = [*iter_bits_fixed_point(val, width, signed=True)] - return bits[0], int(''.join(str(b) for b in bits[1:]), 2) diff --git a/cirq-ft/cirq_ft/infra/bit_tools_test.py b/cirq-ft/cirq_ft/infra/bit_tools_test.py deleted file mode 100644 index 7836549d159..00000000000 --- a/cirq-ft/cirq_ft/infra/bit_tools_test.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import math -import random - -import pytest -from cirq_ft.infra.bit_tools import ( - float_as_fixed_width_int, - iter_bits, - iter_bits_fixed_point, - iter_bits_twos_complement, -) - - -def test_iter_bits(): - assert list(iter_bits(0, 2)) == [0, 0] - assert list(iter_bits(0, 3, signed=True)) == [0, 0, 0] - assert list(iter_bits(1, 2)) == [0, 1] - assert list(iter_bits(1, 2, signed=True)) == [0, 1] - assert list(iter_bits(-1, 2, signed=True)) == [1, 1] - assert list(iter_bits(2, 2)) == [1, 0] - assert list(iter_bits(2, 3, signed=True)) == [0, 1, 0] - assert list(iter_bits(-2, 3, signed=True)) == [1, 1, 0] - assert list(iter_bits(3, 2)) == [1, 1] - with pytest.raises(ValueError): - assert list(iter_bits(4, 2)) == [1, 0, 0] - with pytest.raises(ValueError): - _ = list(iter_bits(-3, 4)) - - -def test_iter_bits_twos(): - assert list(iter_bits_twos_complement(0, 4)) == [0, 0, 0, 0] - assert list(iter_bits_twos_complement(1, 4)) == [0, 0, 0, 1] - assert list(iter_bits_twos_complement(-2, 4)) == [1, 1, 1, 0] - assert list(iter_bits_twos_complement(-3, 4)) == [1, 1, 0, 1] - with pytest.raises(ValueError): - _ = list(iter_bits_twos_complement(100, 2)) - - -random.seed(1234) - - -@pytest.mark.parametrize('val', [random.uniform(-1, 1) for _ in range(10)]) -@pytest.mark.parametrize('width', [*range(2, 20, 2)]) -@pytest.mark.parametrize('signed', [True, False]) -def test_iter_bits_fixed_point(val, width, signed): - if (val < 0) and not signed: - with pytest.raises(AssertionError): - _ = [*iter_bits_fixed_point(val, width, signed=signed)] - else: - bits = [*iter_bits_fixed_point(val, width, signed=signed)] - if signed: - sign, bits = bits[0], bits[1:] - assert sign == (1 if val < 0 else 0) - val = abs(val) - approx_val = math.fsum([b * (1 / 2 ** (1 + i)) for i, b in enumerate(bits)]) - unsigned_width = width - 1 if signed else width - assert math.isclose( - val, approx_val, abs_tol=1 / 2**unsigned_width - ), f'{val}:{approx_val}:{width}' - bits_from_int = [ - *iter_bits(float_as_fixed_width_int(val, unsigned_width + 1)[1], unsigned_width) - ] - assert bits == bits_from_int diff --git a/cirq-ft/cirq_ft/infra/decompose_protocol.py b/cirq-ft/cirq_ft/infra/decompose_protocol.py deleted file mode 100644 index e7b6b6615d0..00000000000 --- a/cirq-ft/cirq_ft/infra/decompose_protocol.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 Any, FrozenSet, Sequence - -import cirq -from cirq.protocols.decompose_protocol import DecomposeResult - -_FREDKIN_GATESET = cirq.Gateset(cirq.FREDKIN, unroll_circuit_op=False) - - -def _fredkin(qubits: Sequence[cirq.Qid], context: cirq.DecompositionContext) -> cirq.OP_TREE: - """Decomposition with 7 T and 10 clifford operations from https://arxiv.org/abs/1308.4134""" - c, t1, t2 = qubits - yield [cirq.CNOT(t2, t1)] - yield [cirq.CNOT(c, t1), cirq.H(t2)] - yield [cirq.T(c), cirq.T(t1) ** -1, cirq.T(t2)] - yield [cirq.CNOT(t2, t1)] - yield [cirq.CNOT(c, t2), cirq.T(t1)] - yield [cirq.CNOT(c, t1), cirq.T(t2) ** -1] - yield [cirq.T(t1) ** -1, cirq.CNOT(c, t2)] - yield [cirq.CNOT(t2, t1)] - yield [cirq.T(t1), cirq.H(t2)] - yield [cirq.CNOT(t2, t1)] - - -def _try_decompose_from_known_decompositions( - val: Any, context: cirq.DecompositionContext -) -> DecomposeResult: - """Returns a flattened decomposition of the object into operations, if possible. - - Args: - val: The object to decompose. - context: Decomposition context storing common configurable options for `cirq.decompose`. - - Returns: - A flattened decomposition of `val` if it's a gate or operation with a known decomposition. - """ - if not isinstance(val, (cirq.Gate, cirq.Operation)): - return None - qubits = cirq.LineQid.for_gate(val) if isinstance(val, cirq.Gate) else val.qubits - known_decompositions = [(_FREDKIN_GATESET, _fredkin)] - - classical_controls: FrozenSet[cirq.Condition] = frozenset() - if isinstance(val, cirq.ClassicallyControlledOperation): - classical_controls = val.classical_controls - val = val.without_classical_controls() - - decomposition = None - for gateset, decomposer in known_decompositions: - if val in gateset: - decomposition = cirq.flatten_to_ops(decomposer(qubits, context)) - break - return ( - tuple(op.with_classical_controls(*classical_controls) for op in decomposition) - if decomposition - else None - ) - - -def _decompose_once_considering_known_decomposition(val: Any) -> DecomposeResult: - """Decomposes a value into operations, if possible. - - Args: - val: The value to decompose into operations. - - Returns: - A tuple of operations if decomposition succeeds. - """ - import uuid - - context = cirq.DecompositionContext( - qubit_manager=cirq.GreedyQubitManager(prefix=f'_{uuid.uuid4()}', maximize_reuse=True) - ) - - decomposed = _try_decompose_from_known_decompositions(val, context) - if decomposed is not None: - return decomposed - - if isinstance(val, cirq.Gate): - decomposed = cirq.decompose_once_with_qubits( - val, cirq.LineQid.for_gate(val), context=context, flatten=False, default=None - ) - else: - decomposed = cirq.decompose_once(val, context=context, flatten=False, default=None) - - return [*cirq.flatten_to_ops(decomposed)] if decomposed is not None else None diff --git a/cirq-ft/cirq_ft/infra/decompose_protocol_test.py b/cirq-ft/cirq_ft/infra/decompose_protocol_test.py deleted file mode 100644 index ac753faf107..00000000000 --- a/cirq-ft/cirq_ft/infra/decompose_protocol_test.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import numpy as np -import pytest -from cirq_ft.infra.decompose_protocol import ( - _fredkin, - _try_decompose_from_known_decompositions, - _decompose_once_considering_known_decomposition, -) - - -def test_fredkin_unitary(): - c, t1, t2 = cirq.LineQid.for_gate(cirq.FREDKIN) - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - np.testing.assert_allclose( - cirq.Circuit(_fredkin((c, t1, t2), context)).unitary(), - cirq.unitary(cirq.FREDKIN(c, t1, t2)), - atol=1e-8, - ) - - -@pytest.mark.parametrize('gate', [cirq.FREDKIN, cirq.FREDKIN**-1]) -def test_decompose_fredkin(gate): - c, t1, t2 = cirq.LineQid.for_gate(cirq.FREDKIN) - op = cirq.FREDKIN(c, t1, t2) - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - want = tuple(cirq.flatten_op_tree(_fredkin((c, t1, t2), context))) - assert want == _try_decompose_from_known_decompositions(op, context) - - op = cirq.FREDKIN(c, t1, t2).with_classical_controls('key') - classical_controls = op.classical_controls - want = tuple( - o.with_classical_controls(*classical_controls) - for o in cirq.flatten_op_tree(_fredkin((c, t1, t2), context)) - ) - assert want == _try_decompose_from_known_decompositions(op, context) - - -def test_known_decomposition_empty_unitary(): - class DecomposeEmptyList(cirq.testing.SingleQubitGate): - def _decompose_(self, _): - return [] - - gate = DecomposeEmptyList() - assert _decompose_once_considering_known_decomposition(gate) == [] diff --git a/cirq-ft/cirq_ft/infra/gate_with_registers.ipynb b/cirq-ft/cirq_ft/infra/gate_with_registers.ipynb deleted file mode 100644 index 8ccc674942c..00000000000 --- a/cirq-ft/cirq_ft/infra/gate_with_registers.ipynb +++ /dev/null @@ -1,242 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3b990f88", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "bf9c80ce", - "metadata": {}, - "source": [ - "# Gate with Registers\n", - "\n", - "This package includes a subclass of `cirq.Gate` called `GateWithRegisters`. Instead of operating on a flat list of `cirq.Qid`, this lets the developer define gates in terms of named registers of given widths." - ] - }, - { - "cell_type": "markdown", - "id": "c0833444", - "metadata": {}, - "source": [ - "## `Signature`\n", - "\n", - "`Register` objects have a name, a bitsize and a shape. `Signature` is an ordered collection of `Register` with some helpful methods." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c75414f2", - "metadata": {}, - "outputs": [], - "source": [ - "from cirq_ft import Register, Signature, infra\n", - "\n", - "control_reg = Register(name='control', bitsize=2)\n", - "target_reg = Register(name='target', bitsize=3)\n", - "control_reg, target_reg" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b38d210c", - "metadata": {}, - "outputs": [], - "source": [ - "r = Signature([control_reg, target_reg])\n", - "r" - ] - }, - { - "cell_type": "markdown", - "id": "2b32274b", - "metadata": {}, - "source": [ - "You can also use the `build` factory method to quickly define a set of registers" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4f10e66e", - "metadata": {}, - "outputs": [], - "source": [ - "r == Signature.build(\n", - " control=2,\n", - " target=3,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "a5955208", - "metadata": {}, - "source": [ - "### `GateWithRegisters`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b3957db4", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "from cirq_ft import GateWithRegisters\n", - "\n", - "class MyGate(GateWithRegisters):\n", - " \n", - " @property\n", - " def signature(self):\n", - " return Signature.build(\n", - " control=2,\n", - " target=3,\n", - " )\n", - " \n", - " def decompose_from_registers(self, context, control, target):\n", - " assert len(control) == 2\n", - " assert len(target) == 3\n", - " \n", - " for c in control:\n", - " for t in target:\n", - " yield cirq.CNOT(c, t)\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2de931eb", - "metadata": {}, - "outputs": [], - "source": [ - "gate = MyGate()\n", - "gate" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef98f3a2", - "metadata": {}, - "outputs": [], - "source": [ - "# Number of qubits is derived from registers\n", - "cirq.num_qubits(gate)" - ] - }, - { - "cell_type": "markdown", - "id": "2d725646", - "metadata": {}, - "source": [ - "The `Signature` object can allocate a dictionary of `cirq.NamedQubit` that we can use to turn our `Gate` into an `Operation`. `GateWithRegisters` exposes an `on_registers` method to compliment Cirq's `on` method where we can use names to make sure each qubit is used appropriately." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "057148da", - "metadata": {}, - "outputs": [], - "source": [ - "r = gate.signature\n", - "quregs = infra.get_named_qubits(r)\n", - "quregs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0257d8f1", - "metadata": {}, - "outputs": [], - "source": [ - "operation = gate.on_registers(**quregs)\n", - "operation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "541f2e91", - "metadata": {}, - "outputs": [], - "source": [ - "from cirq.contrib.svg import SVGCircuit\n", - "SVGCircuit(cirq.Circuit(operation))" - ] - }, - { - "cell_type": "markdown", - "id": "6686f7f8", - "metadata": {}, - "source": [ - "## `GateHelper`\n", - "\n", - "Since `GateWithRegisters` contains enough metadata to derive qubits, an operation, and a circuit we provide a helper class to provide easy access to these quantities." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "93a6c8f2", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq_ft.infra.testing as cq_testing\n", - "\n", - "g = cq_testing.GateHelper(gate)\n", - "\n", - "print('r:', g.r)\n", - "print('quregs:', g.quregs)\n", - "print('operation:', g.operation)\n", - "print('\\ncircuit:\\n', g.circuit)\n", - "print('\\n\\ndecomposed circuit:\\n', cirq.Circuit(cirq.decompose_once(g.operation)))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/infra/gate_with_registers.py b/cirq-ft/cirq_ft/infra/gate_with_registers.py deleted file mode 100644 index 1e9129bdc6a..00000000000 --- a/cirq-ft/cirq_ft/infra/gate_with_registers.py +++ /dev/null @@ -1,377 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import enum -import abc -import itertools -from typing import Dict, Iterable, List, Optional, Sequence, Tuple, Union, overload, Iterator -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft.deprecation import deprecated_cirq_ft_class - - -class Side(enum.Flag): - """Denote LEFT, RIGHT, or THRU signature. - - LEFT signature serve as input lines (only) to the Gate. RIGHT signature are output - lines (only) from the Gate. THRU signature are both input and output. - - Traditional unitary operations will have THRU signature that operate on a collection of - qubits which are then made available to following operations. RIGHT and LEFT signature - imply allocation, deallocation, or reshaping of the signature. - """ - - LEFT = enum.auto() - RIGHT = enum.auto() - THRU = LEFT | RIGHT - - -@deprecated_cirq_ft_class() -@attr.frozen -class Register: - """A quantum register used to define the input/output API of a `cirq_ft.GateWithRegister` - - Attributes: - name: The string name of the register - bitsize: The number of (qu)bits in the register. - shape: A tuple of integer dimensions to declare a multidimensional register. The - total number of bits is the product of entries in this tuple times `bitsize`. - side: Whether this is a left, right, or thru register. See the documentation for `Side` - for more information. - """ - - name: str - bitsize: int = attr.field() - shape: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - side: Side = Side.THRU - - @bitsize.validator - def bitsize_validator(self, attribute, value): - if value <= 0: - raise ValueError(f"Bitsize for {self=} must be a positive integer. Found {value}.") - - def all_idxs(self) -> Iterable[Tuple[int, ...]]: - """Iterate over all possible indices of a multidimensional register.""" - yield from itertools.product(*[range(sh) for sh in self.shape]) - - def total_bits(self) -> int: - """The total number of bits in this register. - - This is the product of each of the dimensions in `shape`. - """ - return self.bitsize * int(np.prod(self.shape)) - - def __repr__(self): - return ( - f'cirq_ft.Register(' - f'name="{self.name}", ' - f'bitsize={self.bitsize}, ' - f'shape={self.shape}, ' - f'side=cirq_ft.infra.{self.side})' - ) - - -def total_bits(registers: Iterable[Register]) -> int: - """Sum of `reg.total_bits()` for each register `reg` in input `signature`.""" - return sum(reg.total_bits() for reg in registers) - - -def split_qubits( - registers: Iterable[Register], qubits: Sequence[cirq.Qid] -) -> Dict[str, NDArray[cirq.Qid]]: # type: ignore[type-var] - """Splits the flat list of qubits into a dictionary of appropriately shaped qubit arrays.""" - - qubit_regs = {} - base = 0 - for reg in registers: - qubit_regs[reg.name] = np.array(qubits[base : base + reg.total_bits()]).reshape( - reg.shape + (reg.bitsize,) - ) - base += reg.total_bits() - return qubit_regs - - -def merge_qubits( - registers: Iterable[Register], - **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]], -) -> List[cirq.Qid]: - """Merges the dictionary of appropriately shaped qubit arrays into a flat list of qubits.""" - - ret: List[cirq.Qid] = [] - for reg in registers: - if reg.name not in qubit_regs: - raise ValueError(f"All qubit registers must be present. {reg.name} not in qubit_regs") - qubits = qubit_regs[reg.name] - qubits = np.array([qubits] if isinstance(qubits, cirq.Qid) else qubits) - full_shape = reg.shape + (reg.bitsize,) - if qubits.shape != full_shape: - raise ValueError( - f'{reg.name} register must of shape {full_shape} but is of shape {qubits.shape}' - ) - ret += qubits.flatten().tolist() - return ret - - -def get_named_qubits(registers: Iterable[Register]) -> Dict[str, NDArray[cirq.Qid]]: - """Returns a dictionary of appropriately shaped named qubit signature for input `signature`.""" - - def _qubit_array(reg: Register): - qubits = np.empty(reg.shape + (reg.bitsize,), dtype=object) - for ii in reg.all_idxs(): - for j in range(reg.bitsize): - prefix = "" if not ii else f'[{", ".join(str(i) for i in ii)}]' - suffix = "" if reg.bitsize == 1 else f"[{j}]" - qubits[ii + (j,)] = cirq.NamedQubit(reg.name + prefix + suffix) - return qubits - - def _qubits_for_reg(reg: Register): - if len(reg.shape) > 0: - return _qubit_array(reg) - - return np.array( - ( - [cirq.NamedQubit(f"{reg.name}")] - if reg.total_bits() == 1 - else cirq.NamedQubit.range(reg.total_bits(), prefix=reg.name) - ), - dtype=object, - ) - - return {reg.name: _qubits_for_reg(reg) for reg in registers} - - -@deprecated_cirq_ft_class() -class Signature: - """An ordered collection of `cirq_ft.Register`. - - Args: - registers: an iterable of the contained `cirq_ft.Register`. - """ - - def __init__(self, registers: Iterable[Register]): - self._registers = tuple(registers) - self._lefts = {r.name: r for r in self._registers if r.side & Side.LEFT} - self._rights = {r.name: r for r in self._registers if r.side & Side.RIGHT} - if len(set(self._lefts) | set(self._rights)) != len(self._registers): - raise ValueError("Please provide unique register names.") - - def __repr__(self): - return f'cirq_ft.Signature({self._registers})' - - @classmethod - def build(cls, **registers: int) -> 'Signature': - return cls(Register(name=k, bitsize=v) for k, v in registers.items() if v > 0) - - @overload - def __getitem__(self, key: int) -> Register: - pass - - @overload - def __getitem__(self, key: slice) -> Tuple[Register, ...]: - pass - - def __getitem__(self, key): - return self._registers[key] - - def get_left(self, name: str) -> Register: - """Get a left register by name.""" - return self._lefts[name] - - def get_right(self, name: str) -> Register: - """Get a right register by name.""" - return self._rights[name] - - def __contains__(self, item: Register) -> bool: - return item in self._registers - - def __iter__(self) -> Iterator[Register]: - yield from self._registers - - def __len__(self) -> int: - return len(self._registers) - - def __eq__(self, other) -> bool: - return self._registers == other._registers - - def __hash__(self): - return hash(self._registers) - - -@attr.frozen -class SelectionRegister(Register): - """Register used to represent SELECT register for various LCU methods. - - `SelectionRegister` extends the `Register` class to store the iteration length - corresponding to that register along with its size. - - LCU methods often make use of coherent for-loops via UnaryIteration, iterating over a range - of values stored as a superposition over the `SELECT` register. Such (nested) coherent - for-loops can be represented using a `Tuple[SelectionRegister, ...]` where the i'th entry - stores the bitsize and iteration length of i'th nested for-loop. - - One useful feature when processing such nested for-loops is to flatten out a composite index, - represented by a tuple of indices (i, j, ...), one for each selection register into a single - integer that can be used to index a flat target register. An example of such a mapping - function is described in Eq.45 of https://arxiv.org/abs/1805.03662. A general version of this - mapping function can be implemented using `numpy.ravel_multi_index` and `numpy.unravel_index`. - - For example: - 1) We can flatten a 2D for-loop as follows - >>> import numpy as np - >>> N, M = 10, 20 - >>> flat_indices = set() - >>> for x in range(N): - ... for y in range(M): - ... flat_idx = x * M + y - ... assert np.ravel_multi_index((x, y), (N, M)) == flat_idx - ... assert np.unravel_index(flat_idx, (N, M)) == (x, y) - ... flat_indices.add(flat_idx) - >>> assert len(flat_indices) == N * M - - 2) Similarly, we can flatten a 3D for-loop as follows - >>> import numpy as np - >>> N, M, L = 10, 20, 30 - >>> flat_indices = set() - >>> for x in range(N): - ... for y in range(M): - ... for z in range(L): - ... flat_idx = x * M * L + y * L + z - ... assert np.ravel_multi_index((x, y, z), (N, M, L)) == flat_idx - ... assert np.unravel_index(flat_idx, (N, M, L)) == (x, y, z) - ... flat_indices.add(flat_idx) - >>> assert len(flat_indices) == N * M * L - """ - - name: str - bitsize: int - iteration_length: int = attr.field() - shape: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - side: Side = Side.THRU - - @iteration_length.default - def _default_iteration_length(self): - return 2**self.bitsize - - @iteration_length.validator - def validate_iteration_length(self, attribute, value): - if len(self.shape) != 0: - raise ValueError(f'Selection register {self.name} should be flat. Found {self.shape=}') - if not (0 <= value <= 2**self.bitsize): - raise ValueError(f'iteration length must be in range [0, 2^{self.bitsize}]') - - def __repr__(self) -> str: - return ( - f'cirq_ft.SelectionRegister(' - f'name="{self.name}", ' - f'bitsize={self.bitsize}, ' - f'shape={self.shape}, ' - f'iteration_length={self.iteration_length})' - ) - - -@deprecated_cirq_ft_class() -class GateWithRegisters(cirq.Gate, metaclass=abc.ABCMeta): - """`cirq.Gate`s extension with support for composite gates acting on multiple qubit registers. - - Though Cirq was nominally designed for circuit construction for near-term devices the core - concept of the `cirq.Gate`, a programmatic representation of an operation on a state without - a complete qubit address specification, can be leveraged to describe more abstract algorithmic - primitives. To define composite gates, users derive from `cirq.Gate` and implement the - `_decompose_` method that yields the sub-operations provided a flat list of qubits. - - This API quickly becomes inconvenient when defining operations that act on multiple qubit - registers of variable sizes. Cirq-FT extends the `cirq.Gate` idea by introducing a new abstract - base class `cirq_ft.GateWithRegisters` containing abstract methods `registers` and optional - method `decompose_from_registers` that provides an overlay to the Cirq flat address API. - - As an example, in the following code snippet we use the `cirq_ft.GateWithRegisters` to - construct a multi-target controlled swap operation: - - >>> import attr - >>> import cirq - >>> import cirq_ft - >>> - >>> @attr.frozen - ... class MultiTargetCSwap(cirq_ft.GateWithRegisters): - ... bitsize: int - ... - ... @property - ... def signature(self) -> cirq_ft.Signature: - ... return cirq_ft.Signature.build(ctrl=1, x=self.bitsize, y=self.bitsize) - ... - ... def decompose_from_registers(self, context, ctrl, x, y) -> cirq.OP_TREE: - ... yield [cirq.CSWAP(*ctrl, qx, qy) for qx, qy in zip(x, y)] - ... - >>> op = MultiTargetCSwap(2).on_registers( - ... ctrl=[cirq.q('ctrl')], - ... x=cirq.NamedQubit.range(2, prefix='x'), - ... y=cirq.NamedQubit.range(2, prefix='y'), - ... ) - >>> print(cirq.Circuit(op)) - ctrl: ───MultiTargetCSwap─── - │ - x0: ─────x────────────────── - │ - x1: ─────x────────────────── - │ - y0: ─────y────────────────── - │ - y1: ─────y──────────────────""" - - @property - @abc.abstractmethod - def signature(self) -> Signature: ... - - def _num_qubits_(self) -> int: - return total_bits(self.signature) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - return NotImplemented - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - qubit_regs = split_qubits(self.signature, qubits) - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - return self.decompose_from_registers(context=context, **qubit_regs) - - def _decompose_(self, qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE: - return self._decompose_with_context_(qubits) - - def on_registers( - self, **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]] - ) -> cirq.Operation: - return self.on(*merge_qubits(self.signature, **qubit_regs)) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - """Default diagram info that uses register names to name the boxes in multi-qubit gates. - - Descendants can override this method with more meaningful circuit diagram information. - """ - wire_symbols = [] - for reg in self.signature: - wire_symbols += [reg.name] * reg.total_bits() - - wire_symbols[0] = self.__class__.__name__ - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/infra/gate_with_registers_test.py b/cirq-ft/cirq_ft/infra/gate_with_registers_test.py deleted file mode 100644 index 437d226521b..00000000000 --- a/cirq-ft/cirq_ft/infra/gate_with_registers_test.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.infra import split_qubits, merge_qubits, get_named_qubits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@allow_deprecated_cirq_ft_use_in_tests -def test_register(): - r = cirq_ft.Register("my_reg", 5, (1, 2)) - assert r.bitsize == 5 - assert r.shape == (1, 2) - - with pytest.raises(ValueError, match="must be a positive integer"): - _ = cirq_ft.Register("zero bitsize register", bitsize=0) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_registers(): - r1 = cirq_ft.Register("r1", 5, side=cirq_ft.infra.Side.LEFT) - r2 = cirq_ft.Register("r2", 2, side=cirq_ft.infra.Side.RIGHT) - r3 = cirq_ft.Register("r3", 1) - regs = cirq_ft.Signature([r1, r2, r3]) - assert len(regs) == 3 - cirq.testing.assert_equivalent_repr(regs, setup_code='import cirq_ft') - - with pytest.raises(ValueError, match="unique"): - _ = cirq_ft.Signature([r1, r1]) - - assert regs[0] == r1 - assert regs[1] == r2 - assert regs[2] == r3 - - assert regs[0:1] == tuple([r1]) - assert regs[0:2] == tuple([r1, r2]) - assert regs[1:3] == tuple([r2, r3]) - - assert regs.get_left("r1") == r1 - assert regs.get_right("r2") == r2 - assert regs.get_left("r3") == r3 - - assert r1 in regs - assert r2 in regs - assert r3 in regs - - assert list(regs) == [r1, r2, r3] - - qubits = cirq.LineQubit.range(8) - qregs = split_qubits(regs, qubits) - assert qregs["r1"].tolist() == cirq.LineQubit.range(5) - assert qregs["r2"].tolist() == cirq.LineQubit.range(5, 5 + 2) - assert qregs["r3"].tolist() == [cirq.LineQubit(7)] - - qubits = qubits[::-1] - - with pytest.raises(ValueError, match="qubit registers must be present"): - _ = merge_qubits(regs, r1=qubits[:5], r2=qubits[5:7], r4=qubits[-1]) - - with pytest.raises(ValueError, match="register must of shape"): - _ = merge_qubits(regs, r1=qubits[:4], r2=qubits[5:7], r3=qubits[-1]) - - merged_qregs = merge_qubits(regs, r1=qubits[:5], r2=qubits[5:7], r3=qubits[-1]) - assert merged_qregs == qubits - - expected_named_qubits = { - "r1": cirq.NamedQubit.range(5, prefix="r1"), - "r2": cirq.NamedQubit.range(2, prefix="r2"), - "r3": [cirq.NamedQubit("r3")], - } - - named_qregs = get_named_qubits(regs) - for reg_name in expected_named_qubits: - assert np.array_equal(named_qregs[reg_name], expected_named_qubits[reg_name]) - - # Python dictionaries preserve insertion order, which should be same as insertion order of - # initial registers. - for reg_order in [[r1, r2, r3], [r2, r3, r1]]: - flat_named_qubits = [ - q for v in get_named_qubits(cirq_ft.Signature(reg_order)).values() for q in v - ] - expected_qubits = [q for r in reg_order for q in expected_named_qubits[r.name]] - assert flat_named_qubits == expected_qubits - - -@pytest.mark.parametrize('n, N, m, M', [(4, 10, 5, 19), (4, 16, 5, 32)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_selection_registers_indexing(n, N, m, M): - regs = [cirq_ft.SelectionRegister('x', n, N), cirq_ft.SelectionRegister('y', m, M)] - for x in range(regs[0].iteration_length): - for y in range(regs[1].iteration_length): - assert np.ravel_multi_index((x, y), (N, M)) == x * M + y - assert np.unravel_index(x * M + y, (N, M)) == (x, y) - - assert np.prod(tuple(reg.iteration_length for reg in regs)) == N * M - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selection_registers_consistent(): - with pytest.raises(ValueError, match="iteration length must be in "): - _ = cirq_ft.SelectionRegister('a', 3, 10) - - with pytest.raises(ValueError, match="should be flat"): - _ = cirq_ft.SelectionRegister('a', bitsize=1, shape=(3, 5), iteration_length=5) - - selection_reg = cirq_ft.Signature( - [ - cirq_ft.SelectionRegister('n', bitsize=3, iteration_length=5), - cirq_ft.SelectionRegister('m', bitsize=4, iteration_length=12), - ] - ) - assert selection_reg[0] == cirq_ft.SelectionRegister('n', 3, 5) - assert selection_reg[1] == cirq_ft.SelectionRegister('m', 4, 12) - assert selection_reg[:1] == tuple([cirq_ft.SelectionRegister('n', 3, 5)]) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_registers_getitem_raises(): - g = cirq_ft.Signature.build(a=4, b=3, c=2) - with pytest.raises(TypeError, match="indices must be integers or slices"): - _ = g[2.5] - - selection_reg = cirq_ft.Signature( - [cirq_ft.SelectionRegister('n', bitsize=3, iteration_length=5)] - ) - with pytest.raises(TypeError, match='indices must be integers or slices'): - _ = selection_reg[2.5] - - -@allow_deprecated_cirq_ft_use_in_tests -def test_registers_build(): - regs1 = cirq_ft.Signature([cirq_ft.Register("r1", 5), cirq_ft.Register("r2", 2)]) - regs2 = cirq_ft.Signature.build(r1=5, r2=2) - assert regs1 == regs2 - - -class _TestGate(cirq_ft.GateWithRegisters): - @property - def signature(self) -> cirq_ft.Signature: - r1 = cirq_ft.Register("r1", 5) - r2 = cirq_ft.Register("r2", 2) - r3 = cirq_ft.Register("r3", 1) - regs = cirq_ft.Signature([r1, r2, r3]) - return regs - - def decompose_from_registers(self, *, context, **quregs) -> cirq.OP_TREE: - yield cirq.H.on_each(quregs['r1']) - yield cirq.X.on_each(quregs['r2']) - yield cirq.X.on_each(quregs['r3']) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_gate_with_registers(): - tg = _TestGate() - assert tg._num_qubits_() == 8 - qubits = cirq.LineQubit.range(8) - circ = cirq.Circuit(tg._decompose_(qubits)) - assert circ.operation_at(cirq.LineQubit(3), 0).gate == cirq.H - - op1 = tg.on_registers(r1=qubits[:5], r2=qubits[6:], r3=qubits[5]) - op2 = tg.on(*qubits[:5], *qubits[6:], qubits[5]) - assert op1 == op2 - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('gate_with_registers') diff --git a/cirq-ft/cirq_ft/infra/jupyter_tools.py b/cirq-ft/cirq_ft/infra/jupyter_tools.py deleted file mode 100644 index b6b98cc9b14..00000000000 --- a/cirq-ft/cirq_ft/infra/jupyter_tools.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 pathlib import Path -from typing import Iterable - -import cirq -import cirq.contrib.svg.svg as ccsvg -import cirq_ft.infra.testing as cq_testing -import IPython.display -import ipywidgets -import nbformat -from cirq_ft.infra import gate_with_registers, t_complexity_protocol, get_named_qubits, merge_qubits -from nbconvert.preprocessors import ExecutePreprocessor - - -def display_gate_and_compilation(g: cq_testing.GateHelper, vertical=False, include_costs=True): - """Use ipywidgets to display SVG circuits for a `GateHelper` next to each other. - - Args: - g: The `GateHelper` to draw - vertical: If true, lay-out the original gate and its decomposition vertically - rather than side-by-side. - include_costs: If true, each operation is annotated with it's T-complexity cost. - """ - out1 = ipywidgets.Output() - out2 = ipywidgets.Output() - if vertical: - box = ipywidgets.VBox([out1, out2]) - else: - box = ipywidgets.HBox([out1, out2]) - - out1.append_display_data(svg_circuit(g.circuit, registers=g.r, include_costs=include_costs)) - out2.append_display_data( - svg_circuit( - cirq.Circuit(cirq.decompose_once(g.operation)), - registers=g.r, - include_costs=include_costs, - ) - ) - - IPython.display.display(box) - - -def circuit_with_costs(circuit: 'cirq.AbstractCircuit') -> 'cirq.AbstractCircuit': - """Annotates each operation in the circuit with its T-complexity cost.""" - - def _map_func(op: cirq.Operation, _): - t_cost = t_complexity_protocol.t_complexity(op) - return op.with_tags(f't:{t_cost.t:g},r:{t_cost.rotations:g}') - - return cirq.map_operations(circuit, map_func=_map_func) - - -def svg_circuit( - circuit: 'cirq.AbstractCircuit', - registers: Iterable[gate_with_registers.Register] = (), - include_costs: bool = False, -): - """Return an SVG object representing a circuit. - - Args: - circuit: The circuit to draw. - registers: Optional `Signature` object to order the qubits. - include_costs: If true, each operation is annotated with it's T-complexity cost. - - Raises: - ValueError: If `circuit` is empty. - """ - if len(circuit) == 0: - raise ValueError("Circuit is empty.") - - if registers: - qubit_order = cirq.QubitOrder.explicit( - merge_qubits(registers, **get_named_qubits(registers)), fallback=cirq.QubitOrder.DEFAULT - ) - else: - qubit_order = cirq.QubitOrder.DEFAULT - - if include_costs: - circuit = circuit_with_costs(circuit) - - tdd = circuit.to_text_diagram_drawer(transpose=False, qubit_order=qubit_order) - if len(tdd.horizontal_lines) == 0: - raise ValueError("No non-empty moments.") - return IPython.display.SVG(ccsvg.tdd_to_svg(tdd)) - - -def execute_notebook(name: str): - """Execute a jupyter notebook in the caller's directory. - - Args: - name: The name of the notebook without extension. - """ - import traceback - - # Assumes that the notebook is in the same path from where the function was called, - # which may be different from `__file__`. - notebook_path = Path(traceback.extract_stack()[-2].filename).parent / f"{name}.ipynb" - with notebook_path.open() as f: - nb = nbformat.read(f, as_version=4) - ep = ExecutePreprocessor(timeout=600, kernel_name="python3") - ep.preprocess(nb) diff --git a/cirq-ft/cirq_ft/infra/jupyter_tools_test.py b/cirq-ft/cirq_ft/infra/jupyter_tools_test.py deleted file mode 100644 index a60f5fb8dd3..00000000000 --- a/cirq-ft/cirq_ft/infra/jupyter_tools_test.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import cirq_ft.infra.testing as cq_testing -import IPython.display -import ipywidgets -import pytest -from cirq_ft.infra.jupyter_tools import display_gate_and_compilation, svg_circuit -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@allow_deprecated_cirq_ft_use_in_tests -def test_svg_circuit(): - g = cq_testing.GateHelper(cirq_ft.And(cv=(1, 1, 1))) - svg = svg_circuit(g.circuit, g.r) - svg_str = svg.data - - # check that the order is respected in the svg data. - assert svg_str.find('ctrl') < svg_str.find('junk') < svg_str.find('target') - - # Check svg_circuit raises. - with pytest.raises(ValueError): - svg_circuit(cirq.Circuit()) - with pytest.raises(ValueError): - svg_circuit(cirq.Circuit(cirq.Moment())) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_display_gate_and_compilation(monkeypatch): - call_args = [] - - def _mock_display(stuff): - call_args.append(stuff) - - monkeypatch.setattr(IPython.display, "display", _mock_display) - g = cq_testing.GateHelper(cirq_ft.And(cv=(1, 1, 1))) - display_gate_and_compilation(g) - - (display_arg,) = call_args - assert isinstance(display_arg, ipywidgets.HBox) - assert len(display_arg.children) == 2 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_circuit_with_costs(): - g = cq_testing.GateHelper(cirq_ft.And(cv=(1, 1, 1))) - circuit = cirq_ft.infra.jupyter_tools.circuit_with_costs(g.circuit) - expected_circuit = cirq.Circuit(g.operation.with_tags('t:8,r:0')) - cirq.testing.assert_same_circuits(circuit, expected_circuit) diff --git a/cirq-ft/cirq_ft/infra/qubit_management_transformers.py b/cirq-ft/cirq_ft/infra/qubit_management_transformers.py deleted file mode 100644 index 517cadd3ccd..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_management_transformers.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 Optional - -import cirq - - -@cirq._compat.deprecated(deadline="v1.4", fix="Use cirq.map_clean_and_borrowable_qubits instead.") -def map_clean_and_borrowable_qubits( - circuit: cirq.AbstractCircuit, *, qm: Optional[cirq.QubitManager] = None -) -> cirq.Circuit: - """This method is deprecated. See docstring of `cirq.map_clean_and_borrowable_qubits`""" - return cirq.map_clean_and_borrowable_qubits(circuit, qm=qm) diff --git a/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py b/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py deleted file mode 100644 index 51557be9453..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft - - -def test_deprecated(): - with cirq.testing.assert_deprecated( - "Use cirq.map_clean_and_borrowable_qubits instead", deadline="v1.4" - ): - _ = cirq_ft.map_clean_and_borrowable_qubits(cirq.Circuit(), qm=cirq.SimpleQubitManager()) diff --git a/cirq-ft/cirq_ft/infra/qubit_manager.py b/cirq-ft/cirq_ft/infra/qubit_manager.py deleted file mode 100644 index 5b94b7a056d..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_manager.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq - - -@cirq._compat.deprecated_class(deadline="v1.4", fix="Use cirq.GreedyQubitManager instead.") -class GreedyQubitManager(cirq.GreedyQubitManager): - pass diff --git a/cirq-ft/cirq_ft/infra/qubit_manager_test.py b/cirq-ft/cirq_ft/infra/qubit_manager_test.py deleted file mode 100644 index e4e88290ff3..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_manager_test.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -from cirq_ft.infra.qubit_manager import GreedyQubitManager - - -def test_deprecated(): - with cirq.testing.assert_deprecated("Use cirq.GreedyQubitManager instead", deadline="v1.4"): - _ = GreedyQubitManager('ancilla') diff --git a/cirq-ft/cirq_ft/infra/t_complexity.ipynb b/cirq-ft/cirq_ft/infra/t_complexity.ipynb deleted file mode 100644 index 3697188b98e..00000000000 --- a/cirq-ft/cirq_ft/infra/t_complexity.ipynb +++ /dev/null @@ -1,174 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "51db731e", - "metadata": {}, - "outputs": [], - "source": [ - "# Copyright 2023 The Cirq Developers\n", - "#\n", - "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", - "# you may not use this file except in compliance with the License.\n", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", - "# See the License for the specific language governing permissions and\n", - "# limitations under the License." - ] - }, - { - "cell_type": "markdown", - "id": "33438ed0", - "metadata": {}, - "source": [ - "# T Complexity" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "abed0743", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "\n", - "from cirq_ft import And, t_complexity, infra" - ] - }, - { - "cell_type": "markdown", - "id": "29f4c77d", - "metadata": {}, - "source": [ - "## Two Qubit And Gate" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "635a411e", - "metadata": {}, - "outputs": [], - "source": [ - "# And of two qubits\n", - "gate = And() # create an And gate\n", - "# create an operation\n", - "operation = gate.on_registers(**infra.get_named_qubits(gate.signature))\n", - "# this operation doesn't directly support TComplexity but it's decomposable and its components are simple.\n", - "print(t_complexity(operation))" - ] - }, - { - "cell_type": "markdown", - "id": "88cfc5f4", - "metadata": {}, - "source": [ - "## Adjoint of two qubit And gate" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3c0301d9", - "metadata": {}, - "outputs": [], - "source": [ - "gate = And() ** -1 # adjoint of And\n", - "operation = gate.on_registers(**infra.get_named_qubits(gate.signature))\n", - "# the deomposition is H, measure, CZ, and Reset\n", - "print(t_complexity(operation))" - ] - }, - { - "cell_type": "markdown", - "id": "8e585436", - "metadata": {}, - "source": [ - "## And gate on n qubits" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a207ea1a", - "metadata": {}, - "outputs": [], - "source": [ - "n = 5\n", - "gate = And((1, )*n)\n", - "operation = gate.on_registers(**infra.get_named_qubits(gate.signature))\n", - "print(t_complexity(operation))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "394032a5", - "metadata": {}, - "outputs": [], - "source": [ - "def Generate(n_max: int = 10):\n", - " \"\"\"Returns the #T when the number of qubits is between 2 and n_max inclusive\"\"\"\n", - " n_controls = []\n", - " t_count = []\n", - " for n in range(2, n_max + 2):\n", - " n_controls.append(n)\n", - " gate = And(cv=(1, )*n)\n", - " op = gate.on_registers(**infra.get_named_qubits(gate.signature))\n", - " c = t_complexity(op)\n", - " t_count.append(c.t)\n", - " return n_controls, t_count" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "919ca418", - "metadata": {}, - "outputs": [], - "source": [ - "n_controls, t_count = Generate()\n", - "plt.plot(n_controls, t_count, label='T')\n", - "plt.ylabel('count')\n", - "plt.xlabel('number of qubits')\n", - "plt.title('And gate')\n", - "plt.legend()\n", - "plt.show()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.16" - }, - "vscode": { - "interpreter": { - "hash": "1882f3b63550a2f9350e6532bf63174910df57e92f62a2be07440f9e606398c8" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/infra/t_complexity_protocol.py b/cirq-ft/cirq_ft/infra/t_complexity_protocol.py deleted file mode 100644 index 52ed7137d59..00000000000 --- a/cirq-ft/cirq_ft/infra/t_complexity_protocol.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 Any, Callable, Hashable, Iterable, Optional, Union, overload - -import attr -import cachetools -import cirq -from cirq_ft.infra.decompose_protocol import _decompose_once_considering_known_decomposition -from typing_extensions import Literal, Protocol -from cirq_ft.deprecation import deprecated_cirq_ft_class, deprecated_cirq_ft_function - -_T_GATESET = cirq.Gateset(cirq.T, cirq.T**-1, unroll_circuit_op=False) - - -@deprecated_cirq_ft_class() -@attr.frozen -class TComplexity: - """Dataclass storing counts of logical T-gates, Clifford gates and single qubit rotations.""" - - t: int = 0 - clifford: int = 0 - rotations: int = 0 - - def __add__(self, other: 'TComplexity') -> 'TComplexity': - return TComplexity( - self.t + other.t, self.clifford + other.clifford, self.rotations + other.rotations - ) - - def __mul__(self, other: int) -> 'TComplexity': - return TComplexity(self.t * other, self.clifford * other, self.rotations * other) - - def __rmul__(self, other: int) -> 'TComplexity': - return self.__mul__(other) - - def __str__(self) -> str: - return ( - f'T-count: {self.t:g}\n' - f'Rotations: {self.rotations:g}\n' - f'Cliffords: {self.clifford:g}\n' - ) - - -class SupportsTComplexity(Protocol): - """An object whose TComplexity can be computed. - - An object whose TComplexity can be computed either implements the `_t_complexity_` function - or is of a type that SupportsDecompose. - """ - - def _t_complexity_(self) -> TComplexity: - """Returns the TComplexity.""" - - -def _has_t_complexity(stc: Any, fail_quietly: bool) -> Optional[TComplexity]: - """Returns TComplexity of stc by calling `stc._t_complexity_()` method, if it exists.""" - estimator = getattr(stc, '_t_complexity_', None) - if estimator is not None: - result = estimator() - if result is not NotImplemented: - return result - if isinstance(stc, cirq.Operation) and stc.gate is not None: - return _has_t_complexity(stc.gate, fail_quietly) - return None - - -def _is_clifford_or_t(stc: Any, fail_quietly: bool) -> Optional[TComplexity]: - """Attempts to infer the type of a gate/operation as one of clifford, T or Rotation.""" - if not isinstance(stc, (cirq.Gate, cirq.Operation)): - return None - - if isinstance(stc, cirq.ClassicallyControlledOperation): - stc = stc.without_classical_controls() - - if cirq.num_qubits(stc) <= 2 and cirq.has_stabilizer_effect(stc): - # Clifford operation. - return TComplexity(clifford=1) - - if stc in _T_GATESET: - # T-gate. - return TComplexity(t=1) # T gate - - if cirq.num_qubits(stc) == 1 and cirq.has_unitary(stc): - # Single qubit rotation operation. - return TComplexity(rotations=1) - return None - - -def _is_iterable(it: Any, fail_quietly: bool) -> Optional[TComplexity]: - if not isinstance(it, Iterable): - return None - t = TComplexity() - for v in it: - r = t_complexity(v, fail_quietly=fail_quietly) - if r is None: - return None - t = t + r - return t - - -def _from_decomposition(stc: Any, fail_quietly: bool) -> Optional[TComplexity]: - # Decompose the object and recursively compute the complexity. - decomposition = _decompose_once_considering_known_decomposition(stc) - if decomposition is None: - return None - return _is_iterable(decomposition, fail_quietly=fail_quietly) - - -def _get_hash(val: Any, fail_quietly: bool = False): - """Returns hash keys for caching a cirq.Operation and cirq.Gate. - - The hash of a cirq.Operation changes depending on its qubits, tags, - classical controls, and other properties it has, none of these properties - affect the TComplexity. - For gates and gate backed operations we intend to compute the hash of the - gate which is a property of the Gate. - - Args: - val: object to compute its hash. - - Returns: - - `val.gate` if `val` is a `cirq.Operation` which has an underlying `val.gate`. - - `val` otherwise - """ - if isinstance(val, cirq.Operation) and val.gate is not None: - val = val.gate - return val - - -def _t_complexity_from_strategies( - stc: Any, fail_quietly: bool, strategies: Iterable[Callable[[Any, bool], Optional[TComplexity]]] -): - ret = None - for strategy in strategies: - ret = strategy(stc, fail_quietly) - if ret is not None: - break - return ret - - -@cachetools.cached(cachetools.LRUCache(128), key=_get_hash, info=True) -def _t_complexity_for_gate_or_op( - gate_or_op: Union[cirq.Gate, cirq.Operation], fail_quietly: bool -) -> Optional[TComplexity]: - strategies = [_has_t_complexity, _is_clifford_or_t, _from_decomposition] - return _t_complexity_from_strategies(gate_or_op, fail_quietly, strategies) - - -@overload -def t_complexity(stc: Any, fail_quietly: Literal[False] = False) -> TComplexity: ... - - -@overload -def t_complexity(stc: Any, fail_quietly: bool) -> Optional[TComplexity]: ... - - -@deprecated_cirq_ft_function() -def t_complexity(stc: Any, fail_quietly: bool = False) -> Optional[TComplexity]: - """Returns the TComplexity. - - Args: - stc: an object to compute its TComplexity. - fail_quietly: bool whether to return None on failure or raise an error. - - Returns: - The TComplexity of the given object or None on failure (and fail_quietly=True). - - Raises: - TypeError: if fail_quietly=False and the methods fails to compute TComplexity. - """ - if isinstance(stc, (cirq.Gate, cirq.Operation)) and isinstance(stc, Hashable): - ret = _t_complexity_for_gate_or_op(stc, fail_quietly) - else: - strategies = [_has_t_complexity, _is_clifford_or_t, _from_decomposition, _is_iterable] - ret = _t_complexity_from_strategies(stc, fail_quietly, strategies) - - if ret is None and not fail_quietly: - raise TypeError("couldn't compute TComplexity of:\n" f"type: {type(stc)}\n" f"value: {stc}") - return ret - - -t_complexity.cache_clear = _t_complexity_for_gate_or_op.cache_clear # type: ignore[attr-defined] -t_complexity.cache_info = _t_complexity_for_gate_or_op.cache_info # type: ignore[attr-defined] -t_complexity.cache = _t_complexity_for_gate_or_op.cache # type: ignore[attr-defined] diff --git a/cirq-ft/cirq_ft/infra/t_complexity_protocol_test.py b/cirq-ft/cirq_ft/infra/t_complexity_protocol_test.py deleted file mode 100644 index 6512d6e5e27..00000000000 --- a/cirq-ft/cirq_ft/infra/t_complexity_protocol_test.py +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook - -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -class SupportTComplexity: - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1) - - -class DoesNotSupportTComplexity: ... - - -class SupportsTComplexityGateWithRegisters(cirq_ft.GateWithRegisters): - @property - def signature(self) -> cirq_ft.Signature: - return cirq_ft.Signature.build(s=1, t=2) - - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1, clifford=2) - - -class SupportTComplexityGate(cirq.Gate): - def _num_qubits_(self) -> int: - return 1 - - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1) - - -class DoesNotSupportTComplexityGate(cirq.Gate): - def _num_qubits_(self): - return 1 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(): - with pytest.raises(TypeError): - _ = cirq_ft.t_complexity(DoesNotSupportTComplexity()) - - with pytest.raises(TypeError): - _ = cirq_ft.t_complexity(DoesNotSupportTComplexityGate()) - assert cirq_ft.t_complexity(DoesNotSupportTComplexity(), fail_quietly=True) is None - assert cirq_ft.t_complexity([DoesNotSupportTComplexity()], fail_quietly=True) is None - assert cirq_ft.t_complexity(DoesNotSupportTComplexityGate(), fail_quietly=True) is None - - assert cirq_ft.t_complexity(SupportTComplexity()) == cirq_ft.TComplexity(t=1) - - g = cirq_ft.testing.GateHelper(SupportsTComplexityGateWithRegisters()) - assert g.gate._decompose_with_context_(g.operation.qubits) is NotImplemented - assert cirq_ft.t_complexity(g.gate) == cirq_ft.TComplexity(t=1, clifford=2) - assert cirq_ft.t_complexity(g.operation) == cirq_ft.TComplexity(t=1, clifford=2) - - assert cirq_ft.t_complexity([cirq.T, cirq.X]) == cirq_ft.TComplexity(t=1, clifford=1) - - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity([cirq.T(q), cirq.X(q)]) == cirq_ft.TComplexity(t=1, clifford=1) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_gates(): - # T gate and its adjoint - assert cirq_ft.t_complexity(cirq.T) == cirq_ft.TComplexity(t=1) - assert cirq_ft.t_complexity(cirq.T**-1) == cirq_ft.TComplexity(t=1) - - assert cirq_ft.t_complexity(cirq.H) == cirq_ft.TComplexity(clifford=1) # Hadamard - assert cirq_ft.t_complexity(cirq.CNOT) == cirq_ft.TComplexity(clifford=1) # CNOT - assert cirq_ft.t_complexity(cirq.S) == cirq_ft.TComplexity(clifford=1) # S - assert cirq_ft.t_complexity(cirq.S**-1) == cirq_ft.TComplexity(clifford=1) # S† - - # Pauli operators are clifford - assert cirq_ft.t_complexity(cirq.X) == cirq_ft.TComplexity(clifford=1) - assert cirq_ft.t_complexity(cirq.Y) == cirq_ft.TComplexity(clifford=1) - assert cirq_ft.t_complexity(cirq.Z) == cirq_ft.TComplexity(clifford=1) - - # Rotation about X, Y, and Z axes - assert cirq_ft.t_complexity(cirq.Rx(rads=2)) == cirq_ft.TComplexity(rotations=1) - assert cirq_ft.t_complexity(cirq.Ry(rads=2)) == cirq_ft.TComplexity(rotations=1) - assert cirq_ft.t_complexity(cirq.Rz(rads=2)) == cirq_ft.TComplexity(rotations=1) - - # clifford+T - assert cirq_ft.t_complexity(cirq_ft.And()) == cirq_ft.TComplexity(t=4, clifford=9) - assert cirq_ft.t_complexity(cirq_ft.And() ** -1) == cirq_ft.TComplexity(clifford=4) - - assert cirq_ft.t_complexity(cirq.FREDKIN) == cirq_ft.TComplexity(t=7, clifford=10) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_operations(): - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity(cirq.T(q)) == cirq_ft.TComplexity(t=1) - - gate = cirq_ft.And() - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - assert cirq_ft.t_complexity(op) == cirq_ft.TComplexity(t=4, clifford=9) - - gate = cirq_ft.And() ** -1 - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - assert cirq_ft.t_complexity(op) == cirq_ft.TComplexity(clifford=4) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_circuits(): - q = cirq.NamedQubit('q') - circuit = cirq.Circuit( - cirq.Rz(rads=0.6)(q), - cirq.T(q), - cirq.X(q) ** 0.5, - cirq.Rx(rads=0.1)(q), - cirq.Ry(rads=0.6)(q), - cirq.measure(q, key='m'), - ) - assert cirq_ft.t_complexity(circuit) == cirq_ft.TComplexity(clifford=2, rotations=3, t=1) - - circuit = cirq.FrozenCircuit(cirq.T(q) ** -1, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m')) - assert cirq_ft.t_complexity(circuit) == cirq_ft.TComplexity(clifford=1, rotations=1, t=1) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_circuit_operations(): - q = cirq.NamedQubit('q') - circuit = cirq.FrozenCircuit( - cirq.T(q), cirq.X(q) ** 0.5, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m') - ) - assert cirq_ft.t_complexity(cirq.CircuitOperation(circuit)) == cirq_ft.TComplexity( - clifford=2, rotations=1, t=1 - ) - assert cirq_ft.t_complexity( - cirq.CircuitOperation(circuit, repetitions=10) - ) == cirq_ft.TComplexity(clifford=20, rotations=10, t=10) - - circuit = cirq.FrozenCircuit(cirq.T(q) ** -1, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m')) - assert cirq_ft.t_complexity(cirq.CircuitOperation(circuit)) == cirq_ft.TComplexity( - clifford=1, rotations=1, t=1 - ) - assert cirq_ft.t_complexity( - cirq.CircuitOperation(circuit, repetitions=3) - ) == cirq_ft.TComplexity(clifford=3, rotations=3, t=3) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_classically_controlled_operations(): - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity(cirq.X(q).with_classical_controls('c')) == cirq_ft.TComplexity( - clifford=1 - ) - assert cirq_ft.t_complexity( - cirq.Rx(rads=0.1)(q).with_classical_controls('c') - ) == cirq_ft.TComplexity(rotations=1) - assert cirq_ft.t_complexity(cirq.T(q).with_classical_controls('c')) == cirq_ft.TComplexity(t=1) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_tagged_operations(): - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity(cirq.X(q).with_tags('tag1')) == cirq_ft.TComplexity(clifford=1) - assert cirq_ft.t_complexity(cirq.T(q).with_tags('tage1')) == cirq_ft.TComplexity(t=1) - assert cirq_ft.t_complexity( - cirq.Ry(rads=0.1)(q).with_tags('tag1', 'tag2') - ) == cirq_ft.TComplexity(rotations=1) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_cache_clear(): - class IsCachable(cirq.Operation): - def __init__(self) -> None: - super().__init__() - self.num_calls = 0 - self._gate = cirq.X - - def _t_complexity_(self) -> cirq_ft.TComplexity: - self.num_calls += 1 - return cirq_ft.TComplexity() - - @property - def qubits(self): - return [cirq.LineQubit(3)] # pragma: no cover - - def with_qubits(self, _): ... - - @property - def gate(self): - return self._gate - - assert cirq_ft.t_complexity(cirq.X) == cirq_ft.TComplexity(clifford=1) - # Using a global cache will result in a failure of this test since `cirq.X` has - # `T-complexity(clifford=1)` but we explicitly return `cirq_ft.TComplexity()` for IsCachable - # operation; for which the hash would be equivalent to the hash of it's subgate i.e. `cirq.X`. - cirq_ft.t_complexity.cache_clear() - op = IsCachable() - assert cirq_ft.t_complexity([op, op]) == cirq_ft.TComplexity() - assert op.num_calls == 1 - cirq_ft.t_complexity.cache_clear() - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('t_complexity') diff --git a/cirq-ft/cirq_ft/infra/testing.py b/cirq-ft/cirq_ft/infra/testing.py deleted file mode 100644 index a10b0877586..00000000000 --- a/cirq-ft/cirq_ft/infra/testing.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 dataclasses import dataclass -from functools import cached_property -from typing import Any, Dict, List, Sequence, Tuple -from numpy.typing import NDArray -import cirq -import numpy as np -from cirq_ft.infra import gate_with_registers, t_complexity_protocol, merge_qubits, get_named_qubits -from cirq_ft.infra.decompose_protocol import _decompose_once_considering_known_decomposition - - -@dataclass(frozen=True) -class GateHelper: - """A collection of related objects derivable from a `GateWithRegisters`. - - These are likely useful to have at one's fingertips while writing tests or - demo notebooks. - - Attributes: - gate: The gate from which all other objects are derived. - """ - - gate: gate_with_registers.GateWithRegisters - context: cirq.DecompositionContext = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - - @cached_property - def r(self) -> gate_with_registers.Signature: - """The Signature system for the gate.""" - return self.gate.signature - - @cached_property - def quregs(self) -> Dict[str, NDArray[cirq.Qid]]: # type: ignore[type-var] - """A dictionary of named qubits appropriate for the signature for the gate.""" - return get_named_qubits(self.r) - - @cached_property - def all_qubits(self) -> List[cirq.Qid]: - """All qubits in Register order.""" - merged_qubits = merge_qubits(self.r, **self.quregs) - decomposed_qubits = self.decomposed_circuit.all_qubits() - return merged_qubits + sorted(decomposed_qubits - frozenset(merged_qubits)) - - @cached_property - def operation(self) -> cirq.Operation: - """The `gate` applied to example qubits.""" - return self.gate.on_registers(**self.quregs) - - @cached_property - def circuit(self) -> cirq.Circuit: - """The `gate` applied to example qubits wrapped in a `cirq.Circuit`.""" - return cirq.Circuit(self.operation) - - @cached_property - def decomposed_circuit(self) -> cirq.Circuit: - """The `gate` applied to example qubits, decomposed and wrapped in a `cirq.Circuit`.""" - return cirq.Circuit(cirq.decompose(self.operation, context=self.context)) - - -def assert_circuit_inp_out_cirqsim( - circuit: cirq.AbstractCircuit, - qubit_order: Sequence[cirq.Qid], - inputs: Sequence[int], - outputs: Sequence[int], - decimals: int = 2, -): - """Use a Cirq simulator to test that `circuit` behaves correctly on an input. - - Args: - circuit: The circuit representing the reversible classical operation. - qubit_order: The qubit order to pass to the cirq simulator. - inputs: The input state bit values. - outputs: The (correct) output state bit values. - decimals: The number of decimals of precision to use when comparing - amplitudes. Reversible classical operations should produce amplitudes - that are 0 or 1. - """ - actual, should_be = get_circuit_inp_out_cirqsim(circuit, qubit_order, inputs, outputs, decimals) - assert actual == should_be - - -def get_circuit_inp_out_cirqsim( - circuit: cirq.AbstractCircuit, - qubit_order: Sequence[cirq.Qid], - inputs: Sequence[int], - outputs: Sequence[int], - decimals: int = 2, -) -> Tuple[str, str]: - """Use a Cirq simulator to get a outputs of a `circuit`. - - Args: - circuit: The circuit representing the reversible classical operation. - qubit_order: The qubit order to pass to the cirq simulator. - inputs: The input state bit values. - outputs: The (correct) output state bit values. - decimals: The number of decimals of precision to use when comparing - amplitudes. Reversible classical operations should produce amplitudes - that are 0 or 1. - - Returns: - actual: The simulated output state as a string bitstring. - should_be: The outputs argument formatted as a string bitstring for ease of comparison. - """ - result = cirq.Simulator(dtype=np.complex128).simulate( - circuit, initial_state=inputs, qubit_order=qubit_order - ) - actual = result.dirac_notation(decimals=decimals)[1:-1] - should_be = "".join(str(x) for x in outputs) - return actual, should_be - - -def assert_decompose_is_consistent_with_t_complexity(val: Any): - t_complexity_method = getattr(val, '_t_complexity_', None) - expected = NotImplemented if t_complexity_method is None else t_complexity_method() - if expected is NotImplemented or expected is None: - return - decomposition = _decompose_once_considering_known_decomposition(val) - if decomposition is None: - return - from_decomposition = t_complexity_protocol.t_complexity(decomposition, fail_quietly=False) - assert expected == from_decomposition, f'{expected} != {from_decomposition}' diff --git a/cirq-ft/cirq_ft/infra/testing_test.py b/cirq-ft/cirq_ft/infra/testing_test.py deleted file mode 100644 index 265642b195e..00000000000 --- a/cirq-ft/cirq_ft/infra/testing_test.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def test_assert_circuit_inp_out_cirqsim(): - qubits = cirq.LineQubit.range(4) - initial_state = [0, 1, 0, 0] - circuit = cirq.Circuit(cirq.X(qubits[3])) - final_state = [0, 1, 0, 1] - - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, qubits, initial_state, final_state) - - final_state = [0, 0, 0, 1] - with pytest.raises(AssertionError): - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, qubits, initial_state, final_state) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_gate_helper(): - g = cirq_ft.testing.GateHelper(cirq_ft.And(cv=(1, 0, 1, 0))) - assert g.gate == cirq_ft.And(cv=(1, 0, 1, 0)) - assert g.r == cirq_ft.Signature( - [ - cirq_ft.Register('ctrl', bitsize=1, shape=4), - cirq_ft.Register('junk', bitsize=1, shape=2, side=cirq_ft.infra.Side.RIGHT), - cirq_ft.Register('target', bitsize=1, side=cirq_ft.infra.Side.RIGHT), - ] - ) - expected_quregs = { - 'ctrl': np.array([[cirq.q(f'ctrl[{i}]')] for i in range(4)]), - 'junk': np.array([[cirq.q(f'junk[{i}]')] for i in range(2)]), - 'target': [cirq.NamedQubit('target')], - } - for key in expected_quregs: - assert np.array_equal(g.quregs[key], expected_quregs[key]) - assert g.operation.qubits == tuple(g.all_qubits) - assert len(g.circuit) == 1 - - -class DoesNotDecompose(cirq.Operation): - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1, clifford=2, rotations=3) - - @property - def qubits(self): - return [] - - def with_qubits(self, _): - pass - - -class InconsistentDecompostion(cirq.Operation): - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(rotations=1) - - def _decompose_(self) -> cirq.OP_TREE: - yield cirq.X(self.qubits[0]) - - @property - def qubits(self): - return tuple(cirq.LineQubit(3).range(3)) - - def with_qubits(self, _): - pass - - -@allow_deprecated_cirq_ft_use_in_tests -def test_assert_decompose_is_consistent_with_t_complexity(): - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(cirq.T) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(DoesNotDecompose()) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity( - cirq_ft.testing.GateHelper(cirq_ft.And()).operation - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_assert_decompose_is_consistent_with_t_complexity_raises(): - with pytest.raises(AssertionError): - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(InconsistentDecompostion()) diff --git a/cirq-ft/cirq_ft/linalg/__init__.py b/cirq-ft/cirq_ft/linalg/__init__.py deleted file mode 100644 index 45cd99c1114..00000000000 --- a/cirq-ft/cirq_ft/linalg/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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 cirq_ft.linalg.lcu_util import preprocess_lcu_coefficients_for_reversible_sampling diff --git a/cirq-ft/cirq_ft/linalg/lcu_util.py b/cirq-ft/cirq_ft/linalg/lcu_util.py deleted file mode 100644 index 6581c6cb35d..00000000000 --- a/cirq-ft/cirq_ft/linalg/lcu_util.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -"""Utility methods for LCU circuits as implemented in https://github.com/quantumlib/OpenFermion""" - -import math - - -def _partial_sums(vals): - """Adds up the items in the input, yielding partial sums along the way.""" - total = 0 - for v in vals: - yield total - total += v - yield total - - -def _differences(weights): - """Iterates over the input yielding differences between adjacent items.""" - previous_weight = None - have_previous_weight = False - for w in weights: - if have_previous_weight: - yield w - previous_weight - previous_weight = w - have_previous_weight = True - - -def _discretize_probability_distribution(unnormalized_probabilities, epsilon): - """Approximates probabilities with integers over a common denominator. - - Args: - unnormalized_probabilities: A list of non-negative floats proportional - to probabilities from a probability distribution. The numbers may - not be normalized (they do not have to add up to 1). - epsilon: The absolute error tolerance. - - Returns: - numerators (list[int]): A list of numerators for each probability. - denominator (int): The common denominator to divide numerators by to - get probabilities. - sub_bit_precision (int): The exponent mu such that - denominator = n * 2**mu - where n = len(unnormalized_probabilities). - - It is guaranteed that numerators[i] / denominator is within epsilon of - the i'th input probability (after normalization). - """ - n = len(unnormalized_probabilities) - sub_bit_precision = max(0, int(math.ceil(-math.log(epsilon * n, 2)))) - bin_count = 2**sub_bit_precision * n - - cumulative = list(_partial_sums(unnormalized_probabilities)) - total = cumulative[-1] - discretized_cumulative = [int(math.floor(c / total * bin_count + 0.5)) for c in cumulative] - discretized = list(_differences(discretized_cumulative)) - return discretized, bin_count, sub_bit_precision - - -def _preprocess_for_efficient_roulette_selection(discretized_probabilities): - """Prepares data for performing efficient roulette selection. - - The output is a tuple (alternates, keep_weights). The output is guaranteed - to satisfy a sampling-equivalence property. Specifically, the following - sampling process is guaranteed to be equivalent to simply picking index i - with probability weights[i] / sum(weights): - - 1. Pick a number i in [0, len(weights) - 1] uniformly at random. - 2. Return i With probability keep_weights[i]*len(weights)/sum(weights). - 3. Otherwise return alternates[i]. - - In other words, the output makes it possible to perform roulette selection - while generating only two random numbers, doing a single lookup of the - relevant (keep_chance, alternate) pair, and doing one comparison. This is - not so useful classically, but in the context of a quantum computation - where all those things are expensive the second sampling process is far - superior. - - Args: - discretized_probabilities: A list of probabilities approximated by - integer numerators (with an implied common denominator). In order - to operate without floating point error, it is required that the - sum of this list is a multiple of the number of items in the list. - - Returns: - alternates (list[int]): An alternate index for each index from 0 to - len(weights) - 1 - keep_weight (list[int]): Indicates how often one should stay at index i - instead of switching to alternates[i]. To get the actual keep - probability of the i'th element, multiply keep_weight[i] by - len(discretized_probabilities) then divide by - sum(discretized_probabilities). - Raises: - ValueError: if `discretized_probabilities` input is empty or if the sum of elements - in the list is not a multiple of the number of items in the list. - """ - weights = list(discretized_probabilities) # Need a copy we can mutate. - if not weights: - raise ValueError('Empty input.') - - n = len(weights) - target_weight = sum(weights) // n - if sum(weights) != n * target_weight: - raise ValueError('sum(weights) must be a multiple of len(weights).') - - # Initially, every item's alternative is itself. - alternates = list(range(n)) - keep_weights = [0] * n - - # Scan for needy items and donors. First pass will handle all - # initially-needy items. Second pass will handle any remaining items that - # started as donors but become needy due to over-donation (though some may - # also be handled during the first pass). - donor_position = 0 - for _ in range(2): - for i in range(n): - # Is this a needy item? - if weights[i] >= target_weight: - continue # Nope. - - # Find a donor. - while weights[donor_position] <= target_weight: - donor_position += 1 - - # Donate. - donated = target_weight - weights[i] - weights[donor_position] -= donated - alternates[i] = donor_position - keep_weights[i] = weights[i] - - # Needy item has been paired. Remove it from consideration. - weights[i] = target_weight - - return alternates, keep_weights - - -def preprocess_lcu_coefficients_for_reversible_sampling(lcu_coefficients, epsilon): - """Prepares data used to perform efficient reversible roulette selection. - - Treats the coefficients of unitaries in the linear combination of - unitaries decomposition of the Hamiltonian as probabilities in order to - decompose them into a list of alternate and keep numerators allowing for - an efficient preparation method of a state where the computational basis - state :math. `|k>` has an amplitude proportional to the coefficient. - - It is guaranteed that following the following sampling process will - sample each index k with a probability within epsilon of - lcu_coefficients[k] / sum(lcu_coefficients) and also, - 1. Uniformly sample an index i from [0, len(lcu_coefficients) - 1]. - 2. With probability keep_numers[i] / by keep_denom, return i. - 3. Otherwise return alternates[i]. - - Args: - lcu_coefficients: A list of non-negative floats, with the i'th float - corresponding to the i'th coefficient of an LCU decomposition - of the Hamiltonian (in an ordering determined by the caller). - epsilon: Absolute error tolerance. - - Returns: - alternates (list[int]): A python list of ints indicating alternative - indices that may be switched to after generating a uniform index. - The int at offset k is the alternate to use when the initial index - is k. - keep_numers (list[int]): A python list of ints indicating the - numerators of the probability that the alternative index should be - used instead of the initial index. - sub_bit_precision (int): A python int indicating the exponent of the - denominator to divide the items in keep_numers by in order to get - a probability. The actual denominator is 2**sub_bit_precision. - """ - numers, denom, sub_bit_precision = _discretize_probability_distribution( - lcu_coefficients, epsilon - ) - assert denom == 2**sub_bit_precision * len(numers) - alternates, keep_numers = _preprocess_for_efficient_roulette_selection(numers) - return alternates, keep_numers, sub_bit_precision diff --git a/cirq-ft/cirq_ft/linalg/lcu_util_test.py b/cirq-ft/cirq_ft/linalg/lcu_util_test.py deleted file mode 100644 index bb724d30626..00000000000 --- a/cirq-ft/cirq_ft/linalg/lcu_util_test.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -"""Tests for lcu_util.py.""" - -import random -import unittest - -from cirq_ft.linalg.lcu_util import ( - _discretize_probability_distribution, - _preprocess_for_efficient_roulette_selection, - preprocess_lcu_coefficients_for_reversible_sampling, -) - - -class DiscretizeDistributionTest(unittest.TestCase): - def assertGetDiscretizedDistribution(self, probabilities, epsilon): - total_probability = sum(probabilities) - numers, denom, mu = _discretize_probability_distribution(probabilities, epsilon) - self.assertEqual(sum(numers), denom) - self.assertEqual(len(numers), len(probabilities)) - self.assertEqual(len(probabilities) * 2**mu, denom) - for i in range(len(numers)): - self.assertAlmostEqual( - numers[i] / denom, probabilities[i] / total_probability, delta=epsilon - ) - return numers, denom - - def test_fuzz(self): - random.seed(8) - for _ in range(100): - n = random.randint(1, 50) - weights = [random.random() for _ in range(n)] - self.assertGetDiscretizedDistribution(weights, 2 ** -random.randint(1, 20)) - - def test_known_discretizations(self): - self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.25), ([4], 4)) - - self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.125), ([8], 8)) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.1, 0.1, 0.1], 0.25), ([2, 2, 2], 6) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.25), ([2, 2, 2], 6) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.1), ([4, 4, 4], 12) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.05), ([7, 9, 8], 24) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.01), ([58, 70, 64], 192) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.00335), - ([115, 141, 128], 384), - ) - - -class PreprocessForEfficientRouletteSelectionTest(unittest.TestCase): - def assertPreprocess(self, weights): - alternates, keep_chances = _preprocess_for_efficient_roulette_selection(weights) - - self.assertEqual(len(alternates), len(keep_chances)) - - target_weight = sum(weights) // len(alternates) - distribution = list(keep_chances) - for i in range(len(alternates)): - distribution[alternates[i]] += target_weight - keep_chances[i] - self.assertEqual(weights, distribution) - - return alternates, keep_chances - - def test_fuzz(self): - random.seed(8) - for _ in range(100): - n = random.randint(1, 50) - weights = [random.randint(0, 100) for _ in range(n)] - weights[-1] += n - sum(weights) % n # Ensure multiple of length. - self.assertPreprocess(weights) - - def test_validation(self): - with self.assertRaises(ValueError): - _ = self.assertPreprocess(weights=[]) - with self.assertRaises(ValueError): - _ = self.assertPreprocess(weights=[1, 2]) - with self.assertRaises(ValueError): - _ = self.assertPreprocess(weights=[3, 3, 2]) - - def test_already_uniform(self): - self.assertEqual(self.assertPreprocess(weights=[1]), ([0], [0])) - self.assertEqual(self.assertPreprocess(weights=[1, 1]), ([0, 1], [0, 0])) - self.assertEqual(self.assertPreprocess(weights=[1, 1, 1]), ([0, 1, 2], [0, 0, 0])) - self.assertEqual(self.assertPreprocess(weights=[2, 2, 2]), ([0, 1, 2], [0, 0, 0])) - - def test_donation(self): - # v2 donates 1 to v0. - self.assertEqual(self.assertPreprocess(weights=[1, 2, 3]), ([2, 1, 2], [1, 0, 0])) - # v0 donates 1 to v1. - self.assertEqual(self.assertPreprocess(weights=[3, 1, 2]), ([0, 0, 2], [0, 1, 0])) - # v0 donates 1 to v1, then 2 to v2. - self.assertEqual(self.assertPreprocess(weights=[5, 1, 0]), ([0, 0, 0], [0, 1, 0])) - - def test_over_donation(self): - # v0 donates 2 to v1, leaving v0 needy, then v2 donates 1 to v0. - self.assertEqual(self.assertPreprocess(weights=[3, 0, 3]), ([2, 0, 2], [1, 0, 0])) - - -class PreprocessLCUCoefficientsForReversibleSamplingTest(unittest.TestCase): - def assertPreprocess(self, lcu_coefs, epsilon): - alternates, keep_numers, mu = preprocess_lcu_coefficients_for_reversible_sampling( - lcu_coefs, epsilon - ) - - n = len(lcu_coefs) - keep_denom = 2**mu - self.assertEqual(len(alternates), n) - self.assertEqual(len(keep_numers), n) - self.assertTrue(all(0 <= e < keep_denom for e in keep_numers)) - - out_distribution = [1 / n * numer / keep_denom for numer in keep_numers] - for i in range(n): - switch_probability = 1 - keep_numers[i] / keep_denom - out_distribution[alternates[i]] += 1 / n * switch_probability - - total = sum(lcu_coefs) - for i in range(n): - self.assertAlmostEqual(out_distribution[i], lcu_coefs[i] / total, delta=epsilon) - - return alternates, keep_numers, keep_denom - - def test_fuzz(self): - random.seed(8) - for _ in range(100): - n = random.randint(1, 50) - weights = [random.randint(0, 100) for _ in range(n)] - weights[-1] += n - sum(weights) % n # Ensure multiple of length. - self.assertPreprocess(weights, 2 ** -random.randint(1, 20)) - - def test_known(self): - self.assertEqual(self.assertPreprocess([1, 2], epsilon=0.01), ([1, 1], [43, 0], 64)) - - self.assertEqual( - self.assertPreprocess([1, 2, 3], epsilon=0.01), ([2, 1, 2], [32, 0, 0], 64) - ) diff --git a/cirq-ft/requirements.txt b/cirq-ft/requirements.txt deleted file mode 100644 index e0eb53a8fb3..00000000000 --- a/cirq-ft/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -attrs -cachetools>=5.3 -ipywidgets -nbconvert -nbformat diff --git a/cirq-ft/setup.py b/cirq-ft/setup.py deleted file mode 100644 index f02d227043a..00000000000 --- a/cirq-ft/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. - -import pathlib -import os - -from setuptools import find_packages, setup - -# This reads the __version__ variable from cirq/_version.py -__version__ = '' -exec(pathlib.Path('cirq_ft/_version.py').read_text(encoding='utf-8')) - -name = 'cirq-ft' - -description = 'A Cirq package for fault-tolerant algorithms' - -# README file as long_description. -long_description = pathlib.Path('README.rst').read_text(encoding='utf-8') - -# If CIRQ_PRE_RELEASE_VERSION is set then we update the version to this value. -# It is assumed that it ends with one of `.devN`, `.aN`, `.bN`, `.rcN` and hence -# it will be a pre-release version on PyPi. See -# https://packaging.python.org/guides/distributing-packages-using-setuptools/#pre-release-versioning -# for more details. -if 'CIRQ_PRE_RELEASE_VERSION' in os.environ: - __version__ = os.environ['CIRQ_PRE_RELEASE_VERSION'] - long_description = ( - "**This is a development version of Cirq-ft and may be " - "unstable.**\n\n**For the latest stable release of Cirq-ft " - "see**\n`here `__.\n\n" + long_description - ) - -# Read in requirements -requirements = pathlib.Path('requirements.txt').read_text(encoding='utf-8').split('\n') -requirements = [r.strip() for r in requirements] - -# Sanity check -assert __version__, 'Version string cannot be empty' - -requirements += [f'cirq-core=={__version__}'] - -cirq_packages = ['cirq_ft'] + [f'cirq_ft.{package}' for package in find_packages(where='cirq_ft')] - - -setup( - name=name, - version=__version__, - url='http://github.com/quantumlib/cirq', - author='The Cirq Developers', - author_email='cirq-dev@googlegroups.com', - python_requires='>=3.10.0', - install_requires=requirements, - license='Apache 2', - description=description, - long_description=long_description, - packages=cirq_packages, - package_data={'cirq_ft': ['py.typed']}, -) diff --git a/cirq-google/cirq_google/__init__.py b/cirq-google/cirq_google/__init__.py index 68cf6fb0a40..89e48b3bacb 100644 --- a/cirq-google/cirq_google/__init__.py +++ b/cirq-google/cirq_google/__init__.py @@ -15,7 +15,6 @@ """Classes for working with Google's Quantum Engine API.""" import sys -from cirq import _compat from cirq_google import api from cirq_google._version import __version__ @@ -114,26 +113,3 @@ from cirq_google.json_resolver_cache import _class_resolver_dictionary _register_resolver(_class_resolver_dictionary) - - -_SERIALIZABLE_GATESET_DEPRECATION_MESSAGE = ( - 'SerializableGateSet and associated classes (GateOpSerializer, GateOpDeserializer,' - ' SerializingArgs, DeserializingArgs) will no longer be supported.' - ' In cirq_google.GridDevice, the new representation of Google devices, the gateset of a device' - ' is represented as a cirq.Gateset and is available as' - ' GridDevice.metadata.gateset.' - ' Engine methods no longer require gate sets to be passed in.' - ' In addition, circuit serialization is replaced by cirq_google.CircuitSerializer.' -) - - -_compat.deprecate_attributes( - __name__, - { - 'XMON': ('v0.16', _SERIALIZABLE_GATESET_DEPRECATION_MESSAGE), - 'FSIM_GATESET': ('v0.16', _SERIALIZABLE_GATESET_DEPRECATION_MESSAGE), - 'SQRT_ISWAP_GATESET': ('v0.16', _SERIALIZABLE_GATESET_DEPRECATION_MESSAGE), - 'SYC_GATESET': ('v0.16', _SERIALIZABLE_GATESET_DEPRECATION_MESSAGE), - 'NAMED_GATESETS': ('v0.16', _SERIALIZABLE_GATESET_DEPRECATION_MESSAGE), - }, -) diff --git a/cirq-google/cirq_google/_version.py b/cirq-google/cirq_google/_version.py index 4190ae6fef5..4e5d0f3872f 100644 --- a/cirq-google/cirq_google/_version.py +++ b/cirq-google/cirq_google/_version.py @@ -28,4 +28,4 @@ 'of cirq (e.g. "python -m pip install cirq==1.1.*")' ) -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-google/cirq_google/_version_test.py b/cirq-google/cirq_google/_version_test.py index 24835d2215e..2e928d66abb 100644 --- a/cirq-google/cirq_google/_version_test.py +++ b/cirq-google/cirq_google/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq_google.__version__ == "1.4.0.dev" + assert cirq_google.__version__ == "1.5.0.dev" diff --git a/cirq-google/cirq_google/api/v2/device.proto b/cirq-google/cirq_google/api/v2/device.proto index 65b779e7046..da0d6a6c426 100644 --- a/cirq-google/cirq_google/api/v2/device.proto +++ b/cirq-google/cirq_google/api/v2/device.proto @@ -58,6 +58,7 @@ message GateSpecification { Wait wait = 11; FSimViaModel fsim_via_model = 12; CZPowGate cz_pow_gate = 13; + InternalGate internal_gate = 14; } // Gate types available to Google devices. @@ -76,6 +77,9 @@ message GateSpecification { message Wait {} message FSimViaModel {} message CZPowGate {} + // This gate gets mapped to the internal representation corresponding + // to . + message InternalGate {} } message GateSet { diff --git a/cirq-google/cirq_google/api/v2/device_pb2.py b/cirq-google/cirq_google/api/v2/device_pb2.py index b1ce40e172b..23f7899852f 100644 --- a/cirq-google/cirq_google/api/v2/device_pb2.py +++ b/cirq-google/cirq_google/api/v2/device_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x63irq_google/api/v2/device.proto\x12\x12\x63irq.google.api.v2\"\xfa\x01\n\x13\x44\x65viceSpecification\x12\x38\n\x0fvalid_gate_sets\x18\x01 \x03(\x0b\x32\x1b.cirq.google.api.v2.GateSetB\x02\x18\x01\x12:\n\x0bvalid_gates\x18\x05 \x03(\x0b\x32%.cirq.google.api.v2.GateSpecification\x12\x14\n\x0cvalid_qubits\x18\x02 \x03(\t\x12\x34\n\rvalid_targets\x18\x03 \x03(\x0b\x32\x1d.cirq.google.api.v2.TargetSet\x12!\n\x19\x64\x65veloper_recommendations\x18\x04 \x01(\t\"\xa1\x08\n\x11GateSpecification\x12\x1b\n\x13gate_duration_picos\x18\x01 \x01(\x03\x12=\n\x03syc\x18\x02 \x01(\x0b\x32..cirq.google.api.v2.GateSpecification.SycamoreH\x00\x12\x45\n\nsqrt_iswap\x18\x03 \x01(\x0b\x32/.cirq.google.api.v2.GateSpecification.SqrtISwapH\x00\x12L\n\x0esqrt_iswap_inv\x18\x04 \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.SqrtISwapInvH\x00\x12\x36\n\x02\x63z\x18\x05 \x01(\x0b\x32(.cirq.google.api.v2.GateSpecification.CZH\x00\x12\x43\n\tphased_xz\x18\x06 \x01(\x0b\x32..cirq.google.api.v2.GateSpecification.PhasedXZH\x00\x12I\n\x0cvirtual_zpow\x18\x07 \x01(\x0b\x32\x31.cirq.google.api.v2.GateSpecification.VirtualZPowH\x00\x12K\n\rphysical_zpow\x18\x08 \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.PhysicalZPowH\x00\x12K\n\rcoupler_pulse\x18\t \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.CouplerPulseH\x00\x12\x41\n\x04meas\x18\n \x01(\x0b\x32\x31.cirq.google.api.v2.GateSpecification.MeasurementH\x00\x12:\n\x04wait\x18\x0b \x01(\x0b\x32*.cirq.google.api.v2.GateSpecification.WaitH\x00\x12L\n\x0e\x66sim_via_model\x18\x0c \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.FSimViaModelH\x00\x12\x46\n\x0b\x63z_pow_gate\x18\r \x01(\x0b\x32/.cirq.google.api.v2.GateSpecification.CZPowGateH\x00\x1a\n\n\x08Sycamore\x1a\x0b\n\tSqrtISwap\x1a\x0e\n\x0cSqrtISwapInv\x1a\x04\n\x02\x43Z\x1a\n\n\x08PhasedXZ\x1a\r\n\x0bVirtualZPow\x1a\x0e\n\x0cPhysicalZPow\x1a\x0e\n\x0c\x43ouplerPulse\x1a\r\n\x0bMeasurement\x1a\x06\n\x04Wait\x1a\x0e\n\x0c\x46SimViaModel\x1a\x0b\n\tCZPowGateB\x06\n\x04gate\"P\n\x07GateSet\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x0bvalid_gates\x18\x02 \x03(\x0b\x32\".cirq.google.api.v2.GateDefinition\"\xa1\x01\n\x0eGateDefinition\x12\n\n\x02id\x18\x01 \x01(\t\x12\x18\n\x10number_of_qubits\x18\x02 \x01(\x05\x12\x35\n\nvalid_args\x18\x03 \x03(\x0b\x32!.cirq.google.api.v2.ArgDefinition\x12\x1b\n\x13gate_duration_picos\x18\x04 \x01(\x03\x12\x15\n\rvalid_targets\x18\x05 \x03(\t\"\xda\x01\n\rArgDefinition\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x04type\x18\x02 \x01(\x0e\x32).cirq.google.api.v2.ArgDefinition.ArgType\x12\x39\n\x0e\x61llowed_ranges\x18\x03 \x03(\x0b\x32!.cirq.google.api.v2.ArgumentRange\"G\n\x07\x41rgType\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\t\n\x05\x46LOAT\x10\x01\x12\x14\n\x10REPEATED_BOOLEAN\x10\x02\x12\n\n\x06STRING\x10\x03\"=\n\rArgumentRange\x12\x15\n\rminimum_value\x18\x01 \x01(\x02\x12\x15\n\rmaximum_value\x18\x02 \x01(\x02\"\xef\x01\n\tTargetSet\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x45\n\x0ftarget_ordering\x18\x02 \x01(\x0e\x32,.cirq.google.api.v2.TargetSet.TargetOrdering\x12+\n\x07targets\x18\x03 \x03(\x0b\x32\x1a.cirq.google.api.v2.Target\"`\n\x0eTargetOrdering\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\r\n\tSYMMETRIC\x10\x01\x12\x12\n\nASYMMETRIC\x10\x02\x1a\x02\x08\x01\x12\x1a\n\x12SUBSET_PERMUTATION\x10\x03\x1a\x02\x08\x01\"\x15\n\x06Target\x12\x0b\n\x03ids\x18\x01 \x03(\tB.\n\x1d\x63om.google.cirq.google.api.v2B\x0b\x44\x65viceProtoP\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x63irq_google/api/v2/device.proto\x12\x12\x63irq.google.api.v2\"\xfa\x01\n\x13\x44\x65viceSpecification\x12\x38\n\x0fvalid_gate_sets\x18\x01 \x03(\x0b\x32\x1b.cirq.google.api.v2.GateSetB\x02\x18\x01\x12:\n\x0bvalid_gates\x18\x05 \x03(\x0b\x32%.cirq.google.api.v2.GateSpecification\x12\x14\n\x0cvalid_qubits\x18\x02 \x03(\t\x12\x34\n\rvalid_targets\x18\x03 \x03(\x0b\x32\x1d.cirq.google.api.v2.TargetSet\x12!\n\x19\x64\x65veloper_recommendations\x18\x04 \x01(\t\"\xfe\x08\n\x11GateSpecification\x12\x1b\n\x13gate_duration_picos\x18\x01 \x01(\x03\x12=\n\x03syc\x18\x02 \x01(\x0b\x32..cirq.google.api.v2.GateSpecification.SycamoreH\x00\x12\x45\n\nsqrt_iswap\x18\x03 \x01(\x0b\x32/.cirq.google.api.v2.GateSpecification.SqrtISwapH\x00\x12L\n\x0esqrt_iswap_inv\x18\x04 \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.SqrtISwapInvH\x00\x12\x36\n\x02\x63z\x18\x05 \x01(\x0b\x32(.cirq.google.api.v2.GateSpecification.CZH\x00\x12\x43\n\tphased_xz\x18\x06 \x01(\x0b\x32..cirq.google.api.v2.GateSpecification.PhasedXZH\x00\x12I\n\x0cvirtual_zpow\x18\x07 \x01(\x0b\x32\x31.cirq.google.api.v2.GateSpecification.VirtualZPowH\x00\x12K\n\rphysical_zpow\x18\x08 \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.PhysicalZPowH\x00\x12K\n\rcoupler_pulse\x18\t \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.CouplerPulseH\x00\x12\x41\n\x04meas\x18\n \x01(\x0b\x32\x31.cirq.google.api.v2.GateSpecification.MeasurementH\x00\x12:\n\x04wait\x18\x0b \x01(\x0b\x32*.cirq.google.api.v2.GateSpecification.WaitH\x00\x12L\n\x0e\x66sim_via_model\x18\x0c \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.FSimViaModelH\x00\x12\x46\n\x0b\x63z_pow_gate\x18\r \x01(\x0b\x32/.cirq.google.api.v2.GateSpecification.CZPowGateH\x00\x12K\n\rinternal_gate\x18\x0e \x01(\x0b\x32\x32.cirq.google.api.v2.GateSpecification.InternalGateH\x00\x1a\n\n\x08Sycamore\x1a\x0b\n\tSqrtISwap\x1a\x0e\n\x0cSqrtISwapInv\x1a\x04\n\x02\x43Z\x1a\n\n\x08PhasedXZ\x1a\r\n\x0bVirtualZPow\x1a\x0e\n\x0cPhysicalZPow\x1a\x0e\n\x0c\x43ouplerPulse\x1a\r\n\x0bMeasurement\x1a\x06\n\x04Wait\x1a\x0e\n\x0c\x46SimViaModel\x1a\x0b\n\tCZPowGate\x1a\x0e\n\x0cInternalGateB\x06\n\x04gate\"P\n\x07GateSet\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x0bvalid_gates\x18\x02 \x03(\x0b\x32\".cirq.google.api.v2.GateDefinition\"\xa1\x01\n\x0eGateDefinition\x12\n\n\x02id\x18\x01 \x01(\t\x12\x18\n\x10number_of_qubits\x18\x02 \x01(\x05\x12\x35\n\nvalid_args\x18\x03 \x03(\x0b\x32!.cirq.google.api.v2.ArgDefinition\x12\x1b\n\x13gate_duration_picos\x18\x04 \x01(\x03\x12\x15\n\rvalid_targets\x18\x05 \x03(\t\"\xda\x01\n\rArgDefinition\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x04type\x18\x02 \x01(\x0e\x32).cirq.google.api.v2.ArgDefinition.ArgType\x12\x39\n\x0e\x61llowed_ranges\x18\x03 \x03(\x0b\x32!.cirq.google.api.v2.ArgumentRange\"G\n\x07\x41rgType\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\t\n\x05\x46LOAT\x10\x01\x12\x14\n\x10REPEATED_BOOLEAN\x10\x02\x12\n\n\x06STRING\x10\x03\"=\n\rArgumentRange\x12\x15\n\rminimum_value\x18\x01 \x01(\x02\x12\x15\n\rmaximum_value\x18\x02 \x01(\x02\"\xef\x01\n\tTargetSet\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x45\n\x0ftarget_ordering\x18\x02 \x01(\x0e\x32,.cirq.google.api.v2.TargetSet.TargetOrdering\x12+\n\x07targets\x18\x03 \x03(\x0b\x32\x1a.cirq.google.api.v2.Target\"`\n\x0eTargetOrdering\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\r\n\tSYMMETRIC\x10\x01\x12\x12\n\nASYMMETRIC\x10\x02\x1a\x02\x08\x01\x12\x1a\n\x12SUBSET_PERMUTATION\x10\x03\x1a\x02\x08\x01\"\x15\n\x06Target\x12\x0b\n\x03ids\x18\x01 \x03(\tB.\n\x1d\x63om.google.cirq.google.api.v2B\x0b\x44\x65viceProtoP\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -30,45 +30,47 @@ _globals['_DEVICESPECIFICATION']._serialized_start=56 _globals['_DEVICESPECIFICATION']._serialized_end=306 _globals['_GATESPECIFICATION']._serialized_start=309 - _globals['_GATESPECIFICATION']._serialized_end=1366 - _globals['_GATESPECIFICATION_SYCAMORE']._serialized_start=1202 - _globals['_GATESPECIFICATION_SYCAMORE']._serialized_end=1212 - _globals['_GATESPECIFICATION_SQRTISWAP']._serialized_start=1214 - _globals['_GATESPECIFICATION_SQRTISWAP']._serialized_end=1225 - _globals['_GATESPECIFICATION_SQRTISWAPINV']._serialized_start=1227 - _globals['_GATESPECIFICATION_SQRTISWAPINV']._serialized_end=1241 - _globals['_GATESPECIFICATION_CZ']._serialized_start=1243 - _globals['_GATESPECIFICATION_CZ']._serialized_end=1247 - _globals['_GATESPECIFICATION_PHASEDXZ']._serialized_start=1249 - _globals['_GATESPECIFICATION_PHASEDXZ']._serialized_end=1259 - _globals['_GATESPECIFICATION_VIRTUALZPOW']._serialized_start=1261 - _globals['_GATESPECIFICATION_VIRTUALZPOW']._serialized_end=1274 - _globals['_GATESPECIFICATION_PHYSICALZPOW']._serialized_start=1276 - _globals['_GATESPECIFICATION_PHYSICALZPOW']._serialized_end=1290 - _globals['_GATESPECIFICATION_COUPLERPULSE']._serialized_start=1292 - _globals['_GATESPECIFICATION_COUPLERPULSE']._serialized_end=1306 - _globals['_GATESPECIFICATION_MEASUREMENT']._serialized_start=1308 - _globals['_GATESPECIFICATION_MEASUREMENT']._serialized_end=1321 - _globals['_GATESPECIFICATION_WAIT']._serialized_start=1323 - _globals['_GATESPECIFICATION_WAIT']._serialized_end=1329 - _globals['_GATESPECIFICATION_FSIMVIAMODEL']._serialized_start=1331 - _globals['_GATESPECIFICATION_FSIMVIAMODEL']._serialized_end=1345 - _globals['_GATESPECIFICATION_CZPOWGATE']._serialized_start=1347 - _globals['_GATESPECIFICATION_CZPOWGATE']._serialized_end=1358 - _globals['_GATESET']._serialized_start=1368 - _globals['_GATESET']._serialized_end=1448 - _globals['_GATEDEFINITION']._serialized_start=1451 - _globals['_GATEDEFINITION']._serialized_end=1612 - _globals['_ARGDEFINITION']._serialized_start=1615 - _globals['_ARGDEFINITION']._serialized_end=1833 - _globals['_ARGDEFINITION_ARGTYPE']._serialized_start=1762 - _globals['_ARGDEFINITION_ARGTYPE']._serialized_end=1833 - _globals['_ARGUMENTRANGE']._serialized_start=1835 - _globals['_ARGUMENTRANGE']._serialized_end=1896 - _globals['_TARGETSET']._serialized_start=1899 - _globals['_TARGETSET']._serialized_end=2138 - _globals['_TARGETSET_TARGETORDERING']._serialized_start=2042 - _globals['_TARGETSET_TARGETORDERING']._serialized_end=2138 - _globals['_TARGET']._serialized_start=2140 - _globals['_TARGET']._serialized_end=2161 + _globals['_GATESPECIFICATION']._serialized_end=1459 + _globals['_GATESPECIFICATION_SYCAMORE']._serialized_start=1279 + _globals['_GATESPECIFICATION_SYCAMORE']._serialized_end=1289 + _globals['_GATESPECIFICATION_SQRTISWAP']._serialized_start=1291 + _globals['_GATESPECIFICATION_SQRTISWAP']._serialized_end=1302 + _globals['_GATESPECIFICATION_SQRTISWAPINV']._serialized_start=1304 + _globals['_GATESPECIFICATION_SQRTISWAPINV']._serialized_end=1318 + _globals['_GATESPECIFICATION_CZ']._serialized_start=1320 + _globals['_GATESPECIFICATION_CZ']._serialized_end=1324 + _globals['_GATESPECIFICATION_PHASEDXZ']._serialized_start=1326 + _globals['_GATESPECIFICATION_PHASEDXZ']._serialized_end=1336 + _globals['_GATESPECIFICATION_VIRTUALZPOW']._serialized_start=1338 + _globals['_GATESPECIFICATION_VIRTUALZPOW']._serialized_end=1351 + _globals['_GATESPECIFICATION_PHYSICALZPOW']._serialized_start=1353 + _globals['_GATESPECIFICATION_PHYSICALZPOW']._serialized_end=1367 + _globals['_GATESPECIFICATION_COUPLERPULSE']._serialized_start=1369 + _globals['_GATESPECIFICATION_COUPLERPULSE']._serialized_end=1383 + _globals['_GATESPECIFICATION_MEASUREMENT']._serialized_start=1385 + _globals['_GATESPECIFICATION_MEASUREMENT']._serialized_end=1398 + _globals['_GATESPECIFICATION_WAIT']._serialized_start=1400 + _globals['_GATESPECIFICATION_WAIT']._serialized_end=1406 + _globals['_GATESPECIFICATION_FSIMVIAMODEL']._serialized_start=1408 + _globals['_GATESPECIFICATION_FSIMVIAMODEL']._serialized_end=1422 + _globals['_GATESPECIFICATION_CZPOWGATE']._serialized_start=1424 + _globals['_GATESPECIFICATION_CZPOWGATE']._serialized_end=1435 + _globals['_GATESPECIFICATION_INTERNALGATE']._serialized_start=1437 + _globals['_GATESPECIFICATION_INTERNALGATE']._serialized_end=1451 + _globals['_GATESET']._serialized_start=1461 + _globals['_GATESET']._serialized_end=1541 + _globals['_GATEDEFINITION']._serialized_start=1544 + _globals['_GATEDEFINITION']._serialized_end=1705 + _globals['_ARGDEFINITION']._serialized_start=1708 + _globals['_ARGDEFINITION']._serialized_end=1926 + _globals['_ARGDEFINITION_ARGTYPE']._serialized_start=1855 + _globals['_ARGDEFINITION_ARGTYPE']._serialized_end=1926 + _globals['_ARGUMENTRANGE']._serialized_start=1928 + _globals['_ARGUMENTRANGE']._serialized_end=1989 + _globals['_TARGETSET']._serialized_start=1992 + _globals['_TARGETSET']._serialized_end=2231 + _globals['_TARGETSET_TARGETORDERING']._serialized_start=2135 + _globals['_TARGETSET_TARGETORDERING']._serialized_end=2231 + _globals['_TARGET']._serialized_start=2233 + _globals['_TARGET']._serialized_end=2254 # @@protoc_insertion_point(module_scope) diff --git a/cirq-google/cirq_google/api/v2/device_pb2.pyi b/cirq-google/cirq_google/api/v2/device_pb2.pyi index 800a39ec770..40a833175ff 100644 --- a/cirq-google/cirq_google/api/v2/device_pb2.pyi +++ b/cirq-google/cirq_google/api/v2/device_pb2.pyi @@ -183,6 +183,18 @@ class GateSpecification(google.protobuf.message.Message): self, ) -> None: ... + @typing_extensions.final + class InternalGate(google.protobuf.message.Message): + """This gate gets mapped to the internal representation corresponding + to . + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + def __init__( + self, + ) -> None: ... + GATE_DURATION_PICOS_FIELD_NUMBER: builtins.int SYC_FIELD_NUMBER: builtins.int SQRT_ISWAP_FIELD_NUMBER: builtins.int @@ -196,6 +208,7 @@ class GateSpecification(google.protobuf.message.Message): WAIT_FIELD_NUMBER: builtins.int FSIM_VIA_MODEL_FIELD_NUMBER: builtins.int CZ_POW_GATE_FIELD_NUMBER: builtins.int + INTERNAL_GATE_FIELD_NUMBER: builtins.int gate_duration_picos: builtins.int """This defines the approximate duration to run the gate on the device, specified as an integer number of picoseconds. @@ -224,6 +237,8 @@ class GateSpecification(google.protobuf.message.Message): def fsim_via_model(self) -> global___GateSpecification.FSimViaModel: ... @property def cz_pow_gate(self) -> global___GateSpecification.CZPowGate: ... + @property + def internal_gate(self) -> global___GateSpecification.InternalGate: ... def __init__( self, *, @@ -240,10 +255,11 @@ class GateSpecification(google.protobuf.message.Message): wait: global___GateSpecification.Wait | None = ..., fsim_via_model: global___GateSpecification.FSimViaModel | None = ..., cz_pow_gate: global___GateSpecification.CZPowGate | None = ..., + internal_gate: global___GateSpecification.InternalGate | None = ..., ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["coupler_pulse", b"coupler_pulse", "cz", b"cz", "cz_pow_gate", b"cz_pow_gate", "fsim_via_model", b"fsim_via_model", "gate", b"gate", "meas", b"meas", "phased_xz", b"phased_xz", "physical_zpow", b"physical_zpow", "sqrt_iswap", b"sqrt_iswap", "sqrt_iswap_inv", b"sqrt_iswap_inv", "syc", b"syc", "virtual_zpow", b"virtual_zpow", "wait", b"wait"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["coupler_pulse", b"coupler_pulse", "cz", b"cz", "cz_pow_gate", b"cz_pow_gate", "fsim_via_model", b"fsim_via_model", "gate", b"gate", "gate_duration_picos", b"gate_duration_picos", "meas", b"meas", "phased_xz", b"phased_xz", "physical_zpow", b"physical_zpow", "sqrt_iswap", b"sqrt_iswap", "sqrt_iswap_inv", b"sqrt_iswap_inv", "syc", b"syc", "virtual_zpow", b"virtual_zpow", "wait", b"wait"]) -> None: ... - def WhichOneof(self, oneof_group: typing_extensions.Literal["gate", b"gate"]) -> typing_extensions.Literal["syc", "sqrt_iswap", "sqrt_iswap_inv", "cz", "phased_xz", "virtual_zpow", "physical_zpow", "coupler_pulse", "meas", "wait", "fsim_via_model", "cz_pow_gate"] | None: ... + def HasField(self, field_name: typing_extensions.Literal["coupler_pulse", b"coupler_pulse", "cz", b"cz", "cz_pow_gate", b"cz_pow_gate", "fsim_via_model", b"fsim_via_model", "gate", b"gate", "internal_gate", b"internal_gate", "meas", b"meas", "phased_xz", b"phased_xz", "physical_zpow", b"physical_zpow", "sqrt_iswap", b"sqrt_iswap", "sqrt_iswap_inv", b"sqrt_iswap_inv", "syc", b"syc", "virtual_zpow", b"virtual_zpow", "wait", b"wait"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["coupler_pulse", b"coupler_pulse", "cz", b"cz", "cz_pow_gate", b"cz_pow_gate", "fsim_via_model", b"fsim_via_model", "gate", b"gate", "gate_duration_picos", b"gate_duration_picos", "internal_gate", b"internal_gate", "meas", b"meas", "phased_xz", b"phased_xz", "physical_zpow", b"physical_zpow", "sqrt_iswap", b"sqrt_iswap", "sqrt_iswap_inv", b"sqrt_iswap_inv", "syc", b"syc", "virtual_zpow", b"virtual_zpow", "wait", b"wait"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["gate", b"gate"]) -> typing_extensions.Literal["syc", "sqrt_iswap", "sqrt_iswap_inv", "cz", "phased_xz", "virtual_zpow", "physical_zpow", "coupler_pulse", "meas", "wait", "fsim_via_model", "cz_pow_gate", "internal_gate"] | None: ... global___GateSpecification = GateSpecification diff --git a/cirq-google/cirq_google/api/v2/program.proto b/cirq-google/cirq_google/api/v2/program.proto index a1ca71ace36..f493fdcca4f 100644 --- a/cirq-google/cirq_google/api/v2/program.proto +++ b/cirq-google/cirq_google/api/v2/program.proto @@ -462,4 +462,4 @@ message IdentityGate { message HPowGate { FloatArg exponent = 1; -} \ No newline at end of file +} diff --git a/cirq-google/cirq_google/api/v2/run_context.proto b/cirq-google/cirq_google/api/v2/run_context.proto index e3722cb652a..1e7fc630bd0 100644 --- a/cirq-google/cirq_google/api/v2/run_context.proto +++ b/cirq-google/cirq_google/api/v2/run_context.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "cirq_google/api/v2/program.proto"; + package cirq.google.api.v2; option java_package = "com.google.cirq.google.api.v2"; @@ -10,6 +12,13 @@ option java_multiple_files = true; message RunContext { // The parameters for operations in a program. repeated ParameterSweep parameter_sweeps = 1; + + // Optional override of select device parameters before program + // execution. Note it is permissible to specify the same device parameter + // here and in a parameter_sweeps, as sweep.single_sweep.parameter. + // If the same parameter is supplied in both places, the provision here in + // device_parameters_override will have no effect. + DeviceParametersDiff device_parameters_override = 2; } // Specifies how to repeatedly sample a circuit, with or without sweeping over @@ -100,6 +109,41 @@ message DeviceParameter { // by the sweep values themselves. } +// A bundle of multiple DeviceParameters and their values. +// The main use case is to set those parameters with the +// values from this bundle before executing a circuit sweep. +// Note multiple device parameters may have common ancestor paths +// and/or share the same parameter names. A DeviceParametersDiff +// stores the resource groups hierarchy extracted from the DeviceParameters' +// paths and maintains a table of strings; thereby storing ancestor resource +// groups only once, and avoiding repeated storage of common parameter names. +message DeviceParametersDiff { + // A resource group a device parameter belongs to. + // The identifier of a resource group is DeviceParameter.path without the + // last component. + message ResourceGroup { + // parent resource group, as an index into the groups repeated field. + int32 parent = 1; + // as index into the strs repeated field. + int32 name = 2; + } + message Param { + // the resource group hosting this parameter key, as index into groups + // repeated field. + int32 resource_group = 1; + // name of this param, as index into the strs repeated field. + int32 name = 2; + // this param's new value, as message ArgValue to allow variant types, + // including bool, string, double, float and arrays. + ArgValue value = 3; + } + repeated ResourceGroup groups = 1; + repeated Param params = 2; + + // List of all key, dir, and deletion names in these contents. + // ResourceGroup.name, Param.name, and Deletion.name are indexes into this list. + repeated string strs = 4; +} // A set of values to loop over for a particular parameter. message SingleSweep { diff --git a/cirq-google/cirq_google/api/v2/run_context.py b/cirq-google/cirq_google/api/v2/run_context.py new file mode 100644 index 00000000000..1e458699569 --- /dev/null +++ b/cirq-google/cirq_google/api/v2/run_context.py @@ -0,0 +1,69 @@ +# Copyright 2024 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. + +import functools +from typing import Sequence +from cirq_google.api.v2 import program_pb2 +from cirq_google.api.v2 import run_context_pb2 + + +# The special index of an empty directory path []. +_EMPTY_RESOURCE_PATH_IDX = -1 + + +def to_device_parameters_diff( + device_params: Sequence[tuple[run_context_pb2.DeviceParameter, program_pb2.ArgValue]] +) -> run_context_pb2.DeviceParametersDiff: + """Constructs a DeviceParametersDiff from multiple DeviceParameters and values. + + Args: + device_params: a list of (DeviceParameter, value) pairs. + + Returns: + a DeviceParametersDiff, which comprises the entire device_params. + """ + diff = run_context_pb2.DeviceParametersDiff() + + @functools.lru_cache(maxsize=None) + def token_id(s: str) -> int: + """Computes the index of s in the string table diff.strs.""" + idx = len(diff.strs) + diff.strs.append(s) + return idx + + # Maps a resource group path to its index in diff.groups. + resource_groups_index: dict[tuple[str, ...], int] = {tuple(): _EMPTY_RESOURCE_PATH_IDX} + + def resource_path_id(path: tuple[str, ...]) -> int: + """Computes the index of a path in diff.groups.""" + idx = resource_groups_index.get(path, None) + if idx is not None: + return idx + # Recursive call to get the assigned index of the parent. Note the base case + # of the empty path, which returns _EMPTY_RESOURCE_PATH_IDX. + parent_id = resource_path_id(path[:-1]) + # This path has not been seen. It will be appended to diff.groups, with idx as + # the size of diff.groups before appending. + idx = len(diff.groups) + diff.groups.add(parent=parent_id, name=token_id(path[-1])) + resource_groups_index[path] = idx + return idx + + for device_param, value in device_params: + resource_path = tuple(device_param.path[:-1]) + param_name = device_param.path[-1] + path_id = resource_path_id(resource_path) + diff.params.add(name=token_id(param_name), resource_group=path_id, value=value) + + return diff diff --git a/cirq-google/cirq_google/api/v2/run_context_pb2.py b/cirq-google/cirq_google/api/v2/run_context_pb2.py index ca9991c2816..2bd0ab8d74d 100644 --- a/cirq-google/cirq_google/api/v2/run_context_pb2.py +++ b/cirq-google/cirq_google/api/v2/run_context_pb2.py @@ -11,32 +11,40 @@ _sym_db = _symbol_database.Default() +from . import program_pb2 as cirq__google_dot_api_dot_v2_dot_program__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$cirq_google/api/v2/run_context.proto\x12\x12\x63irq.google.api.v2\"J\n\nRunContext\x12<\n\x10parameter_sweeps\x18\x01 \x03(\x0b\x32\".cirq.google.api.v2.ParameterSweep\"O\n\x0eParameterSweep\x12\x13\n\x0brepetitions\x18\x01 \x01(\x05\x12(\n\x05sweep\x18\x02 \x01(\x0b\x32\x19.cirq.google.api.v2.Sweep\"\x86\x01\n\x05Sweep\x12;\n\x0esweep_function\x18\x01 \x01(\x0b\x32!.cirq.google.api.v2.SweepFunctionH\x00\x12\x37\n\x0csingle_sweep\x18\x02 \x01(\x0b\x32\x1f.cirq.google.api.v2.SingleSweepH\x00\x42\x07\n\x05sweep\"\xc6\x01\n\rSweepFunction\x12\x45\n\rfunction_type\x18\x01 \x01(\x0e\x32..cirq.google.api.v2.SweepFunction.FunctionType\x12)\n\x06sweeps\x18\x02 \x03(\x0b\x32\x19.cirq.google.api.v2.Sweep\"C\n\x0c\x46unctionType\x12\x1d\n\x19\x46UNCTION_TYPE_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRODUCT\x10\x01\x12\x07\n\x03ZIP\x10\x02\"W\n\x0f\x44\x65viceParameter\x12\x0c\n\x04path\x18\x01 \x03(\t\x12\x10\n\x03idx\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x12\n\x05units\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04_idxB\x08\n\x06_units\"\xc5\x01\n\x0bSingleSweep\x12\x15\n\rparameter_key\x18\x01 \x01(\t\x12,\n\x06points\x18\x02 \x01(\x0b\x32\x1a.cirq.google.api.v2.PointsH\x00\x12\x30\n\x08linspace\x18\x03 \x01(\x0b\x32\x1c.cirq.google.api.v2.LinspaceH\x00\x12\x36\n\tparameter\x18\x04 \x01(\x0b\x32#.cirq.google.api.v2.DeviceParameterB\x07\n\x05sweep\"\x18\n\x06Points\x12\x0e\n\x06points\x18\x01 \x03(\x02\"G\n\x08Linspace\x12\x13\n\x0b\x66irst_point\x18\x01 \x01(\x02\x12\x12\n\nlast_point\x18\x02 \x01(\x02\x12\x12\n\nnum_points\x18\x03 \x01(\x03\x42\x32\n\x1d\x63om.google.cirq.google.api.v2B\x0fRunContextProtoP\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$cirq_google/api/v2/run_context.proto\x12\x12\x63irq.google.api.v2\x1a cirq_google/api/v2/program.proto\"\x98\x01\n\nRunContext\x12<\n\x10parameter_sweeps\x18\x01 \x03(\x0b\x32\".cirq.google.api.v2.ParameterSweep\x12L\n\x1a\x64\x65vice_parameters_override\x18\x02 \x01(\x0b\x32(.cirq.google.api.v2.DeviceParametersDiff\"O\n\x0eParameterSweep\x12\x13\n\x0brepetitions\x18\x01 \x01(\x05\x12(\n\x05sweep\x18\x02 \x01(\x0b\x32\x19.cirq.google.api.v2.Sweep\"\x86\x01\n\x05Sweep\x12;\n\x0esweep_function\x18\x01 \x01(\x0b\x32!.cirq.google.api.v2.SweepFunctionH\x00\x12\x37\n\x0csingle_sweep\x18\x02 \x01(\x0b\x32\x1f.cirq.google.api.v2.SingleSweepH\x00\x42\x07\n\x05sweep\"\xc6\x01\n\rSweepFunction\x12\x45\n\rfunction_type\x18\x01 \x01(\x0e\x32..cirq.google.api.v2.SweepFunction.FunctionType\x12)\n\x06sweeps\x18\x02 \x03(\x0b\x32\x19.cirq.google.api.v2.Sweep\"C\n\x0c\x46unctionType\x12\x1d\n\x19\x46UNCTION_TYPE_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRODUCT\x10\x01\x12\x07\n\x03ZIP\x10\x02\"W\n\x0f\x44\x65viceParameter\x12\x0c\n\x04path\x18\x01 \x03(\t\x12\x10\n\x03idx\x18\x02 \x01(\x03H\x00\x88\x01\x01\x12\x12\n\x05units\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04_idxB\x08\n\x06_units\"\xb7\x02\n\x14\x44\x65viceParametersDiff\x12\x46\n\x06groups\x18\x01 \x03(\x0b\x32\x36.cirq.google.api.v2.DeviceParametersDiff.ResourceGroup\x12>\n\x06params\x18\x02 \x03(\x0b\x32..cirq.google.api.v2.DeviceParametersDiff.Param\x12\x0c\n\x04strs\x18\x04 \x03(\t\x1a-\n\rResourceGroup\x12\x0e\n\x06parent\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\x05\x1aZ\n\x05Param\x12\x16\n\x0eresource_group\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\x05\x12+\n\x05value\x18\x03 \x01(\x0b\x32\x1c.cirq.google.api.v2.ArgValue\"\xc5\x01\n\x0bSingleSweep\x12\x15\n\rparameter_key\x18\x01 \x01(\t\x12,\n\x06points\x18\x02 \x01(\x0b\x32\x1a.cirq.google.api.v2.PointsH\x00\x12\x30\n\x08linspace\x18\x03 \x01(\x0b\x32\x1c.cirq.google.api.v2.LinspaceH\x00\x12\x36\n\tparameter\x18\x04 \x01(\x0b\x32#.cirq.google.api.v2.DeviceParameterB\x07\n\x05sweep\"\x18\n\x06Points\x12\x0e\n\x06points\x18\x01 \x03(\x02\"G\n\x08Linspace\x12\x13\n\x0b\x66irst_point\x18\x01 \x01(\x02\x12\x12\n\nlast_point\x18\x02 \x01(\x02\x12\x12\n\nnum_points\x18\x03 \x01(\x03\x42\x32\n\x1d\x63om.google.cirq.google.api.v2B\x0fRunContextProtoP\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'cirq_google.api.v2.run_context_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None DESCRIPTOR._serialized_options = b'\n\035com.google.cirq.google.api.v2B\017RunContextProtoP\001' - _globals['_RUNCONTEXT']._serialized_start=60 - _globals['_RUNCONTEXT']._serialized_end=134 - _globals['_PARAMETERSWEEP']._serialized_start=136 - _globals['_PARAMETERSWEEP']._serialized_end=215 - _globals['_SWEEP']._serialized_start=218 - _globals['_SWEEP']._serialized_end=352 - _globals['_SWEEPFUNCTION']._serialized_start=355 - _globals['_SWEEPFUNCTION']._serialized_end=553 - _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_start=486 - _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_end=553 - _globals['_DEVICEPARAMETER']._serialized_start=555 - _globals['_DEVICEPARAMETER']._serialized_end=642 - _globals['_SINGLESWEEP']._serialized_start=645 - _globals['_SINGLESWEEP']._serialized_end=842 - _globals['_POINTS']._serialized_start=844 - _globals['_POINTS']._serialized_end=868 - _globals['_LINSPACE']._serialized_start=870 - _globals['_LINSPACE']._serialized_end=941 + _globals['_RUNCONTEXT']._serialized_start=95 + _globals['_RUNCONTEXT']._serialized_end=247 + _globals['_PARAMETERSWEEP']._serialized_start=249 + _globals['_PARAMETERSWEEP']._serialized_end=328 + _globals['_SWEEP']._serialized_start=331 + _globals['_SWEEP']._serialized_end=465 + _globals['_SWEEPFUNCTION']._serialized_start=468 + _globals['_SWEEPFUNCTION']._serialized_end=666 + _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_start=599 + _globals['_SWEEPFUNCTION_FUNCTIONTYPE']._serialized_end=666 + _globals['_DEVICEPARAMETER']._serialized_start=668 + _globals['_DEVICEPARAMETER']._serialized_end=755 + _globals['_DEVICEPARAMETERSDIFF']._serialized_start=758 + _globals['_DEVICEPARAMETERSDIFF']._serialized_end=1069 + _globals['_DEVICEPARAMETERSDIFF_RESOURCEGROUP']._serialized_start=932 + _globals['_DEVICEPARAMETERSDIFF_RESOURCEGROUP']._serialized_end=977 + _globals['_DEVICEPARAMETERSDIFF_PARAM']._serialized_start=979 + _globals['_DEVICEPARAMETERSDIFF_PARAM']._serialized_end=1069 + _globals['_SINGLESWEEP']._serialized_start=1072 + _globals['_SINGLESWEEP']._serialized_end=1269 + _globals['_POINTS']._serialized_start=1271 + _globals['_POINTS']._serialized_end=1295 + _globals['_LINSPACE']._serialized_start=1297 + _globals['_LINSPACE']._serialized_end=1368 # @@protoc_insertion_point(module_scope) diff --git a/cirq-google/cirq_google/api/v2/run_context_pb2.pyi b/cirq-google/cirq_google/api/v2/run_context_pb2.pyi index 75afaa133e1..9946ffecceb 100644 --- a/cirq-google/cirq_google/api/v2/run_context_pb2.pyi +++ b/cirq-google/cirq_google/api/v2/run_context_pb2.pyi @@ -3,6 +3,7 @@ isort:skip_file """ import builtins +import cirq_google.api.v2.program_pb2 import collections.abc import google.protobuf.descriptor import google.protobuf.internal.containers @@ -25,15 +26,26 @@ class RunContext(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor PARAMETER_SWEEPS_FIELD_NUMBER: builtins.int + DEVICE_PARAMETERS_OVERRIDE_FIELD_NUMBER: builtins.int @property def parameter_sweeps(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___ParameterSweep]: """The parameters for operations in a program.""" + @property + def device_parameters_override(self) -> global___DeviceParametersDiff: + """Optional override of select device parameters before program + execution. Note it is permissible to specify the same device parameter + here and in a parameter_sweeps, as sweep.single_sweep.parameter. + If the same parameter is supplied in both places, the provision here in + device_parameters_override will have no effect. + """ def __init__( self, *, parameter_sweeps: collections.abc.Iterable[global___ParameterSweep] | None = ..., + device_parameters_override: global___DeviceParametersDiff | None = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["parameter_sweeps", b"parameter_sweeps"]) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["device_parameters_override", b"device_parameters_override"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["device_parameters_override", b"device_parameters_override", "parameter_sweeps", b"parameter_sweeps"]) -> None: ... global___RunContext = RunContext @@ -226,6 +238,94 @@ class DeviceParameter(google.protobuf.message.Message): global___DeviceParameter = DeviceParameter +@typing_extensions.final +class DeviceParametersDiff(google.protobuf.message.Message): + """A bundle of multiple DeviceParameters and their values. + The main use case is to set those parameters with the + values from this bundle before executing a circuit sweep. + Note multiple device parameters may have common ancestor paths + and/or share the same parameter names. A DeviceParametersDiff + stores the resource groups hierarchy extracted from the DeviceParameters' + paths and maintains a table of strings; thereby storing ancestor resource + groups only once, and avoiding repeated storage of common parameter names. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class ResourceGroup(google.protobuf.message.Message): + """A resource group a device parameter belongs to. + The identifier of a resource group is DeviceParameter.path without the + last component. + """ + + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + PARENT_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + parent: builtins.int + """parent resource group, as an index into the groups repeated field.""" + name: builtins.int + """as index into the strs repeated field.""" + def __init__( + self, + *, + parent: builtins.int = ..., + name: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "parent", b"parent"]) -> None: ... + + @typing_extensions.final + class Param(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + RESOURCE_GROUP_FIELD_NUMBER: builtins.int + NAME_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + resource_group: builtins.int + """the resource group hosting this parameter key, as index into groups + repeated field. + """ + name: builtins.int + """name of this param, as index into the strs repeated field.""" + @property + def value(self) -> cirq_google.api.v2.program_pb2.ArgValue: + """this param's new value, as message ArgValue to allow variant types, + including bool, string, double, float and arrays. + """ + def __init__( + self, + *, + resource_group: builtins.int = ..., + name: builtins.int = ..., + value: cirq_google.api.v2.program_pb2.ArgValue | None = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value", b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["name", b"name", "resource_group", b"resource_group", "value", b"value"]) -> None: ... + + GROUPS_FIELD_NUMBER: builtins.int + PARAMS_FIELD_NUMBER: builtins.int + STRS_FIELD_NUMBER: builtins.int + @property + def groups(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___DeviceParametersDiff.ResourceGroup]: ... + @property + def params(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___DeviceParametersDiff.Param]: ... + @property + def strs(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.str]: + """List of all key, dir, and deletion names in these contents. + ResourceGroup.name, Param.name, and Deletion.name are indexes into this list. + """ + def __init__( + self, + *, + groups: collections.abc.Iterable[global___DeviceParametersDiff.ResourceGroup] | None = ..., + params: collections.abc.Iterable[global___DeviceParametersDiff.Param] | None = ..., + strs: collections.abc.Iterable[builtins.str] | None = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["groups", b"groups", "params", b"params", "strs", b"strs"]) -> None: ... + +global___DeviceParametersDiff = DeviceParametersDiff + @typing_extensions.final class SingleSweep(google.protobuf.message.Message): """A set of values to loop over for a particular parameter.""" diff --git a/cirq-google/cirq_google/api/v2/run_context_test.py b/cirq-google/cirq_google/api/v2/run_context_test.py new file mode 100644 index 00000000000..0d3168e7fe0 --- /dev/null +++ b/cirq-google/cirq_google/api/v2/run_context_test.py @@ -0,0 +1,164 @@ +# Copyright 2024 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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 cirq_google.api.v2 import program_pb2 +from cirq_google.api.v2 import run_context_pb2 +import cirq_google.api.v2.run_context as run_context +import google.protobuf.text_format as text_format + + +def test_converting_multiple_device_params_to_device_parameters_diff() -> None: + """Test of converting a list of DeviceParameter's to a DeviceParametersDiff object.""" + readout_paths = (["q3_4", "readout_default"], ["q5_6", "readout_default"]) + + device_params = [] + for readout_path in readout_paths: + device_params.extend( + [ + ( + run_context_pb2.DeviceParameter( + path=[*readout_path, "readoutDemodDelay"], units="ns" + ), + program_pb2.ArgValue(float_value=5.0), + ), + ( + run_context_pb2.DeviceParameter(path=[*readout_path, "readoutFidelities"]), + program_pb2.ArgValue( + double_values=program_pb2.RepeatedDouble(values=[0.991, 0.993]) + ), + ), + ( + run_context_pb2.DeviceParameter(path=[*readout_path, "demod", "phase_i_rad"]), + program_pb2.ArgValue(double_value=0.0), + ), + ] + ) + diff = run_context.to_device_parameters_diff(device_params) + expected_diff_pb_text = """ + groups { + parent: -1 + } + groups { + parent: 0 + name: 1 + } + groups { + parent: 1 + name: 4 + } + groups { + parent: -1 + name: 6 + } + groups { + parent: 3 + name: 1 + } + groups { + parent: 4 + name: 4 + } + params { + resource_group: 1 + name: 2 + value { + float_value: 5 + } + } + params { + resource_group: 1 + name: 3 + value { + double_values { + values: 0.991 + values: 0.993 + } + } + } + params { + resource_group: 2 + name: 5 + value { + double_value: 0 + } + } + params { + resource_group: 4 + name: 2 + value { + float_value: 5 + } + } + params { + resource_group: 4 + name: 3 + value { + double_values { + values: 0.991 + values: 0.993 + } + } + } + params { + resource_group: 5 + name: 5 + value { + double_value: 0 + } + } + strs: "q3_4" + strs: "readout_default" + strs: "readoutDemodDelay" + strs: "readoutFidelities" + strs: "demod" + strs: "phase_i_rad" + strs: "q5_6" + """ + print(diff) + assert text_format.Parse(expected_diff_pb_text, run_context_pb2.DeviceParametersDiff()) == diff + + +def test_converting_to_device_parameters_diff_token_id_caching_is_correct() -> None: + """Test that multiple calling of run_context.to_device_parameters_diff gives + correct token id assignments. + """ + device_params = [ + ( + run_context_pb2.DeviceParameter( + path=["q1_2", "readout_default", "readoutDemodDelay"], units="ns" + ), + program_pb2.ArgValue(float_value=5.0), + ) + ] + + diff = run_context.to_device_parameters_diff(device_params) + expected_diff_pb_text = """ + groups { + parent: -1 + } + groups { + name: 1 + } + params { + resource_group: 1 + name: 2 + value { + float_value: 5 + } + } + strs: "q1_2" + strs: "readout_default" + strs: "readoutDemodDelay" + """ + assert text_format.Parse(expected_diff_pb_text, run_context_pb2.DeviceParametersDiff()) == diff diff --git a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/async_client.py b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/async_client.py index b7379df2024..029ee919fe1 100644 --- a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/async_client.py +++ b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/async_client.py @@ -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] @@ -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, diff --git a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/client.py b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/client.py index f71838a8f11..b45ab99c300 100644 --- a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/client.py +++ b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/client.py @@ -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] diff --git a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/base.py b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/base.py index a4f489e3437..202c3bb8f4f 100644 --- a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/base.py +++ b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/base.py @@ -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 @@ -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, diff --git a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc.py b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc.py index 42854490b78..c49efb1fc09 100644 --- a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc.py +++ b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc.py @@ -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 diff --git a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc_asyncio.py b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc_asyncio.py index 8223fec9e6d..c0c1f337742 100644 --- a/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc_asyncio.py +++ b/cirq-google/cirq_google/cloud/quantum_v1alpha1/services/quantum_engine_service/transports/grpc_asyncio.py @@ -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, @@ -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, @@ -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 diff --git a/cirq-google/cirq_google/devices/grid_device.py b/cirq-google/cirq_google/devices/grid_device.py index 8bf823eac31..a9ebdc206bd 100644 --- a/cirq-google/cirq_google/devices/grid_device.py +++ b/cirq-google/cirq_google/devices/grid_device.py @@ -164,6 +164,9 @@ class _GateRepresentations: gate_spec_name='fsim_via_model', supported_gates=[cirq.GateFamily(cirq.FSimGate, tags_to_accept=[ops.FSimViaModelTag()])], ), + _GateRepresentations( + gate_spec_name='internal_gate', supported_gates=[cirq.GateFamily(ops.InternalGate)] + ), ] diff --git a/cirq-google/cirq_google/devices/grid_device_test.py b/cirq-google/cirq_google/devices/grid_device_test.py index eebee6fc6cb..e2284044e3f 100644 --- a/cirq-google/cirq_google/devices/grid_device_test.py +++ b/cirq-google/cirq_google/devices/grid_device_test.py @@ -79,6 +79,7 @@ def _create_device_spec_with_horizontal_couplings(): 'wait', 'fsim_via_model', 'cz_pow_gate', + 'internal_gate', ] gate_durations = [(n, i * 1000) for i, n in enumerate(gate_names)] for gate_name, duration in sorted(gate_durations): @@ -113,6 +114,7 @@ def _create_device_spec_with_horizontal_couplings(): cirq.GateFamily(cirq.ops.wait_gate.WaitGate), cirq.GateFamily(cirq.ops.FSimGate, tags_to_accept=[cirq_google.FSimViaModelTag()]), cirq.GateFamily(cirq.CZPowGate), + cirq.GateFamily(cirq_google.InternalGate), ) base_duration = cirq.Duration(picos=1_000) @@ -148,6 +150,7 @@ def _create_device_spec_with_horizontal_couplings(): ): base_duration * 10, cirq.GateFamily(cirq.CZPowGate): base_duration * 11, + cirq.GateFamily(cirq_google.InternalGate): base_duration * 12, } expected_target_gatesets = ( @@ -175,6 +178,7 @@ def _create_device_spec_with_horizontal_couplings(): cirq_google.experimental.ops.coupler_pulse.CouplerPulse, cirq.ops.wait_gate.WaitGate, cirq.GateFamily(cirq.ops.FSimGate, tags_to_accept=[cirq_google.FSimViaModelTag()]), + cirq.GateFamily(cirq_google.InternalGate), ] ), cirq_google.SycamoreTargetGateset(), @@ -202,6 +206,7 @@ def _create_device_spec_with_horizontal_couplings(): cirq_google.experimental.ops.coupler_pulse.CouplerPulse, cirq.ops.wait_gate.WaitGate, cirq.GateFamily(cirq.ops.FSimGate, tags_to_accept=[cirq_google.FSimViaModelTag()]), + cirq.GateFamily(cirq_google.InternalGate), ] ), cirq.CZTargetGateset( @@ -230,6 +235,7 @@ def _create_device_spec_with_horizontal_couplings(): cirq_google.experimental.ops.coupler_pulse.CouplerPulse, cirq.ops.wait_gate.WaitGate, cirq.GateFamily(cirq.ops.FSimGate, tags_to_accept=[cirq_google.FSimViaModelTag()]), + cirq.GateFamily(cirq_google.InternalGate), ], ), ) @@ -548,6 +554,7 @@ def test_device_from_device_information_equals_device_from_proto(): cirq.ops.measurement_gate.MeasurementGate, cirq.ops.wait_gate.WaitGate, cirq.GateFamily(cirq.ops.FSimGate, tags_to_accept=[cirq_google.FSimViaModelTag()]), + cirq.GateFamily(cirq_google.InternalGate), ) base_duration = cirq.Duration(picos=1_000) @@ -573,6 +580,7 @@ def test_device_from_device_information_equals_device_from_proto(): ): base_duration * 10, cirq.GateFamily(cirq.CZPowGate): base_duration * 11, + cirq.GateFamily(cirq_google.InternalGate): base_duration * 12, } device_from_information = cirq_google.GridDevice._from_device_information( @@ -680,6 +688,7 @@ def test_to_proto(): ): base_duration * 10, cirq.GateFamily(cirq.CZPowGate): base_duration * 11, + cirq.GateFamily(cirq_google.InternalGate): base_duration * 12, } spec = cirq_google.GridDevice._from_device_information( diff --git a/cirq-google/cirq_google/devices/specifications/README.md b/cirq-google/cirq_google/devices/specifications/README.md index e721c354a98..f83f41b5ed7 100644 --- a/cirq-google/cirq_google/devices/specifications/README.md +++ b/cirq-google/cirq_google/devices/specifications/README.md @@ -3,4 +3,4 @@ This directory contains snapshots of `DeviceSpecification` proto messages Files with the suffix `_for_grid_device` are equivalent representations of corresponding proto files without the suffix, but in the new `DeviceSpecification` format which is parsed into -`cirq_google.GridDevice`. \ No newline at end of file +`cirq_google.GridDevice`. diff --git a/cirq-google/cirq_google/engine/abstract_processor.py b/cirq-google/cirq_google/engine/abstract_processor.py index 0ec26f2c7e7..c39c3660930 100644 --- a/cirq-google/cirq_google/engine/abstract_processor.py +++ b/cirq-google/cirq_google/engine/abstract_processor.py @@ -56,6 +56,9 @@ class AbstractProcessor(abc.ABC): async def run_async( self, program: cirq.Circuit, + *, + run_name: str, + device_config_name: str, program_id: Optional[str] = None, job_id: Optional[str] = None, param_resolver: Optional[cirq.ParamResolver] = None, @@ -64,14 +67,18 @@ async def run_async( program_labels: Optional[Dict[str, str]] = None, job_description: Optional[str] = None, job_labels: Optional[Dict[str, str]] = None, - run_name: str = "", - device_config_name: str = "", ) -> cirq.Result: """Runs the supplied Circuit on this processor. Args: program: The Circuit to execute. If a circuit is provided, a moment by moment schedule will be used. + run_name: A unique identifier representing an automation run for the + processor. An Automation Run contains a collection of device + configurations for the processor. + device_config_name: An identifier used to select the processor configuration + utilized to run the job. A configuration identifies the set of + available qubits, couplers, and supported gates in the processor. program_id: A user-provided identifier for the program. This must be unique within the Google Cloud project being used. If this parameter is not provided, a random id of the format @@ -87,12 +94,7 @@ async def run_async( program_labels: Optional set of labels to set on the program. job_description: An optional description to set on the job. job_labels: Optional set of labels to set on the job. - run_name: A unique identifier representing an automation run for the - processor. An Automation Run contains a collection of device - configurations for the processor. - device_config_name: An identifier used to select the processor configuration - utilized to run the job. A configuration identifies the set of - available qubits, couplers, and supported gates in the processor. + Returns: A single Result for this run. """ @@ -117,6 +119,9 @@ async def run_async( async def run_sweep_async( self, program: cirq.AbstractCircuit, + *, + run_name: str, + device_config_name: str, program_id: Optional[str] = None, job_id: Optional[str] = None, params: cirq.Sweepable = None, @@ -125,8 +130,6 @@ async def run_sweep_async( program_labels: Optional[Dict[str, str]] = None, job_description: Optional[str] = None, job_labels: Optional[Dict[str, str]] = None, - run_name: str = "", - device_config_name: str = "", ) -> 'abstract_job.AbstractJob': """Runs the supplied Circuit on this processor. @@ -135,6 +138,12 @@ async def run_sweep_async( Args: program: The Circuit to execute. If a circuit is provided, a moment by moment schedule will be used. + run_name: A unique identifier representing an automation run for the + processor. An Automation Run contains a collection of device + configurations for the processor. + device_config_name: An identifier used to select the processor configuration + utilized to run the job. A configuration identifies the set of + available qubits, couplers, and supported gates in the processor. program_id: A user-provided identifier for the program. This must be unique within the Google Cloud project being used. If this parameter is not provided, a random id of the format @@ -150,12 +159,7 @@ async def run_sweep_async( program_labels: Optional set of labels to set on the program. job_description: An optional description to set on the job. job_labels: Optional set of labels to set on the job. - run_name: A unique identifier representing an automation run for the - processor. An Automation Run contains a collection of device - configurations for the processor. - device_config_name: An identifier used to select the processor configuration - utilized to run the job. A configuration identifies the set of - available qubits, couplers, and supported gates in the processor. + Returns: An AbstractJob. If this is iterated over it returns a list of `cirq.Result`, one for each parameter sweep. diff --git a/cirq-google/cirq_google/engine/calibration.py b/cirq-google/cirq_google/engine/calibration.py index 8e0ac4c1560..dfbe73e467d 100644 --- a/cirq-google/cirq_google/engine/calibration.py +++ b/cirq-google/cirq_google/engine/calibration.py @@ -277,7 +277,6 @@ def plot_histograms( show_plot = not ax if not ax: fig, ax = plt.subplots(1, 1) - ax = cast(plt.Axes, ax) if isinstance(keys, str): keys = [keys] diff --git a/cirq-google/cirq_google/engine/engine.py b/cirq-google/cirq_google/engine/engine.py index 4d7a5645d37..e809dc9dcbf 100644 --- a/cirq-google/cirq_google/engine/engine.py +++ b/cirq-google/cirq_google/engine/engine.py @@ -34,7 +34,6 @@ from google.protobuf import any_pb2 import cirq -from cirq._compat import deprecated from cirq_google.api import v2 from cirq_google.engine import ( abstract_engine, @@ -564,21 +563,6 @@ def get_processor(self, processor_id: str) -> engine_processor.EngineProcessor: """ return engine_processor.EngineProcessor(self.project_id, processor_id, self.context) - @deprecated(deadline="v1.0", fix="Use get_sampler instead.") - def sampler(self, processor_id: Union[str, List[str]]) -> 'cirq_google.ProcessorSampler': - """Returns a sampler backed by the engine. - - Args: - processor_id: String identifier, or list of string identifiers, - determining which processors may be used when sampling. - - Returns: - A `cirq.Sampler` instance (specifically a `engine_sampler.ProcessorSampler` - that will send circuits to the Quantum Computing Service - when sampled. - """ - return self.get_sampler(processor_id) - def get_sampler( self, processor_id: Union[str, List[str]], run_name: str = "", device_config_name: str = "" ) -> 'cirq_google.ProcessorSampler': diff --git a/cirq-google/cirq_google/engine/engine_processor.py b/cirq-google/cirq_google/engine/engine_processor.py index df5e498a4d6..bd91c80a033 100644 --- a/cirq-google/cirq_google/engine/engine_processor.py +++ b/cirq-google/cirq_google/engine/engine_processor.py @@ -42,16 +42,6 @@ def _date_to_timestamp( return None -def _fix_deprecated_seconds_kwargs(kwargs): - if 'earliest_timestamp_seconds' in kwargs: - kwargs['earliest_timestamp'] = kwargs['earliest_timestamp_seconds'] - del kwargs['earliest_timestamp_seconds'] - if 'latest_timestamp_seconds' in kwargs: - kwargs['latest_timestamp'] = kwargs['latest_timestamp_seconds'] - del kwargs['latest_timestamp_seconds'] - return kwargs - - class EngineProcessor(abstract_processor.AbstractProcessor): """A processor available via the Quantum Engine API. @@ -125,6 +115,9 @@ def get_sampler( async def run_sweep_async( self, program: cirq.AbstractCircuit, + *, + run_name: str, + device_config_name: str, program_id: Optional[str] = None, job_id: Optional[str] = None, params: cirq.Sweepable = None, @@ -133,8 +126,6 @@ async def run_sweep_async( program_labels: Optional[Dict[str, str]] = None, job_description: Optional[str] = None, job_labels: Optional[Dict[str, str]] = None, - run_name: str = "", - device_config_name: str = "", ) -> 'abstract_job.AbstractJob': """Runs the supplied Circuit on this processor. @@ -144,6 +135,12 @@ async def run_sweep_async( Args: program: The Circuit to execute. If a circuit is provided, a moment by moment schedule will be used. + run_name: A unique identifier representing an automation run for the + processor. An Automation Run contains a collection of device + configurations for the processor. + device_config_name: An identifier used to select the processor configuration + utilized to run the job. A configuration identifies the set of + available qubits, couplers, and supported gates in the processor. program_id: A user-provided identifier for the program. This must be unique within the Google Cloud project being used. If this parameter is not provided, a random id of the format @@ -159,12 +156,6 @@ async def run_sweep_async( program_labels: Optional set of labels to set on the program. job_description: An optional description to set on the job. job_labels: Optional set of labels to set on the job. - run_name: A unique identifier representing an automation run for the - processor. An Automation Run contains a collection of device - configurations for the processor. - device_config_name: An identifier used to select the processor configuration - utilized to run the job. A configuration identifies the set of - available qubits, couplers, and supported gates in the processor. Returns: An AbstractJob. If this is iterated over it returns a list of `cirq.Result`, one for each parameter sweep. @@ -238,20 +229,6 @@ def get_device(self) -> cirq.Device: raise ValueError('Processor does not have a device specification') return grid_device.GridDevice.from_proto(spec) - @cirq._compat.deprecated_parameter( - deadline='v1.0', - fix='Change earliest_timestamp_seconds to earliest_timestamp.', - parameter_desc='earliest_timestamp_seconds', - match=lambda args, kwargs: 'earliest_timestamp_seconds' in kwargs, - rewrite=lambda args, kwargs: (args, _fix_deprecated_seconds_kwargs(kwargs)), - ) - @cirq._compat.deprecated_parameter( - deadline='v1.0', - fix='Change latest_timestamp_seconds to latest_timestamp.', - parameter_desc='latest_timestamp_seconds', - match=lambda args, kwargs: 'latest_timestamp_seconds' in kwargs, - rewrite=lambda args, kwargs: (args, _fix_deprecated_seconds_kwargs(kwargs)), - ) def list_calibrations( self, earliest_timestamp: Optional[Union[datetime.datetime, datetime.date, int]] = None, @@ -260,10 +237,8 @@ def list_calibrations( """Retrieve metadata about a specific calibration run. Params: - earliest_timestamp_seconds: The earliest timestamp of a calibration - to return in UTC. - latest_timestamp_seconds: The latest timestamp of a calibration to - return in UTC. + earliest_timestamp: The earliest timestamp of a calibration to return in UTC. + latest_timestamp: The latest timestamp of a calibration to return in UTC. Returns: The list of calibration data with the most recent first. diff --git a/cirq-google/cirq_google/engine/engine_processor_test.py b/cirq-google/cirq_google/engine/engine_processor_test.py index 709e640b940..a120f309fab 100644 --- a/cirq-google/cirq_google/engine/engine_processor_test.py +++ b/cirq-google/cirq_google/engine/engine_processor_test.py @@ -414,24 +414,6 @@ def test_list_calibrations(list_calibrations): list_calibrations.assert_called_with('a', 'p', f'timestamp >= {today_midnight_timestamp}') -@mock.patch('cirq_google.engine.engine_client.EngineClient.list_calibrations_async') -def test_list_calibrations_old_params(list_calibrations): - # Disable pylint warnings for use of deprecated parameters - # pylint: disable=unexpected-keyword-arg - list_calibrations.return_value = [_CALIBRATION] - processor = cg.EngineProcessor('a', 'p', EngineContext()) - with cirq.testing.assert_deprecated('Change earliest_timestamp_seconds', deadline='v1.0'): - assert [ - c.timestamp for c in processor.list_calibrations(earliest_timestamp_seconds=1562500000) - ] == [1562544000021] - list_calibrations.assert_called_with('a', 'p', 'timestamp >= 1562500000') - with cirq.testing.assert_deprecated('Change latest_timestamp_seconds', deadline='v1.0'): - assert [ - c.timestamp for c in processor.list_calibrations(latest_timestamp_seconds=1562600000) - ] == [1562544000021] - list_calibrations.assert_called_with('a', 'p', 'timestamp <= 1562600000') - - @mock.patch('cirq_google.engine.engine_client.EngineClient.get_calibration_async') def test_get_calibration(get_calibration): get_calibration.return_value = _CALIBRATION @@ -866,7 +848,10 @@ def test_run_sweep_params_with_unary_rpcs(client): processor = cg.EngineProcessor('a', 'p', EngineContext(enable_streaming=False)) job = processor.run_sweep( - program=_CIRCUIT, params=[cirq.ParamResolver({'a': 1}), cirq.ParamResolver({'a': 2})] + program=_CIRCUIT, + params=[cirq.ParamResolver({'a': 1}), cirq.ParamResolver({'a': 2})], + run_name="run", + device_config_name="config_alias", ) results = job.results() assert len(results) == 2 @@ -905,7 +890,10 @@ def test_run_sweep_params_with_stream_rpcs(client): processor = cg.EngineProcessor('a', 'p', EngineContext(enable_streaming=True)) job = processor.run_sweep( - program=_CIRCUIT, params=[cirq.ParamResolver({'a': 1}), cirq.ParamResolver({'a': 2})] + program=_CIRCUIT, + params=[cirq.ParamResolver({'a': 1}), cirq.ParamResolver({'a': 2})], + run_name="run", + device_config_name="config_alias", ) results = job.results() assert len(results) == 2 diff --git a/cirq-google/cirq_google/engine/engine_program.py b/cirq-google/cirq_google/engine/engine_program.py index 2d152f89d59..7789d7132e6 100644 --- a/cirq-google/cirq_google/engine/engine_program.py +++ b/cirq-google/cirq_google/engine/engine_program.py @@ -63,14 +63,14 @@ def __init__( async def run_sweep_async( self, processor_id: str, + *, + run_name: str, + device_config_name: str, job_id: Optional[str] = None, params: cirq.Sweepable = None, repetitions: int = 1, description: Optional[str] = None, labels: Optional[Dict[str, str]] = None, - *, - run_name: str = "", - device_config_name: str = "", ) -> engine_job.EngineJob: """Runs the program on the QuantumEngine. @@ -78,6 +78,13 @@ async def run_sweep_async( does not block until a result is returned. Args: + processor_id: Processor id for running the program. + run_name: A unique identifier representing an automation run for the + specified processor. An Automation Run contains a collection of + device configurations for a processor. + device_config_name: An identifier used to select the processor configuration + utilized to run the job. A configuration identifies the set of + available qubits, couplers, and supported gates in the processor. job_id: Optional job id to use. If this is not provided, a random id of the format 'job-################YYMMDD' will be generated, where # is alphanumeric and YYMMDD is the current year, month, @@ -86,15 +93,6 @@ async def run_sweep_async( repetitions: The number of circuit repetitions to run. description: An optional description to set on the job. labels: Optional set of labels to set on the job. - processor_id: Processor id for running the program. - run_name: A unique identifier representing an automation run for the - specified processor. An Automation Run contains a collection of - device configurations for a processor. If specified, `processor_id` - is required to be set. - device_config_name: An identifier used to select the processor configuration - utilized to run the job. A configuration identifies the set of - available qubits, couplers, and supported gates in the processor. - If specified, `processor_id` is required to be set. Returns: An EngineJob. If this is iterated over it returns a list of @@ -131,19 +129,26 @@ async def run_sweep_async( async def run_async( self, + *, processor_id: str, + run_name: str, + device_config_name: str, job_id: Optional[str] = None, param_resolver: cirq.ParamResolver = cirq.ParamResolver({}), repetitions: int = 1, description: Optional[str] = None, labels: Optional[Dict[str, str]] = None, - *, - run_name: str = "", - device_config_name: str = "", ) -> cirq.Result: """Runs the supplied Circuit via Quantum Engine. Args: + processor_id: Processor id for running the program. + run_name: A unique identifier representing an automation run for the + specified processor. An Automation Run contains a collection of + device configurations for a processor. + device_config_name: An identifier used to select the processor configuration + utilized to run the job. A configuration identifies the set of + available qubits, couplers, and supported gates in the processor. job_id: Optional job id to use. If this is not provided, a random id of the format 'job-################YYMMDD' will be generated, where # is alphanumeric and YYMMDD is the current year, month, @@ -152,15 +157,6 @@ async def run_async( repetitions: The number of repetitions to simulate. description: An optional description to set on the job. labels: Optional set of labels to set on the job. - processor_id: Processor id for running the program. - run_name: A unique identifier representing an automation run for the - specified processor. An Automation Run contains a collection of - device configurations for a processor. If specified, `processor_id` - is required to be set. - device_config_name: An identifier used to select the processor configuration - utilized to run the job. A configuration identifies the set of - available qubits, couplers, and supported gates in the processor. - If specified, `processor_id` is required to be set. Returns: A single Result for this run. diff --git a/cirq-google/cirq_google/engine/engine_program_test.py b/cirq-google/cirq_google/engine/engine_program_test.py index ad8b8f316f7..d92298476db 100644 --- a/cirq-google/cirq_google/engine/engine_program_test.py +++ b/cirq-google/cirq_google/engine/engine_program_test.py @@ -85,7 +85,12 @@ def test_run_sweeps_delegation(create_job_async): program = cg.EngineProgram('my-proj', 'my-prog', EngineContext()) param_resolver = cirq.ParamResolver({}) job = program.run_sweep( - job_id='steve', repetitions=10, params=param_resolver, processor_id='mine' + job_id='steve', + repetitions=10, + params=param_resolver, + processor_id='mine', + run_name="run_name", + device_config_name="config", ) assert job._job == quantum.QuantumJob() @@ -134,7 +139,12 @@ def test_run_delegation(create_job_async, get_results_async): program = cg.EngineProgram('a', 'b', EngineContext()) param_resolver = cirq.ParamResolver({}) results = program.run( - job_id='steve', repetitions=10, param_resolver=param_resolver, processor_id='mine' + job_id='steve', + repetitions=10, + param_resolver=param_resolver, + processor_id='mine', + run_name="run_name", + device_config_name="config", ) assert results == cg.EngineResult( diff --git a/cirq-google/cirq_google/engine/engine_test.py b/cirq-google/cirq_google/engine/engine_test.py index 3449adac62e..80487b75452 100644 --- a/cirq-google/cirq_google/engine/engine_test.py +++ b/cirq-google/cirq_google/engine/engine_test.py @@ -533,12 +533,21 @@ def test_run_multiple_times(client): engine = cg.Engine(project_id='proj', proto_version=cg.engine.engine.ProtoVersion.V2) program = engine.create_program(program=_CIRCUIT) - program.run(processor_id='processor0', param_resolver=cirq.ParamResolver({'a': 1})) + program.run( + processor_id='processor0', + param_resolver=cirq.ParamResolver({'a': 1}), + run_name="run", + device_config_name="config_alias", + ) run_context = v2.run_context_pb2.RunContext() client().create_job_async.call_args[1]['run_context'].Unpack(run_context) sweeps1 = run_context.parameter_sweeps job2 = program.run_sweep( - processor_id='processor0', repetitions=2, params=cirq.Points('a', [3, 4]) + processor_id='processor0', + repetitions=2, + params=cirq.Points('a', [3, 4]), + run_name="run", + device_config_name="config_alias", ) client().create_job_async.call_args[1]['run_context'].Unpack(run_context) sweeps2 = run_context.parameter_sweeps @@ -629,7 +638,9 @@ def test_bad_sweep_proto(): engine = cg.Engine(project_id='project-id', proto_version=cg.ProtoVersion.UNDEFINED) program = cg.EngineProgram('proj', 'prog', engine.context) with pytest.raises(ValueError, match='invalid run context proto version'): - program.run_sweep(processor_id='processor0') + program.run_sweep( + processor_id='processor0', run_name="run", device_config_name="config_alias" + ) @mock.patch('cirq_google.engine.engine_client.EngineClient', autospec=True) @@ -741,9 +752,6 @@ def test_sampler_with_unary_rpcs(client): assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')} assert client().create_program_async.call_args[0][0] == 'proj' - with cirq.testing.assert_deprecated('sampler', deadline='1.0'): - _ = engine.sampler(processor_id='tmp') - with pytest.raises(ValueError, match='list of processors'): _ = engine.get_sampler(['test1', 'test2']) @@ -764,9 +772,6 @@ def test_sampler_with_stream_rpcs(client): assert results[i].measurements == {'q': np.array([[0]], dtype='uint8')} assert client().run_job_over_stream.call_args[1]['project_id'] == 'proj' - with cirq.testing.assert_deprecated('sampler', deadline='1.0'): - _ = engine.sampler(processor_id='tmp') - with pytest.raises(ValueError, match='list of processors'): _ = engine.get_sampler(['test1', 'test2']) diff --git a/cirq-google/cirq_google/engine/simulated_local_processor_test.py b/cirq-google/cirq_google/engine/simulated_local_processor_test.py index 0622d3cb9db..10277f2183d 100644 --- a/cirq-google/cirq_google/engine/simulated_local_processor_test.py +++ b/cirq-google/cirq_google/engine/simulated_local_processor_test.py @@ -128,7 +128,7 @@ def test_run(): proc = SimulatedLocalProcessor(processor_id='test_proc') q = cirq.GridQubit(5, 4) circuit = cirq.Circuit(cirq.X(q), cirq.measure(q, key='m')) - result = proc.run(circuit, repetitions=100) + result = proc.run(circuit, repetitions=100, run_name="run", device_config_name="config_alias") assert np.all(result.measurements['m'] == 1) diff --git a/cirq-google/cirq_google/engine/virtual_engine_factory_test.py b/cirq-google/cirq_google/engine/virtual_engine_factory_test.py index c5f1e10dd4a..3e28bdefefc 100644 --- a/cirq-google/cirq_google/engine/virtual_engine_factory_test.py +++ b/cirq-google/cirq_google/engine/virtual_engine_factory_test.py @@ -26,20 +26,22 @@ def _test_processor(processor: cg.engine.abstract_processor.AbstractProcessor): Also tests the non-Sycamore qubits and gates fail.""" good_qubit = cirq.GridQubit(5, 4) circuit = cirq.Circuit(cirq.X(good_qubit), cirq.measure(good_qubit)) - results = processor.run(circuit, repetitions=100) + results = processor.run(circuit, repetitions=100, run_name="run", device_config_name="config") assert np.all(results.measurements[str(good_qubit)] == 1) with pytest.raises(RuntimeError, match='requested total repetitions'): - _ = processor.run(circuit, repetitions=100_000_000) + _ = processor.run( + circuit, repetitions=100_000_000, run_name="run", device_config_name="config" + ) bad_qubit = cirq.GridQubit(10, 10) circuit = cirq.Circuit(cirq.X(bad_qubit), cirq.measure(bad_qubit)) with pytest.raises(ValueError, match='Qubit not on device'): - _ = processor.run(circuit, repetitions=100) + _ = processor.run(circuit, repetitions=100, run_name="run", device_config_name="config") circuit = cirq.Circuit( cirq.testing.DoesNotSupportSerializationGate()(good_qubit), cirq.measure(good_qubit) ) with pytest.raises(ValueError, match='Cannot serialize op'): - _ = processor.run(circuit, repetitions=100) + _ = processor.run(circuit, repetitions=100, run_name="run", device_config_name="config") def test_create_device_from_processor_id(): @@ -195,13 +197,13 @@ def test_create_default_noisy_quantum_virtual_machine(): bad_qubit = cirq.GridQubit(10, 10) circuit = cirq.Circuit(cirq.X(bad_qubit), cirq.measure(bad_qubit)) with pytest.raises(ValueError, match='Qubit not on device'): - _ = processor.run(circuit, repetitions=100) + _ = processor.run(circuit, repetitions=100, run_name="run", device_config_name="config") good_qubit = cirq.GridQubit(5, 4) circuit = cirq.Circuit( cirq.testing.DoesNotSupportSerializationGate()(good_qubit), cirq.measure(good_qubit) ) with pytest.raises(ValueError, match='.* contains a gate which is not supported.'): - _ = processor.run(circuit, repetitions=100) + _ = processor.run(circuit, repetitions=100, run_name="run", device_config_name="config") device_specification = processor.get_device_specification() expected = factory.create_device_spec_from_processor_id(processor_id) assert device_specification is not None diff --git a/cirq-google/cirq_google/serialization/circuit_serializer_test.py b/cirq-google/cirq_google/serialization/circuit_serializer_test.py index 573b8f018c3..2403e7865ba 100644 --- a/cirq-google/cirq_google/serialization/circuit_serializer_test.py +++ b/cirq-google/cirq_google/serialization/circuit_serializer_test.py @@ -79,11 +79,11 @@ def circuit_proto(json: Dict, qubits: List[str]): op_proto({'xpowgate': {'exponent': {'float_value': 0.125}}, 'qubit_constant_index': [0]}), ), ( - cirq.XPowGate(exponent=np.double(0.125))(Q1), # type: ignore + cirq.XPowGate(exponent=np.double(0.125))(Q1), op_proto({'xpowgate': {'exponent': {'float_value': 0.125}}, 'qubit_constant_index': [0]}), ), ( - cirq.XPowGate(exponent=np.short(1))(Q1), # type: ignore + cirq.XPowGate(exponent=np.short(1))(Q1), op_proto({'xpowgate': {'exponent': {'float_value': 1.0}}, 'qubit_constant_index': [0]}), ), ( diff --git a/cirq-google/cirq_google/serialization/op_deserializer.py b/cirq-google/cirq_google/serialization/op_deserializer.py index e682712a189..44dec4f09d7 100644 --- a/cirq-google/cirq_google/serialization/op_deserializer.py +++ b/cirq-google/cirq_google/serialization/op_deserializer.py @@ -153,5 +153,5 @@ def from_proto( ) return cirq.CircuitOperation( - circuit, repetitions, qubit_map, measurement_key_map, arg_map, rep_ids # type: ignore + circuit, repetitions, qubit_map, measurement_key_map, arg_map, rep_ids ) diff --git a/cirq-google/cirq_google/workflow/processor_record_test.py b/cirq-google/cirq_google/workflow/processor_record_test.py index c5b4148c6fe..0152f83e25d 100644 --- a/cirq-google/cirq_google/workflow/processor_record_test.py +++ b/cirq-google/cirq_google/workflow/processor_record_test.py @@ -148,7 +148,7 @@ def test_engine_result(): circ = cirq.Circuit(cirq.measure(cirq.GridQubit(5, 4))) - res1 = proc.run(circ) + res1 = proc.run(circ, run_name="run", device_config_name="config_alias") assert isinstance(res1, cg.EngineResult) res2 = samp.run(circ) assert isinstance(res2, cg.EngineResult) diff --git a/cirq-google/requirements.txt b/cirq-google/requirements.txt index f2d74cc1d55..39c5cbeba36 100644 --- a/cirq-google/requirements.txt +++ b/cirq-google/requirements.txt @@ -1,3 +1,3 @@ google-api-core[grpc] >= 1.14.0 proto-plus >= 1.20.0 -protobuf >= 3.15.0 +protobuf >= 3.15.0, < 5.0.0 diff --git a/cirq-google/version_policy.md b/cirq-google/version_policy.md index d895648a5e8..ceaa18ffa37 100644 --- a/cirq-google/version_policy.md +++ b/cirq-google/version_policy.md @@ -1,3 +1,3 @@ # cirq-google Version Policy -The `cirq-google` directory should abide by Semantic Versioning 2.0.0. In rare cases, we may introduce backward-incompatible changes to the public API in a release with a minor version increment. Such changes will be noted in `CHANGELOG.md` within the directory before the release. \ No newline at end of file +The `cirq-google` directory should abide by Semantic Versioning 2.0.0. In rare cases, we may introduce backward-incompatible changes to the public API in a release with a minor version increment. Such changes will be noted in `CHANGELOG.md` within the directory before the release. diff --git a/cirq-ionq/cirq_ionq/__init__.py b/cirq-ionq/cirq_ionq/__init__.py index 8403adbef36..fce52d9c9a9 100644 --- a/cirq-ionq/cirq_ionq/__init__.py +++ b/cirq-ionq/cirq_ionq/__init__.py @@ -16,7 +16,7 @@ from cirq_ionq.calibration import Calibration -from cirq_ionq.ionq_devices import IonQAPIDevice, decompose_to_device +from cirq_ionq.ionq_devices import IonQAPIDevice from cirq_ionq.ionq_gateset import IonQTargetGateset, decompose_all_to_all_connect_ccz_gate diff --git a/cirq-ionq/cirq_ionq/_version.py b/cirq-ionq/cirq_ionq/_version.py index 9f43ba84951..016426c9d4f 100644 --- a/cirq-ionq/cirq_ionq/_version.py +++ b/cirq-ionq/cirq_ionq/_version.py @@ -14,4 +14,4 @@ """Define version number here, read it from setup.py automatically""" -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-ionq/cirq_ionq/_version_test.py b/cirq-ionq/cirq_ionq/_version_test.py index 099f35f060d..bf0522db1e0 100644 --- a/cirq-ionq/cirq_ionq/_version_test.py +++ b/cirq-ionq/cirq_ionq/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq_ionq.__version__ == "1.4.0.dev" + assert cirq_ionq.__version__ == "1.5.0.dev" diff --git a/cirq-ionq/cirq_ionq/ionq_devices.py b/cirq-ionq/cirq_ionq/ionq_devices.py index ad657f7e4ba..467043321a9 100644 --- a/cirq-ionq/cirq_ionq/ionq_devices.py +++ b/cirq-ionq/cirq_ionq/ionq_devices.py @@ -72,34 +72,3 @@ def validate_operation(self, operation: cirq.Operation): def is_api_gate(self, operation: cirq.Operation) -> bool: return operation in self.gateset - - -@cirq._compat.deprecated( - deadline='v0.16', - fix='Use cirq.optimize_for_target_gateset(circuit, ' - 'gateset=cirq_ionq.IonQTargetGateset(atol)) ' - 'instead.', -) -def decompose_to_device(operation: cirq.Operation, atol: float = 1e-8) -> cirq.OP_TREE: - """Decompose operation to ionq native operations. - - - Merges single qubit operations and decomposes two qubit operations - into CZ gates. - - Args: - operation: `cirq.Operation` to decompose. - atol: absolute error tolerance to use when declaring two unitary - operations equal. - - Returns: - cirq.OP_TREE containing decomposed operations. - - Raises: - ValueError: If supplied operation cannot be decomposed - for the ionq device. - - """ - return cirq.optimize_for_target_gateset( - cirq.Circuit(operation), gateset=ionq_gateset.IonQTargetGateset(), ignore_failures=False - ).all_operations() diff --git a/cirq-ionq/cirq_ionq/ionq_native_gates.py b/cirq-ionq/cirq_ionq/ionq_native_gates.py index 57999fb11bd..17f938aa19e 100644 --- a/cirq-ionq/cirq_ionq/ionq_native_gates.py +++ b/cirq-ionq/cirq_ionq/ionq_native_gates.py @@ -177,16 +177,16 @@ def __pow__(self, power): @cirq.value.value_equality class MSGate(cirq.Gate): - r"""The Mølmer–Sørensen (MS) gate is a two qubit gate native to trapped ions. + r"""The Mølmer-Sørensen (MS) gate is a two qubit gate native to trapped ions. The unitary matrix of this gate for parameters $\phi_0$, $\phi_1$ and $\theta$ is $$ \begin{bmatrix} - \cos\frac{\theta}{2} & 0 & 0 & -ie^{-i2\pi(\phi_0+\phi_1)}\sin\frac{\theta}{2} \\ - 0 & \cos\frac{\theta}{2} & -ie^{-i2\pi(\phi_0-\phi_1)}\sin\frac{\theta}{2} & 0 \\ - 0 & -ie^{i2\pi(\phi_0-\phi_1)}\sin\frac{\theta}{2} & \cos\frac{\theta}{2} & 0 \\ - -ie^{i2\pi(\phi_0+\phi_1)}\sin\frac{\theta}{2} & 0 & 0 & \cos\frac{\theta}{2} + \cos(\pi \theta) & 0 & 0 & -ie^{-i2\pi(\phi_0+\phi_1)}\sin(\pi\theta) \\ + 0 & \cos(\pi\theta) & -ie^{-i2\pi(\phi_0-\phi_1)}\sin(\pi\theta) & 0 \\ + 0 & -ie^{i2\pi(\phi_0-\phi_1)}\sin(\pi\theta) & \cos(\pi\theta) & 0 \\ + -ie^{i2\pi(\phi_0+\phi_1)}\sin(\pi\theta) & 0 & 0 & \cos(\pi\theta) \end{bmatrix} $$ diff --git a/cirq-ionq/requirements.txt b/cirq-ionq/requirements.txt index 3d368bb39ab..fde7fb282cf 100644 --- a/cirq-ionq/requirements.txt +++ b/cirq-ionq/requirements.txt @@ -1 +1 @@ -requests~=2.18 \ No newline at end of file +requests~=2.18 diff --git a/cirq-pasqal/cirq_pasqal/_version.py b/cirq-pasqal/cirq_pasqal/_version.py index 9f43ba84951..016426c9d4f 100644 --- a/cirq-pasqal/cirq_pasqal/_version.py +++ b/cirq-pasqal/cirq_pasqal/_version.py @@ -14,4 +14,4 @@ """Define version number here, read it from setup.py automatically""" -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-pasqal/cirq_pasqal/_version_test.py b/cirq-pasqal/cirq_pasqal/_version_test.py index fc5f5ef528b..bbb001e4593 100644 --- a/cirq-pasqal/cirq_pasqal/_version_test.py +++ b/cirq-pasqal/cirq_pasqal/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq_pasqal.__version__ == "1.4.0.dev" + assert cirq_pasqal.__version__ == "1.5.0.dev" diff --git a/cirq-pasqal/requirements.txt b/cirq-pasqal/requirements.txt index 3d368bb39ab..fde7fb282cf 100644 --- a/cirq-pasqal/requirements.txt +++ b/cirq-pasqal/requirements.txt @@ -1 +1 @@ -requests~=2.18 \ No newline at end of file +requests~=2.18 diff --git a/cirq-rigetti/cirq_rigetti/_qcs_api_client_decorator.py b/cirq-rigetti/cirq_rigetti/_qcs_api_client_decorator.py deleted file mode 100644 index 587389c9880..00000000000 --- a/cirq-rigetti/cirq_rigetti/_qcs_api_client_decorator.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2021 The Cirq Developers -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# 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. -import functools - -from qcs_api_client.client import build_sync_client - - -def _provide_default_client(function): - """A decorator that will initialize an `httpx.Client` and pass - it to the wrapped function as a kwarg if not already present. This - eases provision of a default `httpx.Client` with Rigetti - QCS configuration and authentication. If the decorator initializes a - default client, it will invoke the wrapped function from within the - `httpx.Client` context. - - Args: - function: The decorated function. - - Returns: - The `function` wrapped with a default `client`. - """ - - @functools.wraps(function) - def wrapper(*args, **kwargs): - if 'client' in kwargs: - return function(*args, **kwargs) - - with build_sync_client() as client: # pragma: no cover - kwargs['client'] = client - return function(*args, **kwargs) - - return wrapper diff --git a/cirq-rigetti/cirq_rigetti/_version.py b/cirq-rigetti/cirq_rigetti/_version.py index 9f43ba84951..016426c9d4f 100644 --- a/cirq-rigetti/cirq_rigetti/_version.py +++ b/cirq-rigetti/cirq_rigetti/_version.py @@ -14,4 +14,4 @@ """Define version number here, read it from setup.py automatically""" -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-rigetti/cirq_rigetti/_version_test.py b/cirq-rigetti/cirq_rigetti/_version_test.py index 41a9598e892..0be206ea2a5 100644 --- a/cirq-rigetti/cirq_rigetti/_version_test.py +++ b/cirq-rigetti/cirq_rigetti/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq_rigetti.__version__ == "1.4.0.dev" + assert cirq_rigetti.__version__ == "1.5.0.dev" diff --git a/cirq-rigetti/cirq_rigetti/aspen_device.py b/cirq-rigetti/cirq_rigetti/aspen_device.py index b6d3b5f2684..5278ca68dfb 100644 --- a/cirq-rigetti/cirq_rigetti/aspen_device.py +++ b/cirq-rigetti/cirq_rigetti/aspen_device.py @@ -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): @@ -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`. @@ -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' @@ -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. @@ -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) diff --git a/cirq-rigetti/cirq_rigetti/aspen_device_test.py b/cirq-rigetti/cirq_rigetti/aspen_device_test.py index 97fa7c9b690..5b29d5b4b3a 100644 --- a/cirq-rigetti/cirq_rigetti/aspen_device_test.py +++ b/cirq-rigetti/cirq_rigetti/aspen_device_test.py @@ -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 ( @@ -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__))) @@ -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(): @@ -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', [ @@ -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) diff --git a/cirq-rigetti/cirq_rigetti/circuit_sweep_executors.py b/cirq-rigetti/cirq_rigetti/circuit_sweep_executors.py index 464662ae757..4894b3ea525 100644 --- a/cirq-rigetti/cirq_rigetti/circuit_sweep_executors.py +++ b/cirq-rigetti/cirq_rigetti/circuit_sweep_executors.py @@ -57,21 +57,22 @@ def _execute_and_read_result( Raises: ValueError: measurement_id_map references an undefined pyQuil readout region. """ - if memory_map is None: - memory_map = {} - for region_name, values in memory_map.items(): - if isinstance(region_name, str): - executable.write_memory(region_name=region_name, value=values) - else: - raise ValueError(f'Symbols not valid for region name {region_name}') - qam_execution_result = quantum_computer.qam.run(executable) + # convert all atomic memory values into 1-length lists + if memory_map is not None: + for region_name, value in memory_map.items(): + if not isinstance(region_name, str): + raise ValueError(f'Symbols not valid for region name {region_name}') + value = [value] if not isinstance(value, Sequence) else value + memory_map[region_name] = value + + qam_execution_result = quantum_computer.qam.run(executable, memory_map) # type: ignore measurements = {} # For every key, value in QuilOutput#measurement_id_map, use the value to read # Rigetti QCS results and assign to measurements by key. for cirq_memory_key, pyquil_region in measurement_id_map.items(): - readout = qam_execution_result.readout_data.get(pyquil_region) + readout = qam_execution_result.get_register_map().get(pyquil_region) if readout is None: raise ValueError(f'readout data does not have values for region "{pyquil_region}"') measurements[cirq_memory_key] = readout @@ -122,9 +123,7 @@ def _prepend_real_declarations( param_dict = _get_param_dict(resolver) for key in param_dict.keys(): declaration = Declare(str(key), "REAL") - program._instructions.insert(0, declaration) - program._synthesized_instructions = None - program.declarations[declaration.name] = declaration + program = Program(declaration) + program logger.debug(f"prepended declaration {declaration}") return program diff --git a/cirq-rigetti/cirq_rigetti/circuit_transformers_test.py b/cirq-rigetti/cirq_rigetti/circuit_transformers_test.py index e214ef58da7..c16e5aac6ad 100644 --- a/cirq-rigetti/cirq_rigetti/circuit_transformers_test.py +++ b/cirq-rigetti/cirq_rigetti/circuit_transformers_test.py @@ -3,6 +3,7 @@ from unittest.mock import create_autospec import cirq import numpy as np +from pyquil import Program from pyquil.gates import MEASURE, RX, DECLARE, H, CNOT, I from pyquil.quilbase import Pragma, Reset from cirq_rigetti import circuit_transformers as transformers @@ -63,7 +64,7 @@ def test_transform_with_post_transformation_hooks( bell_circuit, qubits = bell_circuit_with_qids def reset_hook(program, measurement_id_map): - program._instructions.insert(0, Reset()) + program = Program(Reset()) + program return program, measurement_id_map reset_hook_spec = create_autospec(reset_hook, side_effect=reset_hook) @@ -71,7 +72,7 @@ def reset_hook(program, measurement_id_map): pragma = Pragma('INTIAL_REWIRING', freeform_string='GREEDY') def rewire_hook(program, measurement_id_map): - program._instructions.insert(0, pragma) + program = Program(pragma) + program return program, measurement_id_map rewire_hook_spec = create_autospec(rewire_hook, side_effect=rewire_hook) diff --git a/cirq-rigetti/cirq_rigetti/conftest.py b/cirq-rigetti/cirq_rigetti/conftest.py index da647f43ac8..7338b5aef0f 100644 --- a/cirq-rigetti/cirq_rigetti/conftest.py +++ b/cirq-rigetti/cirq_rigetti/conftest.py @@ -12,19 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Tuple, Optional, List, Union, Generic, TypeVar, Dict +from typing import ( + Any, + Iterable, + Mapping, + Sequence, + Tuple, + Optional, + List, + Union, + Generic, + TypeVar, + Dict, +) from unittest.mock import create_autospec, Mock import pytest from pyquil import Program from pyquil.quantum_processor import AbstractQuantumProcessor, NxQuantumProcessor -from pyquil.api import QAM, QuantumComputer, QuantumExecutable, QAMExecutionResult, EncryptedProgram -from pyquil.api._abstract_compiler import AbstractCompiler -from qcs_api_client.client._configuration.settings import QCSClientConfigurationSettings -from qcs_api_client.client._configuration import ( - QCSClientConfiguration, - QCSClientConfigurationSecrets, +from pyquil.api import ( + QAM, + QuantumComputer, + QuantumExecutable, + QAMExecutionResult, + EncryptedProgram, + MemoryMap, ) +from pyquil.api._abstract_compiler import AbstractCompiler +from qcs_sdk import QCSClient, ExecutionData, ResultData, RegisterData +from qcs_sdk.qvm import QVMResultData import networkx as nx import cirq import sympy @@ -41,10 +57,25 @@ def __init__(self, *args, **kwargs) -> None: self._run_count = 0 self._mock_results: Dict[str, np.ndarray] = {} - def execute(self, executable: QuantumExecutable) -> T: # type: ignore[empty-body] + def execute( + self, + executable: Union[EncryptedProgram, Program], + memory_map: Optional[Mapping[str, Union[Sequence[int], Sequence[float]]]] = ..., + **kwargs: Any, + ) -> Any: pass - def run(self, program: QuantumExecutable) -> QAMExecutionResult: + def execute_with_memory_map_batch( # type: ignore[empty-body] + self, executable: QuantumExecutable, memory_maps: Iterable[MemoryMap], **kwargs: Any + ) -> List[T]: + pass + + def run( + self, + executable: Union[EncryptedProgram, Program], + memory_map: Optional[Mapping[str, Union[Sequence[int], Sequence[float]]]] = ..., + **kwargs: Any, + ) -> Any: raise NotImplementedError def get_result(self, execute_response: T) -> QAMExecutionResult: @@ -55,9 +86,12 @@ class MockCompiler(AbstractCompiler): def quil_to_native_quil(self, program: Program, *, protoquil: Optional[bool] = None) -> Program: raise NotImplementedError - def native_quil_to_executable(self, nq_program: Program) -> QuantumExecutable: + def native_quil_to_executable(self, nq_program: Program, **kwargs: Any) -> QuantumExecutable: raise NotImplementedError + def reset(self) -> None: + pass + @pytest.fixture def qam() -> QAM: @@ -71,18 +105,14 @@ def quantum_processor() -> AbstractQuantumProcessor: @pytest.fixture -def qcs_client_configuration() -> QCSClientConfiguration: - settings = QCSClientConfigurationSettings() - secrets = QCSClientConfigurationSecrets() - return QCSClientConfiguration(profile_name="default", settings=settings, secrets=secrets) +def qcs_client() -> QCSClient: + return QCSClient() @pytest.fixture -def compiler(quantum_processor, qcs_client_configuration) -> AbstractCompiler: +def compiler(quantum_processor, qcs_client) -> AbstractCompiler: return MockCompiler( - client_configuration=qcs_client_configuration, - timeout=0, - quantum_processor=quantum_processor, + client_configuration=qcs_client, timeout=0, quantum_processor=quantum_processor ) @@ -158,14 +188,26 @@ def native_quil_to_executable(nq_program: Program) -> QuantumExecutable: side_effect=native_quil_to_executable, ) - def run(program: Union[Program, EncryptedProgram]) -> QAMExecutionResult: + def run( + program: Union[Program, EncryptedProgram], memory_map: MemoryMap + ) -> QAMExecutionResult: qam = quantum_computer.qam qam._mock_results = qam._mock_results or {} # type: ignore qam._mock_results["m0"] = results[qam._run_count] # type: ignore quantum_computer.qam._run_count += 1 # type: ignore return QAMExecutionResult( - executable=program, readout_data=qam._mock_results # type: ignore + executable=program, + data=ExecutionData( + result_data=ResultData.from_qvm( + QVMResultData.from_memory_map( + { + k: RegisterData.from_f64([v]) + for k, v in qam._mock_results.items() # type: ignore + } + ) + ) + ), ) quantum_computer.qam.run = Mock(quantum_computer.qam.run, side_effect=run) # type: ignore diff --git a/cirq-rigetti/cirq_rigetti/json_test_data/RigettiQCSAspenDevice.json b/cirq-rigetti/cirq_rigetti/json_test_data/RigettiQCSAspenDevice.json index 6142c73ee3e..5c03683acf8 100644 --- a/cirq-rigetti/cirq_rigetti/json_test_data/RigettiQCSAspenDevice.json +++ b/cirq-rigetti/cirq_rigetti/json_test_data/RigettiQCSAspenDevice.json @@ -1,4 +1,4 @@ { "cirq_type": "RigettiQCSAspenDevice", - "isa": {"architecture": {"edges": [{"node_ids": [0, 1]}, {"node_ids": [10, 11]}, {"node_ids": [20, 21]}, {"node_ids": [30, 31]}, {"node_ids": [1, 2]}, {"node_ids": [11, 12]}, {"node_ids": [21, 22]}, {"node_ids": [31, 32]}, {"node_ids": [2, 3]}, {"node_ids": [12, 13]}, {"node_ids": [22, 23]}, {"node_ids": [32, 33]}, {"node_ids": [3, 4]}, {"node_ids": [13, 14]}, {"node_ids": [23, 24]}, {"node_ids": [33, 34]}, {"node_ids": [4, 5]}, {"node_ids": [14, 15]}, {"node_ids": [24, 25]}, {"node_ids": [34, 35]}, {"node_ids": [5, 6]}, {"node_ids": [15, 16]}, {"node_ids": [25, 26]}, {"node_ids": [35, 36]}, {"node_ids": [6, 7]}, {"node_ids": [16, 17]}, {"node_ids": [26, 27]}, {"node_ids": [36, 37]}, {"node_ids": [7, 0]}, {"node_ids": [17, 10]}, {"node_ids": [27, 20]}, {"node_ids": [37, 30]}, {"node_ids": [2, 15]}, {"node_ids": [12, 25]}, {"node_ids": [22, 35]}, {"node_ids": [1, 16]}, {"node_ids": [11, 26]}, {"node_ids": [21, 36]}], "family": "Aspen", "nodes": [{"node_id": 0}, {"node_id": 10}, {"node_id": 20}, {"node_id": 30}, {"node_id": 1}, {"node_id": 11}, {"node_id": 21}, {"node_id": 31}, {"node_id": 2}, {"node_id": 12}, {"node_id": 22}, {"node_id": 32}, {"node_id": 3}, {"node_id": 13}, {"node_id": 23}, {"node_id": 33}, {"node_id": 4}, {"node_id": 14}, {"node_id": 24}, {"node_id": 34}, {"node_id": 5}, {"node_id": 15}, {"node_id": 25}, {"node_id": 35}, {"node_id": 6}, {"node_id": 16}, {"node_id": 26}, {"node_id": 36}, {"node_id": 7}, {"node_id": 17}, {"node_id": 27}, {"node_id": 37}]}, "benchmarks": [{"characteristics": [], "name": "randomized_benchmark_1q", "parameters": [], "sites": [{"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990014768242277, "error": 8.73150855778037e-05}], "node_ids": [0]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9989476368905135, "error": 0.00012217517130854244}], "node_ids": [10]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9986668479374058, "error": 0.0002366968194445901}], "node_ids": [20]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9284759496076576, "error": 0.01384571628704429}], "node_ids": [30]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9781665691238797, "error": 0.001196663930422238}], "node_ids": [1]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9988513580060256, "error": 0.0001549777210615458}], "node_ids": [11]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.999067926457613, "error": 0.00014263619046711899}], "node_ids": [21]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975005110338343, "error": 0.002419988573926701}], "node_ids": [31]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975863310043617, "error": 0.0002051442479213487}], "node_ids": [2]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9987920996598936, "error": 9.66507113457637e-05}], "node_ids": [12]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995, "error": 0.001}], "node_ids": [22]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9101559576214623, "error": 0.033865872960939084}], "node_ids": [32]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9994368887748502, "error": 5.722942225922073e-05}], "node_ids": [3]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990873195939498, "error": 0.00010504994111381637}], "node_ids": [13]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9992947355694914, "error": 0.00018518705476169785}], "node_ids": [23]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.991498471830458, "error": 0.0007461947456058444}], "node_ids": [33]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975364503140911, "error": 0.0001154890721783987}], "node_ids": [4]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9986661688177298, "error": 0.00018386005888028296}], "node_ids": [14]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9992878727851684, "error": 0.00014086170764231775}], "node_ids": [24]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9958065340925156, "error": 0.0003997170830167558}], "node_ids": [34]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9991465698983135, "error": 0.00019659608664435033}], "node_ids": [5]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9983382253979842, "error": 9.751793682274665e-05}], "node_ids": [15]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9991884500270085, "error": 0.00029126343489787107}], "node_ids": [25]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9976101961550574, "error": 0.00015837462362310896}], "node_ids": [35]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8787430035816047, "error": 0.015580033368863978}], "node_ids": [6]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.997632218222324, "error": 0.000358193979832841}], "node_ids": [16]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9982609453772381, "error": 3.282346327086156e-05}], "node_ids": [26]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9985235880380974, "error": 0.00013031842689615315}], "node_ids": [36]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9992413354046864, "error": 0.0001584726199598884}], "node_ids": [7]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9993158553426053, "error": 0.00012001789212572259}], "node_ids": [17]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9967169325473852, "error": 0.00014084268927341739}], "node_ids": [27]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9979959217153511, "error": 0.0001469935047973231}], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "randomized_benchmark_simultaneous_1q", "parameters": [], "sites": [{"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9946114679713964, "error": 0.0005138397010982363, "node_ids": [0]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9976721837794651, "error": 6.403065012110396e-05, "node_ids": [10]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9928196641682931, "error": 0.0004051216462122941, "node_ids": [20]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9439167591720996, "error": 0.006654543460805785, "node_ids": [30]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9924676736348497, "error": 0.0006557038933730506, "node_ids": [1]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9971452991609712, "error": 0.0003790641594124611, "node_ids": [11]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.996850142506625, "error": 0.00015820978199188694, "node_ids": [21]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9779215175567635, "error": 0.0013105670741001842, "node_ids": [31]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9970460343562613, "error": 0.00019696705235894404, "node_ids": [2]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9786782000260732, "error": 0.0026670381434772696, "node_ids": [12]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9909211630670892, "error": 0.0007152094432614018, "node_ids": [22]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9415478931544796, "error": 0.006478913437999898, "node_ids": [32]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9919403414158664, "error": 0.0021398792498404976, "node_ids": [3]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9981863348040538, "error": 9.129916500736489e-05, "node_ids": [13]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9955172035113002, "error": 0.0002772939154287422, "node_ids": [23]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.996377104491955, "error": 0.0002032732295012543, "node_ids": [33]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9973594055686299, "error": 0.00021986632028144252, "node_ids": [4]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9955236078496581, "error": 0.000203890237660312, "node_ids": [14]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9860102097844138, "error": 0.0015401413663105469, "node_ids": [24]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995524060615001, "error": 0.00022246174207002755, "node_ids": [34]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9978429700017419, "error": 0.00015989543148329767, "node_ids": [5]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9995497829780498, "error": 0.003954173650592231, "node_ids": [15]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9960500778954633, "error": 0.0005233098365838534, "node_ids": [25]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9977090027105657, "error": 0.00018549214552767492, "node_ids": [35]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9649929433781621, "error": 0.00662112077735874, "node_ids": [6]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9939967978894072, "error": 0.0007144728746783534, "node_ids": [16]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9964726338900657, "error": 0.00023818130993591338, "node_ids": [26]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9909873575828996, "error": 0.0008442821510109252, "node_ids": [36]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9972167990210178, "error": 0.00015943015558359975, "node_ids": [7]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9966812548731849, "error": 0.00026586831036954956, "node_ids": [17]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9920191864527617, "error": 0.0006318071314806755, "node_ids": [27]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.5811349166005083, "error": 0.19498095021915748, "node_ids": [37]}], "node_ids": [0, 10, 20, 30, 1, 11, 21, 31, 2, 12, 22, 32, 3, 13, 23, 33, 4, 14, 24, 34, 5, 15, 25, 35, 6, 16, 26, 36, 7, 17, 27, 37]}], "node_count": 32}], "instructions": [{"characteristics": [], "name": "RESET", "parameters": [], "sites": [{"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [0]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990000000000001}], "node_ids": [10]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [20]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9965}], "node_ids": [30]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9955000000000002}], "node_ids": [1]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.955}], "node_ids": [11]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9935}], "node_ids": [21]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.996}], "node_ids": [31]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [2]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990000000000001}], "node_ids": [12]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [22]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9339999999999999}], "node_ids": [32]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.991}], "node_ids": [3]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [13]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [23]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9915}], "node_ids": [33]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9985}], "node_ids": [4]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990000000000001}], "node_ids": [14]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.975}], "node_ids": [24]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9995}], "node_ids": [34]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.992}], "node_ids": [5]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [15]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995}], "node_ids": [25]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990000000000001}], "node_ids": [35]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9775}], "node_ids": [6]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [16]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [26]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [36]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9905000000000002}], "node_ids": [7]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9595}], "node_ids": [17]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9895}], "node_ids": [27]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.972}], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "I", "parameters": [], "sites": [{"characteristics": [], "node_ids": [0]}, {"characteristics": [], "node_ids": [10]}, {"characteristics": [], "node_ids": [20]}, {"characteristics": [], "node_ids": [30]}, {"characteristics": [], "node_ids": [1]}, {"characteristics": [], "node_ids": [11]}, {"characteristics": [], "node_ids": [21]}, {"characteristics": [], "node_ids": [31]}, {"characteristics": [], "node_ids": [2]}, {"characteristics": [], "node_ids": [12]}, {"characteristics": [], "node_ids": [22]}, {"characteristics": [], "node_ids": [32]}, {"characteristics": [], "node_ids": [3]}, {"characteristics": [], "node_ids": [13]}, {"characteristics": [], "node_ids": [23]}, {"characteristics": [], "node_ids": [33]}, {"characteristics": [], "node_ids": [4]}, {"characteristics": [], "node_ids": [14]}, {"characteristics": [], "node_ids": [24]}, {"characteristics": [], "node_ids": [34]}, {"characteristics": [], "node_ids": [5]}, {"characteristics": [], "node_ids": [15]}, {"characteristics": [], "node_ids": [25]}, {"characteristics": [], "node_ids": [35]}, {"characteristics": [], "node_ids": [6]}, {"characteristics": [], "node_ids": [16]}, {"characteristics": [], "node_ids": [26]}, {"characteristics": [], "node_ids": [36]}, {"characteristics": [], "node_ids": [7]}, {"characteristics": [], "node_ids": [17]}, {"characteristics": [], "node_ids": [27]}, {"characteristics": [], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "RX", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [], "node_ids": [0]}, {"characteristics": [], "node_ids": [10]}, {"characteristics": [], "node_ids": [20]}, {"characteristics": [], "node_ids": [30]}, {"characteristics": [], "node_ids": [1]}, {"characteristics": [], "node_ids": [11]}, {"characteristics": [], "node_ids": [21]}, {"characteristics": [], "node_ids": [31]}, {"characteristics": [], "node_ids": [2]}, {"characteristics": [], "node_ids": [12]}, {"characteristics": [], "node_ids": [22]}, {"characteristics": [], "node_ids": [32]}, {"characteristics": [], "node_ids": [3]}, {"characteristics": [], "node_ids": [13]}, {"characteristics": [], "node_ids": [23]}, {"characteristics": [], "node_ids": [33]}, {"characteristics": [], "node_ids": [4]}, {"characteristics": [], "node_ids": [14]}, {"characteristics": [], "node_ids": [24]}, {"characteristics": [], "node_ids": [34]}, {"characteristics": [], "node_ids": [5]}, {"characteristics": [], "node_ids": [15]}, {"characteristics": [], "node_ids": [25]}, {"characteristics": [], "node_ids": [35]}, {"characteristics": [], "node_ids": [6]}, {"characteristics": [], "node_ids": [16]}, {"characteristics": [], "node_ids": [26]}, {"characteristics": [], "node_ids": [36]}, {"characteristics": [], "node_ids": [7]}, {"characteristics": [], "node_ids": [17]}, {"characteristics": [], "node_ids": [27]}, {"characteristics": [], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "RZ", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [], "node_ids": [0]}, {"characteristics": [], "node_ids": [10]}, {"characteristics": [], "node_ids": [20]}, {"characteristics": [], "node_ids": [30]}, {"characteristics": [], "node_ids": [1]}, {"characteristics": [], "node_ids": [11]}, {"characteristics": [], "node_ids": [21]}, {"characteristics": [], "node_ids": [31]}, {"characteristics": [], "node_ids": [2]}, {"characteristics": [], "node_ids": [12]}, {"characteristics": [], "node_ids": [22]}, {"characteristics": [], "node_ids": [32]}, {"characteristics": [], "node_ids": [3]}, {"characteristics": [], "node_ids": [13]}, {"characteristics": [], "node_ids": [23]}, {"characteristics": [], "node_ids": [33]}, {"characteristics": [], "node_ids": [4]}, {"characteristics": [], "node_ids": [14]}, {"characteristics": [], "node_ids": [24]}, {"characteristics": [], "node_ids": [34]}, {"characteristics": [], "node_ids": [5]}, {"characteristics": [], "node_ids": [15]}, {"characteristics": [], "node_ids": [25]}, {"characteristics": [], "node_ids": [35]}, {"characteristics": [], "node_ids": [6]}, {"characteristics": [], "node_ids": [16]}, {"characteristics": [], "node_ids": [26]}, {"characteristics": [], "node_ids": [36]}, {"characteristics": [], "node_ids": [7]}, {"characteristics": [], "node_ids": [17]}, {"characteristics": [], "node_ids": [27]}, {"characteristics": [], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "CZ", "parameters": [], "sites": [{"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9753651504345361, "error": 0.004595877776144091}], "node_ids": [10, 11]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9755866659041523, "error": 0.005243058783945565}], "node_ids": [20, 21]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8286900221431495, "error": 0.01143090379181824}], "node_ids": [30, 31]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9001687193979396, "error": 0.0051462406107075035}], "node_ids": [11, 12]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9449743962504659, "error": 0.008870880926108419}], "node_ids": [21, 22]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9085683878344128, "error": 0.0043219459614298635}], "node_ids": [31, 32]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8910153098573566, "error": 0.004731463104524665}], "node_ids": [2, 3]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9108126881271943, "error": 0.005125591528241701}], "node_ids": [12, 13]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9435032010819235, "error": 0.0072239386614387615}], "node_ids": [22, 23]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8898217192054831, "error": 0.005739120215314847}], "node_ids": [32, 33]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9649680103756875, "error": 0.005516937483191729}], "node_ids": [3, 4]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9394355816363972, "error": 0.00988838667854407}], "node_ids": [13, 14]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9765203186195567, "error": 0.0037725624801795182}], "node_ids": [23, 24]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9526204716031628, "error": 0.006468456763689166}], "node_ids": [33, 34]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9780527372139809, "error": 0.00504707888147652}], "node_ids": [4, 5]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9676202635031828, "error": 0.0049731206507193605}], "node_ids": [14, 15]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9609775366189536, "error": 0.00644143184791523}], "node_ids": [24, 25]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.7645607126725205, "error": 0.019745777486102224}], "node_ids": [34, 35]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8303763369116465, "error": 0.010060319465128989}], "node_ids": [5, 6]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9540445504947002, "error": 0.00824474518610689}], "node_ids": [25, 26]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9748429614517267, "error": 0.0038418628910318635}], "node_ids": [35, 36]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8100634896998636, "error": 0.013825786636139733}], "node_ids": [6, 7]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.6931833831938762, "error": 0.014458938417054416}], "node_ids": [26, 27]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9592041261648115, "error": 0.010261521106087522}], "node_ids": [36, 37]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9079312308544519, "error": 0.00462849569244367}], "node_ids": [7, 0]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9850629080777665, "error": 0.0029074065516894376}], "node_ids": [17, 10]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9664265725526411, "error": 0.005271410143088285}], "node_ids": [27, 20]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.898843774874425, "error": 0.00620532032682923}], "node_ids": [37, 30]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.831062204997676, "error": 0.010078958447311916}], "node_ids": [2, 15]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9632969350239442, "error": 0.01399614316407363}], "node_ids": [12, 25]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9672297760436877, "error": 0.005915791891666202}], "node_ids": [22, 35]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8366644245586075, "error": 0.01036661865070287}], "node_ids": [11, 26]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9761030583154751, "error": 0.0054147347800857}], "node_ids": [21, 36]}], "node_count": 2}, {"characteristics": [], "name": "CPHASE", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.01, "error": 0.99}], "node_ids": [0, 1]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.971463228307044, "error": 0.004966716794602377}], "node_ids": [10, 11]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9159572953149564, "error": 0.005160833340164584}], "node_ids": [20, 21]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8628084595375912, "error": 0.008160606048372532}], "node_ids": [30, 31]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8925539716389217, "error": 0.006244663392810073}], "node_ids": [11, 12]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9584133294973214, "error": 0.007852363863179135}], "node_ids": [21, 22]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9160204214206844, "error": 0.004146337212883622}], "node_ids": [31, 32]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8135531082164847, "error": 0.011869659372099307}], "node_ids": [2, 3]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8798751066241616, "error": 0.010196338682047385}], "node_ids": [12, 13]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9026789964398357, "error": 0.004178452124873687}], "node_ids": [22, 23]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9208469573604819, "error": 0.005068014171996361}], "node_ids": [32, 33]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9139225038168148, "error": 0.004746224523520801}], "node_ids": [3, 4]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8481829567666604, "error": 0.010053300115386304}], "node_ids": [13, 14]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9062978129000505, "error": 0.0057453364879587986}], "node_ids": [23, 24]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9723726567581399, "error": 0.006755653591009474}], "node_ids": [33, 34]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9651167470310014, "error": 0.0058397592505465366}], "node_ids": [4, 5]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.90295400434075, "error": 0.008522504495468371}], "node_ids": [14, 15]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8742061183538135, "error": 0.008474532564905907}], "node_ids": [24, 25]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8864299901243485, "error": 0.007743788127405063}], "node_ids": [34, 35]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8270359832510702, "error": 0.01080348282143765}], "node_ids": [5, 6]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9085674351041705, "error": 0.004684825943666367}], "node_ids": [25, 26]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.929812817846686, "error": 0.01368822069184763}], "node_ids": [35, 36]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8546894721495946, "error": 0.009861048187905194}], "node_ids": [6, 7]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8247592143305311, "error": 0.01156746875308558}], "node_ids": [26, 27]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8945168789871193, "error": 0.00747536376919003}], "node_ids": [36, 37]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8549488544090957, "error": 0.008990124631734327}], "node_ids": [7, 0]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9816814387735039, "error": 0.002913510478450287}], "node_ids": [17, 10]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8836580663650178, "error": 0.005364576091308071}], "node_ids": [27, 20]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8124545973743267, "error": 0.011642660641579629}], "node_ids": [37, 30]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8245988553430752, "error": 0.010897575376382074}], "node_ids": [2, 15]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8524297264513226, "error": 0.010322677060797123}], "node_ids": [22, 35]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8675348226369772, "error": 0.008546198017427715}], "node_ids": [11, 26]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9496135242835484, "error": 0.011067609289883268}], "node_ids": [21, 36]}], "node_count": 2}, {"characteristics": [], "name": "XY", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8408157479939731, "error": 0.007848729837003356, "parameter_values": [3.141592653589793]}], "node_ids": [0, 1]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9709860226780591, "error": 0.004091597917307637, "parameter_values": [3.141592653589793]}], "node_ids": [10, 11]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9619869304728842, "error": 0.007472036847948983, "parameter_values": [3.141592653589793]}], "node_ids": [20, 21]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.7890022861131419, "error": 0.014233215746447894, "parameter_values": [3.141592653589793]}], "node_ids": [30, 31]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8765573605619301, "error": 0.007230212688935576, "parameter_values": [3.141592653589793]}], "node_ids": [11, 12]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9397421249035781, "error": 0.009435007887419413, "parameter_values": [3.141592653589793]}], "node_ids": [21, 22]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9061824925262805, "error": 0.0038477930470517245, "parameter_values": [3.141592653589793]}], "node_ids": [31, 32]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.849378211977543, "error": 0.007166695761996091, "parameter_values": [3.141592653589793]}], "node_ids": [2, 3]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8840680096280568, "error": 0.007745303311191681, "parameter_values": [3.141592653589793]}], "node_ids": [12, 13]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.900499175316029, "error": 0.004463837481400907, "parameter_values": [3.141592653589793]}], "node_ids": [22, 23]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8336403155141315, "error": 0.010597887696785517, "parameter_values": [3.141592653589793]}], "node_ids": [32, 33]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9108127609480108, "error": 0.003538280144219538, "parameter_values": [3.141592653589793]}], "node_ids": [3, 4]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9560786183831673, "error": 0.005826844879391893, "parameter_values": [3.141592653589793]}], "node_ids": [13, 14]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9708183482585359, "error": 0.0038399049060449083, "parameter_values": [3.141592653589793]}], "node_ids": [23, 24]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9710797386281217, "error": 0.005615929445161049, "parameter_values": [3.141592653589793]}], "node_ids": [33, 34]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9821007073186795, "error": 0.007377414108448926, "parameter_values": [3.141592653589793]}], "node_ids": [4, 5]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9551859905395716, "error": 0.008451625878969249, "parameter_values": [3.141592653589793]}], "node_ids": [14, 15]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9663900014538211, "error": 0.007411221388838454, "parameter_values": [3.141592653589793]}], "node_ids": [24, 25]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9694620491354923, "error": 0.006227536795285905, "parameter_values": [3.141592653589793]}], "node_ids": [34, 35]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8086263752246989, "error": 0.014569387178511941, "parameter_values": [3.141592653589793]}], "node_ids": [5, 6]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8240523379346644, "error": 0.01020619149175649, "parameter_values": [3.141592653589793]}], "node_ids": [25, 26]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9718891745852615, "error": 0.007522952516893043, "parameter_values": [3.141592653589793]}], "node_ids": [35, 36]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8534291802657573, "error": 0.006298878108899186, "parameter_values": [3.141592653589793]}], "node_ids": [6, 7]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8135556166229594, "error": 0.01315476937658125, "parameter_values": [3.141592653589793]}], "node_ids": [16, 17]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8139470185135277, "error": 0.008910243205244979, "parameter_values": [3.141592653589793]}], "node_ids": [26, 27]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9713067841516583, "error": 0.003905305899671171, "parameter_values": [3.141592653589793]}], "node_ids": [36, 37]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9775132262708732, "error": 0.008213354306011474, "parameter_values": [3.141592653589793]}], "node_ids": [7, 0]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9776629109228542, "error": 0.0064359539619436405, "parameter_values": [3.141592653589793]}], "node_ids": [17, 10]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9089422694822248, "error": 0.0038930465852373987, "parameter_values": [3.141592653589793]}], "node_ids": [27, 20]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8916354354901699, "error": 0.0065613812392840394, "parameter_values": [3.141592653589793]}], "node_ids": [37, 30]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8743317615472828, "error": 0.006341462805366402, "parameter_values": [3.141592653589793]}], "node_ids": [2, 15]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8846873458300735, "error": 0.006271203484774115, "parameter_values": [3.141592653589793]}], "node_ids": [12, 25]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9738714323675588, "error": 0.0058662900384041875, "parameter_values": [3.141592653589793]}], "node_ids": [22, 35]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8377296616522574, "error": 0.007949806734800124, "parameter_values": [3.141592653589793]}], "node_ids": [1, 16]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9608100658834562, "error": 0.01190020699313093, "parameter_values": [3.141592653589793]}], "node_ids": [21, 36]}], "node_count": 2}, {"characteristics": [], "name": "MEASURE", "parameters": [], "sites": [{"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.97}], "node_ids": [0]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.961}], "node_ids": [10]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.983}], "node_ids": [20]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9239999999999999}], "node_ids": [30]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.963}], "node_ids": [1]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.897}], "node_ids": [11]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9620000000000001}], "node_ids": [21]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9469999999999998}], "node_ids": [31]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9469999999999998}], "node_ids": [2]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.7989999999999999}], "node_ids": [12]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.965}], "node_ids": [22]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.88}], "node_ids": [32]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.944}], "node_ids": [3]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.973}], "node_ids": [13]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9739999999999999}], "node_ids": [23]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.911}], "node_ids": [33]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.968}], "node_ids": [4]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.926}], "node_ids": [14]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.968}], "node_ids": [24]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.977}], "node_ids": [34]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.945}], "node_ids": [5]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.955}], "node_ids": [15]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.978}], "node_ids": [25]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.968}], "node_ids": [35]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8180000000000001}], "node_ids": [6]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.871}], "node_ids": [16]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.975}], "node_ids": [26]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.948}], "node_ids": [36]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9670000000000001}], "node_ids": [7]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.905}], "node_ids": [17]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9139999999999999}], "node_ids": [27]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9570000000000001}], "node_ids": [37]}], "node_count": 1}], "name": "Aspen-9"} + "isa": {"architecture": {"edges": [{"node_ids": [0, 1]}, {"node_ids": [10, 11]}, {"node_ids": [20, 21]}, {"node_ids": [30, 31]}, {"node_ids": [1, 2]}, {"node_ids": [11, 12]}, {"node_ids": [21, 22]}, {"node_ids": [31, 32]}, {"node_ids": [2, 3]}, {"node_ids": [12, 13]}, {"node_ids": [22, 23]}, {"node_ids": [32, 33]}, {"node_ids": [3, 4]}, {"node_ids": [13, 14]}, {"node_ids": [23, 24]}, {"node_ids": [33, 34]}, {"node_ids": [4, 5]}, {"node_ids": [14, 15]}, {"node_ids": [24, 25]}, {"node_ids": [34, 35]}, {"node_ids": [5, 6]}, {"node_ids": [15, 16]}, {"node_ids": [25, 26]}, {"node_ids": [35, 36]}, {"node_ids": [6, 7]}, {"node_ids": [16, 17]}, {"node_ids": [26, 27]}, {"node_ids": [36, 37]}, {"node_ids": [7, 0]}, {"node_ids": [17, 10]}, {"node_ids": [27, 20]}, {"node_ids": [37, 30]}, {"node_ids": [2, 15]}, {"node_ids": [12, 25]}, {"node_ids": [22, 35]}, {"node_ids": [1, 16]}, {"node_ids": [11, 26]}, {"node_ids": [21, 36]}], "family": "Aspen", "nodes": [{"node_id": 0}, {"node_id": 10}, {"node_id": 20}, {"node_id": 30}, {"node_id": 1}, {"node_id": 11}, {"node_id": 21}, {"node_id": 31}, {"node_id": 2}, {"node_id": 12}, {"node_id": 22}, {"node_id": 32}, {"node_id": 3}, {"node_id": 13}, {"node_id": 23}, {"node_id": 33}, {"node_id": 4}, {"node_id": 14}, {"node_id": 24}, {"node_id": 34}, {"node_id": 5}, {"node_id": 15}, {"node_id": 25}, {"node_id": 35}, {"node_id": 6}, {"node_id": 16}, {"node_id": 26}, {"node_id": 36}, {"node_id": 7}, {"node_id": 17}, {"node_id": 27}, {"node_id": 37}]}, "benchmarks": [{"characteristics": [], "name": "randomized_benchmark_1q", "parameters": [], "sites": [{"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990014768242276, "error": 8.73150855778037e-05}], "node_ids": [0]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9989476368905136, "error": 0.00012217517130854244}], "node_ids": [10]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9986668479374058, "error": 0.0002366968194445901}], "node_ids": [20]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9284759496076576, "error": 0.01384571628704429}], "node_ids": [30]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9781665691238796, "error": 0.001196663930422238}], "node_ids": [1]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9988513580060256, "error": 0.0001549777210615458}], "node_ids": [11]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.999067926457613, "error": 0.000142636190467119}], "node_ids": [21]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975005110338344, "error": 0.002419988573926701}], "node_ids": [31]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975863310043616, "error": 0.0002051442479213487}], "node_ids": [2]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9987920996598936, "error": 9.66507113457637e-05}], "node_ids": [12]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995, "error": 0.001}], "node_ids": [22]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9101559576214624, "error": 0.033865872960939084}], "node_ids": [32]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9994368887748502, "error": 5.722942225922073e-05}], "node_ids": [3]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9990873195939498, "error": 0.00010504994111381636}], "node_ids": [13]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9992947355694914, "error": 0.00018518705476169785}], "node_ids": [23]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.991498471830458, "error": 0.0007461947456058444}], "node_ids": [33]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975364503140912, "error": 0.0001154890721783987}], "node_ids": [4]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9986661688177298, "error": 0.00018386005888028296}], "node_ids": [14]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9992878727851684, "error": 0.00014086170764231775}], "node_ids": [24]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9958065340925156, "error": 0.0003997170830167558}], "node_ids": [34]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9991465698983136, "error": 0.00019659608664435033}], "node_ids": [5]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9983382253979842, "error": 9.751793682274664e-05}], "node_ids": [15]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9991884500270084, "error": 0.00029126343489787107}], "node_ids": [25]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9976101961550574, "error": 0.00015837462362310896}], "node_ids": [35]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8787430035816047, "error": 0.015580033368863978}], "node_ids": [6]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.997632218222324, "error": 0.000358193979832841}], "node_ids": [16]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998260945377238, "error": 3.282346327086156e-05}], "node_ids": [26]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9985235880380974, "error": 0.00013031842689615315}], "node_ids": [36]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9992413354046864, "error": 0.0001584726199598884}], "node_ids": [7]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9993158553426053, "error": 0.0001200178921257226}], "node_ids": [17]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9967169325473852, "error": 0.0001408426892734174}], "node_ids": [27]}, {"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9979959217153512, "error": 0.0001469935047973231}], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "randomized_benchmark_simultaneous_1q", "parameters": [], "sites": [{"characteristics": [{"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9946114679713964, "error": 0.0005138397010982363, "node_ids": [0]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9976721837794652, "error": 6.403065012110396e-05, "node_ids": [10]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9928196641682931, "error": 0.0004051216462122941, "node_ids": [20]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9439167591720996, "error": 0.006654543460805785, "node_ids": [30]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9924676736348496, "error": 0.0006557038933730506, "node_ids": [1]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9971452991609712, "error": 0.0003790641594124611, "node_ids": [11]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.996850142506625, "error": 0.00015820978199188694, "node_ids": [21]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9779215175567636, "error": 0.0013105670741001842, "node_ids": [31]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9970460343562612, "error": 0.00019696705235894404, "node_ids": [2]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9786782000260732, "error": 0.0026670381434772696, "node_ids": [12]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9909211630670892, "error": 0.0007152094432614018, "node_ids": [22]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9415478931544796, "error": 0.006478913437999898, "node_ids": [32]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9919403414158664, "error": 0.0021398792498404976, "node_ids": [3]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9981863348040538, "error": 9.129916500736488e-05, "node_ids": [13]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9955172035113002, "error": 0.0002772939154287422, "node_ids": [23]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.996377104491955, "error": 0.0002032732295012543, "node_ids": [33]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.99735940556863, "error": 0.00021986632028144252, "node_ids": [4]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995523607849658, "error": 0.000203890237660312, "node_ids": [14]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9860102097844138, "error": 0.0015401413663105469, "node_ids": [24]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995524060615001, "error": 0.00022246174207002755, "node_ids": [34]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.997842970001742, "error": 0.00015989543148329767, "node_ids": [5]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9995497829780498, "error": 0.003954173650592231, "node_ids": [15]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9960500778954632, "error": 0.0005233098365838534, "node_ids": [25]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9977090027105656, "error": 0.00018549214552767492, "node_ids": [35]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.964992943378162, "error": 0.00662112077735874, "node_ids": [6]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9939967978894072, "error": 0.0007144728746783534, "node_ids": [16]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9964726338900656, "error": 0.00023818130993591335, "node_ids": [26]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9909873575828996, "error": 0.0008442821510109252, "node_ids": [36]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9972167990210178, "error": 0.00015943015558359975, "node_ids": [7]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9966812548731848, "error": 0.00026586831036954956, "node_ids": [17]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9920191864527615, "error": 0.0006318071314806755, "node_ids": [27]}, {"name": "fRB", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.5811349166005083, "error": 0.19498095021915748, "node_ids": [37]}], "node_ids": [0, 10, 20, 30, 1, 11, 21, 31, 2, 12, 22, 32, 3, 13, 23, 33, 4, 14, 24, 34, 5, 15, 25, 35, 6, 16, 26, 36, 7, 17, 27, 37]}], "node_count": 32}], "instructions": [{"characteristics": [], "name": "RESET", "parameters": [], "sites": [{"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [0]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.999}], "node_ids": [10]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [20]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9965}], "node_ids": [30]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9955000000000002}], "node_ids": [1]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.955}], "node_ids": [11]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9935}], "node_ids": [21]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.996}], "node_ids": [31]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [2]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.999}], "node_ids": [12]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [22]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.934}], "node_ids": [32]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.991}], "node_ids": [3]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9975}], "node_ids": [13]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [23]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9915}], "node_ids": [33]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9985}], "node_ids": [4]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.999}], "node_ids": [14]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.975}], "node_ids": [24]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9995}], "node_ids": [34]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.992}], "node_ids": [5]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [15]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.995}], "node_ids": [25]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.999}], "node_ids": [35]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9775}], "node_ids": [6]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [16]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [26]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.998}], "node_ids": [36]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9905000000000002}], "node_ids": [7]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9595}], "node_ids": [17]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9895}], "node_ids": [27]}, {"characteristics": [{"name": "fAR", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.972}], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "I", "parameters": [], "sites": [{"characteristics": [], "node_ids": [0]}, {"characteristics": [], "node_ids": [10]}, {"characteristics": [], "node_ids": [20]}, {"characteristics": [], "node_ids": [30]}, {"characteristics": [], "node_ids": [1]}, {"characteristics": [], "node_ids": [11]}, {"characteristics": [], "node_ids": [21]}, {"characteristics": [], "node_ids": [31]}, {"characteristics": [], "node_ids": [2]}, {"characteristics": [], "node_ids": [12]}, {"characteristics": [], "node_ids": [22]}, {"characteristics": [], "node_ids": [32]}, {"characteristics": [], "node_ids": [3]}, {"characteristics": [], "node_ids": [13]}, {"characteristics": [], "node_ids": [23]}, {"characteristics": [], "node_ids": [33]}, {"characteristics": [], "node_ids": [4]}, {"characteristics": [], "node_ids": [14]}, {"characteristics": [], "node_ids": [24]}, {"characteristics": [], "node_ids": [34]}, {"characteristics": [], "node_ids": [5]}, {"characteristics": [], "node_ids": [15]}, {"characteristics": [], "node_ids": [25]}, {"characteristics": [], "node_ids": [35]}, {"characteristics": [], "node_ids": [6]}, {"characteristics": [], "node_ids": [16]}, {"characteristics": [], "node_ids": [26]}, {"characteristics": [], "node_ids": [36]}, {"characteristics": [], "node_ids": [7]}, {"characteristics": [], "node_ids": [17]}, {"characteristics": [], "node_ids": [27]}, {"characteristics": [], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "RX", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [], "node_ids": [0]}, {"characteristics": [], "node_ids": [10]}, {"characteristics": [], "node_ids": [20]}, {"characteristics": [], "node_ids": [30]}, {"characteristics": [], "node_ids": [1]}, {"characteristics": [], "node_ids": [11]}, {"characteristics": [], "node_ids": [21]}, {"characteristics": [], "node_ids": [31]}, {"characteristics": [], "node_ids": [2]}, {"characteristics": [], "node_ids": [12]}, {"characteristics": [], "node_ids": [22]}, {"characteristics": [], "node_ids": [32]}, {"characteristics": [], "node_ids": [3]}, {"characteristics": [], "node_ids": [13]}, {"characteristics": [], "node_ids": [23]}, {"characteristics": [], "node_ids": [33]}, {"characteristics": [], "node_ids": [4]}, {"characteristics": [], "node_ids": [14]}, {"characteristics": [], "node_ids": [24]}, {"characteristics": [], "node_ids": [34]}, {"characteristics": [], "node_ids": [5]}, {"characteristics": [], "node_ids": [15]}, {"characteristics": [], "node_ids": [25]}, {"characteristics": [], "node_ids": [35]}, {"characteristics": [], "node_ids": [6]}, {"characteristics": [], "node_ids": [16]}, {"characteristics": [], "node_ids": [26]}, {"characteristics": [], "node_ids": [36]}, {"characteristics": [], "node_ids": [7]}, {"characteristics": [], "node_ids": [17]}, {"characteristics": [], "node_ids": [27]}, {"characteristics": [], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "RZ", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [], "node_ids": [0]}, {"characteristics": [], "node_ids": [10]}, {"characteristics": [], "node_ids": [20]}, {"characteristics": [], "node_ids": [30]}, {"characteristics": [], "node_ids": [1]}, {"characteristics": [], "node_ids": [11]}, {"characteristics": [], "node_ids": [21]}, {"characteristics": [], "node_ids": [31]}, {"characteristics": [], "node_ids": [2]}, {"characteristics": [], "node_ids": [12]}, {"characteristics": [], "node_ids": [22]}, {"characteristics": [], "node_ids": [32]}, {"characteristics": [], "node_ids": [3]}, {"characteristics": [], "node_ids": [13]}, {"characteristics": [], "node_ids": [23]}, {"characteristics": [], "node_ids": [33]}, {"characteristics": [], "node_ids": [4]}, {"characteristics": [], "node_ids": [14]}, {"characteristics": [], "node_ids": [24]}, {"characteristics": [], "node_ids": [34]}, {"characteristics": [], "node_ids": [5]}, {"characteristics": [], "node_ids": [15]}, {"characteristics": [], "node_ids": [25]}, {"characteristics": [], "node_ids": [35]}, {"characteristics": [], "node_ids": [6]}, {"characteristics": [], "node_ids": [16]}, {"characteristics": [], "node_ids": [26]}, {"characteristics": [], "node_ids": [36]}, {"characteristics": [], "node_ids": [7]}, {"characteristics": [], "node_ids": [17]}, {"characteristics": [], "node_ids": [27]}, {"characteristics": [], "node_ids": [37]}], "node_count": 1}, {"characteristics": [], "name": "CZ", "parameters": [], "sites": [{"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.975365150434536, "error": 0.004595877776144091}], "node_ids": [10, 11]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9755866659041524, "error": 0.005243058783945565}], "node_ids": [20, 21]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8286900221431495, "error": 0.01143090379181824}], "node_ids": [30, 31]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9001687193979396, "error": 0.0051462406107075035}], "node_ids": [11, 12]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.944974396250466, "error": 0.008870880926108419}], "node_ids": [21, 22]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9085683878344128, "error": 0.0043219459614298635}], "node_ids": [31, 32]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8910153098573566, "error": 0.004731463104524665}], "node_ids": [2, 3]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9108126881271944, "error": 0.005125591528241701}], "node_ids": [12, 13]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9435032010819236, "error": 0.0072239386614387615}], "node_ids": [22, 23]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8898217192054831, "error": 0.005739120215314847}], "node_ids": [32, 33]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9649680103756876, "error": 0.005516937483191729}], "node_ids": [3, 4]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9394355816363972, "error": 0.00988838667854407}], "node_ids": [13, 14]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9765203186195568, "error": 0.0037725624801795182}], "node_ids": [23, 24]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9526204716031628, "error": 0.006468456763689166}], "node_ids": [33, 34]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9780527372139808, "error": 0.00504707888147652}], "node_ids": [4, 5]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9676202635031828, "error": 0.0049731206507193605}], "node_ids": [14, 15]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9609775366189536, "error": 0.00644143184791523}], "node_ids": [24, 25]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.7645607126725205, "error": 0.019745777486102224}], "node_ids": [34, 35]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8303763369116465, "error": 0.010060319465128987}], "node_ids": [5, 6]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9540445504947002, "error": 0.00824474518610689}], "node_ids": [25, 26]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9748429614517268, "error": 0.003841862891031863}], "node_ids": [35, 36]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8100634896998636, "error": 0.013825786636139733}], "node_ids": [6, 7]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.6931833831938762, "error": 0.014458938417054416}], "node_ids": [26, 27]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9592041261648117, "error": 0.010261521106087522}], "node_ids": [36, 37]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.907931230854452, "error": 0.00462849569244367}], "node_ids": [7, 0]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9850629080777664, "error": 0.0029074065516894376}], "node_ids": [17, 10]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9664265725526412, "error": 0.005271410143088285}], "node_ids": [27, 20]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.898843774874425, "error": 0.00620532032682923}], "node_ids": [37, 30]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.831062204997676, "error": 0.010078958447311916}], "node_ids": [2, 15]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9632969350239442, "error": 0.01399614316407363}], "node_ids": [12, 25]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9672297760436877, "error": 0.005915791891666202}], "node_ids": [22, 35]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8366644245586075, "error": 0.01036661865070287}], "node_ids": [11, 26]}, {"characteristics": [{"name": "fCZ", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9761030583154752, "error": 0.0054147347800857}], "node_ids": [21, 36]}], "node_count": 2}, {"characteristics": [], "name": "CPHASE", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.01, "error": 0.99}], "node_ids": [0, 1]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.971463228307044, "error": 0.004966716794602377}], "node_ids": [10, 11]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9159572953149564, "error": 0.005160833340164584}], "node_ids": [20, 21]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8628084595375912, "error": 0.008160606048372532}], "node_ids": [30, 31]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8925539716389217, "error": 0.006244663392810073}], "node_ids": [11, 12]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9584133294973214, "error": 0.007852363863179135}], "node_ids": [21, 22]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9160204214206844, "error": 0.004146337212883622}], "node_ids": [31, 32]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8135531082164847, "error": 0.011869659372099307}], "node_ids": [2, 3]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8798751066241616, "error": 0.010196338682047385}], "node_ids": [12, 13]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9026789964398356, "error": 0.004178452124873687}], "node_ids": [22, 23]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.920846957360482, "error": 0.005068014171996361}], "node_ids": [32, 33]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9139225038168148, "error": 0.004746224523520801}], "node_ids": [3, 4]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8481829567666604, "error": 0.010053300115386304}], "node_ids": [13, 14]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9062978129000504, "error": 0.0057453364879587986}], "node_ids": [23, 24]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.97237265675814, "error": 0.006755653591009474}], "node_ids": [33, 34]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9651167470310014, "error": 0.0058397592505465366}], "node_ids": [4, 5]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.90295400434075, "error": 0.008522504495468371}], "node_ids": [14, 15]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8742061183538135, "error": 0.008474532564905907}], "node_ids": [24, 25]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8864299901243485, "error": 0.007743788127405063}], "node_ids": [34, 35]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8270359832510702, "error": 0.01080348282143765}], "node_ids": [5, 6]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9085674351041704, "error": 0.004684825943666367}], "node_ids": [25, 26]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.929812817846686, "error": 0.01368822069184763}], "node_ids": [35, 36]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8546894721495946, "error": 0.009861048187905194}], "node_ids": [6, 7]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8247592143305311, "error": 0.01156746875308558}], "node_ids": [26, 27]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8945168789871193, "error": 0.00747536376919003}], "node_ids": [36, 37]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8549488544090957, "error": 0.008990124631734327}], "node_ids": [7, 0]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.981681438773504, "error": 0.002913510478450287}], "node_ids": [17, 10]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8836580663650178, "error": 0.005364576091308071}], "node_ids": [27, 20]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8124545973743267, "error": 0.011642660641579629}], "node_ids": [37, 30]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8245988553430752, "error": 0.010897575376382074}], "node_ids": [2, 15]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8524297264513226, "error": 0.010322677060797123}], "node_ids": [22, 35]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8675348226369772, "error": 0.008546198017427715}], "node_ids": [11, 26]}, {"characteristics": [{"name": "fCPHASE", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9496135242835484, "error": 0.011067609289883268}], "node_ids": [21, 36]}], "node_count": 2}, {"characteristics": [], "name": "XY", "parameters": [{"name": "theta"}], "sites": [{"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8408157479939731, "error": 0.007848729837003356, "parameter_values": [3.141592653589793]}], "node_ids": [0, 1]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9709860226780592, "error": 0.004091597917307637, "parameter_values": [3.141592653589793]}], "node_ids": [10, 11]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9619869304728842, "error": 0.007472036847948983, "parameter_values": [3.141592653589793]}], "node_ids": [20, 21]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.7890022861131419, "error": 0.014233215746447894, "parameter_values": [3.141592653589793]}], "node_ids": [30, 31]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8765573605619301, "error": 0.007230212688935576, "parameter_values": [3.141592653589793]}], "node_ids": [11, 12]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.939742124903578, "error": 0.009435007887419413, "parameter_values": [3.141592653589793]}], "node_ids": [21, 22]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9061824925262804, "error": 0.003847793047051725, "parameter_values": [3.141592653589793]}], "node_ids": [31, 32]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.849378211977543, "error": 0.007166695761996091, "parameter_values": [3.141592653589793]}], "node_ids": [2, 3]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8840680096280568, "error": 0.007745303311191681, "parameter_values": [3.141592653589793]}], "node_ids": [12, 13]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.900499175316029, "error": 0.004463837481400907, "parameter_values": [3.141592653589793]}], "node_ids": [22, 23]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8336403155141315, "error": 0.010597887696785517, "parameter_values": [3.141592653589793]}], "node_ids": [32, 33]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9108127609480108, "error": 0.003538280144219538, "parameter_values": [3.141592653589793]}], "node_ids": [3, 4]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9560786183831672, "error": 0.005826844879391893, "parameter_values": [3.141592653589793]}], "node_ids": [13, 14]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.970818348258536, "error": 0.003839904906044908, "parameter_values": [3.141592653589793]}], "node_ids": [23, 24]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9710797386281216, "error": 0.005615929445161049, "parameter_values": [3.141592653589793]}], "node_ids": [33, 34]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9821007073186796, "error": 0.007377414108448926, "parameter_values": [3.141592653589793]}], "node_ids": [4, 5]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9551859905395716, "error": 0.008451625878969249, "parameter_values": [3.141592653589793]}], "node_ids": [14, 15]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9663900014538211, "error": 0.007411221388838454, "parameter_values": [3.141592653589793]}], "node_ids": [24, 25]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value":0.9694620491354924, "error": 0.006227536795285905, "parameter_values": [3.141592653589793]}], "node_ids": [34, 35]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8086263752246989, "error":0.01456938717851194, "parameter_values": [3.141592653589793]}], "node_ids": [5, 6]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8240523379346644, "error": 0.01020619149175649, "parameter_values": [3.141592653589793]}], "node_ids": [25, 26]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value":0.9718891745852616, "error": 0.007522952516893043, "parameter_values": [3.141592653589793]}], "node_ids": [35, 36]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8534291802657573, "error": 0.006298878108899186, "parameter_values": [3.141592653589793]}], "node_ids": [6, 7]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8135556166229594, "error": 0.01315476937658125, "parameter_values": [3.141592653589793]}], "node_ids": [16, 17]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8139470185135277, "error": 0.008910243205244979, "parameter_values": [3.141592653589793]}], "node_ids": [26, 27]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value":0.9713067841516584, "error": 0.003905305899671171, "parameter_values": [3.141592653589793]}], "node_ids": [36, 37]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9775132262708732, "error": 0.008213354306011474, "parameter_values": [3.141592653589793]}], "node_ids": [7, 0]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9776629109228542, "error": 0.0064359539619436405, "parameter_values": [3.141592653589793]}], "node_ids": [17, 10]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9089422694822248, "error":0.0038930465852373983, "parameter_values": [3.141592653589793]}], "node_ids": [27, 20]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8916354354901699, "error": 0.0065613812392840394, "parameter_values": [3.141592653589793]}], "node_ids": [37, 30]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8743317615472828, "error": 0.006341462805366402, "parameter_values": [3.141592653589793]}], "node_ids": [2, 15]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8846873458300735, "error": 0.006271203484774115, "parameter_values": [3.141592653589793]}], "node_ids": [12, 25]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9738714323675588, "error": 0.0058662900384041875, "parameter_values": [3.141592653589793]}], "node_ids": [22, 35]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8377296616522574, "error": 0.007949806734800124, "parameter_values": [3.141592653589793]}], "node_ids": [1, 16]}, {"characteristics": [{"name": "fXY", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9608100658834562, "error": 0.01190020699313093, "parameter_values": [3.141592653589793]}], "node_ids": [21, 36]}], "node_count": 2}, {"characteristics": [], "name": "MEASURE", "parameters": [], "sites": [{"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.97}], "node_ids": [0]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.961}], "node_ids": [10]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.983}], "node_ids": [20]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.924}], "node_ids": [30]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.963}], "node_ids": [1]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.897}], "node_ids": [11]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.962}], "node_ids": [21]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9469999999999998}], "node_ids": [31]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.9469999999999998}], "node_ids": [2]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.7989999999999999}], "node_ids": [12]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.965}], "node_ids": [22]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.88}], "node_ids": [32]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.944}], "node_ids": [3]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.973}], "node_ids": [13]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value":0.974}], "node_ids": [23]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.911}], "node_ids": [33]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.968}], "node_ids": [4]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.926}], "node_ids": [14]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.968}], "node_ids": [24]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.977}], "node_ids": [34]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.945}], "node_ids": [5]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.955}], "node_ids": [15]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.978}], "node_ids": [25]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.968}], "node_ids": [35]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.8180000000000001}], "node_ids": [6]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.871}], "node_ids": [16]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.975}], "node_ids": [26]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.948}], "node_ids": [36]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.967}], "node_ids": [7]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.905}], "node_ids": [17]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.914}], "node_ids": [27]}, {"characteristics": [{"name": "fRO", "timestamp": "1970-01-01T00:00:00+00:00", "value": 0.957}], "node_ids": [37]}], "node_count": 1}], "name": "Aspen-9"} } \ No newline at end of file diff --git a/cirq-rigetti/cirq_rigetti/quil_input.py b/cirq-rigetti/cirq_rigetti/quil_input.py index 9de48536fa7..99464298169 100644 --- a/cirq-rigetti/cirq_rigetti/quil_input.py +++ b/cirq-rigetti/cirq_rigetti/quil_input.py @@ -12,10 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Callable, cast, Dict, Union +from typing import Any, Callable, Type, cast, Dict, Union, List, Tuple, Optional +import sympy import numpy as np -from pyquil.parser import parse +from numpy.typing import NDArray + +from pyquil.quil import Program from pyquil.quilbase import ( Declare, DefGate, @@ -24,160 +27,216 @@ Pragma, Reset, ResetQubit, + Fence, + FenceAll, ) - -from cirq import Circuit, LineQubit -from cirq.ops import ( - CCNOT, - CNOT, - CSWAP, - CZ, - CZPowGate, - Gate, - H, - I, - ISWAP, - ISwapPowGate, - MatrixGate, - MeasurementGate, - S, - SWAP, - T, - TwoQubitDiagonalGate, - X, - Y, - Z, - ZPowGate, - rx, - ry, - rz, +from pyquil.quilatom import ( + MemoryReference, + ParameterDesignator, + QubitDesignator, + Function, + BinaryExp, + Add, + Sub, + Mul, + Div, + Pow, + Parameter, + substitute_array, + qubit_index, ) +from pyquil.simulation import matrices + +import cirq +from cirq.circuits.circuit import Circuit +from cirq.devices.insertion_noise_model import InsertionNoiseModel +from cirq.protocols.circuit_diagram_info_protocol import CircuitDiagramInfoArgs, CircuitDiagramInfo +from cirq.devices.line_qubit import LineQubit +from cirq.devices.noise_utils import OpIdentifier +from cirq.value import value_equality +from cirq.protocols import is_parameterized + +from cirq.ops.common_gates import CNOT, CZ, CZPowGate, H, S, T, ZPowGate, YPowGate, XPowGate +from cirq.ops.parity_gates import ZZPowGate, XXPowGate, YYPowGate +from cirq.ops.pauli_gates import X, Y, Z +from cirq.ops.fsim_gate import FSimGate, PhasedFSimGate +from cirq.ops.identity import I +from cirq.ops.matrix_gates import MatrixGate +from cirq.ops.measurement_gate import MeasurementGate +from cirq.ops.swap_gates import ISWAP, ISwapPowGate, SWAP +from cirq.ops.three_qubit_gates import CCNOT, CSWAP +from cirq.ops.raw_types import Gate +from cirq.ops.kraus_channel import KrausChannel +from cirq._compat import cached_method class UndefinedQuilGate(Exception): - pass + """Error for a undefined Quil Gate.""" class UnsupportedQuilInstruction(Exception): - pass + """Error for a unsupported instruction.""" # -# Functions for converting supported parameterized Quil gates. +# Cirq doesn't have direct analogues of these Quil gates # -def cphase(param: float) -> CZPowGate: - """Returns a controlled-phase gate as a Cirq CZPowGate with exponent - determined by the input param. The angle parameter of pyQuil's CPHASE - gate and the exponent of Cirq's CZPowGate differ by a factor of pi. +@value_equality(distinct_child_types=True, approximate=True) +class CPHASE00(Gate): + """Cirq equivalent to Quil CPHASE00.""" - Args: - param: Gate parameter (in radians). + def __init__(self, phi): + super().__init__() + self.phi = phi - Returns: - A CZPowGate equivalent to a CPHASE gate of given angle. - """ - return CZPowGate(exponent=param / np.pi) + def _num_qubits_(self): + return 2 + def _unitary_(self): + return matrices.CPHASE00(self.phi) -def cphase00(phi: float) -> TwoQubitDiagonalGate: - """Returns a Cirq TwoQubitDiagonalGate for pyQuil's CPHASE00 gate. + def _circuit_diagram_info_( + self, args: CircuitDiagramInfoArgs + ) -> CircuitDiagramInfo: # pragma: no cover + return CircuitDiagramInfo(wire_symbols=("@00", "@00"), exponent=self.phi / np.pi) - In pyQuil, CPHASE00(phi) = diag([exp(1j * phi), 1, 1, 1]), and in Cirq, - a TwoQubitDiagonalGate is specified by its diagonal in radians, which - would be [phi, 0, 0, 0]. + def __repr__(self) -> str: # pragma: no cover + """Represent the CPHASE gate as a string.""" + return f"CPHASE00({self.phi:.3f})" - Args: - phi: Gate parameter (in radians). + def _resolve_parameters_( + self, resolver: cirq.ParamResolver, recursive: bool + ) -> Gate: # pragma: no cover + return type(self)(phi=resolver.value_of(self.phi, recursive)) - Returns: - A TwoQubitDiagonalGate equivalent to a CPHASE00 gate of given angle. - """ - return TwoQubitDiagonalGate([phi, 0, 0, 0]) + def _is_parameterized_(self) -> bool: # pragma: no cover + parameter_names = ["phi"] + return any(is_parameterized(getattr(self, p)) for p in parameter_names) + def _value_equality_values_(self): # pragma: no cover + return (self.phi,) -def cphase01(phi: float) -> TwoQubitDiagonalGate: - """Returns a Cirq TwoQubitDiagonalGate for pyQuil's CPHASE01 gate. + def _value_equality_approximate_values_(self): # pragma: no cover + return (self.phi,) - In pyQuil, CPHASE01(phi) = diag(1, [exp(1j * phi), 1, 1]), and in Cirq, - a TwoQubitDiagonalGate is specified by its diagonal in radians, which - would be [0, phi, 0, 0]. - Args: - phi: Gate parameter (in radians). +@value_equality(distinct_child_types=True, approximate=True) +class CPHASE01(Gate): + """Cirq equivalent to Quil CPHASE01.""" - Returns: - A TwoQubitDiagonalGate equivalent to a CPHASE01 gate of given angle. - """ - return TwoQubitDiagonalGate([0, phi, 0, 0]) + def __init__(self, phi): + super().__init__() + self.phi = phi + def _num_qubits_(self): + return 2 -def cphase10(phi: float) -> TwoQubitDiagonalGate: - """Returns a Cirq TwoQubitDiagonalGate for pyQuil's CPHASE10 gate. + def _unitary_(self): + return matrices.CPHASE01(self.phi) - In pyQuil, CPHASE10(phi) = diag(1, 1, [exp(1j * phi), 1]), and in Cirq, - a TwoQubitDiagonalGate is specified by its diagonal in radians, which - would be [0, 0, phi, 0]. + def _circuit_diagram_info_( + self, args: CircuitDiagramInfoArgs + ) -> CircuitDiagramInfo: # pragma: no cover + return CircuitDiagramInfo(wire_symbols=("@01", "@01"), exponent=self.phi / np.pi) - Args: - phi: Gate parameter (in radians). + def __repr__(self) -> str: # pragma: no cover + """Represent the CPHASE gate as a string.""" + return f"CPHASE01({self.phi:.3f})" - Returns: - A TwoQubitDiagonalGate equivalent to a CPHASE10 gate of given angle. - """ - return TwoQubitDiagonalGate([0, 0, phi, 0]) + def _resolve_parameters_( + self, resolver: cirq.ParamResolver, recursive: bool + ) -> Gate: # pragma: no cover + return type(self)(phi=resolver.value_of(self.phi, recursive)) + def _is_parameterized_(self) -> bool: # pragma: no cover + parameter_names = ["phi"] + return any(is_parameterized(getattr(self, p)) for p in parameter_names) -def phase(param: float) -> ZPowGate: - """Returns a single-qubit phase gate as a Cirq ZPowGate with exponent - determined by the input param. The angle parameter of pyQuil's PHASE - gate and the exponent of Cirq's ZPowGate differ by a factor of pi. + def _value_equality_values_(self): # pragma: no cover + return (self.phi,) - Args: - param: Gate parameter (in radians). + def _value_equality_approximate_values_(self): # pragma: no cover + return (self.phi,) - Returns: - A ZPowGate equivalent to a PHASE gate of given angle. - """ - return ZPowGate(exponent=param / np.pi) +@value_equality(distinct_child_types=True, approximate=True) +class CPHASE10(Gate): + """Cirq equivalent to Quil CPHASE10.""" -def pswap(phi: float) -> MatrixGate: - """Returns a Cirq MatrixGate for pyQuil's PSWAP gate. + def __init__(self, phi): + super().__init__() + self.phi = phi - Args: - phi: Gate parameter (in radians). + def _num_qubits_(self): + return 2 - Returns: - A MatrixGate equivalent to a PSWAP gate of given angle. - """ - # fmt: off - pswap_matrix = np.array( - [ - [1, 0, 0, 0], - [0, 0, np.exp(1j * phi), 0], - [0, np.exp(1j * phi), 0, 0], - [0, 0, 0, 1] - ], - dtype=complex, - ) - # fmt: on - return MatrixGate(pswap_matrix) + def _unitary_(self): + return matrices.CPHASE10(self.phi) + def _circuit_diagram_info_( + self, args: CircuitDiagramInfoArgs + ) -> CircuitDiagramInfo: # pragma: no cover + return CircuitDiagramInfo(wire_symbols=("@10", "@10"), exponent=self.phi / np.pi) -def xy(param: float) -> ISwapPowGate: - """Returns an ISWAP-family gate as a Cirq ISwapPowGate with exponent - determined by the input param. The angle parameter of pyQuil's XY gate - and the exponent of Cirq's ISwapPowGate differ by a factor of pi. + def __repr__(self) -> str: # pragma: no cover + """Represent the CPHASE gate as a string.""" + return f"CPHASE10({self.phi:.3f})" - Args: - param: Gate parameter (in radians). + def _resolve_parameters_( + self, resolver: cirq.ParamResolver, recursive: bool + ) -> Gate: # pragma: no cover + return type(self)(phi=resolver.value_of(self.phi, recursive)) - Returns: - An ISwapPowGate equivalent to an XY gate of given angle. - """ - return ISwapPowGate(exponent=param / np.pi) + def _is_parameterized_(self) -> bool: # pragma: no cover + parameter_names = ["phi"] + return any(is_parameterized(getattr(self, p)) for p in parameter_names) + + def _value_equality_values_(self): # pragma: no cover + return (self.phi,) + + def _value_equality_approximate_values_(self): # pragma: no cover + return (self.phi,) + + +@value_equality(distinct_child_types=True, approximate=True) +class PSWAP(Gate): + """Cirq equivalent to Quil PSWAP.""" + + def __init__(self, phi): + super().__init__() + self.phi = phi + + def _num_qubits_(self): + return 2 + + def _unitary_(self): + return matrices.PSWAP(self.phi) + + def _circuit_diagram_info_( + self, args: CircuitDiagramInfoArgs + ) -> CircuitDiagramInfo: # pragma: no cover + return CircuitDiagramInfo(wire_symbols=("PSWAP", "PSWAP"), exponent=self.phi / np.pi) + + def __repr__(self) -> str: # pragma: no cover + """Represent the PSWAP gate as a string.""" + return f"PSWAP({self.phi:.3f})" + + def _resolve_parameters_( + self, resolver: cirq.ParamResolver, recursive: bool + ) -> Gate: # pragma: no cover + return type(self)(phi=resolver.value_of(self.phi, recursive)) + + def _is_parameterized_(self) -> bool: # pragma: no cover + parameter_names = ["phi"] + return any(is_parameterized(getattr(self, p)) for p in parameter_names) + + def _value_equality_values_(self): # pragma: no cover + return (self.phi,) + + def _value_equality_approximate_values_(self): # pragma: no cover + return (self.phi,) PRAGMA_ERROR = """ @@ -190,35 +249,62 @@ def xy(param: float) -> ISwapPowGate: RESET directives have special meaning on QCS, to enable active reset. """ + # Parameterized gates map to functions that produce Gate constructors. SUPPORTED_GATES: Dict[str, Union[Gate, Callable[..., Gate]]] = { "CCNOT": CCNOT, "CNOT": CNOT, "CSWAP": CSWAP, - "CPHASE": cphase, - "CPHASE00": cphase00, - "CPHASE01": cphase01, - "CPHASE10": cphase10, + "CPHASE": CZPowGate, + "CPHASE00": CPHASE00, + "CPHASE01": CPHASE01, + "CPHASE10": CPHASE10, + "PSWAP": PSWAP, "CZ": CZ, - "PHASE": phase, + "PHASE": ZPowGate, "H": H, "I": I, "ISWAP": ISWAP, - "PSWAP": pswap, - "RX": rx, - "RY": ry, - "RZ": rz, + "RX": XPowGate, + "RY": YPowGate, + "RZ": ZPowGate, "S": S, "SWAP": SWAP, "T": T, "X": X, "Y": Y, "Z": Z, - "XY": xy, + "XY": ISwapPowGate, + "RZZ": ZZPowGate, + "RYY": YYPowGate, + "RXX": XXPowGate, + "FSIM": FSimGate, + "PHASEDFSIM": PhasedFSimGate, +} + +# Gate parameters must be transformed to Cirq units +PARAMETRIC_TRANSFORMERS: Dict[str, Callable] = { + "CPHASE": lambda theta: dict(exponent=theta / np.pi, global_shift=0.0), + "CPHASE00": lambda phi: dict(phi=phi), + "CPHASE01": lambda phi: dict(phi=phi), + "CPHASE10": lambda phi: dict(phi=phi), + "PSWAP": lambda phi: dict(phi=phi), + "PHASE": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.0), + "XY": lambda theta: dict(exponent=theta / np.pi, global_shift=0.0), + "RX": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.5), + "RY": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.5), + "RZ": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.5), + "RXX": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.5), + "RYY": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.5), + "RZZ": lambda theta: dict(exponent=theta / np.pi, global_shift=-0.5), + "FSIM": lambda theta, phi: dict(theta=-1 * theta / 2, phi=-1 * phi), + "PHASEDFSIM": lambda theta, zeta, chi, gamma, phi: dict( + theta=-1 * theta / 2, zeta=zeta, chi=chi, gamma=gamma, phi=-1 * phi + ), } -def circuit_from_quil(quil: str) -> Circuit: +def circuit_from_quil(quil: Union[str, Program]) -> Circuit: """Convert a Quil program to a Cirq Circuit. Args: @@ -234,50 +320,118 @@ def circuit_from_quil(quil: str) -> Circuit: References: https://github.com/rigetti/pyquil """ + if isinstance(quil, str): + program = Program(quil) + else: + program = quil circuit = Circuit() - defined_gates = SUPPORTED_GATES.copy() - instructions = parse(quil) - for inst in instructions: - # Add DEFGATE-defined gates to defgates dict using MatrixGate. - if isinstance(inst, DefGate): - if inst.parameters: - raise UnsupportedQuilInstruction( - "Parameterized DEFGATEs are currently unsupported." - ) - defined_gates[inst.name] = MatrixGate(inst.matrix) + defined_gates, parameter_transformers = get_defined_gates(program) + + kraus_model: Dict[Tuple[QubitDesignator, ...], List[NDArray[np.complex_]]] = {} + confusion_maps: Dict[int, NDArray[np.float_]] = {} + + # Interpret the Pragmas + for inst in program: + if not isinstance(inst, Pragma): # pragma: no cover + continue + + # ADD-KRAUS provides Kraus operators that replace the gate operation + if inst.command == "ADD-KRAUS": # pragma: no cover + args = inst.args + gate_name = str(args[0]) + if gate_name in matrices.QUANTUM_GATES: + u = matrices.QUANTUM_GATES[gate_name] + elif gate_name in defined_gates: + u = defined_gates[gate_name] + else: + raise UndefinedQuilGate(f"{gate_name} is not known.") + + entries = np.fromstring( + inst.freeform_string.strip("()").replace("i", "j"), dtype=np.complex_, sep=" " + ) + dim = int(np.sqrt(len(entries))) + kraus_gate_op = entries.reshape((dim, dim)) + + kraus_op = remove_gate_from_kraus([kraus_gate_op], u)[0] + + if args in kraus_model: + kraus_model[args].append(kraus_op) + else: + kraus_model[args] = [kraus_op] + + # READOUT-POVM provides a confusion matrix + elif inst.command == "READOUT-POVM": + qubit = qubit_index(inst.args[0]) + entries = np.fromstring( + inst.freeform_string.strip("()").replace("i", "j"), dtype=np.float_, sep=" " + ) + confusion_matrix = entries.reshape((2, 2)).T + + # these types actually agree - both arrays are floats + confusion_maps[qubit] = confusion_matrix # type: ignore + + else: + raise UnsupportedQuilInstruction(PRAGMA_ERROR) # pragma: no cover + # Interpret the instructions + for inst in program: # Pass when encountering a DECLARE. - elif isinstance(inst, Declare): + if isinstance(inst, Declare): pass # Convert pyQuil gates to Cirq operations. elif isinstance(inst, PyQuilGate): quil_gate_name = inst.name quil_gate_params = inst.params - line_qubits = list(LineQubit(q.index) for q in inst.qubits) + line_qubits = list(LineQubit(qubit_index(q)) for q in inst.qubits) if quil_gate_name not in defined_gates: raise UndefinedQuilGate(f"Quil gate {quil_gate_name} not supported in Cirq.") cirq_gate_fn = defined_gates[quil_gate_name] if quil_gate_params: - circuit += cast(Callable[..., Gate], cirq_gate_fn)(*quil_gate_params)(*line_qubits) + params = [quil_expression_to_sympy(p) for p in quil_gate_params] + transformer = parameter_transformers[quil_gate_name] + circuit += cast(Callable[..., Gate], cirq_gate_fn)(**transformer(*params))( + *line_qubits + ) else: circuit += cirq_gate_fn(*line_qubits) # Convert pyQuil MEASURE operations to Cirq MeasurementGate objects. elif isinstance(inst, PyQuilMeasurement): - line_qubit = LineQubit(inst.qubit.index) + qubit = qubit_index(inst.qubit) + line_qubit = LineQubit(qubit) if inst.classical_reg is None: raise UnsupportedQuilInstruction( f"Quil measurement {inst} without classical register " f"not currently supported in Cirq." ) quil_memory_reference = inst.classical_reg.out() - circuit += MeasurementGate(1, key=quil_memory_reference)(line_qubit) + if qubit in confusion_maps: + cmap: Dict[Tuple[int, ...], NDArray[np.float_]] = {(qubit,): confusion_maps[qubit]} + """ + Argument "confusion_map" to "MeasurementGate" has incompatible type + " Dict[Tuple[int], ndarray[Any, dtype[floating[Any]]]]" + expected + "Optional[Dict[Tuple[int, ...], ndarray[Any, Any]]]" + """ + circuit += MeasurementGate(1, key=quil_memory_reference, confusion_map=cmap)( + line_qubit + ) + else: + circuit += MeasurementGate(1, key=quil_memory_reference)(line_qubit) - # Raise a targeted error when encountering a PRAGMA. + # PRAGMAs elif isinstance(inst, Pragma): - raise UnsupportedQuilInstruction(PRAGMA_ERROR) + continue + + # Drop FENCE statements + elif isinstance(inst, (Fence, FenceAll)): # pragma: no cover + continue + + # Drop DEFGATES + elif isinstance(inst, (DefGate)): # pragma: no cover + continue # Raise a targeted error when encountering a RESET. elif isinstance(inst, (Reset, ResetQubit)): @@ -289,4 +443,208 @@ def circuit_from_quil(quil: str) -> Circuit: f"Quil instruction {inst} of type {type(inst)} not currently supported in Cirq." ) + if len(kraus_model) > 0: # pragma: no cover + noise_model = kraus_noise_model_to_cirq(kraus_model, defined_gates) + circuit = circuit.with_noise(noise_model) + return circuit + + +def get_defined_gates(program: Program) -> Tuple[Dict, Dict]: + """Get the gate definitions for the program. Will include the default SUPPORTED_GATES, in + addition to any gates defined in the Quil + + Args: + program: A pyquil program which may contain some DefGates. + + Returns: + A dictionary mapping quil gate names to Cirq Gates + A dictionary mapping quil gate names to callable parameter transformers + """ + defined_gates = SUPPORTED_GATES.copy() + parameter_transformers = PARAMETRIC_TRANSFORMERS.copy() + for defgate in program.defined_gates: + if defgate.parameters: + defined_gates[defgate.name] = defgate_to_cirq(defgate) + parameter_transformers[defgate.name] = lambda *args: { + p.name: a for p, a in zip(defgate.parameters, args) + } + else: + defined_gates[defgate.name] = MatrixGate(np.asarray(defgate.matrix, dtype=np.complex_)) + return defined_gates, parameter_transformers + + +def kraus_noise_model_to_cirq( + kraus_noise_model: Dict[Tuple[QubitDesignator, ...], List[NDArray[np.complex_]]], + defined_gates: Optional[Dict[QubitDesignator, Gate]] = None, +) -> InsertionNoiseModel: # pragma: no cover + """Construct a Cirq noise model from the provided Kraus operators. + + Args: + kraus_noise_model: A dictionary where the keys are tuples of Quil gate names and qubit + indices and the values are the Kraus representation of the noise channel. + defined_gates: A dictionary mapping Quil gates to Cirq gates. + Returns: + A Cirq InsertionNoiseModel which applies the Kraus operators to the specified gates. + Raises: + Exception: If a QubitDesignator identifier is not an integer. + """ + if defined_gates is None: + # SUPPORTED_GATES values are all safe to use as `Gate` + defined_gates = SUPPORTED_GATES # type: ignore + ops_added = {} + for key, kraus_ops in kraus_noise_model.items(): + gate_name = key[0] + + try: + qubit_indices = [int(q) for q in key[1:]] # type: ignore + except ValueError as e: + raise Exception("Qubit identifier must be integers") from e + qubits = [LineQubit(q) for q in qubit_indices] + + # defined_gates is not None by this point + gate: Type[Gate] = defined_gates[gate_name] # type: ignore + target_op = OpIdentifier(gate, *qubits) + + insert_op = KrausChannel(kraus_ops, validate=True).on(*qubits) + ops_added[target_op] = insert_op + + noise_model = InsertionNoiseModel(ops_added=ops_added, require_physical_tag=False) + + return noise_model + + +def quil_expression_to_sympy(expression: ParameterDesignator): + """Convert a quil expression to a Sympy expression. + + Args: + expression: A quil expression. + + Returns: + The sympy form of the expression. + + Raises: + ValueError: Connect convert unknown BinaryExp. + ValueError: Unrecognized expression. + """ + if type(expression) in {np.int_, np.float_, np.complex_, int, float, complex}: + return expression + elif isinstance(expression, Parameter): # pragma: no cover + return sympy.Symbol(expression.name) + elif isinstance(expression, MemoryReference): + return sympy.Symbol(expression.name + f"_{expression.offset}") + elif isinstance(expression, Function): + if expression.name == "SIN": # pragma: no cover + return sympy.sin(quil_expression_to_sympy(expression.expression)) + elif expression.name == "COS": + return sympy.cos(quil_expression_to_sympy(expression.expression)) + elif expression.name == "SQRT": # pragma: no cover + return sympy.sqrt(quil_expression_to_sympy(expression.expression)) + elif expression.name == "EXP": + return sympy.exp(quil_expression_to_sympy(expression.expression)) + elif expression.name == "CIS": # pragma: no cover + return sympy.exp(1j * quil_expression_to_sympy(expression.expression)) + else: # pragma: no cover + raise ValueError(f"Cannot convert unknown function: {expression}") + + elif isinstance(expression, BinaryExp): + if isinstance(expression, Add): + return quil_expression_to_sympy(expression.op1) + quil_expression_to_sympy( + expression.op2 + ) + elif isinstance(expression, Sub): # pragma: no cover + return quil_expression_to_sympy(expression.op1) - quil_expression_to_sympy( + expression.op2 + ) + elif isinstance(expression, Mul): + return quil_expression_to_sympy(expression.op1) * quil_expression_to_sympy( + expression.op2 + ) + elif isinstance(expression, Div): # pragma: no cover + return quil_expression_to_sympy(expression.op1) / quil_expression_to_sympy( + expression.op2 + ) + elif isinstance(expression, Pow): # pragma: no cover + return quil_expression_to_sympy(expression.op1) ** quil_expression_to_sympy( + expression.op2 + ) + else: # pragma: no cover + raise ValueError(f"Cannot convert unknown BinaryExp: {expression}") + + else: # pragma: no cover + raise ValueError( + f"quil_expression_to_sympy failed to convert {expression} of type {type(expression)}" + ) + + +@cached_method +def defgate_to_cirq(defgate: DefGate): + """Convert a Quil DefGate to a Cirq Gate class. + + For non-parametric gates, it's recommended to create `MatrixGate` object. This function is + intended for the case of parametric gates. + + Args: + defgate: A quil gate defintion. + Returns: + A subclass of `Gate` corresponding to the DefGate. + """ + name = defgate.name + matrix = defgate.matrix + parameters = defgate.parameters + dim = int(np.sqrt(matrix.shape[0])) + if parameters: + parameter_names = set(p.name for p in parameters) + + def constructor(self, **kwargs): + for p, val in kwargs.items(): + assert p in parameter_names, f"{p} is not a known parameter" + setattr(self, p, val) + + def unitary(self, *args): + if parameters: + parameter_map = {p: getattr(self, p.name) for p in parameters} + return substitute_array(matrix, parameter_map) + + else: + + def constructor(self, **kwards: Any): ... + + def unitary(self, *args): # pragma: no cover + return matrix + + def circuit_diagram_info( + self, args: CircuitDiagramInfoArgs + ) -> CircuitDiagramInfo: # pragma: no cover + return CircuitDiagramInfo(wire_symbols=tuple(name for _ in range(dim))) + + def num_qubits(self): + return defgate.num_args() + + gate = type( + name, + (Gate,), + { + "__init__": constructor, + "_num_qubits_": num_qubits, + "_unitary_": unitary, + "_circuit_diagram_info_": circuit_diagram_info, + }, + ) + return gate + + +def remove_gate_from_kraus( + kraus_ops: List[NDArray[np.complex_]], gate_matrix: NDArray[np.complex_] +): # pragma: no cover + """Recover the kraus operators from a kraus composed with a gate. + This function is the reverse of append_kraus_to_gate. + + Args: + kraus_ops: A list of Kraus Operators. + gate_matrix: The gate unitary. + + Returns: + The noise channel without the gate unitary. + """ + return [kju @ gate_matrix.conj().T for kju in kraus_ops] diff --git a/cirq-rigetti/cirq_rigetti/quil_input_test.py b/cirq-rigetti/cirq_rigetti/quil_input_test.py index 453c0551ca7..d6b5b72b7f5 100644 --- a/cirq-rigetti/cirq_rigetti/quil_input_test.py +++ b/cirq-rigetti/cirq_rigetti/quil_input_test.py @@ -12,44 +12,60 @@ # See the License for the specific language governing permissions and # limitations under the License. +from inspect import signature + import numpy as np import pytest -from pyquil import Program +from pyquil.quil import Program +from pyquil.quilbase import Parameter, DefGate +from pyquil.quilatom import quil_cos, quil_sin, quil_exp +from pyquil.simulation import matrices from pyquil.simulation.tools import program_unitary +import sympy +import cirq from cirq import Circuit, LineQubit +from cirq import Simulator, unitary +from cirq.linalg.predicates import allclose_up_to_global_phase from cirq_rigetti.quil_input import ( UndefinedQuilGate, UnsupportedQuilInstruction, + SUPPORTED_GATES, + PARAMETRIC_TRANSFORMERS, + CPHASE00, + CPHASE01, + CPHASE10, + PSWAP, circuit_from_quil, - cphase, - cphase00, - cphase01, - cphase10, - pswap, - xy, -) -from cirq.ops import ( - CCNOT, - CNOT, - CSWAP, - CZ, - H, - I, - ISWAP, - MeasurementGate, - S, - SWAP, - T, - X, - Y, - Z, - rx, - ry, - rz, + defgate_to_cirq, ) +from cirq.ops.common_gates import CNOT, CZ, CZPowGate, H, S, T, ZPowGate, YPowGate, XPowGate +from cirq.ops.pauli_gates import X, Y, Z +from cirq.ops.identity import I +from cirq.ops.measurement_gate import MeasurementGate +from cirq.ops.swap_gates import ISWAP, ISwapPowGate, SWAP +from cirq.ops.three_qubit_gates import CCNOT, CSWAP + + +def test_gate_conversion(): + """Check that the gates all convert with matching unitaries.""" + for quil_gate, cirq_gate in SUPPORTED_GATES.items(): + if quil_gate in PARAMETRIC_TRANSFORMERS: + pyquil_def = getattr(matrices, quil_gate) + sig = signature(pyquil_def) + num_params = len(sig.parameters) + sample_params = list(np.random.random(num_params) * np.pi) + + pyquil_unitary = pyquil_def(*sample_params) + cirq_unitary = unitary(cirq_gate(**PARAMETRIC_TRANSFORMERS[quil_gate](*sample_params))) + assert np.allclose(pyquil_unitary, cirq_unitary) + + else: + assert np.allclose(getattr(matrices, quil_gate), unitary(cirq_gate)) + + QUIL_PROGRAM = """ DECLARE ro BIT[3] I 0 @@ -86,6 +102,7 @@ def test_circuit_from_quil(): + """Convert a test circuit from Quil with a wide range of gates.""" q0, q1, q2 = LineQubit.range(3) cirq_circuit = Circuit( [ @@ -98,22 +115,22 @@ def test_circuit_from_quil(): H(q0), S(q1), T(q2), - Z(q0) ** (1 / 8), - Z(q1) ** (1 / 8), - Z(q2) ** (1 / 8), - rx(np.pi / 2)(q0), - ry(np.pi / 2)(q1), - rz(np.pi / 2)(q2), + ZPowGate(exponent=1 / 8)(q0), + ZPowGate(exponent=1 / 8)(q1), + ZPowGate(exponent=1 / 8)(q2), + XPowGate(exponent=1 / 2, global_shift=-0.5)(q0), + YPowGate(exponent=1 / 2, global_shift=-0.5)(q1), + ZPowGate(exponent=1 / 2, global_shift=-0.5)(q2), CZ(q0, q1), CNOT(q1, q2), - cphase(np.pi / 2)(q0, q1), - cphase00(np.pi / 2)(q1, q2), - cphase01(np.pi / 2)(q0, q1), - cphase10(np.pi / 2)(q1, q2), + CZPowGate(exponent=1 / 2, global_shift=0.0)(q0, q1), + CPHASE00(phi=np.pi / 2)(q1, q2), + CPHASE01(phi=np.pi / 2)(q0, q1), + CPHASE10(phi=np.pi / 2)(q1, q2), ISWAP(q0, q1), - pswap(np.pi / 2)(q1, q2), + PSWAP(phi=np.pi / 2)(q1, q2), SWAP(q0, q1), - xy(np.pi / 2)(q1, q2), + ISwapPowGate(exponent=1 / 2, global_shift=0.0)(q1, q2), CCNOT(q0, q1, q2), CSWAP(q0, q1, q2), MeasurementGate(1, key="ro[0]")(q0), @@ -122,7 +139,7 @@ def test_circuit_from_quil(): ] ) # build the same Circuit, using Quil - quil_circuit = circuit_from_quil(QUIL_PROGRAM) + quil_circuit = circuit_from_quil(Program(QUIL_PROGRAM)) # test Circuit equivalence assert cirq_circuit == quil_circuit @@ -148,9 +165,10 @@ def test_circuit_from_quil(): def test_quil_with_defgate(): + """Convert a Quil program with a DefGate.""" q0 = LineQubit(0) cirq_circuit = Circuit([X(q0), Z(q0)]) - quil_circuit = circuit_from_quil(QUIL_PROGRAM_WITH_DEFGATE) + quil_circuit = circuit_from_quil(Program(QUIL_PROGRAM_WITH_DEFGATE)) assert np.isclose(quil_circuit.unitary(), cirq_circuit.unitary()).all() @@ -160,28 +178,35 @@ def test_quil_with_defgate(): 0,EXP(i*%phi) X 0 -MYPHASE 0 +MYPHASE(pi/2) 0 """ +def test_program_with_parameterized_defgate(): + """Convert a Quil program with a parameterized DefGate.""" + program = Program(QUIL_PROGRAM_WITH_PARAMETERIZED_DEFGATE) + circuit = circuit_from_quil(program) + + pyquil_unitary = np.array([[1, 0], [0, np.exp(1j * np.pi / 2)]]) @ matrices.X + cirq_unitary = circuit.unitary() + + assert allclose_up_to_global_phase(pyquil_unitary, cirq_unitary, atol=1e-8) + + def test_unsupported_quil_instruction(): + """Convert a program with invalid or unsupported instructions.""" with pytest.raises(UnsupportedQuilInstruction): circuit_from_quil("NOP") - with pytest.raises(UnsupportedQuilInstruction): - circuit_from_quil("PRAGMA ADD-KRAUS X 0 \"(0.0 1.0 1.0 0.0)\"") - with pytest.raises(UnsupportedQuilInstruction): circuit_from_quil("RESET") - with pytest.raises(UnsupportedQuilInstruction): - circuit_from_quil(QUIL_PROGRAM_WITH_PARAMETERIZED_DEFGATE) - def test_undefined_quil_gate(): """There are no such things as FREDKIN & TOFFOLI in Quil. The standard names for those gates in Quil are CSWAP and CCNOT. Of course, they can - be defined via DEFGATE / DEFCIRCUIT.""" + be defined via DEFGATE / DEFCIRCUIT. + """ with pytest.raises(UndefinedQuilGate): circuit_from_quil("FREDKIN 0 1 2") @@ -189,7 +214,113 @@ def test_undefined_quil_gate(): circuit_from_quil("TOFFOLI 0 1 2") +QUIL_PROGRAM_WITH_PARAMETERS = """ +DECLARE theta REAL[4] +RX(pi) 0 +RX(theta[0]) 1 +RX(2*theta[1]) 3 +RX(2*theta[2] + 1) 2 +RX(2*COS(theta[3])*EXP(i*theta[3])) 4 +""" + + +def test_parametric_quil(): + """Convert a program which uses parameters and expressions.""" + program = Program(QUIL_PROGRAM_WITH_PARAMETERS) + + circuit = circuit_from_quil(program) + + q0, q1, q2, q3, q4 = LineQubit.range(5) + theta_0, theta_1, theta_2, theta_3 = ( + sympy.Symbol("theta_0"), + sympy.Symbol("theta_1"), + sympy.Symbol("theta_2"), + sympy.Symbol("theta_3"), + ) + cirq_circuit = Circuit( + [ + XPowGate(exponent=1, global_shift=-0.5)(q0), + XPowGate(exponent=theta_0 / np.pi, global_shift=-0.5)(q1), + XPowGate(exponent=(2 / np.pi) * theta_1, global_shift=-0.5)(q3), + XPowGate(exponent=(2 / np.pi) * theta_2 + 1 / np.pi, global_shift=-0.5)(q2), + XPowGate( + exponent=(2 / np.pi) * sympy.cos(theta_3) * sympy.exp(1j * theta_3), + global_shift=-0.5, + )(q4), + ] + ) + + assert cirq_circuit == circuit + + def test_measurement_without_classical_reg(): """Measure operations must declare a classical register.""" with pytest.raises(UnsupportedQuilInstruction): circuit_from_quil("MEASURE 0") + + +# Insert a similar test for Kraus ops + + +QUIL_PROGRAM_WITH_READOUT_NOISE = """ +DECLARE ro BIT[1] +RX(pi) 0 +PRAGMA READOUT-POVM 0 "(0.9 0.050000000000000044 0.09999999999999998 0.95)" +MEASURE 0 ro[0] +""" + + +def test_readout_noise(): + """Convert a program with readout noise.""" + program = Program(QUIL_PROGRAM_WITH_READOUT_NOISE) + circuit = circuit_from_quil(program) + + result = Simulator(seed=0).run(circuit, repetitions=1000) + assert result.histogram(key="ro[0]")[1] < 1000 + assert result.histogram(key="ro[0]")[1] > 900 + + +def test_resolve_parameters(): + """Test that parameters are correctly resolved for defined parametric gate.""" + theta, beta = Parameter("theta"), Parameter("beta") + xy_matrix = np.array( + [ + [1, 0, 0, 0], + [0, quil_cos(theta / 2), 1j * quil_sin(theta / 2) * quil_exp(1j * beta), 0], + [0, 1j * quil_sin(theta / 2) * quil_exp(1j * beta), 1j * quil_cos(theta / 2), 0], + [0, 0, 0, 1], + ] + ) + + defgate = DefGate("PHASEDXY", xy_matrix, parameters=[beta, theta]) + + cirq_phased_xy = defgate_to_cirq(defgate) + + op = cirq_phased_xy(beta=sympy.Symbol("beta"), theta=sympy.Symbol("theta"))( + cirq.LineQubit(0), cirq.LineQubit(1) + ) + + op._resolve_parameters_({"beta": 1.0, "theta": 2.0}, True) + + +def test_op_identifier(): + """Check that converted parametric defgates will be correctly identified.""" + theta, beta = Parameter("theta"), Parameter("beta") + xy_matrix = np.array( + [ + [1, 0, 0, 0], + [0, quil_cos(theta / 2), 1j * quil_sin(theta / 2) * quil_exp(1j * beta), 0], + [0, 1j * quil_sin(theta / 2) * quil_exp(1j * beta), 1j * quil_cos(theta / 2), 0], + [0, 0, 0, 1], + ] + ) + + defgate = DefGate("PHASEDXY", xy_matrix, parameters=[beta, theta]) + + gate1 = defgate_to_cirq(defgate) + gate2 = defgate_to_cirq(defgate) + + op = gate1(beta=np.pi, theta=np.pi)(cirq.LineQubit(0), cirq.LineQubit(1)) + + assert op in cirq.OpIdentifier(gate1) + assert op in cirq.OpIdentifier(gate2) diff --git a/cirq-rigetti/cirq_rigetti/quil_output_test.py b/cirq-rigetti/cirq_rigetti/quil_output_test.py index 2a81de9e25d..f16583c6bc7 100644 --- a/cirq-rigetti/cirq_rigetti/quil_output_test.py +++ b/cirq-rigetti/cirq_rigetti/quil_output_test.py @@ -416,11 +416,11 @@ def test_equivalent_unitaries(): assert np.allclose(pyquil_unitary, cirq_unitary) -QUIL_CPHASES_PROGRAM = """ -CPHASE00(pi/2) 0 1 -CPHASE01(pi/2) 0 1 -CPHASE10(pi/2) 0 1 -CPHASE(pi/2) 0 1 +QUIL_CPHASES_PROGRAM = f""" +CPHASE00({np.pi/2}) 0 1 +CPHASE01({np.pi/2}) 0 1 +CPHASE10({np.pi/2}) 0 1 +CPHASE({np.pi/2}) 0 1 """ QUIL_DIAGONAL_DECOMPOSE_PROGRAM = """ diff --git a/cirq-rigetti/cirq_rigetti/service.py b/cirq-rigetti/cirq_rigetti/service.py index 308cf4626b9..fc82720ea27 100644 --- a/cirq-rigetti/cirq_rigetti/service.py +++ b/cirq-rigetti/cirq_rigetti/service.py @@ -11,24 +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 cast, Optional +from typing import Optional, List import cirq -import httpx from pyquil import get_qc -from qcs_api_client.operations.sync import ( - get_instruction_set_architecture, - get_quilt_calibrations, - list_quantum_processors, -) -from qcs_api_client.models import ( - InstructionSetArchitecture, - GetQuiltCalibrationsResponse, - ListQuantumProcessorsResponse, -) +from qcs_sdk.qpu import list_quantum_processors +from qcs_sdk.qpu.isa import get_instruction_set_architecture, InstructionSetArchitecture +from qcs_sdk.client import QCSClient +from qcs_sdk.qpu.translation import get_quilt_calibrations from pyquil.api import QuantumComputer from cirq_rigetti.sampler import RigettiQCSSampler -from cirq_rigetti._qcs_api_client_decorator import _provide_default_client from cirq_rigetti import circuit_transformers as transformers from cirq_rigetti import circuit_sweep_executors as executors @@ -104,14 +96,11 @@ def sampler(self) -> RigettiQCSSampler: ) @staticmethod - @_provide_default_client - def list_quantum_processors( - client: Optional[httpx.Client], - ) -> ListQuantumProcessorsResponse: # pragma: no cover + def list_quantum_processors(client: Optional[QCSClient] = None) -> List[str]: """Retrieve a list of available Rigetti quantum processors. Args: - 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. @@ -120,18 +109,17 @@ def list_quantum_processors( A qcs_api_client.models.ListQuantumProcessorsResponse containing the identifiers of the available quantum processors.. """ - return cast(ListQuantumProcessorsResponse, list_quantum_processors(client=client).parsed) + return list_quantum_processors(client=client) @staticmethod - @_provide_default_client def get_quilt_calibrations( - quantum_processor_id: str, client: Optional[httpx.Client] - ) -> GetQuiltCalibrationsResponse: + quantum_processor_id: str, client: Optional[QCSClient] = None + ) -> str: # pragma: no cover """Retrieve the calibration data used for client-side Quil-T generation. 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. @@ -140,22 +128,18 @@ def get_quilt_calibrations( A qcs_api_client.models.GetQuiltCalibrationsResponse containing the device calibrations. """ - return cast( # pragma: no cover - GetQuiltCalibrationsResponse, - get_quilt_calibrations(client=client, quantum_processor_id=quantum_processor_id).parsed, - ) + return get_quilt_calibrations(quantum_processor_id=quantum_processor_id, client=client) @staticmethod - @_provide_default_client def get_instruction_set_architecture( - quantum_processor_id: str, client: Optional[httpx.Client] + quantum_processor_id: str, client: Optional[QCSClient] = None ) -> InstructionSetArchitecture: # pragma: no cover """Retrieve the Instruction Set Architecture of a QuantumProcessor by ID. This includes site specific operations and native gate capabilities. 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. @@ -163,11 +147,8 @@ def get_instruction_set_architecture( Returns: A qcs_api_client.models.InstructionSetArchitecture containing the device specification. """ - return cast( - InstructionSetArchitecture, - get_instruction_set_architecture( - client=client, quantum_processor_id=quantum_processor_id - ).parsed, + return get_instruction_set_architecture( + quantum_processor_id=quantum_processor_id, client=client ) diff --git a/cirq-rigetti/cirq_rigetti/service_test.py b/cirq-rigetti/cirq_rigetti/service_test.py index 32302ac4663..ed2ffaebde7 100644 --- a/cirq-rigetti/cirq_rigetti/service_test.py +++ b/cirq-rigetti/cirq_rigetti/service_test.py @@ -1,8 +1,7 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -from typing import Optional, Iterator +from unittest.mock import patch import pytest -import httpx -from cirq_rigetti import get_rigetti_qcs_service, RigettiQCSService +from cirq_rigetti import get_rigetti_qcs_service @pytest.mark.rigetti_integration @@ -14,20 +13,11 @@ def test_get_rigetti_qcs_service(): @pytest.mark.rigetti_integration -def test_rigetti_qcs_service_api_call(): - """test that `RigettiQCSService` will use a custom defined client when the - user specifies one to make an API call.""" +@patch('cirq_rigetti.service.QCSClient') +@patch('cirq_rigetti.service.list_quantum_processors') +def test_list_quantum_processors(mock_list_quantum_processors, MockQCSClient): + client = MockQCSClient() - class Response(httpx.Response): - def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]: - yield b"{\"quantumProcessors\": [{\"id\": \"Aspen-8\"}]}" # pragma: no cover + get_rigetti_qcs_service('9q-square', as_qvm=True).list_quantum_processors(client=client) - class Transport(httpx.BaseTransport): - def handle_request(self, request: httpx.Request) -> httpx.Response: - return Response(200) - - client = httpx.Client(base_url="https://mock.api.qcs.rigetti.com", transport=Transport()) - - response = RigettiQCSService.list_quantum_processors(client=client) - assert 1 == len(response.quantum_processors) - assert 'Aspen-8' == response.quantum_processors[0].id + mock_list_quantum_processors.assert_called_with(client=client) diff --git a/cirq-rigetti/requirements.txt b/cirq-rigetti/requirements.txt index 13fc44d6572..274ad9ad52f 100644 --- a/cirq-rigetti/requirements.txt +++ b/cirq-rigetti/requirements.txt @@ -1 +1 @@ -pyquil>=3.2.0,<4.0.0 +pyquil>=4.11.0,<5.0.0 diff --git a/cirq-web/cirq_ts/.eslintignore b/cirq-web/cirq_ts/.eslintignore index 44a6b377c00..04c4ece1827 100644 --- a/cirq-web/cirq_ts/.eslintignore +++ b/cirq-web/cirq_ts/.eslintignore @@ -1,3 +1,3 @@ build/ **/node_modules -**/dist \ No newline at end of file +**/dist diff --git a/cirq-web/cirq_ts/dist/html/bloch_sphere.html b/cirq-web/cirq_ts/dist/html/bloch_sphere.html index b1c518e4d68..5970f220e0f 100644 --- a/cirq-web/cirq_ts/dist/html/bloch_sphere.html +++ b/cirq-web/cirq_ts/dist/html/bloch_sphere.html @@ -11,4 +11,4 @@ .addVector(1, 0, 0); - \ No newline at end of file + diff --git a/cirq-web/cirq_ts/dist/html/circuit.html b/cirq-web/cirq_ts/dist/html/circuit.html index c1d98bf841f..eac8452d050 100644 --- a/cirq-web/cirq_ts/dist/html/circuit.html +++ b/cirq-web/cirq_ts/dist/html/circuit.html @@ -26,4 +26,4 @@ }); - \ No newline at end of file + diff --git a/cirq-web/cirq_web/_version.py b/cirq-web/cirq_web/_version.py index e7385860766..2133094d533 100644 --- a/cirq-web/cirq_web/_version.py +++ b/cirq-web/cirq_web/_version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.4.0.dev" +__version__ = "1.5.0.dev" diff --git a/cirq-web/cirq_web/_version_test.py b/cirq-web/cirq_web/_version_test.py index ae10fecfdeb..d01a61ba797 100644 --- a/cirq-web/cirq_web/_version_test.py +++ b/cirq-web/cirq_web/_version_test.py @@ -3,4 +3,4 @@ def test_version(): - assert cirq_web.__version__ == "1.4.0.dev" + assert cirq_web.__version__ == "1.5.0.dev" diff --git a/dev_tools/bash_scripts_test.py b/dev_tools/bash_scripts_test.py index 12f6e8bcfa8..7b99107eb4b 100644 --- a/dev_tools/bash_scripts_test.py +++ b/dev_tools/bash_scripts_test.py @@ -346,8 +346,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py HEAD\n' @@ -372,8 +370,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py main\n' @@ -390,8 +386,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py origin/main\n' @@ -408,8 +402,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py upstream/main\n' @@ -426,8 +418,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py upstream/main\n' @@ -456,8 +446,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py HEAD\n' @@ -474,8 +462,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout == ( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py main\n' @@ -499,8 +485,6 @@ def test_pytest_and_incremental_coverage_branch_selection(tmpdir_factory): assert result.stdout.startswith( 'INTERCEPTED check/pytest ' '--cov --cov-config=dev_tools/conf/.coveragerc\n' - 'The annotate command will be removed in a future version.\n' - 'Get in touch if you still use it: ned@nedbatchelder.com\n' 'No data to report.\n' 'INTERCEPTED ' 'python dev_tools/check_incremental_coverage_annotations.py ' diff --git a/dev_tools/conf/mypy.ini b/dev_tools/conf/mypy.ini index c49032bb0a1..bf12f103e86 100644 --- a/dev_tools/conf/mypy.ini +++ b/dev_tools/conf/mypy.ini @@ -1,5 +1,5 @@ [mypy] -exclude = dev_tools/modules_test_data/.*/setup\.py +exclude = dev_tools/modules_test_data/.*/setup\.py|cirq-rigetti/(?!__init__.)*\.py # Temporarily exclude cirq-rigetti (see #6661) show_error_codes = true plugins = duet.typing warn_unused_ignores = true @@ -12,12 +12,12 @@ ignore_missing_imports = true # 3rd-party libs for which we don't have stubs # Google -[mypy-google.api_core.*,google.auth.*,google.colab.*,google.cloud.*] +[mypy-google.api_core.*,google.auth.*,google.colab.*,google.cloud.*,google.oauth2.*] follow_imports = silent ignore_missing_imports = true # Non-Google -[mypy-sympy.*,matplotlib.*,proto.*,pandas.*,scipy.*,freezegun.*,mpl_toolkits.*,networkx.*,ply.*,astroid.*,pytest.*,_pytest.*,pylint.*,setuptools.*,qiskit.*,quimb.*,pylatex.*,filelock.*,sortedcontainers.*,tqdm.*,ruamel.*,absl.*,tensorflow_docs.*,ipywidgets.*, cachetools.*] +[mypy-IPython.*,sympy.*,matplotlib.*,proto.*,pandas.*,scipy.*,freezegun.*,mpl_toolkits.*,networkx.*,ply.*,astroid.*,pytest.*,_pytest.*,pylint.*,setuptools.*,qiskit.*,quimb.*,pylatex.*,filelock.*,sortedcontainers.*,tqdm.*,ruamel.*,absl.*,tensorflow_docs.*,ipywidgets.*,cachetools.*] follow_imports = silent ignore_missing_imports = true diff --git a/dev_tools/modules.py b/dev_tools/modules.py index b684300955f..07299940f4c 100644 --- a/dev_tools/modules.py +++ b/dev_tools/modules.py @@ -160,9 +160,9 @@ def replace_version(search_dir: Path = _DEFAULT_SEARCH_DIR, *, old: str, new: st for m in list_modules(search_dir=search_dir, include_parent=True): version_file = _find_version_file(search_dir / m.root) - content = version_file.read_text("UTF-8") - new_content = content.replace(old, new) - version_file.write_text(new_content) + _rewrite_version(version_file, old, new) + version_test = version_file.parent / "_version_test.py" + _rewrite_version(version_test, old, new) def _validate_version(new_version: str): @@ -170,6 +170,14 @@ def _validate_version(new_version: str): raise ValueError(f"{new_version} is not a valid version number.") +def _rewrite_version(version_file: Path, old: str, new: str) -> None: + pattern = f"(^[^#]*__version__ ==? )(['\"])({re.escape(old)})(\\2)" + repl = f"\\1\\g<2>{new}\\4" + content = version_file.read_text("UTF-8") + new_content = re.sub(pattern, repl, content, flags=re.MULTILINE) + version_file.write_text(new_content) + + def _find_version_file(top: Path) -> Path: for root, _, files in os.walk(str(top)): if "_version.py" in files: diff --git a/dev_tools/modules_test.py b/dev_tools/modules_test.py index a8c32943c68..825a6f41f06 100644 --- a/dev_tools/modules_test.py +++ b/dev_tools/modules_test.py @@ -149,6 +149,7 @@ def test_get_version_on_no_modules(): def test_get_version_on_inconsistent_version_modules(): modules.replace_version(search_dir=Path("./mod2"), old="1.2.3.dev", new="1.2.4.dev") assert modules.get_version(search_dir=Path("./mod2")) == "1.2.4.dev" + assert "1.2.4.dev" in Path("./mod2/pack2/_version_test.py").read_text("UTF-8") with pytest.raises(ValueError, match="Versions should be the same, instead:"): modules.get_version(search_dir=Path(".")) @@ -158,6 +159,8 @@ def test_replace_version(tmpdir_factory): assert modules.get_version() == "1.2.3.dev" modules.replace_version(old="1.2.3.dev", new="1.2.4.dev") assert modules.get_version() == "1.2.4.dev" + assert "1.2.4.dev" in Path("./mod1/pack1/_version_test.py").read_text("UTF-8") + assert "1.2.4.dev" in Path("./mod2/pack2/_version_test.py").read_text("UTF-8") @chdir(target_dir="dev_tools/modules_test_data") diff --git a/dev_tools/modules_test_data/mod1/pack1/_version_test.py b/dev_tools/modules_test_data/mod1/pack1/_version_test.py new file mode 100644 index 00000000000..7eb347d1bd9 --- /dev/null +++ b/dev_tools/modules_test_data/mod1/pack1/_version_test.py @@ -0,0 +1,6 @@ +# pylint: disable=wrong-or-nonexistent-copyright-notice +import pack1._version # type: ignore + + +def test_version(): + assert pack1._version.__version__ == "1.2.3.dev" diff --git a/dev_tools/modules_test_data/mod1/requirements.txt b/dev_tools/modules_test_data/mod1/requirements.txt index d1e14c5287d..a722fa18215 100644 --- a/dev_tools/modules_test_data/mod1/requirements.txt +++ b/dev_tools/modules_test_data/mod1/requirements.txt @@ -1,2 +1,2 @@ req1 -req2 \ No newline at end of file +req2 diff --git a/dev_tools/modules_test_data/mod2/pack2/_version_test.py b/dev_tools/modules_test_data/mod2/pack2/_version_test.py new file mode 100644 index 00000000000..39452b9ecb1 --- /dev/null +++ b/dev_tools/modules_test_data/mod2/pack2/_version_test.py @@ -0,0 +1,6 @@ +# pylint: disable=wrong-or-nonexistent-copyright-notice +import pack2._version # type: ignore + + +def test_version(): + assert pack2._version.__version__ == "1.2.3.dev" diff --git a/dev_tools/notebooks/isolated_notebook_test.py b/dev_tools/notebooks/isolated_notebook_test.py index 10f299139a9..810199d14d7 100644 --- a/dev_tools/notebooks/isolated_notebook_test.py +++ b/dev_tools/notebooks/isolated_notebook_test.py @@ -44,22 +44,10 @@ NOTEBOOKS_DEPENDING_ON_UNRELEASED_FEATURES: List[str] = [ # Requires pinned quimb from #6438 'cirq-core/cirq/contrib/quimb/Contract-a-Grid-Circuit.ipynb', - # Hardcoded qubit placement - 'docs/google/qubit-placement.ipynb', # get_qcs_objects_for_notebook 'docs/noise/calibration_api.ipynb', - 'docs/tutorials/google/colab.ipynb', - 'docs/tutorials/google/identifying_hardware_changes.ipynb', - 'docs/tutorials/google/echoes.ipynb', 'docs/noise/floquet_calibration_example.ipynb', - 'docs/tutorials/google/spin_echoes.ipynb', - 'docs/tutorials/google/start.ipynb', - 'docs/tutorials/google/visualizing_calibration_metrics.ipynb', 'docs/noise/qcvv/xeb_calibration_example.ipynb', - 'docs/named_topologies.ipynb', - 'docs/start/intro.ipynb', - # Circuit routing - 'docs/transform/routing_transformer.ipynb', ] # By default all notebooks should be tested, however, this list contains exceptions to the rule @@ -71,8 +59,6 @@ "**/google/*.ipynb", "**/ionq/*.ipynb", "**/pasqal/*.ipynb", - # skipp cirq-ft notebooks since they are included in individual tests - 'cirq-ft/**', # Rigetti uses local simulation with docker, so should work # if you run into issues locally, run # `docker compose -f cirq-rigetti/docker-compose.test.yaml up` @@ -98,7 +84,7 @@ "papermill", "jupyter", # assumed to be part of colab - "seaborn~=0.11.1", + "seaborn~=0.12", ] diff --git a/dev_tools/notebooks/notebook_test.py b/dev_tools/notebooks/notebook_test.py index 5c8c74a0396..eff937e0e32 100644 --- a/dev_tools/notebooks/notebook_test.py +++ b/dev_tools/notebooks/notebook_test.py @@ -26,6 +26,7 @@ import pytest from dev_tools import shell_tools +from dev_tools.modules import list_modules from dev_tools.notebooks import filter_notebooks, list_all_notebooks, rewrite_notebook from dev_tools.test_utils import only_on_posix @@ -38,11 +39,7 @@ '**/rigetti/*.ipynb', # disabled to unblock Python 3.12. TODO(#6590) - fix and enable. 'cirq-core/cirq/contrib/quimb/Contract-a-Grid-Circuit.ipynb', - # skipp cirq-ft notebooks since they are included in individual tests - 'cirq-ft/**', # skipping fidelity estimation due to - # https://github.com/quantumlib/Cirq/issues/3502 - 'examples/*fidelity*', # skipping quantum utility simulation (too large) 'examples/advanced/*quantum_utility*', # tutorials that use QCS and arent skipped due to one or more cleared output cells @@ -54,8 +51,6 @@ # temporary: need to fix QVM metrics and device spec 'docs/tutorials/google/spin_echoes.ipynb', 'docs/tutorials/google/visualizing_calibration_metrics.ipynb', - # shouldn't have outputs generated for style reasons - 'docs/simulate/qvm_builder_code.ipynb', ] @@ -65,9 +60,18 @@ def require_packages_not_changed(): Raise AssertionError if the pre-existing set of Python packages changes in any way. """ - packages_before = set((d.name, d.version) for d in importlib.metadata.distributions()) + cirq_packages = set(m.name for m in list_modules()).union(["cirq"]) + packages_before = set( + (d.name, d.version) + for d in importlib.metadata.distributions() + if d.name not in cirq_packages + ) yield - packages_after = set((d.name, d.version) for d in importlib.metadata.distributions()) + packages_after = set( + (d.name, d.version) + for d in importlib.metadata.distributions() + if d.name not in cirq_packages + ) assert packages_after == packages_before diff --git a/dev_tools/packaging/isolated_packages_test.py b/dev_tools/packaging/isolated_packages_test.py index 50b186f6b41..e30f4abc932 100644 --- a/dev_tools/packaging/isolated_packages_test.py +++ b/dev_tools/packaging/isolated_packages_test.py @@ -32,14 +32,22 @@ # the "isolation" fails and for example cirq-core would be on the PATH @mock.patch.dict(os.environ, {"PYTHONPATH": ""}) @pytest.mark.parametrize('module', list_modules(), ids=[m.name for m in list_modules()]) -def test_isolated_packages(cloned_env, module): +def test_isolated_packages(cloned_env, module, tmp_path): env = cloned_env("isolated_packages", *PACKAGES) if str(module.root) != "cirq-core": assert f'cirq-core=={module.version}' in module.install_requires + # TODO: Remove after upgrading package builds from setup.py to PEP-517 + # Create per-worker copy of cirq-core sources so that parallel builds + # of cirq-core wheel do not conflict. + opt_cirq_core = ( + [str(shutil.copytree("./cirq-core", tmp_path / "cirq-core"))] + if str(module.root) != "cirq-core" + else [] + ) result = shell_tools.run( - f"{env}/bin/pip install ./{module.root} ./cirq-core".split(), + [f"{env}/bin/pip", "install", f"./{module.root}", *opt_cirq_core], stderr=subprocess.PIPE, check=False, ) diff --git a/dev_tools/packaging/produce-package.sh b/dev_tools/packaging/produce-package.sh index 4673cb68755..1b12b550bc3 100755 --- a/dev_tools/packaging/produce-package.sh +++ b/dev_tools/packaging/produce-package.sh @@ -36,6 +36,13 @@ out_dir=$(realpath "${1}") SPECIFIED_VERSION="${2}" +# Helper to run dev_tools/modules.py without CIRQ_PRE_RELEASE_VERSION +# to avoid environment version override in setup.py. +my_dev_tools_modules() { + env -u CIRQ_PRE_RELEASE_VERSION PYTHONPATH=. \ + python3 dev_tools/modules.py "$@" +} + # Get the working directory to the repo root. cd "$( dirname "${BASH_SOURCE[0]}" )" repo_dir=$(git rev-parse --show-toplevel) @@ -47,21 +54,18 @@ if [ -n "$(git status --short)" ]; then fi tmp_git_dir=$(mktemp -d "/tmp/produce-package-git.XXXXXXXXXXXXXXXX") trap '{ rm -rf "${tmp_git_dir}"; }' EXIT +echo "Creating pristine repository clone at ${tmp_git_dir}" +git clone --shared --quiet "${repo_dir}" "${tmp_git_dir}" cd "${tmp_git_dir}" -git init --quiet -git fetch "${repo_dir}" HEAD --quiet --depth=1 -git checkout FETCH_HEAD -b work --quiet if [ -n "${SPECIFIED_VERSION}" ]; then - CIRQ_PACKAGES=$(env PYTHONPATH=. python dev_tools/modules.py list --mode package-path) - for PROJECT_NAME in $CIRQ_PACKAGES; do - echo '__version__ = "'"${SPECIFIED_VERSION}"'"' > "${tmp_git_dir}/${PROJECT_NAME}/_version.py" - done + CURRENT_VERSION=$(my_dev_tools_modules print_version) + my_dev_tools_modules replace_version --old="${CURRENT_VERSION}" --new="${SPECIFIED_VERSION}" fi # Python 3 wheel. echo "Producing python 3 package files." -CIRQ_MODULES=$(env PYTHONPATH=. python dev_tools/modules.py list --mode folder --include-parent) +CIRQ_MODULES=$(my_dev_tools_modules list --mode folder --include-parent) for m in $CIRQ_MODULES; do echo "processing $m/setup.py..." diff --git a/dev_tools/pr_monitor/Dockerfile b/dev_tools/pr_monitor/Dockerfile index 10769b7c9a5..8a213c155be 100644 --- a/dev_tools/pr_monitor/Dockerfile +++ b/dev_tools/pr_monitor/Dockerfile @@ -34,4 +34,4 @@ RUN pip install -r ./dev_tools/pr_monitor/requirements.txt COPY ./*.py ./dev_tools/ ENV PYTHONPATH=/app -CMD ["python", "dev_tools/pr_monitor.py"] \ No newline at end of file +CMD ["python", "dev_tools/pr_monitor.py"] diff --git a/dev_tools/pr_monitor/cloudbuild-deploy.yaml b/dev_tools/pr_monitor/cloudbuild-deploy.yaml index fd979c8c934..34300fe97e9 100644 --- a/dev_tools/pr_monitor/cloudbuild-deploy.yaml +++ b/dev_tools/pr_monitor/cloudbuild-deploy.yaml @@ -16,4 +16,4 @@ steps: - skaffold - run - --force - - -f=dev_tools/pr_monitor/skaffold.yaml \ No newline at end of file + - -f=dev_tools/pr_monitor/skaffold.yaml diff --git a/dev_tools/pr_monitor/requirements.txt b/dev_tools/pr_monitor/requirements.txt index 09f93c0ad85..bb4557061ba 100644 --- a/dev_tools/pr_monitor/requirements.txt +++ b/dev_tools/pr_monitor/requirements.txt @@ -1,2 +1,2 @@ -requests==2.31.0 -google-cloud-secret-manager==1.0.0 \ No newline at end of file +requests==2.32.0 +google-cloud-secret-manager==1.0.0 diff --git a/dev_tools/pr_monitor/statefulset.yaml b/dev_tools/pr_monitor/statefulset.yaml index c6947c21542..87c5baedcc4 100644 --- a/dev_tools/pr_monitor/statefulset.yaml +++ b/dev_tools/pr_monitor/statefulset.yaml @@ -26,4 +26,4 @@ spec: spec: containers: - name: cirq-pr-monitor - image: us-docker.pkg.dev/cirq-infra/cirq/pr_monitor \ No newline at end of file + image: us-docker.pkg.dev/cirq-infra/cirq/pr_monitor diff --git a/dev_tools/requirements/deps/cirq-all-no-contrib.txt b/dev_tools/requirements/deps/cirq-all-no-contrib.txt index dce8de17989..244ae247cce 100644 --- a/dev_tools/requirements/deps/cirq-all-no-contrib.txt +++ b/dev_tools/requirements/deps/cirq-all-no-contrib.txt @@ -1,6 +1,5 @@ # captures all the modules -r ../../../cirq-aqt/requirements.txt -r ../../../cirq-core/requirements.txt --r ../../../cirq-ft/requirements.txt -r ../../../cirq-google/requirements.txt -r ../../../cirq-rigetti/requirements.txt diff --git a/dev_tools/requirements/deps/dev-tools.txt b/dev_tools/requirements/deps/dev-tools.txt index a7fdce6c38d..b007a7933cc 100644 --- a/dev_tools/requirements/deps/dev-tools.txt +++ b/dev_tools/requirements/deps/dev-tools.txt @@ -14,4 +14,4 @@ asv qiskit-aer~=0.12.0 # For verifying rst -rstcheck~=3.3.1 +rstcheck diff --git a/dev_tools/requirements/deps/mypy.txt b/dev_tools/requirements/deps/mypy.txt index 2597cc8a8e4..e89fad986af 100644 --- a/dev_tools/requirements/deps/mypy.txt +++ b/dev_tools/requirements/deps/mypy.txt @@ -4,6 +4,6 @@ mypy==1.2.0 # packages with stub types for various libraries types-backports==0.1.3 types-cachetools -types-protobuf~=3.20 +types-protobuf>=3.20.0,<5.0.0 types-requests==2.28.1 types-setuptools==62.6.1 diff --git a/dev_tools/requirements/deps/notebook.txt b/dev_tools/requirements/deps/notebook.txt index 12815f39353..8fea53bb411 100644 --- a/dev_tools/requirements/deps/notebook.txt +++ b/dev_tools/requirements/deps/notebook.txt @@ -1,16 +1,14 @@ -r ipython.txt -# Notebook >=6.4.8 + coverage > 6.2 hangs CI: https://github.com/quantumlib/Cirq/issues/4897 -notebook>=6.4.1,<=6.4.7 +notebook~=7.0 -# https://github.com/nteract/papermill/issues/519 -ipykernel==5.3.4 +ipykernel~=6.29 # for executing notebooks in tests -papermill~=2.3.2 +papermill~=2.6 # for notebooks that do `pip install cirq-core[contrib]` -r ../../../cirq-core/cirq/contrib/requirements.txt # assumed to be part of colab -seaborn~=0.11.1 +seaborn~=0.12 diff --git a/dev_tools/requirements/deps/protos.txt b/dev_tools/requirements/deps/protos.txt index 20ec6b9f200..011cf96756d 100644 --- a/dev_tools/requirements/deps/protos.txt +++ b/dev_tools/requirements/deps/protos.txt @@ -3,4 +3,4 @@ # This bundles protoc 3.24.3, which we use for generating proto code. grpcio-tools~=1.59.0 -mypy-protobuf==3.4 +mypy-protobuf~=3.4 diff --git a/dev_tools/requirements/deps/pytest.txt b/dev_tools/requirements/deps/pytest.txt index 2b75d64136c..88a0d316b41 100644 --- a/dev_tools/requirements/deps/pytest.txt +++ b/dev_tools/requirements/deps/pytest.txt @@ -2,18 +2,16 @@ pytest pytest-asyncio -# pytest-cov 4.1.0 discards line hits in subprocess (coverage failures in #6208) -pytest-cov~=3.0 +pytest-cov pytest-randomly -# Notebook >=6.4.8 + coverage > 6.2 hangs CI: https://github.com/quantumlib/Cirq/issues/4897 -coverage<=6.2 +coverage~=7.4 # for parallel testing notebooks -pytest-xdist~=2.2.0 +pytest-xdist filelock~=3.1 # For testing time specific logic -freezegun~=0.3.15 +freezegun # For test_metadata_distributions_after_deprecated_submodule importlib-metadata diff --git a/dev_tools/requirements/deps/tensorflow-docs.txt b/dev_tools/requirements/deps/tensorflow-docs.txt index 1ec6bead62a..25ba5555ee3 100644 --- a/dev_tools/requirements/deps/tensorflow-docs.txt +++ b/dev_tools/requirements/deps/tensorflow-docs.txt @@ -1 +1 @@ -tensorflow-docs@https://github.com/tensorflow/docs/tarball/3a6a7a3322de8620f0031ae3a0a4b62e40d1d7f2 +tensorflow-docs@https://github.com/tensorflow/docs/archive/3a6a7a3322de8620f0031ae3a0a4b62e40d1d7f2/master.tar.gz diff --git a/dev_tools/requirements/isolated-base.env.txt b/dev_tools/requirements/isolated-base.env.txt index d6f35f9668d..78d3ef7b988 100644 --- a/dev_tools/requirements/isolated-base.env.txt +++ b/dev_tools/requirements/isolated-base.env.txt @@ -4,4 +4,4 @@ -r deps/notebook.txt # for shell_tools -requests \ No newline at end of file +requests diff --git a/dev_tools/requirements/mypy.env.txt b/dev_tools/requirements/mypy.env.txt index 66692620363..a98e9c37342 100644 --- a/dev_tools/requirements/mypy.env.txt +++ b/dev_tools/requirements/mypy.env.txt @@ -3,4 +3,4 @@ # compile it with pip-compile dev_tools/requirements/dev.txt -r deps/cirq-all.txt --r deps/mypy.txt \ No newline at end of file +-r deps/mypy.txt diff --git a/dev_tools/requirements/no-contrib.env.txt b/dev_tools/requirements/no-contrib.env.txt index 9502a29c686..a2d10201a44 100644 --- a/dev_tools/requirements/no-contrib.env.txt +++ b/dev_tools/requirements/no-contrib.env.txt @@ -1,2 +1,2 @@ -r deps/cirq-all-no-contrib.txt --r deps/dev-tools.txt \ No newline at end of file +-r deps/dev-tools.txt diff --git a/dev_tools/requirements/test_data/b.req.txt b/dev_tools/requirements/test_data/b.req.txt index 84d21dfe5fd..d88211a68a2 100644 --- a/dev_tools/requirements/test_data/b.req.txt +++ b/dev_tools/requirements/test_data/b.req.txt @@ -1,2 +1,2 @@ # a comment -b \ No newline at end of file +b diff --git a/dev_tools/requirements/test_data/sub/c.req.txt b/dev_tools/requirements/test_data/sub/c.req.txt index 20d4cd22851..f51ef251334 100644 --- a/dev_tools/requirements/test_data/sub/c.req.txt +++ b/dev_tools/requirements/test_data/sub/c.req.txt @@ -1,3 +1,3 @@ # c is included # this one has no new line at the end -c \ No newline at end of file +c diff --git a/dev_tools/triage-party/cloudbuild-deploy.yaml b/dev_tools/triage-party/cloudbuild-deploy.yaml index 343970bdb32..e33dacd44c7 100644 --- a/dev_tools/triage-party/cloudbuild-deploy.yaml +++ b/dev_tools/triage-party/cloudbuild-deploy.yaml @@ -16,4 +16,4 @@ steps: - skaffold - run - --force - - -f=dev_tools/triage-party/skaffold.yaml \ No newline at end of file + - -f=dev_tools/triage-party/skaffold.yaml diff --git a/dev_tools/triage-party/kubernetes/01_ns/namespace.yaml b/dev_tools/triage-party/kubernetes/01_ns/namespace.yaml index b06366c1d8c..cb79a2c5849 100644 --- a/dev_tools/triage-party/kubernetes/01_ns/namespace.yaml +++ b/dev_tools/triage-party/kubernetes/01_ns/namespace.yaml @@ -3,4 +3,4 @@ kind: Namespace metadata: name: triage-party labels: - name: triage-party \ No newline at end of file + name: triage-party diff --git a/dev_tools/triage-party/kubernetes/02_deployment/configmap.yaml b/dev_tools/triage-party/kubernetes/02_deployment/configmap.yaml index 9a32b9997fb..09478889cb3 100644 --- a/dev_tools/triage-party/kubernetes/02_deployment/configmap.yaml +++ b/dev_tools/triage-party/kubernetes/02_deployment/configmap.yaml @@ -208,4 +208,4 @@ data: filters: - tag: recv - responded: +7d - - label: "kind/design-issue" \ No newline at end of file + - label: "kind/design-issue" diff --git a/docs/_book.yaml b/docs/_book.yaml index 0bc677c21c8..ade446ea4f7 100644 --- a/docs/_book.yaml +++ b/docs/_book.yaml @@ -22,8 +22,6 @@ upper_tabs: - name: "Software" path: /software is_default: true - menu: - - include: /_book/menu_software.yaml lower_tabs: # Subsite tabs other: diff --git a/docs/build/_index.yaml b/docs/build/_index.yaml index 079a3365fe6..479fce3ee38 100644 --- a/docs/build/_index.yaml +++ b/docs/build/_index.yaml @@ -4,46 +4,47 @@ title: Build a circuit landing_page: nav: left rows: - - heading: Build a circuit - description: At the core of Cirq is the ability to construct quantum circuits. These are the methods and data structures necessary to do so. - - heading: Circuit construction - description: The core data structures that compose a circuit and how to use them. - options: - - cards - items: - - heading: Circuits - description: Quantum circuits and how to create them. - path: /cirq/build/circuits - - heading: Qubits - description: The quantum bit data structure. - path: /cirq/build/qubits - - heading: Gates and Operations - description: Quantum gates to apply to qubits in a circuit. - path: /cirq/build/gates - - heading: Custom gates - description: Create your own gates with unitaries or decomposition. - path: /cirq/build/custom_gates - - heading: Import/export circuits - description: Importing or exporting circuits into/out of Cirq. - path: /cirq/build/interop + - heading: Build a circuit + description: At the core of Cirq is the ability to construct quantum circuits. + These are the methods and data structures necessary to do so. + - heading: Circuit construction + description: The core data structures that compose a circuit and how to use them. + options: + - cards + items: + - heading: Circuits + description: Quantum circuits and how to create them. + path: /cirq/build/circuits + - heading: Qubits + description: The quantum bit data structure. + path: /cirq/build/qubits + - heading: Gates and Operations + description: Quantum gates to apply to qubits in a circuit. + path: /cirq/build/gates + - heading: Custom gates + description: Create your own gates with unitaries or decomposition. + path: /cirq/build/custom_gates + - heading: Import/export circuits + description: Importing or exporting circuits into/out of Cirq. + path: /cirq/build/interop - - heading: Advanced construction - description: More elaborate ways to build quantum circuits. - options: - - cards - items: - - heading: Operators - description: Unitary operators, measurements and noise channels. - path: /cirq/build/operators - - heading: Observables and PauliStrings - description: Build and measure observables from sums and products of Pauli operators. - path: /cirq/build/pauli_observables - - heading: Qudits - description: Qutrits and higher dimensional quantum systems. - path: /cirq/build/qudits - - heading: Protocols - description: Magic methods supported by Cirq's classes. - path: /cirq/build/protocols - - heading: Tools ecosystem - description: External tools for circuit construction. - path: /cirq/build/ecosystem + - heading: Advanced construction + description: More elaborate ways to build quantum circuits. + options: + - cards + items: + - heading: Operators + description: Unitary operators, measurements and noise channels. + path: /cirq/build/operators + - heading: Observables and PauliStrings + description: Build and measure observables from sums and products of Pauli operators. + path: /cirq/build/pauli_observables + - heading: Qudits + description: Qutrits and higher dimensional quantum systems. + path: /cirq/build/qudits + - heading: Protocols + description: Magic methods supported by Cirq's classes. + path: /cirq/build/protocols + - heading: Tools ecosystem + description: External tools for circuit construction. + path: /cirq/build/ecosystem diff --git a/docs/dev/notebooks.md b/docs/dev/notebooks.md index 454baa4829c..6b3e84533ca 100644 --- a/docs/dev/notebooks.md +++ b/docs/dev/notebooks.md @@ -78,7 +78,7 @@ You should configure notebooks differently depending on whether they rely on fea When you introduce a notebook that depends on pre-release features of Cirq, make sure to - mark the notebook at the top that `Note: this notebook relies on unreleased Cirq features. If you want to try these feature, make sure you install cirq via pip install cirq~=1.0.dev`. - - use `pip install cirq —pre` in the installation instructions + - use `pip install cirq~=1.0.dev` in the installation instructions - make sure [notebook_test.py](https://github.com/quantumlib/Cirq/blob/main/dev_tools/notebooks/notebook_test.py) covers the notebook - exclude the notebook from the [isolated_notebook_test.py](https://github.com/quantumlib/Cirq/blob/main/dev_tools/notebooks/isolated_notebook_test.py) by adding it to `NOTEBOOKS_DEPENDING_ON_UNRELEASED_FEATURES` @@ -92,7 +92,7 @@ When you introduce a notebook that only uses already released features of Cirq, At release time, we change all the **pre-release notebooks** in bulk: - remove the pre-release notices - - change `pip install cirq —pre` to `pip install cirq` + - change `pip install cirq~=1.0.dev` to `pip install cirq` - remove the exclusions in [isolated_notebook_test.py](https://github.com/quantumlib/Cirq/blob/main/dev_tools/notebooks/isolated_notebook_test.py) by making `NOTEBOOKS_DEPENDING_ON_UNRELEASED_FEATURES=[]` As all the notebooks have been tested continuously up to this point, the release notebook PR should pass without issues. diff --git a/docs/experiments/_index.yaml b/docs/experiments/_index.yaml index 139902f6687..b28c487a78b 100644 --- a/docs/experiments/_index.yaml +++ b/docs/experiments/_index.yaml @@ -4,37 +4,43 @@ title: Experiments using quantum circuits landing_page: nav: left rows: - - heading: Experiments using quantum circuits - description: This is a collection of algorithms and experiments written in and using Cirq. A couple of them use only base Cirq, but the rest use additional code stored in ReCirq, a GitHub repository for research code that uses and builds upon Cirq. - - buttons: - - label: Cirq GitHub - path: https://github.com/quantumlib/Cirq - - label: ReCirq GitHub - path: https://github.com/quantumlib/ReCirq - - heading: Algorithms in base Cirq - description: Algorithms and experiments executable using only default Cirq code. - options: - - cards - items: - - heading: Textbook Algorithms Workshop - description: A workshop notebook with examples that covers algorithms commonly shown in quantum textbooks. - path: /cirq/experiments/textbook_algorithms - - heading: Shor's Algorithm - description: The famous integer factorization algorithm by Peter Shor. - path: /cirq/experiments/shor - - heading: Variational Quantum Eigensolver - description: Compute the ground state of a Hamiltonian using the variational principle. - path: /cirq/experiments/variational_algorithm - - heading: Quantum Walks - description: The quantum analog of a random walk algorithm. - path: /cirq/experiments/quantum_walks - - heading: Fourier Checking - description: Demonstrate the separation between quantum and classical computers. - path: /cirq/experiments/fourier_checking - - heading: Hidden Linear Function problem - description: Show quantum separation with a constant depth solution. - path: /cirq/experiments/hidden_linear_function + - heading: Experiments using quantum circuits + description: This is a collection of algorithms and experiments written in and + using Cirq. A couple of them use only base Cirq, but the rest use additional + code stored in ReCirq, a GitHub repository for research code that uses and builds + upon Cirq. + - buttons: + - label: Cirq GitHub + path: https://github.com/quantumlib/Cirq + - label: ReCirq GitHub + path: https://github.com/quantumlib/ReCirq + - heading: Algorithms in base Cirq + description: Algorithms and experiments executable using only default Cirq code. + options: + - cards + items: + - heading: Textbook Algorithms Workshop + description: A workshop notebook with examples that covers algorithms commonly + shown in quantum textbooks. + path: /cirq/experiments/textbook_algorithms + - heading: Shor's Algorithm + description: The famous integer factorization algorithm by Peter Shor. + path: /cirq/experiments/shor + - heading: Variational Quantum Eigensolver + description: Compute the ground state of a Hamiltonian using the variational + principle. + path: /cirq/experiments/variational_algorithm + - heading: Quantum Walks + description: The quantum analog of a random walk algorithm. + path: /cirq/experiments/quantum_walks + - heading: Fourier Checking + description: Demonstrate the separation between quantum and classical computers. + path: /cirq/experiments/fourier_checking + - heading: Hidden Linear Function problem + description: Show quantum separation with a constant depth solution. + path: /cirq/experiments/hidden_linear_function - - heading: ReCirq Experiments - description: Research experiments that use additional library code that resides in the external ReCirq repository. - - include: /cirq/experiments/_index_included.yaml + - heading: ReCirq Experiments + description: Research experiments that use additional library code that resides + in the external ReCirq repository. + - include: /cirq/experiments/_index_included.yaml diff --git a/docs/google/devices.md b/docs/google/devices.md index d011aa7b6a5..5a14e7701e0 100644 --- a/docs/google/devices.md +++ b/docs/google/devices.md @@ -191,8 +191,7 @@ $$ \right] $$ -This gate has a duration of 12ns and can be used in `cirq_google.SYC_GATESET` -or in the `cirq_google.FSIM_GATESET`. +This gate has a duration of 12ns. #### Square root of iSWAP @@ -214,8 +213,7 @@ $$ \right] $$ -This gate has a duration of 32ns and can be used in -`cirq_google.SQRT_ISWAP_GATESET` or in the `cirq_google.FSIM_GATESET`. +This gate has a duration of 32ns. This gate is implemented by using an entangling gate surrounded by Z gates. The preceding Z gates are physical Z gates and will absorb @@ -237,15 +235,6 @@ to see if it is available on the processor you are using. This gate is equivalent to FSimGate(0, π). It has an approximate duration of 26ns. -#### FSim gateset - -The `cirq.FSIM_GATESET` provides all three of the above gates in one set. -In addition, by using this combined gate set, the FSimGate can be parameterized, -which allows for efficient sweeps across varying two-qubit gates. -Note that providing a theta/phi combination that -is not one of the above gates will cause an error when run on hardware. - - ### Wait gate For decay experiments and other applications, a WaitGate is provided diff --git a/docs/google/engine.md b/docs/google/engine.md index 05804ebf8d7..61da112ac9e 100644 --- a/docs/google/engine.md +++ b/docs/google/engine.md @@ -80,7 +80,7 @@ circuit = cirq.Circuit( engine = cg.Engine(project_id=YOUR_PROJECT_ID) # Create a sampler from the engine -sampler = engine.sampler(processor_id='PROCESSOR_ID', gate_set=cg.SYC_GATESET) +sampler = engine.get_sampler(processor_id='PROCESSOR_ID') # This will run the circuit and return the results in a 'Result' results = sampler.run(circuit, repetitions=1000) @@ -193,7 +193,6 @@ engine = cirq_google.Engine(project_id='YOUR_PROJECT_ID') # Create a sampler from the engine job = engine.run_batch(circuit_list, processor_id='PROCESSOR_ID', - gate_set=cirq_google.FSIM_GATESET, repetitions=1000, params_list=param_list) results = job.results() diff --git a/docs/google/qubit-placement.ipynb b/docs/google/qubit-placement.ipynb index a22297c098c..33aef87ed66 100644 --- a/docs/google/qubit-placement.ipynb +++ b/docs/google/qubit-placement.ipynb @@ -39,8 +39,7 @@ "source": [ "# Qubit Placement\n", "\n", - "This notebooks walks through qubit placement runtime features exposed through the `cirq_google.workflow` tools.", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "This notebooks walks through qubit placement runtime features exposed through the `cirq_google.workflow` tools." ] }, { @@ -77,8 +76,7 @@ " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", - " # This depends on unreleased (as of 1.14) qubit placement functions.\n", - " !pip install --quiet cirq~=1.0.dev\n", + " !pip install --quiet cirq\n", " print(\"installed cirq.\")\n", " import cirq" ] diff --git a/docs/hardware/_index.yaml b/docs/hardware/_index.yaml index 517cfe45cb2..a59992ce8c0 100644 --- a/docs/hardware/_index.yaml +++ b/docs/hardware/_index.yaml @@ -4,101 +4,105 @@ title: Hardware landing_page: nav: left rows: - - heading: Represent a quantum hardware device - description: Define the characteristics and constraints of quantum hardware devices, to support running circuits on those devices. - options: - - cards - items: - - heading: Devices - description: Represent the constraints a device imposes on runnable circuits with the Device class. - path: /cirq/hardware/devices + - heading: Represent a quantum hardware device + description: Define the characteristics and constraints of quantum hardware devices, + to support running circuits on those devices. + options: + - cards + items: + - heading: Devices + description: Represent the constraints a device imposes on runnable circuits + with the Device class. + path: /cirq/hardware/devices - - heading: Run a circuit on a hardware device - description: Cirq provides interfaces for running your circuits on quantum hardware provided by many different services. - options: - - cards - items: - - heading: Qubit Picking - description: Information to help you pick good qubits for running your circuit on a hardware or hardware-like device. - path: /cirq/hardware/qubit_picking + - heading: Run a circuit on a hardware device + description: Cirq provides interfaces for running your circuits on quantum hardware + provided by many different services. + options: + - cards + items: + - heading: Qubit Picking + description: Information to help you pick good qubits for running your circuit + on a hardware or hardware-like device. + path: /cirq/hardware/qubit_picking - - heading: AQT hardware - description: Cirq's interface with Alpine Quantum Technologies hardware. - options: - - cards - items: - - heading: Access and authentication - description: How to gain access. - path: /cirq/hardware/aqt/access - - heading: Getting started with AQT hardware - description: How to run your first circuit. - path: /cirq/hardware/aqt/getting_started + - heading: AQT hardware + description: Cirq's interface with Alpine Quantum Technologies hardware. + options: + - cards + items: + - heading: Access and authentication + description: How to gain access. + path: /cirq/hardware/aqt/access + - heading: Getting started with AQT hardware + description: How to run your first circuit. + path: /cirq/hardware/aqt/getting_started - - heading: Azure Quantum - description: Cirq's interface with Microsoft Azure Quantum services. - options: - - cards - items: - - heading: Access and authentication - description: How to gain access. - path: /cirq/hardware/azure-quantum/access - - heading: Getting started with Honeywell on AQT hardware - description: How to run your first circuit on a Honeywell device. - path: /cirq/hardware/azure-quantum/getting_started_honeywell - - heading: Getting started with IonQ on AQT hardware - description: How to run your first circuit on an IonQ device. - path: /cirq/hardware/azure-quantum/getting_started_ionq + - heading: Azure Quantum + description: Cirq's interface with Microsoft Azure Quantum services. + options: + - cards + items: + - heading: Access and authentication + description: How to gain access. + path: /cirq/hardware/azure-quantum/access + - heading: Getting started with Honeywell on AQT hardware + description: How to run your first circuit on a Honeywell device. + path: /cirq/hardware/azure-quantum/getting_started_honeywell + - heading: Getting started with IonQ on AQT hardware + description: How to run your first circuit on an IonQ device. + path: /cirq/hardware/azure-quantum/getting_started_ionq - - heading: IonQ hardware - description: Cirq's interface with IonQ hardware. - options: - - cards - items: - - heading: Access and authentication - description: How to gain access. - path: /cirq/hardware/ionq/access - - heading: Getting started with IonQ hardware - description: How to run your first circuit. - path: /cirq/hardware/ionq/getting_started - - heading: IonQ API Service - description: Using the IonQ API. - path: /cirq/hardware/ionq/service - - heading: IonQ API circuits - description: Writing circuits for the IonQ API. - path: /cirq/hardware/ionq/circuits - - heading: Running IonQ API jobs - description: How to run jobs with the IonQ API. - path: /cirq/hardware/ionq/jobs - - heading: IonQ API calibrations - description: How to get hardware device calibration data through the IonQ API. - path: /cirq/hardware/ionq/calibrations + - heading: IonQ hardware + description: Cirq's interface with IonQ hardware. + options: + - cards + items: + - heading: Access and authentication + description: How to gain access. + path: /cirq/hardware/ionq/access + - heading: Getting started with IonQ hardware + description: How to run your first circuit. + path: /cirq/hardware/ionq/getting_started + - heading: IonQ API Service + description: Using the IonQ API. + path: /cirq/hardware/ionq/service + - heading: IonQ API circuits + description: Writing circuits for the IonQ API. + path: /cirq/hardware/ionq/circuits + - heading: Running IonQ API jobs + description: How to run jobs with the IonQ API. + path: /cirq/hardware/ionq/jobs + - heading: IonQ API calibrations + description: How to get hardware device calibration data through the IonQ API. + path: /cirq/hardware/ionq/calibrations - - heading: Pasqal hardware - description: Cirq's interface with Pasqal hardware. - options: - - cards - items: - - heading: Access and authentication - description: How to gain access. - path: /cirq/hardware/pasqal/access - - heading: Getting started with Pasqal hardware - description: How to run your first circuit. - path: /cirq/hardware/pasqal/getting_started - - heading: Pasqal devices - description: Device objects to specify Pasqal hardware. - path: /cirq/hardware/pasqal/devices - - heading: Pasqal sampler - description: Sampler objects to run on Pasqal hardware. - path: /cirq/hardware/pasqal/sampler + - heading: Pasqal hardware + description: Cirq's interface with Pasqal hardware. + options: + - cards + items: + - heading: Access and authentication + description: How to gain access. + path: /cirq/hardware/pasqal/access + - heading: Getting started with Pasqal hardware + description: How to run your first circuit. + path: /cirq/hardware/pasqal/getting_started + - heading: Pasqal devices + description: Device objects to specify Pasqal hardware. + path: /cirq/hardware/pasqal/devices + - heading: Pasqal sampler + description: Sampler objects to run on Pasqal hardware. + path: /cirq/hardware/pasqal/sampler - - heading: Rigetti hardware - description: Cirq's interface with Rigetti hardware. - options: - - cards - items: - - heading: Access and authentication - description: How to gain access. - path: /cirq/hardware/rigetti/access - - heading: Getting started with Rigetti hardware - description: How to run your first circuit. - path: /cirq/hardware/rigetti/getting_started + - heading: Rigetti hardware + description: Cirq's interface with Rigetti hardware. + options: + - cards + items: + - heading: Access and authentication + description: How to gain access. + path: /cirq/hardware/rigetti/access + - heading: Getting started with Rigetti hardware + description: How to run your first circuit. + path: /cirq/hardware/rigetti/getting_started diff --git a/docs/hardware/pasqal/getting_started.ipynb b/docs/hardware/pasqal/getting_started.ipynb index 49fb428e5a3..0fe4f0483be 100644 --- a/docs/hardware/pasqal/getting_started.ipynb +++ b/docs/hardware/pasqal/getting_started.ipynb @@ -205,10 +205,15 @@ "pasqal_gateset=cirq_pasqal.PasqalGateset(include_additional_controlled_ops=False)\n", "pasqal_circuit = cirq.optimize_for_target_gateset(initial_circuit,\n", " gateset=pasqal_gateset)\n", + "\n", + "# TODO(https://github.com/quantumlib/Cirq/issues/6655) - remove after fixup\n", + "pasqal_circuit = cirq.Circuit(pasqal_circuit.all_operations(),\n", + " strategy=cirq.InsertStrategy.NEW)\n", + "\n", "print(pasqal_circuit)\n", "\n", "# Now the circuit validates correctly!\n", - "p_device.validate_circuit(pasqal_circuit)\n" + "p_device.validate_circuit(pasqal_circuit)" ] }, { diff --git a/docs/named_topologies.ipynb b/docs/named_topologies.ipynb index d5f093bea59..f6377988fc5 100644 --- a/docs/named_topologies.ipynb +++ b/docs/named_topologies.ipynb @@ -64,15 +64,6 @@ "" ] }, - { - "cell_type": "markdown", - "metadata": { - "id": "ea381f53cf89" - }, - "source": [ - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." - ] - }, { "cell_type": "code", "execution_count": null, @@ -85,7 +76,7 @@ " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", - " !pip install --quiet cirq~=1.0.dev\n", + " !pip install --quiet cirq\n", " print(\"installed cirq.\")\n", " \n", "import cirq" diff --git a/docs/noise/_index.yaml b/docs/noise/_index.yaml index fd5516b74ee..b17a31e7bd7 100644 --- a/docs/noise/_index.yaml +++ b/docs/noise/_index.yaml @@ -4,33 +4,36 @@ title: Noise management for running circuits landing_page: nav: left rows: - - heading: Manage noise when running circuits - description: Running circuits on quantum hardware devices means dealing with the noise those devices introduce to the computation. Cirq provides the following ways of managing that noise, to improve the quality of the measured results. + - heading: Manage noise when running circuits + description: Running circuits on quantum hardware devices means dealing with the + noise those devices introduce to the computation. Cirq provides the following + ways of managing that noise, to improve the quality of the measured results. options: - - cards + - cards items: - - heading: Representing Noise - description: Noise models and channels and what types of error they replicate. - path: /cirq/noise/representing_noise - - heading: Characterization and compensation - description: Characterize the error a device is exhibiting, then compensate for that error by changing the circuit or reinterpreting the results. - options: - - cards - items: - - heading: Calibration FAQ - description: Frequently asked questions about characterization and compensation. - path: /cirq/noise/calibration_faq - - heading: Floquet Calibration - description: A characterization method using Floquet theory. - path: /cirq/noise/floquet_calibration_example - - heading: Cross Entropy Benchmarking (XEB) - description: A characterization benchmarking method using cross entropy. - path: /cirq/noise/qcvv/xeb_theory - - heading: Visualizing noise - description: Graphing and plotting methods for visualizing noise. - options: - - cards - items: - - heading: Heatmaps - description: Functions to plot noise characteristics across a 2D grid device. - path: /cirq/noise/heatmaps + - heading: Representing Noise + description: Noise models and channels and what types of error they replicate. + path: /cirq/noise/representing_noise + - heading: Characterization and compensation + description: Characterize the error a device is exhibiting, then compensate for + that error by changing the circuit or reinterpreting the results. + options: + - cards + items: + - heading: Calibration FAQ + description: Frequently asked questions about characterization and compensation. + path: /cirq/noise/calibration_faq + - heading: Floquet Calibration + description: A characterization method using Floquet theory. + path: /cirq/noise/floquet_calibration_example + - heading: Cross Entropy Benchmarking (XEB) + description: A characterization benchmarking method using cross entropy. + path: /cirq/noise/qcvv/xeb_theory + - heading: Visualizing noise + description: Graphing and plotting methods for visualizing noise. + options: + - cards + items: + - heading: Heatmaps + description: Functions to plot noise characteristics across a 2D grid device. + path: /cirq/noise/heatmaps diff --git a/docs/simulate/_index.yaml b/docs/simulate/_index.yaml index 5507a165cbf..9bcddc6a50d 100644 --- a/docs/simulate/_index.yaml +++ b/docs/simulate/_index.yaml @@ -4,41 +4,47 @@ title: Simulate a circuit landing_page: nav: left rows: - - heading: Simulate a circuit - description: Compute the effects of a quantum circuit, by simulating a quantum computer with a classical one. - options: - - cards - items: - - heading: Exact Simulation - description: Simulate a perfectly noiseless quantum computer. - path: /cirq/simulate/simulation - - heading: Noisy Simulation - description: Simulate a more realistic quantum computer, subject to error and noise. - path: /cirq/simulate/noisy_simulation - - heading: Parameter Sweeps - description: Efficiently evaluate many circuits which only differ in operation parameter values. - path: /cirq/simulate/params - - heading: State Histograms - description: Visualize the results of simulation as a histogram over basis states. - path: /cirq/simulate/state_histograms + - heading: Simulate a circuit + description: Compute the effects of a quantum circuit, by simulating a quantum + computer with a classical one. + options: + - cards + items: + - heading: Exact Simulation + description: Simulate a perfectly noiseless quantum computer. + path: /cirq/simulate/simulation + - heading: Noisy Simulation + description: Simulate a more realistic quantum computer, subject to error and + noise. + path: /cirq/simulate/noisy_simulation + - heading: Parameter Sweeps + description: Efficiently evaluate many circuits which only differ in operation + parameter values. + path: /cirq/simulate/params + - heading: State Histograms + description: Visualize the results of simulation as a histogram over basis states. + path: /cirq/simulate/state_histograms + - heading: Quantum Virtual Machine + description: Run circuits on a virtual version of quantum hardware, complete with + an identical interface and noisy simulation that mimics hardware devices. + options: + - cards + items: - heading: Quantum Virtual Machine - description: Run circuits on a virtual version of quantum hardware, complete with an identical interface and noisy simulation that mimics hardware devices. - options: - - cards - items: - - heading: Quantum Virtual Machine - description: Build and use a QVM with a virtual Engine and realistic noise model. - path: /cirq/simulate/quantum_virtual_machine - - heading: QVM Circuit Preparation - description: Prepare and run a circuit on a QVM in detail. - path: /cirq/simulate/qvm_basic_example - - heading: QVM Stabilizer Example - description: Run a stabilizer circuit on QVMs representing different devices and different noise. - path: /cirq/simulate/qvm_stabilizer_example - - heading: QVM Creation Template - description: Run a device-ready circuit on a QVM with default settings. - path: /cirq/simulate/qvm_builder_code - - heading: Virtual Engine Interface - description: Details on the virtual version of the hardware Engine API and how to use it. - path: /cirq/simulate/virtual_engine_interface \ No newline at end of file + description: Build and use a QVM with a virtual Engine and realistic noise model. + path: /cirq/simulate/quantum_virtual_machine + - heading: QVM Circuit Preparation + description: Prepare and run a circuit on a QVM in detail. + path: /cirq/simulate/qvm_basic_example + - heading: QVM Stabilizer Example + description: Run a stabilizer circuit on QVMs representing different devices + and different noise. + path: /cirq/simulate/qvm_stabilizer_example + - heading: QVM Creation Template + description: Run a device-ready circuit on a QVM with default settings. + path: /cirq/simulate/qvm_builder_code + - heading: Virtual Engine Interface + description: Details on the virtual version of the hardware Engine API and how + to use it. + path: /cirq/simulate/virtual_engine_interface diff --git a/docs/start/intro.ipynb b/docs/start/intro.ipynb index 67d216a357c..f1d11d01f5f 100644 --- a/docs/start/intro.ipynb +++ b/docs/start/intro.ipynb @@ -82,10 +82,7 @@ "source": [ "To use Cirq one first needs to install Cirq. Installation instructions are available at [https://quantumai.google/cirq/start/install](https://quantumai.google/cirq/start/install). For the purpose of this tutorial, we run `pip install cirq` as shown in the following code cell to install the latest release of Cirq. \n", "\n", - "> Different notebook execution systems exist, but for most part they have \"run\" button on a cell which you can click, or \"shift + enter\" is often the shortcut to run the cell. \n", - "\n", - "\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`.\n" + "> Different notebook execution systems exist, but for most part they have \"run\" button on a cell which you can click, or \"shift + enter\" is often the shortcut to run the cell. \n" ] }, { @@ -100,7 +97,7 @@ " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", - " !pip install --quiet cirq~=1.0.dev\n", + " !pip install --quiet cirq\n", " print(\"installed cirq.\")\n", " import cirq\n", "\n", diff --git a/docs/transform/_index.yaml b/docs/transform/_index.yaml index e0e6fdb9215..eb9b16e674e 100644 --- a/docs/transform/_index.yaml +++ b/docs/transform/_index.yaml @@ -4,17 +4,19 @@ title: Transform a circuit landing_page: nav: left rows: - - heading: Transform a circuit - description: Programmatically transform a provided circuit into another one to implement any compilation or optimization process that changes a circuit. - options: - - cards - items: - - heading: Circuit Transformers - description: The Transformer class and contract to represent some process that changes a supplied circuit. - path: /cirq/transform/transformers - - heading: Custom Transformers - description: Write your own Transformer with decorators, primitives and decompositions. - path: /cirq/transform/custom_transformers - - heading: Routing as a Transformer - description: Qubit Routing utilities for easily executing circuits on hardware. - path: /cirq/transform/routing_transformer + - heading: Transform a circuit + description: Programmatically transform a provided circuit into another one to + implement any compilation or optimization process that changes a circuit. + options: + - cards + items: + - heading: Circuit Transformers + description: The Transformer class and contract to represent some process that + changes a supplied circuit. + path: /cirq/transform/transformers + - heading: Custom Transformers + description: Write your own Transformer with decorators, primitives and decompositions. + path: /cirq/transform/custom_transformers + - heading: Routing as a Transformer + description: Qubit Routing utilities for easily executing circuits on hardware. + path: /cirq/transform/routing_transformer diff --git a/docs/transform/routing_transformer.ipynb b/docs/transform/routing_transformer.ipynb index 5441a1700ed..8f1c5723d15 100644 --- a/docs/transform/routing_transformer.ipynb +++ b/docs/transform/routing_transformer.ipynb @@ -69,8 +69,7 @@ "id": "RrRN9ilV0Ltg" }, "source": [ - "## Setup\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "## Setup\n" ] }, { @@ -85,7 +84,7 @@ " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", - " !pip install --quiet cirq~=1.0.dev\n", + " !pip install --quiet cirq\n", " import cirq\n", "\n", " print(\"installed cirq.\")" diff --git a/docs/tutorials/google/colab.ipynb b/docs/tutorials/google/colab.ipynb index 417364818ca..57d48c8c2de 100644 --- a/docs/tutorials/google/colab.ipynb +++ b/docs/tutorials/google/colab.ipynb @@ -68,8 +68,7 @@ "id": "fe7e28f44667" }, "source": [ - "## Setup\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "## Setup\n" ] }, { diff --git a/docs/tutorials/google/echoes.ipynb b/docs/tutorials/google/echoes.ipynb index 238d2e24c34..dfb98c16ef4 100644 --- a/docs/tutorials/google/echoes.ipynb +++ b/docs/tutorials/google/echoes.ipynb @@ -99,9 +99,7 @@ "id": "bpBUR4JblClA" }, "source": [ - "We first install Cirq then import packages we will use.\n", - "\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "We first install Cirq then import packages we will use.\n" ] }, { @@ -115,7 +113,7 @@ "try:\n", " import cirq\n", "except ImportError:\n", - " !pip install --quiet cirq~=1.0.dev" + " !pip install --quiet cirq" ] }, { diff --git a/docs/tutorials/google/identifying_hardware_changes.ipynb b/docs/tutorials/google/identifying_hardware_changes.ipynb index e021c243677..4e3122e4300 100644 --- a/docs/tutorials/google/identifying_hardware_changes.ipynb +++ b/docs/tutorials/google/identifying_hardware_changes.ipynb @@ -137,9 +137,7 @@ "source": [ "### Setup\n", "\n", - "First, install Cirq and import the necessary packages.\n", - "\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "First, install Cirq and import the necessary packages.\n" ] }, { @@ -188,7 +186,7 @@ "try:\n", " import cirq\n", "except ImportError:\n", - " !pip install --quiet cirq~=1.0.dev" + " !pip install --quiet cirq" ] }, { diff --git a/docs/tutorials/google/spin_echoes.ipynb b/docs/tutorials/google/spin_echoes.ipynb index a37ef5de744..d3287f01848 100644 --- a/docs/tutorials/google/spin_echoes.ipynb +++ b/docs/tutorials/google/spin_echoes.ipynb @@ -93,8 +93,7 @@ "id": "BRfLi9YSMg4v" }, "source": [ - "## Setup\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "## Setup\n" ] }, { @@ -109,7 +108,7 @@ " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", - " !pip install --quiet cirq~=1.0.dev\n", + " !pip install --quiet cirq\n", " print(\"installed cirq.\")" ] }, diff --git a/docs/tutorials/google/visualizing_calibration_metrics.ipynb b/docs/tutorials/google/visualizing_calibration_metrics.ipynb index 74ad7dc2b0b..b7e4990492a 100644 --- a/docs/tutorials/google/visualizing_calibration_metrics.ipynb +++ b/docs/tutorials/google/visualizing_calibration_metrics.ipynb @@ -70,8 +70,7 @@ "id": "fe7e28f44667" }, "source": [ - "## Setup\n", - "Note: this notebook relies on unreleased Cirq features. If you want to try these features, make sure you install cirq via `pip install cirq~=1.0.dev`." + "## Setup\n" ] }, { @@ -88,7 +87,7 @@ " import cirq\n", "except ImportError:\n", " print(\"installing cirq...\")\n", - " !pip install --quiet cirq~=1.0.dev\n", + " !pip install --quiet cirq\n", " print(\"installed cirq.\")\n", "import cirq\n", "import cirq_google\n", diff --git a/examples/stabilizer_code.ipynb b/examples/stabilizer_code.ipynb index 55f9e6aea1c..3e0aae20db0 100644 --- a/examples/stabilizer_code.ipynb +++ b/examples/stabilizer_code.ipynb @@ -25,6 +25,7 @@ " print(\"installing cirq...\")\n", " !pip install --quiet cirq\n", " print(\"installed cirq.\")\n", + " import cirq\n", " \n", "from cirq.contrib.svg import SVGCircuit\n", "import examples.stabilizer_code as sc"