Agent Skillsaiming-lab/AutoResearchClaw › quantum-qiskit

quantum-qiskit

GitHub

提供 Qiskit 2.x 变分量子机器学习开发参考,涵盖 VQC、VQE、特征映射及噪声模型集成,指导正确导入与迁移避坑。

researchclaw/skills/builtin/domain/quantum-qiskit/SKILL.md aiming-lab/AutoResearchClaw

Trigger Scenarios

编写基于 Qiskit 的量子机器学习代码 处理 Qiskit 1.x 到 2.x 的迁移问题

Install

npx skills add aiming-lab/AutoResearchClaw --skill quantum-qiskit -g -y
More Options

Non-standard path

npx skills add https://github.com/aiming-lab/AutoResearchClaw/tree/main/researchclaw/skills/builtin/domain/quantum-qiskit -g -y

Use without installing

npx skills use aiming-lab/AutoResearchClaw@quantum-qiskit

指定 Agent (Claude Code)

npx skills add aiming-lab/AutoResearchClaw --skill quantum-qiskit -a claude-code -g -y

安装 repo 全部 skill

npx skills add aiming-lab/AutoResearchClaw --all -g -y

预览 repo 内 skill

npx skills add aiming-lab/AutoResearchClaw --list

SKILL.md

Frontmatter
{
    "name": "quantum-qiskit",
    "metadata": {
        "author": "researchclaw",
        "version": "2.0",
        "category": "domain",
        "priority": "1",
        "trigger-keywords": "qiskit,quantum,vqc,vqe,encoding,feature_map,featuremap,statevector,ansatz,aer,qubit,parameterized circuit,quantum machine learning,quantum classifier,quantum circuit,amplitude_encoding,angle_encoding,zz_feature_map,statepreparation,zfeaturemap,zzfeaturemap,mps,matrix product state,tensor network,bond dimension,layerwise,re-uploading,reuploading,barren plateau,qaoa,maxcut,autoencoder,swap test,quantum kernel,quantum autoencoder",
        "applicable-stages": "10,13"
    },
    "description": "Reference qiskit 2.x patterns for variational quantum machine learning. Covers data-encoding feature maps, variational quantum classifier (VQC) training, variational quantum eigensolver (VQE) for chemistry, matrix-product-state circuits, and noise model integration. Use when writing Python code that imports `qiskit`, `qiskit_aer`, `qiskit_algorithms`, `qiskit_machine_learning`, or `qiskit_nature`."
}

Qiskit 2.x reference for variational quantum machine learning

This skill is a canonical reference for writing Python code that uses qiskit 2.x and its ecosystem (qiskit_aer, qiskit_algorithms, qiskit_machine_learning, qiskit_nature). It documents the API shapes that work in qiskit 2.x today, the qiskit-1.x → 2.x migration breaks that affect VQE and chemistry code, and a small number of common mistakes with concrete fixes.

Section overview:

  1. Imports
  2. Data-encoding feature maps
  3. Variational ansatz construction
  4. VQC training (qiskit_machine_learning)
  5. VQE for chemistry (qiskit 2.x compatible)
  6. MPS-structured circuits
  7. Noise model integration
  8. qiskit 2.x compatibility notes
  9. Common errors and fixes
  10. Autoclaw integration: metric logging convention

1. Imports

import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector
from qiskit.circuit.library import (
    ZFeatureMap,
    ZZFeatureMap,
    StatePreparation,
    EfficientSU2,
)
from qiskit.primitives import StatevectorSampler, StatevectorEstimator  # V2 primitives
from qiskit.quantum_info import Statevector, SparsePauliOp
from qiskit_aer import AerSimulator
from qiskit_algorithms.optimizers import SPSA, COBYLA, L_BFGS_B, ADAM
from qiskit_algorithms.utils import algorithm_globals
from qiskit_machine_learning.algorithms.classifiers import VQC

For chemistry:

