Source code for foxes_opt.constraints.area_geometry

from typing import Any

import numpy as np
from foxes.utils.geom2d import AreaGeometry

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


[docs] class AreaGeometryConstraint(FarmConstraint): """ Constrains turbine positions to the inside of a given area geometry. """ def __init__( self, problem: FarmOptProblem, name: str, geometry: AreaGeometry, sel_turbines: list[int] | None = None, disc_inside: bool = False, D: float | None = None, **kwargs: Any, ) -> None: """ Parameters ---------- problem The underlying optimization problem name The name of the constraint geometry The area geometry sel_turbines The selected turbines disc_inside Ensure full rotor disc inside boundary D Use this radius for rotor disc inside condition kwargs Additional parameters for `iwopy.Constraint` """ self.geometry = geometry self.disc_inside = disc_inside self.D = D selt = problem.sel_turbines if sel_turbines is None else sel_turbines vrs = [] cns = [] for ti in selt: vrs += [problem.tvar(FV.X, ti), problem.tvar(FV.Y, ti)] cns.append(f"{name}_{ti:04d}") super().__init__( problem, name, sel_turbines, vnames_float=vrs, cnames=cns, **kwargs )
[docs] def n_components(self) -> int: """ Returns the number of components of the function. Returns ------- value The number of components. """ return self.n_sel_turbines
[docs] def vardeps_float(self) -> np.ndarray[tuple[int, int], np.dtype[np.bool_]]: """ Gets the dependencies of all components on the function float variables Returns ------- deps The dependencies of components on function variables, shape """ deps = np.zeros((self.n_components(), self.n_components(), 2), dtype=bool) np.fill_diagonal(deps[:, :, 0], True) np.fill_diagonal(deps[:, :, 1], True) return deps.reshape(self.n_components(), self.n_components() * 2)
[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,) """ s: slice | np.ndarray[Any, np.dtype[np.int_]] = np.s_[:] if components is not None and len(components) < self.n_components(): s = np.asarray(components, dtype=int) xy = vars_float.reshape(self.n_components(), 2)[s] dists = self.geometry.points_distance(xy) dists[self.geometry.points_inside(xy)] *= -1 if self.disc_inside: if self.D is None: dists += problem_results[FV.D].to_numpy()[0, self.sel_turbines][s] / 2 else: dists += self.D / 2 return dists
[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 = len(vars_float) n_cmpnts = self.n_components() s: slice | np.ndarray[Any, np.dtype[np.int_]] = np.s_[:] if components is not None and len(components) < self.n_components(): n_cmpnts = len(components) s = np.asarray(components, dtype=int) xy = vars_float[:, s].reshape(n_pop * n_cmpnts, 2) dists = self.geometry.points_distance(xy) dists[self.geometry.points_inside(xy)] *= -1 dists = dists.reshape(n_pop, n_cmpnts) if self.disc_inside: if self.D is None: dists += ( problem_results[FV.D].to_numpy()[None, 0, self.sel_turbines][s] / 2 ) else: dists += self.D / 2 return dists
[docs] class FarmBoundaryConstraint(AreaGeometryConstraint): """ Constrains turbine positions to the inside of the wind farm boundary """ def __init__( self, problem: FarmOptProblem, name: str = "boundary", **kwargs: Any ) -> None: """ Parameters ---------- problem The underlying optimization problem name The name of the constraint kwargs Additional parameters for `AreaGeometryConstraint` """ b = problem.farm.boundary assert b is not None, f"Constraint '{name}': Missing wind farm boundary." super().__init__(problem, name, geometry=b, **kwargs)