simulator.flow_rock

  1from simulator.opm import flow
  2from importlib import import_module
  3import datetime as dt
  4import numpy as np
  5import os
  6from misc import ecl, grdecl
  7import shutil
  8import glob
  9from subprocess import Popen, PIPE
 10import mat73
 11from copy import deepcopy
 12from sklearn.cluster import KMeans
 13from sklearn.preprocessing import StandardScaler
 14
 15
 16class flow_sim2seis(flow):
 17    """
 18    Couple the OPM-flow simulator with a sim2seis simulator such that both reservoir quantities and petro-elastic
 19    quantities can be calculated. Inherit the flow class, and use super to call similar functions.
 20    """
 21
 22    def __init__(self, input_dict=None, filename=None, options=None):
 23        super().__init__(input_dict, filename, options)
 24        self._getpeminfo(input_dict)
 25
 26        self.dum_file_root = 'dummy.txt'
 27        self.dum_entry = str(0)
 28        self.date_slack = None
 29        if 'date_slack' in input_dict:
 30            self.date_slack = int(input_dict['date_slack'])
 31
 32        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
 33        # run a user defined code to do this.
 34        self.saveinfo = None
 35        if 'savesiminfo' in input_dict:
 36            # Make sure "ANALYSISDEBUG" gives a list
 37            if isinstance(input_dict['savesiminfo'], list):
 38                self.saveinfo = input_dict['savesiminfo']
 39            else:
 40                self.saveinfo = [input_dict['savesiminfo']]
 41
 42        self.scale = []
 43
 44    def _getpeminfo(self, input_dict):
 45        """
 46        Get, and return, flow and PEM modules
 47        """
 48        if 'pem' in input_dict:
 49            self.pem_input = {}
 50            for elem in input_dict['pem']:
 51                if elem[0] == 'model':  # Set the petro-elastic model
 52                    self.pem_input['model'] = elem[1]
 53                if elem[0] == 'depth':  # provide the npz of depth values
 54                    self.pem_input['depth'] = elem[1]
 55                if elem[0] == 'actnum':  # the npz of actnum values
 56                    self.pem_input['actnum'] = elem[1]
 57                if elem[0] == 'baseline':  # the time for the baseline 4D measurment
 58                    self.pem_input['baseline'] = elem[1]
 59                if elem[0] == 'vintage':
 60                    self.pem_input['vintage'] = elem[1]
 61                    if not type(self.pem_input['vintage']) == list:
 62                        self.pem_input['vintage'] = [elem[1]]
 63                if elem[0] == 'ntg':
 64                    self.pem_input['ntg'] = elem[1]
 65                if elem[0] == 'press_conv':
 66                    self.pem_input['press_conv'] = elem[1]
 67                if elem[0] == 'compaction':
 68                    self.pem_input['compaction'] = True
 69                if elem[0] == 'overburden':  # the npz of overburden values
 70                    self.pem_input['overburden'] = elem[1]
 71                if elem[0] == 'percentile':  # use for scaling
 72                    self.pem_input['percentile'] = elem[1]
 73
 74            pem = getattr(import_module('simulator.rockphysics.' +
 75                          self.pem_input['model'].split()[0]), self.pem_input['model'].split()[1])
 76
 77            self.pem = pem(self.pem_input)
 78
 79        else:
 80            self.pem = None
 81
 82    def setup_fwd_run(self):
 83        super().setup_fwd_run()
 84
 85    def run_fwd_sim(self, state, member_i, del_folder=True):
 86        # The inherited simulator also has a run_fwd_sim. Call this.
 87        self.ensemble_member = member_i
 88        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
 89
 90        return self.pred_data
 91
 92    def call_sim(self, folder=None, wait_for_proc=False):
 93        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
 94        # Then, get the pem.
 95        success = super().call_sim(folder, wait_for_proc)
 96
 97        if success:
 98            # need a if to check that we have correct sim2seis
 99            # copy relevant sim2seis files into folder.
