Skip to content

Commit

Permalink
Raise informative errors if v3 input files passed in (#829)
Browse files Browse the repository at this point in the history
* Check floris input file for v3 fields.

* turbine checks for v3.

* convert to v4 compatibility to avoid v3 deprecation errors.

* Add utility for converting floris input files from v3 to v4.

* Ruff and isort.

* absolute power copied in from nrel_5MW.

* Explain user needs to update their multidim csv file.

* Errors/printouts for attempting to convert multidimensional turbines.
  • Loading branch information
misi9170 authored Mar 4, 2024
1 parent dc1b570 commit 57ec83c
Show file tree
Hide file tree
Showing 5 changed files with 283 additions and 125 deletions.
39 changes: 39 additions & 0 deletions floris/simulation/farm.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ def __attrs_post_init__(self) -> None:
if len(_turbine_types) == 1:
_turbine_types *= self.n_turbines

# Check that turbine definitions contain any v3 keys
for t in _turbine_types:
check_turbine_definition_for_v3_keys(turbine_definition_cache[t])

# Map each turbine definition to its index in this list
self.turbine_definitions = [
copy.deepcopy(turbine_definition_cache[t]) for t in _turbine_types
Expand Down Expand Up @@ -404,3 +408,38 @@ def coordinates(self):
@property
def n_turbines(self):
return len(self.layout_x)

def check_turbine_definition_for_v3_keys(turbine_definition: dict):
"""Check that the turbine definition does not contain any v3 keys."""
v3_deprecation_msg = (
"Consider using the convert_turbine_v3_to_v4.py utility in floris/tools "
+ "to convert from a FLORIS v3 turbine definition to FLORIS v4. "
+ "See https://nrel.github.io/floris/upgrade_guides/v3_to_v4.html for more information."
)
if "generator_efficiency" in turbine_definition:
raise ValueError(
"generator_efficiency is no longer supported as power is specified in absolute terms "
+ "in FLORIS v4. "
+ v3_deprecation_msg
)

v3_renamed_keys = ["pP", "pT", "ref_density_cp_ct", "ref_tilt_cp_ct"]
if any(k in turbine_definition for k in v3_renamed_keys):
v3_list_keys = ", ".join(map(str,v3_renamed_keys[:-1]))+", and "+v3_renamed_keys[-1]
v4_versions = (
"cosine_loss_exponent_yaw, cosine_loss_exponent_tilt, ref_air_density, and ref_tilt"
)
raise ValueError(
v3_list_keys
+ " have been renamed to "
+ v4_versions
+ ", respectively, and placed under the power_thrust_table field in FLORIS v4. "
+ v3_deprecation_msg
)

if "thrust" in turbine_definition["power_thrust_table"]:
raise ValueError(
"thrust has been renamed thrust_coefficient in FLORIS v4 (and power is now specified "
"in absolute terms with units kW, rather than as a coefficient). "
+ v3_deprecation_msg
)
34 changes: 34 additions & 0 deletions floris/simulation/floris.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ def from_file(cls, input_file_path: str | Path) -> Floris:
Floris: The class object instance.
"""
input_dict = load_yaml(Path(input_file_path).resolve())
check_input_file_for_v3_keys(input_dict)
return Floris.from_dict(input_dict)

def to_file(self, output_file_path: str) -> None:
Expand All @@ -362,3 +363,36 @@ def to_file(self, output_file_path: str) -> None:
sort_keys=False,
default_flow_style=False
)

def check_input_file_for_v3_keys(input_dict) -> None:
"""
Checks if any FLORIS v3 keys are present in the input file and raises special errors if
the extra keys belong to a v3 definition of the input_dct.
and raises special errors if the extra arguments belong to a v3 definition of the class.
Args:
input_dict (dict): The input dictionary to be checked for v3 keys.
"""
v3_deprecation_msg = (
"Consider using the convert_floris_input_v3_to_v4.py utility in floris/tools "
+ "to convert from a FLORIS v3 input file to FLORIS v4. "
"See https://nrel.github.io/floris/upgrade_guides/v3_to_v4.html for more information."
)
if "turbulence_intensity" in input_dict["flow_field"]:
raise AttributeError(
"turbulence_intensity has been updated to turbulence_intensities in FLORIS v4. "
+ v3_deprecation_msg
)
elif not hasattr(input_dict["flow_field"]["turbulence_intensities"], "__len__"):
raise AttributeError(
"turbulence_intensities must be a list of floats in FLORIS v4. "
+ v3_deprecation_msg
)

if input_dict["wake"]["model_strings"]["velocity_model"] == "multidim_cp_ct":
raise AttributeError(
"Dedicated 'multidim_cp_ct' velocity model has been removed in FLORIS v4 in favor of "
+ "supporting all available wake models. To recover previous operation, set "
+ "velocity_model to gauss. "
+ v3_deprecation_msg
)
70 changes: 70 additions & 0 deletions floris/tools/convert_floris_input_v3_to_v4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

import sys
from pathlib import Path

import yaml

from floris.utilities import load_yaml


"""
This script is intended to be called with an argument and converts a floris input
yaml file specified for FLORIS v3 to one specified for FLORIS v4.
Usage:
python convert_floris_input_v3_to_v4.py <path/to/floris_input>.yaml
The resulting floris input file is placed in the same directory as the original yaml,
and is appended _v4.
"""


if __name__ == "__main__":
if len(sys.argv) != 2:
raise Exception(
"Usage: python convert_floris_input_v3_to_v4.py <path/to/floris_input>.yaml"
)

input_yaml = sys.argv[1]

# Handling the path and new filename
input_path = Path(input_yaml)
split_input = input_path.parts
[filename_v3, extension] = split_input[-1].split(".")
filename_v4 = filename_v3 + "_v4"
split_output = list(split_input[:-1]) + [filename_v4+"."+extension]
output_path = Path(*split_output)

# Load existing v3 model
v3_floris_input_dict = load_yaml(input_yaml)
v4_floris_input_dict = v3_floris_input_dict.copy()

# Change turbulence_intensity field to turbulence_intensities as list
if "turbulence_intensities" in v3_floris_input_dict["flow_field"]:
if "turbulence_intensity" in v3_floris_input_dict["flow_field"]:
del v4_floris_input_dict["flow_field"]["turbulence_intensity"]
elif "turbulence_intensity" in v3_floris_input_dict["flow_field"]:
v4_floris_input_dict["flow_field"]["turbulence_intensities"] = (
[v3_floris_input_dict["flow_field"]["turbulence_intensity"]]
)
del v4_floris_input_dict["flow_field"]["turbulence_intensity"]

# Change multidim_cp_ct velocity model to gauss
if v3_floris_input_dict["wake"]["model_strings"]["velocity_model"] == "multidim_cp_ct":
print(
"multidim_cp_ct velocity model specified. Changing to gauss, "
+ "but note that other velocity models are also compatible with multidimensional "
+ "turbines in FLORIS v4. "
+ "You will also need to convert your multidimensional turbine yaml files and their "
+ "corresponding power/thrust csv files to be compatible with FLORIS v4 and to reflect "
+ " the absolute power curve, rather than the power coefficient curve."
)
v4_floris_input_dict["wake"]["model_strings"]["velocity_model"] = "gauss"

yaml.dump(
v4_floris_input_dict,
open(output_path, "w"),
sort_keys=False
)

print(output_path, "created.")
11 changes: 9 additions & 2 deletions floris/tools/convert_turbine_v3_to_v4.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
yaml file specified for FLORIS v3 to one specified for FLORIS v4.
Usage:
python convert_turbine_yaml_v3_to_v4.py <path/to/turbine>.yaml
python convert_turbine_v3_to_v4.py <path/to/turbine>.yaml
The resulting turbine is placed in the same directory as the original yaml,
and is appended _v4.
Expand All @@ -20,7 +20,7 @@

if __name__ == "__main__":
if len(sys.argv) != 2:
raise Exception("Usage: python convert_turbine_yaml_v3_to_v4.py <path/to/turbine>.yaml")
raise Exception("Usage: python convert_turbine_v3_to_v4.py <path/to/turbine>.yaml")

input_yaml = sys.argv[1]

Expand All @@ -37,6 +37,13 @@

# Split into components expected by build_turbine_dict
power_thrust_table = v3_turbine_dict["power_thrust_table"]
if "power_thrust_data_file" in power_thrust_table:
raise ValueError(
"Cannot convert multidimensional turbine model. Please manually update your "
+ "turbine yaml. Note that the power_thrust_data_file csv needs to be updated to "
+ "reflect the absolute power curve, rather than the power coefficient curve,"
+ "and that `thrust` has been replaced by `thrust_coefficient`."
)
power_thrust_table["power_coefficient"] = power_thrust_table["power"]
power_thrust_table["thrust_coefficient"] = power_thrust_table["thrust"]
power_thrust_table.pop("power")
Expand Down
Loading

0 comments on commit 57ec83c

Please sign in to comment.