qxmt 0.0.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.
- qxmt/__init__.py +45 -0
- qxmt/configs.py +70 -0
- qxmt/constants.py +24 -0
- qxmt/datasets/__init__.py +7 -0
- qxmt/datasets/builder.py +233 -0
- qxmt/datasets/dummy/__init__.py +5 -0
- qxmt/datasets/dummy/linear.py +26 -0
- qxmt/datasets/raw_preprocess/__init__.py +0 -0
- qxmt/datasets/raw_preprocess/sampling.py +22 -0
- qxmt/datasets/schema.py +32 -0
- qxmt/datasets/transform/__init__.py +0 -0
- qxmt/datasets/transform/reduction_by_pca.py +37 -0
- qxmt/decorators.py +10 -0
- qxmt/devices/__init__.py +5 -0
- qxmt/devices/base.py +32 -0
- qxmt/devices/builder.py +23 -0
- qxmt/devices/device_info.py +40 -0
- qxmt/evaluation/__init__.py +12 -0
- qxmt/evaluation/base.py +54 -0
- qxmt/evaluation/defaults.py +68 -0
- qxmt/evaluation/evaluation.py +131 -0
- qxmt/exceptions.py +42 -0
- qxmt/experiment/__init__.py +4 -0
- qxmt/experiment/experiment.py +528 -0
- qxmt/experiment/schema.py +31 -0
- qxmt/feature_maps/__init__.py +3 -0
- qxmt/feature_maps/base.py +68 -0
- qxmt/feature_maps/pennylane/__init__.py +15 -0
- qxmt/feature_maps/pennylane/defaults.py +81 -0
- qxmt/generators/__init__.py +5 -0
- qxmt/generators/description.py +71 -0
- qxmt/generators/prompts.py +21 -0
- qxmt/kernels/__init__.py +3 -0
- qxmt/kernels/base.py +153 -0
- qxmt/kernels/pennylane/__init__.py +5 -0
- qxmt/kernels/pennylane/fidelity_kernel.py +38 -0
- qxmt/logger.py +11 -0
- qxmt/models/__init__.py +10 -0
- qxmt/models/base.py +121 -0
- qxmt/models/builder.py +81 -0
- qxmt/models/qsvm.py +35 -0
- qxmt/types.py +6 -0
- qxmt/utils/__init__.py +21 -0
- qxmt/utils/github.py +117 -0
- qxmt/utils/yaml.py +102 -0
- qxmt/visualization/__init__.py +14 -0
- qxmt/visualization/graph_settings.py +9 -0
- qxmt/visualization/plot_dataset.py +75 -0
- qxmt/visualization/plot_metrics.py +126 -0
- qxmt/visualization/plot_model_performance.py +172 -0
- qxmt-0.0.0.dist-info/LICENSE +21 -0
- qxmt-0.0.0.dist-info/METADATA +149 -0
- qxmt-0.0.0.dist-info/RECORD +54 -0
- qxmt-0.0.0.dist-info/WHEEL +4 -0
qxmt/__init__.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from qxmt.configs import (
|
|
2
|
+
DatasetConfig,
|
|
3
|
+
DeviceConfig,
|
|
4
|
+
EvaluationConfig,
|
|
5
|
+
ExperimentConfig,
|
|
6
|
+
FeatureMapConfig,
|
|
7
|
+
KernelConfig,
|
|
8
|
+
ModelConfig,
|
|
9
|
+
PathConfig,
|
|
10
|
+
)
|
|
11
|
+
from qxmt.exceptions import (
|
|
12
|
+
ExperimentNotInitializedError,
|
|
13
|
+
ExperimentRunSettingError,
|
|
14
|
+
InputShapeError,
|
|
15
|
+
InvalidFileExtensionError,
|
|
16
|
+
InvalidModelNameError,
|
|
17
|
+
InvalidPlatformError,
|
|
18
|
+
InvalidQunatumDeviceError,
|
|
19
|
+
JsonEncodingError,
|
|
20
|
+
ModelSettingError,
|
|
21
|
+
ReproductionError,
|
|
22
|
+
)
|
|
23
|
+
from qxmt.experiment.experiment import Experiment
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"ExperimentNotInitializedError",
|
|
27
|
+
"ExperimentRunSettingError",
|
|
28
|
+
"InputShapeError",
|
|
29
|
+
"InvalidFileExtensionError",
|
|
30
|
+
"InvalidModelNameError",
|
|
31
|
+
"InvalidPlatformError",
|
|
32
|
+
"InvalidQunatumDeviceError",
|
|
33
|
+
"JsonEncodingError",
|
|
34
|
+
"ModelSettingError",
|
|
35
|
+
"ReproductionError",
|
|
36
|
+
"Experiment",
|
|
37
|
+
"ExperimentConfig",
|
|
38
|
+
"DatasetConfig",
|
|
39
|
+
"DeviceConfig",
|
|
40
|
+
"EvaluationConfig",
|
|
41
|
+
"FeatureMapConfig",
|
|
42
|
+
"KernelConfig",
|
|
43
|
+
"ModelConfig",
|
|
44
|
+
"PathConfig",
|
|
45
|
+
]
|
qxmt/configs.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Literal, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from qxmt.constants import MODULE_HOME
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PathConfig(BaseModel):
|
|
10
|
+
data: Path | str
|
|
11
|
+
label: Path | str
|
|
12
|
+
|
|
13
|
+
def model_post_init(self, __context: dict[str, Any]) -> None:
|
|
14
|
+
if not Path(self.data).is_absolute():
|
|
15
|
+
self.data = MODULE_HOME / self.data
|
|
16
|
+
|
|
17
|
+
if not Path(self.label).is_absolute():
|
|
18
|
+
self.label = MODULE_HOME / self.label
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DatasetConfig(BaseModel):
|
|
22
|
+
type: Literal["file", "generate"]
|
|
23
|
+
path: PathConfig
|
|
24
|
+
random_seed: int
|
|
25
|
+
test_size: float = Field(ge=0.0, le=1.0)
|
|
26
|
+
features: Optional[list[str]] = None
|
|
27
|
+
raw_preprocess_logic: Optional[dict[str, Any]] = None
|
|
28
|
+
transform_logic: Optional[dict[str, Any]] = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DeviceConfig(BaseModel):
|
|
32
|
+
platform: str
|
|
33
|
+
name: str
|
|
34
|
+
n_qubits: int
|
|
35
|
+
shots: Optional[int] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class FeatureMapConfig(BaseModel):
|
|
39
|
+
module_name: str
|
|
40
|
+
implement_name: str
|
|
41
|
+
params: Optional[dict[str, Any]] = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class KernelConfig(BaseModel):
|
|
45
|
+
module_name: str
|
|
46
|
+
implement_name: str
|
|
47
|
+
params: Optional[dict[str, Any]] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ModelConfig(BaseModel):
|
|
51
|
+
name: str
|
|
52
|
+
file_name: str
|
|
53
|
+
params: dict[str, Any]
|
|
54
|
+
feature_map: Optional[FeatureMapConfig] = None
|
|
55
|
+
kernel: Optional[KernelConfig] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class EvaluationConfig(BaseModel):
|
|
59
|
+
default_metrics: list[str]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ExperimentConfig(BaseModel):
|
|
63
|
+
path: Path | str = ""
|
|
64
|
+
description: str = ""
|
|
65
|
+
dataset: DatasetConfig
|
|
66
|
+
device: DeviceConfig
|
|
67
|
+
feature_map: Optional[FeatureMapConfig] = None
|
|
68
|
+
kernel: Optional[KernelConfig] = None
|
|
69
|
+
model: ModelConfig
|
|
70
|
+
evaluation: EvaluationConfig
|
qxmt/constants.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import pennylane as qml
|
|
5
|
+
import pytz
|
|
6
|
+
|
|
7
|
+
MODULE_HOME: Path = Path(__file__).resolve().parents[1]
|
|
8
|
+
MODULE_SRC: Path = Path(__file__).resolve().parents[0]
|
|
9
|
+
|
|
10
|
+
DEFAULT_N_JOBS = 3
|
|
11
|
+
|
|
12
|
+
DEFAULT_EXP_DIRC: Path = MODULE_HOME / "experiments"
|
|
13
|
+
DEFAULT_EXP_DB_FILE: Path = Path("experiment.json")
|
|
14
|
+
|
|
15
|
+
SUPPORTED_PLATFORMS: list[str] = ["pennylane"]
|
|
16
|
+
PENNYLANE_DEVICES: tuple[Any, ...] = (qml.devices.Device, qml.Device, qml.QubitDevice)
|
|
17
|
+
|
|
18
|
+
DEFAULT_MODEL_NAME: str = "model.pkl"
|
|
19
|
+
|
|
20
|
+
DEFAULT_METRICS_NAME: list[str] = ["accuracy", "precision", "recall", "f1_score"]
|
|
21
|
+
|
|
22
|
+
LLM_MODEL_PATH = "microsoft/Phi-3-mini-128k-instruct"
|
|
23
|
+
|
|
24
|
+
TZ: pytz.BaseTzInfo = pytz.timezone("Asia/Tokyo")
|
qxmt/datasets/builder.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from logging import Logger
|
|
3
|
+
from typing import Callable, Optional, get_type_hints
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from sklearn.model_selection import train_test_split
|
|
7
|
+
|
|
8
|
+
from qxmt.configs import ExperimentConfig
|
|
9
|
+
from qxmt.datasets.dummy import generate_linear_separable_data
|
|
10
|
+
from qxmt.datasets.schema import Dataset
|
|
11
|
+
from qxmt.logger import set_default_logger
|
|
12
|
+
from qxmt.utils import load_object_from_yaml
|
|
13
|
+
|
|
14
|
+
RAW_DATA_TYPE = np.ndarray
|
|
15
|
+
RAW_LABEL_TYPE = np.ndarray
|
|
16
|
+
RAW_DATASET_TYPE = tuple[RAW_DATA_TYPE, RAW_LABEL_TYPE]
|
|
17
|
+
PROCESSCED_DATASET_TYPE = tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]
|
|
18
|
+
|
|
19
|
+
LOGGER = set_default_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DatasetBuilder:
|
|
23
|
+
def __init__(self, config: ExperimentConfig, logger: Logger = LOGGER) -> None:
|
|
24
|
+
self.config: ExperimentConfig = config
|
|
25
|
+
self.logger: Logger = logger
|
|
26
|
+
|
|
27
|
+
if self.config.dataset.raw_preprocess_logic is not None:
|
|
28
|
+
raw_preprocess_logic = load_object_from_yaml(self.config.dataset.raw_preprocess_logic)
|
|
29
|
+
self._validate_raw_preprocess_logic(raw_preprocess_logic, self.logger)
|
|
30
|
+
self.custom_raw_preprocess: Optional[Callable] = raw_preprocess_logic
|
|
31
|
+
else:
|
|
32
|
+
self.custom_raw_preprocess = None
|
|
33
|
+
|
|
34
|
+
if self.config.dataset.transform_logic is not None:
|
|
35
|
+
transform_logic = load_object_from_yaml(self.config.dataset.transform_logic)
|
|
36
|
+
self._validate_transform_logic(transform_logic, self.logger)
|
|
37
|
+
self.custom_transform: Optional[Callable] = transform_logic
|
|
38
|
+
else:
|
|
39
|
+
self.custom_transform = None
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def _validate_raw_preprocess_logic(raw_preprocess_logic: Callable, logger: Logger) -> None:
|
|
43
|
+
"""Validate the custom raw preprocess function.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
raw_preprocess_logic (Callable): custom raw preprocess function
|
|
47
|
+
logger (Logger): logger for output messages
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: argment lenght of the custom raw preprocess function is less than 2
|
|
51
|
+
ValueError: return type of the custom raw preprocess function is not a tuple of numpy arrays
|
|
52
|
+
ValueError: argument type of the custom raw preprocess function is not numpy array
|
|
53
|
+
"""
|
|
54
|
+
type_hint_dict = get_type_hints(raw_preprocess_logic)
|
|
55
|
+
parameter_dict = inspect.signature(raw_preprocess_logic).parameters
|
|
56
|
+
|
|
57
|
+
# check argment length. -1 means return type
|
|
58
|
+
if len(type_hint_dict) - 1 != len(parameter_dict):
|
|
59
|
+
logger.warning(
|
|
60
|
+
"All arguments of the custom raw preprocess function assigned to the type hint."
|
|
61
|
+
"Input and return type validation will be skipped."
|
|
62
|
+
)
|
|
63
|
+
return
|
|
64
|
+
elif len(type_hint_dict) - 1 < 2:
|
|
65
|
+
raise ValueError("The custom raw preprocess function must have at least 2 arguments (X, y).")
|
|
66
|
+
|
|
67
|
+
# check argument type and return type
|
|
68
|
+
for arg_name, arg_type in type_hint_dict.items():
|
|
69
|
+
if (arg_name == "return") and (arg_type != RAW_DATASET_TYPE):
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"The return type of the custom raw preprocess function must be a tuple of numpy arrays."
|
|
72
|
+
)
|
|
73
|
+
# [TODO]: Handle anthor data types
|
|
74
|
+
# elif (arg_name != "return") and (arg_type != RAW_DATA_TYPE):
|
|
75
|
+
# raise ValueError(f'The arguments of the custom raw preprocess function must be "{RAW_DATA_TYPE}".')
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _validate_transform_logic(transform_logic: Callable, logger: Logger) -> None:
|
|
79
|
+
"""Validate the custom transform function.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
transform_logic (Callable): custom transform function
|
|
83
|
+
logger (Logger): logger for output messages
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValueError: argment lenght of the custom transform function is less than 4
|
|
87
|
+
ValueError: return type of the custom transform function is not a tuple of numpy arrays
|
|
88
|
+
ValueError: argument type of the custom transform function is not numpy array
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
type_hint_dict = get_type_hints(transform_logic)
|
|
92
|
+
parameter_dict = inspect.signature(transform_logic).parameters
|
|
93
|
+
|
|
94
|
+
# check argment length. -1 means return type
|
|
95
|
+
if len(type_hint_dict) - 1 != len(parameter_dict):
|
|
96
|
+
logger.warning(
|
|
97
|
+
"All arguments of the custom raw preprocess function assigned to the type hint."
|
|
98
|
+
"Input and return type validation will be skipped."
|
|
99
|
+
)
|
|
100
|
+
return
|
|
101
|
+
elif len(type_hint_dict) - 1 < 4:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"The custom transform function must have at least 4 arguments (X_train, y_train, X_test, y_test)."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# check argument type and return type
|
|
107
|
+
for arg_name, arg_type in type_hint_dict.items():
|
|
108
|
+
if (arg_name == "return") and (arg_type != PROCESSCED_DATASET_TYPE):
|
|
109
|
+
raise ValueError("The return type of the custom transform function must be a tuple of numpy arrays.")
|
|
110
|
+
# # [TODO]: Handle athor data types
|
|
111
|
+
# elif (arg_name != "return") and (arg_type != RAW_DATA_TYPE):
|
|
112
|
+
# raise ValueError(f'The arguments of the custom transform function must be "{RAW_DATA_TYPE}".')
|
|
113
|
+
|
|
114
|
+
def load(self) -> RAW_DATASET_TYPE:
|
|
115
|
+
"""Load the dataset from the path defined in config.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
RAW_DATASET_TYPE: features and labels of the dataset
|
|
119
|
+
"""
|
|
120
|
+
if self.config.dataset.type == "file":
|
|
121
|
+
# [TODO]: Implement other file formats
|
|
122
|
+
X = np.load(self.config.dataset.path.data, allow_pickle=True)
|
|
123
|
+
y = np.load(self.config.dataset.path.label, allow_pickle=True)
|
|
124
|
+
elif self.config.dataset.type == "generate":
|
|
125
|
+
# [TODO]: Implement other dataset generation methods
|
|
126
|
+
X, y = generate_linear_separable_data()
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(f"Invalid dataset type: {self.config.dataset.type}")
|
|
129
|
+
|
|
130
|
+
return X, y
|
|
131
|
+
|
|
132
|
+
def default_raw_preprocess(self, X: np.ndarray, y: np.ndarray) -> RAW_DATASET_TYPE:
|
|
133
|
+
"""Default raw preprocess method. This method does not apply any preprocess.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
X (np.ndarray): raw features of the dataset
|
|
137
|
+
y (np.ndarray): raw labels of the dataset
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
RAW_DATASET_TYPE: raw features and labels of the dataset
|
|
141
|
+
"""
|
|
142
|
+
return X, y
|
|
143
|
+
|
|
144
|
+
def raw_preprocess(self, X: np.ndarray, y: np.ndarray) -> RAW_DATASET_TYPE:
|
|
145
|
+
"""Preprocess the raw dataset. This process executes before splitting the dataset.
|
|
146
|
+
ex) filtering, data sampling, etc.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
X (np.ndarray): raw features of the dataset
|
|
150
|
+
y (np.ndarray): raw labels of the dataset
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
RAW_DATASET_TYPE: preprocessed features and labels of the dataset
|
|
154
|
+
"""
|
|
155
|
+
if self.custom_raw_preprocess is not None:
|
|
156
|
+
return self.custom_raw_preprocess(X, y)
|
|
157
|
+
else:
|
|
158
|
+
return self.default_raw_preprocess(X, y)
|
|
159
|
+
|
|
160
|
+
def split(self, X: np.ndarray, y: np.ndarray) -> PROCESSCED_DATASET_TYPE:
|
|
161
|
+
"""Split the dataset into train and test sets.
|
|
162
|
+
Test set size is defined in the config.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
X (np.ndarray): raw features of the dataset
|
|
166
|
+
y (np.ndarray): raw labels of the dataset
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
PROCESSCED_DATASET_TYPE: train and test split of dataset (features and labels)
|
|
170
|
+
"""
|
|
171
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
172
|
+
X, y, test_size=self.config.dataset.test_size, random_state=self.config.dataset.random_seed
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return X_train, y_train, X_test, y_test
|
|
176
|
+
|
|
177
|
+
def default_transform(
|
|
178
|
+
self,
|
|
179
|
+
X_train: np.ndarray,
|
|
180
|
+
y_train: np.ndarray,
|
|
181
|
+
X_test: np.ndarray,
|
|
182
|
+
y_test: np.ndarray,
|
|
183
|
+
) -> PROCESSCED_DATASET_TYPE:
|
|
184
|
+
"""Default transform method. This method does not apply any transformation.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
X_train (np.ndarray): raw features of the training data
|
|
188
|
+
y_train (np.ndarray): raw labels of the training data
|
|
189
|
+
X_test (np.ndarray): raw features of the test data
|
|
190
|
+
y_test (np.ndarray): raw labels of the test data
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
PROCESSCED_DATASET_TYPE: train and test split of dataset (features and labels)
|
|
194
|
+
"""
|
|
195
|
+
return X_train, y_train, X_test, y_test
|
|
196
|
+
|
|
197
|
+
def transform(
|
|
198
|
+
self,
|
|
199
|
+
X_train: np.ndarray,
|
|
200
|
+
y_train: np.ndarray,
|
|
201
|
+
X_test: np.ndarray,
|
|
202
|
+
y_test: np.ndarray,
|
|
203
|
+
) -> PROCESSCED_DATASET_TYPE:
|
|
204
|
+
"""Transform the dataset.
|
|
205
|
+
ex) feature scaling, dimension reduction, etc.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
X_train (np.ndarray): raw features of the training data
|
|
209
|
+
y_train (np.ndarray): raw labels of the training data
|
|
210
|
+
X_test (np.ndarray): raw features of the test data
|
|
211
|
+
y_test (np.ndarray): raw labels of the test data
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
PROCESSCED_DATASET_TYPE: transformed train and test split of dataset (features and labels)
|
|
215
|
+
"""
|
|
216
|
+
if self.custom_transform is not None:
|
|
217
|
+
return self.custom_transform(X_train, y_train, X_test, y_test)
|
|
218
|
+
else:
|
|
219
|
+
return self.default_transform(X_train, y_train, X_test, y_test)
|
|
220
|
+
|
|
221
|
+
def build(self) -> Dataset:
|
|
222
|
+
X, y = self.load()
|
|
223
|
+
X, y = self.raw_preprocess(X, y)
|
|
224
|
+
X_train, y_train, X_test, y_test = self.split(X, y)
|
|
225
|
+
X_train_trs, y_train_trs, X_test_trs, y_test_trs = self.transform(X_train, y_train, X_test, y_test)
|
|
226
|
+
|
|
227
|
+
return Dataset(
|
|
228
|
+
X_train=X_train_trs,
|
|
229
|
+
y_train=y_train_trs,
|
|
230
|
+
X_test=X_test_trs,
|
|
231
|
+
y_test=y_test_trs,
|
|
232
|
+
config=self.config.dataset,
|
|
233
|
+
)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def generate_linear_separable_data(
|
|
5
|
+
n_samples: int = 100,
|
|
6
|
+
n_features: int = 2,
|
|
7
|
+
noise: float = 0.1,
|
|
8
|
+
scale: float = 1.0,
|
|
9
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
10
|
+
"""Generate random linear separable data.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
n_samples (int, optional): sample size. Defaults to 100.
|
|
14
|
+
n_features (int, optional): dimension of feature. Defaults to 2.
|
|
15
|
+
noise (float, optional): noise level. Defaults to 0.1.
|
|
16
|
+
scale (float, optional): scale of data. Defaults to 1.0.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
tuple[np.ndarray, np.ndarray]: generated data and label
|
|
20
|
+
"""
|
|
21
|
+
X = scale * np.random.randn(n_samples, n_features)
|
|
22
|
+
w = scale * np.random.randn(n_features)
|
|
23
|
+
bias = scale * np.random.randn(n_samples) * noise
|
|
24
|
+
y = np.sign(X @ w + bias)
|
|
25
|
+
|
|
26
|
+
return X, y
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from qxmt.datasets.builder import RAW_DATASET_TYPE
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sampling_by_each_class(X: np.ndarray, y: np.ndarray, n_samples: int) -> RAW_DATASET_TYPE:
|
|
7
|
+
"""Data sampling by each class
|
|
8
|
+
|
|
9
|
+
Args:
|
|
10
|
+
X (np.ndarray): input data
|
|
11
|
+
y (np.ndarray): label of input data
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
RAW_DATASET_TYPE: sampled data
|
|
15
|
+
"""
|
|
16
|
+
labels = [0, 1]
|
|
17
|
+
# n_sample = 100
|
|
18
|
+
y = np.array([int(label) for label in y])
|
|
19
|
+
indices = np.where(np.isin(y, labels))[0]
|
|
20
|
+
X, y = X[indices][:n_samples], y[indices][:n_samples]
|
|
21
|
+
|
|
22
|
+
return X, y
|
qxmt/datasets/schema.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from pydantic import BaseModel, PlainSerializer, PlainValidator
|
|
5
|
+
|
|
6
|
+
from qxmt.configs import DatasetConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate(v: Any) -> np.ndarray:
|
|
10
|
+
if isinstance(v, np.ndarray):
|
|
11
|
+
return v
|
|
12
|
+
else:
|
|
13
|
+
raise TypeError(f"Expected numpy array, got {type(v)}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def serialize(v: np.ndarray) -> list[list[float]]:
|
|
17
|
+
return v.tolist()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
DataArray = Annotated[
|
|
21
|
+
np.ndarray,
|
|
22
|
+
PlainValidator(validate),
|
|
23
|
+
PlainSerializer(serialize),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Dataset(BaseModel):
|
|
28
|
+
X_train: DataArray
|
|
29
|
+
y_train: DataArray
|
|
30
|
+
X_test: DataArray
|
|
31
|
+
y_test: DataArray
|
|
32
|
+
config: DatasetConfig
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from sklearn.decomposition import PCA
|
|
3
|
+
from sklearn.preprocessing import StandardScaler
|
|
4
|
+
|
|
5
|
+
from qxmt.datasets.builder import PROCESSCED_DATASET_TYPE
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dimension_reduction_by_pca(
|
|
9
|
+
X_train: np.ndarray,
|
|
10
|
+
y_train: np.ndarray,
|
|
11
|
+
X_test: np.ndarray,
|
|
12
|
+
y_test: np.ndarray,
|
|
13
|
+
n_components: int,
|
|
14
|
+
) -> PROCESSCED_DATASET_TYPE:
|
|
15
|
+
"""Dimension reduction by PCA
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
X_train (np.ndarray): numpy array of training data
|
|
19
|
+
y_train (np.ndarray): numpy array of training label
|
|
20
|
+
X_test (np.ndarray): numpy array of testing data
|
|
21
|
+
y_test (np.ndarray): numpy array of testing label
|
|
22
|
+
n_components (int): number of components to keep
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
PROCESSCED_DATASET_TYPE: tuple of processed dataset
|
|
26
|
+
"""
|
|
27
|
+
scaler = StandardScaler()
|
|
28
|
+
scaler.fit(X_train)
|
|
29
|
+
x_train_scaled = scaler.transform(X_train)
|
|
30
|
+
x_test_scaled = scaler.transform(X_test)
|
|
31
|
+
|
|
32
|
+
pca = PCA(n_components=n_components)
|
|
33
|
+
pca.fit(x_train_scaled)
|
|
34
|
+
X_train_pca = pca.transform(x_train_scaled)
|
|
35
|
+
X_test_pca = pca.transform(x_test_scaled)
|
|
36
|
+
|
|
37
|
+
return X_train_pca, y_train, X_test_pca, y_test
|
qxmt/decorators.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from typing import Any, Callable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def notify_long_running(func: Callable) -> Callable:
|
|
5
|
+
def wrapper(*args: dict, **kwargs: dict) -> Any:
|
|
6
|
+
print(f'Executing "{func.__name__}". This may take some time...')
|
|
7
|
+
result = func(*args, **kwargs)
|
|
8
|
+
return result
|
|
9
|
+
|
|
10
|
+
return wrapper
|
qxmt/devices/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from qxmt.devices.base import BaseDevice
|
|
2
|
+
from qxmt.devices.builder import DeviceBuilder
|
|
3
|
+
from qxmt.devices.device_info import get_number_of_qubits, get_platform_from_device
|
|
4
|
+
|
|
5
|
+
__all__ = ["DeviceBuilder", "BaseDevice", "get_number_of_qubits", "get_platform_from_device"]
|
qxmt/devices/base.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
from qxmt.exceptions import InvalidPlatformError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseDevice:
|
|
7
|
+
def __init__(self, platform: str, name: str, n_qubits: int, shots: Optional[int]) -> None:
|
|
8
|
+
self.platform = platform
|
|
9
|
+
self.name = name
|
|
10
|
+
self.n_qubits = n_qubits
|
|
11
|
+
self.shots = shots
|
|
12
|
+
self._set_device()
|
|
13
|
+
|
|
14
|
+
def __call__(self) -> Any:
|
|
15
|
+
return self.device
|
|
16
|
+
|
|
17
|
+
def _set_device(self) -> None:
|
|
18
|
+
"""Set quantum device.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
InvalidPlatformError: platform is not implemented.
|
|
22
|
+
"""
|
|
23
|
+
if self.platform == "pennylane":
|
|
24
|
+
from pennylane import qml
|
|
25
|
+
|
|
26
|
+
self.device = qml.device(
|
|
27
|
+
name=self.name,
|
|
28
|
+
wires=self.n_qubits,
|
|
29
|
+
shots=self.shots,
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
raise InvalidPlatformError(f'"{self.platform}" is not implemented.')
|
qxmt/devices/builder.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from qxmt.configs import DeviceConfig
|
|
2
|
+
from qxmt.devices.base import BaseDevice
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DeviceBuilder:
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
config: DeviceConfig,
|
|
9
|
+
) -> None:
|
|
10
|
+
self.config: DeviceConfig = config
|
|
11
|
+
|
|
12
|
+
def build(self) -> BaseDevice:
|
|
13
|
+
"""Build a quantum device. it can be a general-purpose device overseeing multiple platforms.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
BaseDevice: General-purpose device overseeing multiple platforms
|
|
17
|
+
"""
|
|
18
|
+
return BaseDevice(
|
|
19
|
+
platform=self.config.platform,
|
|
20
|
+
name=self.config.name,
|
|
21
|
+
n_qubits=self.config.n_qubits,
|
|
22
|
+
shots=self.config.shots,
|
|
23
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from qxmt.constants import PENNYLANE_DEVICES
|
|
2
|
+
from qxmt.devices.base import BaseDevice
|
|
3
|
+
from qxmt.exceptions import InvalidQunatumDeviceError
|
|
4
|
+
from qxmt.types import QuantumDeviceType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_platform_from_device(device: BaseDevice | QuantumDeviceType) -> str:
|
|
8
|
+
"""Get the platform name from the device.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
device (BaseDevice | QuantumDeviceType): quantum device
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
str: platform name
|
|
15
|
+
"""
|
|
16
|
+
if isinstance(device, BaseDevice):
|
|
17
|
+
return device.platform
|
|
18
|
+
|
|
19
|
+
if isinstance(device, PENNYLANE_DEVICES):
|
|
20
|
+
return "pennylane"
|
|
21
|
+
else:
|
|
22
|
+
raise InvalidQunatumDeviceError(f"Device {device} is not supported.")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_number_of_qubits(device: BaseDevice | QuantumDeviceType) -> int:
|
|
26
|
+
"""Get the number of qubits from the device.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
device (BaseDevice | QuantumDeviceType): quantum device
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
int: number of qubits
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(device, BaseDevice):
|
|
35
|
+
return device.n_qubits
|
|
36
|
+
|
|
37
|
+
if isinstance(device, PENNYLANE_DEVICES):
|
|
38
|
+
return len(device.wires)
|
|
39
|
+
else:
|
|
40
|
+
raise InvalidQunatumDeviceError(f"Device {device} is not supported.")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from qxmt.evaluation.base import BaseMetric
|
|
2
|
+
from qxmt.evaluation.defaults import Accuracy, F1Score, Precision, Recall
|
|
3
|
+
from qxmt.evaluation.evaluation import Evaluation
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"BaseMetric",
|
|
7
|
+
"Accuracy",
|
|
8
|
+
"Recall",
|
|
9
|
+
"Precision",
|
|
10
|
+
"F1Score",
|
|
11
|
+
"Evaluation",
|
|
12
|
+
]
|