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
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,7 @@ vis:
time: [656, 696, 736, 776, 816, 856, 896, 936]
#[600, 620, 640, 660, 680, 700, 720, 740, 760, 780, 800, 820, 840, 860, 880, 900, 920, 940, 960, 980, 1000, 1020, 1040, 1060, 1080, 1100, 1120, 1140, 1160, 1180]
# Master-switch to generate & store flow field data
plot: True
plot: False
# Generates a mountain range like visualization of the wind speed based on the OPs
mountains: True
# Steps between the OPs -> 1 every OP, 5 -> every 5th OP
Expand Down
79 changes: 46 additions & 33 deletions 03_Code/off/off.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# along with this program (see COPYING file). If not, see <https://www.gnu.org/licenses/>.

import os
from concurrent.futures import ThreadPoolExecutor

import logging
lg = logging.getLogger('off')
Expand Down Expand Up @@ -229,57 +230,69 @@ def run_sim(self) -> tuple:

iteration = 0

run_in_parallel = False

def _predict_turbine(idx_tur):
idx, tur = idx_tur

# Plotting flags
if (self.settings_vis["debug"]["effective_wf_layout"] and
t in self.settings_vis["debug"]["time"] and
idx in self.settings_vis["debug"]["iT"]):
self.wake_solver.raise_flag_plot_wakes()

if (self.settings_vis["debug"]["effective_wf_tile"] and
t in self.settings_vis["debug"]["time"]):
grid_points_iT = self.visualizer_ff.vis_get_grid_points_iT(idx)
self.wake_solver.raise_flag_plot_tile(
grid_points_iT[:, 0], grid_points_iT[:, 1],
np.array(self.settings_vis["grid"]["slice_2d_xy"]))

uv_r_loc, uv_op, m_tmp = self.wake_solver.get_measurements(idx, self.wind_farm)
pow_loc = tur.calc_power(util.ot_uv2abs(uv_r_loc[0], uv_r_loc[1]))
m_tmp['power_OFF'] = pow_loc
m_tmp.t_idx = idx
m_tmp['time'] = t
c_tmp = self.controller.get_applied_settings(tur, idx, t)

tile_values = None
if (self.settings_vis["debug"]["effective_wf_tile"] and
t in self.settings_vis["debug"]["time"]):
tile_values = self.wake_solver.get_tile_u().flatten()

return idx, uv_r_loc, pow_loc, uv_op, m_tmp, c_tmp, tile_values

for t in np.arange(self.settings_sim['time start'],
self.settings_sim['time end'] + self.settings_sim['time step'],
self.settings_sim['time step']):
lg.info('Starting time step: %s s.' % t)

# ///////////////////// PREDICT ///////////////////////
# Get wind speeds at the rotor plane and to propagate the OPs
for idx, tur in enumerate(self.wind_farm.turbines):
# Plotting flags
if (self.settings_vis["debug"]["effective_wf_layout"] and
t in self.settings_vis["debug"]["time"] and
idx in self.settings_vis["debug"]["iT"]):
# Plots the wind farm as simulated in the steady state model
self.wake_solver.raise_flag_plot_wakes()

if (self.settings_vis["debug"]["effective_wf_tile"] and
t in self.settings_vis["debug"]["time"]):
# Set flag to calculate wind speed in wake model at grid points belonging to turbine iT
grid_points_iT = self.visualizer_ff.vis_get_grid_points_iT(idx)
self.wake_solver.raise_flag_plot_tile(
grid_points_iT[:,0], grid_points_iT[:,1],
np.array(self.settings_vis["grid"]["slice_2d_xy"]))



# for turbine 'tur': Run wake solver and retrieve measurements from the wake model
uv_r[idx, :], uv_op, m_tmp = self.wake_solver.get_measurements(idx, self.wind_farm)

# Calculate the power generated
pow_t[idx, :] = tur.calc_power(util.ot_uv2abs(uv_r[idx, 0], uv_r[idx, 1]))
m_tmp['power_OFF'] = pow_t[idx, :]
turbine_items = list(enumerate(self.wind_farm.turbines))
if run_in_parallel and len(turbine_items) > 1:
print(f"Running prediction for {len(turbine_items)} turbines in parallel using {min(os.cpu_count() or 1, len(turbine_items))} threads.")
with ThreadPoolExecutor(max_workers=min(os.cpu_count() or 1, len(turbine_items))) as executor:
results = list(executor.map(_predict_turbine, turbine_items))
else:
results = [_predict_turbine(item) for item in turbine_items]

