pyqit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. pyqit/__init__.py +19 -0
  2. pyqit/ansatzes/__init__.py +18 -0
  3. pyqit/ansatzes/base.py +60 -0
  4. pyqit/ansatzes/basic_entangler.py +46 -0
  5. pyqit/ansatzes/cnot_ladder.py +45 -0
  6. pyqit/ansatzes/hardware_efficient.py +101 -0
  7. pyqit/ansatzes/sel.py +72 -0
  8. pyqit/ansatzes/simplified_two_design.py +59 -0
  9. pyqit/base/__init__.py +4 -0
  10. pyqit/base/_tags.py +574 -0
  11. pyqit/base/base_object.py +60 -0
  12. pyqit/core/__init__.py +59 -0
  13. pyqit/core/adapters/__init__.py +13 -0
  14. pyqit/core/adapters/lightning.py +284 -0
  15. pyqit/core/callbacks/__init__.py +14 -0
  16. pyqit/core/callbacks/base.py +84 -0
  17. pyqit/core/callbacks/checkpoint.py +368 -0
  18. pyqit/core/callbacks/early_stopping.py +119 -0
  19. pyqit/core/callbacks/history.py +45 -0
  20. pyqit/core/config.py +92 -0
  21. pyqit/core/embeddings.py +255 -0
  22. pyqit/core/losses/__init__.py +15 -0
  23. pyqit/core/losses/_registry.py +32 -0
  24. pyqit/core/losses/base.py +78 -0
  25. pyqit/core/losses/cross_entropy.py +75 -0
  26. pyqit/core/losses/hinge.py +44 -0
  27. pyqit/core/losses/mse.py +39 -0
  28. pyqit/core/measurements.py +38 -0
  29. pyqit/core/pipeline.py +561 -0
  30. pyqit/core/trainer/__init__.py +13 -0
  31. pyqit/core/trainer/_reporting.py +271 -0
  32. pyqit/core/trainer/history.py +95 -0
  33. pyqit/core/trainer/loops/__init__.py +14 -0
  34. pyqit/core/trainer/loops/_registry.py +52 -0
  35. pyqit/core/trainer/loops/base.py +151 -0
  36. pyqit/core/trainer/loops/lightning_loop.py +126 -0
  37. pyqit/core/trainer/loops/pennylane_loop.py +185 -0
  38. pyqit/core/trainer/trainer.py +404 -0
  39. pyqit/data/__init__.py +7 -0
  40. pyqit/data/datamodule.py +796 -0
  41. pyqit/models/__init__.py +21 -0
  42. pyqit/models/base/__init__.py +6 -0
  43. pyqit/models/base/base.py +168 -0
  44. pyqit/models/base/quantum_model.py +138 -0
  45. pyqit/models/classification/__init__.py +13 -0
  46. pyqit/models/classification/classifier_mixin.py +112 -0
  47. pyqit/models/classification/dressed.py +157 -0
  48. pyqit/models/classification/reuploading.py +153 -0
  49. pyqit/models/classification/vqc.py +128 -0
  50. pyqit/models/layers/__init__.py +6 -0
  51. pyqit/models/layers/dense.py +42 -0
  52. pyqit/models/layers/stages.py +163 -0
  53. pyqit/models/layers/vqc.py +118 -0
  54. pyqit/models/regression/__init__.py +6 -0
  55. pyqit/models/regression/regressor_mixin.py +15 -0
  56. pyqit/models/regression/vqr.py +132 -0
  57. pyqit/tests/__init__.py +0 -0
  58. pyqit/tests/_fixture_generators.py +117 -0
  59. pyqit/tests/scenarios.py +117 -0
  60. pyqit/tests/test_all_ansatz.py +98 -0
  61. pyqit/tests/test_all_embeddings.py +83 -0
  62. pyqit/tests/test_all_layers.py +35 -0
  63. pyqit/tests/test_all_losses.py +57 -0
  64. pyqit/tests/test_all_models.py +146 -0
  65. pyqit/tests/test_base.py +24 -0
  66. pyqit/tests/test_datamodule.py +247 -0
  67. pyqit/tests/test_pipeline.py +444 -0
  68. pyqit/tests/test_trainer.py +603 -0
  69. pyqit/utils/__init__.py +0 -0
  70. pyqit/utils/diagnostic.py +472 -0
  71. pyqit/utils/utils.py +133 -0
  72. pyqit-0.1.0.dist-info/METADATA +172 -0
  73. pyqit-0.1.0.dist-info/RECORD +76 -0
  74. pyqit-0.1.0.dist-info/WHEEL +5 -0
  75. pyqit-0.1.0.dist-info/licenses/LICENSE +191 -0
  76. pyqit-0.1.0.dist-info/top_level.txt +1 -0
