Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
noisy_descriptor.py
Go to the documentation of this file.
1 from __future__ import annotations
2 
3 from typing import Any, Iterable, Mapping
4 
5 from squander.partitioning.noisy_planner_surface_builders import (
6  build_phase3_continuity_planner_surface,
7  preflight_planner_request,
8 )
9 from squander.partitioning.noisy_types import (
10  DEFAULT_PARTITION_DESCRIPTOR_MAX_QUBITS,
11  PARTITIONED_DENSITY_MODE,
12  PLANNER_OP_KIND_GATE,
13  PLANNER_OP_KIND_NOISE,
14  CanonicalNoisyPlannerOperation,
15  CanonicalNoisyPlannerSurface,
16  NoisyPartitionDescriptor,
17  NoisyPartitionDescriptorMember,
18  NoisyPartitionDescriptorSet,
19 )
20 from squander.partitioning.noisy_validation_errors import NoisyDescriptorValidationError
21 
23  descriptor_set: NoisyPartitionDescriptorSet,
24  member: NoisyPartitionDescriptorMember,
25 ) -> dict[str, Any]:
26  op = descriptor_set.canonical_operation_for(member)
27  return {
28  "partition_member_index": member.partition_member_index,
29  "canonical_operation_index": member.canonical_operation_index,
30  "kind": op.kind,
31  "name": op.name,
32  "is_unitary": op.is_unitary,
33  "source_gate_index": op.source_gate_index,
34  "target_qbit": op.target_qbit,
35  "control_qbit": op.control_qbit,
36  "qubit_support": list(op.qubit_support),
37  "local_target_qbit": member.local_target_qbit,
38  "local_control_qbit": member.local_control_qbit,
39  "local_qubit_support": list(member.local_qubit_support),
40  "param_count": op.param_count,
41  "param_start": op.param_start,
42  "local_param_start": member.local_param_start,
43  "fixed_value": op.fixed_value,
44  }
45 
46 
48  descriptor_set: NoisyPartitionDescriptorSet,
49  partition: NoisyPartitionDescriptor,
50 ) -> dict[str, Any]:
51  routing = descriptor_set.partition_parameter_routing(partition)
52  return {
53  "partition_index": partition.partition_index,
54  "member_count": partition.member_count,
55  "gate_count": descriptor_set.partition_gate_count(partition),
56  "noise_count": descriptor_set.partition_noise_count(partition),
57  "canonical_operation_indices": list(
58  descriptor_set.partition_canonical_operation_indices(partition)
59  ),
60  # partition_qubit_span duplicates local_to_global_qbits for frozen JSON compatibility.
61  "partition_qubit_span": list(partition.partition_qubit_span),
62  "local_to_global_qbits": list(partition.local_to_global_qbits),
63  "global_to_local_qbits": [
64  {"global_qbit": global_qbit, "local_qbit": local_qbit}
65  for global_qbit, local_qbit in partition.global_to_local_qbits
66  ],
67  "requires_remap": partition.requires_remap,
68  "partition_parameter_count": descriptor_set.partition_parameter_count(partition),
69  "parameter_routing": [
70  {
71  "global_param_start": global_param_start,
72  "local_param_start": local_param_start,
73  "param_count": param_count,
74  }
75  for global_param_start, local_param_start, param_count in routing
76  ],
77  "members": [m.to_dict(descriptor_set) for m in partition.members],
78  }
79 
81  surface: CanonicalNoisyPlannerSurface, *, max_partition_qubits: int
82 ) -> None:
83  if max_partition_qubits <= 0:
84  raise NoisyDescriptorValidationError(
85  category="descriptor_request",
86  first_unsupported_condition="max_partition_qubits",
87  failure_stage="descriptor_generation_preflight",
88  source_type=surface.source_type,
89  requested_mode=surface.requested_mode,
90  workload_id=surface.workload_id,
91  reason="Partition descriptor generation requires max_partition_qubits > 0",
92  )
93  if surface.max_qubit_span > max_partition_qubits:
94  raise NoisyDescriptorValidationError(
95  category="partition_span",
96  first_unsupported_condition="max_partition_qubits",
97  failure_stage="descriptor_generation_preflight",
98  source_type=surface.source_type,
99  requested_mode=surface.requested_mode,
100  workload_id=surface.workload_id,
101  reason=(
102  "Partition descriptor generation requires max_partition_qubits >= "
103  "the canonical max_qubit_span {} for workload '{}'".format(
104  surface.max_qubit_span, surface.workload_id
105  )
106  ),
107  )
108 
109 
110 
122  surface: CanonicalNoisyPlannerSurface, *, max_partition_qubits: int
123 ) -> tuple[tuple[CanonicalNoisyPlannerOperation, ...], ...]:
124  partitions: list[tuple[CanonicalNoisyPlannerOperation, ...]] = []
125  current_partition: list[CanonicalNoisyPlannerOperation] = []
126  current_support: set[int] = set()
127 
128  for operation in surface.operations:
129  operation_support = set(operation.qubit_support)
130  expanded_support = current_support | operation_support
131  if current_partition and len(expanded_support) > max_partition_qubits:
132  partitions.append(tuple(current_partition))
133  current_partition = []
134  current_support = set()
135  current_partition.append(operation)
136  current_support |= operation_support
137 
138  if current_partition:
139  partitions.append(tuple(current_partition))
140 
141  return tuple(partitions)
142 
143 
145  operations: tuple[CanonicalNoisyPlannerOperation, ...], *, partition_index: int
146 ) -> NoisyPartitionDescriptor:
147  local_to_global_qbits = tuple(
148  sorted({qbit for operation in operations for qbit in operation.qubit_support})
149  )
150  global_to_local = {
151  global_qbit: local_qbit
152  for local_qbit, global_qbit in enumerate(local_to_global_qbits)
153  }
154 
155  members: list[NoisyPartitionDescriptorMember] = []
156  local_param_start = 0
157 
158  for partition_member_index, operation in enumerate(operations):
159  member = NoisyPartitionDescriptorMember(
160  partition_member_index=partition_member_index,
161  canonical_operation_index=operation.index,
162  local_target_qbit=(
163  None
164  if operation.target_qbit is None
165  else global_to_local[operation.target_qbit]
166  ),
167  local_control_qbit=(
168  None
169  if operation.control_qbit is None
170  else global_to_local[operation.control_qbit]
171  ),
172  local_qubit_support=tuple(
173  global_to_local[qbit] for qbit in operation.qubit_support
174  ),
175  local_param_start=local_param_start,
176  )
177  members.append(member)
178  local_param_start += operation.param_count
179 
180  return NoisyPartitionDescriptor(
181  partition_index=partition_index,
182  local_to_global_qbits=local_to_global_qbits,
183  members=tuple(members),
184  )
185 
186 
188  surface: CanonicalNoisyPlannerSurface,
189  *,
190  max_partition_qubits: int = DEFAULT_PARTITION_DESCRIPTOR_MAX_QUBITS,
191 ) -> NoisyPartitionDescriptorSet:
192  _validate_descriptor_request(surface, max_partition_qubits=max_partition_qubits)
193  partitions = tuple(
195  partition_operations, partition_index=partition_index
196  )
197  for partition_index, partition_operations in enumerate(
199  surface, max_partition_qubits=max_partition_qubits
200  )
201  )
202  )
203  descriptor_set = NoisyPartitionDescriptorSet(
204  requested_mode=surface.requested_mode,
205  source_type=surface.source_type,
206  workload_id=surface.workload_id,
207  qbit_num=surface.qbit_num,
208  parameter_count=surface.parameter_count,
209  max_partition_qubits=max_partition_qubits,
210  operations=surface.operations,
211  partitions=partitions,
212  )
213  return validate_partition_descriptor_set_against_surface(surface, descriptor_set)
214 
215 
217  vqe,
218  *,
219  workload_id: str | None = None,
220  requested_mode: str = PARTITIONED_DENSITY_MODE,
221  max_partition_qubits: int = DEFAULT_PARTITION_DESCRIPTOR_MAX_QUBITS,
222 ) -> NoisyPartitionDescriptorSet:
224  vqe,
225  workload_id=workload_id,
226  requested_mode=requested_mode,
227  )
229  surface, max_partition_qubits=max_partition_qubits
230  )
231 
232 
234  *,
235  source_type: str,
236  workload_id: str,
237  requested_mode: str = PARTITIONED_DENSITY_MODE,
238  bridge_metadata: Mapping[str, Any] | None = None,
239  operation_specs: Iterable[Mapping[str, Any]] | None = None,
240  qbit_num: int | None = None,
241  legacy_circuit: Any | None = None,
242  density_noise: Iterable[Mapping[str, Any]] | None = None,
243  strict_phase3_support: bool = True,
244  max_partition_qubits: int = DEFAULT_PARTITION_DESCRIPTOR_MAX_QUBITS,
245 ) -> NoisyPartitionDescriptorSet:
246  surface = preflight_planner_request(
247  source_type=source_type,
248  workload_id=workload_id,
249  requested_mode=requested_mode,
250  bridge_metadata=bridge_metadata,
251  operation_specs=operation_specs,
252  qbit_num=qbit_num,
253  legacy_circuit=legacy_circuit,
254  density_noise=density_noise,
255  strict_phase3_support=strict_phase3_support,
256  )
258  surface, max_partition_qubits=max_partition_qubits
259  )
260 
261 
263  descriptor_set: NoisyPartitionDescriptorSet,
264  *,
265  category: str,
266  first_unsupported_condition: str,
267  failure_stage: str,
268  reason: str,
269 ) -> NoisyDescriptorValidationError:
270  return NoisyDescriptorValidationError(
271  category=category,
272  first_unsupported_condition=first_unsupported_condition,
273  failure_stage=failure_stage,
274  source_type=descriptor_set.source_type,
275  requested_mode=descriptor_set.requested_mode,
276  workload_id=descriptor_set.workload_id,
277  reason=reason,
278  )
279 
280 
282  descriptor_set: NoisyPartitionDescriptorSet,
283 ) -> NoisyPartitionDescriptorSet:
284  expected_partition_index = 0
285  expected_canonical_index = 0
286 
287  if len(descriptor_set.operations) != descriptor_set.descriptor_member_count:
288  raise _descriptor_error(
289  descriptor_set,
290  category="dropped_operations",
291  first_unsupported_condition="operations_table_size",
292  failure_stage="descriptor_validation",
293  reason=(
294  "Descriptor operations table length {} does not match "
295  "descriptor_member_count {}".format(
296  len(descriptor_set.operations),
297  descriptor_set.descriptor_member_count,
298  )
299  ),
300  )
301 
302  for partition in descriptor_set.partitions:
303  if partition.partition_index != expected_partition_index:
304  raise _descriptor_error(
305  descriptor_set,
306  category="reordering_across_noise_boundaries",
307  first_unsupported_condition="partition_index",
308  failure_stage="descriptor_validation",
309  reason=(
310  "Partition descriptors require contiguous partition indices starting "
311  "at 0; got partition_index {} while expecting {}".format(
312  partition.partition_index, expected_partition_index
313  )
314  ),
315  )
316  if not partition.members:
317  raise _descriptor_error(
318  descriptor_set,
319  category="descriptor_request",
320  first_unsupported_condition="empty_partition",
321  failure_stage="descriptor_validation",
322  reason="Partition descriptors do not support empty partitions",
323  )
324 
325  if len(set(partition.local_to_global_qbits)) != len(
326  partition.local_to_global_qbits
327  ):
328  raise _descriptor_error(
329  descriptor_set,
330  category="incomplete_remapping",
331  first_unsupported_condition="duplicate_global_qbit",
332  failure_stage="descriptor_validation",
333  reason=(
334  "Partition descriptor partition {} contains duplicate entries in "
335  "local_to_global_qbits".format(partition.partition_index)
336  ),
337  )
338 
339  global_to_local = {
340  global_qbit: local_qbit
341  for local_qbit, global_qbit in enumerate(partition.local_to_global_qbits)
342  }
343  expected_parameter_routing: list[tuple[int, int, int]] = []
344  expected_local_param_start = 0
345 
346  for member_index, member in enumerate(partition.members):
347  if member.partition_member_index != member_index:
348  raise _descriptor_error(
349  descriptor_set,
350  category="reordering_across_noise_boundaries",
351  first_unsupported_condition="partition_member_index",
352  failure_stage="descriptor_validation",
353  reason=(
354  "Partition descriptor partition {} requires contiguous "
355  "partition_member_index values".format(
356  partition.partition_index
357  )
358  ),
359  )
360  if member.canonical_operation_index != expected_canonical_index:
361  raise _descriptor_error(
362  descriptor_set,
363  category="reordering_across_noise_boundaries",
364  first_unsupported_condition="canonical_operation_index",
365  failure_stage="descriptor_validation",
366  reason=(
367  "Partition descriptors must preserve contiguous canonical "
368  "operation coverage; partition {} member {} expected canonical "
369  "index {} but got {}".format(
370  partition.partition_index,
371  member_index,
372  expected_canonical_index,
373  member.canonical_operation_index,
374  )
375  ),
376  )
377  op = descriptor_set.operations[member.canonical_operation_index]
378  expected_canonical_index += 1
379 
380  expected_local_qubit_support = tuple(
381  global_to_local[qbit] for qbit in op.qubit_support
382  )
383  if member.local_qubit_support != expected_local_qubit_support:
384  raise _descriptor_error(
385  descriptor_set,
386  category="incomplete_remapping",
387  first_unsupported_condition="local_qubit_support",
388  failure_stage="descriptor_validation",
389  reason=(
390  "Partition descriptor partition {} has inconsistent local_qubit_support "
391  "for canonical operation {}".format(
392  partition.partition_index, member.canonical_operation_index
393  )
394  ),
395  )
396  expected_local_target = (
397  None
398  if op.target_qbit is None
399  else global_to_local[op.target_qbit]
400  )
401  if member.local_target_qbit != expected_local_target:
402  raise _descriptor_error(
403  descriptor_set,
404  category="incomplete_remapping",
405  first_unsupported_condition="local_target_qbit",
406  failure_stage="descriptor_validation",
407  reason=(
408  "Partition descriptor partition {} has inconsistent local_target_qbit "
409  "for canonical operation {}".format(
410  partition.partition_index, member.canonical_operation_index
411  )
412  ),
413  )
414  expected_local_control = (
415  None
416  if op.control_qbit is None
417  else global_to_local[op.control_qbit]
418  )
419  if member.local_control_qbit != expected_local_control:
420  raise _descriptor_error(
421  descriptor_set,
422  category="incomplete_remapping",
423  first_unsupported_condition="local_control_qbit",
424  failure_stage="descriptor_validation",
425  reason=(
426  "Partition descriptor partition {} has inconsistent local_control_qbit "
427  "for canonical operation {}".format(
428  partition.partition_index, member.canonical_operation_index
429  )
430  ),
431  )
432  if member.local_param_start != expected_local_param_start:
433  raise _descriptor_error(
434  descriptor_set,
435  category="ambiguous_parameter_routing",
436  first_unsupported_condition="local_param_start",
437  failure_stage="descriptor_validation",
438  reason=(
439  "Partition descriptor partition {} has non-contiguous local_param_start "
440  "for canonical operation {}".format(
441  partition.partition_index, member.canonical_operation_index
442  )
443  ),
444  )
445  if op.param_count:
446  expected_parameter_routing.append(
447  (op.param_start, member.local_param_start, op.param_count)
448  )
449  expected_local_param_start += op.param_count
450 
451  derived_routing = descriptor_set.partition_parameter_routing(partition)
452  if derived_routing != tuple(expected_parameter_routing):
453  raise _descriptor_error(
454  descriptor_set,
455  category="ambiguous_parameter_routing",
456  first_unsupported_condition="parameter_routing",
457  failure_stage="descriptor_validation",
458  reason=(
459  "Partition descriptor partition {} must keep parameter_routing aligned "
460  "with ordered member parameter metadata".format(
461  partition.partition_index
462  )
463  ),
464  )
465  expected_partition_index += 1
466 
467  if expected_canonical_index != len(descriptor_set.operations):
468  raise _descriptor_error(
469  descriptor_set,
470  category="dropped_operations",
471  first_unsupported_condition="descriptor_member_count",
472  failure_stage="descriptor_validation",
473  reason=(
474  "Partition descriptor coverage ended at canonical index {} but "
475  "operations table has length {}".format(
476  expected_canonical_index, len(descriptor_set.operations)
477  )
478  ),
479  )
480 
481  return descriptor_set
482 
483 
485  expected: CanonicalNoisyPlannerOperation,
486  actual: CanonicalNoisyPlannerOperation,
487 ) -> tuple[str, str]:
488  """Map first differing planner field to historical unsupported categories."""
489  for field in (
490  "kind",
491  "name",
492  "is_unitary",
493  "source_gate_index",
494  "target_qbit",
495  "control_qbit",
496  "param_count",
497  "param_start",
498  "fixed_value",
499  ):
500  if getattr(actual, field) != getattr(expected, field):
501  category = (
502  "hidden_noise_placement"
503  if expected.kind == PLANNER_OP_KIND_NOISE
504  or actual.kind == PLANNER_OP_KIND_NOISE
505  else "dropped_operations"
506  )
507  return category, field
508  if actual.qubit_support != expected.qubit_support:
509  category = (
510  "hidden_noise_placement"
511  if expected.kind == PLANNER_OP_KIND_NOISE
512  or actual.kind == PLANNER_OP_KIND_NOISE
513  else "dropped_operations"
514  )
515  return category, "qubit_support"
516  return "dropped_operations", "operation_mismatch"
517 
518 
520  surface: CanonicalNoisyPlannerSurface,
521  descriptor_set: NoisyPartitionDescriptorSet,
522 ) -> NoisyPartitionDescriptorSet:
523  validated = validate_partition_descriptor_set(descriptor_set)
524  if (
525  validated.requested_mode != surface.requested_mode
526  or validated.source_type != surface.source_type
527  or validated.workload_id != surface.workload_id
528  or validated.qbit_num != surface.qbit_num
529  or validated.parameter_count != surface.parameter_count
530  ):
531  raise _descriptor_error(
532  validated,
533  category="descriptor_request",
534  first_unsupported_condition="provenance_mismatch",
535  failure_stage="descriptor_validation",
536  reason=(
537  "Partition descriptor provenance and size metadata must match the "
538  "canonical planner surface for workload '{}'".format(
539  surface.workload_id
540  )
541  ),
542  )
543  if validated.descriptor_member_count != surface.operation_count:
544  raise _descriptor_error(
545  validated,
546  category="dropped_operations",
547  first_unsupported_condition="operation_count",
548  failure_stage="descriptor_validation",
549  reason=(
550  "Partition descriptor workload '{}' records {} descriptor members but "
551  "the canonical surface contains {} operations".format(
552  surface.workload_id,
553  validated.descriptor_member_count,
554  surface.operation_count,
555  )
556  ),
557  )
558  if validated.noise_count != surface.noise_count:
559  raise _descriptor_error(
560  validated,
561  category="hidden_noise_placement",
562  first_unsupported_condition="noise_count",
563  failure_stage="descriptor_validation",
564  reason=(
565  "Partition descriptor workload '{}' records {} noise operations but "
566  "the canonical surface contains {}".format(
567  surface.workload_id,
568  validated.noise_count,
569  surface.noise_count,
570  )
571  ),
572  )
573 
574  if validated.operations != surface.operations:
575  if len(validated.operations) != len(surface.operations):
576  raise _descriptor_error(
577  validated,
578  category="dropped_operations",
579  first_unsupported_condition="operation_count",
580  failure_stage="descriptor_validation",
581  reason=(
582  "Partition descriptor workload '{}' operations table length {} does not "
583  "match canonical surface length {}".format(
584  surface.workload_id,
585  len(validated.operations),
586  len(surface.operations),
587  )
588  ),
589  )
590  for index, (desc_op, surf_op) in enumerate(
591  zip(validated.operations, surface.operations)
592  ):
593  if desc_op == surf_op:
594  continue
595  category, first_cond = _descriptor_operation_mismatch_category(
596  surf_op, desc_op
597  )
598  raise _descriptor_error(
599  validated,
600  category=category,
601  first_unsupported_condition=first_cond,
602  failure_stage="descriptor_validation",
603  reason=(
604  "Partition descriptor canonical operation {} mismatches the "
605  "planner surface".format(index)
606  ),
607  )
608 
609  return validated
610 
611 
613  descriptor_set: NoisyPartitionDescriptorSet,
614  *,
615  metadata: Mapping[str, Any] | None = None,
616 ) -> dict[str, Any]:
617  payload = validate_partition_descriptor_set(descriptor_set).to_dict()
618  return {
619  "requested_mode": payload["requested_mode"],
620  "provenance": payload["provenance"],
621  "summary": {
622  "qbit_num": payload["qbit_num"],
623  "parameter_count": payload["parameter_count"],
624  "partition_count": payload["partition_count"],
625  "descriptor_member_count": payload["descriptor_member_count"],
626  "gate_count": payload["gate_count"],
627  "noise_count": payload["noise_count"],
628  "max_partition_qubits": payload["max_partition_qubits"],
629  "max_partition_span": payload["max_partition_span"],
630  "partition_member_counts": payload["partition_member_counts"],
631  "remapped_partition_count": sum(
632  partition["requires_remap"] for partition in payload["partitions"]
633  ),
634  "parameter_routing_segment_count": sum(
635  len(partition["parameter_routing"])
636  for partition in payload["partitions"]
637  ),
638  },
639  "partitions": payload["partitions"],
640  "metadata": dict(metadata) if metadata is not None else {},
641  }
def _descriptor_operation_mismatch_category
def build_phase3_continuity_planner_surface
Build a canonical planner surface from a Phase 2 continuity VQE instance.
def validate_partition_descriptor_set
def build_phase3_continuity_partition_descriptor_set
def _partition_surface_operations
Partition the operations of a canonical planner surface into partitions.
def validate_partition_descriptor_set_against_surface