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

Parameter for gradient calculation.

Process

Quantum program process.

Quant

List of qubits.

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

Parameter for gradient calculation.

This class represents a parameter for gradient calculation in a quantum process. It should not be instanced directly, but rather obtained from the param method.

property value: float

Retrieve the parameter actual value.

property param: float

Retrieve the original value of the parameter.

property grad: float | None

Retrieve the gradient value if available.

class Process(execution_target: BatchExecution | LiveExecution | None = None, num_qubits: int | None = None, simulator: Literal['sparse', 'dense', 'dense v2', '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 v2": Dense simulator with a smaller memory footprint. Not necessarily more efficient than "dense".

  • "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. 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 axillary qubits

get_instructions() list[dict]

Retrieve quantum instructions from the process.

The format of the instructions is defined in the runtime library Libket.

Example

>>> from ket import *
>>> p = Process()
>>> a, b = p.alloc(2)
>>> CNOT(H(a), b)
>>> # Get quantum instructions
>>> instructions = p.get_instructions()
>>> pprint(instructions)
[{'Gate': {'control': [],
           'gate': 'Hadamard',
           'target': {'Main': {'index': 0}}}},
 {'Gate': {'control': [{'Main': {'index': 0}}],
           'gate': 'PauliX',
           'target': {'Main': {'index': 1}}}}]
Returns:

A list of dictionaries containing quantum instructions extracted from the process.

get_mapped_instructions() list[dict] | None

Retrieve quantum instructions after the circuit mapping.

The format of the instructions is defined in the runtime library Libket.

The instructions are extracted after the circuit mapping step, which is performed during the compilation process. A qubit coupling graph must be passed to the process for the quantum circuit mapping to happen. Note that at this point, the single qubit gates have not been translated to the native gate set of the quantum hardware yet.

Example

>>> n = 4
>>> coupling_graph = [(0, 1), (1, 2), (2, 3), (3, 0)]
>>> p = Process(num_qubits=n, coupling_graph=coupling_graph)
>>> q = p.alloc(n)
>>> ctrl(H(q[0]), X)(q[1:])
>>> m = measure(q)
>>> p.prepare_for_execution()
>>> pprint(p.get_mapped_instructions())
[{'Gate': {'control': [], 'gate': 'Hadamard', 'target': {'index': 0}}},
 {'Gate': {'control': [{'index': 0}],
           'gate': 'PauliX',
           'target': {'index': 1}}},
 'Identity',
 {'Gate': {'control': [{'index': 3}],
           'gate': 'PauliX',
           'target': {'index': 0}}},
 {'Gate': {'control': [{'index': 0}],
           'gate': 'PauliX',
           'target': {'index': 3}}},
 {'Gate': {'control': [{'index': 3}],
           'gate': 'PauliX',
           'target': {'index': 2}}},
 {'Measure': {'index': 0,
              'qubits': [{'index': 3},
                         {'index': 1},
                         {'index': 0},
                         {'index': 2}]}}]
Returns:

A list of dictionaries containing quantum instructions extracted from the process if the process has been transpiled, otherwise None.

get_metadata() dict[str, Any]

Retrieve metadata from the quantum process.

Example

>>> n = 4
>>> coupling_graph = [(0, 1), (1, 2), (2, 3), (3, 0)]
>>> p = Process(num_qubits=n, coupling_graph=coupling_graph)
>>> q = p.alloc(n)
>>> ctrl(H(q[0]), X)(q[1:])
>>> m = measure(q)
>>> p.prepare_for_execution()
>>> pprint(p.get_mapped_instructions())
{'allocated_qubits': 4,
 'decomposition': {'NoAuxCX': 3},
 'logical_circuit_depth': 3,
 'logical_gate_count': {'1': 1, '2': 3},
 'physical_circuit_depth': 5,
 'physical_gate_count': {'1': 1, '2': 4},
 'terminated': True}
Returns:

A dictionary containing metadata information extracted from the process.

param(*param) list[Parameter] | Parameter

Register a parameter for gradient calculation.

Parameters:

*param – Variable-length argument list of floats.

Returns:

A list of Parameter objects.

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)

Interprets and initializes the quantum register as a quantum integer.

Parameters:

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

Returns:

A quantum integer data structure wrapping this register.

Return type:

Qint

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

Interprets and initializes the quantum register as a fixed-point quantum real.

The real number is represented internally as an integer scaled by \(2^\texttt{exp}\). A positive exponent provides fractional precision, while a negative exponent allows representing larger numbers at the cost of fine-grained precision.

Parameters:
  • exp – The exponent defining the fixed-point scale (value x 2**exp).

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

Returns:

A quantum real number data structure wrapping this register.

Return type:

Qreal

dump_format()

Format for dump.

Used internally em calling dump.