100            for file in glob.glob('sim2seis_config/*'):
101                shutil.copy(file, 'En_' + str(self.ensemble_member) + os.sep)
102
103            self.ecl_case = ecl.EclipseCase(
104                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
105            grid = self.ecl_case.grid()
106
107            phases = self.ecl_case.init.phases
108            if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
109                vintage = []
110                # loop over seismic vintages
111                for v, assim_time in enumerate(self.pem_input['vintage']):
112                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
113                        dt.timedelta(days=assim_time)
114                    pem_input = {}
115                    # get active porosity
116                    tmp = self.ecl_case.cell_data('PORO')
117                    if 'compaction' in self.pem_input:
118                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
119
120                        pem_input['PORO'] = np.array(
121                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
122                    else:
123                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
124                    # get active NTG if needed
125                    if 'ntg' in self.pem_input:
126                        if self.pem_input['ntg'] == 'no':
127                            pem_input['NTG'] = None
128                        else:
129                            tmp = self.ecl_case.cell_data('NTG')
130                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
131                    else:
132                        tmp = self.ecl_case.cell_data('NTG')
133                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
134
135                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
136                        tmp = self.ecl_case.cell_data(var, time)
137                        # only active, and conv. to float
138                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
139
140                    if 'press_conv' in self.pem_input:
141                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
142                            self.pem_input['press_conv']
143
144                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
145                    if hasattr(self.pem, 'p_init'):
146                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
147                    else:
148                        # initial pressure is first
149                        P_init = np.array(tmp[~tmp.mask], dtype=float)
150
151                    if 'press_conv' in self.pem_input:
152                        P_init = P_init*self.pem_input['press_conv']
153
154                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
155                                   for ph in phases]
156                    # Get the pressure
157                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
158                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
159                                        ensembleMember=self.ensemble_member)
160
161                    grdecl.write(f'En_{str(self.ensemble_member)}/Vs{v+1}.grdecl', {
162                                 'Vs': self.pem.getShearVel()*.1, 'DIMENS': grid['DIMENS']}, multi_file=False)
163                    grdecl.write(f'En_{str(self.ensemble_member)}/Vp{v+1}.grdecl', {
164                                 'Vp': self.pem.getBulkVel()*.1, 'DIMENS': grid['DIMENS']}, multi_file=False)
165                    grdecl.write(f'En_{str(self.ensemble_member)}/rho{v+1}.grdecl',
166                                 {'rho': self.pem.getDens(), 'DIMENS': grid['DIMENS']}, multi_file=False)
167
168            current_folder = os.getcwd()
169            run_folder = current_folder + os.sep + 'En_' + str(self.ensemble_member)
170            # The sim2seis is invoked via a shell script. The simulations provides outputs. Run, and get all output. Search
171            # for Done. If not finished in reasonable time -> kill
172            p = Popen(['./sim2seis.sh', run_folder], stdout=PIPE)
173            start = time
174            while b'done' not in p.stdout.readline():
175                pass
176
177            # Todo: handle sim2seis or pem error
178
179        return success
180
181    def extract_data(self, member):
182        # start by getting the data from the flow simulator
183        super().extract_data(member)
184
185        # get the sim2seis from file
186        for prim_ind in self.l_prim:
187            # Loop over all keys in pred_data (all data types)
188            for key in self.all_data_types:
189                if key in ['sim2seis']:
190                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
191                        result = mat73.loadmat(f'En_{member}/Data_conv.mat')['data_conv']
192                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
193                        self.pred_data[prim_ind][key] = np.sum(
194                            np.abs(result[:, :, :, v]), axis=0).flatten()
195
196class flow_rock(flow):
197    """
198    Couple the OPM-flow simulator with a rock-physics simulator such that both reservoir quantities and petro-elastic
199    quantities can be calculated. Inherit the flow class, and use super to call similar functions.
200    """
201
202    def __init__(self, input_dict=None, filename=None, options=None):
203        super().__init__(input_dict, filename, options)
204        self._getpeminfo(input_dict)
205
206        self.dum_file_root = 'dummy.txt'
207        self.dum_entry = str(0)
208        self.date_slack = None
209        if 'date_slack' in input_dict:
210            self.date_slack = int(input_dict['date_slack'])
211
212        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
213        # run a user defined code to do this.
214        self.saveinfo = None
215        if 'savesiminfo' in input_dict:
216            # Make sure "ANALYSISDEBUG" gives a list
217            if isinstance(input_dict['savesiminfo'], list):
218                self.saveinfo = input_dict['savesiminfo']
219            else:
220                self.saveinfo = [input_dict['savesiminfo']]
221
222        self.scale = []
223
224    def _getpeminfo(self, input_dict):
225        """
226        Get, and return, flow and PEM modules
227        """
228        if 'pem' in input_dict:
229            self.pem_input = {}
230            for elem in input_dict['pem']:
231                if elem[0] == 'model':  # Set the petro-elastic model
232                    self.pem_input['model'] = elem[1]
233                if elem[0] == 'depth':  # provide the npz of depth values
234                    self.pem_input['depth'] = elem[1]
235                if elem[0] == 'actnum':  # the npz of actnum values
236                    self.pem_input['actnum'] = elem[1]
237                if elem[0] == 'baseline':  # the time for the baseline 4D measurment
238                    self.pem_input['baseline'] = elem[1]
239                if elem[0] == 'vintage':
240                    self.pem_input['vintage'] = elem[1]
241                    if not type(self.pem_input['vintage']) == list:
242                        self.pem_input['vintage'] = [elem[1]]
243                if elem[0] == 'ntg':
244                    self.pem_input['ntg'] = elem[1]
245                if elem[0] == 'press_conv':
246                    self.pem_input['press_conv'] = elem[1]
247                if elem[0] == 'compaction':
248                    self.pem_input['compaction'] = True
249                if elem[0] == 'overburden':  # the npz of overburden values
250                    self.pem_input['overburden'] = elem[1]
251                if elem[0] == 'percentile':  # use for scaling
252                    self.pem_input['percentile'] = elem[1]
253
254            pem = getattr(import_module('simulator.rockphysics.' +
255                          self.pem_input['model'].split()[0]), self.pem_input['model'].split()[1])
256
257            self.pem = pem(self.pem_input)
258
259        else:
260            self.pem = None
261
262    def setup_fwd_run(self, redund_sim):
263        super().setup_fwd_run(redund_sim=redund_sim)
264
265    def run_fwd_sim(self, state, member_i, del_folder=True):
266        # The inherited simulator also has a run_fwd_sim. Call this.
267        self.ensemble_member = member_i
268        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
269
270        return self.pred_data
271
272    def call_sim(self, folder=None, wait_for_proc=False):
273        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
274        # Then, get the pem.
275        success = super().call_sim(folder, wait_for_proc)
276
277        if success:
278            self.ecl_case = ecl.EclipseCase(
279                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
280            phases = self.ecl_case.init.phases
281            #if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
282            if 'WAT' in phases and 'GAS' in phases:
283                vintage = []
284                # loop over seismic vintages
285                for v, assim_time in enumerate(self.pem_input['vintage']):
286                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
287                        dt.timedelta(days=assim_time)
288                    pem_input = {}
289                    # get active porosity
290                    tmp = self.ecl_case.cell_data('PORO')
291                    if 'compaction' in self.pem_input:
292                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
293
294                        pem_input['PORO'] = np.array(
295                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
296                    else:
297                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
298                    # get active NTG if needed
299                    if 'ntg' in self.pem_input:
300                        if self.pem_input['ntg'] == 'no':
301                            pem_input['NTG'] = None
302                        else:
303                            tmp = self.ecl_case.cell_data('NTG')
304                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
305                    else:
306                        tmp = self.ecl_case.cell_data('NTG')
307                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
308
309                    pem_input['RS'] = None
310                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
311                        try:
312                            tmp = self.ecl_case.cell_data(var, time)
313                        except:
314                            pass
315                        # only active, and conv. to float
316                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
317
318                    if 'press_conv' in self.pem_input:
319                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
320                            self.pem_input['press_conv']
321
322                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
323                    if hasattr(self.pem, 'p_init'):
324                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
325                    else:
326                        # initial pressure is first
327                        P_init = np.array(tmp[~tmp.mask], dtype=float)
328
329                    if 'press_conv' in self.pem_input:
330                        P_init = P_init*self.pem_input['press_conv']
331
332                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
333                                   for ph in phases]
334                    # Get the pressure
335                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
336                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
337                                        ensembleMember=self.ensemble_member)
338                    # mask the bulkimp to get proper dimensions
339                    tmp_value = np.zeros(self.ecl_case.init.shape)
340                    tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
341                    self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
342                                                   mask=deepcopy(self.ecl_case.init.mask))
343                    # run filter
344                    self.pem._filter()
345                    vintage.append(deepcopy(self.pem.bulkimp))
346
347            if hasattr(self.pem, 'baseline'):  # 4D measurement
348                base_time = dt.datetime(self.startDate['year'], self.startDate['month'],
349                                        self.startDate['day']) + dt.timedelta(days=self.pem.baseline)
350                # pem_input = {}
351                # get active porosity
352                tmp = self.ecl_case.cell_data('PORO')
353
354                if 'compaction' in self.pem_input:
355                    multfactor = self.ecl_case.cell_data('PORV_RC', base_time)
356
357                    pem_input['PORO'] = np.array(
358                        multfactor[~tmp.mask] * tmp[~tmp.mask], dtype=float)
359                else:
360                    pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
361
362                pem_input['RS'] = None
363                for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
364                    try:
365                        tmp = self.ecl_case.cell_data(var, base_time)
366                    except:
367                        pass
368                    # only active, and conv. to float
369                    pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
370
371                if 'press_conv' in self.pem_input:
372                    pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
373                        self.pem_input['press_conv']
374
375                saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
376                               for ph in phases]
377                # Get the pressure
378                self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
379                                    ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
380                                    ensembleMember=None)
381
382                # mask the bulkimp to get proper dimensions
383                tmp_value = np.zeros(self.ecl_case.init.shape)
384
385                tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
386                # kill if values are inf or nan
387                assert not np.isnan(tmp_value).any()
388                assert not np.isinf(tmp_value).any()
389                self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
390                                               mask=deepcopy(self.ecl_case.init.mask))
391                self.pem._filter()
392
393                # 4D response
394                self.pem_result = []
395                for i, elem in enumerate(vintage):
396                    self.pem_result.append(elem - deepcopy(self.pem.bulkimp))
397            else:
398                for i, elem in enumerate(vintage):
399                    self.pem_result.append(elem)
400
401        return success
402
403    def extract_data(self, member):
404        # start by getting the data from the flow simulator
405        super().extract_data(member)
406
407        # get the sim2seis from file
408        for prim_ind in self.l_prim:
409            # Loop over all keys in pred_data (all data types)
410            for key in self.all_data_types:
411                if key in ['bulkimp']:
412                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
413                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
414                        self.pred_data[prim_ind][key] = self.pem_result[v].data.flatten()
415
416class flow_barycenter(flow):
417    """
418    Couple the OPM-flow simulator with a rock-physics simulator such that both reservoir quantities and petro-elastic
419    quantities can be calculated. Inherit the flow class, and use super to call similar functions. In the end, the
420    barycenter and moment of interia for the bulkimpedance objects, are returned as observations. The objects are
421    identified using k-means clustering, and the number of objects are determined using and elbow strategy.
422    """
423
424    def __init__(self, input_dict=None, filename=None, options=None):
425        super().__init__(input_dict, filename, options)
426        self._getpeminfo(input_dict)
427
428        self.dum_file_root = 'dummy.txt'
429        self.dum_entry = str(0)
430        self.date_slack = None
431        if 'date_slack' in input_dict:
432            self.date_slack = int(input_dict['date_slack'])
433
434        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
435        # run a user defined code to do this.
436        self.saveinfo = None
437        if 'savesiminfo' in input_dict:
438            # Make sure "ANALYSISDEBUG" gives a list
439            if isinstance(input_dict['savesiminfo'], list):
440                self.saveinfo = input_dict['savesiminfo']
441            else:
442                self.saveinfo = [input_dict['savesiminfo']]
443
444        self.scale = []
445        self.pem_result = []
446        self.bar_result = []
447
448    def _getpeminfo(self, input_dict):
449        """
450        Get, and return, flow and PEM modules
451        """
452        if 'pem' in input_dict:
453            self.pem_input = {}
454            for elem in input_dict['pem']:
455                if elem[0] == 'model':  # Set the petro-elastic model
456                    self.pem_input['model'] = elem[1]
457                if elem[0] == 'depth':  # provide the npz of depth values
458                    self.pem_input['depth'] = elem[1]
459                if elem[0] == 'actnum':  # the npz of actnum values
460                    self.pem_input['actnum'] = elem[1]
461                if elem[0] == 'baseline':  # the time for the baseline 4D measurment
462                    self.pem_input['baseline'] = elem[1]
463                if elem[0] == 'vintage':
464                    self.pem_input['vintage'] = elem[1]
465                    if not type(self.pem_input['vintage']) == list:
466                        self.pem_input['vintage'] = [elem[1]]
467                if elem[0] == 'ntg':
468                    self.pem_input['ntg'] = elem[1]
469                if elem[0] == 'press_conv':
470                    self.pem_input['press_conv'] = elem[1]
471                if elem[0] == 'compaction':
472                    self.pem_input['compaction'] = True
473                if elem[0] == 'overburden':  # the npz of overburden values
474                    self.pem_input['overburden'] = elem[1]
475                if elem[0] == 'percentile':  # use for scaling
476                    self.pem_input['percentile'] = elem[1]
477                if elem[0] == 'clusters':  # number of clusters for each barycenter
478                    self.pem_input['clusters'] = elem[1]
479
480            pem = getattr(import_module('simulator.rockphysics.' +
481                          self.pem_input['model'].split()[0]), self.pem_input['model'].split()[1])
482
483            self.pem = pem(self.pem_input)
484
485        else:
486            self.pem = None
487
488    def setup_fwd_run(self, redund_sim):
489        super().setup_fwd_run(redund_sim=redund_sim)
490
491    def run_fwd_sim(self, state, member_i, del_folder=True):
492        # The inherited simulator also has a run_fwd_sim. Call this.
493        self.ensemble_member = member_i
494        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
495
496        return self.pred_data
497
498    def call_sim(self, folder=None, wait_for_proc=False):
499        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
500        # Then, get the pem.
501        success = super().call_sim(folder, wait_for_proc)
502
503        if success:
504            self.ecl_case = ecl.EclipseCase(
505                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
506            phases = self.ecl_case.init.phases
507            #if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
508            if 'WAT' in phases and 'GAS' in phases:
509                vintage = []
510                # loop over seismic vintages
511                for v, assim_time in enumerate(self.pem_input['vintage']):
512                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
513                        dt.timedelta(days=assim_time)
514                    pem_input = {}
515                    # get active porosity
516                    tmp = self.ecl_case.cell_data('PORO')
517                    if 'compaction' in self.pem_input:
518                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
519
520                        pem_input['PORO'] = np.array(
521                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
522                    else:
523                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
524                    # get active NTG if needed
525                    if 'ntg' in self.pem_input:
526                        if self.pem_input['ntg'] == 'no':
527                            pem_input['NTG'] = None
528                        else:
529                            tmp = self.ecl_case.cell_data('NTG')
530                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
531                    else:
532                        tmp = self.ecl_case.cell_data('NTG')
533                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
534
535                    pem_input['RS'] = None
536                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
537                        try:
538                            tmp = self.ecl_case.cell_data(var, time)
539                        except:
540                            pass
541                        # only active, and conv. to float
542                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
543
544                    if 'press_conv' in self.pem_input:
545                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
546                            self.pem_input['press_conv']
547
548                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
549                    if hasattr(self.pem, 'p_init'):
550                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
551                    else:
552                        # initial pressure is first
553                        P_init = np.array(tmp[~tmp.mask], dtype=float)
554
555                    if 'press_conv' in self.pem_input:
556                        P_init = P_init*self.pem_input['press_conv']
557
558                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
559                                   for ph in phases]
560                    # Get the pressure
561                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
562                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
563                                        ensembleMember=self.ensemble_member)
564                    # mask the bulkimp to get proper dimensions
565                    tmp_value = np.zeros(self.ecl_case.init.shape)
566                    tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
567                    self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
568                                                   mask=deepcopy(self.ecl_case.init.mask))
569                    # run filter
570                    self.pem._filter()
571                    vintage.append(deepcopy(self.pem.bulkimp))
572
573            if hasattr(self.pem, 'baseline'):  # 4D measurement
574                base_time = dt.datetime(self.startDate['year'], self.startDate['month'],
575                                        self.startDate['day']) + dt.timedelta(days=self.pem.baseline)
576                # pem_input = {}
577                # get active porosity
578                tmp = self.ecl_case.cell_data('PORO')
579
580                if 'compaction' in self.pem_input:
581                    multfactor = self.ecl_case.cell_data('PORV_RC', base_time)
582
583                    pem_input['PORO'] = np.array(
584                        multfactor[~tmp.mask] * tmp[~tmp.mask], dtype=float)
585                else:
586                    pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
587
588                pem_input['RS'] = None
589                for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
590                    try:
591                        tmp = self.ecl_case.cell_data(var, base_time)
592                    except:
593                        pass
594                    # only active, and conv. to float
595                    pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
596
597                if 'press_conv' in self.pem_input:
598                    pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
599                        self.pem_input['press_conv']
600
601                saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
602                               for ph in phases]
603                # Get the pressure
604                self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
605                                    ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
606                                    ensembleMember=None)
607
608                # mask the bulkimp to get proper dimensions
609                tmp_value = np.zeros(self.ecl_case.init.shape)
610
611                tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
612                # kill if values are inf or nan
613                assert not np.isnan(tmp_value).any()
614                assert not np.isinf(tmp_value).any()
615                self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
616                                               mask=deepcopy(self.ecl_case.init.mask))
617                self.pem._filter()
618
619                # 4D response
620                for i, elem in enumerate(vintage):
621                    self.pem_result.append(elem - deepcopy(self.pem.bulkimp))
622            else:
623                for i, elem in enumerate(vintage):
624                    self.pem_result.append(elem)
625
626            #  Extract k-means centers and interias for each element in pem_result
627            if 'clusters' in self.pem_input:
628                npzfile = np.load(self.pem_input['clusters'], allow_pickle=True)
629                n_clusters_list = npzfile['n_clusters_list']
630                npzfile.close()
631            else:
632                n_clusters_list = len(self.pem_result)*[2]
633            kmeans_kwargs = {"init": "random", "n_init": 10, "max_iter": 300, "random_state": 42}
634            for i, bulkimp in enumerate(self.pem_result):
635                std = np.std(bulkimp)
636                features = np.argwhere(np.squeeze(np.reshape(np.abs(bulkimp), self.ecl_case.init.shape,)) > 3 * std)
637                scaler = StandardScaler()
638                scaled_features = scaler.fit_transform(features)
639                kmeans = KMeans(n_clusters=n_clusters_list[i], **kmeans_kwargs)
640                kmeans.fit(scaled_features)
641                kmeans_center = np.squeeze(scaler.inverse_transform(kmeans.cluster_centers_))  # data / measurements
642                self.bar_result.append(np.append(kmeans_center, kmeans.inertia_))
643
644        return success
645
646    def extract_data(self, member):
647        # start by getting the data from the flow simulator
648        super().extract_data(member)
649
650        # get the barycenters and inertias
651        for prim_ind in self.l_prim:
652            # Loop over all keys in pred_data (all data types)
653            for key in self.all_data_types:
654                if key in ['barycenter']:
655                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
656                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
657                        self.pred_data[prim_ind][key] = self.bar_result[v].flatten()
class flow_sim2seis(simulator.opm.flow):
 17class flow_sim2seis(flow):
 18    """
 19    Couple the OPM-flow simulator with a sim2seis simulator such that both reservoir quantities and petro-elastic
 20    quantities can be calculated. Inherit the flow class, and use super to call similar functions.
 21    """
 22
 23    def __init__(self, input_dict=None, filename=None, options=None):
 24        super().__init__(input_dict, filename, options)
 25        self._getpeminfo(input_dict)
 26
 27        self.dum_file_root = 'dummy.txt'
 28        self.dum_entry = str(0)
 29        self.date_slack = None
 30        if 'date_slack' in input_dict:
 31            self.date_slack = int(input_dict['date_slack'])
 32
 33        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
 34        # run a user defined code to do this.
 35        self.saveinfo = None
 36        if 'savesiminfo' in input_dict:
 37            # Make sure "ANALYSISDEBUG" gives a list
 38            if isinstance(input_dict['savesiminfo'], list):
 39                self.saveinfo = input_dict['savesiminfo']
 40            else:
 41                self.saveinfo = [input_dict['savesiminfo']]
 42
 43        self.scale = []
 44
 45    def _getpeminfo(self, input_dict):
 46        """
 47        Get, and return, flow and PEM modules
 48        """
 49        if 'pem' in input_dict:
 50            self.pem_input = {}
 51            for elem in input_dict['pem']:
 52                if elem[0] == 'model':  # Set the petro-elastic model
 53                    self.pem_input['model'] = elem[1]
 54                if elem[0] == 'depth':  # provide the npz of depth values
 55                    self.pem_input['depth'] = elem[1]
 56                if elem[0] == 'actnum':  # the npz of actnum values
 57                    self.pem_input['actnum'] = elem[1]
 58                if elem[0] == 'baseline':  # the time for the baseline 4D measurment
 59                    self.pem_input['baseline'] = elem[1]
 60                if elem[0] == 'vintage':
 61                    self.pem_input['vintage'] = elem[1]
 62                    if not type(self.pem_input['vintage']) == list:
 63                        self.pem_input['vintage'] = [elem[1]]
 64                if elem[0] == 'ntg':
 65                    self.pem_input['ntg'] = elem[1]
 66                if elem[0] == 'press_conv':
 67                    self.pem_input['press_conv'] = elem[1]
 68                if elem[0] == 'compaction':
 69                    self.pem_input['compaction'] = True
 70                if elem[0] == 'overburden':  # the npz of overburden values
 71                    self.pem_input['overburden'] = elem[1]
 72                if elem[0] == 'percentile':  # use for scaling
 73                    self.pem_input['percentile'] = elem[1]
 74
 75            pem = getattr(import_module('simulator.rockphysics.' +
 76                          self.pem_input['model'].split()[0]), self.pem_input['model'].split()[1])
 77
 78            self.pem = pem(self.pem_input)
 79
 80        else:
 81            self.pem = None
 82
 83    def setup_fwd_run(self):
 84        super().setup_fwd_run()
 85
 86    def run_fwd_sim(self, state, member_i, del_folder=True):
 87        # The inherited simulator also has a run_fwd_sim. Call this.
 88        self.ensemble_member = member_i
 89        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
 90
 91        return self.pred_data
 92
 93    def call_sim(self, folder=None, wait_for_proc=False):
 94        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
 95        # Then, get the pem.
 96        success = super().call_sim(folder, wait_for_proc)
 97
 98        if success:
 99            # need a if to check that we have correct sim2seis
