Skills › Scientific Agent Skills › qiskit › check_environment.py
check_environment.py
260 lines · skills/qiskit/scripts/check_environment.py at 5f2bc03
1#!/usr/bin/env python3
2"""Inspect a Qiskit environment without network or credential access."""
4from __future__ import annotations
6import argparse
7import importlib
8import json
9import platform
10import struct
11import sys
12from importlib import metadata
13from typing import Any
16VERIFIED_VERSIONS = {
17 "qiskit": "2.5.2",
18 "qiskit-ibm-runtime": "0.50.0",
19 "qiskit-aer": "0.17.2",
20 "qiskit-algorithms": "0.4.0",
21 "qiskit-nature": "0.8.0",
22 "qiskit-machine-learning": "0.9.1",
23 "qiskit-optimization": "0.7.0",
24}
26IMPORT_NAMES = {
27 "qiskit": "qiskit",
28 "qiskit-ibm-runtime": "qiskit_ibm_runtime",
29 "qiskit-aer": "qiskit_aer",
30 "qiskit-algorithms": "qiskit_algorithms",
31 "qiskit-nature": "qiskit_nature",
32 "qiskit-machine-learning": "qiskit_machine_learning",
33 "qiskit-optimization": "qiskit_optimization",
34}
37def installed_version(distribution: str) -> str | None:
38 """Return an installed distribution version, or None."""
39 try:
40 return metadata.version(distribution)
41 except metadata.PackageNotFoundError:
42 return None
45def import_status(module_name: str) -> dict[str, str | bool | None]:
46 """Import one module and return JSON-safe status details."""
47 try:
48 module = importlib.import_module(module_name)
49 except Exception as error: # Report broken optional environments clearly.
50 return {
51 "ok": False,
52 "module_file": None,
53 "error": f"{type(error).__name__}: {error}",
54 }
56 return {
57 "ok": True,
58 "module_file": getattr(module, "__file__", None),
59 "error": None,
60 }
63def collect_report(
64 require_runtime: bool,
65 require_aer: bool,
66 strict: bool,
67) -> tuple[dict[str, Any], list[str], list[str]]:
68 """Collect environment details, errors, and warnings."""
69 errors: list[str] = []
70 warnings: list[str] = []
72 pointer_bits = struct.calcsize("P") * 8
73 python_ok = sys.version_info >= (3, 10)
74 platform_ok = pointer_bits == 64
76 if not python_ok:
77 errors.append("Qiskit 2.5 requires Python 3.10 or newer.")
78 if not platform_ok:
79 errors.append("Qiskit 2.x requires a supported 64-bit platform.")
81 required = {"qiskit"}
82 if require_runtime:
83 required.add("qiskit-ibm-runtime")
84 if require_aer:
85 required.add("qiskit-aer")
87 packages: dict[str, Any] = {}
88 for distribution, verified in VERIFIED_VERSIONS.items():
89 current = installed_version(distribution)
90 module = IMPORT_NAMES[distribution]
91 status: dict[str, Any] = {
92 "installed_version": current,
93 "verified_version": verified,
94 "required": distribution in required,
95 "version_matches_verified": current == verified,
96 "import": None,
97 }
99 if current is None:
100 if distribution in required:
101 errors.append(f"Required distribution is missing: {distribution}")
102 else:
103 status["import"] = import_status(module)
104 if not status["import"]["ok"]:
105 errors.append(
106 f"{distribution} is installed but cannot be imported: "
107 f"{status['import']['error']}"
108 )
109 if current != verified:
110 message = (
111 f"{distribution}=={current} differs from the verified "
112 f"baseline {verified}."
113 )
114 if strict and distribution in required:
115 errors.append(message)
116 else:
117 warnings.append(message)
119 packages[distribution] = status
121 terra_version = installed_version("qiskit-terra")
122 if terra_version is not None:
123 errors.append(
124 "Legacy qiskit-terra is installed. Create a clean environment "
125 "with the qiskit distribution instead of mixing namespaces."
126 )
128 core_api: dict[str, Any] = {
129 "ok": False,
130 "error": None,
131 }
132 if packages["qiskit"]["installed_version"] is not None:
133 try:
134 from qiskit import QuantumCircuit
135 from qiskit.primitives import (
136 StatevectorEstimator,
137 StatevectorSampler,
138 )
139 from qiskit.transpiler import generate_preset_pass_manager
141 circuit = QuantumCircuit(1)
142 circuit.h(0)
143 _ = (
144 StatevectorSampler,
145 StatevectorEstimator,
146 generate_preset_pass_manager,
147 )
148 core_api["ok"] = circuit.num_qubits == 1
149 except Exception as error:
150 core_api["error"] = f"{type(error).__name__}: {error}"
151 errors.append(f"Core Qiskit API smoke check failed: {error}")
153 report: dict[str, Any] = {
154 "ok": not errors,
155 "python": {
156 "version": platform.python_version(),
157 "implementation": platform.python_implementation(),
158 "executable": sys.executable,
159 "meets_minimum": python_ok,
160 },
161 "platform": {
162 "description": platform.platform(),
163 "pointer_bits": pointer_bits,
164 "supported_width": platform_ok,
165 },
166 "legacy_qiskit_terra": terra_version,
167 "packages": packages,
168 "core_api_smoke_check": core_api,
169 "warnings": warnings,
170 "errors": errors,
171 }
172 return report, errors, warnings
175def build_parser() -> argparse.ArgumentParser:
176 parser = argparse.ArgumentParser(
177 description=(
178 "Inspect installed Qiskit distributions. This script performs "
179 "no network calls and reads no credentials."
180 )
181 )
182 parser.add_argument(
183 "--require-runtime",
184 action="store_true",
185 help="Fail if qiskit-ibm-runtime is unavailable.",
186 )
187 parser.add_argument(
188 "--require-aer",
189 action="store_true",
190 help="Fail if qiskit-aer is unavailable.",
191 )
192 parser.add_argument(
193 "--strict",
194 action="store_true",
195 help="Fail when required package versions differ from the baseline.",
196 )
197 parser.add_argument(
198 "--json",
199 action="store_true",
200 help="Emit a JSON report.",
201 )
202 return parser
205def print_human_report(report: dict[str, Any]) -> None:
206 print(
207 "Python:",
208 report["python"]["version"],
209 f"({report['python']['implementation']})",
210 )
211 print("Executable:", report["python"]["executable"])
212 print(
213 "Platform:",
214 report["platform"]["description"],
215 f"({report['platform']['pointer_bits']}-bit)",
216 )
217 print("\nDistributions:")
219 for distribution, details in report["packages"].items():
220 current = details["installed_version"] or "not installed"
221 marker = "required" if details["required"] else "optional"
222 print(
223 f" {distribution}: {current} "
224 f"(verified {details['verified_version']}; {marker})"
225 )
226 import_details = details["import"]
227 if import_details and not import_details["ok"]:
228 print(f" import error: {import_details['error']}")
230 if report["warnings"]:
231 print("\nWarnings:")
232 for warning in report["warnings"]:
233 print(f" - {warning}")
235 if report["errors"]:
236 print("\nErrors:")
237 for error in report["errors"]:
238 print(f" - {error}")
240 print("\nStatus:", "OK" if report["ok"] else "FAILED")
243def main() -> int:
244 args = build_parser().parse_args()
245 report, errors, _warnings = collect_report(
246 require_runtime=args.require_runtime,
247 require_aer=args.require_aer,
248 strict=args.strict,
249 )
251 if args.json:
252 print(json.dumps(report, indent=2, sort_keys=True))
253 else:
254 print_human_report(report)
256 return 1 if errors else 0
259if __name__ == "__main__":
260 raise SystemExit(main())