Skip to content

Operations

Operations are common PrimitiveBlocks, these are often called gates elsewhere.

Constant blocks

X(target)

Bases: PrimitiveBlock

The X gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    super().__init__((target,))

Y(target)

Bases: PrimitiveBlock

The Y gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    super().__init__((target,))

Z(target)

Bases: PrimitiveBlock

The Z gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    super().__init__((target,))

I(target)

Bases: PrimitiveBlock

The identity gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    super().__init__((target,))

H(target)

Bases: PrimitiveBlock

The Hadamard or H gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    self.generator = (1 / np.sqrt(2)) * (X(target) + Z(target) - np.sqrt(2) * I(target))
    super().__init__((target,))

S(target)

Bases: PrimitiveBlock

The S / Phase gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    self.generator = I(target) - Z(target)
    super().__init__((target,))

SDagger(target)

Bases: PrimitiveBlock

The Hermitian adjoint/conjugate transpose of the S / Phase gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    self.generator = I(target) - Z(target)
    super().__init__((target,))

SWAP(control, target)

Bases: PrimitiveBlock

The SWAP gate.

Source code in qadence/operations/primitive.py
def __init__(self, control: int, target: int) -> None:
    a11 = 0.5 * (Z(control) - I(control))
    a22 = -0.5 * (Z(target) + I(target))
    a12 = 0.5 * (chain(X(control), Z(control)) + X(control))
    a21 = 0.5 * (chain(Z(target), X(target)) + X(target))
    self.generator = (
        kron(-1.0 * a22, a11) + kron(-1.0 * a11, a22) + kron(a12, a21) + kron(a21, a12)
    )
    super().__init__((control, target))

T(target)

Bases: PrimitiveBlock

The T gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    self.generator = I(target) - Z(target)
    super().__init__((target,))

TDagger(target)

Bases: PrimitiveBlock

The Hermitian adjoint/conjugate transpose of the T gate.

Source code in qadence/operations/primitive.py
def __init__(self, target: int):
    self.generator = I(target) - Z(target)
    super().__init__((target,))

CNOT(control, target)

Bases: ControlBlock

The CNot, or CX, gate.

Source code in qadence/operations/control_ops.py
def __init__(self, control: int, target: int) -> None:
    self.generator = kron(N(control), X(target) - I(target))
    super().__init__((control,), X(target))

CY gate not implemented

CZ(control, target)

Bases: MCZ

The CZ gate.

Source code in qadence/operations/control_ops.py
def __init__(self, control: int, target: int) -> None:
    super().__init__((control,), target)

CPHASE(control, target, parameter)

Bases: MCPHASE

The CPHASE gate.

Source code in qadence/operations/control_ops.py
def __init__(
    self,
    control: int,
    target: int,
    parameter: Parameter | TNumber | sympy.Expr | str,
):
    super().__init__((control,), target, parameter)

Parametrized blocks

RX(target, parameter)

Bases: ParametricBlock

The Rx gate.

Source code in qadence/operations/parametric.py
def __init__(self, target: int, parameter: Parameter | TParameter | ParamMap):
    # TODO: should we give them more meaningful names? like 'angle'?
    self.parameters = (
        parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter)
    )
    self.generator = X(target)
    super().__init__((target,))

RY(target, parameter)

Bases: ParametricBlock

The Ry gate.

Source code in qadence/operations/parametric.py
def __init__(self, target: int, parameter: Parameter | TParameter | ParamMap):
    self.parameters = (
        parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter)
    )
    self.generator = Y(target)
    super().__init__((target,))

RZ(target, parameter)

Bases: ParametricBlock

The Rz gate.

Source code in qadence/operations/parametric.py
def __init__(self, target: int, parameter: Parameter | TParameter | ParamMap):
    self.parameters = (
        parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter)
    )
    self.generator = Z(target)
    super().__init__((target,))

CRX(control, target, parameter)

Bases: MCRX

The CRX gate.