100            # copy relevant sim2seis files into folder.
101            for file in glob.glob('sim2seis_config/*'):
102                shutil.copy(file, 'En_' + str(self.ensemble_member) + os.sep)
103
104            self.ecl_case = ecl.EclipseCase(
105                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
106            grid = self.ecl_case.grid()
107
108            phases = self.ecl_case.init.phases
109            if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
110                vintage = []
111                # loop over seismic vintages
112                for v, assim_time in enumerate(self.pem_input['vintage']):
113                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
114                        dt.timedelta(days=assim_time)
115                    pem_input = {}
116                    # get active porosity
117                    tmp = self.ecl_case.cell_data('PORO')
118                    if 'compaction' in self.pem_input:
119                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
120
121                        pem_input['PORO'] = np.array(
122                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
123                    else:
124                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
125                    # get active NTG if needed
126                    if 'ntg' in self.pem_input:
127                        if self.pem_input['ntg'] == 'no':
128                            pem_input['NTG'] = None
129                        else:
130                            tmp = self.ecl_case.cell_data('NTG')
131                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
132                    else:
133                        tmp = self.ecl_case.cell_data('NTG')
134                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
135
136                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
137                        tmp = self.ecl_case.cell_data(var, time)
138                        # only active, and conv. to float
139                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
140
141                    if 'press_conv' in self.pem_input:
142                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
143                            self.pem_input['press_conv']
144
145                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
146                    if hasattr(self.pem, 'p_init'):
147                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
148                    else:
149                        # initial pressure is first
150                        P_init = np.array(tmp[~tmp.mask], dtype=float)
151
152                    if 'press_conv' in self.pem_input:
153                        P_init = P_init*self.pem_input['press_conv']
154
155                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
156                                   for ph in phases]
157                    # Get the pressure
158                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
159                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
160                                        ensembleMember=self.ensemble_member)
161
162                    grdecl.write(f'En_{str(self.ensemble_member)}/Vs{v+1}.grdecl', {
163                                 'Vs': self.pem.getShearVel()*.1, 'DIMENS': grid['DIMENS']}, multi_file=False)
164                    grdecl.write(f'En_{str(self.ensemble_member)}/Vp{v+1}.grdecl', {
165                                 'Vp': self.pem.getBulkVel()*.1, 'DIMENS': grid['DIMENS']}, multi_file=False)
166                    grdecl.write(f'En_{str(self.ensemble_member)}/rho{v+1}.grdecl',
167                                 {'rho': self.pem.getDens(), 'DIMENS': grid['DIMENS']}, multi_file=False)
168
169            current_folder = os.getcwd()
170            run_folder = current_folder + os.sep + 'En_' + str(self.ensemble_member)
171            # The sim2seis is invoked via a shell script. The simulations provides outputs. Run, and get all output. Search
172            # for Done. If not finished in reasonable time -> kill
173            p = Popen(['./sim2seis.sh', run_folder], stdout=PIPE)
174            start = time
175            while b'done' not in p.stdout.readline():
176                pass
177
178            # Todo: handle sim2seis or pem error
179
180        return success
181
182    def extract_data(self, member):
183        # start by getting the data from the flow simulator
184        super().extract_data(member)
185
186        # get the sim2seis from file
187        for prim_ind in self.l_prim:
188            # Loop over all keys in pred_data (all data types)
189            for key in self.all_data_types:
190                if key in ['sim2seis']:
191                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
192                        result = mat73.loadmat(f'En_{member}/Data_conv.mat')['data_conv']
193                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
194                        self.pred_data[prim_ind][key] = np.sum(
195                            np.abs(result[:, :, :, v]), axis=0).flatten()

