Analog
The qilisdk.analog
layer lets you build symbolic Hamiltonians and time-dependent schedules that can be simulated or submitted to Qili backends. Its core types are Hamiltonian
, Schedule
, and LinearSchedule
.
Hamiltonian
: Tools to build arbitrary Hamiltonians using symbolic Pauli operators.Schedule
: A system for defining time-dependent evolution of Hamiltonians.
Quick Start
from qilisdk.analog import Hamiltonian, LinearSchedule, X, Z
driver = sum(X(i) for i in range(2))
problem = Z(0) * Z(1)
schedule = LinearSchedule(
T=10,
dt=1,
hamiltonians={"driver": driver, "problem": problem},
schedule={
0: {"driver": 1.0, "problem": 0.0},
10: {"driver": 0.0, "problem": 1.0},
},
)
Hamiltonian
The Hamiltonian
class represents a symbolic Hamiltonian as a sum of weighted Pauli operators. You can create Hamiltonians using the built-in Pauli operators and combine them with standard arithmetic operations.
Common constructors
X(i)
,Y(i)
,Z(i)
,I(i)
produces a Hamiltonian with a single-qubit Pauli.Construction using arithmetic operations. The operations follow Python syntax, for example:
2 * Z(0) + Z(1)
andZ(0) * Z(1)
build multi-qubit Hamiltonian.
Pauli operators:
X(i)
- Pauli X acting on qubit iY(i)
- Pauli Y acting on qubit iZ(i)
- Pauli Z acting on qubit iI(i)
- Identity acting on qubit i
Arithmetic operations:
Addition:
H1 + H2
Scalar multiplication:
5 * H
multiplication:
H0 * H1
Subtraction:
H1 - H2
Division by scalar:
H / 5
Negation:
-H
** Extra Symbolic Operators**:
commutator:
H1.commutator(H2)
anticommutator:
H1.anticommutator(H2)
vector_norm:
H.vector_norm()
forbenius_norm:
H.frobenius_norm()
trace:
H.trace()
** Exporting Hamiltonians **:
to matrix:
H.to_matrix(nqubits)
to qtensor:
H.to_qtensor(nqubits)
** Importing Hamiltonians **:
from qtensor:
Hamiltonian.from_qtensor(qtensor)
from string:
Hamiltonian.parse(hamiltonian_string)
Example: Ising Hamiltonian
To define an Ising Hamiltonian of the form:
you can use the Pauli Z
operators from the library:
from qilisdk.analog import Z
nqubits = 3
J = {(0, 1): 1, (0, 2): 2, (1, 2): 4}
h = {0: 1, 1: 2, 2: 3}
coupling = sum(weight * Z(i) * Z(j) for (i, j), weight in J.items())
fields = sum(weight * Z(i) for i, weight in h.items())
H = -(coupling + fields)
print(H)
Output:
- Z(0) Z(1) - 2 Z(0) Z(2) - 4 Z(1) Z(2) - Z(0) - 2 Z(1) - 3 Z(2)
Schedule
The Schedule
class maps time steps to Hamiltonian
coefficients.
T (float): total duration of the schedule in units of nano seconds.
dt (int): resolution of time steps in units of nano seconds. Default is 1.
hamiltonians (dict[str, Hamiltonian]): Map of labels to
Hamiltonian
instances.schedule_map (dict[int, dict[str, float]]): time-step index to Hamiltonian coefficient mapping. Each key is a time step index (0 to T/dt), and each value is a dictionary mapping Hamiltonian labels to their coefficients at that time step index.
Example 1: Dictionary-Based Schedule
import numpy as np
from qilisdk.analog import Schedule, X, Z
T, dt = 10, 1
steps = np.linspace(0, T, int(T / dt))
h1 = X(0) + X(1) + X(2)
h2 = -Z(0) - Z(1) - 2 * Z(2) + 3 * Z(0) * Z(1)
schedule = Schedule(
T=T,
dt=dt,
hamiltonians={"driver": h1, "problem": h2},
schedule={
i: {"driver": 1 - t / T, "problem": t / T}
for i, t in enumerate(steps)
},
)
schedule.draw()
Example 2: Functional Schedule with add_hamiltonian()
Alternatively, You can add Hamiltonians one at a time, supplying a callable for the coefficient:
T, dt = 10, 1
steps = np.linspace(0, T, int(T / dt))
h1 = X(0) + X(1) + X(2)
h2 = -Z(0) - Z(1) - 2 * Z(2) + 3 * Z(0) * Z(1)
schedule = Schedule(T, dt)
# Add h1 with a time‐dependent coefficient function
schedule.add_hamiltonian(
label="driver",
hamiltonian=h1,
schedule=lambda t: 1 - steps[t] / T
)
# Add h2 similarly
schedule.add_hamiltonian(
label="problem",
hamiltonian=h2,
schedule=lambda t: steps[t] / T
)
schedule.draw()
This provides more flexibility and modularity for dynamic or conditional evolution.
Note: if you add a Hamiltonian with the same label multiple times, the last one will overwrite the previous ones.
Modifying a Schedule
Once constructed, you can refine or extend the schedule:
Add a new time step:
schedule.add_schedule_step(time_step=11, hamiltonian_coefficient_list={"h1": 0.3})
Update an existing coefficient:
schedule.update_hamiltonian_coefficient_at_time_step(
time_step=1,
hamiltonian_label="h1",
new_coefficient=0.2
)
This lets you insert or override coefficients without rebuilding the full map.
Parameterized Schedules
Schedule coefficients can be symbolic, enabling classical optimization loops or
experiments that scan over a family of time profiles. Coefficients can be
instances of Parameter
or algebraic
expressions (Term
) built from parameters.
The schedule tracks every parameter it encounters so you can query or set them
later.
from qilisdk.analog import Schedule, Z
from qilisdk.common.variables import Parameter
T, dt = 10, 1
gamma = Parameter("gamma", value=0.5, bounds=(0.0, 1.0))
schedule = Schedule(
T=T,
dt=dt,
hamiltonians={"problem": Z(0)},
schedule={0: {"problem": gamma}},
)
schedule.set_parameter_bounds({"gamma": (0.2, 0.8)})
schedule.set_parameter_values([0.7])
print(schedule.get_parameter_values()) # [0.7]
When coefficients arise from expressions (for example 0.5 * gamma
or
gamma * (1 - t / T)
inside add_hamiltonian()
),
the resulting Term
is also recorded. The
helpers below give programmatic access to these symbolic coefficients:
- get_parameter_names()
and get_parameters()
surface labels and values.
- set_parameters({"gamma": 0.6})
updates selected entries in-place.
- get_parameter_bounds()
returns the per-parameter bounds dictionary.
LinearSchedule
The LinearSchedule
subclass provides
linear interpolation between explicitly defined time steps. You supply the
same inputs as Schedule
, and the class fills
in intermediate coefficients on demand.
from qilisdk.analog import LinearSchedule, X, Z
gamma = Parameter("gamma", value=0.1, bounds=(0.0, 1.0))
linear = LinearSchedule(
T=10,
dt=1,
hamiltonians={
"driver": X(0) + X(1),
"problem": Z(0) * Z(1),
},
schedule={
0: {"driver": 1.0, "problem": 0.0},
9: {"driver": gamma, "problem": 1.0},
},
)
mid = linear[5]
print("Midpoint schedule:", mid)
coeff = linear.get_coefficient(time_step=5, hamiltonian_key="driver")
print("Driver coefficient at midpoint (evaluated parameters):", coeff)
coeff = linear.get_coefficient_expression(time_step=5, hamiltonian_key="driver")
print("Driver coefficient at midpoint (unevaluated parameters):", coeff)
Output:
Midpoint schedule: 0.5 X(0) + 0.5 X(1) + 0.5555555555555556 Z(0) Z(1)
Driver coefficient at midpoint (evaluated parameters): 0.5
Driver coefficient at midpoint (unevaluated parameters): (0.4444444444444444) + (0.5555555555555556) * gamma
LinearSchedule
preserves symbolic parameters when computing interpolation
results. Use get_coefficient_expression()
to obtain the unevaluated expression (with parameters intact) or
get_coefficient()
to
retrieve the numeric value under the current parameter assignment. This makes
it convenient to visualize or export schedules with parametric sweeps while
reusing the same workflows as Schedule
.