qhyper 0.dev1__tar.gz
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.
- qhyper-0.dev1/LICENSE.txt +21 -0
- qhyper-0.dev1/PKG-INFO +18 -0
- qhyper-0.dev1/QHyper/__init__.py +0 -0
- qhyper-0.dev1/QHyper/optimizers/__init__.py +17 -0
- qhyper-0.dev1/QHyper/optimizers/base.py +39 -0
- qhyper-0.dev1/QHyper/optimizers/basinhopping.py +79 -0
- qhyper-0.dev1/QHyper/optimizers/cem.py +128 -0
- qhyper-0.dev1/QHyper/optimizers/qml_gradient_descent.py +72 -0
- qhyper-0.dev1/QHyper/optimizers/random.py +90 -0
- qhyper-0.dev1/QHyper/optimizers/scipy_minimizer.py +65 -0
- qhyper-0.dev1/QHyper/problems/__init__.py +0 -0
- qhyper-0.dev1/QHyper/problems/base.py +62 -0
- qhyper-0.dev1/QHyper/problems/community_detection.py +185 -0
- qhyper-0.dev1/QHyper/problems/knapsack.py +176 -0
- qhyper-0.dev1/QHyper/problems/maxcut.py +37 -0
- qhyper-0.dev1/QHyper/problems/tsp.py +190 -0
- qhyper-0.dev1/QHyper/problems/workflow_scheduling.py +220 -0
- qhyper-0.dev1/QHyper/solvers/__init__.py +14 -0
- qhyper-0.dev1/QHyper/solvers/base.py +86 -0
- qhyper-0.dev1/QHyper/solvers/converter.py +128 -0
- qhyper-0.dev1/QHyper/solvers/cqm/__init__.py +1 -0
- qhyper-0.dev1/QHyper/solvers/cqm/cqm.py +66 -0
- qhyper-0.dev1/QHyper/solvers/dqm/__init__.py +1 -0
- qhyper-0.dev1/QHyper/solvers/dqm/dqm.py +23 -0
- qhyper-0.dev1/QHyper/solvers/gurobi/__init__.py +1 -0
- qhyper-0.dev1/QHyper/solvers/gurobi/gurobi.py +77 -0
- qhyper-0.dev1/QHyper/solvers/vqa/__init__.py +25 -0
- qhyper-0.dev1/QHyper/solvers/vqa/base.py +108 -0
- qhyper-0.dev1/QHyper/solvers/vqa/eval_funcs/__init__.py +0 -0
- qhyper-0.dev1/QHyper/solvers/vqa/eval_funcs/base.py +40 -0
- qhyper-0.dev1/QHyper/solvers/vqa/eval_funcs/wfeval.py +32 -0
- qhyper-0.dev1/QHyper/solvers/vqa/pqc/__init__.py +0 -0
- qhyper-0.dev1/QHyper/solvers/vqa/pqc/base.py +133 -0
- qhyper-0.dev1/QHyper/solvers/vqa/pqc/h_qaoa.py +86 -0
- qhyper-0.dev1/QHyper/solvers/vqa/pqc/mixers.py +12 -0
- qhyper-0.dev1/QHyper/solvers/vqa/pqc/qaoa.py +130 -0
- qhyper-0.dev1/QHyper/solvers/vqa/pqc/wf_qaoa.py +80 -0
- qhyper-0.dev1/QHyper/util.py +4 -0
- qhyper-0.dev1/README.md +54 -0
- qhyper-0.dev1/qhyper.egg-info/PKG-INFO +18 -0
- qhyper-0.dev1/qhyper.egg-info/SOURCES.txt +44 -0
- qhyper-0.dev1/qhyper.egg-info/dependency_links.txt +1 -0
- qhyper-0.dev1/qhyper.egg-info/requires.txt +8 -0
- qhyper-0.dev1/qhyper.egg-info/top_level.txt +1 -0
- qhyper-0.dev1/setup.cfg +4 -0
- qhyper-0.dev1/setup.py +46 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Tomasz Lamża
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
qhyper-0.dev1/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qhyper
|
|
3
|
+
Version: 0.dev1
|
|
4
|
+
Summary: Quantum and classical problem solvers
|
|
5
|
+
Author: ACK Cyfronet AGH
|
|
6
|
+
Author-email: tomasz.lamza@cyfronet.pl
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: python,qhyper,quantum,solver,experiment
|
|
9
|
+
Classifier: Development Status :: 1 - Planning
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: Unix
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE.txt
|
|
17
|
+
|
|
18
|
+
A package that allows to build and solve quantum and classical problems using predefined solvers and problems.
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from .base import Optimizer
|
|
4
|
+
|
|
5
|
+
from .cem import CEM
|
|
6
|
+
from .qml_gradient_descent import QmlGradientDescent
|
|
7
|
+
from .random import Random
|
|
8
|
+
from .scipy_minimizer import ScipyOptimizer
|
|
9
|
+
from .basinhopping import Basinhopping
|
|
10
|
+
|
|
11
|
+
OPTIMIZERS_BY_NAME: dict[str, Type[Optimizer]] = {
|
|
12
|
+
'scipy': ScipyOptimizer,
|
|
13
|
+
'random': Random,
|
|
14
|
+
'qml': QmlGradientDescent,
|
|
15
|
+
'cem': CEM,
|
|
16
|
+
'basinhopping': Basinhopping
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Optimizer:
|
|
9
|
+
"""
|
|
10
|
+
Abstract base class for optimizers.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def minimize(
|
|
15
|
+
self,
|
|
16
|
+
func: Callable[[npt.NDArray[np.float64]], float],
|
|
17
|
+
init: npt.NDArray[np.float64]
|
|
18
|
+
) -> tuple[float, npt.NDArray[np.float64]]:
|
|
19
|
+
"""
|
|
20
|
+
Abstract method that minimizes the given function using the implemented optimization algorithm.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
func : callable
|
|
25
|
+
The objective function to be minimized.
|
|
26
|
+
init : numpy.ndarray
|
|
27
|
+
The initial point for the optimization algorithm.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
tuple
|
|
32
|
+
A tuple containing the minimum function value and the corresponding optimal point.
|
|
33
|
+
|
|
34
|
+
Raises
|
|
35
|
+
------
|
|
36
|
+
NotImplementedError
|
|
37
|
+
If the `minimize` method is not implemented by the derived class.
|
|
38
|
+
"""
|
|
39
|
+
...
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from scipy.optimize import basinhopping
|
|
2
|
+
import numpy as np
|
|
3
|
+
import numpy.typing as npt
|
|
4
|
+
from typing import Callable, Any
|
|
5
|
+
|
|
6
|
+
from .base import Optimizer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Basinhopping(Optimizer):
|
|
10
|
+
"""
|
|
11
|
+
Class for the Basin-hopping algorithm for global optimization.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
bounds : list[tuple[float, float]]
|
|
16
|
+
A list of tuples specifying the lower and upper bounds for each dimension of the search space.
|
|
17
|
+
niter : int
|
|
18
|
+
The number of basin-hopping iterations to perform.
|
|
19
|
+
maxfun : int, optional
|
|
20
|
+
Maximum number of function evaluations.
|
|
21
|
+
Default is 200.
|
|
22
|
+
config : dict, optional
|
|
23
|
+
Additional configuration options for the basinhopping function.
|
|
24
|
+
Default is an empty dictionary.
|
|
25
|
+
|
|
26
|
+
Attributes
|
|
27
|
+
----------
|
|
28
|
+
niter : int
|
|
29
|
+
The number of basin-hopping iterations to perform.
|
|
30
|
+
maxfun : int
|
|
31
|
+
Maximum number of function evaluations.
|
|
32
|
+
bounds : numpy.ndarray
|
|
33
|
+
An array of shape (n, 2) specifying the lower and upper bounds for each dimension of the search space.
|
|
34
|
+
config : dict
|
|
35
|
+
Additional configuration options for the basinhopping function.
|
|
36
|
+
"""
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
bounds: list[tuple[float, float]],
|
|
40
|
+
niter: int,
|
|
41
|
+
maxfun: int = 200,
|
|
42
|
+
config: dict[str, Any] = {}
|
|
43
|
+
) -> None:
|
|
44
|
+
self.niter = niter
|
|
45
|
+
self.maxfun = maxfun
|
|
46
|
+
self.bounds = np.array(bounds)
|
|
47
|
+
self.config = config
|
|
48
|
+
|
|
49
|
+
def minimize(
|
|
50
|
+
self,
|
|
51
|
+
func: Callable[[npt.NDArray[np.float64]], float],
|
|
52
|
+
init: npt.NDArray[np.float64]
|
|
53
|
+
) -> tuple[float, npt.NDArray[np.float64]]:
|
|
54
|
+
"""
|
|
55
|
+
Minimize the given function using the Basin-hopping algorithm.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
func : callable
|
|
60
|
+
The objective function to be minimized.
|
|
61
|
+
The function should take a single argument, which is a NumPy array of type np.float64,
|
|
62
|
+
and return a float value.
|
|
63
|
+
init : numpy.ndarray
|
|
64
|
+
The initial point for the optimization algorithm.
|
|
65
|
+
The array should have dtype np.float64.
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
tuple
|
|
70
|
+
A tuple containing the minimum function value and the corresponding optimal point.
|
|
71
|
+
"""
|
|
72
|
+
result = basinhopping(
|
|
73
|
+
func, init.flatten(), niter=self.niter,
|
|
74
|
+
minimizer_kwargs={
|
|
75
|
+
'options': {'maxfun': self.maxfun},
|
|
76
|
+
'bounds': self.bounds
|
|
77
|
+
}, **self.config)
|
|
78
|
+
|
|
79
|
+
return result.fun, np.array(result.x).reshape(init.shape)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import multiprocessing as mp
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from typing import Callable
|
|
5
|
+
import numpy.typing as npt
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import tqdm
|
|
9
|
+
|
|
10
|
+
from .base import Optimizer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class CEM(Optimizer):
|
|
15
|
+
bounds: npt.NDArray[np.float64]
|
|
16
|
+
epochs: int = 10
|
|
17
|
+
samples_per_epoch: int = 100
|
|
18
|
+
elite_frac: float = 0.1
|
|
19
|
+
processes: int = mp.cpu_count()
|
|
20
|
+
print_on_epochs: list[int] = field(default_factory=list)
|
|
21
|
+
disable_tqdm: bool = False
|
|
22
|
+
n_elite: int = field(init=False)
|
|
23
|
+
|
|
24
|
+
def __post_init__(self) -> None:
|
|
25
|
+
self.n_elite: int = max(
|
|
26
|
+
int(self.samples_per_epoch * self.elite_frac), 1)
|
|
27
|
+
self.bounds = np.array(self.bounds)
|
|
28
|
+
|
|
29
|
+
"""Implementation of the Cross Entropy Method for hyperparameter tuning
|
|
30
|
+
|
|
31
|
+
Attributes
|
|
32
|
+
----------
|
|
33
|
+
epochs : int
|
|
34
|
+
number of epochs (default 10)
|
|
35
|
+
samples_per_epoch : int
|
|
36
|
+
number of samples in each epoch (default 100)
|
|
37
|
+
elite_frac : float
|
|
38
|
+
indicate the percent of how many top samples will be used to calculate
|
|
39
|
+
the mean and cov for the next epoch (default 0.1)
|
|
40
|
+
processes : int
|
|
41
|
+
number of processors that will be used (default cpu count)
|
|
42
|
+
n_elite : int
|
|
43
|
+
calulated by multiplying samples_per_epoch by elite_frac
|
|
44
|
+
print_on_epochs: list[int]
|
|
45
|
+
list indicating after which epochs print best results
|
|
46
|
+
disable_tqdm: bool
|
|
47
|
+
if set to True, tdqm will be disabled
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def _get_points(
|
|
51
|
+
self,
|
|
52
|
+
mean: npt.NDArray[np.float64],
|
|
53
|
+
cov: npt.NDArray[np.float64]
|
|
54
|
+
) -> list[npt.NDArray[np.float64]]:
|
|
55
|
+
hyperparams: list[npt.NDArray[np.float64]] = []
|
|
56
|
+
while len(hyperparams) < self.samples_per_epoch:
|
|
57
|
+
point = np.random.multivariate_normal(mean, cov)
|
|
58
|
+
if (
|
|
59
|
+
(self.bounds[:, 0] <= point).all()
|
|
60
|
+
and (point < self.bounds[:, 1]).all()
|
|
61
|
+
):
|
|
62
|
+
hyperparams.append(point)
|
|
63
|
+
|
|
64
|
+
return hyperparams
|
|
65
|
+
|
|
66
|
+
def minimize(
|
|
67
|
+
self,
|
|
68
|
+
func: Callable[[npt.NDArray[np.float64]], float],
|
|
69
|
+
init: npt.NDArray[np.float64]
|
|
70
|
+
) -> tuple[float, npt.NDArray[np.float64]]:
|
|
71
|
+
"""Returns hyperparameters which lead to the lowest values
|
|
72
|
+
returned by optimizer
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
func_creator : Callable[[ArgsType], Callable[[ArgsType], float]]
|
|
77
|
+
function which receives hyperparameters, and returns
|
|
78
|
+
a function which will be optimized using an optimizer
|
|
79
|
+
optimizer : Optimizer
|
|
80
|
+
object of the Optimizer class
|
|
81
|
+
init : ArgsType
|
|
82
|
+
initial args for optimizer
|
|
83
|
+
hyperparams_init : ArgsType
|
|
84
|
+
initial hyperparameters
|
|
85
|
+
evaluation_func : Callable[[ArgsType], Callable[[ArgsType], float]]
|
|
86
|
+
function, which receives hyperparameters, and returns
|
|
87
|
+
function which receives params and return evaluation
|
|
88
|
+
bounds : list[float]
|
|
89
|
+
bounds for hyperparameters (default None)
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
ArgsType
|
|
94
|
+
hyperparameters which lead to the lowest values
|
|
95
|
+
returned by the optimizer
|
|
96
|
+
"""
|
|
97
|
+
# wrapper = Wrapper(func_creator, optimizer, evaluation_func, init)
|
|
98
|
+
_init = np.array(init)
|
|
99
|
+
mean = _init
|
|
100
|
+
cov = np.identity(len(_init))
|
|
101
|
+
best_hyperparams = _init
|
|
102
|
+
best_score = func(_init)
|
|
103
|
+
|
|
104
|
+
for _ in range(1, self.epochs+1):
|
|
105
|
+
hyperparams = self._get_points(mean, cov)
|
|
106
|
+
|
|
107
|
+
with mp.Pool(processes=self.processes) as p:
|
|
108
|
+
results = list(tqdm.tqdm(
|
|
109
|
+
p.imap(func, hyperparams),
|
|
110
|
+
total=len(hyperparams),
|
|
111
|
+
disable=self.disable_tqdm
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
elite_idxs = np.array(results).argsort()[:self.n_elite]
|
|
115
|
+
|
|
116
|
+
elite_weights = [hyperparams[i].flatten() for i in elite_idxs]
|
|
117
|
+
|
|
118
|
+
best_hyperparams = elite_weights[0].reshape(_init.shape)
|
|
119
|
+
|
|
120
|
+
reward = func(best_hyperparams)
|
|
121
|
+
if reward < best_score:
|
|
122
|
+
best_hyperparams = best_hyperparams
|
|
123
|
+
best_score = reward
|
|
124
|
+
|
|
125
|
+
mean = np.mean(elite_weights, axis=0)
|
|
126
|
+
cov = np.cov(np.stack((elite_weights), axis=1), bias=True)
|
|
127
|
+
|
|
128
|
+
return func(best_hyperparams), best_hyperparams
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
import numpy.typing as npt
|
|
3
|
+
|
|
4
|
+
import pennylane as qml
|
|
5
|
+
from pennylane import numpy as np
|
|
6
|
+
|
|
7
|
+
from .base import Optimizer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QmlGradientDescent(Optimizer):
|
|
11
|
+
"""Gradient Descent Optimizer
|
|
12
|
+
|
|
13
|
+
Using GradientDescentOptimizer from library PennyLane.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
optimizer : qml.GradientDescentOptimizer
|
|
18
|
+
object of class GradientDescentOptimizer or inheriting from this class
|
|
19
|
+
optimization_steps : int
|
|
20
|
+
number of optimization steps
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
optimizer: qml.GradientDescentOptimizer = None,
|
|
26
|
+
optimization_steps: int = 200
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
optimizer : qml.GradientDescentOptimizer
|
|
32
|
+
object of class GradientDescentOptimizer
|
|
33
|
+
or inheriting from this class
|
|
34
|
+
optimization_steps : int
|
|
35
|
+
number of optimization steps
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
self.optimizer = optimizer if optimizer else qml.AdamOptimizer(
|
|
39
|
+
stepsize=0.005
|
|
40
|
+
)
|
|
41
|
+
self.optimization_steps = optimization_steps
|
|
42
|
+
|
|
43
|
+
def minimize(
|
|
44
|
+
self,
|
|
45
|
+
func: Callable[[npt.NDArray[np.float64]], float],
|
|
46
|
+
init: npt.NDArray[np.float64]
|
|
47
|
+
) -> tuple[float, npt.NDArray[np.float64]]:
|
|
48
|
+
"""Returns params which lead to the lowest value of
|
|
49
|
+
the provided function and cost history
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
func : Callable[[ArgsType], float]
|
|
54
|
+
function which will be minimized
|
|
55
|
+
init : ArgsType
|
|
56
|
+
initial args for optimizer
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
tuple[ArgsType, Any]
|
|
61
|
+
Returns tuple which contains params taht lead to the lowest value
|
|
62
|
+
of the provided function and cost history
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
cost_history = []
|
|
66
|
+
params = np.array(init, requires_grad=True)
|
|
67
|
+
if "reset" in dir(self.optimizer):
|
|
68
|
+
self.optimizer.reset()
|
|
69
|
+
for _ in range(self.optimization_steps):
|
|
70
|
+
params, cost = self.optimizer.step_and_cost(func, params)
|
|
71
|
+
cost_history.append(cost)
|
|
72
|
+
return cost, params
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import multiprocessing as mp
|
|
3
|
+
from typing import Callable
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import tqdm
|
|
8
|
+
|
|
9
|
+
from .base import Optimizer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class Random(Optimizer):
|
|
14
|
+
number_of_samples: int
|
|
15
|
+
processes: int
|
|
16
|
+
disable_tqdm: bool
|
|
17
|
+
bounds: npt.NDArray[np.float64]
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
bounds: list[tuple[float, float]],
|
|
22
|
+
number_of_samples: int = 100,
|
|
23
|
+
processes: int = 1,
|
|
24
|
+
disable_tqdm: bool = False,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
number_of_samples : int
|
|
30
|
+
number of random samples (default 100)
|
|
31
|
+
processes : int
|
|
32
|
+
number of processors that will be used (default cpu count)
|
|
33
|
+
disable_tqdm: bool
|
|
34
|
+
if set to True, tdqm will be disabled (default False)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
self.number_of_samples: int = number_of_samples
|
|
38
|
+
self.processes: int = processes
|
|
39
|
+
self.disable_tqdm: bool = disable_tqdm
|
|
40
|
+
self.bounds = np.array(bounds)
|
|
41
|
+
|
|
42
|
+
def minimize(
|
|
43
|
+
self,
|
|
44
|
+
func: Callable[[npt.NDArray[np.float64]], float],
|
|
45
|
+
init: npt.NDArray[np.float64]
|
|
46
|
+
) -> tuple[float, npt.NDArray[np.float64]]:
|
|
47
|
+
"""Returns hyperparameters which lead to the lowest values
|
|
48
|
+
returned by the optimizer
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
func_creator : Callable[[ArgsType], Callable[[ArgsType], float]]
|
|
53
|
+
function which receives hyperparameters and returns
|
|
54
|
+
a function which will be optimized using the optimizer
|
|
55
|
+
optimizer : Optimizer
|
|
56
|
+
object of the Optimizer class
|
|
57
|
+
init : ArgsType
|
|
58
|
+
initial args for the optimizer
|
|
59
|
+
hyperparams_init : ArgsType
|
|
60
|
+
initial hyperparameters
|
|
61
|
+
evaluation_func : Callable[[ArgsType], Callable[[ArgsType], float]]
|
|
62
|
+
function, which receives hyperparameters, and returns
|
|
63
|
+
function which receives params and return evaluation
|
|
64
|
+
bounds : list[float]
|
|
65
|
+
bounds for hyperparameters (default None)
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
ArgsType
|
|
70
|
+
Returns hyperparameters which lead to the lowest values
|
|
71
|
+
returned by the optimizer
|
|
72
|
+
"""
|
|
73
|
+
hyperparams_init = np.array(init)
|
|
74
|
+
hyperparams = (
|
|
75
|
+
(self.bounds[:, 1] - self.bounds[:, 0])
|
|
76
|
+
* np.random.rand(self.number_of_samples, *hyperparams_init.shape)
|
|
77
|
+
+ self.bounds[:, 0])
|
|
78
|
+
|
|
79
|
+
# results = [func(hyperparam) for hyperparam in hyperparams]
|
|
80
|
+
with mp.Pool(processes=self.processes) as p:
|
|
81
|
+
results = list(tqdm.tqdm(
|
|
82
|
+
p.imap(func, hyperparams),
|
|
83
|
+
total=self.number_of_samples,
|
|
84
|
+
disable=self.disable_tqdm
|
|
85
|
+
))
|
|
86
|
+
|
|
87
|
+
min_idx = np.argmin(results)
|
|
88
|
+
return results[min_idx], hyperparams[min_idx]
|
|
89
|
+
# return HyperOptimizerResults.from_solver_results(
|
|
90
|
+
# results[min_idx], hyperparams[min_idx])
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import numpy.typing as npt
|
|
2
|
+
from typing import Optional, Callable
|
|
3
|
+
|
|
4
|
+
import scipy
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from .base import Optimizer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ScipyOptimizer(Optimizer):
|
|
11
|
+
"""
|
|
12
|
+
Class for the SciPy minimizer.
|
|
13
|
+
|
|
14
|
+
Attributes
|
|
15
|
+
----------
|
|
16
|
+
maxfun : int
|
|
17
|
+
Maximum number of function evaluations.
|
|
18
|
+
bounds : list[tuple[float, float]] or None
|
|
19
|
+
A list of tuples specifying the lower and upper bounds for each dimension of the search space,
|
|
20
|
+
or None if no bounds are provided.
|
|
21
|
+
"""
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
maxfun: int,
|
|
25
|
+
bounds: Optional[list[tuple[float, float]]] = None
|
|
26
|
+
) -> None:
|
|
27
|
+
self.maxfun = maxfun
|
|
28
|
+
self.bounds = bounds
|
|
29
|
+
|
|
30
|
+
def minimize(
|
|
31
|
+
self,
|
|
32
|
+
func: Callable[[npt.NDArray[np.float64]], float],
|
|
33
|
+
init: npt.NDArray[np.float64]
|
|
34
|
+
) -> tuple[float, npt.NDArray[np.float64]]:
|
|
35
|
+
"""
|
|
36
|
+
Minimize the given function using the SciPy minimize.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
func : callable
|
|
41
|
+
The objective function to be minimized.
|
|
42
|
+
The function should take a single argument, which is a NumPy array of type np.float64,
|
|
43
|
+
and return a float value.
|
|
44
|
+
init : numpy.ndarray
|
|
45
|
+
The initial point for the optimization algorithm.
|
|
46
|
+
The array should have dtype np.float64.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
tuple
|
|
51
|
+
A tuple containing the minimum function value and the corresponding optimal point.
|
|
52
|
+
"""
|
|
53
|
+
def wrapper(params: npt.NDArray[np.float64]) -> float:
|
|
54
|
+
return func(np.array(params).reshape(np.array(init).shape))
|
|
55
|
+
|
|
56
|
+
result = scipy.optimize.minimize(
|
|
57
|
+
wrapper,
|
|
58
|
+
np.array(init).flatten(),
|
|
59
|
+
bounds=(
|
|
60
|
+
self.bounds if self.bounds is not None
|
|
61
|
+
else [(0, 2*np.pi)]*len(np.array(init).flatten())
|
|
62
|
+
),
|
|
63
|
+
options={'maxfun': self.maxfun}
|
|
64
|
+
)
|
|
65
|
+
return result.fun, result.x.reshape(np.array(init).shape)
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
import sympy
|
|
4
|
+
|
|
5
|
+
from QHyper.hyperparameter_gen.parser import Expression
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Problem(ABC):
|
|
9
|
+
"""Interface for different combinatorial optimization problems
|
|
10
|
+
|
|
11
|
+
Objective function and constrians are the main components
|
|
12
|
+
and should be written in the SymPy syntax.
|
|
13
|
+
Depending on the selcted solver, these parts can be used
|
|
14
|
+
separately or, e.g., as a Quadratic Unconstrained
|
|
15
|
+
Binary Optimization (QUBO) formularion.
|
|
16
|
+
|
|
17
|
+
If the QUBO is provided, it should be passed to the
|
|
18
|
+
objective_function and the constraints should be empty.
|
|
19
|
+
|
|
20
|
+
Attributes
|
|
21
|
+
----------
|
|
22
|
+
objective_function: Expression
|
|
23
|
+
objective function in SymPy syntax
|
|
24
|
+
constraints : list[Expression]
|
|
25
|
+
list of constraints in SymPy syntax
|
|
26
|
+
variables : list[Any]
|
|
27
|
+
list of variables in the problem
|
|
28
|
+
name: str
|
|
29
|
+
helps to differentiate various problems (default "")
|
|
30
|
+
cases: int
|
|
31
|
+
number of variable cases (values)
|
|
32
|
+
(default 1 - denoting binary variable)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
objective_function: Expression
|
|
36
|
+
constraints: list[Expression]
|
|
37
|
+
variables: tuple[sympy.Symbol]
|
|
38
|
+
name: str = ""
|
|
39
|
+
cases: int = 1
|
|
40
|
+
|
|
41
|
+
def get_score(self, result: str, penalty: float = 0) -> float:
|
|
42
|
+
"""Returns score of the outcome provided as a binary string
|
|
43
|
+
|
|
44
|
+
Necessary to evaluate results.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
result : str
|
|
49
|
+
outcome as a string of zeros and ones
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
float
|
|
54
|
+
Returns float indicating the score, if function should be
|
|
55
|
+
maximized the returned value should be returned with negative sign
|
|
56
|
+
"""
|
|
57
|
+
raise Exception("Unimplemented")
|
|
58
|
+
|
|
59
|
+
def __repr__(self) -> str:
|
|
60
|
+
if self.name == "":
|
|
61
|
+
return super().__repr__()
|
|
62
|
+
return f"{self.__class__.__name__}_{self.name}"
|