Couple the OPM-flow simulator with a sim2seis simulator such that both reservoir quantities and petro-elastic quantities can be calculated. Inherit the flow class, and use super to call similar functions.

flow_sim2seis(input_dict=None, filename=None, options=None)
23    def __init__(self, input_dict=None, filename=None, options=None):
24        super().__init__(input_dict, filename, options)
25        self._getpeminfo(input_dict)
26
27        self.dum_file_root = 'dummy.txt'
28        self.dum_entry = str(0)
29        self.date_slack = None
30        if 'date_slack' in input_dict:
31            self.date_slack = int(input_dict['date_slack'])
32
33        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
34        # run a user defined code to do this.
35        self.saveinfo = None
36        if 'savesiminfo' in input_dict:
37            # Make sure "ANALYSISDEBUG" gives a list
38            if isinstance(input_dict['savesiminfo'], list):
39                self.saveinfo = input_dict['savesiminfo']
40            else:
41                self.saveinfo = [input_dict['savesiminfo']]
42
43        self.scale = []

The inputs are all optional, but in the same fashion as the other simulators a system must be followed. The input_dict can be utilized as a single input. Here all nescessary info is stored. Alternatively, if input_dict is not defined, all the other input variables must be defined.

Parameters
  • input_dict (dict, optional): Dictionary containing all information required to run the simulator.

    - parallel: number of forward simulations run in parallel
    - simoptions: options for the simulations
        - mpi: option to use mpi (always use > 2 cores)
        - sim_path: Path to the simulator
        - sim_flag: Flags sent to the simulator (see simulator documentation for all possibilities)
    - sim_limit: maximum number of seconds a simulation can run before being killed
    - runfile: name of the simulation input file
    - reportpoint: these are the dates the simulator reports results
    - reporttype: this key states that the report poins are given as dates
    - datatype: the data types the simulator reports
    - replace: replace failed simulations with randomly selected successful ones
    - rerun: in case of failure, try to rerun the simulator the given number of times
    - startdate: simulaton start date
    - saveforecast: save the predicted measurements for each iteration
    
  • filename (str, optional): Name of the .mako file utilized to generate the ECL input .DATA file. Must be in uppercase for the ECL simulator.
  • options (dict, optional): Dictionary with options for the simulator.
Returns
  • initial_object (object): Initial object from the class ecl_100.
dum_file_root
dum_entry
date_slack
saveinfo
scale
def setup_fwd_run(self):
83    def setup_fwd_run(self):
84        super().setup_fwd_run()

Setup the simulator.

Parameters
  • assimIndex (int): Gives the index-type (e.g. step,time,etc.) and the index for the data to be assimilated
  • trueOrder:: Gives the index-type (e.g. step,time,etc.) and the index of the true data
def run_fwd_sim(self, state, member_i, del_folder=True):
86    def run_fwd_sim(self, state, member_i, del_folder=True):
87        # The inherited simulator also has a run_fwd_sim. Call this.
88        self.ensemble_member = member_i
89        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
90
91        return self.pred_data

Setup and run the ecl_100 forward simulator. All the parameters are defined as attributes, and the name of the parameters are initialized in setupFwdRun. This method sets up and runs all the individual ensemble members. This method is based on writing .DATA file for each ensemble member using the mako template, for more info regarding mako see http://www.makotemplates.org/

Parameters
  • state (dict): Dictionary containing the ensemble state.
  • member_i (int): Index of the ensemble member.
  • del_folder (bool, optional): Boolean to determine if the ensemble folder should be deleted. Default is False.
def call_sim(self, folder=None, wait_for_proc=False):
 93    def call_sim(self, folder=None, wait_for_proc=False):
 94        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
 95        # Then, get the pem.
 96        success = super().call_sim(folder, wait_for_proc)
 97
 98        if success:
 99            # need a if to check that we have correct sim2seis
100            # copy relevant sim2seis files into folder.
101            for file in glob.glob('sim2seis_config/*'):
102                shutil.copy(file, 'En_' + str(self.ensemble_member) + os.sep)
103
104            self.ecl_case = ecl.EclipseCase(
105                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
106            grid = self.ecl_case.grid()
107
108            phases = self.ecl_case.init.phases
109            if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
110                vintage = []
111                # loop over seismic vintages
112                for v, assim_time in enumerate(self.pem_input['vintage']):
113                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
114                        dt.timedelta(days=assim_time)
115                    pem_input = {}
116                    # get active porosity
117                    tmp = self.ecl_case.cell_data('PORO')
118                    if 'compaction' in self.pem_input:
119                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
120
121                        pem_input['PORO'] = np.array(
122                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
123                    else:
124                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
125                    # get active NTG if needed
126                    if 'ntg' in self.pem_input:
127                        if self.pem_input['ntg'] == 'no':
128                            pem_input['NTG'] = None
129                        else:
130                            tmp = self.ecl_case.cell_data('NTG')
131                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
132                    else:
133                        tmp = self.ecl_case.cell_data('NTG')
134                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
135
136                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
137                        tmp = self.ecl_case.cell_data(var, time)
138                        # only active, and conv. to float
139                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
140
141                    if 'press_conv' in self.pem_input:
142                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
143                            self.pem_input['press_conv']
144
145                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
146                    if hasattr(self.pem, 'p_init'):
147                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
148                    else:
149                        # initial pressure is first
150                        P_init = np.array(tmp[~tmp.mask], dtype=float)
151
152                    if 'press_conv' in self.pem_input:
153                        P_init = P_init*self.pem_input['press_conv']
154
155                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
156                                   for ph in phases]
157                    # Get the pressure
158                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
159                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
160                                        ensembleMember=self.ensemble_member)
161
162                    grdecl.write(f'En_{str(self.ensemble_member)}/Vs{v+1}.grdecl', {
163                                 'Vs': self.pem.getShearVel()*.1, 'DIMENS': grid['DIMENS']}, multi_file=False)
164                    grdecl.write(f'En_{str(self.ensemble_member)}/Vp{v+1}.grdecl', {
165                                 'Vp': self.pem.getBulkVel()*.1, 'DIMENS': grid['DIMENS']}, multi_file=False)
166                    grdecl.write(f'En_{str(self.ensemble_member)}/rho{v+1}.grdecl',
167                                 {'rho': self.pem.getDens(), 'DIMENS': grid['DIMENS']}, multi_file=False)
168
169            current_folder = os.getcwd()
170            run_folder = current_folder + os.sep + 'En_' + str(self.ensemble_member)
171            # The sim2seis is invoked via a shell script. The simulations provides outputs. Run, and get all output. Search
172            # for Done. If not finished in reasonable time -> kill
173            p = Popen(['./sim2seis.sh', run_folder], stdout=PIPE)
174            start = time
175            while b'done' not in p.stdout.readline():
176                pass
177
178            # Todo: handle sim2seis or pem error
179
180        return success