from qiskit_nature.units import DistanceUnit
from qiskit_nature.second_q.drivers import PySCFDriver
from qiskit_nature.second_q.mappers import ParityMapper, JordanWignerMapper

Do not import from qiskit_nature.second_q.algorithms or qiskit_algorithms.VQE under qiskit 2.x (they fail at import time, see section 8).


2. Data-encoding feature maps

Three standard families. Each builder returns a parameterized circuit suitable for use as the feature_map argument of VQC or for direct contraction with a variational ansatz.

def build_angle_encoding(num_features: int) -> QuantumCircuit:
    """Hadamard plus single-qubit Z-rotation per feature.

    Mathematically equivalent to ZFeatureMap(reps=1).
    """
    return ZFeatureMap(feature_dimension=num_features, reps=1)


def build_amplitude_encoding(num_features: int):
    """Load an L2-normalized, zero-padded input as the amplitudes of a
    quantum state. The encoding uses ceil(log2(num_features)) qubits.

    Returns (circuit, parameter_vector, num_qubits). The caller binds
    parameters per-sample via the helper below.
    """
    num_qubits = int(np.ceil(np.log2(max(num_features, 2))))
    full_dim = 2 ** num_qubits
    params = ParameterVector("x_amp", full_dim)
    qc = QuantumCircuit(num_qubits)
    qc.append(StatePreparation(list(params)), range(num_qubits))
    return qc, params, num_qubits


def amplitude_binding(x: np.ndarray, params, num_qubits: int) -> dict:
    """Build the parameter-value dict for a single input sample."""
    x_norm = x / max(float(np.linalg.norm(x)), 1e-12)
    padded = np.zeros(2 ** num_qubits, dtype=np.float64)
    padded[: len(x_norm)] = x_norm
    padded = padded / max(float(np.linalg.norm(padded)), 1e-12)
    return {params[i]: float(padded[i]) for i in range(len(padded))}


def build_zz_feature_map(num_features: int) -> QuantumCircuit:
    """Two repetitions of Hadamard plus pairwise ZZ entangling rotations."""
    return ZZFeatureMap(
        feature_dimension=num_features, reps=2, entanglement="linear"
    )

To verify that two encoders produce distinguishable output for a fixed input (catches dispatch bugs in code that constructs multiple encoders in a loop):

def assert_different_output_states(qc_a, qc_b, x, tol: float = 1e-6):
    sv_a = Statevector(qc_a.assign_parameters(x))
    sv_b = Statevector(qc_b.assign_parameters(x))
    diff = float(np.linalg.norm(sv_a.data - sv_b.data))
    assert diff > tol, f"encoders produced identical states (diff={diff})"

3. Variational ansatz construction

def build_ansatz(num_qubits: int, reps: int = 2) -> QuantumCircuit:
    """Hardware-efficient ansatz with alternating Pauli rotations
    and a linear chain of CNOT entanglers. Trainable parameter count
    is (reps + 1) * num_qubits for the default su2_gates = ['ry']."""
    return EfficientSU2(
        num_qubits=num_qubits, reps=reps, entanglement="linear"
    )

ansatz.num_parameters gives the trainable parameter count, useful for matching parameter budgets against classical baselines.


4. VQC training (qiskit_machine_learning)

def train_vqc(
    feature_map: QuantumCircuit,
    ansatz: QuantumCircuit,
    X_train: np.ndarray,
    y_train: np.ndarray,
    seed: int,
    maxiter: int = 200,
) -> VQC:
    algorithm_globals.random_seed = seed
    vqc = VQC(
        feature_map=feature_map,
        ansatz=ansatz,
        loss="cross_entropy",
        optimizer=COBYLA(maxiter=maxiter),
        sampler=StatevectorSampler(seed=seed),
    )
    vqc.fit(X_train, y_train)
    return vqc

Supported VQC.__init__ kwargs in qiskit_machine_learning: feature_map, ansatz, loss, optimizer, sampler, initial_point, callback, warm_start. Other names raise TypeError.

