Digital
The digital
module in the Qili SDK facilitates the construction and simulation of digital quantum systems. It is composed of the following primary components:
gates
: A collection of quantum gates and tools for gate construction.Circuit
: A class to create and manage digital quantum circuits.ansatz
: Predefined circuits that can serve as Ansätze.
Gates
This submodule provides the necessary components to define and manipulate quantum gates for use in digital quantum circuits.
Simple Gates
Use these constructors to apply standard single- and two-qubit operations:
X(qubit: int)
Pauli X (bit-flip) on the specified qubit.Y(qubit: int)
Pauli Y (bit-and-phase-flip).Z(qubit: int)
Pauli Z (phase-flip).H(qubit: int)
Hadamard: creates superposition.I(qubit: int)
Identity gate: leaves the qubit unchanged.S(qubit: int)
Phase gate (π/2 rotation about Z).T(qubit: int)
T gate (π/4 rotation about Z).RX(qubit: int, theta: float)
Rotation by angle theta around X.RY(qubit: int, theta: float)
Rotation by angle theta around Y.RZ(qubit: int, phi: float)
Rotation by angle phi around Z.U1(qubit: int, *, phi: float)
Phase shift equivalent to RZ plus global phase.U2(qubit: int, *, phi: float, gamma: float)
π/2 Y-rotation sandwiched by Z-rotations.U3(qubit: int, *, theta: float, phi: float, gamma: float)
General single-qubit unitary: RZ-RY-RZ decomposition.SWAP(a: int, b: int)
Exchanges the states of qubitsa
andb
.CNOT(control: int, target: int)
Controlled-X: flips target if control is |1⟩.CZ(control: int, target: int)
Controlled-Z: applies Z on target if control is |1⟩.M(*qubits: int)
Measures the listed qubits in the computational basis.
Controlled Gates
Any basic gate can be turned into a controlled gate using the Controlled
class:
from qilisdk.digital.gates import Controlled, Y
controlled_y = Controlled(0, basic_gate=Y(1))
Adjoint Gates
You can create the Hermitian conjugate (dagger) of a gate using the Adjoint
class:
from qilisdk.digital.gates import Adjoint, Y
adjoint_y = Adjoint(basic_gate=Y(1))
Exponential Gates
To apply a gate as an exponential operator, use the Exponential
class:
from qilisdk.digital.gates import Exponential, Y
exp_y = Exponential(basic_gate=Y(1))
Circuits
Quantum circuits can be built using the Circuit
class. You can sequentially add gates to define the circuit:
Initialization
from qilisdk.digital import Circuit
# Create a 3-qubit circuit
circuit = Circuit(nqubits=3)
Adding Gates
from qilisdk.digital import H, CNOT
circuit.add(H(0)) # Hadamard on qubit 0
circuit.add(CNOT(0, 1)) # CNOT: control 0 → target 2
circuit.draw()
Parameterized Circuits
Circuits can include parameterized gates. Adding them is similar to regular gates:
from qilisdk.digital import RX
import numpy as np
circuit.add(RX(0, theta=np.pi))
You can retrieve the current parameter:
print("Initial Parameters:", circuit.get_parameters())
Output:
Initial Parameters: {'RX(0)_theta_0': 3.141592653589793}
You can also retrieve the current parameter values only:
print("Initial parameter values:", circuit.get_parameter_values())
Output:
Initial parameter values: [3.141592653589793]
To update parameter by key:
circuit.set_parameters({"RX(0)_theta_0": 2 * np.pi})
To update parameter by value:
circuit.set_parameter_values([2 * np.pi])
Warning
The order of parameters in the list passed to set_parameter_values
must match the order in which the gates were added to the circuit.
Visualization
Use draw()
to render a circuit with Matplotlib. By default, the renderer applies the library’s built-in styling (including the bundled default font if available). You can bypass all defaults by passing a custom CircuitStyle
, which confines styling to the specific call without modifying global Matplotlib settings.
from qilisdk.digital import Circuit, H, CNOT
circuit = Circuit(num_qubits=3)
circuit.add(H(0))
circuit.add(CNOT(0, 2))
# Render in a window
circuit.draw()
Saving to a file
# Save as SVG (use .png, .pdf, etc. as needed)
circuit.draw(filepath="my_circuit.svg")
Custom styling with CircuitStyle
Create a style object to control theme, fonts, spacing, DPI, labels, and more. Passing this object to draw
overrides the library defaults for this call.
You can also change if the order of the draw follows the order they are added in or if it compacts the layers as much as possible changing the patameter layout to “normal” (default) or “compact” respectively.
from qilisdk.digital import Circuit, H, CNOT
from qilisdk.utils.visualization import CircuitStyle, light, dark
circuit = Circuit(3)
circuit.add(H(0))
circuit.add(CNOT(0, 2))
# Example 1: dark theme, larger text, higher DPI
style_dark = CircuitStyle(
theme=dark,
dpi=200,
fontsize=12,
title="Teleportation (fragment)",
)
circuit.draw(style=style_dark)
# Example 2: use a system font family and bypass the bundled font
style_font = CircuitStyle(
theme=light,
fontfname=None, # do not force a specific TTF file
fontfamily=["DejaVu Sans", "Arial"], # fallback list
fontsize=11,
)
circuit.draw(style=style_font, filepath="circuit_custom_font.png")
# Example 3: adjust layout spacing
compact = CircuitStyle(
theme=dark,
wire_sep=0.45, # vertical distance between wires (inches)
layer_sep=0.45, # horizontal distance between layers (inches)
gate_margin=0.10, # side margin inside each layer cell (inches)
label_pad=0.08, # left padding for wire labels (inches)
layout="compact", # compresses the circuit whenever possible
title="Compact layout",
)
circuit.draw(style=compact)
Note
CircuitStyle
fields map directly to the renderer’s layout and font configuration. In particular, you can switch fonts in two ways:
(1) provide a specific font file via fontfname="/path/to/MyFont.ttf"
; or
(2) set fontfname=None
and choose a family list with fontfamily=[...]
to use system-resolved fonts. Both approaches only affect the current draw call.
Ansatz
The ansatz
submodule provides ready-to-use circuit templates (Ansätze). For example:
HardwareEfficientAnsatz
HardwareEfficientAnsatz
is a hardware-efficient ansatz tailored to quantum device topologies. Configuration options:
layers: Number of repeating layers of gates.
connectivity: -
Circular
: Qubits form a ring. -Linear
: Qubits are connected linearly. -Full
: All-to-all connectivity.one_qubit_gate: Choose the parameterized single-qubit gate (e.g.,
U1
,U2
,U3
).two_qubit_gate: Choose the two-qubit interaction type (e.g.,
CNOT
,CZ
).structure: -
grouped
: Applies all single-qubit gates first, followed by all two-qubit gates. -interposed
: Interleaves single and two-qubit gates.
Example
from qilisdk.digital.ansatz import HardwareEfficientAnsatz
from qilisdk.digital import U3, CNOT
ansatz = HardwareEfficientAnsatz(
nqubits=4,
layers=3,
connectivity="Circular",
one_qubit_gate=U3,
two_qubit_gate=CNOT,
structure="Interposed"
)
Parameter Utilities
Circuits collect the symbolic parameters contributed by each gate. Beyond the quick examples above, you can query names, current values, and bounds, or update them selectively:
import numpy as np
from qilisdk.common.variables import Parameter
from qilisdk.digital import Circuit, RX, RZ
circuit = Circuit(nqubits=2)
theta = Parameter("theta", value=np.pi / 4, bounds=(0.0, np.pi))
circuit.add(RX(0, theta=theta))
circuit.add(RZ(1, phi=np.pi / 2))
print(circuit.get_parameter_names()) # ['RX(0)_theta_0', 'RZ(1)_phi_1']
print(circuit.get_parameters()) # {'RX(0)_theta_0': 0.785..., 'RZ(1)_phi_1': 1.570...}
print(circuit.get_parameter_bounds()) # {'RX(0)_theta_0': (0.0, 3.1415...), 'RZ(1)_phi_1': (None, None)}
circuit.set_parameter_bounds({"RX(0)_theta_0": (0.1, np.pi / 2)})
circuit.set_parameters({"RX(0)_theta_0": np.pi / 3})
circuit.set_parameter_values([np.pi / 3, np.pi / 2])
These helpers make it straightforward to plug the circuit into classical optimization loops while keeping parameter metadata synchronized.
Gate Modifiers and Measurement
Every base gate inherits convenience methods to produce derived operations
without manually instantiating Controlled
,
Adjoint
, or
Exponential
. The measurement gate
M
lets you add classical readout at the end of
the circuit.
import numpy as np
from qilisdk.digital import Circuit, H, X, RX, M
circuit = Circuit(2)
circuit.add(H(0))
# Promote a basic gate to a controlled version on the fly
circuit.add(X(1).controlled(0))
# Generate adjoint / exponential variants, preserving parameters
circuit.add(RX(1, theta=np.pi / 4).adjoint())
circuit.add(RX(0, theta=np.pi / 3).exponential())
# Record measurement results for both qubits
circuit.add(M(0, 1))
Controlled gates validate that control and target qubits are disjoint, and all wrapper gates forward parameter accessors to the underlying operation.