Call OPM flow simulator via shell.

Parameters
  • folder (str): Folder with runfiles.
  • wait_for_proc (bool): Boolean determining if we wait for the process to be done or not.
Changelog
  • ST 18/10-18
def extract_data(self, member):
182    def extract_data(self, member):
183        # start by getting the data from the flow simulator
184        super().extract_data(member)
185
186        # get the sim2seis from file
187        for prim_ind in self.l_prim:
188            # Loop over all keys in pred_data (all data types)
189            for key in self.all_data_types:
190                if key in ['sim2seis']:
191                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
192                        result = mat73.loadmat(f'En_{member}/Data_conv.mat')['data_conv']
193                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
194                        self.pred_data[prim_ind][key] = np.sum(
195                            np.abs(result[:, :, :, v]), axis=0).flatten()
class flow_rock(simulator.opm.flow):
197class flow_rock(flow):
198    """
199    Couple the OPM-flow simulator with a rock-physics simulator such that both reservoir quantities and petro-elastic
200    quantities can be calculated. Inherit the flow class, and use super to call similar functions.
201    """
202
203    def __init__(self, input_dict=None, filename=None, options=None):
204        super().__init__(input_dict, filename, options)
205        self._getpeminfo(input_dict)
206
207        self.dum_file_root = 'dummy.txt'
208        self.dum_entry = str(0)
209        self.date_slack = None
210        if 'date_slack' in input_dict:
211            self.date_slack = int(input_dict['date_slack'])
212
213        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
214        # run a user defined code to do this.
215        self.saveinfo = None
216        if 'savesiminfo' in input_dict:
217            # Make sure "ANALYSISDEBUG" gives a list
218            if isinstance(input_dict['savesiminfo'], list):
219                self.saveinfo = input_dict['savesiminfo']
220            else:
221                self.saveinfo = [input_dict['savesiminfo']]
222
223        self.scale = []
224
225    def _getpeminfo(self, input_dict):
226        """
227        Get, and return, flow and PEM modules
228        """
229        if 'pem' in input_dict:
230            self.pem_input = {}
231            for elem in input_dict['pem']:
232                if elem[0] == 'model':  # Set the petro-elastic model
233                    self.pem_input['model'] = elem[1]
234                if elem[0] == 'depth':  # provide the npz of depth values
235                    self.pem_input['depth'] = elem[1]
236                if elem[0] == 'actnum':  # the npz of actnum values
237                    self.pem_input['actnum'] = elem[1]
238                if elem[0] == 'baseline':  # the time for the baseline 4D measurment
239                    self.pem_input['baseline'] = elem[1]
240                if elem[0] == 'vintage':
241                    self.pem_input['vintage'] = elem[1]
242                    if not type(self.pem_input['vintage']) == list:
243                        self.pem_input['vintage'] = [elem[1]]
244                if elem[0] == 'ntg':
245                    self.pem_input['ntg'] = elem[1]
246                if elem[0] == 'press_conv':
247                    self.pem_input['press_conv'] = elem[1]
248                if elem[0] == 'compaction':
249                    self.pem_input['compaction'] = True
250                if elem[0] == 'overburden':  # the npz of overburden values
251                    self.pem_input['overburden'] = elem[1]
252                if elem[0] == 'percentile':  # use for scaling
253                    self.pem_input['percentile'] = elem[1]
254
255            pem = getattr(import_module('simulator.rockphysics.' +
256                          self.pem_input['model'].split()[0]), self.pem_input['model'].split()[1])
257
258            self.pem = pem(self.pem_input)
259
260        else:
261            self.pem = None
262
263    def setup_fwd_run(self, redund_sim):
264        super().setup_fwd_run(redund_sim=redund_sim)
265
266    def run_fwd_sim(self, state, member_i, del_folder=True):
267        # The inherited simulator also has a run_fwd_sim. Call this.
268        self.ensemble_member = member_i
269        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
270
271        return self.pred_data
272
273    def call_sim(self, folder=None, wait_for_proc=False):
274        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
275        # Then, get the pem.
276        success = super().call_sim(folder, wait_for_proc)
277
278        if success:
279            self.ecl_case = ecl.EclipseCase(
280                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
281            phases = self.ecl_case.init.phases
282            #if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
283            if 'WAT' in phases and 'GAS' in phases:
284                vintage = []
285                # loop over seismic vintages
286                for v, assim_time in enumerate(self.pem_input['vintage']):
287                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
288                        dt.timedelta(days=assim_time)
289                    pem_input = {}
290                    # get active porosity
291                    tmp = self.ecl_case.cell_data('PORO')
292                    if 'compaction' in self.pem_input:
293                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
294
295                        pem_input['PORO'] = np.array(
296                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
297                    else:
298                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
299                    # get active NTG if needed
300                    if 'ntg' in self.pem_input:
301                        if self.pem_input['ntg'] == 'no':
302                            pem_input['NTG'] = None
303                        else:
304                            tmp = self.ecl_case.cell_data('NTG')
305                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
306                    else:
307                        tmp = self.ecl_case.cell_data('NTG')
308                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
309
310                    pem_input['RS'] = None
311                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
312                        try:
313                            tmp = self.ecl_case.cell_data(var, time)
314                        except:
315                            pass
316                        # only active, and conv. to float
317                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
318
319                    if 'press_conv' in self.pem_input:
320                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
321                            self.pem_input['press_conv']
322
323                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
324                    if hasattr(self.pem, 'p_init'):
325                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
326                    else:
327                        # initial pressure is first
328                        P_init = np.array(tmp[~tmp.mask], dtype=float)
329
330                    if 'press_conv' in self.pem_input:
331                        P_init = P_init*self.pem_input['press_conv']
332
333                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
334                                   for ph in phases]
335                    # Get the pressure
336                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
337                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
338                                        ensembleMember=self.ensemble_member)
339                    # mask the bulkimp to get proper dimensions
340                    tmp_value = np.zeros(self.ecl_case.init.shape)
341                    tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
342                    self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
343                                                   mask=deepcopy(self.ecl_case.init.mask))
344                    # run filter
345                    self.pem._filter()
346                    vintage.append(deepcopy(self.pem.bulkimp))
347
348            if hasattr(self.pem, 'baseline'):  # 4D measurement
349                base_time = dt.datetime(self.startDate['year'], self.startDate['month'],
350                                        self.startDate['day']) + dt.timedelta(days=self.pem.baseline)
351                # pem_input = {}
352                # get active porosity
353                tmp = self.ecl_case.cell_data('PORO')
354
355                if 'compaction' in self.pem_input:
356                    multfactor = self.ecl_case.cell_data('PORV_RC', base_time)
357
358                    pem_input['PORO'] = np.array(
359                        multfactor[~tmp.mask] * tmp[~tmp.mask], dtype=float)
360                else:
361                    pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
362
363                pem_input['RS'] = None
364                for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
365                    try:
366                        tmp = self.ecl_case.cell_data(var, base_time)
367                    except:
368                        pass
369                    # only active, and conv. to float
370                    pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
371
372                if 'press_conv' in self.pem_input:
373                    pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
374                        self.pem_input['press_conv']
375
376                saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
377                               for ph in phases]
378                # Get the pressure
379                self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
380                                    ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
381                                    ensembleMember=None)
382
383                # mask the bulkimp to get proper dimensions
384                tmp_value = np.zeros(self.ecl_case.init.shape)
385
386                tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
387                # kill if values are inf or nan
388                assert not np.isnan(tmp_value).any()
389                assert not np.isinf(tmp_value).any()
390                self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
391                                               mask=deepcopy(self.ecl_case.init.mask))
392                self.pem._filter()
393
394                # 4D response
395                self.pem_result = []
396                for i, elem in enumerate(vintage):
397                    self.pem_result.append(elem - deepcopy(self.pem.bulkimp))
398            else:
399                for i, elem in enumerate(vintage):
400                    self.pem_result.append(elem)
401
402        return success
403
404    def extract_data(self, member):
405        # start by getting the data from the flow simulator
406        super().extract_data(member)
407
408        # get the sim2seis from file
409        for prim_ind in self.l_prim:
410            # Loop over all keys in pred_data (all data types)
411            for key in self.all_data_types:
412                if key in ['bulkimp']:
413                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
414                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
415                        self.pred_data[prim_ind][key] = self.pem_result[v].data.flatten()

Couple the OPM-flow simulator with a rock-physics simulator such that both reservoir quantities and petro-elastic quantities can be calculated. Inherit the flow class, and use super to call similar functions.

