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

## Version 0.10.2 (2026-06-09)

### Bug fixes

* Fixed a bug for using `StrategicProfile` in the field `opex_fixed` of a node and `TwoLevelTree` as time structure.
* Fixed a bug for using `StrategicProfile` in the field `emission_limit` of a modeltype and `TwoLevelTree` as time structure.
* Fixed bug in function `check_strategic_profile` when utilizing `StrategicStochasticProfile`.

## Version 0.10.1 (2026-04-14)

### Minor updates
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "EnergyModelsBase"
uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
authors = ["Lars Hellemo <Lars.Hellemo@sintef.no>, Julian Straus <Julian.Straus@sintef.no>"]
version = "0.10.1"
version = "0.10.2"

[deps]
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
Expand Down
34 changes: 13 additions & 21 deletions src/checks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,6 @@ Checks the `modeltype` .
"""
function check_model(case, modeltype::EnergyModel, check_timeprofiles::Bool)
𝒯 = get_time_struct(case)
𝒯ᴵⁿᵛ = strategic_periods(𝒯)

# Check for inclusion of all emission resources
for p ∈ get_products(case)
Expand All @@ -310,27 +309,21 @@ function check_model(case, modeltype::EnergyModel, check_timeprofiles::Bool)
end

for p ∈ keys(emission_limit(modeltype))
em_limit = emission_limit(modeltype, p)
# Check for the strategic periods
if isa(em_limit, StrategicProfile) && check_timeprofiles
@assert_or_log(
length(em_limit.vals) == length(𝒯ᴵⁿᵛ),
"The timeprofile provided for resource `" *
string(p) *
"` in the field " *
"`emission_limit` does not match the strategic structure."
)
end

# Check for potential indexing problems
em_limit = emission_limit(modeltype, p)
message =
"are not allowed for the resource `" *
string(p) *
"` in the dictionary " *
"`emission_limit`."
check_strategic_profile(em_limit, message)

# Check for the strategic periods
check_timeprofiles || continue
check_profile("emission_limit[" * string(p) * "]", em_limit, 𝒯)
end

# Check for the strategic periods
for p ∈ keys(emission_price(modeltype))
em_price = emission_price(modeltype, p)
check_timeprofiles || continue
Expand Down Expand Up @@ -662,6 +655,13 @@ function check_strategic_profile(time_profile::TimeProfile, message::String)
for l1_profile ∈ time_profile.vals
sub_msg = "in strategic profiles " * message
bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp)
!bool_sp && break
end
elseif isa(time_profile, StrategicStochasticProfile)
for sp_array ∈ time_profile.vals, l1_profile ∈ sp_array
sub_msg = "in strategic stochastic profiles " * message
bool_sp = check_strat_sub_profile(l1_profile, sub_msg, bool_sp)
!bool_sp && break
end
end

Expand Down Expand Up @@ -1017,14 +1017,6 @@ returns a `TimeProfile`.
periods.
"""
function check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles::Bool)
if isa(opex_fixed(n), StrategicProfile) && check_timeprofiles
@assert_or_log(
length(opex_fixed(n).vals) == length(𝒯ᴵⁿᵛ),
"The timeprofile provided for the field `opex_fixed` does not match the " *
"strategic structure."
)
end

# Check for potential indexing problems
message = "are not allowed for the field `opex_fixed`."
bool_sp = check_strategic_profile(opex_fixed(n), message)
Expand Down
43 changes: 30 additions & 13 deletions test/test_checks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,9 @@ end
StrategicProfile([OperationalProfile([5])]),
StrategicProfile([ScenarioProfile([5])]),
StrategicProfile([RepresentativeProfile([5])]),
StrategicStochasticProfile([[OperationalProfile([5])]]),
StrategicStochasticProfile([[ScenarioProfile([5])]]),
StrategicStochasticProfile([[RepresentativeProfile([5])]]),
]
for tp ∈ profiles
@test_throws AssertionError EMB.check_strategic_profile(tp, "")
Expand Down Expand Up @@ -514,26 +517,24 @@ end
end

@testset "Checks - Nodes" begin

# Resources used in the checks
NG = ResourceEmit("NG", 0.2)
Power = ResourceCarrier("Power", 0.0)
CO2 = ResourceEmit("CO2", 1.0)
aux = ResourceCarrier("aux", 0.0)

