Source code for foxes_opt.constraints.min_dist

import numpy as np

from foxes_opt.core.farm_constraint import FarmConstraint
import foxes.variables as FV
import foxes.constants as FC


[docs] class MinDistConstraint(FarmConstraint): """ Turbines must keep at least a minimal spatial distance. Attributes ---------- farm: foxes.WindFarm The wind farm sel_turbines: list The selected turbines min_dist: float The minimal distance min_dist_unit: str The minimal distance unit, either m or D :group: opt.constraints """
[docs] def __init__( self, problem, min_dist, min_dist_unit="m", name="dist", sel_turbines=None, **kwargs, ): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying optimization problem min_dist: float The minimal distance min_dist_unit: str The minimal distance unit, either m or D name: str The name of the constraint sel_turbines: list of int, optional The selected turbines kwargs: dict, optional Additional parameters for `iwopy.Constraint` """ self.min_dist = min_dist self.min_dist_unit = min_dist_unit selt = problem.sel_turbines if sel_turbines is None else sel_turbines vrs = [] for ti in selt: vrs += [problem.tvar(FV.X, ti), problem.tvar(FV.Y, ti)] super().__init__(problem, name, sel_turbines, vnames_float=vrs, **kwargs)
[docs] def initialize(self, verbosity=0): """ Initialize the constaint. Parameters ---------- verbosity: int The verbosity level, 0 = silent """ N = self.farm.n_turbines self._i2t = [] # i --> (ti, tj) self._t2i = np.full([N, N], -1) # (ti, tj) --> i i = 0 for ti in self.sel_turbines: for tj in range(N): if ti != tj and self._t2i[ti, tj] < 0: self._i2t.append([ti, tj]) self._t2i[ti, tj] = i self._t2i[tj, ti] = i i += 1 self._i2t = np.array(self._i2t) self._cnames = [f"{self.name}_{ti}_{tj}" for ti, tj in self._i2t] super().initialize(verbosity)
[docs] def n_components(self): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return len(self._i2t)
[docs] def vardeps_float(self): """ Gets the dependencies of all components on the function float variables Returns ------- deps: numpy.ndarray of bool The dependencies of components on function variables, shape: (n_components, n_vars_float) """ turbs = list(self.problem.sel_turbines) deps = np.zeros((self.n_components(), len(turbs), 2), dtype=bool) for i, titj in enumerate(self._i2t): for t in titj: if t in turbs: j = turbs.index(t) deps[i, j] = True return deps.reshape(self.n_components(), 2 * len(turbs))
[docs] def calc_individual(self, vars_int, vars_float, problem_results, components=None): """ Calculate values for a single individual of the underlying problem. Parameters ---------- vars_int: np.array The integer variable values, shape: (n_vars_int,) vars_float: np.array The float variable values, shape: (n_vars_float,) problem_results: Any The results of the variable application to the problem components: list of int, optional The selected components or None for all Returns ------- values: np.array The component values, shape: (n_sel_components,) """ xy = np.stack( [problem_results[FV.X].to_numpy(), problem_results[FV.Y].to_numpy()], axis=-1, ) if not np.all(np.abs(np.min(xy, axis=0) - np.max(xy, axis=0)) < 1e-13): raise ValueError(f"Constraint '{self.name}': Require state independet XY") xy = xy[0] s = np.s_[:] if components is not None and len(components) < self.n_components(): s = components a = np.take_along_axis(xy, self._i2t[s][:, 0, None], axis=0) b = np.take_along_axis(xy, self._i2t[s][:, 1, None], axis=0) d = np.linalg.norm(a - b, axis=-1) if self.min_dist_unit == "m": mind = self.min_dist elif self.min_dist_unit == "D": D = problem_results[FV.D].to_numpy() if not np.all(np.abs(np.min(D, axis=0) - np.max(D, axis=0)) < 1e-13): raise ValueError( f"Constraint '{self.name}': Require state independet D" ) D = D[0] Da = np.take_along_axis(D, self._i2t[s][:, 0], axis=0) Db = np.take_along_axis(D, self._i2t[s][:, 1], axis=0) mind = self.min_dist * np.maximum(Da, Db) return mind - d
[docs] def calc_population(self, vars_int, vars_float, problem_results, components=None): """ Calculate values for all individuals of a population. Parameters ---------- vars_int: np.array The integer variable values, shape: (n_pop, n_vars_int) vars_float: np.array The float variable values, shape: (n_pop, n_vars_float) problem_results: Any The results of the variable application to the problem components: list of int, optional The selected components or None for all Returns ------- values: np.array The component values, shape: (n_pop, n_sel_components) """ n_pop = problem_results["n_pop"].values n_states = problem_results["n_org_states"].values n_turbines = problem_results.sizes[FC.TURBINE] xy = np.stack( [problem_results[FV.X].to_numpy(), problem_results[FV.Y].to_numpy()], axis=-1, ) xy = xy.reshape(n_pop, n_states, n_turbines, 2) if not np.all(np.abs(np.min(xy, axis=1) - np.max(xy, axis=1)) < 1e-13): raise ValueError(f"Constraint '{self.name}': Require state independet XY") xy = xy[:, 0] s = np.s_[:] if components is not None and len(components) < self.n_components(): s = components a = np.take_along_axis(xy, self._i2t[s][None, :, 0, None], axis=1) b = np.take_along_axis(xy, self._i2t[s][None, :, 1, None], axis=1) d = np.linalg.norm(a - b, axis=-1) if self.min_dist_unit == "m": mind = self.min_dist elif self.min_dist_unit == "D": D = problem_results[FV.D].to_numpy().reshape(n_pop, n_states, n_turbines) if not np.all(np.abs(np.min(D, axis=1) - np.max(D, axis=1)) < 1e-13): raise ValueError( f"Constraint '{self.name}': Require state independet D" ) D = D[:, 0] Da = np.take_along_axis(D, self._i2t[s][None, :, 0], axis=1) Db = np.take_along_axis(D, self._i2t[s][None, :, 1], axis=1) mind = self.min_dist * np.maximum(Da, Db) return mind - d