Source code in qadence/operations/control_ops.py
def __init__(
    self,
    control: int,
    target: int,
    parameter: Parameter | TNumber | sympy.Expr | str,
):
    super().__init__((control,), target, parameter)

CRY(control, target, parameter)

Bases: MCRY

The CRY gate.

Source code in qadence/operations/control_ops.py
def __init__(
    self,
    control: int,
    target: int,
    parameter: TParameter,
):
    super().__init__((control,), target, parameter)

CRZ(control, target, parameter)

Bases: MCRZ

The CRZ gate.

Source code in qadence/operations/control_ops.py
def __init__(
    self,
    control: int,
    target: int,
    parameter: Parameter | TNumber | sympy.Expr | str,
):
    super().__init__((control,), target, parameter)

PHASE(target, parameter)

Bases: ParametricBlock

The Parametric Phase / S gate.

Source code in qadence/operations/parametric.py
def __init__(self, target: int, parameter: Parameter | TNumber | sympy.Expr | str):
    self.parameters = ParamMap(parameter=parameter)
    self.generator = I(target) - Z(target)
    super().__init__((target,))

Hamiltonian Evolution

HamEvo(generator, parameter, qubit_support=None, duration=None)

Bases: TimeEvolutionBlock

A block implementing the Hamiltonian evolution operation H where:

H = exp(-iG, t)

where G represents a square generator and t represents the time parameter which can be parametrized.

PARAMETER DESCRIPTION
generator

Either a AbstractBlock, torch.Tensor or numpy.ndarray.

TYPE: Union[TGenerator, AbstractBlock]

parameter

A scalar or vector of numeric or torch.Tensor type.

TYPE: TParameter

qubit_support

The qubits on which the evolution will be performed on.

TYPE: tuple[int, ...] DEFAULT: None

duration

duration of evolution in case of time-dependent generator

TYPE: float | None DEFAULT: None

Examples:

from qadence import RX, HamEvo, run, PI
import torch
hevo = HamEvo(generator=RX(0, PI), parameter=torch.rand(2))
print(run(hevo))
# Now lets use a torch.Tensor as a generator, Now we have to pass the support
gen = torch.rand(2,2, dtype=torch.complex128)
hevo = HamEvo(generator=gen, parameter=torch.rand(2), qubit_support=(0,))
print(run(hevo))
tensor([[ 1.2452-5.2363e-17j, -0.7420+3.1202e-17j],
        [ 1.2779-5.7062e-17j, -0.7956+3.5527e-17j]])
tensor([[ 1.1810-0.9301j, -0.4869-1.3492j],
        [ 1.2058-0.4195j, -0.1002-0.5828j]])
Source code in qadence/operations/ham_evo.py
def __init__(
    self,
    generator: Union[TGenerator, AbstractBlock],
    parameter: TParameter,
    qubit_support: tuple[int, ...] = None,
    duration: float | None = None,
):
    gen_exprs = {}
    if qubit_support is None and not isinstance(generator, AbstractBlock):
        raise ValueError("You have to supply a qubit support for non-block generators.")
    super().__init__(qubit_support if qubit_support else generator.qubit_support)
    if isinstance(generator, AbstractBlock):
        qubit_support = generator.qubit_support
        if generator.is_parametric:
            gen_exprs = {str(e): e for e in expressions(generator)}

            if generator.is_time_dependent and duration is None:
                raise ValueError("For time-dependent generators, a duration must be specified.")

    elif isinstance(generator, torch.Tensor):
        msg = "Please provide a square generator."
        if len(generator.shape) == 2:
            assert generator.shape[0] == generator.shape[1], msg
        elif len(generator.shape) == 3:
            assert generator.shape[1] == generator.shape[2], msg
            assert generator.shape[0] == 1, "Qadence doesnt support batched generators."
        else:
            raise TypeError(
                "Only 2D or 3D generators are supported.\
                            In case of a 3D generator, the batch dim\
                            is expected to be at dim 0."
            )
        gen_exprs = {str(generator.__hash__()): generator}
    elif isinstance(generator, (sympy.Basic, sympy.Array)):
        gen_exprs = {str(generator): generator}
    else:
        raise TypeError(
            f"Generator of type {type(generator)} not supported.\
                        If you're using a numpy.ndarray, please cast it to a torch tensor."
        )
    ps = {"parameter": Parameter(parameter), **gen_exprs}
    self.parameters = ParamMap(**ps)
    self.generator = generator
    self.duration = duration

