Coverage for qml_essentials/ansaetze.py: 94%
286 statements
« prev ^ index » next coverage.py v7.9.2, created at 2026-02-20 14:03 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2026-02-20 14:03 +0000
1from abc import ABC, abstractmethod
2from typing import Any, Optional, List, Union, Callable, Tuple
3import jax.numpy as np
4import jax
5import logging
6import warnings
8from qml_essentials.gates import Gates, PulseInformation
9from qml_essentials.topologies import Topology
11jax.config.update("jax_enable_x64", True)
12log = logging.getLogger(__name__)
15class Circuit(ABC):
16 """Abstract base class for quantum circuit ansätze."""
18 def __init__(self) -> None:
19 """Initialize the circuit."""
20 pass
22 @abstractmethod
23 def n_params_per_layer(self, n_qubits: int) -> int:
24 """
25 Get the number of parameters per circuit layer.
27 Args:
28 n_qubits (int): Number of qubits in the circuit.
30 Returns:
31 int: Number of parameters required per layer.
33 Raises:
34 NotImplementedError: Must be implemented by subclasses.
35 """
36 raise NotImplementedError("n_params_per_layer method is not implemented")
38 def n_pulse_params_per_layer(self, n_qubits: int) -> int:
39 """
40 Get the number of pulse parameters per circuit layer.
42 Subclasses that do not use pulse-level simulation do not need to
43 override this method.
45 Args:
46 n_qubits (int): Number of qubits in the circuit.
48 Returns:
49 int: Number of pulse parameters required per layer.
51 Raises:
52 NotImplementedError: If called but not overridden by subclass.
53 """
54 raise NotImplementedError("n_pulse_params_per_layer method is not implemented")
56 @abstractmethod
57 def get_control_indices(self, n_qubits: int) -> Optional[List[int]]:
58 """
59 Get indices for controlled rotation gates in one layer.
61 Returns slice indices [start:stop:step] for extracting controlled
62 gate parameters from a full parameter array for one layer.
64 Args:
65 n_qubits (int): Number of qubits in the circuit.
67 Returns:
68 Optional[List[int]]: List of three integers [start, stop, step]
69 for slicing, or None if the circuit contains no controlled
70 rotation gates.
72 Raises:
73 NotImplementedError: Must be implemented by subclasses.
74 """
75 raise NotImplementedError("get_control_indices method is not implemented")
77 def get_control_angles(self, w: np.ndarray, n_qubits: int) -> Optional[np.ndarray]:
78 """
79 Extract angles for controlled rotation gates from parameter array.
81 Args:
82 w (np.ndarray): Parameter array for one layer.
83 n_qubits (int): Number of qubits in the circuit.
85 Returns:
86 Optional[np.ndarray]: Array of controlled gate parameters,
87 or empty array if circuit contains no controlled gates.
88 """
89 indices = self.get_control_indices(n_qubits)
90 if indices is None:
91 return np.array([])
93 if len(indices) == 3 and None in indices:
94 return w[indices[0] : indices[1] : indices[2]]
95 else:
96 return w.take(np.array(indices))
98 def _build(self, w: np.ndarray, n_qubits: int, **kwargs) -> Any:
99 """
100 Build one layer of the circuit using unitary or pulse-level parameters.
102 Internal method that handles pulse parameter validation and context
103 management before delegating to the build() method.
105 Args:
106 w (np.ndarray): Parameter array for the current layer.
107 n_qubits (int): Number of qubits in the circuit.
108 **kwargs: Additional keyword arguments:
109 - gate_mode (str): "unitary" (default) or "pulse" for
110 pulse-level simulation.
111 - pulse_params (np.ndarray): Pulse parameters if gate_mode="pulse".
112 - noise_params (Dict): Noise parameters dictionary.
114 Returns:
115 Any: Result from the build() method.
117 Raises:
118 ValueError: If pulse_params length doesn't match expected count.
119 """
120 gate_mode = kwargs.get("gate_mode", "unitary")
122 if gate_mode == "pulse" and "pulse_params" in kwargs:
123 pulse_params_per_layer = self.n_pulse_params_per_layer(n_qubits)
125 if len(kwargs["pulse_params"]) != pulse_params_per_layer:
126 raise ValueError(
127 f"Pulse params length {len(kwargs['pulse_params'])} "
128 f"does not match expected {pulse_params_per_layer} "
129 f"for {n_qubits} qubits"
130 )
132 with Gates.pulse_manager_context(kwargs["pulse_params"]):
133 return self.build(w, n_qubits, **kwargs)
134 else:
135 return self.build(w, n_qubits, **kwargs)
137 @abstractmethod
138 def build(self, w: np.ndarray, n_qubits: int, **kwargs) -> Any:
139 """
140 Build one layer of the quantum circuit.
142 Args:
143 w (np.ndarray): Parameter array for the current layer.
144 n_qubits (int): Number of qubits in the circuit.
145 **kwargs: Additional keyword arguments passed from _build.
147 Returns:
148 Any: Circuit construction result.
150 Raises:
151 NotImplementedError: Must be implemented by subclasses.
152 """
153 raise NotImplementedError("build method is not implemented")
155 def __call__(self, *args: Any, **kwds: Any) -> Any:
156 """Call the _build method with provided arguments."""
157 self._build(*args, **kwds)
160class DeclarativeCircuit(Circuit):
161 """
162 A circuit defined entirely by a sequence of Block descriptors.
164 Subclasses only need to set the class attribute `structure` — a tuple of
166 All of `n_params_per_layer`, `n_pulse_params_per_layer`,
167 `get_control_indices`, and `build` are derived automatically.
168 """
170 @staticmethod
171 def structure() -> Tuple[Any, ...]:
172 """Override in subclass to return the structure tuple."""
173 raise NotImplementedError
175 @classmethod
176 def n_params_per_layer(cls, n_qubits: int) -> int:
177 structure = cls.structure()
178 n_params = 0
179 for block in structure:
180 # we can rely on n_params only returning a valid number
181 _n_params = block.n_params(n_qubits)
183 n_params += _n_params
185 return n_params
187 @classmethod
188 def n_pulse_params_per_layer(cls, n_qubits: int) -> int:
189 structure = cls.structure()
190 return sum(block.n_pulse_params(n_qubits) for block in structure)
192 @classmethod
193 def get_control_indices(cls, n_qubits: int) -> Optional[List]:
194 """
195 Computes parameter indices for controlled rotation Gates.
196 Scans the structure for Block with
197 [start, stop, step] into the flat parameter vector, or None.
198 """
199 structure = cls.structure()
200 total_params = sum(block.n_params(n_qubits) for block in structure)
202 # Collect which parameter indices correspond to controlled rotations
203 controlled_indices = []
204 offset = 0
205 for block in structure:
206 n = block.n_params(n_qubits)
207 if block.is_controlled_rotation:
208 controlled_indices.extend(range(offset, offset + n))
209 offset += n
211 # FIXME: this last part should be reworked
213 if not controlled_indices:
214 return None
216 # Check if indices form a contiguous tail (the common case)
217 # This preserves backwards compatibility with the [start, None, None] format
218 if controlled_indices == list(
219 range(total_params - len(controlled_indices), total_params)
220 ):
221 return [-len(controlled_indices), None, None]
223 # Fallback: return raw indices (future-proof)
224 return controlled_indices
226 @classmethod
227 def build(cls, w: np.ndarray, n_qubits: int, **kwargs) -> None:
228 structure = cls.structure()
229 w_idx = 0
230 for block in structure:
231 w_idx = block.apply(w, w_idx, n_qubits, **kwargs)
234class Block:
235 def __init__(
236 self,
237 gate: str,
238 topology: Any = None,
239 **kwargs,
240 ):
241 """
242 Initialize a Block object; the atoms of Ansatzes.
244 Args:
245 gate (str): Name of the Gate class to use.
246 topology (Any, optional): Topology of the gate for entangling gates.
247 Defaults to None.
248 kwargs: Additional keyword arguments passed to the topology function.
249 """
250 if isinstance(gate, str):
251 self.gate = getattr(Gates, gate)
252 else:
253 self.gate = gate
255 if self.is_entangling:
256 assert (
257 topology is not None
258 ), "Topology must be specified for entangling gates"
260 self.topology = topology
261 self.kwargs = kwargs
263 def __repr__(self):
264 if self.topology is None:
265 return f"{self.__class__.__name__}({self.gate.__name__})"
266 else:
267 return (
268 f"{self.__class__.__name__}"
269 f"({self.topology.__name__}[{self.gate.__name__}])"
270 )
272 @property
273 def is_entangling(self):
274 return Gates.is_entangling(self.gate)
276 @property
277 def is_rotational(self):
278 return Gates.is_rotational(self.gate)
280 @property
281 def is_controlled_rotation(self):
282 return self.is_entangling and self.is_rotational
284 def enough_qubits(self, n_qubits):
285 if self.is_entangling:
286 # NOTE This must be adjusted if default values
287 # in Topology change
288 span = self.kwargs.get("span", 1)
289 if callable(span):
290 span = span(n_qubits)
292 return (n_qubits >= 2) and (n_qubits > span)
294 return n_qubits >= 1
296 def n_params(self, n_qubits: int) -> int:
297 assert n_qubits > 0, "Number of qubits must be positive"
299 if self.is_rotational:
300 if self.is_entangling:
301 if not self.enough_qubits(n_qubits):
302 warnings.warn(
303 f"Skipping {self.topology.__name__} with n_qubits={n_qubits} "
304 f"as there are not enough qubits"
305 f"for this topology."
306 )
307 return 0
308 else:
309 return len(self.topology(n_qubits=n_qubits, **self.kwargs))
310 else:
311 return n_qubits if self.gate.__name__ != "Rot" else 3 * n_qubits
313 return 0
315 def n_pulse_params(self, n_qubits: int) -> int:
316 return PulseInformation.num_params(self.gate) * n_qubits
318 def apply(self, w: np.ndarray, w_idx: int, n_qubits: int, **kwargs) -> int:
319 assert n_qubits > 0, "Number of qubits must be positive"
321 iterator = (
322 self.topology(n_qubits=n_qubits, **self.kwargs)
323 if self.is_entangling
324 else range(n_qubits)
325 )
327 for wires in iterator:
328 if self.is_entangling and not self.enough_qubits(n_qubits):
329 warnings.warn(
330 f"Skipping {self.topology.__name__} with n_qubits={n_qubits} "
331 f"as there are not enough qubits"
332 f"for this topology."
333 )
334 continue
336 if self.is_rotational:
337 if self.gate.__name__ == "Rot":
338 self.gate(
339 w[w_idx], w[w_idx + 1], w[w_idx + 2], wires=wires, **kwargs
340 )
341 w_idx += 3
342 else:
343 self.gate(w[w_idx], wires=wires, **kwargs)
344 w_idx += 1
345 else:
346 self.gate(wires=wires, **kwargs)
347 return w_idx
350class Ansaetze:
351 def get_available():
352 return [
353 Ansaetze.No_Ansatz,
354 Ansaetze.Circuit_1,
355 Ansaetze.Circuit_2,
356 Ansaetze.Circuit_3,
357 Ansaetze.Circuit_4,
358 Ansaetze.Circuit_5,
359 Ansaetze.Circuit_6,
360 Ansaetze.Circuit_7,
361 Ansaetze.Circuit_8,
362 Ansaetze.Circuit_9,
363 Ansaetze.Circuit_10,
364 Ansaetze.Circuit_13,
365 Ansaetze.Circuit_14,
366 Ansaetze.Circuit_15,
367 Ansaetze.Circuit_16,
368 Ansaetze.Circuit_17,
369 Ansaetze.Circuit_18,
370 Ansaetze.Circuit_19,
371 Ansaetze.Circuit_20,
372 Ansaetze.No_Entangling,
373 Ansaetze.Strongly_Entangling,
374 Ansaetze.Hardware_Efficient,
375 Ansaetze.GHZ,
376 ]
378 class No_Ansatz(DeclarativeCircuit):
379 @staticmethod
380 def structure():
381 return ()
383 class GHZ(DeclarativeCircuit):
384 @staticmethod
385 def structure():
386 return (
387 Block(gate=Gates.H),
388 Block(gate=Gates.CX, topology=Topology.stairs, reverse=True),
389 )
391 @staticmethod
392 def build(w: np.ndarray, n_qubits: int, **kwargs):
393 Gates.H(wires=0, **kwargs)
394 for q in range(n_qubits - 1):
395 Gates.CX(wires=[q, q + 1], **kwargs)
397 @staticmethod
398 def n_pulse_params_per_layer(n_qubits: int) -> int:
399 n_params = PulseInformation.num_params("H") # only 1 H
400 n_params += (n_qubits - 1) * PulseInformation.num_params(Gates.CX)
401 return n_params
403 class Circuit_1(DeclarativeCircuit):
404 @staticmethod
405 def structure():
406 return (
407 Block(gate=Gates.RX),
408 Block(gate=Gates.RZ),
409 )
411 class Circuit_2(DeclarativeCircuit):
412 @staticmethod
413 def structure():
414 return (
415 Block(gate=Gates.RX),
416 Block(gate=Gates.RZ),
417 Block(
418 gate=Gates.CX,
419 topology=Topology.stairs,
420 ),
421 )
423 class Circuit_3(DeclarativeCircuit):
424 @staticmethod
425 def structure():
426 return (
427 Block(gate=Gates.RX),
428 Block(gate=Gates.RZ),
429 Block(gate=Gates.CRZ, topology=Topology.stairs),
430 )
432 class Circuit_4(DeclarativeCircuit):
433 @staticmethod
434 def structure():
435 return (
436 Block(gate=Gates.RX),
437 Block(gate=Gates.RZ),
438 Block(gate=Gates.CRX, topology=Topology.stairs),
439 )
441 class Circuit_5(DeclarativeCircuit):
442 @staticmethod
443 def structure():
444 return (
445 Block(gate=Gates.RX),
446 Block(gate=Gates.RZ),
447 Block(gate=Gates.CRZ, topology=Topology.all_to_all),
448 Block(gate=Gates.RX),
449 Block(gate=Gates.RZ),
450 )
452 class Circuit_6(DeclarativeCircuit):
453 @staticmethod
454 def structure():
455 return (
456 Block(gate=Gates.RX),
457 Block(gate=Gates.RZ),
458 Block(gate=Gates.CRX, topology=Topology.all_to_all),
459 Block(gate=Gates.RX),
460 Block(gate=Gates.RZ),
461 )
463 class Circuit_7(DeclarativeCircuit):
464 @staticmethod
465 def structure():
466 return (
467 Block(gate=Gates.RX),
468 Block(gate=Gates.RZ),
469 Block(
470 gate=Gates.CRZ,
471 topology=Topology.bricks,
472 ),
473 Block(gate=Gates.RX),
474 Block(gate=Gates.RZ),
475 Block(
476 gate=Gates.CRZ,
477 topology=Topology.bricks,
478 offset=1,
479 ),
480 )
482 class Circuit_8(DeclarativeCircuit):
483 @staticmethod
484 def structure():
485 return (
486 Block(gate=Gates.RX),
487 Block(gate=Gates.RZ),
488 Block(
489 gate=Gates.CRX,
490 topology=Topology.bricks,
491 ),
492 Block(gate=Gates.RX),
493 Block(gate=Gates.RZ),
494 Block(
495 gate=Gates.CRX,
496 topology=Topology.bricks,
497 offset=1,
498 ),
499 )
501 class Circuit_9(DeclarativeCircuit):
502 @staticmethod
503 def structure():
504 return (
505 Block(gate=Gates.H),
506 Block(gate="CZ", topology=Topology.stairs),
507 Block(gate=Gates.RX),
508 )
510 class Circuit_10(DeclarativeCircuit):
511 @staticmethod
512 def structure():
513 return (
514 Block(gate=Gates.RY),
515 Block(gate="CZ", topology=Topology.stairs, offset=-1, wrap=True),
516 Block(gate=Gates.RY),
517 )
519 class Circuit_13(DeclarativeCircuit):
520 @staticmethod
521 def structure():
522 return (
523 Block(gate=Gates.RY),
524 Block(
525 gate=Gates.CRZ,
526 topology=Topology.stairs,
527 wrap=True,
528 reverse=True,
529 mirror=False,
530 ),
531 Block(gate=Gates.RY),
532 Block(
533 gate=Gates.CRZ,
534 topology=Topology.stairs,
535 reverse=False,
536 mirror=False,
537 offset=lambda n: n - 1,
538 span=3,
539 wrap=True,
540 ),
541 )
543 class Circuit_14(DeclarativeCircuit):
544 @staticmethod
545 def structure():
546 return (
547 Block(gate=Gates.RY),
548 Block(
549 gate=Gates.CRX,
550 topology=Topology.stairs,
551 wrap=True,
552 reverse=True,
553 mirror=False,
554 ),
555 Block(gate=Gates.RY),
556 Block(
557 gate=Gates.CRX,
558 topology=Topology.stairs,
559 reverse=False,
560 mirror=False,
561 offset=lambda n: n - 1,
562 span=3,
563 wrap=True,
564 ),
565 )
567 class Circuit_15(DeclarativeCircuit):
568 @staticmethod
569 def structure():
570 return (
571 Block(gate=Gates.RY),
572 Block(
573 gate=Gates.CX,
574 topology=Topology.stairs,
575 wrap=True,
576 reverse=True,
577 mirror=False,
578 ),
579 Block(gate=Gates.RY),
580 Block(
581 gate=Gates.CX,
582 topology=Topology.stairs,
583 reverse=False,
584 mirror=False,
585 offset=lambda n: n - 1,
586 span=3,
587 wrap=True,
588 ),
589 )
591 class Circuit_16(DeclarativeCircuit):
592 @staticmethod
593 def structure():
594 return (
595 Block(gate=Gates.RX),
596 Block(gate=Gates.RZ),
597 Block(
598 gate=Gates.CRZ,
599 topology=Topology.bricks,
600 ),
601 Block(
602 gate=Gates.CRZ,
603 topology=Topology.bricks,
604 offset=1,
605 ),
606 )
608 class Circuit_17(DeclarativeCircuit):
609 @staticmethod
610 def structure():
611 return (
612 Block(gate=Gates.RX),
613 Block(gate=Gates.RZ),
614 Block(
615 gate=Gates.CRX,
616 topology=Topology.bricks,
617 ),
618 Block(
619 gate=Gates.CRX,
620 topology=Topology.bricks,
621 offset=1,
622 ),
623 )
625 class Circuit_18(DeclarativeCircuit):
626 @staticmethod
627 def structure():
628 return (
629 Block(gate=Gates.RX),
630 Block(gate=Gates.RZ),
631 Block(
632 gate=Gates.CRZ,
633 topology=Topology.stairs,
634 wrap=True,
635 mirror=False,
636 ),
637 )
639 class Circuit_19(DeclarativeCircuit):
640 @staticmethod
641 def structure():
642 return (
643 Block(gate=Gates.RX),
644 Block(gate=Gates.RZ),
645 Block(
646 gate=Gates.CRX,
647 topology=Topology.stairs,
648 wrap=True,
649 mirror=False,
650 ),
651 )
653 class Circuit_20(DeclarativeCircuit):
654 @staticmethod
655 def structure():
656 return (
657 Block(gate=Gates.RY),
658 Block(
659 gate=Gates.CX,
660 topology=Topology.stairs,
661 wrap=True,
662 reverse=True,
663 mirror=False,
664 ),
665 Block(gate=Gates.RY),
666 Block(
667 gate=Gates.CX,
668 topology=Topology.stairs,
669 reverse=False,
670 offset=lambda n: n - 2,
671 span=1,
672 wrap=True,
673 ),
674 )
676 class No_Entangling(DeclarativeCircuit):
677 @staticmethod
678 def structure():
679 return (Block(gate=Gates.Rot),)
681 class Hardware_Efficient(DeclarativeCircuit):
682 @staticmethod
683 def structure():
684 return (
685 Block(gate=Gates.RY),
686 Block(gate=Gates.RZ),
687 Block(gate=Gates.RY),
688 Block(
689 gate=Gates.CX,
690 topology=Topology.bricks,
691 mirror=False,
692 ),
693 Block(
694 gate=Gates.CX,
695 topology=Topology.bricks,
696 offset=-1,
697 modulo=True,
698 wrap=True,
699 mirror=False,
700 ),
701 )
703 class Strongly_Entangling(DeclarativeCircuit):
704 @staticmethod
705 def structure():
706 return (
707 Block(gate=Gates.Rot),
708 Block(
709 gate=Gates.CX,
710 topology=Topology.stairs,
711 wrap=True,
712 reverse=False,
713 mirror=False,
714 ),
715 Block(gate=Gates.Rot),
716 Block(
717 gate=Gates.CX,
718 topology=Topology.stairs,
719 reverse=False,
720 span=lambda n: n // 2,
721 wrap=True,
722 mirror=False,
723 ),
724 )
727class Encoding:
728 def __init__(
729 self, strategy: str, gates: Union[str, Callable, List[Union[str, Callable]]]
730 ):
731 """
732 Initializes an Encoding object.
734 Implementations closely follow https://doi.org/10.22331/q-2023-12-20-1210
736 Parameters
737 ----------
738 strategy : str
739 The encoding strategy to use. Available options:
740 ['hamming', 'binary', 'ternary']
741 gates : Union[str, Callable, List[Union[str, Callable]]]
742 The gates to use for encoding. Can be a string, a callable or a list
743 of strings or callables.
745 Returns
746 -------
747 None
749 Raises
750 -------
751 ValueError
752 If the encoding strategy is not implemented.
753 ValueError
754 If there is an error parsing the Gates.
755 """
756 if strategy not in ["hamming", "binary", "ternary"]:
757 raise ValueError(
758 f"Encoding strategy {strategy} not implemented. "
759 "Available options: ['hamming', 'binary', 'ternary']"
760 )
761 self._strategy = strategy
762 strategy = getattr(self, strategy)
764 log.debug(f"Using encoding strategy: '{strategy.__name__}'")
766 try:
767 self._gates = Gates.parse_gates(gates, Gates)
768 except ValueError as e:
769 raise ValueError(f"Error parsing encodings: {e}")
771 self.callable = [strategy(g) for g in self._gates]
773 def __len__(self):
774 return len(self.callable)
776 def __getitem__(self, idx):
777 return self.callable[idx]
779 def get_n_freqs(self, omegas):
780 """
781 Returns the number of frequencies required for the encoding strategy.
783 Parameters
784 ----------
785 omegas : int
786 The number of frequencies to encode.
788 Returns
789 -------
790 int
791 The number of frequencies required for the encoding strategy.
792 """
793 if self._strategy == "hamming":
794 return int(2 * omegas + 1)
795 elif self._strategy == "binary":
796 return int(2 ** (omegas + 1) - 1)
797 elif self._strategy == "ternary":
798 return int(3 ** (omegas))
799 else:
800 raise NotImplementedError
802 def get_spectrum(self, omegas):
803 """
804 Spectrum for one of the following encoding strategies:
806 Hamming: {-n_q -(n_q-1), ..., n_q}
807 Binary: {-2^{n_q}+1, ..., 2^{n_q}-1}
808 Ternary: {-floor(3^{n_q}/2), ..., floor(3^(n_q)/2)}
810 See https://doi.org/10.22331/q-2023-12-20-1210 for more details.
812 Parameters
813 ----------
814 omegas : int
815 The number of frequencies to encode.
817 Returns
818 -------
819 np.ndarray
820 The spectrum of the encoding strategy.
821 """
822 if self._strategy == "hamming":
823 return np.arange(-omegas, omegas + 1)
824 elif self._strategy == "binary":
825 return np.arange(-(2**omegas) + 1, 2**omegas)
826 elif self._strategy == "ternary":
827 limit = int(np.floor(3**omegas / 2))
828 return np.arange(-limit, limit + 1)
829 else:
830 raise NotImplementedError
832 def hamming(self, enc):
833 """
834 Hamming encoding strategy.
836 Returns an encoding function that uses the Hamming encoding strategy
837 which uses 2 * omegas + 1 frequencies for the encoding.
838 See https://doi.org/10.22331/q-2023-12-20-1210 for more details.
840 Parameters
841 ----------
842 enc : Callable
843 The encoding function to be wrapped.
845 Returns
846 -------
847 Callable
848 The wrapped encoding function.
849 """
850 return enc
852 def binary(self, enc):
853 """
854 Binary encoding strategy.
856 Returns an encoding function that scales the input by a factor of 2^wires.
858 Binary encoding uses 2^(omegas + 1) - 1 frequencies for the encoding.
859 See https://doi.org/10.22331/q-2023-12-20-1210 for more details.
861 Parameters
862 ----------
863 enc : Callable
864 The encoding function to be wrapped.
866 Returns
867 -------
868 Callable
869 The wrapped encoding function.
870 """
872 def _enc(inputs, wires, **kwargs):
873 return enc(inputs * (2**wires), wires, **kwargs)
875 return _enc
877 def ternary(self, enc):
878 """
879 Ternary encoding strategy.
881 Returns an encoding function that scales the input by a factor of 3^wires.
883 Ternary encoding uses 3^(omegas + 1) - 1 frequencies for the encoding.
884 See https://doi.org/10.22331/q-2023-12-20-1210 for more details.
886 Parameters
887 ----------
888 enc : Callable
889 The encoding function to be wrapped.
891 Returns
892 -------
893 Callable
894 The wrapped encoding function.
895 """
897 def _enc(inputs, wires, **kwargs):
898 return enc(inputs * (3**wires), wires, **kwargs)
900 return _enc
902 def golomb(self, enc):
903 raise NotImplementedError