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

qasm

An OpenQASM string.

qir

A QIR (Quantum Intermediate Representation) object for cross-platform compilation.

qsharp

A compiled qsharp._native.Circuit object for inspection and visualization.

qsharp_op

A Q# callable — a native Q# operation that can be composed with other Q# operations.

qsharp_factory

A QsharpFactoryData — a deferred Q# program that compiles on demand (see Q# factory harness below).

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:

program

A Q# callable — a reference to a Q# operation that constructs the circuit. In practice this is typically a Q# factory function exposed via the qsharp Python package (e.g., QSHARP_UTILS.StatePreparation.MakeStatePreparationCircuit).

parameter

A 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

get_qsharp_circuit()

Returns a qsharp._native.Circuit for inspection. Accepts prune_classical_qubits to remove unused qubits.

get_qir()

Returns the QIR representation. Compiles from Q# factory or converts from QASM if needed.

get_qasm()

Returns the OpenQASM string. Converts from QIR via Qiskit if only QIR is available.

get_qiskit_circuit()

Returns a Qiskit QuantumCircuit. Requires qiskit to be installed.

estimate()

Runs Q#’s resource estimator on the circuit. Accepts optional params for estimation configuration.

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

qasm

str | None

OpenQASM circuit string, if available.

qir

QirInputData | str | None

QIR representation, if available. Lazily compiled from the Q# factory on first access.

qsharp

qsharp._native.Circuit | None

Compiled Q# circuit object, if available.

encoding

str | None

Qubit encoding label (e.g., "jordan-wigner"), if set.

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