ket.base

Base classes for quantum programming.

This module provides the base classes for quantum programming in Ket, including the Process, which is the gateway for qubit allocation and quantum execution, and the Quant, which stores the qubit’s reference.

With the exception of Process, the classes in this module are not intended to be instantiated directly by the user. Instead, they are meant to be created through provided functions.

Classes ket.base

Parameter

A differentiable scalar parameter for variational quantum circuits.

Process

Quantum program process.

Quant

List of qubits.

class Parameter(process, index, value, multiplier=1)

A differentiable scalar parameter for variational quantum circuits.

This class wraps a floating-point value and registers it with a Process so that the runtime can compute analytical gradients (e.g., using the parameter-shift rule). It supports scalar arithmetic (*, /, unary -) so that a single registered parameter can be reused across multiple gates with different multipliers.

Important

Do not instantiate this class directly. Obtain Parameter objects from param.

Example

from math import pi
from ket import Process, RX, RZ
p = Process(gradient=True)
theta = p.param(pi / 3)
q = p.alloc()
RX(theta, q)       # use the parameter directly
RZ(theta / 2, q)   # reuse with a scaled copy
property value: float

The effective (scaled) value of this parameter.

Returns the product of the original registered value and any multiplier applied via arithmetic operators.

Returns:

param * multiplier.

property param: float

The original, unscaled value as registered with the process.

Returns:

The raw value passed when creating this parameter via param.

property grad: float | None

The gradient of the circuit output with respect to this parameter.

Lazily fetched from the process after circuit execution. Returns None if the gradient has not yet been computed (i.e., the process has not executed) or if gradient computation was not enabled on the Process.

Returns:

The gradient value, or None if unavailable.

Example

from math import pi
import ket
p = ket.Process(gradient=True)
theta = p.param(pi / 4)
q = p.alloc()
ket.RX(theta, q)
with ket.gates.obs():
    h = ket.Z(q)
ev = ket.exp_value(h)
ev.get()         # execute and retrieve expected value
print(theta.grad)  # d<Z>/d(theta)
class Process(execution_target: BatchExecution | LiveExecution | None = None, num_qubits: int | None = None, simulator: Literal['sparse', 'dense', 'dense gpu'] | None = None, execution: Literal['live', 'batch'] | None = None, gradient: bool = False, **kwargs)

Quantum program process.

A Process in Ket is responsible for preparing and executing quantum circuits. It serves as a direct interface to the underlying Rust runtime library. The primary way to interact with a process is through the alloc method to allocate qubits.

Example

from ket import Process
p = Process()
qubits = p.alloc(10)  # Allocate 10 qubits

By default, quantum execution is handled by the KBW simulator in sparse mode with support for up to 32 qubits. In sparse mode, qubits are represented using a data structure similar to a sparse matrix. This mode performs well when the quantum state involves the superposition of a small number of basis states, such as GHZ states, and is suitable as a general default when the number of qubits is unknown.

The dense simulation mode, on the other hand, has exponential time complexity in the number of qubits. It leverages CPU parallelism more effectively, but requires careful management of the number of qubits, defaulting to 12. The choice between sparse and dense simulation depends on the specific quantum algorithm being implemented, as each mode has trade-offs in performance and scalability.

The execution mode of the simulator can be set to either "live" or "batch":

  • Live (default): Quantum instructions are executed immediately upon invocation. This mode is ideal for interactive simulation.

  • Batch: Quantum instructions are queued and executed only at the a measurement result is requested. This mode better reflects the behavior of real quantum hardware and is recommended for preparing code for deployment to QPUs.

Batch Execution Example:

from ket import *
p = Process(execution="batch")
a, b = p.alloc(2)
CNOT(H(a), b)  # Prepare a Bell state
d = sample(a + b)
print(d.get())  # Execution happens here
CNOT(a, b)  # Raises an error: process already executed

Live Execution Example:

from ket import *
p = Process(execution="live")
a, b = p.alloc(2)
CNOT(H(a), b)  # Prepare a Bell state
print(sample(a + b).get())  # Output is available immediately
CNOT(a, b)
H(a)
print(sample(a + b).get())
Simulators:

KBW provides four simulators with different performance characteristics. The best simulator to use depends on the number of qubits being simulated and on the specific quantum algorithm. Benchmarking is recommended to determine the most suitable simulator for a given workload.

  • "sparse": Sparse simulator with limited multithreading capabilities.

  • "dense": Dense simulator with good multithreaded performance.

  • "dense gpu": Dense simulator designed to run on most GPUs, including integrated Intel, AMD, and Apple GPUs, as well as NVIDIA GPUs. This simulator is generally recommended for simulations involving a large number of qubits.

