Skip to content
Draft
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
9 changes: 8 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Release notes

## Version 0.1.0 (2026-05-28)
## Version 0.1.0 (2026-06-05)

### Extended WindFarmParameters

* Extended `WindFarmParameters` to include an optional `turbine_power_curve` argument, which allows users to specify a custom power curve for the wind turbine.
The `turbine_power_curve` is expected to be a `DataFrame` with columns `wind_speed` and `power_curve`, where `wind_speed` represents the wind speed values (in m/s) and `power_curve` represents the corresponding power output of the turbine at those wind speeds.
* The parameters `sigma` and `wakeloss` were also added to `WindFarmParameters` to enable all options of the `wind_power_timeseries` repository.
* The `shape` parameter was corrected from a string to a float.

### Added WindFarmParameters

Expand Down
1 change: 1 addition & 0 deletions docs/src/library/internals/methods-EMLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ EMLI.pvgis_profile
EMLI.get_pvgis_data
EMLI.get_met_data
EMLI.heat_demand_profile
EMLI.to_pandas_series
```

## [Macros](@id lib-int-mac-util)
Expand Down
113 changes: 103 additions & 10 deletions src/datastructures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,13 @@ end
lat::Real,
lon::Real,
turbine_height::Real;
orientation = missing,
shape = missing,
orientation::Union{Real, Nothing} = nothing,
shape::Union{Real, Nothing} = nothing,
method::String = "Ninja",
source::String = "NORA3",
turbine_power_curve::Union{DataFrame, String, Nothing} = nothing,
sigma::Union{Real, Nothing} = nothing,
wakeloss::Union{Real, Nothing} = nothing,
)

A structure to hold wind farm parameters and metadata for wind power time series generation.
Expand All @@ -113,13 +116,28 @@ A structure to hold wind farm parameters and metadata for wind power time series
- **`lat::Real`** is the latitude of the location in decimal degrees (e.g., `52.5` for 52°30′ N, `-33.75` for 33°45′ S).
- **`lon::Real`** is the longitude of the location in decimal degrees (e.g., `13.5` for 13°30′ E, `-122.25` for 122°15′ W).
- **`turbine_height::Real`** is the height of the wind turbines in meters.
- **`orientation`** is the orientation of the wind farm (default: `missing`).
- **`shape`** is the shape of the wind farm (default: `missing`).
- **`orientation::Union{Real, Nothing}`** is the orientation of the wind farm (default: `nothing`).
It is given in degrees from north, typically aligned with dominant wind direction (e.g., 0 for north,
90 for east, 180 for south, 270 for west). Must be in the interval `[0, 360)` if provided.
- **`shape::Union{Real, Nothing}`** is the aspect ratio (must be positive if provided), number of columns
(i.e. number of turbines in a row) divided by number of rows of turbines (default: `nothing`).
- **`method::String`** is the chosen method for data retrieval. The user can choose between the
strings "Ninja", "Tradewind_offshore", "Tradewind_upland", and "Tradewind_lowland".
The default value is "Ninja".
- **`source::String`** is the data source for wind data. The user can choose between the strings
"NORA3" and "ERA5". The default value is "NORA3".
- **`turbine_power_curve::Union{String, DataFrame, Nothing}`** optional power curve input
(e.g. curve name or dataset-based interpolated curve), default: `nothing`.
For `String` input, available options are: "VestasV80", "Tradewind_lowland", "Tradewind_upland",
"Tradewind_offshore", "Tradewind_offshore_2030", "IEA_15MW_240_RWT", "IEA_10MW_198_RWT",
"NREL_5MW_126_RWT", and "DTU_10MW_178_RWT".
For `DataFrame` input, the `DataFrame` must contain two columns: "wind_speed" and "power_curve", where
"wind_speed" is the wind speed in m/s and "power_curve" is the normalized power output (both must be non-negative)
corresponding to each wind speed (values are normalized by default by the `wind_power_timeseries` module).
Values are set to zero for wind speeds outside the range of the provided power curve.
Must have at least 2 rows to allow for interpolation.
- **`sigma::Union{Real, Nothing}`** optional Ninja smoothing parameter, default: `nothing`.
- **`wakeloss::Union{Real, Nothing}`** optional Ninja wakeloss parameter, default: `nothing`.

!!! note "Key word argument in constructors"
If not all fields with default values are provided, the user must use the keyword arguments.
Expand All @@ -129,19 +147,26 @@ struct WindFarmParameters <: AbstractParameters
lat::Real
lon::Real
turbine_height::Real
orientation::Any
shape::Any
orientation::Union{Real,Nothing}
shape::Union{Real,Nothing}
method::String
source::String
turbine_power_curve::Union{String,DataFrame,Nothing}
sigma::Union{Real,Nothing}
wakeloss::Union{Real,Nothing}

function WindFarmParameters(
id::String,
lat::Real,
lon::Real,
turbine_height::Real,
orientation::Any,
shape::Any,
orientation::Union{Real,Nothing},
shape::Union{Real,Nothing},
method::String,
source::String,
turbine_power_curve::Union{String,DataFrame,Nothing},
sigma::Union{Real,Nothing},
wakeloss::Union{Real,Nothing},
)
errors = String[]
if lat < -90 || lat > 90
Expand All @@ -164,6 +189,59 @@ struct WindFarmParameters <: AbstractParameters
push!(errors, "source must be one of $(sources).")
end

if orientation isa Real && (orientation < 0 || orientation >= 360)
push!(errors, "orientation must be in [0, 360) or `nothing`.")
end

if shape isa Real && shape <= 0
push!(errors, "shape must be a positive Real or `nothing`.")
end

if turbine_power_curve isa DataFrame
Comment thread
Zetison marked this conversation as resolved.
required_columns = ["wind_speed", "power_curve"]
if nrow(turbine_power_curve) < 2
push!(errors, "turbine_power_curve DataFrame must have at least 2 rows.")
end
missing_columns =
[col for col ∈ required_columns if !(col in names(turbine_power_curve))]
if !isempty(missing_columns)
Comment thread
Zetison marked this conversation as resolved.
push!(
errors,
"turbine_power_curve DataFrame must contain columns: $(required_columns). Missing: $(missing_columns).",
)
end

for col ∈ required_columns
if col ∈ names(turbine_power_curve)
vals = turbine_power_curve[!, col]
if any(ismissing, vals) || any(x -> !(x isa Real), vals) ||
any(x -> x < 0, vals)
push!(
errors,
"turbine_power_curve '$col' values must be non-negative Reals.",
)
end
end
end
elseif turbine_power_curve isa String
valid_curves =
("VestasV80", "Tradewind_lowland", "Tradewind_upland", "Tradewind_offshore",
"Tradewind_offshore_2030", "IEA_15MW_240_RWT", "IEA_10MW_198_RWT",
"NREL_5MW_126_RWT",
"DTU_10MW_178_RWT")
if !(turbine_power_curve in valid_curves)
push!(errors, "turbine_power_curve string must be one of $(valid_curves).")
end
end

if sigma isa Real && sigma < 0
push!(errors, "sigma must be a non-negative Real or `nothing`.")
end

if wakeloss isa Real && (wakeloss < 0 || wakeloss > 1)
push!(errors, "wakeloss must be a Real in [0, 1] or `nothing`.")
Comment thread
Zetison marked this conversation as resolved.
end

if !isempty(errors)
throw(ArgumentError(join(errors, " ")))
end
Expand All @@ -177,6 +255,9 @@ struct WindFarmParameters <: AbstractParameters
shape,
method,
source,
turbine_power_curve,
sigma,
wakeloss,
)
end
end
Expand All @@ -185,10 +266,13 @@ function WindFarmParameters(
lat::Real,
lon::Real,
turbine_height::Real;
orientation = missing,
shape = missing,
orientation::Union{Real,Nothing} = nothing,
shape::Union{Real,Nothing} = nothing,
method::String = "Ninja",
source::String = "NORA3",
turbine_power_curve::Union{String,DataFrame,Nothing} = nothing,
sigma::Union{Real,Nothing} = nothing,
wakeloss::Union{Real,Nothing} = nothing,
)
return WindFarmParameters(
id,
Expand All @@ -199,6 +283,9 @@ function WindFarmParameters(
shape,
method,
source,
turbine_power_curve,
sigma,
wakeloss,
)
end

Expand Down Expand Up @@ -303,6 +390,9 @@ function WindPower(
data::Vector{<:ExtensionData} = ExtensionData[],
data_path::String = "",
)
power_curve = wind_params.turbine_power_curve
turbine_power_curve =
power_curve isa DataFrame ? to_pandas_series(power_curve) : power_curve
power = call_python_function(
"wind_power_timeseries",
"sample.wind_power";
Expand All @@ -312,6 +402,9 @@ function WindPower(
method = wind_params.method,
data_path = data_path,
source = wind_params.source,
turbine_power_curve = turbine_power_curve,
sigma = wind_params.sigma,
wakeloss = wind_params.wakeloss,
)
profile = OperationalProfile(power)

Expand Down
9 changes: 9 additions & 0 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -509,3 +509,12 @@ function get_met_data(
end
return df
end

"""
to_pandas_series(df::DataFrame)

Convert a power-curve DataFrame to a Pandas Series.
The DataFrame must contain columns "wind_speed" (used as the Series index) and "power_curve" (used as the Series values).
"""
Comment thread
Zetison marked this conversation as resolved.
to_pandas_series(df::DataFrame) =
pyimport("pandas").Series(df[!, "power_curve"], index = df[!, "wind_speed"])
2 changes: 1 addition & 1 deletion submodules/wind_power_timeseries
Submodule wind_power_timeseries updated from 515175 to 2fe585
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
EnergyModelsHeat = "ad1b8b27-e232-4da9-b498-bea9c19a30d7"
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ using Dates
using JSON
using HiGHS
using JuMP
using DataFrames

const EMLI = EnergyModelsLanguageInterfaces
const EMB = EnergyModelsBase
Expand Down
Loading