digital_decomposition(approximation=LTSOrder.ST4)

Decompose the Hamiltonian evolution into digital gates.

PARAMETER DESCRIPTION
approximation

Choose the type of decomposition. Defaults to "st4". Available types are: * 'basic' = apply first-order Trotter formula and decompose each term of the exponential into digital gates. It is exact only if applied to an operator whose terms are mutually commuting. * 'st2' = Trotter-Suzuki 2nd order formula for approximating non-commuting Hamiltonians. * 'st4' = Trotter-Suzuki 4th order formula for approximating non-commuting Hamiltonians.

TYPE: str DEFAULT: ST4

RETURNS DESCRIPTION
AbstractBlock

a block with the digital decomposition

TYPE: AbstractBlock

Source code in qadence/operations/ham_evo.py
def digital_decomposition(self, approximation: LTSOrder = LTSOrder.ST4) -> AbstractBlock:
    """Decompose the Hamiltonian evolution into digital gates.

    Args:
        approximation (str, optional): Choose the type of decomposition. Defaults to "st4".
            Available types are:
            * 'basic' = apply first-order Trotter formula and decompose each term of
                the exponential into digital gates. It is exact only if applied to an
                operator whose terms are mutually commuting.
            * 'st2' = Trotter-Suzuki 2nd order formula for approximating non-commuting
                Hamiltonians.
            * 'st4' = Trotter-Suzuki 4th order formula for approximating non-commuting
                Hamiltonians.

    Returns:
        AbstractBlock: a block with the digital decomposition
    """

    # psi(t) = exp(-i * H * t * psi0)
    # psi(t) = exp(-i * lambda * t * psi0)
    # H = sum(Paulin) + sum(Pauli1*Pauli2)
    logger.info("Quantum simulation of the time-independent Schrödinger equation.")

    blocks = []

    # how to change the type/dict to enum effectively

    # when there is a term including non-commuting matrices use st2 or st4

    # 1) should check that the given generator respects the constraints
    # single-qubit gates

    assert isinstance(
        self.generator, AbstractBlock
    ), "Only a generator represented as a block can be decomposed"

    if block_is_qubit_hamiltonian(self.generator):
        try:
            block_is_commuting_hamiltonian(self.generator)
            approximation = LTSOrder.BASIC  # use the simpler approach if the H is commuting
        except TypeError:
            logger.warning(
                """Non-commuting terms in the Pauli operator.
                The Suzuki-Trotter approximation is applied."""
            )

        blocks.extend(
            lie_trotter_suzuki(
                block=self.generator,
                parameter=self.parameters.parameter,
                order=LTSOrder[approximation],
            )
        )

        # 2) return an AbstractBlock instance with the set of gates
        # resulting from the decomposition

        return chain(*blocks)
    else:
        raise NotImplementedError(
            "The current digital decomposition can be applied only to Pauli Hamiltonians."
        )

AnalogSWAP(control, target, parameter=3 * PI / 4)

Bases: HamEvo

Single time-independent Hamiltonian evolution over a Rydberg Ising.

hamiltonian yielding a SWAP (up to global phase).

Derived from Bapat et al. where it is applied to XX-type Hamiltonian

Source code in qadence/operations/analog.py
def __init__(self, control: int, target: int, parameter: TParameter = 3 * PI / 4):
    rydberg_ising_hamiltonian_generator = (
        4.0 * kron((I(control) - Z(control)) / 2.0, (I(target) - Z(target)) / 2.0)
        + (2.0 / 3.0) * np.sqrt(2.0) * X(control)
        + (2.0 / 3.0) * np.sqrt(2.0) * X(target)
        + (1.0 + np.sqrt(5.0) / 3) * Z(control)
        + (1.0 + np.sqrt(5.0) / 3) * Z(target)
    )
    super().__init__(rydberg_ising_hamiltonian_generator, parameter, (control, target))

