simulator.eclipse

Wrap Eclipse

   1"""Wrap Eclipse"""
   2# External imports
   3import numpy as np
   4import sys
   5import os
   6from copy import deepcopy
   7from mako.lookup import TemplateLookup
   8from mako.runtime import Context
   9from multiprocessing import Process
  10import datetime as dt
  11from scipy import interpolate
  12from subprocess import call, DEVNULL
  13from misc import ecl, grdecl
  14from shutil import rmtree, copytree  # rmtree for removing folders
  15import time
  16# import rips
  17
  18# Internal imports
  19from misc.system_tools.environ_var import EclipseRunEnvironment
  20from pipt.misc_tools.analysis_tools import store_ensemble_sim_information
  21
  22
  23class eclipse:
  24    """
  25    Class for running the Schlumberger eclipse 100 black oil reservoir simulator. For more information see  GeoQuest:
  26    ECLIPSE reference manual 2009.1. Schlumberger, GeoQuest (2009).
  27
  28    To run this class, eclipse must be installed and elcrun must be in the system path!
  29    """
  30
  31    def __init__(self, input_dict=None, filename=None, options=None):
  32        """
  33        The inputs are all optional, but in the same fashion as the other simulators a system must be followed.
  34        The input_dict can be utilized as a single input. Here all nescessary info is stored. Alternatively,
  35        if input_dict is not defined, all the other input variables must be defined.
  36
  37        Parameters
  38        ----------
  39        input_dict : dict, optional
  40            Dictionary containing all information required to run the simulator.
  41
  42                - parallel: number of forward simulations run in parallel
  43                - simoptions: options for the simulations
  44                    - mpi: option to use mpi (always use > 2 cores)
  45                    - sim_path: Path to the simulator
  46                    - sim_flag: Flags sent to the simulator (see simulator documentation for all possibilities)
  47                - sim_limit: maximum number of seconds a simulation can run before being killed
  48                - runfile: name of the simulation input file
  49                - reportpoint: these are the dates the simulator reports results
  50                - reporttype: this key states that the report poins are given as dates
  51                - datatype: the data types the simulator reports
  52                - replace: replace failed simulations with randomly selected successful ones
  53                - rerun: in case of failure, try to rerun the simulator the given number of times
  54                - startdate: simulaton start date
  55                - saveforecast: save the predicted measurements for each iteration
  56
  57        filename : str, optional
  58            Name of the .mako file utilized to generate the ECL input .DATA file. Must be in uppercase for the
  59            ECL simulator.
  60
  61        options : dict, optional
  62            Dictionary with options for the simulator.
  63
  64        Returns
  65        -------
  66        initial_object : object
  67            Initial object from the class ecl_100.
  68        """
  69
  70        # IN
  71        self.input_dict = input_dict
  72        self.file = filename
  73        self.options = options
  74
  75        self.upscale = None
  76        # If input option 1 is selected
  77        if self.input_dict is not None:
  78            self._extInfoInputDict()
  79
  80        # Allocate for internal use
  81        self.inv_stat = None
  82        self.static_state = None
  83
  84    def _extInfoInputDict(self):
  85        """
  86        Extract the manditory and optional information from the input_dict dictionary.
  87
  88        Parameters
  89        ----------
  90        input_dict : dict
  91            Dictionary containing all information required to run the simulator (defined in self).
  92
  93        Returns
  94        -------
  95        filename : str
  96            Name of the .mako file utilized to generate the ECL input .DATA file.
  97        """
  98        # Chech for mandatory keys
  99        assert 'reporttype' in self.input_dict, 'Reporttype is missing, please specify this'
 100        assert 'reportpoint' in self.input_dict, 'Reportpoint is missing, please specify this'
 101
 102        self.true_prim = [self.input_dict['reporttype'], self.input_dict['reportpoint']]
 103
 104        self.true_order = [self.input_dict['reporttype'], self.input_dict['reportpoint']]
 105        self.all_data_types = self.input_dict['datatype']
 106        self.l_prim = [int(i) for i in range(len(self.true_prim[1]))]
 107
 108        # In the ecl framework, all reference to the filename should be uppercase
 109        self.file = self.input_dict['runfile'].upper()
 110        self.options = {}
 111        self.options['sim_path'] = ''
 112        self.options['sim_flag'] = ''
 113        self.options['mpi'] = ''
 114        self.options['parsing-strictness'] = ''
 115        # Loop over options in SIMOPTIONS and extract the parameters we want
 116        if 'simoptions' in self.input_dict:
 117            if type(self.input_dict['simoptions'][0]) == str:
 118                self.input_dict['simoptions'] = [self.input_dict['simoptions']]
 119            for i, opt in enumerate(list(zip(*self.input_dict['simoptions']))[0]):
 120                if opt == 'sim_path':
 121                    self.options['sim_path'] = self.input_dict['simoptions'][i][1]
 122                if opt == 'sim_flag':
 123                    self.options['sim_flag'] = self.input_dict['simoptions'][i][1]
 124                if opt == 'mpi':
 125                    self.options['mpi'] = self.input_dict['simoptions'][i][1]
 126                if opt == 'parsing-strictness':
 127                    self.options['parsing-strictness'] = self.input_dict['simoptions'][i][1]
 128        if 'sim_limit' in self.input_dict:
 129            self.options['sim_limit'] = self.input_dict['sim_limit']
 130
 131        if 'reportdates' in self.input_dict:
 132            self.reportdates = [
 133                x * 30 for x in range(1, int(self.input_dict['reportdates'][1]))]
 134
 135        if 'read_sch' in self.input_dict:
 136            # self.read_sch = self.input_dict['read_sch'][0]
 137            load_file = np.load(self.input_dict['read_sch'], allow_pickle=True)
 138            self.reportdates = load_file[load_file.files[0]]
 139
 140        if 'startdate' in self.input_dict:
 141            self.startDate = {}
 142            # assume date is on form day/month/year
 143            tmpDate = [int(elem) for elem in self.input_dict['startdate'].split('/')]
 144
 145            self.startDate['day'] = tmpDate[0]
 146            self.startDate['month'] = tmpDate[1]
 147            self.startDate['year'] = tmpDate[2]
 148
 149        if 'realizations' in self.input_dict:
 150            self.realizations = self.input_dict['realizations']
 151
 152        if 'trunc_level' in self.input_dict:
 153            self.trunc_level = self.input_dict['trunc_level']
 154
 155        if 'rerun' in self.input_dict:
 156            self.rerun = int(self.input_dict['rerun'])
 157        else:
 158            self.rerun = 0
 159
 160        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
 161        # run a user defined code to do this.
 162        self.saveinfo = None
 163        if 'savesiminfo' in self.input_dict:
 164            # Make sure "ANALYSISDEBUG" gives a list
 165            if isinstance(self.input_dict['savesiminfo'], list):
 166                self.saveinfo = self.input_dict['savesiminfo']
 167            else:
 168                self.saveinfo = [self.input_dict['savesiminfo']]
 169
 170        if 'upscale' in self.input_dict:
 171            self.upscale = {}
 172            for i in range(0, len(self.input_dict['upscale'])):
 173                if self.input_dict['upscale'][i][0] == 'state':
 174                    # Set the parameter we upscale with regards to, must be the same as one free parameter
 175                    self.upscale['state'] = self.input_dict['upscale'][i][1]
 176                    # Set the dimension of the parameterfield to be upscaled (x and y)
 177                    self.upscale['dim'] = self.input_dict['upscale'][i][2]
 178                if self.input_dict['upscale'][i][0] == 'maxtrunc':
 179                    # Set the ratio for the maximum truncation value
 180                    self.upscale['maxtrunc'] = self.input_dict['upscale'][i][1]
 181                if self.input_dict['upscale'][i][0] == 'maxdiff':
 182                    # Set the ratio for the maximum truncation of differences
 183                    self.upscale['maxdiff'] = self.input_dict['upscale'][i][1]
 184                if self.input_dict['upscale'][i][0] == 'wells':
 185                    # Set the list of well indexes, this is a list of lists where each element in the outer list gives
 186                    # a well coordinate (x and y) as elements in the inner list.
 187                    self.upscale['wells'] = []
 188                    for j in range(1, len(self.input_dict['upscale'][i])):
 189                        self.upscale['wells'].append(
 190                            [int(elem) for elem in self.input_dict['upscale'][i][j]])
 191                if self.input_dict['upscale'][i][0] == 'radius':
 192                    # List of radius lengths
 193                    self.upscale['radius'] = [int(elem)
 194                                              for elem in self.input_dict['upscale'][i][1]]
 195                if self.input_dict['upscale'][i][0] == 'us_type':
 196                    self.upscale['us_type'] = self.input_dict['upscale'][i][1]
 197
 198            # Check that we have as many radius elements as wells
 199            if 'radius' in self.upscale:
 200                if len(self.upscale['radius']) != len(self.upscale['wells']):
 201                    sys.exit('ERROR: Missmatch between number of well inputs and number of radius elements. Please check '
 202                             'the input file')
 203            else:
 204                self.upscale['radius'] = []
 205                self.upscale['wells'] = []
 206
 207        # The simulator should run on different levels
 208        if 'multilevel' in self.input_dict:
 209            # extract list of levels
 210            self.multilevel = self.input_dict['multilevel']
 211        else:
 212            # if not initiallize as list with one element
 213            self.multilevel = [False]
 214
 215    def setup_fwd_run(self, **kwargs):
 216        """
 217        Setup the simulator.
 218
 219        Parameters
 220        ----------
 221        assimIndex: int
 222            Gives the index-type (e.g. step,time,etc.) and the index for the
 223            data to be assimilated
 224        trueOrder:
 225            Gives the index-type (e.g. step,time,etc.) and the index of the true data
 226        """
 227        self.__dict__.update(kwargs)  # parse kwargs input into class attributes
 228
 229        if hasattr(self, 'reportdates'):
 230            self.report = {'dates': self.reportdates}
 231        elif 'reportmonths' in self.input_dict:  # for optimization
 232            self.report = {
 233                'days': [30 * i for i in range(1, int(self.input_dict['reportmonths'][1]))]}
 234        else:
 235            assimIndex = [i for i in range(len(self.l_prim))]
 236            trueOrder = self.true_order
 237
 238        self.pred_data = [deepcopy({}) for _ in range(len(assimIndex))]
 239        for ind in self.l_prim:
 240            for key in self.all_data_types:
 241                self.pred_data[ind][key] = np.zeros((1, 1))
 242
 243        if isinstance(trueOrder[1], list):  # Check if true data prim. ind. is a list
 244            self.true_prim = [trueOrder[0], [x for x in trueOrder[1]]]
 245        else:  # Float
 246            self.true_prim = [trueOrder[0], [trueOrder[1]]]
 247        # self.all_data_types = list(pred_data[0].keys())
 248
 249        # Initiallise space to store the number of active cells. This is only for the upscaling option.
 250        if 'upscale' in self.input_dict:
 251            self.num_act = []
 252
 253        # Initialise error summary
 254        self.error_smr = []
 255
 256        # Initiallize run time summary
 257        self.run_time = []
 258
 259        # Check that the .mako file is in the current working directory
 260        if not os.path.isfile('%s.mako' % self.file):
 261            sys.exit(
 262                'ERROR: .mako file is not in the current working directory. This file must be defined')
 263
 264    def run_fwd_sim(self, state, member_i, del_folder=True):
 265        """
 266        Setup and run the ecl_100 forward simulator. All the parameters are defined as attributes, and the name of the
 267        parameters are initialized in setupFwdRun. This method sets up and runs all the individual ensemble members.
 268        This method is based on writing .DATA file for each ensemble member using the mako template, for more info
 269        regarding mako see http://www.makotemplates.org/
 270
 271        Parameters
 272        ----------
 273        state : dict
 274            Dictionary containing the ensemble state.
 275
 276        member_i : int
 277            Index of the ensemble member.
 278
 279        del_folder : bool, optional
 280            Boolean to determine if the ensemble folder should be deleted. Default is False.
 281        """
 282        if hasattr(self, 'level'):
 283            state['level'] = self.level
 284        else:
 285            state['level'] = 0  # default value
 286        os.mkdir('En_' + str(member_i))
 287        folder = 'En_' + str(member_i) + os.sep
 288
 289        state['member'] = member_i
 290        # If the run is upscaled, run the upscaling procedure
 291        if self.upscale is not None:
 292            if hasattr(self, 'level'):  # if this is a multilevel run, we must set the level
 293                self.upscale['maxtrunc'] = self.trunc_level[self.level]
 294            if self.upscale['maxtrunc'] > 0:  # if the truncation level is 0, we do not perform upscaling
 295                self.coarsen(folder, state)
 296            # if the level is 0, and upscaling has been performed earlier this must be
 297            elif hasattr(self, 'coarse'):
 298                # removed
 299                del self.coarse
 300
 301        # start by generating the .DATA file, using the .mako template situated in ../folder
 302        self._runMako(folder, state)
 303        success = False
 304        rerun = self.rerun
 305        while rerun >= 0 and not success:
 306            success = self.call_sim(folder, True)
 307            rerun -= 1
 308        if success:
 309            self.extract_data(member_i)
 310            if del_folder:
 311                if self.saveinfo is not None:  # Try to save information
 312                    store_ensemble_sim_information(self.saveinfo, member_i)
 313                self.remove_folder(member_i)
 314            return self.pred_data
 315        else:
 316            if self.redund_sim is not None:
 317                success = self.redund_sim.call_sim(folder, True)
 318                if success:
 319                    self.extract_data(member_i)
 320                    if del_folder:
 321                        if self.saveinfo is not None:  # Try to save information
 322                            store_ensemble_sim_information(self.saveinfo, member_i)
 323                        self.remove_folder(member_i)
 324                    return self.pred_data
 325                else:
 326                    if del_folder:
 327                        self.remove_folder(member_i)
 328                    return False
 329            else:
 330                if del_folder:
 331                    self.remove_folder(member_i)
 332                return False
 333
 334    def remove_folder(self, member):
 335        folder = 'En_' + str(member) + os.sep
 336        try:
 337            rmtree(folder)  # Try to delete folder
 338        except:  # If deleting fails, just rename to 'En_<ensembleMember>_date'
 339            os.rename(folder, folder + '_' +
 340                      dt.datetime.now().strftime("%m-%d-%Y_%H-%M-%S"))
 341
 342    def extract_data(self, member):
 343        # get the formated data
 344        for prim_ind in self.l_prim:
 345            # Loop over all keys in pred_data (all data types)
 346            for key in self.all_data_types:
 347                if self.pred_data[prim_ind][key] is not None:  # Obs. data at assim. step
 348                    true_data_info = [self.true_prim[0], self.true_prim[1][prim_ind]]
 349                    try:
 350                        data_array = self.get_sim_results(key, true_data_info, member)
 351                        self.pred_data[prim_ind][key] = data_array
 352                    except:
 353                        pass
 354
 355    def coarsen(self, folder, ensembleMember=None):
 356        """
 357        This method utilized one field parameter to upscale the computational grid. A coarsening file is written to the
 358        ensemble folder, and the eclipse calculates the upscaled permeabilities, porosities and transmissibilities
 359        based on the new grid and the original parameter values.
 360
 361        Parameters
 362        ----------
 363        Folder : str, optional
 364            Path to the ecl_100 run folder.
 365
 366        ensembleMember : int, optional
 367            Index of the ensemble member to run.
 368
 369        Changelog
 370        ---------
 371        - KF 17/9-2015 Added uniform upscaling as an option
 372        - KF 6/01-17
 373        """
 374
 375        # Get the parameter values for the current ensemble member. Note that we select only one parameter
 376        if ensembleMember is not None:
 377            coarsenParam = self.inv_state[self.upscale['state']][:, ensembleMember]
 378        else:
 379            coarsenParam = self.inv_state[self.upscale['state']]
 380
 381        if self.upscale['us_type'].lower() == 'haar':  # the upscaling is based on a Haar wavelet
 382            # Do not add any dead-cells
 383            # TODO: Make a better method for determining dead or inactive cells.
 384
 385            orig_wght = np.ones((self.upscale['dim'][0], self.upscale['dim'][1]))
 386            # Get the new cell structure by a 2-D unbalanced Haar transform.
 387            wght = self._Haar(coarsenParam.reshape(
 388                (self.upscale['dim'][0], self.upscale['dim'][1])), orig_wght)
 389
 390        elif self.upscale['us_type'].lower() == 'unif':
 391            # This option generates a grid where the cells are upscaled uniformly in a dyadic manner. We utilize the
 392            # same framework to define the parameters to be coarsened as in the Haar case. Hence, we only need to
 393            # calculate the wght variable. The level of upscaling can be determined by a fraction, however, for this
 394            # case the fraction gives the number of upscaling levels, 1 is all possible levels, 0 is no upscaling.
 395            wght = self._unif((self.upscale['dim'][0], self.upscale['dim'][1]))
 396
 397        self.write_coarse(folder, wght, coarsenParam.reshape(
 398            (int(self.upscale['dim'][0]), int(self.upscale['dim'][1]))))
 399
 400    def write_coarse(self, folder, whgt, image):
 401        """
 402        This function writes the include file coarsen to the ecl run. This file tels ECL to coarsen the grid.
 403
 404        Parameters
 405        ----------
 406        folder : str
 407            Path to the ecl_100 run.
 408
 409        whgt : float
 410            Weight of the transformed cells.
 411
 412        image : array-like
 413            Original image.
 414
 415        Changelog
 416        ---------
 417        - KF 17/9-2015
 418        """
 419
 420        well = self.upscale['wells']
 421        radius = self.upscale['radius']
 422
 423        well_cells = self._nodeIndex(image.shape[1], image.shape[0], well, radius)
 424        coarse = np.array([[True] * image.shape[1]] * image.shape[0])
 425        tmp = coarse.flatten()
 426        tmp[well_cells] = False
 427        coarse = tmp.reshape(image.shape)
 428
 429        # f = open(folder + 'coarsen.dat', 'w+')
 430        # f.write('COARSEN \n')
 431        ecl_coarse = []
 432
 433        for level in range(len(whgt) - 1, -1, -1):
 434            weight_at_level = whgt[level]
 435            x_dim = weight_at_level.shape[0]
 436            y_dim = weight_at_level.shape[1]
 437
 438            merged_at_level = weight_at_level[int(x_dim / 2):, :int(y_dim / 2)]
 439
 440            level_dim = 2 ** (level + 1)
 441
 442            for i in range(0, merged_at_level.shape[0]):
 443                for j in range(0, merged_at_level.shape[1]):
 444                    if merged_at_level[i, j] == 1:
 445                        # Do this in two stages to avoid errors with the dimensions
 446                        #
 447                        # only merging square cells
 448                        if (i + 1) * level_dim <= image.shape[0] and (j + 1) * level_dim <= image.shape[1]:
 449                            if coarse[i * level_dim:(i + 1) * level_dim, j * level_dim:(j + 1) * level_dim].all():
 450                                coarse[i * level_dim:(i + 1) * level_dim,
 451                                       j * level_dim:(j + 1) * level_dim] = False
 452                                ecl_coarse.append([j * level_dim + 1, (j + 1) * level_dim,
 453                                                   i * level_dim + 1, (i + 1) * level_dim, 1, 1, 1, 1, 1])
 454                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, (j + 1) * level_dim,
 455                                #                                         i * level_dim + 1, (i + 1) * level_dim))
 456                        # cells at first edge, non-square
 457                        if (i + 1)*level_dim > image.shape[0] and (j + 1) * level_dim <= image.shape[1]:
 458                            if coarse[i * level_dim::, j * level_dim:(j + 1) * level_dim].all():
 459                                coarse[i * level_dim::, j *
 460                                       level_dim:(j + 1) * level_dim] = False
 461                                ecl_coarse.append([j * level_dim + 1, (j + 1) * level_dim,
 462                                                   i * level_dim + 1, image.shape[0], 1, 1, 1, 1, 1])
 463                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, (j + 1) * level_dim,
 464                                #                                         i * level_dim + 1, image.shape[0]))
 465                        # cells at second edge, non-square
 466                        if (j + 1)*level_dim > image.shape[1] and (i + 1) * level_dim <= image.shape[0]:
 467                            if coarse[i * level_dim:(i + 1) * level_dim, j * level_dim::].all():
 468                                coarse[i * level_dim:(i + 1) * level_dim,
 469                                       j * level_dim::] = False
 470                                ecl_coarse.append([j * level_dim + 1, image.shape[1],
 471                                                   i * level_dim + 1, (i + 1) * level_dim, 1, 1, 1, 1, 1])
 472                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, image.shape[1],
 473                                #                                         i * level_dim + 1, (i + 1) * level_dim))
 474                        # cells at intersection between first and second edge, non-square
 475                        if (i + 1) * level_dim > image.shape[0] and (j + 1) * level_dim > image.shape[1]:
 476                            if coarse[i * level_dim::, j * level_dim::].all():
 477                                coarse[i * level_dim::, j * level_dim::] = False
 478                                ecl_coarse.append([j * level_dim + 1, image.shape[1],
 479                                                   i * level_dim + 1, image.shape[0], 1, 1, 1, 1, 1])
 480                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, image.shape[1],
 481                                #                                         i * level_dim + 1, image.shape[0]))
 482        # f.write('/')
 483        # f.close()
 484        self.coarse = ecl_coarse
 485
 486    def _nodeIndex(self, x_dir, y_dir, cells, listRadius):
 487        # Find the node index for the cells, and the cells in a radius around.
 488        index = []
 489        for k in range(0, len(cells)):
 490            cell_x = cells[k][0] - 1  # Remove one to make python equivalent to ecl format
 491            cell_y = cells[k][1] - 1  # Remove one to make python equivalent to ecl format
 492            radius = listRadius[k]
 493            # tmp_index = [cell_x + cell_y*x_dir]
 494            tmp_index = []
 495            for i in range(-radius, radius + 1):
 496                for j in range(-radius, radius + 1):
 497                    x = cell_x + i
 498                    y = cell_y + j
 499                    if (0 <= x < x_dir and 0 <= y < y_dir) and (np.sqrt(i ** 2 + j ** 2) <= radius) \
 500                            and (x + y * x_dir < y_dir * x_dir):
 501                        tmp_index.append(x + y * x_dir)
 502            index.extend(tmp_index)
 503
 504        return index
 505
 506    def _Haar(self, image, weights):
 507        """
 508        The 2D unconditional Haar transform.
 509
 510        Parameters
 511        ----------
 512        image : array-like
 513            The original image where the Haar-transform is performed. This could be the permeability or the porosity field.
 514
 515        orig_weights : array-like
 516            The original weights of the image. If some cells are inactive or dead, their weight can be set to zero.
 517
 518        Returns
 519        -------
 520        tot_weights : array-like
 521            A vector/matrix of the same size as the image, with the weights of the new cells. This is utilized for writing
 522            the coarsening file but is not sufficient to recreate the image.
 523        """
 524        # Todo: make an option which stores the transformed field.
 525
 526        # Define the two truncation levels as a fraction of the larges value in the original image. The fraction is defined
 527        # through the inputs.
 528        max_value = self.upscale['maxtrunc'] * np.max(image)
 529        max_diff = self.upscale['maxdiff'] * (np.max(image) - np.min(image))
 530
 531        tot_transf = []
 532        tot_weight = []
 533        tot_alpha = []
 534        count = 0
 535        allow_merge = np.ones(image.shape)
 536        while image.shape[0] > 1:
 537            level_alpha = []
 538            if isinstance(image.shape, tuple):
 539                # the image has two axes.
 540                # Perform the transformation for all the coulmns, and then for the transpose of the results
 541
 542                for axis in range(0, 2):
 543                    # Initialise the matrix for storing the details and smoothing
 544                    level_diff_columns = np.empty(
 545                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 546                    # level_diff_rows = np.empty((len(image[:, 0]), int(np.ceil(len(image[0, :])/2))))
 547                    level_smooth_columns = np.empty(
 548                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 549                    level_weight_columns = np.empty(
 550                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 551                    level_alpha_columns = np.empty(
 552                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 553
 554                    for i in range(0, image.shape[1]):
 555                        if len(image[:, i]) % 2 == 0:
 556                            diff = image[1::2, i] - image[::2, i]
 557                            new_weights = weights[::2, i] + weights[1::2, i]
 558                            alpha = (weights[1::2, i] / new_weights)
 559                            smooth = image[::2, i] + alpha * diff
 560
 561                        else:
 562                            tmp_img = image[:-1, i]
 563                            diff = tmp_img[1::2] - tmp_img[::2]
 564
 565                            tmp_weight = weights[:-1, i]
 566                            new_weights = tmp_weight[::2] + tmp_weight[1::2]
 567                            new_weights = np.append(new_weights, weights[-1, i])
 568
 569                            tmp_img = image[:-1, i]
 570                            alpha = (weights[1::2, i] / new_weights[:-1])
 571                            smooth = tmp_img[::2] + alpha * diff
 572                            smooth = np.append(smooth, image[-1, i])
 573                            alpha = np.append(alpha, 0)
 574                            diff = np.append(diff, 0)
 575
 576                        level_diff_columns[:, i] = diff
 577                        level_weight_columns[:, i] = new_weights
 578                        level_smooth_columns[:, i] = smooth
 579                        level_alpha_columns[:, i] = alpha
 580
 581                    # image = level_smooth_columns.T
 582                    image = np.vstack((level_smooth_columns, level_diff_columns)).T
 583                    weights = np.vstack((level_weight_columns, level_weight_columns)).T
 584                    level_alpha.append(level_alpha_columns)
 585
 586                image, weights, allow_merge, end_ctrl = self._haarTrunc(image, weights, max_value, max_diff,
 587                                                                        allow_merge)
 588
 589                tot_transf.append(image)
 590                tot_weight.append(weights)
 591                tot_alpha.append(level_alpha)
 592
 593                if end_ctrl:
 594                    break
 595                image = image[:image.shape[0] / 2, :image.shape[1] / 2]
 596                weights = weights[:weights.shape[0] / 2, :weights.shape[1] / 2]
 597
 598            else:
 599                if len(image) % 2 == 0:
 600                    diff = image[1::2] - image[::2]
 601                    new_weights = weights[::2] + weights[1::2]
 602                    smooth = image[::2] + (weights[1::2] / new_weights) * diff
 603
 604                else:
 605                    tmp_img = image[:-1]
 606                    diff = tmp_img[1::2] - tmp_img[::2]
 607
 608                    tmp_weight = weights[:-1]
 609                    new_weights = tmp_weight[::2] + tmp_weight[1::2]
 610                    new_weights = np.append(new_weights, weights[-1])
 611
 612                    tmp_img = image[:-1]
 613                    smooth = tmp_img[::2] + (weights[1::2] / new_weights[:-1]) * diff
 614                    smooth = np.append(smooth, image[-1])
 615
 616                tot_transf.append(smooth)
 617                tot_weight.append(new_weights)
 618
 619            # weights = new_weights
 620            # image = smooth
 621            count += 1
 622
 623        return tot_weight
 624
 625    def _unif(self, dim):
 626        # calculate the uniform upscaling steps
 627
 628        min_dim = min(dim)  # find the lowest dimension
 629        max_levels = 0
 630        while min_dim/2 >= 1:
 631            min_dim = min_dim/2
 632            max_levels += 1
 633
 634        # conservative choice
 635        trunc_level = int(np.floor(max_levels*self.upscale['maxtrunc']))
 636
 637        # all the initial cells are upscaled
 638        wght = [np.ones(tuple([int(elem) for elem in dim]))]
 639
 640        for i in range(trunc_level):
 641            new_dim = [int(np.ceil(elem/2))
 642                       for elem in wght[i].shape]  # always take a larger dimension
 643            wght.append(np.ones(tuple(new_dim)))
 644
 645        return wght
 646
 647    def _haarTrunc(self, image, weights, max_val, max_diff, merge):
 648        """
 649        Function for truncating the wavelets. Based on the max_val and max_diff values this function set the detail
 650        coaffiecient to zero if the value of this coefficient is below max_diff, and the value of the smooth coefficient
 651        is below max_val.
 652
 653        Parameters
 654        ----------
 655        image : array-like
 656            The transformed image.
 657
 658        weights : array-like
 659            The weights of the transformed image.
 660
 661        max_val : float
 662            Smooth values above this value are not allowed.
 663
 664        max_diff : float
 665            Detail coefficients higher than this value are not truncated.
 666
 667        merge : array-like
 668            Matrix/vector of booleans defining whether a merge is allowed.
 669
 670        Returns
 671        -------
 672        image : array-like
 673            Transformed image with truncated coefficients.
 674
 675        weights : array-like
 676            Updated weights.
 677
 678        allow_merge : array-like
 679            Booleans keeping track of allowed merges.
 680
 681        end_ctrl : bool
 682            Boolean to control whether further upscaling is possible.
 683        """
 684        # the image will always contain even elements in x and y dir
 685        x_dir = int(image.shape[0] / 2)
 686        y_dir = int(image.shape[1] / 2)
 687
 688        smooth = image[:x_dir, :y_dir]
 689        smooth_diff = image[x_dir:, :y_dir]
 690        diff_smooth = image[:x_dir, y_dir:]
 691        diff_diff = image[x_dir:, y_dir:]
 692
 693        weights_diff = weights[x_dir:, :y_dir]
 694
 695        allow_merge = np.array(
 696            [[False] * y_dir] * x_dir)
 697
 698        # ensure that last row does not be left behind during non-dyadic upscaling
 699        if image.shape[0] > merge.shape[0]:
 700            merge = np.insert(merge, -1, merge[-1, :], axis=0)
 701        if image.shape[1] > merge.shape[1]:
 702            merge = np.insert(merge, -1, merge[:, -1], axis=1)
 703
 704        cand1 = zip(*np.where(smooth < max_val))
 705
 706        end_ctrl = True
 707
 708        for elem in cand1:
 709            # If the method wants to merge cells outside the box the if sentence gives an error, make an exception for
 710            # this hence do not merge these cells.
 711            try:
 712                if abs(smooth_diff[elem]) <= max_diff and abs(diff_smooth[elem]) <= max_diff and \
 713                        merge[elem[0] * 2, elem[1] * 2] and merge[elem[0] * 2 + 1, elem[1] * 2] and \
 714                        merge[elem[0] * 2 + 1, elem[1] * 2 + 1] and merge[elem[0] * 2, elem[1] * 2 + 1]:
 715                    diff_smooth[elem] = 0
 716                    smooth_diff[elem] = 0
 717                    diff_diff[elem] = 0
 718                    weights_diff[elem] = True
 719                    allow_merge[elem] = True
 720                    end_ctrl = False
 721            except:
 722                pass
 723        # for elem in cand2:
 724        #     if smooth[elem] <= max_val:
 725        #         diff_smooth[elem] = 0
 726        #         smooth_diff[elem] = 0
 727        #         diff_diff[elem] = 0
 728
 729        image[x_dir:, :y_dir] = smooth_diff
 730        image[:x_dir, y_dir:] = diff_smooth
 731        image[x_dir:, y_dir:] = diff_diff
 732
 733        weights[x_dir:, :y_dir] = weights_diff
 734
 735        return image, weights, allow_merge, end_ctrl
 736
 737    def _runMako(self, folder, state):
 738        """
 739        Read the mako template (.mako) file from ../folder, and render the correct data file (.DATA) in folder.
 740
 741        Parameters
 742        ----------
 743        Folder : str, optional
 744            Folder for the ecl_100 run.
 745
 746        ensembleMember : int, optional
 747            Index of the ensemble member to run.
 748
 749        Changelog
 750        ---------
 751        - KF 14/9-2015
 752        """
 753
 754        # Check and add report time
 755        if hasattr(self, 'report'):
 756            for key in self.report:
 757                state[key] = self.report[key]
 758
 759        # Convert drilling order (float) to drilling queue (integer) - drilling order optimization
 760        # if "drillingorder" in en_dict:
 761        #     dorder = en_dict['drillingorder']
 762        #     en_dict['drillingqueue'] = np.argsort(dorder)[::-1][:len(dorder)]
 763
 764        # Add startdate
 765        if hasattr(self, 'startDate'):
 766            state['startdate'] = dt.datetime(
 767                self.startDate['year'], self.startDate['month'], self.startDate['day'])
 768
 769        # Add the coarsing values
 770        if hasattr(self, 'coarse'):
 771            state['coarse'] = self.coarse
 772
 773        # Look for the mako file
 774        lkup = TemplateLookup(directories=os.getcwd(),
 775                              input_encoding='utf-8')
 776
 777        # Get template
 778        # If we need the E300 run, define a E300 data file with _E300 added to the end.
 779        if hasattr(self, 'E300'):
 780            if self.E300:
 781                tmpl = lkup.get_template('%s.mako' % (self.file + '_E300'))
 782            else:
 783                tmpl = lkup.get_template('%s.mako' % self.file)
 784        else:
 785            tmpl = lkup.get_template('%s.mako' % self.file)
 786
 787        # use a context and render onto a file
 788        with open('{0}{1}'.format(folder + self.file, '.DATA'), 'w') as f:
 789            ctx = Context(f, **state)
 790            tmpl.render_context(ctx)
 791
 792    def get_sim_results(self, whichResponse, ext_data_info=None, member=None):
 793        """
 794        Read the output from simulator and store as an array. Optionally, if the DA method is based on an ensemble
 795        method, the output must be read inside a folder.
 796
 797        Parameters
 798        ----------
 799        file_rsm : str
 800            Summary file from ECL 100.
 801
 802        file_rst : str
 803            Restart file from ECL 100.
 804
 805        whichResponse : str
 806            Which of the responses is to be outputted (e.g., WBHP PRO-1, WOPR, PRESS, OILSAT, etc).
 807
 808        member : int, optional
 809            Ensemble member that is finished.
 810
 811        ext_data_info : tuple, optional
 812            Tuple containing the assimilation step information, including the place of assimilation (e.g., which TIME) and the
 813            index of this assimilation place.
 814
 815        Returns
 816        -------
 817        yFlow : array-like
 818            Array containing the response from ECL 100. The response type is chosen by the user in options['data_type'].
 819
 820        Notes
 821        -----
 822        - Modified the ecl package to allow reading the summary data directly, hence, we get cell, summary, and field data
 823        from the ecl package.
 824        - KF 29/10-2015
 825        - Modified the ecl package to read RFT files. To obtain, e.g,. RFT pressures form well 'PRO-1" whichResponse
 826        must be rft_PRESSURE PRO-1
 827        """
 828        # Check that we have no trailing spaces
 829        whichResponse = whichResponse.strip()
 830
 831        # if ensemble DA method
 832        if member is not None:
 833            # Get results
 834            if hasattr(self, 'ecl_case'):
 835                # En_XX/YYYY.DATA is the folder setup
 836                rt_mem = int(self.ecl_case.root.split('/')[0].split('_')[1])
 837                if rt_mem != member:  # wrong case
 838                    self.ecl_case = ecl.EclipseCase('En_' + str(member) + os.sep + self.file + '.DATA')
 839            else:
 840                self.ecl_case = ecl.EclipseCase('En_' + str(member) + os.sep + self.file + '.DATA')
 841            if ext_data_info[0] == 'days':
 842                time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
 843                    dt.timedelta(days=ext_data_info[1])
 844                dates = self.ecl_case.by_date
 845                if time not in dates and 'date_slack' in self.input_dict:
 846                    slack = int(self.input_dict['date_slack'])
 847                    if slack > 0:
 848                        v = [el for el in dates if np.abs(
 849                            (el-time).total_seconds()) < slack]
 850                        if len(v) > 0:
 851                            time = v[0]
 852            else:
 853                time = ext_data_info[1]
 854
 855            # Check if the data is a field or well data, by checking if the well is defined
 856            if len(whichResponse.split(' ')) == 2:
 857                # if rft, search for rft_
 858                if 'rft_' in whichResponse:
 859                    # to speed up when performing the prediction step
 860                    if hasattr(self, 'rft_case'):
 861                        rt_mem = int(self.rft_case.root.split('/')[0].split('_')[1])
 862                        if rt_mem != member:
 863                            self.rft_case = ecl.EclipseRFT('En_' + str(member) + os.sep + self.file)
 864                    else:
 865                        self.rft_case = ecl.EclipseRFT(
 866                            'En_' + str(member) + os.sep + self.file)
 867                    # Get the data. Due to formating we can slice the property.
 868                    rft_prop = self.rft_case.rft_data(well=whichResponse.split(
 869                        ' ')[1], prop=whichResponse.split(' ')[0][4:])
 870                    # rft_data are collected for open connections. This may vary throughout the simulation, hence we
 871                    # must also collect the depth for the rft_data to check if all data is present
 872                    rft_depth = self.rft_case.rft_data(
 873                        well=whichResponse.split(' ')[1], prop='DEPTH')
 874                    # to check this we import the referance depth if this is available. If not we assume that the data
 875                    # is ok.
 876                    try:
 877                        ref_depth_f = np.load(whichResponse.split(
 878                            ' ')[1].upper() + '_rft_ref_depth.npz')
 879                        ref_depth = ref_depth_f[ref_depth_f.files[0]]
 880                        yFlow = np.array([])
 881                        interp = interpolate.interp1d(rft_depth, rft_prop, kind='linear', bounds_error=False,
 882                                                      fill_value=(rft_prop[0], rft_prop[-1]))
 883                        for d in ref_depth:
 884                            yFlow = np.append(yFlow, interp(d))
 885                    except:
 886                        yFlow = rft_prop
 887                else:
 888                    # If well, read the rsm file
 889                    if ext_data_info is not None:  # Get the data at a specific well and time
 890                        yFlow = self.ecl_case.summary_data(whichResponse, time)
 891            elif len(whichResponse.split(' ')) == 1:  # field data
 892                if whichResponse.upper() in ['FOPT', 'FWPT', 'FGPT', 'FWIT', 'FGIT']:
 893                    if ext_data_info is not None:
 894                        yFlow = self.ecl_case.summary_data(whichResponse, time)
 895                elif whichResponse.upper() in ['PERMX', 'PERMY', 'PERMZ', 'PORO', 'NTG', 'SATNUM',
 896                                               'MULTNUM', 'OPERNUM']:
 897                    yFlow = np.array([self.ecl_case.cell_data(whichResponse).flatten()[time]]) # assume that time is the index
 898                else:
 899                    yFlow = self.ecl_case.cell_data(whichResponse, time).flatten()
 900                    if yFlow is None:
 901                        yFlow = self.ecl_case.cell_data(whichResponse).flatten()
 902
 903            # store the run time. NB: elapsed must be defined in .DATA file for this to work
 904            if 'save_elapsed' in self.input_dict and len(self.run_time) <= member:
 905                self.run_time.extend(self.ecl_case.summary_data('ELAPSED', time))
 906
 907            # If we have performed coarsening, we store the number of active grid-cells
 908            if self.upscale is not None:
 909                # Get this number from INIT file
 910                with ecl.EclipseFile('En_' + str(member) + os.sep + self.file, 'INIT') as case:
 911                    intHead = case.get('INTEHEAD')
 912                # The active cell is element 12 of this vector, index 11 in python indexing...
 913                active_cells = intHead[11]
 914                if len(self.num_act) <= member:
 915                    self.num_act.extend([active_cells])
 916
 917        else:
 918            case = ecl.EclipseCase(self.file + '.DATA')
 919            if ext_data_info[0] == 'days':
 920                time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
 921                    dt.timedelta(days=ext_data_info[1])
 922            else:
 923                time = ext_data_info[1]
 924
 925            # Check if the data is a field or well data, by checking if the well is defined
 926            if len(whichResponse.split(' ')) == 2:
 927                # if rft, search for rft_
 928                if 'rft_' in whichResponse:
 929                    rft_case = ecl.EclipseRFT(self.file)
 930                    # Get the data. Due to formating we can slice the property.
 931                    rft_prop = rft_case.rft_data(well=whichResponse.split(
 932                        ' ')[1], prop=whichResponse.split(' ')[0][4:])
 933                    # rft_data are collected for open connections. This may vary throughout the simulation, hence we
 934                    # must also collect the depth for the rft_data to check if all data is present
 935                    rft_depth = rft_case.rft_data(
 936                        well=whichResponse.split(' ')[1], prop='DEPTH')
 937                    try:
 938                        ref_depth_f = np.load(whichResponse.split(
 939                            ' ')[1].upper() + '_rft_ref_depth.npz')
 940                        ref_depth = ref_depth_f[ref_depth_f.files[0]]
 941                        yFlow = np.array([])
 942                        interp = interpolate.interp1d(rft_depth, rft_prop, kind='linear', bounds_error=False,
 943                                                      fill_value=(rft_prop[0], rft_prop[-1]))
 944                        for d in ref_depth:
 945                            yFlow = np.append(yFlow, interp(d))
 946                    except:
 947                        yFlow = rft_prop
 948                else:
 949                    # If well, read the rsm file
 950                    if ext_data_info is not None:  # Get the data at a specific well and time
 951                        yFlow = case.summary_data(whichResponse, time)
 952            elif len(whichResponse.split(' ')) == 1:
 953                if whichResponse in ['FOPT', 'FWPT', 'FGPT', 'FWIT', 'FGIT']:
 954                    if ext_data_info is not None:
 955                        yFlow = case.summary_data(whichResponse, time)
 956                else:
 957                    yFlow = case.cell_data(whichResponse, time).flatten()
 958                    if yFlow is None:
 959                        yFlow = case.cell_data(whichResponse).flatten()
 960
 961            # If we have performed coarsening, we store the number of active grid-cells
 962            if self.upscale is not None:
 963                # Get this number from INIT file
 964                with ecl.EclipseFile('En_' + str(member) + os.sep + self.file,'INIT') as case:
 965                    intHead = case.get('INTEHEAD')
 966                # The active cell is element 12 of this vector, index 11 in python indexing...
 967                active_cells = intHead[11]
 968                if len(self.num_act) <= member:
 969                    self.num_act.extend([active_cells])
 970
 971        return yFlow
 972
 973    def store_fwd_debug(self, assimstep):
 974        if 'fwddebug' in self.keys_fwd:
 975            # Init dict. of variables to save
 976            save_dict = {}
 977
 978            # Make sure "ANALYSISDEBUG" gives a list
 979            if isinstance(self.keys_fwd['fwddebug'], list):
 980                debug = self.keys_fwd['fwddebug']
 981            else:
 982                debug = [self.keys_fwd['fwddebug']]
 983
 984            # Loop over variables to store in save list
 985            for var in debug:
 986                # Save with key equal variable name and the actual variable
 987                if isinstance(eval('self.' + var), dict):
 988                    # save directly
 989                    np.savez('fwd_debug_%s_%i' % (var, assimstep), **eval('self.' + var))
 990                else:
 991                    save_dict[var] = eval('self.' + var)
 992
 993            np.savez('fwd_debug_%i' % (assimstep), **save_dict)
 994
 995    def write_to_grid(self, value, propname, path, dim, t_ind=None):
 996        if t_ind == None:
 997            trans_dict = {}
 998
 999            def _lookup(kw):
1000                return trans_dict[kw] if kw in trans_dict else kw
1001
1002            # Write a quantity to the grid as a grdecl file
1003            with open(path + propname + '.grdecl', 'wb') as fileobj:
1004                grdecl._write_kw(fileobj, propname, value, _lookup, dim)
1005        else:
1006            pass
1007            # some errors with rips
1008            # p = Process(target=_write_to_resinsight, args=(list(value[~value.mask]),propname, t_ind))
1009            # Find an open resinsight case
1010            # p.start()
1011            # time.sleep(1)
1012            # p.terminate()
1013
1014# def _write_to_resinsight(value, name,t_ind):
1015#     resinsight = rips.Instance.find()
1016#     case = resinsight.project.case(case_id=0)
1017#     case.set_active_cell_property(value, 'GENERATED', name,t_ind)
1018
1019
1020class ecl_100(eclipse):
1021    '''
1022    ecl_100 class
1023    '''
1024
1025    def call_sim(self, path=None, wait_for_proc=False):
1026        """
1027        Method for calling the ecl_100 simulator.
1028
1029        Parameters
1030        ----------
1031        Path : str
1032            Alternative folder for the ecl_100.data file.
1033
1034        Wait_for_proc : bool, optional
1035            Logical variable to wait for the simulator to finish. Default is False.
1036
1037        Returns
1038        -------
1039        .RSM : str
1040            Run summary file in the standard ECL format. Well data are collected from this file.
1041
1042        .RST : str
1043            Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
1044
1045        .PRT : str
1046            Info file to be used for checking errors in the run.
1047
1048
1049        Changelog
1050        ---------
1051        - KF 14/9-2015
1052        """
1053
1054        # Filename
1055        if path is not None:
1056            filename = path + self.file
1057        else:
1058            filename = self.file
1059
1060        # Run the simulator:
1061        success = True
1062        try:
1063            with EclipseRunEnvironment(filename):
1064                com = ['eclrun', '--nocleanup', 'eclipse', filename + '.DATA']
1065                if 'sim_limit' in self.options:
1066                    call(com, stdout=DEVNULL, timeout=self.options['sim_limit'])
1067                else:
1068                    call(com, stdout=DEVNULL)
1069                raise ValueError
1070        except:
1071            print('\nError in the eclipse run.')  # add rerun?
1072            if not os.path.exists('Crashdump'):
1073                copytree(path, 'Crashdump')
1074            success = False
1075
1076        return success
1077
1078
1079class ecl_300(eclipse):
1080    '''
1081    eclipse 300 class
1082    '''
1083
1084    def call_sim(self, path=None, wait_for_proc=False):
1085        """
1086        Method for calling the ecl_300 simulator.
1087
1088        Parameters
1089        ----------
1090        Path : str
1091            Alternative folder for the ecl_100.data file.
1092
1093        Wait_for_proc : bool, optional
1094            Logical variable to wait for the simulator to finish. Default is False.
1095
1096        NOTE: For now, this option is only utilized in a single localization option.
1097
1098        Returns
1099        -------
1100        RSM : str
1101            Run summary file in the standard ECL format. Well data are collected from this file.
1102
1103        RST : str
1104            Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
1105
1106        PRT : str
1107            Info file to be used for checking errors in the run.
1108
1109        Changelog
1110        ---------
1111        - KF 8/10-2015
1112
1113
1114        """
1115        # Filename
1116        if path is not None:
1117            filename = path + self.file
1118        else:
1119            filename = self.file
1120
1121        # Run the simulator:
1122        with EclipseRunEnvironment(filename):
1123            call(['eclrun', '--nocleanup', 'e300', filename + '.DATA'], stdout=DEVNULL)
class eclipse:
  24class eclipse:
  25    """
  26    Class for running the Schlumberger eclipse 100 black oil reservoir simulator. For more information see  GeoQuest:
  27    ECLIPSE reference manual 2009.1. Schlumberger, GeoQuest (2009).
  28
  29    To run this class, eclipse must be installed and elcrun must be in the system path!
  30    """
  31
  32    def __init__(self, input_dict=None, filename=None, options=None):
  33        """
  34        The inputs are all optional, but in the same fashion as the other simulators a system must be followed.
  35        The input_dict can be utilized as a single input. Here all nescessary info is stored. Alternatively,
  36        if input_dict is not defined, all the other input variables must be defined.
  37
  38        Parameters
  39        ----------
  40        input_dict : dict, optional
  41            Dictionary containing all information required to run the simulator.
  42
  43                - parallel: number of forward simulations run in parallel
  44                - simoptions: options for the simulations
  45                    - mpi: option to use mpi (always use > 2 cores)
  46                    - sim_path: Path to the simulator
  47                    - sim_flag: Flags sent to the simulator (see simulator documentation for all possibilities)
  48                - sim_limit: maximum number of seconds a simulation can run before being killed
  49                - runfile: name of the simulation input file
  50                - reportpoint: these are the dates the simulator reports results
  51                - reporttype: this key states that the report poins are given as dates
  52                - datatype: the data types the simulator reports
  53                - replace: replace failed simulations with randomly selected successful ones
  54                - rerun: in case of failure, try to rerun the simulator the given number of times
  55                - startdate: simulaton start date
  56                - saveforecast: save the predicted measurements for each iteration
  57
  58        filename : str, optional
  59            Name of the .mako file utilized to generate the ECL input .DATA file. Must be in uppercase for the
  60            ECL simulator.
  61
  62        options : dict, optional
  63            Dictionary with options for the simulator.
  64
  65        Returns
  66        -------
  67        initial_object : object
  68            Initial object from the class ecl_100.
  69        """
  70
  71        # IN
  72        self.input_dict = input_dict
  73        self.file = filename
  74        self.options = options
  75
  76        self.upscale = None
  77        # If input option 1 is selected
  78        if self.input_dict is not None:
  79            self._extInfoInputDict()
  80
  81        # Allocate for internal use
  82        self.inv_stat = None
  83        self.static_state = None
  84
  85    def _extInfoInputDict(self):
  86        """
  87        Extract the manditory and optional information from the input_dict dictionary.
  88
  89        Parameters
  90        ----------
  91        input_dict : dict
  92            Dictionary containing all information required to run the simulator (defined in self).
  93
  94        Returns
  95        -------
  96        filename : str
  97            Name of the .mako file utilized to generate the ECL input .DATA file.
  98        """
  99        # Chech for mandatory keys
 100        assert 'reporttype' in self.input_dict, 'Reporttype is missing, please specify this'
 101        assert 'reportpoint' in self.input_dict, 'Reportpoint is missing, please specify this'
 102
 103        self.true_prim = [self.input_dict['reporttype'], self.input_dict['reportpoint']]
 104
 105        self.true_order = [self.input_dict['reporttype'], self.input_dict['reportpoint']]
 106        self.all_data_types = self.input_dict['datatype']
 107        self.l_prim = [int(i) for i in range(len(self.true_prim[1]))]
 108
 109        # In the ecl framework, all reference to the filename should be uppercase
 110        self.file = self.input_dict['runfile'].upper()
 111        self.options = {}
 112        self.options['sim_path'] = ''
 113        self.options['sim_flag'] = ''
 114        self.options['mpi'] = ''
 115        self.options['parsing-strictness'] = ''
 116        # Loop over options in SIMOPTIONS and extract the parameters we want
 117        if 'simoptions' in self.input_dict:
 118            if type(self.input_dict['simoptions'][0]) == str:
 119                self.input_dict['simoptions'] = [self.input_dict['simoptions']]
 120            for i, opt in enumerate(list(zip(*self.input_dict['simoptions']))[0]):
 121                if opt == 'sim_path':
 122                    self.options['sim_path'] = self.input_dict['simoptions'][i][1]
 123                if opt == 'sim_flag':
 124                    self.options['sim_flag'] = self.input_dict['simoptions'][i][1]
 125                if opt == 'mpi':
 126                    self.options['mpi'] = self.input_dict['simoptions'][i][1]
 127                if opt == 'parsing-strictness':
 128                    self.options['parsing-strictness'] = self.input_dict['simoptions'][i][1]
 129        if 'sim_limit' in self.input_dict:
 130            self.options['sim_limit'] = self.input_dict['sim_limit']
 131
 132        if 'reportdates' in self.input_dict:
 133            self.reportdates = [
 134                x * 30 for x in range(1, int(self.input_dict['reportdates'][1]))]
 135
 136        if 'read_sch' in self.input_dict:
 137            # self.read_sch = self.input_dict['read_sch'][0]
 138            load_file = np.load(self.input_dict['read_sch'], allow_pickle=True)
 139            self.reportdates = load_file[load_file.files[0]]
 140
 141        if 'startdate' in self.input_dict:
 142            self.startDate = {}
 143            # assume date is on form day/month/year
 144            tmpDate = [int(elem) for elem in self.input_dict['startdate'].split('/')]
 145
 146            self.startDate['day'] = tmpDate[0]
 147            self.startDate['month'] = tmpDate[1]
 148            self.startDate['year'] = tmpDate[2]
 149
 150        if 'realizations' in self.input_dict:
 151            self.realizations = self.input_dict['realizations']
 152
 153        if 'trunc_level' in self.input_dict:
 154            self.trunc_level = self.input_dict['trunc_level']
 155
 156        if 'rerun' in self.input_dict:
 157            self.rerun = int(self.input_dict['rerun'])
 158        else:
 159            self.rerun = 0
 160
 161        # If we want to extract, or evaluate, something uniquely from the ensemble specific run we can
 162        # run a user defined code to do this.
 163        self.saveinfo = None
 164        if 'savesiminfo' in self.input_dict:
 165            # Make sure "ANALYSISDEBUG" gives a list
 166            if isinstance(self.input_dict['savesiminfo'], list):
 167                self.saveinfo = self.input_dict['savesiminfo']
 168            else:
 169                self.saveinfo = [self.input_dict['savesiminfo']]
 170
 171        if 'upscale' in self.input_dict:
 172            self.upscale = {}
 173            for i in range(0, len(self.input_dict['upscale'])):
 174                if self.input_dict['upscale'][i][0] == 'state':
 175                    # Set the parameter we upscale with regards to, must be the same as one free parameter
 176                    self.upscale['state'] = self.input_dict['upscale'][i][1]
 177                    # Set the dimension of the parameterfield to be upscaled (x and y)
 178                    self.upscale['dim'] = self.input_dict['upscale'][i][2]
 179                if self.input_dict['upscale'][i][0] == 'maxtrunc':
 180                    # Set the ratio for the maximum truncation value
 181                    self.upscale['maxtrunc'] = self.input_dict['upscale'][i][1]
 182                if self.input_dict['upscale'][i][0] == 'maxdiff':
 183                    # Set the ratio for the maximum truncation of differences
 184                    self.upscale['maxdiff'] = self.input_dict['upscale'][i][1]
 185                if self.input_dict['upscale'][i][0] == 'wells':
 186                    # Set the list of well indexes, this is a list of lists where each element in the outer list gives
 187                    # a well coordinate (x and y) as elements in the inner list.
 188                    self.upscale['wells'] = []
 189                    for j in range(1, len(self.input_dict['upscale'][i])):
 190                        self.upscale['wells'].append(
 191                            [int(elem) for elem in self.input_dict['upscale'][i][j]])
 192                if self.input_dict['upscale'][i][0] == 'radius':
 193                    # List of radius lengths
 194                    self.upscale['radius'] = [int(elem)
 195                                              for elem in self.input_dict['upscale'][i][1]]
 196                if self.input_dict['upscale'][i][0] == 'us_type':
 197                    self.upscale['us_type'] = self.input_dict['upscale'][i][1]
 198
 199            # Check that we have as many radius elements as wells
 200            if 'radius' in self.upscale:
 201                if len(self.upscale['radius']) != len(self.upscale['wells']):
 202                    sys.exit('ERROR: Missmatch between number of well inputs and number of radius elements. Please check '
 203                             'the input file')
 204            else:
 205                self.upscale['radius'] = []
 206                self.upscale['wells'] = []
 207
 208        # The simulator should run on different levels
 209        if 'multilevel' in self.input_dict:
 210            # extract list of levels
 211            self.multilevel = self.input_dict['multilevel']
 212        else:
 213            # if not initiallize as list with one element
 214            self.multilevel = [False]
 215
 216    def setup_fwd_run(self, **kwargs):
 217        """
 218        Setup the simulator.
 219
 220        Parameters
 221        ----------
 222        assimIndex: int
 223            Gives the index-type (e.g. step,time,etc.) and the index for the
 224            data to be assimilated
 225        trueOrder:
 226            Gives the index-type (e.g. step,time,etc.) and the index of the true data
 227        """
 228        self.__dict__.update(kwargs)  # parse kwargs input into class attributes
 229
 230        if hasattr(self, 'reportdates'):
 231            self.report = {'dates': self.reportdates}
 232        elif 'reportmonths' in self.input_dict:  # for optimization
 233            self.report = {
 234                'days': [30 * i for i in range(1, int(self.input_dict['reportmonths'][1]))]}
 235        else:
 236            assimIndex = [i for i in range(len(self.l_prim))]
 237            trueOrder = self.true_order
 238
 239        self.pred_data = [deepcopy({}) for _ in range(len(assimIndex))]
 240        for ind in self.l_prim:
 241            for key in self.all_data_types:
 242                self.pred_data[ind][key] = np.zeros((1, 1))
 243
 244        if isinstance(trueOrder[1], list):  # Check if true data prim. ind. is a list
 245            self.true_prim = [trueOrder[0], [x for x in trueOrder[1]]]
 246        else:  # Float
 247            self.true_prim = [trueOrder[0], [trueOrder[1]]]
 248        # self.all_data_types = list(pred_data[0].keys())
 249
 250        # Initiallise space to store the number of active cells. This is only for the upscaling option.
 251        if 'upscale' in self.input_dict:
 252            self.num_act = []
 253
 254        # Initialise error summary
 255        self.error_smr = []
 256
 257        # Initiallize run time summary
 258        self.run_time = []
 259
 260        # Check that the .mako file is in the current working directory
 261        if not os.path.isfile('%s.mako' % self.file):
 262            sys.exit(
 263                'ERROR: .mako file is not in the current working directory. This file must be defined')
 264
 265    def run_fwd_sim(self, state, member_i, del_folder=True):
 266        """
 267        Setup and run the ecl_100 forward simulator. All the parameters are defined as attributes, and the name of the
 268        parameters are initialized in setupFwdRun. This method sets up and runs all the individual ensemble members.
 269        This method is based on writing .DATA file for each ensemble member using the mako template, for more info
 270        regarding mako see http://www.makotemplates.org/
 271
 272        Parameters
 273        ----------
 274        state : dict
 275            Dictionary containing the ensemble state.
 276
 277        member_i : int
 278            Index of the ensemble member.
 279
 280        del_folder : bool, optional
 281            Boolean to determine if the ensemble folder should be deleted. Default is False.
 282        """
 283        if hasattr(self, 'level'):
 284            state['level'] = self.level
 285        else:
 286            state['level'] = 0  # default value
 287        os.mkdir('En_' + str(member_i))
 288        folder = 'En_' + str(member_i) + os.sep
 289
 290        state['member'] = member_i
 291        # If the run is upscaled, run the upscaling procedure
 292        if self.upscale is not None:
 293            if hasattr(self, 'level'):  # if this is a multilevel run, we must set the level
 294                self.upscale['maxtrunc'] = self.trunc_level[self.level]
 295            if self.upscale['maxtrunc'] > 0:  # if the truncation level is 0, we do not perform upscaling
 296                self.coarsen(folder, state)
 297            # if the level is 0, and upscaling has been performed earlier this must be
 298            elif hasattr(self, 'coarse'):
 299                # removed
 300                del self.coarse
 301
 302        # start by generating the .DATA file, using the .mako template situated in ../folder
 303        self._runMako(folder, state)
 304        success = False
 305        rerun = self.rerun
 306        while rerun >= 0 and not success:
 307            success = self.call_sim(folder, True)
 308            rerun -= 1
 309        if success:
 310            self.extract_data(member_i)
 311            if del_folder:
 312                if self.saveinfo is not None:  # Try to save information
 313                    store_ensemble_sim_information(self.saveinfo, member_i)
 314                self.remove_folder(member_i)
 315            return self.pred_data
 316        else:
 317            if self.redund_sim is not None:
 318                success = self.redund_sim.call_sim(folder, True)
 319                if success:
 320                    self.extract_data(member_i)
 321                    if del_folder:
 322                        if self.saveinfo is not None:  # Try to save information
 323                            store_ensemble_sim_information(self.saveinfo, member_i)
 324                        self.remove_folder(member_i)
 325                    return self.pred_data
 326                else:
 327                    if del_folder:
 328                        self.remove_folder(member_i)
 329                    return False
 330            else:
 331                if del_folder:
 332                    self.remove_folder(member_i)
 333                return False
 334
 335    def remove_folder(self, member):
 336        folder = 'En_' + str(member) + os.sep
 337        try:
 338            rmtree(folder)  # Try to delete folder
 339        except:  # If deleting fails, just rename to 'En_<ensembleMember>_date'
 340            os.rename(folder, folder + '_' +
 341                      dt.datetime.now().strftime("%m-%d-%Y_%H-%M-%S"))
 342
 343    def extract_data(self, member):
 344        # get the formated data
 345        for prim_ind in self.l_prim:
 346            # Loop over all keys in pred_data (all data types)
 347            for key in self.all_data_types:
 348                if self.pred_data[prim_ind][key] is not None:  # Obs. data at assim. step
 349                    true_data_info = [self.true_prim[0], self.true_prim[1][prim_ind]]
 350                    try:
 351                        data_array = self.get_sim_results(key, true_data_info, member)
 352                        self.pred_data[prim_ind][key] = data_array
 353                    except:
 354                        pass
 355
 356    def coarsen(self, folder, ensembleMember=None):
 357        """
 358        This method utilized one field parameter to upscale the computational grid. A coarsening file is written to the
 359        ensemble folder, and the eclipse calculates the upscaled permeabilities, porosities and transmissibilities
 360        based on the new grid and the original parameter values.
 361
 362        Parameters
 363        ----------
 364        Folder : str, optional
 365            Path to the ecl_100 run folder.
 366
 367        ensembleMember : int, optional
 368            Index of the ensemble member to run.
 369
 370        Changelog
 371        ---------
 372        - KF 17/9-2015 Added uniform upscaling as an option
 373        - KF 6/01-17
 374        """
 375
 376        # Get the parameter values for the current ensemble member. Note that we select only one parameter
 377        if ensembleMember is not None:
 378            coarsenParam = self.inv_state[self.upscale['state']][:, ensembleMember]
 379        else:
 380            coarsenParam = self.inv_state[self.upscale['state']]
 381
 382        if self.upscale['us_type'].lower() == 'haar':  # the upscaling is based on a Haar wavelet
 383            # Do not add any dead-cells
 384            # TODO: Make a better method for determining dead or inactive cells.
 385
 386            orig_wght = np.ones((self.upscale['dim'][0], self.upscale['dim'][1]))
 387            # Get the new cell structure by a 2-D unbalanced Haar transform.
 388            wght = self._Haar(coarsenParam.reshape(
 389                (self.upscale['dim'][0], self.upscale['dim'][1])), orig_wght)
 390
 391        elif self.upscale['us_type'].lower() == 'unif':
 392            # This option generates a grid where the cells are upscaled uniformly in a dyadic manner. We utilize the
 393            # same framework to define the parameters to be coarsened as in the Haar case. Hence, we only need to
 394            # calculate the wght variable. The level of upscaling can be determined by a fraction, however, for this
 395            # case the fraction gives the number of upscaling levels, 1 is all possible levels, 0 is no upscaling.
 396            wght = self._unif((self.upscale['dim'][0], self.upscale['dim'][1]))
 397
 398        self.write_coarse(folder, wght, coarsenParam.reshape(
 399            (int(self.upscale['dim'][0]), int(self.upscale['dim'][1]))))
 400
 401    def write_coarse(self, folder, whgt, image):
 402        """
 403        This function writes the include file coarsen to the ecl run. This file tels ECL to coarsen the grid.
 404
 405        Parameters
 406        ----------
 407        folder : str
 408            Path to the ecl_100 run.
 409
 410        whgt : float
 411            Weight of the transformed cells.
 412
 413        image : array-like
 414            Original image.
 415
 416        Changelog
 417        ---------
 418        - KF 17/9-2015
 419        """
 420
 421        well = self.upscale['wells']
 422        radius = self.upscale['radius']
 423
 424        well_cells = self._nodeIndex(image.shape[1], image.shape[0], well, radius)
 425        coarse = np.array([[True] * image.shape[1]] * image.shape[0])
 426        tmp = coarse.flatten()
 427        tmp[well_cells] = False
 428        coarse = tmp.reshape(image.shape)
 429
 430        # f = open(folder + 'coarsen.dat', 'w+')
 431        # f.write('COARSEN \n')
 432        ecl_coarse = []
 433
 434        for level in range(len(whgt) - 1, -1, -1):
 435            weight_at_level = whgt[level]
 436            x_dim = weight_at_level.shape[0]
 437            y_dim = weight_at_level.shape[1]
 438
 439            merged_at_level = weight_at_level[int(x_dim / 2):, :int(y_dim / 2)]
 440
 441            level_dim = 2 ** (level + 1)
 442
 443            for i in range(0, merged_at_level.shape[0]):
 444                for j in range(0, merged_at_level.shape[1]):
 445                    if merged_at_level[i, j] == 1:
 446                        # Do this in two stages to avoid errors with the dimensions
 447                        #
 448                        # only merging square cells
 449                        if (i + 1) * level_dim <= image.shape[0] and (j + 1) * level_dim <= image.shape[1]:
 450                            if coarse[i * level_dim:(i + 1) * level_dim, j * level_dim:(j + 1) * level_dim].all():
 451                                coarse[i * level_dim:(i + 1) * level_dim,
 452                                       j * level_dim:(j + 1) * level_dim] = False
 453                                ecl_coarse.append([j * level_dim + 1, (j + 1) * level_dim,
 454                                                   i * level_dim + 1, (i + 1) * level_dim, 1, 1, 1, 1, 1])
 455                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, (j + 1) * level_dim,
 456                                #                                         i * level_dim + 1, (i + 1) * level_dim))
 457                        # cells at first edge, non-square
 458                        if (i + 1)*level_dim > image.shape[0] and (j + 1) * level_dim <= image.shape[1]:
 459                            if coarse[i * level_dim::, j * level_dim:(j + 1) * level_dim].all():
 460                                coarse[i * level_dim::, j *
 461                                       level_dim:(j + 1) * level_dim] = False
 462                                ecl_coarse.append([j * level_dim + 1, (j + 1) * level_dim,
 463                                                   i * level_dim + 1, image.shape[0], 1, 1, 1, 1, 1])
 464                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, (j + 1) * level_dim,
 465                                #                                         i * level_dim + 1, image.shape[0]))
 466                        # cells at second edge, non-square
 467                        if (j + 1)*level_dim > image.shape[1] and (i + 1) * level_dim <= image.shape[0]:
 468                            if coarse[i * level_dim:(i + 1) * level_dim, j * level_dim::].all():
 469                                coarse[i * level_dim:(i + 1) * level_dim,
 470                                       j * level_dim::] = False
 471                                ecl_coarse.append([j * level_dim + 1, image.shape[1],
 472                                                   i * level_dim + 1, (i + 1) * level_dim, 1, 1, 1, 1, 1])
 473                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, image.shape[1],
 474                                #                                         i * level_dim + 1, (i + 1) * level_dim))
 475                        # cells at intersection between first and second edge, non-square
 476                        if (i + 1) * level_dim > image.shape[0] and (j + 1) * level_dim > image.shape[1]:
 477                            if coarse[i * level_dim::, j * level_dim::].all():
 478                                coarse[i * level_dim::, j * level_dim::] = False
 479                                ecl_coarse.append([j * level_dim + 1, image.shape[1],
 480                                                   i * level_dim + 1, image.shape[0], 1, 1, 1, 1, 1])
 481                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, image.shape[1],
 482                                #                                         i * level_dim + 1, image.shape[0]))
 483        # f.write('/')
 484        # f.close()
 485        self.coarse = ecl_coarse
 486
 487    def _nodeIndex(self, x_dir, y_dir, cells, listRadius):
 488        # Find the node index for the cells, and the cells in a radius around.
 489        index = []
 490        for k in range(0, len(cells)):
 491            cell_x = cells[k][0] - 1  # Remove one to make python equivalent to ecl format
 492            cell_y = cells[k][1] - 1  # Remove one to make python equivalent to ecl format
 493            radius = listRadius[k]
 494            # tmp_index = [cell_x + cell_y*x_dir]
 495            tmp_index = []
 496            for i in range(-radius, radius + 1):
 497                for j in range(-radius, radius + 1):
 498                    x = cell_x + i
 499                    y = cell_y + j
 500                    if (0 <= x < x_dir and 0 <= y < y_dir) and (np.sqrt(i ** 2 + j ** 2) <= radius) \
 501                            and (x + y * x_dir < y_dir * x_dir):
 502                        tmp_index.append(x + y * x_dir)
 503            index.extend(tmp_index)
 504
 505        return index
 506
 507    def _Haar(self, image, weights):
 508        """
 509        The 2D unconditional Haar transform.
 510
 511        Parameters
 512        ----------
 513        image : array-like
 514            The original image where the Haar-transform is performed. This could be the permeability or the porosity field.
 515
 516        orig_weights : array-like
 517            The original weights of the image. If some cells are inactive or dead, their weight can be set to zero.
 518
 519        Returns
 520        -------
 521        tot_weights : array-like
 522            A vector/matrix of the same size as the image, with the weights of the new cells. This is utilized for writing
 523            the coarsening file but is not sufficient to recreate the image.
 524        """
 525        # Todo: make an option which stores the transformed field.
 526
 527        # Define the two truncation levels as a fraction of the larges value in the original image. The fraction is defined
 528        # through the inputs.
 529        max_value = self.upscale['maxtrunc'] * np.max(image)
 530        max_diff = self.upscale['maxdiff'] * (np.max(image) - np.min(image))
 531
 532        tot_transf = []
 533        tot_weight = []
 534        tot_alpha = []
 535        count = 0
 536        allow_merge = np.ones(image.shape)
 537        while image.shape[0] > 1:
 538            level_alpha = []
 539            if isinstance(image.shape, tuple):
 540                # the image has two axes.
 541                # Perform the transformation for all the coulmns, and then for the transpose of the results
 542
 543                for axis in range(0, 2):
 544                    # Initialise the matrix for storing the details and smoothing
 545                    level_diff_columns = np.empty(
 546                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 547                    # level_diff_rows = np.empty((len(image[:, 0]), int(np.ceil(len(image[0, :])/2))))
 548                    level_smooth_columns = np.empty(
 549                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 550                    level_weight_columns = np.empty(
 551                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 552                    level_alpha_columns = np.empty(
 553                        (int(np.ceil(len(image[:, 0]) / 2)), len(image[0, :])))
 554
 555                    for i in range(0, image.shape[1]):
 556                        if len(image[:, i]) % 2 == 0:
 557                            diff = image[1::2, i] - image[::2, i]
 558                            new_weights = weights[::2, i] + weights[1::2, i]
 559                            alpha = (weights[1::2, i] / new_weights)
 560                            smooth = image[::2, i] + alpha * diff
 561
 562                        else:
 563                            tmp_img = image[:-1, i]
 564                            diff = tmp_img[1::2] - tmp_img[::2]
 565
 566                            tmp_weight = weights[:-1, i]
 567                            new_weights = tmp_weight[::2] + tmp_weight[1::2]
 568                            new_weights = np.append(new_weights, weights[-1, i])
 569
 570                            tmp_img = image[:-1, i]
 571                            alpha = (weights[1::2, i] / new_weights[:-1])
 572                            smooth = tmp_img[::2] + alpha * diff
 573                            smooth = np.append(smooth, image[-1, i])
 574                            alpha = np.append(alpha, 0)
 575                            diff = np.append(diff, 0)
 576
 577                        level_diff_columns[:, i] = diff
 578                        level_weight_columns[:, i] = new_weights
 579                        level_smooth_columns[:, i] = smooth
 580                        level_alpha_columns[:, i] = alpha
 581
 582                    # image = level_smooth_columns.T
 583                    image = np.vstack((level_smooth_columns, level_diff_columns)).T
 584                    weights = np.vstack((level_weight_columns, level_weight_columns)).T
 585                    level_alpha.append(level_alpha_columns)
 586
 587                image, weights, allow_merge, end_ctrl = self._haarTrunc(image, weights, max_value, max_diff,
 588                                                                        allow_merge)
 589
 590                tot_transf.append(image)
 591                tot_weight.append(weights)
 592                tot_alpha.append(level_alpha)
 593
 594                if end_ctrl:
 595                    break
 596                image = image[:image.shape[0] / 2, :image.shape[1] / 2]
 597                weights = weights[:weights.shape[0] / 2, :weights.shape[1] / 2]
 598
 599            else:
 600                if len(image) % 2 == 0:
 601                    diff = image[1::2] - image[::2]
 602                    new_weights = weights[::2] + weights[1::2]
 603                    smooth = image[::2] + (weights[1::2] / new_weights) * diff
 604
 605                else:
 606                    tmp_img = image[:-1]
 607                    diff = tmp_img[1::2] - tmp_img[::2]
 608
 609                    tmp_weight = weights[:-1]
 610                    new_weights = tmp_weight[::2] + tmp_weight[1::2]
 611                    new_weights = np.append(new_weights, weights[-1])
 612
 613                    tmp_img = image[:-1]
 614                    smooth = tmp_img[::2] + (weights[1::2] / new_weights[:-1]) * diff
 615                    smooth = np.append(smooth, image[-1])
 616
 617                tot_transf.append(smooth)
 618                tot_weight.append(new_weights)
 619
 620            # weights = new_weights
 621            # image = smooth
 622            count += 1
 623
 624        return tot_weight
 625
 626    def _unif(self, dim):
 627        # calculate the uniform upscaling steps
 628
 629        min_dim = min(dim)  # find the lowest dimension
 630        max_levels = 0
 631        while min_dim/2 >= 1:
 632            min_dim = min_dim/2
 633            max_levels += 1
 634
 635        # conservative choice
 636        trunc_level = int(np.floor(max_levels*self.upscale['maxtrunc']))
 637
 638        # all the initial cells are upscaled
 639        wght = [np.ones(tuple([int(elem) for elem in dim]))]
 640
 641        for i in range(trunc_level):
 642            new_dim = [int(np.ceil(elem/2))
 643                       for elem in wght[i].shape]  # always take a larger dimension
 644            wght.append(np.ones(tuple(new_dim)))
 645
 646        return wght
 647
 648    def _haarTrunc(self, image, weights, max_val, max_diff, merge):
 649        """
 650        Function for truncating the wavelets. Based on the max_val and max_diff values this function set the detail
 651        coaffiecient to zero if the value of this coefficient is below max_diff, and the value of the smooth coefficient
 652        is below max_val.
 653
 654        Parameters
 655        ----------
 656        image : array-like
 657            The transformed image.
 658
 659        weights : array-like
 660            The weights of the transformed image.
 661
 662        max_val : float
 663            Smooth values above this value are not allowed.
 664
 665        max_diff : float
 666            Detail coefficients higher than this value are not truncated.
 667
 668        merge : array-like
 669            Matrix/vector of booleans defining whether a merge is allowed.
 670
 671        Returns
 672        -------
 673        image : array-like
 674            Transformed image with truncated coefficients.
 675
 676        weights : array-like
 677            Updated weights.
 678
 679        allow_merge : array-like
 680            Booleans keeping track of allowed merges.
 681
 682        end_ctrl : bool
 683            Boolean to control whether further upscaling is possible.
 684        """
 685        # the image will always contain even elements in x and y dir
 686        x_dir = int(image.shape[0] / 2)
 687        y_dir = int(image.shape[1] / 2)
 688
 689        smooth = image[:x_dir, :y_dir]
 690        smooth_diff = image[x_dir:, :y_dir]
 691        diff_smooth = image[:x_dir, y_dir:]
 692        diff_diff = image[x_dir:, y_dir:]
 693
 694        weights_diff = weights[x_dir:, :y_dir]
 695
 696        allow_merge = np.array(
 697            [[False] * y_dir] * x_dir)
 698
 699        # ensure that last row does not be left behind during non-dyadic upscaling
 700        if image.shape[0] > merge.shape[0]:
 701            merge = np.insert(merge, -1, merge[-1, :], axis=0)
 702        if image.shape[1] > merge.shape[1]:
 703            merge = np.insert(merge, -1, merge[:, -1], axis=1)
 704
 705        cand1 = zip(*np.where(smooth < max_val))
 706
 707        end_ctrl = True
 708
 709        for elem in cand1:
 710            # If the method wants to merge cells outside the box the if sentence gives an error, make an exception for
 711            # this hence do not merge these cells.
 712            try:
 713                if abs(smooth_diff[elem]) <= max_diff and abs(diff_smooth[elem]) <= max_diff and \
 714                        merge[elem[0] * 2, elem[1] * 2] and merge[elem[0] * 2 + 1, elem[1] * 2] and \
 715                        merge[elem[0] * 2 + 1, elem[1] * 2 + 1] and merge[elem[0] * 2, elem[1] * 2 + 1]:
 716                    diff_smooth[elem] = 0
 717                    smooth_diff[elem] = 0
 718                    diff_diff[elem] = 0
 719                    weights_diff[elem] = True
 720                    allow_merge[elem] = True
 721                    end_ctrl = False
 722            except:
 723                pass
 724        # for elem in cand2:
 725        #     if smooth[elem] <= max_val:
 726        #         diff_smooth[elem] = 0
 727        #         smooth_diff[elem] = 0
 728        #         diff_diff[elem] = 0
 729
 730        image[x_dir:, :y_dir] = smooth_diff
 731        image[:x_dir, y_dir:] = diff_smooth
 732        image[x_dir:, y_dir:] = diff_diff
 733
 734        weights[x_dir:, :y_dir] = weights_diff
 735
 736        return image, weights, allow_merge, end_ctrl
 737
 738    def _runMako(self, folder, state):
 739        """
 740        Read the mako template (.mako) file from ../folder, and render the correct data file (.DATA) in folder.
 741
 742        Parameters
 743        ----------
 744        Folder : str, optional
 745            Folder for the ecl_100 run.
 746
 747        ensembleMember : int, optional
 748            Index of the ensemble member to run.
 749
 750        Changelog
 751        ---------
 752        - KF 14/9-2015
 753        """
 754
 755        # Check and add report time
 756        if hasattr(self, 'report'):
 757            for key in self.report:
 758                state[key] = self.report[key]
 759
 760        # Convert drilling order (float) to drilling queue (integer) - drilling order optimization
 761        # if "drillingorder" in en_dict:
 762        #     dorder = en_dict['drillingorder']
 763        #     en_dict['drillingqueue'] = np.argsort(dorder)[::-1][:len(dorder)]
 764
 765        # Add startdate
 766        if hasattr(self, 'startDate'):
 767            state['startdate'] = dt.datetime(
 768                self.startDate['year'], self.startDate['month'], self.startDate['day'])
 769
 770        # Add the coarsing values
 771        if hasattr(self, 'coarse'):
 772            state['coarse'] = self.coarse
 773
 774        # Look for the mako file
 775        lkup = TemplateLookup(directories=os.getcwd(),
 776                              input_encoding='utf-8')
 777
 778        # Get template
 779        # If we need the E300 run, define a E300 data file with _E300 added to the end.
 780        if hasattr(self, 'E300'):
 781            if self.E300:
 782                tmpl = lkup.get_template('%s.mako' % (self.file + '_E300'))
 783            else:
 784                tmpl = lkup.get_template('%s.mako' % self.file)
 785        else:
 786            tmpl = lkup.get_template('%s.mako' % self.file)
 787
 788        # use a context and render onto a file
 789        with open('{0}{1}'.format(folder + self.file, '.DATA'), 'w') as f:
 790            ctx = Context(f, **state)
 791            tmpl.render_context(ctx)
 792
 793    def get_sim_results(self, whichResponse, ext_data_info=None, member=None):
 794        """
 795        Read the output from simulator and store as an array. Optionally, if the DA method is based on an ensemble
 796        method, the output must be read inside a folder.
 797
 798        Parameters
 799        ----------
 800        file_rsm : str
 801            Summary file from ECL 100.
 802
 803        file_rst : str
 804            Restart file from ECL 100.
 805
 806        whichResponse : str
 807            Which of the responses is to be outputted (e.g., WBHP PRO-1, WOPR, PRESS, OILSAT, etc).
 808
 809        member : int, optional
 810            Ensemble member that is finished.
 811
 812        ext_data_info : tuple, optional
 813            Tuple containing the assimilation step information, including the place of assimilation (e.g., which TIME) and the
 814            index of this assimilation place.
 815
 816        Returns
 817        -------
 818        yFlow : array-like
 819            Array containing the response from ECL 100. The response type is chosen by the user in options['data_type'].
 820
 821        Notes
 822        -----
 823        - Modified the ecl package to allow reading the summary data directly, hence, we get cell, summary, and field data
 824        from the ecl package.
 825        - KF 29/10-2015
 826        - Modified the ecl package to read RFT files. To obtain, e.g,. RFT pressures form well 'PRO-1" whichResponse
 827        must be rft_PRESSURE PRO-1
 828        """
 829        # Check that we have no trailing spaces
 830        whichResponse = whichResponse.strip()
 831
 832        # if ensemble DA method
 833        if member is not None:
 834            # Get results
 835            if hasattr(self, 'ecl_case'):
 836                # En_XX/YYYY.DATA is the folder setup
 837                rt_mem = int(self.ecl_case.root.split('/')[0].split('_')[1])
 838                if rt_mem != member:  # wrong case
 839                    self.ecl_case = ecl.EclipseCase('En_' + str(member) + os.sep + self.file + '.DATA')
 840            else:
 841                self.ecl_case = ecl.EclipseCase('En_' + str(member) + os.sep + self.file + '.DATA')
 842            if ext_data_info[0] == 'days':
 843                time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
 844                    dt.timedelta(days=ext_data_info[1])
 845                dates = self.ecl_case.by_date
 846                if time not in dates and 'date_slack' in self.input_dict:
 847                    slack = int(self.input_dict['date_slack'])
 848                    if slack > 0:
 849                        v = [el for el in dates if np.abs(
 850                            (el-time).total_seconds()) < slack]
 851                        if len(v) > 0:
 852                            time = v[0]
 853            else:
 854                time = ext_data_info[1]
 855
 856            # Check if the data is a field or well data, by checking if the well is defined
 857            if len(whichResponse.split(' ')) == 2:
 858                # if rft, search for rft_
 859                if 'rft_' in whichResponse:
 860                    # to speed up when performing the prediction step
 861                    if hasattr(self, 'rft_case'):
 862                        rt_mem = int(self.rft_case.root.split('/')[0].split('_')[1])
 863                        if rt_mem != member:
 864                            self.rft_case = ecl.EclipseRFT('En_' + str(member) + os.sep + self.file)
 865                    else:
 866                        self.rft_case = ecl.EclipseRFT(
 867                            'En_' + str(member) + os.sep + self.file)
 868                    # Get the data. Due to formating we can slice the property.
 869                    rft_prop = self.rft_case.rft_data(well=whichResponse.split(
 870                        ' ')[1], prop=whichResponse.split(' ')[0][4:])
 871                    # rft_data are collected for open connections. This may vary throughout the simulation, hence we
 872                    # must also collect the depth for the rft_data to check if all data is present
 873                    rft_depth = self.rft_case.rft_data(
 874                        well=whichResponse.split(' ')[1], prop='DEPTH')
 875                    # to check this we import the referance depth if this is available. If not we assume that the data
 876                    # is ok.
 877                    try:
 878                        ref_depth_f = np.load(whichResponse.split(
 879                            ' ')[1].upper() + '_rft_ref_depth.npz')
 880                        ref_depth = ref_depth_f[ref_depth_f.files[0]]
 881                        yFlow = np.array([])
 882                        interp = interpolate.interp1d(rft_depth, rft_prop, kind='linear', bounds_error=False,
 883                                                      fill_value=(rft_prop[0], rft_prop[-1]))
 884                        for d in ref_depth:
 885                            yFlow = np.append(yFlow, interp(d))
 886                    except:
 887                        yFlow = rft_prop
 888                else:
 889                    # If well, read the rsm file
 890                    if ext_data_info is not None:  # Get the data at a specific well and time
 891                        yFlow = self.ecl_case.summary_data(whichResponse, time)
 892            elif len(whichResponse.split(' ')) == 1:  # field data
 893                if whichResponse.upper() in ['FOPT', 'FWPT', 'FGPT', 'FWIT', 'FGIT']:
 894                    if ext_data_info is not None:
 895                        yFlow = self.ecl_case.summary_data(whichResponse, time)
 896                elif whichResponse.upper() in ['PERMX', 'PERMY', 'PERMZ', 'PORO', 'NTG', 'SATNUM',
 897                                               'MULTNUM', 'OPERNUM']:
 898                    yFlow = np.array([self.ecl_case.cell_data(whichResponse).flatten()[time]]) # assume that time is the index
 899                else:
 900                    yFlow = self.ecl_case.cell_data(whichResponse, time).flatten()
 901                    if yFlow is None:
 902                        yFlow = self.ecl_case.cell_data(whichResponse).flatten()
 903
 904            # store the run time. NB: elapsed must be defined in .DATA file for this to work
 905            if 'save_elapsed' in self.input_dict and len(self.run_time) <= member:
 906                self.run_time.extend(self.ecl_case.summary_data('ELAPSED', time))
 907
 908            # If we have performed coarsening, we store the number of active grid-cells
 909            if self.upscale is not None:
 910                # Get this number from INIT file
 911                with ecl.EclipseFile('En_' + str(member) + os.sep + self.file, 'INIT') as case:
 912                    intHead = case.get('INTEHEAD')
 913                # The active cell is element 12 of this vector, index 11 in python indexing...
 914                active_cells = intHead[11]
 915                if len(self.num_act) <= member:
 916                    self.num_act.extend([active_cells])
 917
 918        else:
 919            case = ecl.EclipseCase(self.file + '.DATA')
 920            if ext_data_info[0] == 'days':
 921                time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
 922                    dt.timedelta(days=ext_data_info[1])
 923            else:
 924                time = ext_data_info[1]
 925
 926            # Check if the data is a field or well data, by checking if the well is defined
 927            if len(whichResponse.split(' ')) == 2:
 928                # if rft, search for rft_
 929                if 'rft_' in whichResponse:
 930                    rft_case = ecl.EclipseRFT(self.file)
 931                    # Get the data. Due to formating we can slice the property.
 932                    rft_prop = rft_case.rft_data(well=whichResponse.split(
 933                        ' ')[1], prop=whichResponse.split(' ')[0][4:])
 934                    # rft_data are collected for open connections. This may vary throughout the simulation, hence we
 935                    # must also collect the depth for the rft_data to check if all data is present
 936                    rft_depth = rft_case.rft_data(
 937                        well=whichResponse.split(' ')[1], prop='DEPTH')
 938                    try:
 939                        ref_depth_f = np.load(whichResponse.split(
 940                            ' ')[1].upper() + '_rft_ref_depth.npz')
 941                        ref_depth = ref_depth_f[ref_depth_f.files[0]]
 942                        yFlow = np.array([])
 943                        interp = interpolate.interp1d(rft_depth, rft_prop, kind='linear', bounds_error=False,
 944                                                      fill_value=(rft_prop[0], rft_prop[-1]))
 945                        for d in ref_depth:
 946                            yFlow = np.append(yFlow, interp(d))
 947                    except:
 948                        yFlow = rft_prop
 949                else:
 950                    # If well, read the rsm file
 951                    if ext_data_info is not None:  # Get the data at a specific well and time
 952                        yFlow = case.summary_data(whichResponse, time)
 953            elif len(whichResponse.split(' ')) == 1:
 954                if whichResponse in ['FOPT', 'FWPT', 'FGPT', 'FWIT', 'FGIT']:
 955                    if ext_data_info is not None:
 956                        yFlow = case.summary_data(whichResponse, time)
 957                else:
 958                    yFlow = case.cell_data(whichResponse, time).flatten()
 959                    if yFlow is None:
 960                        yFlow = case.cell_data(whichResponse).flatten()
 961
 962            # If we have performed coarsening, we store the number of active grid-cells
 963            if self.upscale is not None:
 964                # Get this number from INIT file
 965                with ecl.EclipseFile('En_' + str(member) + os.sep + self.file,'INIT') as case:
 966                    intHead = case.get('INTEHEAD')
 967                # The active cell is element 12 of this vector, index 11 in python indexing...
 968                active_cells = intHead[11]
 969                if len(self.num_act) <= member:
 970                    self.num_act.extend([active_cells])
 971
 972        return yFlow
 973
 974    def store_fwd_debug(self, assimstep):
 975        if 'fwddebug' in self.keys_fwd:
 976            # Init dict. of variables to save
 977            save_dict = {}
 978
 979            # Make sure "ANALYSISDEBUG" gives a list
 980            if isinstance(self.keys_fwd['fwddebug'], list):
 981                debug = self.keys_fwd['fwddebug']
 982            else:
 983                debug = [self.keys_fwd['fwddebug']]
 984
 985            # Loop over variables to store in save list
 986            for var in debug:
 987                # Save with key equal variable name and the actual variable
 988                if isinstance(eval('self.' + var), dict):
 989                    # save directly
 990                    np.savez('fwd_debug_%s_%i' % (var, assimstep), **eval('self.' + var))
 991                else:
 992                    save_dict[var] = eval('self.' + var)
 993
 994            np.savez('fwd_debug_%i' % (assimstep), **save_dict)
 995
 996    def write_to_grid(self, value, propname, path, dim, t_ind=None):
 997        if t_ind == None:
 998            trans_dict = {}
 999
1000            def _lookup(kw):
1001                return trans_dict[kw] if kw in trans_dict else kw
1002
1003            # Write a quantity to the grid as a grdecl file
1004            with open(path + propname + '.grdecl', 'wb') as fileobj:
1005                grdecl._write_kw(fileobj, propname, value, _lookup, dim)
1006        else:
1007            pass
1008            # some errors with rips
1009            # p = Process(target=_write_to_resinsight, args=(list(value[~value.mask]),propname, t_ind))
1010            # Find an open resinsight case
1011            # p.start()
1012            # time.sleep(1)
1013            # p.terminate()

Class for running the Schlumberger eclipse 100 black oil reservoir simulator. For more information see GeoQuest: ECLIPSE reference manual 2009.1. Schlumberger, GeoQuest (2009).

To run this class, eclipse must be installed and elcrun must be in the system path!

eclipse(input_dict=None, filename=None, options=None)
32    def __init__(self, input_dict=None, filename=None, options=None):
33        """
34        The inputs are all optional, but in the same fashion as the other simulators a system must be followed.
35        The input_dict can be utilized as a single input. Here all nescessary info is stored. Alternatively,
36        if input_dict is not defined, all the other input variables must be defined.
37
38        Parameters
39        ----------
40        input_dict : dict, optional
41            Dictionary containing all information required to run the simulator.
42
43                - parallel: number of forward simulations run in parallel
44                - simoptions: options for the simulations
45                    - mpi: option to use mpi (always use > 2 cores)
46                    - sim_path: Path to the simulator
47                    - sim_flag: Flags sent to the simulator (see simulator documentation for all possibilities)
48                - sim_limit: maximum number of seconds a simulation can run before being killed
49                - runfile: name of the simulation input file
50                - reportpoint: these are the dates the simulator reports results
51                - reporttype: this key states that the report poins are given as dates
52                - datatype: the data types the simulator reports
53                - replace: replace failed simulations with randomly selected successful ones
54                - rerun: in case of failure, try to rerun the simulator the given number of times
55                - startdate: simulaton start date
56                - saveforecast: save the predicted measurements for each iteration
57
58        filename : str, optional
59            Name of the .mako file utilized to generate the ECL input .DATA file. Must be in uppercase for the
60            ECL simulator.
61
62        options : dict, optional
63            Dictionary with options for the simulator.
64
65        Returns
66        -------
67        initial_object : object
68            Initial object from the class ecl_100.
69        """
70
71        # IN
72        self.input_dict = input_dict
73        self.file = filename
74        self.options = options
75
76        self.upscale = None
77        # If input option 1 is selected
78        if self.input_dict is not None:
79            self._extInfoInputDict()
80
81        # Allocate for internal use
82        self.inv_stat = None
83        self.static_state = None

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.
input_dict
file
options
upscale
inv_stat
static_state
def setup_fwd_run(self, **kwargs):
216    def setup_fwd_run(self, **kwargs):
217        """
218        Setup the simulator.
219
220        Parameters
221        ----------
222        assimIndex: int
223            Gives the index-type (e.g. step,time,etc.) and the index for the
224            data to be assimilated
225        trueOrder:
226            Gives the index-type (e.g. step,time,etc.) and the index of the true data
227        """
228        self.__dict__.update(kwargs)  # parse kwargs input into class attributes
229
230        if hasattr(self, 'reportdates'):
231            self.report = {'dates': self.reportdates}
232        elif 'reportmonths' in self.input_dict:  # for optimization
233            self.report = {
234                'days': [30 * i for i in range(1, int(self.input_dict['reportmonths'][1]))]}
235        else:
236            assimIndex = [i for i in range(len(self.l_prim))]
237            trueOrder = self.true_order
238
239        self.pred_data = [deepcopy({}) for _ in range(len(assimIndex))]
240        for ind in self.l_prim:
241            for key in self.all_data_types:
242                self.pred_data[ind][key] = np.zeros((1, 1))
243
244        if isinstance(trueOrder[1], list):  # Check if true data prim. ind. is a list
245            self.true_prim = [trueOrder[0], [x for x in trueOrder[1]]]
246        else:  # Float
247            self.true_prim = [trueOrder[0], [trueOrder[1]]]
248        # self.all_data_types = list(pred_data[0].keys())
249
250        # Initiallise space to store the number of active cells. This is only for the upscaling option.
251        if 'upscale' in self.input_dict:
252            self.num_act = []
253
254        # Initialise error summary
255        self.error_smr = []
256
257        # Initiallize run time summary
258        self.run_time = []
259
260        # Check that the .mako file is in the current working directory
261        if not os.path.isfile('%s.mako' % self.file):
262            sys.exit(
263                'ERROR: .mako file is not in the current working directory. This file must be defined')

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):
265    def run_fwd_sim(self, state, member_i, del_folder=True):
266        """
267        Setup and run the ecl_100 forward simulator. All the parameters are defined as attributes, and the name of the
268        parameters are initialized in setupFwdRun. This method sets up and runs all the individual ensemble members.
269        This method is based on writing .DATA file for each ensemble member using the mako template, for more info
270        regarding mako see http://www.makotemplates.org/
271
272        Parameters
273        ----------
274        state : dict
275            Dictionary containing the ensemble state.
276
277        member_i : int
278            Index of the ensemble member.
279
280        del_folder : bool, optional
281            Boolean to determine if the ensemble folder should be deleted. Default is False.
282        """
283        if hasattr(self, 'level'):
284            state['level'] = self.level
285        else:
286            state['level'] = 0  # default value
287        os.mkdir('En_' + str(member_i))
288        folder = 'En_' + str(member_i) + os.sep
289
290        state['member'] = member_i
291        # If the run is upscaled, run the upscaling procedure
292        if self.upscale is not None:
293            if hasattr(self, 'level'):  # if this is a multilevel run, we must set the level
294                self.upscale['maxtrunc'] = self.trunc_level[self.level]
295            if self.upscale['maxtrunc'] > 0:  # if the truncation level is 0, we do not perform upscaling
296                self.coarsen(folder, state)
297            # if the level is 0, and upscaling has been performed earlier this must be
298            elif hasattr(self, 'coarse'):
299                # removed
300                del self.coarse
301
302        # start by generating the .DATA file, using the .mako template situated in ../folder
303        self._runMako(folder, state)
304        success = False
305        rerun = self.rerun
306        while rerun >= 0 and not success:
307            success = self.call_sim(folder, True)
308            rerun -= 1
309        if success:
310            self.extract_data(member_i)
311            if del_folder:
312                if self.saveinfo is not None:  # Try to save information
313                    store_ensemble_sim_information(self.saveinfo, member_i)
314                self.remove_folder(member_i)
315            return self.pred_data
316        else:
317            if self.redund_sim is not None:
318                success = self.redund_sim.call_sim(folder, True)
319                if success:
320                    self.extract_data(member_i)
321                    if del_folder:
322                        if self.saveinfo is not None:  # Try to save information
323                            store_ensemble_sim_information(self.saveinfo, member_i)
324                        self.remove_folder(member_i)
325                    return self.pred_data
326                else:
327                    if del_folder:
328                        self.remove_folder(member_i)
329                    return False
330            else:
331                if del_folder:
332                    self.remove_folder(member_i)
333                return False

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 remove_folder(self, member):
335    def remove_folder(self, member):
336        folder = 'En_' + str(member) + os.sep
337        try:
338            rmtree(folder)  # Try to delete folder
339        except:  # If deleting fails, just rename to 'En_<ensembleMember>_date'
340            os.rename(folder, folder + '_' +
341                      dt.datetime.now().strftime("%m-%d-%Y_%H-%M-%S"))
def extract_data(self, member):
343    def extract_data(self, member):
344        # get the formated data
345        for prim_ind in self.l_prim:
346            # Loop over all keys in pred_data (all data types)
347            for key in self.all_data_types:
348                if self.pred_data[prim_ind][key] is not None:  # Obs. data at assim. step
349                    true_data_info = [self.true_prim[0], self.true_prim[1][prim_ind]]
350                    try:
351                        data_array = self.get_sim_results(key, true_data_info, member)
352                        self.pred_data[prim_ind][key] = data_array
353                    except:
354                        pass
def coarsen(self, folder, ensembleMember=None):
356    def coarsen(self, folder, ensembleMember=None):
357        """
358        This method utilized one field parameter to upscale the computational grid. A coarsening file is written to the
359        ensemble folder, and the eclipse calculates the upscaled permeabilities, porosities and transmissibilities
360        based on the new grid and the original parameter values.
361
362        Parameters
363        ----------
364        Folder : str, optional
365            Path to the ecl_100 run folder.
366
367        ensembleMember : int, optional
368            Index of the ensemble member to run.
369
370        Changelog
371        ---------
372        - KF 17/9-2015 Added uniform upscaling as an option
373        - KF 6/01-17
374        """
375
376        # Get the parameter values for the current ensemble member. Note that we select only one parameter
377        if ensembleMember is not None:
378            coarsenParam = self.inv_state[self.upscale['state']][:, ensembleMember]
379        else:
380            coarsenParam = self.inv_state[self.upscale['state']]
381
382        if self.upscale['us_type'].lower() == 'haar':  # the upscaling is based on a Haar wavelet
383            # Do not add any dead-cells
384            # TODO: Make a better method for determining dead or inactive cells.
385
386            orig_wght = np.ones((self.upscale['dim'][0], self.upscale['dim'][1]))
387            # Get the new cell structure by a 2-D unbalanced Haar transform.
388            wght = self._Haar(coarsenParam.reshape(
389                (self.upscale['dim'][0], self.upscale['dim'][1])), orig_wght)
390
391        elif self.upscale['us_type'].lower() == 'unif':
392            # This option generates a grid where the cells are upscaled uniformly in a dyadic manner. We utilize the
393            # same framework to define the parameters to be coarsened as in the Haar case. Hence, we only need to
394            # calculate the wght variable. The level of upscaling can be determined by a fraction, however, for this
395            # case the fraction gives the number of upscaling levels, 1 is all possible levels, 0 is no upscaling.
396            wght = self._unif((self.upscale['dim'][0], self.upscale['dim'][1]))
397
398        self.write_coarse(folder, wght, coarsenParam.reshape(
399            (int(self.upscale['dim'][0]), int(self.upscale['dim'][1]))))

This method utilized one field parameter to upscale the computational grid. A coarsening file is written to the ensemble folder, and the eclipse calculates the upscaled permeabilities, porosities and transmissibilities based on the new grid and the original parameter values.

Parameters
  • Folder (str, optional): Path to the ecl_100 run folder.
  • ensembleMember (int, optional): Index of the ensemble member to run.
Changelog
  • KF 17/9-2015 Added uniform upscaling as an option
  • KF 6/01-17
def write_coarse(self, folder, whgt, image):
401    def write_coarse(self, folder, whgt, image):
402        """
403        This function writes the include file coarsen to the ecl run. This file tels ECL to coarsen the grid.
404
405        Parameters
406        ----------
407        folder : str
408            Path to the ecl_100 run.
409
410        whgt : float
411            Weight of the transformed cells.
412
413        image : array-like
414            Original image.
415
416        Changelog
417        ---------
418        - KF 17/9-2015
419        """
420
421        well = self.upscale['wells']
422        radius = self.upscale['radius']
423
424        well_cells = self._nodeIndex(image.shape[1], image.shape[0], well, radius)
425        coarse = np.array([[True] * image.shape[1]] * image.shape[0])
426        tmp = coarse.flatten()
427        tmp[well_cells] = False
428        coarse = tmp.reshape(image.shape)
429
430        # f = open(folder + 'coarsen.dat', 'w+')
431        # f.write('COARSEN \n')
432        ecl_coarse = []
433
434        for level in range(len(whgt) - 1, -1, -1):
435            weight_at_level = whgt[level]
436            x_dim = weight_at_level.shape[0]
437            y_dim = weight_at_level.shape[1]
438
439            merged_at_level = weight_at_level[int(x_dim / 2):, :int(y_dim / 2)]
440
441            level_dim = 2 ** (level + 1)
442
443            for i in range(0, merged_at_level.shape[0]):
444                for j in range(0, merged_at_level.shape[1]):
445                    if merged_at_level[i, j] == 1:
446                        # Do this in two stages to avoid errors with the dimensions
447                        #
448                        # only merging square cells
449                        if (i + 1) * level_dim <= image.shape[0] and (j + 1) * level_dim <= image.shape[1]:
450                            if coarse[i * level_dim:(i + 1) * level_dim, j * level_dim:(j + 1) * level_dim].all():
451                                coarse[i * level_dim:(i + 1) * level_dim,
452                                       j * level_dim:(j + 1) * level_dim] = False
453                                ecl_coarse.append([j * level_dim + 1, (j + 1) * level_dim,
454                                                   i * level_dim + 1, (i + 1) * level_dim, 1, 1, 1, 1, 1])
455                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, (j + 1) * level_dim,
456                                #                                         i * level_dim + 1, (i + 1) * level_dim))
457                        # cells at first edge, non-square
458                        if (i + 1)*level_dim > image.shape[0] and (j + 1) * level_dim <= image.shape[1]:
459                            if coarse[i * level_dim::, j * level_dim:(j + 1) * level_dim].all():
460                                coarse[i * level_dim::, j *
461                                       level_dim:(j + 1) * level_dim] = False
462                                ecl_coarse.append([j * level_dim + 1, (j + 1) * level_dim,
463                                                   i * level_dim + 1, image.shape[0], 1, 1, 1, 1, 1])
464                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, (j + 1) * level_dim,
465                                #                                         i * level_dim + 1, image.shape[0]))
466                        # cells at second edge, non-square
467                        if (j + 1)*level_dim > image.shape[1] and (i + 1) * level_dim <= image.shape[0]:
468                            if coarse[i * level_dim:(i + 1) * level_dim, j * level_dim::].all():
469                                coarse[i * level_dim:(i + 1) * level_dim,
470                                       j * level_dim::] = False
471                                ecl_coarse.append([j * level_dim + 1, image.shape[1],
472                                                   i * level_dim + 1, (i + 1) * level_dim, 1, 1, 1, 1, 1])
473                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, image.shape[1],
474                                #                                         i * level_dim + 1, (i + 1) * level_dim))
475                        # cells at intersection between first and second edge, non-square
476                        if (i + 1) * level_dim > image.shape[0] and (j + 1) * level_dim > image.shape[1]:
477                            if coarse[i * level_dim::, j * level_dim::].all():
478                                coarse[i * level_dim::, j * level_dim::] = False
479                                ecl_coarse.append([j * level_dim + 1, image.shape[1],
480                                                   i * level_dim + 1, image.shape[0], 1, 1, 1, 1, 1])
481                                # f.write('%i %i %i %i 1 1 1 1 1 / \n' % (j * level_dim + 1, image.shape[1],
482                                #                                         i * level_dim + 1, image.shape[0]))
483        # f.write('/')
484        # f.close()
485        self.coarse = ecl_coarse

This function writes the include file coarsen to the ecl run. This file tels ECL to coarsen the grid.

Parameters
  • folder (str): Path to the ecl_100 run.
  • whgt (float): Weight of the transformed cells.
  • image (array-like): Original image.
Changelog
  • KF 17/9-2015
def get_sim_results(self, whichResponse, ext_data_info=None, member=None):
793    def get_sim_results(self, whichResponse, ext_data_info=None, member=None):
794        """
795        Read the output from simulator and store as an array. Optionally, if the DA method is based on an ensemble
796        method, the output must be read inside a folder.
797
798        Parameters
799        ----------
800        file_rsm : str
801            Summary file from ECL 100.
802
803        file_rst : str
804            Restart file from ECL 100.
805
806        whichResponse : str
807            Which of the responses is to be outputted (e.g., WBHP PRO-1, WOPR, PRESS, OILSAT, etc).
808
809        member : int, optional
810            Ensemble member that is finished.
811
812        ext_data_info : tuple, optional
813            Tuple containing the assimilation step information, including the place of assimilation (e.g., which TIME) and the
814            index of this assimilation place.
815
816        Returns
817        -------
818        yFlow : array-like
819            Array containing the response from ECL 100. The response type is chosen by the user in options['data_type'].
820
821        Notes
822        -----
823        - Modified the ecl package to allow reading the summary data directly, hence, we get cell, summary, and field data
824        from the ecl package.
825        - KF 29/10-2015
826        - Modified the ecl package to read RFT files. To obtain, e.g,. RFT pressures form well 'PRO-1" whichResponse
827        must be rft_PRESSURE PRO-1
828        """
829        # Check that we have no trailing spaces
830        whichResponse = whichResponse.strip()
831
832        # if ensemble DA method
833        if member is not None:
834            # Get results
835            if hasattr(self, 'ecl_case'):
836                # En_XX/YYYY.DATA is the folder setup
837                rt_mem = int(self.ecl_case.root.split('/')[0].split('_')[1])
838                if rt_mem != member:  # wrong case
839                    self.ecl_case = ecl.EclipseCase('En_' + str(member) + os.sep + self.file + '.DATA')
840            else:
841                self.ecl_case = ecl.EclipseCase('En_' + str(member) + os.sep + self.file + '.DATA')
842            if ext_data_info[0] == 'days':
843                time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
844                    dt.timedelta(days=ext_data_info[1])
845                dates = self.ecl_case.by_date
846                if time not in dates and 'date_slack' in self.input_dict:
847                    slack = int(self.input_dict['date_slack'])
848                    if slack > 0:
849                        v = [el for el in dates if np.abs(
850                            (el-time).total_seconds()) < slack]
851                        if len(v) > 0:
852                            time = v[0]
853            else:
854                time = ext_data_info[1]
855
856            # Check if the data is a field or well data, by checking if the well is defined
857            if len(whichResponse.split(' ')) == 2:
858                # if rft, search for rft_
859                if 'rft_' in whichResponse:
860                    # to speed up when performing the prediction step
861                    if hasattr(self, 'rft_case'):
862                        rt_mem = int(self.rft_case.root.split('/')[0].split('_')[1])
863                        if rt_mem != member:
864                            self.rft_case = ecl.EclipseRFT('En_' + str(member) + os.sep + self.file)
865                    else:
866                        self.rft_case = ecl.EclipseRFT(
867                            'En_' + str(member) + os.sep + self.file)
868                    # Get the data. Due to formating we can slice the property.
869                    rft_prop = self.rft_case.rft_data(well=whichResponse.split(
870                        ' ')[1], prop=whichResponse.split(' ')[0][4:])
871                    # rft_data are collected for open connections. This may vary throughout the simulation, hence we
872                    # must also collect the depth for the rft_data to check if all data is present
873                    rft_depth = self.rft_case.rft_data(
874                        well=whichResponse.split(' ')[1], prop='DEPTH')
875                    # to check this we import the referance depth if this is available. If not we assume that the data
876                    # is ok.
877                    try:
878                        ref_depth_f = np.load(whichResponse.split(
879                            ' ')[1].upper() + '_rft_ref_depth.npz')
880                        ref_depth = ref_depth_f[ref_depth_f.files[0]]
881                        yFlow = np.array([])
882                        interp = interpolate.interp1d(rft_depth, rft_prop, kind='linear', bounds_error=False,
883                                                      fill_value=(rft_prop[0], rft_prop[-1]))
884                        for d in ref_depth:
885                            yFlow = np.append(yFlow, interp(d))
886                    except:
887                        yFlow = rft_prop
888                else:
889                    # If well, read the rsm file
890                    if ext_data_info is not None:  # Get the data at a specific well and time
891                        yFlow = self.ecl_case.summary_data(whichResponse, time)
892            elif len(whichResponse.split(' ')) == 1:  # field data
893                if whichResponse.upper() in ['FOPT', 'FWPT', 'FGPT', 'FWIT', 'FGIT']:
894                    if ext_data_info is not None:
895                        yFlow = self.ecl_case.summary_data(whichResponse, time)
896                elif whichResponse.upper() in ['PERMX', 'PERMY', 'PERMZ', 'PORO', 'NTG', 'SATNUM',
897                                               'MULTNUM', 'OPERNUM']:
898                    yFlow = np.array([self.ecl_case.cell_data(whichResponse).flatten()[time]]) # assume that time is the index
899                else:
900                    yFlow = self.ecl_case.cell_data(whichResponse, time).flatten()
901                    if yFlow is None:
902                        yFlow = self.ecl_case.cell_data(whichResponse).flatten()
903
904            # store the run time. NB: elapsed must be defined in .DATA file for this to work
905            if 'save_elapsed' in self.input_dict and len(self.run_time) <= member:
906                self.run_time.extend(self.ecl_case.summary_data('ELAPSED', time))
907
908            # If we have performed coarsening, we store the number of active grid-cells
909            if self.upscale is not None:
910                # Get this number from INIT file
911                with ecl.EclipseFile('En_' + str(member) + os.sep + self.file, 'INIT') as case:
912                    intHead = case.get('INTEHEAD')
913                # The active cell is element 12 of this vector, index 11 in python indexing...
914                active_cells = intHead[11]
915                if len(self.num_act) <= member:
916                    self.num_act.extend([active_cells])
917
918        else:
919            case = ecl.EclipseCase(self.file + '.DATA')
920            if ext_data_info[0] == 'days':
921                time = dt.datetime(self.startDate['year'], self.startDate['month'], self.startDate['day']) + \
922                    dt.timedelta(days=ext_data_info[1])
923            else:
924                time = ext_data_info[1]
925
926            # Check if the data is a field or well data, by checking if the well is defined
927            if len(whichResponse.split(' ')) == 2:
928                # if rft, search for rft_
929                if 'rft_' in whichResponse:
930                    rft_case = ecl.EclipseRFT(self.file)
931                    # Get the data. Due to formating we can slice the property.
932                    rft_prop = rft_case.rft_data(well=whichResponse.split(
933                        ' ')[1], prop=whichResponse.split(' ')[0][4:])
934                    # rft_data are collected for open connections. This may vary throughout the simulation, hence we
935                    # must also collect the depth for the rft_data to check if all data is present
936                    rft_depth = rft_case.rft_data(
937                        well=whichResponse.split(' ')[1], prop='DEPTH')
938                    try:
939                        ref_depth_f = np.load(whichResponse.split(
940                            ' ')[1].upper() + '_rft_ref_depth.npz')
941                        ref_depth = ref_depth_f[ref_depth_f.files[0]]
942                        yFlow = np.array([])
943                        interp = interpolate.interp1d(rft_depth, rft_prop, kind='linear', bounds_error=False,
944                                                      fill_value=(rft_prop[0], rft_prop[-1]))
945                        for d in ref_depth:
946                            yFlow = np.append(yFlow, interp(d))
947                    except:
948                        yFlow = rft_prop
949                else:
950                    # If well, read the rsm file
951                    if ext_data_info is not None:  # Get the data at a specific well and time
952                        yFlow = case.summary_data(whichResponse, time)
953            elif len(whichResponse.split(' ')) == 1:
954                if whichResponse in ['FOPT', 'FWPT', 'FGPT', 'FWIT', 'FGIT']:
955                    if ext_data_info is not None:
956                        yFlow = case.summary_data(whichResponse, time)
957                else:
958                    yFlow = case.cell_data(whichResponse, time).flatten()
959                    if yFlow is None:
960                        yFlow = case.cell_data(whichResponse).flatten()
961
962            # If we have performed coarsening, we store the number of active grid-cells
963            if self.upscale is not None:
964                # Get this number from INIT file
965                with ecl.EclipseFile('En_' + str(member) + os.sep + self.file,'INIT') as case:
966                    intHead = case.get('INTEHEAD')
967                # The active cell is element 12 of this vector, index 11 in python indexing...
968                active_cells = intHead[11]
969                if len(self.num_act) <= member:
970                    self.num_act.extend([active_cells])
971
972        return yFlow

Read the output from simulator and store as an array. Optionally, if the DA method is based on an ensemble method, the output must be read inside a folder.

Parameters
  • file_rsm (str): Summary file from ECL 100.
  • file_rst (str): Restart file from ECL 100.
  • whichResponse (str): Which of the responses is to be outputted (e.g., WBHP PRO-1, WOPR, PRESS, OILSAT, etc).
  • member (int, optional): Ensemble member that is finished.
  • ext_data_info (tuple, optional): Tuple containing the assimilation step information, including the place of assimilation (e.g., which TIME) and the index of this assimilation place.
Returns
  • yFlow (array-like): Array containing the response from ECL 100. The response type is chosen by the user in options['data_type'].
Notes
  • Modified the ecl package to allow reading the summary data directly, hence, we get cell, summary, and field data from the ecl package.
  • KF 29/10-2015
  • Modified the ecl package to read RFT files. To obtain, e.g,. RFT pressures form well 'PRO-1" whichResponse must be rft_PRESSURE PRO-1
def store_fwd_debug(self, assimstep):
974    def store_fwd_debug(self, assimstep):
975        if 'fwddebug' in self.keys_fwd:
976            # Init dict. of variables to save
977            save_dict = {}
978
979            # Make sure "ANALYSISDEBUG" gives a list
980            if isinstance(self.keys_fwd['fwddebug'], list):
981                debug = self.keys_fwd['fwddebug']
982            else:
983                debug = [self.keys_fwd['fwddebug']]
984
985            # Loop over variables to store in save list
986            for var in debug:
987                # Save with key equal variable name and the actual variable
988                if isinstance(eval('self.' + var), dict):
989                    # save directly
990                    np.savez('fwd_debug_%s_%i' % (var, assimstep), **eval('self.' + var))
991                else:
992                    save_dict[var] = eval('self.' + var)
993
994            np.savez('fwd_debug_%i' % (assimstep), **save_dict)
def write_to_grid(self, value, propname, path, dim, t_ind=None):
 996    def write_to_grid(self, value, propname, path, dim, t_ind=None):
 997        if t_ind == None:
 998            trans_dict = {}
 999
1000            def _lookup(kw):
1001                return trans_dict[kw] if kw in trans_dict else kw
1002
1003            # Write a quantity to the grid as a grdecl file
1004            with open(path + propname + '.grdecl', 'wb') as fileobj:
1005                grdecl._write_kw(fileobj, propname, value, _lookup, dim)
1006        else:
1007            pass
1008            # some errors with rips
1009            # p = Process(target=_write_to_resinsight, args=(list(value[~value.mask]),propname, t_ind))
1010            # Find an open resinsight case
1011            # p.start()
1012            # time.sleep(1)
1013            # p.terminate()
class ecl_100(eclipse):
1021class ecl_100(eclipse):
1022    '''
1023    ecl_100 class
1024    '''
1025
1026    def call_sim(self, path=None, wait_for_proc=False):
1027        """
1028        Method for calling the ecl_100 simulator.
1029
1030        Parameters
1031        ----------
1032        Path : str
1033            Alternative folder for the ecl_100.data file.
1034
1035        Wait_for_proc : bool, optional
1036            Logical variable to wait for the simulator to finish. Default is False.
1037
1038        Returns
1039        -------
1040        .RSM : str
1041            Run summary file in the standard ECL format. Well data are collected from this file.
1042
1043        .RST : str
1044            Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
1045
1046        .PRT : str
1047            Info file to be used for checking errors in the run.
1048
1049
1050        Changelog
1051        ---------
1052        - KF 14/9-2015
1053        """
1054
1055        # Filename
1056        if path is not None:
1057            filename = path + self.file
1058        else:
1059            filename = self.file
1060
1061        # Run the simulator:
1062        success = True
1063        try:
1064            with EclipseRunEnvironment(filename):
1065                com = ['eclrun', '--nocleanup', 'eclipse', filename + '.DATA']
1066                if 'sim_limit' in self.options:
1067                    call(com, stdout=DEVNULL, timeout=self.options['sim_limit'])
1068                else:
1069                    call(com, stdout=DEVNULL)
1070                raise ValueError
1071        except:
1072            print('\nError in the eclipse run.')  # add rerun?
1073            if not os.path.exists('Crashdump'):
1074                copytree(path, 'Crashdump')
1075            success = False
1076
1077        return success

ecl_100 class

def call_sim(self, path=None, wait_for_proc=False):
1026    def call_sim(self, path=None, wait_for_proc=False):
1027        """
1028        Method for calling the ecl_100 simulator.
1029
1030        Parameters
1031        ----------
1032        Path : str
1033            Alternative folder for the ecl_100.data file.
1034
1035        Wait_for_proc : bool, optional
1036            Logical variable to wait for the simulator to finish. Default is False.
1037
1038        Returns
1039        -------
1040        .RSM : str
1041            Run summary file in the standard ECL format. Well data are collected from this file.
1042
1043        .RST : str
1044            Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
1045
1046        .PRT : str
1047            Info file to be used for checking errors in the run.
1048
1049
1050        Changelog
1051        ---------
1052        - KF 14/9-2015
1053        """
1054
1055        # Filename
1056        if path is not None:
1057            filename = path + self.file
1058        else:
1059            filename = self.file
1060
1061        # Run the simulator:
1062        success = True
1063        try:
1064            with EclipseRunEnvironment(filename):
1065                com = ['eclrun', '--nocleanup', 'eclipse', filename + '.DATA']
1066                if 'sim_limit' in self.options:
1067                    call(com, stdout=DEVNULL, timeout=self.options['sim_limit'])
1068                else:
1069                    call(com, stdout=DEVNULL)
1070                raise ValueError
1071        except:
1072            print('\nError in the eclipse run.')  # add rerun?
1073            if not os.path.exists('Crashdump'):
1074                copytree(path, 'Crashdump')
1075            success = False
1076
1077        return success

Method for calling the ecl_100 simulator.

Parameters
  • Path (str): Alternative folder for the ecl_100.data file.
  • Wait_for_proc (bool, optional): Logical variable to wait for the simulator to finish. Default is False.
Returns
  • .RSM (str): Run summary file in the standard ECL format. Well data are collected from this file.
  • .RST (str): Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
  • .PRT (str): Info file to be used for checking errors in the run.
Changelog
  • KF 14/9-2015
class ecl_300(eclipse):
1080class ecl_300(eclipse):
1081    '''
1082    eclipse 300 class
1083    '''
1084
1085    def call_sim(self, path=None, wait_for_proc=False):
1086        """
1087        Method for calling the ecl_300 simulator.
1088
1089        Parameters
1090        ----------
1091        Path : str
1092            Alternative folder for the ecl_100.data file.
1093
1094        Wait_for_proc : bool, optional
1095            Logical variable to wait for the simulator to finish. Default is False.
1096
1097        NOTE: For now, this option is only utilized in a single localization option.
1098
1099        Returns
1100        -------
1101        RSM : str
1102            Run summary file in the standard ECL format. Well data are collected from this file.
1103
1104        RST : str
1105            Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
1106
1107        PRT : str
1108            Info file to be used for checking errors in the run.
1109
1110        Changelog
1111        ---------
1112        - KF 8/10-2015
1113
1114
1115        """
1116        # Filename
1117        if path is not None:
1118            filename = path + self.file
1119        else:
1120            filename = self.file
1121
1122        # Run the simulator:
1123        with EclipseRunEnvironment(filename):
1124            call(['eclrun', '--nocleanup', 'e300', filename + '.DATA'], stdout=DEVNULL)

eclipse 300 class

def call_sim(self, path=None, wait_for_proc=False):
1085    def call_sim(self, path=None, wait_for_proc=False):
1086        """
1087        Method for calling the ecl_300 simulator.
1088
1089        Parameters
1090        ----------
1091        Path : str
1092            Alternative folder for the ecl_100.data file.
1093
1094        Wait_for_proc : bool, optional
1095            Logical variable to wait for the simulator to finish. Default is False.
1096
1097        NOTE: For now, this option is only utilized in a single localization option.
1098
1099        Returns
1100        -------
1101        RSM : str
1102            Run summary file in the standard ECL format. Well data are collected from this file.
1103
1104        RST : str
1105            Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
1106
1107        PRT : str
1108            Info file to be used for checking errors in the run.
1109
1110        Changelog
1111        ---------
1112        - KF 8/10-2015
1113
1114
1115        """
1116        # Filename
1117        if path is not None:
1118            filename = path + self.file
1119        else:
1120            filename = self.file
1121
1122        # Run the simulator:
1123        with EclipseRunEnvironment(filename):
1124            call(['eclrun', '--nocleanup', 'e300', filename + '.DATA'], stdout=DEVNULL)

Method for calling the ecl_300 simulator.

Parameters
  • Path (str): Alternative folder for the ecl_100.data file.
  • Wait_for_proc (bool, optional): Logical variable to wait for the simulator to finish. Default is False.
  • NOTE (For now, this option is only utilized in a single localization option.):
Returns
  • RSM (str): Run summary file in the standard ECL format. Well data are collected from this file.
  • RST (str): Restart file in the standard ECL format. Pressure and saturation data are collected from this file.
  • PRT (str): Info file to be used for checking errors in the run.
Changelog
  • KF 8/10-2015