Skip to content
Closed
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
68 changes: 61 additions & 7 deletions docs/wind.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
# Wind Farm Components

## Wind_MesoToPower
Hercules provides four wind farm simulation components that differ in their approach to wake modeling and data sources. The first three components support both simple filter-based turbine models and 1-degree-of-freedom (1-DOF) turbine dynamics, while the fourth component uses SCADA power data directly.

Wind_MesoToPower is a comprehensive wind farm simulator that focuses on meso-scale phenomena by applying a separate wind speed time signal to each turbine model derived from data. It combines FLORIS wake modeling with detailed turbine dynamics for long-term wind farm performance analysis.
## Wind_MesoToPower (Dynamic Wake Model)

Wind_MesoToPower is a comprehensive wind farm simulator that computes wake effects dynamically at each time step (or at intervals specified by `floris_update_time_s`). It focuses on meso-scale phenomena by applying a separate wind speed time signal to each turbine model derived from data. This model combines FLORIS wake modeling with detailed turbine dynamics for long-term wind farm performance analysis.

**Use this model when:**
- Turbines have individual power setpoints or non-uniform operation
- Precise wake modeling is required for each control action
- Turbines may be partially derated or individually controlled

## Wind_MesoToPowerPrecomFloris (Precomputed Wake Model)

Wind_MesoToPowerPrecomFloris is an optimized variant that pre-computes all FLORIS wake deficits at initialization for improved simulation performance. This approach provides significant speed improvements while conservatively assuming wakes are always based on nominal operation.

**Use this model when:**
- Not investigating wakes of derated turbines or wake losses can be conservatively estimated.


## Wind_MesoToPowerNoAddedWakes (No Wake Modeling)

Wind_MesoToPowerNoAddedWakes assumes that wake effects are already included in the input wind data and performs no wake modeling during simulation. Model is appropriate for using SCADA data of operational farm since wake losses already included in data.


## WindFarmSCADAPower (SCADA Power Data)

WindFarmSCADAPower uses SCADA power measurements directly rather than computing power from wind speeds and turbine models. This component applies a filter to the SCADA power data to simulate turbine response dynamics and respects power setpoint constraints.

## Wind_MesoToPowerPrecomFloris

Wind_MesoToPowerPrecomFloris is an optimized variant of Wind_MesoToPower that pre-computes FLORIS wake deficits for improved simulation performance. This approach trades some accuracy for significant speed improvements in specific operating scenarios.

## Overview

Both wind farm components integrate FLORIS for wake effects with individual turbine models to simulate wind farm behavior over extended periods. They support both simple filter-based turbine models and 1-degree-of-freedom (1-DOF) turbine dynamics.
The first three wind farm components apply wind speed time signals to turbine models to simulate wind farm behavior over extended periods. They differ only in how wake effects are computed and applied. The WindFarmSCADAPower component uses a fundamentally different approach by using actual SCADA power measurements as input.

### Precomputed FLORIS Approach