flow_rock(input_dict=None, filename=None, options=None)
203    def __init__(self, input_dict=None, filename=None, options=None):
204        super().__init__(input_dict, filename, options)
205        self._getpeminfo(input_dict)
206
207        self.dum_file_root = 'dummy.txt'
208        self.dum_entry = str(0)
209        self.date_slack = None
210        if 'date_slack' in input_dict:
211            self.date_slack = int(input_dict['date_slack'])
212
213        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
214        # run a user defined code to do this.
215        self.saveinfo = None
216        if 'savesiminfo' in input_dict:
217            # Make sure "ANALYSISDEBUG" gives a list
218            if isinstance(input_dict['savesiminfo'], list):
219                self.saveinfo = input_dict['savesiminfo']
220            else:
221                self.saveinfo = [input_dict['savesiminfo']]
222
223        self.scale = []

The inputs are all optional, but in the same fashion as the other simulators a system must be followed. The input_dict can be utilized as a single input. Here all nescessary info is stored. Alternatively, if input_dict is not defined, all the other input variables must be defined.

Parameters
  • input_dict (dict, optional): Dictionary containing all information required to run the simulator.

    - parallel: number of forward simulations run in parallel
    - simoptions: options for the simulations
        - mpi: option to use mpi (always use > 2 cores)
        - sim_path: Path to the simulator
        - sim_flag: Flags sent to the simulator (see simulator documentation for all possibilities)
    - sim_limit: maximum number of seconds a simulation can run before being killed
    - runfile: name of the simulation input file
    - reportpoint: these are the dates the simulator reports results
    - reporttype: this key states that the report poins are given as dates
    - datatype: the data types the simulator reports
    - replace: replace failed simulations with randomly selected successful ones
    - rerun: in case of failure, try to rerun the simulator the given number of times
    - startdate: simulaton start date
    - saveforecast: save the predicted measurements for each iteration
    
  • filename (str, optional): Name of the .mako file utilized to generate the ECL input .DATA file. Must be in uppercase for the ECL simulator.
  • options (dict, optional): Dictionary with options for the simulator.
Returns
  • initial_object (object): Initial object from the class ecl_100.
dum_file_root
dum_entry
date_slack
saveinfo
scale
def setup_fwd_run(self, redund_sim):
263    def setup_fwd_run(self, redund_sim):
264        super().setup_fwd_run(redund_sim=redund_sim)

Setup the simulator.

Parameters
  • assimIndex (int): Gives the index-type (e.g. step,time,etc.) and the index for the data to be assimilated
  • trueOrder:: Gives the index-type (e.g. step,time,etc.) and the index of the true data
def run_fwd_sim(self, state, member_i, del_folder=True):
266    def run_fwd_sim(self, state, member_i, del_folder=True):
267        # The inherited simulator also has a run_fwd_sim. Call this.
268        self.ensemble_member = member_i
269        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
270
271        return self.pred_data

Setup and run the ecl_100 forward simulator. All the parameters are defined as attributes, and the name of the parameters are initialized in setupFwdRun. This method sets up and runs all the individual ensemble members. This method is based on writing .DATA file for each ensemble member using the mako template, for more info regarding mako see http://www.makotemplates.org/

Parameters
  • state (dict): Dictionary containing the ensemble state.
  • member_i (int): Index of the ensemble member.
  • del_folder (bool, optional): Boolean to determine if the ensemble folder should be deleted. Default is False.
def call_sim(self, folder=None, wait_for_proc=False):
273    def call_sim(self, folder=None, wait_for_proc=False):
274        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
275        # Then, get the pem.
276        success = super().call_sim(folder, wait_for_proc)
277
278        if success:
279            self.ecl_case = ecl.EclipseCase(
280                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
281            phases = self.ecl_case.init.phases
282            #if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
283            if 'WAT' in phases and 'GAS' in phases:
284                vintage = []
285                # loop over seismic vintages
286                for v, assim_time in enumerate(self.pem_input['vintage']):
287                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
288                        dt.timedelta(days=assim_time)
289                    pem_input = {}
290                    # get active porosity
291                    tmp = self.ecl_case.cell_data('PORO')
292                    if 'compaction' in self.pem_input:
293                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
294
295                        pem_input['PORO'] = np.array(
296                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
297                    else:
298                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
299                    # get active NTG if needed
300                    if 'ntg' in self.pem_input:
301                        if self.pem_input['ntg'] == 'no':
302                            pem_input['NTG'] = None
303                        else:
304                            tmp = self.ecl_case.cell_data('NTG')
305                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
306                    else:
307                        tmp = self.ecl_case.cell_data('NTG')
308                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
309
310                    pem_input['RS'] = None
311                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
312                        try:
313                            tmp = self.ecl_case.cell_data(var, time)
314                        except:
315                            pass
316                        # only active, and conv. to float
317                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
318
319                    if 'press_conv' in self.pem_input:
320                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
321                            self.pem_input['press_conv']
322
323                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
324                    if hasattr(self.pem, 'p_init'):
325                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
326                    else:
327                        # initial pressure is first
328                        P_init = np.array(tmp[~tmp.mask], dtype=float)
329
330                    if 'press_conv' in self.pem_input:
331                        P_init = P_init*self.pem_input['press_conv']
332
333                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
334                                   for ph in phases]
335                    # Get the pressure
336                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
337                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
338                                        ensembleMember=self.ensemble_member)
339                    # mask the bulkimp to get proper dimensions
340                    tmp_value = np.zeros(self.ecl_case.init.shape)
341                    tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
342                    self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
343                                                   mask=deepcopy(self.ecl_case.init.mask))
344                    # run filter
345                    self.pem._filter()
346                    vintage.append(deepcopy(self.pem.bulkimp))
347
348            if hasattr(self.pem, 'baseline'):  # 4D measurement
349                base_time = dt.datetime(self.startDate['year'], self.startDate['month'],
350                                        self.startDate['day']) + dt.timedelta(days=self.pem.baseline)
351                # pem_input = {}
352                # get active porosity
353                tmp = self.ecl_case.cell_data('PORO')
354
355                if 'compaction' in self.pem_input:
356                    multfactor = self.ecl_case.cell_data('PORV_RC', base_time)
357
358                    pem_input['PORO'] = np.array(
359                        multfactor[~tmp.mask] * tmp[~tmp.mask], dtype=float)
360                else:
361                    pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
362
363                pem_input['RS'] = None
364                for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
365                    try:
366                        tmp = self.ecl_case.cell_data(var, base_time)
367                    except:
368                        pass
369                    # only active, and conv. to float
370                    pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
371
372                if 'press_conv' in self.pem_input:
373                    pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
374                        self.pem_input['press_conv']
375
376                saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
377                               for ph in phases]
378                # Get the pressure
379                self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
380                                    ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
381                                    ensembleMember=None)
382
383                # mask the bulkimp to get proper dimensions
384                tmp_value = np.zeros(self.ecl_case.init.shape)
385
386                tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
387                # kill if values are inf or nan
388                assert not np.isnan(tmp_value).any()
389                assert not np.isinf(tmp_value).any()
390                self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
391                                               mask=deepcopy(self.ecl_case.init.mask))
392                self.pem._filter()
393
394                # 4D response
395                self.pem_result = []
396                for i, elem in enumerate(vintage):
397                    self.pem_result.append(elem - deepcopy(self.pem.bulkimp))
398            else:
399                for i, elem in enumerate(vintage):
400                    self.pem_result.append(elem)
401
402        return success

Call OPM flow simulator via shell.

Parameters
  • folder (str): Folder with runfiles.
  • wait_for_proc (bool): Boolean determining if we wait for the process to be done or not.
Changelog
  • ST 18/10-18
