Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
noisy_planner_surface_builders.py
Go to the documentation of this file.
1 from __future__ import annotations
2 
3 from typing import Any, Iterable, Mapping, cast
4 
6  qgd_Variational_Quantum_Eigensolver_Base,
7 )
8 
9 from squander.partitioning.noisy_types import (
10  PARTITIONED_DENSITY_MODE,
11  SUPPORTED_GATE_NAMES,
12  SUPPORTED_NOISE_NAMES,
13  SUPPORTED_PLANNER_SOURCE_TYPES,
14  PLANNER_OP_KIND_GATE,
15  PLANNER_OP_KIND_NOISE,
16  PLANNER_OP_KINDS,
17  PlannerOperationKindWire,
18  CanonicalNoisyPlannerOperation,
19  CanonicalNoisyPlannerSurface,
20 )
21 from squander.partitioning.noisy_validation_errors import NoisyPlannerValidationError
22 
23 def _normalize_gate_name(name: str) -> str:
24  normalized = name.strip()
25  aliases = {
26  "u": "U3",
27  "u3": "U3",
28  "cx": "CNOT",
29  "cnot": "CNOT",
30  }
31  return aliases.get(normalized.lower(), normalized.upper())
32 
33 
34 def _normalize_noise_name(name: str) -> str:
35  normalized = name.strip()
36  aliases = {
37  "localdepolarizing": "local_depolarizing",
38  "local_depolarizing": "local_depolarizing",
39  "depolarizing": "depolarizing",
40  "amplitudedamping": "amplitude_damping",
41  "amplitude_damping": "amplitude_damping",
42  "phasedamping": "phase_damping",
43  "phase_damping": "phase_damping",
44  "dephasing": "phase_damping",
45  }
46  return aliases.get(normalized.replace(" ", "").lower(), normalized.lower())
47 
48 
49 def _coerce_operation_kind(value: Any) -> PlannerOperationKindWire:
50  raw_kind = str(value)
51  if raw_kind not in PLANNER_OP_KINDS:
52  raise ValueError(
53  "Unsupported planner kind '{}', expected one of {}".format(
54  raw_kind, sorted(PLANNER_OP_KINDS)
55  )
56  )
57  return cast(PlannerOperationKindWire, raw_kind)
58 
59 
60 def _canonicalize_operation_name(name: str, kind: PlannerOperationKindWire) -> str:
61  if kind == PLANNER_OP_KIND_GATE:
62  return _normalize_gate_name(name)
63  if kind == PLANNER_OP_KIND_NOISE:
64  return _normalize_noise_name(name)
65  return name
66 
67 
68 def _coerce_optional_int(value: Any) -> int | None:
69  if value is None:
70  return None
71  return int(value)
72 
73 
74 def _coerce_optional_float(value: Any) -> float | None:
75  if value is None:
76  return None
77  return float(value)
78 
79 
81  payload: Mapping[str, Any], *, fallback_index: int
82 ) -> CanonicalNoisyPlannerOperation:
83  kind = _coerce_operation_kind(payload["kind"])
84 
85  return CanonicalNoisyPlannerOperation(
86  index=int(payload.get("index", fallback_index)),
87  kind=kind,
88  name=_canonicalize_operation_name(str(payload["name"]), kind),
89  is_unitary=bool(payload["is_unitary"]),
90  source_gate_index=int(payload["source_gate_index"]),
91  target_qbit=_coerce_optional_int(payload.get("target_qbit")),
92  control_qbit=_coerce_optional_int(payload.get("control_qbit")),
93  param_count=int(payload["param_count"]),
94  param_start=int(payload["param_start"]),
95  fixed_value=_coerce_optional_float(payload.get("fixed_value")),
96  )
97 
98 
100  payload: Mapping[str, Any],
101  *,
102  index: int,
103  default_param_start: int,
104  gate_index: int,
105 ) -> CanonicalNoisyPlannerOperation:
106  kind = _coerce_operation_kind(payload["kind"])
107 
108  return CanonicalNoisyPlannerOperation(
109  index=index,
110  kind=kind,
111  name=_canonicalize_operation_name(str(payload["name"]), kind),
112  is_unitary=bool(payload.get("is_unitary", kind == PLANNER_OP_KIND_GATE)),
113  source_gate_index=int(payload.get("source_gate_index", gate_index)),
114  target_qbit=_coerce_optional_int(payload.get("target_qbit")),
115  control_qbit=_coerce_optional_int(payload.get("control_qbit")),
116  param_count=int(payload.get("param_count", 0)),
117  param_start=int(payload.get("param_start", default_param_start)),
118  fixed_value=_coerce_optional_float(payload.get("fixed_value")),
119  )
120 
121 
122 def _validate_mode(requested_mode: str, *, stage: str, source_type: str) -> None:
123  if requested_mode != PARTITIONED_DENSITY_MODE:
124  raise NoisyPlannerValidationError(
125  category="mode",
126  first_unsupported_condition="unsupported_mode",
127  failure_stage=stage,
128  source_type=source_type,
129  requested_mode=requested_mode,
130  reason=(
131  "Phase 3 canonical planner surface supports only '{}' requests, got "
132  "'{}'".format(PARTITIONED_DENSITY_MODE, requested_mode)
133  ),
134  )
135 
136 
138  surface: CanonicalNoisyPlannerSurface,
139  *,
140  stage: str,
141  strict_phase3_support: bool,
142 ) -> CanonicalNoisyPlannerSurface:
143  if surface.qbit_num <= 0:
144  raise ValueError("Canonical planner surface requires qbit_num > 0")
145  if surface.parameter_count < 0:
146  raise ValueError("Canonical planner surface requires parameter_count >= 0")
147  if not surface.operations:
148  raise ValueError("Canonical planner surface requires at least one operation")
149 
150  expected_index = 0
151  gate_count = 0
152  seen_param_starts: list[int] = []
153  for operation in surface.operations:
154  if operation.index != expected_index:
155  raise ValueError(
156  "Canonical planner operations must use contiguous indices starting at 0"
157  )
158  if operation.param_start < 0 or operation.param_count < 0:
159  raise ValueError(
160  "Planner operation parameter metadata must be non-negative"
161  )
162  seen_param_starts.append(operation.param_start)
163 
164  if operation.kind == PLANNER_OP_KIND_GATE:
165  gate_count += 1
166  if operation.target_qbit is None:
167  raise ValueError(
168  "{} planner records must provide target_qbit".format(
169  PLANNER_OP_KIND_GATE
170  )
171  )
172  if strict_phase3_support and operation.name not in SUPPORTED_GATE_NAMES:
173  raise NoisyPlannerValidationError(
174  category="gate_family",
175  first_unsupported_condition=operation.name,
176  failure_stage=stage,
177  source_type=surface.source_type,
178  requested_mode=surface.requested_mode,
179  reason=(
180  "Unsupported Phase 3 planner gate '{}' in canonical surface"
181  ).format(operation.name),
182  )
183  elif operation.kind == PLANNER_OP_KIND_NOISE:
184  if operation.target_qbit is None:
185  raise ValueError(
186  "{} planner records must provide target_qbit".format(
187  PLANNER_OP_KIND_NOISE
188  )
189  )
190  if (
191  operation.source_gate_index < 0
192  or operation.source_gate_index >= gate_count
193  ):
194  raise NoisyPlannerValidationError(
195  category="noise_insertion",
196  first_unsupported_condition="after_gate_index",
197  failure_stage=stage,
198  source_type=surface.source_type,
199  requested_mode=surface.requested_mode,
200  reason=(
201  "Canonical planner noise operation references unsupported "
202  "after_gate_index {}".format(operation.source_gate_index)
203  ),
204  )
205  if strict_phase3_support and operation.name not in SUPPORTED_NOISE_NAMES:
206  raise NoisyPlannerValidationError(
207  category="noise_type",
208  first_unsupported_condition=operation.name,
209  failure_stage=stage,
210  source_type=surface.source_type,
211  requested_mode=surface.requested_mode,
212  reason=(
213  "Unsupported Phase 3 planner noise model '{}' in canonical "
214  "surface"
215  ).format(operation.name),
216  )
217  else:
218  raise ValueError("Unsupported planner kind '{}'".format(operation.kind))
219 
220  expected_index += 1
221 
222  if seen_param_starts != sorted(seen_param_starts):
223  raise ValueError(
224  "Canonical planner operations must preserve monotonically increasing "
225  "parameter starts"
226  )
227 
228  return surface
229 
230 
232  bridge_metadata: Mapping[str, Any],
233  *,
234  workload_id: str,
235  requested_mode: str = PARTITIONED_DENSITY_MODE,
236  source_type: str | None = None,
237  strict_phase3_support: bool = True,
238 ) -> CanonicalNoisyPlannerSurface:
239  resolved_source_type = str(source_type or bridge_metadata["source_type"])
241  requested_mode,
242  stage="planner_entry_from_bridge_metadata_preflight",
243  source_type=resolved_source_type,
244  )
245 
246  operations = tuple(
247  _build_operation_from_mapping(payload, fallback_index=index)
248  for index, payload in enumerate(bridge_metadata["operations"])
249  )
250 
251  surface = CanonicalNoisyPlannerSurface(
252  requested_mode=requested_mode,
253  source_type=resolved_source_type,
254  workload_id=workload_id,
255  qbit_num=int(bridge_metadata["qbit_num"]),
256  parameter_count=int(bridge_metadata["parameter_count"]),
257  operations=operations,
258  )
259  return _validate_surface(
260  surface,
261  stage="planner_entry_from_bridge_metadata_preflight",
262  strict_phase3_support=strict_phase3_support,
263  )
264 
265 
267  *,
268  qbit_num: int,
269  source_type: str,
270  workload_id: str,
271  operation_specs: Iterable[Mapping[str, Any]],
272  requested_mode: str = PARTITIONED_DENSITY_MODE,
273  strict_phase3_support: bool = True,
274 ) -> CanonicalNoisyPlannerSurface:
276  requested_mode,
277  stage="planner_entry_from_operation_specs_preflight",
278  source_type=source_type,
279  )
280 
281  canonical_operations: list[CanonicalNoisyPlannerOperation] = []
282  param_start = 0
283  gate_index = -1
284  for index, payload in enumerate(operation_specs):
285  kind = _coerce_operation_kind(payload["kind"])
286  if kind == PLANNER_OP_KIND_GATE:
287  gate_index += 1
288  operation = _build_operation_from_spec(
289  payload,
290  index=index,
291  default_param_start=param_start,
292  gate_index=gate_index,
293  )
294  canonical_operations.append(operation)
295  param_start = max(param_start, operation.param_start + operation.param_count)
296 
297  surface = CanonicalNoisyPlannerSurface(
298  requested_mode=requested_mode,
299  source_type=source_type,
300  workload_id=workload_id,
301  qbit_num=int(qbit_num),
302  parameter_count=max(
303  (
304  operation.param_start + operation.param_count
305  for operation in canonical_operations
306  ),
307  default=0,
308  ),
309  operations=tuple(canonical_operations),
310  )
311  return _validate_surface(
312  surface,
313  stage="planner_entry_from_operation_specs_preflight",
314  strict_phase3_support=strict_phase3_support,
315  )
316 
317 
319  spec: Mapping[str, Any],
320  *,
321  channel: str,
322  source_type: str,
323  requested_mode: str,
324 ) -> float:
325  if "value" in spec:
326  return float(spec["value"])
327  key_by_channel = {
328  "local_depolarizing": "error_rate",
329  "amplitude_damping": "gamma",
330  "phase_damping": "lambda",
331  }
332  value_key = key_by_channel.get(channel)
333  if value_key is None or value_key not in spec:
334  raise NoisyPlannerValidationError(
335  category="noise_type",
336  first_unsupported_condition=channel,
337  failure_stage="planner_entry_preflight",
338  source_type=source_type,
339  requested_mode=requested_mode,
340  reason=(
341  "Legacy-source planner lowering requires an explicit fixed value for "
342  "noise channel '{}'".format(channel)
343  ),
344  )
345  return float(spec[value_key])
346 
347 
349  density_noise: Iterable[Mapping[str, Any]],
350  *,
351  gate_count: int,
352  source_type: str,
353  requested_mode: str,
354 ) -> list[dict[str, Any]]:
355  normalized_specs: list[dict[str, Any]] = []
356  for spec in density_noise:
357  channel = _normalize_noise_name(str(spec["channel"]))
358  if channel not in SUPPORTED_NOISE_NAMES:
359  raise NoisyPlannerValidationError(
360  category="noise_type",
361  first_unsupported_condition=channel,
362  failure_stage="planner_entry_preflight",
363  source_type=source_type,
364  requested_mode=requested_mode,
365  reason=(
366  "Unsupported Phase 3 planner noise model '{}' in legacy-source "
367  "lowering".format(channel)
368  ),
369  )
370  after_gate_index = int(spec["after_gate_index"])
371  if after_gate_index < 0 or after_gate_index >= gate_count:
372  raise NoisyPlannerValidationError(
373  category="noise_insertion",
374  first_unsupported_condition="after_gate_index",
375  failure_stage="planner_entry_preflight",
376  source_type=source_type,
377  requested_mode=requested_mode,
378  reason=(
379  "Legacy-source planner lowering references unsupported "
380  "after_gate_index {}".format(after_gate_index)
381  ),
382  )
383  normalized_specs.append(
384  {
385  "kind": "noise",
386  "name": channel,
387  "target_qbit": int(spec["target"]),
388  "source_gate_index": after_gate_index,
389  "fixed_value": _extract_legacy_noise_value(
390  spec,
391  channel=channel,
392  source_type=source_type,
393  requested_mode=requested_mode,
394  ),
395  "param_count": 0,
396  }
397  )
398  return normalized_specs
399 
400 
402  legacy_circuit: Any,
403  *,
404  workload_id: str,
405  density_noise: Iterable[Mapping[str, Any]] | None = None,
406  requested_mode: str = PARTITIONED_DENSITY_MODE,
407  source_type: str = "legacy_qgd_circuit_exact",
408  strict_phase3_support: bool = True,
409 ) -> CanonicalNoisyPlannerSurface:
411  requested_mode,
412  stage="planner_entry_preflight",
413  source_type=source_type,
414  )
415 
416  required_methods = ("get_Gates", "get_Qbit_Num")
417  if any(not hasattr(legacy_circuit, method) for method in required_methods):
418  raise NoisyPlannerValidationError(
419  category="source_type",
420  first_unsupported_condition="legacy_source_type",
421  failure_stage="planner_entry_preflight",
422  source_type=source_type,
423  requested_mode=requested_mode,
424  reason=(
425  "Legacy-source planner lowering requires a qgd_Circuit- or "
426  "Gates_block-like object exposing get_Gates() and get_Qbit_Num()"
427  ),
428  )
429 
430  gate_specs: list[dict[str, Any]] = []
431  for gate in legacy_circuit.get_Gates():
432  gate_name = _normalize_gate_name(str(gate.get_Name()))
433  if strict_phase3_support and gate_name not in SUPPORTED_GATE_NAMES:
434  raise NoisyPlannerValidationError(
435  category="gate_family",
436  first_unsupported_condition=gate_name,
437  failure_stage="planner_entry_preflight",
438  source_type=source_type,
439  requested_mode=requested_mode,
440  reason=(
441  "Unsupported Phase 3 planner gate '{}' in legacy-source lowering"
442  ).format(gate_name),
443  )
444  spec = {
445  "kind": "gate",
446  "name": gate_name,
447  "target_qbit": int(gate.get_Target_Qbit()),
448  "param_count": int(gate.get_Parameter_Num()),
449  "param_start": int(gate.get_Parameter_Start_Index()),
450  }
451  if hasattr(gate, "get_Control_Qbit"):
452  control_qbit = int(gate.get_Control_Qbit())
453  if control_qbit >= 0:
454  spec["control_qbit"] = control_qbit
455  gate_specs.append(spec)
456 
457  if not gate_specs:
458  raise NoisyPlannerValidationError(
459  category="source_type",
460  first_unsupported_condition="empty_legacy_circuit",
461  failure_stage="planner_entry_preflight",
462  source_type=source_type,
463  requested_mode=requested_mode,
464  reason="Legacy-source planner lowering requires at least one supported gate",
465  )
466 
467  noise_specs = _normalize_legacy_noise_specs(
468  [] if density_noise is None else density_noise,
469  gate_count=len(gate_specs),
470  source_type=source_type,
471  requested_mode=requested_mode,
472  )
473 
474  noise_by_gate: dict[int, list[dict[str, Any]]] = {}
475  for noise_spec in noise_specs:
476  noise_by_gate.setdefault(noise_spec["source_gate_index"], []).append(noise_spec)
477 
478  operation_specs: list[dict[str, Any]] = []
479  for gate_index, gate_spec in enumerate(gate_specs):
480  operation_specs.append(gate_spec)
481  operation_specs.extend(noise_by_gate.get(gate_index, []))
482 
484  qbit_num=int(legacy_circuit.get_Qbit_Num()),
485  source_type=source_type,
486  workload_id=workload_id,
487  operation_specs=operation_specs,
488  requested_mode=requested_mode,
489  strict_phase3_support=strict_phase3_support,
490  )
491 
492 
494  circuit: Any,
495  *,
496  workload_id: str,
497  density_noise: Iterable[Mapping[str, Any]] | None = None,
498  requested_mode: str = PARTITIONED_DENSITY_MODE,
499  strict_phase3_support: bool = True,
500 ) -> CanonicalNoisyPlannerSurface:
502  circuit,
503  workload_id=workload_id,
504  density_noise=density_noise,
505  requested_mode=requested_mode,
506  source_type="legacy_qgd_circuit_exact",
507  strict_phase3_support=strict_phase3_support,
508  )
509 
510 
512  *,
513  source_type: str,
514  workload_id: str,
515  requested_mode: str = PARTITIONED_DENSITY_MODE,
516  bridge_metadata: Mapping[str, Any] | None = None,
517  operation_specs: Iterable[Mapping[str, Any]] | None = None,
518  qbit_num: int | None = None,
519  legacy_circuit: Any | None = None,
520  density_noise: Iterable[Mapping[str, Any]] | None = None,
521  strict_phase3_support: bool = True,
522 ) -> CanonicalNoisyPlannerSurface:
523  resolved_source_type = str(source_type)
524  if resolved_source_type not in SUPPORTED_PLANNER_SOURCE_TYPES:
525  raise NoisyPlannerValidationError(
526  category="source_type",
527  first_unsupported_condition=resolved_source_type,
528  failure_stage="planner_entry_preflight",
529  source_type=resolved_source_type,
530  requested_mode=requested_mode,
531  reason=(
532  "Unsupported Phase 3 planner source type '{}'".format(
533  resolved_source_type
534  )
535  ),
536  )
537 
538  provided_payload_count = sum(
539  payload is not None
540  for payload in (bridge_metadata, operation_specs, legacy_circuit)
541  )
542  if provided_payload_count != 1:
543  raise NoisyPlannerValidationError(
544  category="malformed_request",
545  first_unsupported_condition=(
546  "missing_source_payload"
547  if provided_payload_count == 0
548  else "ambiguous_source_payload"
549  ),
550  failure_stage="planner_entry_preflight",
551  source_type=resolved_source_type,
552  requested_mode=requested_mode,
553  reason=(
554  "Phase 3 planner preflight requires exactly one source payload "
555  "(bridge_metadata, operation_specs, or legacy_circuit)"
556  ),
557  )
558 
559  if bridge_metadata is not None:
561  bridge_metadata,
562  workload_id=workload_id,
563  requested_mode=requested_mode,
564  source_type=resolved_source_type,
565  strict_phase3_support=strict_phase3_support,
566  )
567 
568  if operation_specs is not None:
569  if qbit_num is None:
570  raise NoisyPlannerValidationError(
571  category="malformed_request",
572  first_unsupported_condition="missing_qbit_num",
573  failure_stage="planner_entry_preflight",
574  source_type=resolved_source_type,
575  requested_mode=requested_mode,
576  reason="Operation-spec planner preflight requires qbit_num",
577  )
579  qbit_num=qbit_num,
580  source_type=resolved_source_type,
581  workload_id=workload_id,
582  operation_specs=operation_specs,
583  requested_mode=requested_mode,
584  strict_phase3_support=strict_phase3_support,
585  )
586 
588  legacy_circuit,
589  workload_id=workload_id,
590  density_noise=density_noise,
591  requested_mode=requested_mode,
592  source_type=resolved_source_type,
593  strict_phase3_support=strict_phase3_support,
594  )
595 
596 
597 
604  vqe: qgd_Variational_Quantum_Eigensolver_Base,
605  *,
606  workload_id: str | None = None,
607  requested_mode: str = PARTITIONED_DENSITY_MODE,
608 ) -> CanonicalNoisyPlannerSurface:
609  bridge_metadata = vqe.describe_density_bridge()
610  resolved_workload_id = workload_id or "phase2_xxz_hea_q{}_continuity".format(
611  bridge_metadata["qbit_num"]
612  )
614  bridge_metadata,
615  workload_id=resolved_workload_id,
616  requested_mode=requested_mode,
617  strict_phase3_support=True,
618  )
619 
620 
622  surface: CanonicalNoisyPlannerSurface,
623  *,
624  metadata: Mapping[str, Any] | None = None,
625 ) -> dict[str, Any]:
626  payload = surface.to_dict()
627  return {
628  "requested_mode": payload["requested_mode"],
629  "provenance": payload["provenance"],
630  "summary": {
631  "qbit_num": payload["qbit_num"],
632  "parameter_count": payload["parameter_count"],
633  "operation_count": payload["operation_count"],
634  "gate_count": payload["gate_count"],
635  "noise_count": payload["noise_count"],
636  "gate_sequence": payload["gate_sequence"],
637  "noise_sequence": payload["noise_sequence"],
638  "max_qubit_span": payload["max_qubit_span"],
639  },
640  "operations": payload["operations"],
641  "metadata": dict(metadata) if metadata is not None else {},
642  }
643 
644 
645 
651  surface: CanonicalNoisyPlannerSurface, bridge_metadata: Mapping[str, Any]
652 ) -> dict[str, Any]:
653  payload = surface.to_dict()
654  mismatches: list[dict[str, Any]] = []
655 
656  for key in ("parameter_count", "operation_count", "gate_count", "noise_count"):
657  if payload[key] != bridge_metadata[key]:
658  mismatches.append(
659  {
660  "kind": "summary",
661  "field": key,
662  "actual": payload[key],
663  "expected": bridge_metadata[key],
664  }
665  )
666 
667  for index, (actual, expected) in enumerate(
668  zip(payload["operations"], bridge_metadata["operations"])
669  ):
670  for key in (
671  "kind",
672  "name",
673  "is_unitary",
674  "source_gate_index",
675  "target_qbit",
676  "control_qbit",
677  "param_count",
678  "param_start",
679  "fixed_value",
680  ):
681  if actual[key] != expected[key]:
682  mismatches.append(
683  {
684  "kind": "operation",
685  "index": index,
686  "field": key,
687  "actual": actual[key],
688  "expected": expected[key],
689  }
690  )
691  expected_support = [
692  q
693  for q in (expected["control_qbit"], expected["target_qbit"])
694  if q is not None
695  ]
696  if actual["qubit_support"] != expected_support:
697  mismatches.append(
698  {
699  "kind": "operation",
700  "index": index,
701  "field": "qubit_support",
702  "actual": actual["qubit_support"],
703  "expected": expected_support,
704  }
705  )
706 
707  return {
708  "bridge_overlap_pass": not mismatches,
709  "mismatches": mismatches,
710  "compared_operation_count": min(
711  len(payload["operations"]), len(bridge_metadata["operations"])
712  ),
713  }
def build_bridge_overlap_report
Build a bridge overlap report from a canonical planner surface and a bridge metadata.
def build_phase3_continuity_planner_surface
Build a canonical planner surface from a Phase 2 continuity VQE instance.