diff --git a/examples/cases/KUL_LES/wind_energy_system/analysis_VM.yaml b/examples/cases/KUL_LES/wind_energy_system/analysis_VM.yaml index adeceb0..0e1cace 100644 --- a/examples/cases/KUL_LES/wind_energy_system/analysis_VM.yaml +++ b/examples/cases/KUL_LES/wind_energy_system/analysis_VM.yaml @@ -64,7 +64,7 @@ HPC_config: mesh_node_number: 2 mesh_ntasks_per_node: 48 mesh_wall_time_hours: 1 - run_partition: "" + #run_partition: "" # wckey: "" diff --git a/pyproject.toml b/pyproject.toml index 099bc51..d774bb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ classifiers = [ requires-python = ">=3.9,<3.12" dependencies = [ "py_wake>=2.6.5", - "foxes>=1.7.0", + "foxes @ git+https://github.com/FraunhoferIWES/foxes.git@eu_flow", "windIO @ git+https://github.com/EUFlow/windIO.git", "wayve @ git+https://gitlab.kuleuven.be/TFSO-software/wayve@dev_foxes", #"numpy<2", @@ -68,6 +68,9 @@ Homepage = "https://github.com/EUFLOW/WIFA/" Repository = "https://github.com/EUFLOW/WIFA.git" "Bug Tracker" = "https://github.com/EUFLOW/WIFA/issues" +[tool.pytest.ini_options] +pythonpath = ["tests"] + [tool.setuptools.packages.find] exclude = ["notebooks", "examples", "tests", "docs", "recipe", "results", "output"] diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..6d497bc --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,236 @@ +import numpy as np + +# DTU 10MW turbine data +# (from examples/cases/windio_4turbines/plant_energy_turbine/DTU_10MW_turbine.yaml) +_TURBINE = { + "name": "DTU 10MW Offshore Reference Turbine", + "hub_height": 119.0, + "rotor_diameter": 178.3, + "performance": { + "power_curve": { + "power_wind_speeds": [ + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + ], + "power_values": [ + 263388.0, + 751154.0, + 1440738.0, + 2355734.0, + 3506858.0, + 4993092.0, + 6849310.0, + 9116402.0, + 10000754.0, + 10009590.0, + 10000942.0, + 10042678.0, + 10003480.0, + 10001600.0, + 10001506.0, + 10013632.0, + 10007428.0, + 10005360.0, + 10002728.0, + 10001130.0, + 10004984.0, + 9997558.0, + ], + }, + "Ct_curve": { + "Ct_wind_speeds": [ + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + ], + "Ct_values": [ + 0.923, + 0.919, + 0.904, + 0.858, + 0.814, + 0.814, + 0.814, + 0.814, + 0.577, + 0.419, + 0.323, + 0.259, + 0.211, + 0.175, + 0.148, + 0.126, + 0.109, + 0.095, + 0.084, + 0.074, + 0.066, + 0.059, + ], + }, + }, +} + +# 3 turbines in a row, ~7D spacing (7 * 178.3 = 1248.1) +_LAYOUT_X = [0, 1248.1, 2496.2] +_LAYOUT_Y = [0, 0, 0] + +# Jensen wake model, minimal analysis config +_ANALYSIS = { + "wind_deficit_model": { + "name": "Jensen", + "wake_expansion_coefficient": {"k_a": 0.0, "k_b": 0.04}, + }, + "deflection_model": {"name": "None"}, + "turbulence_model": {"name": "STF2005", "c1": 1.0, "c2": 1.0}, + "superposition_model": { + "ws_superposition": "Linear", + "ti_superposition": "Squared", + }, + "rotor_averaging": {"name": "Center"}, + "blockage_model": {"name": "None"}, +} + + +def make_timeseries_per_turbine_system_dict(flow_model_name): + """Build a complete system dict with per-turbine timeseries data including density. + + 3 turbines, 6 timesteps, all variables have dims ["time", "wind_turbine"]. + """ + n_times = 6 + n_turbines = 3 + + # fmt: off + ws_data = [ + [9.0, 8.5, 9.2], + [10.0, 9.8, 10.5], + [7.5, 7.2, 7.8], + [8.0, 7.6, 8.3], + [11.0, 10.8, 11.2], + [9.5, 9.0, 9.8], + ] + wd_data = [ + [270.0, 269.5, 270.5], + [268.0, 267.5, 268.8], + [272.0, 271.0, 272.5], + [270.5, 270.0, 271.0], + [269.0, 268.5, 269.5], + [271.0, 270.5, 271.5], + ] + ti_data = [ + [0.06, 0.07, 0.05], + [0.08, 0.09, 0.07], + [0.05, 0.06, 0.05], + [0.07, 0.08, 0.06], + [0.10, 0.09, 0.08], + [0.06, 0.07, 0.06], + ] + # Turbine 0 off at timesteps 2-3 + operating_data = [ + [1, 1, 1], + [1, 1, 1], + [0, 1, 1], + [0, 1, 1], + [1, 1, 1], + [1, 1, 1], + ] + density_data = [ + [1.225, 1.223, 1.227], + [1.220, 1.218, 1.222], + [1.230, 1.228, 1.232], + [1.228, 1.226, 1.230], + [1.218, 1.220, 1.222], + [1.235, 1.233, 1.230], + ] + # fmt: on + + return { + "name": "Dict test: timeseries per-turbine with density", + "site": { + "name": "Test site", + "boundaries": { + "polygons": [{"x": [-90, 2600, 2600, -90], "y": [90, 90, -90, -90]}] + }, + "energy_resource": { + "name": "Test resource", + "wind_resource": { + "time": list(range(n_times)), + "wind_turbine": list(range(n_turbines)), + "wind_speed": { + "data": ws_data, + "dims": ["time", "wind_turbine"], + }, + "wind_direction": { + "data": wd_data, + "dims": ["time", "wind_turbine"], + }, + "turbulence_intensity": { + "data": ti_data, + "dims": ["time", "wind_turbine"], + }, + "operating": { + "data": operating_data, + "dims": ["time", "wind_turbine"], + }, + "density": { + "data": density_data, + "dims": ["time", "wind_turbine"], + }, + }, + }, + }, + "wind_farm": { + "name": "Test farm", + "layouts": [{"coordinates": {"x": _LAYOUT_X, "y": _LAYOUT_Y}}], + "turbines": _TURBINE, + }, + "attributes": { + "flow_model": {"name": flow_model_name}, + "analysis": _ANALYSIS, + "model_outputs_specification": { + "turbine_outputs": { + "turbine_nc_filename": "turbine_data.nc", + "output_variables": ["power"], + }, + }, + }, + } diff --git a/tests/test_foxes.py b/tests/test_foxes.py index 4e32388..deb17de 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -2,6 +2,7 @@ from pathlib import Path from shutil import rmtree +import numpy as np from windIO import __path__ as wiop from windIO import validate as validate_yaml @@ -81,6 +82,35 @@ def test_foxes_timeseries_with_operating_flag(): _run_foxes(wes_dir) +def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): + import foxes.variables as FV + from conftest import make_timeseries_per_turbine_system_dict + + # Run with density + system_dict = make_timeseries_per_turbine_system_dict("foxes") + output_dir = tmp_path / "output_foxes_ts" + farm_results = run_foxes(system_dict, verbosity=0, output_dir=str(output_dir))[0] + farmP_with = farm_results[FV.P].sum() + # print("Farm power with density:", farmP_with) + assert np.isfinite(farmP_with) and farmP_with > 0 + + # Run without density — same config but density removed + system_dict_no = make_timeseries_per_turbine_system_dict("foxes") + del system_dict_no["site"]["energy_resource"]["wind_resource"]["density"] + output_dir_no = tmp_path / "output_foxes_ts_no_density" + farm_results_no = run_foxes( + system_dict_no, verbosity=0, output_dir=str(output_dir_no) + )[0] + farmP_without = farm_results_no[FV.P].sum() + # print("Farm power without density:", farmP_without) + + rmtree(output_dir) + rmtree(output_dir_no) + + # Density correction should change AEP (test data varies around 1.225) + assert farmP_with != farmP_without + + if __name__ == "__main__": test_foxes_KUL() test_foxes_4wts() @@ -90,3 +120,4 @@ def test_foxes_timeseries_with_operating_flag(): test_foxes_heterogeneous_wind_rose_at_turbines() test_foxes_heterogeneous_wind_rose_map() test_foxes_simple_wind_rose() + test_timeseries_per_turbine_with_density()