Source code for ard.layout.sunflower

import numpy as np
import scipy.spatial

import ard.layout.templates as templates
import ard.layout.fullfarm as fullfarm


phi_golden = (1 + np.sqrt(5)) / 2  # golden ratio


[docs] def sunflower( n: float, alpha: float = 0, # proportion of points that should end on boundary n_b: float = None, # for overriding with the number of boundary elements geodesic=False, # use geodesic step function ): """ generate a sunflower seed packing pattern adapted from a stackoverflow post: https://stackoverflow.com/questions/28567166/uniformly-distribute-x-points-inside-a-circle#28572551 in turn from the wolfram demonstrations page: Joost de Jong (2013), "Sunflower Seed Arrangements" Wolfram Demonstrations Project. demonstrations.wolfram.com/SunflowerSeedArrangements/. appears to originate from: doi:10.1016/0025-5564(79)90080-4 """ def radius(k: int, n: int, b: int): """ radius at which a seed should live b sets the number of boundary points remainder of n points have sequence-baased location """ if k > n - b: return 1.0 else: return np.sqrt(k - 0.5) / np.sqrt(n - (b + 1) / 2) points = [] # initialize a set of points # each next angle should step by a certain amount angle_stride = 2 * np.pi * phi_golden if geodesic else 2 * np.pi / phi_golden**2 b = ( n_b if n_b is not None else round(alpha * np.sqrt(n)) ) # number of boundary points for k in range(1, n + 1): r = radius(k, n, b) # get radius theta = k * angle_stride # get angle points.append((r * np.cos(theta), r * np.sin(theta))) return points
[docs] class SunflowerFarmLayout(templates.LayoutTemplate): """ A sunflower-inspired structured layout algorithm Options ------- modeling_options : dict a modeling options dictionary (inherited from `templates.LayoutTemplate`) N_turbines : int the number of turbines that should be in the farm layout (inherited from `templates.LayoutTemplate`) Inputs ------ alpha : float a parameter to control the number of boundary (v. interior) turbines spacing_target : float a parameter to control the target average minimum spacing Outputs ------- x_turbines : np.ndarray a 1-D numpy array that represents that x (i.e. Easting) coordinate of the location of each of the turbines in the farm in meters (inherited from `templates.LayoutTemplate`) y_turbines : np.ndarray a 1-D numpy array that represents that y (i.e. Northing) coordinate of the location of each of the turbines in the farm in meters (inherited from `templates.LayoutTemplate`) spacing_effective_primary : float a measure of the spacing on a primary axis of a rectangular farm that would be equivalent to this one for the purposes of computing BOS costs measured in rotor diameters (inherited from `templates.LayoutTemplate`) spacing_effective_secondary : float a measure of the spacing on a secondary axis of a rectangular farm that would be equivalent to this one for the purposes of computing BOS costs measured in rotor diameters (inherited from `templates.LayoutTemplate`) """
[docs] def initialize(self): """Initialization of OM component.""" super().initialize()
[docs] def setup(self): """Setup of OM component.""" super().setup() # add parameters for sunflower farm DVs # self.add_input("alpha", 0.0, desc="boundary point control param.") self.add_input("spacing_target", 0.0, desc="target spacing in rotor diameters")
[docs] def setup_partials(self): """Derivative setup for OM component.""" # run FD for the layout tools self.declare_partials("*", "*", method="fd")
[docs] def compute(self, inputs, outputs): """Computation for the OM component.""" # get the desired mean nearest-neighbor distance D_rotor = self.windIO["wind_farm"]["turbine"][ "rotor_diameter" ] # get rotor diameter spacing_target = D_rotor * inputs["spacing_target"] # generate the points points = np.array(sunflower(self.N_turbines, geodesic=True)) dist_mtx = scipy.spatial.distance.squareform( scipy.spatial.distance.pdist(points) ) np.fill_diagonal(dist_mtx, np.inf) # self-distance not meaningful, remove d_mean_NN = np.mean(np.min(dist_mtx, axis=0)) # rescale points to achieve target spacing points *= spacing_target / d_mean_NN # pipe in the outputs outputs["x_turbines"] = points[:, 0].tolist() outputs["y_turbines"] = points[:, 1].tolist() outputs["spacing_effective_primary"] = spacing_target # ??? outputs["spacing_effective_secondary"] = spacing_target # ???
[docs] class SunflowerFarmLanduse(fullfarm.FullFarmLanduse): pass # just use the full-farm landuse component