foxes-opt 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. foxes_opt/VERSION +1 -0
  2. foxes_opt/__init__.py +9 -0
  3. foxes_opt/constraints/__init__.py +6 -0
  4. foxes_opt/constraints/area_geometry.py +214 -0
  5. foxes_opt/constraints/min_dist.py +239 -0
  6. foxes_opt/core/__init__.py +9 -0
  7. foxes_opt/core/farm_constraint.py +96 -0
  8. foxes_opt/core/farm_objective.py +97 -0
  9. foxes_opt/core/farm_opt_problem.py +346 -0
  10. foxes_opt/core/farm_vars_problem.py +219 -0
  11. foxes_opt/core/pop_states.py +206 -0
  12. foxes_opt/objectives/__init__.py +6 -0
  13. foxes_opt/objectives/farm_vars.py +323 -0
  14. foxes_opt/objectives/max_n_turbines.py +142 -0
  15. foxes_opt/problems/__init__.py +7 -0
  16. foxes_opt/problems/layout/__init__.py +9 -0
  17. foxes_opt/problems/layout/farm_layout.py +137 -0
  18. foxes_opt/problems/layout/geom_layouts/__init__.py +10 -0
  19. foxes_opt/problems/layout/geom_layouts/constraints.py +802 -0
  20. foxes_opt/problems/layout/geom_layouts/geom_layout.py +290 -0
  21. foxes_opt/problems/layout/geom_layouts/geom_layout_gridded.py +276 -0
  22. foxes_opt/problems/layout/geom_layouts/geom_reggrid.py +351 -0
  23. foxes_opt/problems/layout/geom_layouts/geom_reggrids.py +482 -0
  24. foxes_opt/problems/layout/geom_layouts/objectives.py +666 -0
  25. foxes_opt/problems/layout/reggrids_layout.py +417 -0
  26. foxes_opt/problems/layout/regular_layout.py +350 -0
  27. foxes_opt/problems/opt_farm_vars.py +586 -0
  28. foxes_opt-0.1.dist-info/LICENSE +21 -0
  29. foxes_opt-0.1.dist-info/METADATA +66 -0
  30. foxes_opt-0.1.dist-info/RECORD +33 -0
  31. foxes_opt-0.1.dist-info/WHEEL +5 -0
  32. foxes_opt-0.1.dist-info/top_level.txt +1 -0
  33. foxes_opt-0.1.dist-info/zip-safe +1 -0
