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 for gradient calculation. |
|
Quantum program process. |
|
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
parammethod.
- 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
Processin 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 theallocmethod 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
Quantobject.Each qubit is assigned a unique index, and the resulting
Quantobject encapsulates the allocated qubits along with a reference to the parentProcessobject.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.
- 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.
- 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
allocmethod.A
Quantserves 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 twoQuantobjects. The processes must be the same.Indexing (
[index]): Returns a newQuantobject with selected qubits based on the provided index.Iteration (
for q in qubits): Allows iterating over qubits in aQuantobject.Reversal (
reversed(qubits)): Returns a newQuantobject with reversed qubits.Length (
len(qubits)): Returns the number of qubits in theQuantobject.
- at(index: list[int]) Quant¶
Return a subset of qubits at specified indices.
Create a new
Quantobject with qubit references at the positions defined by the providedindexlist.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])
- 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:
- 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: