Skills › Scientific Agent Skills › qiskit › run_local_primitives.py
run_local_primitives.py
200 lines · skills/qiskit/scripts/run_local_primitives.py at 5f2bc03
1#!/usr/bin/env python3
2"""Run a parameterized circuit with Qiskit V2 local primitives."""
4from __future__ import annotations
6import argparse
7import json
8import math
9from importlib.metadata import PackageNotFoundError, version
10from typing import Any
13MAX_SHOTS = 1_000_000
16def positive_bounded_shots(value: str) -> int:
17 shots = int(value)
18 if shots < 1:
19 raise argparse.ArgumentTypeError("shots must be positive")
20 if shots > MAX_SHOTS:
21 raise argparse.ArgumentTypeError(f"shots must not exceed {MAX_SHOTS:,}")
22 return shots
25def finite_float(value: str) -> float:
26 parsed = float(value)
27 if not math.isfinite(parsed):
28 raise argparse.ArgumentTypeError("theta must be finite")
29 return parsed
32def qiskit_version() -> str:
33 try:
34 return version("qiskit")
35 except PackageNotFoundError:
36 return "not installed"
39def run_workflow(
40 shots: int,
41 seed: int,
42 theta_value: float,
43) -> dict[str, Any]:
44 import numpy as np
45 from qiskit import QuantumCircuit
46 from qiskit.circuit import Parameter
47 from qiskit.primitives import (
48 StatevectorEstimator,
49 StatevectorSampler,
50 )
51 from qiskit.quantum_info import SparsePauliOp
53 theta = Parameter("theta")
54 circuit = QuantumCircuit(2, name="parameterized_bell")
55 circuit.ry(theta, 0)
56 circuit.cx(0, 1)
58 parameter_values = [[theta_value]]
59 parameter_order = [parameter.name for parameter in circuit.parameters]
61 observable = SparsePauliOp.from_list(
62 [
63 ("ZI", 1.0),
64 ("XX", 0.5),
65 ]
66 )
67 estimator = StatevectorEstimator(seed=seed)
68 estimator_result = estimator.run(
69 [(circuit, observable, parameter_values)]
70 ).result()[0]
72 expectation_value = float(np.asarray(estimator_result.data.evs).reshape(-1)[0])
73 standard_deviation = float(np.asarray(estimator_result.data.stds).reshape(-1)[0])
74 analytic_expectation = math.cos(theta_value) + 0.5 * math.sin(theta_value)
76 measured_circuit = circuit.copy()
77 measured_circuit.measure_all()
78 sampler = StatevectorSampler(seed=seed)
79 sampler_result = sampler.run(
80 [(measured_circuit, parameter_values)],
81 shots=shots,
82 ).result()[0]
83 counts = sampler_result.data.meas.get_counts(0)
85 observed_shots = sum(counts.values())
86 probabilities = {
87 bitstring: count / observed_shots for bitstring, count in sorted(counts.items())
88 }
89 expected_probabilities = {
90 "00": math.cos(theta_value / 2) ** 2,
91 "11": math.sin(theta_value / 2) ** 2,
92 }
94 return {
95 "qiskit_version": qiskit_version(),
96 "seed": seed,
97 "shots": shots,
98 "theta": theta_value,
99 "parameter_order": parameter_order,
100 "observable": "1.0 * ZI + 0.5 * XX",
101 "estimator": {
102 "expectation_value": expectation_value,
103 "standard_deviation": standard_deviation,
104 "analytic_expectation": analytic_expectation,
105 "absolute_error": abs(expectation_value - analytic_expectation),
106 },
107 "sampler": {
108 "counts": dict(sorted(counts.items())),
109 "probabilities": probabilities,
110 "expected_probabilities": expected_probabilities,
111 },
112 }
115def build_parser() -> argparse.ArgumentParser:
116 parser = argparse.ArgumentParser(
117 description=(
118 "Run a two-qubit parameterized circuit with "
119 "StatevectorSampler and StatevectorEstimator."
120 )
121 )
122 parser.add_argument(
123 "--shots",
124 type=positive_bounded_shots,
125 default=1024,
126 help=f"Sampler shots (1-{MAX_SHOTS:,}; default: 1024).",
127 )
128 parser.add_argument(
129 "--seed",
130 type=int,
131 default=7,
132 help="Local sampler and estimator seed (default: 7).",
133 )
134 parser.add_argument(
135 "--theta",
136 type=finite_float,
137 default=math.pi / 2,
138 help="Circuit angle in radians (default: pi/2).",
139 )
140 parser.add_argument(
141 "--json",
142 action="store_true",
143 help="Emit JSON instead of human-readable output.",
144 )
145 return parser
148def print_human(result: dict[str, Any]) -> None:
149 print("Qiskit:", result["qiskit_version"])
150 print(
151 "Inputs:",
152 f"theta={result['theta']:.8f}",
153 f"shots={result['shots']}",
154 f"seed={result['seed']}",
155 )
156 print("Parameter order:", result["parameter_order"])
157 print("Observable:", result["observable"])
159 estimator = result["estimator"]
160 print(
161 "Estimator:",
162 f"{estimator['expectation_value']:.12f}",
163 "(analytic",
164 f"{estimator['analytic_expectation']:.12f},",
165 "absolute error",
166 f"{estimator['absolute_error']:.3e})",
167 )
169 sampler = result["sampler"]
170 print("Sampler counts:", sampler["counts"])
171 print("Sampler probabilities:", sampler["probabilities"])
172 print(
173 "Expected probabilities:",
174 sampler["expected_probabilities"],
175 )
178def main() -> int:
179 args = build_parser().parse_args()
180 try:
181 result = run_workflow(
182 shots=args.shots,
183 seed=args.seed,
184 theta_value=args.theta,
185 )
186 except ModuleNotFoundError as error:
187 raise SystemExit(
188 "Qiskit is not installed. Install the pinned core package "
189 'with: uv pip install "qiskit==2.5.2"'
190 ) from error
192 if args.json:
193 print(json.dumps(result, indent=2, sort_keys=True))
194 else:
195 print_human(result)
196 return 0
199if __name__ == "__main__":
200 raise SystemExit(main())