cqlib-qml 0.1.0b1__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 (48) hide show
  1. cqlib_qml/__init__.py +58 -0
  2. cqlib_qml/_configuration.py +53 -0
  3. cqlib_qml/_numerics.py +31 -0
  4. cqlib_qml/algorithms/HQNN_classification.py +265 -0
  5. cqlib_qml/algorithms/QKM.py +362 -0
  6. cqlib_qml/algorithms/QNN_classification.py +245 -0
  7. cqlib_qml/algorithms/QSVM.py +340 -0
  8. cqlib_qml/algorithms/VQC.py +578 -0
  9. cqlib_qml/algorithms/__init__.py +3 -0
  10. cqlib_qml/ansatz/BasicQNN.py +153 -0
  11. cqlib_qml/ansatz/CRADL.py +133 -0
  12. cqlib_qml/ansatz/CRAML.py +177 -0
  13. cqlib_qml/ansatz/HE_ansatz.py +239 -0
  14. cqlib_qml/ansatz/__init__.py +45 -0
  15. cqlib_qml/ansatz/ansatz.py +1209 -0
  16. cqlib_qml/data/__init__.py +63 -0
  17. cqlib_qml/data/data.py +286 -0
  18. cqlib_qml/data/data_preprocess.py +315 -0
  19. cqlib_qml/differentiator/__init__.py +47 -0
  20. cqlib_qml/differentiator/adjoint.py +509 -0
  21. cqlib_qml/differentiator/parameter_shift.py +284 -0
  22. cqlib_qml/encoder/FRQI.py +404 -0
  23. cqlib_qml/encoder/NEQR.py +163 -0
  24. cqlib_qml/encoder/QubitLattice.py +127 -0
  25. cqlib_qml/encoder/ZZFeature.py +159 -0
  26. cqlib_qml/encoder/__init__.py +56 -0
  27. cqlib_qml/encoder/amplitude.py +162 -0
  28. cqlib_qml/encoder/angle.py +121 -0
  29. cqlib_qml/encoder/basis.py +93 -0
  30. cqlib_qml/encoder/image_encoder.py +131 -0
  31. cqlib_qml/layer/__init__.py +56 -0
  32. cqlib_qml/layer/activation.py +438 -0
  33. cqlib_qml/layer/layer.py +297 -0
  34. cqlib_qml/layer/linear.py +328 -0
  35. cqlib_qml/loss.py +407 -0
  36. cqlib_qml/models/HQNN.py +171 -0
  37. cqlib_qml/models/QNN.py +154 -0
  38. cqlib_qml/models/__init__.py +44 -0
  39. cqlib_qml/models/module.py +397 -0
  40. cqlib_qml/optimizer.py +684 -0
  41. cqlib_qml/scheduler.py +614 -0
  42. cqlib_qml/utils/__init__.py +37 -0
  43. cqlib_qml/utils/grad_matrix.py +29 -0
  44. cqlib_qml-0.1.0b1.dist-info/METADATA +131 -0
  45. cqlib_qml-0.1.0b1.dist-info/RECORD +48 -0
  46. cqlib_qml-0.1.0b1.dist-info/WHEEL +5 -0
  47. cqlib_qml-0.1.0b1.dist-info/licenses/LICENSE +201 -0
  48. cqlib_qml-0.1.0b1.dist-info/top_level.txt +1 -0