# Function for setting up the system for testing `Sink` and `Source`
function simple_graph(;
function check_graph_src_snk(;
src_cap::TimeProfile = FixedProfile(10),
src_opex_var::TimeProfile = FixedProfile(10),
src_opex_fixed::TimeProfile = FixedProfile(0),
src_output::Dict = Dict(Power => 1),
snk_cap::TimeProfile = OperationalProfile([6, 8, 10, 6, 8]),
snk_pen::Dict = Dict(:surplus => FixedProfile(4), :deficit => FixedProfile(10)),
snk_input::Dict = Dict(Power => 1),
T = TwoLevel(2, 2, SimpleTimes(5, 2); op_per_strat = 10),
)
resources = [Power, CO2]
ops = SimpleTimes(5, 2)
T = TwoLevel(2, 2, ops; op_per_strat = 10)

source =
RefSource(
Expand Down Expand Up @@ -566,39 +567,55 @@ end
# Sink used in the analysis
# Test that a wrong capacity is caught by the checks.
src_cap = FixedProfile(-4)
@test_throws AssertionError simple_graph(;src_cap)
@test_throws AssertionError check_graph_src_snk(;src_cap)

# Test that a wrong output dictionary is caught by the checks.
src_output = Dict(Power => -1)
@test_throws AssertionError simple_graph(;src_output)
@test_throws AssertionError check_graph_src_snk(;src_output)

# Test that a wrong fixed OPEX is caught by the checks.
src_opex_fixed = FixedProfile(-5)
@test_throws AssertionError simple_graph(;src_opex_fixed)
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed)

# Test that a wrong profile for fixed OPEX is caught by the checks.
# Test that a wrong profile for fixed OPEX is caught by the checks both with a
# `TwoLevelTree` and `TwoLevel` time structure
# - check_fixed_opex(n::Node, 𝒯ᴵⁿᵛ, check_timeprofiles::Bool)
src_opex_fixed = StrategicProfile([1])
@test_throws AssertionError simple_graph(;src_opex_fixed)
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed)
src_opex_fixed = OperationalProfile([1])
@test_throws AssertionError simple_graph(;src_opex_fixed)
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed)
src_opex_fixed = StrategicProfile([OperationalProfile([1]), OperationalProfile([1])])
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed)

T = TwoLevelTree(2, [2], SimpleTimes(5, 1); op_per_strat = 10.)
src_opex_fixed = StrategicProfile([1])
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T)
src_opex_fixed = StrategicProfile([1, 2, 3])
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T)
src_opex_fixed = OperationalProfile([1])
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T)
src_opex_fixed = StrategicStochasticProfile([
[OperationalProfile([1])],
[OperationalProfile([1]), OperationalProfile([1])],
])
@test_throws AssertionError check_graph_src_snk(; src_opex_fixed, T)
end

# Test that the fields of a Sink are correctly checked
# - check_node(n::Sink, 𝒯, modeltype::EnergyModel)
@testset "Sink" begin
# Test that an inconsistent Sink.penalty dictionaries is caught by the checks.
snk_pen = Dict(:surplus => FixedProfile(4), :def => FixedProfile(2))
@test_throws AssertionError simple_graph(;snk_pen)
@test_throws AssertionError check_graph_src_snk(;snk_pen)

# The penalties in this Sink node lead to an infeasible optimum. Test that the
# checks forbids it.
snk_pen = Dict(:surplus => FixedProfile(-4), :deficit => FixedProfile(2))
@test_throws AssertionError simple_graph(;snk_pen)
@test_throws AssertionError check_graph_src_snk(;snk_pen)

# Check that a wrong capacity in a sink is caught by the checks.
snk_cap = OperationalProfile(-[6, 8, 10, 6, 8])
@test_throws AssertionError simple_graph(;snk_cap)
@test_throws AssertionError check_graph_src_snk(;snk_cap)
end

# Function for setting up the system for testing a `NetworkNode`
Expand Down
6 changes: 3 additions & 3 deletions test/test_investments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,9 @@ using EnergyModelsInvestments
# Test results
# (-724 compared to 0.5.x as RefStorage as emission source does not require a charge
# capacity any longer in 0.7.x)
# (-10736 compared to 0.9.x due to the potential of early retirment)
@test round(objective_value(m)) ≈ -313360.0
# (-10736 compared to 0.9.x due to the potential of early retirement)
# (-16689 compared to 10.1.x due to the bugfix 0.9.1 in EMI)
@test round(objective_value(m)) ≈ -296671.0

# Test that investments are happening
𝒯ᴵⁿᵛ = strategic_periods(get_time_struct(case))
Expand All @@ -247,7 +248,6 @@ using EnergyModelsInvestments
@test sum(
sum(value.(m[:stor_charge_add][n, t_inv]) > 0 for n ∈ 𝒩ᶜʰᵃʳᵍᵉ)
for t_inv ∈ 𝒯ᴵⁿᵛ) > 0

end

@testset "Link - OPEX and investments" begin
Expand Down
Loading