AnalogSWAP should be turned into a proper analog block


Analog blocks

AnalogRX(angle, qubit_support='global', add_pattern=True)

Analog X rotation.

Shorthand for AnalogRot:

φ=2.4; Ω=π; t = φ/Ω * 1000
AnalogRot(duration=t, omega=Ω)
PARAMETER DESCRIPTION
angle

Rotation angle [rad]

TYPE: float | str | Parameter

qubit_support

Defines the (local/global) qubit support

TYPE: str | QubitSupport | Tuple DEFAULT: 'global'

RETURNS DESCRIPTION
ConstantAnalogRotation

ConstantAnalogRotation

Source code in qadence/operations/analog.py
def AnalogRX(
    angle: float | str | Parameter,
    qubit_support: str | QubitSupport | Tuple = "global",
    add_pattern: bool = True,
) -> ConstantAnalogRotation:
    """Analog X rotation.

    Shorthand for [`AnalogRot`][qadence.operations.AnalogRot]:

    ```python
    φ=2.4; Ω=π; t = φ/Ω * 1000
    AnalogRot(duration=t, omega=Ω)
    ```

    Arguments:
        angle: Rotation angle [rad]
        qubit_support: Defines the (local/global) qubit support

    Returns:
        ConstantAnalogRotation
    """
    return _analog_rot(angle, qubit_support, phase=0, add_pattern=add_pattern)

AnalogRY(angle, qubit_support='global', add_pattern=True)

Analog Y rotation.

Shorthand for AnalogRot:

φ=2.4; Ω=π; t = φ/Ω * 1000
AnalogRot(duration=t, omega=Ω, phase=-π/2)
Arguments: angle: Rotation angle [rad] qubit_support: Defines the (local/global) qubit support

RETURNS DESCRIPTION
ConstantAnalogRotation

ConstantAnalogRotation

Source code in qadence/operations/analog.py
def AnalogRY(
    angle: float | str | Parameter,
    qubit_support: str | QubitSupport | Tuple = "global",
    add_pattern: bool = True,
) -> ConstantAnalogRotation:
    """Analog Y rotation.

    Shorthand for [`AnalogRot`][qadence.operations.AnalogRot]:

    ```python
    φ=2.4; Ω=π; t = φ/Ω * 1000
    AnalogRot(duration=t, omega=Ω, phase=-π/2)
    ```
    Arguments:
        angle: Rotation angle [rad]
        qubit_support: Defines the (local/global) qubit support

    Returns:
        ConstantAnalogRotation
    """
    return _analog_rot(angle, qubit_support, phase=-PI / 2, add_pattern=add_pattern)

AnalogRZ(angle, qubit_support='global', add_pattern=True)

Analog Z rotation. Shorthand for AnalogRot:

φ=2.4; δ=π; t = φ/δ * 100)
AnalogRot(duration=t, delta=δ, phase=π/2)

Source code in qadence/operations/analog.py
def AnalogRZ(
    angle: float | str | Parameter,
    qubit_support: str | QubitSupport | Tuple = "global",
    add_pattern: bool = True,
) -> ConstantAnalogRotation:
    """Analog Z rotation. Shorthand for [`AnalogRot`][qadence.operations.AnalogRot]:
    ```
    φ=2.4; δ=π; t = φ/δ * 100)
    AnalogRot(duration=t, delta=δ, phase=π/2)
    ```
    """
    q = _cast(QubitSupport, qubit_support)
    alpha = _cast(Parameter, angle)
    delta = PI
    omega = 0
    duration = alpha / delta * 1000
    h_norm = sympy.sqrt(omega**2 + delta**2)
    ps = ParamMap(
        alpha=alpha, duration=duration, omega=omega, delta=delta, phase=0.0, h_norm=h_norm
    )
    return ConstantAnalogRotation(qubit_support=q, parameters=ps, add_pattern=add_pattern)