Parameters:
  • execution_target – Quantum execution target object. If not provided, the KBW simulator is used.

  • num_qubits – Number of qubits for the KBW simulator. Defaults to 32 for sparse mode, or 12 for dense mode.

  • simulator – Simulation mode for the KBW simulator. Options are "sparse", "dense", and "dense gpu". Defaults to "sparse".

  • execution – Execution mode for the KBW simulator, either "live" or "batch". Defaults to "live".

alloc(num_qubits: int = 1) Quant

Allocate qubits and return a Quant object.

Each qubit is assigned a unique index, and the resulting Quant object encapsulates the allocated qubits along with a reference to the parent Process object.

Example

from ket import Process
p = Process()
qubits = p.alloc(3)
print(qubits)
# <Ket 'Quant' [0, 1, 2] pid=0x...>
Parameters:

num_qubits – The number of qubits to allocate. Defaults to 1.

Returns:

A list like object representing the allocated qubits.

alloc_aux(num_qubits: int = 1) Quant

Allocate auxiliary qubits managed by the process for uncomputation.

Auxiliary (ancilla) qubits are temporary qubits used in intermediate computation steps, such as in multi-controlled gates or temporary registers. The process tracks them separately and prevents accidental measurement. When the returned Quant object goes out of scope or is used as a context manager, the auxiliary qubits are automatically freed and returned to the internal qubit pool for reuse.

The recommended usage is as a context manager with the with statement:

Example

from ket import Process, ctrl, X
p = Process()
c, t = p.alloc(), p.alloc()
with p.alloc_aux() as aux:         # aux is freed on exit
    with around(ctrl(c, X), aux):  # use aux as intermediate
        ctrl(aux, X)(t)            # fanout to target
Parameters:

num_qubits – The number of auxiliary qubits to allocate. Defaults to 1.

Returns:

A Quant object containing the auxiliary qubits. It supports use as a context manager: on exit the qubits are returned to the qubit pool.

Raises:

ValueError – If num_qubits is less than 1.

param(*param: float) list[Parameter] | Parameter

Register one or more differentiable parameters for gradient computation.

Each numeric value is wrapped in a Parameter object that can be scaled (via multiplication or division) and passed directly to parameterized gates such as RX, RY, or P. After circuit execution, the gradient of the expected value with respect to each parameter can be retrieved via grad.

Note

Gradient computation requires creating the process with gradient=True.

Example

from math import pi
from ket import Process, RX, exp_value
from ket.gates import obs
import ket
p = Process(gradient=True)
theta = p.param(pi / 4)
q = p.alloc()
RX(theta, q)
with obs():
    h = ket.Z(q)
ev = ket.exp_value(h)
ev.get()        # trigger execution
theta.grad      # d<Z>/d(theta)
Parameters:

*param – One or more initial float values for the parameters.

Returns:

A single Parameter if one value is provided, otherwise a list of Parameter objects.

gates()

Return the gate sequence of the process as a parsed JSON object.

Serializes the internal circuit representation into a JSON-compatible Python object (typically a list of gate dictionaries). This is primarily used for inspection, debugging, or exporting the circuit.

Returns:

A JSON-parsed object representing the gate sequence of the current process.

Example

from ket import Process, H, X
p = Process()
q = p.alloc(2)
H(q[0])
X(q[1])
circuit = p.gates()
print(type(circuit))
# <class 'list'>
append_block(block, check_qubits=True)

Append a pre-built gate block to the process circuit.

Caution

This is an internal method and is not intended for direct use by library consumers. Use quantum gate functions instead.

If there is a currently active nested block (e.g., inside a block_builder context), the block is appended to the innermost active block. Otherwise, it is appended directly to the process circuit.

When check_qubits is True (the default), the method validates that no uncomputation rules are violated, for example, it prevents applying non-diagonal operations to auxiliary qubits that are currently blocked.

Parameters:
  • block – The compiled gate block to append.

  • check_qubits – If True, validate qubit operation rules before appending. Defaults to True.

Raises:

RuntimeError – If check_qubits is True and the block contains an operation that violates uncomputation constraints.

block_builder(inverse=False, control: list[int] | None = None, append: bool = True, diagonal=False, permutation=False)

Context manager for constructing and optionally appending a gate block.

Caution

This is an internal method. Prefer using the high-level gate API and context managers (control, inverse, around).

All quantum operations performed inside the with block are collected into a single circuit Block object. On exit, the block can optionally be inverted, wrapped in a controlled operation, and/or appended to the process circuit.

