Skip to content

Commit

Permalink
Use FLORIS's methods for merging fmodels and setting model paramete…
Browse files Browse the repository at this point in the history
…rs (#182)
  • Loading branch information
misi9170 authored Apr 9, 2024
1 parent 969cd7d commit 2488643
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 246 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions examples_smarteole/08_emgauss_tuning_day_night.ipynb

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions flasc/model_fitting/floris_tuning.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from flasc.analysis import energy_ratio as er, total_uplift as tup
from flasc.analysis.energy_ratio_input import EnergyRatioInput
from flasc.utilities.energy_ratio_utilities import add_power_ref, add_power_test
from flasc.utilities.tuner_utilities import replicate_nan_values, resim_floris, set_fm_param
from flasc.utilities.tuner_utilities import replicate_nan_values, resim_floris


def evaluate_overall_wake_loss(df_, df_freq=None):
Expand Down Expand Up @@ -100,9 +100,10 @@ def sweep_velocity_model_parameter_for_overall_wake_losses(

# Now loop over FLORIS candidates and collect the wake loss
floris_wake_losses = np.zeros(len(value_candidates))
fm = fm_in.copy()
for idx, vc in enumerate(value_candidates):
# Set the parameter
fm = set_fm_param(fm_in, parameter, vc, param_idx)
fm.set_param(parameter, vc, param_idx)

# Collect the FLORIS results
df_floris = resim_floris(fm, df_scada.to_pandas(), yaw_angles=yaw_angles)
Expand Down Expand Up @@ -366,15 +367,16 @@ def sweep_deflection_parameter_for_total_uplift(
# df_list = []
for idx, vc in enumerate(value_candidates):
# Set the parameter for baseline and wake steering
fi_baseline = set_fm_param(fm_in, parameter, vc)
fi_wakesteering = fi_baseline.copy()
fm_baseline = fm_in.copy()
fm_baseline.set_param(parameter, vc)
fm_wakesteering = fm_baseline.copy()

# Collect the FLORIS results
df_floris_baseline = resim_floris(
fi_baseline, df_scada_baseline, yaw_angles=yaw_angles_baseline
fm_baseline, df_scada_baseline, yaw_angles=yaw_angles_baseline
)
df_floris_wakesteering = resim_floris(
fi_wakesteering, df_scada_wakesteering, yaw_angles=yaw_angles_wakesteering
fm_wakesteering, df_scada_wakesteering, yaw_angles=yaw_angles_wakesteering
)

df_floris_baseline = pl.from_pandas(df_floris_baseline)
Expand Down
102 changes: 1 addition & 101 deletions flasc/utilities/floris_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from floris import FlorisModel, TimeSeries, WindTIRose
from floris import TimeSeries, WindTIRose
from floris.utilities import wrap_360
from scipy import interpolate
from scipy.stats import norm
Expand All @@ -21,106 +21,6 @@
# ruff: noqa: E501


def merge_floris_objects(fm_list, reference_wind_height=None):
"""Merge a list of FlorisModel objects into a single FlorisModel object. Note that it uses
the very first object specified in fi_list to build upon, so it uses those wake model parameters,
air density, and so on.
Args:
fi_list (list): Array-like of FlorisModel objects.
reference_wind_height (float, optional): Height in meters at which the reference wind speed is
assigned. If None, will assume this value is equal to the reference wind height specified in
the FlorisModel objects. This only works if all objects have the same value for their
reference_wind_height.
Returns:
fi_merged (FlorisModel): The merged FlorisModel object, merged in the same order as fi_list.
The objects are merged on the turbine locations and turbine types, but not on the wake parameters
or general solver settings.
"""

# Make sure the entries in fi_list are FlorisModel objects
if not isinstance(fm_list[0], FlorisModel):
raise UserWarning(
"Incompatible input specified. Please merge FlorisModel objects before inserting them into ParallelComputingInterface and UncertainFlorisModel."
)

# Get the turbine locations and specifications for each subset and save as a list
x_list = []
y_list = []
turbine_type_list = []
reference_wind_heights = []
for fm in fm_list:
x_list.extend(fm.layout_x)
y_list.extend(fm.layout_y)

fi_turbine_type = fm.core.farm.turbine_type
if len(fi_turbine_type) == 1:
fi_turbine_type = fi_turbine_type * len(fm.layout_x)
elif not len(fi_turbine_type) == len(fm.layout_x):
raise UserWarning("Incompatible format of turbine_type in FlorisModel.")

turbine_type_list.extend(fi_turbine_type)
reference_wind_heights.append(fm.core.flow_field.reference_wind_height)

# Derive reference wind height, if unspecified by the user
if reference_wind_height is None:
reference_wind_height = np.mean(reference_wind_heights)
if np.any(np.abs(np.array(reference_wind_heights) - reference_wind_height) > 1.0e-3):
raise UserWarning(
"Cannot automatically derive a fitting reference_wind_height since they substantially differ between FlorisModel objects. Please specify 'reference_wind_height' manually."
)

# Construct the merged FLORIS model based on the first entry in fi_list
fi_merged = fm_list[0].copy()
fi_merged.set(
layout_x=x_list,
layout_y=y_list,
turbine_type=turbine_type_list,
reference_wind_height=reference_wind_height,
)

return fi_merged


def reduce_floris_object(fm, turbine_list, copy=False):
"""Reduce a large FLORIS object to a subset selection of wind turbines.
Args:
fm (FlorisModel): FLORIS object.
turbine_list (list, array-like): List of turbine indices which should be maintained.
Returns:
fi_reduced (FlorisModel): The reduced FlorisModel object.
"""

# Copy, if necessary
if copy:
fm_reduced = fm.copy()
else:
fm_reduced = fm

# Get the turbine locations from the floris object
x = np.array(fm.layout_x, dtype=float, copy=True)
y = np.array(fm.layout_y, dtype=float, copy=True)

# Get turbine definitions from floris object
fi_turbine_type = fm.core.farm.turbine_type
if len(fi_turbine_type) == 1:
fi_turbine_type = fi_turbine_type * len(fm.layout_x)
elif not len(fi_turbine_type) == len(fm.layout_x):
raise UserWarning("Incompatible format of turbine_type in FlorisModel.")

# Construct the merged FLORIS model based on the first entry in fi_list
fm_reduced.set(
layout_x=x[turbine_list],
layout_y=y[turbine_list],
turbine_type=list(np.array(fi_turbine_type)[turbine_list]),
)

return fm_reduced


def interpolate_floris_from_df_approx(
df,
df_approx,
Expand Down
19 changes: 0 additions & 19 deletions flasc/utilities/tuner_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,6 @@ def nested_set(dic: Dict[str, Any], keys: List[str], value: Any, idx: Optional[i
dic[keys[-1]] = par_list


def set_fm_param(
fm_in: FlorisModel, param: List[str], value: Any, param_idx: Optional[int] = None
) -> FlorisModel:
"""Set a parameter in a FlorisModel object.
Args:
fm_in (FlorisModel): The FlorisModel object to modify.
param (List[str]): A list of keys to traverse the FlorisModel dictionary.
value (Any): The value to set.
idx (Optional[int], optional): The index to set the value at. Defaults to None.
Returns:
FlorisModel: The modified FlorisModel object.
"""
fi_dict_mod = fm_in.core.as_dict()
nested_set(fi_dict_mod, param, value, param_idx)
return FlorisModel(fi_dict_mod)


def resim_floris(fm_in: FlorisModel, df_scada: pd.DataFrame, yaw_angles: np.array = None):
# Get wind speeds and directions
wind_speeds = df_scada["ws"].values
Expand Down
18 changes: 9 additions & 9 deletions tests/floris_tools_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import numpy as np
import pandas as pd
from floris import FlorisModel

from flasc.utilities.floris_tools import (
add_gaussian_blending_to_floris_approx_table,
calc_floris_approx_table,
get_dependent_turbines_by_wd,
interpolate_floris_from_df_approx,
merge_floris_objects,
)
from flasc.utilities.utilities_examples import load_floris_artificial as load_floris

Expand All @@ -20,20 +20,20 @@ def test_floris_merge(self):
fm_2.set(layout_x=[-500.0, -500.0], layout_y=[0.0, 500.0])

# Check if layouts are merged appropriately
fi_merged = merge_floris_objects([fm_1, fm_2])
self.assertTrue(np.all(fi_merged.layout_x == np.hstack([fm_1.layout_x, fm_2.layout_x])))
self.assertTrue(np.all(fi_merged.layout_y == np.hstack([fm_1.layout_y, fm_2.layout_y])))
fm_merged = FlorisModel.merge_floris_models([fm_1, fm_2])
self.assertTrue(np.all(fm_merged.layout_x == np.hstack([fm_1.layout_x, fm_2.layout_x])))
self.assertTrue(np.all(fm_merged.layout_y == np.hstack([fm_1.layout_y, fm_2.layout_y])))
#
# Check if layouts are merged appropriately
fm_merged = merge_floris_objects([fm_1, fm_2], reference_wind_height=200.0)
fm_merged = FlorisModel.merge_floris_models([fm_1, fm_2], reference_wind_height=200.0)
self.assertTrue(fm_merged.core.flow_field.reference_wind_height == 200.0)

# Also test that we raise a UserWarning if we have two different reference wind heights and
# don't specify a reference_wind_height for the merged object
with self.assertRaises(UserWarning):
# Also test that we raise a ValueError if we have two different reference wind heights and
# don't specify a reference_wind_height for the merged model
with self.assertRaises(ValueError):
fm_1.set(reference_wind_height=90.0)
fm_2.set(reference_wind_height=91.0)
fm_merged = merge_floris_objects([fm_1, fm_2])
fm_merged = FlorisModel.merge_floris_models([fm_1, fm_2])

def test_floris_approx_table(self):
# Load FLORIS object
Expand Down

0 comments on commit 2488643

Please sign in to comment.