qhyper 0.2.1__tar.gz → 0.3.0__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.
Files changed (76) hide show
  1. {qhyper-0.2.1 → qhyper-0.3.0}/PKG-INFO +14 -5
  2. {qhyper-0.2.1/QHyper/solvers/vqa/eval_funcs → qhyper-0.3.0/QHyper}/__init__.py +3 -1
  3. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/constraint.py +43 -8
  4. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/converter.py +20 -6
  5. qhyper-0.3.0/QHyper/optimizers/__init__.py +111 -0
  6. qhyper-0.3.0/QHyper/optimizers/base.py +181 -0
  7. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/cem.py +23 -25
  8. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/dummy.py +9 -8
  9. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/grid_search.py +24 -29
  10. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/qml_gradient_descent.py +18 -19
  11. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/random.py +15 -17
  12. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/scipy_minimizer.py +13 -18
  13. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/util.py +2 -3
  14. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/parser.py +61 -0
  15. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/polynomial.py +69 -9
  16. qhyper-0.3.0/QHyper/problems/__init__.py +117 -0
  17. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/problems/base.py +8 -4
  18. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/problems/community_detection.py +4 -4
  19. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/problems/knapsack.py +46 -46
  20. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/problems/maxcut.py +10 -13
  21. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/problems/tsp.py +17 -17
  22. qhyper-0.3.0/QHyper/solvers/__init__.py +236 -0
  23. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/solvers/base.py +5 -6
  24. qhyper-0.3.0/QHyper/solvers/classical/gurobi/__init__.py +1 -0
  25. {qhyper-0.2.1/QHyper/solvers/classical → qhyper-0.3.0/QHyper/solvers/classical/gurobi}/gurobi.py +34 -17
  26. {qhyper-0.2.1/QHyper → qhyper-0.3.0/QHyper/solvers/gate_based/pennylane}/__init__.py +4 -0
  27. qhyper-0.3.0/QHyper/solvers/gate_based/pennylane/h_qaoa.py +165 -0
  28. qhyper-0.3.0/QHyper/solvers/gate_based/pennylane/qaoa.py +232 -0
  29. qhyper-0.3.0/QHyper/solvers/gate_based/pennylane/qml_qaoa.py +88 -0
  30. qhyper-0.3.0/QHyper/solvers/gate_based/pennylane/wf_qaoa.py +121 -0
  31. qhyper-0.3.0/QHyper/solvers/hyper_optimizer.py +109 -0
  32. qhyper-0.3.0/QHyper/solvers/quantum_annealing/dwave/__init__.py +3 -0
  33. qhyper-0.3.0/QHyper/solvers/quantum_annealing/dwave/advantage.py +147 -0
  34. {qhyper-0.2.1/QHyper/solvers/quantum_annealing → qhyper-0.3.0/QHyper/solvers/quantum_annealing/dwave}/cqm.py +8 -19
  35. {qhyper-0.2.1/QHyper/solvers/quantum_annealing → qhyper-0.3.0/QHyper/solvers/quantum_annealing/dwave}/dqm.py +10 -12
  36. qhyper-0.3.0/QHyper/util.py +220 -0
  37. qhyper-0.3.0/README.md +61 -0
  38. {qhyper-0.2.1 → qhyper-0.3.0}/qhyper.egg-info/PKG-INFO +14 -5
  39. {qhyper-0.2.1 → qhyper-0.3.0}/qhyper.egg-info/SOURCES.txt +13 -17
  40. {qhyper-0.2.1 → qhyper-0.3.0}/qhyper.egg-info/requires.txt +3 -3
  41. {qhyper-0.2.1 → qhyper-0.3.0}/setup.py +24 -3
  42. qhyper-0.3.0/tests/test_optimizers.py +70 -0
  43. qhyper-0.3.0/tests/test_optimizers_full.py +237 -0
  44. {qhyper-0.2.1 → qhyper-0.3.0}/tests/test_problems.py +8 -5
  45. {qhyper-0.2.1 → qhyper-0.3.0}/tests/test_vqa.py +37 -29
  46. qhyper-0.2.1/QHyper/optimizers/__init__.py +0 -59
  47. qhyper-0.2.1/QHyper/optimizers/base.py +0 -136
  48. qhyper-0.2.1/QHyper/problems/__init__.py +0 -84
  49. qhyper-0.2.1/QHyper/solvers/__init__.py +0 -127
  50. qhyper-0.2.1/QHyper/solvers/quantum_annealing/advantage.py +0 -192
  51. qhyper-0.2.1/QHyper/solvers/vqa/__init__.py +0 -44
  52. qhyper-0.2.1/QHyper/solvers/vqa/base.py +0 -177
  53. qhyper-0.2.1/QHyper/solvers/vqa/eval_funcs/base.py +0 -45
  54. qhyper-0.2.1/QHyper/solvers/vqa/eval_funcs/wfeval.py +0 -39
  55. qhyper-0.2.1/QHyper/solvers/vqa/pqc/__init__.py +0 -48
  56. qhyper-0.2.1/QHyper/solvers/vqa/pqc/base.py +0 -163
  57. qhyper-0.2.1/QHyper/solvers/vqa/pqc/h_qaoa.py +0 -135
  58. qhyper-0.2.1/QHyper/solvers/vqa/pqc/mixers.py +0 -17
  59. qhyper-0.2.1/QHyper/solvers/vqa/pqc/qaoa.py +0 -212
  60. qhyper-0.2.1/QHyper/solvers/vqa/pqc/qml_qaoa.py +0 -60
  61. qhyper-0.2.1/QHyper/solvers/vqa/pqc/wf_qaoa.py +0 -96
  62. qhyper-0.2.1/QHyper/util.py +0 -107
  63. qhyper-0.2.1/README.md +0 -80
  64. qhyper-0.2.1/tests/test_optimizers.py +0 -91
  65. qhyper-0.2.1/tests/test_optimizers_full.py +0 -204
  66. {qhyper-0.2.1 → qhyper-0.3.0}/LICENSE.txt +0 -0
  67. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/optimizers/basinhopping.py +0 -0
  68. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/problems/workflow_scheduling.py +0 -0
  69. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/solvers/classical/__init__.py +0 -0
  70. {qhyper-0.2.1/QHyper/solvers/gurobi → qhyper-0.3.0/QHyper/solvers/gate_based}/__init__.py +0 -0
  71. {qhyper-0.2.1 → qhyper-0.3.0}/QHyper/solvers/quantum_annealing/__init__.py +0 -0
  72. {qhyper-0.2.1 → qhyper-0.3.0}/qhyper.egg-info/dependency_links.txt +0 -0
  73. {qhyper-0.2.1 → qhyper-0.3.0}/qhyper.egg-info/top_level.txt +0 -0
  74. {qhyper-0.2.1 → qhyper-0.3.0}/setup.cfg +0 -0
  75. {qhyper-0.2.1 → qhyper-0.3.0}/tests/test_converter.py +0 -0
  76. {qhyper-0.2.1 → qhyper-0.3.0}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: qhyper
