Problem statement
While we have TwoLevelTree included for strategic uncertainty, we only provide a single constructor for the type called regular_tree in which 1) the same time structure is used in all StrategicNodes, 2) all probabilities of the branches are the same, and 3) latter branching is always the same, independent on which branch one is beforehand as shown in the documentation.
This is for a first version fine, limits however the future applicability in cases in which we do not have the same probability.
Proposed approach
It is in general difficult to come up with an idea that is both elegant but simultaneously allows for simple case descriptions. The following approach does not necessarily achieve elegancy, but can represent all potential structures. In relies on the introduction of a new concrete type TreeNode{S, T, OP<:TimeStructure{T}, U} with multiple constructors for ease of use:
using TimeStruct
# Core type with a `duration` within the strategic period, the corresponding time structure
# `ts`, a probability vector `probability` for its children, and a vector of `children.
struct TreeNode{S, T, OP<:TimeStructure{T}, U}
duration::S
ts::OP
probability::Vector{Float64}
children::Vector{U}
end
# Constructor for the last TreeNode in a branch, i.e., the leaf
function TreeNode(duration::Number, ts::TimeStructure)
return TreeNode(duration, ts, [1.0], [nothing])
end
# Constructor for a case in which the children time structure does not incorporate uncertainty
function TreeNode(duration::Number, ts::TimeStructure, children::TreeNode)
return TreeNode(duration, ts, [1.0], children)
end
# Constructor for a case in which all children have the same probability
function TreeNode(duration::Number, ts::TimeStructure, children::Vector{<:TreeNode})
len_child = length(children)
return TreeNode(duration, ts, ones(len_child)./len_child, children)
end
# Constructor for a case in which all children are equal (time structure, children, and so on)
# and have the same probability
function TreeNode(duration::Number, ts::TimeStructure, len::Int64, sub_tn::TreeNode)
return TreeNode(duration, ts, ones(len)./len, fill(sub_tn, len))
end
# Constructor for a case in which all children are equal (time structure, children, and so on),
# but can have a different probability
function TreeNode(duration::Number, ts::TimeStructure, probability::Vector{<:Float64}, sub_tn::TreeNode)
len = length(probability)
return TreeNode(duration, ts, probability, fill(sub_tn, len))
end
This allows us then to construct a TwoLevelTree with different properties:
day = SimpleTimes(24, 1)
week = SimpleTimes(168, 1)
rps = RepresentativePeriods(2, 1, day)
tree_nodes = TreeNode(5, day, [0.2, 0.6, 0.2],
[
TreeNode(2, day, [0.2, 0.8],
[
TreeNode(2, day),
TreeNode(2, day, 2, TreeNode(2, day))
]
),
TreeNode(2, day, 2, TreeNode(2, week)),
TreeNode(2, day, TreeNode(2, rps, TreeNode(2, rps))),
]
)
while it is not really nice to look at, it provides at least an overview of the individual structure of the Tree. Note that in this case, we can also have a different number of strategic periods in the strategic scenarios.
We can then create the TwoLevelTree through the following functionality
using .TimeStruct: TwoLevelTree, _total_duration, StratNode, _strat_per
# Function for adding the nodes to the tree structure
function add_node(
nodes::Vector{<:StratNode},
tn::TreeNode{S, T, OP, U},
parent::Union{Nothing,StratNode},
prob::Float64,
sp::Int64,
op_per_strat::Real,
) where {S,T,OP<:TimeStructure{T},U}
oper = tn.ts
mult_sp = tn.duration * op_per_strat / _total_duration(oper)
node = StratNode(
sp,
count(n -> _strat_per(n) == sp, nodes) + 1,
tn.duration,
mult_sp,
prob,
parent,
oper,
)
push!(nodes, node)
for (sub_prob, sub_tn) in zip(tn.probability, tn.children)
isnothing(sub_tn) && continue
total_prob = prob * sub_prob
add_node(nodes, sub_tn, node, total_prob, sp+1, op_per_strat)
end
return nodes
end
# Function for creating the instance
function TwoLevelTree(tn::TreeNode; op_per_strat=8760.0)
nodes = StratNode[]
nodes = add_node(nodes, tn, nothing, 1.0, 1, op_per_strat)
nodes = convert(Array{typejoin(typeof.(nodes)...)}, nodes)
return TwoLevelTree(length(nodes), nodes[1], nodes, op_per_strat)
end
ts = TwoLevelTree(tree_nodes)
It is not the most elegant implementation, but it handles it sufficiently well. One problem I encountered was the type conversion requirement as TwoLevelTree requires the StratNodes to have the same parameters. It was difficult to identify a solution which works as the differing time structures result in typeof(ts.nodes) = Vector{StratNode{Int64, Int64, OP} where OP<:TimeStructure{Int64}}.
Do you have any thoughts on it @hellemo and @trulsf? Shall I create a separate PR and test it a bit further?
Problem statement
While we have
TwoLevelTreeincluded for strategic uncertainty, we only provide a single constructor for the type calledregular_treein which 1) the same time structure is used in allStrategicNodes, 2) all probabilities of the branches are the same, and 3) latter branching is always the same, independent on which branch one is beforehand as shown in the documentation.This is for a first version fine, limits however the future applicability in cases in which we do not have the same probability.
Proposed approach
It is in general difficult to come up with an idea that is both elegant but simultaneously allows for simple case descriptions. The following approach does not necessarily achieve elegancy, but can represent all potential structures. In relies on the introduction of a new concrete type
TreeNode{S, T, OP<:TimeStructure{T}, U}with multiple constructors for ease of use:This allows us then to construct a
TwoLevelTreewith different properties:while it is not really nice to look at, it provides at least an overview of the individual structure of the Tree. Note that in this case, we can also have a different number of strategic periods in the strategic scenarios.
We can then create the
TwoLevelTreethrough the following functionalityIt is not the most elegant implementation, but it handles it sufficiently well. One problem I encountered was the type conversion requirement as
TwoLevelTreerequires theStratNodes to have the same parameters. It was difficult to identify a solution which works as the differing time structures result intypeof(ts.nodes) = Vector{StratNode{Int64, Int64, OP} where OP<:TimeStructure{Int64}}.Do you have any thoughts on it @hellemo and @trulsf? Shall I create a separate PR and test it a bit further?