This is the primary building primitive used internally by gate functions and higher-level operations like around and inverse.

Parameters:
  • inverse – If True, invert the block before appending. Defaults to False.

  • control – If provided, wrap the block in a multi-qubit controlled operation on the specified qubit indices. Defaults to None.

  • append – If True, append the block to the process circuit on exit. Set to False to obtain the block without appending it. Defaults to True.

  • diagonal – If True, mark the block as a diagonal operation. Defaults to False.

  • permutation – If True, mark the block as a permutation operation. Defaults to False.

Yields:

Block – The in-progress gate block being constructed.

class Quant(*, qubits: list[int], process: Process, undo=None, source=None)

List of qubits.

This class represents a list of qubit indices within a quantum process. Direct instantiation of this class is not recommended. Instead, it should be created by calling the alloc method.

A Quant serves as a fundamental quantum object where quantum operations should be applied.

Example

from ket import *
# Create a quantum process
p = Process()
# Allocate 2 qubits
q1 = p.alloc(2)
# Apply a Hadamard gates on the first qubit of `q1`
H(q1[0])
# Allocate more 2 qubits
q2 = p.alloc(2)
# Concatenate two Quant objects
result_quant = q1 + q2
print(result_quant)
# <Ket 'Quant' [0, 1, 2, 3] pid=0x...>
# Use the fist qubit to control the application of
# a Pauli X gate on the other qubits
ctrl(result_quant[0], X)(result_quant[1:])
# Select qubits at specific indexes
selected_quant = result_quant.at([0, 1])
print(selected_quant)
# <Ket 'Quant' [0, 1] pid=0x...>

Supported operations:

  • Addition (+): Concatenates two Quant objects. The processes must be the same.

  • Indexing ([index]): Returns a new Quant object with selected qubits based on the provided index.

  • Iteration (for q in qubits): Allows iterating over qubits in a Quant object.

  • Reversal (reversed(qubits)): Returns a new Quant object with reversed qubits.

  • Length (len(qubits)): Returns the number of qubits in the Quant object.

at(index: list[int]) Quant

Return a subset of qubits at specified indices.

Create a new Quant object with qubit references at the positions defined by the provided index list.

Example

from ket import *
# Create a quantum process
p = Process()
# Allocate 5 qubits
q = p.alloc(5)
# Select qubits at odd indices (1, 3)
odd_qubits = q.at([1, 3])
Parameters:

index – List of indices specifying the positions of qubits to be included in the new Quant.

Returns:

A new Quant object containing the selected qubits.

as_int(number: int = 0)

Interpret and initialize this quantum register as a quantum integer.

Wraps the register as a Qint, enabling quantum arithmetic operations (addition, subtraction, comparison, etc.) on the underlying qubits. The register is initialized to the given classical integer value using X gates.

The Qint uses a two’s-complement signed representation internally.

Example

from ket import Process, measure
p = Process()
q = p.alloc(5)
qi = q.as_int(5)    # register initialized to |5⟩
qi += 3             # in-place addition: |5⟩ → |8⟩
print(measure(qi).value)
# 8
Parameters:

number – The initial classical integer value to encode into the quantum register. Defaults to 0.

Returns:

A quantum integer wrapping this register, initialized to number.

as_real(exp: int, number: float = 0.0)

Interpret and initialize this quantum register as a fixed-point quantum real number.

Wraps the register as a Qreal, enabling quantum arithmetic operations on floating-point values encoded in a fixed-point binary representation.

The real number is stored internally as an integer scaled by \(2^{\texttt{exp}}\):

  • A positive exp increases fractional precision (smaller representable step size of \(2^{-\texttt{exp}}\)).

  • A negative exp increases the representable magnitude at the cost of precision.

Example

from ket import Process, measure
p = Process()
q = p.alloc(8)         # 8 qubits for fixed-point
qr = q.as_real(4, 1.5) # precision: 1/16, initialized to 1.5
qr += 0.25             # in-place addition
print(measure(qr).value)
# 1.75
Parameters:
  • exp – The exponent defining the fixed-point scale. The stored integer n represents the real value n / 2**exp.

  • number – The initial classical float value to encode into the quantum register. Defaults to 0.0.

Returns:

A quantum real number wrapping this register, initialized to number.

dump_format()

Return the state-formatting callable used by dump.

Provides a function that converts a raw integer basis-state index into a zero-padded binary string of the correct width for this register. This is used internally by QuantumState to display multi-register states with per-register labels.

Returns:

A function that accepts an integer basis-state value and returns its binary string representation (zero-padded to len(self) bits).