3
- Version: 0.2.1
3
+ Version: 0.3.0
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,22 @@ 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
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: requires-dist
33
+ Dynamic: summary
25
34
 
26
35
  A package that allows to build and solve quantum and classical problems using predefined solvers and problems.
@@ -2,4 +2,6 @@
2
2
  # Smart Growth Operational Programme (2014-2020), Measure 4.2
3
3
  # under the grant agreement no. POIR.04.02.00-00-D014/20-00
4
4
 
5
-
5
+ """
6
+ API reference documentation for QHyper.
7
+ """
@@ -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 to represent a constraint.
58
+ A class representing the constraint.
25
59
 
26
60
  Attributes
27
61
  ----------
28
- lhs : Polynomial
62
+ lhs : PolynomialType
29
63
  The left-hand side of the constraint.
30
- rhs : Polynomial, default Polynomial(0)
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: Polynomial,
48
- rhs: Polynomial = Polynomial(0),
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: Polynomial = lhs
55
- self.rhs: Polynomial = 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 assign_weights_to_constraints(
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, weights: list[float]) -> Polynomial:
102
- of_weight = weights[0] if len(weights) else 1
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
- constraints_weights = weights[1:]
106
- for weight, constraint in Converter.assign_weights_to_constraints(
107
- constraints_weights, problem.constraints
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
- (self.bounds[:, 0] <= point).all()
99
- and (point < self.bounds[:, 1]).all()
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 minimize_(
106
+ def minimize(
106
107
  self,
107
- func: Callable[[NDArray], OptimizationResult],
108
- init: NDArray | None,
108
+ func: Callable[[list[float]], OptimizationResult],
109
+ init: OptimizationParameter,
109
110
  ) -> OptimizationResult:
110
- if init is None:
111
- raise OptimizerError("Initial point must be provided.")
112
-
113
- self.check_bounds(init)
111
+ init.assert_bounds_init()
114
112
 
115
- mean = init.flatten()
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 minimize_(
22
+ def minimize(
23
23
  self,
24
- func: Callable[[NDArray], OptimizationResult],
25
- init: NDArray | None,
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]])