Qdislib — 07 · Caching & performance¶
Cutting produces many functionally equivalent subcircuits (repeated fragments, or the same ansatz across iterations). The semantic cache keys each subcircuit by a ZX-reduced Weisfeiler–Lehman hash and simulates an equivalent fragment only once. Install with pip install Qdislib[cache].
[ ]:
from qibo import models, gates
def example_circuit():
"""A small 5-qubit circuit: cuttable (CZ chain), non-trivial <ZZZZZ> ~= 0.44."""
circuit = models.Circuit(5)
circuit.add(gates.RY(0, 0.8))
circuit.add(gates.CZ(0, 1))
circuit.add(gates.RY(1, 0.5))
circuit.add(gates.CZ(1, 2))
circuit.add(gates.RY(2, 0.6))
circuit.add(gates.CZ(2, 3))
circuit.add(gates.RY(3, 0.4))
circuit.add(gates.CZ(3, 4))
circuit.add(gates.RX(4, 0.3))
return circuit
circuit = example_circuit()
print(circuit.draw())
[ ]:
import Qdislib as qd
Enable a cache¶
You choose the store: "memory" (here), "lmdb" (persistent on disk), or "redis" (which becomes the BSC COMPSs/PSCO store under PyCOMPSs). Pass it to any cutting call.
[ ]:
cache = qd.default_circuit_cache("memory")
circuit = example_circuit()
cut = qd.find_cut(circuit, gate_cut=True, wire_cut=False)
value = qd.gate_cutting(example_circuit(), cut, cache=cache, shots=8192)
print("value:", value)
print("cache:", cache.stats())
stats() reports hits, misses, hit-rate and size. Caching never changes the result — a circuit that can’t be keyed just falls back to a normal simulation.
Why it’s semantic¶
The key isn’t a hash of the gate list — semantic_key hashes the circuit’s ZX-reduced form, so two circuits that realise the same unitary collide even when written differently. H·H·H reduces to a single H, so the two share a key and the second is served from cache.
[ ]:
import qibo
from qibo import Circuit, gates
qibo.set_backend('numpy')
one = Circuit(2)
one.add(gates.H(0)); one.add(gates.CZ(0, 1)); one.add(gates.M(0, 1))
three = Circuit(2)
for _ in range(3):
three.add(gates.H(0)) # H·H·H == H
three.add(gates.CZ(0, 1)); three.add(gates.M(0, 1))
print('same semantic key:', qd.semantic_key(one) == qd.semantic_key(three))
Cache granularity¶
cache_mode="expectation" (default) caches the per-subcircuit value keyed by hash + observable. cache_mode="counts" caches raw measurement counts keyed by the hash alone, so one simulation serves several observables.
[ ]:
cache = qd.default_circuit_cache("memory")
for obs in ["ZZZZZ", "ZIZIZ"]:
qd.gate_cutting(example_circuit(), cut, observables=obs,
cache=cache, cache_mode="counts", shots=8192)
print("counts-mode cache:", cache.stats())
Diagnostics¶
There are no verbose= flags; turn on Qdislib’s diagnostic logging with one switch (or the QDISLIB_LOG_LEVEL env var).
[ ]:
qd.set_log_level("INFO") # "DEBUG" for candidate cut scores, backend, ...
qd.find_cut(example_circuit(), gate_cut=True, wire_cut=False)
qd.set_log_level("WARNING") # back to quiet
Under PyCOMPSs the cache is shared across tasks (Redis/PSCO store) and a placeholder/refresh scheme keeps hit rates up under high concurrency.
Next: 08 · Backends under the hood — how a backend is chosen, and how to add your own.