Skip to content

Commit 85c12a2

Browse files
MathiasMNilsenMathias Methlie NilsenMathias Methlie Nilsen
authored
Updates (#98)
* update to TrustRegion * some design changes to TrustRegion * decoupled GenOpt from Ensemble * decoupled GenOpt from Ensemble --------- Co-authored-by: Mathias Methlie Nilsen <mani@cno-0006.ad.norceresearch.no> Co-authored-by: Mathias Methlie Nilsen <mani@bgo-1714.ad.norceresearch.no>
1 parent 873f0a1 commit 85c12a2

6 files changed

Lines changed: 233 additions & 182 deletions

File tree

popt/loop/ensemble.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from popt.misc_tools import optim_tools as ot
1111
from pipt.misc_tools import analysis_tools as at
1212
from ensemble.ensemble import Ensemble as PETEnsemble
13-
from popt.loop.extensions import GenOptExtension
1413

1514

1615
class Ensemble(PETEnsemble):
@@ -137,12 +136,6 @@ def __set__variable(var_name=None, defalut=None):
137136
self.bias_weights = np.ones(self.num_samples) / self.num_samples # initialize with equal weights
138137
self.bias_points = None # this is the points used to estimate the bias correction
139138

140-
# Setup GenOpt
141-
self.genopt = GenOptExtension(self.get_state(),
142-
self.get_cov(),
143-
func=self.function,
144-
ne=self.num_samples)
145-
146139
def get_state(self):
147140
"""
148141
Returns
Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,63 +9,81 @@
99
from popt.misc_tools import optim_tools as ot
1010
from pipt.misc_tools import analysis_tools as at
1111
from ensemble.ensemble import Ensemble as PETEnsemble
12+
from simulator.simple_models import noSimulation
1213

13-
class EnsembleOptimizationBase(PETEnsemble):
14+
class EnsembleOptimizationBaseClass(PETEnsemble):
1415
'''
1516
Base class for the popt ensemble
1617
'''
17-
def __init__(self, kwargs_ens, sim, obj_func):
18+
def __init__(self, options, simulator, objective):
1819
'''
1920
Parameters
2021
----------
21-
kwargs_ens : dict
22+
options : dict
2223
Options for the ensemble class
2324
24-
sim : callable
25-
The forward simulator (e.g. flow)
25+
simulator : callable
26+
The forward simulator (e.g. flow). If None, no simulation is performed.
2627
27-
obj_func : callable
28+
objective : callable
2829
The objective function (e.g. npv)
2930
'''
31+
if simulator is None:
32+
sim = noSimulation()
33+
else:
34+
sim = simulator
3035

3136
# Initialize PETEnsemble
32-
super().__init__(kwargs_ens, sim)
33-
34-
self.save_prediction = kwargs_ens.get('save_prediction', None)
35-
self.num_models = kwargs_ens.get('num_models', 1)
36-
self.transform = kwargs_ens.get('transform', False)
37-
self.num_samples = self.ne
37+
super().__init__(options, sim)
3838

39-
# Get bounds and varaince
40-
self.upper_bound = []
41-
self.lower_bound = []
39+
# Unpack some options
40+
self.save_prediction = options.get('save_prediction', None)
41+
self.num_models = options.get('num_models', 1)
42+
self.transform = options.get('transform', False)
43+
self.num_samples = self.ne
44+
45+
# Define some variables
46+
self.lb = []
47+
self.ub = []
4248
self.bounds = []
4349
self.cov = np.array([])
44-
for name in self.prior_info.keys():
45-
self.state[name] = np.asarray(self.prior_info[name]['mean'])
46-
num_state_var = len(self.state[name])
47-
value_cov = self.prior_info[name]['variance'] * np.ones((num_state_var,))
48-
if 'limits' in self.prior_info[name].keys():
49-
lb = self.prior_info[name]['limits'][0]
50-
ub = self.prior_info[name]['limits'][1]
51-
self.lower_bound.append(lb)
52-
self.upper_bound.append(ub)
50+
51+
# Get bounds and varaince, and initialize state
52+
for key in self.prior_info.keys():
53+
variable = self.prior_info[key]
54+
55+
# mean
56+
self.state[key] = np.asarray(variable['mean'])
57+
58+
# Covariance
59+
dim = self.state[key].size
60+
cov = variable['variance']*np.ones(dim)
61+
62+
if 'limits' in variable.keys():
63+
lb, ub = variable['limits']
64+
self.lb(lb)
65+
self.ub(ub)
66+
67+
# transform cov to [0, 1] if transform is True
5368
if self.transform:
54-
value_cov = value_cov / (ub - lb)**2
55-
np.clip(value_cov, 0, 1, out=value_cov)
56-
self.bounds += num_state_var*[(0, 1)]
69+
cov = np.clip(cov/(ub - lb)**2, 0, 1, out=cov)
70+
self.bounds += dim*[(0, 1)]
5771
else:
58-
self.bounds += num_state_var*[(lb, ub)]
59-
self.cov = np.append(self.cov, value_cov)
72+
self.bounds += dim*[(lb, ub)]
6073
else:
61-
self.bounds += num_state_var*[(None, None)]
74+
self.bounds += dim*[(None, None)]
75+
76+
# Add to covariance
77+
self.cov = np.append(self.cov, cov)
6278

63-
64-
self._scale_state()
79+
# Make cov full covariance matrix
6580
self.cov = np.diag(self.cov)
6681

82+
# Scale the state to [0, 1] if transform is True
83+
self._scale_state()
84+
6785
# Set objective function (callable)
68-
self.obj_func = obj_func
86+
self.obj_func = objective
6987

7088
# Objective function values
7189
self.state_func_values = None
@@ -78,8 +96,13 @@ def get_state(self):
7896
x : numpy.ndarray
7997
Control vector as ndarray, shape (number of controls, number of perturbations)
8098
"""
81-
x = ot.aug_optim_state(self.state, list(self.state.keys()))
82-
return x
99+
return ot.aug_optim_state(self.state, list(self.state.keys()))
100+
101+
def vec_to_state(self, x):
102+
"""
103+
Converts a control vector to the internal state representation.
104+
"""
105+
return ot.update_optim_state(x, self.state, list(self.state.keys()))
83106

84107
def get_bounds(self):
85108
"""
@@ -112,7 +135,10 @@ def function(self, x, *args):
112135
else:
113136
self.ne = x.shape[1]
114137

115-
self.state = ot.update_optim_state(x, self.state, list(self.state.keys())) # go from nparray to dict
138+
# convert x to state
139+
self.state = self.vec_to_state(x) # go from nparray to dict
140+
141+
# run the simulation
116142
self._invert_scale_state() # ensure that state is in [lb,ub]
117143
run_success = self.calc_prediction(save_prediction=self.save_prediction) # calculate flow data
118144
self._scale_state() # scale back to [0, 1]
@@ -147,17 +173,17 @@ def _scale_state(self):
147173
"""
148174
Transform the internal state from [lb, ub] to [0, 1]
149175
"""
150-
if self.transform and (self.upper_bound and self.lower_bound):
176+
if self.transform and (self.lb and self.ub):
151177
for i, key in enumerate(self.state):
152-
self.state[key] = (self.state[key] - self.lower_bound[i])/(self.upper_bound[i] - self.lower_bound[i])
178+
self.state[key] = (self.state[key] - self.lb[i])/(self.ub[i] - self.lb[i])
153179
np.clip(self.state[key], 0, 1, out=self.state[key])
154180

155181
def _invert_scale_state(self):
156182
"""
157183
Transform the internal state from [0, 1] to [lb, ub]
158184
"""
159-
if self.transform and (self.upper_bound and self.lower_bound):
185+
if self.transform and (self.lb and self.ub):
160186
for i, key in enumerate(self.state):
161187
if self.transform:
162-
self.state[key] = self.lower_bound[i] + self.state[key]*(self.upper_bound[i] - self.lower_bound[i])
163-
np.clip(self.state[key], self.lower_bound[i], self.upper_bound[i], out=self.state[key])
188+
self.state[key] = self.lb[i] + self.state[key]*(self.ub[i] - self.lb[i])
189+
np.clip(self.state[key], self.lb[i], self.ub[i], out=self.state[key])

popt/loop/generalized_ensemble.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,32 @@
1010
# Internal imports
1111
from popt.misc_tools import optim_tools as ot
1212
from pipt.misc_tools import analysis_tools as at
13-
from popt.loop.base import EnsembleOptimizationBase
13+
from popt.loop.ensemble_base import EnsembleOptimizationBaseClass
1414

15-
class GeneralizedEnsemble(EnsembleOptimizationBase):
15+
class GeneralizedEnsemble(EnsembleOptimizationBaseClass):
1616

17-
def __init__(self, kwargs_ens, sim, obj_func):
17+
def __init__(self, options, simulator, objective):
1818
'''
1919
Parameters
2020
----------
21-
kwargs_ens : dict
21+
options : dict
2222
Options for the ensemble class
2323
24-
sim : callable
25-
The forward simulator (e.g. flow)
24+
simulator : callable
25+
The forward simulator (e.g. flow). If None, no simulation is performed.
2626
27-
obj_func : callable
27+
objective : callable
2828
The objective function (e.g. npv)
2929
'''
30-
super().__init__(kwargs_ens, sim, obj_func)
31-
32-
self.dim = self.get_state().size
30+
super().__init__(options, simulator, objective)
3331

3432
# construct corr matrix
3533
std = np.sqrt(np.diag(self.cov))
3634
self.corr = self.cov/np.outer(std, std)
35+
self.dim = std
3736

3837
# choose marginal
39-
marginal = kwargs_ens.get('marginal', 'Beta')
38+
marginal = options.get('marginal', 'BetaMC')
4039

4140
if marginal in ['Beta', 'BetaMC', 'Logistic', 'TruncGaussian', 'Gaussian']:
4241

@@ -45,7 +44,7 @@ def __init__(self, kwargs_ens, sim, obj_func):
4544

4645
if marginal == 'Beta':
4746
self.margs = Beta()
48-
self.theta = kwargs_ens.get('theta', np.array([[20.0, 20.0] for _ in range(self.dim)]))
47+
self.theta = options.get('theta', np.array([[20.0, 20.0] for _ in range(self.dim)]))
4948
self.eps = self.var2eps()
5049
self.grad_scale = 1/(2*self.eps)
5150
self.hess_scale = 1/(4*self.eps**2)
@@ -56,20 +55,20 @@ def __init__(self, kwargs_ens, sim, obj_func):
5655
var = np.diag(self.cov)
5756
self.margs = BetaMC(lb, ub, 0.1*np.sqrt(var[0]))
5857
default_theta = np.array([var_to_concentration(state[i], var[i], lb[i], ub[i]) for i in range(self.dim)])
59-
self.theta = kwargs_ens.get('theta', default_theta)
58+
self.theta = options.get('theta', default_theta)
6059

6160
elif marginal == 'Logistic':
6261
self.margs = Logistic()
63-
self.theta = kwargs_ens.get('theta', self.margs.var_to_scale(np.diag(self.cov)))
62+
self.theta = options.get('theta', self.margs.var_to_scale(np.diag(self.cov)))
6463

6564
elif marginal == 'TruncGaussian':
6665
lb, ub = np.array(self.bounds).T
6766
self.margs = TruncGaussian(lb,ub)
68-
self.theta = kwargs_ens.get('theta', np.sqrt(np.diag(self.cov)))
67+
self.theta = options.get('theta', np.sqrt(np.diag(self.cov)))
6968

7069
elif marginal == 'Gaussian':
7170
self.margs = Gaussian()
72-
self.theta = kwargs_ens.get('theta', np.sqrt(np.diag(self.cov)))
71+
self.theta = options.get('theta', np.sqrt(np.diag(self.cov)))
7372

7473
def get_theta(self):
7574
return self.theta

popt/loop/optimize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def run_loop(self):
166166
self.save()
167167

168168
# Check if max iterations was reached
169-
if self.iteration > self.max_iter:
169+
if self.iteration >= self.max_iter:
170170
self.optimize_result['message'] = 'Iterations stopped due to max iterations reached!'
171171
else:
172172
if not isinstance(self.msg, str): self.msg = ''

popt/update_schemes/linesearch.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# Internal imports
1414
from popt.misc_tools import optim_tools as ot
1515
from popt.loop.optimize import Optimize
16+
from popt.update_schemes import optimizers
1617

1718
def LineSearch(fun, x, jac, method='GD', hess=None, args=(), bounds=None, callback=None, **options):
1819
'''
@@ -373,6 +374,13 @@ def calc_update(self, iter_resamp=0):
373374
pk = - np.matmul(self.Hk_inv, self.jk)
374375
if self.method == 'Newton':
375376
pk = - np.matmul(la.inv(self.Hk), self.jk)
377+
378+
# remove components that point out of the hybercube given by [lb,ub]
379+
lb = np.array(self.bounds)[:, 0]
380+
ub = np.array(self.bounds)[:, 1]
381+
for i in range(self.xk.size):
382+
if (self.xk[i] <= lb[i] and pk[i] < 0) or (self.xk[i] >= ub[i] and pk[i] > 0):
383+
pk[i] = 0
376384

377385
# Set step_size
378386
step_size = self._set_step_size(pk)

0 commit comments

Comments
 (0)