1"""
2An example of the lake problem using the ema workbench.
3
4The model itself is adapted from the Rhodium example by Dave Hadka,
5see https://gist.github.com/dhadka/a8d7095c98130d8f73bc
6
7"""
8
9import math
10
11import numpy as np
12from scipy.optimize import brentq
13
14from ema_workbench import (
15 Model,
16 RealParameter,
17 ScalarOutcome,
18 Constant,
19 ema_logging,
20 MultiprocessingEvaluator,
21)
22from ema_workbench.em_framework.evaluators import Samplers
23
24
25def lake_problem(
26 b=0.42, # decay rate for P in lake (0.42 = irreversible)
27 q=2.0, # recycling exponent
28 mean=0.02, # mean of natural inflows
29 stdev=0.001, # future utility discount rate
30 delta=0.98, # standard deviation of natural inflows
31 alpha=0.4, # utility from pollution
32 nsamples=100, # Monte Carlo sampling of natural inflows
33 **kwargs,
34):
35 try:
36 decisions = [kwargs[str(i)] for i in range(100)]
37 except KeyError:
38 decisions = [0] * 100
39 print("No valid decisions found, using 0 water release every year as default")
40
41 nvars = len(decisions)
42 decisions = np.array(decisions)
43
44 # Calculate the critical pollution level (Pcrit)
45 Pcrit = brentq(lambda x: x**q / (1 + x**q) - b * x, 0.01, 1.5)
46
47 # Generate natural inflows using lognormal distribution
48 natural_inflows = np.random.lognormal(
49 mean=math.log(mean**2 / math.sqrt(stdev**2 + mean**2)),
50 sigma=math.sqrt(math.log(1.0 + stdev**2 / mean**2)),
51 size=(nsamples, nvars),
52 )
53
54 # Initialize the pollution level matrix X
55 X = np.zeros((nsamples, nvars))
56
57 # Loop through time to compute the pollution levels
58 for t in range(1, nvars):
59 X[:, t] = (
60 (1 - b) * X[:, t - 1]
61 + (X[:, t - 1] ** q / (1 + X[:, t - 1] ** q))
62 + decisions[t - 1]
63 + natural_inflows[:, t - 1]
64 )
65
66 # Calculate the average daily pollution for each time step
67 average_daily_P = np.mean(X, axis=0)
68
69 # Calculate the reliability (probability of the pollution level being below Pcrit)
70 reliability = np.sum(X < Pcrit) / float(nsamples * nvars)
71
72 # Calculate the maximum pollution level (max_P)
73 max_P = np.max(average_daily_P)
74
75 # Calculate the utility by discounting the decisions using the discount factor (delta)
76 utility = np.sum(alpha * decisions * np.power(delta, np.arange(nvars)))
77
78 # Calculate the inertia (the fraction of time steps with changes larger than 0.02)
79 inertia = np.sum(np.abs(np.diff(decisions)) > 0.02) / float(nvars - 1)
80
81 return max_P, utility, inertia, reliability
82
83
84if __name__ == "__main__":
85 ema_logging.log_to_stderr(ema_logging.INFO)
86
87 # instantiate the model
88 lake_model = Model("lakeproblem", function=lake_problem)
89 lake_model.time_horizon = 100
90
91 # specify uncertainties
92 lake_model.uncertainties = [
93 RealParameter("b", 0.1, 0.45),
94 RealParameter("q", 2.0, 4.5),
95 RealParameter("mean", 0.01, 0.05),
96 RealParameter("stdev", 0.001, 0.005),
97 RealParameter("delta", 0.93, 0.99),
98 ]
99
100 # set levers, one for each time step
101 lake_model.levers = [RealParameter(str(i), 0, 0.1) for i in range(lake_model.time_horizon)]
102
103 # specify outcomes
104 lake_model.outcomes = [
105 ScalarOutcome("max_P"),
106 ScalarOutcome("utility"),
107 ScalarOutcome("inertia"),
108 ScalarOutcome("reliability"),
109 ]
110
111 # override some of the defaults of the model
112 lake_model.constants = [Constant("alpha", 0.41), Constant("nsamples", 150)]
113
114 # generate some random policies by sampling over levers
115 n_scenarios = 1000
116 n_policies = 4
117
118 with MultiprocessingEvaluator(lake_model) as evaluator:
119 res = evaluator.perform_experiments(n_scenarios, n_policies, lever_sampling=Samplers.MC)