diff --git a/README.md b/README.md
index f938ca4f..c084ad92 100644
--- a/README.md
+++ b/README.md
@@ -14,16 +14,22 @@ We can create an influence diagram as follows:
```julia
using DecisionProgramming
-S = States([2, 2, 2, 2])
-C = [ChanceNode(2, [1]), ChanceNode(3, [1])]
-D = [DecisionNode(1, Node[]), DecisionNode(4, [2, 3])]
-V = [ValueNode(5, [4])]
-X = [Probabilities(2, [0.4 0.6; 0.6 0.4]), Probabilities(3, [0.7 0.3; 0.3 0.7])]
-Y = [Consequences(5, [1.5, 1.7])]
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
-P = DefaultPathProbability(C, X)
-U = DefaultPathUtility(V, Y)
+
+diagram = InfluenceDiagram()
+
+add_node!(diagram, DecisionNode("A", [], ["a", "b"]))
+add_node!(diagram, DecisionNode("D", ["B", "C"], ["k", "l"]))
+add_node!(diagram, ChanceNode("B", ["A"], ["x", "y"]))
+add_node!(diagram, ChanceNode("C", ["A"], ["v", "w"]))
+add_node!(diagram, ValueNode("V", ["D"]))
+
+generate_arcs!(diagram)
+
+add_probabilities!(diagram, "B", [0.4 0.6; 0.6 0.4])
+add_probabilities!(diagram, "C", [0.7 0.3; 0.3 0.7])
+add_utilities!(diagram, "V", [1.5, 1.7])
+
+generate_diagram!(diagram)
```
Using the influence diagram, we create the decision model as follow:
@@ -31,13 +37,13 @@ Using the influence diagram, we create the decision model as follow:
```julia
using JuMP
model = Model()
-z = DecisionVariables(model, S, D)
-x_s = PathCompatibilityVariables(model, z, S, P)
-EV = expected_value(model, x_s, U, P)
+z = DecisionVariables(model, diagram)
+x_s = PathCompatibilityVariables(model, diagram, z)
+EV = expected_value(model, diagram, x_s)
@objective(model, Max, EV)
```
-Finally, we can optimize the model using MILP solver.
+We can optimize the model using MILP solver.
```julia
using Gurobi
diff --git a/docs/src/api.md b/docs/src/api.md
index c04f7d19..002d88cf 100644
--- a/docs/src/api.md
+++ b/docs/src/api.md
@@ -1,25 +1,27 @@
# API Reference
`DecisionProgramming.jl` API reference.
+
## `influence_diagram.jl`
### Nodes
```@docs
Node
+Name
+AbstractNode
ChanceNode
DecisionNode
ValueNode
State
States
-States(::Vector{Tuple{State, Vector{Node}}})
-validate_influence_diagram
```
### Paths
```@docs
Path
-paths(::AbstractVector{State})
-paths(::AbstractVector{State}, ::Dict{Node, State})
ForbiddenPath
+FixedPath
+paths(::AbstractVector{State})
+paths(::AbstractVector{State}, FixedPath)
```
### Probabilities
@@ -33,9 +35,10 @@ AbstractPathProbability
DefaultPathProbability
```
-### Consequences
+### Utilities
```@docs
-Consequences
+Utility
+Utilities
```
### Path Utility
@@ -44,6 +47,18 @@ AbstractPathUtility
DefaultPathUtility
```
+### InfluenceDiagram
+InfluenceDiagram
+generate_arcs!
+generate_diagram!
+add_node!
+ProbabilityMatrix
+set_probability!
+add_probabilities!
+UtilityMatrix
+set_utility!
+add_utilities!
+
### Decision Strategy
```@docs
LocalDecisionStrategy
@@ -61,10 +76,8 @@ lazy_probability_cut(::Model, ::PathCompatibilityVariables, ::AbstractPathProbab
### Objective Functions
```@docs
-PositivePathUtility
-NegativePathUtility
-expected_value(::Model, ::PathCompatibilityVariables, ::AbstractPathUtility, ::AbstractPathProbability; ::Float64)
-conditional_value_at_risk(::Model, ::PathCompatibilityVariables{N}, ::AbstractPathUtility, ::AbstractPathProbability, ::Float64; ::Float64) where N
+expected_value(::Model, ::InfluenceDiagram, ::PathCompatibilityVariables; ::Float64)
+conditional_value_at_risk(::Model, ::InfluenceDiagram, ::PathCompatibilityVariables{N}, ::Float64; ::Float64) where N
```
### Decision Strategy from Variables
@@ -76,13 +89,14 @@ DecisionStrategy(::DecisionVariables)
## `analysis.jl`
```@docs
CompatiblePaths
+CompatiblePaths(::InfluenceDiagram, ::DecisionStrategy, ::FixedPath)
UtilityDistribution
-UtilityDistribution(::States, ::AbstractPathProbability, ::AbstractPathUtility, ::DecisionStrategy)
+UtilityDistribution(::InfluenceDiagram, ::DecisionStrategy)
StateProbabilities
-StateProbabilities(::States, ::AbstractPathProbability, ::DecisionStrategy)
-StateProbabilities(::States, ::AbstractPathProbability, ::DecisionStrategy, ::Node, ::State, ::StateProbabilities)
-value_at_risk(::Vector{Float64}, ::Vector{Float64}, ::Float64)
-conditional_value_at_risk(::Vector{Float64}, ::Vector{Float64}, ::Float64)
+StateProbabilities(::InfluenceDiagram, ::DecisionStrategy)
+StateProbabilities(::InfluenceDiagram, ::DecisionStrategy, ::Name, ::Name, ::StateProbabilities)
+value_at_risk(::UtilityDistribution, ::Float64)
+conditional_value_at_risk(::UtilityDistribution, ::Float64)
```
## `printing.jl`
diff --git a/docs/src/examples/CHD_preventative_care.md b/docs/src/examples/CHD_preventative_care.md
index e6d0660a..dc6d7509 100644
--- a/docs/src/examples/CHD_preventative_care.md
+++ b/docs/src/examples/CHD_preventative_care.md
@@ -6,19 +6,19 @@
In this example, we will showcase the subproblem, which optimises the decision strategy given a single prior risk level. The chosen risk level in this example is 12%. The solution to the main problem is found in [^1].
-## Influence Diagram
+## Influence diagram

The influence diagram representation of the problem is seen above. The chance nodes $R$ represent the patient's risk estimate – the prior risk estimate being $R0$. The risk estimate nodes $R0$, $R1$ and $R2$ have 101 states $R = \{0\%, 1\%, ..., 100\%\}$, which are the discretised risk levels for the risk estimates.
-The risk estimate is updated according to the first and second test decisions, which are represented by decision nodes $T1$ and $T2$. These nodes have states $T = \{\text{TRS, GRS, no test}\}$. The health of the patient represented by chance node $H$ also affects the update of the risk estimate. In this model, the health of the patient indicates whether they will have a CHD event in the next ten years or not. Thus, the node has states $H = \{\text{CHD, no CHD}\}$. The treatment decision is represented by node $TD$ and it has states $TD = \{\text{treatment, no treatment}\}$.
+The risk estimate is updated according to the first and second testing decisions, which are represented by decision nodes $T1$ and $T2$. These nodes have states $T = \{\text{TRS, GRS, no test}\}$. The health of the patient, represented by chance node $H$, also affects the update of the risk estimate. In this model, the health of the patient indicates whether they will have a CHD event in the next ten years or not. Thus, the node has states $H = \{\text{CHD, no CHD}\}$. The treatment decision is represented by node $TD$ and it has states $TD = \{\text{treatment, no treatment}\}$.
The prior risk estimate represented by node $R0$ influences the health node $H$, because in the model we make the assumption that the prior risk estimate accurately describes the probability of having a CHD event.
The value nodes in the model are $TC$ and $HB$. Node $TC$ represents the testing costs incurred due to the testing decisions $T1$ and $T2$. Node $HB$ represents the health benefits achieved. The testing costs and health benefits are measured in QALYs. These parameter values were evaluated in the study [^2].
-We begin by declaring the chosen prior risk level and reading the conditional probability data for the tests. The risk level 12% is referred to as 13 because indexing begins from 1 and the first risk level is 0\%. Note also that the sample data in this repository is dummy data due to distribution restrictions on the real data. We also define functions ```update_risk_distribution ```, ```state_probabilities``` and ```analysing_results ```. These functions will be discussed in the following sections.
+We begin by declaring the chosen prior risk level and reading the conditional probability data for the tests. Note that the sample data in this repository is dummy data due to distribution restrictions on the real data. We also define functions ```update_risk_distribution ``` and ```state_probabilities```. These functions will be discussed in the following sections.
```julia
using Logging
@@ -27,7 +27,7 @@ using DecisionProgramming
using CSV, DataFrames, PrettyTables
-const chosen_risk_level = 13
+const chosen_risk_level = 12%
const data = CSV.read("risk_prediction_data.csv", DataFrame)
function update_risk_distribution(prior::Int64, t::Int64)...
@@ -35,69 +35,66 @@ end
function state_probabilities(risk_p::Array{Float64}, t::Int64, h::Int64, prior::Int64)...
end
-
-function analysing_results(Z::DecisionStrategy, sprobs::StateProbabilities)...
-end
```
-
-We define the decision programming model by first defining the node indices and states:
+### Initialise influence diagram
+We start defining the Decision Programming model by initialising the influence diagram.
```julia
-const R0 = 1
-const H = 2
-const T1 = 3
-const R1 = 4
-const T2 = 5
-const R2 = 6
-const TD = 7
-const TC = 8
-const HB = 9
+diagram = InfluenceDiagram()
+```
+
+For brevity in the next sections, we define the states of the nodes to be readily available. Notice, that $R_{states}$ is a vector with values $0\%, 1\%,..., 100\%$.
+```julia
const H_states = ["CHD", "no CHD"]
const T_states = ["TRS", "GRS", "no test"]
const TD_states = ["treatment", "no treatment"]
-const R_states = map( x -> string(x) * "%", [0:1:100;])
-const TC_states = ["TRS", "GRS", "TRS & GRS", "no tests"]
-const HB_states = ["CHD & treatment", "CHD & no treatment", "no CHD & treatment", "no CHD & no treatment"]
-
-@info("Creating the influence diagram.")
-S = States([
- (length(R_states), [R0, R1, R2]),
- (length(H_states), [H]),
- (length(T_states), [T1, T2]),
- (length(TD_states), [TD])
-])
-
-C = Vector{ChanceNode}()
-D = Vector{DecisionNode}()
-V = Vector{ValueNode}()
-X = Vector{Probabilities}()
-Y = Vector{Consequences}()
+const R_states = [string(x) * "%" for x in [0:1:100;]]
+```
+
+ We then add the nodes. The chance and decision nodes are identified by their names. When declaring the nodes, they are also given information sets and states. Notice that nodes $R0$ and $H$ are root nodes, meaning that their information sets are empty. In Decision Programming, we add the chance and decision nodes in the follwoing way.
+ ```julia
+add_node!(diagram, ChanceNode("R0", [], R_states))
+add_node!(diagram, ChanceNode("R1", ["R0", "H", "T1"], R_states))
+add_node!(diagram, ChanceNode("R2", ["R1", "H", "T2"], R_states))
+add_node!(diagram, ChanceNode("H", ["R0"], H_states))
+
+add_node!(diagram, DecisionNode("T1", ["R0"], T_states))
+add_node!(diagram, DecisionNode("T2", ["R1"], T_states))
+add_node!(diagram, DecisionNode("TD", ["R2"], TD_states))
```
+The value nodes are added in a similar fashion. However, value nodes do not have states because they map their information states to utility values instead.
-Next, we define the nodes with their information sets and corresponding probabilities for chance nodes and consequences for value nodes.
+```julia
+add_node!(diagram, ValueNode("TC", ["T1", "T2"]))
+add_node!(diagram, ValueNode("HB", ["H", "TD"]))
+```
+
+### Generate arcs
+Now that all of the nodes have been added to the influence diagram we generate the arcs between the nodes. This step automatically orders the nodes, gives them indices and reorganises the information into the appropriate form.
+```julia
+generate_arcs!(diagram)
+```
-### Prior risk estimate and health of the patient
+### Probabilities of the prior risk estimate and health of the patient
-In this subproblem, the prior risk estimate is given and therefore the node $R0$ is in effect a deterministic node. In decision programming a deterministic node is added as a chance node for which the probability of one state is set to one and the probabilities of the rest of the states are set to zero. In this case
+In this subproblem, the prior risk estimate is given and therefore the node $R0$ is in effect a deterministic node. In Decision Programming a deterministic node is added as a chance node for which the probability of one state is set to one and the probabilities of the rest of the states are set to zero. In this case
$$ℙ(R0 = 12\%)=1$$
and
$$ℙ(R0 \neq 12\%)= 0. $$
-Notice also that node $R0$ is the first node in the influence diagram, meaning that its information set $I(R0)$ is empty. In decision programming we add node $R0$ and its state probabilities as follows:
+The probability matrix of node $R0$ is added in the following way. Remember that the `ProbabilityMatrix` function initialises the matrix with zeros.
```julia
-I_R0 = Vector{Node}()
-X_R0 = zeros(S[R0])
-X_R0[chosen_risk_level] = 1
-push!(C, ChanceNode(R0, I_R0))
-push!(X, Probabilities(R0, X_R0))
+X_R0 = ProbabilityMatrix(diagram, "R0")
+set_probability!(X_R0, [chosen_risk_level], 1)
+add_probabilities!(diagram, "R0", X_R0)
```
-Next we add node $H$ and its state probabilities. For modeling purposes, we define the information set of node $H$ to include the prior risk node $R0$. We set the probability that the patient experiences a CHD event in the next ten years according to the prior risk level such that
+Next we add the state probabilities of node $H$. For modeling purposes, we define the information set of node $H$ to include the prior risk node $R0$. We set the probability that the patient experiences a CHD event in the next ten years according to the prior risk level such that
$$ℙ(H = \text{CHD} | R0 = \alpha) = \alpha.$$
@@ -107,140 +104,110 @@ $$ℙ(H = \text{no CHD} | R0 = \alpha) = 1 - \alpha$$
Since node $R0$ is deterministic and the health node $H$ is defined in this way, in our model the patient has a 12% chance of experiencing a CHD event and 88% chance of remaining healthy.
-Node $H$ and its probabilities are added in the following way.
-
+In Decision Programming the probability matrix of node $H$ has dimensions (101, 2) because its information set consisting of node $R0$ has 101 states and node $H$ has 2 states. We first set the column related to the state $CHD$ with values from `data.risk_levels` which are $0.00, 0.01, ..., 0.99, 1.00$ and the other column as its complement event.
```julia
-I_H = [R0]
-X_H = zeros(S[R0], S[H])
-X_H[:, 1] = data.risk_levels
-X_H[:, 2] = 1 .- X_H[:, 1]
-push!(C, ChanceNode(H, I_H))
-push!(X, Probabilities(H, X_H))
+X_H = ProbabilityMatrix(diagram, "H")
+set_probability!(X_H, [:, "CHD"], data.risk_levels)
+set_probability!(X_H, [:, "no CHD"], 1 .- data.risk_levels)
+add_probabilities!(diagram, "H", X_H)
```
-### Test decisions and updating the risk estimate
-
-The node representing the first test decision is added to the model.
-
-```julia
-I_T1 = [R0]
-push!(D, DecisionNode(T1, I_T1))
-```
+### Probabilities of the updated the risk estimates
-For node $R1%$, the probabilities of the states are calculated by aggregating the updated risk estimates, after a test is performed, into the risk levels. The updated risk estimates are calculated using the function ```update_risk_distribution```, which calculates the posterior probability distribution for a given health state, test and prior risk estimate.
+For node $R1%$, the probabilities of the states are calculated by aggregating the updated risk estimates into the risk levels after a test is performed. The updated risk estimates are calculated using the function ```update_risk_distribution```, which calculates the posterior probability distribution for a given health state, test and prior risk estimate.
$$\textit{risk estimate} = P(\text{CHD} \mid \text{test result}) = \frac{P(\text{test result} \mid \text{CHD})P(\text{CHD})}{P(\text{test result})}$$
The probabilities $P(\text{test result} \mid \text{CHD})$ are test specific and these are read from the CSV data file. The updated risk estimates are aggregated according to the risk levels. These aggregated probabilities are then the state probabilities of node $R1$. The aggregating is done using function ```state_probabilities```.
-The node $R1$ and its probabilities are added in the following way.
+In Decision Programming the probability distribution over the states of node $R1$ is defined into a probability matrix with dimensions $(101,2,3,101)$. This is because its information set consists of nodes $R0, H$ and, $T$ which have 101, 2 and 3 states respectively and the node $R1$ itself has 101 states. Here, one must know that in Decision Programming the states of the nodes are mapped to numbers in the back-end. For instance, the health states $\text{CHD}$ and $\text{no CHD}$ are indexed 1 and 2. The testing decision states TRS, GRS and no test are 1, 2 and 3. The order of the states is determined by the order in which they are defined when adding the nodes. Knowing this, we can set the probability values into the probability matrix using a very compact syntax. Notice that we add 101 probability values at a time into the matrix.
```julia
-I_R1 = [R0, H, T1]
-X_R1 = zeros(S[I_R1]..., S[R1])
+X_R = ProbabilityMatrix(diagram, "R1")
for s_R0 = 1:101, s_H = 1:2, s_T1 = 1:3
- X_R1[s_R0, s_H, s_T1, :] = state_probabilities(update_risk_distribution(s_R0, s_T1), s_T1, s_H, s_R0)
+ X_R[s_R0, s_H, s_T1, :] = state_probabilities(update_risk_distribution(s_R0, s_T1), s_T1, s_H, s_R0)
end
-push!(C, ChanceNode(R1, I_R1))
-push!(X, Probabilities(R1, X_R1))
+add_probabilities!(diagram, "R1", X_R)
```
-Nodes $T2$ and $R2$ are added in a similar fashion to nodes $T1$ and $R1$ above.
+We notice that the probability distrubtion is identical in $R1$ and $R2$ because their information states are identical. Therefore we can simply add the same matrix from above as the probability matrix of node $R2$.
```julia
-I_T2 = [R1]
-push!(D, DecisionNode(T2, I_T2))
-
-
-I_R2 = [H, R1, T2]
-X_R2 = zeros(S[I_R2]..., S[R2])
-for s_R1 = 1:101, s_H = 1:2, s_T2 = 1:3
- X_R2[s_H, s_R1, s_T2, :] = state_probabilities(update_risk_distribution(s_R1, s_T2), s_T2, s_H, s_R1)
-end
-push!(C, ChanceNode(R2, I_R2))
-push!(X, Probabilities(R2, X_R2))
+add_probabilities!(diagram, "R2", X_R)
```
-We also add the treatment decision node $TD$. The treatment decision is made based on the risk estimate achieved with the testing process.
+### Utilities of testing costs and health benefits
-```julia
-I_TD = [R2]
-push!(D, DecisionNode(TD, I_TD))
-```
-
-### Test costs and health benefits
-
-To add the value node $TC$, which represents testing costs, we need to define the consequences for its different information states. The node and the consequences are added in the following way.
+We define a utility matrix for node $TC$, which maps all its information states to testing
+costs. The unit in which the testing costs are added is quality-adjusted life-year (QALYs). The utility matrix is defined and added in the following way.
```julia
-I_TC = [T1, T2]
-Y_TC = zeros(S[I_TC]...)
cost_TRS = -0.0034645
cost_GRS = -0.004
-cost_forbidden = 0 #the cost of forbidden test combinations is negligible
-Y_TC[1 , 1] = cost_forbidden
-Y_TC[1 , 2] = cost_TRS + cost_GRS
-Y_TC[1, 3] = cost_TRS
-Y_TC[2, 1] = cost_GRS + cost_TRS
-Y_TC[2, 2] = cost_forbidden
-Y_TC[2, 3] = cost_GRS
-Y_TC[3, 1] = cost_TRS
-Y_TC[3, 2] = cost_GRS
-Y_TC[3, 3] = 0
-push!(V, ValueNode(TC, I_TC))
-push!(Y, Consequences(TC, Y_TC))
+forbidden = 0 #the cost of forbidden test combinations is negligible
+
+Y_TC = UtilityMatrix(diagram, "TC")
+set_utility!(Y_TC, ["TRS", "TRS"], forbidden)
+set_utility!(Y_TC, ["TRS", "GRS"], cost_TRS + cost_GRS)
+set_utility!(Y_TC, ["TRS", "no test"], cost_TRS)
+set_utility!(Y_TC, ["GRS", "TRS"], cost_TRS + cost_GRS)
+set_utility!(Y_TC, ["GRS", "GRS"], forbidden)
+set_utility!(Y_TC, ["GRS", "no test"], cost_GRS)
+set_utility!(Y_TC, ["no test", "TRS"], cost_TRS)
+set_utility!(Y_TC, ["no test", "GRS"], cost_GRS)
+set_utility!(Y_TC, ["no test", "no test"], 0)
+add_utilities!(diagram, "TC", Y_TC)
+
```
-The health benefits that are achieved are determined by whether treatment is administered and by the health of the patient. We add the final node to the model.
+The health benefits that are achieved are determined by whether treatment is administered and by the health of the patient. We add the final utility matrix to the model.
```julia
-I_HB = [H, TD]
-Y_HB = zeros(S[I_HB]...)
-Y_HB[1 , 1] = 6.89713671259061 # sick & treat
-Y_HB[1 , 2] = 6.65436854256236 # sick & don't treat
-Y_HB[2, 1] = 7.64528451705134 # healthy & treat
-Y_HB[2, 2] = 7.70088349200034 # healthy & don't treat
-push!(V, ValueNode(HB, I_HB))
-push!(Y, Consequences(HB, Y_HB))
+Y_HB = UtilityMatrix(diagram, "HB")
+set_utility!(Y_HB, ["CHD", "treatment"], 6.89713671259061)
+set_utility!(Y_HB, ["CHD", "no treatment"], 6.65436854256236 )
+set_utility!(Y_HB, ["no CHD", "treatment"], 7.64528451705134)
+set_utility!(Y_HB, ["no CHD", "no treatment"], 7.70088349200034)
+add_utilities!(diagram, "HB", Y_HB)
```
-### Validating the Influence Diagram
-Before creating the decision model, we need to validate the influence diagram and sort the nodes, probabilities and consequences in increasing order by the node indices.
+### Generate influence diagram
+Finally, we generate the full influence diagram before defining the decision model. By default this function uses the default path probabilities and utilities, which are defined as the joint probability of all chance events in the diagram and the sum of utilities in value nodes, respectively. In the [Contingent Portfolio Programming](contingent-portfolio-programming.md) example, we show how to use a user-defined custom path utility function.
+
+
+## Decision Model
+We define the JuMP model and declare the decision variables.
```julia
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
+model = Model()
+z = DecisionVariables(model, diagram)
```
-We also define the path probability and the path utility. We use the default path utility, which is the sum of the consequences of the path.
+In this problem, we want to forbid the model from choosing paths where the same test is repeated twice and where the first testing decision is not to perform a test but the second testing decision is to perform a test. We forbid the paths by declaring these combinations of states as forbidden paths.
+
```julia
-P = DefaultPathProbability(C, X)
-U = DefaultPathUtility(V, Y)
+forbidden_tests = ForbiddenPath(diagram, ["T1","T2"], [("TRS", "TRS"),("GRS", "GRS"),("no test", "TRS"), ("no test", "GRS")])
```
+We fix the state of the deterministic $R0$ node by declaring it as a fixed path. Fixing the state of node $R0$ is not necessary because of how the probabilities were defined. However, the fixed state reduces the need for some computation in the back-end.
-## Decision Model
-We define our model and declare the decision variables.
```julia
-model = Model()
-z = DecisionVariables(model, S, D)
+fixed_R0 = FixedPath(diagram, Dict("R0" => chosen_risk_level))
```
-In this problem, we want to forbid the model from choosing paths where the same test is repeated twice and where the first testing decision is not to perform a test but the second testing decision is to perform a test. We forbid the paths by declaring these combinations of states as forbidden paths.
-
-We also choose a scale factor of 1000, which will be used to scale the path probabilities. The probabilities need to be scaled because in this specific problem they are very small since the $R$ nodes have many states. Scaling the probabilities helps the solver find an optimal solution.
+We also choose a scale factor of 10000, which will be used to scale the path probabilities. The probabilities need to be scaled because in this specific problem they are very small since the $R$ nodes have a large number of states. Scaling the probabilities helps the solver find an optimal solution.
-We declare the path compatibility variables. We fix the state of the deterministic $R0$ node and forbid the unwanted testing strategies and scale the probabilities by giving them as parameters in the function call.
+We then declare the path compatibility variables. We fix the state of the deterministic $R0$ node , forbid the unwanted testing strategies and scale the probabilities by giving them as parameters in the function call.
```julia
-forbidden_tests = ForbiddenPath[([T1,T2], Set([(1,1),(2,2),(3,1), (3,2)]))]
scale_factor = 10000.0
-x_s = PathCompatibilityVariables(model, z, S, P; fixed = Dict(1 => chosen_risk_level), forbidden_paths = forbidden_tests, probability_cut=false)
+x_s = PathCompatibilityVariables(model, diagram, z; fixed = fixed_R0, forbidden_paths = [forbidden_tests], probability_cut=false)
+
```
We define the objective function as the expected value.
```julia
-EV = expected_value(model, x_s, U, P, probability_scale_factor= scale_factor)
+EV = expected_value(model, diagram, x_s, probability_scale_factor = scale_factor)
@objective(model, Max, EV)
```
@@ -258,72 +225,85 @@ optimize!(model)
-## Analyzing Results
-
-### Decision Strategy
-We obtain the optimal decision strategy from the z variable values.
+## Analyzing results
+We extract the results in the following way.
```julia
Z = DecisionStrategy(z)
+S_probabilities = StateProbabilities(diagram, Z)
+U_distribution = UtilityDistribution(diagram, Z)
+
```
-We use the function ```analysing_results``` to visualise the results in order to inspect the decision strategy. We use this tailor made function merely for convinience. From the printout, we can see that when the prior risk level is 12% the optimal decision strategy is to first perform TRS testing. At the second decision stage, GRS should be conducted if the updated risk estimate is between 16% and 28% and otherwise no further testing should be conducted. Treatment should be provided to those who have a final risk estimate greater than 18%. Notice that the blank spaces in the table are states which have a probability of zero, which means that given this data it is impossible for the patient to have their risk estimate updated to those risk levels.
+### Decision strategy
+We inspect the decision strategy. From the printout, we can see that when the prior risk level is 12% the optimal decision strategy is to first perform TRS testing. At the second decision stage, GRS should be conducted if the updated risk estimate is between 16% and 28% and otherwise no further testing should be conducted. Treatment should be provided to those who have a final risk estimate greater than 18%. Notice that the incompatible states are not included in the printout. The incompatible states are those that have a state probability of zero, which means that given this data it is impossible for the patient to have their risk estimate updated to those risk levels.
```julia
-sprobs = StateProbabilities(S, P, Z)
-```
-```julia
-julia> println(analysing_results(Z, sprobs))
-┌─────────────────┬────────┬────────┬────────┐
-│ Information_set │ T1 │ T2 │ TD │
-│ String │ String │ String │ String │
-├─────────────────┼────────┼────────┼────────┤
-│ 0% │ │ 3 │ 2 │
-│ 1% │ │ 3 │ 2 │
-│ 2% │ │ │ 2 │
-│ 3% │ │ 3 │ 2 │
-│ 4% │ │ │ │
-│ 5% │ │ │ │
-│ 6% │ │ 3 │ 2 │
-│ 7% │ │ 3 │ 2 │
-│ 8% │ │ │ 2 │
-│ 9% │ │ │ 2 │
-│ 10% │ │ 3 │ 2 │
-│ 11% │ │ 3 │ 2 │
-│ 12% │ 1 │ │ 2 │
-│ 13% │ │ 3 │ 2 │
-│ 14% │ │ 3 │ 2 │
-│ 15% │ │ │ 2 │
-│ 16% │ │ 2 │ 2 │
-│ 17% │ │ 2 │ 2 │
-│ 18% │ │ 2 │ 1 │
-│ 19% │ │ │ 1 │
-│ 20% │ │ │ 1 │
-│ 21% │ │ 2 │ 1 │
-│ 22% │ │ 2 │ 1 │
-│ 23% │ │ 2 │ 1 │
-│ 24% │ │ │ 1 │
-│ 25% │ │ │ 1 │
-│ 26% │ │ │ 1 │
-│ 27% │ │ │ 1 │
-│ 28% │ │ 3 │ 1 │
-│ 29% │ │ 3 │ 1 │
-│ 30% │ │ │ 1 │
-│ ⋮ │ ⋮ │ ⋮ │ ⋮ │
-└─────────────────┴────────┴────────┴────────┘
- 70 rows omitted
+julia> print_decision_strategy(diagram, Z, S_probabilities)
+┌────────────────┬────────────────┐
+│ State(s) of R0 │ Decision in T1 │
+├────────────────┼────────────────┤
+│ 12% │ TRS │
+└────────────────┴────────────────┘
+┌────────────────┬────────────────┐
+│ State(s) of R1 │ Decision in T2 │
+├────────────────┼────────────────┤
+│ 0% │ no test │
+│ 1% │ no test │
+│ 3% │ no test │
+│ 6% │ no test │
+│ 7% │ no test │
+│ 10% │ no test │
+│ 11% │ no test │
+│ 13% │ no test │
+│ 14% │ no test │
+│ 16% │ GRS │
+│ 17% │ GRS │
+│ 18% │ GRS │
+│ 21% │ GRS │
+│ 22% │ GRS │
+│ 23% │ GRS │
+│ 28% │ no test │
+│ 29% │ no test │
+│ 31% │ no test │
+│ 34% │ no test │
+│ ⋮ │ ⋮ │
+└────────────────┴────────────────┘
+ rows omitted
+
+┌────────────────┬────────────────┐
+│ State(s) of R2 │ Decision in TD │
+├────────────────┼────────────────┤
+│ 0% │ no treatment │
+│ 1% │ no treatment │
+│ 2% │ no treatment │
+│ 3% │ no treatment │
+│ 6% │ no treatment │
+│ 7% │ no treatment │
+│ 8% │ no treatment │
+│ 9% │ no treatment │
+│ 10% │ no treatment │
+│ 11% │ no treatment │
+│ 12% │ no treatment │
+│ 13% │ no treatment │
+│ 14% │ no treatment │
+│ 15% │ no treatment │
+│ 16% │ no treatment │
+│ 17% │ no treatment │
+│ 18% │ treatment │
+│ 19% │ treatment │
+│ 20% │ treatment │
+│ ⋮ │ ⋮ │
+└────────────────┴────────────────┘
+ rows omitted
```
-### Utility Distribution
+### Utility distribution
We can also print the utility distribution for the optimal strategy and some basic statistics for the distribution.
```julia
-udist = UtilityDistribution(S, P, U, Z)
-```
-
-```julia
-julia> print_utility_distribution(udist)
+julia> print_utility_distribution(U_distribution)
┌──────────┬─────────────┐
│ Utility │ Probability │
│ Float64 │ Float64 │
@@ -339,7 +319,7 @@ julia> print_utility_distribution(udist)
└──────────┴─────────────┘
```
```julia
-julia> print_statistics(udist)
+julia> print_statistics(U_distribution)
┌──────────┬────────────┐
│ Name │ Statistics │
│ String │ Float64 │
diff --git a/docs/src/examples/n-monitoring.md b/docs/src/examples/n-monitoring.md
index 4ff0145e..86d10bf3 100644
--- a/docs/src/examples/n-monitoring.md
+++ b/docs/src/examples/n-monitoring.md
@@ -8,7 +8,7 @@ The $N$-monitoring problem is described in [^1], sections 4.1 and 6.1.
The influence diagram of the generalized $N$-monitoring problem where $N≥1$ and indices $k=1,...,N.$ The nodes are associated with states as follows. **Load state** $L=\{high, low\}$ denotes the load on a structure, **report states** $R_k=\{high, low\}$ report the load state to the **action states** $A_k=\{yes, no\}$ which represent different decisions to fortify the structure. The **failure state** $F=\{failure, success\}$ represents whether or not the (fortified) structure fails under the load $L$. Finally, the utility at target $T$ depends on the whether $F$ fails and the fortification costs.
-We draw the cost of fortification $c_k∼U(0,1)$ from a uniform distribution, and the magnitude of fortification is directly proportional to the cost. Fortification is defined as
+We begin by choosing $N$ and defining our fortification cost function. We draw the cost of fortification $c_k∼U(0,1)$ from a uniform distribution, and the magnitude of fortification is directly proportional to the cost. Fortification is defined as
$$f(A_k=yes) = c_k$$
@@ -19,53 +19,67 @@ using Logging, Random
using JuMP, Gurobi
using DecisionProgramming
-Random.seed!(13)
-
const N = 4
-const L = [1]
-const R_k = [k + 1 for k in 1:N]
-const A_k = [(N + 1) + k for k in 1:N]
-const F = [2*N + 2]
-const T = [2*N + 3]
-const L_states = ["high", "low"]
-const R_k_states = ["high", "low"]
-const A_k_states = ["yes", "no"]
-const F_states = ["failure", "success"]
+
+Random.seed!(13)
const c_k = rand(N)
const b = 0.03
fortification(k, a) = [c_k[k], 0][a]
+```
+
+### Initialising the influence diagram
+We initialise the influence diagram before adding nodes to it.
-S = States([
- (length(L_states), L),
- (length(R_k_states), R_k),
- (length(A_k_states), A_k),
- (length(F_states), F)
-])
-C = Vector{ChanceNode}()
-D = Vector{DecisionNode}()
-V = Vector{ValueNode}()
-X = Vector{Probabilities}()
-Y = Vector{Consequences}()
+```julia
+diagram = InfluenceDiagram
```
-### Load State Probability
-The probability that the load is high, $ℙ(L=high)$, is drawn from a uniform distribution.
+### Adding nodes
+Add node $L$ which represents the load on the structure. This node is the root node and thus, has an empty information set. Its states describe the state of the load, they are $high$ and $low$.
-$$ℙ(L=high)∼U(0,1)$$
+```julia
+add_node!(diagram, ChanceNode("L", [], ["high", "low"]))
+```
+
+The report nodes $R_k$ and action nodes $A_k$ are easily added with a for-loop. The report nodes have node $L$ in their information sets and their states are $high$ and $low$. The actions are made based on these reports, which is represented by the action nodes $A_k$ having the report nodes $R_k$ in their information sets. The action nodes have states $yes$ and $no$, which represents decisions whether to fortify the structure or not.
```julia
-for j in L
- I_j = Vector{Node}()
- X_j = zeros(S[I_j]..., S[j])
- X_j[1] = rand()
- X_j[2] = 1.0 - X_j[1]
- push!(C, ChanceNode(j, I_j))
- push!(X, Probabilities(j, X_j))
+for i in 1:N
+ add_node!(diagram, ChanceNode("R$i", ["L"], ["high", "low"]))
+ add_node!(diagram, DecisionNode("A$i", ["R$i"], ["yes", "no"]))
end
```
-### Reporting Probability
-The probabilities of the report states correspond to the load state. We draw the values $x∼U(0,1)$ and $y∼U(0,1)$ from uniform distribution.
+The failure node $F$ has the load node $L$ and all of the action nodes $A_k$ in its information set. The failure node has states $failure$ and $success$.
+```julia
+add_node!(diagram, ChanceNode("F", ["L", ["A$i" for i in 1:N]...], ["failure", "success"]))
+```
+
+The value node $T$ is added as follows.
+```julia
+add_node!(diagram, ValueNode("T", ["F", ["A$i" for i in 1:N]...]))
+```
+
+### Generating arcs
+Now that all of the nodes have been added to the influence diagram we generate the arcs between the nodes. This step automatically orders the nodes, gives them indices and reorganises the information into the appropriate form.
+```julia
+generate_arcs!(diagram)
+```
+
+
+### Load State Probabilities
+After generating the arcs, the probabilities and utilities can be added. The probability that the load is high, $ℙ(L=high)$, is drawn from a uniform distribution. For different syntax options for adding probabilities and utilities, see the [usage page](../usage.md).
+
+$$ℙ(L=high)∼U(0,1)$$
+
+```julia
+X_L = [rand(), 0]
+X_L[2] = 1.0 - X_L[1]
+add_probabilities!(diagram, "L", X_L)
+```
+
+### Reporting Probabilities
+The probabilities of the report states correspond to the load state. We draw the values $x∼U(0,1)$ and $y∼U(0,1)$ from uniform distributions.
$$ℙ(R_k=high∣L=high)=\max\{x,1-x\}$$
@@ -73,124 +87,110 @@ $$ℙ(R_k=low∣L=low)=\max\{y,1-y\}$$
The probability of a correct report is thus in the range [0.5,1]. (This reflects the fact that a probability under 50% would not even make sense, since we would notice that if the test suggests a high load, the load is more likely to be low, resulting in that a low report "turns into" a high report and vice versa.)
+In Decision Programming we add these probabilities by declaring probabilty matrices for nodes $R_k$. The probability matrix of a report node $R_k$ has dimensions (2,2), where the rows correspond to the states $high$ and $low$ of its predecessor node $L$ and the columns to its own states $high$ and $low$.
+
```julia
-for j in R_k
- I_j = L
+for i in 1:N
x, y = rand(2)
- X_j = zeros(S[I_j]..., S[j])
- X_j[1, 1] = max(x, 1-x)
- X_j[1, 2] = 1.0 - X_j[1, 1]
- X_j[2, 2] = max(y, 1-y)
- X_j[2, 1] = 1.0 - X_j[2, 2]
- push!(C, ChanceNode(j, I_j))
- push!(X, Probabilities(j, X_j))
+ X_R = ProbabilityMatrix(diagram, "R$i")
+ set_probability!(X_R, ["high", "high"], max(x, 1-x))
+ set_probability!(X_R, ["high", "low"], 1-max(x, 1-x))
+ set_probability!(X_R, ["low", "low"], max(y, 1-y))
+ set_probability!(X_R, ["low", "high"], 1-max(y, 1-y))
+ add_probabilities!(diagram, "R$i", X_R)
end
```
-### Decision to Fortify
+### Probability of Failure
+The probability of failure is decresead by fortification actions. We draw the values $x∼U(0,1)$ and $y∼U(0,1)$ from uniform distribution.
-Only the corresponding load report is known when making the fortification decision, thus $I(A_k)=R_k$.
+$$ℙ(F=failure∣A_N,...,A_1,L=high)=\frac{\max{\{x, 1-x\}}}{\exp{(b ∑_{k=1,...,N} f(A_k))}}$$
+$$ℙ(F=failure∣A_N,...,A_1,L=low)=\frac{\min{\{y, 1-y\}}}{\exp{(b ∑_{k=1,...,N} f(A_k))}}$$
+
+First we initialise the probability matrix for node $F$.
```julia
-for (i, j) in zip(R_k, A_k)
- I_j = [i]
- push!(D, DecisionNode(j, I_j))
-end
+X_F = ProbabilityMatrix(diagram, "F")
```
-### Probability of Failure
-The probabilities of failure which are decresead by fortifications. We draw the values $x∼U(0,1)$ and $y∼U(0,1)$ from uniform distribution.
+This matrix has dimensions $(2, \textcolor{orange}{2, 2, 2, 2}, 2)$ because node $L$ and nodes $A_k$, which form the information set of $F$, all have 2 states and node $F$ itself also has 2 states. The orange colored dimensions correspond to the states of the action nodes $A_k$.
-$$ℙ(F=failure∣A_N,...,A_1,L=high)=\frac{\max{\{x, 1-x\}}}{\exp{(b ∑_{k=1,...,N} f(A_k))}}$$
+To set the probabilities we have to iterate over the information states. Here it helps to know that in Decision Programming the states of each node are mapped to numbers in the back-end. For instance, the load states $high$ and $low$ are referred to as 1 and 2. The same applies for the action states $yes$ and $no$, they are states 1 and 2. The `paths` function allows us to iterate over the subpaths of specific nodes. In these paths, the states are refer to by their indices. Using this information, we can easily iterate over the information states using the `paths` function and set the probability values into the probability matrix.
-$$ℙ(F=failure∣A_N,...,A_1,L=low)=\frac{\min{\{y, 1-y\}}}{\exp{(b ∑_{k=1,...,N} f(A_k))}}$$
```julia
-for j in F
- I_j = L ∪ A_k
- x, y = rand(2)
- X_j = zeros(S[I_j]..., S[j])
- for s in paths(S[A_k])
- d = exp(b * sum(fortification(k, a) for (k, a) in enumerate(s)))
- X_j[1, s..., 1] = max(x, 1-x) / d
- X_j[1, s..., 2] = 1.0 - X_j[1, s..., 1]
- X_j[2, s..., 1] = min(y, 1-y) / d
- X_j[2, s..., 2] = 1.0 - X_j[2, s..., 1]
- end
- push!(C, ChanceNode(j, I_j))
- push!(X, Probabilities(j, X_j))
+x_F, y_F = rand(2)
+for s in paths([State(2) for i in 1:N])
+ denominator = exp(b * sum(fortification(k, a) for (k, a) in enumerate(s)))
+ X_F[1, s..., 1] = max(x_F, 1-x_F) / denominator
+ X_F[1, s..., 2] = 1.0 - X_F[1, s..., 1]
+ X_F[2, s..., 1] = min(y_F, 1-y_F) / denominator
+ X_F[2, s..., 2] = 1.0 - X_F[2, s..., 1]
end
```
-### Consequences
-Utility from consequences at target $T$ from failure state $F$
+After declaring the probability matrix, we add it to the influence diagram.
+```julia
+add_probabilities!(diagram, "F", X_F)
+```
+
+### Utility
+The utility from the different scenarios of the failure state at target $T$ are
$$g(F=failure) = 0$$
-$$g(F=success) = 100$$
+$$g(F=success) = 100$$.
-Utility from consequences at target $T$ from action states $A_k$ is
+Utilities from the action states $A_k$ at target $T$ are
$$f(A_k=yes) = c_k$$
-$$f(A_k=no) = 0$$
+$$f(A_k=no) = 0.$$
-Total cost
+The total cost is thus
-$$Y(F, A_N, ..., A_1) = g(F) + (-f(A_N)) + ... + (-f(A_1))$$
+$$Y(F, A_N, ..., A_1) = g(F) + (-f(A_N)) + ... + (-f(A_1)).$$
+We first declare the utility matrix for node $T$.
```julia
-for j in T
- I_j = A_k ∪ F
- Y_j = zeros(S[I_j]...)
- for s in paths(S[A_k])
- cost = sum(-fortification(k, a) for (k, a) in enumerate(s))
- Y_j[s..., 1] = cost + 0
- Y_j[s..., 2] = cost + 100
- end
- push!(V, ValueNode(j, I_j))
- push!(Y, Consequences(j, Y_j))
-end
+Y_T = UtilityMatrix(diagram, "T")
```
-
-### Validating the Influence Diagram
-
-Finally, we need to validate the influence diagram and sort the nodes, probabilities and consequences in increasing order by the node indices.
+This matrix has dimensions $(2, \textcolor{orange}{2, 2, 2, 2})$, where the dimensions correspond to the numbers of states the nodes in the information set have. Similarly as before, the first dimension corresponds to the states of node $F$ and the other 4 dimensions (in oragne) correspond to the states of the $A_k$ nodes. The utilities are set and added similarly to how the probabilities were added above.
```julia
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
+for s in paths([State(2) for i in 1:N])
+ cost = sum(-fortification(k, a) for (k, a) in enumerate(s))
+ Y_T[1, s...] = 0 + cost
+ Y_T[2, s...] = 100 + cost
+end
+add_utilities!(diagram, "T", Y_T)
```
-We define the path probability.
-```julia
-P = DefaultPathProbability(C, X)
-```
+### Generate Influence Diagram
+The full influence diagram can now be generated. We use the default path probabilities and utilities, which are the default setting in this function. In the [Contingent Portfolio Programming](contingent-portfolio-programming.md) example, we show how to use a user-defined custom path utility function.
+
+In this particular problem, some of the path utilities are negative. In this case, we choose to use the [positive path utility](../decision-programming/decision-model.md) transformation, which translates the path utilities to positive values. This allows us to exclude the probability cut in the next section.
-As the path utility, we use the default, which is the sum of the consequences given the path.
```julia
-U = DefaultPathUtility(V, Y)
+generate_diagram!(diagram, positive_path_utility = true)
```
-
## Decision Model
-
-An affine transformation is applied to the path utility, making all utilities positive. See the [section](../decision-programming/decision-model.md) on positive path utilities for details.
+We initialise the JuMP model and declare the decision and path compatibility variables. Since we applied an affine transformation to the utility function, the probability cut can be excluded from the model formulation.
```julia
-U⁺ = PositivePathUtility(S, U)
model = Model()
-z = DecisionVariables(model, S, D)
-x_s = PathCompatibilityVariables(model, z, S, P, probability_cut = false)
+z = DecisionVariables(model, diagram)
+x_s = PathCompatibilityVariables(model, diagram, z, probability_cut = false)
```
-`
The expected utility is used as the objective and the problem is solved using Gurobi.
```julia
-EV = expected_value(model, π_s, U⁺)
+EV = expected_value(model, diagram, x_s)
@objective(model, Max, EV)
+
optimizer = optimizer_with_attributes(
() -> Gurobi.Optimizer(Gurobi.Env()),
"IntFeasTol" => 1e-9,
@@ -202,47 +202,46 @@ optimize!(model)
## Analyzing Results
-The decision strategy shows us that the optimal strategy is to make all four fortifications regardless of the reports (state 1 in fortification nodes corresponds to the option "yes").
+We obtain the decision strategy, state probabilities and utility distribution from the solution.
```julia
Z = DecisionStrategy(z)
+U_distribution = UtilityDistribution(diagram, Z)
+S_probabilities = StateProbabilities(diagram, Z)
```
+The decision strategy shows us that the optimal strategy is to make all four fortifications regardless of the reports.
+
```julia-repl
-julia> print_decision_strategy(S, Z)
-┌────────┬──────┬───┐
-│ Nodes │ (2,) │ 6 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 1 │
-│ States │ (2,) │ 1 │
-└────────┴──────┴───┘
-┌────────┬──────┬───┐
-│ Nodes │ (3,) │ 7 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 1 │
-│ States │ (2,) │ 1 │
-└────────┴──────┴───┘
-┌────────┬──────┬───┐
-│ Nodes │ (4,) │ 8 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 1 │
-│ States │ (2,) │ 1 │
-└────────┴──────┴───┘
-┌────────┬──────┬───┐
-│ Nodes │ (5,) │ 9 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 1 │
-│ States │ (2,) │ 1 │
-└────────┴──────┴───┘
+julia> print_decision_strategy(diagram, Z, S_probabilities)
+┌────────────────┬────────────────┐
+│ State(s) of R1 │ Decision in A1 │
+├────────────────┼────────────────┤
+│ high │ yes │
+│ low │ yes │
+└────────────────┴────────────────┘
+┌────────────────┬────────────────┐
+│ State(s) of R2 │ Decision in A2 │
+├────────────────┼────────────────┤
+│ high │ yes │
+│ low │ yes │
+└────────────────┴────────────────┘
+┌────────────────┬────────────────┐
+│ State(s) of R3 │ Decision in A3 │
+├────────────────┼────────────────┤
+│ high │ yes │
+│ low │ yes │
+└────────────────┴────────────────┘
+┌────────────────┬────────────────┐
+│ State(s) of R4 │ Decision in A4 │
+├────────────────┼────────────────┤
+│ high │ yes │
+│ low │ yes │
+└────────────────┴────────────────┘
```
-The state probabilities for the strategy $Z$ can also be obtained. These tell the probability of each state in each node, given the strategy $Z$.
-
-
-```julia
-sprobs = StateProbabilities(S, P, Z)
-```
+The state probabilities for strategy $Z$ are also obtained. These tell the probability of each state in each node, given strategy $Z$.
```julia-repl
julia> print_state_probabilities(sprobs, L)
@@ -283,12 +282,8 @@ julia> print_state_probabilities(sprobs, F)
We can also print the utility distribution for the optimal strategy and some basic statistics for the distribution.
-```julia
-udist = UtilityDistribution(S, P, U, Z)
-```
-
```julia-repl
-julia> print_utility_distribution(udist)
+julia> print_utility_distribution(U_distribution)
┌───────────┬─────────────┐
│ Utility │ Probability │
│ Float64 │ Float64 │
@@ -299,7 +294,7 @@ julia> print_utility_distribution(udist)
```
```julia-repl
-julia> print_statistics(udist)
+julia> print_statistics(U_distribution)
┌──────────┬────────────┐
│ Name │ Statistics │
│ String │ Float64 │
diff --git a/docs/src/examples/pig-breeding.md b/docs/src/examples/pig-breeding.md
index 446825ee..29f8282c 100644
--- a/docs/src/examples/pig-breeding.md
+++ b/docs/src/examples/pig-breeding.md
@@ -2,56 +2,75 @@
## Description
The pig breeding problem as described in [^1].
-"A pig breeder is growing pigs for a period of four months and subsequently selling them. During this period the pig may or may not develop a certain disease. If the pig has the disease at the time it must be sold, the pig must be sold for slaughtering, and its expected market price is then 300 DKK (Danish kroner). If it is disease free, its expected market price as a breeding animal is 1000 DKK
+>A pig breeder is growing pigs for a period of four months and subsequently selling them. During this period the pig may or may not develop a certain disease. If the pig has the disease at the time it must be sold, the pig must be sold for slaughtering, and its expected market price is then 300 DKK (Danish kroner). If it is disease free, its expected market price as a breeding animal is 1000 DKK
+>
+>Once a month, a veterinary doctor sees the pig and makes a test for presence of the disease. If the pig is ill, the test will indicate this with probability 0.80, and if the pig is healthy, the test will indicate this with probability 0.90. At each monthly visit, the doctor may or may not treat the pig for the disease by injecting a certain drug. The cost of an injection is 100 DKK.
+>
+>A pig has the disease in the first month with probability 0.10. A healthy pig develops the disease in the subsequent month with probability 0.20 without injection, whereas a healthy and treated pig develops the disease with probability 0.10, so the injection has some preventive effect. An untreated pig that is unhealthy will remain so in the subsequent month with probability 0.90, whereas the similar probability is 0.50 for an unhealthy pig that is treated. Thus spontaneous cure is possible, but treatment is beneficial on average.
-Once a month, a veterinary doctor sees the pig and makes a test for presence of the disease. If the pig is ill, the test will indicate this with probability 0.80, and if the pig is healthy, the test will indicate this with probability 0.90. At each monthly visit, the doctor may or may not treat the pig for the disease by injecting a certain drug. The cost of an injection is 100 DKK.
-A pig has the disease in the first month with probability 0.10. A healthy pig develops the disease in the subsequent month with probability 0.20 without injection, whereas a healthy and treated pig develops the disease with probability 0.10, so the injection has some preventive effect. An untreated pig that is unhealthy will remain so in the subsequent month with probability 0.90, whereas the similar probability is 0.50 for an unhealthy pig that is treated. Thus spontaneous cure is possible, but treatment is beneficial on average."
-
-
-## Influence Diagram
+## Influence diagram

-The influence diagram for the the generalized $N$-month pig breeding. The nodes are associated with the following states. **Health states** $h_k=\{ill,healthy\}$ represent the health of the pig at month $k=1,...,N$. **Test states** $t_k=\{positive,negative\}$ represent the result from testing the pig at month $k=1,...,N-1$. **Treat states** $d_k=\{treat, pass\}$ represent the decision to treat the pig with an injection at month $k=1,...,N-1$.
+The influence diagram for the generalized $N$-month pig breeding problem. The nodes are associated with the following states. **Health states** $h_k=\{ill,healthy\}$ represent the health of the pig at month $k=1,...,N$. **Test states** $t_k=\{positive,negative\}$ represent the result from testing the pig at month $k=1,...,N-1$. **Treatment states** $d_k=\{treat, pass\}$ represent the decision to treat the pig with an injection at month $k=1,...,N-1$.
> The dashed arcs represent the no-forgetting principle and we can toggle them on and off in the formulation.
-In decision programming, we start by defining the node indices and states, as follows:
+In this example, we solve the 4 month pig breeding problem and thus, declare $N = 4$.
```julia
using JuMP, Gurobi
using DecisionProgramming
const N = 4
-const health = [3*k - 2 for k in 1:N]
-const test = [3*k - 1 for k in 1:(N-1)]
-const treat = [3*k for k in 1:(N-1)]
-const cost = [(3*N - 2) + k for k in 1:(N-1)]
-const price = [(3*N - 2) + N]
-const health_states = ["ill", "healthy"]
-const test_states = ["positive", "negative"]
-const treat_states = ["treat", "pass"]
-
-S = States([
- (length(health_states), health),
- (length(test_states), test),
- (length(treat_states), treat),
-])
-C = Vector{ChanceNode}()
-D = Vector{DecisionNode}()
-V = Vector{ValueNode}()
-X = Vector{Probabilities}()
-Y = Vector{Consequences}()
+```
+In Decision Programming, we start by initialising an empty influence diagram. Then we define the nodes with their information sets and states and add them to the influence diagram.
+```julia
+diagram = InfluenceDiagram()
```
-Next, we define the nodes with their information sets and corresponding probabilities or consequences.
-### Health at First Month
+### Health at first month
-As seen in the influence diagram, the node $h_1$ has no arcs into it, making it a root node. Therefore, the information set $I(h_1)$ is empty.
+As seen in the influence diagram, the node $h_1$ has no arcs into it making it a root node. Therefore, the information set $I(h_1)$ is empty. The states of this node are $ill$ and $healthy$.
-The probability that pig is ill in the first month is
+
+```julia
+add_node!(diagram, ChanceNode("H1", [], ["ill", "healthy"]))
+```
+
+### Health, test results and treatment decisions at subsequent months
+The chance and decision nodes representing the health, test results, treatment decisions for the following months can be added easily using a for-loop. The value node representing the treatment costs in each month is also added. Each node is given a name, its information set and states. Remember that value nodes do not have states. Notice that we do not assume the no-forgetting principle and thus, the information sets of the treatment decisions only contain the previous test result.
+
+```julia
+for i in 1:N-1
+ # Testing result
+ add_node!(diagram, ChanceNode("T$i", ["H$i"], ["positive", "negative"]))
+ # Decision to treat
+ add_node!(diagram, DecisionNode("D$i", ["T$i"], ["treat", "pass"]))
+ # Cost of treatment
+ add_node!(diagram, ValueNode("C$i", ["D$i"]))
+ # Health of next period
+ add_node!(diagram, ChanceNode("H$(i+1)", ["H$(i)", "D$(i)"], ["ill", "healthy"]))
+end
+```
+
+### Market price
+The final value node, representing the market price, is added. It has the final health node $h_N$ as its information set.
+```julia
+add_node!(diagram, ValueNode("MP", ["H$N"]))
+```
+
+### Generate arcs
+Now that all of the nodes have been added to the influence diagram, we generate the arcs between the nodes. This step automatically orders the nodes, gives them indices and reorganises the information into the correct form.
+```julia
+generate_arcs!(diagram)
+```
+
+### Probabilities
+
+We define probability distributions for all chance nodes. For the first health node, the probability distribution is defined over its two states $ill$ and $healthy$. The probability that pig is ill in the first month is
$$ℙ(h_1 = ill)=0.1.$$
@@ -59,159 +78,106 @@ We obtain the complement probabilities for binary states by subtracting from one
$$ℙ(h_1 = healthy)=1-ℙ(h_1 = ill).$$
-In decision programming, we add the nodes and probabilities as follows:
-
+In Decision Programming, we add these probabilities for node $h_1$ as follows. Notice, that the probability vector is ordered according to the order that the states were given in when defining node $h_1$. More information on the syntax of adding probabilities is found on the [usage page](../usage.md).
```julia
-for j in health[[1]]
- I_j = Vector{Node}()
- X_j = zeros(S[I_j]..., S[j])
- X_j[1] = 0.1
- X_j[2] = 1.0 - X_j[1]
- push!(C, ChanceNode(j, I_j))
- push!(X, Probabilities(j, X_j))
-end
+add_probabilities!(diagram, "H1", [0.1, 0.9])
```
-### Health at Subsequent Months
-The probability that the pig is ill in the subsequent months $k=2,...,N$ depends on the treatment decision and state of health in the previous month $k-1$. The nodes $h_{k-1}$ and $d_{k-1}$ are thus in the information set $I(h_k)$, meaning that the probability distribution of $h_k$ is conditional on these nodes:
-
-$$ℙ(h_k = ill ∣ d_{k-1} = pass, h_{k-1} = healthy)=0.2,$$
+The probability distributions for the other health nodes are identical. Thus, we define one probability matrix and use it for all the subsequent months' health nodes. The probability that the pig is ill in the subsequent months $k=2,...,N$ depends on the treatment decision and state of health in the previous month $k-1$. The nodes $h_{k-1}$ and $d_{k-1}$ are thus in the information set $I(h_k)$, meaning that the probability distribution of $h_k$ is conditional on these nodes:
-$$ℙ(h_k = ill ∣ d_{k-1} = treat, h_{k-1} = healthy)=0.1,$$
+$$ℙ(h_k = ill ∣ h_{k-1} = healthy, \ d_{k-1} = pass)=0.2,$$
-$$ℙ(h_k = ill ∣ d_{k-1} = pass, h_{k-1} = ill)=0.9,$$
+$$ℙ(h_k = ill ∣ h_{k-1} = healthy, \ d_{k-1} = treat)=0.1,$$
-$$ℙ(h_k = ill ∣ d_{k-1} = treat, h_{k-1} = ill)=0.5.$$
+$$ℙ(h_k = ill ∣ h_{k-1} = ill, \ d_{k-1} = pass)=0.9,$$
-In decision programming:
+$$ℙ(h_k = ill ∣ h_{k-1} = ill, \ d_{k-1} = treat)=0.5.$$
+In Decision Programming, the probability matrix is define in the following way. Notice, that the ordering of the information state corresponds to the order in which the information set was defined when adding the health nodes.
```julia
-for (i, k, j) in zip(health[1:end-1], treat, health[2:end])
- I_j = [i, k]
- X_j = zeros(S[I_j]..., S[j])
- X_j[2, 2, 1] = 0.2
- X_j[2, 2, 2] = 1.0 - X_j[2, 2, 1]
- X_j[2, 1, 1] = 0.1
- X_j[2, 1, 2] = 1.0 - X_j[2, 1, 1]
- X_j[1, 2, 1] = 0.9
- X_j[1, 2, 2] = 1.0 - X_j[1, 2, 1]
- X_j[1, 1, 1] = 0.5
- X_j[1, 1, 2] = 1.0 - X_j[1, 1, 1]
- push!(C, ChanceNode(j, I_j))
- push!(X, Probabilities(j, X_j))
-end
+X_H = ProbabilityMatrix(diagram, "H2")
+set_probability!(X_H, ["healthy", "pass", :], [0.2, 0.8])
+set_probability!(X_H, ["healthy", "treat", :], [0.1, 0.9])
+set_probability!(X_H, ["ill", "pass", :], [0.9, 0.1])
+set_probability!(X_H, ["ill", "treat", :], [0.5, 0.5])
```
-Note that the order of states indexing the probabilities is reversed compared to the mathematical definition.
-
-### Health Test
-For the probabilities that the test indicates a pig's health correctly at month $k=1,...,N-1$, we have
+Next we define the probability matrix for the test results. Here again, we note that the probability distributions for all test results are identical, and thus we only define the probability matrix once. For the probabilities that the test indicates a pig's health correctly at month $k=1,...,N-1$, we have
$$ℙ(t_k = positive ∣ h_k = ill) = 0.8,$$
$$ℙ(t_k = negative ∣ h_k = healthy) = 0.9.$$
-In decision programming:
+In Decision Programming:
```julia
-for (i, j) in zip(health, test)
- I_j = [i]
- X_j = zeros(S[I_j]..., S[j])
- X_j[1, 1] = 0.8
- X_j[1, 2] = 1.0 - X_j[1, 1]
- X_j[2, 2] = 0.9
- X_j[2, 1] = 1.0 - X_j[2, 2]
- push!(C, ChanceNode(j, I_j))
- push!(X, Probabilities(j, X_j))
-end
+X_T = ProbabilityMatrix(diagram, "T1")
+set_probability!(X_T, ["ill", "positive"], 0.8)
+set_probability!(X_T, ["ill", "negative"], 0.2)
+set_probability!(X_T, ["healthy", "negative"], 0.9)
+set_probability!(X_T, ["healthy", "positive"], 0.1)
```
-### Decision to Treat
-In decision programing, we add the decision nodes for decision to treat the pig as follows:
+We add the probability matrices into the influence diagram using a for-loop.
```julia
-for (i, j) in zip(test, treat)
- I_j = [i]
- push!(D, DecisionNode(j, I_j))
+for i in 1:N-1
+ add_probabilities!(diagram, "T$i", X_T)
+ add_probabilities!(diagram, "H$(i+1)", X_H)
end
```
-The no-forgetting assumption does not hold, and the information set $I(d_k)$ only comprises the previous test result.
-### Cost of Treatment
-The cost of treatment decision for the pig at month $k=1,...,N-1$ is defined
+### Utilities
+
+The cost incurred by the treatment decision at month $k=1,...,N-1$ is
$$Y(d_k=treat) = -100,$$
$$Y(d_k=pass) = 0.$$
-In decision programming:
+In Decision Programming the utility values are added using utility matrices. Notice that the utility values in the matrix are given in the same order as the states of node $h_N$ were defined when node $h_N$ was added.
```julia
-for (i, j) in zip(treat, cost)
- I_j = [i]
- Y_j = zeros(S[I_j]...)
- Y_j[1] = -100
- Y_j[2] = 0
- push!(V, ValueNode(j, I_j))
- push!(Y, Consequences(j, Y_j))
+for i in 1:N-1
+ add_utilities!(diagram, "C$i", [-100.0, 0.0])
end
```
-
-### Selling Price
-The price of given the pig health at month $N$ is defined
+The market price of the pig given its health at month $N$ is
$$Y(h_N=ill) = 300,$$
$$Y(h_N=healthy) = 1000.$$
-In decision programming:
+In Decision Programming:
```julia
-for (i, j) in zip(health[end], price)
- I_j = [i]
- Y_j = zeros(S[I_j]...)
- Y_j[1] = 300
- Y_j[2] = 1000
- push!(V, ValueNode(j, I_j))
- push!(Y, Consequences(j, Y_j))
-end
+add_utilities!(diagram, "MP", [300.0, 1000.0])
```
-### Validating Influence Diagram
-Finally, we need to validate the influence diagram and sort the nodes, probabilities and consequences in increasing order by the node indices.
+### Generate influence diagram
+After adding nodes, generating arcs and defining probability and utility values, we generate the full influence diagram. By default this function uses the default path probabilities and utilities, which are defined as the joint probability of all chance events in the diagram and the sum of utilities in value nodes, respectively. In the [Contingent Portfolio Programming](contingent-portfolio-programming.md) example, we show how to use a user-defined custom path utility function.
-```julia
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
-```
+In the pig breeding problem, when the $N$ is large some of the path utilities become negative. In this case, we choose to use the [positive path utility](../decision-programming/decision_model.md) transformation, which allows us to exclude the probability cut in the next section.
-We define the path probability.
```julia
-P = DefaultPathProbability(C, X)
+generate_diagram!(diagram, positive_path_utility = true)
```
-As the path utility, we use the default, which is the sum of the consequences given the path.
-```julia
-U = DefaultPathUtility(V, Y)
-```
-
-
-## Decision Model
+## Decision model
-We apply an affine transformation to the utility function, making all path utilities positive. Now that all path utilities are positive, the probability cut can be excluded from the model. The purpose of this is discussed in the [theoretical section](../decision-programming/decision-model.md) of this documentation.
+Next we initialise the JuMP model and add the decision variables. Then we add the path compatibility variables. Since we applied an affine transformation to the utility function, making all path utilities positive, the probability cut can be excluded from the model. The purpose of this is discussed in the [theoretical section](../decision-programming/decision-model.md) of this documentation.
```julia
-U⁺ = PositivePathUtility(S, U)
model = Model()
-z = DecisionVariables(model, S, D)
-x_s = PathCompatibilityVariables(model, z, S, P, probability_cut = false)
+z = DecisionVariables(model, diagram)
+x_s = PathCompatibilityVariables(model, diagram, z, probability_cut = false)
```
We create the objective function
```julia
-EV = expected_value(model, x_s, U⁺, P)
+EV = expected_value(model, diagram, x_s)
@objective(model, Max, EV)
```
@@ -227,88 +193,92 @@ optimize!(model)
```
-## Analyzing Results
-### Decision Strategy
+## Analyzing results
-We obtain the optimal decision strategy:
+Once the model is solved, we extract the results. The results are the decision strategy, state probabilities and utility distribution.
```julia
Z = DecisionStrategy(z)
+S_probabilities = StateProbabilities(diagram, Z)
+U_distribution = UtilityDistribution(diagram, Z)
```
+### Decision strategy
+
+The optimal decision strategy is:
+
```julia-repl
-julia> print_decision_strategy(S, Z)
-┌────────┬──────┬───┐
-│ Nodes │ (2,) │ 3 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 2 │
-│ States │ (2,) │ 2 │
-└────────┴──────┴───┘
-┌────────┬──────┬───┐
-│ Nodes │ (5,) │ 6 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 1 │
-│ States │ (2,) │ 2 │
-└────────┴──────┴───┘
-┌────────┬──────┬───┐
-│ Nodes │ (8,) │ 9 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 1 │
-│ States │ (2,) │ 2 │
-└────────┴──────┴───┘
+julia> print_decision_strategy(diagram, Z, S_probabilities)
+┌────────────────┬────────────────┐
+│ State(s) of T1 │ Decision in D1 │
+├────────────────┼────────────────┤
+│ positive │ pass │
+│ negative │ pass │
+└────────────────┴────────────────┘
+┌────────────────┬────────────────┐
+│ State(s) of T2 │ Decision in D2 │
+├────────────────┼────────────────┤
+│ positive │ treat │
+│ negative │ pass │
+└────────────────┴────────────────┘
+┌────────────────┬────────────────┐
+│ State(s) of T3 │ Decision in D3 │
+├────────────────┼────────────────┤
+│ positive │ treat │
+│ negative │ pass │
+└────────────────┴────────────────┘
```
-The optimal strategy is as follows. In the first period, state 2 (no treatment) is chosen in node 3 ($d_1$) regardless of the state of node 2 ($t_1$). In other words, the pig is not treated in the first month. In the two subsequent months, state 1 (treat) is chosen if the corresponding test result is 1 (positive).
+The optimal strategy is to not treat the pig in the first month regardless of if it is sick or not. In the two subsequent months, the pig should be treated if the test result is positive.
-### State Probabilities
+### State probabilities
-The state probabilities for the strategy $Z$ can also be obtained. These tell the probability of each state in each node, given the strategy $Z$.
+The state probabilities for strategy $Z$ tell the probability of each state in each node, given strategy $Z$.
-```julia
-sprobs = StateProbabilities(S, P, Z)
-```
```julia-repl
-julia> print_state_probabilities(sprobs, health)
-┌───────┬──────────┬──────────┬─────────────┐
-│ Node │ State 1 │ State 2 │ Fixed state │
-│ Int64 │ Float64 │ Float64 │ String │
-├───────┼──────────┼──────────┼─────────────┤
-│ 1 │ 0.100000 │ 0.900000 │ │
-│ 4 │ 0.270000 │ 0.730000 │ │
-│ 7 │ 0.295300 │ 0.704700 │ │
-│ 10 │ 0.305167 │ 0.694833 │ │
-└───────┴──────────┴──────────┴─────────────┘
-julia> print_state_probabilities(sprobs, test)
-┌───────┬──────────┬──────────┬─────────────┐
-│ Node │ State 1 │ State 2 │ Fixed state │
-│ Int64 │ Float64 │ Float64 │ String │
-├───────┼──────────┼──────────┼─────────────┤
-│ 2 │ 0.170000 │ 0.830000 │ │
-│ 5 │ 0.289000 │ 0.711000 │ │
-│ 8 │ 0.306710 │ 0.693290 │ │
-└───────┴──────────┴──────────┴─────────────┘
-julia> print_state_probabilities(sprobs, treat)
-┌───────┬──────────┬──────────┬─────────────┐
-│ Node │ State 1 │ State 2 │ Fixed state │
-│ Int64 │ Float64 │ Float64 │ String │
-├───────┼──────────┼──────────┼─────────────┤
-│ 3 │ 0.000000 │ 1.000000 │ │
-│ 6 │ 0.289000 │ 0.711000 │ │
-│ 9 │ 0.306710 │ 0.693290 │ │
-└───────┴──────────┴──────────┴─────────────┘
+julia> health_nodes = [["H$i" for i in 1:N]...]
+julia> print_state_probabilities(diagram, S_probabilities, health_nodes)
+
+┌────────┬──────────┬──────────┬─────────────┐
+│ Node │ State 1 │ State 2 │ Fixed state │
+│ String │ Float64 │ Float64 │ String │
+├────────┼──────────┼──────────┼─────────────┤
+│ H1 │ 0.100000 │ 0.900000 │ │
+│ H2 │ 0.270000 │ 0.730000 │ │
+│ H3 │ 0.295300 │ 0.704700 │ │
+│ H4 │ 0.305167 │ 0.694833 │ │
+└────────┴──────────┴──────────┴─────────────┘
+
+julia> test_nodes = [["T$i" for i in 1:N-1]...]
+julia> print_state_probabilities(diagram, S_probabilities, test_nodes)
+┌────────┬──────────┬──────────┬─────────────┐
+│ Node │ State 1 │ State 2 │ Fixed state │
+│ String │ Float64 │ Float64 │ String │
+├────────┼──────────┼──────────┼─────────────┤
+│ T1 │ 0.170000 │ 0.830000 │ │
+│ T2 │ 0.289000 │ 0.711000 │ │
+│ T3 │ 0.306710 │ 0.693290 │ │
+└────────┴──────────┴──────────┴─────────────┘
+
+julia> treatment_nodes = [["D$i" for i in 1:N-1]...]
+julia> print_state_probabilities(diagram, S_probabilities, treatment_nodes)
+┌────────┬──────────┬──────────┬─────────────┐
+│ Node │ State 1 │ State 2 │ Fixed state │
+│ String │ Float64 │ Float64 │ String │
+├────────┼──────────┼──────────┼─────────────┤
+│ D1 │ 0.000000 │ 1.000000 │ │
+│ D2 │ 0.289000 │ 0.711000 │ │
+│ D3 │ 0.306710 │ 0.693290 │ │
+└────────┴──────────┴──────────┴─────────────┘
```
-### Utility Distribution
-
-We can also print the utility distribution for the optimal strategy. The selling prices for a healthy and an ill pig are 1000DKK and 300DKK, respectively, while the cost of treatment is 100DKK. We can see that the probability of the pig being ill in the end is the sum of three first probabilities, approximately 30.5%. This matches the probability of state 1 in node 10 in the state probabilities shown above.
+### Utility distribution
-```julia
-udist = UtilityDistribution(S, P, U, Z)
-```
+We can also print the utility distribution for the optimal strategy. The selling prices for a healthy and an ill pig are 1000DKK and 300DKK, respectively, while the cost of treatment is 100DKK. We can see that the probability of the pig being ill in the end is the sum of three first probabilities, approximately 30.5%. This matches the probability of state $ill$ in the last node $h_4$ in the state probabilities shown above.
```julia-repl
-julia> print_utility_distribution(udist)
+julia> print_utility_distribution(U_distribution)
┌─────────────┬─────────────┐
│ Utility │ Probability │
│ Float64 │ Float64 │
@@ -325,7 +295,7 @@ julia> print_utility_distribution(udist)
Finally, we print some statistics for the utility distribution. The expected value of the utility is 727DKK, the same as in [^1].
```julia-repl
-julia> print_statistics(udist)
+julia> print_statistics(U_distribution)
┌──────────┬────────────┐
│ Name │ Statistics │
│ String │ Float64 │
diff --git a/docs/src/examples/used-car-buyer.md b/docs/src/examples/used-car-buyer.md
index f1a0a697..2880c7ec 100644
--- a/docs/src/examples/used-car-buyer.md
+++ b/docs/src/examples/used-car-buyer.md
@@ -15,130 +15,127 @@ We now add two new features to the problem. A stranger approaches Joe and offers
We present the new influence diagram above. The decision node $T$ denotes the decision to accept or decline the stranger's offer, and $R$ is the outcome of the test. We introduce new value nodes $V_1$ and $V_2$ to represent the testing costs and the base profit from purchasing the car. Additionally, the decision node $A$ now can choose to buy with a guarantee.
+We start by defining the influence diagram structure. The nodes, as well as their information sets and states, are defined in the first block. Next, the influence diagram parameters consisting of the probabilities and utilities are defined.
+
+
```julia
using JuMP, Gurobi
using DecisionProgramming
-
-const O = 1 # Chance node: lemon or peach
-const T = 2 # Decision node: pay stranger for advice
-const R = 3 # Chance node: observation of state of the car
-const A = 4 # Decision node: purchase alternative
-const O_states = ["lemon", "peach"]
-const T_states = ["no test", "test"]
-const R_states = ["no test", "lemon", "peach"]
-const A_states = ["buy without guarantee", "buy with guarantee", "don't buy"]
-
-S = States([
- (length(O_states), [O]),
- (length(T_states), [T]),
- (length(R_states), [R]),
- (length(A_states), [A]),
-])
-C = Vector{ChanceNode}()
-D = Vector{DecisionNode}()
-V = Vector{ValueNode}()
-X = Vector{Probabilities}()
-Y = Vector{Consequences}()
+diagram = InfluenceDiagram()
```
-We start by defining the influence diagram structure. The decision and chance nodes, as well as their states, are defined in the first block. Next, the influence diagram parameters consisting of the node sets, probabilities, consequences and the state spaces of the nodes are defined.
-
-### Car's State
+### Car's state
-The chance node $O$ is defined by its information set $I(O)$ and probability distribution $X_O$. As seen in the influence diagram, the information set is empty and the node is a root node. The probability distribution is thus simply defined over the two states of $O$.
+The chance node $O$ is defined by its name, its information set $I(O)$ and its states $lemon$ and $peach$. As seen in the influence diagram, the information set is empty and the node is a root node.
```julia
-I_O = Vector{Node}()
-X_O = [0.2, 0.8]
-push!(C, ChanceNode(O, I_O))
-push!(X, Probabilities(O, X_O))
+add_node!(diagram, ChanceNode("O", [], ["lemon", "peach"]))
```
-### Stranger's Offer Decision
+### Stranger's offer decision
-A decision node is simply defined by its information state.
+A decision node is also defined by its name, its information set and its states.
```julia
-I_T = Vector{Node}()
-push!(D, DecisionNode(T, I_T))
+add_node!(diagram, DecisionNode("T", [], ["no test", "test"]))
```
-### Test's Outcome
+### Test's outcome
-The second chance node, $R$, has nodes $O$ and $T$ in its information set, and the probabilities $ℙ(s_j∣𝐬_{I(j)})$ must thus be defined for all combinations of states in $O$, $T$ and $R$.
+The second chance node $R$ has nodes $O$ and $T$ in its information set, and three states describing the situations of no test being done, and the test declaring the car to be a lemon or a peach.
```julia
-I_R = [O, T]
-X_R = zeros(S[O], S[T], S[R])
-X_R[1, 1, :] = [1,0,0]
-X_R[1, 2, :] = [0,1,0]
-X_R[2, 1, :] = [1,0,0]
-X_R[2, 2, :] = [0,0,1]
-push!(C, ChanceNode(R, I_R))
-push!(X, Probabilities(R, X_R))
+add_node!(diagram, ChanceNode("R", ["O", "T"], ["no test", "lemon", "peach"]))
```
-### Purchace Decision
+### Purchace decision
+The purchase decision represented by node $A$ is added as follows.
```julia
-I_A = [R]
-push!(D, DecisionNode(A, I_A))
+add_node!(diagram, DecisionNode("A", ["R"], ["buy without guarantee", "buy with guarantee", "don't buy"]))
```
+### Testing fee, base profit and repair costs
-### Testing Cost
+Value nodes are defined by only their names and information sets because they do not have states. Instead, value nodes map their information states to utility values which will be added later on.
+```julia
+add_node!(diagram, ValueNode("V1", ["T"]))
+add_node!(diagram, ValueNode("V2", ["A"]))
+add_node!(diagram, ValueNode("V3", ["O", "A"]))
+```
-We continue by defining the utilities (consequences) associated with value nodes. The value nodes are defined similarly as the chance nodes, except that instead of probabilities, we define consequences $Y_j(𝐬_{I(j)})$. Value nodes can be named just like the other nodes, e.g. $V1 = 5$, but considering that the index of value nodes is not needed elsewhere (value nodes can't be in information sets), we choose to simply use the index number when creating the node.
+### Generate arcs
+Now that all of the nodes have been added to our influence diagram we generate the arcs between the nodes. This step automatically orders the nodes, gives them indices and reorganises the information into the appropriate form in the influence diagram structure.
+```julia
+generate_arcs!(diagram)
+```
+### Probabilities
+We continue by defining probability distributions for each chance node.
+
+Node $O$ is a root node and has two states thus, its probability distribution is simply defined over the two states. We can use the `ProbabilityMatrix` structure in creating the probability matrix easily without having to worry about the matrix dimensions. We then set the probability values and add the probabililty matrix to the influence diagram.
```julia
-I_V1 = [T]
-Y_V1 = [0.0, -25.0]
-push!(V, ValueNode(5, I_V1))
-push!(Y, Consequences(5, Y_V1))
+X_O = ProbabilityMatrix(diagram, "O")
+set_probability!(X_O, ["peach"], 0.8)
+set_probability!(X_O, ["lemon"], 0.2)
+add_probabilities!(diagram, "O", X_O)
```
-### Base Profit of Purchase
+Node $R$ has two nodes in its information set and three states. The probabilities $P(s_j \mid s_{I(j)})$ must thus be defined for all combinations of states in $O$, $T$ and $R$. We declare the probability distribution over the states of node $R$ for each information state in the following way. More information on defining probability matrices can be found on the [usage page](../usage.md).
```julia
-I_V2 = [A]
-Y_V2 = [100.0, 40.0, 0.0]
-push!(V, ValueNode(6, I_V2))
-push!(Y, Consequences(6, Y_V2))
+X_R = ProbabilityMatrix(diagram, "R")
+set_probability!(X_R, ["lemon", "no test", :], [1,0,0])
+set_probability!(X_R, ["lemon", "test", :], [0,1,0])
+set_probability!(X_R, ["peach", "no test", :], [1,0,0])
+set_probability!(X_R, ["peach", "test", :], [0,0,1])
+add_probabilities!(diagram, "R", X_R)
```
-### Repairing Cost
-The rows of the consequence matrix Y_V3 correspond to the state of the car, while the columns correspond to the decision made in node $A$.
+### Utilities
+We continue by defining the utilities associated with the information states of the value nodes. The utilities $Y_j(𝐬_{I(j)})$ are defined and added similarly to the probabilities.
+
+Value node $V1$ has only node $T$ in its information set and node $T$ only has two states. Therefore, the utility matrix of node $V1$ should hold utility values corresponding to states $test$ and $no \ test$.
```julia
-I_V3 = [O, A]
-Y_V3 = [-200.0 0.0 0.0;
- -40.0 -20.0 0.0]
-push!(V, ValueNode(7, I_V3))
-push!(Y, Consequences(7, Y_V3))
+Y_V1 = UtilityMatrix(diagram, "V1")
+set_utility!(Y_V1, ["test"], -25)
+set_utility!(Y_V1, ["no test"], 0)
+add_utilities!(diagram, "V1", Y_V1)
```
-### Validating the Influence Diagram
-Validate influence diagram and sort nodes, probabilities and consequences
+We then define the utilities associated with the base profit of the purchase in different scenarios.
+```julia
+Y_V2 = UtilityMatrix(diagram, "V2")
+set_utility!(Y_V2, ["buy without guarantee"], 100)
+set_utility!(Y_V2, ["buy with guarantee"], 40)
+set_utility!(Y_V2, ["don't buy"], 0)
+add_utilities!(diagram, "V2", Y_V2)
+```
+Finally, we define the utilities corresponding to the repair costs. The rows of the utilities matrix `Y_V3` correspond to the state of the car, while the columns correspond to the decision made in node $A$. Notice that the utility values for the second row are added as a vector, in this case it is important to give the utility values in the correct order. The order of the columns is determined by the order in which the states are given when declaring node $A$. See the [usage page](../usage.md) for more information on the syntax.
```julia
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
+Y_V3 = UtilityMatrix(diagram, "V3")
+set_utility!(Y_V3, ["lemon", "buy without guarantee"], -200)
+set_utility!(Y_V3, ["lemon", "buy with guarantee"], 0)
+set_utility!(Y_V3, ["lemon", "don't buy"], 0)
+set_utility!(Y_V3, ["peach", :], [-40, -20, 0])
+add_utilities!(diagram, "V3", Y_V3)
```
-Default path probabilities and utilities are defined as the joint probability of all chance events in the diagram and the sum of utilities in value nodes, respectively. In the [Contingent Portfolio Programming](contingent-portfolio-programming.md) example, we show how to use a user-defined custom path utility function.
+### Generate influence diagram
+Finally, generate the full influence diagram before defining the decision model. By default this function uses the default path probabilities and utilities, which are defined as the joint probability of all chance events in the diagram and the sum of utilities in value nodes, respectively. In the [Contingent Portfolio Programming](contingent-portfolio-programming.md) example, we show how to use a user-defined custom path utility function.
```julia
-P = DefaultPathProbability(C, X)
-U = DefaultPathUtility(V, Y)
+generate_diagram!(diagram)
```
-
-## Decision Model
-We then construct the decision model using the DecisionProgramming.jl package, using the expected value as the objective.
+## Decision model
+We then construct the decision model by declaring a JuMP model and adding decision variables and path compatibility variables to the model. We define the objective function to be the expected value.
```julia
model = Model()
-z = DecisionVariables(model, S, D)
-x_s = PathCompatibilityVariables(model, z, S, P)
-EV = expected_value(model, x_s, U, P)
+z = DecisionVariables(model, diagram)
+x_s = PathCompatibilityVariables(model, diagram, z)
+EV = expected_value(model, diagram, x_s)
@objective(model, Max, EV)
```
@@ -154,41 +151,36 @@ optimize!(model)
```
-## Analyzing Results
-### Decision Strategy
-Once the model is solved, we obtain the following decision strategy:
+## Analyzing results
+Once the model is solved, we extract the results.
```julia
Z = DecisionStrategy(z)
+S_probabilities = StateProbabilities(diagram, Z)
+U_distribution = UtilityDistribution(diagram, Z)
```
-```julia-repl
-julia> print_decision_strategy(S, Z)
-┌────────┬────┬───┐
-│ Nodes │ () │ 2 │
-├────────┼────┼───┤
-│ States │ () │ 2 │
-└────────┴────┴───┘
-┌────────┬──────┬───┐
-│ Nodes │ (3,) │ 4 │
-├────────┼──────┼───┤
-│ States │ (1,) │ 3 │
-│ States │ (2,) │ 2 │
-│ States │ (3,) │ 1 │
-└────────┴──────┴───┘
-```
-
-To start explaining this output, let's take a look at the top table. On the right, we have the decision node 2. We defined earlier that the node $T$ is node number 2. On the left, we have the information set of that decision node, which is empty. The strategy in the first decision node is to choose alternative 2, which we defined to be testing the car.
-
-In the bottom table, we have node number 4 (node $A$) and its predecessor, node number 3 (node $R$). The first row, where we obtain no test result, is invalid for this strategy since we tested the car. If the car is a lemon, Joe should buy the car with a guarantee (alternative 2), and if it is a peach, buy the car without guarantee (alternative 1).
-
-### Utility Distribution
-```julia
-udist = UtilityDistribution(S, P, U, Z)
-```
+### Decision strategy
+We obtain the following optimal decision strategy:
+```julia-repl
+julia> print_decision_strategy(diagram, Z, S_probabilities)
+┌───────────────┐
+│ Decision in T │
+├───────────────┤
+│ test │
+└───────────────┘
+┌───────────────┬───────────────────────┐
+│ State(s) of R │ Decision in A │
+├───────────────┼───────────────────────┤
+│ lemon │ buy with guarantee │
+│ peach │ buy without guarantee │
+└───────────────┴───────────────────────┘
+```
+
+### Utility distribution
```julia-repl
-julia> print_utility_distribution(udist)
+julia> print_utility_distribution(U_distribution)
┌───────────┬─────────────┐
│ Utility │ Probability │
│ Float64 │ Float64 │
@@ -201,7 +193,7 @@ julia> print_utility_distribution(udist)
From the utility distribution, we can see that Joe's profit with this strategy is 15 USD, with a 20% probability (the car is a lemon) and 35 USD with an 80% probability (the car is a peach).
```julia-repl
-julia> print_statistics(udist)
+julia> print_statistics(U_distribution)
┌──────────┬────────────┐
│ Name │ Statistics │
│ String │ Float64 │
diff --git a/docs/src/figures/2chance_1decision_1value.svg b/docs/src/figures/2chance_1decision_1value.svg
new file mode 100644
index 00000000..1b4d05b4
--- /dev/null
+++ b/docs/src/figures/2chance_1decision_1value.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/docs/src/figures/chance-node.svg b/docs/src/figures/chance-node.svg
deleted file mode 100644
index 019a220b..00000000
--- a/docs/src/figures/chance-node.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
2 \\ \{1,2,3\...
1 \\ \{1,2\}
3 \\ \{1,2\}Viewer does not support full SVG 1.1
\ No newline at end of file
diff --git a/docs/src/figures/decision-node.svg b/docs/src/figures/decision-node.svg
deleted file mode 100644
index c96f4bab..00000000
--- a/docs/src/figures/decision-node.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
2 \\ \{1,2,3\...
1 \\ \{1,2\}
3 \\ \{1,2\}Viewer does not support full SVG 1.1
\ No newline at end of file
diff --git a/docs/src/figures/getting-started.drawio b/docs/src/figures/getting-started.drawio
deleted file mode 100644
index e3bc6a09..00000000
--- a/docs/src/figures/getting-started.drawio
+++ /dev/null
@@ -1 +0,0 @@
-7Vldb5swFP01kbaHSWAIIY8LbVdNm/ZQaZv25oABdwZTY/KxX79rbJLQhjTZmpKoSVBiHxt/3XOObsLACbLFJ4GL9CuPCBsgK1oMnKsBQmPfhk8FLDXgWb4GEkEjDdlr4I7+IQa0DFrRiJStjpJzJmnRBkOe5ySULQwLweftbjFn7VkLnJAnwF2I2VP0B41kqlF/aK3xW0KTtJnZtkxLhpvOZogyxRGfa6ju41wPnEBwLnUpWwSEqbNrzkUPdNPRulqYILnc5wbv+9Rb5veLb78ePpflz+JPtEQfhmZtctlsmESwf1PNeQ5fE8GrPCJqGAtqXMiUJzzH7AvnBYA2gPdEyqWJHq4kByiVGTOtMWUs4IyLegonHqo34KUU/DfZaPHq16qlOXE4q4leqFpd5/6bg+aVCMmOTTc8wiIhckc/ZxUlYDfhGZFiCfcJwrCks/Y6sOFZsuq3DgUUTDQOiIwZd4ZZZWYaII9JdTYFzlsx8x4qRaJJqA/yo1pjMn0HdIALZrc2Su/X3aGU6G8XLjXZYBioSxdGE1hCAHDg1NUr3a9ZBWxKL6QZ5DGPGAONKvrMUyrJXYHrmMzBJdrkwGWhhRvThSLZP7Al5rncwPUL8IIICtEgQs1O88RQqYtcMyIkWeym11M6mBtco3vjc6jxgfmGaxgo3TCMBntx/rhvUdnoHJSNtihbKcvuUmBLfY+C+p/iiobEj9xtAfPR1KkD1qOI3J415LyyBzt7MeDiv9uoszJcwx0b9Uwe2+nDgfcQ9HFzK3dPC7ZHvWZX7iW9Oit5O+jE8iu7l59OvcvbOw95e5cca18l9Z1kaaacaZZ1MDXASm9wRpk6/FvCZkTSEJsGYwK2CjRmNMmhEkJUiThQ8F0E7KDZSyTu/qllX34f9hzHxAvDbdGKRuOpZR3dnsd72jNCvdrz+JJ9nVX25Vknln019vK25L16AnLa8m4GvmRfzyup7+wLbfs38sjZ1yFOK1KeTavyead9wcxqh5i7DOF4mdXIe7XMCqrrx5F128YzXef6Lw==
\ No newline at end of file
diff --git a/docs/src/figures/usage.drawio b/docs/src/figures/usage.drawio
new file mode 100644
index 00000000..8d5d492c
--- /dev/null
+++ b/docs/src/figures/usage.drawio
@@ -0,0 +1 @@
+7Vldb5swFP01kbaHSWASkj4u6ZemTXuo1E17c+AC7gymxvmgv342thNoPppsWUi2pFGA4wu27z3n6FZ0vFE6v+M4T76wEGgHOeG84113EBr0ffmrgFIDPnI0EHMSashdAg/kBQxowyYkhKIRKBijguRNMGBZBoFoYJhzNmuGRYw2Z81xDCvAQ4DpKvqNhCIx2+o5S/weSJzYmV3HjKTYBptHFAkO2UxDVYx30/FGnDGhz9L5CKjKnc2LftDthtHFwjhkYpcb/MexX2ZP868/nj8Vxff8JSzRh55ZmyjthiGU+zeXGcvkYcjZJAtBPcaRV4yLhMUsw/QzY7kEXQk+gRClqR6eCCahRKTUjEaE0hGjjFdTeFFP/Um8EJz9hNqIX30WIzbjMlfD1e3avLIJD2DLHi1tMI9BbInzdJxKQG0Ck8w7YCkIXsoADhQLMm0SBBuexYu4ZSnkianGHpUxz51iOjEzdZBPhcpNjrNGzfzniSLRMNCJ/KjWGI/fSTrIr5zdqZ29X4bLs1gfu/I7clVQb6S++qQ/nOubSn14qcBrHW4XI/em12Of9ZpOlEqpKhbNEiLgIcdVrWbSLJocwUWu9RuRueLab5AmYpmo4foj8Rw4kUUBrmYnWWwYtQfHpsAFzLeywox2jfyN3SFrB7OaeRgoqfmGxQ5Oo+5/IHB0lgJHawSulHW9RohYKnDclN+rqv6husIeDMLuuooN0NirKtaiiroti8g7thejVQpM5U2ziwO/zZ2F5RryuKhl9rheGx68g6IP2mR1dzRht39SLmzXfemzzkvlHjqxRstt5V+pY6vcP1OV+5dma2cptd1tWe6sFmtNWwSyWNFhiyXd7RanhKp03AOdgiABNgNGl65KPaYkzuRFIFMPfE8NbqLEhsIfoqcenFpfNGjDMaMI/CBYV62wfzV2nEM75tWOjonQaTnm1aUvOse+yHdOrC+yLvNPq3zxauLMVL7g16UveltKbfdFljtHs+TH/ayWJywdT4q3rfaAHdYWNW9yhL/XYfX9o3VY8nL55rAaq71+9W5+AQ==
\ No newline at end of file
diff --git a/docs/src/figures/value-node.svg b/docs/src/figures/value-node.svg
deleted file mode 100644
index b5405551..00000000
--- a/docs/src/figures/value-node.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
2 \\ \{1,2,3\...
1 \\ \{1,2\}
3Viewer does not support full SVG 1.1
\ No newline at end of file
diff --git a/docs/src/usage.md b/docs/src/usage.md
index c63393aa..46d84713 100644
--- a/docs/src/usage.md
+++ b/docs/src/usage.md
@@ -1,61 +1,265 @@
# Usage
-On this page, we demonstrate common patterns for expressing influence diagrams and creating decision models using `DecisionProgramming.jl`. We can import the package with the `using` keyword.
+On this page, we demonstrate common patterns for expressing influence diagrams and creating decision models using `DecisionProgramming.jl`. We also discuss the abstraction that is created using the influence diagram structure. We can import the package with the `using` keyword.
```julia
using DecisionProgramming
```
-## Chance Nodes
-
+## Adding nodes
+
-Given the above influence diagram, we can create the `ChanceNode` and `Probabilities` structures for the node `3` as follows:
+Given the above influence diagram, we express it as a Decision Programming model as follows. We create `ChanceNode` and `DecisionNode` instances and add them to the influence diagram. Creating a `ChanceNode` or `DecisionNode` requires giving it a unique name, its information set and its states. If the node is a root node, the information set is left empty using square brackets. The order in which nodes are added does not matter.
```julia
-S = States([2, 3, 2])
-j = 3
-I_j = Node[1, 2]
-X_j = zeros(S[I_j]..., S[j])
-X_j[1, 1, :] = [0.1, 0.9]
-X_j[1, 2, :] = [0.0, 1.0]
-X_j[1, 3, :] = [0.3, 0.7]
-X_j[2, 1, :] = [0.2, 0.8]
-X_j[2, 2, :] = [0.4, 0.6]
-X_j[2, 3, :] = [1.0, 0.0]
-ChanceNode(j, I_j)
-Probabilities(j, X_j)
+diagram = InfluenceDiagram()
+add_node!(diagram, DecisionNode("D1", [], ["a", "b"]))
+add_node!(diagram, ChanceNode("C2", ["D1", "C1"], ["v", "w"]))
+add_node!(diagram, ChanceNode("C1", [], ["x", "y", "z"]))
```
+Value nodes are added by simply giving it a name and its information set. Value nodes do not have states because their purpose is to map their information state to utility values.
-## Decision Nodes
-
+```julia
+add_node!(diagram, ValueNode("V", ["C2"]))
+```
+
+Once all the nodes are added, we generate the arcs. This orders the nodes and gives each one a number, so that their predecessors have numbers less than theirs. In effect, the chance and decision nodes are numbered such that $C \cup D = \{1,...,n\}$, where $n = \mid C\mid + \mid D\mid$. The value nodes are numbered $V = \{n+1,..., N\}$, where $N = \mid C\mid + \mid D\mid + \mid V \mid$. For more details on influence diagrams see page [influence diagram](decision-programming/influence-diagram.md).
+```julia
+generate_arcs!(diagram)
+```
+Now the fields `Names`, `I_j`, `States`, `S`, `C`, `D` and `V` in the influence diagram structure have been properly filled. The `Names` field holds the names of all nodes in the order of their numbers. From this we can see that node D1 has been numbered 1, node C1 has been numbered 2 and node C2 has been numbered 3. Field `I_j` holds the information sets of each node. Notice, that the nodes are identified by their numbers. Field `States` holds the names of the states of each node and field `S` holds the number of states each node has. Fields `C`, `D` and `V` contain the chance, decision and value nodes respectively.
+
+```julia
+julia> diagram.Names
+4-element Array{String,1}:
+ "D1"
+ "C1"
+ "C2"
+ "V"
+
+julia> diagram.I_j
+4-element Array{Array{Int16,1},1}:
+ []
+ []
+ [1, 2]
+ [3]
+
+julia> diagram.States
+3-element Array{Array{String,1},1}:
+ ["a", "b"]
+ ["x", "y", "z"]
+ ["v", "w"]
+
+julia> diagram.S
+3-element States:
+ 2
+ 3
+ 2
+
+julia> diagram.C
+2-element Array{Int16,1}:
+ 2
+ 3
+
+julia> diagram.D
+1-element Array{Int16,1}:
+ 1
+
+julia> diagram.V
+1-element Array{Int16,1}:
+ 4
+```
+
+## Probability Matrices
+Each chance node needs a probability matrix which describes the probability distribution over its states given an information state. It holds probability values
+$$ℙ(X_j=s_j∣X_{I(j)}=𝐬_{I(j)})$$
+
+for all $s_j \in S_j$ and $𝐬_{I(j)} \in 𝐒_{I(j)}$.
+
+Thus, the probability matrix of a chance node needs to have dimensions that correspond to the number of states of the nodes in its information set and number of state of the node itself.
+
+For example, the node C1 in the influence diagram above has an empty information set and three states $x, y$, and $z$. Therefore its probability matrix needs dimensions (3,1). If the probabilities of events $x, y$, and $z$ occuring are $10\%, 30\%$ and $60\%$, then the probability matrix $X_{C1}$ should be $[0.1 \quad 0.3 \quad 0.6]$. The order of the probability values is determined by the order in which the states are given when the node is added. The states are also stored in this order in the `States` vector.
+
+In Decision Programming the probability matrix of node C1 can be added in the following way. Note, that probability matrices can only be added after the arcs have been generated.
+
+```julia
+# How C1 was added: add_node!(diagram, ChanceNode("C1", [], ["x", "y", "z"]))
+X_C1 = [0.1, 0.3, 0.6]
+add_probabilities!(diagram, "C1", X_C1)
+```
+
+The `add_probabilities!` function adds the probability matrix as a `Probabilities` structure into the influence diagram's `X` field.
+```julia
+julia> diagram.X
+1-element Array{Probabilities,1}:
+ [0.1, 0.3, 0.6]
+```
+
+
+As another example, we will add the probability matrix of node C2. It has two nodes in its information set: C1 and D1. These nodes have 3 and 2 state respectively. Node C2 itself has 2 states. The question is should the dimensions of the probability matrix be $(|S_{C1}|, |\ S_{D1}|, |\ S_{C2}|) = (3, 2, 2)$ or $(|S_{D1}|, |\ S_{C1}|, \ |S_{C2}|) = (2, 3, 2)$? The answer is that the dimensions should be in ascending order of the nodes' numbers that they correspond to. This is also the order that the information set is in in the field `I_j`. In this case the influence diagram looks like this:
+```julia
+julia> diagram.Names
+4-element Array{String,1}:
+ "D1"
+ "C1"
+ "C2"
+ "V"
+
+ julia> diagram.I_j
+4-element Array{Array{Int16,1},1}:
+ []
+ []
+ [1, 2]
+ [3]
+
+ julia> diagram.S
+3-element States:
+ 2
+ 3
+ 2
+```
+
+Therefore, the probability matrix of node C2 should have dimensions $(|S_{D1}|, |\ S_{C1}|, \ |S_{C2}|) = (2, 3, 2)$. The probability matrix can be added by declaring the matrix and then filling in the probability values as shown below.
+```julia
+X_C2 = zeros(2, 3, 2)
+X_C2[1, 1, 1] = ...
+X_C2[1, 1, 2] = ...
+X_C2[1, 1, 2] = ...
+⋮
+add_probabilities!(diagram, "C2", X_C2)
+```
+In order to be able to fill in the probability values, it is crucial to understand what the matrix indices represent. The indices represent a subpath in the influence diagram. The states in the path are referred to with their numbers instead of with their names. The states of a node are numbered according to their positions in the vector of states in field `States`. The order of the states of each node is seen below. From this, we can deduce that for nodes D1, C1, C2 the subpath `(1,1,1)` corresponds to subpath $(a, x, v)$ and subpath `(1, 3, 2)` corresponds to subpath $(a, z, w)$. Therefore, the probability value at `X_C2[1, 3, 2]` should be the probability of the scenario $(a, z, w)$ occuring.
+```julia
+julia> diagram.States
+3-element Array{Array{String,1},1}:
+ ["a", "b"]
+ ["x", "y", "z"]
+ ["v", "w"]
+```
+### Helper Syntax
+Figuring out the dimensions of a probability matrix and adding the probability values is difficult. Therefore, we have implemented an easier syntax.
+
+A probability matrix can be initialised with the correct dimensions using the `ProbabilityMatrix` function. It initiliases the probability matrix with zeros.
+```julia
+julia> X_C2 = ProbabilityMatrix(diagram, "C2")
+2×3×2 ProbabilityMatrix{3}:
+[:, :, 1] =
+ 0.0 0.0 0.0
+ 0.0 0.0 0.0
-Given the above influence diagram, we can create the `DecisionNode` structure for the node `3` as follows:
+[:, :, 2] =
+ 0.0 0.0 0.0
+ 0.0 0.0 0.0
+
+julia> size(X_C2)
+(2, 3, 2)
+```
+A matrix of type `ProbabilityMatrix` can be filled using the names of the states. The states must however be given in the correct order, according to the order of the nodes in the information set vector `I_j`. Notice that if we use the `Colon` (`:`) to indicate several elements of the matrix, the probability values have to be given in the correct order of the states in `States`.
+```julia
+julia> set_probability!(X_C2, ["a", "z", "w"], 0.25)
+0.25
+
+julia> set_probability!(X_C2, ["z", "a", "v"], 0.75)
+ERROR: DomainError with Node D1 does not have a state called z.:
+
+julia> set_probability!(X_C2, ["a", "z", "v"], 0.75)
+0.75
+
+julia> set_probability!(X_C2, ["a", "x", :], [0.3, 0.7])
+2-element Array{Float64,1}:
+ 0.3
+ 0.7
+```
+
+A matrix of type `ProbabilityMatrix` can also be filled using the matrix indices if that is more convient. The following achieves the same as what was done above.
+```julia
+julia> X_C2[1, 3, 2] = 0.25
+0.25
+
+julia> X_C2[1, 3, 1] = 0.75
+0.75
+
+julia> X_C2[1, 1, :] = [0.3, 0.7]
+2-element Array{Float64,1}:
+ 0.3
+ 0.7
+````
+
+Now, the probability matrix X_C2 is partially filled.
+```julia
+julia> X_C2
+2×3×2 ProbabilityMatrix{3}:
+[:, :, 1] =
+ 0.3 0.0 0.75
+ 0.0 0.0 0.0
+
+[:, :, 2] =
+ 0.7 0.0 0.25
+ 0.0 0.0 0.0
+```
+
+The probability matrix can be added to the influence diagram once it has been filled with probability values. The probability matrix of node C2 is added exactly like before, despite X_C2 now being a matrix of type `ProbabilityMatrix`.
+```julia
+julia> add_probabilities!(diagram, "C2", X_C2)
+```
+
+## Utility Matrices
+Each value node maps its information states to utility values. In Decision Programming the utility values are passed to the influence diagram using utility matrices. Utility matrices are very similar to probability matrices of chance nodes. There are only two important differences. First, the utility matrices hold utility values instead of probabilities, meaning that they do not need to sum to one. Second, since value nodes do not have states, the cardinality of a utility matrix depends only on the number of states of the nodes in the information set.
+
+As an example, the utility matrix of node V should have dimensions (2,1) because its information set consists of node C2, which has two states. If state $v$ of node C2 yields a utility of -100 and state $w$ yields utility of 400, then the utility matrix of node V can be added in the following way. Note, that utility matrices can only be added after the arcs have been generated.
```julia
-S = States([2, 3, 2])
-j = 3
-I_j = Node[1, 2]
-DecisionNode(j, I_j)
+julia> Y_V = zeros(2)
+2-element Array{Float64,1}:
+ 0.0
+ 0.0
+
+julia> Y_V[1] = -100
+-100
+
+julia> Y_V[2] = 400
+400
+
+julia> add_utilities!(diagram, "V", Y_V)
```
-## Value Nodes
-
+The other option is to add the utility matrix using the `UtilityMatrix` type. This is very similar to the `ProbabilityMatrix` type. The `UtilityMatrix` function initialises the values to `Inf`. Using the `UtilityMatrix` type's functionalities, the utility matrix of node V could also be added like shown below. This achieves the exact same result as we did above with the more abstract syntax.
+
-Given the above influence diagram, we can create `ValueNode` and `Consequences` structures for node `3` as follows:
+```julia
+julia> Y_V = UtilityMatrix(diagram, "V")
+2-element UtilityMatrix{1}:
+ Inf
+ Inf
+
+julia> set_utility!(Y_V, ["w"], 400)
+400
+
+julia> set_utility!(Y_V, ["v"], -100)
+-100
+
+julia> add_utilities!(diagram, "V", Y_V)
+```
+The `add_utilities!` function adds the utility matrix as a `Utilities` structure into the influence diagram's `Y` field.
```julia
-S = States([2, 3])
-j = 3
-I_j = [1, 2]
-Y_j = zeros(S[I_j]...)
-Y_j[1, 1] = -1.3
-Y_j[1, 2] = 2.5
-Y_j[1, 3] = 0.1
-Y_j[2, 1] = 0.0
-Y_j[2, 2] = 3.2
-Y_j[2, 3] = -2.7
-ValueNode(j, I_j)
-Consequences(j, Y_j)
+julia> diagram.Y
+1-element Array{Utilities,1}:
+ [-100.0, 400.0]
```
+
+## Generating the influence diagram
+
+The final part of modeling an influence diagram using the Decision Programming package is generating the full influence diagram. This is done using the `generate_diagram!` function.
+```julia
+generate_diagram!(diagram)
+```
+In this function, first, the probability and utility matrices in fields `X` and `Y` are sorted according to the chance and value nodes' indices.
+
+Second, the path probability and path utility types are declared and added into fields `P` and `U` respectively. These types define how the path probability $p(𝐬)$ and path utility $\mathcal{U}(𝐬)$ are defined in the model. By default, the function will set them to default path probability and default path utility. See the [influence diagram](decision-programming/influence-diagram.md) for more information on default path probability and utility.
+
+
+
+
diff --git a/examples/CHD_preventative_care.jl b/examples/CHD_preventative_care.jl
index d1c34561..24870234 100644
--- a/examples/CHD_preventative_care.jl
+++ b/examples/CHD_preventative_care.jl
@@ -6,12 +6,11 @@ using CSV, DataFrames, PrettyTables
# Setting subproblem specific parameters
-const chosen_risk_level = 13
+const chosen_risk_level = "12%"
# Reading tests' technical performance data (dummy data in this case)
-data = CSV.read("CHD_preventative_care_data.csv", DataFrame)
-
+data = CSV.read("examples/CHD_preventative_care_data.csv", DataFrame)
# Bayes posterior risk probabilities calculation function
@@ -73,13 +72,13 @@ function state_probabilities(risk_p::Array{Float64}, t::Int64, h::Int64, prior::
#if no test is performed, then the probabilities of moving to states (other than the prior risk level) are 0 and to the prior risk element is 1
if t == 3
- state_probabilites = zeros(101, 1)
+ state_probabilites = zeros(101)
state_probabilites[prior] = 1.0
return state_probabilites
end
# return vector
- state_probabilites = zeros(101,1)
+ state_probabilites = zeros(101)
# copying the probabilities of the scores for ease of readability
if h == 1 && t == 1 # CHD and TRS
@@ -108,157 +107,84 @@ function state_probabilities(risk_p::Array{Float64}, t::Int64, h::Int64, prior::
end
-function analysing_results(Z::DecisionStrategy, sprobs::StateProbabilities)
-
- d = Z.D[1] #taking one of the decision nodes to retrieve the information_set_R
- information_set_R = vec(collect(paths(S[d.I_j])))
- results = DataFrame(Information_set = map( x -> string(x) * "%", [0:1:100;]))
- # T1
- Z_j = Z.Z_j[1]
- probs = map(x -> x > 0 ? 1 : 0, get(sprobs.probs, 1,0)) #these are zeros and ones
- dec = [Z_j(s_I) for s_I in information_set_R]
- results[!, "T1"] = map(x -> x == 0 ? "" : "$x", probs.*dec)
-
- # T2
- Z_j = Z.Z_j[2]
- probs = map(x -> x > 0 ? 1 : 0, (get(sprobs.probs, 4,0))) #these are zeros and ones
- dec = [Z_j(s_I) for s_I in information_set_R]
- results[!, "T2"] = map(x -> x == 0 ? "" : "$x", probs.*dec)
-
- # TD
- Z_j = Z.Z_j[3]
- probs = map(x -> x > 0 ? 1 : 0, (get(sprobs.probs, 6,0))) #these are zeros and ones
- dec = [Z_j(s_I) for s_I in information_set_R]
- results[!, "TD"] = map(x -> x == 0 ? "" : "$x", probs.*dec)
-
- pretty_table(results)
-end
-
-
-const R0 = 1
-const H = 2
-const T1 = 3
-const R1 = 4
-const T2 = 5
-const R2 = 6
-const TD = 7
-const TC = 8
-const HB = 9
-
+@info("Creating the influence diagram.")
+diagram = InfluenceDiagram()
const H_states = ["CHD", "no CHD"]
const T_states = ["TRS", "GRS", "no test"]
const TD_states = ["treatment", "no treatment"]
-const R_states = map( x -> string(x) * "%", [0:1:100;])
-const TC_states = ["TRS", "GRS", "TRS & GRS", "no tests"]
-const HB_states = ["CHD & treatment", "CHD & no treatment", "no CHD & treatment", "no CHD & no treatment"]
+const R_states = [string(x) * "%" for x in [0:1:100;]]
-@info("Creating the influence diagram.")
-S = States([
- (length(R_states), [R0, R1, R2]),
- (length(H_states), [H]),
- (length(T_states), [T1, T2]),
- (length(TD_states), [TD])
-])
-C = Vector{ChanceNode}()
-D = Vector{DecisionNode}()
-V = Vector{ValueNode}()
-X = Vector{Probabilities}()
-Y = Vector{Consequences}()
+add_node!(diagram, ChanceNode("R0", [], R_states))
+add_node!(diagram, ChanceNode("R1", ["R0", "H", "T1"], R_states))
+add_node!(diagram, ChanceNode("R2", ["R1", "H", "T2"], R_states))
+add_node!(diagram, ChanceNode("H", ["R0"], H_states))
+add_node!(diagram, DecisionNode("T1", ["R0"], T_states))
+add_node!(diagram, DecisionNode("T2", ["R1"], T_states))
+add_node!(diagram, DecisionNode("TD", ["R2"], TD_states))
-I_R0 = Vector{Node}()
-X_R0 = zeros(S[R0])
-X_R0[chosen_risk_level] = 1
-push!(C, ChanceNode(R0, I_R0))
-push!(X, Probabilities(R0, X_R0))
+add_node!(diagram, ValueNode("TC", ["T1", "T2"]))
+add_node!(diagram, ValueNode("HB", ["H", "TD"]))
-I_H = [R0]
-X_H = zeros(S[R0], S[H])
-X_H[:, 1] = data.risk_levels # 1 = "CHD"
-X_H[:, 2] = 1 .- X_H[:, 1] # 2 = "no CHD"
-push!(C, ChanceNode(H, I_H))
-push!(X, Probabilities(H, X_H))
+generate_arcs!(diagram)
+X_R0 = ProbabilityMatrix(diagram, "R0")
+set_probability!(X_R0, [chosen_risk_level], 1)
+add_probabilities!(diagram, "R0", X_R0)
-I_T1 = [R0]
-push!(D, DecisionNode(T1, I_T1))
+X_H = ProbabilityMatrix(diagram, "H")
+set_probability!(X_H, [:, "CHD"], data.risk_levels)
+set_probability!(X_H, [:, "no CHD"], 1 .- data.risk_levels)
+add_probabilities!(diagram, "H", X_H)
-I_R1 = [R0, H, T1]
-X_R1 = zeros(S[I_R1]..., S[R1])
+X_R = ProbabilityMatrix(diagram, "R1")
for s_R0 = 1:101, s_H = 1:2, s_T1 = 1:3
- X_R1[s_R0, s_H, s_T1, :] = state_probabilities(update_risk_distribution(s_R0, s_T1), s_T1, s_H, s_R0)
+ X_R[s_R0, s_H, s_T1, :] = state_probabilities(update_risk_distribution(s_R0, s_T1), s_T1, s_H, s_R0)
end
-push!(C, ChanceNode(R1, I_R1))
-push!(X, Probabilities(R1, X_R1))
-
-
-I_T2 = [R1]
-push!(D, DecisionNode(T2, I_T2))
+add_probabilities!(diagram, "R1", X_R)
+add_probabilities!(diagram, "R2", X_R)
-I_R2 = [H, R1, T2]
-X_R2 = zeros(S[I_R2]..., S[R2])
-for s_R1 = 1:101, s_H = 1:2, s_T2 = 1:3
- X_R2[s_H, s_R1, s_T2, :] = state_probabilities(update_risk_distribution(s_R1, s_T2), s_T2, s_H, s_R1)
-end
-push!(C, ChanceNode(R2, I_R2))
-push!(X, Probabilities(R2, X_R2))
-
-
-I_TD = [R2]
-push!(D, DecisionNode(TD, I_TD))
-
-
-I_TC = [T1, T2]
-Y_TC = zeros(S[I_TC]...)
cost_TRS = -0.0034645
cost_GRS = -0.004
-cost_forbidden = 0 #the cost of forbidden test combinations is negligible
-Y_TC[1 , 1] = cost_forbidden
-Y_TC[1 , 2] = cost_TRS + cost_GRS
-Y_TC[1, 3] = cost_TRS
-Y_TC[2, 1] = cost_GRS + cost_TRS
-Y_TC[2, 2] = cost_forbidden
-Y_TC[2, 3] = cost_GRS
-Y_TC[3, 1] = cost_TRS
-Y_TC[3, 2] = cost_GRS
-Y_TC[3, 3] = 0
-push!(V, ValueNode(TC, I_TC))
-push!(Y, Consequences(TC, Y_TC))
-
-
-I_HB = [H, TD]
-Y_HB = zeros(S[I_HB]...)
-Y_HB[1 , 1] = 6.89713671259061 # sick & treat
-Y_HB[1 , 2] = 6.65436854256236 # sick & don't treat
-Y_HB[2, 1] = 7.64528451705134 # healthy & treat
-Y_HB[2, 2] = 7.70088349200034 # healthy & don't treat
-push!(V, ValueNode(HB, I_HB))
-push!(Y, Consequences(HB, Y_HB))
-
-
-@info("Validate influence diagram.")
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
-
-P = DefaultPathProbability(C, X)
-U = DefaultPathUtility(V, Y)
+forbidden = 0 #the cost of forbidden test combinations is negligible
+Y_TC = UtilityMatrix(diagram, "TC")
+set_utility!(Y_TC, ["TRS", "TRS"], forbidden)
+set_utility!(Y_TC, ["TRS", "GRS"], cost_TRS + cost_GRS)
+set_utility!(Y_TC, ["TRS", "no test"], cost_TRS)
+set_utility!(Y_TC, ["GRS", "TRS"], cost_TRS + cost_GRS)
+set_utility!(Y_TC, ["GRS", "GRS"], forbidden)
+set_utility!(Y_TC, ["GRS", "no test"], cost_GRS)
+set_utility!(Y_TC, ["no test", "TRS"], cost_TRS)
+set_utility!(Y_TC, ["no test", "GRS"], cost_GRS)
+set_utility!(Y_TC, ["no test", "no test"], 0)
+add_utilities!(diagram, "TC", Y_TC)
+
+Y_HB = UtilityMatrix(diagram, "HB")
+set_utility!(Y_HB, ["CHD", "treatment"], 6.89713671259061)
+set_utility!(Y_HB, ["CHD", "no treatment"], 6.65436854256236 )
+set_utility!(Y_HB, ["no CHD", "treatment"], 7.64528451705134)
+set_utility!(Y_HB, ["no CHD", "no treatment"], 7.70088349200034)
+add_utilities!(diagram, "HB", Y_HB)
+
+generate_diagram!(diagram)
@info("Creating the decision model.")
model = Model()
-z = DecisionVariables(model, S, D)
+z = DecisionVariables(model, diagram)
# Defining forbidden paths to include all those where a test is repeated twice
-forbidden_tests = ForbiddenPath[([T1,T2], Set([(1,1),(2,2),(3,1), (3,2)]))]
+forbidden_tests = ForbiddenPath(diagram, ["T1","T2"], [("TRS", "TRS"),("GRS", "GRS"),("no test", "TRS"), ("no test", "GRS")])
+fixed_R0 = FixedPath(diagram, Dict("R0" => chosen_risk_level))
scale_factor = 10000.0
-x_s = PathCompatibilityVariables(model, z, S, P; fixed = Dict(1 => chosen_risk_level), forbidden_paths = forbidden_tests, probability_cut=false)
+x_s = PathCompatibilityVariables(model, diagram, z; fixed = fixed_R0, forbidden_paths = [forbidden_tests], probability_cut=false)
-EV = expected_value(model, x_s, U, P, probability_scale_factor = scale_factor)
+EV = expected_value(model, diagram, x_s, probability_scale_factor = scale_factor)
@objective(model, Max, EV)
@info("Starting the optimization process.")
@@ -268,25 +194,24 @@ optimizer = optimizer_with_attributes(
"MIPGap" => 1e-6,
)
set_optimizer(model, optimizer)
+GC.enable(false)
optimize!(model)
+GC.enable(true)
@info("Extracting results.")
Z = DecisionStrategy(z)
+S_probabilities = StateProbabilities(diagram, Z)
+U_distribution = UtilityDistribution(diagram, Z)
@info("Printing decision strategy using tailor made function:")
-sprobs = StateProbabilities(S, P, Z)
-analysing_results(Z, sprobs)
+print_decision_strategy(diagram, Z, S_probabilities)
@info("Printing state probabilities:")
# Here we can see that the probability of having a CHD event is exactly that of the chosen risk level
-print_state_probabilities(sprobs, [R0, R1, R2])
-
-@info("Computing utility distribution.")
-udist = UtilityDistribution(S, P, U, Z)
+print_state_probabilities(diagram, S_probabilities, ["R0", "R1", "R2"])
@info("Printing utility distribution.")
-print_utility_distribution(udist)
+print_utility_distribution(U_distribution)
-@info("Printing statistics")
-print_statistics(udist)
+print_statistics(U_distribution)
diff --git a/examples/contingent-portfolio-programming.jl b/examples/contingent-portfolio-programming.jl
index 8d4baa2b..26f39b19 100644
--- a/examples/contingent-portfolio-programming.jl
+++ b/examples/contingent-portfolio-programming.jl
@@ -4,43 +4,31 @@ using DecisionProgramming
Random.seed!(42)
-const dᴾ = 1 # Decision node: range for number of patents
-const cᵀ = 2 # Chance node: technical competitiveness
-const dᴬ = 3 # Decision node: range for number of applications
-const cᴹ = 4 # Chance node: market share
-const DP_states = ["0-3 patents", "3-6 patents", "6-9 patents"]
-const CT_states = ["low", "medium", "high"]
-const DA_states = ["0-5 applications", "5-10 applications", "10-15 applications"]
-const CM_states = ["low", "medium", "high"]
-
-S = States([
- (length(DP_states), [dᴾ]),
- (length(CT_states), [cᵀ]),
- (length(DA_states), [dᴬ]),
- (length(CM_states), [cᴹ]),
-])
-C = Vector{ChanceNode}()
-D = Vector{DecisionNode}()
-V = Vector{ValueNode}()
-X = Vector{Probabilities}()
-Y = Vector{Consequences}()
-
-I_DP = Vector{Node}()
-push!(D, DecisionNode(dᴾ, I_DP))
-
-I_CT = [dᴾ]
-X_CT = zeros(S[dᴾ], S[cᵀ])
+function num_states(diagram::InfluenceDiagram, node::Name)
+ idx = findfirst(isequal(node), diagram.Names)
+ if isnothing(idx)
+ throw(DomainError("Name $node not found in the diagram."))
+ end
+ return diagram.S[idx]
+end
+
+@info("Creating the influence diagram.")
+diagram = InfluenceDiagram()
+
+add_node!(diagram, DecisionNode("DP", [], ["0-3 patents", "3-6 patents", "6-9 patents"]))
+add_node!(diagram, ChanceNode("CT", ["DP"], ["low", "medium", "high"]))
+add_node!(diagram, DecisionNode("DA", ["DP", "CT"], ["0-5 applications", "5-10 applications", "10-15 applications"]))
+add_node!(diagram, ChanceNode("CM", ["CT", "DA"], ["low", "medium", "high"]))
+
+generate_arcs!(diagram)
+
+X_CT = ProbabilityMatrix(diagram, "CT")
X_CT[1, :] = [1/2, 1/3, 1/6]
X_CT[2, :] = [1/3, 1/3, 1/3]
X_CT[3, :] = [1/6, 1/3, 1/2]
-push!(C, ChanceNode(cᵀ, I_CT))
-push!(X, Probabilities(cᵀ, X_CT))
+add_probabilities!(diagram, "CT", X_CT)
-I_DA = [dᴾ, cᵀ]
-push!(D, DecisionNode(dᴬ, I_DA))
-
-I_CM = [cᵀ, dᴬ]
-X_CM = zeros(S[cᵀ], S[dᴬ], S[cᴹ])
+X_CM = ProbabilityMatrix(diagram, "CM")
X_CM[1, 1, :] = [2/3, 1/4, 1/12]
X_CM[1, 2, :] = [1/2, 1/3, 1/6]
X_CM[1, 3, :] = [1/3, 1/3, 1/3]
@@ -50,23 +38,15 @@ X_CM[2, 3, :] = [1/6, 1/3, 1/2]
X_CM[3, 1, :] = [1/3, 1/3, 1/3]
X_CM[3, 2, :] = [1/6, 1/3, 1/2]
X_CM[3, 3, :] = [1/12, 1/4, 2/3]
-push!(C, ChanceNode(cᴹ, I_CM))
-push!(X, Probabilities(cᴹ, X_CM))
+add_probabilities!(diagram, "CM", X_CM)
-# Dummy value node
-push!(V, ValueNode(5, [cᴹ]))
-push!(Y, Consequences(5, zeros(S[cᴹ])))
+generate_diagram!(diagram, default_utility=false)
-@info("Validate influence diagram.")
-validate_influence_diagram(S, C, D, V)
-sort!.((C, D, V, X, Y), by = x -> x.j)
-@info("Creating path probability.")
-P = DefaultPathProbability(C, X)
-@info("Defining DecisionModel")
+@info("Creating the decision model.")
model = Model()
-z = DecisionVariables(model, S, D)
+z = DecisionVariables(model, diagram)
@info("Creating problem specific constraints and expressions")
@@ -85,12 +65,12 @@ O_t = rand(1:3,n_T) # number of patents for each tech project
I_a = rand(n_T)*2 # costs of application projects
O_a = rand(2:4,n_T) # number of applications for each appl. project
-V_A = rand(S[cᴹ], n_A).+0.5 # Value of an application
+V_A = rand(num_states(diagram, "CM"), n_A).+0.5 # Value of an application
V_A[1, :] .+= -0.5 # Low market share: less value
V_A[3, :] .+= 0.5 # High market share: more value
-x_T = variables(model, [S[dᴾ]...,n_T]; binary=true)
-x_A = variables(model, [S[dᴾ]...,S[cᵀ]...,S[dᴬ]..., n_A]; binary=true)
+x_T = variables(model, [num_states(diagram, "DP"), n_T]; binary=true)
+x_A = variables(model, [num_states(diagram, "DP"), num_states(diagram, "CT"), num_states(diagram, "DA"), n_A]; binary=true)
M = 20 # a large constant
ε = 0.5*minimum([O_t O_a]) # a helper variable, allows using ≤ instead of < in constraints (28b) and (29b)
@@ -125,10 +105,11 @@ z_dA = z.z[2]
@constraint(model, [i=1:3, j=1:3, k=1:3], x_A[i,j,k,2] <= x_T[i,2])
@info("Creating model objective.")
-patent_investment_cost = @expression(model, [i=1:S[1]], sum(x_T[i, t] * I_t[t] for t in 1:n_T))
-application_investment_cost = @expression(model, [i=1:S[1], j=1:S[2], k=1:S[3]], sum(x_A[i, j, k, a] * I_a[a] for a in 1:n_A))
-application_value = @expression(model, [i=1:S[1], j=1:S[2], k=1:S[3], l=1:S[4]], sum(x_A[i, j, k, a] * V_A[l, a] for a in 1:n_A))
-@objective(model, Max, sum( sum( P((i,j,k,l)) * (application_value[i,j,k,l] - application_investment_cost[i,j,k]) for j in 1:S[2], k in 1:S[3], l in 1:S[4] ) - patent_investment_cost[i] for i in 1:S[1] ))
+patent_investment_cost = @expression(model, [i=1:diagram.S[1]], sum(x_T[i, t] * I_t[t] for t in 1:n_T))
+application_investment_cost = @expression(model, [i=1:diagram.S[1], j=1:diagram.S[2], k=1:diagram.S[3]], sum(x_A[i, j, k, a] * I_a[a] for a in 1:n_A))
+application_value = @expression(model, [i=1:diagram.S[1], j=1:diagram.S[2], k=1:diagram.S[3], l=1:diagram.S[4]], sum(x_A[i, j, k, a] * V_A[l, a] for a in 1:n_A))
+@objective(model, Max, sum( sum( diagram.P((i,j,k,l)) * (application_value[i,j,k,l] - application_investment_cost[i,j,k]) for j in State(1):diagram.S[2], k in State(1):diagram.S[3], l in State(1):diagram.S[4] ) - patent_investment_cost[i] for i in State(1):diagram.S[1] ))
+
@info("Starting the optimization process.")
optimizer = optimizer_with_attributes(
@@ -141,29 +122,30 @@ optimize!(model)
@info("Extracting results.")
Z = DecisionStrategy(z)
+S_probabilities = StateProbabilities(diagram, Z)
@info("Printing decision strategy:")
-print_decision_strategy(S, Z)
+print_decision_strategy(diagram, Z, S_probabilities)
@info("Extracting path utilities")
struct PathUtility <: AbstractPathUtility
data::Array{AffExpr}
end
-Base.getindex(U::PathUtility, i::Int) = getindex(U.data, i)
-Base.getindex(U::PathUtility, I::Vararg{Int,N}) where N = getindex(U.data, I...)
+Base.getindex(U::PathUtility, i::State) = getindex(U.data, i)
+Base.getindex(U::PathUtility, I::Vararg{State,N}) where N = getindex(U.data, I...)
(U::PathUtility)(s::Path) = value.(U[s...])
path_utility = [@expression(model,
sum(x_A[s[1:3]..., a] * (V_A[s[4], a] - I_a[a]) for a in 1:n_A) -
- sum(x_T[s[1], t] * I_t[t] for t in 1:n_T)) for s in paths(S)]
-U = PathUtility(path_utility)
+ sum(x_T[s[1], t] * I_t[t] for t in 1:n_T)) for s in paths(diagram.S)]
+diagram.U = PathUtility(path_utility)
@info("Computing utility distribution.")
-udist = UtilityDistribution(S, P, U, Z)
+U_distribution = UtilityDistribution(diagram, Z)
@info("Printing utility distribution.")
-print_utility_distribution(udist)
+print_utility_distribution(U_distribution)
@info("Printing statistics")
-print_statistics(udist)
+print_statistics(U_distribution)
diff --git a/examples/figures/simple-id.drawio b/examples/figures/simple-id.drawio
index f824a7f8..3303c031 100644
--- a/examples/figures/simple-id.drawio
+++ b/examples/figures/simple-id.drawio
@@ -1 +1 @@
-7Vhdb5swFP01SNvDJDDYyR6XtFsndQ9THvbsgPnoDKaOKUl//WxsEgghoWoEmTaCgn18ba7v8bm2sNxluv3GcR7/YAGhFrCDreXeWQCg2Uz+K2CnAQ/YGoh4EmjIOQCr5JUYsDYrkoBsWoaCMSqSvA36LMuIL1oY5pyVbbOQ0fZbcxyRDrDyMe2iv5JAxBqdQ/uAP5Akius3O7ZpSXFtbIbYxDhgpYYqG/fecpecMaFL6XZJqIpdHRc90Nee1r1jnGRiSIfd489X9FwG5BU+wALk4ep7/Omz8U3s6gmTQM7fVDOWyceCsyILiBrGljXGRcwilmH6yFguQUeCT0SInWEPF4JJKBYpNa0bwdnvffzkzBdhQumSUcarl7oBJPPA21s2WuZg7SIkW7Sjyrve+deBZgX3yZlJ1+sI84iIM3Zwz5Jc3YSlRPCd7McJxSJ5afuBzTqL9nYHKmTBsPEGZsy4L5gW5k0W8OStQAsu1a0Ls4U0VRVQ1e60WZdXSqVmFJ1lnAiyynEVo1Kqtk0W3uRaSGGyVaQfcxVC9TvFFaou1YNlooHr6+Q6yAlPZLwIV/4kWWRgM3fCBdmep7tLj+ngIiPEOvF4pl42ZGyguKHgGrs6obMOJe/W1duZ6TJwRV2BgbrqIW4cXc1vI+MN4GoCZrwpmQEnMh6iQsUox1mLM/RcqG1z4euwfVE+RusP0FE5sUqGreLHQw9ZivRTZUlnaDLVfshpaVfqMY5W0jsz64BdcMwMCrwby6Buz5YIe7Y8HrN0XWwub3nH2gxD4PuneAjQGsGxeUD2jfFQn82nzqHjnhq9v+HU6F3Ioc3k5143+f3rB0yIppal0+VkkqPN5fR5RVnCgbJ0p5QlHP9o4/0/2vRr15uPtqXK6uEbT9XW+FDm3v8B
\ No newline at end of file
+5Vhbb5swFP41SNvDJG4m2WNDunZS9zBF2p4NmMtqMHVMgPz62dgkEHJruiZMJZGwv3N8O5/PZwvNctPqgcI8/kEChDVTDyrNmmumaYCJyV8CqRViGAqJaBIobAsskjVSoK7QIgnQsufICMEsyfugT7IM+ayHQUpJ2XcLCe6PmsMIDYCFD/EQ/Z0ELJboFOhb/BElUdyObOjKksLWWXWxjGFASgk1Pta9ZrmUECZLaeUiLKLXxkV29O2AdTMxijJ2ToP66efaeSkDtAaPoDDzcPE9/vJVzY3V7YJRwNevqhnJ+GtGSZEFSHSj8xqhLCYRySB+IiTnoMHBP4ixWrEHC0Y4FLMUK+uSUfK8iR9f+SxMMHYJJrQZ1AoAmgb2xrNjmZqe5TjcMlxuG1dSUB8dWWO7bSCNEDviB6SfCEBnABXMB0RSxGjNHSjCkCWr/gaBap9FG78tFbyg2HgFM6rfFcSFGkkzbf7ngdA14Iq/LExmlWaKSt3U5tJtyCvGPGcEnWWcMLTIYRO0kudtnyy4zGUihUklSN/lKgTit48rp3lEC5KxDi6fvfsgRzTh8UJUzCfJIgUfJHuFKEPVUXqU1XJUIirlsW1VLztprKC4k8Et9s8JnQwoeXNevZ6ZIQOX55V5Zl4Zo8qr6TgU7wyu3p8Ze1TMmHsUz8FMxCiHWY8z56UQx+bMl2G7E3OMvE/AEJrYiGGv+Hnbgpci+RYqeTcUUygbeX0xlfPgy5JTafvY2UlvVNYzTsFrKqhpj0xBrQNH4q8DRx6NSeoVy9NH3m5uhqHp+/t4CBzPAdfmwdFHxkN7N7+1hr7rrdH+L2+N9gkN7YqfOxS/lRS/8hLx++gXTODcOi2NISc3udqcls/L0xKcmZbWqNISXP9qMx9m9zNvhJu2cmyPbpp8+MuOPb3aIcur268+ja3z8cy6/ws=
\ No newline at end of file
diff --git a/examples/figures/simple-id.svg b/examples/figures/simple-id.svg
index 0b5eba7d..2fc5a9ce 100644
--- a/examples/figures/simple-id.svg
+++ b/examples/figures/simple-id.svg
@@ -1,3 +1,3 @@
-