def extract_data(self, member):
404    def extract_data(self, member):
405        # start by getting the data from the flow simulator
406        super().extract_data(member)
407
408        # get the sim2seis from file
409        for prim_ind in self.l_prim:
410            # Loop over all keys in pred_data (all data types)
411            for key in self.all_data_types:
412                if key in ['bulkimp']:
413                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
414                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
415                        self.pred_data[prim_ind][key] = self.pem_result[v].data.flatten()
class flow_barycenter(simulator.opm.flow):
417class flow_barycenter(flow):
418    """
419    Couple the OPM-flow simulator with a rock-physics simulator such that both reservoir quantities and petro-elastic
420    quantities can be calculated. Inherit the flow class, and use super to call similar functions. In the end, the
421    barycenter and moment of interia for the bulkimpedance objects, are returned as observations. The objects are
422    identified using k-means clustering, and the number of objects are determined using and elbow strategy.
423    """
424
425    def __init__(self, input_dict=None, filename=None, options=None):
426        super().__init__(input_dict, filename, options)
427        self._getpeminfo(input_dict)
428
429        self.dum_file_root = 'dummy.txt'
430        self.dum_entry = str(0)
431        self.date_slack = None
432        if 'date_slack' in input_dict:
433            self.date_slack = int(input_dict['date_slack'])
434
435        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
436        # run a user defined code to do this.
437        self.saveinfo = None
438        if 'savesiminfo' in input_dict:
439            # Make sure "ANALYSISDEBUG" gives a list
440            if isinstance(input_dict['savesiminfo'], list):
441                self.saveinfo = input_dict['savesiminfo']
442            else:
443                self.saveinfo = [input_dict['savesiminfo']]
444
445        self.scale = []
446        self.pem_result = []
447        self.bar_result = []
448
449    def _getpeminfo(self, input_dict):
450        """
451        Get, and return, flow and PEM modules
452        """
453        if 'pem' in input_dict:
454            self.pem_input = {}
455            for elem in input_dict['pem']:
456                if elem[0] == 'model':  # Set the petro-elastic model
457                    self.pem_input['model'] = elem[1]
458                if elem[0] == 'depth':  # provide the npz of depth values
459                    self.pem_input['depth'] = elem[1]
460                if elem[0] == 'actnum':  # the npz of actnum values
461                    self.pem_input['actnum'] = elem[1]
462                if elem[0] == 'baseline':  # the time for the baseline 4D measurment
463                    self.pem_input['baseline'] = elem[1]
464                if elem[0] == 'vintage':
465                    self.pem_input['vintage'] = elem[1]
466                    if not type(self.pem_input['vintage']) == list:
467                        self.pem_input['vintage'] = [elem[1]]
468                if elem[0] == 'ntg':
469                    self.pem_input['ntg'] = elem[1]
470                if elem[0] == 'press_conv':
471                    self.pem_input['press_conv'] = elem[1]
472                if elem[0] == 'compaction':
473                    self.pem_input['compaction'] = True
474                if elem[0] == 'overburden':  # the npz of overburden values
475                    self.pem_input['overburden'] = elem[1]
476                if elem[0] == 'percentile':  # use for scaling
477                    self.pem_input['percentile'] = elem[1]
478                if elem[0] == 'clusters':  # number of clusters for each barycenter
479                    self.pem_input['clusters'] = elem[1]
480
481            pem = getattr(import_module('simulator.rockphysics.' +
482                          self.pem_input['model'].split()[0]), self.pem_input['model'].split()[1])
483
484            self.pem = pem(self.pem_input)
485
486        else:
487            self.pem = None
488
489    def setup_fwd_run(self, redund_sim):
490        super().setup_fwd_run(redund_sim=redund_sim)
491
492    def run_fwd_sim(self, state, member_i, del_folder=True):
493        # The inherited simulator also has a run_fwd_sim. Call this.
494        self.ensemble_member = member_i
495        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
496
497        return self.pred_data
498
499    def call_sim(self, folder=None, wait_for_proc=False):
500        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
501        # Then, get the pem.
502        success = super().call_sim(folder, wait_for_proc)
503
504        if success:
505            self.ecl_case = ecl.EclipseCase(
506                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
507            phases = self.ecl_case.init.phases
508            #if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
509            if 'WAT' in phases and 'GAS' in phases:
510                vintage = []
511                # loop over seismic vintages
512                for v, assim_time in enumerate(self.pem_input['vintage']):
513                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
514                        dt.timedelta(days=assim_time)
515                    pem_input = {}
516                    # get active porosity
517                    tmp = self.ecl_case.cell_data('PORO')
518                    if 'compaction' in self.pem_input:
519                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
520
521                        pem_input['PORO'] = np.array(
522                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
523                    else:
524                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
525                    # get active NTG if needed
526                    if 'ntg' in self.pem_input:
527                        if self.pem_input['ntg'] == 'no':
528                            pem_input['NTG'] = None
529                        else:
530                            tmp = self.ecl_case.cell_data('NTG')
531                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
532                    else:
533                        tmp = self.ecl_case.cell_data('NTG')
534                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
535
536                    pem_input['RS'] = None
537                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
538                        try:
539                            tmp = self.ecl_case.cell_data(var, time)
540                        except:
541                            pass
542                        # only active, and conv. to float
543                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
544
545                    if 'press_conv' in self.pem_input:
546                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
547                            self.pem_input['press_conv']
548
549                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
550                    if hasattr(self.pem, 'p_init'):
551                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
552                    else:
553                        # initial pressure is first
554                        P_init = np.array(tmp[~tmp.mask], dtype=float)
555
556                    if 'press_conv' in self.pem_input:
557                        P_init = P_init*self.pem_input['press_conv']
558
559                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
560                                   for ph in phases]
561                    # Get the pressure
562                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
563                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
564                                        ensembleMember=self.ensemble_member)
565                    # mask the bulkimp to get proper dimensions
566                    tmp_value = np.zeros(self.ecl_case.init.shape)
567                    tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
568                    self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
569                                                   mask=deepcopy(self.ecl_case.init.mask))
570                    # run filter
571                    self.pem._filter()
572                    vintage.append(deepcopy(self.pem.bulkimp))
573
574            if hasattr(self.pem, 'baseline'):  # 4D measurement
575                base_time = dt.datetime(self.startDate['year'], self.startDate['month'],
576                                        self.startDate['day']) + dt.timedelta(days=self.pem.baseline)
577                # pem_input = {}
578                # get active porosity
579                tmp = self.ecl_case.cell_data('PORO')
580
581                if 'compaction' in self.pem_input:
582                    multfactor = self.ecl_case.cell_data('PORV_RC', base_time)
583
584                    pem_input['PORO'] = np.array(
585                        multfactor[~tmp.mask] * tmp[~tmp.mask], dtype=float)
586                else:
587                    pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
588
589                pem_input['RS'] = None
590                for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
591                    try:
592                        tmp = self.ecl_case.cell_data(var, base_time)
593                    except:
594                        pass
595                    # only active, and conv. to float
596                    pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
597
598                if 'press_conv' in self.pem_input:
599                    pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
600                        self.pem_input['press_conv']
601
602                saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
603                               for ph in phases]
604                # Get the pressure
605                self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
606                                    ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
607                                    ensembleMember=None)
608
609                # mask the bulkimp to get proper dimensions
610                tmp_value = np.zeros(self.ecl_case.init.shape)
611
612                tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
613                # kill if values are inf or nan
614                assert not np.isnan(tmp_value).any()
615                assert not np.isinf(tmp_value).any()
616                self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
617                                               mask=deepcopy(self.ecl_case.init.mask))
618                self.pem._filter()
619
620                # 4D response
621                for i, elem in enumerate(vintage):
622                    self.pem_result.append(elem - deepcopy(self.pem.bulkimp))
623            else:
624                for i, elem in enumerate(vintage):
625                    self.pem_result.append(elem)
626
627            #  Extract k-means centers and interias for each element in pem_result
628            if 'clusters' in self.pem_input:
629                npzfile = np.load(self.pem_input['clusters'], allow_pickle=True)
630                n_clusters_list = npzfile['n_clusters_list']
631                npzfile.close()
632            else:
633                n_clusters_list = len(self.pem_result)*[2]
634            kmeans_kwargs = {"init": "random", "n_init": 10, "max_iter": 300, "random_state": 42}
635            for i, bulkimp in enumerate(self.pem_result):
636                std = np.std(bulkimp)
637                features = np.argwhere(np.squeeze(np.reshape(np.abs(bulkimp), self.ecl_case.init.shape,)) > 3 * std)
638                scaler = StandardScaler()
639                scaled_features = scaler.fit_transform(features)
640                kmeans = KMeans(n_clusters=n_clusters_list[i], **kmeans_kwargs)
641                kmeans.fit(scaled_features)
642                kmeans_center = np.squeeze(scaler.inverse_transform(kmeans.cluster_centers_))  # data / measurements
643                self.bar_result.append(np.append(kmeans_center, kmeans.inertia_))
644
645        return success
646
647    def extract_data(self, member):
648        # start by getting the data from the flow simulator
649        super().extract_data(member)
650
651        # get the barycenters and inertias
652        for prim_ind in self.l_prim:
653            # Loop over all keys in pred_data (all data types)
654            for key in self.all_data_types:
655                if key in ['barycenter']:
656                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
657                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
658                        self.pred_data[prim_ind][key] = self.bar_result[v].flatten()

Couple the OPM-flow simulator with a rock-physics simulator such that both reservoir quantities and petro-elastic quantities can be calculated. Inherit the flow class, and use super to call similar functions. In the end, the barycenter and moment of interia for the bulkimpedance objects, are returned as observations. The objects are identified using k-means clustering, and the number of objects are determined using and elbow strategy.

flow_barycenter(input_dict=None, filename=None, options=None)
425    def __init__(self, input_dict=None, filename=None, options=None):
426        super().__init__(input_dict, filename, options)
427        self._getpeminfo(input_dict)
428
429        self.dum_file_root = 'dummy.txt'
430        self.dum_entry = str(0)
431        self.date_slack = None
432        if 'date_slack' in input_dict:
433            self.date_slack = int(input_dict['date_slack'])
434
435        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
436        # run a user defined code to do this.
437        self.saveinfo = None
438        if 'savesiminfo' in input_dict:
439            # Make sure "ANALYSISDEBUG" gives a list
440            if isinstance(input_dict['savesiminfo'], list):
441                self.saveinfo = input_dict['savesiminfo']
442            else:
443                self.saveinfo = [input_dict['savesiminfo']]
444
445        self.scale = []
446        self.pem_result = []
447        self.bar_result = []

The inputs are all optional, but in the same fashion as the other simulators a system must be followed. The input_dict can be utilized as a single input. Here all nescessary info is stored. Alternatively, if input_dict is not defined, all the other input variables must be defined.

Parameters
  • input_dict (dict, optional): Dictionary containing all information required to run the simulator.

    - parallel: number of forward simulations run in parallel
    - simoptions: options for the simulations
        - mpi: option to use mpi (always use > 2 cores)
        - sim_path: Path to the simulator
        - sim_flag: Flags sent to the simulator (see simulator documentation for all possibilities)
    - sim_limit: maximum number of seconds a simulation can run before being killed
    - runfile: name of the simulation input file
    - reportpoint: these are the dates the simulator reports results
    - reporttype: this key states that the report poins are given as dates
    - datatype: the data types the simulator reports
    - replace: replace failed simulations with randomly selected successful ones
    - rerun: in case of failure, try to rerun the simulator the given number of times
    - startdate: simulaton start date
    - saveforecast: save the predicted measurements for each iteration
    
  • filename (str, optional): Name of the .mako file utilized to generate the ECL input .DATA file. Must be in uppercase for the ECL simulator.
  • options (dict, optional): Dictionary with options for the simulator.
