Skip to content

Commit 58a97e7

Browse files
authored
Inclusion of default checks (#50)
* Changed checks and tests to `all()` * Introduced default checks that can be reused * Fixed error in documentation
1 parent 3717573 commit 58a97e7

12 files changed

Lines changed: 311 additions & 313 deletions

File tree

NEWS.md

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

3+
## Version 0.8.3 (2024-11-29)
4+
5+
### Reference checks possible to be called
6+
7+
* Created function `check_node_default()`.
8+
* The function can be called from other `check_node` to include all default checks
9+
310
## Version 0.8.2 (2024-11-27)
411

512
### Restructuring of function calls

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "EnergyModelsBase"
22
uuid = "5d7e687e-f956-46f3-9045-6f5a5fd49f50"
33
authors = ["Lars Hellemo <Lars.Hellemo@sintef.no>, Julian Straus <Julian.Straus@sintef.no>"]
4-
version = "0.8.2"
4+
version = "0.8.3"
55

66
[deps]
77
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"

docs/src/library/internals/functions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ check_data
4949
check_case_data
5050
check_model
5151
check_node
52+
check_node_default
5253
check_fixed_opex
5354
check_node_data(n::Node, data::EmissionsData, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
5455
check_time_structure

docs/src/manual/philosophy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ One key aim in the development of `EnergyModelsBase` was to create an energy sys
99
3. is designed in a way such that the thought process for understanding the model is straight forward.
1010

1111
Julia as a programming language offers the flexibility required in points 1 and 2 through the concept of multiple dispatch.
12-
The philosophy hence focueses on only creating variables and constraintes that are used, instead of creating all potential constraints and variables and constrain a large fraction of these variables to a value of 0.
12+
`EnergyModelsBase` hence focuses on only creating variables and constraints that are used, instead of creating all potential constraints and variables and constrain a large fraction of these variables to a value of 0.
1313
In that respect, `EnergyModelsBase` moves away from a parameter driven flexibility to a type driven flexibility.
1414
Point 3 is achieved through a one direction flow in function calls, that is that we limit the number of required files and function calls for the individual technology constraint creations, and meaningful names of the individual functions.
1515

docs/src/nodes/storage.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ Hence, if you do not have to call additional functions, but only plan to include
143143

144144
```math
145145
\begin{aligned}
146-
\texttt{stor\_level\_use}[n, t] & = \texttt{stor\_level\_inst}[n, t] \\
147-
\texttt{stor\_charge\_use}[n, t] & = \texttt{stor\_charge\_inst}[n, t] \\
148-
\texttt{stor\_discharge\_use}[n, t] & = \texttt{stor\_discharge\_inst}[n, t]
146+
\texttt{stor\_level\_use}[n, t] & \leq \texttt{stor\_level\_inst}[n, t] \\
147+
\texttt{stor\_charge\_use}[n, t] & \leq \texttt{stor\_charge\_inst}[n, t] \\
148+
\texttt{stor\_discharge\_use}[n, t] & \leq \texttt{stor\_discharge\_inst}[n, t]
149149
\end{aligned}
150150
```
151151

ext/EMIExt/checks.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,7 @@ function check_inv_data(
145145
if isa(inv_data, StartInvData)
146146
if bool_sp
147147
@assert_or_log(
148-
sum(inv_data.initial[t_inv] EMI.max_installed(inv_data, t_inv) for t_inv 𝒯ᴵⁿᵛ) ==
149-
length(𝒯ᴵⁿᵛ),
148+
all(inv_data.initial[t_inv] EMI.max_installed(inv_data, t_inv) for t_inv 𝒯ᴵⁿᵛ),
150149
"The value for the field `initial` in the investment data " * message *
151150
" can not be larger than the maximum installed constraint."
152151
)
@@ -158,8 +157,7 @@ function check_inv_data(
158157
bool_sp = EMB.check_strategic_profile(capacity_profile, submessage)
159158
if bool_sp
160159
@assert_or_log(
161-
sum(capacity_profile[t_inv] EMI.max_installed(inv_data, t_inv) for t_inv 𝒯ᴵⁿᵛ) ==
162-
length(𝒯ᴵⁿᵛ),
160+
all(capacity_profile[t_inv] EMI.max_installed(inv_data, t_inv) for t_inv 𝒯ᴵⁿᵛ),
163161
"The existing capacity can not be larger than the maximum installed value in " *
164162
"all strategic periods for the capacity coupled to the investment data " *
165163
message * "."
@@ -170,7 +168,7 @@ function check_inv_data(
170168
# Check on the minmimum and maximum added capacities
171169
if isa(EMI.investment_mode(inv_data), Union{ContinuousInvestment,SemiContiInvestment})
172170
@assert_or_log(
173-
sum(EMI.min_add(inv_data, t) EMI.max_add(inv_data, t) for t 𝒯) == length(𝒯),
171+
all(EMI.min_add(inv_data, t) EMI.max_add(inv_data, t) for t 𝒯),
174172
"`min_add` has to be less than `max_add` in the investment data " *
175173
message * "."
176174
)

src/checks.jl

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -556,23 +556,37 @@ end
556556
check_node(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
557557
558558
Check that the fields of a `Node` corresponds to required structure.
559+
560+
The default approach calls the subroutine [`check_node_default`](@ref) which provides the
561+
user with default checks for [`Source`](@ref), [`NetworkNode`](@ref), [`Availability`](@ref),
562+
[`Storage`](@ref), and [`Sink`](@ref) nodes.
563+
564+
!!! tip "Creating a new node type"
565+
When developing a new node with new checks, it is important to create a new method for
566+
`check_node`. You can then call within this function the default tests for the corresponding
567+
supertype through calling the function [`check_node_default`](@ref).
559568
"""
560-
function check_node(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) end
569+
check_node(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) =
570+
check_node_default(n, 𝒯, modeltype, check_timeprofiles)
561571
"""
562-
check_node(n::Availability, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
572+
check_node_default(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
563573
564-
This method checks that an `Availability` node is valid. By default, that does not include
574+
This method checks that a `Node` node is valid. By default, that does not include
565575
any checks.
566576
"""
567-
function check_node(n::Availability, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) end
577+
function check_node_default(n::Node, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) end
568578
"""
569-
check_node(n::Source, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
579+
check_node_default(n::Availability, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
570580
571-
This method checks that a `Source` node is valid.
581+
This method checks that an `Availability` node is valid. By default, that does not include
582+
any checks.
583+
"""
584+
function check_node_default(n::Availability, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool) end
585+
"""
586+
check_node_default(n::Source, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
572587
573-
These checks are always performed, if the user is not creating a new method. Hence, it is
574-
important that a new `Source` type includes at least the same fields as in the [`RefSource`](@ref)
575-
node or that a new `Source` type receives a new method for `check_node`.
588+
Subroutine that can be utilized in other packages for incorporating the standard tests for
589+
a [`Source`](@ref) node.
576590
577591
## Checks
578592
- The field `cap` is required to be non-negative.
@@ -581,27 +595,24 @@ node or that a new `Source` type receives a new method for `check_node`.
581595
accessible through a `StrategicPeriod` as outlined in the function
582596
[`check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles)`](@ref).
583597
"""
584-
function check_node(n::Source, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
598+
function check_node_default(n::Source, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
585599
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
586600

587601
@assert_or_log(
588-
sum(capacity(n, t) 0 for t 𝒯) == length(𝒯),
602+
all(capacity(n, t) 0 for t 𝒯),
589603
"The capacity must be non-negative."
590604
)
591605
@assert_or_log(
592-
sum(outputs(n, p) 0 for p outputs(n)) == length(outputs(n)),
606+
all(outputs(n, p) 0 for p outputs(n)),
593607
"The values for the Dictionary `output` must be non-negative."
594608
)
595609
check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles)
596610
end
597611
"""
598-
check_node(n::NetworkNode, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
599-
600-
This method checks that a `NetworkNode` node is valid.
612+
check_node_default(n::NetworkNode, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
601613
602-
These checks are always performed, if the user is not creating a new method. Hence, it is
603-
important that a new `NetworkNode` type includes at least the same fields as in the
604-
[`RefNetworkNode`(@ref) node or that a new `NetworkNode` type receives a new method for `check_node`.
614+
Subroutine that can be utilized in other packages for incorporating the standard tests for
615+
a [`NetworkNode`](@ref) node.
605616
606617
## Checks
607618
- The field `cap` is required to be non-negative.
@@ -611,31 +622,28 @@ important that a new `NetworkNode` type includes at least the same fields as in
611622
accessible through a `StrategicPeriod` as outlined in the function
612623
[`check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles)`](@ref).
613624
"""
614-
function check_node(n::NetworkNode, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
625+
function check_node_default(n::NetworkNode, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
615626
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
616627

617628
@assert_or_log(
618-
sum(capacity(n, t) 0 for t 𝒯) == length(𝒯),
629+
all(capacity(n, t) 0 for t 𝒯),
619630
"The capacity must be non-negative."
620631
)
621632
@assert_or_log(
622-
sum(inputs(n, p) 0 for p inputs(n)) == length(inputs(n)),
633+
all(inputs(n, p) 0 for p inputs(n)),
623634
"The values for the Dictionary `input` must be non-negative."
624635
)
625636
@assert_or_log(
626-
sum(outputs(n, p) 0 for p outputs(n)) == length(outputs(n)),
637+
all(outputs(n, p) 0 for p outputs(n)),
627638
"The values for the Dictionary `output` must be non-negative."
628639
)
629640
check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles)
630641
end
631642
"""
632-
check_node(n::Storage, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
643+
check_node_default(n::Storage, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
633644
634-
This method checks that a `Storage` node is valid.
635-
636-
These checks are always performed, if the user is not creating a new method. Hence, it is
637-
important that a new `Storage` type includes at least the same fields as in the
638-
[`RefStorage`](@ref) node or that a new `Storage` type receives a new method for `check_node`.
645+
Subroutine that can be utilized in other packages for incorporating the standard tests for
646+
a [`Storage`](@ref) node.
639647
640648
## Checks
641649
- The `TimeProfile` of the field `capacity` in the type in the field `charge` is required
@@ -650,69 +658,67 @@ important that a new `Storage` type includes at least the same fields as in the
650658
- The values of the dictionary `input` are required to be non-negative.
651659
- The values of the dictionary `output` are required to be non-negative.
652660
"""
653-
function check_node(n::Storage, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
661+
function check_node_default(n::Storage, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
654662
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
655663
par_charge = charge(n)
656664
par_level = level(n)
657665
par_discharge = discharge(n)
658666

659667
if isa(par_charge, UnionCapacity)
660668
@assert_or_log(
661-
sum(capacity(par_charge, t) 0 for t 𝒯) == length(𝒯),
669+
all(capacity(par_charge, t) 0 for t 𝒯),
662670
"The charge capacity must be non-negative."
663671
)
664672
end
665673
if isa(par_charge, UnionOpexFixed)
666674
check_fixed_opex(par_charge, 𝒯ᴵⁿᵛ, check_timeprofiles)
667675
end
668676
@assert_or_log(
669-
sum(capacity(par_level, t) 0 for t 𝒯) == length(𝒯),
677+
all(capacity(par_level, t) 0 for t 𝒯),
670678
"The level capacity must be non-negative."
671679
)
672680
if isa(par_level, UnionOpexFixed)
673681
check_fixed_opex(par_level, 𝒯ᴵⁿᵛ, check_timeprofiles)
674682
end
675683
if isa(par_discharge, UnionCapacity)
676684
@assert_or_log(
677-
sum(capacity(par_discharge, t) 0 for t 𝒯) == length(𝒯),
678-
"The charge capacity must be non-negative."
685+
all(capacity(par_discharge, t) 0 for t 𝒯),
686+
"The discharge capacity must be non-negative."
679687
)
680688
end
681689
if isa(par_discharge, UnionOpexFixed)
682690
check_fixed_opex(par_discharge, 𝒯ᴵⁿᵛ, check_timeprofiles)
683691
end
684-
@assert_or_log(
685-
sum(inputs(n, p) 0 for p inputs(n)) == length(inputs(n)),
692+
has_input(n) && @assert_or_log(
693+
all(inputs(n, p) 0 for p inputs(n)),
686694
"The values for the Dictionary `input` must be non-negative."
687695
)
688-
@assert_or_log(
689-
sum(outputs(n, p) 0 for p outputs(n)) == length(outputs(n)),
696+
has_output(n) && @assert_or_log(
697+
all(outputs(n, p) 0 for p outputs(n)),
690698
"The values for the Dictionary `output` must be non-negative."
691699
)
692700
end
693-
"""
694-
check_node(n::Sink, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
695701

696-
This method checks that a `Sink` node is valid.
702+
"""
703+
check_node_default(n::Sink, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
697704
698-
These checks are always performed, if the user is not creating a new method. Hence, it is
699-
important that a new `Sink` type includes at least the same fields as in the [`RefSink`](@ref)
700-
node or that a new `Source` type receives a new method for `check_node`.
705+
Subroutine that can be utilized in other packages for incorporating the standard tests for
706+
a [`Sink`](@ref) node.
701707
702708
## Checks
703709
- The field `cap` is required to be non-negative.
704710
- The values of the dictionary `input` are required to be non-negative.
705711
- The dictionary `penalty` is required to have the keys `:deficit` and `:surplus`.
706712
- The sum of the values `:deficit` and `:surplus` in the dictionary `penalty` has to be
707-
non-negative to avoid an infeasible model.
713+
non-negative to avoid an infeasible model.
708714
"""
709-
function check_node(n::Sink, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
715+
function check_node_default(n::Sink, 𝒯, modeltype::EnergyModel, check_timeprofiles::Bool)
710716
@assert_or_log(
711-
sum(capacity(n, t) 0 for t 𝒯) == length(𝒯),
717+
all(capacity(n, t) 0 for t 𝒯),
712718
"The capacity must be non-negative."
713719
)
714720
@assert_or_log(
715-
sum(inputs(n, p) 0 for p inputs(n)) == length(inputs(n)),
721+
all(inputs(n, p) 0 for p inputs(n)),
716722
"The values for the Dictionary `input` must be non-negative."
717723
)
718724
@assert_or_log(
@@ -723,11 +729,13 @@ function check_node(n::Sink, 𝒯, modeltype::EnergyModel, check_timeprofiles::B
723729
if :surplus keys(n.penalty) && :deficit keys(n.penalty)
724730
# The if-condition was checked above.
725731
@assert_or_log(
726-
sum(surplus_penalty(n, t) + deficit_penalty(n, t) 0 for t 𝒯) == length(𝒯),
732+
all(surplus_penalty(n, t) + deficit_penalty(n, t) 0 for t 𝒯),
727733
"An inconsistent combination of `:surplus` and `:deficit` leads to an infeasible model."
728734
)
729735
end
730736
end
737+
738+
731739
"""
732740
check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles::Bool)
733741
@@ -758,7 +766,7 @@ function check_fixed_opex(n, 𝒯ᴵⁿᵛ, check_timeprofiles::Bool)
758766
# Check that the value is positive in all cases
759767
if bool_sp
760768
@assert_or_log(
761-
sum(opex_fixed(n, t_inv) 0 for t_inv 𝒯ᴵⁿᵛ) == length(𝒯ᴵⁿᵛ),
769+
all(opex_fixed(n, t_inv) 0 for t_inv 𝒯ᴵⁿᵛ),
762770
"The fixed OPEX must be non-negative."
763771
)
764772
end

src/structures/resource.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Base.show(io::IO, r::Resource) = print(io, "$(r.id)")
77
"""
88
ResourceEmit{T<:Real} <: Resource
99
10-
Resources that can can be emitted (*e.g.*, CO₂, CH₄, NOₓ).
10+
Resources that can be emitted (*e.g.*, CO₂, CH₄, NOₓ).
1111
1212
These resources can be included as resources that are emitted, *e.g*, in the variable
1313
[`emissions_strategic`](@ref man-opt_var-emissions).

test/runtests.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ const TS = TimeStruct
1010
const TEST_ATOL = 1e-6
1111
ENV["EMB_TEST"] = true # Set flag for example scripts to check if they are run as part of the tests
1212

13-
1413
@testset "Base" begin
1514
@testset "Base | General" begin
1615
include("test_general.jl")

0 commit comments

Comments
 (0)