Source code for ard.layout.spacing
import numpy as np
import jax.numpy as jnp
import jax
import ard.utils.mathematics
import openmdao.api as om
[docs]
class TurbineSpacing(om.ExplicitComponent):
"""
A class to return distances between each pair of turbines without duplicates.
Options
-------
modeling_options : dict
a modeling options dictionary (inherited from `FarmAeroTemplate`)
Inputs
------
x_turbines : np.ndarray
a 1D numpy array indicating the x-dimension locations of the turbines,
with length `N_turbines` (mirrored w.r.t. `FarmAeroTemplate`)
y_turbines : np.ndarray
a 1D numpy array indicating the y-dimension locations of the turbines,
with length `N_turbines` (mirrored w.r.t. `FarmAeroTemplate`)
turbine_spacing : np.ndarray
a 1D numpy array indicating the distances between turbines,
with length (N_turbines - 1)*N_turbines/2. where, for 3 turbines, turbine_spacing[0]
is the distance between turbines 0 and 1, turbine_spacing[1] is the distance between
turbines 0 and 2, and turbine_spacing[2] is the distance between turbines 1 and 2.
The array is the flattened upper-triangular portion of the distance matrix.
"""
[docs]
def initialize(self):
"""Initialization of the OpenMDAO component."""
self.options.declare("modeling_options")
[docs]
def setup(self):
"""Setup of the OpenMDAO component."""
# load modeling options
self.modeling_options = self.options["modeling_options"]
self.N_turbines = int(self.modeling_options["layout"]["N_turbines"])
self.N_distances = int((self.N_turbines - 1) * self.N_turbines / 2)
# set up inputs and outputs for mooring system
self.add_input(
"x_turbines", jnp.zeros((self.N_turbines,)), units="km"
) # x location of the mooring platform in km w.r.t. reference coordinates
self.add_input(
"y_turbines", jnp.zeros((self.N_turbines,)), units="km"
) # y location of the mooring platform in km w.r.t. reference coordinates
self.add_output(
"turbine_spacing",
jnp.zeros(self.N_distances),
units="km",
)
[docs]
def setup_partials(self):
"""Derivative setup for the OpenMDAO component."""
# the default (but not preferred!) derivatives are FDM
self.declare_partials("*", "*", method="exact")
[docs]
def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
"""Computation for the OpenMDAO component."""
# unpack the working variables
x_turbines = inputs["x_turbines"]
y_turbines = inputs["y_turbines"]
spacing_distances = calculate_turbine_spacing(x_turbines, y_turbines)
outputs["turbine_spacing"] = spacing_distances
[docs]
def compute_partials(self, inputs, partials, discrete_inputs=None):
# unpack the working variables
x_turbines = inputs["x_turbines"]
y_turbines = inputs["y_turbines"]
jacobian = calculate_turbine_spacing_jac(x_turbines, y_turbines)
partials["turbine_spacing", "x_turbines"] = jacobian[0]
partials["turbine_spacing", "y_turbines"] = jacobian[1]
[docs]
def calculate_turbine_spacing(
x_turbines: np.ndarray,
y_turbines: np.ndarray,
) -> np.ndarray:
"""Calculate the spacing between every pair of turbines with no duplicates.
Args:
x_turbines (np.ndarray): a 1D numpy array indicating the x-dimension locations of the turbines,
with length `N_turbines`.
y_turbines (np.ndarray): a 1D numpy array indicating the y-dimension locations of the turbines,
with length `N_turbines`.
Returns:
turbine distances (np.ndarray): a 1D numpy array indicating the distances between turbines
with length (N_turbines - 1)*N_turbines/2. The array is the flattened
upper-triangular portion of the distance matrix. For 3 turbines, turbine_spacing[0] is the
distance between turbines 0 and 1, turbine_spacing[1] is the distance between
turbines 0 and 2, and turbine_spacing[2] is the distance between turbines 1 and 2.
"""
N_turbines = len(x_turbines)
# Create index pairs for i < j (upper triangle without diagonal)
i_indices, j_indices = jnp.triu_indices(N_turbines, k=1)
# Compute deltas
dx = x_turbines[j_indices] - x_turbines[i_indices]
dy = y_turbines[j_indices] - y_turbines[i_indices]
deltas = jnp.stack([dx, dy], axis=1)
# Vectorized norm calculation
spacing_distance = ard.utils.mathematics.smooth_norm_vec(deltas)
return spacing_distance
calculate_turbine_spacing = jax.jit(calculate_turbine_spacing)
calculate_turbine_spacing_jac = jax.jacrev(calculate_turbine_spacing, argnums=[0, 1])