QiliSim Backend

The QiliSim backend is the default CPU simulator developed by Qilimanjaro and written in C++. It implements every primitive functional natively, supports a noise model on all execution paths, and is included with the core qilisdk installation — no extra dependency or hardware is required.

Installation

QiliSim is bundled with the core qilisdk installation, so no extra package is required.

Quick start

import numpy as np
from qilisdk.digital import Circuit, H, CNOT
from qilisdk.backends import QiliSim
from qilisdk.functionals import DigitalPropagation
from qilisdk.readout import Readout

# Build a simple circuit
circuit = Circuit(5)
circuit.add(H(0))
circuit.add(CNOT(0, 1))

# Create DigitalPropagation functional
functional = DigitalPropagation(circuit)

# Execute with the QiliSim backend
backend = QiliSim()
result = backend.execute(functional, Readout().with_sampling(nshots=500))
print(result.get_samples())

Functional support

QiliSim natively supports all primitive functionals through dedicated C++ routines:

Functional

Support

Notes

DigitalPropagation

Statevector-based simulation, configurable via DigitalMethod.

AnalogEvolution

Multiple integration schemes, configurable via AnalogMethod.

QuantumReservoir

Native C++ implementation; supports both Circuit and Schedule reservoir steps.

VariationalProgram

Reuses the primitive functional handlers above for each optimization step.

Configuration

QiliSim is configured at construction time through three orthogonal sections, all defined in qilisdk.backends.backend_config:

  • AnalogMethod — chooses the analog time-evolution scheme and its hyperparameters.

  • DigitalMethod — chooses the digital simulation strategy and its hyperparameters.

  • ExecutionConfig — global execution controls (threads, random seed, Monte Carlo trajectories, measurement-collapse behaviour).

from qilisdk.backends import (
    AnalogMethod,
    DigitalMethod,
    ExecutionConfig,
    MonteCarloConfig,
    QiliSim,
)

backend = QiliSim(
    analog_simulation_method=AnalogMethod.arnoldi(dim=16, num_substeps=2),
    digital_simulation_method=DigitalMethod.statevector(
        matrix_free=True,
        max_cache_size=2_000,
        combine_single_qubit_gates=True,
    ),
    execution_config=ExecutionConfig(
        num_threads=4,
        seed=42,
        monte_carlo=MonteCarloConfig(trajectories=200),
        measurement_collapse=False,
    ),
)

If any argument is omitted, QiliSim falls back to:

Analog simulation methods

Use the classmethods of AnalogMethod to choose how the schedule is integrated:

Constructor

Underlying scheme

When to use it

AnalogMethod.integrator(matrix_free=True)

RK4

Default. Fixed-step Runge-Kutta 4; matrix-free is faster for sparse Hamiltonians.

AnalogMethod.adaptive_integrator(tol=1e-2)

Dormand-Prince RK4/5

Adaptive step size; tol bounds the fidelity error between the RK4 and RK5 estimates.

AnalogMethod.arnoldi(dim=10, num_substeps=1)

Krylov / Arnoldi

Can offer decent scaling for large sparse Hamiltonians; tune dim for the Krylov subspace size.

AnalogMethod.direct()

Matrix exponential

Reference scheme for small systems; cost grows quickly with qubit count.

Example, using the adaptive integrator for a stiffer schedule:

from qilisdk.backends import AnalogMethod, QiliSim

backend = QiliSim(analog_simulation_method=AnalogMethod.adaptive_integrator(tol=1e-2))

Digital simulation methods

Digital execution is currently always state-vector based; the DigitalMethod configuration tunes its performance characteristics rather than choosing a different algorithm:

Option

Meaning

matrix_free

Apply gates directly to the statevector instead of building dense matrices. Default True.

max_cache_size

Maximum number of precomputed gate matrices cached between executions.

combine_single_qubit_gates

Merge adjacent single-qubit gates into a single operation before propagating. Disabled if gate-specific noise is used.

normalize_after_each_gate

Renormalize the statevector after each gate to mitigate numerical drift, at a runtime cost.

from qilisdk.backends import DigitalMethod, QiliSim

backend = QiliSim(
    digital_simulation_method=DigitalMethod.statevector(
        matrix_free=False,
        normalize_after_each_gate=True,
    ),
)

Execution and Monte Carlo

ExecutionConfig controls threading, randomness, and optional Monte Carlo trajectory sampling for open-system simulations.

  • num_threads=0 (default) lets the simulator use every physical core.

  • seed=None (default) draws a fresh random seed at construction time; pass an integer for reproducibility.

  • monte_carlo=MonteCarloConfig(trajectories=N) enables stochastic trajectory sampling for noise models that admit a Monte Carlo unraveling; leave it None for deterministic master-equation evolution.

  • measurement_collapse controls whether measurements collapse the statevector in place (relevant for mid-circuit measurement and reservoir computing); defaults to False.

from qilisdk.backends import ExecutionConfig, MonteCarloConfig, QiliSim

backend = QiliSim(
    execution_config=ExecutionConfig(
        num_threads=8,
        seed=1234,
        monte_carlo=MonteCarloConfig(trajectories=500),
        measurement_collapse=True,
    ),
)

Noise model support

Any NoiseModel accepted by the SDK can be passed directly to the constructor; QiliSim applies it inside the C++ solver, so digital, analog, and reservoir runs all see the same noise channels:

from qilisdk.backends import QiliSim
from qilisdk.noise import NoiseModel, Depolarizing

nm = NoiseModel()
nm.add(Depolarizing(probability=1e-3))

backend = QiliSim(noise_model=nm)