Use VQC.fit(X, y) and VQC.predict(X). Do not write a custom optimization loop that calls the Sampler directly inside a COBYLA closure: VQC.fit already does this with correct parameter-shift gradients and shot accounting.


5. VQE for quantum chemistry (qiskit 2.x compatible)

Build the qubit Hamiltonian from PySCF, then run a manual optimization loop over a StatevectorEstimator. The classes qiskit_algorithms.VQE and the qiskit_nature.second_q.algorithms.* submodule are not importable in qiskit 2.x (see section 8); the pattern below uses only the safe parts of those packages.

def build_h2_hamiltonian(bond_length_angstrom: float):
    driver = PySCFDriver(
        atom=f"H 0 0 0; H 0 0 {bond_length_angstrom}",
        basis="sto3g",
        charge=0,
        spin=0,
        unit=DistanceUnit.ANGSTROM,
    )
    problem = driver.run()
    num_particles = tuple(problem.num_particles)         # (1, 1) for H2
    mapper = ParityMapper(num_particles=num_particles)   # 2-qubit reduction
    qubit_op = mapper.map(problem.hamiltonian.second_q_op())
    e_nuclear = float(problem.nuclear_repulsion_energy)
    # H2 in STO-3G with parity mapping plus 2-qubit reduction produces a
    # 2-qubit Hamiltonian (not 4-qubit).
    return qubit_op, e_nuclear


def run_vqe(qubit_op, e_nuclear, optimizer_name: str, seed: int):
    algorithm_globals.random_seed = seed
    rng = np.random.RandomState(seed)
    ansatz = build_ansatz(num_qubits=qubit_op.num_qubits, reps=2)
    initial_point = rng.normal(0.0, 0.1, ansatz.num_parameters)

    estimator = StatevectorEstimator(seed=seed)
    energy_history: list[tuple[int, float]] = []

    def energy(theta: np.ndarray) -> float:
        bound = ansatz.assign_parameters(theta)
        result = estimator.run([(bound, qubit_op)]).result()
        e = float(result[0].data.evs) + e_nuclear
        energy_history.append((len(energy_history) + 1, e))
        return e

    optimizers = {
        "spsa": SPSA(maxiter=200),
        "cobyla": COBYLA(maxiter=200, rhobeg=0.1, tol=1e-4),
        "lbfgsb": L_BFGS_B(maxiter=100, ftol=1e-6),
        "adam": ADAM(maxiter=200, lr=0.05, beta_1=0.9, beta_2=0.999),
    }
    if optimizer_name not in optimizers:
        raise ValueError(f"unknown optimizer: {optimizer_name}")
    result = optimizers[optimizer_name].minimize(energy, initial_point)
    return result, energy_history

The number of energy evaluations is len(energy_history). Cumulative shots equals len(energy_history) * shots_per_eval. For shot-budget studies, emulate shot noise by adding Gaussian noise N(0, sigma) to each value with sigma ≈ ||H||_1 / sqrt(shots_per_eval).

A running-mean convergence check is needed at low shot counts because the per-evaluation energy variance can exceed the chemical-accuracy threshold even when the optimizer has converged:

from collections import deque


def cumulative_shots_to_threshold(
    energy_history: list[tuple[int, float]],
    e_target: float,
    threshold: float = 0.0016,    # 1.6 mHa
    shots_per_eval: int = 1024,
    window: int = 5,
) -> int | None:
    """Return cumulative shots at the first point where the running mean
    over `window` evaluations stays within `threshold` of `e_target` for
    `window` consecutive windows. Return None if never reached."""
    buf = deque(maxlen=window)
    streak = 0
    for eval_count, energy in energy_history:
        buf.append(energy)
        if len(buf) < window:
            continue
        if abs(sum(buf) / window - e_target) <= threshold:
            streak += 1
            if streak >= window:
                return eval_count * shots_per_eval
        else:
            streak = 0
    return None

6. MPS-structured circuits