# Add turbine index & timestamp to data
m_tmp.t_idx = idx
m_tmp['time'] = t
for idx, uv_r_loc, pow_loc, uv_op, m_tmp, c_tmp, tile_values in results:
uv_r[idx, :] = uv_r_loc
pow_t[idx, :] = pow_loc

# Append turbine measurements to general measurement data
measurements = pd.concat([measurements, m_tmp], ignore_index=True)

# Set propagation speed of the OPs of the turbine 'tur'
tur.observation_points.set_op_propagation_speed(uv_op)
self.wind_farm.turbines[idx].observation_points.set_op_propagation_speed(uv_op)

# Store turbine state applied in controller
c_tmp = self.controller.get_applied_settings(tur, idx, t)
control_applied = pd.concat([control_applied, c_tmp], ignore_index=True)

# Store flow field points
if (self.settings_vis["debug"]["effective_wf_tile"] and
t in self.settings_vis["debug"]["time"]):
self.visualizer_ff.vis_store_u_values(
self.wake_solver.get_tile_u().flatten(), idx)
if tile_values is not None:
self.visualizer_ff.vis_store_u_values(tile_values, idx)


lg.info('Rotor wind speed of all turbines:')
Expand Down
12 changes: 12 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@ name = "off"
version = "0.0.1"
dependencies = ["numpy"]

[project.optional-dependencies]
test = [
"pytest>=8",
]

[tool.setuptools.package-dir]
"" = "src"

[tool.setuptools.packages.find]
where = ["src"]
include = ["off*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-config --strict-markers"
markers = [
"compatibility: tests for automatic OFF module compatibility checking",
]
43 changes: 34 additions & 9 deletions src/off/OFFModule.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,25 @@ def describe_compatibility(cls) -> str:
# return report

def check_compatibility(modules) -> list[tuple]:
provided: dict[tuple[str, str], CompatibilityLevel] = {}
compat_infos: list[tuple[type[OFFModule], OFFCompatibility]] = []
""" Checks compatibility between given modules. Returns a list of component requirements
that are not met by **all** modules of a given type.

For example, a given implementation of WakeModel requires a TurbineModel with implemented `obs_power_curve`
method with requirement level `FULL`. So all implementations in `modules` with TurbineModel as basetype need
to have `obs_power_curve` implemented with the same level of support.

Args:
modules (nd.array[OFFModule]): List of OFFModules

Raises:
TypeError: If one of the modules is not derived from OFFModule

Returns:
list[tuple]: List of unmet requirements, in the format:
(Module, Module Type Required From, Required Component, Required Minimum Compatibility Level, Supported Compatability Level by Component)
"""
providers: dict[str, list[OFFCompatibility]] = {}
compat_infos: list[OFFCompatibility] = []

for module in modules:
if inspect.isclass(module) and issubclass(module, OFFModule):
Expand All @@ -160,19 +177,27 @@ def check_compatibility(modules) -> list[tuple]:
if compat is None:
continue

compat_infos.append((module_cls, compat))
compat_infos.append(compat)
provider_types = {compat.module_type}
for base in module_cls.__mro__:
if issubclass(base, OFFModule) and base is not OFFModule:
for name, level in compat.provides.items():
key = (base.__name__, name)
current = provided.get(key, CompatibilityLevel.NONE)
provided[key] = max(current, level, key=lambda item: item.value)
provider_types.add(base.__name__)

for provider_type in provider_types:
providers.setdefault(provider_type, []).append(compat)

unmet = []
for module_cls, compat in compat_infos:
for compat in compat_infos:
for module_type, funcs in compat.requires.items():
for name, min_level in funcs.items():
have = provided.get((module_type, name), CompatibilityLevel.NONE)
have = min(
(
provider.provides.get(name, CompatibilityLevel.NONE)
for provider in providers.get(module_type, [])
),
key=lambda item: item.value,
default=CompatibilityLevel.NONE,
)
if have.value < min_level.value:
unmet.append((compat.module_type, module_type, name, min_level, have))
return unmet
Loading