Constructing arbitrary Hamiltonians
At the heart of digital-analog quantum computing is the description and execution of analog blocks, which represent a set of interacting qubits under some interaction Hamiltonian.
For this purpose, Qadence relies on the hamiltonian_factory
function to create arbitrary Hamiltonian blocks to be used as generators of HamEvo
or as observables to be measured.
Arbitrary all-to-all Hamiltonians
Arbitrary all-to-all interaction Hamiltonians can be easily created by passing the number of qubits in the first argument. The type of interaction
can be chosen from the available ones in the Interaction
enum type.
from qadence import hamiltonian_factory
from qadence import N, X, Y, Z
from qadence import Interaction
n_qubits = 3
hamilt = hamiltonian_factory(n_qubits, interaction=Interaction.ZZ)
Single-qubit terms can also be added by passing the respective operator directly to the detuning
argument. For example, the total magnetization is commonly used as an observable to be measured:
For further customization, arbitrary coefficients can be passed as arrays to the interaction_strength
and detuning_strength
arguments for the two-qubits and single-qubit terms respectively.
n_qubits = 3
hamilt = hamiltonian_factory(
n_qubits,
interaction=Interaction.ZZ,
detuning=Z,
interaction_strength=[0.5, 0.2, 0.1],
detuning_strength=[0.1, 0.5, -0.3]
)
Ordering interaction strengths matters
When passing interaction strengths as an array, the ordering must be identical to the one
obtained from the edge
property of a Qadence Register
:
For one more example, let's create a transverse-field Ising model,
n_qubits = 4
n_edges = int(0.5 * n_qubits * (n_qubits - 1))
z_terms = [1.0] * n_qubits
zz_terms = [2.0] * n_edges
zz_ham = hamiltonian_factory(
n_qubits,
interaction=Interaction.ZZ,
detuning=Z,
interaction_strength=zz_terms,
detuning_strength=z_terms
)
x_terms = [-1.0] * n_qubits
x_ham = hamiltonian_factory(n_qubits, detuning = X, detuning_strength = x_terms)
transverse_ising = zz_ham + x_ham
AddBlock(0,1,2,3)
├── AddBlock(0,1,2,3)
│ ├── [mul: 1.00000000000000]
│ │ └── Z(0)
│ ├── [mul: 1.00000000000000]
│ │ └── Z(1)
│ ├── [mul: 1.00000000000000]
│ │ └── Z(2)
│ ├── [mul: 1.00000000000000]
│ │ └── Z(3)
│ ├── [mul: 2.00000000000000]
│ │ └── KronBlock(0,1)
│ │ ├── Z(0)
│ │ └── Z(1)
│ ├── [mul: 2.00000000000000]
│ │ └── KronBlock(0,2)
│ │ ├── Z(0)
│ │ └── Z(2)
│ ├── [mul: 2.00000000000000]
│ │ └── KronBlock(0,3)
│ │ ├── Z(0)
│ │ └── Z(3)
│ ├── [mul: 2.00000000000000]
│ │ └── KronBlock(1,2)
│ │ ├── Z(1)
│ │ └── Z(2)
│ ├── [mul: 2.00000000000000]
│ │ └── KronBlock(1,3)
│ │ ├── Z(1)
│ │ └── Z(3)
│ └── [mul: 2.00000000000000]
│ └── KronBlock(2,3)
│ ├── Z(2)
│ └── Z(3)
└── AddBlock(0,1,2,3)
├── [mul: -1.00000000000000]
│ └── X(0)
├── [mul: -1.00000000000000]
│ └── X(1)
├── [mul: -1.00000000000000]
│ └── X(2)
└── [mul: -1.00000000000000]
└── X(3)
Random interaction coefficients
Random interaction coefficients can be chosen between -1 and 1 by simply passing random_strength = True
instead of detuning_strength
and interaction_strength
.
Arbitrary Hamiltonian topologies
Arbitrary interaction topologies can be created using the Qadence Register
.
Simply pass the register with the desired topology as the first argument to the hamiltonian_factory
:
from qadence import Register
reg = Register.square(qubits_side=2)
square_hamilt = hamiltonian_factory(reg, interaction=Interaction.NN)
Custom Hamiltonian coefficients can also be added to the register beforehand using the "strength"
key.
reg = Register.square(qubits_side = 2)
for i, edge in enumerate(reg.edges):
reg.edges[edge]["strength"] = (0.5 * i) ** 2
square_hamilt = hamiltonian_factory(reg, interaction=Interaction.NN)
Alternatively, if the register already stores interaction or detuning strengths, it is possible to override them in the Hamiltonian creation by using force_update = True
.
Adding variational parameters
Finally, fully parameterized Hamiltonians can be created by passing a string to the strength arguments: