Source code for foxes_opt.problems.layout.geom_layouts.constraints

from typing import Any

import numpy as np
from scipy.spatial.distance import cdist
from iwopy import Constraint, Problem

from foxes.config import config


[docs] class Valid(Constraint): """ Validity constraint for purely geometrical layouts problems. """ def __init__(self, problem: Problem, name: str = "valid", **kwargs: Any) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem name The constraint name kwargs Additioal parameters for the base class """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), **kwargs, )
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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,) """ __, valid = problem_results return np.atleast_1d(np.sum(~valid))
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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) """ __, valid = problem_results return np.sum(~valid, axis=1)[:, None]
[docs] class Boundary(Constraint): """ Boundary constraint for purely geometrical layouts problems. """ def __init__( self, problem: Problem, n_turbines: int | None = None, D: float | None = None, name: str = "boundary", **kwargs: Any, ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem n_turbines The number of turbines D The rotor diameter name The constraint name kwargs Additioal parameters for the base class """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), **kwargs, ) self.n_turbines = problem.n_turbines if n_turbines is None else n_turbines self.D = problem.D if D is None else D
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return self.n_turbines
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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, __ = problem_results dists = self.problem.boundary.points_distance(xy) dists[self.problem.boundary.points_inside(xy)] *= -1 if self.D is not None: dists += self.D / 2 return dists
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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) """ xy, __ = problem_results n_pop, n_xy = xy.shape[:2] xy = xy.reshape(n_pop * n_xy, 2) dists = self.problem.boundary.points_distance(xy) dists[self.problem.boundary.points_inside(xy)] *= -1 dists = dists.reshape(n_pop, n_xy) if self.D is not None: dists += self.D / 2 return dists
[docs] class MinDist(Constraint): """ Minimal distance constraint for purely geometrical layouts problems. """ def __init__( self, problem: Problem, min_dist: float | None = None, n_turbines: int | None = None, name: str = "min_dist", **kwargs: Any, ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem min_dist The minimal distance between turbines n_turbines The number of turbines name The constraint name kwargs Additioal parameters for the base class """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), **kwargs, ) self.min_dist = problem.min_dist if min_dist is None else min_dist self.n_turbines = problem.n_turbines if n_turbines is None else n_turbines
[docs] def initialize(self, verbosity: int = 0) -> None: """ Initialize the constaint. Parameters ---------- verbosity The verbosity level, 0 = silent """ N = self.n_turbines i2t: list[list[int]] = [] # i --> (ti, tj) self._t2i: np.ndarray[tuple[int, ...], np.dtype[Any]] = np.full( [N, N], -1 ) # (ti, tj) --> i i = 0 for ti in range(N): 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 calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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, __ = problem_results a = np.take_along_axis(xy, self._i2t[:, 0, None], axis=0) b = np.take_along_axis(xy, self._i2t[:, 1, None], axis=0) d = np.linalg.norm(a - b, axis=-1) return self.min_dist - d
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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) """ xy, __ = problem_results a = np.take_along_axis(xy, self._i2t[None, :, 0, None], axis=1) b = np.take_along_axis(xy, self._i2t[None, :, 1, None], axis=1) d = np.linalg.norm(a - b, axis=-1) return self.min_dist - d
[docs] class CMinN(Constraint): """ Minimal number of turbines constraint for purely geometrical layouts problems. """ def __init__( self, problem: Problem, N: int, name: str = "cminN", **kwargs: Any ) -> None: super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), **kwargs, ) """ Constructor. Parameters ---------- problem The underlying geometrical layout optimization problem N The minimal number of turbines name The constraint name kwargs Additioal parameters for the base class """ self.N = N
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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,) """ __, valid = problem_results return np.atleast_1d(self.N - np.sum(valid))
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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) """ __, valid = problem_results return self.N - np.sum(valid, axis=1)[:, None]
[docs] class CMaxN(Constraint): """ Maximal number of turbines constraint for purely geometrical layouts problems. """ def __init__( self, problem: Problem, N: int, name: str = "cmaxN", **kwargs: Any ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem N The maximal number of turbines name The constraint name kwargs Additioal parameters for the base class """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), **kwargs, ) self.N = N
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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,) """ __, valid = problem_results return np.atleast_1d(np.sum(valid) - self.N)
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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) """ __, valid = problem_results return np.sum(valid, axis=1)[:, None] - self.N
[docs] class CFixN(Constraint): """ Fixed number of turbines constraint for purely geometrical layouts problems. """ def __init__( self, problem: Problem, N: int, name: str = "cfixN", **kwargs: Any ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem N The number of turbines name The constraint name kwargs Additioal parameters for the base class """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), tol=0.1, **kwargs, ) self.N = N
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 2
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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,) """ __, valid = problem_results vld = np.sum(valid) return np.array([self.N - vld, vld - self.N])
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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) """ __, valid = problem_results vld = np.sum(valid, axis=1) return np.stack([self.N - vld, vld - self.N], axis=-1)
[docs] class CMinDensity(Constraint): """ Minimal turbine density constraint for purely geometrical layouts problems. """ def __init__( self, problem: Problem, min_value: float, dfactor: int = 1, name: str = "min_density", ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem min_value The minimal turbine density dfactor Delta factor for grid spacing name The constraint name kwargs Additioal parameters for the base class """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), ) self.min_value = min_value self.dfactor = dfactor
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def initialize(self, verbosity: int) -> None: """ Initialize the object. Parameters ---------- verbosity The verbosity level, 0 = silent """ super().initialize(verbosity) # define regular grid of probe points: geom = self.problem.boundary pmin = geom.p_min() pmax = geom.p_max() detlta = self.problem.min_dist / self.dfactor self._probes = np.stack( np.meshgrid( np.arange(pmin[0] - detlta, pmax[0] + 2 * detlta, detlta), np.arange(pmin[1] - detlta, pmax[1] + 2 * detlta, detlta), indexing="ij", ), axis=-1, ) nx, ny = self._probes.shape[:2] n: int = nx * ny self._probes = self._probes.reshape(n, 2) # reduce to points within geometry: valid = geom.points_inside(self._probes) self._probes = self._probes[valid]
[docs] def calc_individual( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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, valid = problem_results xy = xy[valid] dists: np.ndarray[tuple[int, ...], np.dtype[np.floating]] = cdist( self._probes, xy ) return np.atleast_1d(np.nanmax(np.nanmin(dists, axis=1)) - self.min_value)
[docs] def calc_population( self, vars_int: np.ndarray, vars_float: np.ndarray, problem_results: tuple[np.ndarray, np.ndarray], cmpnts: 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 = vars_float.shape[0] xy, valid = problem_results out = np.full(n_pop, 1e20, dtype=config.dtype_double) for pi in range(n_pop): if np.any(valid[pi]): hxy = xy[pi][valid[pi]] dists: np.ndarray[tuple[int, ...], np.dtype[np.floating]] = cdist( self._probes, hxy ) out[pi] = np.nanmax(np.nanmin(dists, axis=1)) - self.min_value return out[:, None]