Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1057a94
bring in the exclusions and their viz implications
cfrontin Jan 6, 2026
c08b5b7
added exclusion code
cfrontin Jan 7, 2026
7a7110e
Apply suggestions from code review
cfrontin Jan 7, 2026
11f2466
added eagle density code and tested it
cfrontin Jan 7, 2026
469093d
fix filepath
cfrontin Jan 7, 2026
923deb2
improved documentation, added and tested derivatives on eagles splines
cfrontin Jan 7, 2026
6a7545f
black reformat
cfrontin Jan 7, 2026
8191c9f
first tranche of copilot changes
cfrontin Jan 7, 2026
eabd1e9
second tranche of copilot fixes
cfrontin Jan 7, 2026
ec3255e
Apply suggestions from copilot code review
cfrontin Jan 7, 2026
9b620cd
more consistent error messages
cfrontin Jan 7, 2026
c2bfb64
Merge branch 'feature/eco_eagles_field' of github.com:cfrontin/Ard in…
cfrontin Jan 7, 2026
5f06bb4
black reformat
cfrontin Jan 7, 2026
62d4eca
Merge branch 'develop' of github.com:WISDEM/Ard into feature/exclusions
cfrontin Jan 13, 2026
133276d
Merge branch 'feature/exclusions' into feature/eco_eagles_field
cfrontin Jan 13, 2026
44c1b37
new gold standard test, weird failure
cfrontin Jan 13, 2026
8dd81f8
fixed value of the exclusion distance; still failing now on two: myst…
cfrontin Jan 13, 2026
bfc06f9
add a boundary test case that lights up the strange error
cfrontin Jan 14, 2026
1a83abf
rename and add some tests
cfrontin Jan 14, 2026
2b11ec6
added exclusion test even though it's in tatters
cfrontin Jan 14, 2026
473c89a
debugging
jaredthomas68 Jan 15, 2026
31cbca9
use switch instead of pad to fix boundary in/out identification. also…
jaredthomas68 Jan 15, 2026
cd8ada3
black reformat
cfrontin Jan 16, 2026
507a934
Merge branch 'feature/exclusions' into feature/exclusions
cfrontin Jan 16, 2026
4eb52b5
Merge pull request #6 from jaredthomas68/feature/exclusions
cfrontin Jan 16, 2026
c7d9425
black reformat
cfrontin Jan 16, 2026
b551ff9
Merge branch 'feature/exclusions' into feature/eco_eagles_field
cfrontin Jan 16, 2026
6682379
Merge branch 'develop' of github.com:WISDEM/Ard into feature/exclusions
cfrontin Jan 16, 2026
a821c2b
Merge branch 'feature/exclusions' into feature/eco_eagles_field
cfrontin Jan 16, 2026
238b9f0
Merge branch 'develop' of github.com:WISDEM/Ard into feature/eco_eagl…
cfrontin Jan 20, 2026
3ed37cd
address jared comments
cfrontin Jan 20, 2026
7499864
Merge branch 'develop' of github.com:WISDEM/Ard into feature/eco_eagl…
cfrontin Jan 20, 2026
9b86e2c
fix accidentally stashed and dropped changes
cfrontin Jan 20, 2026
265d703
black reformat...
cfrontin Jan 20, 2026
0187464
fix calling convention
cfrontin Jan 20, 2026
3ddf3c5
black are you kidding me
cfrontin Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions ard/eco/eagle_density.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import numpy as np
from scipy.interpolate import RectBivariateSpline

import openmdao.api as om


class EagleDensityFunction(om.ExplicitComponent):
"""
OpenMDAO component to evaluate eagle presence density at turbine locations.

An Ard/OpenMDAO component that evaluates the eagle presence density metric
calculated by the National Laboratory of the Rockies's Stochastic Soaring
Raptor Simulator (SSRS) at the turbine locations. The eagle presence density
is an output of an SSRS simulation indicating the unit density function of
a raptor flying through the point during a given migratory period.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it just for migration?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think it's across the year, like the period of a wave but referring to southbound then northbound migratory flow. i snipped the language from their paper although it uses a fair amount of terminology of art and i was only skimming.


Options
-------
modeling_options : dict
a modeling options dictionary (inherited from
`templates.LanduseTemplate`)

Inputs
------
x_turbines : np.ndarray
a 1-D numpy array that represents the x (i.e. Easting) coordinate of
the location of each of the turbines in the farm in meters
y_turbines : np.ndarray
a 1-D numpy array that represents the y (i.e. Northing) coordinate of
the location of each of the turbines in the farm in meters

Outputs
-------
eagle_normalized_density : np.ndarray
a 1-D numpy array that represents the normalized eagle presence density
at each of the turbine locations (unitless)
"""

def initialize(self):
"""Initialization of OM component."""
self.options.declare("modeling_options")

def setup(self):
"""Setup of OM component."""

# load modeling options and turbine count
modeling_options = self.modeling_options = self.options["modeling_options"]

self.N_turbines = modeling_options["layout"]["N_turbines"]