cqlib_qml/__init__.py ADDED
@@ -0,0 +1,58 @@
1
+ # cqlib_qml/__init__.py
2
+ """
3
+ Cqlib-QML: Quantum machine learning package based on Cqlib.
4
+
5
+ This package provides quantum machine learning building blocks on top of
6
+ the `cqlib <https://github.com/cq-lib/cqlib>`_ quantum computing library,
7
+ including data encoders, parameterized quantum circuits (ansatz),
8
+ differentiators, neural network models, and quantum algorithms.
9
+
10
+ Available Subpackages:
11
+ - algorithms: Quantum machine learning algorithms (QKM, QSVM, VQC).
12
+ - ansatz: Parameterized quantum circuits (HEAnsatz, BasicQNN, CRADL, CRAML).
13
+ - data: Classical data loading and preprocessing utilities.
14
+ - differentiator: Gradient computation strategies (adjoint, parameter-shift).
15
+ - encoder: Quantum data encoders (Amplitude, Angle, Basis, FRQI, NEQR).
16
+ - layer: Hybrid quantum-classical layers (Linear, activations).
17
+ - models: Quantum neural network models (QNN, HQNN).
18
+ - utils: Gradient matrix computation and other utilities.
19
+
20
+ Examples:
21
+ >>> import numpy as np
22
+ >>> from cqlib_qml.ansatz import HEAnsatz
23
+ >>> from cqlib_qml.models import QNN
24
+ >>>
25
+ >>> ansatz = HEAnsatz(n_qubits=4, d=2, layers=["RY", "CX"])
26
+ >>> ansatz.set_measurement(readouts=[0])
27
+ >>> model = QNN(ansatz=ansatz)
28
+ """
29
+
30
+ from . import algorithms, ansatz, data, differentiator, encoder, layer, models, utils
31
+ from .loss import BCELoss, CrossEntropy, HingeLoss, LossFun, MSELoss, SoftmaxCrossEntropy
32
+ from .optimizer import AdaGrad, Adam, OptimizerBase, OptimizerInitializer, RMSProp, SGD
33
+
34
+ __version__ = "0.1.0-beta.1"
35
+
36
+ __all__ = [
37
+ "algorithms",
38
+ "ansatz",
39
+ "data",
40
+ "differentiator",
41
+ "encoder",
42
+ "layer",
43
+ "models",
44
+ "utils",
45
+ "LossFun",
46
+ "HingeLoss",
47
+ "MSELoss",
48
+ "BCELoss",
49
+ "CrossEntropy",
50
+ "SoftmaxCrossEntropy",
51
+ "OptimizerBase",
52
+ "OptimizerInitializer",
53
+ "SGD",
54
+ "AdaGrad",
55
+ "RMSProp",
56
+ "Adam",
57
+ "__version__",
58
+ ]
@@ -0,0 +1,53 @@
1
+ """Parse constructor-like configuration without evaluating Python code."""
2
+ import ast
3
+ import inspect
4
+
5
+
6
+ def parse_configuration(value, registry, nested=None):
7
+ """Accept a registered name or keyword-only call with literal values.
8
+
9
+ Nested calls are restricted to the supplied scheduler registry. Attribute
10
+ access, positional arguments and expanded arguments are never evaluated.
11
+ """
12
+ try:
13
+ tree = ast.parse(value.strip(), mode='eval').body
14
+ except (SyntaxError, TypeError, AttributeError) as exc:
15
+ raise ValueError(f'Invalid configuration: {value!r}') from exc
16
+
17
+ def construct(node, constructors):
18
+ if isinstance(node, ast.Name):
19
+ name, keywords = node.id, []
20
+ elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and not node.args:
21
+ name, keywords = node.func.id, node.keywords
22
+ else:
23
+ raise ValueError('Configuration must be a registered name or keyword-only call')
24
+ constructor = constructors.get(name.casefold())
25
+ if constructor is None:
26
+ raise ValueError(f'Unsupported configuration name: {name}')
27
+ allowed = {key for key, parameter in inspect.signature(constructor).parameters.items()
28
+ if parameter.kind in (parameter.POSITIONAL_OR_KEYWORD, parameter.KEYWORD_ONLY)}
29
+ kwargs = {}
30
+ for keyword in keywords:
31
+ key = keyword.arg
32
+ if key not in allowed or key in kwargs:
33
+ raise ValueError(f'Unknown or duplicate parameter: {key}')
34
+ if isinstance(keyword.value, ast.Call) and key == 'lr_scheduler' and nested:
35
+ kwargs[key] = construct(keyword.value, nested)
36
+ else:
37
+ try:
38
+ kwargs[key] = ast.literal_eval(keyword.value)
39
+ except (ValueError, TypeError) as exc:
40
+ raise ValueError(f'Parameter {key} must be a literal') from exc
41
+ return constructor(**kwargs)
42
+
43
+ return construct(tree, registry)
44
+
45
+
46
+ def same_parameter_value(old, new):
47
+ """Compare scalar configurations without assuming array equality is scalar."""
48
+ if old is new:
49
+ return True
50
+ try:
51
+ return bool(old == new)
52
+ except (TypeError, ValueError):
53
+ return False
cqlib_qml/_numerics.py ADDED
@@ -0,0 +1,31 @@
1
+ """Internal scaling helpers that avoid complex reciprocal overflow."""
2
+ import numpy as np
3
+
4
+
5
+ def scaled_vector(vector):
6
+ """Return a vector at unit component scale and its real scale factor.
7
+
8
+ Divide real and imaginary components separately: complex division can
9
+ overflow its reciprocal for a finite, subnormal real denominator.
10
+ Exact zero is returned unchanged with scale zero.
11
+ """
12
+ vector = np.asarray(vector)
13
+ if not np.isfinite(vector).all():
14
+ raise ValueError("Vector components must be finite.")
15
+ scale = max(np.max(np.abs(vector.real), initial=0.),
16
+ np.max(np.abs(vector.imag), initial=0.))
17
+ if scale == 0:
18
+ return vector.copy(), scale
19
+ if np.iscomplexobj(vector):
20
+ scaled = np.empty(vector.shape, dtype=np.complex128)
21
+ scaled.real = vector.real / scale
22
+ scaled.imag = vector.imag / scale
23
+ else:
24
+ scaled = vector / scale
25
+ return scaled, scale
26
+
27
+
28
+ def stable_norm(vector):
29
+ """Compute a Euclidean norm without squaring unscaled components."""
30
+ scaled, scale = scaled_vector(vector)
31
+ return scale * np.linalg.norm(scaled)
@@ -0,0 +1,265 @@
1
+ # cqlib_qml/algorithms/HQNN_classification.py
2
+ """
3
+ Hybrid Quantum Neural Network for classification tasks.
4
+
5
+ This module provides a complete training pipeline for Hybrid Quantum Neural Networks
6
+ (HQNNs) on classification problems. It includes training and validation loops with
7
+ checkpoint saving, TensorBoard logging, and configuration management.
8
+
9
+ The HQNN combines a parameterized quantum circuit (ansatz) with a classical
10
+ fully-connected layer, leveraging the strengths of both quantum and classical
11
+ computing paradigms.
12
+
13
+ Examples:
14
+ >>> from cqlib_qml.algorithms.HQNN_classification import train, validate
15
+ >>> from cqlib_qml.models import HQNN
16
+ >>> from cqlib_qml.ansatz import HEAnsatz
17
+ >>> from cqlib_qml.encoder import FRQI
18
+ >>>
19
+ >>> # Setup model
20
+ >>> ansatz = HEAnsatz(n_qubits=5, d=3, layers=["RY", "CX"])
21
+ >>> model = HQNN(ansatz=ansatz, out_dim=3)
22
+ >>>
23
+ >>> # Train
24
+ >>> for epoch in range(EPOCHS):
25
+ ... train(epoch, 0, model, train_loader, loss_fun, model_path, EPOCHS, tb)
26
+ ... validate(epoch, model, test_loader, loss_fun, BATCH_SIZE, tb)
27
+ """
28
+
29
+ import time
30
+ from pathlib import Path
31
+ import tqdm
32
+ import yaml
33
+ import numpy as np
34
+ from typing import Optional, Any
35
+
36
+ from cqlib_qml.data.data_preprocess import get_mnist_dataloader
37
+ from cqlib_qml.models import HQNN
38
+ from cqlib_qml.ansatz import *
39
+ from cqlib_qml.encoder import *
40
+ from cqlib_qml.loss import *
41
+ from cqlib_qml.optimizer import *
42
+
43
+ try:
44
+ import torch.utils.tensorboard
45
+
46
+ TENSORBOARD_AVAILABLE = True
47
+ except ImportError:
48
+ TENSORBOARD_AVAILABLE = False
49
+
50
+
51
+ def train(
52
+ ep: int,
53
+ it_start: int,
54
+ net: HQNN,
55
+ train_loader,
56
+ loss_fun,
57
+ model_path: str,
58
+ total_epochs: int,
59
+ tb: Optional[Any] = None,
60
+ ) -> None:
61
+ """
62
+ Train the HQNN model for one epoch.
63
+
64
+ This function iterates through the training data, computes forward and
65
+ backward passes, updates model parameters, and logs training metrics.
66
+
67
+ Args:
68
+ ep (int): Current epoch number (0-indexed).
69
+ it_start (int): Starting iteration index within the epoch. Used for
70
+ resuming training from a checkpoint.
71
+ net (HQNN): The Hybrid Quantum Neural Network model to train.
72
+ train_loader: DataLoader for training data.
73
+ loss_fun: Loss function instance.
74
+ model_path (str): Path to save checkpoints.
75
+ total_epochs (int): Total number of epochs for training.
76
+ tb (optional): TensorBoard SummaryWriter instance for logging.
77
+ If None, logging is disabled. Defaults to None.
78
+
79
+ Note:
80
+ The function uses the global variable `EPOCH` defined in the main script
81
+ for determining the latest checkpoint.
82
+
83
+ Examples:
84
+ >>> for epoch in range(10):
85
+ ... train(epoch, 0, model, train_loader, loss_fun, "./checkpoints/", 10, tensorboard_writer)
86
+ """
87
+ loader = tqdm.tqdm(train_loader, desc="Training epoch {}".format(ep + 1), leave=True)
88
+ for it, (x_train, y_train) in enumerate(loader):
89
+ if it < it_start:
90
+ continue
91
+ it_start = 0
92
+ expectations = net.forward(x_train) # (batch_size, n_classes)
93
+ # Prepare labels for CrossEntropy loss
94
+ y_true = np.zeros(expectations.shape)
95
+ for i in range(len(y_train)):
96
+ idx = y_train[i]
97
+ y_true[i, idx] = 1
98
+ y_pred = expectations
99
+
100
+ # Compute loss
101
+ loss = loss_fun(y_pred, y_true)
102
+ # Optimize: backward pass and parameter update
103
+ net.backward(loss_fun.grads())
104
+ net.update(cur_loss=loss)
105
+ net.zero_grad()
106
+
107
+ # Compute accuracy
108
+ correct = sum(y_pred.argmax(axis=1) == y_train)
109
+ accuracy = correct / len(y_train)
110
+ loader.set_postfix(it=it, loss="{:.3f}".format(loss), accuracy="{:.3f}".format(accuracy))
111
+ # Log metrics
112
+ if tb is not None:
113
+ tb.add_scalar("train/loss", loss, ep * len(loader) + it)
114
+ tb.add_scalar("train/accuracy", accuracy, ep * len(loader) + it)
115
+ # Save checkpoint
116
+ latest = (ep + 1) == total_epochs and (it + 1) == len(loader)
117
+ if (it != 0 and it % 30 == 0) or latest:
118
+ net.save_checkpoint(model_path, ep, it, latest)
119
+
120
+
121
+ def validate(ep: int, net: HQNN, test_loader, loss_fun, batch_size: int, tb: Optional[Any] = None) -> tuple:
122
+ """
123
+ Validate the HQNN model on the test dataset.
124
+
125
+ This function evaluates the model's performance on the test set and
126
+ computes average loss and accuracy across all batches.
127
+
128
+ Args:
129
+ ep (int): Current epoch number (0-indexed).
130
+ net (HQNN): The Hybrid Quantum Neural Network model to validate.
131
+ test_loader: DataLoader for test data.
132
+ loss_fun: Loss function instance.
133
+ batch_size (int): Batch size used in the dataloader.
134
+ tb (optional): TensorBoard SummaryWriter instance for logging.
135
+ If None, logging is disabled. Defaults to None.
136
+
137
+ Returns:
138
+ tuple: (avg_loss, avg_acc) where:
139
+ - avg_loss (float): Average loss over the validation set.
140
+ - avg_acc (float): Average accuracy over the validation set.
141
+
142
+ Note:
143
+ Model is evaluated in non-trainable mode.
144
+
145
+ Examples:
146
+ >>> avg_loss, avg_acc = validate(epoch, model, test_loader, loss_fun, 32, tensorboard_writer)
147
+ >>> print(f"Validation: loss={avg_loss:.4f}, acc={avg_acc:.4f}")
148
+ """
149
+ loader_val = tqdm.tqdm(test_loader, desc="Validating epoch {}".format(ep + 1), leave=True)
150
+ total_loss = 0.0
151
+ total_samples = 0
152
+ total_correct = 0
153
+ for it, (x_test, y_test) in enumerate(loader_val):
154
+ expectations_val = net.forward(x_test, trainable=False)
155
+ # Prepare labels for CrossEntropy loss
156
+ y_true_val = np.zeros(expectations_val.shape)
157
+ for i in range(len(y_test)):
158
+ idx = y_test[i]
159
+ y_true_val[i, idx] = 1
160
+ y_pred_val = expectations_val
161
+
162
+ loss_val = loss_fun(y_pred_val, y_true_val)
163
+ count = len(y_test)
164
+ total_samples += count
165
+ total_loss += loss_val if getattr(loss_fun, "reduction", "mean") == "sum" else loss_val * count
166
+
167
+ # Compute accuracy
168
+ correct_val = sum(y_pred_val.argmax(axis=1) == y_test)
169
+ total_correct += correct_val
170
+ accuracy_val = correct_val / len(y_test)
171
+ loader_val.set_postfix(
172
+ it=it,
173
+ loss="{:.3f}".format(loss_val),
174
+ accuracy="{:.3f}".format(accuracy_val),
175
+ )
176
+
177
+ if not total_samples:
178
+ raise ValueError("Validation dataset must not be empty")
179
+ avg_loss = total_loss / total_samples
180
+ avg_acc = total_correct / total_samples
181
+ if tb is not None:
182
+ tb.add_scalar("validation/loss", avg_loss, ep)
183
+ tb.add_scalar("validation/accuracy", avg_acc, ep)
184
+ print("Validation Average Loss: {}, Accuracy: {}".format(avg_loss, avg_acc))
185
+ return avg_loss, avg_acc
186
+
187
+
188
+ if __name__ == "__main__":
189
+ """
190
+ Main execution block for training HQNN on MNIST dataset.
191
+
192
+ This script demonstrates a complete training pipeline including:
193
+ 1. Data loading and preprocessing (MNIST with FRQI encoding)
194
+ 2. Model initialization (HEAnsatz + HQNN)
195
+ 3. Training loop with progress tracking
196
+ 4. Validation and checkpoint saving
197
+ 5. Configuration logging
198
+
199
+ Configuration:
200
+ - Dataset: MNIST (classes 0, 1, 2)
201
+ - Image size: 4x4 pixels
202
+ - Grayscale: 2 levels
203
+ - Encoding: FRQI (Flexible Representation of Quantum Images)
204
+ - Ansatz: HEAnsatz (Hardware-Efficient)
205
+ - Loss: SoftmaxCrossEntropy
206
+ - Optimizer: Adam (lr=0.01)
207
+ - Epochs: 10
208
+ - Batch size: 32
209
+ """
210
+ # Training parameters
211
+ EPOCH = 10
212
+ BATCH_SIZE = 32
213
+ LR = 0.01
214
+ SEED = 0
215
+ ep_start = 0
216
+ it_start = 0
217
+ np.random.seed(SEED)
218
+
219
+ classes = [0, 1, 2]
220
+ RESIZE = (4, 4)
221
+ GRAYSCALE = 2
222
+ n_pixels = RESIZE[0] * RESIZE[1]
223
+ # Initialize encoder, loss function, and optimizer
224
+ encoding = FRQI(n_pixels, GRAYSCALE)
225
+ loss_fun = SoftmaxCrossEntropy()
226
+ optimizer = Adam(lr=LR)
227
+
228
+ # Setup quantum circuit parameters
229
+ pos_qubits = int(np.log2(n_pixels))
230
+ color_qubits = 1
231
+ n_qubits = pos_qubits + color_qubits
232
+
233
+ # Initialize ansatz and model
234
+ ansatz = HEAnsatz(n_qubits, d=5, layers=["RZ", "RY", "RZ", "CX"])
235
+ train_loader, test_loader = get_mnist_dataloader(classes, RESIZE, encoding, BATCH_SIZE, GRAYSCALE)
236
+ net = HQNN(ansatz=ansatz, out_dim=len(classes), optimizer=optimizer)
237
+
238
+ # Setup logging and checkpoint directories
239
+ now_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
240
+ model_path = "./HQNN_MNIST_" + now_time + "/"
241
+ Path(model_path).mkdir(parents=True, exist_ok=True)
242
+ tb = torch.utils.tensorboard.SummaryWriter(log_dir=model_path + "logs") if TENSORBOARD_AVAILABLE else None
243
+
244
+ # Save configuration
245
+ config = {
246
+ "classes": str(classes),
247
+ "encoding": str(encoding),
248
+ "ansatz": str(ansatz),
249
+ "grayscale": GRAYSCALE,
250
+ "resize": str(RESIZE),
251
+ "LR": LR,
252
+ "loss_fun": str(loss_fun),
253
+ "seed": SEED,
254
+ }
255
+ with open(model_path + "config.yaml", "w") as config_file:
256
+ config_file.write(yaml.dump(config))
257
+
258
+ # Training loop
259
+ for ep in range(ep_start, EPOCH):
260
+ if ep > ep_start:
261
+ it_start = 0
262
+ train(ep, it_start, net, train_loader, loss_fun, model_path, EPOCH, tb)
263
+ avg_loss, avg_acc = validate(ep, net, test_loader, loss_fun, BATCH_SIZE, tb)
264
+ with open(model_path + "result.yaml", "a") as result_file:
265
+ result_file.write("Validation Average Loss: {}, Accuracy: {}\n".format(avg_loss, avg_acc))