A matrix product state classifier with bond dimension chi is mathematically equivalent to a qiskit circuit with one qubit per input feature (or pixel), a linear-chain entangling ansatz of depth reps = log2(chi), and class-label measurements as expectation values. Running this on AerSimulator(method="matrix_product_state") with an internal bond-dimension cap gives an efficient classical simulation even at 32 to 128 qubits.

def encode_features_to_circuit(x: np.ndarray, n_qubits: int) -> QuantumCircuit:
    """Per-feature embedding equivalent to phi(x) = [cos(pi*x/2), sin(pi*x/2)].
    Apply RY(pi * x_i) on qubit i so |0> maps to cos(pi*x_i/2)|0> + sin(pi*x_i/2)|1>."""
    qc = QuantumCircuit(n_qubits)
    for i in range(n_qubits):
        qc.ry(float(x[i]) * np.pi, i)
    return qc


def build_mps_ansatz(n_qubits: int, reps_for_chi: int) -> QuantumCircuit:
    """Linear-chain entangling ansatz; effective bond dimension <= 2 ** reps_for_chi.
    reps_for_chi=4 covers chi up to 16."""
    return EfficientSU2(
        num_qubits=n_qubits, reps=reps_for_chi, entanglement="linear"
    )


def mps_class_logits(
    x: np.ndarray,
    theta: np.ndarray,
    ansatz: QuantumCircuit,
    n_classes: int,
    max_bond: int = 16,
) -> np.ndarray:
    """Return one logit per class, computed via AerSimulator MPS method.

    Each class c corresponds to a Pauli observable acting on the first
    ceil(log2(n_classes)) qubits with sign pattern fixed by the bits of c."""
    import math

    n_qubits = ansatz.num_qubits
    sim = AerSimulator(
        method="matrix_product_state",
        matrix_product_state_max_bond_dimension=int(max_bond),
    )
    bound_ansatz = ansatz.assign_parameters(theta)
    qc = encode_features_to_circuit(x, n_qubits)
    qc.compose(bound_ansatz, inplace=True)

    n_label_qubits = max(1, math.ceil(math.log2(n_classes)))
    logits = []
    for c in range(n_classes):
        pauli = list("I" * n_qubits)
        for bit_idx in range(n_label_qubits):
            if (c >> bit_idx) & 1:
                pauli[bit_idx] = "Z"
        obs = SparsePauliOp.from_list([("".join(pauli[::-1]), 1.0)])
        qc_with_save = qc.copy()
        qc_with_save.save_expectation_value(obs, list(range(n_qubits)))
        result = sim.run(qc_with_save).result()
        logits.append(float(result.data(0)["expectation_value"]))
    return np.array(logits)

Train with parameter-shift gradients on the cross-entropy of softmax(logits) against the one-hot labels.

When a manual NumPy MPS implementation is used instead, three subtle errors are common and produce silently-degenerate models:

  • Initialising tensors near the identity makes every class share the same logit. The classifier collapses to test accuracy = 1/n_classes independent of bond dimension.
  • Manual bond-index bookkeeping in the contraction can leave some tensors disconnected from the gradient and never updated.
  • The cos/sin embedding requires the factor of pi. Forgetting it gives a feature map that is approximately constant across inputs.

Using the qiskit-circuit form above avoids all three: the circuit representation is unambiguous, parameter-shift gradients are correct by construction, and matrix_product_state_max_bond_dimension enforces the bond cap inside the simulator.


7. Noise model integration

The qiskit primitive samplers (Sampler V1 and StatevectorSampler V2) do not accept a noise_model argument; they are noiseless by definition. To inject noise, the noise model must live on a qiskit_aer.AerSimulator backend, and the sampler then wraps that backend via BackendSamplerV2:

from qiskit_aer.noise import NoiseModel, depolarizing_error
from qiskit.primitives import BackendSamplerV2


