Skip to content

Commit 331e214

Browse files
authored
Added support for TwoLevelTree in checks (#83)
* Included checks for TwoLevelTree * Added checks for StrategicStochasticProfile
1 parent 98f6951 commit 331e214

5 files changed

Lines changed: 186 additions & 18 deletions

File tree

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release notes
22

3+
## Unversioned
4+
5+
* Introduced support in checks for `TwoLevelTree` and `StrategicStochasticProfile`.
6+
37
## Version 0.9.5 (2026-03-25)
48

59
### Minor updates

docs/src/nodes/sink.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,8 @@ Hence, if you do not have to call additional functions, but only plan to include
111111
```math
112112
\begin{aligned}
113113
\texttt{opex\_var}[n, t_{inv}] = & scale\_op\_sp(t_{inv}, t) \times \\
114-
\sum_{t \in t_{inv}} & ( surplus\_penalty(n, t) \times \texttt{sink\_surplus}[n, t] + \\ &
115-
deficit\_penalty(n, t) \times \texttt{sink\_deficit}[n, t]) \\ &
116-
114+
& \sum_{t \in t_{inv}} ( surplus\_penalty(n, t) \times \texttt{sink\_surplus}[n, t] + \\
115+
& deficit\_penalty(n, t) \times \texttt{sink\_deficit}[n, t])
117116
\end{aligned}
118117
```
119118

src/checks.jl

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -354,25 +354,39 @@ function check_time_structure(x::AbstractElement, 𝒯)
354354
end
355355

356356
"""
357-
check_profile(fieldname, value::TimeProfile, 𝒯)
357+
check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel)
358+
check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel)
359+
check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel)
358360
359-
Check that an individual `TimeProfile` corresponds to the time structure `𝒯`.
360-
It currently does not include support for identifying `OperationalScenarios`.
361+
check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree)
362+
check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree)
363+
check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree)
364+
365+
Check that an individual `TimeProfile` corresponds to the time structure `𝒯`. The individual
366+
checks are depending on the profile type and the time structure.
361367
"""
368+
function check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel)
369+
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
370+
for t_inv 𝒯ᴵⁿᵛ
371+
p_msg = "strategic period $(t_inv.sp)"
372+
check_profile(fieldname, value, t_inv.operational, p_msg)
373+
end
374+
end
362375
function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel)
363376
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
364377

