Operations
Operations are common PrimitiveBlocks, these are often
called gates elsewhere.
Constant blocks
Section titled “Constant blocks”
X(target, noise=None)
Section titled “
X(target, noise=None)
”
Bases: PrimitiveBlock
The X gate.
Source code in qadence/operations/primitive.py
3536def __init__(self, target: int, noise: NoiseHandler | None = None) -> None: super().__init__((target,), noise=noise)
Y(target, noise=None)
Section titled “
Y(target, noise=None)
”
Bases: PrimitiveBlock
The Y gate.
Source code in qadence/operations/primitive.py
5657def __init__(self, target: int, noise: NoiseHandler | None = None) -> None: super().__init__((target,), noise=noise)
Z(target, noise=None)
Section titled “
Z(target, noise=None)
”
Bases: PrimitiveBlock
The Z gate.
Source code in qadence/operations/primitive.py
7778def __init__(self, target: int, noise: NoiseHandler | None = None) -> None: super().__init__((target,), noise=noise)
I(target, noise=None)
Section titled “
I(target, noise=None)
”
Bases: PrimitiveBlock
The identity gate.
Source code in qadence/operations/primitive.py
9899def __init__(self, target: int, noise: NoiseHandler | None = None) -> None: super().__init__((target,), noise=noise)
H(target, noise=None)
Section titled “
H(target, noise=None)
”
Bases: PrimitiveBlock
The Hadamard or H gate.
Source code in qadence/operations/primitive.py
235236237238239240241def __init__( self, target: int, noise: NoiseHandler | None = None,) -> None: self.generator = (1 / np.sqrt(2)) * (X(target) + Z(target) - np.sqrt(2) * I(target)) super().__init__((target,), noise=noise)
S(target, noise=None)
Section titled “
S(target, noise=None)
”
Bases: PrimitiveBlock
The S / Phase gate.
Source code in qadence/operations/primitive.py
185186187188189190191def __init__( self, target: int, noise: NoiseHandler | None = None,) -> None: self.generator = I(target) - Z(target) super().__init__((target,), noise=noise)
SDagger(target, noise=None)
Section titled “
SDagger(target, noise=None)
”
Bases: PrimitiveBlock
The Hermitian adjoint/conjugate transpose of the S / Phase gate.
Source code in qadence/operations/primitive.py
210211212213214215216def __init__( self, target: int, noise: NoiseHandler | None = None,) -> None: self.generator = I(target) - Z(target) super().__init__((target,), noise=noise)
SWAP(control, target, noise=None)
Section titled “
SWAP(control, target, noise=None)
”
Bases: PrimitiveBlock
The SWAP gate.
Source code in qadence/operations/primitive.py
356357358359360361362363364365366367368369def __init__( self, control: int, target: int, noise: NoiseHandler | None = None,) -> 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), noise=noise)
T(target, noise=None)
Section titled “
T(target, noise=None)
”
Bases: PrimitiveBlock
The T gate.
Source code in qadence/operations/primitive.py
297298299300301302303def __init__( self, target: int, noise: NoiseHandler | None = None,) -> None: self.generator = I(target) - Z(target) super().__init__((target,), noise)
TDagger(target, noise=None)
Section titled “
TDagger(target, noise=None)
”
Bases: PrimitiveBlock
The Hermitian adjoint/conjugate transpose of the T gate.
Source code in qadence/operations/primitive.py
327328329330331332333def __init__( self, target: int, noise: NoiseHandler | None = None,) -> None: self.generator = I(target) - Z(target) super().__init__((target,), noise)
CNOT(control, target, noise=None)
Section titled “
CNOT(control, target, noise=None)
”
Bases: ControlBlock
The CNot, or CX, gate.
Source code in qadence/operations/control_ops.py
394041def __init__(self, control: int, target: int, noise: NoiseHandler | None = None) -> None: self.generator = kron(N(control), X(target) - I(target)) super().__init__((control,), X(target), noise=noise)
CZ(control, target, noise=None)
Section titled “
CZ(control, target, noise=None)
”
Bases: MCZ
The CZ gate.
Source code in qadence/operations/control_ops.py
99100def __init__(self, control: int, target: int, noise: NoiseHandler | None = None) -> None: super().__init__((control,), target, noise=noise)
CPHASE(control, target, parameter, noise=None)
Section titled “
CPHASE(control, target, parameter, noise=None)
”
Bases: MCPHASE
The CPHASE gate.
Source code in qadence/operations/control_ops.py
274275276277278279280281def __init__( self, control: int, target: int, parameter: Parameter | TNumber | sympy.Expr | str, noise: NoiseHandler | None = None,): super().__init__((control,), target, parameter, noise=noise)Parametrized blocks
Section titled “Parametrized blocks”
RX(target, parameter, noise=None)
Section titled “
RX(target, parameter, noise=None)
”
Bases: ParametricBlock
The Rx gate.
Source code in qadence/operations/parametric.py
656667686970717273747576def __init__( self, target: int, parameter: Parameter | TParameter | ParamMap, noise: NoiseHandler | None = None,) -> None: # 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,), noise=noise)
RY(target, parameter, noise=None)
Section titled “
RY(target, parameter, noise=None)
”
Bases: ParametricBlock
The Ry gate.
Source code in qadence/operations/parametric.py
98 99100101102103104105106107108def __init__( self, target: int, parameter: Parameter | TParameter | ParamMap, noise: NoiseHandler | None = None,) -> None: self.parameters = ( parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter) ) self.generator = Y(target) super().__init__((target,), noise=noise)
RZ(target, parameter, noise=None)
Section titled “
RZ(target, parameter, noise=None)
”
Bases: ParametricBlock
The Rz gate.
Source code in qadence/operations/parametric.py
130131132133134135136137138139140def __init__( self, target: int, parameter: Parameter | TParameter | ParamMap, noise: NoiseHandler | None = None,) -> None: self.parameters = ( parameter if isinstance(parameter, ParamMap) else ParamMap(parameter=parameter) ) self.generator = Z(target) super().__init__((target,), noise=noise)
CRX(control, target, parameter, noise=None)
Section titled “
CRX(control, target, parameter, noise=None)
”
Bases: MCRX
The CRX gate.
Source code in qadence/operations/control_ops.py
136137138139140141142143def __init__( self, control: int, target: int, parameter: Parameter | TNumber | sympy.Expr | str, noise: NoiseHandler | None = None,): super().__init__((control,), target, parameter, noise=noise)
CRY(control, target, parameter, noise=None)
Section titled “
CRY(control, target, parameter, noise=None)
”
Bases: MCRY
The CRY gate.
Source code in qadence/operations/control_ops.py
179180181182def __init__( self, control: int, target: int, parameter: TParameter, noise: NoiseHandler | None = None): super().__init__((control,), target, parameter, noise=noise)
CRZ(control, target, parameter, noise=None)
Section titled “
CRZ(control, target, parameter, noise=None)
”
Bases: MCRZ
The CRZ gate.
Source code in qadence/operations/control_ops.py
218219220221222223224225def __init__( self, control: int, target: int, parameter: Parameter | TNumber | sympy.Expr | str, noise: NoiseHandler | None = None,): super().__init__((control,), target, parameter, noise=noise)
PHASE(target, parameter, noise=None)
Section titled “
PHASE(target, parameter, noise=None)
”
Bases: ParametricBlock
The Parametric Phase / S gate.
Source code in qadence/operations/parametric.py
363738394041424344def __init__( self, target: int, parameter: Parameter | TNumber | sympy.Expr | str, noise: NoiseHandler | None = None,) -> None: self.parameters = ParamMap(parameter=parameter) self.generator = I(target) - Z(target) super().__init__((target,), noise=noise)Hamiltonian Evolution
Section titled “Hamiltonian Evolution”
HamEvo(generator, parameter, qubit_support=None, duration=None, noise_operators=list())
Section titled “
HamEvo(generator, parameter, qubit_support=None, duration=None, noise_operators=list())
”
Bases: TimeEvolutionBlock
The Hamiltonian evolution operator U(t).
For time-independent Hamiltonians the solution is exact:
U(t) = exp(-iGt)where G represents an Hermitian generator, or Hamiltonian and t represents the time parameter. For time-dependent Hamiltonians, the solution is obtained by numerical integration of the Schrodinger equation.
| PARAMETER | DESCRIPTION |
|---|---|
generator
|
Hamiltonian generator, either symbolic as an AbstractBlock, or as a torch.Tensor or numpy.ndarray.
TYPE:
|
parameter
|
The time parameter for evolution operator. For the time-independent case, it represents the actual value for which the evolution will be evaluated. For the time-dependent case, it should be an instance of TimeParameter to signal the solver the variable that will be integrated over.
TYPE:
|
qubit_support
|
The qubits on which the evolution will be performed on. Only required for generators that are not a composition of blocks.
TYPE:
|
duration
|
(optional) duration of the evolution in case of time-dependent generator. By default, a FeatureParameter with tag "duration" will be initialized, and the value will then be required in the values dict.
TYPE:
|
noise_operators
|
(optional) the list of jump operators to use when using a shrodinger solver, allowing to perform noisy simulations.
TYPE:
|
Examples:
from qadence import X, HamEvo, PI, add, runfrom qadence import FeatureParameter, TimeParameterimport torch
n_qubits = 3
# Hamiltonian as a block compositionhamiltonian = add(X(i) for i in range(n_qubits))hevo = HamEvo(hamiltonian, parameter=torch.rand(2))state = run(hevo)
# Hamiltonian as a random matrixhamiltonian = torch.rand(2, 2, dtype=torch.complex128)hevo = HamEvo(hamiltonian, parameter=torch.rand(2), qubit_support=(0,))state = run(hevo)
# Time-dependent Hamiltoniant = TimeParameter("t")hamiltonian = t * add(X(i) for i in range(n_qubits))hevo = HamEvo(hamiltonian, parameter=t)state = run(hevo, values = {"duration": torch.tensor(1.0)})
# Adding noise operatorsnoise_ops = [X(0)]hevo = HamEvo(hamiltonian, parameter=t, noise_operators=noise_ops)Source code in qadence/operations/ham_evo.py
95 96 97 98 99100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170def __init__( self, generator: Union[TGenerator, AbstractBlock], parameter: TParameter, qubit_support: tuple[int, ...] = None, duration: TParameter | None = None, noise_operators: list[AbstractBlock] = list(),): params = {} 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: params = {str(e): e for e in expressions(generator)} if generator.is_time_dependent: if isinstance(duration, str): duration = Parameter(duration, trainable=False) elif duration is None: duration = Parameter("duration", trainable=False) if not generator.is_time_dependent and duration is not None: raise TypeError( "Duration argument is only supported for time-dependent generators." ) elif isinstance(generator, torch.Tensor): if duration is not None: raise TypeError( "Duration argument is only supported for time-dependent generators." ) 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." ) params = {str(generator.__hash__()): generator} elif isinstance(generator, (sympy.Basic, sympy.Array)): if duration is not None: raise TypeError( "Duration argument is only supported for time-dependent generators." ) params = {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." ) if duration is not None: params = {"duration": Parameter(duration), **params} params = {"parameter": Parameter(parameter), **params} self.parameters = ParamMap(**params) self.time_param = parameter self.generator = generator self.duration = duration
if len(noise_operators) > 0: if not all( [ len(set(op.qubit_support + self.qubit_support) - set(self.qubit_support)) == 0 for op in noise_operators ] ): raise ValueError( "Noise operators should be defined" " over the same or a subset of the qubit support" ) if True in [op.is_parametric for op in noise_operators]: raise ValueError("Parametric operators are not supported") self.noise_operators = noise_operators
digital_decomposition(approximation=LTSOrder.ST4)
Section titled “
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:
|
| RETURNS | DESCRIPTION |
|---|---|
AbstractBlock
|
a block with the digital decomposition
TYPE:
|
Source code in qadence/operations/ham_evo.py
207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268def 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)
Section titled “
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. (external) where it is applied to XX-type Hamiltonian
Source code in qadence/operations/analog.py
484950515253545556def __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))Analog blocks
Section titled “Analog blocks”
AnalogRX(angle, qubit_support='global', add_pattern=True)
Section titled “
AnalogRX(angle, qubit_support='global', add_pattern=True)
”Analog X rotation.
Shorthand for AnalogRot:
φ=2.4; Ω=π; t = φ/Ω * 1000AnalogRot(duration=t, omega=Ω)| PARAMETER | DESCRIPTION |
|---|---|
angle
|
Rotation angle [rad]
TYPE:
|
qubit_support
|
Defines the (local/global) qubit support
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
ConstantAnalogRotation
|
ConstantAnalogRotation |
Source code in qadence/operations/analog.py
175176177178179180181182183184185186187188189190191192193194195196def 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)
Section titled “
AnalogRY(angle, qubit_support='global', add_pattern=True)
”Analog Y rotation.
Shorthand for AnalogRot:
φ=2.4; Ω=π; t = φ/Ω * 1000AnalogRot(duration=t, omega=Ω, phase=-π/2)| RETURNS | DESCRIPTION |
|---|---|
ConstantAnalogRotation
|
ConstantAnalogRotation |
Source code in qadence/operations/analog.py
199200201202203204205206207208209210211212213214215216217218219def 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)
Section titled “
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
222223224225226227228229230231232233234235236237238239240241242def 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)
Section titled “
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:
|
omega
|
Rotation frequency [rad/μs]
TYPE:
|
delta
|
Rotation frequency [rad/μs]
TYPE:
|
phase
|
Phase angle [rad]
TYPE:
|
qubit_support
|
Defines the (local/global) qubit support
TYPE:
|
add_pattern
|
False disables the semi-local addressing pattern for the execution of this specific block.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
ConstantAnalogRotation
|
ConstantAnalogRotation |
Source code in qadence/operations/analog.py
111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147def 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)
Section titled “
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:
|
qubit_support
|
Qubits the
TYPE:
|
add_pattern
|
False disables the semi-local addressing pattern for the execution of this specific block.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
InteractionBlock
|
a |
Source code in qadence/operations/analog.py
63646566676869707172737475767778798081828384def 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)