def build_noisy_sampler(depolarizing_rate: float, seed: int) -> BackendSamplerV2:
    noise_model = NoiseModel()
    if depolarizing_rate > 0:
        single_qubit_error = depolarizing_error(depolarizing_rate, 1)
        noise_model.add_all_qubit_quantum_error(
            single_qubit_error, ["ry", "rz", "rx", "h"]
        )
        two_qubit_error = depolarizing_error(depolarizing_rate, 2)
        noise_model.add_all_qubit_quantum_error(two_qubit_error, ["cx"])
    backend = AerSimulator(noise_model=noise_model, seed_simulator=seed)
    return BackendSamplerV2(backend=backend)


def build_ideal_sampler(seed: int) -> StatevectorSampler:
    return StatevectorSampler(seed=seed)

Use it with VQC:

sampler = build_noisy_sampler(depolarizing_rate=0.005, seed=seed)
vqc = VQC(
    feature_map=fm,
    ansatz=ansatz,
    sampler=sampler,
    optimizer=COBYLA(maxiter=200),
)
vqc.fit(X_train, y_train)

Each evaluation regime needs its own sampler instance. A model trained on a noisy sampler at rate p_train can be evaluated on a separate noisy sampler at a different rate p_test to probe noise robustness, or on build_ideal_sampler to probe the clean-test transfer.

Do not silently swallow exceptions raised by vqc.fit. If training fails at high noise rates, either let the cell fail with a documented error or record a training_failed=True marker in the metrics rather than calling vqc.predict on an unfitted model, which raises QiskitMachineLearningError: 'The model has not been fitted yet'.


8. qiskit 2.x compatibility notes

Qiskit 2.0 removed qiskit.primitives.Estimator and qiskit.primitives.BaseEstimator (the V1 interfaces). Two consequences:

  • from qiskit_algorithms import VQE fails because the file imports BaseEstimator. Use a manual VQE loop with qiskit.primitives.StatevectorEstimator (V2) and the qiskit_algorithms.optimizers.* classes' .minimize() methods directly. See section 5.
  • from qiskit_nature.second_q.algorithms import GroundStateEigensolver fails for the same reason. The qiskit_nature.second_q.drivers and qiskit_nature.second_q.mappers submodules are still safe.

Other 2.x notes:

  • transpile(circuits=list, coupling_map=single) raises TranspilerError. Either call transpile per circuit or omit coupling_map (statevector backends do not need it).
  • Statevector(qc) returns a Statevector object; use .data for the numpy array of amplitudes.
  • algorithm_globals.random_seed is the global seed for SPSA and random ansatz initialization. Set it before each training run.

9. Common errors and fixes

Wrong Why Correct
Sampler(noise_model=NoiseModel()) V1/V2 Samplers are noiseless BackendSamplerV2(backend=AerSimulator(noise_model=...))
from qiskit_algorithms import VQE imports removed V1 BaseEstimator Manual loop over StatevectorEstimator plus optimizer.minimize()
VQC(..., gradient=...) not a supported kwarg Drop the kwarg; VQC.fit handles gradients internally
params = pv_a.concatenate(pv_b) ParameterVector is not numpy params = list(pv_a) + list(pv_b)
Plain qc.rz(x[i], i) for angle encoding RZ on ` 0>` is a global phase, has no effect
qc.ry(x[i], i) + qc.cx(...) labeled as amplitude encoding This is angle encoding, not amplitude Use StatePreparation over the L2-normalized, zero-padded vector
scipy.optimize.minimize(...) in VQE requires hand-rolled shot accounting and convergence checks qiskit_algorithms.optimizers.{SPSA, COBYLA, L_BFGS_B, ADAM}.minimize(energy, x0)
Per-step abs(raw_energy - e_fci) <= threshold for convergence Per-step noise can exceed the threshold even when converged Running mean over a window (see cumulative_shots_to_threshold)
Report maxiter * evals_per_iter * shots_per_eval as "shots to convergence" when not actually converged This is the upper bound, not a measurement Report None (or a documented sentinel) for non-converged runs
near_identity_init for a NumPy MPS classifier every class logit collapses to the same value Random initialization, or use the qiskit-circuit form (section 6)