Returns
  • initial_object (object): Initial object from the class ecl_100.
dum_file_root
dum_entry
date_slack
saveinfo
scale
pem_result
bar_result
def setup_fwd_run(self, redund_sim):
489    def setup_fwd_run(self, redund_sim):
490        super().setup_fwd_run(redund_sim=redund_sim)

Setup the simulator.

Parameters
  • assimIndex (int): Gives the index-type (e.g. step,time,etc.) and the index for the data to be assimilated
  • trueOrder:: Gives the index-type (e.g. step,time,etc.) and the index of the true data
def run_fwd_sim(self, state, member_i, del_folder=True):
492    def run_fwd_sim(self, state, member_i, del_folder=True):
493        # The inherited simulator also has a run_fwd_sim. Call this.
494        self.ensemble_member = member_i
495        self.pred_data = super().run_fwd_sim(state, member_i, del_folder=True)
496
497        return self.pred_data

Setup and run the ecl_100 forward simulator. All the parameters are defined as attributes, and the name of the parameters are initialized in setupFwdRun. This method sets up and runs all the individual ensemble members. This method is based on writing .DATA file for each ensemble member using the mako template, for more info regarding mako see http://www.makotemplates.org/

Parameters
  • state (dict): Dictionary containing the ensemble state.
  • member_i (int): Index of the ensemble member.
  • del_folder (bool, optional): Boolean to determine if the ensemble folder should be deleted. Default is False.
def call_sim(self, folder=None, wait_for_proc=False):
499    def call_sim(self, folder=None, wait_for_proc=False):
500        # the super run_fwd_sim will invoke call_sim. Modify this such that the fluid simulator is run first.
501        # Then, get the pem.
502        success = super().call_sim(folder, wait_for_proc)
503
504        if success:
505            self.ecl_case = ecl.EclipseCase(
506                'En_' + str(self.ensemble_member) + os.sep + self.file + '.DATA')
507            phases = self.ecl_case.init.phases
508            #if 'OIL' in phases and 'WAT' in phases and 'GAS' in phases:  # This should be extended
509            if 'WAT' in phases and 'GAS' in phases:
510                vintage = []
511                # loop over seismic vintages
512                for v, assim_time in enumerate(self.pem_input['vintage']):
513                    time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
514                        dt.timedelta(days=assim_time)
515                    pem_input = {}
516                    # get active porosity
517                    tmp = self.ecl_case.cell_data('PORO')
518                    if 'compaction' in self.pem_input:
519                        multfactor = self.ecl_case.cell_data('PORV_RC', time)
520
521                        pem_input['PORO'] = np.array(
522                            multfactor[~tmp.mask]*tmp[~tmp.mask], dtype=float)
523                    else:
524                        pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
525                    # get active NTG if needed
526                    if 'ntg' in self.pem_input:
527                        if self.pem_input['ntg'] == 'no':
528                            pem_input['NTG'] = None
529                        else:
530                            tmp = self.ecl_case.cell_data('NTG')
531                            pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
532                    else:
533                        tmp = self.ecl_case.cell_data('NTG')
534                        pem_input['NTG'] = np.array(tmp[~tmp.mask], dtype=float)
535
536                    pem_input['RS'] = None
537                    for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
538                        try:
539                            tmp = self.ecl_case.cell_data(var, time)
540                        except:
541                            pass
542                        # only active, and conv. to float
543                        pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
544
545                    if 'press_conv' in self.pem_input:
546                        pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
547                            self.pem_input['press_conv']
548
549                    tmp = self.ecl_case.cell_data('PRESSURE', 1)
550                    if hasattr(self.pem, 'p_init'):
551                        P_init = self.pem.p_init*np.ones(tmp.shape)[~tmp.mask]
552                    else:
553                        # initial pressure is first
554                        P_init = np.array(tmp[~tmp.mask], dtype=float)
555
556                    if 'press_conv' in self.pem_input:
557                        P_init = P_init*self.pem_input['press_conv']
558
559                    saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
560                                   for ph in phases]
561                    # Get the pressure
562                    self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
563                                        ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
564                                        ensembleMember=self.ensemble_member)
565                    # mask the bulkimp to get proper dimensions
566                    tmp_value = np.zeros(self.ecl_case.init.shape)
567                    tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
568                    self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
569                                                   mask=deepcopy(self.ecl_case.init.mask))
570                    # run filter
571                    self.pem._filter()
572                    vintage.append(deepcopy(self.pem.bulkimp))
573
574            if hasattr(self.pem, 'baseline'):  # 4D measurement
575                base_time = dt.datetime(self.startDate['year'], self.startDate['month'],
576                                        self.startDate['day']) + dt.timedelta(days=self.pem.baseline)
577                # pem_input = {}
578                # get active porosity
579                tmp = self.ecl_case.cell_data('PORO')
580
581                if 'compaction' in self.pem_input:
582                    multfactor = self.ecl_case.cell_data('PORV_RC', base_time)
583
584                    pem_input['PORO'] = np.array(
585                        multfactor[~tmp.mask] * tmp[~tmp.mask], dtype=float)
586                else:
587                    pem_input['PORO'] = np.array(tmp[~tmp.mask], dtype=float)
588
589                pem_input['RS'] = None
590                for var in ['SWAT', 'SGAS', 'PRESSURE', 'RS']:
591                    try:
592                        tmp = self.ecl_case.cell_data(var, base_time)
593                    except:
594                        pass
595                    # only active, and conv. to float
596                    pem_input[var] = np.array(tmp[~tmp.mask], dtype=float)
597
598                if 'press_conv' in self.pem_input:
599                    pem_input['PRESSURE'] = pem_input['PRESSURE'] * \
600                        self.pem_input['press_conv']
601
602                saturations = [1 - (pem_input['SWAT'] + pem_input['SGAS']) if ph == 'OIL' else pem_input['S{}'.format(ph)]
603                               for ph in phases]
604                # Get the pressure
605                self.pem.calc_props(phases, saturations, pem_input['PRESSURE'], pem_input['PORO'],
606                                    ntg=pem_input['NTG'], Rs=pem_input['RS'], press_init=P_init,
607                                    ensembleMember=None)
608
609                # mask the bulkimp to get proper dimensions
610                tmp_value = np.zeros(self.ecl_case.init.shape)
611
612                tmp_value[self.ecl_case.init.actnum] = self.pem.bulkimp
613                # kill if values are inf or nan
614                assert not np.isnan(tmp_value).any()
615                assert not np.isinf(tmp_value).any()
616                self.pem.bulkimp = np.ma.array(data=tmp_value, dtype=float,
617                                               mask=deepcopy(self.ecl_case.init.mask))
618                self.pem._filter()
619
620                # 4D response
621                for i, elem in enumerate(vintage):
622                    self.pem_result.append(elem - deepcopy(self.pem.bulkimp))
623            else:
624                for i, elem in enumerate(vintage):
625                    self.pem_result.append(elem)
626
627            #  Extract k-means centers and interias for each element in pem_result
628            if 'clusters' in self.pem_input:
629                npzfile = np.load(self.pem_input['clusters'], allow_pickle=True)
630                n_clusters_list = npzfile['n_clusters_list']
631                npzfile.close()
632            else:
633                n_clusters_list = len(self.pem_result)*[2]
634            kmeans_kwargs = {"init": "random", "n_init": 10, "max_iter": 300, "random_state": 42}
635            for i, bulkimp in enumerate(self.pem_result):
636                std = np.std(bulkimp)
637                features = np.argwhere(np.squeeze(np.reshape(np.abs(bulkimp), self.ecl_case.init.shape,)) > 3 * std)
638                scaler = StandardScaler()
639                scaled_features = scaler.fit_transform(features)
640                kmeans = KMeans(n_clusters=n_clusters_list[i], **kmeans_kwargs)
641                kmeans.fit(scaled_features)
642                kmeans_center = np.squeeze(scaler.inverse_transform(kmeans.cluster_centers_))  # data / measurements
643                self.bar_result.append(np.append(kmeans_center, kmeans.inertia_))
644
645        return success

Call OPM flow simulator via shell.

Parameters
  • folder (str): Folder with runfiles.
  • wait_for_proc (bool): Boolean determining if we wait for the process to be done or not.
Changelog
  • ST 18/10-18
def extract_data(self, member):
647    def extract_data(self, member):
648        # start by getting the data from the flow simulator
649        super().extract_data(member)
650
651        # get the barycenters and inertias
652        for prim_ind in self.l_prim:
653            # Loop over all keys in pred_data (all data types)
654            for key in self.all_data_types:
655                if key in ['barycenter']:
656                    if self.true_prim[1][prim_ind] in self.pem_input['vintage']:
657                        v = self.pem_input['vintage'].index(self.true_prim[1][prim_ind])
658                        self.pred_data[prim_ind][key] = self.bar_result[v].flatten()