# grab the eagle presence density settings
self.pres = self.modeling_options["eco"]["eagle_presence_density_map"]
self.eagle_density_function = RectBivariateSpline(
self.pres["x"], self.pres["y"], self.pres["normalized_presence_density"]
)
self.eagle_density_function_dx = self.eagle_density_function.partial_derivative(
dx=1, dy=0
)
self.eagle_density_function_dy = self.eagle_density_function.partial_derivative(
dx=0, dy=1
)

# add the full layout inputs
self.add_input(
"x_turbines",
np.zeros((self.N_turbines,)),
units="m",
desc="turbine location in x-direction",
)
self.add_input(
"y_turbines",
np.zeros((self.N_turbines,)),
units="m",
desc="turbine location in y-direction",
)

# add outputs that are universal
self.add_output(
"eagle_normalized_density",
np.zeros((self.N_turbines,)),
units=None,
desc="normalized eagle presence density",
)

def setup_partials(self):
"""Setup the OpenMDAO component partial derivatives."""
self.declare_partials(
"eagle_normalized_density",
"x_turbines",
diagonal=True,
method="exact",
)
self.declare_partials(
"eagle_normalized_density",
"y_turbines",
diagonal=True,
method="exact",
)

def compute(self, inputs, outputs):
"""
Computation for the OM component.
"""

# unpack the turbine locations
x_turbines = inputs["x_turbines"] # m
y_turbines = inputs["y_turbines"] # m

# evaluate the density function at each turbine point
outputs["eagle_normalized_density"] = self.eagle_density_function(
x_turbines,
y_turbines,
grid=False,
)

def compute_partials(self, inputs, partials):
"""
Compute the partials for the OM component
"""

# unpack the turbine locations
x_turbines = inputs["x_turbines"] # m
y_turbines = inputs["y_turbines"] # m

# evaluate the gradients for each variable
dfdx = self.eagle_density_function_dx(x_turbines, y_turbines, grid=False)
dfdy = self.eagle_density_function_dy(x_turbines, y_turbines, grid=False)
partials["eagle_normalized_density", "x_turbines"] = dfdx
partials["eagle_normalized_density", "y_turbines"] = dfdy
12 changes: 9 additions & 3 deletions ard/layout/exclusions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ class FarmExclusionDistancePolygon(om.ExplicitComponent):
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`)

Outputs
-------
exclusion_distances : np.ndarray
a 1D array of distances (in meters) from each turbine to its assigned
polygonal exclusion region
"""

def initialize(self):
Expand All @@ -49,7 +55,7 @@ def setup(self):
"The circular exclusions from windIO have not been implemented here, yet."
)
if "polygons" not in self.windIO["site"]["exclusions"]:
raise KeyError(
raise NotImplementedError(
"Currently only polygon exclusions from windIO have been implemented and none were found."
)
self.exclusion_vertices = [
Expand All @@ -62,7 +68,7 @@ def setup(self):
for polygon in self.windIO["site"]["exclusions"]["polygons"]
]
self.exclusion_regions = self.modeling_options.get("exclusions", {}).get(
"turbine_exclusion_assignments", # get the exclusion region assignments from modeling_options, if there
"turbine_exclusion_assignments", # exclusion region assignments, if there
np.zeros(self.N_turbines, dtype=int), # default to zero for all turbines
)

Expand All @@ -87,7 +93,7 @@ def setup(self):

def setup_partials(self):
"""Derivative setup for the OpenMDAO component."""
# the default (but not preferred!) derivatives are FDM
# override the OpenMDAO default FDM derivatives by declaring exact derivatives
self.declare_partials(
"*",
"*",
Expand Down
8 changes: 4 additions & 4 deletions ard/layout/gridfarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def setup(self):
angle_skew = self.modeling_options["layout"]["angle_skew"]

# add four-parameter grid farm layout DVs
self.add_input("spacing_primary", spacing_primary, units="unitless")
self.add_input("spacing_secondary", spacing_secondary, units="unitless")
self.add_input("spacing_primary", spacing_primary, units=None)
self.add_input("spacing_secondary", spacing_secondary, units=None)
self.add_input("angle_orientation", angle_orientation, units="deg")
self.add_input("angle_skew", angle_skew, units="deg")

Expand Down Expand Up @@ -227,13 +227,13 @@ def setup(self):
self.add_input(
"spacing_primary",
self.modeling_options["layout"]["spacing_primary"],
units="unitless",
units=None,
desc="turbine row spacing in rotor diameters",
)
self.add_input(
"spacing_secondary",
self.modeling_options["layout"]["spacing_secondary"],
units="unitless",
units=None,
desc="turbine column spacing (along rows) in rotor diameters",
)
self.add_input(
Expand Down
6 changes: 6 additions & 0 deletions test/ard/unit/eco/inputs/ard_system_eagle_density.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
modeling_options: &modeling_options
windIO_plant:
layout:
N_turbines: 9
eco:
eagle_presence_density_map: !include presence_density.yaml
Loading
Loading