Coverage for qml_essentials / unitary.py: 91%
186 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-06-11 15:51 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-06-11 15:51 +0000
1from typing import Optional, List, Union, Dict, Tuple
2import itertools
3import jax.numpy as jnp
4import jax
6from qml_essentials import operations as op
7import logging
9from qml_essentials.utils import safe_random_split
11log = logging.getLogger(__name__)
14# Cache for computed rulers
15_GOLOMB_RULER_CACHE: Dict[int, Tuple[int, ...]] = {}
18def _greedy_golomb(d: int) -> Tuple[int, ...]:
19 """Construct a valid Golomb ruler of order *d* using a greedy algorithm.
21 Starting from mark 0, each subsequent mark is the smallest integer
22 whose pairwise differences with all existing marks are distinct.
23 This always succeeds and produces a valid ruler, though it may not
24 be optimal (i.e. the max mark may not be minimal).
26 Args:
27 d: Order of the ruler (number of marks).
29 Returns:
30 Tuple of *d* non-negative integers forming a valid Golomb ruler.
31 """
32 if d <= 0:
33 return ()
34 marks = [0]
35 diffs: set = set()
36 candidate = 1
37 while len(marks) < d:
38 new_diffs: set = set()
39 valid = True
40 for existing in marks:
41 diff = candidate - existing
42 if diff in diffs or diff in new_diffs:
43 valid = False
44 break
45 new_diffs.add(diff)
46 if valid:
47 marks.append(candidate)
48 diffs |= new_diffs
49 candidate += 1
50 return tuple(marks)
53def golomb_ruler(d: int) -> Tuple[int, ...]:
54 """Return a valid Golomb ruler of order *d*.
56 A Golomb ruler is a set of *d* non-negative integers such that all
57 pairwise differences are distinct. When used as the diagonal of a
58 data-encoding Hamiltonian ``H = diag(marks)``, the resulting Fourier
59 spectrum ``\\Omega`` has ``|\\Omega| = d(d-1) + 1`` distinct frequencies
60 with ``|R(k)| = 1`` for all ``k ≠ 0`` — the minimal possible degeneracy
61 for any *d*-dimensional Hamiltonian.
63 Uses a greedy construction that always produces a valid ruler.
64 Results are cached for efficiency.
66 Args:
67 d: Order of the ruler (number of marks, equal to the Hilbert
68 space dimension ``2^n_qubits``).
70 Returns:
71 Tuple of *d* non-negative integers forming a Golomb ruler.
73 Raises:
74 ValueError: If ``d <= 0``.
76 References:
77 Peters et al., "Generalization despite overfitting in quantum
78 machine learning models", arXiv:2209.05523, Appendix C.4.
79 """
80 if d <= 0:
81 raise ValueError(f"Golomb ruler order must be positive, got {d}")
82 if d not in _GOLOMB_RULER_CACHE:
83 _GOLOMB_RULER_CACHE[d] = _greedy_golomb(d)
84 return _GOLOMB_RULER_CACHE[d]
87class UnitaryGates:
88 """Collection of unitary quantum gates with optional noise simulation."""
90 batch_gate_error = True
92 @staticmethod
93 def NQubitDepolarizingChannel(p: float, wires: List[int]) -> op.QubitChannel:
94 """
95 Generate Kraus operators for n-qubit depolarizing channel.
97 The n-qubit depolarizing channel models uniform depolarizing noise
98 acting on n qubits simultaneously, useful for simulating realistic
99 multi-qubit noise affecting entangling gates.
101 Args:
102 p (float): Total probability of depolarizing error (0 ≤ p ≤ 1).
103 wires (List[int]): Qubit indices on which the channel acts.
104 Must contain at least 2 qubits.
106 Returns:
107 op.QubitChannel: QubitChannel with Kraus operators
108 representing the depolarizing noise channel.
110 Raises:
111 ValueError: If p is not in [0, 1] or if fewer than 2 qubits provided.
112 """
114 def n_qubit_depolarizing_kraus(p: float, n: int) -> List[jnp.ndarray]:
115 if not (0.0 <= p <= 1.0):
116 raise ValueError(f"Probability p must be between 0 and 1, got {p}")
117 if n < 2:
118 raise ValueError(f"Number of qubits must be >= 2, got {n}")
120 Id = jnp.eye(2)
121 X = op.PauliX._matrix
122 Y = op.PauliY._matrix
123 Z = op.PauliZ._matrix
124 paulis = [Id, X, Y, Z]
126 dim = 2**n
127 all_ops = []
129 # Generate all n-qubit Pauli tensor products:
130 for indices in itertools.product(range(4), repeat=n):
131 P = jnp.eye(1)
132 for idx in indices:
133 P = jnp.kron(P, paulis[idx])
134 all_ops.append(P)
136 # Identity operator corresponds to all zeros indices (Id^n)
137 K0 = jnp.sqrt(1 - p * (4**n - 1) / (4**n)) * jnp.eye(dim)
139 kraus_ops = []
140 for i, P in enumerate(all_ops):
141 if i == 0:
142 # Skip the identity, already handled as K0
143 continue
144 kraus_ops.append(jnp.sqrt(p / (4**n)) * P)
146 return [K0] + kraus_ops
148 return op.QubitChannel(n_qubit_depolarizing_kraus(p, len(wires)), wires=wires)
150 @staticmethod
151 def Noise(
152 wires: Union[int, List[int]], noise_params: Optional[Dict[str, float]] = None
153 ) -> None:
154 """
155 Apply noise channels to specified qubits.
157 Applies various single-qubit and multi-qubit noise channels based on
158 the provided noise parameters dictionary.
160 Args:
161 wires (Union[int, List[int]]): Qubit index or list of qubit indices
162 to apply noise to.
163 noise_params (Optional[Dict[str, float]]): Dictionary of noise
164 parameters. Supported keys:
165 - "BitFlip" (float): Bit flip error probability
166 - "PhaseFlip" (float): Phase flip error probability
167 - "Depolarizing" (float): Single-qubit depolarizing probability
168 - "MultiQubitDepolarizing" (float): Multi-qubit depolarizing
169 probability (applies if len(wires) > 1)
170 All parameters default to 0.0 if not provided.
172 Returns:
173 None: Noise channels are applied in-place to the circuit.
174 """
175 if noise_params is not None:
176 if isinstance(wires, int):
177 wires = [wires] # single qubit gate
179 # noise on single qubits
180 for wire in wires:
181 bf = noise_params.get("BitFlip", 0.0)
182 if bf > 0:
183 op.BitFlip(bf, wires=wire)
185 pf = noise_params.get("PhaseFlip", 0.0)
186 if pf > 0:
187 op.PhaseFlip(pf, wires=wire)
189 dp = noise_params.get("Depolarizing", 0.0)
190 if dp > 0:
191 op.DepolarizingChannel(dp, wires=wire)
193 # noise on two-qubits
194 if len(wires) > 1:
195 p = noise_params.get("MultiQubitDepolarizing", 0.0)
196 if p > 0:
197 UnitaryGates.NQubitDepolarizingChannel(p, wires)
199 @staticmethod
200 def GateError(
201 w: Union[float, jnp.ndarray, List[float]],
202 noise_params: Optional[Dict[str, float]] = None,
203 random_key: Optional[jax.random.PRNGKey] = None,
204 ) -> Tuple[jnp.ndarray, jax.random.PRNGKey]:
205 """
206 Apply gate error noise to rotation angle(s).
208 Adds Gaussian noise to gate rotation angles to simulate imperfect
209 gate implementations.
211 Args:
212 w (Union[float, jnp.ndarray, List[float]]): Rotation angle(s) in radians.
213 noise_params (Optional[Dict[str, float]]): Dictionary with optional
214 "GateError" key specifying standard deviation of Gaussian noise.
215 random_key (Optional[jax.random.PRNGKey]): JAX random key for
216 stochastic noise generation.
218 Returns:
219 Tuple[jnp.ndarray, jax.random.PRNGKey]: Tuple containing:
220 - Modified rotation angle(s) with applied noise
221 - Updated JAX random key
223 Raises:
224 AssertionError: If noise_params contains "GateError" but random_key is None.
225 """
226 if noise_params is not None and noise_params.get("GateError", None) is not None:
227 assert random_key is not None, (
228 "A random_key must be provided when using GateError"
229 )
231 if UnitaryGates.batch_gate_error:
232 random_key, sub_key = safe_random_split(random_key)
233 else:
234 # Use a fixed key so that every batch element (under vmap)
235 # draws the same noise value, effectively broadcasting.
236 sub_key = jax.random.key(0)
238 w += noise_params["GateError"] * jax.random.normal(
239 sub_key,
240 (
241 w.shape
242 if isinstance(w, jnp.ndarray) and UnitaryGates.batch_gate_error
243 else ()
244 ),
245 )
246 return w, random_key
248 @staticmethod
249 def Rot(
250 phi: Union[float, jnp.ndarray, List[float]],
251 theta: Union[float, jnp.ndarray, List[float]],
252 omega: Union[float, jnp.ndarray, List[float]],
253 wires: Union[int, List[int]],
254 noise_params: Optional[Dict[str, float]] = None,
255 random_key: Optional[jax.random.PRNGKey] = None,
256 ) -> None:
257 """
258 Apply general rotation gate with optional noise.
260 Applies a three-angle rotation Rot(phi, theta, omega) with optional
261 gate errors and noise channels.
263 Args:
264 phi (Union[float, jnp.ndarray, List[float]]): First rotation angle.
265 theta (Union[float, jnp.ndarray, List[float]]): Second rotation angle.
266 omega (Union[float, jnp.ndarray, List[float]]): Third rotation angle.
267 wires (Union[int, List[int]]): Qubit index or indices to apply rotation to.
268 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
269 Supports BitFlip, PhaseFlip, Depolarizing, and GateError.
270 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
272 Returns:
273 None: Gate and noise are applied in-place to the circuit.
274 """
275 if noise_params is not None and "GateError" in noise_params:
276 phi, random_key = UnitaryGates.GateError(phi, noise_params, random_key)
277 theta, random_key = UnitaryGates.GateError(theta, noise_params, random_key)
278 omega, random_key = UnitaryGates.GateError(omega, noise_params, random_key)
279 op.Rot(phi, theta, omega, wires=wires)
280 UnitaryGates.Noise(wires, noise_params)
282 @staticmethod
283 def PauliRot(
284 theta: float,
285 pauli: str,
286 wires: Union[int, List[int]],
287 noise_params: Optional[Dict[str, float]] = None,
288 random_key: Optional[jax.random.PRNGKey] = None,
289 ) -> None:
290 """
291 Apply general rotation gate with optional noise.
293 Applies a three-angle rotation Rot(phi, theta, omega) with optional
294 gate errors and noise channels.
296 Args:
297 theta (Union[float, jnp.ndarray, List[float]]): Second rotation angle.
298 pauli (str): Pauli operator to apply. Must be "X", "Y", or "Z".
299 wires (Union[int, List[int]]): Qubit index or indices to apply rotation to.
300 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
301 Supports BitFlip, PhaseFlip, Depolarizing, and GateError.
302 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
304 Returns:
305 None: Gate and noise are applied in-place to the circuit.
306 """
307 if noise_params is not None and "GateError" in noise_params:
308 theta, random_key = UnitaryGates.GateError(theta, noise_params, random_key)
309 op.PauliRot(theta, pauli, wires=wires)
310 UnitaryGates.Noise(wires, noise_params)
312 @staticmethod
313 def RX(
314 w: Union[float, jnp.ndarray, List[float]],
315 wires: Union[int, List[int]],
316 noise_params: Optional[Dict[str, float]] = None,
317 random_key: Optional[jax.random.PRNGKey] = None,
318 ) -> None:
319 """
320 Apply X-axis rotation with optional noise.
322 Args:
323 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
324 wires (Union[int, List[int]]): Qubit index or indices.
325 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
326 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
328 Returns:
329 None: Gate and noise are applied in-place to the circuit.
330 """
331 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
332 op.RX(w, wires=wires)
333 UnitaryGates.Noise(wires, noise_params)
335 @staticmethod
336 def RY(
337 w: Union[float, jnp.ndarray, List[float]],
338 wires: Union[int, List[int]],
339 noise_params: Optional[Dict[str, float]] = None,
340 random_key: Optional[jax.random.PRNGKey] = None,
341 ) -> None:
342 """
343 Apply Y-axis rotation with optional noise.
345 Args:
346 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
347 wires (Union[int, List[int]]): Qubit index or indices.
348 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
349 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
351 Returns:
352 None: Gate and noise are applied in-place to the circuit.
353 """
354 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
355 op.RY(w, wires=wires)
356 UnitaryGates.Noise(wires, noise_params)
358 @staticmethod
359 def RZ(
360 w: Union[float, jnp.ndarray, List[float]],
361 wires: Union[int, List[int]],
362 noise_params: Optional[Dict[str, float]] = None,
363 random_key: Optional[jax.random.PRNGKey] = None,
364 ) -> None:
365 """
366 Apply Z-axis rotation with optional noise.
368 Args:
369 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
370 wires (Union[int, List[int]]): Qubit index or indices.
371 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
372 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
374 Returns:
375 None: Gate and noise are applied in-place to the circuit.
376 """
377 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
378 op.RZ(w, wires=wires)
379 UnitaryGates.Noise(wires, noise_params)
381 @staticmethod
382 def CRX(
383 w: Union[float, jnp.ndarray, List[float]],
384 wires: Union[int, List[int]],
385 noise_params: Optional[Dict[str, float]] = None,
386 random_key: Optional[jax.random.PRNGKey] = None,
387 ) -> None:
388 """
389 Apply controlled X-rotation with optional noise.
391 Args:
392 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
393 wires (Union[int, List[int]]): Control and target qubit indices.
394 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
395 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
397 Returns:
398 None: Gate and noise are applied in-place to the circuit.
399 """
400 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
401 op.CRX(w, wires=wires)
402 UnitaryGates.Noise(wires, noise_params)
404 @staticmethod
405 def CRY(
406 w: Union[float, jnp.ndarray, List[float]],
407 wires: Union[int, List[int]],
408 noise_params: Optional[Dict[str, float]] = None,
409 random_key: Optional[jax.random.PRNGKey] = None,
410 ) -> None:
411 """
412 Apply controlled Y-rotation with optional noise.
414 Args:
415 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
416 wires (Union[int, List[int]]): Control and target qubit indices.
417 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
418 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
420 Returns:
421 None: Gate and noise are applied in-place to the circuit.
422 """
423 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
424 op.CRY(w, wires=wires)
425 UnitaryGates.Noise(wires, noise_params)
427 @staticmethod
428 def CRZ(
429 w: Union[float, jnp.ndarray, List[float]],
430 wires: Union[int, List[int]],
431 noise_params: Optional[Dict[str, float]] = None,
432 random_key: Optional[jax.random.PRNGKey] = None,
433 ) -> None:
434 """
435 Apply controlled Z-rotation with optional noise.
437 Args:
438 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
439 wires (Union[int, List[int]]): Control and target qubit indices.
440 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
441 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
443 Returns:
444 None: Gate and noise are applied in-place to the circuit.
445 """
446 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
447 op.CRZ(w, wires=wires)
448 UnitaryGates.Noise(wires, noise_params)
450 @staticmethod
451 def RXX(
452 w: Union[float, jnp.ndarray, List[float]],
453 wires: Union[int, List[int]],
454 noise_params: Optional[Dict[str, float]] = None,
455 random_key: Optional[jax.random.PRNGKey] = None,
456 ) -> None:
457 """
458 Apply two-qubit XX rotation with optional noise.
460 Implements ``RXX(theta) = exp(-i theta/2 X ⊗ X)``.
462 Args:
463 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
464 wires (Union[int, List[int]]): Two qubit indices.
465 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
466 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
468 Returns:
469 None: Gate and noise are applied in-place to the circuit.
470 """
471 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
472 op.RXX(w, wires=wires)
473 UnitaryGates.Noise(wires, noise_params)
475 @staticmethod
476 def RYY(
477 w: Union[float, jnp.ndarray, List[float]],
478 wires: Union[int, List[int]],
479 noise_params: Optional[Dict[str, float]] = None,
480 random_key: Optional[jax.random.PRNGKey] = None,
481 ) -> None:
482 """
483 Apply two-qubit YY rotation with optional noise.
485 Implements ``RYY(theta) = exp(-i theta/2 Y ⊗ Y)``.
487 Args:
488 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
489 wires (Union[int, List[int]]): Two qubit indices.
490 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
491 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
493 Returns:
494 None: Gate and noise are applied in-place to the circuit.
495 """
496 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
497 op.RYY(w, wires=wires)
498 UnitaryGates.Noise(wires, noise_params)
500 @staticmethod
501 def RZZ(
502 w: Union[float, jnp.ndarray, List[float]],
503 wires: Union[int, List[int]],
504 noise_params: Optional[Dict[str, float]] = None,
505 random_key: Optional[jax.random.PRNGKey] = None,
506 ) -> None:
507 """
508 Apply two-qubit ZZ rotation with optional noise.
510 Implements ``RZZ(theta) = exp(-i theta/2 Z ⊗ Z)``.
512 Args:
513 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
514 wires (Union[int, List[int]]): Two qubit indices.
515 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
516 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
518 Returns:
519 None: Gate and noise are applied in-place to the circuit.
520 """
521 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
522 op.RZZ(w, wires=wires)
523 UnitaryGates.Noise(wires, noise_params)
525 @staticmethod
526 def RZX(
527 w: Union[float, jnp.ndarray, List[float]],
528 wires: Union[int, List[int]],
529 noise_params: Optional[Dict[str, float]] = None,
530 random_key: Optional[jax.random.PRNGKey] = None,
531 ) -> None:
532 """
533 Apply two-qubit ZX rotation with optional noise.
535 Implements ``RZX(theta) = exp(-i theta/2 Z ⊗ X)``, with ``Z`` acting
536 on the first wire and ``X`` on the second wire.
538 Args:
539 w (Union[float, jnp.ndarray, List[float]]): Rotation angle.
540 wires (Union[int, List[int]]): Two qubit indices ``[zwire, xwire]``.
541 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
542 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
544 Returns:
545 None: Gate and noise are applied in-place to the circuit.
546 """
547 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
548 op.RZX(w, wires=wires)
549 UnitaryGates.Noise(wires, noise_params)
551 @staticmethod
552 def CPhase(
553 w: Union[float, jnp.ndarray, List[float]],
554 wires: Union[int, List[int]],
555 noise_params: Optional[Dict[str, float]] = None,
556 random_key: Optional[jax.random.PRNGKey] = None,
557 ) -> None:
558 """
559 Apply controlled phase shift gate with optional noise.
561 This is a generalization of the CZ gate, applying a phase shift of
562 exp(i*w) to the |11⟩ state. When w=π, this reduces to CZ.
564 Args:
565 w (Union[float, jnp.ndarray, List[float]]): Phase shift angle.
566 wires (Union[int, List[int]]): Control and target qubit indices.
567 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
568 random_key (Optional[jax.random.PRNGKey]): JAX random key for noise.
570 Returns:
571 None: Gate and noise are applied in-place to the circuit.
572 """
573 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
574 op.ControlledPhaseShift(w, wires=wires)
575 UnitaryGates.Noise(wires, noise_params)
577 @staticmethod
578 def CX(
579 wires: Union[int, List[int]],
580 noise_params: Optional[Dict[str, float]] = None,
581 random_key: Optional[jax.random.PRNGKey] = None,
582 ) -> None:
583 """
584 Apply controlled-NOT (CNOT) gate with optional noise.
586 Args:
587 wires (Union[int, List[int]]): Control and target qubit indices.
588 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
589 random_key (Optional[jax.random.PRNGKey]): JAX random key for compatibility
590 (not used in this gate).
592 Returns:
593 None: Gate and noise are applied in-place to the circuit.
594 """
595 op.CX(wires=wires)
596 UnitaryGates.Noise(wires, noise_params)
598 @staticmethod
599 def CY(
600 wires: Union[int, List[int]],
601 noise_params: Optional[Dict[str, float]] = None,
602 random_key: Optional[jax.random.PRNGKey] = None,
603 ) -> None:
604 """
605 Apply controlled-Y gate with optional noise.
607 Args:
608 wires (Union[int, List[int]]): Control and target qubit indices.
609 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
610 random_key (Optional[jax.random.PRNGKey]): JAX random key for compatibility
611 (not used in this gate).
613 Returns:
614 None: Gate and noise are applied in-place to the circuit.
615 """
616 op.CY(wires=wires)
617 UnitaryGates.Noise(wires, noise_params)
619 @staticmethod
620 def CZ(
621 wires: Union[int, List[int]],
622 noise_params: Optional[Dict[str, float]] = None,
623 random_key: Optional[jax.random.PRNGKey] = None,
624 ) -> None:
625 """
626 Apply controlled-Z gate with optional noise.
628 Args:
629 wires (Union[int, List[int]]): Control and target qubit indices.
630 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
631 random_key (Optional[jax.random.PRNGKey]): JAX random key for compatibility
632 (not used in this gate).
634 Returns:
635 None: Gate and noise are applied in-place to the circuit.
636 """
637 op.CZ(wires=wires)
638 UnitaryGates.Noise(wires, noise_params)
640 @staticmethod
641 def H(
642 wires: Union[int, List[int]],
643 noise_params: Optional[Dict[str, float]] = None,
644 random_key: Optional[jax.random.PRNGKey] = None,
645 ) -> None:
646 """
647 Apply Hadamard gate with optional noise.
649 Args:
650 wires (Union[int, List[int]]): Qubit index or indices.
651 noise_params (Optional[Dict[str, float]]): Noise parameters dictionary.
652 random_key (Optional[jax.random.PRNGKey]): JAX random key for compatibility
653 (not used in this gate).
655 Returns:
656 None: Gate and noise are applied in-place to the circuit.
657 """
658 op.H(wires=wires)
659 UnitaryGates.Noise(wires, noise_params)
661 @staticmethod
662 def GolombEncoding(
663 w: Union[float, jnp.ndarray],
664 wires: Union[int, List[int]],
665 noise_params: Optional[Dict[str, float]] = None,
666 random_key: Optional[jax.random.PRNGKey] = None,
667 ) -> None:
668 """Apply Golomb encoding as a diagonal unitary on all given wires.
670 Implements ``S(x) = exp(-i H x)`` where
671 ``H = diag(g_0, g_1, ..., g_{d-1})`` and the ``g_j`` are the marks
672 of a Golomb ruler of order ``d = 2^len(wires)``. This produces a
673 maximally non-degenerate Fourier spectrum with
674 ``|\\Omega| = d(d-1) + 1`` distinct frequencies, each with degeneracy
675 ``|R(k)| = 1``.
677 See Peters et al., arXiv:2209.05523, Sec. 3.1 and Appendix C.4.
679 Args:
680 w: Scalar input value (the data point *x* to encode).
681 wires: Qubit indices this encoding acts on. All qubits are
682 acted upon simultaneously via a single multi-qubit diagonal
683 gate.
684 noise_params: Optional noise parameters dictionary.
685 random_key: JAX random key for stochastic noise.
687 Returns:
688 None: Gate and noise are applied in-place to the circuit.
689 """
690 wires_list = list(wires) if isinstance(wires, (list, tuple)) else [wires]
691 d = 2 ** len(wires_list)
692 marks = jnp.array(golomb_ruler(d), dtype=float)
694 # Apply gate error to the input angle
695 w, random_key = UnitaryGates.GateError(w, noise_params, random_key)
697 # Build diagonal: exp(-i * mark_j * x)
698 diag = jnp.exp(-1j * marks * w)
700 op.DiagonalQubitUnitary(diag, wires=wires_list)
701 UnitaryGates.Noise(wires_list, noise_params)