pyqit/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ from importlib.metadata import version as _version
2
+ import logging
3
+
4
+ __version__ = _version("pyqit")
5
+
6
+ logging.getLogger("pyqit").addHandler(logging.NullHandler())
7
+
8
+ from pyqit.core.config import get_backend, set_backend, set_seed
9
+ from pyqit.core.trainer import Trainer
10
+ from pyqit.data.datamodule import DataModule
11
+
12
+ __all__ = [
13
+ "DataModule",
14
+ "Trainer",
15
+ "__version__",
16
+ "get_backend",
17
+ "set_backend",
18
+ "set_seed",
19
+ ]
@@ -0,0 +1,18 @@
1
+ """A module for quantum ansatzes."""
2
+
3
+ from pyqit.ansatzes.base import BaseAnsatz
4
+ from pyqit.ansatzes.basic_entangler import BasicEntanglerAnsatz
5
+ from pyqit.ansatzes.cnot_ladder import CNOTLadderAnsatz
6
+ from pyqit.ansatzes.hardware_efficient import EfficientSU2Ansatz, RealAmplitudesAnsatz
7
+ from pyqit.ansatzes.sel import SELAnsatz
8
+ from pyqit.ansatzes.simplified_two_design import SimplifiedTwoDesignAnsatz
9
+
10
+ __all__ = [
11
+ "BaseAnsatz",
12
+ "BasicEntanglerAnsatz",
13
+ "CNOTLadderAnsatz",
14
+ "EfficientSU2Ansatz",
15
+ "RealAmplitudesAnsatz",
16
+ "SELAnsatz",
17
+ "SimplifiedTwoDesignAnsatz",
18
+ ]
pyqit/ansatzes/base.py ADDED
@@ -0,0 +1,60 @@
1
+ from abc import abstractmethod
2
+
3
+ from pyqit.base.base_object import _PyQitObject
4
+
5
+
6
+ class BaseAnsatz(_PyQitObject):
7
+ """Base class for a parameterized quantum circuit block."""
8
+
9
+ _tags = {
10
+ "object_type": "ansatz",
11
+ "ansatz_type": None,
12
+ "n_qubits_min": 1,
13
+ "differentiable": None,
14
+ }
15
+
16
+ def __init__(self, n_qubits: int, n_layers: int = 1):
17
+ """
18
+ Base configuration for any Quantum Ansatz.
19
+
20
+ Parameters
21
+ ----------
22
+ n_qubits: int
23
+ The number of wires in the circuit.
24
+ n_layers: int
25
+ The depth of the circuit (repeating blocks).
26
+ """
27
+ self.n_qubits = n_qubits
28
+ self.n_layers = n_layers
29
+ super().__init__()
30
+
31
+ @abstractmethod
32
+ def build_circuit(self, weights):
33
+ """
34
+ The actual PennyLane circuit logic.
35
+
36
+ Parameters
37
+ ----------
38
+ weights:
39
+ Trainable parameters.
40
+ """
41
+ pass
42
+
43
+ @abstractmethod
44
+ def get_weight_shapes(self) -> dict:
45
+ """
46
+ Returns the shape of trainable weights required by this ansatz.
47
+ Used by wrappers to initialize parameters.
48
+
49
+ Returns
50
+ -------
51
+ dict:
52
+ e.g., {"weights": (n_layers, n_qubits, 3)}
53
+ """
54
+ pass
55
+
56
+ def get_circuit_func(self):
57
+ """
58
+ Returns the bound method to be passed to a QNode.
59
+ """
60
+ return self.build_circuit
@@ -0,0 +1,46 @@
1
+ import pennylane as qml
2
+
3
+ from pyqit.ansatzes.base import BaseAnsatz
4
+
5
+
6
+ class BasicEntanglerAnsatz(BaseAnsatz):
7
+ """Basic entangler layers: one rotation per qubit and a CNOT ring per layer.
8
+
9
+ Wraps PennyLane's `BasicEntanglerLayers` (Schuld et al. 2020 lineage).
10
+ The weights are one tensor, `weights`, of shape `(n_layers, n_qubits)`.
11
+
12
+ Parameters
13
+ ----------
14
+ n_qubits : int
15
+ n_layers : int, default 2
16
+ rotation : type, optional
17
+ Single-qubit rotation gate class. PennyLane's default is `qml.RX`.
18
+
19
+ Examples
20
+ --------
21
+ >>> import pennylane as qml
22
+ >>> from pyqit.ansatzes import BasicEntanglerAnsatz
23
+ >>> ansatz = BasicEntanglerAnsatz(n_qubits=4, n_layers=2, rotation=qml.RY)
24
+ >>> ansatz.get_weight_shapes()
25
+ {'weights': (2, 4)}
26
+ """
27
+
28
+ def __init__(self, n_qubits: int, n_layers: int = 2, rotation=None):
29
+ self.rotation = rotation
30
+ super().__init__(n_qubits, n_layers)
31
+
32
+ def build_circuit(self, weights):
33
+ """Apply the layers. Expects `weights["weights"]` of shape
34
+ `(n_layers, n_qubits)`."""
35
+ qml.BasicEntanglerLayers(
36
+ weights["weights"], wires=range(self.n_qubits), rotation=self.rotation
37
+ )
38
+
39
+ def get_weight_shapes(self) -> dict:
40
+ """Return `{"weights": (n_layers, n_qubits)}`."""
41
+ return {"weights": qml.BasicEntanglerLayers.shape(self.n_layers, self.n_qubits)}
42
+
43
+ @classmethod
44
+ def get_test_params(cls):
45
+ """List constructor kwargs used to parametrize this class in the test suite."""
46
+ return [{"n_qubits": 3, "n_layers": 2}, {"n_qubits": 2, "rotation": qml.RY}]
@@ -0,0 +1,45 @@
1
+ import pennylane as qml
2
+
3
+ from pyqit.ansatzes.base import BaseAnsatz
4
+
5
+
6
+ class CNOTLadderAnsatz(BaseAnsatz):
7
+ """The variational block of Mari et al. (2020): a CNOT ladder, then RY.
8
+
9
+ Each layer applies CNOT to the wire pairs ``(0, 1), (2, 3), ...``, then to
10
+ ``(1, 2), (3, 4), ...``, then one RY per wire.
11
+
12
+ Parameters
13
+ ----------
14
+ n_qubits : int
15
+ n_layers : int, default 6
16
+ ``q_depth`` in the paper.
17
+
18
+ References
19
+ ----------
20
+ Mari, Bromley, Izaac, Schuld, Killoran, "Transfer learning in hybrid
21
+ classical-quantum neural networks", Quantum 4, 340 (2020). PennyLane's
22
+ "Quantum transfer learning" demo is the reference implementation.
23
+ """
24
+
25
+ def __init__(self, n_qubits: int, n_layers: int = 6):
26
+ super().__init__(n_qubits, n_layers)
27
+
28
+ def build_circuit(self, weights):
29
+ """Apply the layers. Expects `weights["weights"]` of shape
30
+ `(n_layers, n_qubits)`."""
31
+ for layer in range(self.n_layers):
32
+ for start in (0, 1):
33
+ for i in range(start, self.n_qubits - 1, 2):
34
+ qml.CNOT(wires=[i, i + 1])
35
+ for w in range(self.n_qubits):
36
+ qml.RY(weights["weights"][layer, w], wires=w)
37
+
38
+ def get_weight_shapes(self) -> dict:
39
+ """Return `{"weights": (n_layers, n_qubits)}`."""
40
+ return {"weights": (self.n_layers, self.n_qubits)}
41
+
42
+ @classmethod
43
+ def get_test_params(cls):
44
+ """List constructor kwargs used to parametrize this class in the test suite."""
45
+ return [{"n_qubits": 3, "n_layers": 2}, {"n_qubits": 2}]
@@ -0,0 +1,101 @@
1
+ import pennylane as qml
2
+ from skbase.utils.dependencies import _check_soft_dependencies
3
+
4
+ from pyqit.ansatzes.base import BaseAnsatz
5
+
6
+
7
+ class RealAmplitudesAnsatz(BaseAnsatz):
8
+ """Qiskit's `RealAmplitudes`: RY layers separated by CX entanglers.
9
+
10
+ The hardware-efficient ansatz of Kandala et al. 2017 (Nature) as Qiskit's
11
+ circuit library builds it, and the default ansatz of Qiskit ML's `VQC`.
12
+ The circuit is Qiskit's own, converted through the `pennylane-qiskit`
13
+ plugin, so it needs the `qiskit` extra and Python 3.11 or newer.
14
+
15
+ The weights are Qiskit's flat parameter vector, `weights`, of shape
16
+ `(n_qubits * (n_layers + 1),)`. With `skip_final_rotation_layer` it is
17
+ `(n_qubits * n_layers,)`.
18
+
19
+ Parameters
20
+ ----------
21
+ n_qubits : int
22
+ n_layers : int, default 3
23
+ Qiskit's `reps`: the number of entangling blocks. Rotation layers
24
+ number `n_layers + 1` unless `skip_final_rotation_layer`.
25
+ entanglement : str, default "reverse_linear"
26
+ Any entanglement Qiskit accepts, e.g. `"linear"`, `"full"`,
27
+ `"circular"`.
28
+ skip_final_rotation_layer : bool, default False
29
+
30
+ References
31
+ ----------
32
+ Kandala et al., "Hardware-efficient variational quantum eigensolver for
33
+ small molecules and quantum magnets", Nature 549, 242 (2017).
34
+
35
+ Examples
36
+ --------
37
+ >>> from pyqit.ansatzes import RealAmplitudesAnsatz
38
+ >>> from pyqit.core import ZZFeatureMap
39
+ >>> from pyqit.models import VQCClassifier
40
+ >>> model = VQCClassifier(
41
+ ... n_qubits=2, ansatz=RealAmplitudesAnsatz, encoder=ZZFeatureMap
42
+ ... )
43
+ """
44
+
45
+ _tags = {"python_dependencies": "pennylane-qiskit"}
46
+ _qiskit_circuit = "real_amplitudes"
47
+
48
+ def __init__(
49
+ self,
50
+ n_qubits: int,
51
+ n_layers: int = 3,
52
+ entanglement: str = "reverse_linear",
53
+ skip_final_rotation_layer: bool = False,
54
+ ):
55
+ self.entanglement = entanglement
56
+ self.skip_final_rotation_layer = skip_final_rotation_layer
57
+ super().__init__(n_qubits, n_layers)
58
+
59
+ if not _check_soft_dependencies("pennylane-qiskit", severity="none"):
60
+ raise ImportError(
61
+ f"{type(self).__name__} wraps Qiskit's circuit through the "
62
+ "pennylane-qiskit plugin, which is not installed. Install it with "
63
+ "`pip install pyqit[qiskit]`"
64
+ )
65
+ from qiskit.circuit import library
66
+
67
+ self.circuit = getattr(library, self._qiskit_circuit)(
68
+ n_qubits,
69
+ entanglement=entanglement,
70
+ reps=n_layers,
71
+ skip_final_rotation_layer=skip_final_rotation_layer,
72
+ )
73
+ self._template = qml.from_qiskit(self.circuit)
74
+
75
+ def build_circuit(self, weights):
76
+ """Apply the circuit. Expects `weights["weights"]` as a flat vector in
77
+ the order of `circuit.parameters`."""
78
+ self._template(weights["weights"])
79
+
80
+ def get_weight_shapes(self) -> dict:
81
+ """Return `{"weights": (n_params,)}`, Qiskit's flat parameter vector."""
82
+ return {"weights": (len(self.circuit.parameters),)}
83
+
84
+ @classmethod
85
+ def get_test_params(cls):
86
+ """List constructor kwargs used to parametrize this class in the test suite."""
87
+ return [
88
+ {"n_qubits": 3, "n_layers": 2},
89
+ {"n_qubits": 2, "n_layers": 1, "entanglement": "circular"},
90
+ ]
91
+
92
+
93
+ class EfficientSU2Ansatz(RealAmplitudesAnsatz):
94
+ """Qiskit's `EfficientSU2`: RY and RZ layers separated by CX entanglers.
95
+
96
+ `RealAmplitudesAnsatz` with an RZ layer after every RY layer, Qiskit's
97
+ default `su2_gates`. Same parameters and the same reference. The second
98
+ rotation doubles the weight vector to `(2 * n_qubits * (n_layers + 1),)`.
99
+ """
100
+
101
+ _qiskit_circuit = "efficient_su2"
pyqit/ansatzes/sel.py ADDED
@@ -0,0 +1,72 @@
1
+ import pennylane as qml
2
+
3
+ from pyqit.ansatzes.base import BaseAnsatz
4
+
5
+
6
+ class SELAnsatz(BaseAnsatz):
7
+ """Strongly entangling layers of Schuld et al. (2020).
8
+
9
+ Wraps PennyLane's `StronglyEntanglingLayers`. Each layer applies three
10
+ rotations to every qubit, then a CNOT layer whose range grows with the
11
+ layer index. The weights are one tensor, `weights`, of shape
12
+ `(n_layers, n_qubits, 3)`.
13
+
14
+ Parameters
15
+ ----------
16
+ n_qubits : int
17
+ n_layers : int, default 2
18
+
19
+ References
20
+ ----------
21
+ Schuld, Bocharov, Svore, Wiebe, "Circuit-centric quantum classifiers",
22
+ Phys. Rev. A 101, 032308 (2020).
23
+
24
+ Examples
25
+ --------
26
+ >>> from pyqit.ansatzes import SELAnsatz
27
+ >>> from pyqit.models import VQCClassifier
28
+ >>> model = VQCClassifier(n_qubits=4, n_layers=3, ansatz=SELAnsatz)
29
+ """
30
+
31
+ def __init__(self, n_qubits: int, n_layers: int = 2):
32
+ super().__init__(n_qubits, n_layers)
33
+
34
+ def build_circuit(self, weights):
35
+ """
36
+ Construct and apply the strongly entangling layers to the quantum circuit.
37
+
38
+ Parameters
39
+ ----------
40
+ weights : dict
41
+ A dictionary containing the parameter tensors. Must include the
42
+ key `"weights"` with a tensor of shape `(n_layers, n_qubits, 3)`.
43
+
44
+ """
45
+ w_tensor = weights["weights"]
46
+ qml.templates.StronglyEntanglingLayers(w_tensor, wires=range(self.n_qubits))
47
+
48
+ def get_weight_shapes(self) -> dict:
49
+ """
50
+ Get the shapes of the trainable weights required by the ansatz.
51
+
52
+ Returns
53
+ -------
54
+ dict
55
+ A dictionary mapping the weight parameter name (`"weights"`) to
56
+ its expected shape tuple `(n_layers, n_qubits, 3)`.
57
+ """
58
+ shape = (self.n_layers, self.n_qubits, 3)
59
+ return {"weights": shape}
60
+
61
+ @classmethod
62
+ def get_test_params(cls):
63
+ """
64
+ Retrieve a set of default parameters for testing the ansatz.
65
+
66
+ Returns
67
+ -------
68
+ list of dict
69
+ A list containing a dictionary of valid initialization parameters
70
+ for the class.
71
+ """
72
+ return [{"n_qubits": 3, "n_layers": 2}]
@@ -0,0 +1,59 @@
1
+ import pennylane as qml
2
+
3
+ from pyqit.ansatzes.base import BaseAnsatz
4
+
5
+
6
+ class SimplifiedTwoDesignAnsatz(BaseAnsatz):
7
+ """Simplified two-design ansatz of Cerezo et al. 2021 (Nat. Commun.).
8
+
9
+ Wraps PennyLane's `SimplifiedTwoDesign`: an initial RY layer, then
10
+ `n_layers` of controlled-Z gates on alternating pairs each followed by RY
11
+ rotations. This is the circuit the local-cost trainability result was
12
+ proved on, so it pairs with the barren-plateau diagnostic.
13
+
14
+ There are two weight tensors, `initial_layer_weights` of shape
15
+ `(n_qubits,)` and `weights` of shape `(n_layers, n_qubits - 1, 2)`.
16
+
17
+ Parameters
18
+ ----------
19
+ n_qubits : int
20
+ At least 2.
21
+ n_layers : int, default 2
22
+
23
+ References
24
+ ----------
25
+ Cerezo, Sone, Volkoff, Cincio, Coles, "Cost function dependent barren
26
+ plateaus in shallow parametrized quantum circuits", Nat. Commun. 12, 1791
27
+ (2021).
28
+
29
+ Examples
30
+ --------
31
+ >>> from pyqit.ansatzes import SimplifiedTwoDesignAnsatz
32
+ >>> SimplifiedTwoDesignAnsatz(n_qubits=3, n_layers=2).get_weight_shapes()
33
+ {'initial_layer_weights': (3,), 'weights': (2, 2, 2)}
34
+ """
35
+
36
+ _tags = {"n_qubits_min": 2}
37
+
38
+ def __init__(self, n_qubits: int, n_layers: int = 2):
39
+ super().__init__(n_qubits, n_layers)
40
+
41
+ def build_circuit(self, weights):
42
+ """Apply the layers. Expects `weights["initial_layer_weights"]` of shape
43
+ `(n_qubits,)` and `weights["weights"]` of shape `(n_layers, n_qubits - 1, 2)`.
44
+ """
45
+ qml.SimplifiedTwoDesign(
46
+ weights["initial_layer_weights"],
47
+ weights["weights"],
48
+ wires=range(self.n_qubits),
49
+ )
50
+
51
+ def get_weight_shapes(self) -> dict:
52
+ """Return the two weight shapes, `initial_layer_weights` and `weights`."""
53
+ initial, layers = qml.SimplifiedTwoDesign.shape(self.n_layers, self.n_qubits)
54
+ return {"initial_layer_weights": initial, "weights": layers}
55
+
56
+ @classmethod
57
+ def get_test_params(cls):
58
+ """List constructor kwargs used to parametrize this class in the test suite."""
59
+ return [{"n_qubits": 3, "n_layers": 2}, {"n_qubits": 2, "n_layers": 1}]
pyqit/base/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Base module for PyQit."""
2
+
3
+ from pyqit.base._tags import OBJECT_TAGS, object_overview
4
+ from pyqit.base.base_object import _PyQitObject, all_objects