Expand Down Expand Up @@ -45,19 +67,49 @@ Required parameters for Wind_MesoToPowerPrecomFloris:
- `floris_update_time_s`: Determines the cadence of wake precomputation. At each cadence tick, the last `floris_update_time_s` seconds are averaged and used to evaluate FLORIS. The computed wake deficits are then applied until the next cadence tick.
- `log_channels`: List of output channels to log. See [Logging Configuration](#logging-configuration) section below for details.

### Wind_MesoToPowerNoAddedWakes Specific Parameters

Required parameters for Wind_MesoToPowerNoAddedWakes:
- `floris_update_time_s`: Required for interface consistency but not used (no FLORIS calculations performed)
- `floris_input_file`: Still required to read turbine power curve and properties
- `log_channels`: List of output channels to log. See [Logging Configuration](#logging-configuration) section below for details.

### WindFarmSCADAPower Specific Parameters

Required parameters for WindFarmSCADAPower:
- `scada_filename`: Path to SCADA data file (CSV, pickle, or feather format)
- `turbine_file_name`: Turbine model configuration (for filter parameters)
- `log_channels`: List of output channels to log. See [Logging Configuration](#logging-configuration) section below for details.

**SCADA File Format:**

The SCADA file must contain the following columns:
- `time_utc`: Timestamps in UTC (ISO 8601 format or parseable datetime strings)
- `wd_mean`: Mean wind direction in degrees
- `pow_###`: Power output for each turbine (e.g., `pow_000`, `pow_001`, `pow_002`)

Optional columns:
- `ws_###`: Wind speed for each turbine (e.g., `ws_000`, `ws_001`, `ws_002`)
- `ws_mean`: Mean wind speed (used if individual turbine speeds not provided)
- `ti_###`: Turbulence intensity for each turbine (defaults to 0.08 if not provided)

The number of turbines and rated power are automatically inferred from the SCADA data.

## Turbine Models

**Note:** WindFarmSCADAPower uses only the filter model for power smoothing, as power values come directly from SCADA data rather than being computed from wind speeds.

### Filter Model
Simple first-order filter for power output smoothing with configurable time constants.

### 1-DOF Model
Advanced model with rotor dynamics, pitch control, and generator torque control.
Advanced model with rotor dynamics, pitch control, and generator torque control. Not applicable to WindFarmSCADAPower.

## Outputs

### Common Outputs

Both components provide these outputs in the h_dict at each simulation step:
All four components provide these outputs in the h_dict at each simulation step:
- `power`: Total wind farm power (kW)
- `turbine_powers`: Individual turbine power outputs (array, kW)
- `turbine_power_setpoints`: Current power setpoint values (array, kW)
Expand All @@ -67,6 +119,8 @@ Both components provide these outputs in the h_dict at each simulation step:
- `wind_speeds_background`: Per-turbine background wind speeds (array, m/s)
- `wind_speeds_withwakes`: Per-turbine with-wakes wind speeds (array, m/s)

**Note for Wind_MesoToPowerNoAddedWakes and WindFarmSCADAPower:** In these models (no wake modeling), `wind_speeds_withwakes` equals `wind_speeds_background` and `wind_speed_mean_withwakes` equals `wind_speed_mean_background`.

## Logging Configuration

The `log_channels` parameter controls which outputs are written to the HDF5 output file. This is a list of channel names. The `power` channel is always logged, even if not explicitly specified.
Expand Down
31 changes: 31 additions & 0 deletions examples/02c_wind_farm_realistic_inflow_direct/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Example 02c: Wind Farm Realistic Inflow (Direct - No Wake Modeling)

## Description

This example demonstrates the `Wind_MesoToPowerNoAddedWakes` wake model, which assumes that wake effects are already included in the input wind data and performs no additional wake modeling.

The `Wind_MesoToPowerNoAddedWakes` component type uses `wake_model="no_added_wakes"` internally, which means:
- No FLORIS calculations are performed during the simulation (only at initialization to read turbine properties)
- `wind_speeds_withwakes` equals `wind_speeds_background` at all times
- Wake deficits are always zero
- Turbine dynamics (filter model or DOF1 model) still operate normally

This example automatically generates the necessary input files in the centralized `examples/inputs/` folder when first run.

## Running

To run the example, execute the following command in the terminal:

```bash
python hercules_runscript.py
```

## Outputs

To plot the outputs run the following command in the terminal:

```bash
python plot_outputs.py
```


42 changes: 42 additions & 0 deletions examples/02c_wind_farm_realistic_inflow_direct/hercules_input.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Input YAML for hercules

# Name
name: example_02c

###
# Describe this simulation setup
description: Wind Only Realistic Inflow (Direct - No Wake Modeling)

dt: 1.0
starttime_utc: "2024-06-24T16:59:08Z" # Jun 24, 2024 16:59:08 UTC (Zulu time)
endtime_utc: "2024-06-26T16:59:00Z" # ≈48 hours later (Jun 26, 2024 16:59:00 UTC)
verbose: False

plant:
interconnect_limit: 45000 # kW

wind_farm:

component_type: Wind_MesoToPowerNoAddedWakes
floris_input_file: ../inputs/floris_input_large.yaml
wind_input_filename: ../inputs/wind_input_large.ftr
turbine_file_name: ../inputs/turbine_filter_model.yaml
log_file_name: outputs/log_wind_sim.log
log_channels:
- power
- wind_speed_mean_background
- wind_speed_mean_withwakes
- wind_direction_mean
- turbine_powers
- wind_speeds_withwakes
- wind_speeds_background
- turbine_power_setpoints
floris_update_time_s: 300.0 # Not used but kept for interface consistency
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Requiring floris_update_time_s for the Wind_MesoToPowerNoAddedWakes component type when it's explicitly not used creates unnecessary configuration burden. Consider making this parameter optional for wake models that don't use it, or document in the code why this design decision was made for interface consistency.

Suggested change
floris_update_time_s: 300.0 # Not used but kept for interface consistency
# Note: floris_update_time_s is not required for Wind_MesoToPowerNoAddedWakes and is intentionally omitted.

Copilot uses AI. Check for mistakes.


controller:





Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import numpy as np
from hercules.hercules_model import HerculesModel
from hercules.utilities_examples import ensure_example_inputs_exist, prepare_output_directory

prepare_output_directory()

# Ensure example inputs exist
ensure_example_inputs_exist()

# Initialize the Hercules model
hmodel = HerculesModel("hercules_input.yaml")


# Define a simple controller that sets all power setpoints to full rating
class ControllerFullRating:
"""A simple controller that sets all turbines to full rating.

This controller is appropriate for the direct wake model where
wake effects are already included in the input wind data.
"""

def __init__(self, h_dict):
"""Initialize the controller.

Args:
h_dict (dict): The hercules input dictionary.
"""
pass

def step(self, h_dict):
"""Execute one control step.

Args:
h_dict (dict): The hercules input dictionary.

Returns:
dict: The updated hercules input dictionary.
"""
# Set all turbines to full rating
h_dict["wind_farm"]["turbine_power_setpoints"] = 5000 * np.ones(
h_dict["wind_farm"]["n_turbines"]
)

return h_dict


# Assign the controller to the Hercules model
hmodel.assign_controller(ControllerFullRating(hmodel.h_dict))

# Run the simulation
hmodel.run()

hmodel.logger.info("Process completed successfully")
79 changes: 79 additions & 0 deletions examples/02c_wind_farm_realistic_inflow_direct/plot_outputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Plot the outputs of the simulation

import matplotlib.pyplot as plt
from hercules import HerculesOutput

# Read the Hercules output file using HerculesOutput
ho = HerculesOutput("outputs/hercules_output.h5")

# Print metadata information
print("Simulation Metadata:")
ho.print_metadata()
print()

# Create a shortcut to the dataframe
df = ho.df

# Limit to the first 4 hours
df = df.iloc[: 3600 * 4]

# Set number of turbines
turbines_to_plot = [0, 8]

# Define a consistent color map with 9
colors = [
"tab:blue",
"tab:orange",
"tab:green",
"tab:red",
"tab:purple",
"tab:brown",
"tab:pink",
"tab:gray",
"tab:olive",
]

fig, axarr = plt.subplots(2, 1, sharex=True)

# Plot the wind speeds
ax = axarr[0]
for t_idx in turbines_to_plot:
ax.plot(
df["time"],
df[f"wind_farm.wind_speeds_background.{t_idx:03}"],
label=f"Wind Speed {t_idx}",
color=colors[t_idx],
)

# Note: In direct mode, wind_speeds_withwakes == wind_speeds_background

# Plot the mean wind speed
ax.plot(
df["time"],
df["wind_farm.wind_speed_mean_background"],
label="Mean Wind Speed",
color="black",
lw=2,
)

ax.grid(True)
ax.legend()
ax.set_ylabel("Wind Speed [m/s]")
ax.set_title("Direct Wake Model (No Wake Modeling)")


# Plot the power
ax = axarr[1]
for t_idx in turbines_to_plot:
ax.plot(
df["time"],
df[f"wind_farm.turbine_powers.{t_idx:03}"],
label=f"Turbine {t_idx}",
color=colors[t_idx],
)

ax.grid(True)
ax.legend()
ax.set_xlabel("Time [s]")
ax.set_ylabel("Power [kW]")
plt.show()
40 changes: 27 additions & 13 deletions hercules/hybrid_plant.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from hercules.plant_components.battery_simple import BatterySimple
from hercules.plant_components.electrolyzer_plant import ElectrolyzerPlant
from hercules.plant_components.solar_pysam_pvwatts import SolarPySAMPVWatts
from hercules.plant_components.wind_meso_to_power import Wind_MesoToPower
from hercules.plant_components.wind_meso_to_power_precom_floris import Wind_MesoToPowerPrecomFloris
from hercules.plant_components.wind_farm import WindFarm
from hercules.plant_components.wind_farm_scada_power import WindFarmSCADAPower
from hercules.utilities import get_available_component_names, get_available_generator_names


Expand Down Expand Up @@ -95,25 +95,39 @@ def get_plant_component(self, component_name, h_dict):
Raises:
Exception: If the component_type is not recognized.
"""
if h_dict[component_name]["component_type"] == "Wind_MesoToPower":
return Wind_MesoToPower(h_dict)

if h_dict[component_name]["component_type"] == "Wind_MesoToPowerPrecomFloris":
return Wind_MesoToPowerPrecomFloris(h_dict)

if h_dict[component_name]["component_type"] == "SolarPySAMPVWatts":
component_type = h_dict[component_name]["component_type"]

# Handle wind farm component types with unified WindFarm class
if component_type in [
"Wind_MesoToPower",
"Wind_MesoToPowerPrecomFloris",
"Wind_MesoToPowerNoAddedWakes",
]:
# Map component_type to wake_model
wake_model_map = {
"Wind_MesoToPower": "dynamic",
"Wind_MesoToPowerPrecomFloris": "precomputed",
"Wind_MesoToPowerNoAddedWakes": "no_added_wakes",
}
wake_model = wake_model_map[component_type]
return WindFarm(h_dict, wake_model=wake_model)

if component_type == "WindFarmSCADAPower":
return WindFarmSCADAPower(h_dict)

if component_type == "SolarPySAMPVWatts":
return SolarPySAMPVWatts(h_dict)

if h_dict[component_name]["component_type"] == "BatteryLithiumIon":
if component_type == "BatteryLithiumIon":
return BatteryLithiumIon(h_dict)

if h_dict[component_name]["component_type"] == "BatterySimple":
if component_type == "BatterySimple":
return BatterySimple(h_dict)

if h_dict[component_name]["component_type"] == "ElectrolyzerPlant":
if component_type == "ElectrolyzerPlant":
return ElectrolyzerPlant(h_dict)

raise Exception("Unknown component_type: ", h_dict[component_name]["component_type"])
raise Exception("Unknown component_type: ", component_type)

def step(self, h_dict):
"""Execute one simulation step for all plant components.
Expand Down
Loading