AnalogRot(duration, omega=0, delta=0, phase=0, qubit_support='global', add_pattern=True)

General analog rotation operation.

PARAMETER DESCRIPTION
duration

Duration of the rotation [ns].

TYPE: float | str | Parameter

omega

Rotation frequency [rad/μs]

TYPE: float | str | Parameter DEFAULT: 0

delta

Rotation frequency [rad/μs]

TYPE: float | str | Parameter DEFAULT: 0

phase

Phase angle [rad]

TYPE: float | str | Parameter DEFAULT: 0

qubit_support

Defines the (local/global) qubit support

TYPE: str | QubitSupport | Tuple DEFAULT: 'global'

add_pattern

False disables the semi-local addressing pattern for the execution of this specific block.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
ConstantAnalogRotation

ConstantAnalogRotation

Source code in qadence/operations/analog.py
def AnalogRot(
    duration: float | str | Parameter,
    omega: float | str | Parameter = 0,
    delta: float | str | Parameter = 0,
    phase: float | str | Parameter = 0,
    qubit_support: str | QubitSupport | Tuple = "global",
    add_pattern: bool = True,
) -> ConstantAnalogRotation:
    """General analog rotation operation.

    Arguments:
        duration: Duration of the rotation [ns].
        omega: Rotation frequency [rad/μs]
        delta: Rotation frequency [rad/μs]
        phase: Phase angle [rad]
        qubit_support: Defines the (local/global) qubit support
        add_pattern: False disables the semi-local addressing pattern
            for the execution of this specific block.

    Returns:
        ConstantAnalogRotation
    """

    if omega == 0 and delta == 0:
        raise ValueError("Parameters omega and delta cannot both be 0.")

    q = _cast(QubitSupport, qubit_support)
    duration = Parameter(duration)
    omega = Parameter(omega)
    delta = Parameter(delta)
    phase = Parameter(phase)
    h_norm = sympy.sqrt(omega**2 + delta**2)
    alpha = duration * h_norm / 1000
    ps = ParamMap(
        alpha=alpha, duration=duration, omega=omega, delta=delta, phase=phase, h_norm=h_norm
    )
    return ConstantAnalogRotation(parameters=ps, qubit_support=q, add_pattern=add_pattern)

AnalogInteraction(duration, qubit_support='global', add_pattern=True)

Evolution of the interaction term for a register of qubits.

Constructs a InteractionBlock.

PARAMETER DESCRIPTION
duration

Time to evolve the interaction for in nanoseconds.

TYPE: TNumber | Basic

qubit_support

Qubits the InteractionBlock is applied to. Can be either "global" to evolve the interaction block to all qubits or a tuple of integers.

TYPE: str | QubitSupport | tuple DEFAULT: 'global'

add_pattern

False disables the semi-local addressing pattern for the execution of this specific block.

TYPE: bool DEFAULT: True

RETURNS DESCRIPTION
InteractionBlock

a InteractionBlock

Source code in qadence/operations/analog.py
def AnalogInteraction(
    duration: TNumber | sympy.Basic,
    qubit_support: str | QubitSupport | tuple = "global",
    add_pattern: bool = True,
) -> InteractionBlock:
    """Evolution of the interaction term for a register of qubits.

    Constructs a [`InteractionBlock`][qadence.blocks.analog.InteractionBlock].

    Arguments:
        duration: Time to evolve the interaction for in nanoseconds.
        qubit_support: Qubits the `InteractionBlock` is applied to. Can be either
            `"global"` to evolve the interaction block to all qubits or a tuple of integers.
        add_pattern: False disables the semi-local addressing pattern
            for the execution of this specific block.

    Returns:
        a `InteractionBlock`
    """
    q = _cast(QubitSupport, qubit_support)
    ps = ParamMap(duration=duration)
    return InteractionBlock(parameters=ps, qubit_support=q, add_pattern=add_pattern)