Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b8dcbea
Clean up and updates for numpy v2
misi9170 May 8, 2026
fef95ec
First pass at basic interface
misi9170 May 8, 2026
a846c63
Use base wind field
misi9170 May 22, 2026
ecc5b57
Minor update to allow non-unity Uinf
misi9170 Jun 9, 2026
78e3ca2
Temporary, switch for windfarm type but planning to dedicate to Curle…
misi9170 Jun 9, 2026
ff4410b
add temporary development example; tailor to CurledWindfarm
misi9170 Jun 16, 2026
aa74b25
Prototyped with inbuilt op model for rotor
misi9170 Jun 16, 2026
d7938eb
Work through tilt angles
misi9170 Jun 16, 2026
cecbb39
Option for Windfield, but does not work (internal solver requires onl…
misi9170 Jun 30, 2026
c535916
Remove full windfield (didnt work)'
misi9170 Jun 30, 2026
1e8e7e7
Saving powers for reporting
misi9170 Jun 30, 2026
4cfc77b
Notes on u4, v4, Ctprime
misi9170 Jun 30, 2026
deaf393
Rename file for clarity
misi9170 Jun 30, 2026
132a355
Yaw, tilt comparison script
misi9170 Jul 9, 2026
18b6484
WIP, updates to match floris
misi9170 Jul 22, 2026
73fbcb9
Work in near wake velocity calculations for tilted, yawed rotor
misi9170 Jul 22, 2026
1a00d62
Switch to using unity power and hub height
misi9170 Jul 22, 2026
7e94bb8
updates for FLORIS branch
misi9170 Jul 29, 2026
5fddc5d
Cleanup, clarify comments and todo items
misi9170 Jul 29, 2026
287385e
Check and fix multidim turbine, floating turbine correction, update c…
misi9170 Jul 29, 2026
a657db0
beta -> beta_s following https://github.com/Howland-Lab/Unified-Momen…
misi9170 Aug 3, 2026
e518fff
Updates and bugfixes for full rotor integration
misi9170 Aug 3, 2026
28ad0f3
Committing changes to call with MITRotor model
misi9170 Aug 3, 2026
27452e2
Update pyproject.toml, documentation, remove forced x0 value
misi9170 Aug 3, 2026
205311a
Optionally include alpha * TI term in x0 calculation
misi9170 Aug 4, 2026
e7d8dcb
Add rotor averaging, tests
misi9170 Aug 4, 2026
d72a95c
Clean up examples, compare to gauss
misi9170 Aug 4, 2026
948a103
Add short explainer and demo to quickstart notebook
misi9170 Aug 4, 2026
83f3622
Pass through beta_s from init
misi9170 Aug 4, 2026
b701d87
Update package dependencies
misi9170 Aug 4, 2026
a077b86
Move x0 calc to dedicated call in CurledWindfarm model
misi9170 Aug 6, 2026
9b250d2
Move example output figure to file
misi9170 Aug 6, 2026
a38b131
Remove returned x0 from near_wake_velocities method
misi9170 Aug 6, 2026
9b5e019
Bugfix---remove double application of Us
misi9170 Aug 7, 2026
b5ab2b1
Use saved versions of quantities on rotor_solution
misi9170 Aug 7, 2026
55431bf
Apply @skygering suggestions
misi9170 Aug 12, 2026
83086be
Update poetry lock
misi9170 Aug 12, 2026
7d5d329
Move floris dependency to optional group
misi9170 Aug 12, 2026
79b62d8
Update github test workflow to use poetry
misi9170 Aug 12, 2026
1f03231
Update .github/workflows/pytest.yml to point to main branch
misi9170 Aug 12, 2026
a6bd74e
Apply suggestion from @skygering
misi9170 Aug 12, 2026
a7a497b
Update .github/workflows/pytest.yml
misi9170 Aug 12, 2026
5b19688
Update pyproject.toml
skygering Aug 12, 2026
754b052
Apply suggestion from @skygering
skygering Aug 12, 2026
4e26a0d
Apply suggestion from @skygering
skygering Aug 12, 2026
d0e4140
Apply suggestion from @skygering
skygering Aug 12, 2026
08e7693
Apply suggestion from @skygering
skygering Aug 12, 2026
4499f94
Apply suggestion from @skygering
skygering Aug 12, 2026
540cbac
Apply suggestion from @skygering
skygering Aug 12, 2026
4c88124
Apply suggestion from @skygering
skygering Aug 12, 2026
3e154b0
Update lockfile
misi9170 Aug 12, 2026
f8ed217
Poetry update
misi9170 Aug 17, 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
18 changes: 10 additions & 8 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ name: Pytest

on:
push:
branches:
- main
pull_request:
branches:
- master
- main

jobs:
test:
Expand All @@ -13,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- name: Checkout code
Expand All @@ -24,11 +26,11 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install .
pip install pytest
- name: Install Poetry
uses: snok/install-poetry@v1

- name: Install dependencies with floris
run: poetry install --with floris

- name: Run pytest
run: python -m pytest
run: poetry run pytest
113 changes: 88 additions & 25 deletions examples/MITWindfarm_quickstart.ipynb

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions examples/example_10_BEM_yaw_tilt.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def objective_func(x):

fig.suptitle("$C_P$ for Equivalent Turbine Setups", size = 16)
deg_yaw_tilt, deg_eff_yaw = np.rad2deg(yaw_tilt_val), np.rad2deg(eff_yaw)
axes[0, 0].set_title(f"Yaw {np.round(deg_eff_yaw, decimals=1)}$^\circ$\n$C_P$: {yaw_sol.Cp:2.3f}")
axes[0, 1].set_title(f"Tilt {np.round(deg_eff_yaw, decimals=1)}$^\circ$\n$C_P$: {tilt_sol.Cp:2.3f}")
axes[0, 2].set_title(f"Yaw {np.round(deg_yaw_tilt, decimals=1)}$^\circ$ & Tilt {np.round(deg_yaw_tilt, decimals=1)}$^\circ$\n$C_P$: {yaw_and_tilt_sol.Cp:2.3f}")
axes[0, 0].set_title(f"Yaw {np.round(deg_eff_yaw, decimals=1)}$^\\circ$\n$C_P$: {yaw_sol.Cp:2.3f}")
axes[0, 1].set_title(f"Tilt {np.round(deg_eff_yaw, decimals=1)}$^\\circ$\n$C_P$: {tilt_sol.Cp:2.3f}")
axes[0, 2].set_title(f"Yaw {np.round(deg_yaw_tilt, decimals=1)}$^\\circ$ & Tilt {np.round(deg_yaw_tilt, decimals=1)}$^\\circ$\n$C_P$: {yaw_and_tilt_sol.Cp:2.3f}")
plt.tight_layout()
plt.savefig(
FIGDIR / "example_10_yaw_tilt_comparison.png", dpi=300, bbox_inches="tight"
Expand Down
203 changes: 203 additions & 0 deletions examples/example_11_floris_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"""
Demonstrates the use of the FlorisCurledWindfarm interface to call the CurledWindfarm
wake model from the FLORIS wake modeling framework (https://github.com/NatLabRockies/floris/).

The example compares the results of simulating a wind farm using:
- The CurledWindfarm wake model directly
- The CurledWindfarm wake model called via the FlorisCurledWindfarm interface
- The "gauss" wake model in FLORIS

Additionally, the example demonstrates how to set yaw and tilt angles for the turbines in the farm.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
from floris import FlorisModel
from floris.flow_visualization import visualize_cut_plane
from floris.layout_visualization import plot_turbine_rotors

from mitwindfarm import FlorisCurledWindfarm, Layout, Plotting, PowerLaw
from mitwindfarm.Rotor import UnifiedAD_TI
from mitwindfarm.windfarm import CurledWindfarm

FIGDIR = Path(__file__).parent.parent / "fig"
FIGDIR.mkdir(exist_ok=True, parents=True)

def run_model_comparison(yaw_angle=0.0, tilt=False):

# Turbine parameters (for normalization)
D = 242.24
H = 150.0

# Sample points for querying the flow field
samples_x = np.array([1000, 1000, 2000, 2000])
samples_y = np.array([0, 100, 0, 100])
samples_z = np.array([H, H, H, H])

# Flow parameters
wind_shear = 0.0
TI = 0.06
U = 8.0
rotation_angle = -5 # First wind direction is 270; second is 270 + rotation_angle

# Create figure for placing plots
fig, axes = plt.subplots(2, 3, figsize=(15, 5))
fig.suptitle(f"Wind farm comparison: yaw={yaw_angle} deg, tilt={tilt}")

# CurledWindfarm parameters
layout = Layout([0, 12 / 2, 24 / 2], [0, 0, 0], [0, 0, 0])

solver_kwargs = {
"dy": 1 / 10,
"dz": 1 / 10,
"integrator": "scipy_rk23",
"k_model": "k-l",
"verbose": False,
}

# (Ct-prime, yaw, tilt)
setpoints = [
(2, np.deg2rad(yaw_angle), np.deg2rad(6) if tilt else 0),
(2, np.deg2rad(yaw_angle), np.deg2rad(6) if tilt else 0),
(2, np.deg2rad(yaw_angle), np.deg2rad(6) if tilt else 0),
]

windfarm = CurledWindfarm(
rotor_model=UnifiedAD_TI(),
base_windfield=PowerLaw(Uref=1.0, zref=H/D, exp=wind_shear, TIamb=TI),
solver_kwargs=solver_kwargs,
TIamb=TI,
)

windfarm_sol = windfarm(layout, setpoints)
windfarm_sol_rotated = windfarm(layout.rotate(rotation_angle), setpoints)

Plotting.plot_windfarm(windfarm_sol, axes[0, 0], vmin=0, vmax=2) # Left plots
Plotting.plot_windfarm(windfarm_sol_rotated, axes[1, 0], vmin=0, vmax=2)
axes[0, 0].scatter(samples_x/D, samples_y/D, color="k", marker=".")

windfarm_sol = windfarm(layout, setpoints)
windfarm_v_rel = windfarm_sol.windfield.wsp(samples_x/D, samples_y/D, (samples_z-H)/D)
windfarm_sol_rotated = windfarm(layout.rotate(rotation_angle), setpoints)

rotated_x = (
(samples_x/D - 6) * np.cos(np.radians(rotation_angle))
- (samples_y/D - 0) * np.sin(np.radians(rotation_angle))
+ 6
)
rotated_y = (
(samples_y/D - 0) * np.cos(np.radians(rotation_angle))
+ (samples_x/D - 6) * np.sin(np.radians(rotation_angle))
+ 0
)
axes[1, 0].scatter(rotated_x, rotated_y, color="k", marker=".")
axes[0, 0].set_title("Direct CurledWindfarm call")

windfarm_v_rel_rot = windfarm_sol_rotated.windfield.wsp(rotated_x, rotated_y, (samples_z-H)/D)
windfarm_v_rel = np.vstack([windfarm_v_rel, windfarm_v_rel_rot])
print("\nDirect CurledWindfarm sampled velocities [m/s]:")
print(windfarm_v_rel * U)

# Establish Floris model. Note that FLORIS alone will tilt the rotor according to
# the shaft tilt angle.
fmodel = FlorisModel("defaults") # Defaults to the "gauss" wake model in FLORIS
fmodel.set(
layout_x=layout.x * D,
layout_y=layout.y * D,
wind_speeds=[U, U],
wind_directions=[270.0, 270.0 + rotation_angle],
turbulence_intensities=[TI, TI],
turbine_type=["iea_15MW"]*len(layout.x),
reference_wind_height=H, # IEA 15MW hub height
wind_shear=wind_shear,
yaw_angles=yaw_angle*np.ones((2, 3)),
)
fmodel.run()
powers_gauss = fmodel.get_turbine_powers()
floris_visualization(
fmodel, H, rotation_angle, samples_x, samples_y, "FLORIS Gauss wake model",
axes[0, 2], axes[1, 2] # Right plots
)

# Assign MITWindfarm wake model, rerun
fmodel.set_wake_model(FlorisCurledWindfarm(
solver_kwargs=solver_kwargs,
use_floris_tilt=tilt,
))
fmodel.run()
powers_cwf = fmodel.get_turbine_powers()
floris_visualization(
fmodel, H, rotation_angle, samples_x, samples_y, "CurledWindfarm via FLORIS",
axes[0, 1], axes[1, 1] # Middle plots
)
floris_vels = fmodel.sample_flow_at_points(samples_x, samples_y, samples_z)

print("\nFLORIS sampled velocities [m/s]:")
print(floris_vels)

print("\nRelative velocity differences (MITWindfarm - FLORIS) [%]:")
print((windfarm_v_rel * U - floris_vels) / (windfarm_v_rel * U) * 100)

print("\nFLORIS Gauss turbine powers [MW]:")
print(powers_gauss / 1e6)

print("\nFLORIS CurledWindfarm turbine powers [MW]:")
print(powers_cwf / 1e6)

tilt_append = "_tilt" if tilt else ""
fig.savefig(
FIGDIR / f"{Path(__file__).stem}_yaw_{yaw_angle}{tilt_append}.png", bbox_inches="tight"
)

def floris_visualization(fmodel, H, rotation_angle, samples_x, samples_y, title, ax0, ax1):
cmap_floris = "pink"
# Extracting and plotting FLORIS results
horizontal_plane = fmodel.calculate_horizontal_plane(
x_resolution=200,
y_resolution=100,
height=H,
findex_for_viz=0,
)
plot_turbine_rotors(fmodel, ax=ax0)
visualize_cut_plane(
horizontal_plane,
ax=ax0,
cmap=cmap_floris,
clevels=100,
levels=[],
)
horizontal_plane = fmodel.calculate_horizontal_plane(
x_resolution=200,
y_resolution=100,
height=H,
findex_for_viz=1,
)
plot_turbine_rotors(
fmodel, ax=ax1, yaw_angles=[-rotation_angle]*len(fmodel.layout_x)
)
visualize_cut_plane(
horizontal_plane,
ax=ax1,
cmap=cmap_floris,
clevels=100,
levels=[],
)

ax0.scatter(samples_x, samples_y, color="k", marker=".")
ax1.scatter(samples_x, samples_y, color="k", marker=".")
ax0.set_title(title)


if __name__ == "__main__":
# Formatting for printing arrays
np.set_printoptions(precision=3, suppress=True)

# Not yawed, not tilted
print("="*30+"\nNo yaw, remove tilt\n"+"="*30)
run_model_comparison(yaw_angle=0.0, tilt=False)

# Include yaw and tilt
print("\n\n"+"="*30+"\nYawed 25 degrees, include tilt\n"+"="*30)
run_model_comparison(yaw_angle=25.0, tilt=True)
plt.close()
9 changes: 2 additions & 7 deletions mitwindfarm/CurledWake.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from scipy.interpolate import interpn, make_interp_spline

from mitwindfarm.Windfield import Windfield
from mitwindfarm.Rotor import RotorSolution
from mitwindfarm.Rotor import RotorSolution, compute_x0_with_TI
from mitwindfarm.utils.integrate import (
Integrator,
IntegrationException,
Expand Down Expand Up @@ -951,12 +951,7 @@ def get_heaviside(x, yax, turbines, default_x0=1):
"""
ret = np.zeros_like(yax)
for t in turbines:
try:
x0 = t.rotor_solution.extra.x0
if x0 == np.inf:
x0 = default_x0
except AttributeError:
x0 = default_x0
x0 = compute_x0_with_TI(t.rotor_solution)

if x >= t.xt and x < t.xt + x0:
# yids = (yax >= (t.yt - t.D/2)) & (yax <= (t.yt + t.D/2))
Expand Down
Loading
Loading