10. Autoclaw integration: metric logging convention

When this skill is used inside the autoclaw bench runner (stage 12 or stage 13 sandbox), per-cell metrics should be emitted to stdout as single lines starting with METRIC_RESULT followed by a JSON object. The autoclaw sandbox parser aggregates these into condition_summaries at stage 14.

import json


def emit_metric_result(condition: str, dataset: str, seed: int, **metrics) -> None:
    payload = {"condition": condition, "dataset": dataset, "seed": int(seed)}
    payload.update({k: float(v) for k, v in metrics.items() if v is not None})
    print("METRIC_RESULT " + json.dumps(payload))

This section is specific to the autoclaw pipeline. Outside of autoclaw, choose a metric-logging convention appropriate to the host system.

Version History

  • e2e23c9 Current 2026-07-25 07:48

Same Skill Collection

.claude/skills/a-evolve/SKILL.md
.claude/skills/biology-biopython/SKILL.md
.claude/skills/chemistry-rdkit/SKILL.md
.claude/skills/hypothesis-formulation/SKILL.md
.claude/skills/literature-search/SKILL.md
.claude/skills/researchclaw/SKILL.md
.claude/skills/scientific-visualization/SKILL.md
.claude/skills/scientific-writing/SKILL.md
.claude/skills/statistical-reporting/SKILL.md
external/agents/Biology-Agent/skills/fba-simulator/SKILL.md
external/agents/Biology-Agent/skills/flux-analyzer/SKILL.md
external/agents/Biology-Agent/skills/gsmm-builder/SKILL.md
external/agents/Biology-Agent/skills/gsmm-validator/SKILL.md
external/agents/Biology-Agent/skills/metabolic-study-planner/SKILL.md
external/agents/Biology-Agent/skills/mfa-pipeline-orchestrator/SKILL.md
external/agents/stat_research_agent/skills/stat-research-orchestrator/SKILL.md
external/agents/stat_research_agent/skills/stat-result-validator/SKILL.md
external/agents/stat_research_agent/skills/statistical-experimental-evaluation/SKILL.md
external/agents/stat_research_agent/skills/statistical-method-design/SKILL.md
external/agents/stat_research_agent/skills/statistical-problem-formulation/SKILL.md
external/agents/stat_research_agent/skills/statistical-theory-analysis/SKILL.md
researchclaw/skills/builtin/domain/biology-biopython/SKILL.md
researchclaw/skills/builtin/domain/chemistry-rdkit/SKILL.md
researchclaw/skills/builtin/domain/cv-classification/SKILL.md
researchclaw/skills/builtin/domain/cv-detection/SKILL.md
researchclaw/skills/builtin/domain/nlp-alignment/SKILL.md
researchclaw/skills/builtin/domain/nlp-pretraining/SKILL.md
researchclaw/skills/builtin/domain/rl-policy-optimization/SKILL.md
researchclaw/skills/builtin/experiment/experimental-design/SKILL.md
researchclaw/skills/builtin/experiment/hypothesis-formulation/SKILL.md
researchclaw/skills/builtin/experiment/literature-search/SKILL.md
researchclaw/skills/builtin/experiment/meta-analysis/SKILL.md
researchclaw/skills/builtin/experiment/scientific-visualization/SKILL.md
researchclaw/skills/builtin/experiment/scientific-writing/SKILL.md
researchclaw/skills/builtin/experiment/statistical-reporting/SKILL.md
researchclaw/skills/builtin/experiment/systematic-review/SKILL.md
researchclaw/skills/builtin/tooling/data-loading/SKILL.md
researchclaw/skills/builtin/tooling/distributed-training/SKILL.md
researchclaw/skills/builtin/tooling/mixed-precision/SKILL.md
researchclaw/skills/builtin/tooling/pytorch-training/SKILL.md

Metadata

Files
0
Version
be4ba47
Hash
d4c5ae63
Indexed
2026-07-25 07:48

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-20 06:40
浙ICP备14020137号-1 $mapa de visitantes$