foxes_opt/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1
foxes_opt/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ """
2
+ Wind farm optimization.
3
+ """
4
+
5
+ from .core import FarmOptProblem, FarmObjective, FarmConstraint
6
+
7
+ from . import problems
8
+ from . import constraints
9
+ from . import objectives
@@ -0,0 +1,6 @@
1
+ """
2
+ Wind farm optimization constraints.
3
+ """
4
+
5
+ from .area_geometry import AreaGeometryConstraint, FarmBoundaryConstraint
6
+ from .min_dist import MinDistConstraint
@@ -0,0 +1,214 @@
1
+ import numpy as np
2
+
3
+ from foxes_opt.core.farm_constraint import FarmConstraint
4
+ import foxes.variables as FV
5
+
6
+
7
+ class AreaGeometryConstraint(FarmConstraint):
8
+ """
9
+ Constrains turbine positions to the inside
10
+ of a given area geometry.
11
+
12
+ Attributes
13
+ ----------
14
+ farm: foxes.WindFarm
15
+ The wind farm
16
+ sel_turbines: list
17
+ The selected turbines
18
+ geometry: foxes.utils.geom2d.AreaGeometry
19
+ The area geometry
20
+ disc_inside: bool
21
+ Ensure full rotor disc inside boundary
22
+ D: float
23
+ Use this radius for rotor disc inside condition
24
+
25
+ :group: opt.constraints
26
+
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ problem,
32
+ name,
33
+ geometry,
34
+ sel_turbines=None,
35
+ disc_inside=False,
36
+ D=None,
37
+ **kwargs,
38
+ ):
39
+ """
40
+ Constructor.
41
+
42
+ Parameters
43
+ ----------
44
+ problem : foxes_opt.FarmOptProblem
45
+ The underlying optimization problem
46
+ name : str
47
+ The name of the constraint
48
+ geometry : foxes.utils.geom2d.AreaGeometry
49
+ The area geometry
50
+ sel_turbines : list of int, optional
51
+ The selected turbines
52
+ disc_inside : bool
53
+ Ensure full rotor disc inside boundary
54
+ D : float, optional
55
+ Use this radius for rotor disc inside condition
56
+ kwargs : dict, optional
57
+ Additional parameters for `iwopy.Constraint`
58
+
59
+ """
60
+ self.geometry = geometry
61
+ self.disc_inside = disc_inside
62
+ self.D = D
63
+
64
+ selt = problem.sel_turbines if sel_turbines is None else sel_turbines
65
+ vrs = []
66
+ cns = []
67
+ for ti in selt:
68
+ vrs += [problem.tvar(FV.X, ti), problem.tvar(FV.Y, ti)]
69
+ cns.append(f"{name}_{ti:04d}")
70
+
71
+ super().__init__(
72
+ problem, name, sel_turbines, vnames_float=vrs, cnames=cns, **kwargs
73
+ )
74
+
75
+ def n_components(self):
76
+ """
77
+ Returns the number of components of the
78
+ function.
79
+
80
+ Returns
81
+ -------
82
+ int:
83
+ The number of components.
84
+
85
+ """
86
+ return self.n_sel_turbines
87
+
88
+ def vardeps_float(self):
89
+ """
90
+ Gets the dependencies of all components
91
+ on the function float variables
92
+
93
+ Returns
94
+ -------
95
+ deps: numpy.ndarray of bool
96
+ The dependencies of components on function
97
+ variables, shape: (n_components, n_vars_float)
98
+
99
+ """
100
+ deps = np.zeros((self.n_components(), self.n_components(), 2), dtype=bool)
101
+ np.fill_diagonal(deps[:, :, 0], True)
102
+ np.fill_diagonal(deps[:, :, 1], True)
103
+ return deps.reshape(self.n_components(), self.n_components() * 2)
104
+
105
+ def calc_individual(self, vars_int, vars_float, problem_results, components=None):
106
+ """
107
+ Calculate values for a single individual of the
108
+ underlying problem.
109
+
110
+ Parameters
111
+ ----------
112
+ vars_int: np.array
113
+ The integer variable values, shape: (n_vars_int,)
114
+ vars_float: np.array
115
+ The float variable values, shape: (n_vars_float,)
116
+ problem_results: Any
117
+ The results of the variable application
118
+ to the problem
119
+ components: list of int, optional
120
+ The selected components or None for all
121
+
122
+ Returns
123
+ -------
124
+ values: np.array
125
+ The component values, shape: (n_sel_components,)
126
+
127
+ """
128
+ s = np.s_[:]
129
+ if components is not None and len(components) < self.n_components():
130
+ s = components
131
+ xy = vars_float.reshape(self.n_components(), 2)[s]
132
+
133
+ dists = self.geometry.points_distance(xy)
134
+ dists[self.geometry.points_inside(xy)] *= -1
135
+
136
+ if self.disc_inside:
137
+ if self.D is None:
138
+ dists += problem_results[FV.D].to_numpy()[0, self.sel_turbines][s] / 2
139
+ else:
140
+ dists += self.D / 2
141
+
142
+ return dists
143
+
144
+ def calc_population(self, vars_int, vars_float, problem_results, components=None):
145
+ """
146
+ Calculate values for all individuals of a population.
147
+
148
+ Parameters
149
+ ----------
150
+ vars_int: np.array
151
+ The integer variable values, shape: (n_pop, n_vars_int)
152
+ vars_float: np.array
153
+ The float variable values, shape: (n_pop, n_vars_float)
154
+ problem_results: Any
155
+ The results of the variable application
156
+ to the problem
157
+ components: list of int, optional
158
+ The selected components or None for all
159
+
160
+ Returns
161
+ -------
162
+ values: np.array
163
+ The component values, shape: (n_pop, n_sel_components)
164
+
165
+ """
166
+ n_pop = len(vars_float)
167
+ n_cmpnts = self.n_components()
168
+ s = np.s_[:]
169
+ if components is not None and len(components) < self.n_components():
170
+ n_cmpnts = len(components)
171
+ s = components
172
+ xy = vars_float[:, s].reshape(n_pop * n_cmpnts, 2)
173
+
174
+ dists = self.geometry.points_distance(xy)
175
+ dists[self.geometry.points_inside(xy)] *= -1
176
+ dists = dists.reshape(n_pop, n_cmpnts)
177
+
178
+ if self.disc_inside:
179
+ if self.D is None:
180
+ dists += (
181
+ problem_results[FV.D].to_numpy()[None, 0, self.sel_turbines][s] / 2
182
+ )
183
+ else:
184
+ dists += self.D / 2
185
+
186
+ return dists
187
+
188
+
189
+ class FarmBoundaryConstraint(AreaGeometryConstraint):
190
+ """
191
+ Constrains turbine positions to the inside of
192
+ the wind farm boundary
193
+
194
+ :group: opt.constraints
195
+
196
+ """
197
+
198
+ def __init__(self, problem, name="boundary", **kwargs):
199
+ """
200
+ Constructor.
201
+
202
+ Parameters
203
+ ----------
204
+ problem: foxes_opt.FarmOptProblem
205
+ The underlying optimization problem
206
+ name: str
207
+ The name of the constraint
208
+ kwargs: dict, optional
209
+ Additional parameters for `AreaGeometryConstraint`
210
+
211
+ """
212
+ b = problem.farm.boundary
213
+ assert b is not None, f"Constraint '{name}': Missing wind farm boundary."
214
+ super().__init__(problem, name, geometry=b, **kwargs)
@@ -0,0 +1,239 @@
1
+ import numpy as np
2
+
3
+ from foxes_opt.core.farm_constraint import FarmConstraint
4
+ import foxes.variables as FV
5
+ import foxes.constants as FC
6
+
7
+
8
+ class MinDistConstraint(FarmConstraint):
9
+ """
10
+ Turbines must keep at least a minimal
11
+ spatial distance.
12
+
13
+ Attributes
14
+ ----------
15
+ farm: foxes.WindFarm
16
+ The wind farm
17
+ sel_turbines: list
18
+ The selected turbines
19
+ min_dist: float
20
+ The minimal distance
21
+ min_dist_unit: str
22
+ The minimal distance unit, either m or D
23
+
24
+ :group: opt.constraints
25
+
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ problem,
31
+ min_dist,
32
+ min_dist_unit="m",
33
+ name="dist",
34
+ sel_turbines=None,
35
+ **kwargs,
36
+ ):
37
+ """
38
+ Constructor.
39
+
40
+ Parameters
41
+ ----------
42
+ problem: foxes_opt.FarmOptProblem
43
+ The underlying optimization problem
44
+ min_dist: float
45
+ The minimal distance
46
+ min_dist_unit: str
47
+ The minimal distance unit, either m or D
48
+ name: str
49
+ The name of the constraint
50
+ sel_turbines: list of int, optional
51
+ The selected turbines
52
+ kwargs: dict, optional
53
+ Additional parameters for `iwopy.Constraint`
54
+
55
+ """
56
+ self.min_dist = min_dist
57
+ self.min_dist_unit = min_dist_unit
58
+
59
+ selt = problem.sel_turbines if sel_turbines is None else sel_turbines
60
+ vrs = []
61
+ for ti in selt:
62
+ vrs += [problem.tvar(FV.X, ti), problem.tvar(FV.Y, ti)]
63
+
64
+ super().__init__(problem, name, sel_turbines, vnames_float=vrs, **kwargs)
65
+
66
+ def initialize(self, verbosity=0):
67
+ """
68
+ Initialize the constaint.
69
+
70
+ Parameters
71
+ ----------
72
+ verbosity: int
73
+ The verbosity level, 0 = silent
74
+
75
+ """
76
+ N = self.farm.n_turbines
77
+ self._i2t = [] # i --> (ti, tj)
78
+ self._t2i = np.full([N, N], -1) # (ti, tj) --> i
79
+ i = 0
80
+ for ti in self.sel_turbines:
81
+ for tj in range(N):
82
+ if ti != tj and self._t2i[ti, tj] < 0:
83
+ self._i2t.append([ti, tj])
84
+ self._t2i[ti, tj] = i
85
+ self._t2i[tj, ti] = i
86
+ i += 1
87
+ self._i2t = np.array(self._i2t)
88
+ self._cnames = [f"{self.name}_{ti}_{tj}" for ti, tj in self._i2t]
89
+ super().initialize(verbosity)
90
+
91
+ def n_components(self):
92
+ """
93
+ Returns the number of components of the
94
+ function.
95
+
96
+ Returns
97
+ -------
98
+ int:
99
+ The number of components.
100
+
101
+ """
102
+ return len(self._i2t)
103
+
104
+ def vardeps_float(self):
105
+ """
106
+ Gets the dependencies of all components
107
+ on the function float variables
108
+
109
+ Returns
110
+ -------
111
+ deps: numpy.ndarray of bool
112
+ The dependencies of components on function
113
+ variables, shape: (n_components, n_vars_float)
114
+
115
+ """
116
+ turbs = list(self.problem.sel_turbines)
117
+ deps = np.zeros((self.n_components(), len(turbs), 2), dtype=bool)
118
+ for i, titj in enumerate(self._i2t):
119
+ for t in titj:
120
+ if t in turbs:
121
+ j = turbs.index(t)
122
+ deps[i, j] = True
123
+ return deps.reshape(self.n_components(), 2 * len(turbs))
124
+
125
+ def calc_individual(self, vars_int, vars_float, problem_results, components=None):
126
+ """
127
+ Calculate values for a single individual of the
128
+ underlying problem.
129
+
130
+ Parameters
131
+ ----------
132
+ vars_int: np.array
133
+ The integer variable values, shape: (n_vars_int,)
134
+ vars_float: np.array
135
+ The float variable values, shape: (n_vars_float,)
136
+ problem_results: Any
137
+ The results of the variable application
138
+ to the problem
139
+ components: list of int, optional
140
+ The selected components or None for all
141
+
142
+ Returns
143
+ -------
144
+ values: np.array
145
+ The component values, shape: (n_sel_components,)
146
+
147
+ """
148
+ xy = np.stack(
149
+ [problem_results[FV.X].to_numpy(), problem_results[FV.Y].to_numpy()],
150
+ axis=-1,
151
+ )
152
+ if not np.all(np.abs(np.min(xy, axis=0) - np.max(xy, axis=0)) < 1e-13):
153
+ raise ValueError(f"Constraint '{self.name}': Require state independet XY")
154
+ xy = xy[0]
155
+
156
+ s = np.s_[:]
157
+ if components is not None and len(components) < self.n_components():
158
+ s = components
159
+
160
+ a = np.take_along_axis(xy, self._i2t[s][:, 0, None], axis=0)
161
+ b = np.take_along_axis(xy, self._i2t[s][:, 1, None], axis=0)
162
+ d = np.linalg.norm(a - b, axis=-1)
163
+
164
+ if self.min_dist_unit == "m":
165
+ mind = self.min_dist
166
+
167
+ elif self.min_dist_unit == "D":
168
+ D = problem_results[FV.D].to_numpy()
169
+ if not np.all(np.abs(np.min(D, axis=0) - np.max(D, axis=0)) < 1e-13):
170
+ raise ValueError(
171
+ f"Constraint '{self.name}': Require state independet D"
172
+ )
173
+ D = D[0]
174
+
175
+ Da = np.take_along_axis(D, self._i2t[s][:, 0], axis=0)
176
+ Db = np.take_along_axis(D, self._i2t[s][:, 1], axis=0)
177
+ mind = self.min_dist * np.maximum(Da, Db)
178
+
179
+ return mind - d
180
+
181
+ def calc_population(self, vars_int, vars_float, problem_results, components=None):
182
+ """
183
+ Calculate values for all individuals of a population.
184
+
185
+ Parameters
186
+ ----------
187
+ vars_int: np.array
188
+ The integer variable values, shape: (n_pop, n_vars_int)
189
+ vars_float: np.array
190
+ The float variable values, shape: (n_pop, n_vars_float)
191
+ problem_results: Any
192
+ The results of the variable application
193
+ to the problem
194
+ components: list of int, optional
195
+ The selected components or None for all
196
+
197
+ Returns
198
+ -------
199
+ values: np.array
200
+ The component values, shape: (n_pop, n_sel_components)
201
+
202
+ """
203
+ n_pop = problem_results["n_pop"].values
204
+ n_states = problem_results["n_org_states"].values
205
+ n_turbines = problem_results.sizes[FC.TURBINE]
206
+
207
+ xy = np.stack(
208
+ [problem_results[FV.X].to_numpy(), problem_results[FV.Y].to_numpy()],
209
+ axis=-1,
210
+ )
211
+ xy = xy.reshape(n_pop, n_states, n_turbines, 2)
212
+ if not np.all(np.abs(np.min(xy, axis=1) - np.max(xy, axis=1)) < 1e-13):
213
+ raise ValueError(f"Constraint '{self.name}': Require state independet XY")
214
+ xy = xy[:, 0]
215
+
216
+ s = np.s_[:]
217
+ if components is not None and len(components) < self.n_components():
218
+ s = components
219
+
220
+ a = np.take_along_axis(xy, self._i2t[s][None, :, 0, None], axis=1)
221
+ b = np.take_along_axis(xy, self._i2t[s][None, :, 1, None], axis=1)
222
+ d = np.linalg.norm(a - b, axis=-1)
223
+
224
+ if self.min_dist_unit == "m":
225
+ mind = self.min_dist
226
+
227
+ elif self.min_dist_unit == "D":
228
+ D = problem_results[FV.D].to_numpy().reshape(n_pop, n_states, n_turbines)
229
+ if not np.all(np.abs(np.min(D, axis=1) - np.max(D, axis=1)) < 1e-13):
230
+ raise ValueError(
231
+ f"Constraint '{self.name}': Require state independet D"
232
+ )
233
+ D = D[:, 0]
234
+
235
+ Da = np.take_along_axis(D, self._i2t[s][None, :, 0], axis=1)
236
+ Db = np.take_along_axis(D, self._i2t[s][None, :, 1], axis=1)
237
+ mind = self.min_dist * np.maximum(Da, Db)
238
+
239
+ return mind - d
@@ -0,0 +1,9 @@
1
+ """
2
+ Abstract classes and core functionality for wind farm optimization.
3
+ """
4
+
5
+ from .farm_opt_problem import FarmOptProblem
6
+ from .farm_vars_problem import FarmVarsProblem
7
+ from .farm_objective import FarmObjective
8
+ from .farm_constraint import FarmConstraint
9
+ from .pop_states import PopStates
@@ -0,0 +1,96 @@
1
+ from iwopy import Constraint
2
+
3
+ from foxes.utils import all_subclasses
4
+
5
+
6
+ class FarmConstraint(Constraint):
7
+ """
8
+ Abstract base class for foxes wind farm
9
+ optimization constraints.
10
+
11
+ :group: opt.core
12
+
13
+ """
14
+
15
+ def __init__(self, problem, name, sel_turbines=None, **kwargs):
16
+ """
17
+ Constructor.
18
+
19
+ Parameters
20
+ ----------
21
+ problem: foxes_opt.FarmOptProblem
22
+ The underlying optimization problem
23
+ name: str
24
+ The name of the constraint
25
+ sel_turbines: list of int, optional
26
+ The selected turbines
27
+ kwargs: dict, optional
28
+ Additional parameters for `iwopy.Constraint`
29
+
30
+ """
31
+ super().__init__(problem, name, **kwargs)
32
+ self._sel_turbines = sel_turbines
33
+
34
+ @property
35
+ def farm(self):
36
+ """
37
+ The wind farm
38
+
39
+ Returns
40
+ -------
41
+ foxes.core.WindFarm :
42
+ The wind farm
43
+
44
+ """
45
+ return self.problem.farm
46
+
47
+ @property
48
+ def sel_turbines(self):
49
+ """
50
+ The list of selected turbines
51
+
52
+ Returns
53
+ -------
54
+ list of int :
55
+ The list of selected turbines
56
+
57
+ """
58
+ return (
59
+ self.problem.sel_turbines
60
+ if self._sel_turbines is None
61
+ else self._sel_turbines
62
+ )
63
+
64
+ @property
65
+ def n_sel_turbines(self):
66
+ """
67
+ The numer of selected turbines
68
+
69
+ Returns
70
+ -------
71
+ int :
72
+ The numer of selected turbines
73
+
74
+ """
75
+ return len(self.sel_turbines)
76
+
77
+ def add_to_layout_figure(self, ax, **kwargs):
78
+ """
79
+ Add to a layout figure
80
+
81
+ Parameters
82
+ ----------
83
+ ax: matplotlib.pyplot.Axis
84
+ The figure axis
85
+
86
+ """
87
+ return ax
88
+
89
+ @classmethod
90
+ def print_models(cls):
91
+ """
92
+ Prints all model names.
93
+ """
94
+ names = sorted([scls.__name__ for scls in all_subclasses(cls)])
95
+ for n in names:
96
+ print(n)
@@ -0,0 +1,97 @@
1
+ from abc import abstractmethod
2
+ from iwopy import Objective
3
+
4
+ from foxes.utils import all_subclasses
5
+
6
+
7
+ class FarmObjective(Objective):
8
+ """
9
+ Abstract base class for foxes wind farm
10
+ objective functions.
11
+
12
+ :group: opt.core
13
+
14
+ """
15
+
16
+ def __init__(self, problem, name, sel_turbines=None, **kwargs):
17
+ """
18
+ Constraints.
19
+
20
+ Parameters
21
+ ----------
22
+ problem: foxes_opt.FarmOptProblem
23
+ The underlying optimization problem
24
+ name: str
25
+ The name of the objective function
26
+ sel_turbines: list of int, optional
27
+ The selected turbines
28
+ kwargs: dict, optional
29
+ Additional parameters for `iwopy.Objective`
30
+
31
+ """
32
+ super().__init__(problem, name, **kwargs)
33
+ self._sel_turbines = sel_turbines
34
+
35
+ @property
36
+ def farm(self):
37
+ """
38
+ The wind farm
39
+
40
+ Returns
41
+ -------
42
+ foxes.core.WindFarm :
43
+ The wind farm
44
+
45
+ """
46
+ return self.problem.farm
47
+
48
+ @property
49
+ def sel_turbines(self):
50
+ """
51
+ The list of selected turbines
52
+
53
+ Returns
54
+ -------
55
+ list of int :
56
+ The list of selected turbines
57
+
58
+ """
59
+ return (
60
+ self.problem.sel_turbines
61
+ if self._sel_turbines is None
62
+ else self._sel_turbines
63
+ )
64
+
65
+ @property
66
+ def n_sel_turbines(self):
67
+ """
68
+ The numer of selected turbines
69
+
70
+ Returns
71
+ -------
72
+ int :
73
+ The numer of selected turbines
74
+
75
+ """
76
+ return len(self.sel_turbines)
77
+
78
+ def add_to_layout_figure(self, ax, **kwargs):
79
+ """
80
+ Add to a layout figure
81
+
82
+ Parameters
83
+ ----------
84
+ ax: matplotlib.pyplot.Axis
85
+ The figure axis
86
+
87
+ """
88
+ return ax
89
+
90
+ @classmethod
91
+ def print_models(cls):
92
+ """
93
+ Prints all model names.
94
+ """
95
+ names = sorted([scls.__name__ for scls in all_subclasses(cls)])
96
+ for n in names:
97
+ print(n)