Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
bridge_validation.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 """Validation: bridge micro-validation matrix.
3 
4 Validates the supported VQE-side density bridge on deterministic 1 to 3 qubit
5 microcases. The goal is not observable-threshold closure, but proof that the
6 required generated-`HEA` bridge surface lowers cleanly into the supported
7 `NoisyCircuit` vocabulary and remains execution-ready on small cases.
8 
9 Run with:
10  python benchmarks/density_matrix/bridge_scope/bridge_validation.py
11 """
12 
13 from __future__ import annotations
14 
15 import argparse
16 import json
17 import math
18 import sys
19 from pathlib import Path
20 
21 import numpy as np
22 
23 REPO_ROOT = Path(__file__).resolve().parents[3]
24 if str(REPO_ROOT) not in sys.path:
25  sys.path.insert(0, str(REPO_ROOT))
26 
27 from examples.VQE.shot_noise_measurement import generate_zz_xx_hamiltonian
28 from benchmarks.density_matrix.workflow_evidence.exact_density_vqe_validation import (
29  DEFAULT_ANSATZ,
30  DEFAULT_INNER_BLOCKS,
31  DEFAULT_LAYERS,
32  PRIMARY_BACKEND,
33  build_case_metadata,
34  build_software_metadata,
35  build_reference_bridge_metadata,
36  build_optimizer_config,
37  build_hamiltonian_metadata,
38  build_initial_parameters,
39  build_open_chain_topology,
40 )
41 from squander import Variational_Quantum_Eigensolver
42 from squander.partitioning.noisy_planner import (
43  PLANNER_OP_KIND_GATE,
44  PLANNER_OP_KIND_NOISE,
45 )
46 
47 SUITE_NAME = "bridge_micro_validation"
48 ARTIFACT_FILENAME = "bridge_micro_validation_bundle.json"
49 ARTIFACT_CORE_FIELDS = (
50  "suite_name",
51  "status",
52  "backend",
53  "requirements",
54  "summary",
55  "software",
56  "cases",
57 )
58 
59 MANDATORY_BRIDGE_MICROCASES = (
60  {
61  "case_name": "bridge_1q_local_depolarizing_after_u3",
62  "qbit_num": 1,
63  "purpose": "Verify the generated-HEA bridge lowers a required U3-only microcase with local depolarizing noise.",
64  "required_gate_families": ["U3"],
65  "required_noise_models": ["local_depolarizing"],
66  "density_noise": [
67  {
68  "channel": "local_depolarizing",
69  "target": 0,
70  "after_gate_index": 0,
71  "error_rate": 0.1,
72  }
73  ],
74  "requires_cnot": False,
75  },
76  {
77  "case_name": "bridge_1q_amplitude_damping_after_u3",
78  "qbit_num": 1,
79  "purpose": "Verify the generated-HEA bridge lowers a required U3-only microcase with amplitude damping noise.",
80  "required_gate_families": ["U3"],
81  "required_noise_models": ["amplitude_damping"],
82  "density_noise": [
83  {
84  "channel": "amplitude_damping",
85  "target": 0,
86  "after_gate_index": 0,
87  "gamma": 0.05,
88  }
89  ],
90  "requires_cnot": False,
91  },
92  {
93  "case_name": "bridge_1q_phase_damping_after_u3",
94  "qbit_num": 1,
95  "purpose": "Verify the generated-HEA bridge lowers a required U3-only microcase with phase damping noise.",
96  "required_gate_families": ["U3"],
97  "required_noise_models": ["phase_damping"],
98  "density_noise": [
99  {
100  "channel": "phase_damping",
101  "target": 0,
102  "after_gate_index": 0,
103  "lambda": 0.07,
104  }
105  ],
106  "requires_cnot": False,
107  },
108  {
109  "case_name": "bridge_2q_cnot_with_local_depolarizing",
110  "qbit_num": 2,
111  "purpose": "Verify the required CNOT bridge slice lowers cleanly on a 2-qubit anchor microcase.",
112  "required_gate_families": ["U3", "CNOT"],
113  "required_noise_models": ["local_depolarizing"],
114  "density_noise": [
115  {
116  "channel": "local_depolarizing",
117  "target": 0,
118  "after_gate_index": 2,
119  "error_rate": 0.1,
120  }
121  ],
122  "requires_cnot": True,
123  },
124  {
125  "case_name": "bridge_3q_mixed_local_noise_sequence",
126  "qbit_num": 3,
127  "purpose": "Verify mixed required local-noise insertion remains ordered and auditable on a 3-qubit bridge microcase.",
128  "required_gate_families": ["U3", "CNOT"],
129  "required_noise_models": [
130  "local_depolarizing",
131  "amplitude_damping",
132  "phase_damping",
133  ],
134  "density_noise": [
135  {
136  "channel": "local_depolarizing",
137  "target": 0,
138  "after_gate_index": 0,
139  "error_rate": 0.1,
140  },
141  {
142  "channel": "amplitude_damping",
143  "target": 1,
144  "after_gate_index": 1,
145  "gamma": 0.05,
146  },
147  {
148  "channel": "phase_damping",
149  "target": 0,
150  "after_gate_index": 2,
151  "lambda": 0.07,
152  },
153  ],
154  "requires_cnot": True,
155  },
156 )
157 
158 
160  return {
161  "source_type": "generated_hea",
162  "required_gate_families": ["U3", "CNOT"],
163  "required_noise_models": [
164  "local_depolarizing",
165  "amplitude_damping",
166  "phase_damping",
167  ],
168  "microcase_qubits": [1, 2, 3],
169  "required_pass_rate": 1.0,
170  "canonical_bridge_fields": [
171  "bridge_source_type",
172  "bridge_parameter_count",
173  "bridge_operation_count",
174  "bridge_gate_count",
175  "bridge_noise_count",
176  "bridge_operations",
177  ],
178  }
179 
180 
181 def build_bridge_vqe(qbit_num: int, density_noise):
182  topology = build_open_chain_topology(qbit_num)
183  hamiltonian, _ = generate_zz_xx_hamiltonian(
184  n_qubits=qbit_num,
185  h=0.5,
186  topology=topology,
187  Jz=1.0,
188  Jx=1.0,
189  Jy=1.0,
190  )
191  vqe = Variational_Quantum_Eigensolver(
192  hamiltonian,
193  qbit_num,
195  backend=PRIMARY_BACKEND,
196  density_noise=density_noise,
197  )
198  vqe.set_Ansatz(DEFAULT_ANSATZ)
199  vqe.Generate_Circuit(layers=DEFAULT_LAYERS, inner_blocks=DEFAULT_INNER_BLOCKS)
200  parameters = build_initial_parameters(vqe.get_Parameter_Num())
201  vqe.set_Optimized_Parameters(parameters)
202  return vqe, hamiltonian, topology, parameters
203 
204 
206  base_circuit = vqe.get_Qiskit_Circuit()
207  noise_by_gate = {}
208  for noise_spec in vqe.density_noise:
209  noise_by_gate.setdefault(noise_spec["after_gate_index"], []).append(noise_spec)
210 
211  expected = []
212  param_start = 0
213  for gate_index, instruction in enumerate(base_circuit.data):
214  qubit_indices = [base_circuit.find_bit(qubit).index for qubit in instruction.qubits]
215  gate_name = instruction.operation.name
216 
217  if gate_name == "u":
218  expected.append(
219  {
220  "index": len(expected),
221  "kind": PLANNER_OP_KIND_GATE,
222  "name": "U3",
223  "is_unitary": True,
224  "source_gate_index": gate_index,
225  "target_qbit": qubit_indices[0],
226  "control_qbit": None,
227  "param_count": 3,
228  "param_start": param_start,
229  "fixed_value": None,
230  }
231  )
232  param_start += 3
233  elif gate_name in {"cx", "cnot"}:
234  expected.append(
235  {
236  "index": len(expected),
237  "kind": PLANNER_OP_KIND_GATE,
238  "name": "CNOT",
239  "is_unitary": True,
240  "source_gate_index": gate_index,
241  "target_qbit": qubit_indices[1],
242  "control_qbit": qubit_indices[0],
243  "param_count": 0,
244  "param_start": param_start,
245  "fixed_value": None,
246  }
247  )
248  else:
249  raise ValueError(
250  f"Unsupported bridge micro-validation gate in expected bridge: {gate_name}"
251  )
252 
253  for noise_spec in noise_by_gate.get(gate_index, []):
254  expected.append(
255  {
256  "index": len(expected),
257  "kind": PLANNER_OP_KIND_NOISE,
258  "name": noise_spec["channel"],
259  "is_unitary": False,
260  "source_gate_index": gate_index,
261  "target_qbit": noise_spec["target"],
262  "control_qbit": None,
263  "param_count": 0,
264  "param_start": param_start,
265  "fixed_value": noise_spec["value"],
266  }
267  )
268 
269  return expected
270 
271 
272 def bridge_operations_match(actual_operations, expected_operations):
273  if len(actual_operations) != len(expected_operations):
274  return False
275 
276  for actual, expected in zip(actual_operations, expected_operations):
277  for key in (
278  "index",
279  "kind",
280  "name",
281  "is_unitary",
282  "source_gate_index",
283  "target_qbit",
284  "control_qbit",
285  "param_count",
286  "param_start",
287  ):
288  if actual.get(key) != expected.get(key):
289  return False
290 
291  actual_fixed = actual.get("fixed_value")
292  expected_fixed = expected.get("fixed_value")
293  if expected_fixed is None:
294  if actual_fixed is not None:
295  return False
296  elif actual_fixed is None or not math.isclose(actual_fixed, expected_fixed, rel_tol=0.0, abs_tol=1e-12):
297  return False
298 
299  return True
300 
301 
302 def _case_base(case, topology):
303  metadata = build_case_metadata(
304  backend=PRIMARY_BACKEND,
305  qbit_num=case["qbit_num"],
306  topology=topology,
307  density_noise=case["density_noise"],
308  hamiltonian=build_hamiltonian_metadata(),
309  )
310  metadata.update(
311  {
312  "case_name": case["case_name"],
313  "case_kind": "bridge_micro_validation",
314  "purpose": case["purpose"],
315  "required_gate_families": case["required_gate_families"],
316  "required_noise_models": case["required_noise_models"],
317  }
318  )
319  return metadata
320 
321 
322 def validate_bridge_microcase(case, verbose=True):
323  vqe, _, topology, parameters = build_bridge_vqe(case["qbit_num"], case["density_noise"])
324  bridge = vqe.describe_density_bridge()
325  expected_operations = build_expected_bridge_operations(vqe)
326  energy = float(vqe.Optimization_Problem(parameters))
327 
328  gate_names = [op["name"] for op in bridge["operations"] if op["kind"] == "gate"]
329  noise_names = [op["name"] for op in bridge["operations"] if op["kind"] == "noise"]
330  source_pass = bridge["source_type"] == "generated_hea"
331  gate_pass = "U3" in gate_names and (
332  (not case["requires_cnot"]) or ("CNOT" in gate_names)
333  )
334  noise_pass = all(
335  required_model in noise_names for required_model in case["required_noise_models"]
336  )
337  operation_match_pass = bridge_operations_match(
338  bridge["operations"], expected_operations
339  )
340  execution_ready = bool(np.isfinite(energy))
341  bridge_pass = (
342  source_pass
343  and gate_pass
344  and noise_pass
345  and operation_match_pass
346  and execution_ready
347  )
348 
349  result = _case_base(case, topology)
350  result.update(
351  {
352  "status": "pass" if bridge_pass else "fail",
353  "parameter_vector": parameters.tolist(),
354  "execution_energy": energy,
355  "execution_ready": execution_ready,
356  "source_pass": source_pass,
357  "gate_pass": gate_pass,
358  "noise_pass": noise_pass,
359  "operation_match_pass": operation_match_pass,
360  "expected_operation_count": len(expected_operations),
361  "expected_bridge_operations": expected_operations,
363  }
364  )
365 
366  if verbose:
367  print(
368  " {case_name:<46} q={qbit_num} ops={ops} noise={noise} status={status}".format(
369  case_name=result["case_name"],
370  qbit_num=result["qbit_num"],
371  ops=result["bridge_operation_count"],
372  noise=result["bridge_noise_count"],
373  status=result["status"].upper(),
374  )
375  )
376 
377  return result
378 
379 
380 def capture_bridge_microcase(case, verbose=True):
381  try:
382  return validate_bridge_microcase(case, verbose=verbose)
383  except Exception as exc:
384  topology = build_open_chain_topology(case["qbit_num"])
385  result = _case_base(case, topology)
386  result.update(
387  {
388  "status": "fail",
389  "execution_ready": False,
390  "source_pass": False,
391  "gate_pass": False,
392  "noise_pass": False,
393  "operation_match_pass": False,
394  "error_message": str(exc),
395  }
396  )
397  if verbose:
398  print(
399  " {case_name:<46} q={qbit_num} ERROR {message}".format(
400  case_name=result["case_name"],
401  qbit_num=result["qbit_num"],
402  message=result["error_message"],
403  )
404  )
405  return result
406 
407 
408 def run_validation(verbose=True):
409  print("=" * 78)
410  print(" Bridge Micro-Validation [{}]".format(PRIMARY_BACKEND))
411  print("=" * 78)
412 
413  results = []
414  for case in MANDATORY_BRIDGE_MICROCASES:
415  results.append(capture_bridge_microcase(case, verbose=verbose))
416  return results
417 
418 
420  passed = sum(1 for result in results if result["status"] == "pass")
421  total = len(results)
422  pass_rate = 0.0 if total == 0 else passed / total
423  bundle = {
424  "suite_name": SUITE_NAME,
425  "status": "pass" if pass_rate == 1.0 else "fail",
426  "backend": PRIMARY_BACKEND,
427  "requirements": build_requirement_metadata(),
428  "software": build_software_metadata(),
429  "summary": {
430  "total_cases": total,
431  "passed_cases": passed,
432  "failed_cases": total - passed,
433  "pass_rate": pass_rate,
434  "cases_per_qbit": {
435  str(qbit_num): sum(
436  1 for result in results if result["qbit_num"] == qbit_num
437  )
438  for qbit_num in sorted({case["qbit_num"] for case in MANDATORY_BRIDGE_MICROCASES})
439  },
440  },
441  "cases": results,
442  }
444  return bundle
445 
446 
448  missing_fields = [field for field in ARTIFACT_CORE_FIELDS if field not in bundle]
449  if missing_fields:
450  raise ValueError(
451  "Artifact bundle is missing required fields: {}".format(
452  ", ".join(missing_fields)
453  )
454  )
455 
456 
457 def write_artifact_bundle(output_path: Path, bundle):
459  output_path.parent.mkdir(parents=True, exist_ok=True)
460  output_path.write_text(json.dumps(bundle, indent=2) + "\n", encoding="utf-8")
461 
462 
463 def print_summary(bundle):
464  print("\n" + "=" * 78)
465  print(" SUMMARY")
466  print("=" * 78)
467  for result in bundle["cases"]:
468  details = []
469  if not result.get("source_pass", False):
470  details.append("source")
471  if not result.get("gate_pass", False):
472  details.append("gate")
473  if not result.get("noise_pass", False):
474  details.append("noise")
475  if not result.get("operation_match_pass", False):
476  details.append("shape")
477  if not result.get("execution_ready", False):
478  details.append("exec")
479  if "error_message" in result:
480  details.append("error")
481 
482  print(
483  " {case_name:<46} q={qbit_num} status={status}{detail_suffix}".format(
484  case_name=result["case_name"],
485  qbit_num=result["qbit_num"],
486  status=result["status"].upper(),
487  detail_suffix="" if not details else " (" + ",".join(details) + ")",
488  )
489  )
490 
491  print("\n" + "-" * 78)
492  print(
493  " Total: {passed}/{total} mandatory bridge microcases passed".format(
494  passed=bundle["summary"]["passed_cases"],
495  total=bundle["summary"]["total_cases"],
496  )
497  )
498  if bundle["status"] == "pass":
499  print("\n ALL TESTS PASSED - Bridge micro-validation gate is closed.")
500  else:
501  print("\n Some mandatory bridge microcases failed - bridge micro-validation is not yet closed.")
502  print("=" * 78)
503 
504 
505 def main():
506  parser = argparse.ArgumentParser(description=__doc__)
507  parser.add_argument(
508  "--output-dir",
509  type=Path,
510  default=None,
511  help="Optional directory for the bridge micro-validation JSON artifact bundle.",
512  )
513  args = parser.parse_args()
514 
515  results = run_validation()
516  bundle = build_artifact_bundle(results)
517  print_summary(bundle)
518 
519  if args.output_dir is not None:
520  write_artifact_bundle(args.output_dir / ARTIFACT_FILENAME, bundle)
521 
522  if bundle["status"] != "pass":
523  raise SystemExit(1)
524 
525 
526 if __name__ == "__main__":
527  main()
def build_optimizer_config()
def build_open_chain_topology
def _case_base(case, topology)
def validate_artifact_bundle(bundle)
def capture_bridge_microcase(case, verbose=True)
def build_artifact_bundle(results)
def validate_bridge_microcase(case, verbose=True)
def run_validation(verbose=True)
def build_software_metadata()
def bridge_operations_match(actual_operations, expected_operations)
def build_expected_bridge_operations(vqe)
def print_summary(bundle)