Circuit
The Circuit class in QDK/Chemistry represents a quantum circuit.
It stores the circuit in one or more formats and converts between them on demand.
Worked example
The following example defines a Q# operation from a string, wraps the compiled circuit in a Circuit, inspects the gate-level diagram, and runs resource estimation:
import json
import qdk
import qsharp
from qdk_chemistry.data import Circuit
from qdk_chemistry.data.circuit import QsharpFactoryData
# 1. Define a Q# operation from string
qsharp.eval(
"""
operation GHZSample(n: Int) : Result[] {
use qs = Qubit[n];
H(qs[0]);
ApplyToEach(CNOT(qs[0], _), qs[1...]);
let results = MeasureEachZ(qs);
ResetAll(qs);
return results;
}
"""
)
# 2. Get a Q# circuit object and wrap it
qsharp_factory = QsharpFactoryData(
program=qdk.code.GHZSample,
parameter={"n": 3},
)
circuit = Circuit(qsharp_factory=qsharp_factory)
# 3. Inspect the circuit
qsharp_circuit = circuit.get_qsharp_circuit()
print(qsharp_circuit)
# Return an ASCII diagram of the circuit
# q_0 ── H ──── ● ──── ● ──── M ──── |0〉 ──
# │ │ ╘════════════
# q_1 ───────── X ─────┼───── M ──── |0〉 ──
# │ ╘════════════
# q_2 ──────────────── X ──── M ──── |0〉 ──
# ╘════════════
# 4. Access the gate-level JSON structure
circuit_json = json.loads(qsharp_circuit.json())
print(f"Qubits: {len(circuit_json['qubits'])}")
# 5. Resource estimation
estimate_result = circuit.estimate()
formatted = estimate_result["physicalCountsFormatted"]
print(f"Physical qubits: {formatted['physicalQubits']}")
print(f"Runtime: {formatted['runtime']}")
Overview
A Circuit wraps a quantum circuit that may be represented in any combination of the following formats:
Format |
Description |
|---|---|
|
An OpenQASM string. |
|
A QIR (Quantum Intermediate Representation) object for cross-platform compilation. |
|
A compiled |
|
A Q# callable — a native Q# operation that can be composed with other Q# operations. |
|
A |
At least one representation must be provided at construction.
When a format that is not stored is requested, the class converts automatically — for example, calling get_qsharp_circuit() on a circuit that only has qasm will compile it to Q# via the qsharp package.
The full constructor signature is:
Circuit(
qasm: str | None = None,
qir: QirInputData | str | None = None,
qsharp: qsharp._native.Circuit | None = None,
qsharp_op: Callable | None = None,
qsharp_factory: QsharpFactoryData | None = None,
encoding: str | None = None,
)
The encoding parameter records the fermion-to-qubit mapping assumed by the circuit (e.g., "jordan-wigner").
Q# factory harness
The Q# factory is the primary mechanism by which QDK/Chemistry builds circuits internally. Rather than constructing a circuit as a gate sequence, the library stores a reference to a Q# callable along with the classical parameters that configure it. The circuit is compiled only when a concrete representation is actually needed.
A QsharpFactoryData is a frozen dataclass with two fields:
programA Q# callable — a reference to a Q# operation that constructs the circuit. In practice this is typically a Q# factory function exposed via the
qsharpPython package (e.g.,QSHARP_UTILS.StatePreparation.MakeStatePreparationCircuit).parameterA
dict[str, Any]of classical parameters that are passed to the Q# callable at compilation time. These are Python-native values — lists, ints, floats, nested dicts, or even other Q# operations — that the Q# program uses to configure the circuit it produces.
The standard pattern for building a factory is:
# Prepare single-reference state with Q# factory parameters
from qdk_chemistry.data import Circuit
from qdk_chemistry.utils.qsharp import QSHARP_UTILS
# 1. Build a parameter object (a dictionary or a Q# structured parameter class)
bitstring = [1, 1, 0, 0]
params = QSHARP_UTILS.StatePreparation.SingleReferenceParams(
bitStrings=bitstring, numQubits=len(bitstring)
)
# 2. Create the factory — vars() converts the dataclass to a dict
factory = QsharpFactoryData(
program=QSHARP_UTILS.StatePreparation.MakeSingleReferenceStateCircuit,
parameter=vars(params),
)
# 3. Wrap in a Circuit — nothing is compiled yet
circuit = Circuit(qsharp_factory=factory, encoding="jordan-wigner")
# 4. Compilation happens on demand:
circuit.get_qsharp_circuit() # → qsharp.circuit(program, **params)
circuit.get_qir() # → qsharp.compile(program, **params)
This pattern is what enables end-to-end Q# composition: because the factory stores Q# callables rather than serialized gate sequences, multi-stage circuits compose natively in the Q# runtime without intermediate format conversions.
Conversion methods
Each method returns the circuit in the requested format, converting from whatever representation is available:
Method |
Description |
|---|---|
Returns a |
|
Returns the QIR representation. Compiles from Q# factory or converts from QASM if needed. |
|
Returns the OpenQASM string. Converts from QIR via Qiskit if only QIR is available. |
|
Returns a Qiskit |
|
Runs Q#’s resource estimator on the circuit. Accepts optional |
Example: Convert state preparation circuits to different formats
import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import Circuit, Structure
# When algorithms produce circuits, they carry native Q# operations internally.
# This enables end-to-end Q# composition without format conversions.
coords = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]])
structure = Structure(coords, symbols=["H", "H"])
scf = create("scf_solver")
_, wfn = scf.run(structure, charge=0, spin_multiplicity=1, basis_or_guess="sto-3g")
ham = create("hamiltonian_constructor").run(wfn.get_orbitals())
_, wfn_cas = create("multi_configuration_calculator").run(ham, 1, 1)
# StatePreparation produces a Circuit with a native Q# factory
state_prep = create("state_prep", "sparse_isometry_gf2x")
circuit = state_prep.run(wfn_cas)
# Inspect the Q# circuit (prune unused qubits for clarity)
pruned_qsharp_circuit = circuit.get_qsharp_circuit(prune_classical_qubits=True)
print(pruned_qsharp_circuit)
# Get the QIR representation
qir = circuit.get_qir()
print(qir)
# Export to OpenQASM or Qiskit when needed (if qiskit is installed)
try:
qasm_str = circuit.get_qasm()
print(qasm_str)
qiskit_circuit = circuit.get_qiskit_circuit()
print(qiskit_circuit)
except (ImportError, RuntimeError):
print("Qiskit not installed — skipping OpenQASM/Qiskit export")
Properties
Property |
Type |
Description |
|---|---|---|
|
str | None |
OpenQASM circuit string, if available. |
|
QirInputData | str | None |
QIR representation, if available. Lazily compiled from the Q# factory on first access. |
|
qsharp._native.Circuit | None |
Compiled Q# circuit object, if available. |
|
str | None |
Qubit encoding label (e.g., |
Serialization
Circuit supports the same serialization formats as other QDK/Chemistry data classes:
# Save to JSON
circuit.to_json_file("example_circuit.circuit.json")
# Load from JSON
loaded = Circuit.from_json_file("example_circuit.circuit.json")
# Save to HDF5
circuit.to_hdf5_file("example_circuit.circuit.h5")
# Load from HDF5
loaded_h5 = Circuit.from_hdf5_file("example_circuit.circuit.h5")
Note
Serialization persists the OpenQASM and QIR representations. Q# callables and factories are not directly serializable — they are reconstructed when the producing algorithm re-creates the circuit.
Further reading
The above examples can be downloaded as a complete Python script.
CircuitExecutor: Execute circuits on backends
Serialization: Data persistence formats