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
2 changes: 1 addition & 1 deletion .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
apt-get update && apt-get install -y git-lfs
git lfs install
pip install --upgrade pip
pip install -e .[test]
pip install -e ".[all,test]"
- name: Test with pytest
run: |
py.test --durations=0 tests/
21 changes: 16 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,27 @@ classifiers = [
]
requires-python = ">=3.9,<3.12"
dependencies = [
"py_wake>=2.6.5",
"foxes>=1.6.2",
"windIO @ git+https://github.com/EUFlow/windIO.git",
"wayve @ git+https://gitlab.kuleuven.be/TFSO-software/wayve@dev_foxes",
"floris @ git+https://github.com/lejeunemax/floris.git@windIO",
"xarray>=2022.0.0,<2025",
"mpmath",
"scipy",
"pyyaml",
]

[project.optional-dependencies]
pywake = ["py_wake>=2.6.5"]
foxes = ["foxes>=1.6.2"]
floris = ["floris @ git+https://github.com/lejeunemax/floris.git@windIO"]
wayve = [
"wayve @ git+https://gitlab.kuleuven.be/TFSO-software/wayve",
"mpmath",
]
cs = [] # code_saturne is a subprocess, no pip dependency
all = [
"wifa[pywake]",
"wifa[foxes]",
"wifa[floris]",
"wifa[wayve]",
]
test = [
"pytest",
"pytest-cov",
Expand Down
11 changes: 10 additions & 1 deletion tests/test_floris.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,21 @@

import os
import shutil
import sys
from pathlib import Path

import floris
import numpy as np
import pytest
import xarray as xr

if sys.version_info < (3, 10):
pytest.skip("floris requires Python >= 3.10", allow_module_level=True)

pytest.importorskip(
"floris", reason="floris not installed, install with: pip install wifa[floris]"
)

import floris
from floris.turbine_library import build_cosine_loss_turbine_dict
from windIO import __path__ as wiop
from windIO import load_yaml
Expand Down
6 changes: 6 additions & 0 deletions tests/test_foxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
from shutil import rmtree

import numpy as np
import pytest

pytest.importorskip(
"foxes", reason="foxes not installed, install with: pip install wifa[foxes]"
)

from windIO import __path__ as wiop
from windIO import validate as validate_yaml

Expand Down
5 changes: 5 additions & 0 deletions tests/test_pywake.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
import numpy as np
import pytest
import xarray as xr

pytest.importorskip(
"py_wake", reason="py_wake not installed, install with: pip install wifa[pywake]"
)

from py_wake.deficit_models.gaussian import BastankhahGaussian
from py_wake.examples.data.dtu10mw._dtu10mw import DTU10MW
from py_wake.examples.data.hornsrev1 import Hornsrev1Site
Expand Down
6 changes: 6 additions & 0 deletions tests/test_wayve.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import os
from pathlib import Path

import pytest

pytest.importorskip(
"wayve", reason="wayve not installed, install with: pip install wifa[wayve]"
)

from windIO import __path__ as wiop
from windIO import validate as validate_yaml

Expand Down
1 change: 1 addition & 0 deletions wifa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# __init__.py

from .cs_api.cs_modules.csLaunch.cs_run_function import run_code_saturne
from .floris_api import run_floris
from .foxes_api import run_foxes
from .main_api import run_api
from .pywake_api import run_pywake
Expand Down
10 changes: 10 additions & 0 deletions wifa/_optional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import importlib.util


def require(package_name: str, extra_name: str) -> None:
"""Raise a clear ImportError if an optional dependency is missing."""
if importlib.util.find_spec(package_name) is None:
raise ImportError(
f"'{package_name}' is required for this functionality but is not installed. "
f"Install it with: pip install wifa[{extra_name}]"
)
3 changes: 3 additions & 0 deletions wifa/floris_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import windIO
from windIO import load_yaml

from wifa._optional import require

if TYPE_CHECKING:
from floris import FlorisModel

Expand All @@ -30,6 +32,7 @@ def run_floris(yaml_input):
- Some advanced windIO properties may not be fully supported. These include:
blockage_model, rotor_averaging, and axial_induction_model.
"""
require("floris", "floris")

from floris import FlorisModel
from floris.read_windio import TrackedDict
Expand Down
3 changes: 3 additions & 0 deletions wifa/foxes_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from windIO import load_yaml

from wifa._optional import require


def run_foxes(
input_yaml,
Expand Down Expand Up @@ -54,6 +56,7 @@ def run_foxes(
foxes output class

"""
require("foxes", "foxes")

from foxes.input.yaml import run_dict
from foxes.input.yaml.windio import read_windio_dict
Expand Down
4 changes: 4 additions & 0 deletions wifa/pywake_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from windIO import dict_to_netcdf, load_yaml
from windIO import validate as validate_yaml

from wifa._optional import require

# Define default values for wind_deficit_model parameters
DEFAULTS = {
"wind_deficit_model": {
Expand Down Expand Up @@ -1052,6 +1054,8 @@ def run_pywake(yaml_input, output_dir="output"):
float: Total AEP in GWh
"""
# Step 1: Load and validate configuration
require("py_wake", "pywake")

system_dat, output_dir = load_and_validate_config(yaml_input, output_dir)

# Step 2: Create turbine objects
Expand Down
19 changes: 13 additions & 6 deletions wifa/wayve_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
import warnings
from pathlib import Path

import matplotlib.pyplot as plt
import mpmath
import numpy as np
import xarray as xr
from scipy.interpolate import interp1d
from scipy.special import gamma as scipy_gamma
from windIO import load_yaml

from wifa._optional import require


def run_wayve(yamlFile, output_dir="output", debug_mode=False):
require("wayve", "wayve")

# General APM setup
from wayve.apm import APM
from wayve.grid.grid import Stat2Dgrid
Expand Down Expand Up @@ -271,6 +273,8 @@ def run_wayve(yamlFile, output_dir="output", debug_mode=False):

def nieuwstadt83_profiles(zh, v, wd, z0=1.0e-1, h=1.5e3, fc=1.0e-4, ust=0.666):
"""Set up the cubic analytical profile from Nieuwstadt (1983), based on hub height velocity information"""
import mpmath

# Atmospheric state setup
from wayve.abl.abl_tools import Cg_cubic, alpha_cubic

Expand Down Expand Up @@ -393,6 +397,8 @@ def rotate_xy_arrays(xs, ys, angle):
def ci_fitting(
zs, ths, l_mo=5.0e3, blh=1.0e3, dh_max=300.0, serz=True, plot_fits=False
):
import matplotlib.pyplot as plt

# Atmospheric state setup
from wayve.abl import ci_methods

Expand Down Expand Up @@ -638,8 +644,6 @@ def wm_coupling_setup(analysis_dat, wake_model):


def wake_model_setup(analysis_dat, debug_mode=False):
# WAYVE imports
from wayve.couplings.foxes_coupling import FoxesWakeModel
from wayve.forcing.wind_farms.wake_model_coupling.wake_models.lanzilao_merging import (
Lanzilao,
)
Expand All @@ -666,9 +670,11 @@ def wake_model_setup(analysis_dat, debug_mode=False):
# Use wake merging method of Lanzilao and Meyers (2021)
wake_model = Lanzilao(ka=k_a, kb=k_b, eps_beta=ceps)
elif wake_tool == "foxes":
require("foxes", "foxes")
from foxes import ModelBook
from foxes.input.yaml.windio.read_attributes import _read_analysis
from foxes.utils import Dict
from wayve.couplings.foxes_coupling import FoxesWakeModel

verbosity = 1 if debug_mode else 0

Expand Down Expand Up @@ -824,8 +830,9 @@ def flow_io_abl(wind_resource_dat, time_index, zh, h1, dh_max=None, serz=True):
)
# Geostrophic wind speed
z = np.linspace(h, 15.0e3, 1000)
U3 = np.trapz(np.interp(z, zs, us), z) / (15.0e3 - h)
V3 = np.trapz(np.interp(z, zs, vs), z) / (15.0e3 - h)
_trapz = getattr(np, "trapezoid", np.trapz)
U3 = _trapz(np.interp(z, zs, us), z) / (15.0e3 - h)
V3 = _trapz(np.interp(z, zs, vs), z) / (15.0e3 - h)
# Upper layer thickness
h2 = h - h1
if (
Expand Down