365378
len_vals = length(value.vals)
366379
len_ts = length(𝒯ᴵⁿᵛ)
367380
if len_vals > len_ts
368-
message = "` is longer than the strategic time structure. \
369-
Its last $(len_vals - len_ts) value(s) will be omitted."
381+
message = "` is longer than the strategic time structure. " *
382+
"Its last $(len_vals - len_ts) value(s) will be omitted."
370383
elseif len_vals < len_ts
371-
message = "` is shorter than the strategic time structure. It will use the last \
372-
value for the last $(len_ts - len_vals) strategic period(s)."
384+
message = "` is shorter than the strategic time structure. It will use the last " *
385+
"value for the last $(len_ts - len_vals) strategic period(s)."
373386
end
374-
@assert_or_log len_vals ==
375-
len_ts "The `TimeProfile` of field `" * string(fieldname) * message
387+
@assert_or_log(
388+
len_vals == len_ts, "The `TimeProfile` of field `" * string(fieldname) * message
389+
)
376390
for t_inv 𝒯ᴵⁿᵛ
377391
p_msg = "strategic period $(t_inv.sp)"
378392
check_profile(
@@ -383,13 +397,102 @@ function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel)
383397
)
384398
end
385399
end
386-
function check_profile(fieldname, value, 𝒯::TwoLevel)
400+
function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel)
401+
@warn(
402+
"Using `StrategicStochasticProfile` with `TwoLevel` is dangerous, " *
403+
"as it may lead to unexpected behaviour. " *
404+
"In this case, only the profiles of the first scenario are used in the model and " *
405+
"tested.",
406+
maxlog = 1
407+
)
408+
prof = StrategicProfile([op_prof[1] for op_prof value.vals])
409+
check_profile(fieldname, prof, 𝒯)
410+
end
411+
function check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree)
387412
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
388413
for t_inv 𝒯ᴵⁿᵛ
389-
p_msg = "strategic period $(t_inv.sp)"
414+
p_msg = "branch $(t_inv.branch) in strategic period $(t_inv.sp)"
390415
check_profile(fieldname, value, t_inv.operational, p_msg)
391416
end
392417
end
418+
function check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree)
419+
𝒯ˢˢᶜ = strategic_scenarios(𝒯)
420+
t_inv_vec = []
421+
422+
for ssc 𝒯ˢˢᶜ
423+
𝒯ᴵⁿᵛ = strategic_periods(ssc)
424+
len_vals = length(value.vals)
425+
len_ts = length(𝒯ᴵⁿᵛ)
426+
if len_vals > len_ts
427+
message = "` is longer than strategic scenario $(ssc.scen). " *
428+
"Its last $(len_vals - len_ts) value(s) will be omitted."
429+
elseif len_vals < len_ts
430+
message = "` is shorter than strategic scenario $(ssc.scen). It will use the " *
431+
"last value for the last $(len_ts - len_vals) strategic period(s)."
432+
end
433+
@assert_or_log(
434+
len_vals == len_ts, "The `TimeProfile` of field `" * string(fieldname) * message,
435+
)
436+
for t_inv 𝒯ᴵⁿᵛ
437+
t_inv t_inv_vec && continue
438+
push!(t_inv_vec, t_inv)
439+
440+
p_msg = "branch $(t_inv.branch) in strategic period $(t_inv.sp)"
441+
check_profile(
442+
fieldname,
443+
value.vals[minimum([t_inv.sp, length(value.vals)])],
444+
t_inv.operational,
445+
p_msg,
446+
)
447+
end
448+
end
449+
end
450+
function check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree)
451+
# Check for the number of strategic periods
452+
len_vals = length(value.vals)
453+
len_ts = n_strat_per(𝒯)
454+
if len_vals > len_ts
455+
message = "` is longer than the strategic time structure. " *
456+
"Its last $(len_vals - len_ts) value(s) will be omitted."
457+
elseif len_vals < len_ts
458+
message = "` is shorter than the strategic time structure. It will use the last " *
459+
"value for the last $(len_ts - len_vals) strategic period(s)."
460+
end
461+
@assert_or_log(
462+
len_vals == len_ts,
463+
"The `TimeProfile` of field `" * string(fieldname) * message,
464+
)
465+
466+
# Check each individual branch
467+
for sp 1:n_strat_per(𝒯)
468+
pre_msg = "` in strategic period $(sp) has "
469+
len_vals = length(value.vals[minimum([sp, length(value.vals)])])
470+
len_branches = n_branches(𝒯, sp)
471+
if len_vals > len_branches
472+
message = pre_msg * "more branches than the time structure. " *
473+
"Its last $(len_vals - len_branches) value(s) will be omitted."
474+
elseif len_vals < len_branches
475+
message = pre_msg * "less branches than the time structure. It will use the " *
476+
"last value for the last $(len_branches - len_vals) branche(s)."
477+
end
478+
@assert_or_log(
479+
len_vals == len_branches,
480+
"The `TimeProfile` of field `" * string(fieldname) * message,
481+
)
482+
end
483+
484+
# Check the sub profiles
485+
for t_inv strategic_periods(𝒯)
486+
p_msg = "branch $(t_inv.branch) in strategic period $(t_inv.sp)"
487+
sp_prof = value.vals[minimum([t_inv.sp, length(value.vals)])]
488+
check_profile(
489+
fieldname,
490+
sp_prof[minimum([t_inv.branch, length(sp_prof)])],
491+
t_inv.operational,
492+
p_msg,
493+
)
494+
end
495+
end
393496

394497
"""
395498
check_profile(fieldname, value::TimeProfile, ts::TimeStructure, p_msg)

src/model.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ function objective(m, 𝒳ᵛᵉᶜ, 𝒫, 𝒯, modeltype::EnergyModel)
783783
for t_inv 𝒯ᴵⁿᵛ)
784784
)
785785
end
786+
786787
"""
787788
objective_operational(m, 𝒳, 𝒯ᴵⁿᵛ::TS.AbstractStratPers, modeltype::EnergyModel)
788789

test/test_checks.jl

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,24 +278,85 @@ end
278278

