Skills › Scientific Agent Skills › qiskit › inspect_runtime.py
inspect_runtime.py
224 lines · skills/qiskit/scripts/inspect_runtime.py at 5f2bc03
1#!/usr/bin/env python3
2"""Inspect one IBM Runtime backend without submitting a quantum job."""
4from __future__ import annotations
6import argparse
7import json
8import sys
9from importlib.metadata import PackageNotFoundError, version
10from typing import Any
13def positive_qubits(value: str) -> int:
14 qubits = int(value)
15 if qubits < 1:
16 raise argparse.ArgumentTypeError("minimum qubits must be positive")
17 if qubits > 10_000:
18 raise argparse.ArgumentTypeError("minimum qubits is implausibly large")
19 return qubits
22def distribution_version(distribution: str) -> str | None:
23 try:
24 return version(distribution)
25 except PackageNotFoundError:
26 return None
29def select_backend(
30 service: Any,
31 backend_name: str | None,
32 min_qubits: int,
33 use_fractional_gates: bool | None,
34) -> Any:
35 feature_kwargs: dict[str, Any] = {}
36 if use_fractional_gates is not None:
37 feature_kwargs["use_fractional_gates"] = use_fractional_gates
39 if backend_name:
40 return service.backend(
41 backend_name,
42 **feature_kwargs,
43 )
45 return service.least_busy(
46 operational=True,
47 simulator=False,
48 min_num_qubits=min_qubits,
49 **feature_kwargs,
50 )
53def inspect_backend(backend: Any) -> dict[str, Any]:
54 status = backend.status()
55 operation_names = sorted(backend.operation_names)
57 coupling_map = backend.coupling_map
58 coupling_edges = list(coupling_map.get_edges()) if coupling_map is not None else []
60 control_flow_names = {
61 "if_else",
62 "while_loop",
63 "for_loop",
64 "switch_case",
65 "store",
66 }
67 exposed_control_flow = sorted(control_flow_names.intersection(operation_names))
69 fractional_candidates = {
70 "rx",
71 "rzz",
72 }
73 exposed_fractional_candidates = sorted(
74 fractional_candidates.intersection(operation_names)
75 )
77 target = backend.target
78 return {
79 "backend": {
80 "name": backend.name,
81 "num_qubits": backend.num_qubits,
82 "operational": getattr(
83 status,
84 "operational",
85 None,
86 ),
87 "pending_jobs": getattr(
88 status,
89 "pending_jobs",
90 None,
91 ),
92 "status_message": getattr(
93 status,
94 "status_msg",
95 None,
96 ),
97 "max_circuits": getattr(
98 backend,
99 "max_circuits",
100 None,
101 ),
102 },
103 "target": {
104 "operation_names": operation_names,
105 "control_flow_operations": exposed_control_flow,
106 "fractional_gate_candidates": (exposed_fractional_candidates),
107 "coupling_edge_count": len(coupling_edges),
108 "coupling_edges": coupling_edges,
109 "dt_seconds": getattr(target, "dt", None),
110 "dtm_seconds": getattr(target, "dtm", None),
111 },
112 "versions": {
113 "qiskit": distribution_version("qiskit"),
114 "qiskit-ibm-runtime": distribution_version("qiskit-ibm-runtime"),
115 },
116 "submitted_jobs": 0,
117 }
120def build_parser() -> argparse.ArgumentParser:
121 parser = argparse.ArgumentParser(
122 description=(
123 "Select and inspect one accessible IBM Quantum backend. "
124 "Uses saved credentials, performs network reads, and never "
125 "submits a job."
126 )
127 )
128 parser.add_argument(
129 "--backend",
130 help=(
131 "Inspect this backend name. If omitted, select the least "
132 "busy accessible backend matching --min-qubits."
133 ),
134 )
135 parser.add_argument(
136 "--min-qubits",
137 type=positive_qubits,
138 default=5,
139 help="Minimum qubits for automatic selection (default: 5).",
140 )
141 parser.add_argument(
142 "--fractional-gates",
143 action=argparse.BooleanOptionalAction,
144 default=None,
145 help=(
146 "Explicitly request or disable a fractional-gate target. "
147 "By default, use the service default."
148 ),
149 )
150 parser.add_argument(
151 "--json",
152 action="store_true",
153 help="Emit JSON.",
154 )
155 return parser
158def print_human(report: dict[str, Any]) -> None:
159 backend = report["backend"]
160 target = report["target"]
162 print("Backend:", backend["name"])
163 print("Qubits:", backend["num_qubits"])
164 print("Operational:", backend["operational"])
165 print("Pending jobs:", backend["pending_jobs"])
166 print("Status:", backend["status_message"])
167 print("Maximum circuits per job:", backend["max_circuits"])
168 print("Coupling edges:", target["coupling_edge_count"])
169 print(
170 "Control flow operations:",
171 target["control_flow_operations"] or "none exposed",
172 )
173 print(
174 "Fractional-gate candidates:",
175 target["fractional_gate_candidates"] or "none exposed",
176 )
177 print("Target operations:")
178 print(" " + ", ".join(target["operation_names"]))
179 print("Versions:", report["versions"])
180 print("Submitted jobs: 0")
183def main() -> int:
184 args = build_parser().parse_args()
186 try:
187 from qiskit_ibm_runtime import QiskitRuntimeService
188 except ModuleNotFoundError:
189 print(
190 "qiskit-ibm-runtime is not installed. Install the pinned "
191 'package with: uv pip install "qiskit-ibm-runtime==0.50.0"',
192 file=sys.stderr,
193 )
194 return 2
196 try:
197 service = QiskitRuntimeService()
198 backend = select_backend(
199 service=service,
200 backend_name=args.backend,
201 min_qubits=args.min_qubits,
202 use_fractional_gates=args.fractional_gates,
203 )
204 report = inspect_backend(backend)
205 except Exception as error:
206 # Do not echo request payloads, account details, or credential data.
207 print(
208 "Runtime inspection failed "
209 f"({type(error).__name__}). Verify the saved "
210 "ibm_quantum_platform account, instance access, network, "
211 "and requested backend.",
212 file=sys.stderr,
213 )
214 return 2
216 if args.json:
217 print(json.dumps(report, indent=2, sort_keys=True))
218 else:
219 print_human(report)
220 return 0
223if __name__ == "__main__":
224 raise SystemExit(main())