Source code for mitiq.ddd.rules.rules

# Copyright (C) Unitary Fund
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.

"""Built-in rules determining what DDD sequence should be applied in a given
slack window.
"""

from itertools import cycle
from typing import List

import numpy as np
from cirq import (
    Circuit,
    Gate,
    I,
    LineQubit,
    X,
    Y,
    allclose_up_to_global_phase,
    unitary,
)


[docs] def general_rule( slack_length: int, gates: List[Gate], spacing: int = -1 ) -> Circuit: """Returns a digital dynamical decoupling sequence, based on inputs. Args: slack_length: Length of idle window to fill. spacing: How many identity spacing gates to apply between dynamical decoupling gates, as a non-negative int. Negative int corresponds to default. Defaults to maximal spacing that fits a single sequence in the given slack window. gates: A list of single qubit Cirq gates to build the rule. E.g. [X, X] is the xx sequence, [X, Y, X, Y] is the xyxy sequence. - Note: To repeat the sequence, specify a repeated gateset. Returns: A digital dynamical decoupling sequence, as a Cirq circuit. Example: When ``slack_length = 8`` and ``gates = [X, X]`` the spacing defaults to 2 and the rule returns the sequence:: ──I──I──X──I──I──X──I──I── When ``slack_length = 9`` and ``gates [X, Y, X, Y]`` the spacing defaults to 1 and the rule returns the sequence:: ──I──X──I──Y──I──X──I──Y──I── """ if len(gates) < 2: raise ValueError("Gateset too short to make a ddd sequence.") if slack_length < 2 or slack_length < len(gates): return Circuit() num_decoupling_gates = len(gates) slack_difference = slack_length - num_decoupling_gates if spacing < 0: spacing = slack_difference // (num_decoupling_gates + 1) slack_remainder = slack_length - ( spacing * (num_decoupling_gates + 1) + num_decoupling_gates ) if slack_remainder < 0: return Circuit() q = LineQubit(0) slack_gates = [I(q) for _ in range(spacing)] sequence = Circuit(slack_gates) for gate in gates: sequence.append(gate.on(q)) sequence.append(slack_gates) if not allclose_up_to_global_phase(np.eye(2), unitary(sequence)): raise ValueError("Sequence is not equivalent to the identity!") for i in range(slack_remainder): sequence.append(I(q)) if i % 2 else sequence.insert(0, I(q)) return sequence
[docs] def xx(slack_length: int, spacing: int = -1) -> Circuit: """Returns an XX digital dynamical decoupling sequence, based on inputs. Args: slack_length: Length of idle window to fill. spacing: How many identity spacing gates to apply between dynamical decoupling gates, as a non-negative int. Negative int corresponds to default. Defaults to maximal spacing that fits a single sequence in the given slack window. E.g. given slack_length = 8 the spacing defaults to 2 and this rule returns the sequence: ──I──I──X──I──I──X──I──I──. Returns: An XX digital dynamical decoupling sequence, as a Cirq circuit. """ xx_sequence = general_rule( slack_length=slack_length, spacing=spacing, gates=[X, X], ) return xx_sequence
[docs] def xyxy(slack_length: int, spacing: int = -1) -> Circuit: """Returns an XYXY digital dynamical decoupling sequence, based on inputs. Args: slack_length: Length of idle window to fill. spacing: How many identity spacing gates to apply between dynamical decoupling gates, as a non-negative int. Negative int corresponds to default. Defaults to maximal spacing that fits a single sequence in the given slack window. E.g. given slack_length = 9 the spacing defaults to 1 and this rule returns the sequence: ──I──X──I──Y──I──X──I──Y──I──. Returns: An XYXY digital dynamical decoupling sequence, as a Cirq circuit. """ xyxy_sequence = general_rule( slack_length=slack_length, spacing=spacing, gates=[X, Y, X, Y], ) return xyxy_sequence
[docs] def yy(slack_length: int, spacing: int = -1) -> Circuit: """Returns a YY digital dynamical decoupling sequence, based on inputs. Args: slack_length: Length of idle window to fill. spacing: How many identity spacing gates to apply between dynamical decoupling gates, as a non-negative int. Negative int corresponds to default. Defaults to maximal spacing that fits a single sequence in the given slack window. E.g. given slack_length = 8 the spacing defaults to 2 and this rule returns the sequence: ──I──I──Y──I──I──Y──I──I──. Returns: An YY digital dynamical decoupling sequence, as a Cirq circuit. """ yy_sequence = general_rule( slack_length=slack_length, spacing=spacing, gates=[Y, Y], ) return yy_sequence
[docs] def repeated_rule(slack_length: int, gates: List[Gate]) -> Circuit: """Returns a general digital dynamical decoupling sequence that repeats until the slack is filled without spacing, up to a complete repetition. Args: slack_length: Length of idle window to fill. gates: A list of single qubit Cirq gates to build the rule. E.g. [X, X] is the xx sequence, [X, Y, X, Y] is the xyxy sequence. Returns: A repeated digital dynamical decoupling sequence, as a Cirq circuit. Warning: Where :func:`.general_rule()` fills a slack window with a single sequence, this rule attempts to fill every moment with sequence repetitions (up to a complete repetition of the gate set). E.g. given ``slack_length = 8`` and ``gates = [X, Y, X, Y]``, this rule returns the sequence:: ──X──Y──X──Y──X──Y──X──Y── """ num_decoupling_gates = len(gates) if num_decoupling_gates > slack_length: return Circuit() sequence = general_rule( slack_length=slack_length, spacing=0, gates=[ gate for (_, gate) in zip( range(slack_length - (slack_length % num_decoupling_gates)), cycle(gates), ) ], ) return sequence