Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions tests/test_pywake.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,40 @@ def test_pywake_dict_timeseries_per_turbine_with_density(tmp_path):
assert aep_with != aep_without


def test_site_bounds_circle_and_polygons():
from wifa.pywake_api import _site_bounds

# Circle: bounding box is center +/- radius
assert _site_bounds(
{"circle": {"center": {"x": 100.0, "y": -50.0}, "radius": 500.0}}
) == (-400.0, 600.0, -550.0, 450.0)

# Polygons: bounding box spans all polygons, not just the first
assert _site_bounds(
{
"polygons": [
{"x": [0, 10], "y": [0, 10]},
{"x": [-5, 3], "y": [2, 20]},
]
}
) == (-5, 10, 0, 20)

with pytest.raises(ValueError, match="polygons.*or.*circle"):
_site_bounds({})


def test_pywake_circle_boundaries(tmp_path):
"""Circle-based site boundaries run through pywake (GH issue #26)."""
from conftest import make_timeseries_per_turbine_system_dict

system_dict = make_timeseries_per_turbine_system_dict("pywake")
system_dict["site"]["boundaries"] = {
"circle": {"center": {"x": 1250.0, "y": 0.0}, "radius": 1500.0}
}
aep = run_pywake(system_dict, output_dir=str(tmp_path))
assert np.isfinite(aep) and aep > 0


# if __name__ == "__main__":
# test_heterogeneous_wind_rose()
# simple_yaml_to_pywake('../examples/cases/windio_4turbines_multipleTurbines/plant_energy_turbine/IEA_10MW_turbine.yaml')
Expand Down
31 changes: 26 additions & 5 deletions wifa/pywake_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,31 @@ def get_flow_field_param(system_dat, param_name, default=None):
return default


def _site_bounds(boundaries):
"""Bounding box (xlb, xub, ylb, yub) of the windIO site boundaries.

Supports both boundary types of the windIO site schema: a group of
polygons, or a circle with center and radius.
"""
if "polygons" in boundaries:
xs = np.concatenate([np.atleast_1d(p["x"]) for p in boundaries["polygons"]])
ys = np.concatenate([np.atleast_1d(p["y"]) for p in boundaries["polygons"]])
return np.min(xs), np.max(xs), np.min(ys), np.max(ys)
if "circle" in boundaries:
center = boundaries["circle"]["center"]
radius = boundaries["circle"]["radius"]
return (
center["x"] - radius,
center["x"] + radius,
center["y"] - radius,
center["y"] + radius,
)
raise ValueError(
"Site boundaries must define either 'polygons' or 'circle', "
f"got: {list(boundaries)}"
)


def construct_site(system_dat, resource_dat, hub_heights, x_positions):
"""Construct site object and wind conditions for simulation.

Expand All @@ -272,11 +297,7 @@ def construct_site(system_dat, resource_dat, hub_heights, x_positions):
from windIO import dict_to_netcdf

# Get flow field bounds from config or site boundaries
boundaries = system_dat["site"]["boundaries"]["polygons"][0]
WFXLB = np.min(boundaries["x"])
WFXUB = np.max(boundaries["x"])
WFYLB = np.min(boundaries["y"])
WFYUB = np.max(boundaries["y"])
WFXLB, WFXUB, WFYLB, WFYUB = _site_bounds(system_dat["site"]["boundaries"])

# Override with explicit flow field bounds if specified
WFXLB = get_flow_field_param(system_dat, "xlb", WFXLB)
Expand Down
Loading