From 3ab28ff08f65c06d2cb8f32a68d9b9e408c15a9b Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 20 Jan 2026 13:31:54 +0100 Subject: [PATCH 1/7] updating foxes tests --- pyproject.toml | 2 +- tests/test_foxes.py | 27 +++++++-------------------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7243809..099bc51 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.6.2", + "foxes>=1.7.0", "windIO @ git+https://github.com/EUFlow/windIO.git", "wayve @ git+https://gitlab.kuleuven.be/TFSO-software/wayve@dev_foxes", #"numpy<2", diff --git a/tests/test_foxes.py b/tests/test_foxes.py index ac22881..4e32388 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -1,7 +1,7 @@ import os from pathlib import Path +from shutil import rmtree -from foxes import Engine, reset_engine from windIO import __path__ as wiop from windIO import validate as validate_yaml @@ -10,30 +10,17 @@ test_path = Path(os.path.dirname(__file__)) windIO_path = Path(wiop[0]) -engine = None - def _run_foxes(wes_dir): assert wes_dir.is_dir(), f"{wes_dir} is not a directory" - global engine - if engine is None: - engine = Engine.new("default", verbosity=0) - engine.initialize() - print("SETTING ENGINE:", engine) - for yaml_input in wes_dir.glob("system.yaml"): - if "_noXYgrid" not in str(yaml_input): - print("\nRUNNING FOXES ON", yaml_input, "\n") - validate_yaml(yaml_input, Path("plant/wind_energy_system")) - output_dir_name = Path("output_test_foxes") - output_dir_name.mkdir(parents=True, exist_ok=True) - try: - run_foxes(yaml_input, output_dir=output_dir_name, engine=None) - except Exception as e: - reset_engine() - engine = None - raise e + print("\nRUNNING FOXES ON", yaml_input, "\n") + validate_yaml(yaml_input, Path("plant/wind_energy_system")) + output_dir_name = Path("output_test_foxes") + output_dir_name.mkdir(parents=True, exist_ok=True) + run_foxes(yaml_input, output_dir=output_dir_name) + rmtree(output_dir_name) def test_foxes_KUL(): From 9e600b4ef78f2e7ff1376fa65978316d1dc7082b Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 17 Feb 2026 13:22:31 +0100 Subject: [PATCH 2/7] adding turbine data test for foxes --- pyproject.toml | 2 +- tests/conftest.py | 233 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_foxes.py | 43 ++++++-- 3 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 tests/conftest.py diff --git a/pyproject.toml b/pyproject.toml index 099bc51..f5d41cc 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", diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..5154530 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,233 @@ +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..2b8ee50 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -1,4 +1,5 @@ import os +import numpy as np from pathlib import Path from shutil import rmtree @@ -80,13 +81,39 @@ def test_foxes_timeseries_with_operating_flag(): ) _run_foxes(wes_dir) +def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): + from conftest import make_timeseries_per_turbine_system_dict + import foxes.variables as FV + + # 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() - test_foxes_abl() - test_foxes_abl_stable() - test_foxes_profiles() - test_foxes_heterogeneous_wind_rose_at_turbines() - test_foxes_heterogeneous_wind_rose_map() - test_foxes_simple_wind_rose() + #test_foxes_KUL() + #test_foxes_4wts() + #test_foxes_abl() + #test_foxes_abl_stable() + #test_foxes_profiles() + #test_foxes_heterogeneous_wind_rose_at_turbines() + #test_foxes_heterogeneous_wind_rose_map() + #test_foxes_simple_wind_rose() + test_timeseries_per_turbine_with_density() From 0fa4896ffa3ad4b66ca536b8bfa85ea7efb5325b Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 17 Feb 2026 13:24:51 +0100 Subject: [PATCH 3/7] formatting --- .../wind_energy_system/analysis_VM.yaml | 2 +- tests/conftest.py | 5 +++- tests/test_foxes.py | 26 +++++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) 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/tests/conftest.py b/tests/conftest.py index 5154530..6d497bc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -122,7 +122,10 @@ }, "deflection_model": {"name": "None"}, "turbulence_model": {"name": "STF2005", "c1": 1.0, "c2": 1.0}, - "superposition_model": {"ws_superposition": "Linear", "ti_superposition": "Squared"}, + "superposition_model": { + "ws_superposition": "Linear", + "ti_superposition": "Squared", + }, "rotor_averaging": {"name": "Center"}, "blockage_model": {"name": "None"}, } diff --git a/tests/test_foxes.py b/tests/test_foxes.py index 2b8ee50..198c2fa 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -1,8 +1,8 @@ import os -import numpy as np 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,9 +81,10 @@ def test_foxes_timeseries_with_operating_flag(): ) _run_foxes(wes_dir) + def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): - from conftest import make_timeseries_per_turbine_system_dict 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") @@ -97,7 +98,9 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): 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] + 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) @@ -107,13 +110,14 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): # 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() - #test_foxes_abl() - #test_foxes_abl_stable() - #test_foxes_profiles() - #test_foxes_heterogeneous_wind_rose_at_turbines() - #test_foxes_heterogeneous_wind_rose_map() - #test_foxes_simple_wind_rose() + # test_foxes_KUL() + # test_foxes_4wts() + # test_foxes_abl() + # test_foxes_abl_stable() + # test_foxes_profiles() + # test_foxes_heterogeneous_wind_rose_at_turbines() + # test_foxes_heterogeneous_wind_rose_map() + # test_foxes_simple_wind_rose() test_timeseries_per_turbine_with_density() From 28a01c194004097130845c9f8fe6524085266d9e Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 17 Feb 2026 13:27:39 +0100 Subject: [PATCH 4/7] dev foxes tests --- tests/test_foxes.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_foxes.py b/tests/test_foxes.py index 198c2fa..e812627 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -112,12 +112,12 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): if __name__ == "__main__": - # test_foxes_KUL() - # test_foxes_4wts() - # test_foxes_abl() - # test_foxes_abl_stable() - # test_foxes_profiles() - # test_foxes_heterogeneous_wind_rose_at_turbines() - # test_foxes_heterogeneous_wind_rose_map() - # test_foxes_simple_wind_rose() + test_foxes_KUL() + test_foxes_4wts() + test_foxes_abl() + test_foxes_abl_stable() + test_foxes_profiles() + test_foxes_heterogeneous_wind_rose_at_turbines() + test_foxes_heterogeneous_wind_rose_map() + test_foxes_simple_wind_rose() test_timeseries_per_turbine_with_density() From 3703a483b31b86467177051597f5027f4605debe Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 17 Feb 2026 13:40:47 +0100 Subject: [PATCH 5/7] dev foxes tests --- tests/test_foxes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_foxes.py b/tests/test_foxes.py index e812627..0cbd937 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -91,7 +91,7 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): 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) + #print("Farm power with density:", farmP_with) assert np.isfinite(farmP_with) and farmP_with > 0 # Run without density — same config but density removed @@ -102,7 +102,7 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): 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) + #print("Farm power without density:", farmP_without) rmtree(output_dir) rmtree(output_dir_no) From fa561e616a1a525ecd6500fc018337c8742e07e6 Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 17 Feb 2026 13:43:02 +0100 Subject: [PATCH 6/7] formatting --- tests/test_foxes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_foxes.py b/tests/test_foxes.py index 0cbd937..deb17de 100644 --- a/tests/test_foxes.py +++ b/tests/test_foxes.py @@ -91,7 +91,7 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): 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) + # print("Farm power with density:", farmP_with) assert np.isfinite(farmP_with) and farmP_with > 0 # Run without density — same config but density removed @@ -102,7 +102,7 @@ def test_timeseries_per_turbine_with_density(tmp_path=Path(".")): 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) + # print("Farm power without density:", farmP_without) rmtree(output_dir) rmtree(output_dir_no) From 1df7e830523d49f5d00d52a6bc67665e7ec73a65 Mon Sep 17 00:00:00 2001 From: Jonas Schulte Date: Tue, 17 Feb 2026 13:46:30 +0100 Subject: [PATCH 7/7] dev pyproject for tests path --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f5d41cc..d774bb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"]