279279
# Test that there is an error with wrong strategic profiles
280280
# - EMB.check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel)
281-
ts = TwoLevel(2, 1, day)
281+
# - EMB.check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree)
282+
ts = TwoLevel(3, 1, day)
283+
ts_tree_reg = TwoLevelTree(1, [2, 1], day)
284+
ts_tree_irreg = TwoLevelTree(
285+
TreeNode(1, day, [
286+
TreeNode(1, day, TreeNode(1, day, TreeNode(1, day))),
287+
TreeNode(1, day)
288+
])
289+
)
282290
ops = OperationalProfile(ones(24))
283291
profiles = [
284-
StrategicProfile([ops, ops, ops]),
285-
StrategicProfile([ops]),
292+
StrategicProfile([ops, ops, ops, ops]),
293+
StrategicProfile([ops, ops]),
294+
]
295+
for tp profiles
296+
@test_throws AssertionError create_simple_graph(ts, tp)
297+
@test_throws AssertionError create_simple_graph(ts_tree_reg, tp)
298+
@test_throws AssertionError create_simple_graph(ts_tree_irreg, tp)
299+
end
300+
301+
# Test that there is a warning when using `StrategicStochasticProfile` and `TwoLevel`
302+
# - EMB.check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel)
303+
profiles_ssc = StrategicStochasticProfile([[ops], [ops], [ops]])
304+
msg =
305+
"Using `StrategicStochasticProfile` with `TwoLevel` is dangerous, " *
306+
"as it may lead to unexpected behaviour. " *
307+
"In this case, only the profiles of the first scenario are used in the model and " *
308+
"tested."
309+
@test_logs (:warn, msg) create_simple_graph(ts, profiles_ssc);
310+
311+
# Test that there is an error with wrong `StrategicStochasticProfile`s with `TwoLevel`
312+
# - EMB.check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel)
313+
profiles = [
314+
StrategicStochasticProfile([[ops], [ops], [ops], [ops]]),
315+
StrategicStochasticProfile([[ops], [ops]]),
286316
]
287317
for tp profiles
288318
@test_throws AssertionError create_simple_graph(ts, tp)
289319
end
290320

321+
# Test that there is an error with wrong `StrategicStochasticProfile`s with `TwoLevelTree`
322+
# - EMB.check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree)
323+
profiles = [
324+
StrategicStochasticProfile([[ops], [ops, ops], [ops, ops], [ops, ops]]),
325+
StrategicStochasticProfile([[ops], [ops, ops]]),
326+
StrategicStochasticProfile([[ops], [ops, ops], [ops]]),
327+
StrategicStochasticProfile([[ops], [ops, ops], [ops, ops, ops]]),
328+
]
329+
for tp profiles
330+
@test_throws AssertionError create_simple_graph(ts_tree_reg, tp)
331+
end
332+
291333
# Test that there is an error with wrong `OperationalProfile`s
334+
# - EMB.check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevel)
335+
# - EMB.check_profile(fieldname, value::StrategicProfile, 𝒯::TwoLevelTree)
336+
# - EMB.check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevel)
337+
# - EMB.check_profile(fieldname, value::StrategicStochasticProfile, 𝒯::TwoLevelTree)
338+
# - EMB.check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevel)
339+
# - EMB.check_profile(fieldname, value::TimeProfile, 𝒯::TwoLevelTree)
292340
# - EMB.check_profile(fieldname, value::OperationalProfile, ts::SimpleTimes, sp)
293341
profiles = [
294342
OperationalProfile(ones(20)),
295343
OperationalProfile(ones(30)),
296344
]
297-
for tp profiles
345+
profiles_sp = [
346+
StrategicProfile([ops, ops, profiles[1]]),
347+
StrategicProfile([ops, ops, profiles[2]]),
348+
]
349+
profiles_ssc = [
350+
StrategicStochasticProfile([[ops], [ops, ops], [profiles[1], ops]]),
351+
StrategicStochasticProfile([[ops], [ops, ops], [profiles[2], ops]]),
352+
]
353+
for (tp, tp_sp, tp_ssc) zip(profiles, profiles_sp, profiles_ssc)
298354
@test_throws AssertionError create_simple_graph(ts, tp)
355+
@test_throws AssertionError create_simple_graph(ts, tp_sp)
356+
@test_throws AssertionError create_simple_graph(ts, tp_ssc)
357+
@test_throws AssertionError create_simple_graph(ts_tree_reg, tp)
358+
@test_throws AssertionError create_simple_graph(ts_tree_reg, tp_sp)
359+
@test_throws AssertionError create_simple_graph(ts_tree_reg, tp_ssc)
299360
end
300361

301362
# Test that there is an error with wrong `OperationalProfile`s in operational scenarios

0 commit comments

Comments
 (0)