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
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Release Notes

## Version 0.9.1 (2026-04-16)

### Bug fixes

* Fixed a bug in the checks when using `StrategicProfile` for the stack replacement costs of `AbstractElectrolyzer` nodes and `TwoLevelTree` as time structure.

## Version 0.9.0 (2026-04-14)

### Breaking changes
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 = "EnergyModelsHydrogen"
uuid = "44855f8b-b147-4985-ac18-48817d03c548"
authors = ["Julian Straus <Julian.Straus@sintef.no>, Avinash Subramanian"]
version = "0.9.0"
version = "0.9.1"

[deps]
EnergyModelsBase = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
Expand Down
9 changes: 1 addition & 8 deletions src/checks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ additional check on the data.
- The field `degradation_rate` is required to be in the range [0,1).
- The `TimeProfile` of the field `stack_replacement` is required to be non-negative and
accessible through a `StrategicPeriod` as outlined in the function
[`EMB.check_fixed_opex()`](@extref EnergyModelsBase.check_fixed_opex).
[`EMB.check_strategic_profile()`](@extref EnergyModelsBase.check_strategic_profile).
- The field `stack_lifetime` is required to be non-negative.
"""
function EMB.check_node(
Expand All @@ -39,13 +39,6 @@ function EMB.check_node(
"The stack degradation rate must be in the range [0, 100)."
)

if isa(stack_replacement_cost(n), StrategicProfile) && check_timeprofiles
@assert_or_log(
length(stack_replacement_cost(n).vals) == length(𝒯ᴵⁿᵛ),
"The timeprofile provided for the field `stack_replacement_cost` does not " *
"match the strategic structure."
)
end
# Check for potential indexing problems
message = "are not allowed for the field `stack_replacement_cost`."
bool_sp = EMB.check_strategic_profile(stack_replacement_cost(n), message)
Expand Down
137 changes: 75 additions & 62 deletions test/test_checks.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ H2 = ResourceCarrier("Hydrogen", 0.0)
CO2 = ResourceEmit("CO2", 1.0)

# Function for setting up the system for testing an `AbstractElectrolyzer` node
function simple_graph_elec(;
function check_elec(;
cap = FixedProfile(-25), # Installed capacity [MW]
opex_var = FixedProfile(5), # Variable Opex
opex_fixed = FixedProfile(100), # Fixed Opex
input = Dict(Power => 1), # Input: Ratio of Input flows to characteristic throughput
output = Dict(H2 => 0.62), # Ouput: Ratio of Output flow to characteristic throughput
load_limits = LoadLimits(0, 1), # Minimum and maximum load
degradation_rate = 0.1, # Degradation rate
stack_replacement_cost = FixedProfile(3e5), # Stack replacement costs
stack_rep_cost = FixedProfile(3e5), # Stack replacement costs
stack_lifetime = 60000, # Stack lifetime in h
T = TwoLevel(2, 2, SimpleTimes(5, 2); op_per_strat=10),
)

# Used source, network, and sink
Expand Down Expand Up @@ -45,13 +46,11 @@ function simple_graph_elec(;
ExtensionData[],
load_limits,
degradation_rate,
stack_replacement_cost,
stack_rep_cost,
stack_lifetime
)

resources = [Power, H2, CO2]
ops = SimpleTimes(5, 2)
T = TwoLevel(2, 2, ops; op_per_strat=10)

nodes = [source, elec, sink]
links = [
Expand All @@ -73,41 +72,55 @@ end
@testset "Test checks - AbstractElectrolyzer" begin

# Test that a wrong capacity is caught by the checks
@test_throws AssertionError simple_graph_elec(cap=FixedProfile(-25))
@test_throws AssertionError check_elec(cap=FixedProfile(-25))

# Test that a wrong fixed OPEX is caught by the checks
@test_throws AssertionError simple_graph_elec(;opex_var=FixedProfile(5))
@test_throws AssertionError check_elec(; opex_var=FixedProfile(5))

# Test that a wrong input dictionary is caught by the checks
@test_throws AssertionError simple_graph_elec(;input=Dict(Power => -1))
@test_throws AssertionError check_elec(; input=Dict(Power => -1))

# Test that a wrong output dictionary is caught by the checks
@test_throws AssertionError simple_graph_elec(;output=Dict(H2 => -0.62))
@test_throws AssertionError check_elec(; output=Dict(H2 => -0.62))

# Test that a wrong minimum load is caught by the checks
@test_throws AssertionError simple_graph_elec(;load_limits=LoadLimits(-0.5, 1.0))
@test_throws AssertionError check_elec(; load_limits=LoadLimits(-0.5, 1.0))

# Test that a wrong maximum load is caught by the checks
@test_throws AssertionError simple_graph_elec(;load_limits=LoadLimits(1.5, 1.0))
@test_throws AssertionError check_elec(; load_limits=LoadLimits(1.5, 1.0))

# Test that a wrong degradation rate load is caught by the checks
@test_throws AssertionError simple_graph_elec(;degradation_rate=-0.1)
@test_throws AssertionError simple_graph_elec(;degradation_rate=100)
@test_throws AssertionError check_elec(; degradation_rate=-0.1)
@test_throws AssertionError check_elec(; degradation_rate=100)

# Test that a wrong stack replacement profile is caught by the checks
stack_replacement_cost = FixedProfile(-5)
@test_throws AssertionError simple_graph_elec(;stack_replacement_cost)
stack_replacement_cost = StrategicProfile([10])
@test_throws AssertionError simple_graph_elec(;stack_replacement_cost)
stack_replacement_cost = OperationalProfile([10])
@test_throws AssertionError simple_graph_elec(;stack_replacement_cost)
stack_rep_cost = FixedProfile(-5)
@test_throws AssertionError check_elec(; stack_rep_cost)
T = TwoLevelTree(2, [2], SimpleTimes(5, 1); op_per_strat=10.0)
oprofile = OperationalProfile(ones(4))
profiles = [
oprofile,
StrategicProfile([4]),
StrategicProfile([oprofile, oprofile, oprofile, oprofile]),
]
for tp ∈ profiles
@test_throws AssertionError check_elec(; stack_rep_cost=tp)
@test_throws AssertionError check_elec(; stack_rep_cost=tp, T)
end
stack_rep_cost = StrategicStochasticProfile([[4]])
@test_throws AssertionError check_elec(; stack_rep_cost, T)

stack_rep_cost = StrategicProfile([10])
@test_throws AssertionError check_elec(; stack_rep_cost)
stack_rep_cost = OperationalProfile([10])
@test_throws AssertionError check_elec(; stack_rep_cost)

# Test that a wrong lifetime is caught by the checks
@test_throws AssertionError simple_graph_elec(;stack_lifetime=-10)
@test_throws AssertionError check_elec(; stack_lifetime=-10)
end

# Function for setting up the system for testing a `Reformer` node
function simple_graph_ref(;
function check_ref(;
cap = FixedProfile(-25), # Installed capacity [MW]
opex_var = FixedProfile(5), # Variable Opex
opex_fixed = FixedProfile(100), # Fixed Opex
Expand Down Expand Up @@ -175,49 +188,49 @@ end
@testset "Test checks - AbstractReformer" begin

# Test that a wrong capacity is caught by the checks
@test_throws AssertionError simple_graph_ref(;cap=FixedProfile(-25))
@test_throws AssertionError check_ref(; cap=FixedProfile(-25))

# Test that a wrong fixed OPEX is caught by the checks
@test_throws AssertionError simple_graph_ref(;opex_fixed=FixedProfile(-100))
@test_throws AssertionError check_ref(; opex_fixed=FixedProfile(-100))

# Test that a wrong input dictionary is caught by the checks
@test_throws AssertionError simple_graph_ref(;input=Dict(NG => -1))
@test_throws AssertionError check_ref(; input=Dict(NG => -1))

# Test that a wrong output dictionary is caught by the checks
@test_throws AssertionError simple_graph_ref(;output=Dict(H2 => -1.0))
@test_throws AssertionError check_ref(; output=Dict(H2 => -1.0))

# Test that a wrong minimum load is caught by the checks
@test_throws AssertionError simple_graph_ref(;load_limits=LoadLimits(-0.5, 1.0))
@test_throws AssertionError check_ref(; load_limits=LoadLimits(-0.5, 1.0))

# Test that a wrong maximum load is caught by the checks
@test_throws AssertionError simple_graph_ref(;load_limits=LoadLimits(1.5, 1.0))
@test_throws AssertionError check_ref(; load_limits=LoadLimits(1.5, 1.0))

# Test that a wrong unit commitment times are caught by the checks
commit_param = CommitParameters(FixedProfile(-1), FixedProfile(1))
@test_throws AssertionError simple_graph_ref(;startup=commit_param)
@test_throws AssertionError simple_graph_ref(;shutdown=commit_param)
@test_throws AssertionError simple_graph_ref(;offline=commit_param)
@test_throws AssertionError check_ref(; startup=commit_param)
@test_throws AssertionError check_ref(; shutdown=commit_param)
@test_throws AssertionError check_ref(; offline=commit_param)

# Test that a wrong profiles for minimum time of unit commitment are caught by the checks
# - check_commitment_profile()
startup = CommitParameters(FixedProfile(1), OperationalProfile([10]))
@test_throws AssertionError simple_graph_ref(;startup)
@test_throws AssertionError check_ref(; startup)
startup = CommitParameters(FixedProfile(1), StrategicProfile([OperationalProfile([10])]))
@test_throws AssertionError simple_graph_ref(;startup)
@test_throws AssertionError check_ref(; startup)
startup = CommitParameters(FixedProfile(1), FixedProfile(-5))
@test_throws AssertionError simple_graph_ref(;startup)
@test_throws AssertionError check_ref(; startup)
startup = CommitParameters(FixedProfile(1), StrategicProfile([-5]))
@test_throws AssertionError simple_graph_ref(;startup)
@test_throws AssertionError check_ref(; startup)
startup = CommitParameters(FixedProfile(1), StrategicProfile([10, 10]))
@test_throws AssertionError simple_graph_ref(;startup)
@test_throws AssertionError check_ref(; startup)

# Test that a wrong rate of change value is caught by the checks
@test_throws AssertionError simple_graph_ref(;rate_limit=RampBi(FixedProfile(-1)))
@test_throws AssertionError simple_graph_ref(;rate_limit=RampBi(FixedProfile(1.5)))
@test_throws AssertionError check_ref(; rate_limit=RampBi(FixedProfile(-1)))
@test_throws AssertionError check_ref(; rate_limit=RampBi(FixedProfile(1.5)))
end

# Function for setting up the system for testing a `SimpleHydrogenStorage` node
function simple_graph_simple_stor(;
function check_simple_stor(;
charge_cap = FixedProfile(10), # Installed capacity [MW]
level_cap = FixedProfile(1000), # Installed capacity [MWh]
charge_opex_fixed = FixedProfile(5), # Fixed Opex
Expand Down Expand Up @@ -279,29 +292,29 @@ end
@testset "Test checks - SimpleHydrogenStorage" begin

# Test that a wrong capacity is caught by the checks
@test_throws AssertionError simple_graph_simple_stor(;charge_cap=FixedProfile(-25))
@test_throws AssertionError simple_graph_simple_stor(;level_cap=FixedProfile(-25))
@test_throws AssertionError check_simple_stor(; charge_cap=FixedProfile(-25))
@test_throws AssertionError check_simple_stor(; level_cap=FixedProfile(-25))

# Test that a wrong fixed OPEX is caught by the checks
@test_throws AssertionError simple_graph_simple_stor(;charge_opex_fixed=FixedProfile(-100))
@test_throws AssertionError simple_graph_simple_stor(;level_opex_fixed=FixedProfile(-100))
@test_throws AssertionError check_simple_stor(; charge_opex_fixed=FixedProfile(-100))
@test_throws AssertionError check_simple_stor(; level_opex_fixed=FixedProfile(-100))

# Test that a wrong input dictionary is caught by the checks
@test_throws AssertionError simple_graph_simple_stor(;input=Dict(H2 => -1))
@test_throws AssertionError check_simple_stor(; input=Dict(H2 => -1))

# Test that a wrong output dictionary is caught by the checks
@test_throws AssertionError simple_graph_simple_stor(;output=Dict(H2 => -1.0))
@test_throws AssertionError check_simple_stor(; output=Dict(H2 => -1.0))

# Test that a wrong discharge to charge ratio is caught by the checks
@test_throws AssertionError simple_graph_simple_stor(;discharge_charge=-0.5)
@test_throws AssertionError check_simple_stor(; discharge_charge=-0.5)

# Test that a wrong level to charge ratio is caught by the checks
@test_throws AssertionError simple_graph_simple_stor(;level_charge=-0.5)
@test_throws AssertionError simple_graph_simple_stor(;level_charge=1000.0)
@test_throws AssertionError check_simple_stor(; level_charge=-0.5)
@test_throws AssertionError check_simple_stor(; level_charge=1000.0)
end

# Function for setting up the system for testing a `HydrogenStorage` node
function simple_graph_h2_stor(;
function check_h2_stor(;
charge_cap = FixedProfile(10), # Installed capacity [MW]
level_cap = FixedProfile(1000), # Installed capacity [MWh]
charge_opex_fixed = FixedProfile(5), # Fixed Opex
Expand Down Expand Up @@ -366,34 +379,34 @@ end
@testset "Test checks - HydrogenStorage" begin

# Test that a wrong capacity is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;charge_cap=FixedProfile(-25))
@test_throws AssertionError simple_graph_h2_stor(;level_cap=FixedProfile(-25))
@test_throws AssertionError check_h2_stor(; charge_cap=FixedProfile(-25))
@test_throws AssertionError check_h2_stor(; level_cap=FixedProfile(-25))

# Test that a wrong fixed OPEX is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;charge_opex_fixed=FixedProfile(-100))
@test_throws AssertionError simple_graph_h2_stor(;level_opex_fixed=FixedProfile(-100))
@test_throws AssertionError check_h2_stor(; charge_opex_fixed=FixedProfile(-100))
@test_throws AssertionError check_h2_stor(; level_opex_fixed=FixedProfile(-100))

# Test that a wrong discharge to charge ratio is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;discharge_charge=-0.5)
@test_throws AssertionError check_h2_stor(; discharge_charge=-0.5)

# Test that a wrong level to charge ratio is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;level_charge=-0.5)
@test_throws AssertionError simple_graph_h2_stor(;level_charge=1000.0)
@test_throws AssertionError check_h2_stor(; level_charge=-0.5)
@test_throws AssertionError check_h2_stor(; level_charge=1000.0)

# Test that a wrong minimum pressure is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;p_min=-0.5)
@test_throws AssertionError simple_graph_h2_stor(;p_min=160.5)
@test_throws AssertionError check_h2_stor(; p_min=-0.5)
@test_throws AssertionError check_h2_stor(; p_min=160.5)

# Test that a wrong minimum pressure is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;p_min=-0.5)
@test_throws AssertionError simple_graph_h2_stor(;p_min=160.5)
@test_throws AssertionError check_h2_stor(; p_min=-0.5)
@test_throws AssertionError check_h2_stor(; p_min=160.5)

# Test that a wrong charge pressure is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;p_charge=-0.5)
@test_throws AssertionError simple_graph_h2_stor(;p_charge=160.5)
@test_throws AssertionError check_h2_stor(; p_charge=-0.5)
@test_throws AssertionError check_h2_stor(; p_charge=160.5)

# Test that a wrong maximum pressure is caught by the checks
@test_throws AssertionError simple_graph_h2_stor(;p_max=-0.5)
@test_throws AssertionError check_h2_stor(; p_max=-0.5)
end

# Set the global again to false
Expand Down
Loading