qhyper 0.2.2__tar.gz → 0.3.1__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.2.2 → qhyper-0.3.1}/PKG-INFO +15 -5
- {qhyper-0.2.2/QHyper/solvers/vqa/eval_funcs → qhyper-0.3.1/QHyper}/__init__.py +3 -1
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/constraint.py +43 -8
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/converter.py +20 -6
- qhyper-0.3.1/QHyper/optimizers/__init__.py +111 -0
- qhyper-0.3.1/QHyper/optimizers/base.py +181 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/cem.py +23 -25
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/dummy.py +9 -8
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/grid_search.py +24 -29
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/qml_gradient_descent.py +18 -19
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/random.py +15 -17
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/scipy_minimizer.py +13 -18
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/util.py +2 -3
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/parser.py +61 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/polynomial.py +69 -9
- qhyper-0.3.1/QHyper/problems/__init__.py +117 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/problems/base.py +8 -4
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/problems/community_detection.py +4 -4
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/problems/knapsack.py +46 -46
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/problems/maxcut.py +10 -13
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/problems/tsp.py +17 -17
- qhyper-0.3.1/QHyper/solvers/__init__.py +236 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/solvers/base.py +5 -6
- qhyper-0.3.1/QHyper/solvers/classical/gurobi/__init__.py +1 -0
- {qhyper-0.2.2/QHyper/solvers/classical → qhyper-0.3.1/QHyper/solvers/classical/gurobi}/gurobi.py +34 -17
- {qhyper-0.2.2/QHyper → qhyper-0.3.1/QHyper/solvers/gate_based/pennylane}/__init__.py +4 -0
- qhyper-0.3.1/QHyper/solvers/gate_based/pennylane/h_qaoa.py +165 -0
- qhyper-0.3.1/QHyper/solvers/gate_based/pennylane/qaoa.py +232 -0
- qhyper-0.3.1/QHyper/solvers/gate_based/pennylane/qml_qaoa.py +88 -0
- qhyper-0.3.1/QHyper/solvers/gate_based/pennylane/wf_qaoa.py +121 -0
- qhyper-0.3.1/QHyper/solvers/hyper_optimizer.py +109 -0
- qhyper-0.3.1/QHyper/solvers/quantum_annealing/dwave/__init__.py +3 -0
- qhyper-0.3.1/QHyper/solvers/quantum_annealing/dwave/advantage.py +147 -0
- {qhyper-0.2.2/QHyper/solvers/quantum_annealing → qhyper-0.3.1/QHyper/solvers/quantum_annealing/dwave}/cqm.py +8 -19
- {qhyper-0.2.2/QHyper/solvers/quantum_annealing → qhyper-0.3.1/QHyper/solvers/quantum_annealing/dwave}/dqm.py +10 -12
- qhyper-0.3.1/QHyper/util.py +220 -0
- qhyper-0.3.1/README.md +61 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/qhyper.egg-info/PKG-INFO +15 -5
- {qhyper-0.2.2 → qhyper-0.3.1}/qhyper.egg-info/SOURCES.txt +13 -17
- qhyper-0.3.1/qhyper.egg-info/requires.txt +8 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/setup.py +2 -21
- qhyper-0.3.1/tests/test_optimizers.py +70 -0
- qhyper-0.3.1/tests/test_optimizers_full.py +237 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/tests/test_problems.py +8 -5
- {qhyper-0.2.2 → qhyper-0.3.1}/tests/test_vqa.py +37 -29
- qhyper-0.2.2/QHyper/optimizers/__init__.py +0 -59
- qhyper-0.2.2/QHyper/optimizers/base.py +0 -136
- qhyper-0.2.2/QHyper/problems/__init__.py +0 -84
- qhyper-0.2.2/QHyper/solvers/__init__.py +0 -127
- qhyper-0.2.2/QHyper/solvers/quantum_annealing/advantage.py +0 -192
- qhyper-0.2.2/QHyper/solvers/vqa/__init__.py +0 -44
- qhyper-0.2.2/QHyper/solvers/vqa/base.py +0 -177
- qhyper-0.2.2/QHyper/solvers/vqa/eval_funcs/base.py +0 -45
- qhyper-0.2.2/QHyper/solvers/vqa/eval_funcs/wfeval.py +0 -39
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/__init__.py +0 -48
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/base.py +0 -163
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/h_qaoa.py +0 -135
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/mixers.py +0 -17
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/qaoa.py +0 -212
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/qml_qaoa.py +0 -60
- qhyper-0.2.2/QHyper/solvers/vqa/pqc/wf_qaoa.py +0 -96
- qhyper-0.2.2/QHyper/util.py +0 -107
- qhyper-0.2.2/README.md +0 -80
- qhyper-0.2.2/qhyper.egg-info/requires.txt +0 -8
- qhyper-0.2.2/tests/test_optimizers.py +0 -91
- qhyper-0.2.2/tests/test_optimizers_full.py +0 -204
- {qhyper-0.2.2 → qhyper-0.3.1}/LICENSE.txt +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/optimizers/basinhopping.py +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/problems/workflow_scheduling.py +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/solvers/classical/__init__.py +0 -0
- {qhyper-0.2.2/QHyper/solvers/gurobi → qhyper-0.3.1/QHyper/solvers/gate_based}/__init__.py +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/QHyper/solvers/quantum_annealing/__init__.py +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/qhyper.egg-info/dependency_links.txt +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/qhyper.egg-info/top_level.txt +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/setup.cfg +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/tests/test_converter.py +0 -0
- {qhyper-0.2.2 → qhyper-0.3.1}/tests/test_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qhyper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Quantum and classical problem solvers
|
|
5
5
|
Author: ACK Cyfronet AGH
|
|
6
6
|
Author-email: tomasz.lamza@cyfronet.pl
|
|
@@ -14,13 +14,23 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
|
14
14
|
Classifier: Operating System :: Microsoft :: Windows
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
License-File: LICENSE.txt
|
|
17
|
-
Requires-Dist: numpy
|
|
18
|
-
Requires-Dist: PennyLane
|
|
17
|
+
Requires-Dist: numpy~=1.26.4
|
|
18
|
+
Requires-Dist: PennyLane~=0.38
|
|
19
19
|
Requires-Dist: tdqm
|
|
20
|
-
Requires-Dist: sympy==1.
|
|
20
|
+
Requires-Dist: sympy==1.13.1
|
|
21
21
|
Requires-Dist: dwave-system
|
|
22
22
|
Requires-Dist: gurobipy
|
|
23
23
|
Requires-Dist: pandas
|
|
24
24
|
Requires-Dist: wfcommons
|
|
25
|
+
Dynamic: author
|
|
26
|
+
Dynamic: author-email
|
|
27
|
+
Dynamic: classifier
|
|
28
|
+
Dynamic: description
|
|
29
|
+
Dynamic: description-content-type
|
|
30
|
+
Dynamic: keywords
|
|
31
|
+
Dynamic: license
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: summary
|
|
25
35
|
|
|
26
36
|
A package that allows to build and solve quantum and classical problems using predefined solvers and problems.
|
|
@@ -1,10 +1,42 @@
|
|
|
1
|
+
"""This module contains the Constraint class which is used to represent a, well,
|
|
2
|
+
constraint. It is used in the QUBO formulation to define the left and right
|
|
3
|
+
hand sides of the constraints, and the operator.
|
|
4
|
+
|
|
5
|
+
.. rubric:: Main class
|
|
6
|
+
|
|
7
|
+
.. autosummary::
|
|
8
|
+
:toctree: generated
|
|
9
|
+
|
|
10
|
+
Constraint -- implementation of the constraint.
|
|
11
|
+
|
|
12
|
+
.. rubric:: Enum Classes
|
|
13
|
+
|
|
14
|
+
.. autoclass:: MethodsForInequalities
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
.. rubric:: Functions
|
|
18
|
+
|
|
19
|
+
.. autofunction:: get_number_of_constraints
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
1
23
|
import uuid
|
|
2
24
|
from enum import Enum
|
|
3
25
|
|
|
4
|
-
from QHyper.polynomial import Polynomial
|
|
26
|
+
from QHyper.polynomial import Polynomial, PolynomialType
|
|
5
27
|
|
|
6
28
|
|
|
7
29
|
class MethodsForInequalities(Enum):
|
|
30
|
+
"""Enum class with different methods for handling inequalities.
|
|
31
|
+
|
|
32
|
+
There are two available methods for handling inequalities:
|
|
33
|
+
.. list-table::
|
|
34
|
+
|
|
35
|
+
* - SLACKS_LOG_2
|
|
36
|
+
- UNBALANCED_PENALIZATION
|
|
37
|
+
* - the method uses slack variables in number of log2(n)
|
|
38
|
+
- the method uses unbalanced penalization
|
|
39
|
+
"""
|
|
8
40
|
SLACKS_LOG_2 = 0
|
|
9
41
|
UNBALANCED_PENALIZATION = 1
|
|
10
42
|
|
|
@@ -14,6 +46,8 @@ UNBALANCED_PENALIZATION = MethodsForInequalities.UNBALANCED_PENALIZATION
|
|
|
14
46
|
|
|
15
47
|
|
|
16
48
|
class Operator(Enum):
|
|
49
|
+
"""Enum class with different operators.
|
|
50
|
+
"""
|
|
17
51
|
EQ = "=="
|
|
18
52
|
GE = ">="
|
|
19
53
|
LE = "<="
|
|
@@ -21,13 +55,13 @@ class Operator(Enum):
|
|
|
21
55
|
|
|
22
56
|
class Constraint:
|
|
23
57
|
"""
|
|
24
|
-
A class
|
|
58
|
+
A class representing the constraint.
|
|
25
59
|
|
|
26
60
|
Attributes
|
|
27
61
|
----------
|
|
28
|
-
lhs :
|
|
62
|
+
lhs : PolynomialType
|
|
29
63
|
The left-hand side of the constraint.
|
|
30
|
-
rhs :
|
|
64
|
+
rhs : PolynomialType, default 0
|
|
31
65
|
The right-hand side of the constraint.
|
|
32
66
|
operator : Operator, default Operator.EQ
|
|
33
67
|
The operator of the constraint. It can be ==, >=, <=.
|
|
@@ -42,17 +76,18 @@ class Constraint:
|
|
|
42
76
|
Example use is assigning same weight to the constraints with the same
|
|
43
77
|
group when creating qubo.
|
|
44
78
|
"""
|
|
79
|
+
|
|
45
80
|
def __init__(
|
|
46
81
|
self,
|
|
47
|
-
lhs:
|
|
48
|
-
rhs:
|
|
82
|
+
lhs: PolynomialType,
|
|
83
|
+
rhs: PolynomialType = 0,
|
|
49
84
|
operator: Operator = Operator.EQ,
|
|
50
85
|
method_for_inequalities: MethodsForInequalities | None = None,
|
|
51
86
|
label: str = "",
|
|
52
87
|
group: int = -1,
|
|
53
88
|
) -> None:
|
|
54
|
-
self.lhs
|
|
55
|
-
self.rhs
|
|
89
|
+
self.lhs = lhs if isinstance(lhs, Polynomial) else Polynomial(lhs)
|
|
90
|
+
self.rhs = rhs if isinstance(rhs, Polynomial) else Polynomial(rhs)
|
|
56
91
|
self.operator: Operator = operator
|
|
57
92
|
|
|
58
93
|
if operator != Operator.EQ and method_for_inequalities is None:
|
|
@@ -3,6 +3,20 @@
|
|
|
3
3
|
# under the grant agreement no. POIR.04.02.00-00-D014/20-00
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
"""This module contains the Converter class with static methods for converting Problem objects to different formats.
|
|
7
|
+
|
|
8
|
+
To use different solvers, but with the same Problem representation, some form
|
|
9
|
+
of conversion is needed. This module provides methods for converting Problem
|
|
10
|
+
objects to ConstrainedQuadraticModel, DiscreteQuadraticModel, and QUBO formats.
|
|
11
|
+
|
|
12
|
+
.. autosummary::
|
|
13
|
+
:toctree: generated
|
|
14
|
+
|
|
15
|
+
Converter
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
from typing import cast
|
|
7
21
|
|
|
8
22
|
import dimod
|
|
@@ -63,7 +77,7 @@ class Converter:
|
|
|
63
77
|
return weight[0]*lhs + weight[1]*lhs**2
|
|
64
78
|
|
|
65
79
|
@staticmethod
|
|
66
|
-
def
|
|
80
|
+
def assign_penalty_weights_to_constraints(
|
|
67
81
|
constraints_weights: list[float], constraints: list[Constraint]
|
|
68
82
|
) -> list[tuple[list[float], Constraint]]:
|
|
69
83
|
weights_constraints_list = []
|
|
@@ -98,13 +112,13 @@ class Converter:
|
|
|
98
112
|
return weights_constraints_list
|
|
99
113
|
|
|
100
114
|
@staticmethod
|
|
101
|
-
def create_qubo(problem: Problem,
|
|
102
|
-
of_weight =
|
|
115
|
+
def create_qubo(problem: Problem, penalty_weights: list[float]) -> Polynomial:
|
|
116
|
+
of_weight = penalty_weights[0] if len(penalty_weights) else 1
|
|
103
117
|
result = float(of_weight) * problem.objective_function
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
for weight, constraint in Converter.
|
|
107
|
-
|
|
119
|
+
constraints_penalty_weights = penalty_weights[1:]
|
|
120
|
+
for weight, constraint in Converter.assign_penalty_weights_to_constraints(
|
|
121
|
+
constraints_penalty_weights, problem.constraints
|
|
108
122
|
):
|
|
109
123
|
if constraint.operator == Operator.EQ:
|
|
110
124
|
result += float(weight[0]) * (
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# This work was supported by the EuroHPC PL infrastructure funded at the
|
|
2
|
+
# Smart Growth Operational Programme (2014-2020), Measure 4.2
|
|
3
|
+
# under the grant agreement no. POIR.04.02.00-00-D014/20-00
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
This module contains implementations of different optimizers.
|
|
7
|
+
Some of the optimizers are written from scratch based on popular algorithms,
|
|
8
|
+
while others are just a wrapper for existing solutions.
|
|
9
|
+
No optimizer is imported by deafult to reduce number of dependencies.
|
|
10
|
+
To use any optimizer you can import it directly like
|
|
11
|
+
|
|
12
|
+
.. code-block:: python
|
|
13
|
+
|
|
14
|
+
from QHyper.optimizers.random import Random
|
|
15
|
+
|
|
16
|
+
or use function :py:func:`Optimizers.get` with the name of the optimizer.
|
|
17
|
+
Any optimizer that is in directory 'QHyper/custom' or 'custom' will be
|
|
18
|
+
also available in this function.
|
|
19
|
+
|
|
20
|
+
.. rubric:: Optimization result dataclass
|
|
21
|
+
|
|
22
|
+
.. autosummary::
|
|
23
|
+
:toctree: generated
|
|
24
|
+
|
|
25
|
+
OptimizationResult
|
|
26
|
+
OptimizationParameter
|
|
27
|
+
|
|
28
|
+
.. rubric:: Interface
|
|
29
|
+
|
|
30
|
+
.. autosummary::
|
|
31
|
+
:toctree: generated
|
|
32
|
+
|
|
33
|
+
Optimizer
|
|
34
|
+
|
|
35
|
+
.. rubric:: Available optimizers
|
|
36
|
+
|
|
37
|
+
.. autosummary::
|
|
38
|
+
:toctree: generated
|
|
39
|
+
|
|
40
|
+
scipy_minimizer.ScipyOptimizer -- Wrapper for the scipy.optimize.minimize function.
|
|
41
|
+
qml_gradient_descent.QmlGradientDescent -- Wrapper for the PennyLane gradient descent optimizers.
|
|
42
|
+
cem.CEM -- Cross-entropy method optimizer.
|
|
43
|
+
random.Random -- Random search optimizer.
|
|
44
|
+
grid_search.GridSearch -- Grid search optimizer.
|
|
45
|
+
dummy.Dummy -- Dummy optimizer.
|
|
46
|
+
|
|
47
|
+
.. rubric:: Additional functions
|
|
48
|
+
|
|
49
|
+
.. autoclass:: Optimizers
|
|
50
|
+
:members:
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
import copy
|
|
54
|
+
from typing import Type, Any
|
|
55
|
+
|
|
56
|
+
from QHyper.util import search_for
|
|
57
|
+
|
|
58
|
+
from QHyper.optimizers.base import ( # noqa: F401
|
|
59
|
+
Optimizer, OptimizationResult, OptimizerError, OptimizationParameter) # noqa: F401
|
|
60
|
+
|
|
61
|
+
from .dummy import Dummy
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Optimizers:
|
|
65
|
+
custom_optimizers: None | dict[str, type] = None
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def get(name: str) -> Type[Optimizer]:
|
|
69
|
+
"""
|
|
70
|
+
Get Optimizer class by name.
|
|
71
|
+
|
|
72
|
+
The optimizer will be available by the 'name' attribute if defined or
|
|
73
|
+
by the class name. Letters case doesn't matter.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
if Optimizers.custom_optimizers is None:
|
|
77
|
+
Optimizers.custom_optimizers = (
|
|
78
|
+
search_for(Optimizer, 'QHyper/custom')
|
|
79
|
+
| search_for(Optimizer, 'custom'))
|
|
80
|
+
|
|
81
|
+
name_ = name.lower()
|
|
82
|
+
|
|
83
|
+
if name_ in Optimizers.custom_optimizers:
|
|
84
|
+
return Optimizers.custom_optimizers[name_]
|
|
85
|
+
elif name_ in ["scipy", "scipyminimizer"]:
|
|
86
|
+
from .scipy_minimizer import ScipyOptimizer
|
|
87
|
+
return ScipyOptimizer
|
|
88
|
+
elif name_ in ["random", "randomsearch"]:
|
|
89
|
+
from .random import Random
|
|
90
|
+
return Random
|
|
91
|
+
elif name_ in ["qml", "qmlgradientdescent"]:
|
|
92
|
+
from .qml_gradient_descent import QmlGradientDescent
|
|
93
|
+
return QmlGradientDescent
|
|
94
|
+
elif name_ in ["cem", "crossentropymethod"]:
|
|
95
|
+
from .cem import CEM
|
|
96
|
+
return CEM
|
|
97
|
+
elif name_ in ["grid", "gridsearch"]:
|
|
98
|
+
from .grid_search import GridSearch
|
|
99
|
+
return GridSearch
|
|
100
|
+
elif name_ in ["dummy"]:
|
|
101
|
+
return Dummy
|
|
102
|
+
else:
|
|
103
|
+
raise OptimizerError(f"Optimizer {name} not found")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def create_optimizer(config: dict[str, Any]) -> Optimizer:
|
|
107
|
+
config_ = copy.deepcopy(config)
|
|
108
|
+
opt_type = config_.pop('type')
|
|
109
|
+
|
|
110
|
+
optimizer_class = Optimizers.get(opt_type)
|
|
111
|
+
return optimizer_class(**config_)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# This work was supported by the EuroHPC PL infrastructure funded at the
|
|
2
|
+
# Smart Growth Operational Programme (2014-2020), Measure 4.2
|
|
3
|
+
# under the grant agreement no. POIR.04.02.00-00-D014/20-00
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
import abc
|
|
7
|
+
import dataclasses
|
|
8
|
+
|
|
9
|
+
from abc import abstractmethod
|
|
10
|
+
|
|
11
|
+
from typing import Callable, Self
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclasses.dataclass
|
|
15
|
+
class OptimizationParameter:
|
|
16
|
+
"""
|
|
17
|
+
Dataclass for storing bounds, steps and init values for parameters
|
|
18
|
+
that might be optimized. Most of the time some of this values are not
|
|
19
|
+
required, but it depends on the chosen optimization algorithm.
|
|
20
|
+
Check the documentation of the chosen algorithm to see which values are
|
|
21
|
+
required.
|
|
22
|
+
|
|
23
|
+
Attributes
|
|
24
|
+
----------
|
|
25
|
+
min : list[float]
|
|
26
|
+
List of minimum values for each parameter.
|
|
27
|
+
max : list[float]
|
|
28
|
+
List of maximum values for each parameter.
|
|
29
|
+
step : list[float]
|
|
30
|
+
List of step values for each parameter. Used for example in the grid
|
|
31
|
+
search algorithm. For 0-th parameter the following values will be
|
|
32
|
+
generated: min[0], min[0] + step[0], min[0] + 2*step[0], ...
|
|
33
|
+
init : list[float]
|
|
34
|
+
List of initial values for each parameter. Some algorithms require
|
|
35
|
+
starting point to be set.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
min: list[float] = dataclasses.field(default_factory=list)
|
|
39
|
+
max: list[float] = dataclasses.field(default_factory=list)
|
|
40
|
+
step: list[float] = dataclasses.field(default_factory=list)
|
|
41
|
+
init: list[float] = dataclasses.field(default_factory=list)
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
self.min = list(self.min)
|
|
45
|
+
self.max = list(self.max)
|
|
46
|
+
self.step = list(self.step)
|
|
47
|
+
self.init = list(self.init)
|
|
48
|
+
|
|
49
|
+
def assert_bounds(self) -> None:
|
|
50
|
+
"""Check if bounds are correctly set.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
if not self.min:
|
|
54
|
+
raise ValueError("Min bounds are required")
|
|
55
|
+
if not self.max:
|
|
56
|
+
raise ValueError("Max bounds are required")
|
|
57
|
+
if len(self.min) != len(self.max):
|
|
58
|
+
raise ValueError("Min and Max bounds must have the same length")
|
|
59
|
+
|
|
60
|
+
def assert_step(self) -> None:
|
|
61
|
+
"""Check if steps are correctly set.
|
|
62
|
+
"""
|
|
63
|
+
self.assert_bounds()
|
|
64
|
+
if len(self.step) == 0:
|
|
65
|
+
raise ValueError("Steps are required")
|
|
66
|
+
if len(self.min) != len(self.step):
|
|
67
|
+
raise ValueError("Steps must have the same length as bounds")
|
|
68
|
+
|
|
69
|
+
def assert_init(self) -> None:
|
|
70
|
+
"""Check if init values are correctly set.
|
|
71
|
+
"""
|
|
72
|
+
if len(self.init) == 0:
|
|
73
|
+
raise ValueError("Init are required")
|
|
74
|
+
|
|
75
|
+
def assert_bounds_init(self) -> None:
|
|
76
|
+
"""Check if bounds and init values are correctly set.
|
|
77
|
+
"""
|
|
78
|
+
self.assert_bounds()
|
|
79
|
+
self.assert_init()
|
|
80
|
+
if len(self.min) != len(self.init):
|
|
81
|
+
raise ValueError("Init must have the same length as bounds")
|
|
82
|
+
|
|
83
|
+
def __add__(self, other: Self) -> 'OptimizationParameter':
|
|
84
|
+
min_ = self.min + other.min
|
|
85
|
+
max_ = self.max + other.max
|
|
86
|
+
step_ = self.step + other.step
|
|
87
|
+
init_ = self.init + other.init
|
|
88
|
+
return OptimizationParameter(min_, max_, step_, init_)
|
|
89
|
+
|
|
90
|
+
def __len__(self) -> int:
|
|
91
|
+
if self.min:
|
|
92
|
+
return len(self.min)
|
|
93
|
+
if self.max:
|
|
94
|
+
return len(self.max)
|
|
95
|
+
if self.init:
|
|
96
|
+
return len(self.init)
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
def update(self,
|
|
100
|
+
min: list[float] | None = None,
|
|
101
|
+
max: list[float] | None = None,
|
|
102
|
+
step: list[float] | None = None,
|
|
103
|
+
init: list[float] | None = None) -> 'OptimizationParameter':
|
|
104
|
+
if min is None:
|
|
105
|
+
min = self.min.copy()
|
|
106
|
+
if max is None:
|
|
107
|
+
max = self.max.copy()
|
|
108
|
+
if step is None:
|
|
109
|
+
step = self.step.copy()
|
|
110
|
+
if init is None:
|
|
111
|
+
init = self.init.copy()
|
|
112
|
+
return OptimizationParameter(min, max, step, init)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def bounds(self) -> list[tuple[float, float]]:
|
|
116
|
+
return list(zip(self.min, self.max))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class OptimizerError(Exception):
|
|
120
|
+
"""
|
|
121
|
+
Base class for exceptions in this module.
|
|
122
|
+
"""
|
|
123
|
+
...
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclasses.dataclass
|
|
127
|
+
class OptimizationResult:
|
|
128
|
+
"""
|
|
129
|
+
Dataclass for storing the results of an optimization run.
|
|
130
|
+
|
|
131
|
+
Attributes
|
|
132
|
+
----------
|
|
133
|
+
value : float
|
|
134
|
+
The minimum function value found by the optimization algorithm.
|
|
135
|
+
params : list[float]
|
|
136
|
+
The optimal point (function arguments) found by the optimization
|
|
137
|
+
algorithm.
|
|
138
|
+
history : list[list[OptimizationResult]]
|
|
139
|
+
The history of the optimization algorithm. Each element of the list
|
|
140
|
+
represents the results of the objective function at each
|
|
141
|
+
iteration - there can be multiple results per each iteration (epoch).
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
value: float
|
|
145
|
+
params: list[float]
|
|
146
|
+
history: list[list['OptimizationResult']] = dataclasses.field(
|
|
147
|
+
default_factory=list)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class Optimizer(abc.ABC):
|
|
151
|
+
"""
|
|
152
|
+
Base class for Optimizer.
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
@abstractmethod
|
|
157
|
+
def minimize(
|
|
158
|
+
self,
|
|
159
|
+
func: Callable[[list[float]], OptimizationResult],
|
|
160
|
+
init: OptimizationParameter
|
|
161
|
+
) -> OptimizationResult:
|
|
162
|
+
"""
|
|
163
|
+
Method that minimizes the given function using the
|
|
164
|
+
implemented optimization algorithm.
|
|
165
|
+
This method has to be implemented by the subclass.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
func : Callable[[list[float]], OptimizationResult]
|
|
170
|
+
The objective function to be minimized.
|
|
171
|
+
init : OptimizationParameter
|
|
172
|
+
The initial parameter for the optimization algorithm.
|
|
173
|
+
The required fields are defined by subclass.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
OptimizationResult
|
|
178
|
+
Result contains the minimum function value, the
|
|
179
|
+
corresponding optimal point, and the history of
|
|
180
|
+
the optimization.
|
|
181
|
+
"""
|
|
@@ -11,18 +11,23 @@ from numpy.typing import NDArray
|
|
|
11
11
|
from QHyper.optimizers.util import run_parallel
|
|
12
12
|
|
|
13
13
|
from QHyper.optimizers.base import (
|
|
14
|
-
Optimizer, OptimizationResult, OptimizerError)
|
|
14
|
+
Optimizer, OptimizationResult, OptimizerError, OptimizationParameter)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class CEM(Optimizer):
|
|
18
18
|
"""Implementation of the Cross Entropy Method for hyperparameter tuning
|
|
19
19
|
|
|
20
|
+
The Cross Entropy Method is a stochastic optimization algorithm that
|
|
21
|
+
iteratively samples candidate solutions from a multivariate normal
|
|
22
|
+
distribution. The mean and covariance of the distribution are updated
|
|
23
|
+
based on the best samples from the previous iteration.
|
|
24
|
+
This alogrithm requries the following parameters to be set:
|
|
25
|
+
- `min` and `max` bounds for each parameter
|
|
26
|
+
- `init` initial values for each parameter
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
Attributes
|
|
21
30
|
----------
|
|
22
|
-
bounds : numpy.ndarray, optional
|
|
23
|
-
The bounds for the optimization algorithm. Not all optimizers
|
|
24
|
-
support bounds. The shape of the array should be (n, 2), where
|
|
25
|
-
n is the number of parameters (`init` in method :meth:`minimize`).
|
|
26
31
|
verbose : bool, default False
|
|
27
32
|
Whether to print the optimization progress.
|
|
28
33
|
disable_tqdm : bool, default True
|
|
@@ -41,7 +46,6 @@ class CEM(Optimizer):
|
|
|
41
46
|
`samples_per_epoch * elite_frac`.
|
|
42
47
|
"""
|
|
43
48
|
|
|
44
|
-
bounds: NDArray
|
|
45
49
|
verbose: bool
|
|
46
50
|
disable_tqdm: bool
|
|
47
51
|
epochs: int
|
|
@@ -52,7 +56,6 @@ class CEM(Optimizer):
|
|
|
52
56
|
|
|
53
57
|
def __init__(
|
|
54
58
|
self,
|
|
55
|
-
bounds: NDArray,
|
|
56
59
|
verbose: bool = False,
|
|
57
60
|
disable_tqdm: bool = True,
|
|
58
61
|
epochs: int = 5,
|
|
@@ -73,7 +76,6 @@ class CEM(Optimizer):
|
|
|
73
76
|
processes : int, default 1
|
|
74
77
|
"""
|
|
75
78
|
|
|
76
|
-
self.bounds = bounds
|
|
77
79
|
self.verbose = verbose
|
|
78
80
|
self.disable_tqdm = disable_tqdm
|
|
79
81
|
self.epochs = epochs
|
|
@@ -85,44 +87,40 @@ class CEM(Optimizer):
|
|
|
85
87
|
int(self.samples_per_epoch * self.elite_frac), 1)
|
|
86
88
|
|
|
87
89
|
def _get_points(
|
|
88
|
-
self, mean: NDArray, cov: NDArray
|
|
90
|
+
self, mean: NDArray, cov: NDArray, init: OptimizationParameter
|
|
89
91
|
) -> NDArray:
|
|
90
|
-
if self.bounds is None:
|
|
91
|
-
raise OptimizerError('This optimizer requires bounds')
|
|
92
|
-
|
|
93
92
|
# TODO
|
|
94
93
|
hyperparams: list[NDArray] = []
|
|
94
|
+
bounds = np.array(init.bounds)
|
|
95
|
+
|
|
95
96
|
while len(hyperparams) < self.samples_per_epoch:
|
|
96
97
|
point = np.random.multivariate_normal(mean, cov)
|
|
97
98
|
if (
|
|
98
|
-
(
|
|
99
|
-
and (point <
|
|
99
|
+
(bounds[:, 0] <= point).all()
|
|
100
|
+
and (point < bounds[:, 1]).all()
|
|
100
101
|
):
|
|
101
102
|
hyperparams.append(point)
|
|
102
103
|
|
|
103
104
|
return np.vstack(hyperparams)
|
|
104
105
|
|
|
105
|
-
def
|
|
106
|
+
def minimize(
|
|
106
107
|
self,
|
|
107
|
-
func: Callable[[
|
|
108
|
-
init:
|
|
108
|
+
func: Callable[[list[float]], OptimizationResult],
|
|
109
|
+
init: OptimizationParameter,
|
|
109
110
|
) -> OptimizationResult:
|
|
110
|
-
|
|
111
|
-
raise OptimizerError("Initial point must be provided.")
|
|
112
|
-
|
|
113
|
-
self.check_bounds(init)
|
|
111
|
+
init.assert_bounds_init()
|
|
114
112
|
|
|
115
|
-
mean = init.
|
|
113
|
+
mean = np.array(init.init)
|
|
116
114
|
cov = np.identity(len(mean))
|
|
117
|
-
best_hyperparams = init
|
|
118
|
-
best_result = OptimizationResult(np.inf, init, [])
|
|
115
|
+
best_hyperparams = init.init
|
|
116
|
+
best_result = OptimizationResult(np.inf, init.init, [])
|
|
119
117
|
history: list[list[OptimizationResult]] = []
|
|
120
118
|
|
|
121
119
|
for i in range(self.epochs):
|
|
122
120
|
if self.verbose:
|
|
123
121
|
print(f'Epoch {i+1}/{self.epochs}')
|
|
124
122
|
|
|
125
|
-
hyperparams = self._get_points(mean, cov)
|
|
123
|
+
hyperparams = self._get_points(mean, cov, init)
|
|
126
124
|
results = run_parallel(func, hyperparams, self.processes,
|
|
127
125
|
self.disable_tqdm)
|
|
128
126
|
|
|
@@ -5,10 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
from typing import Callable
|
|
7
7
|
|
|
8
|
-
from numpy.typing import NDArray
|
|
9
|
-
|
|
10
8
|
from QHyper.optimizers.base import (
|
|
11
|
-
Optimizer, OptimizationResult, OptimizerError)
|
|
9
|
+
Optimizer, OptimizationResult, OptimizerError, OptimizationParameter)
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class Dummy(Optimizer):
|
|
@@ -16,16 +14,19 @@ class Dummy(Optimizer):
|
|
|
16
14
|
Dummy optimizer.
|
|
17
15
|
|
|
18
16
|
This optimizer is used as a default optimizer in the case
|
|
19
|
-
when no optimizer is selected.
|
|
17
|
+
when no optimizer is selected. It simply evaluates the function.
|
|
18
|
+
It requires the initial point to be provided.
|
|
19
|
+
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def minimize(
|
|
23
23
|
self,
|
|
24
|
-
func: Callable[[
|
|
25
|
-
init:
|
|
24
|
+
func: Callable[[list[float]], OptimizationResult],
|
|
25
|
+
init: OptimizationParameter | None,
|
|
26
26
|
) -> OptimizationResult:
|
|
27
27
|
if init is None:
|
|
28
28
|
raise OptimizerError("Initial point must be provided.")
|
|
29
|
+
init.assert_init()
|
|
29
30
|
|
|
30
|
-
result = func(init)
|
|
31
|
+
result = func(init.init)
|
|
31
32
|
return OptimizationResult(result.value, result.params, [[result]])
|