Source code for foxes_opt.constraints.min_dist

from typing import Any

import numpy as np

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


[docs] class MinDistConstraint(FarmConstraint): """ Turbines must keep at least a minimal spatial distance. """ def __init__( self, problem: FarmOptProblem, min_dist: float, min_dist_unit: str = "m", name: str = "dist", sel_turbines: list[int] | None = None, **kwargs: Any, ) -> None: """ Parameters ---------- problem The underlying optimization problem min_dist The minimal distance min_dist_unit The minimal distance unit, either m or D name The name of the constraint sel_turbines The selected turbines kwargs Additional parameters for `iwopy.Constraint` """ self.min_dist: float = min_dist self.min_dist_unit: str = 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: int = 0) -> None: """ Initialize the constaint. Parameters ---------- verbosity The verbosity level, 0 = silent """ N = self.farm.n_turbines i2t: list[list[int]] = [] # i --> (ti, tj) self._t2i: np.ndarray[tuple[int, int], np.dtype[np.int_]] = 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: i2t.append([ti, tj]) self._t2i[ti, tj] = i self._t2i[tj, ti] = i i += 1 self._i2t: np.ndarray[tuple[int, int], np.dtype[np.int_]] = np.asarray( i2t, dtype=int ) self._cnames: list[str] = [f"{self.name}_{ti}_{tj}" for ti, tj in self._i2t] super().initialize(verbosity)
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return len(self._i2t)
[docs] def vardeps_float(self) -> np.ndarray[tuple[int, int], np.dtype[Any]]: """ Gets the dependencies of all components on the function float variables Returns ------- deps The dependencies of components on function variables, shape """ turbs = list(self.problem.sel_turbines) deps: np.ndarray[tuple[int, int, int], np.dtype[Any]] = 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: int = turbs.index(t) deps[i, j] = True return deps.reshape(self.n_components(), 2 * len(turbs))
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: Any, components: list[int] | None = None, ) -> np.ndarray: """ Calculate values for a single individual of the underlying problem. Parameters ---------- vars_int The integer variable values, shape: (n_vars_int,) vars_float The float variable values, shape: (n_vars_float,) problem_results The results of the variable application to the problem components The selected components or None for all Returns ------- values 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] cidx = np.arange(self.n_components(), dtype=int) if components is not None and len(components) < self.n_components(): cidx = np.asarray(components, dtype=int) a: np.ndarray[tuple[int, ...], np.dtype[Any]] = np.take_along_axis( xy, self._i2t[cidx][:, 0, None], axis=0 ) b: np.ndarray[tuple[int, ...], np.dtype[Any]] = np.take_along_axis( xy, self._i2t[cidx][:, 1, None], axis=0 ) d = np.linalg.norm(a - b, axis=-1) if self.min_dist_unit == "m": mind: Any = 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[cidx][:, 0], axis=0) Db = np.take_along_axis(D, self._i2t[cidx][:, 1], axis=0) mind = self.min_dist * np.maximum(Da, Db) return mind - d
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: Any, components: list[int] | None = None, ) -> np.ndarray: """ Calculate values for all individuals of a population. Parameters ---------- vars_int The integer variable values, shape: (n_pop, n_vars_int) vars_float The float variable values, shape: (n_pop, n_vars_float) problem_results The results of the variable application to the problem components The selected components or None for all Returns ------- values 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_states, n_pop, n_turbines, 2) 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] cidx = np.arange(self.n_components(), dtype=int) if components is not None and len(components) < self.n_components(): cidx = np.asarray(components, dtype=int) a: np.ndarray[tuple[int, ...], np.dtype[Any]] = np.take_along_axis( xy, self._i2t[cidx][None, :, 0, None], axis=1 ) b: np.ndarray[tuple[int, ...], np.dtype[Any]] = np.take_along_axis( xy, self._i2t[cidx][None, :, 1, None], axis=1 ) d = np.linalg.norm(a - b, axis=-1) if self.min_dist_unit == "m": mind: Any = self.min_dist elif self.min_dist_unit == "D": D = problem_results[FV.D].to_numpy().reshape(n_states, n_pop, n_turbines) 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[cidx][None, :, 0], axis=1) Db = np.take_along_axis(D, self._i2t[cidx][None, :, 1], axis=1) mind = self.min_dist * np.maximum(Da, Db) return mind - d