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

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

from foxes.config import config


[docs] class OMaxN(Objective): """ Maximal number of turbines objective for purely geometrical layouts problems. """ def __init__(self, problem: Problem, name: str = "maxN") -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem name The constraint name """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), )
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def maximize(self) -> list[bool]: """ Returns flag for maximization of each component. Returns ------- flags Bool array for component maximization, shape """ return [True]
[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 OMinN(OMaxN): """ Minimal number of turbines objective for purely geometrical layouts problems. """ def __init__(self, problem: Problem, name: str = "ominN") -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem name The constraint name """ super().__init__(problem, name)
[docs] def maximize(self) -> list[bool]: return [False]
[docs] class OFixN(Objective): """ Fixed number of turbines objective for purely geometrical layouts problems. """ def __init__(self, problem: Problem, N: int, name: str = "ofixN") -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem N The number of turbines name The constraint name """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), ) 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 maximize(self) -> list[bool]: """ Returns flag for maximization of each component. Returns ------- flags Bool array for component maximization, shape """ return [False]
[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 N: np.float64 = np.sum(valid, dtype=np.float64) return np.atleast_1d(np.maximum(N - self.N, self.N - 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 N = np.sum(valid, axis=1, dtype=np.float64)[:, None] return np.maximum(N - self.N, self.N - N)
[docs] class MaxGridSpacing(Objective): """ Maximal grid spacing objective for purely geometrical layouts problems. """ def __init__(self, problem: Problem, name: str = "max_dxdy") -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem name The constraint name """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), )
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def maximize(self) -> list[bool]: """ Returns flag for maximization of each component. Returns ------- flags Bool array for component maximization, shape """ return [True]
[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,) """ vflt = vars_float.reshape(self.problem.n_grids, 5) delta = np.minimum(vflt[:, 2], vflt[:, 3]) return np.atleast_1d(np.nanmin(delta))
[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] vflt = vars_float.reshape(n_pop, self.problem.n_grids, 5) delta = np.minimum(vflt[:, :, 2], vflt[:, :, 3]) return np.nanmin(delta, axis=1)[:, None]
[docs] class MaxDensity(Objective): """ Maximal turbine density objective for purely geometrical layouts problems. """ def __init__( self, problem: Problem, dfactor: int = 1, min_dist: float | None = None, name: str = "max_density", ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem dfactor Delta factor for grid spacing min_dist The minimal distance name The constraint name """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), ) self.dfactor: int = dfactor self.min_dist = problem.min_dist if min_dist is None else min_dist
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def maximize(self) -> list[bool]: """ Returns flag for maximization of each component. Returns ------- flags Bool array for component maximization, shape """ return [False]
[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.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)))
[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)) return out[:, None]
[docs] class MeMiMaDist(Objective): """ Mean-min-max distance objective for purely geometrical layouts problems. """ def __init__( self, problem: Problem, scale: float = 500.0, c1: int = 1, c2: int = 1, c3: int = 1, name: str = "MiMaMean", ) -> None: """ Parameters ---------- problem The underlying geometrical layout optimization problem scale The distance scale c1 Parameter for mean weighting c2 Parameter for max diff weighting c3 Parameter for min diff weighting name The constraint name """ super().__init__( problem, name, vnames_int=problem.var_names_int(), vnames_float=problem.var_names_float(), ) self.scale: float = scale self.c1: int = c1 self.c2: int = c2 self.c3: int = c3
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return 1
[docs] def maximize(self) -> list[bool]: """ Returns flag for maximization of each component. Returns ------- flags Bool array for component maximization, shape """ return [True]
[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(xy, xy) np.fill_diagonal(dists, np.inf) dists = np.min(dists, axis=1) / self.scale / len(xy) mean: np.floating = np.average(dists) mi: np.floating = np.min(dists) ma: np.floating = np.max(dists) return np.atleast_1d( self.c1 * mean**2 - self.c2 * (mean - mi) ** 2 - self.c3 * (mean - ma) ** 2 )
[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, valid = problem_results n_pop, n_xy = xy.shape[:2] out = np.zeros((n_pop, 1), dtype=config.dtype_double) for pi in range(n_pop): hxy = xy[pi] # , valid[pi]] dists: np.ndarray[tuple[int, ...], np.dtype[np.floating]] = cdist(hxy, hxy) np.fill_diagonal(dists, np.inf) dists = np.min(dists, axis=1) / self.scale / n_xy mean: np.floating = np.average(dists) mi: np.floating = np.min(dists) ma: np.floating = np.max(dists) out[pi, 0] = ( self.c1 * mean**2 - self.c2 * (mean - mi) ** 2 - self.c3 * (mean - ma) ** 2 ) return out