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

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

from foxes.config import config


[docs] class Valid(Constraint): """ Validity constraint for purely geometrical layouts problems. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__(self, problem, name="valid", **kwargs): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem name: str The constraint name kwargs: dict, optional 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): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return 1
[docs] def calc_individual(self, vars_int, vars_float, problem_results, cmpnts=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,) """ __, valid = problem_results return np.sum(~valid)
[docs] def calc_population(self, vars_int, vars_float, problem_results, cmpnts=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) """ __, valid = problem_results return np.sum(~valid, axis=1)[:, None]
[docs] class Boundary(Constraint): """ Boundary constraint for purely geometrical layouts problems. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__(self, problem, n_turbines=None, D=None, name="boundary", **kwargs): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem n_turbines: int, optional The number of turbines D: float, optional The rotor diameter name: str The constraint name kwargs: dict, optional 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): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return self.n_turbines
[docs] def calc_individual(self, vars_int, vars_float, problem_results, cmpnts=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, __ = 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, vars_float, problem_results, cmpnts=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) """ 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. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__( self, problem, min_dist=None, n_turbines=None, name="min_dist", **kwargs ): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem min_dist: float, optional The minimal distance between turbines n_turbines: int, optional The number of turbines name: str The constraint name kwargs: dict, optional 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=0): """ Initialize the constaint. Parameters ---------- verbosity: int The verbosity level, 0 = silent """ N = self.n_turbines self._i2t = [] # i --> (ti, tj) self._t2i = 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: 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 calc_individual(self, vars_int, vars_float, problem_results, cmpnts=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, __ = 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, vars_float, problem_results, cmpnts=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) """ 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. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__(self, problem, N, name="cminN", **kwargs): super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), **kwargs, ) """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem N: int The minimal number of turbines name: str The constraint name kwargs: dict, optional Additioal parameters for the base class """ self.N = N
[docs] def n_components(self): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return 1
[docs] def calc_individual(self, vars_int, vars_float, problem_results, cmpnts=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,) """ __, valid = problem_results return self.N - np.sum(valid)
[docs] def calc_population(self, vars_int, vars_float, problem_results, cmpnts=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) """ __, 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. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__(self, problem, N, name="cmaxN", **kwargs): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem N: int The maximal number of turbines name: str The constraint name kwargs: dict, optional 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): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return 1
[docs] def calc_individual(self, vars_int, vars_float, problem_results, cmpnts=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,) """ __, valid = problem_results return np.sum(valid) - self.N
[docs] def calc_population(self, vars_int, vars_float, problem_results, cmpnts=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) """ __, 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. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__(self, problem, N, name="cfixN", **kwargs): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem N: int The number of turbines name: str The constraint name kwargs: dict, optional 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): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return 2
[docs] def calc_individual(self, vars_int, vars_float, problem_results, cmpnts=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,) """ __, valid = problem_results vld = np.sum(valid) return np.array([self.N - vld, vld - self.N])
[docs] def calc_population(self, vars_int, vars_float, problem_results, cmpnts=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) """ __, 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. :group: opt.problems.layout.geom_layouts.constraints """
[docs] def __init__(self, problem, min_value, dfactor=1, name="min_density"): """ Constructor. Parameters ---------- problem: foxes_opt.FarmOptProblem The underlying geometrical layout optimization problem min_value: float The minimal turbine density dfactor: float Delta factor for grid spacing name: str The constraint name kwargs: dict, optional 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): """ Returns the number of components of the function. Returns ------- int: The number of components. """ return 1
[docs] def initialize(self, verbosity): """ Initialize the object. Parameters ---------- verbosity: int 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 = 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, vars_float, problem_results, cmpnts=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, valid = problem_results xy = xy[valid] dists = cdist(self._probes, xy) return np.nanmax(np.nanmin(dists, axis=1)) - self.min_value
[docs] def calc_population(self, vars_int, vars_float, problem_results, cmpnts=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 = 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 = cdist(self._probes, hxy) out[pi] = np.nanmax(np.nanmin(dists, axis=1)) - self.min_value return out[:, None]