1 """Mandatory micro-validation matrix for the exact observable path. 3 Validates the required 1- to 3-qubit microcases against Qiskit Aer 4 density-matrix simulation using the frozen exact-observable contract: 5 `Re Tr(H*rho)`, density validity, trace preservation, and Hermitian-observable 8 Run with: python benchmarks/density_matrix/validate_squander_vs_qiskit.py 11 from __future__
import annotations
15 from pathlib
import Path
21 from qiskit.quantum_info
import DensityMatrix
as QiskitDensityMatrix
22 from qiskit.quantum_info
import state_fidelity
24 REPO_ROOT = Path(__file__).resolve().parents[2]
25 if str(REPO_ROOT)
not in sys.path:
26 sys.path.insert(0, str(REPO_ROOT))
28 from benchmarks.density_matrix.circuits
import MANDATORY_MICROCASES_BY_QUBITS
30 PRIMARY_BACKEND =
"density_matrix" 31 REFERENCE_BACKEND =
"qiskit_aer_density_matrix" 32 SUITE_NAME =
"mandatory_micro_validation" 33 ENERGY_ERROR_TOL = 1e-10
36 OBSERVABLE_IMAG_TOL = 1e-10
37 ARTIFACT_FILENAME =
"micro_validation_bundle.json" 38 ARTIFACT_CORE_FIELDS = (
51 """Return machine-readable metadata for one microcase builder operation.""" 52 if operation.startswith(
"U3("):
53 payload = operation[len(
"U3(") : -1].
split(
",")
57 "target_qbit":
int(payload[0]),
60 "raw_operation": operation,
63 if operation.startswith(
"CNOT("):
64 control, target = operation[len(
"CNOT(") : -1].
split(
"->")
68 "target_qbit":
int(target),
69 "control_qbit":
int(control),
71 "raw_operation": operation,
74 if operation.startswith(
"LocalDepol("):
75 target, value = operation[len(
"LocalDepol(") : -1].
split(
",")
78 "name":
"local_depolarizing",
79 "target_qbit":
int(target),
81 "value": float(value),
82 "raw_operation": operation,
85 if operation.startswith(
"AD("):
86 target, value = operation[len(
"AD(") : -1].
split(
",")
89 "name":
"amplitude_damping",
90 "target_qbit":
int(target),
92 "value": float(value),
93 "raw_operation": operation,
96 if operation.startswith(
"PD("):
97 target, value = operation[len(
"PD(") : -1].
split(
",")
100 "name":
"phase_damping",
101 "target_qbit":
int(target),
102 "control_qbit":
None,
103 "value": float(value),
104 "raw_operation": operation,
107 if operation.startswith(
"Depol("):
108 value = operation[len(
"Depol(") : -1]
111 "name":
"depolarizing",
113 "control_qbit":
None,
114 "value": float(value),
115 "raw_operation": operation,
118 for gate_name
in (
"H",
"X",
"Y",
"Z",
"S",
"T",
"Sdg",
"Tdg",
"SX"):
119 prefix = f
"{gate_name}(" 120 if operation.startswith(prefix):
121 target = operation[len(prefix) : -1]
125 "target_qbit":
int(target),
126 "control_qbit":
None,
128 "raw_operation": operation,
131 if operation.startswith(
"CZ("):
132 control, target = operation[len(
"CZ(") : -1].
split(
",")
136 "target_qbit":
int(target),
137 "control_qbit":
int(control),
139 "raw_operation": operation,
142 raise ValueError(f
"Unsupported microcase operation format: {operation}")
146 """Summarize required gate/noise coverage and mixed-sequence order.""" 149 operation
for operation
in operation_metadata
if operation[
"kind"] ==
"gate" 152 operation
for operation
in operation_metadata
if operation[
"kind"] ==
"noise" 154 gate_sequence = [operation[
"name"]
for operation
in gate_operations]
155 noise_sequence = [operation[
"name"]
for operation
in noise_operations]
156 noise_targets = [operation[
"target_qbit"]
for operation
in noise_operations]
157 noise_values = [operation[
"value"]
for operation
in noise_operations]
159 required_gate_coverage_pass = set(case[
"required_gate_family"]).issubset(
162 required_noise_model_coverage_pass = set(case[
"required_noise_models"]).issubset(
165 noise_sequence_match_pass = noise_sequence == case[
"required_noise_models"]
166 mixed_sequence_order_pass = (
167 noise_sequence_match_pass
if case[
"case_kind"] ==
"mixed_sequence" else None 169 operation_audit_pass = (
170 required_gate_coverage_pass
171 and required_noise_model_coverage_pass
172 and noise_sequence_match_pass
176 "operation_metadata": operation_metadata,
177 "operation_count": len(operation_metadata),
178 "gate_operation_sequence": gate_sequence,
179 "noise_operation_sequence": noise_sequence,
180 "noise_operation_targets": noise_targets,
181 "noise_operation_values": noise_values,
182 "required_gate_coverage_pass": required_gate_coverage_pass,
183 "required_noise_model_coverage_pass": required_noise_model_coverage_pass,
184 "noise_sequence_match_pass": noise_sequence_match_pass,
185 "mixed_sequence_order_pass": mixed_sequence_order_pass,
186 "operation_audit_pass": operation_audit_pass,
191 """Compute trace distance: D(Ï,Ï) = (1/2)||Ï - Ï||_1.""" 193 eigenvalues = np.linalg.eigvalsh(diff @ diff.conj().T)
194 return float(0.5 * np.sum(np.sqrt(np.maximum(eigenvalues, 0))))
198 """Return the real and imaginary parts of Tr(H*rho).""" 199 energy = np.trace(hamiltonian @ density_matrix)
200 return float(np.real(energy)), float(np.imag(energy))
205 "absolute_energy_error": ENERGY_ERROR_TOL,
206 "rho_is_valid_tol": VALIDITY_TOL,
207 "trace_deviation": TRACE_TOL,
208 "observable_imag_abs": OBSERVABLE_IMAG_TOL,
209 "required_pass_rate": 1.0,
215 "python": sys.version.split()[0],
216 "numpy": np.__version__,
217 "qiskit": getattr(qiskit,
"__version__",
"unknown"),
218 "qiskit_aer": getattr(qiskit_aer,
"__version__",
"unknown"),
224 "case_name": case[
"case_name"],
226 "backend": PRIMARY_BACKEND,
227 "reference_backend": REFERENCE_BACKEND,
228 "qbit_num": case[
"qbit_num"],
229 "case_kind": case[
"case_kind"],
230 "purpose": case[
"purpose"],
231 "required_gate_family": case[
"required_gate_family"],
232 "required_noise_models": case[
"required_noise_models"],
233 "hamiltonian": case[
"hamiltonian_metadata"],
238 """Evaluate one mandatory micro-validation microcase.""" 239 builder = case[
"builder_fn"]()
240 squander_rho = builder.run_squander()
241 qiskit_rho = builder.run_qiskit()
242 operations = list(builder.ops)
245 squander_arr = np.asarray(squander_rho.to_numpy())
246 qiskit_arr = np.asarray(qiskit_rho)
250 QiskitDensityMatrix(squander_arr), QiskitDensityMatrix(qiskit_arr)
253 max_diff = float(np.max(np.abs(squander_arr - qiskit_arr)))
254 rho_trace = squander_rho.trace()
255 trace_deviation = float(abs(rho_trace - 1.0))
256 density_valid =
bool(squander_rho.is_valid(tol=VALIDITY_TOL))
257 squander_purity = float(np.real(np.trace(squander_arr @ squander_arr)))
258 qiskit_purity = float(np.real(np.trace(qiskit_arr @ qiskit_arr)))
262 case[
"hamiltonian_matrix"], squander_arr
265 case[
"hamiltonian_matrix"], qiskit_arr
267 energy_error = float(abs(squander_energy_real - reference_energy_real))
269 energy_pass = energy_error <= ENERGY_ERROR_TOL
270 density_valid_pass = density_valid
271 trace_pass = trace_deviation <= TRACE_TOL
272 observable_pass = abs(squander_energy_imag) <= OBSERVABLE_IMAG_TOL
273 state_comparison_status = (
274 "pass" if fidelity > 0.99999
else (
"warn" if fidelity > 0.999
else "fail")
278 and density_valid_pass
281 and operation_audit[
"operation_audit_pass"]
287 "status":
"pass" if case_pass
else "fail",
288 "parameter_vector": builder.get_parameter_vector().tolist(),
289 "operations": operations,
290 "state_fidelity": fidelity,
291 "trace_distance": state_distance,
292 "max_diff": max_diff,
293 "state_comparison_status": state_comparison_status,
294 "squander_purity": squander_purity,
295 "reference_purity": qiskit_purity,
296 "squander_trace_real": float(np.real(rho_trace)),
297 "squander_trace_imag": float(np.imag(rho_trace)),
298 "trace_deviation": trace_deviation,
299 "rho_is_valid": density_valid,
300 "rho_is_valid_tol": VALIDITY_TOL,
301 "squander_energy_real": squander_energy_real,
302 "squander_energy_imag": squander_energy_imag,
303 "reference_energy_real": reference_energy_real,
304 "reference_energy_imag": reference_energy_imag,
305 "absolute_energy_error": energy_error,
306 "energy_pass": energy_pass,
307 "density_valid_pass": density_valid_pass,
308 "trace_pass": trace_pass,
309 "observable_pass": observable_pass,
316 " {case_name:<38} [{backend} vs {reference}] " 317 "|ÎE|={energy_error:.3e} |Tr-1|={trace_dev:.3e} " 318 "|ImE|={imag:.3e} status={status}".format(
319 case_name=result[
"case_name"],
320 backend=result[
"backend"],
321 reference=result[
"reference_backend"],
322 energy_error=result[
"absolute_energy_error"],
323 trace_dev=result[
"trace_deviation"],
324 imag=abs(result[
"squander_energy_imag"]),
325 status=result[
"status"].upper(),
333 """Capture validation output for one mandatory micro-validation microcase.""" 336 except Exception
as exc:
341 "error_message": str(exc),
342 "energy_pass":
False,
343 "density_valid_pass":
False,
345 "observable_pass":
False,
350 " {case_name:<38} [{backend} vs {reference}] ERROR {message}".format(
351 case_name=result[
"case_name"],
352 backend=result[
"backend"],
353 reference=result[
"reference_backend"],
354 message=result[
"error_message"],
361 """Run the mandatory micro-validation micro-validation matrix.""" 365 " micro-validation Micro-Validation [{} vs {}]".format(
366 PRIMARY_BACKEND, REFERENCE_BACKEND
372 for qbit_num
in sorted(MANDATORY_MICROCASES_BY_QUBITS.keys()):
374 print(f
"\n--- {qbit_num}-QUBIT MANDATORY MICROCASES ---", flush=
True)
375 for case
in MANDATORY_MICROCASES_BY_QUBITS[qbit_num]:
381 passed = sum(1
for result
in results
if result[
"status"] ==
"pass")
383 pass_rate = 0.0
if total == 0
else passed / total
385 "suite_name": SUITE_NAME,
386 "status":
"pass" if pass_rate == 1.0
else "fail",
387 "backend": PRIMARY_BACKEND,
388 "reference_backend": REFERENCE_BACKEND,
392 "total_cases": total,
393 "passed_cases": passed,
394 "failed_cases": total - passed,
395 "pass_rate": pass_rate,
404 missing_fields = [field
for field
in ARTIFACT_CORE_FIELDS
if field
not in bundle]
407 "Artifact bundle is missing required fields: {}".format(
408 ", ".join(missing_fields)
415 output_path.parent.mkdir(parents=
True, exist_ok=
True)
416 output_path.write_text(json.dumps(bundle, indent=2) +
"\n", encoding=
"utf-8")
420 """Print micro-validation summary.""" 421 print(
"\n" +
"=" * 78)
425 for result
in results:
427 if not result.get(
"energy_pass",
False):
428 details.append(
"energy")
429 if not result.get(
"density_valid_pass",
False):
430 details.append(
"density")
431 if not result.get(
"trace_pass",
False):
432 details.append(
"trace")
433 if not result.get(
"observable_pass",
False):
434 details.append(
"imag")
435 if "error_message" in result:
436 details.append(
"error")
439 " {case_name:<38} [{backend} vs {reference}] " 440 "|ÎE|={energy_error:.3e} status={status}{detail_suffix}".format(
441 case_name=result[
"case_name"],
442 backend=result[
"backend"],
443 reference=result[
"reference_backend"],
444 energy_error=result.get(
"absolute_energy_error", float(
"nan")),
445 status=result[
"status"].upper(),
446 detail_suffix=
"" if not details
else " (" +
",".join(details) +
")",
450 print(
"\n" +
"-" * 78)
452 passed = sum(1
for result
in results
if result[
"status"] ==
"pass")
453 print(f
" Total: {passed}/{total} mandatory microcases passed")
456 print(
"\n ALL TESTS PASSED - micro-validation mandatory micro-validation gate is closed.")
458 print(
"\n Some mandatory microcases failed - micro-validation is not yet closed.")
464 parser = argparse.ArgumentParser(description=__doc__)
469 help=
"Optional directory for the micro-validation JSON artifact bundle.",
471 args = parser.parse_args()
477 if args.output_dir
is not None:
480 if bundle[
"status"] !=
"pass":
484 if __name__ ==
"__main__":
def _microcase_base(case)
def validate_microcase(case, verbose=True)
def parse_microcase_operation
def capture_microcase(case, verbose=True)
def build_microcase_operation_audit(case, operations)
def run_validation(verbose=True)
def build_software_metadata()
def build_artifact_bundle(results)
def print_summary(results)
def validate_artifact_bundle(bundle)
def write_artifact_bundle
def build_threshold_metadata()