Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
noisy_types.py
Go to the documentation of this file.
1 from __future__ import annotations
2 
3 from dataclasses import dataclass
4 from typing import Any, Literal
5 
6 PARTITIONED_DENSITY_MODE = "partitioned_density"
7 DEFAULT_PARTITION_DESCRIPTOR_MAX_QUBITS = 2
8 
9 SUPPORTED_GATE_NAMES = frozenset({"U3", "CNOT"})
10 SUPPORTED_NOISE_NAMES = frozenset(
11  {"local_depolarizing", "amplitude_damping", "phase_damping"}
12 )
13 SUPPORTED_PLANNER_SOURCE_TYPES = frozenset(
14  {
15  "generated_hea",
16  "microcase_builder",
17  "structured_family_builder",
18  "legacy_qgd_circuit_exact",
19  }
20 )
21 
22 PLANNER_OP_KIND_GATE = "gate"
23 PLANNER_OP_KIND_NOISE = "noise"
24 PLANNER_OP_KINDS: frozenset[str] = frozenset(
25  {PLANNER_OP_KIND_GATE, PLANNER_OP_KIND_NOISE}
26 )
27 PlannerOperationKindWire = Literal["gate", "noise"]
28 
29 
30 @dataclass(frozen=True)
32  index: int
33  kind: PlannerOperationKindWire
34  name: str
35  is_unitary: bool
36  source_gate_index: int
37  target_qbit: int | None
38  control_qbit: int | None
39  param_count: int
40  param_start: int
41  fixed_value: float | None
42 
43  @property
44  def qubit_support(self) -> tuple[int, ...]:
45  support: list[int] = []
46  if self.control_qbit is not None:
47  support.append(self.control_qbit)
48  if self.target_qbit is not None:
49  support.append(self.target_qbit)
50  return tuple(support)
51 
52  def to_dict(self) -> dict[str, Any]:
53  return {
54  "index": self.index,
55  "kind": self.kind,
56  "name": self.name,
57  "is_unitary": self.is_unitary,
58  "source_gate_index": self.source_gate_index,
59  "target_qbit": self.target_qbit,
60  "control_qbit": self.control_qbit,
61  "qubit_support": list(self.qubit_support),
62  "param_count": self.param_count,
63  "param_start": self.param_start,
64  "fixed_value": self.fixed_value,
65  }
66 
67 
68 @dataclass(frozen=True)
70  requested_mode: str
71  source_type: str
72  workload_id: str
73  qbit_num: int
74  parameter_count: int
75  operations: tuple[CanonicalNoisyPlannerOperation, ...]
76 
77  @property
78  def operation_count(self) -> int:
79  return len(self.operations)
80 
81  @property
82  def gate_count(self) -> int:
83  return sum(op.kind == PLANNER_OP_KIND_GATE for op in self.operations)
84 
85  @property
86  def noise_count(self) -> int:
87  return sum(op.kind == PLANNER_OP_KIND_NOISE for op in self.operations)
88 
89  @property
90  def gate_names(self) -> tuple[str, ...]:
91  return tuple(
92  op.name for op in self.operations if op.kind == PLANNER_OP_KIND_GATE
93  )
94 
95  @property
96  def noise_names(self) -> tuple[str, ...]:
97  return tuple(
98  op.name for op in self.operations if op.kind == PLANNER_OP_KIND_NOISE
99  )
100 
101  @property
102  def max_qubit_span(self) -> int:
103  return max((len(op.qubit_support) for op in self.operations), default=0)
104 
105  @property
106  def provenance(self) -> dict[str, Any]:
107  return {
108  "source_type": self.source_type,
109  "workload_id": self.workload_id,
110  }
111 
112  def to_dict(self) -> dict[str, Any]:
113  return {
114  "requested_mode": self.requested_mode,
115  "source_type": self.source_type,
116  "workload_id": self.workload_id,
117  "provenance": self.provenance,
118  "qbit_num": self.qbit_num,
119  "parameter_count": self.parameter_count,
120  "operation_count": self.operation_count,
121  "gate_count": self.gate_count,
122  "noise_count": self.noise_count,
123  "gate_sequence": list(self.gate_names),
124  "noise_sequence": list(self.noise_names),
125  "max_qubit_span": self.max_qubit_span,
126  "operations": [op.to_dict() for op in self.operations],
127  }
128 
129 
130 @dataclass(frozen=True)
132  """Partition-local overlay; canonical gate/noise fields live in `NoisyPartitionDescriptorSet.operations`."""
133 
134  partition_member_index: int
135  canonical_operation_index: int
136  local_target_qbit: int | None
137  local_control_qbit: int | None
138  local_qubit_support: tuple[int, ...]
139  local_param_start: int
140 
141  def to_dict(self, descriptor_set: NoisyPartitionDescriptorSet) -> dict[str, Any]:
142  from squander.partitioning.noisy_descriptor import descriptor_member_to_dict
143 
144  return descriptor_member_to_dict(descriptor_set, self)
145 
146 
147 @dataclass(frozen=True)
149  partition_index: int
150  local_to_global_qbits: tuple[int, ...]
151  members: tuple[NoisyPartitionDescriptorMember, ...]
152 
153  @property
154  def member_count(self) -> int:
155  return len(self.members)
156 
157  @property
158  def partition_qubit_span(self) -> tuple[int, ...]:
159  """Legacy compatibility name: same tuple as ``local_to_global_qbits`` (global indices)."""
160  return self.local_to_global_qbits
161 
162  @property
163  def requires_remap(self) -> bool:
164  return self.local_to_global_qbits != tuple(
165  range(len(self.local_to_global_qbits))
166  )
167 
168  @property
169  def global_to_local_qbits(self) -> tuple[tuple[int, int], ...]:
170  return tuple(
171  (global_qbit, local_qbit)
172  for local_qbit, global_qbit in enumerate(self.local_to_global_qbits)
173  )
174 
175  def to_dict(self, descriptor_set: NoisyPartitionDescriptorSet) -> dict[str, Any]:
176  from squander.partitioning.noisy_descriptor import descriptor_partition_to_dict
177 
178  return descriptor_partition_to_dict(descriptor_set, self)
179 
180 
181 @dataclass(frozen=True)
183  requested_mode: str
184  source_type: str
185  workload_id: str
186  qbit_num: int
187  parameter_count: int
188  max_partition_qubits: int
189  # Ordered canonical operations (same contract as CanonicalNoisyPlannerSurface.operations).
190  operations: tuple[CanonicalNoisyPlannerOperation, ...]
191  partitions: tuple[NoisyPartitionDescriptor, ...]
192 
194  self, member: NoisyPartitionDescriptorMember
195  ) -> CanonicalNoisyPlannerOperation:
196  return self.operations[member.canonical_operation_index]
197 
198  def partition_gate_count(self, partition: NoisyPartitionDescriptor) -> int:
199  return sum(
200  1
201  for m in partition.members
202  if self.canonical_operation_for(m).kind == PLANNER_OP_KIND_GATE
203  )
204 
205  def partition_noise_count(self, partition: NoisyPartitionDescriptor) -> int:
206  return sum(
207  1
208  for m in partition.members
209  if self.canonical_operation_for(m).kind == PLANNER_OP_KIND_NOISE
210  )
211 
212  def partition_parameter_count(self, partition: NoisyPartitionDescriptor) -> int:
213  return sum(
214  self.canonical_operation_for(m).param_count for m in partition.members
215  )
216 
218  self, partition: NoisyPartitionDescriptor
219  ) -> tuple[int, ...]:
220  return tuple(m.canonical_operation_index for m in partition.members)
221 
223  self, partition: NoisyPartitionDescriptor
224  ) -> tuple[tuple[int, int, int], ...]:
225  return tuple(
226  (
227  self.canonical_operation_for(m).param_start,
228  m.local_param_start,
229  self.canonical_operation_for(m).param_count,
230  )
231  for m in partition.members
232  if self.canonical_operation_for(m).param_count > 0
233  )
234 
235  @property
236  def provenance(self) -> dict[str, Any]:
237  return {
238  "source_type": self.source_type,
239  "workload_id": self.workload_id,
240  }
241 
242  @property
243  def partition_count(self) -> int:
244  return len(self.partitions)
245 
246  @property
247  def descriptor_member_count(self) -> int:
248  return sum(partition.member_count for partition in self.partitions)
249 
250  @property
251  def gate_count(self) -> int:
252  return sum(self.partition_gate_count(p) for p in self.partitions)
253 
254  @property
255  def noise_count(self) -> int:
256  return sum(self.partition_noise_count(p) for p in self.partitions)
257 
258  @property
259  def max_partition_span(self) -> int:
260  return max(
261  (len(partition.partition_qubit_span) for partition in self.partitions),
262  default=0,
263  )
264 
265  @property
266  def partition_member_counts(self) -> tuple[int, ...]:
267  return tuple(partition.member_count for partition in self.partitions)
268 
269  def to_dict(self) -> dict[str, Any]:
270  return {
271  "requested_mode": self.requested_mode,
272  "source_type": self.source_type,
273  "workload_id": self.workload_id,
274  "provenance": self.provenance,
275  "qbit_num": self.qbit_num,
276  "parameter_count": self.parameter_count,
277  "max_partition_qubits": self.max_partition_qubits,
278  "partition_count": self.partition_count,
279  "descriptor_member_count": self.descriptor_member_count,
280  "gate_count": self.gate_count,
281  "noise_count": self.noise_count,
282  "max_partition_span": self.max_partition_span,
283  "partition_member_counts": list(self.partition_member_counts),
284  "partitions": [p.to_dict(self) for p in self.partitions],
285  }