class EclSumKeyWordVector(BaseCClass): TYPE_NAME = "ecl_sum_vector" _alloc = EclPrototype("void* ecl_sum_vector_alloc(ecl_sum)", bind=False) _free = EclPrototype("void ecl_sum_vector_free(ecl_sum_vector)") _add = EclPrototype("bool ecl_sum_vector_add_key(ecl_sum_vector, char*)") _add_multiple = EclPrototype( "void ecl_sum_vector_add_keys(ecl_sum_vector, char*)") _get_size = EclPrototype("int ecl_sum_vector_get_size(ecl_sum_vector)") def __init__(self, ecl_sum): c_pointer = self._alloc(ecl_sum) super(EclSumKeyWordVector, self).__init__(c_pointer) def __len__(self): return self._get_size() def free(self): self._free() def add_keyword(self, keyword): success = self._add(keyword) if not success: raise KeyError("Failed to add keyword to vector") def add_keywords(self, keyword_pattern): self._add_multiple(keyword_pattern) def __repr__(self): return self._create_repr('len=%d' % len(self))
class EclUtil(object): _get_num_cpu = EclPrototype("int ecl_util_get_num_cpu( char* )", bind=False) _get_file_type = EclPrototype( "ecl_file_enum ecl_util_get_file_type( char* , bool* , int*)", bind=False) _get_start_date = EclPrototype("time_t ecl_util_get_start_date( char* )", bind=False) _get_report_step = EclPrototype("int ecl_util_filename_report_nr( char* )", bind=False) @staticmethod def get_num_cpu(datafile): """ Parse ECLIPSE datafile and determine how many CPUs are needed. Will look for the "PARALLELL" keyword, and then read off the number of CPUs required. Will return one if no PARALLELL keyword is found. """ return EclUtil._get_num_cpu(datafile) @staticmethod def get_file_type(filename): """ Will inspect an ECLIPSE filename and return an integer type flag. """ file_type, fmt, step = EclUtil.inspectExtension(filename) return file_type @staticmethod def get_start_date(datafile): return EclUtil._get_start_date(datafile).datetime() @staticmethod def inspectExtension(filename): """Will inspect an ECLIPSE filename and return a tuple consisting of file type (EclFileEnum), a bool for formatted or not, and an integer for the step number. """ fmt_file = ctypes.c_bool() report_step = ctypes.c_int(-1) file_type = EclUtil._get_file_type(filename, ctypes.byref(fmt_file), ctypes.byref(report_step)) if report_step.value == -1: step = None else: step = report_step.value return (file_type, fmt_file.value, step) @staticmethod def reportStep(filename): report_step = EclUtil._get_report_step(filename) if report_step < 0: raise ValueError("Could not infer report step from: %s" % filename) return report_step
class FaultBlockCollection(BaseCClass): TYPE_NAME = "fault_block_collection" _alloc = EclPrototype("void* fault_block_collection_alloc(ecl_grid)", bind=False) _free = EclPrototype("void fault_block_collection_free(fault_block_collection)") _num_layers = EclPrototype("int fault_block_collection_num_layers(fault_block_collection)") _scan_keyword = EclPrototype("bool fault_block_collection_scan_kw(fault_block_collection, ecl_kw)") _get_layer = EclPrototype("fault_block_layer_ref fault_block_collection_get_layer(fault_block_collection, int)") def __init__(self, grid): c_ptr = self._alloc(grid) if c_ptr: super(FaultBlockCollection, self).__init__(c_ptr) else: raise ValueError("Invalid input - failed to create FaultBlockCollection") # The underlying C implementation uses lazy evaluation and # needs to hold on to the grid reference. We therefor take # references to it here, to protect against premature garbage # collection. self.grid_ref = grid def __len__(self): return self._num_layers() def __repr__(self): return self._create_repr('len=%s' % len(self)) def __getitem__(self, index): """ @rtype: FaultBlockLayer """ if isinstance(index, int): if 0 <= index < len(self): return self._get_layer(index).setParent(self) else: raise IndexError("Index:%d out of range [0,%d)" % (index, len(self))) else: raise TypeError("Index should be integer type") def get_layer(self, k): """ @rtype: FaultBlockLayer """ return self[k] def free(self): self._free() def scan_keyword(self, fault_block_kw): ok = self._scan_keyword(fault_block_kw) if not ok: raise ValueError("The fault block keyword had wrong type/size")
class EclSumTStep(BaseCClass): TYPE_NAME = "ecl_sum_tstep" _alloc = EclPrototype( "void* ecl_sum_tstep_alloc_new(int, int, float, void*)", bind=False) _free = EclPrototype("void ecl_sum_tstep_free(ecl_sum_tstep)") _get_sim_days = EclPrototype( "double ecl_sum_tstep_get_sim_days(ecl_sum_tstep)") _get_sim_time = EclPrototype( "time_t ecl_sum_tstep_get_sim_time(ecl_sum_tstep)") _get_report = EclPrototype("int ecl_sum_tstep_get_report(ecl_sum_tstep)") _get_ministep = EclPrototype( "int ecl_sum_tstep_get_ministep(ecl_sum_tstep)") _set_from_key = EclPrototype( "void ecl_sum_tstep_set_from_key(ecl_sum_tstep, char*, float)") _get_from_key = EclPrototype( "double ecl_sum_tstep_get_from_key(ecl_sum_tstep, char*)") _has_key = EclPrototype("bool ecl_sum_tstep_has_key(ecl_sum_tstep)") def __init__(self, report_step, mini_step, sim_days, smspec): sim_seconds = sim_days * 24 * 60 * 60 c_pointer = self._alloc(report_step, mini_step, sim_seconds, smspec) super(EclSumTStep, self).__init__(c_pointer) def get_sim_days(self): """ @rtype: double """ return self._get_sim_days() def get_report(self): """ @rtype: int """ return self._get_report() def get_mini_step(self): """ @rtype: int """ return self._get_ministep() def get_sim_time(self): """ @rtype: CTime """ return self._get_sim_time() def __getitem__(self, key): """ @rtype: double """ if not key in self: raise KeyError("Key '%s' is not available." % key) return self._get_from_key(key) def __setitem__(self, key, value): if not key in self: raise KeyError("Key '%s' is not available." % key) self._set_from_key(key, value) def __contains__(self, key): return self._has_key(key) def free(self): self._free(self)
class EclRestartHead(BaseCClass): TYPE_NAME = "ecl_rsthead" _alloc = EclPrototype("void* ecl_rsthead_alloc(ecl_file_view , int )", bind=False) _alloc_from_kw = EclPrototype( "void* ecl_rsthead_alloc_from_kw(int , ecl_kw , ecl_kw , ecl_kw )", bind=False) _free = EclPrototype("void ecl_rsthead_free(ecl_rsthead)") _get_report_step = EclPrototype( "int ecl_rsthead_get_report_step(ecl_rsthead)") _get_sim_time = EclPrototype( "time_t ecl_rsthead_get_sim_time(ecl_rsthead)") _get_sim_days = EclPrototype( "double ecl_rsthead_get_sim_days(ecl_rsthead)") _get_nxconz = EclPrototype("int ecl_rsthead_get_nxconz(ecl_rsthead)") _get_ncwmax = EclPrototype("int ecl_rsthead_get_ncwmax(ecl_rsthead)") def __init__(self, kw_arg=None, rst_view=None): if kw_arg is None and rst_view is None: raise ValueError( 'Cannot construct EclRestartHead without one of kw_arg and rst_view, both were None!' ) if not kw_arg is None: report_step, intehead_kw, doubhead_kw, logihead_kw = kw_arg c_ptr = self._alloc_from_kw(report_step, intehead_kw, doubhead_kw, logihead_kw) else: c_ptr = self._alloc(rst_view, -1) super(EclRestartHead, self).__init__(c_ptr) def free(self): self._free() def get_report_step(self): return self._get_report_step() def get_sim_date(self): ct = CTime(self._get_sim_time()) return ct.datetime() def get_sim_days(self): return self._get_sim_days() def well_details(self): return {"NXCONZ": self._get_nxconz(), "NCWMAX": self._get_ncwmax()}
class EclRFTCell(RFTCell): TYPE_NAME = "ecl_rft_cell" _alloc_RFT = EclPrototype( "void* ecl_rft_cell_alloc_RFT( int, int , int , double , double , double , double)", bind=False) _get_swat = EclPrototype("double ecl_rft_cell_get_swat( ecl_rft_cell )") _get_soil = EclPrototype("double ecl_rft_cell_get_soil( ecl_rft_cell )") _get_sgas = EclPrototype("double ecl_rft_cell_get_sgas( ecl_rft_cell )") def __init__(self, i, j, k, depth, pressure, swat, sgas): c_ptr = self._alloc_RFT(i, j, k, depth, pressure, swat, sgas) super(EclRFTCell, self).__init__(c_ptr) @property def swat(self): return self._get_swat() @property def sgas(self): return self._get_sgas() @property def soil(self): return 1 - (self._get_sgas() + self._get_swat())
class RFTCell(BaseCClass): """The RFTCell is a base class for the cells which are part of an RFT/PLT. The RFTCell class contains the elements which are common to both RFT and PLT. The list of common elements include the corrdinates (i,j,k) the pressure and the depth of the cell. Actual user access should be based on the derived classes EclRFTCell and EclPLTCell. Observe that from june 2013 the properties i,j and k which return offset 1 coordinate values are deprecated, and you should rather use the methods get_i(), get_j() and get_k() which return offset 0 coordinate values. """ TYPE_NAME = "rft_cell" _free = EclPrototype("void ecl_rft_cell_free( rft_cell )") _get_pressure = EclPrototype( "double ecl_rft_cell_get_pressure( rft_cell )") _get_depth = EclPrototype("double ecl_rft_cell_get_depth( rft_cell )") _get_i = EclPrototype("int ecl_rft_cell_get_i( rft_cell )") _get_j = EclPrototype("int ecl_rft_cell_get_j( rft_cell )") _get_k = EclPrototype("int ecl_rft_cell_get_k( rft_cell )") def free(self): self._free() def get_i(self): return self._get_i() def get_j(self): return self._get_j() def get_k(self): return self._get_k() def get_ijk(self): return (self.get_i(), self.get_j(), self.get_k()) @property def pressure(self): return self._get_pressure() @property def depth(self): return self._get_depth()
class EclFileView(BaseCClass): TYPE_NAME = "ecl_file_view" _iget_kw = EclPrototype( "ecl_kw_ref ecl_file_view_iget_kw( ecl_file_view , int)") _iget_named_kw = EclPrototype( "ecl_kw_ref ecl_file_view_iget_named_kw( ecl_file_view , char* , int)" ) _get_size = EclPrototype( "int ecl_file_view_get_size( ecl_file_view )") _get_num_named_kw = EclPrototype( "int ecl_file_view_get_num_named_kw( ecl_file_view , char* )" ) _get_unique_size = EclPrototype( "int ecl_file_view_get_num_distinct_kw( ecl_file_view )") _create_block_view = EclPrototype( "ecl_file_view_ref ecl_file_view_add_blockview( ecl_file_view , char*, int )" ) _create_block_view2 = EclPrototype( "ecl_file_view_ref ecl_file_view_add_blockview2( ecl_file_view , char*, char*, int )" ) _restart_view = EclPrototype( "ecl_file_view_ref ecl_file_view_add_restart_view( ecl_file_view , int, int, time_t, double )" ) def __init__(self): raise NotImplementedError("Can not instantiate directly") def __iget(self, index): return self._iget_kw(index).setParent(parent=self) def __repr__(self): return 'EclFileView(size=%d) %s' % (len(self), self._ad_str()) def iget_named_kw(self, kw_name, index): if not kw_name in self: raise KeyError("No such keyword: %s" % kw_name) if index >= self.numKeywords(kw_name): raise IndexError("Too large index: %d" % index) return self._iget_named_kw(kw_name, index).setParent(parent=self) def __getitem__(self, index): """ Implements [] operator; index can be integer or key. Will look up EclKW instances from the current EclFile instance. The @index argument can either be an integer, in which case the method will return EclKW number @index, or alternatively a keyword string, in which case the method will return a list of EclKW instances with that keyword: restart_file = ecl_file.EclFile("ECLIPSE.UNRST") kw9 = restart_file[9] swat_list = restart_file["SWAT"] The keyword based lookup can be combined with an extra [] to get EclKW instance nr: swat9 = restart_file["SWAT"][9] Will return the 10'th SWAT keyword from the restart file. The following example will iterate over all the SWAT keywords in a restart file: restart_file = ecl_file.EclFile("ECLIPSE.UNRST") for swat in restart_file["SWAT"]: .... """ if isinstance(index, int): ls = len(self) idx = index if idx < 0: idx += ls if 0 <= idx < ls: return self.__iget(idx) else: raise IndexError('Index must be in [0, %d), was: %d.' % (ls, index)) if isinstance(index, slice): indices = index.indices(len(self)) kw_list = [] for i in range(*indices): kw_list.append(self[i]) return kw_list else: if isinstance(index, bytes): index = index.decode('ascii') if isinstance(index, string_types): if index in self: kw_index = index kw_list = [] for index in range(self.numKeywords(kw_index)): kw_list.append(self.iget_named_kw(kw_index, index)) return kw_list else: raise KeyError("Unrecognized keyword:\'%s\'" % index) else: raise TypeError("Index must be integer or string (keyword)") def __len__(self): return self._get_size() def __contains__(self, kw): if self.numKeywords(kw) > 0: return True else: return False def num_keywords(self, kw): return self._get_num_named_kw(kw) def unique_size(self): return self._get_unique_size() def block_view2(self, start_kw, stop_kw, start_index): idx = start_index if start_kw: if not start_kw in self: raise KeyError("The keyword:%s is not in file" % start_kw) ls = self.numKeywords(start_kw) if idx < 0: idx += ls if not (0 <= idx < ls): raise IndexError('Index must be in [0, %d), was: %d.' % (ls, start_index)) if stop_kw: if not stop_kw in self: raise KeyError("The keyword:%s is not in file" % stop_kw) view = self._create_block_view2(start_kw, stop_kw, idx) view.setParent(parent=self) return view def block_view(self, kw, kw_index): num = self.numKeywords(kw) if num == 0: raise KeyError("Unknown keyword: %s" % kw) idx = kw_index if idx < 0: idx += num if not (0 <= idx < num): raise IndexError('Index must be in [0, %d), was: %d.' % (num, kw_index)) view = self._create_block_view(kw, kw_index) view.setParent(parent=self) return view def restart_view(self, seqnum_index=None, report_step=None, sim_time=None, sim_days=None): if report_step is None: report_step = -1 if sim_time is None: sim_time = -1 if sim_days is None: sim_days = -1 if seqnum_index is None: seqnum_index = -1 view = self._restart_view(seqnum_index, report_step, CTime(sim_time), sim_days) if view is None: raise ValueError("No such restart block could be identiefied") view.setParent(parent=self) return view
class EclRegion(BaseCClass): TYPE_NAME = "ecl_region" _alloc = EclPrototype("void* ecl_region_alloc( ecl_grid , bool )", bind=False) _alloc_copy = EclPrototype( "ecl_region_obj ecl_region_alloc_copy( ecl_region )") _set_kw_int = EclPrototype( "void ecl_region_set_kw_int( ecl_region , ecl_kw , int, bool) ") _set_kw_float = EclPrototype( "void ecl_region_set_kw_float( ecl_region , ecl_kw , float, bool ) ") _set_kw_double = EclPrototype( "void ecl_region_set_kw_double( ecl_region , ecl_kw , double , bool) ") _shift_kw_int = EclPrototype( "void ecl_region_shift_kw_int( ecl_region , ecl_kw , int, bool) ") _shift_kw_float = EclPrototype( "void ecl_region_shift_kw_float( ecl_region , ecl_kw , float, bool ) ") _shift_kw_double = EclPrototype( "void ecl_region_shift_kw_double( ecl_region , ecl_kw , double , bool) " ) _scale_kw_int = EclPrototype( "void ecl_region_scale_kw_int( ecl_region , ecl_kw , int, bool) ") _scale_kw_float = EclPrototype( "void ecl_region_scale_kw_float( ecl_region , ecl_kw , float, bool ) ") _scale_kw_double = EclPrototype( "void ecl_region_scale_kw_double( ecl_region , ecl_kw , double , bool) " ) _free = EclPrototype("void ecl_region_free( ecl_region )") _reset = EclPrototype("void ecl_region_reset( ecl_region )") _select_all = EclPrototype("void ecl_region_select_all( ecl_region )") _deselect_all = EclPrototype("void ecl_region_deselect_all( ecl_region )") _select_equal = EclPrototype( "void ecl_region_select_equal( ecl_region , ecl_kw , int )") _deselect_equal = EclPrototype( "void ecl_region_deselect_equal( ecl_region , ecl_kw , int)") _select_less = EclPrototype( "void ecl_region_select_smaller( ecl_region , ecl_kw , float )") _deselect_less = EclPrototype( "void ecl_region_deselect_smaller( ecl_region , ecl_kw , float )") _select_more = EclPrototype( "void ecl_region_select_larger( ecl_region , ecl_kw , float )") _deselect_more = EclPrototype( "void ecl_region_deselect_larger( ecl_region , ecl_kw , float )") _select_in_interval = EclPrototype( "void ecl_region_select_in_interval( ecl_region, ecl_kw , float , float )" ) _deselect_in_interval = EclPrototype( "void ecl_region_deselect_in_interval( ecl_region, ecl_kw, float , float )" ) _invert_selection = EclPrototype( "void ecl_region_invert_selection( ecl_region )") _select_box = EclPrototype( "void ecl_region_select_from_ijkbox(ecl_region , int , int , int , int , int , int)" ) _deselect_box = EclPrototype( "void ecl_region_deselect_from_ijkbox(ecl_region , int , int , int , int , int , int)" ) _imul_kw = EclPrototype( "void ecl_region_kw_imul( ecl_region , ecl_kw , ecl_kw , bool)") _idiv_kw = EclPrototype( "void ecl_region_kw_idiv( ecl_region , ecl_kw , ecl_kw , bool)") _iadd_kw = EclPrototype( "void ecl_region_kw_iadd( ecl_region , ecl_kw , ecl_kw , bool)") _isub_kw = EclPrototype( "void ecl_region_kw_isub( ecl_region , ecl_kw , ecl_kw , bool)") _copy_kw = EclPrototype( "void ecl_region_kw_copy( ecl_region , ecl_kw , ecl_kw , bool)") _intersect = EclPrototype( "void ecl_region_intersection( ecl_region , ecl_region )") _combine = EclPrototype("void ecl_region_union( ecl_region , ecl_region )") _subtract = EclPrototype( "void ecl_region_subtract( ecl_region , ecl_region )") _xor = EclPrototype("void ecl_region_xor( ecl_region , ecl_region )") _get_kw_index_list = EclPrototype( "int_vector_ref ecl_region_get_kw_index_list( ecl_region , ecl_kw , bool )" ) _get_active_list = EclPrototype( "int_vector_ref ecl_region_get_active_list( ecl_region )") _get_global_list = EclPrototype( "int_vector_ref ecl_region_get_global_list( ecl_region )") _get_active_global = EclPrototype( "int_vector_ref ecl_region_get_global_active_list( ecl_region )") _select_cmp_less = EclPrototype( "void ecl_region_cmp_select_less( ecl_region , ecl_kw , ecl_kw)") _select_cmp_more = EclPrototype( "void ecl_region_cmp_select_more( ecl_region , ecl_kw , ecl_kw)") _deselect_cmp_less = EclPrototype( "void ecl_region_cmp_deselect_less( ecl_region , ecl_kw , ecl_kw)") _deselect_cmp_more = EclPrototype( "void ecl_region_cmp_deselect_more( ecl_region , ecl_kw , ecl_kw)") _select_islice = EclPrototype( "void ecl_region_select_i1i2( ecl_region , int , int )") _deselect_islice = EclPrototype( "void ecl_region_deselect_i1i2( ecl_region , int , int )") _select_jslice = EclPrototype( "void ecl_region_select_j1j2( ecl_region , int , int )") _deselect_jslice = EclPrototype( "void ecl_region_deselect_j1j2( ecl_region , int , int )") _select_kslice = EclPrototype( "void ecl_region_select_k1k2( ecl_region , int , int )") _deselect_kslice = EclPrototype( "void ecl_region_deselect_k1k2( ecl_region , int , int )") _select_deep_cells = EclPrototype( "void ecl_region_select_deep_cells( ecl_region , double )") _deselect_deep_cells = EclPrototype( "void ecl_region_select_deep_cells( ecl_region , double )") _select_shallow_cells = EclPrototype( "void ecl_region_select_shallow_cells( ecl_region , double )") _deselect_shallow_cells = EclPrototype( "void ecl_region_select_shallow_cells( ecl_region , double )") _select_small = EclPrototype( "void ecl_region_select_small_cells( ecl_region , double )") _deselect_small = EclPrototype( "void ecl_region_deselect_small_cells( ecl_region , double )") _select_large = EclPrototype( "void ecl_region_select_large_cells( ecl_region , double )") _deselect_large = EclPrototype( "void ecl_region_deselect_large_cells( ecl_region , double )") _select_thin = EclPrototype( "void ecl_region_select_thin_cells( ecl_region , double )") _deselect_thin = EclPrototype( "void ecl_region_deselect_thin_cells( ecl_region , double )") _select_thick = EclPrototype( "void ecl_region_select_thick_cells( ecl_region , double )") _deselect_thick = EclPrototype( "void ecl_region_deselect_thick_cells( ecl_region , double )") _select_active = EclPrototype( "void ecl_region_select_active_cells( ecl_region )") _select_inactive = EclPrototype( "void ecl_region_select_inactive_cells( ecl_region )") _deselect_active = EclPrototype( "void ecl_region_deselect_active_cells( ecl_region )") _deselect_inactive = EclPrototype( "void ecl_region_deselect_inactive_cells( ecl_region )") _select_above_plane = EclPrototype( "void ecl_region_select_above_plane( ecl_region , double* , double* )" ) _select_below_plane = EclPrototype( "void ecl_region_select_below_plane( ecl_region , double* , double* )" ) _deselect_above_plane = EclPrototype( "void ecl_region_deselect_above_plane( ecl_region, double* , double* )" ) _deselect_below_plane = EclPrototype( "void ecl_region_deselect_below_plane( ecl_region, double* , double* )" ) _select_inside_polygon = EclPrototype( "void ecl_region_select_inside_polygon( ecl_region , geo_polygon)") _select_outside_polygon = EclPrototype( "void ecl_region_select_outside_polygon( ecl_region , geo_polygon)") _deselect_inside_polygon = EclPrototype( "void ecl_region_deselect_inside_polygon( ecl_region , geo_polygon)") _deselect_outside_polygon = EclPrototype( "void ecl_region_deselect_outside_polygon( ecl_region , geo_polygon)") _set_name = EclPrototype("void ecl_region_set_name( ecl_region , char*)") _get_name = EclPrototype("char* ecl_region_get_name( ecl_region )") _contains_ijk = EclPrototype( "void ecl_region_contains_ijk( ecl_region , int , int , int)") _contains_global = EclPrototype( "void ecl_region_contains_global( ecl_region, int )") _contains_active = EclPrototype( "void ecl_region_contains_active( ecl_region , int )") _equal = EclPrototype("bool ecl_region_equal( ecl_region , ecl_region )") _select_true = EclPrototype( "void ecl_region_select_true( ecl_region , ecl_kw)") _select_false = EclPrototype( "void ecl_region_select_false( ecl_region , ecl_kw)") _deselect_true = EclPrototype( "void ecl_region_deselect_true( ecl_region , ecl_kw)") _deselect_false = EclPrototype( "void ecl_region_deselect_false( ecl_region , ecl_kw)") _select_from_layer = EclPrototype( "void ecl_region_select_from_layer( ecl_region , layer , int , int)") _deselect_from_layer = EclPrototype( "void ecl_region_deselect_from_layer( ecl_region , layer , int , int)") def __init__(self, grid, preselect): """ Create a new region selector for cells in @grid. Will create a new region selector to select and deselect the cells in the grid given by @grid. The input argument @grid should be a EclGrid instance. You can start with either all cells, or no cells, selected, depending on the value of @preselect. """ self.grid = grid self.active_index = False c_ptr = self._alloc(grid, preselect) super(EclRegion, self).__init__(c_ptr) def free(self): self._free() def __eq__(self, other): return self._equal(other) def __hash__(self): return hash(hash(self.grid) + hash(self.active_index)) def __deep_copy__(self, memo): """ Creates a deep copy of the current region. """ return self._alloc_copy() def __zero__(self): global_list = self.getGlobalList() if len(global_list) > 0: return True else: return False def __iand__(self, other): """ Will perform set intersection operation inplace. Will update the current region selection so that the elements selected in self are also selected in @other. Bound to the inplace & operator, i.e. reg1 &= reg2 will eventually call this method. """ if isinstance(other, EclRegion): self._intersect(other) else: raise TypeError( "Ecl region can only intersect with other EclRegion instances") return self def __isub__(self, other): """ Inplace "subtract" one selection from another. Bound to reg -= reg2 """ if isinstance(other, EclRegion): self._subtract(other) else: raise TypeError( "Ecl region can only subtract with other EclRegion instances") return self def __ior__(self, other): """ Will perform set operation union in place. The current region selection will be updated to contain all the elements which are selected either in the current region, or in @other; bound to to inplace | operator, so you can write e.g. reg1 |= reg2 to update reg1 with the selections from reg2. """ if isinstance(other, EclRegion): self._combine(other) else: raise TypeError( "Ecl region can only be combined with other EclRegion instances" ) return self def __iadd__(self, other): """ Combines to regions - see __ior__(). """ return self.__ior__(other) def __or__(self, other): """ Creates a new region which is the union of @self and other. The method will create a new region which selection status is given by the logical or of regions @self and @other; the two initial regions will not be modified. Bound to the unary | operator: new_reg = reg1 | reg2 """ new_region = self.copy() new_region.__ior__(other) return new_region def __and__(self, other): """ Creates a new region which is the intersection of @self and other. The method will create a new region which selection status is given by the logical and of regions @self and @other; the two initial regions will not be modified. Bound to the unary & operator: new_reg = reg1 & reg2 """ new_region = self.copy() new_region.__iand__(other) return new_region def __add__(self, other): """ Unary add operator for two regions - implemented by __or__(). """ return self.__or__(other) def __sub__(self, other): """ Unary del operator for two regions. """ new_region = self.copy() new_region.__isub__(other) return new_region def union_with(self, other): """ Will update self with the union of @self and @other. See doscumentation of __ior__(). """ return self.__ior__(other) def intersect_with(self, other): """ Will update self with the intersection of @self and @other. See doscumentation of __iand__(). """ return self.__iand__(other) def copy(self): return self.__deep_copy__({}) def reset(self): """ Clear selections according to constructor argument @preselect. Will clear all selections, depending on the value of the constructor argument @preselect. If @preselect is true everything will be selected after calling reset(), otherwise no cells will be selected after calling reset(). """ self._reset() ################################################################## @select_method def select_more(self, ecl_kw, limit, intersect=False): """ Select all cells where keyword @ecl_kw is above @limit. This method is used to select all the cells where an arbitrary field, contained in @ecl_kw, is above a limiting value @limit. The EclKW instance must have either nactive or nx*ny*nz elements; if this is not satisfied method will fail hard. The datatype of @ecl_kw must be numeric, i.e. ECL_INT_TYPE, ECL_DOUBLE_TYPE or ECL_FLOAT_TYPE. In the example below we select all the cells with water saturation above 0.85: restart_file = ecl.EclFile( "ECLIPSE.X0067" ) swat_kw = restart_file["SWAT"][0] grid = ecl.EclGrid( "ECLIPSE.EGRID" ) region = ecl.EclRegion( grid , False ) region.select_more( swat_kw , 0.85 ) """ self._select_more(ecl_kw, limit) def deselect_more(self, ecl_kw, limit): """ Deselects cells with value above limit. See select_more() for further documentation. """ self._deselect_more(ecl_kw, limit) @select_method def select_less(self, ecl_kw, limit, intersect=False): """ Select all cells where keyword @ecl_kw is below @limit. See select_more() for further documentation. """ self._select_less(ecl_kw, limit) def deselect_less(self, ecl_kw, limit): """ Deselect all cells where keyword @ecl_kw is below @limit. See select_more() for further documentation. """ self._deselect_less(ecl_kw, limit) @select_method def select_equal(self, ecl_kw, value, intersect=False): """ Select all cells where @ecl_kw is equal to @value. The EclKW instance @ecl_kw must be of size nactive or nx*ny*nz, and it must be of integer type; testing for equality is not supported for floating point numbers. In the example below we select all the cells in PVT regions 2 and 4: init_file = ecl.EclFile( "ECLIPSE.INIT" ) pvtnum_kw = init_file.iget_named_kw( "PVTNUM" , 0 ) grid = ecl.EclGrid( "ECLIPSE.GRID" ) region = ecl.EclRegion( grid , False ) region.select_equal( pvtnum_kw , 2 ) region.select_equal( pvtnum_kw , 4 ) """ if not ecl_kw.data_type.is_int(): raise ValueError( "The select_equal method must have an integer valued keyword - got:%s" % ecl_kw.typeName()) self._select_equal(ecl_kw, value) def deselect_equal(self, ecl_kw, value): """ Select all cells where @ecl_kw is equal to @value. See select_equal() for further documentation. """ if not ecl_kw.data_type.is_int(): raise ValueError( "The select_equal method must have an integer valued keyword - got:%s" % ecl_kw.typeName()) self._deselect_equal(ecl_kw, value) @select_method def select_in_range(self, ecl_kw, lower_limit, upper_limit, select=False): """ Select all cells where @ecl_kw is in the half-open interval [ , ). Will select all the cells where EclKW instance @ecl_kw has value in the half-open interval [@lower_limit , @upper_limit). The input argument @ecl_kw must have size nactive or nx*ny*nz, and it must be of type ECL_FLOAT_TYPE. The following example will select all cells with porosity in the range [0.15,0.20): init_file = ecl.EclFile( "ECLIPSE.INIT" ) poro_kw = init_file.iget_named_kw( "PORO" , 0 ) grid = ecl.EclGrid( "ECLIPSE.GRID" ) region = ecl.EclRegion( grid , False ) region.select_in_range( poro_kw , 0.15, 0.20 ) """ self._select_in_interval(ecl_kw, lower_limit, upper_limit) def deselect_in_range(self, ecl_kw, lower_limit, upper_limit): """ Deselect all cells where @ecl_kw is in the half-open interval [ , ). See select_in_range() for further documentation. """ self._deselect_in_interval(ecl_kw, lower_limit, upper_limit) @select_method def select_cmp_less(self, kw1, kw2, intersect=False): """ Will select all cells where kw2 < kw1. Will compare the ECLIPSE keywords @kw1 and @kw2, and select all the cells where the numerical value of @kw1 is less than the numerical value of @kw2. The ECLIPSE keywords @kw1 and @kw2 must both be of the same size, nactive or nx*ny*nz. In addition they must both be of type type ECL_FLOAT_TYPE. In the example below we select all the cells where the pressure has dropped: restart_file = ecl.EclFile("ECLIPSE.UNRST") pressure1 = restart_file.iget_named_kw( "PRESSURE" , 0) pressure2 = restart_file.iget_named_kw( "PRESSURE" , 100) region.select_cmp_less( pressure2 , pressure1) """ self._select_cmp_less(kw1, kw2) def deselect_cmp_less(self, kw1, kw2): """ Will deselect all cells where kw2 < kw1. See select_cmp_less() for further documentation. """ self._deselect_cmp_less(kw1, kw2) @select_method def select_cmp_more(self, kw1, kw2, intersect=False): """ Will select all cells where kw2 > kw1. See select_cmp_less() for further documentation. """ self._select_cmp_more(kw1, kw2) def deselect_cmp_more(self, kw1, kw2): """ Will deselect all cells where kw2 > kw1. See select_cmp_less() for further documentation. """ self._deselect_cmp_more(kw1, kw2) @select_method def select_active(self, intersect=False): """ Will select all the active grid cells. """ self._select_active() def deselect_active(self): """ Will deselect all the active grid cells. """ self._deselect_active() @select_method def select_inactive(self, intersect=False): """ Will select all the inactive grid cells. """ self._select_inactive() def deselect_inactive(self): """ Will deselect all the inactive grid cells. """ self._deselect_inactive() def select_all(self): """ Will select all the cells. """ self._select_all() def deselect_all(self): """ Will deselect all the cells. """ self._deselect_all() def clear(self): """ Will deselect all cells. """ self.deselect_all() @select_method def select_deep(self, depth, intersect=False): """ Will select all cells below @depth. """ self._select_deep_cells(depth) def deselect_deep(self, depth): """ Will deselect all cells below @depth. """ self._deselect_deep_cells(depth) @select_method def select_shallow(self, depth, intersect=False): """ Will select all cells above @depth. """ self._select_shallow_cells(depth) def deselect_shallow(self, depth): """ Will deselect all cells above @depth. """ self._deselect_shallow_cells(depth) @select_method def select_small(self, size_limit, intersect=False): """ Will select all cells smaller than @size_limit. """ self._select_small(size_limit) def deselect_small(self, size_limit): """ Will deselect all cells smaller than @size_limit. """ self._deselect_small(size_limit) @select_method def select_large(self, size_limit, intersect=False): """ Will select all cells larger than @size_limit. """ self._select_large(size_limit) def deselect_large(self, size_limit): """ Will deselect all cells larger than @size_limit. """ self._deselect_large(size_limit) @select_method def select_thin(self, size_limit, intersect=False): """ Will select all cells thinner than @size_limit. """ self._select_thin(size_limit) def deselect_thin(self, size_limit): """ Will deselect all cells thinner than @size_limit. """ self._deselect_thin(size_limit) @select_method def select_thick(self, size_limit, intersect=False): """ Will select all cells thicker than @size_limit. """ self._select_thick(size_limit) def deselect_thick(self, size_limit): """ Will deselect all cells thicker than @size_limit. """ self._deselect_thick(size_limit) @select_method def select_box(self, ijk1, ijk2, intersect=False): """ Will select all cells in box. Will select all the the cells in the box given by @ijk1 and @ijk2. The two arguments @ijk1 and @ijk2 are tuples (1,j,k) representing two arbitrary - diagonally opposed corners - of a box. All the elements in @ijk1 and @ijk2 are inclusive, i.e. select_box( (10,12,8) , (8 , 16,4) ) will select the box defined by [8,10] x [12,16] x [4,8]. """ self._select_box(ijk1[0], ijk2[0], ijk1[1], ijk2[1], ijk1[2], ijk2[2]) def deselect_box(self, ijk1, ijk2): """ Will deselect all elements in box. See select_box() for further documentation. """ self._deselect_box(ijk1[0], ijk2[0], ijk1[1], ijk2[1], ijk1[2], ijk2[2]) @select_method def select_islice(self, i1, i2, intersect=False): """ Will select all cells with i in [@i1, @i2]. @i1 and @i2 are zero offset. """ self._select_islice(i1, i2) def deselect_islice(self, i1, i2): """ Will deselect all cells with i in [@i1, @i2]. @i1 and @i2 are zero offset. """ self._deselect_islice(i1, i2) @select_method def select_jslice(self, j1, j2, intersect=False): """ Will select all cells with j in [@j1, @j2]. @i1 and @i2 are zero offset. """ self._select_jslice(j1, j2) def deselect_jslice(self, j1, j2): """ Will deselect all cells with j in [@j1, @j2]. @i1 and @i2 are zero offset. """ self._deselect_jslice(j1, j2) @select_method def select_kslice(self, k1, k2, intersect=False): """ Will select all cells with k in [@k1, @k2]. @i1 and @i2 are zero offset. """ self._select_kslice(k1, k2) def deselect_kslice(self, k1, k2): """ Will deselect all cells with k in [@k1, @k2]. @i1 and @i2 are zero offset. """ self._deselect_kslice(k1, k2) def invert(self): """ Will invert the current selection. """ self._invert_selection() def __init_plane_select(self, n, p): n_vec = ctypes.cast((ctypes.c_double * 3)(), ctypes.POINTER(ctypes.c_double)) p_vec = ctypes.cast((ctypes.c_double * 3)(), ctypes.POINTER(ctypes.c_double)) for i in range(3): n_vec[i] = n[i] p_vec[i] = p[i] return (n_vec, p_vec) @select_method def select_above_plane(self, n, p, intersect=False): """ Will select all the cells 'above' the plane defined by n & p. @n is the surface normal vector of the plane in question and @p is a point on the plane surface. The point @p should be given in (utm_x , utm_y , tvd) coordinates. The term 'above' means that the cell center has a positive distance to the plain; correspondingly 'below' means that the cell center has a negative disatnce to the plane. """ (n_vec, p_vec) = self.__init_plane_select(n, p) self._select_above_plane(n_vec, p_vec) @select_method def select_below_plane(self, n, p, interscet=False): """ Will select all the cells 'below' the plane defined by n & p. See method 'select_above_plane' for further documentation. """ (n_vec, p_vec) = self.__init_plane_select(n, p) self._select_below_plane(n_vec, p_vec) def deselect_above_plane(self, n, p): """ Will deselect all the cells 'above' the plane defined by n & p. See method 'select_above_plane' for further documentation. """ (n_vec, p_vec) = self.__init_plane_select(n, p) self._deselect_above_plane(n_vec, p_vec) def deselect_below_plane(self, n, p): """ Will deselect all the cells 'below' the plane defined by n & p. See method 'select_above_plane' for further documentation. """ (n_vec, p_vec) = self.__init_plane_select(n, p) self._deselect_below_plane(n_vec, p_vec) @select_method def select_inside_polygon(self, points, intersect=False): """ Will select all points inside polygon. Will select all points inside polygon specified by input variable @points. Points should be a list of two-element tuples (x,y). So to select all the points within the rectangle bounded by the lower left rectangle (0,0) and upper right (100,100) the @points list should be: points = [(0,0) , (0,100) , (100,100) , (100,0)] The elements in the points list should be (utm_x, utm_y) values. These values will be compared with the centerpoints of the cells in the grid. The selection is based the top k=0 layer, and then extending this selection to all k values; this implies that the selection polygon will effectively be translated if the pillars are not vertical. """ self._select_inside_polygon(CPolyline(init_points=points)) @select_method def select_outside_polygon(self, points, intersect=False): """ Will select all points outside polygon. See select_inside_polygon for more docuemntation. """ self._select_outside_polygon(CPolyline(init_points=points)) def deselect_inside_polygon(self, points): """ Will select all points outside polygon. See select_inside_polygon for more docuemntation. """ self._deselect_inside_polygon(CPolyline(init_points=points)) def deselect_outside_polygon(self, points): """ Will select all points outside polygon. See select_inside_polygon for more docuemntation. """ self._deselect_outside_polygon(CPolyline(init_points=points)) @select_method def selectTrue(self, ecl_kw, intersect=False): """ Assume that input ecl_kw is a boolean mask. """ self._select_true(ecl_kw) @select_method def selectFalse(self, ecl_kw, intersect=False): """ Assume that input ecl_kw is a boolean mask. """ self._select_false(ecl_kw) @select_method def selectFromLayer(self, layer, k, value, intersect=False): """Will select all the cells in in @layer with value @value - at vertical coordinate @k. The input @layer should be of type Layer - from the ecl.ecl.faults.layer module. The k value must in the range [0,grid.nz) and the dimensions of the layer must correspond exactly to nx,ny of the grid. """ grid = self.grid if k < 0 or k >= grid.getNZ(): raise ValueError("Invalid k value:%d - must be in range [0,%d)" % (k, grid.getNZ())) if grid.getNX() != layer.getNX(): raise ValueError("NX dimension mismatch. Grid:%d layer:%d" % (grid.getNX(), layer.getNX())) if grid.getNY() != layer.getNY(): raise ValueError("NY dimension mismatch. Grid:%d layer:%d" % (grid.getNY(), layer.getNY())) self._select_from_layer(layer, k, value) ################################################################# def scalar_apply_kw(self, target_kw, scalar, func_dict, force_active=False): """ Helper function to apply a function with one scalar arg on target_kw. """ data_type = target_kw.data_type if func_dict.has_key(data_type): func = func_dict[data_type] func(target_kw, scalar, force_active) else: raise Exception( "scalar_apply_kw() only supported for INT/FLOAT/DOUBLE") def iadd_kw(self, target_kw, delta_kw, force_active=False): """ The functions iadd_kw(), copy_kw(), set_kw(), scale_kw() and shift_kw() are not meant to be used as methods of the EclRegion class (altough that is of course perfectly OK) - rather a EclRegion instance is passed as an argument to an EclKW method, and then that method "flips things around" and calls one of these methods with the EclKW instance as argument. This applies to all the EclKW methods which take an optional "mask" argument. """ if isinstance(delta_kw, EclKW): if target_kw.assert_binary(delta_kw): self._iadd_kw(target_kw, delta_kw, force_active) else: raise TypeError("Type mismatch") else: self.shift_kw(target_kw, delta_kw, force_active=force_active) def shift_kw(self, ecl_kw, shift, force_active=False): """ See usage documentation on iadd_kw(). """ self.scalar_apply_kw( ecl_kw, shift, { EclDataType.ECL_INT: self._shift_kw_int, EclDataType.ECL_FLOAT: self._shift_kw_float, EclDataType.ECL_DOUBLE: self._shift_kw_double }, force_active) def isub_kw(self, target_kw, delta_kw, force_active=False): if isinstance(delta_kw, EclKW): if target_kw.assert_binary(delta_kw): self._isub_kw(target_kw, delta_kw, force_active) else: raise TypeError("Type mismatch") else: self.shift_kw(target_kw, -delta_kw, force_active=force_active) def scale_kw(self, ecl_kw, scale, force_active=False): """ See usage documentation on iadd_kw(). """ self.scalar_apply_kw( ecl_kw, scale, { EclDataType.ECL_INT: self._scale_kw_int, EclDataType.ECL_FLOAT: self._scale_kw_float, EclDataType.ECL_DOUBLE: self._scale_kw_double }, force_active) def imul_kw(self, target_kw, other, force_active=False): if isinstance(other, EclKW): if target_kw.assert_binary(other): self._imul_kw(target_kw, other) else: raise TypeError("Type mismatch") else: self.scale_kw(target_kw, other, force_active) def idiv_kw(self, target_kw, other, force_active=False): if isinstance(other, EclKW): if target_kw.assert_binary(other): self._idiv_kw(target_kw, other) else: raise TypeError("Type mismatch") else: self.scale_kw(target_kw, 1 / other, force_active) def copy_kw(self, target_kw, src_kw, force_active=False): """ See usage documentation on iadd_kw(). """ if target_kw.assert_binary(src_kw): self._copy_kw(target_kw, src_kw, force_active) else: raise TypeError("Type mismatch") def set_kw(self, ecl_kw, value, force_active=False): """ See usage documentation on iadd_kw(). """ self.scalar_apply_kw( ecl_kw, value, { EclDataType.ECL_INT: self._set_kw_int, EclDataType.ECL_FLOAT: self._set_kw_float, EclDataType.ECL_DOUBLE: self._set_kw_double }, force_active) ################################################################# def ecl_region_instance(self): """ Helper function (attribute) to support run-time typechecking. """ return True def getActiveList(self): """ IntVector instance with active indices in the region. """ active_list = self._get_active_list() active_list.setParent(self) return active_list def getGlobalList(self): """ IntVector instance with global indices in the region. """ global_list = self._get_global_list() global_list.setParent(self) return global_list def getIJKList(self): """ WIll return a Python list of (ij,k) tuples for the region. """ global_list = self.getGlobalList() ijk_list = [] for g in global_list: ijk_list.append(self.grid.get_ijk(global_index=g)) return ijk_list def contains_ijk(self, i, j, k): """ Will check if the cell given by i,j,k is part of the region. """ return self._contains_ijk(i, j, k) def contains_global(self, global_index): """ Will check if the cell given by @global_index is part of the region. """ return self._contains_global(global_index) def contains_active(self, active_index): """ Will check if the cell given by @active_index is part of the region. """ return self._contains_active(active_index) def kw_index_list(self, ecl_kw, force_active): c_ptr = self._get_kw_index_list(ecl_kw, force_active) index_list = IntVector.createCReference(c_ptr, self) return index_list def getName(self): return self._get_name() def setName(self, name): self._set_name(name)
class EclRFT(BaseCClass): """The EclRFT class contains the information for *one* RFT. The ECLIPSE RFT file can contain three different types of RFT like objects which are lumped together; the EclRFTClass is a container for such objects. The three different object types which can be found in an RFT file are: RFT: This is old-fashioned RFT which contains measurements of saturations for each of the completed cells. PLT: This contains production and flow rates for each phase in each cell. SEGMENT: Not implemented. In addition to the measurements specific for RFT and PLT each cell has coordinates, pressure and depth. """ TYPE_NAME = "ecl_rft" _alloc = EclPrototype("void* ecl_rft_node_alloc_new( char* , char* , time_t , double)" , bind = False) _free = EclPrototype("void ecl_rft_node_free( ecl_rft )") _get_type = EclPrototype("int ecl_rft_node_get_type( ecl_rft )") _get_well = EclPrototype("char* ecl_rft_node_get_well_name( ecl_rft )") _get_date = EclPrototype("time_t ecl_rft_node_get_date( ecl_rft )") _get_size = EclPrototype("int ecl_rft_node_get_size( ecl_rft )") _iget_cell = EclPrototype("void* ecl_rft_node_iget_cell( ecl_rft )") _iget_cell_sorted = EclPrototype("void* ecl_rft_node_iget_cell_sorted( ecl_rft )") _sort_cells = EclPrototype("void* ecl_rft_node_inplace_sort_cells( ecl_rft )") _iget_depth = EclPrototype("double ecl_rft_node_iget_depth( ecl_rft )") _iget_pressure = EclPrototype("double ecl_rft_node_iget_pressure(ecl_rft)") _iget_ijk = EclPrototype("void ecl_rft_node_iget_ijk( ecl_rft , int , int*, int*, int*)") _iget_swat = EclPrototype("double ecl_rft_node_iget_swat(ecl_rft)") _iget_sgas = EclPrototype("double ecl_rft_node_iget_sgas(ecl_rft)") _iget_orat = EclPrototype("double ecl_rft_node_iget_orat(ecl_rft)") _iget_wrat = EclPrototype("double ecl_rft_node_iget_wrat(ecl_rft)") _iget_grat = EclPrototype("double ecl_rft_node_iget_grat(ecl_rft)") _lookup_ijk = EclPrototype("void* ecl_rft_node_lookup_ijk( ecl_rft , int , int , int)") _is_RFT = EclPrototype("bool ecl_rft_node_is_RFT( ecl_rft )") _is_PLT = EclPrototype("bool ecl_rft_node_is_PLT( ecl_rft )") _is_SEGMENT = EclPrototype("bool ecl_rft_node_is_SEGMENT( ecl_rft )") _is_MSW = EclPrototype("bool ecl_rft_node_is_MSW( ecl_rft )") def __init__(self , name , type_string , date , days): c_ptr = self._alloc( name , type_string , CTime( date ) , days ) super(EclRFT , self).__init__( c_ptr ) def free(self): self._free( ) def __repr__(self): rs = [] rs.append('completed_cells = %d' % len(self)) rs.append('date = %s' % self.getDate()) if self.is_RFT(): rs.append('RFT') if self.is_PLT(): rs.append('PLT') if self.is_SEGMENT(): rs.append('SEGMENT') if self.is_MSW(): rs.append('MSW') rstr = ', '.join(rs) return self._create_repr(rstr) def __len__(self): """ The number of completed cells in this RFT. """ return self._get_size( ) def is_RFT(self): """ Is instance an RFT; in that case all the cells will be EclRFTCell instances. """ return self._is_RFT( ) def is_PLT(self): """ Is instance a PLT; in that case all the cells will be EclPLTCell instances. """ return self._is_PLT( ) def is_SEGMENT(self): """ Is this a SEGMENT - not implemented. """ return self._is_SEGMENT( ) def is_MSW(self): """ Is this well a MSW well. Observe that the test ONLY applies to PLTs. """ return self._is_MSW( ) def get_well_name(self): """ The name of the well we are considering. """ return self._get_well( ) def get_date(self): """ The date when this RFT/PLT/... was recorded. """ ct = CTime(self._get_date( )) return ct.date() def __cell_ref( self , cell_ptr ): if self.is_RFT(): return EclRFTCell.createCReference( cell_ptr , self ) elif self.is_PLT(): return EclPLTCell.createCReference( cell_ptr , self ) else: raise NotImplementedError("Only RFT and PLT cells are implemented") def assert_cell_index( self , index ): if isinstance( index , int): length = self.__len__() if index < 0 or index >= length: raise IndexError else: raise TypeError("Index should be integer type") def __getitem__(self , index): """Implements the [] operator to return the cells. To get the object related to cell nr 5: cell = rft[4] The return value from the __getitem__() method is either an EclRFTCell instance or a EclPLTCell instance, depending on the type of this particular RFT object. """ self.assert_cell_index( index ) cell_ptr = self._iget_cell( index ) return self.__cell_ref( cell_ptr ) def iget( self , index ): return self[index] def iget_sorted( self , index ): """ Will return the cell nr @index in the list of sorted cells. See method sort() for further documentation. """ self.assert_cell_index( index ) cell_ptr = self._iget_cell_sorted( index ) return self.__cell_ref( cell_ptr ) def sort(self): """ Will sort cells in RFT; currently only applies to MSW wells. By default the cells in the RFT come in the order they are specified in the ECLIPSE input file; that is not necessarily in a suitable order. In the case of MSW wells it is possible to sort the connections after distance along the wellpath. To access the cells in sort order you have two options: 1. Sort the cells using the sort() method, and then subsequently access them sequentially: rft.sort() for cell in rft: print cell 2. Let the rft object stay unsorted, but access the cells using the iget_sorted() method: for i in range(len(rft)): cell = rft.iget_sorted( i ) Currently only MSW/PLTs are sorted, based on the CONLENST keyword; for other wells the sort() method does nothing. """ self._sort_cells( ) # ijk are zero offset def ijkget( self , ijk ): """ Will look up the cell based on (i,j,k). If the cell (i,j,k) is not part of this RFT/PLT None will be returned. The (i,j,k) input values should be zero offset, i.e. you must subtract 1 from the (i,j,k) values given in the ECLIPSE input. """ cell_ptr = self._lookup_ijk( ijk[0] , ijk[1] , ijk[2]) if cell_ptr: return self.__cell_ref( cell_ptr ) else: return None
class Layer(BaseCClass): TYPE_NAME = "layer" _alloc = EclPrototype("void* layer_alloc(int, int)", bind=False) _copy = EclPrototype("void layer_memcpy(layer, layer)") _free = EclPrototype("void layer_free(layer)") _get_nx = EclPrototype("int layer_get_nx(layer)") _get_ny = EclPrototype("int layer_get_ny(layer)") _set_cell = EclPrototype( "void layer_iset_cell_value(layer, int, int, int)") _get_cell = EclPrototype("int layer_iget_cell_value(layer, int, int)") _get_bottom_barrier = EclPrototype( "bool layer_iget_bottom_barrier(layer, int, int)") _get_left_barrier = EclPrototype( "bool layer_iget_left_barrier(layer, int, int)") _cell_contact = EclPrototype( "bool layer_cell_contact(layer, int, int, int, int)") _add_barrier = EclPrototype("void layer_add_barrier(layer, int, int)") _add_ijbarrier = EclPrototype( "void layer_add_ijbarrier(layer, int, int, int, int)") _add_interp_barrier = EclPrototype( "void layer_add_interp_barrier(layer, int, int)") _clear_cells = EclPrototype("void layer_clear_cells(layer)") _assign = EclPrototype("void layer_assign(layer, int)") _cell_sum = EclPrototype("int layer_get_cell_sum(layer)") _update_connected = EclPrototype( "void layer_update_connected_cells(layer,int,int,int,int)") _cells_equal = EclPrototype( "void layer_cells_equal(layer, int,int_vector,int_vector)") _count_equal = EclPrototype("int layer_count_equal(layer, int)") _active_cell = EclPrototype("bool layer_iget_active(layer, int,int)") _update_active = EclPrototype( "bool layer_update_active(layer, ecl_grid, int)") def __init__(self, nx, ny): c_ptr = self._alloc(nx, ny) if c_ptr: super(Layer, self).__init__(c_ptr) else: raise ValueError("Invalid input - no Layer object created") @classmethod def copy(cls, src): layer = Layer(src.getNX(), src.getNY()) layer._copy(layer, src) return layer def _assert_ij(self, i, j): if i < 0 or i >= self.getNX(): raise ValueError("Invalid layer i:%d" % i) if j < 0 or j >= self.getNY(): raise ValueError("Invalid layer j:%d" % j) def __unpack_index(self, index): try: (i, j) = index except TypeError: raise ValueError("Index:%s is invalid - must have two integers" % str(index)) self._assert_ij(i, j) return (i, j) def __setitem__(self, index, value): (i, j) = self.__unpack_index(index) self._set_cell(i, j, value) def active_cell(self, i, j): self._assert_ij(i, j) return self._active_cell(i, j) def update_active(self, grid, k): if grid.getNX() != self.getNX(): raise ValueError("NX dimension mismatch. Grid:%d layer:%d" % (grid.getNX(), self.getNX())) if grid.getNY() != self.getNY(): raise ValueError("NY dimension mismatch. Grid:%d layer:%d" % (grid.getNY(), self.getNY())) if k >= grid.getNZ(): raise ValueError("K value invalid: Grid range [0,%d)" % grid.getNZ()) self._update_active(grid, k) def __getitem__(self, index): (i, j) = self.__unpack_index(index) return self._get_cell(i, j) def bottom_barrier(self, i, j): self._assert_ij(i, j) return self._get_bottom_barrier(i, j) def left_barrier(self, i, j): self._assert_ij(i, j) return self._get_left_barrier(i, j) def get_nx(self): return self._get_nx() @property def nx(self): return self._get_nx() def get_ny(self): return self._get_ny() @property def ny(self): return self._get_ny() def free(self): self._free() def cell_contact(self, p1, p2): i1, j1 = p1 i2, j2 = p2 if not 0 <= i1 < self.getNX(): raise IndexError("Invalid i1:%d" % i1) if not 0 <= i2 < self.getNX(): raise IndexError("Invalid i2:%d" % i2) if not 0 <= j1 < self.getNY(): raise IndexError("Invalid i1:%d" % j1) if not 0 <= j2 < self.getNY(): raise IndexError("Invalid i2:%d" % j2) return self._cell_contact(i1, j1, i2, j2) def add_interp_barrier(self, c1, c2): self._add_interp_barrier(c1, c2) def add_polyline_barrier(self, polyline, grid, k): if len(polyline) > 1: for i in range(len(polyline) - 1): x1, y1 = polyline[i] x2, y2 = polyline[i + 1] c1 = grid.findCellCornerXY(x1, y1, k) c2 = grid.findCellCornerXY(x2, y2, k) self.addInterpBarrier(c1, c2) def add_fault_barrier(self, fault, K, link_segments=True): fault_layer = fault[K] num_lines = len(fault_layer) for index, fault_line in enumerate(fault_layer): for segment in fault_line: c1, c2 = segment.getCorners() self._add_barrier(c1, c2) if index < num_lines - 1: next_line = fault_layer[index + 1] next_segment = next_line[0] next_c1, next_c2 = next_segment.getCorners() if link_segments: self.addInterpBarrier(c2, next_c1) def add_ij_barrier(self, ij_list): if len(ij_list) < 2: raise ValueError("Must have at least two (i,j) points") nx = self.getNX() ny = self.getNY() p1 = ij_list[0] i1, j1 = p1 for p2 in ij_list[1:]: i2, j2 = p2 if i1 == i2 or j1 == j2: if not 0 <= i2 <= nx: raise ValueError( "i value:%d invalid. Valid range: [0,%d] " % (i1, i2)) if not 0 <= j2 <= ny: raise ValueError( "i value:%d invalid. Valid range: [0,%d] " % (j1, j2)) self._add_ijbarrier(i1, j1, i2, j2) p1 = p2 i1, j1 = p1 else: raise ValueError("Must have i1 == i2 or j1 == j2") def cell_sum(self): return self._cell_sum() def clear_cells(self): """ Will reset all cell and edge values to zero. Barriers will be left unchanged. """ self._clear_cells() def assign(self, value): """ Will set the cell value to @value in all cells. Barriers will not be changed """ self._assign(value) def update_connected(self, ij, new_value, org_value=None): """ Will update cell value of all cells in contact with cell ij to the value @new_value. If org_value is not supplied, the current value in cell ij is used. """ if org_value is None: org_value = self[ij] if self[ij] == org_value: self._update_connected(ij[0], ij[1], org_value, new_value) else: raise ValueError("Cell %s is not equal to %d \n" % (ij, org_value)) def cells_equal(self, value): """ Will return a list [(i1,j1),(i2,j2), ...(in,jn)] of all cells with value @value. """ i_list = IntVector() j_list = IntVector() self._cells_equal(value, i_list, j_list) ij_list = [] for (i, j) in zip(i_list, j_list): ij_list.append((i, j)) return ij_list def count_equal(self, value): return self._count_equal(value)
class EclPLTCell(RFTCell): TYPE_NAME = "ecl_plt_cell" _alloc_PLT = EclPrototype( "void* ecl_rft_cell_alloc_PLT( int, int , int , double , double , double, double , double, double , double , double , double , double , double )", bind=False) _get_orat = EclPrototype("double ecl_rft_cell_get_orat( ecl_plt_cell )") _get_grat = EclPrototype("double ecl_rft_cell_get_grat( ecl_plt_cell )") _get_wrat = EclPrototype("double ecl_rft_cell_get_wrat( ecl_plt_cell )") _get_flowrate = EclPrototype( "double ecl_rft_cell_get_flowrate( ecl_plt_cell )") _get_oil_flowrate = EclPrototype( "double ecl_rft_cell_get_oil_flowrate( ecl_plt_cell )") _get_gas_flowrate = EclPrototype( "double ecl_rft_cell_get_gas_flowrate( ecl_plt_cell )") _get_water_flowrate = EclPrototype( "double ecl_rft_cell_get_water_flowrate( ecl_plt_cell )") _get_conn_start = EclPrototype( "double ecl_rft_cell_get_connection_start( ecl_plt_cell )") _get_conn_end = EclPrototype( "double ecl_rft_cell_get_connection_end( ecl_plt_cell )") def __init__(self, i, j, k, depth, pressure, orat, grat, wrat, conn_start, conn_end, flowrate, oil_flowrate, gas_flowrate, water_flowrate): c_ptr = self._alloc_PLT(i, j, k, depth, pressure, orat, grat, wrat, conn_start, conn_end, flowrate, oil_flowrate, gas_flowrate, water_flowrate) super(EclPLTCell, self).__init__(c_ptr) @property def orat(self): return self._get_orat() @property def grat(self): return self._get_grat() @property def wrat(self): return self._get_wrat() @property def conn_start(self): """Will return the length from wellhead(?) to connection. For MSW wells this property will return the distance from a fixed point (wellhead) to the current connection. This value will be used to sort the completed cells along the well path. In the case of non MSW wells this will just return a fixed default value. """ return self._get_conn_start() @property def conn_end(self): """Will return the length from wellhead(?) to connection end. For MSW wells this property will return the distance from a fixed point (wellhead) to the current connection end. This value will be used to sort the completed cells along the well path. In the case of non MSW wells this will just return a fixed default value. """ return self._get_conn_end() @property def flowrate(self): return self._get_flowrate() @property def oil_flowrate(self): return self._get_oil_flowrate() @property def gas_flowrate(self): return self._get_gas_flowrate() @property def water_flowrate(self): return self._get_water_flowrate()
# the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ERT is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. # # See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html> # for more details. from ecl.ecl import EclPrototype __arglist = 'double, double, double, ' __arglist += 'ecl_grid, ecl_file, ' __arglist += 'ecl_kw, ecl_kw, ecl_kw, ecl_kw, ecl_kw, ecl_kw' _phase_deltag = EclPrototype("double ecl_grav_phase_deltag(%s)" % __arglist) def phase_deltag(xyz, grid, init, sat1, rho1, porv1, sat2, rho2, porv2): return _phase_deltag(xyz[0], xyz[1], xyz[2], grid.c_ptr, init.c_ptr, sat1.c_ptr, rho1.c_ptr, porv1.c_ptr, sat2.c_ptr, rho2.c_ptr, porv2.c_ptr) def deltag(xyz, grid, init_file, restart_file1, restart_file2): """ 1. All restart files should have water, i.e. the SWAT keyword. 2. All phases present in the restart file should also be present as densities, in addition the model must contain one additional phase - which should have a density. 3. The restart files can never contain oil saturation. """
class EclKW(BaseCClass): """ The EclKW class contains the information from one ECLIPSE keyword. The ecl_kw type is the lowest level type in the libecl C library, and all the other datatypes like e.g. ecl_grid and ecl_sum are based on collections of ecl_kw instances, and interpreting the content of the ecl_kw keywords. Many of the special __xxx___() functions have been implemented, so that the EclKW class supports both numerical operations and also [] based lookup. Many of the methods accept an optional @mask argument; this should be a EclRegion instance which can be used to limit the operation to a part of the EclKW. """ int_kw_set = set([ "PVTNUM", "FIPNUM", "EQLNUM", "FLUXNUM", "MULTNUM", "ACTNUM", "SPECGRID", "REGIONS" ]) TYPE_NAME = "ecl_kw" _alloc_new = EclPrototype( "void* ecl_kw_alloc_python(char*, int, ecl_data_type)", bind=False) _fread_alloc = EclPrototype("ecl_kw_obj ecl_kw_fread_alloc(fortio)", bind=False) _load_grdecl = EclPrototype( "ecl_kw_obj ecl_kw_fscanf_alloc_grdecl_dynamic_python(FILE, char*, bool, ecl_data_type)", bind=False) _fseek_grdecl = EclPrototype( "bool ecl_kw_grdecl_fseek_kw(char*, bool, FILE)", bind=False) _sub_copy = EclPrototype( "ecl_kw_obj ecl_kw_alloc_sub_copy(ecl_kw, char*, int, int)") _copyc = EclPrototype("ecl_kw_obj ecl_kw_alloc_copy(ecl_kw)") _slice_copyc = EclPrototype( "ecl_kw_obj ecl_kw_alloc_slice_copy(ecl_kw, int, int, int)") _fprintf_grdecl = EclPrototype( "void ecl_kw_fprintf_grdecl(ecl_kw, FILE)") _fprintf_data = EclPrototype( "void ecl_kw_fprintf_data(ecl_kw, char*, FILE)") _get_size = EclPrototype("int ecl_kw_get_size(ecl_kw)") _get_fortio_size = EclPrototype("size_t ecl_kw_fortio_size(ecl_kw)") _get_type = EclPrototype("ecl_type_enum ecl_kw_get_type(ecl_kw)") _iget_char_ptr = EclPrototype("char* ecl_kw_iget_char_ptr(ecl_kw, int)") _iset_char_ptr = EclPrototype( "void ecl_kw_iset_char_ptr(ecl_kw, int, char*)") _iget_string_ptr = EclPrototype( "char* ecl_kw_iget_string_ptr(ecl_kw, int)") _iset_string_ptr = EclPrototype( "void ecl_kw_iset_string_ptr(ecl_kw, int, char*)") _iget_bool = EclPrototype("bool ecl_kw_iget_bool(ecl_kw, int)") _iset_bool = EclPrototype("bool ecl_kw_iset_bool(ecl_kw, int, bool)") _iget_int = EclPrototype("int ecl_kw_iget_int(ecl_kw, int)") _iget_double = EclPrototype("double ecl_kw_iget_double(ecl_kw, int)") _iget_float = EclPrototype("float ecl_kw_iget_float(ecl_kw, int)") _float_ptr = EclPrototype("float* ecl_kw_get_float_ptr(ecl_kw)") _int_ptr = EclPrototype("int* ecl_kw_get_int_ptr(ecl_kw)") _double_ptr = EclPrototype("double* ecl_kw_get_double_ptr(ecl_kw)") _free = EclPrototype("void ecl_kw_free(ecl_kw)") _fwrite = EclPrototype("void ecl_kw_fwrite(ecl_kw, fortio)") _get_header = EclPrototype("char* ecl_kw_get_header (ecl_kw)") _set_header = EclPrototype( "void ecl_kw_set_header_name (ecl_kw, char*)") _get_data_type = EclPrototype( "ecl_data_type_obj ecl_kw_get_data_type_python(ecl_kw)") _int_sum = EclPrototype("int ecl_kw_element_sum_int(ecl_kw)") _float_sum = EclPrototype("double ecl_kw_element_sum_float(ecl_kw)") _iadd = EclPrototype("void ecl_kw_inplace_add(ecl_kw, ecl_kw)") _imul = EclPrototype("void ecl_kw_inplace_mul(ecl_kw, ecl_kw)") _idiv = EclPrototype("void ecl_kw_inplace_div(ecl_kw, ecl_kw)") _isub = EclPrototype("void ecl_kw_inplace_sub(ecl_kw, ecl_kw)") _iabs = EclPrototype("void ecl_kw_inplace_abs(ecl_kw)") _equal = EclPrototype("bool ecl_kw_equal(ecl_kw, ecl_kw)") _equal_numeric = EclPrototype( "bool ecl_kw_numeric_equal(ecl_kw, ecl_kw, double, double)") _assert_binary = EclPrototype( "bool ecl_kw_size_and_numeric_type_equal(ecl_kw, ecl_kw)") _scale_int = EclPrototype("void ecl_kw_scale_int(ecl_kw, int)") _scale_float = EclPrototype( "void ecl_kw_scale_float_or_double(ecl_kw, double)") _shift_int = EclPrototype("void ecl_kw_shift_int(ecl_kw, int)") _shift_float = EclPrototype( "void ecl_kw_shift_float_or_double(ecl_kw, double)") _copy_data = EclPrototype("void ecl_kw_memcpy_data(ecl_kw, ecl_kw)") _set_int = EclPrototype("void ecl_kw_scalar_set_int(ecl_kw, int)") _set_float = EclPrototype( "void ecl_kw_scalar_set_float_or_double(ecl_kw, double)") _max_min_int = EclPrototype( "void ecl_kw_max_min_int(ecl_kw, int*, int*)") _max_min_float = EclPrototype( "void ecl_kw_max_min_float(ecl_kw, float*, float*)") _max_min_double = EclPrototype( "void ecl_kw_max_min_double(ecl_kw, double*, double*)") _fix_uninitialized = EclPrototype( "void ecl_kw_fix_uninitialized(ecl_kw,int, int, int, int*)") _first_different = EclPrototype( "int ecl_kw_first_different(ecl_kw, ecl_kw, int, double, double)") _resize = EclPrototype("void ecl_kw_resize(ecl_kw, int)") @classmethod def createCReference(cls, c_ptr, parent=None): ecl_kw = super(EclKW, cls).createCReference(c_ptr, parent=parent) if ecl_kw is None: raise ValueError("Failed to create EclKW instance") ecl_kw.__private_init() return ecl_kw @classmethod def createPythonObject(cls, c_ptr): ecl_kw = super(EclKW, cls).createPythonObject(c_ptr) if ecl_kw is None: raise ValueError("Failed to create EclKW instance") ecl_kw.__private_init() return ecl_kw @classmethod def add_int_kw(cls, kw): """Will add keyword @kw to the standard set of integer keywords.""" cls.int_kw_set.add(kw) @classmethod def del_int_kw(cls, kw): """Will remove keyword @kw from the standard set of integer keywords.""" cls.int_kw_set.discard(kw) @classmethod def int_keywords(cls): """Will return the current set of integer keywords.""" return cls.int_kw_set def slice_copy(self, slice_range): (start, stop, step) = slice_range.indices(len(self)) if stop > start: return self._slice_copyc(start, stop, step) else: return None def copy(self): """ Will create a deep copy of the current kw instance. """ return self._copyc() @classmethod def read_grdecl(cls, fileH, kw, strict=True, ecl_type=None): """ Function to load an EclKW instance from a grdecl formatted filehandle. This constructor can be used to load an EclKW instance from a grdecl formatted file; the input files for petrophysical properties are typically given as grdecl files. The @file argument should be a Python filehandle to an open file. The @kw argument should be the keyword header you are searching for, e.g. "PORO" or "PVTNUM"[1], the method will then search forward through the file to look for this @kw. If the keyword can not be found the method will return None. The searching will start from the current position in the file; so if you want to reposition the file pointer you should use the seek() method of the file object first. Observe that there is a strict 8 character limit on @kw - altough you could in principle use an arbitrary external program to create grdecl files with more than 8 character length headers, this implementation will refuse to even try loading them. In that case you will have to rename the keywords in your file - sorry. A TypeError exception will be raised if @kw has more than 8 characters. The implementation in ert can read integer and float type keywords from grdecl files; however the grdecl files have no datatype header, and it is impossible to determine the type reliably by inspection. Hence the type must be known when reading the file. The algorithm for specifying type, in order of presedence, is as follows: 1. The optional argument @ecl_type can be used to specify the type: special_int_kw = EclKW.read_grdecl(fileH, 'INTKW', ecl_type=ECL_INT) If ecl_type is different from ECL_INT or ECL_FLOAT a TypeError exception will be raised. If ecl_type == None (the default), the method will continue to point 2. or 3. to determine the correct type. 2. If the keyword is included in the set built in set 'int_kw_set' the type will be ECL_INT_TYPE. pvtnum_kw = EclKW.read_grdecl(fileH, 'PVTNUM') Observe that (currently) no case conversions take place when checking the 'int_kw_set'. The current built in set is accesible through the int_kw property. 3. Otherwise the default is float, i.e. ECL_FLOAT. EclKw reads grdecl with EclDataType poro_kw = EclKW.read_grdecl(fileH, 'PORO') Observe that since the grdecl files are quite weakly structured it is difficult to verify the integrity of the files, malformed input might therefor pass unnoticed before things blow up at a later stage. [1]: It is possible, but not recommended, to pass in None for @kw, in which case the method will load the first keyword it finds in the file. """ cfile = CFILE(fileH) if kw: if len(kw) > 8: raise TypeError( "Sorry keyword:%s is too long, must be eight characters or less." % kw) if ecl_type is None: if cls.int_kw_set.__contains__(kw): ecl_type = EclDataType.ECL_INT else: ecl_type = EclDataType.ECL_FLOAT ecl_type = warn_and_cast_data_type(ecl_type) if not isinstance(ecl_type, EclDataType): raise TypeError("Expected EclDataType, was: %s" % type(ecl_type)) if not ecl_type in [EclDataType.ECL_FLOAT, EclDataType.ECL_INT]: raise ValueError("The type:%s is invalid when loading keyword:%s" % (ecl_type.type_name, kw)) return cls._load_grdecl(cfile, kw, strict, ecl_type) @classmethod def fseek_grdecl(cls, fileH, kw, rewind=False): """ Will search through the open file and look for string @kw. If the search succeeds the function will return and the file pointer will be positioned at the start of the kw, if the search fails the function will return false and the file pointer will be repositioned at the position it had prior to the call. Only @kw instances which are found at the beginning of a line (with optional leading space characters) are considered, i.e. searching for the string PERMX in the cases below will fail: -- PERMX EQUIL PERMX / The function will start searching from the current position in the file and forwards, if the optional argument @rewind is true the function rewind to the beginning of the file and search from there after the initial search. """ cfile = CFILE(fileH) return cls._fseek_grdecl(kw, rewind, cfile) @classmethod def fread(cls, fortio): """ Will read a new EclKW instance from the open FortIO file. """ return cls._fread_alloc(fortio) def free(self): self._free() def __repr__(self): si = len(self) nm = self.getName() mm = 'type=%s' % str(self.getEclType()) if self.isNumeric(): mi, ma = self.getMinMax() mm = 'min=%.2f, max=%.2f' % (mi, ma) ad = self._ad_str() fmt = 'EclKW(size=%d, name="%s", %s) %s' return fmt % (si, nm, mm, ad) def __init__(self, name, size, data_type): """Creates a brand new EclKW instance. This method will create a grand spanking new EclKW instance. The instance will get name @name @size elements and datatype @data_type. Using this method you could create a SOIL keyword with: soil_kw = EclKW("SOIL", 10000, ECL_FLOAT_TYPE) """ if len(name) > 8: raise ValueError( "Sorry - maximum eight characters in keyword name") data_type = warn_and_cast_data_type(data_type) if not isinstance(data_type, EclDataType): raise TypeError("Expected an EclDataType, received: %s" % type(data_type)) c_ptr = self._alloc_new(name, size, data_type) super(EclKW, self).__init__(c_ptr) self.__private_init() def __private_init(self): self.data_ptr = None if self.data_type.is_int(): self.data_ptr = self._int_ptr() self.dtype = numpy.int32 self.str_fmt = "%8d" elif self.data_type.is_float(): self.data_ptr = self._float_ptr() self.dtype = numpy.float32 self.str_fmt = "%13.4f" elif self.data_type.is_double(): self.data_ptr = self._double_ptr() self.dtype = numpy.float64 self.str_fmt = "%13.4f" else: # Iteration not supported for CHAR / BOOL self.data_ptr = None self.dtype = None if self.data_type.is_char(): self.str_fmt = "%8s" elif self.data_type.is_bool(): self.str_fmt = "%d" elif self.data_type.is_mess(): self.str_fmt = "%s" #"Message type" elif self.data_type.is_string(): self.str_fmt = "%" + str(self.data_type.element_size) + "s" else: raise ValueError("Unknown EclDataType (%s)!" % self.data_type.type_name) def sub_copy(self, offset, count, new_header=None): """ Will create a new block copy of the src keyword. If @new_header == None the copy will get the same 'name' as the src, otherwise the keyword will get the @new_header as header. The copy will start at @block of the src keyword and copy @count elements; a negative value of @count is interpreted as 'the rest of the elements' new1 = src.sub_copy(0, 10, new_header="NEW1") new2 = src.sub_copy(10, -1, new_header="NEW2") If the count or index arguments are in some way invalid the method will raise IndexError. """ if offset < 0 or offset >= len(self): raise IndexError("Offset:%d invalid - valid range:[0,%d)" % (offset, len(self))) if offset + count > len(self): raise IndexError("Invalid value of (offset + count):%d" % (offset + count)) return self._sub_copy(new_header, offset, count) def is_numeric(self): """ Will check if the keyword contains numeric data, i.e int, float or double. """ return self.data_type.is_numeric() def ecl_kw_instance(self): return True def __len__(self): """ Returns the number of elements. Implements len() """ return self._get_size() def __deep_copy__(self, memo): """ Python special routine used to perform deep copy. """ ecl_kw = self.copy() return ecl_kw def __getitem__(self, index): """ Function to support index based lookup: y = kw[index] """ if isinstance(index, int): length = len(self) if index < 0: # We allow one level of negative indexing index += len(self) if index < 0 or index >= length: raise IndexError else: if self.data_ptr: return self.data_ptr[index] else: if self.data_type.is_bool(): return self._iget_bool(index) elif self.data_type.is_char(): return self._iget_char_ptr(index) elif self.data_type.is_string(): return self._iget_string_ptr(index) else: raise TypeError("Internal implementation error ...") elif isinstance(index, slice): return self.slice_copy(index) else: raise TypeError("Index should be integer type") def __setitem__(self, index, value): """ Function to support index based assignment: kw[index] = value """ if isinstance(index, int): length = len(self) if index < 0: # Will only wrap backwards once index = len(self) + index if index < 0 or index >= length: raise IndexError else: if self.data_ptr: self.data_ptr[index] = value else: if self.data_type.is_bool(): self._iset_bool(index, value) elif self.data_type.is_char(): return self._iset_char_ptr(index, value) elif self.data_type.is_string(): return self._iset_string_ptr(index, value) else: raise SystemError("Internal implementation error ...") elif isinstance(index, slice): (start, stop, step) = index.indices(len(self)) index = start while index < stop: self[index] = value index += step else: raise TypeError("Index should be integer type") ################################################################# def __IMUL__(self, factor, mul=True): if self.isNumeric(): if hasattr(factor, "ecl_kw_instance"): if self.assert_binary(factor): if mul: self._imul(factor) else: self._idiv(factor) else: raise TypeError("Type mismatch") else: if not mul: factor = 1.0 / factor if self.data_type.is_int(): if isinstance(factor, int): self._scale_int(factor) else: raise TypeError("Type mismatch") else: if isinstance(factor, int) or isinstance(factor, float): self._scale_float(factor) else: raise TypeError( "Only muliplication with scalar supported") else: raise TypeError("Not numeric type") return self def __IADD__(self, delta, add=True): if self.isNumeric(): if type(self) == type(delta): if self.assert_binary(delta): if add: self._iadd(delta) else: self._isub(delta) else: raise TypeError("Type / size mismatch") else: if add: sign = 1 else: sign = -1 if self.data_type.is_int(): if isinstance(delta, int): self._shift_int(delta * sign) else: raise TypeError("Type mismatch") else: if isinstance(delta, int) or isinstance(delta, float): self._shift_float( delta * sign ) # Will call the _float() or _double() function in the C layer. else: raise TypeError("Type mismatch") else: raise TypeError("Type / size mismatch") return self def __iadd__(self, delta): return self.__IADD__(delta, True) def __isub__(self, delta): return self.__IADD__(delta, False) def __imul__(self, delta): return self.__IMUL__(delta, True) def __idiv__(self, delta): return self.__IMUL__(delta, False) ################################################################# def __abs__(self): if self.isNumeric(): copy = self.copy() copy._iabs() return copy else: raise TypeError( "The __abs__() function is only implemented for numeric types") def __add__(self, delta): copy = self.copy() copy += delta return copy def __radd__(self, delta): return self.__add__(delta) def __sub__(self, delta): copy = self.copy() copy -= delta return copy def __rsub__(self, delta): return self.__sub__(delta) * -1 def __mul__(self, factor): copy = self.copy() copy *= factor return copy def __rmul__(self, factor): return self.__mul__(factor) def __div__(self, factor): copy = self.copy() copy /= factor return copy # No __rdiv__() def sum(self): """ Will calculate the sum of all the elements in the keyword. String: Raise ValueError exception. Bool: The number of true values """ if self.data_type.is_int(): return self._int_sum() elif self.data_type.is_float(): return self._float_sum() elif self.data_type.is_double(): return self._float_sum() elif self.data_type.is_bool(): sum = 0 for elm in self: if elm: sum += 1 return sum else: raise ValueError( 'The keyword "%s" is of string type - sum is not implemented' % self.getName()) def assert_binary(self, other): """ Utility function to assert that keywords @self and @other can be combined. """ return self._assert_binary(other) ################################################################# def assign(self, value, mask=None, force_active=False): """ Assign a value to current kw instance. This method is used to assign value(s) to the current EclKW instance. The @value parameter can either be a scalar, or another EclKW instance. To set all elements of a keyword to 1.0: kw.assign(1.0) The numerical type of @value must be compatible with the current keyword. The optional @mask argument should be an EclRegion instance which can be used to limit the assignment to only parts of the EclKW. In the example below we select all the elements with PORO below 0.10, and then assign EQLNUM value 88 to those cells: grid = ecl.EclGrid("ECLIPSE.EGRID") reg = ecl.EclRegion(grid, false) init = ecl.EclFile("ECLIPSE.INIT") poro = init["PORO"][0] eqlnum = init["EQLNUM"][0] reg.select_below(poro, 0.10) eqlnum.assign(88, mask = reg) The EclRegion instance has two equivalent sets of selected indices; one consisting of active indices and one consisting of global indices. By default the assign() method will select the global indices if the keyword has nx*ny*nz elements and the active indices if the kw has nactive elements. By setting the optional argument @force_active to true, you can force the method to only modify the active indices, even though the keyword has nx*ny*nz elements; if the keyword has nactive elements the @force_active flag is not considered. """ if self.isNumeric(): if type(value) == type(self): if mask: mask.copy_kw(self, value, force_active) else: if self.assert_binary(value): self._copy_data(value) else: raise TypeError("Type / size mismatch") else: if mask: mask.set_kw(self, value, force_active) else: if self.data_type.is_int(): if isinstance(value, int): self._set_int(value) else: raise TypeError("Type mismatch") else: if isinstance(value, int) or isinstance(value, float): self._set_float(value) else: raise TypeError( "Only muliplication with scalar supported") def add(self, other, mask=None, force_active=False): """ See method assign() for documentation of optional arguments @mask and @force_active. """ if mask: mask.iadd_kw(self, other, force_active) else: return self.__iadd__(other) def sub(self, other, mask=None, force_active=False): """ See method assign() for documentation of optional arguments @mask and @force_active. """ if mask: mask.isub_kw(self, other, force_active) else: return self.__isub__(other) def mul(self, other, mask=None, force_active=False): """ See method assign() for documentation of optional arguments @mask and @force_active. """ if mask: mask.imul_kw(self, other, force_active) else: return self.__imul__(other) def div(self, other, mask=None, force_active=False): """ See method assign() for documentation of optional arguments @mask and @force_active. """ if mask: mask.idiv_kw(self, other, force_active) else: return self.__idiv__(other) def apply(self, func, arg=None, mask=None, force_active=False): """ Will apply the function @func on the keyword - inplace. The function @func should take a scalar value from the ecl_kw vector as input, and return a scalar value of the same type; optionally you can supply a second argument with the @arg attribute: def cutoff(x, limit): if x > limit: return x else: return 0 kw.apply(math.sin) kw.apply(cutoff, arg=0.10) See method assign() for documentation of optional arguments @mask and @force_active. """ if mask: active_list = mask.kw_index_list(self, force_active) if arg: for index in active_list: self.data_ptr[index] = func(self.data_ptr[index], arg) else: for index in active_list: self.data_ptr[index] = func(self.data_ptr[index]) else: if arg: for i in range(len(self)): self.data_ptr[i] = func(self.data_ptr[i], arg) else: for i in range(len(self)): self.data_ptr[i] = func(self.data_ptr[i]) def equal(self, other): """ Will check if the two keywords are (exactly) equal. The check is based on the content of the keywords, and not pointer comparison. """ if isinstance(other, EclKW): return self._equal(other) else: raise TypeError("Can only compare with another EclKW") def __eq__(self, other): return self.equal(other) def __hash__(self): return hash(self._get_header()) def equal_numeric(self, other, epsilon=1e-6, abs_epsilon=None, rel_epsilon=None): """Will check if two numerical keywords are ~nearly equal. If the keywords are of type integer, the comparison is absolute. If you pass in xxx_epsilon <= 0 the xxx_epsilon will be ignored in the test. """ if isinstance(other, EclKW): if abs_epsilon is None: abs_epsilon = epsilon if rel_epsilon is None: rel_epsilon = epsilon return self._equal_numeric(other, abs_epsilon, rel_epsilon) else: raise TypeError("Can only compare with another EclKW") ################################################################# def deep_copy(self): ecl_kw = self.__deep_copy__({}) return ecl_kw def fort_io_size(self): """ The number of bytes this keyword would occupy in a BINARY file. """ return self._get_fortio_size() def set_name(self, name): if len(name) > 8: raise ValueError( "Sorry: the name property must be max 8 characters long :-(") self._set_header(name) @property def name(self): n = self._get_header() return str(n) if n else '' def get_name(self): return self.name def resize(self, new_size): """ Will set the new size of the kw to @new_size. """ if new_size >= 0: self._resize(new_size) # Iteration is based on a pointer to the underlying storage, # that will generally by reset by the resize() call; i.e. we # need to call the __private_init() method again. self.__private_init() def get_min_max(self): """ Will return a touple (min,max) for numerical types. Will raise TypeError exception if the keyword is not of numerical type. """ if self.data_type.is_float(): min_ = ctypes.c_float() max_ = ctypes.c_float() self._max_min_float(ctypes.byref(max_), ctypes.byref(min_)) elif self.data_type.is_double(): min_ = ctypes.c_double() max_ = ctypes.c_double() self._max_min_double(ctypes.byref(max_), ctypes.byref(min_)) elif self.data_type.is_int(): min_ = ctypes.c_int() max_ = ctypes.c_int() self._max_min_int(ctypes.byref(max_), ctypes.byref(min_)) else: raise TypeError( "min_max property not defined for keywords of type: %s" % self.type) return (min_.value, max_.value) def get_max(self): mm = self.getMinMax() return mm[1] def get_min(self): mm = self.getMinMax() return mm[0] @property def type(self): return self.getEclType() @property def data_type(self): return self._get_data_type() @property def type_name(self): return self.data_type.type_name def type_name(self): return self.data_type.type_name def get_ecl_type(self): warnings.warn( "EclTypeEnum is deprecated. " + "You should instead provide an EclDataType", DeprecationWarning) return self._get_type() @property def header(self): return (self.getName(), len(self), self.typeName()) @property def array(self): a = self.data_ptr if not a == None: a.size = len(self) a.__parent__ = self # Inhibit GC return a def str_data(self, width, index1, index2, fmt): """ Helper function for str() method. """ data = [] s = "" for index in range(index1, index2): data.append(self[index]) for index in range(len(data)): s += fmt % data[index] if index % width == (width - 1): s += "\n" return s def str(self, width=5, max_lines=10, fmt=None): """ Return string representation of kw for pretty printing. The function will return a string consisting of a header, and then a chunk of data. The data will be formatted in @width columns, and a maximum of @max_lines lines. If @max_lines is not sufficient the first elements in the kewyord are represented, a .... continuation line and then the last part of the keyword. If @max_lines is None all of the vector will be printed, irrespective of how long it is. If a value is given for @fmt that is used as format string for each element, otherwise a type-specific default format is used. If given the @fmt string should contain spacing between the elements. The implementation of the builtin method __str__() is based on this method. """ s = "%-8s %8d %-4s\n" % (self.getName(), len(self), self.typeName()) lines = len(self) // width if not fmt: fmt = self.str_fmt + " " if max_lines is None or lines <= max_lines: s += self.str_data(width, 0, len(self), fmt) else: s1 = width * max_lines // 2 s += self.str_data(width, 0, s1, fmt) s += " .... \n" s += self.str_data(width, len(self) - s1, len(self), fmt) return s def __str__(self): """ Return string representation - see method str(). """ return self.str(width=5, max_lines=10) def numpy_view(self): """Will return a numpy view to the underlying data. The data in this numpy array is *shared* with the EclKW instance, meaning that updates in one will be reflected in the other. """ if self.dtype is numpy.float64: ct = ctypes.c_double elif self.dtype is numpy.float32: ct = ctypes.c_float elif self.dtype is numpy.int32: ct = ctypes.c_int else: raise ValueError( "Invalid type - numpy array only valid for int/float/double") ap = ctypes.cast(self.data_ptr, ctypes.POINTER(ct * len(self))) return numpy.frombuffer(ap.contents, dtype=self.dtype) def numpy_copy(self): """Will return a numpy array which contains a copy of the EclKW data. The numpy array has a separate copy of the data, so that changes to either the numpy array or the EclKW will *not* be reflected in the other datastructure. This is in contrast to the EclKW.numpyView() method where the underlying data is shared. """ view = self.numpyView() return numpy.copy(view) def fwrite(self, fortio): self._fwrite(fortio) def write_grdecl(self, file): """ Will write keyword in GRDECL format. This method will write the current keyword in GRDECL format, the @file argument must be a Python file handle to an already opened file. In the example below we load the porosity from an existing GRDECL file, set all poro values below 0.05 to 0.00 and write back an updated GRDECL file. poro = ecl.EclKW.read_grdecl(open("poro1.grdecl", "r"), "PORO") grid = ecl.EclGrid("ECLIPSE.EGRID") reg = ecl.EclRegion(grid, False) reg.select_below(poro, 0.05) poro.assign(0.0, mask=reg) fileH = open("poro2.grdecl", "w") poro.write_grdecl(fileH) fileH.close() """ cfile = CFILE(file) self._fprintf_grdecl(cfile) def fprintf_data(self, file, fmt=None): """ Will print the keyword data formatted to file. The @file argument should be a python file handle to a file opened for writing. The @fmt argument is used as fprintf() format specifier, observe that the format specifier should include a separation character between the elements. If no @fmt argument is supplied the default str_fmt specifier is used for every element, separated by a newline. In the case of boolean data the function will print o and 1 for False and True respectively. For string data the function will print the data as 8 characters long string with blank padding on the right. """ if fmt is None: fmt = self.str_fmt + "\n" cfile = CFILE(file) self._fprintf_data(fmt, cfile) def fix_uninitialized(self, grid): """ Special case function for region code. """ dims = grid.getDims() actnum = grid.exportACTNUM() self._fix_uninitialized(dims[0], dims[1], dims[2], actnum.getDataPtr()) def get_data_ptr(self): if self.data_type.is_int(): return self._int_ptr() elif self.data_type.is_float(): return self._float_ptr() elif self.data_type.is_double(): return self._double_ptr() else: raise ValueError("Only numeric types can export data pointer") def first_different(self, other, offset=0, epsilon=0, abs_epsilon=None, rel_epsilon=None): if len(self) != len(other): raise ValueError("Keywords must have equal size") if offset >= len(self): raise IndexError("Offset:%d invalid - size:%d" % (offset, len(self))) if self.getEclType() != other.getEclType(): raise TypeError("The two keywords have different type") if abs_epsilon is None: abs_epsilon = epsilon if rel_epsilon is None: rel_epsilon = epsilon return self._first_different(other, offset, abs_epsilon, rel_epsilon)
# # ERT is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ERT is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. # # See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html> # for more details. from ecl.ecl import EclPrototype _phase_deltag = EclPrototype( "double ecl_grav_phase_deltag( double, double ,double , ecl_grid , ecl_file , ecl_kw , ecl_kw , ecl_kw , ecl_kw , ecl_kw , ecl_kw" ) def phase_deltag(xyz, grid, init, sat1, rho1, porv1, sat2, rho2, porv2): return _phase_deltag(xyz[0], xyz[1], xyz[2], grid.c_ptr, init.c_ptr, sat1.c_ptr, rho1.c_ptr, porv1.c_ptr, sat2.c_ptr, rho2.c_ptr, porv2.c_ptr) # 1. All restart files should have water, i.e. the SWAT keyword. # 2. All phases present in the restart file should also be present as densities, # in addition the model must contain one additional phase - which should have a density. # 3. The restart files can never contain oil saturation.
class EclDataType(BaseCClass): TYPE_NAME = "ecl_data_type" _alloc = EclPrototype("void* ecl_type_alloc_python(ecl_type_enum, size_t)", bind=False) _alloc_from_type = EclPrototype( "void* ecl_type_alloc_from_type_python(ecl_type_enum)", bind=False) _alloc_from_name = EclPrototype( "void* ecl_type_alloc_from_name_python(char*)", bind=False) _free = EclPrototype("void ecl_type_free_python(ecl_data_type)") _get_type = EclPrototype( "ecl_type_enum ecl_type_get_type_python(ecl_data_type)") _get_element_size = EclPrototype( "size_t ecl_type_get_sizeof_ctype_fortio_python(ecl_data_type)") _is_int = EclPrototype("bool ecl_type_is_int_python(ecl_data_type)") _is_char = EclPrototype("bool ecl_type_is_char_python(ecl_data_type)") _is_float = EclPrototype("bool ecl_type_is_float_python(ecl_data_type)") _is_double = EclPrototype("bool ecl_type_is_double_python(ecl_data_type)") _is_mess = EclPrototype("bool ecl_type_is_mess_python(ecl_data_type)") _is_bool = EclPrototype("bool ecl_type_is_bool_python(ecl_data_type)") _is_string = EclPrototype("bool ecl_type_is_string_python(ecl_data_type)") _get_name = EclPrototype("char* ecl_type_alloc_name_python(ecl_data_type)") _is_numeric = EclPrototype( "bool ecl_type_is_numeric_python(ecl_data_type)") _is_equal = EclPrototype( "bool ecl_type_is_equal_python(ecl_data_type, ecl_data_type)") def __init__(self, type_enum=None, element_size=None, type_name=None): self._assert_valid_arguments(type_enum, element_size, type_name) if type_name: c_ptr = self._alloc_from_name(type_name) elif element_size is None: c_ptr = self._alloc_from_type(type_enum) else: c_ptr = self._alloc(type_enum, element_size) super(EclDataType, self).__init__(c_ptr) def _assert_valid_arguments(self, type_enum, element_size, type_name): if type_name is not None: if type_enum is not None or element_size is not None: err_msg = ("Type name given (%s). Expected both " + "type_enum and element_size to be None") raise ValueError(err_msg % type_name) elif type_enum is None: raise ValueError("Both type_enum and type_name is None!") elif type_enum == EclTypeEnum.ECL_STRING_TYPE: if element_size is None: raise ValueError("When creating an ECL_STRING one must " + "provide an element size!") if not (0 <= element_size <= 999): raise ValueError("Expected element_size to be in the range " + "[0, 999], was: %d" % element_size) @property def type(self): return self._get_type() @property def element_size(self): return self._get_element_size() @property def type_name(self): return self._get_name() def free(self): self._free() def is_int(self): return self._is_int() def is_char(self): return self._is_char() def is_float(self): return self._is_float() def is_double(self): return self._is_double() def is_mess(self): return self._is_mess() def is_bool(self): return self._is_bool() def is_string(self): return self._is_string() def is_numeric(self): return self._is_numeric() def is_equal(self, other): return self._is_equal(other) def __eq__(self, other): if isinstance(other, self.__class__): return self.is_equal(other) return False def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.type, self.element_size)) @classmethod def create_from_type_name(cls, name): return EclDataType(type_name=name) # Enables one to fetch a type as EclDataType.ECL_XXXX class classproperty(object): def __init__(self, fget): self.fget = fget def __get__(self, owner_self, owner_cls): return self.fget(owner_cls) @classproperty def ECL_INT(cls): return EclDataType(EclTypeEnum.ECL_INT_TYPE) @classproperty def ECL_FLOAT(cls): return EclDataType(EclTypeEnum.ECL_FLOAT_TYPE) @classproperty def ECL_DOUBLE(cls): return EclDataType(EclTypeEnum.ECL_DOUBLE_TYPE) @classproperty def ECL_BOOL(cls): return EclDataType(EclTypeEnum.ECL_BOOL_TYPE) @classproperty def ECL_MESS(cls): return EclDataType(EclTypeEnum.ECL_MESS_TYPE) @classproperty def ECL_CHAR(cls): return EclDataType(EclTypeEnum.ECL_CHAR_TYPE) @classmethod def ECL_STRING(cls, elem_size): return EclDataType(EclTypeEnum.ECL_STRING_TYPE, elem_size)
class EclGridGenerator: _alloc_rectangular = EclPrototype("ecl_grid_obj ecl_grid_alloc_rectangular( int , int , int , double , double , double , int*)" , bind = False) @classmethod def createRectangular(cls, dims , dV , actnum = None): """ Will create a new rectangular grid. @dims = (nx,ny,nz) @dVg = (dx,dy,dz) With the default value @actnum == None all cells will be active, """ if actnum is None: ecl_grid = cls._alloc_rectangular( dims[0] , dims[1] , dims[2] , dV[0] , dV[1] , dV[2] , None ) else: if not isinstance(actnum , IntVector): tmp = IntVector(initial_size = len(actnum)) for (index , value) in enumerate(actnum): tmp[index] = value actnum = tmp if not len(actnum) == dims[0] * dims[1] * dims[2]: raise ValueError("ACTNUM size mismatch: len(ACTNUM):%d Expected:%d" % (len(actnum) , dims[0] * dims[1] * dims[2])) ecl_grid = cls._alloc_rectangular( dims[0] , dims[1] , dims[2] , dV[0] , dV[1] , dV[2] , actnum.getDataPtr() ) # If we have not succeeded in creatin the grid we *assume* the # error is due to a failed malloc. if ecl_grid is None: raise MemoryError("Failed to allocated regualar grid") return ecl_grid @classmethod def createSingleCellGrid(cls, corners): """ Provided with the corners of the grid in a similar manner as the eight corners are output for a single cell, this method will create a grid consisting of a single cell with the specified corners as its corners. """ zcorn = [corners[i][2] for i in range(8)] flatten = lambda l : [elem for sublist in l for elem in sublist] coord = [(corners[i], corners[i+4]) for i in range(4)] coord = flatten(flatten(coord)) def constructFloatKW(name, values): kw = EclKW(name, len(values), EclDataType.ECL_FLOAT) for i in range(len(values)): kw[i] = values[i] return kw grid = EclGrid.create((1,1,1), constructFloatKW("ZCORN", zcorn), constructFloatKW("COORD", coord), None) if not corners == [grid.getCellCorner(i, 0) for i in range(8)]: raise AssertionError("Failed to generate single cell grid. " + "Did not end up the expected corners.") return grid @classmethod def createGrid(cls, dims, dV, offset=1, escape_origo_shift=(1,1,0), irregular_offset=False, irregular=False, concave=False, faults=False, scale=1, translation=(0,0,0), rotate=False, misalign=False): """ Will create a new grid where each cell is a parallelogram (skewed by z-value). The number of cells are given by @dims = (nx, ny, nz) and the dimention of each cell by @dV = (dx, dy, dz). All cells are guaranteed to not be self-intersecting. Hence, no twisted cells and somewhat meaningfull cells. @offset gives how much the layers should fluctuate or "wave" as you move along the X-axis. @irregular_offset decides whether the offset should be constant or increase by dz/2 every now and then. @irregular if true some of the layers will be inclining and others declining at the start. @concave decides whether the cells are to be convex or not. In particular, if set to False, all cells of the grid will be concave. @escape_origo_shift is used to prevent any cell of having corners in (0,0,z) as there is a heuristic in ecl_grid.c that marks such cells as tainted. @faults decides if there are to be faults in the grid. @scale A positive number that scales the "lower" endpoint of all coord's. In particular, @scale != 1 creates trapeziod cells in both the XZ and YZ-plane. @translation the lower part of the grid is translated ("slided") by the specified additive factor. @rotate the lower part of the grid is rotated 90 degrees around its center. @misalign will toggle COORD's slightly in various directions to break alignment Note that cells in the lowermost layer can have multiple corners at the same point. For testing it should give good coverage of the various scenarios this method can produce, by leting @dims be (10,10,10), @dV=(2,2,2), @offset=1, and try all 4 different configurations of @concave and @irregular_offset. """ nx, ny, nz = dims dx, dy, dz = dV # Validate arguments if min(dims + dV) <= 0: raise ValueError("Expected positive grid and cell dimentions") if offset < 0: raise ValueError("Expected non-negative offset") if irregular and offset + (dz/2. if irregular_offset else 0) > dz: raise AssertionError("Arguments can result in self-" + "intersecting cells. Increase dz, deactivate eiter " + "irregular or irregular_offset, or decrease offset to avoid " + "any problems") verbose = lambda l : [elem for elem in l for i in range(2)][1:-1:] flatten = lambda l : [elem for sublist in l for elem in sublist] # Compute zcorn z = escape_origo_shift[2] zcorn = [z]*(4*nx*ny) for k in range(nz-1): z = z+dz local_offset = offset + (dz/2. if irregular_offset and k%2 == 0 else 0) layer = [] for i in range(ny+1): shift = ((i if concave else 0) + (k/2 if irregular else 0)) % 2 path = [z if i%2 == shift else z+local_offset for i in range(nx+1)] layer.append(verbose(path)) zcorn = zcorn + (2*flatten(verbose(layer))) z = z+dz zcorn = zcorn + ([z]*(4*nx*ny)) if faults: # Ensure that drop does not align with grid structure drop = (offset+dz)/2. if abs(offset-dz/2.) > 0.2 else offset + 0.4 zcorn = cls.__createFaults(nx, ny, nz, zcorn, drop) cls.assertZcorn(nx, ny, nz, zcorn) # Compute coord coord = [] for j, i in itertools.product(range(ny+1), range(nx+1)): x, y = i*dx+escape_origo_shift[0], j*dy+escape_origo_shift[1] coord = coord + [x, y, escape_origo_shift[2], x, y, z] # Apply transformations lower_center = ( nx*dx/2. + escape_origo_shift[0], ny*dy/2. + escape_origo_shift[1] ) if misalign: coord = cls.__misalignCoord(coord, dims, dV) coord = cls.__scaleCoord(coord, scale, lower_center) if rotate: coord = cls.__rotateCoord(coord, lower_center) coord = cls.__translateCoord(coord, translation) cls.assertCoord(nx, ny, nz, coord) # Construct grid def constructFloatKW(name, values): kw = EclKW(name, len(values), EclDataType.ECL_FLOAT) for i in range(len(values)): kw[i] = values[i] return kw return EclGrid.create(dims, constructFloatKW("ZCORN", zcorn), constructFloatKW("COORD", coord), None) @classmethod def __createFaults(cls, nx, ny, nz, zcorn, drop): """ Will create several faults consisting of all cells such that either its i or j index is 1 modulo 3. """ plane_size = 4*nx*ny for x, y, z in itertools.product(range(nx), range(ny), range(nz)): if x%3 != 1 and y%3 != 1: continue corner = [0]*8 corner[0] = 2*z*plane_size + 4*y*nx + 2*x corner[1] = corner[0] + 1 corner[2] = corner[0] + 2*nx corner[3] = corner[2] + 1 for i in range(4, 8): corner[i] = corner[i-4] + plane_size for c in corner: zcorn[c] = zcorn[c] + drop return zcorn @classmethod def assertZcorn(cls, nx, ny, nz, zcorn): """ Raises an AssertionError if the zcorn is not as expected. In patricular, it is verified that: - zcorn has the approperiate length (8*nx*ny*nz) and - that no cell is twisted. """ if len(zcorn) != 8*nx*ny*nz: raise AssertionError( "Expected len(zcorn) to be %d, was %d" % (8*nx*ny*nz, len(zcorn)) ) plane_size = 4*nx*ny for p in range(8*nx*ny*nz - plane_size): if zcorn[p] > zcorn[p + plane_size]: raise AssertionError( "Twisted cell was created. " + "Decrease offset or increase dz to avoid this!" ) @classmethod def __scaleCoord(cls, coord, scale, lower_center): coord = numpy.array([ map(float, coord[i:i+6:]) for i in range(0, len(coord), 6) ]) origo = numpy.array(3*[0.] + list(lower_center) + [0]) scale = numpy.array(3*[1.] + 2*[scale] + [1]) coord = scale * (coord-origo) + origo return coord.flatten().tolist() @classmethod def __misalignCoord(cls, coord, dims, dV): nx, ny, nz = dims coord = numpy.array([ map(float, coord[i:i+6:]) for i in range(0, len(coord), 6) ]) adjustment = numpy.array([ (0, 0, 0, i*dV[0]/2., j*dV[1]/2., 0) for i, j in itertools.product([-1, 0, 1], repeat=2) ]) for i, c in enumerate(coord): # Leave the outermost coords alone if i < nx+1 or i >= len(coord)-(nx+1): continue if i%(nx+1) in [0, nx]: continue c += adjustment[i%len(adjustment)] return coord.flatten().tolist() @classmethod def __rotateCoord(cls, coord, lower_center): coord = numpy.array([ map(float, coord[i:i+6:]) for i in range(0, len(coord), 6) ]) origo = numpy.array(3*[0.] + list(lower_center) + [0]) coord -= origo for c in coord: c[3], c[4] = -c[4], c[3] coord += origo return coord.flatten().tolist() @classmethod def __translateCoord(cls, coord, translation): coord = numpy.array([ map(float, coord[i:i+6:]) for i in range(0, len(coord), 6) ]) translation = numpy.array(3*[0.] + list(translation)) coord = coord + translation return coord.flatten().tolist() @classmethod def assertCoord(cls, nx, ny, nz, coord): """ Raises an AssertionError if the coord is not as expected. In particular, it is verfied that: - coord has the approperiate length (6*(nx+1)*(ny+1)) and - that all values are positive. """ if len(coord) != 6*(nx+1)*(ny+1): raise AssertionError( "Expected len(coord) to be %d, was %d" % (6*(nx+1)*(ny+1), len(coord)) ) if min(coord) < 0: raise AssertionError("Negative COORD values was generated. " + "This is likely due to a tranformation. " + "Increasing the escape_origio_shift will most likely " + "fix the problem")
class FaultBlockLayer(BaseCClass): TYPE_NAME = "fault_block_layer" _alloc = EclPrototype("void* fault_block_layer_alloc(ecl_grid , int)", bind = False) _free = EclPrototype("void fault_block_layer_free(fault_block_layer)") _size = EclPrototype("int fault_block_layer_get_size(fault_block_layer)") _iget_block = EclPrototype("fault_block_ref fault_block_layer_iget_block(fault_block_layer, int)") _add_block = EclPrototype("fault_block_ref fault_block_layer_add_block(fault_block_layer, int)") _get_block = EclPrototype("fault_block_ref fault_block_layer_get_block(fault_block_layer, int)") _del_block = EclPrototype("void fault_block_layer_del_block(fault_block_layer, int)") _has_block = EclPrototype("bool fault_block_layer_has_block(fault_block_layer, int)") _scan_keyword = EclPrototype("bool fault_block_layer_scan_kw(fault_block_layer, ecl_kw)") _load_keyword = EclPrototype("bool fault_block_layer_load_kw(fault_block_layer, ecl_kw)") _getK = EclPrototype("int fault_block_layer_get_k(fault_block_layer)") _get_next_id = EclPrototype("int fault_block_layer_get_next_id(fault_block_layer)") _scan_layer = EclPrototype("void fault_block_layer_scan_layer( fault_block_layer , layer)") _insert_block_content = EclPrototype("void fault_block_layer_insert_block_content( fault_block_layer , fault_block)") _export_kw = EclPrototype("bool fault_block_layer_export( fault_block_layer , ecl_kw )") _get_layer = EclPrototype("layer_ref fault_block_layer_get_layer( fault_block_layer )") def __init__(self , grid , k): c_ptr = self._alloc( grid , k) if c_ptr: super(FaultBlockLayer, self).__init__(c_ptr) else: raise ValueError("Invalid input - failed to create FaultBlockLayer") # The underlying C implementation uses lazy evaluation and # needs to hold on to the grid reference. We therefor take # references to it here, to protect against premature garbage # collection. self.grid_ref = grid def __len__(self): return self._size() def __getitem__(self , index): """ @rtype: FaultBlock """ if isinstance(index, int): if index < 0: index += len(self) if 0 <= index < len(self): return self._iget_block( index ).setParent(self) else: raise IndexError("Index:%d out of range: [0,%d)" % (index , len(self))) elif isinstance(index,tuple): i,j = index if 0 <= i < self.grid_ref.getNX() and 0 <= j < self.grid_ref.getNY(): geo_layer = self.getGeoLayer() block_id = geo_layer[i,j] if block_id == 0: raise ValueError("No fault block defined for location (%d,%d)" % (i,j)) else: return self.getBlock( block_id ) else: raise IndexError("Invalid i,j : (%d,%d)" % (i,j)) else: raise TypeError("Index should be integer type") def __contains__(self , block_id): return self._has_block( block_id) def scanKeyword(self , fault_block_kw): """ Will reorder the block ids, and ensure single connectedness. Assign block_id to zero blocks. """ ok = self._scan_keyword( fault_block_kw ) if not ok: raise ValueError("The fault block keyword had wrong type/size: type:%s size:%d grid_size:%d" % (fault_block_kw.type_name , len(fault_block_kw) , self.grid_ref.getGlobalSize())) def loadKeyword(self , fault_block_kw): """ Will load directly from keyword - without reorder; ignoring zero. """ ok = self._load_keyword( fault_block_kw ) if not ok: raise ValueError("The fault block keyword had wrong type/size: type:%s size:%d grid_size:%d" % (fault_block_kw.typeName() , len(fault_block_kw) , self.grid_ref.getGlobalSize())) def getBlock(self , block_id): """ @rtype: FaultBlock """ if block_id in self: return self._get_block(block_id).setParent(self) else: raise KeyError("No blocks with ID:%d in this layer" % block_id) def deleteBlock(self , block_id): if block_id in self: self._del_block( block_id) else: raise KeyError("No blocks with ID:%d in this layer" % block_id) def addBlock(self , block_id = None): if block_id is None: block_id = self.getNextID() if block_id in self: raise KeyError("Layer already contains block with ID:%s" % block_id) else: return self._add_block(block_id).setParent(self) def getNextID(self): return self._get_next_id( ) def getK(self): return self._getK( ) def free(self): self._free( ) def scanLayer( self , layer): self._scan_layer( layer ) def insertBlockContent(self , block): self._insert_block_content( block ) def exportKeyword(self , kw): if len(kw) != self.grid_ref.getGlobalSize(): raise ValueError("The size of the target keyword must be equal to the size of the grid. Got:%d Expected:%d" % (len(kw) , self.grid_ref.getGlobalSize())) if not kw.data_type.is_int(): raise TypeError("The target kewyord must be of integer type") self._export_kw( kw ) def addFaultBarrier(self , fault , link_segments = False): layer = self.getGeoLayer( ) layer.addFaultBarrier( fault , self.getK() , link_segments ) def addFaultLink(self , fault1 , fault2 ): if not fault1.intersectsFault( fault2 , self.getK()): layer = self.getGeoLayer() layer.addIJBarrier( fault1.extendToFault( fault2 , self.getK() ) ) def joinFaults(self , fault1 , fault2): if not fault1.intersectsFault( fault2 , self.getK()): layer = self.getGeoLayer() try: layer.addIJBarrier( Fault.joinFaults( fault1 , fault2 , self.getK()) ) except ValueError: print('Failed to join faults %s and %s' % (fault1.getName() , fault2.getName())) raise ValueError("") def addPolylineBarrier(self , polyline): layer = self.getGeoLayer() p0 = polyline[0] c0 = self.grid_ref.findCellCornerXY( p0[0] , p0[1] , self.getK() ) i,j = self.grid_ref.findCellXY( p0[0] , p0[1] , self.getK() ) print('%g,%g -> %d,%d %d' % (p0[0] , p0[1] , i,j,c0)) for index in range(1,len(polyline)): p1 = polyline[index] c1 = self.grid_ref.findCellCornerXY( p1[0] , p1[1] , self.getK() ) i,j = self.grid_ref.findCellXY( p1[0] , p1[1] , self.getK() ) layer.addInterpBarrier( c0 , c1 ) print('%g,%g -> %d,%d %d' % (p1[0] , p1[1] , i,j,c1)) print('Adding barrier %d -> %d' % (c0 , c1)) c0 = c1 def getGeoLayer(self): """Returns the underlying geometric layer.""" return self._get_layer( ) def cellContact(self , p1 , p2): layer = self.getGeoLayer() return layer.cellContact(p1,p2)
class EclSubsidence(BaseCClass): """ Holding ECLIPSE results for calculating subsidence changes. The EclSubsidence class is a collection class holding the results from ECLIPSE forward modelling of subsidence surveys. Observe that the class is focused on the ECLIPSE side of things, and does not have any notion of observed values or measurement locations; that should be handled by the scope using the EclSubsidence class. Typical use of the EclSubsidence class involves the following steps: 1. Create the EclSubsidence instance. 2. Add surveys with the add_survey_XXXX() methods. 3. Evalute the subsidence response with the eval() method. """ TYPE_NAME = "ecl_subsidence" _alloc = EclPrototype("void* ecl_subsidence_alloc( ecl_grid , ecl_file )", bind=False) _free = EclPrototype("void ecl_subsidence_free( ecl_subsidence )") _add_survey_PRESSURE = EclPrototype( "void* ecl_subsidence_add_survey_PRESSURE( ecl_subsidence , char* , ecl_file_view )" ) _eval = EclPrototype( "double ecl_subsidence_eval( ecl_subsidence , char* , char* , ecl_region , double , double , double, double, double)" ) _eval_geertsma = EclPrototype( "double ecl_subsidence_eval_geertsma( ecl_subsidence , char* , char* , ecl_region , double , double , double, double, double, double)" ) _has_survey = EclPrototype( "bool ecl_subsidence_has_survey( ecl_subsidence , char*)") def __init__(self, grid, init_file): """ Creates a new EclSubsidence instance. The input arguments @grid and @init_file should be instances of EclGrid and EclFile respectively. """ self.init_file = init_file # Inhibit premature garbage collection of init_file c_ptr = self._alloc(grid, init_file) super(EclSubsidence, self).__init__(c_ptr) def __contains__(self, survey_name): return self._has_survey(survey_name) def add_survey_PRESSURE(self, survey_name, restart_file): """ Add new survey based on PRESSURE keyword. Add a new survey; in this context a survey is the state of reservoir, i.e. an ECLIPSE restart file. The @survey_name input argument will be used when refering to this survey at a later stage. The @restart_file input argument should be an EclFile instance with data from one report step. A typical way to load the @restart_file argument is: import datetime import ecl.ecl.ecl as ecl ... ... date = datetime.datetime( year , month , day ) restart_file1 = ecl.EclFile.restart_block( "ECLIPSE.UNRST" , dtime = date) restart_file2 = ecl.EclFile.restart_block( "ECLIPSE.UNRST" , report_step = 67 ) The pore volume is calculated from the initial pore volume and the PRESSURE keyword from the restart file. """ self._add_survey_PRESSURE(survey_name, restart_file) def eval_geertsma(self, base_survey, monitor_survey, pos, youngs_modulus, poisson_ratio, seabed, region=None): if not base_survey in self: raise KeyError("No such survey: %s" % base_survey) if monitor_survey is not None: if not monitor_survey in self: raise KeyError("No such survey: %s" % monitor_survey) return self._eval_geertsma(base_survey, monitor_survey, region, pos[0], pos[1], pos[2], youngs_modulus, poisson_ratio, seabed) def eval(self, base_survey, monitor_survey, pos, compressibility, poisson_ratio, region=None): """ Calculates the subsidence change between two surveys. This is the method everything is leading up to; will calculate the change in subsidence, in centimeters, between the two surveys named @base_survey and @monitor_survey. The monitor survey can be 'None' - the resulting answer has nothing whatsovever to do with subsidence, but can be interesting to determine the numerical size of the quantities which are subtracted in a 4D study. The @pos argument should be a tuple of three elements with the (utm_x , utm_y , depth) position where we want to evaluate the change in subsidence. If supplied the optional argument @region should be an EclRegion() instance; this region will be used to limit the part of the reserviour included in the subsidence calculations. The argument @compressibility is the total reservoir compressibility. """ if not base_survey in self: raise KeyError("No such survey: %s" % base_survey) if not monitor_survey in self: raise KeyError("No such survey: %s" % monitor_survey) return self._eval(base_survey, monitor_survey, region, pos[0], pos[1], pos[2], compressibility, poisson_ratio) def free(self): self._free()
Will export all well related variables for wells 'OP1' and 'OP2' and all total field vectors. """ if keys is None: var_list = self.keys() else: var_list = StringList() for key in keys: var_list |= self.keys(pattern=key) self._export_csv(filename, var_list, date_format, sep) import ecl.ecl.ecl_sum_keyword_vector EclSum._dump_csv_line = EclPrototype( "void ecl_sum_fwrite_interp_csv_line(ecl_sum, time_t, ecl_sum_vector, FILE)", bind=False) EclSum._get_interp_vector = EclPrototype( "void ecl_sum_get_interp_vector(ecl_sum, time_t, ecl_sum_vector, double_vector)", bind=False) monkey_the_camel(EclSum, 'varType', EclSum.var_type, classmethod) monkey_the_camel(EclSum, 'addVariable', EclSum.add_variable) monkey_the_camel(EclSum, 'addTStep', EclSum.add_t_step) monkey_the_camel(EclSum, 'assertKeyValid', EclSum.assert_key_valid) monkey_the_camel(EclSum, 'scaleVector', EclSum.scale_vector) monkey_the_camel(EclSum, 'shiftVector', EclSum.shift_vector) monkey_the_camel(EclSum, 'timeRange', EclSum.time_range) monkey_the_camel(EclSum, 'blockedProduction', EclSum.blocked_production) monkey_the_camel(EclSum, 'getDataStartTime', EclSum.get_data_start_time) monkey_the_camel(EclSum, 'getStartTime', EclSum.get_start_time)
class EclSum(BaseCClass): TYPE_NAME = "ecl_sum" _fread_alloc_case = EclPrototype( "void* ecl_sum_fread_alloc_case__(char*, char*, bool)", bind=False) _fread_alloc = EclPrototype( "void* ecl_sum_fread_alloc(char*, stringlist, char*, bool)", bind=False) _create_restart_writer = EclPrototype( "ecl_sum_obj ecl_sum_alloc_restart_writer(char*, char*, bool, bool, char*, time_t, bool, int, int, int)", bind=False) _iiget = EclPrototype("double ecl_sum_iget(ecl_sum, int, int)") _free = EclPrototype("void ecl_sum_free(ecl_sum)") _data_length = EclPrototype("int ecl_sum_get_data_length(ecl_sum)") _scale_vector = EclPrototype( "void ecl_sum_scale_vector(ecl_sum, int, double)") _shift_vector = EclPrototype( "void ecl_sum_shift_vector(ecl_sum, int, double)") _iget_sim_days = EclPrototype( "double ecl_sum_iget_sim_days(ecl_sum, int) ") _iget_report_step = EclPrototype( "int ecl_sum_iget_report_step(ecl_sum, int) ") _iget_mini_step = EclPrototype( "int ecl_sum_iget_mini_step(ecl_sum, int) ") _iget_sim_time = EclPrototype( "time_t ecl_sum_iget_sim_time(ecl_sum, int) ") _get_report_end = EclPrototype( "int ecl_sum_iget_report_end(ecl_sum, int)") _get_general_var = EclPrototype( "double ecl_sum_get_general_var(ecl_sum, int, char*)") _get_general_var_index = EclPrototype( "int ecl_sum_get_general_var_params_index(ecl_sum, char*)") _get_general_var_from_sim_days = EclPrototype( "double ecl_sum_get_general_var_from_sim_days(ecl_sum, double, char*)" ) _get_general_var_from_sim_time = EclPrototype( "double ecl_sum_get_general_var_from_sim_time(ecl_sum, time_t, char*)" ) _solve_days = EclPrototype( "double_vector_obj ecl_sum_alloc_days_solution(ecl_sum, char*, double, bool)" ) _solve_dates = EclPrototype( "time_t_vector_obj ecl_sum_alloc_time_solution(ecl_sum, char*, double, bool)" ) _get_first_gt = EclPrototype( "int ecl_sum_get_first_gt(ecl_sum, int, double)") _get_first_lt = EclPrototype( "int ecl_sum_get_first_lt(ecl_sum, int, double)") _get_start_date = EclPrototype("time_t ecl_sum_get_start_time(ecl_sum)") _get_end_date = EclPrototype("time_t ecl_sum_get_end_time(ecl_sum)") _get_last_report_step = EclPrototype( "int ecl_sum_get_last_report_step(ecl_sum)") _get_first_report_step = EclPrototype( "int ecl_sum_get_first_report_step(ecl_sum)") _select_matching_keys = EclPrototype( "void ecl_sum_select_matching_general_var_list(ecl_sum, char*, stringlist)" ) _has_key = EclPrototype("bool ecl_sum_has_general_var(ecl_sum, char*)") _check_sim_time = EclPrototype( "bool ecl_sum_check_sim_time(ecl_sum, time_t)") _check_sim_days = EclPrototype( "bool ecl_sum_check_sim_days(ecl_sum, double)") _sim_length = EclPrototype("double ecl_sum_get_sim_length(ecl_sum)") _get_first_day = EclPrototype("double ecl_sum_get_first_day(ecl_sum)") _get_data_start = EclPrototype("time_t ecl_sum_get_data_start(ecl_sum)") _get_unit = EclPrototype("char* ecl_sum_get_unit(ecl_sum, char*)") _get_simcase = EclPrototype("char* ecl_sum_get_case(ecl_sum)") _get_base = EclPrototype("char* ecl_sum_get_base(ecl_sum)") _get_path = EclPrototype("char* ecl_sum_get_path(ecl_sum)") _get_abs_path = EclPrototype("char* ecl_sum_get_abs_path(ecl_sum)") _get_report_step_from_time = EclPrototype( "int ecl_sum_get_report_step_from_time(ecl_sum, time_t)") _get_report_step_from_days = EclPrototype( "int ecl_sum_get_report_step_from_days(ecl_sum, double)") _get_report_time = EclPrototype( "time_t ecl_sum_get_report_time(ecl_sum, int)") _fwrite_sum = EclPrototype("void ecl_sum_fwrite(ecl_sum)") _set_case = EclPrototype("void ecl_sum_set_case(ecl_sum, char*)") _alloc_time_vector = EclPrototype( "time_t_vector_obj ecl_sum_alloc_time_vector(ecl_sum, bool)") _alloc_data_vector = EclPrototype( "double_vector_obj ecl_sum_alloc_data_vector(ecl_sum, int, bool)") _get_var_node = EclPrototype( "smspec_node_ref ecl_sum_get_general_var_node(ecl_sum, char*)") _create_well_list = EclPrototype( "stringlist_obj ecl_sum_alloc_well_list(ecl_sum, char*)") _create_group_list = EclPrototype( "stringlist_obj ecl_sum_alloc_group_list(ecl_sum, char*)") _add_variable = EclPrototype( "smspec_node_ref ecl_sum_add_var(ecl_sum, char*, char*, int, char*, double)" ) _add_tstep = EclPrototype( "ecl_sum_tstep_ref ecl_sum_add_tstep(ecl_sum, int, double)") _export_csv = EclPrototype( "void ecl_sum_export_csv(ecl_sum, char*, stringlist, char*, char*)") _identify_var_type = EclPrototype( "ecl_sum_var_type ecl_sum_identify_var_type(char*)", bind=False) def __init__(self, load_case, join_string=":", include_restart=True): """ Loads a new EclSum instance with summary data. Loads a new summary results from the ECLIPSE case given by argument @load_case; @load_case should be the basename of the ECLIPSE simulation you want to load. @load_case can contain a leading path component, and also an extension - the latter will be ignored. The @join_string is the string used when combining elements from the WGNAMES, KEYWORDS and NUMS vectors into a composit key; with @join_string == ":" the water cut in well OP_1 will be available as "WWCT:OP_1". If the @include_restart parameter is set to true the summary loader will, in the case of a restarted ECLIPSE simulation, try to load summary results also from the restarted case. """ if not load_case: raise ValueError( 'load_case must be the basename of the simulation') c_pointer = self._fread_alloc_case(load_case, join_string, include_restart) if c_pointer is None: raise IOError( "Failed to create summary instance from argument:%s" % load_case) super(EclSum, self).__init__(c_pointer) self.__private_init() self._load_case = load_case @classmethod def load(cls, smspec_file, unsmry_file, key_join_string=":", include_restart=True): if not os.path.isfile(smspec_file): raise IOError("No such file: %s" % smspec_file) if not os.path.isfile(unsmry_file): raise IOError("No such file: %s" % unsmry_file) data_files = StringList() data_files.append(unsmry_file) c_ptr = cls._fread_alloc(smspec_file, data_files, key_join_string, include_restart) if c_ptr is None: raise IOError("Failed to create summary instance") ecl_sum = cls.createPythonObject(c_ptr) ecl_sum._load_case = smspec_file return ecl_sum @classmethod def createCReference(cls, c_pointer, parent=None): result = super(EclSum, cls).createCReference(c_pointer, parent) if not result is None: result.__private_init() return result @classmethod def createPythonObject(cls, c_pointer): result = super(EclSum, cls).createPythonObject(c_pointer) result.__private_init() return result @classmethod def var_type(cls, keyword): return cls._identify_var_type(keyword) @staticmethod def writer(case, start_time, nx, ny, nz, fmt_output=False, unified=True, time_in_days=True, key_join_string=":"):
class FaultBlock(BaseCClass): TYPE_NAME = "fault_block" _get_xc = EclPrototype("double fault_block_get_xc(fault_block)") _get_yc = EclPrototype("double fault_block_get_yc(fault_block)") _get_block_id = EclPrototype( "int fault_block_get_id(fault_block)") _get_size = EclPrototype( "int fault_block_get_size(fault_block)") _export_cell = EclPrototype( "void fault_block_export_cell(fault_block , int , int* , int* , int* , double* , double* , double*)" ) _assign_to_region = EclPrototype( "void fault_block_assign_to_region(fault_block , int)") _get_region_list = EclPrototype( "int_vector_ref fault_block_get_region_list(fault_block)") _add_cell = EclPrototype( "void fault_block_add_cell(fault_block, int , int)") _get_global_index_list = EclPrototype( "int_vector_ref fault_block_get_global_index_list(fault_block)") _trace_edge = EclPrototype( "void fault_block_trace_edge( fault_block, double_vector , double_vector , int_vector)" ) _get_neighbours = EclPrototype( "void fault_block_list_neighbours( fault_block , bool , geo_polygon_collection , int_vector)" ) _free = EclPrototype("void fault_block_free__(fault_block)") def __init__(self, *args, **kwargs): raise NotImplementedError("Class can not be instantiated directly!") def __getitem__(self, index): if isinstance(index, int): if index < 0: index += len(self) if 0 <= index < len(self): x = ctypes.c_double() y = ctypes.c_double() z = ctypes.c_double() i = ctypes.c_int() j = ctypes.c_int() k = ctypes.c_int() self._export_cell(index, ctypes.byref(i), ctypes.byref(j), ctypes.byref(k), ctypes.byref(x), ctypes.byref(y), ctypes.byref(z)) return FaultBlockCell(i.value, j.value, k.value, x.value, y.value, z.value) else: raise IndexError("Index:%d out of range: [0,%d)" % (index, len(self))) else: raise TypeError("Index:%s wrong type - integer expected") def __str__(self): return "Block ID: %d" % self.getBlockID() def __len__(self): return self._get_size() def free(self): self._free() def getCentroid(self): xc = self._get_xc() yc = self._get_yc() return (xc, yc) def countInside(self, polygon): """ Will count the number of points in block which are inside polygon. """ inside = 0 for p in self: if GeometryTools.pointInPolygon((p.x, p.y), polygon): inside += 1 return inside def getBlockID(self): return self._get_block_id() def assignToRegion(self, region_id): self._assign_to_region(region_id) def getRegionList(self): regionList = self._get_region_list() return regionList.copy() def addCell(self, i, j): self._add_cell(i, j) def getGlobalIndexList(self): return self._get_global_index_list() def getEdgePolygon(self): x_list = DoubleVector() y_list = DoubleVector() cell_list = IntVector() self._trace_edge(x_list, y_list, cell_list) p = Polyline() for (x, y) in zip(x_list, y_list): p.addPoint(x, y) return p def containsPolyline(self, polyline): """ Will return true if at least one point from the polyline is inside the block. """ edge_polyline = self.getEdgePolygon() for p in polyline: if GeometryTools.pointInPolygon(p, edge_polyline): return True else: edge_polyline.assertClosed() return GeometryTools.polylinesIntersect(edge_polyline, polyline) def getNeighbours(self, polylines=None, connected_only=True): """ Will return a list of FaultBlock instances which are in direct contact with this block. """ neighbour_id_list = IntVector() if polylines is None: polylines = CPolylineCollection() self._get_neighbours(connected_only, polylines, neighbour_id_list) parent_layer = self.getParentLayer() neighbour_list = [] for id in neighbour_id_list: neighbour_list.append(parent_layer.getBlock(id)) return neighbour_list def getParentLayer(self): return self.parent()
class EclGrav(BaseCClass): """ Holding ECLIPSE results for calculating gravity changes. The EclGrav class is a collection class holding the results from ECLIPSE forward modelling of gravity surveys. Observe that the class is focused on the ECLIPSE side of things, and does not have any notion of observed values or measurement locations; that should be handled by the scope using the EclGrav class. Typical use of the EclGrav class involves the following steps: 1. Create the EclGrav instance. 2. Add surveys with the add_survey_XXXX() methods. 3. Evalute the gravitational response with the eval() method. """ TYPE_NAME = "ecl_grav" _grav_alloc = EclPrototype("void* ecl_grav_alloc(ecl_grid, ecl_file)", bind=False) _free = EclPrototype("void ecl_grav_free(ecl_grav)") _add_survey_RPORV = EclPrototype( "void* ecl_grav_add_survey_RPORV(ecl_grav, char*, ecl_file_view)") _add_survey_PORMOD = EclPrototype( "void* ecl_grav_add_survey_PORMOD(ecl_grav, char*, ecl_file_view)") _add_survey_FIP = EclPrototype( "void* ecl_grav_add_survey_FIP(ecl_grav, char*, ecl_file_view)") _add_survey_RFIP = EclPrototype( "void* ecl_grav_add_survey_RFIP(ecl_grav, char*, ecl_file_view)") _new_std_density = EclPrototype( "void ecl_grav_new_std_density(ecl_grav, int, double)") _add_std_density = EclPrototype( "void ecl_grav_add_std_density(ecl_grav, int, int, double)") _eval = EclPrototype( "double ecl_grav_eval(ecl_grav, char*, char*, ecl_region, double, double, double, int)" ) def __init__(self, grid, init_file): """ Creates a new EclGrav instance. The input arguments @grid and @init_file should be instances of EclGrid and EclFile respectively. """ self.init_file = init_file # Inhibit premature garbage collection of init_file c_ptr = self._grav_alloc(grid, init_file) super(EclGrav, self).__init__(c_ptr) self.dispatch = { "FIP": self.add_survey_FIP, "RFIP": self.add_survey_RFIP, "PORMOD": self.add_survey_PORMOD, "RPORV": self.add_survey_RPORV } def add_survey_RPORV(self, survey_name, restart_view): """ Add new survey based on RPORV keyword. Add a new survey; in this context a survey is the state of reservoir, i.e. an ECLIPSE restart file. The @survey_name input argument will be used when refering to this survey at a later stage. The @restart_view input argument should be an EclFile instance with data from one report step. A typical way to load the @restart_view argument is: import datetime from ecl.ecl import EclRestartFile ... ... date = datetime.datetime(year, month, day) rst_file = EclRestartFile("ECLIPSE.UNRST") restart_view1 = rst_file.restartView(sim_time=date) restart_view2 = rst_file.restartView(report_step=67) The pore volume of each cell will be calculated based on the RPORV keyword from the restart files. The methods add_survey_PORMOD() and add_survey_FIP() are alternatives which are based on other keywords. """ self._add_survey_RPORV(survey_name, restart_view) def add_survey_PORMOD(self, survey_name, restart_view): """ Add new survey based on PORMOD keyword. The pore volum is calculated from the initial pore volume and the PORV_MOD keyword from the restart file; see add_survey_RPORV() for further details. """ self._add_survey_PORMOD(survey_name, restart_view) def add_survey_FIP(self, survey_name, restart_view): """ Add new survey based on FIP keywords. This method adds a survey as add_survey_RPORV() and add_survey_PORMOD; but the mass content in each cell is calculated based on the FIPxxx keyword along with the mass density at standard conditions of the respective phases. The mass density at standard conditions must be specified with the new_std_density() (and possibly also add_std_density()) method before calling the add_survey_FIP() method. """ self._add_survey_FIP(survey_name, restart_view) def add_survey_RFIP(self, survey_name, restart_view): """ Add new survey based on RFIP keywords. This method adds a survey as add_survey_RPORV() and add_survey_PORMOD; but the mass content in each cell is calculated based on the RFIPxxx keyword along with the per-cell mass density of the respective phases. """ self._add_survey_RFIP(survey_name, restart_view) def add_survey(self, name, restart_view, method): method = self.dispatch[method] return method(name, restart_view) def eval(self, base_survey, monitor_survey, pos, region=None, phase_mask=EclPhaseEnum.ECL_OIL_PHASE + EclPhaseEnum.ECL_GAS_PHASE + EclPhaseEnum.ECL_WATER_PHASE): """ Calculates the gravity change between two surveys. This is the method everything is leading up to; will calculate the change in gravitational strength, in units of micro Gal, between the two surveys named @base_survey and @monitor_survey. The monitor survey can be 'None' - the resulting answer has nothing whatsovever to do with gravitation, but can be interesting to determine the numerical size of the quantities which are subtracted in a 4D study. The @pos argument should be a tuple of three elements with the (utm_x, utm_y, depth) position where we want to evaluate the change in gravitational strength. If supplied the optional argument @region should be an EclRegion() instance; this region will be used to limit the part of the reserviour included in the gravity calculations. The optional argument @phase_mask is an integer flag to indicate which phases you are interested in. It should be a sum of the relevant integer constants 'ECL_OIL_PHASE', 'ECL_GAS_PHASE' and 'ECL_WATER_PHASE'. """ return self._eval(base_survey, monitor_survey, region, pos[0], pos[1], pos[2], phase_mask) def new_std_density(self, phase_enum, default_density): """ Adds a new phase with a corresponding density. @phase_enum is one of the integer constants ECL_OIL_PHASE, ECL_GAS_PHASE or ECL_WATER_PHASE, all available in the ecl_util and also ecl modules. @default_density is the density, at standard conditions, for this particular phase. By default @default_density will be used for all the cells in the model; by using the add_std_density() method you can specify different densities for different PVT regions. The new_std_density() and add_std_density() methods must be used before you use the add_survey_FIP() method to add a survey based on the FIP keyword. """ self._new_std_density(phase_enum, default_density) def add_std_density(self, phase_enum, pvtnum, density): """ Add standard conditions density for PVT region @pvtnum. The new_std_density() method will add a standard conditions density which applies to all cells in the model. Using the add_std_density() method it is possible to add standard conditions densities on a per PVT region basis. You can add densities for as many PVT regions as you like, and then fall back to the default density for the others. The new_std_density() method must be called before calling the add_std_density() method. The new_std_density() and add_std_density() methods must be used before you use the add_survey_FIP() method to add a survey based on the FIP keyword. """ self._add_std_density(phase_enum, pvtnum, density) def free(self): self._free()
class EclIndexedReadTest(ExtendedTestCase): _freadIndexedData = EclPrototype( "void ecl_kw_fread_indexed_data_python(fortio, int, ecl_data_type, int, int_vector, char*)", bind=False) # fortio, offset, type, count, index_map, buffer _eclFileIndexedRead = EclPrototype( "void ecl_file_indexed_read(ecl_file, char*, int, int_vector, char*)", bind=False) # ecl_file, kw, index, index_map, buffer def test_ecl_kw_indexed_read(self): with TestAreaContext("ecl_kw_indexed_read") as area: fortio = FortIO("index_test", mode=FortIO.WRITE_MODE) element_count = 100000 ecl_kw = EclKW("TEST", element_count, EclDataType.ECL_INT) for index in range(element_count): ecl_kw[index] = index ecl_kw.fwrite(fortio) fortio.close() fortio = FortIO("index_test", mode=FortIO.READ_MODE) new_ecl_kw = EclKW.fread(fortio) for index in range(element_count): self.assertEqual(new_ecl_kw[index], index) index_map = IntVector() index_map.append(2) index_map.append(3) index_map.append(5) index_map.append(7) index_map.append(11) index_map.append(13) index_map.append(313) index_map.append(1867) index_map.append(5227) index_map.append(7159) index_map.append(12689) index_map.append(18719) index_map.append(32321) index_map.append(37879) index_map.append(54167) index_map.append(77213) index_map.append(88843) index_map.append(99991) char_buffer = ctypes.create_string_buffer( len(index_map) * ctypes.sizeof(ctypes.c_int)) self._freadIndexedData(fortio, 24, EclDataType.ECL_INT, element_count, index_map, char_buffer) int_buffer = ctypes.cast(char_buffer, ctypes.POINTER(ctypes.c_int)) for index, index_map_value in enumerate(index_map): self.assertEqual(index_map_value, int_buffer[index]) def test_ecl_file_indexed_read(self): with TestAreaContext("ecl_file_indexed_read") as area: fortio = FortIO("ecl_file_index_test", mode=FortIO.WRITE_MODE) element_count = 100000 ecl_kw_1 = EclKW("TEST1", element_count, EclDataType.ECL_INT) ecl_kw_2 = EclKW("TEST2", element_count, EclDataType.ECL_INT) for index in range(element_count): ecl_kw_1[index] = index ecl_kw_2[index] = index + 3 ecl_kw_1.fwrite(fortio) ecl_kw_2.fwrite(fortio) fortio.close() ecl_file = EclFile("ecl_file_index_test") index_map = IntVector() index_map.append(2) index_map.append(3) index_map.append(5) index_map.append(7) index_map.append(11) index_map.append(13) index_map.append(313) index_map.append(1867) index_map.append(5227) index_map.append(7159) index_map.append(12689) index_map.append(18719) index_map.append(32321) index_map.append(37879) index_map.append(54167) index_map.append(77213) index_map.append(88843) index_map.append(99991) char_buffer_1 = ctypes.create_string_buffer( len(index_map) * ctypes.sizeof(ctypes.c_int)) char_buffer_2 = ctypes.create_string_buffer( len(index_map) * ctypes.sizeof(ctypes.c_int)) self._eclFileIndexedRead(ecl_file, "TEST2", 0, index_map, char_buffer_2) self._eclFileIndexedRead(ecl_file, "TEST1", 0, index_map, char_buffer_1) int_buffer_1 = ctypes.cast(char_buffer_1, ctypes.POINTER(ctypes.c_int)) int_buffer_2 = ctypes.cast(char_buffer_2, ctypes.POINTER(ctypes.c_int)) for index, index_map_value in enumerate(index_map): self.assertEqual(index_map_value, int_buffer_1[index]) self.assertEqual(index_map_value, int_buffer_2[index] - 3)
class EclGridGenerator: _alloc_rectangular = EclPrototype( "ecl_grid_obj ecl_grid_alloc_rectangular(int, int, int, double, double, double, int*)", bind=False) @classmethod def createRectangular(cls, dims, dV, actnum=None): """ Will create a new rectangular grid. @dims = (nx,ny,nz) @dVg = (dx,dy,dz) With the default value @actnum == None all cells will be active, """ if actnum is None: ecl_grid = cls._alloc_rectangular(dims[0], dims[1], dims[2], dV[0], dV[1], dV[2], None) else: if not isinstance(actnum, IntVector): tmp = IntVector(initial_size=len(actnum)) for (index, value) in enumerate(actnum): tmp[index] = value actnum = tmp if not len(actnum) == dims[0] * dims[1] * dims[2]: raise ValueError( "ACTNUM size mismatch: len(ACTNUM):%d Expected:%d" % (len(actnum), dims[0] * dims[1] * dims[2])) ecl_grid = cls._alloc_rectangular(dims[0], dims[1], dims[2], dV[0], dV[1], dV[2], actnum.getDataPtr()) # If we have not succeeded in creatin the grid we *assume* the # error is due to a failed malloc. if ecl_grid is None: raise MemoryError("Failed to allocated regualar grid") return ecl_grid @classmethod def create_single_cell_grid(cls, corners): """ Provided with the corners of the grid in a similar manner as the eight corners are output for a single cell, this method will create a grid consisting of a single cell with the specified corners as its corners. """ zcorn = [corners[i][2] for i in range(8)] coord = [(corners[i], corners[i + 4]) for i in range(4)] coord = flatten(flatten(coord)) def construct_floatKW(name, values): kw = EclKW(name, len(values), EclDataType.ECL_FLOAT) for i in range(len(values)): kw[i] = values[i] return kw grid = EclGrid.create((1, 1, 1), construct_floatKW("ZCORN", zcorn), construct_floatKW("COORD", coord), None) if not corners == [grid.getCellCorner(i, 0) for i in range(8)]: raise AssertionError("Failed to generate single cell grid. " + "Did not end up the expected corners.") return grid @classmethod def create_zcorn(cls, dims, dV, offset=1, escape_origo_shift=(1, 1, 0), irregular_offset=False, irregular=False, concave=False, faults=False): cls.__assert_zcorn_parameters(dims, dV, offset, escape_origo_shift, irregular_offset, irregular, concave, faults) nx, ny, nz = dims dx, dy, dz = dV # Compute zcorn z = escape_origo_shift[2] zcorn = [z] * (4 * nx * ny) for k in range(nz - 1): z = z + dz local_offset = offset + (dz / 2. if irregular_offset and k % 2 == 0 else 0) layer = [] for i in range(ny + 1): shift = ((i if concave else 0) + (k / 2 if irregular else 0)) % 2 path = [ z if i % 2 == shift else z + local_offset for i in range(nx + 1) ] layer.append(duplicate_inner(path)) zcorn = zcorn + (2 * flatten(duplicate_inner(layer))) z = z + dz zcorn = zcorn + ([z] * (4 * nx * ny)) if faults: # Ensure that drop does not align with grid structure drop = (offset + dz) / 2. if abs(offset - dz / 2.) > 0.2 else offset + 0.4 zcorn = cls.__create_faults(nx, ny, nz, zcorn, drop) if z != escape_origo_shift[2] + nz * dz: raise ValueError("%f != %f" % (z, escape_origo_shift[2] + nz * dz)) cls.assert_zcorn(nx, ny, nz, zcorn) return construct_floatKW("ZCORN", zcorn) @classmethod def create_coord(cls, dims, dV, escape_origo_shift=(1, 1, 0), scale=1, translation=(0, 0, 0), rotate=False, misalign=False): nx, ny, nz = dims dx, dy, dz = dV # Compute coord z = escape_origo_shift[2] + nz * dz coord = [] for j, i in itertools.product(range(ny + 1), range(nx + 1)): x, y = i * dx + escape_origo_shift[0], j * dy + escape_origo_shift[ 1] coord = coord + [x, y, escape_origo_shift[2], x, y, z] # Apply transformations lower_center = (nx * dx / 2. + escape_origo_shift[0], ny * dy / 2. + escape_origo_shift[1]) if misalign: coord = cls.__misalign_coord(coord, dims, dV) coord = cls.__scale_coord(coord, scale, lower_center) if rotate: coord = cls.__rotate_coord(coord, lower_center) coord = cls.__translate_lower_coord(coord, translation) cls.assert_coord(nx, ny, nz, coord) return construct_floatKW("COORD", coord) @classmethod def __assert_zcorn_parameters(cls, dims, dV, offset, escape_origo_shift, irregular_offset, irregular, concave, faults): nx, ny, nz = dims dx, dy, dz = dV # Validate arguments if min(dims + dV) <= 0: raise ValueError("Expected positive grid and cell dimentions") if offset < 0: raise ValueError("Expected non-negative offset") if irregular and offset + (dz / 2. if irregular_offset else 0) > dz: raise AssertionError( "Arguments can result in self-" + "intersecting cells. Increase dz, deactivate eiter " + "irregular or irregular_offset, or decrease offset to avoid " + "any problems") @classmethod def create_grid(cls, dims, dV, offset=1, escape_origo_shift=(1, 1, 0), irregular_offset=False, irregular=False, concave=False, faults=False, scale=1, translation=(0, 0, 0), rotate=False, misalign=False): """ Will create a new grid where each cell is a parallelogram (skewed by z-value). The number of cells are given by @dims = (nx, ny, nz) and the dimention of each cell by @dV = (dx, dy, dz). All cells are guaranteed to not be self-intersecting. Hence, no twisted cells and somewhat meaningfull cells. @offset gives how much the layers should fluctuate or "wave" as you move along the X-axis. @irregular_offset decides whether the offset should be constant or increase by dz/2 every now and then. @irregular if true some of the layers will be inclining and others declining at the start. @concave decides whether the cells are to be convex or not. In particular, if set to False, all cells of the grid will be concave. @escape_origo_shift is used to prevent any cell of having corners in (0,0,z) as there is a heuristic in ecl_grid.c that marks such cells as tainted. @faults decides if there are to be faults in the grid. @scale A positive number that scales the "lower" endpoint of all coord's. In particular, @scale != 1 creates trapeziod cells in both the XZ and YZ-plane. @translation the lower part of the grid is translated ("slided") by the specified additive factor. @rotate the lower part of the grid is rotated 90 degrees around its center. @misalign will toggle COORD's slightly in various directions to break alignment Note that cells in the lowermost layer can have multiple corners at the same point. For testing it should give good coverage of the various scenarios this method can produce, by leting @dims be (10,10,10), @dV=(2,2,2), @offset=1, and try all 4 different configurations of @concave and @irregular_offset. """ zcorn = cls.create_zcorn(dims, dV, offset, escape_origo_shift, irregular_offset, irregular, concave, faults) coord = cls.create_coord(dims, dV, escape_origo_shift, scale, translation, rotate, misalign) return EclGrid.create(dims, zcorn, coord, None) @classmethod def __create_faults(cls, nx, ny, nz, zcorn, drop): """ Will create several faults consisting of all cells such that either its i or j index is 1 modulo 3. """ plane_size = 4 * nx * ny for x, y, z in itertools.product(range(nx), range(ny), range(nz)): if x % 3 != 1 and y % 3 != 1: continue corner = [0] * 8 corner[0] = 2 * z * plane_size + 4 * y * nx + 2 * x corner[1] = corner[0] + 1 corner[2] = corner[0] + 2 * nx corner[3] = corner[2] + 1 for i in range(4, 8): corner[i] = corner[i - 4] + plane_size for c in corner: zcorn[c] = zcorn[c] + drop return zcorn @classmethod def assert_zcorn(cls, nx, ny, nz, zcorn, twisted_check=True): """ Raises an AssertionError if the zcorn is not as expected. In patricular, it is verified that: - zcorn has the approperiate length (8*nx*ny*nz) and - that no cell is twisted. """ if len(zcorn) != 8 * nx * ny * nz: raise AssertionError("Expected len(zcorn) to be %d, was %d" % (8 * nx * ny * nz, len(zcorn))) plane_size = 4 * nx * ny for p in range(8 * nx * ny * nz - plane_size): if zcorn[p] > zcorn[p + plane_size] and twisted_check: raise AssertionError( "Twisted cell was created. " + "Decrease offset or increase dz to avoid this!") @classmethod def __scale_coord(cls, coord, scale, lower_center): coord = numpy.array( [map(float, coord[i:i + 6:]) for i in range(0, len(coord), 6)]) origo = numpy.array(3 * [0.] + list(lower_center) + [0]) scale = numpy.array(3 * [1.] + 2 * [scale] + [1]) coord = scale * (coord - origo) + origo return coord.flatten().tolist() @classmethod def __misalign_coord(cls, coord, dims, dV): nx, ny, nz = dims coord = numpy.array( [map(float, coord[i:i + 6:]) for i in range(0, len(coord), 6)]) tf = lambda i, j: 1. / 2 if abs(i) + abs(j) <= 1 else 0.25 adjustment = numpy.array([ (0, 0, 0, i * tf(i, j) * dV[0], j * tf(i, j) * dV[1], 0) for i, j in itertools.product([-1, 0, 1], repeat=2) ]) for i, c in enumerate(coord): # Leave the outermost coords alone if i < nx + 1 or i >= len(coord) - (nx + 1): continue if i % (nx + 1) in [0, nx]: continue c += adjustment[i % len(adjustment)] return coord.flatten().tolist() @classmethod def __rotate_coord(cls, coord, lower_center): coord = numpy.array( [map(float, coord[i:i + 6:]) for i in range(0, len(coord), 6)]) origo = numpy.array(3 * [0.] + list(lower_center) + [0]) coord -= origo for c in coord: c[3], c[4] = -c[4], c[3] coord += origo return coord.flatten().tolist() @classmethod def __translate_lower_coord(cls, coord, translation): coord = numpy.array( [map(float, coord[i:i + 6:]) for i in range(0, len(coord), 6)]) translation = numpy.array(3 * [0.] + list(translation)) coord = coord + translation return coord.flatten().tolist() @classmethod def assert_coord(cls, nx, ny, nz, coord, negative_values=False): """ Raises an AssertionError if the coord is not as expected. In particular, it is verified that: - coord has the approperiate length (6*(nx+1)*(ny+1)) and - that all values are positive unless negative_values are explicitly allowed. """ if len(coord) != 6 * (nx + 1) * (ny + 1): raise AssertionError("Expected len(coord) to be %d, was %d" % (6 * (nx + 1) * (ny + 1), len(coord))) if not negative_values and min(coord) < 0: raise AssertionError( "Negative COORD values was generated. " + "This is likely due to a tranformation. " + "Increasing the escape_origio_shift will most likely " + "fix the problem") @classmethod def assert_actnum(cls, nx, ny, nz, actnum): """ Raises an AssertionError if the actnum is not as expected. In particular, it is verified that: - actnum has the approperiate length nx*ny*nz and - that all values are either 0 or 1. """ if actnum is None: return if len(actnum) != nx * ny * nz: raise AssertionError( "Expected the length of ACTNUM to be %d, was %s." % (nx * ny * nz, len(actnum))) if set(actnum) - set([0, 1]): raise AssertionError( "Expected ACTNUM to consist of 0's and 1's, was %s." % ", ".join(map(str, set(actnum)))) @classmethod def extract_coord(cls, dims, coord, ijk_bounds): nx, ny, nz = dims (lx, ux), (ly, uy), (lz, uz) = ijk_bounds new_nx, new_ny, new_nz = ux - lx + 1, uy - ly + 1, uz - lz + 1 cls.assert_coord(nx, ny, nz, coord, negative_values=True) # Format COORD coord = divide(divide(coord, 6), nx + 1) # Extract new COORD new_coord = [ coord_slice[lx:ux + 2:] for coord_slice in coord[ly:uy + 2] ] # Flatten and verify new_coord = flatten(flatten(new_coord)) cls.assert_coord(new_nx, new_ny, new_nz, new_coord, negative_values=True) return construct_floatKW("COORD", new_coord) @classmethod def extract_zcorn(cls, dims, zcorn, ijk_bounds): nx, ny, nz = dims (lx, ux), (ly, uy), (lz, uz) = ijk_bounds new_nx, new_ny, new_nz = ux - lx + 1, uy - ly + 1, uz - lz + 1 cls.assert_zcorn(nx, ny, nz, zcorn, twisted_check=False) # Format ZCORN zcorn = divide(divide(zcorn, 2 * nx), 2 * ny) # Extract new ZCORN new_zcorn = [ y_slice[2 * lx:2 * ux + 2:] for z_slice in zcorn[2 * lz:2 * uz + 2:] for y_slice in z_slice[2 * ly:2 * uy + 2:] ] # Flatten and verify new_zcorn = flatten(new_zcorn) cls.assert_zcorn(new_nx, new_ny, new_nz, new_zcorn) return construct_floatKW("ZCORN", new_zcorn) @classmethod def extract_actnum(cls, dims, actnum, ijk_bounds): if actnum is None: return None nx, ny, nz = dims (lx, ux), (ly, uy), (lz, uz) = ijk_bounds new_nx, new_ny, new_nz = ux - lx + 1, uy - ly + 1, uz - lz + 1 cls.assert_actnum(nx, ny, nz, actnum) actnum = divide(divide(actnum, nx), ny) new_actnum = [ y_slice[lx:ux + 1:] for z_slice in actnum[lz:uz + 1:] for y_slice in z_slice[ly:uy + 1:] ] new_actnum = flatten(new_actnum) cls.assert_actnum(new_nx, new_ny, new_nz, new_actnum) actnumkw = EclKW("ACTNUM", len(new_actnum), EclDataType.ECL_INT) for i, value in enumerate(new_actnum): actnumkw[i] = value return actnumkw @classmethod def __translate_coord(cls, coord, translation): coord = numpy.array( [map(float, coord[i:i + 6:]) for i in range(0, len(coord), 6)]) translation = numpy.array(list(translation) + list(translation)) coord = coord + translation return construct_floatKW("COORD", coord.flatten().tolist()) @classmethod def extract_subgrid(cls, grid, ijk_bounds, decomposition_change=False, translation=None): """ Extracts a subgrid from the given grid according to the specified bounds. @ijk_bounds: The bounds describing the subgrid. Should be a tuple of length 3, where each element gives the bound for the i, j, k coordinates of the subgrid to be described, respectively. Each bound should either be an interval of the form (a, b) where 0 <= a <= b < nx or a single integer a which is equivialent to the bound (a, a). NOTE: The given bounds are including endpoints. @decomposition_change: Depending on the given ijk_bounds, libecl might decompose the cells of the subgrid differently when extracted from grid. This is somewhat unexpected behaviour and if this event occur we give an exception together with an description for how to avoid this, unless decompostion_change is set to True. @translation: Gives the possibility of translating the subgrid. Should be given as a tuple (dx, dy, dz), where each coordinate of the grid will be moved by di in direction i. """ gdims = grid.getDims()[:-1:] nx, ny, nz = gdims ijk_bounds = cls.assert_ijk_bounds(gdims, ijk_bounds) coord = grid.export_coord() cls.assert_coord(nx, ny, nz, coord, negative_values=True) zcorn = grid.export_zcorn() cls.assert_zcorn(nx, ny, nz, zcorn) actnum = grid.export_actnum() cls.assert_actnum(nx, ny, nz, actnum) mapaxes = grid.export_mapaxes() sub_data = cls.extract_subgrid_data( gdims, coord, zcorn, ijk_bounds=ijk_bounds, actnum=actnum, mapaxes=mapaxes, decomposition_change=decomposition_change, translation=translation) sdim = tuple([b - a + 1 for a, b in ijk_bounds]) sub_coord, sub_zcorn, sub_actnum = sub_data return EclGrid.create(sdim, sub_zcorn, sub_coord, sub_actnum, mapaxes=mapaxes) @classmethod def extract_subgrid_data(cls, dims, coord, zcorn, ijk_bounds, actnum=None, mapaxes=None, decomposition_change=False, translation=None): """ Extracts subgrid data from COORD, ZCORN and potentially ACTNUM. It returns similar formatted data for the subgrid described by the bounds. @dims: The dimentions (nx, ny, nz) of the grid @coord: The COORD data of the grid. @zcorn: The ZCORN data of the grid. @ijk_bounds: The bounds describing the subgrid. Should be a tuple of length 3, where each element gives the bound for the i, j, k coordinates of the subgrid to be described, respectively. Each bound should either be an interval of the form (a, b) where 0 <= a <= b < nx or a single integer a which is equivialent to the bound (a, a). NOTE: The given bounds are including endpoints. @actnum: The ACTNUM data of the grid. @mapaxes The MAPAXES data of the grid. @decomposition_change: Depending on the given ijk_bounds, libecl might decompose the cells of the subgrid differently when extracted from grid. This is somewhat unexpected behaviour and if this event occur we give an exception together with an description for how to avoid this, unless decompostion_change is set to True. @translation: Gives the possibility of translating the subgrid. Should be given as a tuple (dx, dy, dz), where each coordinate of the grid will be moved by di in direction i. """ coord, zcorn = list(coord), list(zcorn) actnum = None if actnum is None else list(actnum) ijk_bounds = cls.assert_ijk_bounds(dims, ijk_bounds) cls.assert_decomposition_change(ijk_bounds, decomposition_change) nx, ny, nz = dims (lx, ux), (ly, uy), (lz, uz) = ijk_bounds new_nx, new_ny, new_nz = ux - lx + 1, uy - ly + 1, uz - lz + 1 new_coord = cls.extract_coord(dims, coord, ijk_bounds) new_zcorn = cls.extract_zcorn(dims, zcorn, ijk_bounds) new_actnum = cls.extract_actnum(dims, actnum, ijk_bounds) if translation is not None: mtranslation = pre_mapaxes_translation(translation, mapaxes) new_coord = cls.__translate_coord(new_coord, mtranslation) for i in range(len(new_zcorn)): new_zcorn[i] += translation[2] return new_coord, new_zcorn, new_actnum @classmethod def assert_ijk_bounds(cls, dims, ijk_bounds): ijk_bounds = list(ijk_bounds) for i in range(len(ijk_bounds)): if isinstance(ijk_bounds[i], int): ijk_bounds[i] = [ijk_bounds[i]] if len(ijk_bounds[i]) == 1: ijk_bounds[i] += ijk_bounds[i] if len(ijk_bounds) != 3: raise ValueError( "Expected ijk_bounds to contain three intervals, " + "contained only %d" % len(ijk_bounds)) for n, bound in zip(dims, ijk_bounds): if len(bound) != 2: raise ValueError( "Expected bound to consist of two elements, was %s", str(bound)) if not (isinstance(bound[0], int) and isinstance(bound[1], int)): raise TypeError( "Expected bound to consist of two integers, ", "was %s (%s)" % (str(bound), str((map(type, bound))))) if not (0 <= bound[0] <= bound[1] < n): raise ValueError( "Expected bounds to have the following format: " + "0 <= lower bound <= upper_bound < ni, " + "was %d <=? %d <=? %d <? %d." % (0, bound[0], bound[1], n)) return ijk_bounds @classmethod def assert_decomposition_change(cls, ijk_bounds, decomposition_change): if sum(zip(*ijk_bounds)[0]) % 2 == 1 and not decomposition_change: raise ValueError( "The subgrid defined by %s " % str(ijk_bounds) + "will cause an unintended decomposition change. " + "Either change one of the lower bounds by 1 " + "or activate decomposition_change.")
class EclGrid(BaseCClass): """ Class for loading and internalizing ECLIPSE GRID/EGRID files. """ TYPE_NAME = "ecl_grid" _fread_alloc = EclPrototype("void* ecl_grid_load_case__( char* , bool )" , bind = False) _grdecl_create = EclPrototype("ecl_grid_obj ecl_grid_alloc_GRDECL_kw( int , int , int , ecl_kw , ecl_kw , ecl_kw , ecl_kw)" , bind = False) _alloc_rectangular = EclPrototype("ecl_grid_obj ecl_grid_alloc_rectangular( int , int , int , double , double , double , int*)" , bind = False) _exists = EclPrototype("bool ecl_grid_exists( char* )" , bind = False) _get_lgr = EclPrototype("ecl_grid_ref ecl_grid_get_lgr( ecl_grid , char* )") _get_cell_lgr = EclPrototype("ecl_grid_ref ecl_grid_get_cell_lgr1( ecl_grid , int )") _num_coarse_groups = EclPrototype("int ecl_grid_get_num_coarse_groups( ecl_grid )") _in_coarse_group1 = EclPrototype("bool ecl_grid_cell_in_coarse_group1( ecl_grid , int)") _free = EclPrototype("void ecl_grid_free( ecl_grid )") _get_nx = EclPrototype("int ecl_grid_get_nx( ecl_grid )") _get_ny = EclPrototype("int ecl_grid_get_ny( ecl_grid )") _get_nz = EclPrototype("int ecl_grid_get_nz( ecl_grid )") _get_global_size = EclPrototype("int ecl_grid_get_global_size( ecl_grid )") _get_active = EclPrototype("int ecl_grid_get_active_size( ecl_grid )") _get_active_fracture = EclPrototype("int ecl_grid_get_nactive_fracture( ecl_grid )") _get_name = EclPrototype("char* ecl_grid_get_name( ecl_grid )") _ijk_valid = EclPrototype("bool ecl_grid_ijk_valid(ecl_grid , int , int , int)") _get_active_index3 = EclPrototype("int ecl_grid_get_active_index3( ecl_grid , int , int , int)") _get_global_index3 = EclPrototype("int ecl_grid_get_global_index3( ecl_grid , int , int , int)") _get_active_index1 = EclPrototype("int ecl_grid_get_active_index1( ecl_grid , int )") _get_active_fracture_index1 = EclPrototype("int ecl_grid_get_active_fracture_index1( ecl_grid , int )") _get_global_index1A = EclPrototype("int ecl_grid_get_global_index1A( ecl_grid , int )") _get_global_index1F = EclPrototype("int ecl_grid_get_global_index1F( ecl_grid , int )") _get_ijk1 = EclPrototype("void ecl_grid_get_ijk1( ecl_grid , int , int* , int* , int*)") _get_ijk1A = EclPrototype("void ecl_grid_get_ijk1A( ecl_grid , int , int* , int* , int*)") _get_xyz3 = EclPrototype("void ecl_grid_get_xyz3( ecl_grid , int , int , int , double* , double* , double*)") _get_xyz1 = EclPrototype("void ecl_grid_get_xyz1( ecl_grid , int , double* , double* , double*)") _get_cell_corner_xyz1 = EclPrototype("void ecl_grid_get_cell_corner_xyz1( ecl_grid , int , int , double* , double* , double*)") _get_corner_xyz = EclPrototype("void ecl_grid_get_corner_xyz( ecl_grid , int , int , int, double* , double* , double*)") _get_xyz1A = EclPrototype("void ecl_grid_get_xyz1A( ecl_grid , int , double* , double* , double*)") _get_ij_xy = EclPrototype("bool ecl_grid_get_ij_from_xy( ecl_grid , double , double , int , int* , int*)") _get_ijk_xyz = EclPrototype("int ecl_grid_get_global_index_from_xyz( ecl_grid , double , double , double , int)") _cell_contains = EclPrototype("bool ecl_grid_cell_contains_xyz1( ecl_grid , int , double , double , double )") _cell_regular = EclPrototype("bool ecl_grid_cell_regular1( ecl_grid , int)") _num_lgr = EclPrototype("int ecl_grid_get_num_lgr( ecl_grid )") _has_lgr = EclPrototype("bool ecl_grid_has_lgr( ecl_grid , char* )") _grid_value = EclPrototype("double ecl_grid_get_property( ecl_grid , ecl_kw , int , int , int)") _get_cell_volume = EclPrototype("double ecl_grid_get_cell_volume1( ecl_grid , int )") _get_cell_thickness = EclPrototype("double ecl_grid_get_cell_thickness1( ecl_grid , int )") _get_cell_dx = EclPrototype("double ecl_grid_get_cell_dx1( ecl_grid , int )") _get_cell_dy = EclPrototype("double ecl_grid_get_cell_dy1( ecl_grid , int )") _get_depth = EclPrototype("double ecl_grid_get_cdepth1( ecl_grid , int )") _fwrite_grdecl = EclPrototype("void ecl_grid_grdecl_fprintf_kw( ecl_grid , ecl_kw , char* , FILE , double)") _load_column = EclPrototype("void ecl_grid_get_column_property( ecl_grid , ecl_kw , int , int , double_vector)") _get_top = EclPrototype("double ecl_grid_get_top2( ecl_grid , int , int )") _get_top1A = EclPrototype("double ecl_grid_get_top1A(ecl_grid , int )") _get_bottom = EclPrototype("double ecl_grid_get_bottom2( ecl_grid , int , int )") _locate_depth = EclPrototype("int ecl_grid_locate_depth( ecl_grid , double , int , int )") _invalid_cell = EclPrototype("bool ecl_grid_cell_invalid1( ecl_grid , int)") _valid_cell = EclPrototype("bool ecl_grid_cell_valid1( ecl_grid , int)") _get_distance = EclPrototype("void ecl_grid_get_distance( ecl_grid , int , int , double* , double* , double*)") _fprintf_grdecl2 = EclPrototype("void ecl_grid_fprintf_grdecl2( ecl_grid , FILE , ecl_unit_enum) ") _fwrite_GRID2 = EclPrototype("void ecl_grid_fwrite_GRID2( ecl_grid , char* , ecl_unit_enum)") _fwrite_EGRID2 = EclPrototype("void ecl_grid_fwrite_EGRID2( ecl_grid , char*, ecl_unit_enum)") _equal = EclPrototype("bool ecl_grid_compare(ecl_grid , ecl_grid , bool, bool)") _dual_grid = EclPrototype("bool ecl_grid_dual_grid( ecl_grid )") _init_actnum = EclPrototype("void ecl_grid_init_actnum_data( ecl_grid , int* )") _compressed_kw_copy = EclPrototype("void ecl_grid_compressed_kw_copy( ecl_grid , ecl_kw , ecl_kw)") _global_kw_copy = EclPrototype("void ecl_grid_global_kw_copy( ecl_grid , ecl_kw , ecl_kw)") _create_volume_keyword = EclPrototype("ecl_kw_obj ecl_grid_alloc_volume_kw( ecl_grid , bool)") _use_mapaxes = EclPrototype("bool ecl_grid_use_mapaxes(ecl_grid)") _export_coord = EclPrototype("ecl_kw_obj ecl_grid_alloc_coord_kw( ecl_grid )") _export_zcorn = EclPrototype("ecl_kw_obj ecl_grid_alloc_zcorn_kw( ecl_grid )") _export_actnum = EclPrototype("ecl_kw_obj ecl_grid_alloc_actnum_kw( ecl_grid )") _export_mapaxes = EclPrototype("ecl_kw_obj ecl_grid_alloc_mapaxes_kw( ecl_grid )") @classmethod def loadFromGrdecl(cls , filename): """Will create a new EclGrid instance from grdecl file. This function will scan the input file @filename and look for the keywords required to build a grid. The following keywords are required: SPECGRID ZCORN COORD In addition the function will look for and use the ACTNUM and MAPAXES keywords if they are found; if ACTNUM is not found all cells are assumed to be active. Slightly more exotic grid concepts like dual porosity, NNC mapping, LGR and coarsened cells will be completely ignored; if you need such concepts you must have an EGRID file and use the default EclGrid() constructor - that is also considerably faster. """ if os.path.isfile(filename): with open(filename) as f: specgrid = EclKW.read_grdecl(f, "SPECGRID", ecl_type=EclDataType.ECL_INT, strict=False) zcorn = EclKW.read_grdecl(f, "ZCORN") coord = EclKW.read_grdecl(f, "COORD") try: actnum = EclKW.read_grdecl(f, "ACTNUM", ecl_type=EclDataType.ECL_INT) except ValueError: actnum = None try: mapaxes = EclKW.read_grdecl(f, "MAPAXES") except ValueError: mapaxes = None return EclGrid.create( specgrid , zcorn , coord , actnum , mapaxes ) else: raise IOError("No such file:%s" % filename) @classmethod def loadFromFile(cls , filename): """ Will inspect the @filename argument and create a new EclGrid instance. """ if FortIO.isFortranFile( filename ): return EclGrid( filename ) else: return EclGrid.loadFromGrdecl( filename ) @classmethod def create(cls , specgrid , zcorn , coord , actnum , mapaxes = None ): """ Create a new grid instance from existing keywords. This is a class method which can be used to create an EclGrid instance based on the EclKW instances @specgrid, @zcorn, @coord and @actnum. An ECLIPSE EGRID file contains the SPECGRID, ZCORN, COORD and ACTNUM keywords, so a somewhat involved way to create a EclGrid instance could be: file = ecl.EclFile( "ECLIPSE.EGRID" ) specgrid_kw = file.iget_named_kw( "SPECGRID" , 0) zcorn_kw = file.iget_named_kw( "ZCORN" , 0) coord_kw = file.iget_named_kw( "COORD" , 0) actnum_kw = file.iget_named_kw( "ACTNUM" , 0 ) grid = EclGrid.create( specgrid_kw , zcorn_kw , coord_kw , actnum_kw) If you are so inclined ... """ return cls._grdecl_create( specgrid[0] , specgrid[1] , specgrid[2] , zcorn , coord , actnum , mapaxes ) @classmethod def createRectangular(cls, dims , dV , actnum = None): """ Will create a new rectangular grid. @dims = (nx,ny,nz) @dVg = (dx,dy,dz) With the default value @actnum == None all cells will be active, """ warnings.warn("EclGrid.createRectangular is deprecated. " + "Please used the similar method in EclGridGenerator!", DeprecationWarning) if actnum is None: ecl_grid = cls._alloc_rectangular( dims[0] , dims[1] , dims[2] , dV[0] , dV[1] , dV[2] , None ) else: if not isinstance(actnum , IntVector): tmp = IntVector(initial_size = len(actnum)) for (index , value) in enumerate(actnum): tmp[index] = value actnum = tmp if not len(actnum) == dims[0] * dims[1] * dims[2]: raise ValueError("ACTNUM size mismatch: len(ACTNUM):%d Expected:%d" % (len(actnum) , dims[0] * dims[1] * dims[2])) ecl_grid = cls._alloc_rectangular( dims[0] , dims[1] , dims[2] , dV[0] , dV[1] , dV[2] , actnum.getDataPtr() ) # If we have not succeeded in creatin the grid we *assume* the # error is due to a failed malloc. if ecl_grid is None: raise MemoryError("Failed to allocated regualar grid") return ecl_grid def __init__(self , filename , apply_mapaxes = True): """ Will create a grid structure from an EGRID or GRID file. """ c_ptr = self._fread_alloc( filename , apply_mapaxes) if c_ptr: super(EclGrid, self).__init__(c_ptr) else: raise IOError("Loading grid from:%s failed" % filename) def free(self): self._free( ) def _nicename(self): """name is often full path to grid, if so, output basename, else name""" name = self.getName() if os.path.isfile(name): name = os.path.basename(name) return name def __repr__(self): """Returns, e.g.: EclGrid("NORNE_ATW2013.EGRID", 46x112x22, global_size = 113344, active_size = 44431) at 0x28c4a70 """ name = self._nicename() if name: name = '"%s", ' % name g_size = self.getGlobalSize() a_size = self.getNumActive() xyz_s = '%dx%dx%d' % (self.getNX(),self.getNY(),self.getNZ()) return self._create_repr('%s%s, global_size = %d, active_size = %d' % (name, xyz_s, g_size, a_size)) def __len__(self): """ len(grid) wil return the total number of cells. """ return self._get_global_size( ) def equal(self , other , include_lgr = True , include_nnc = False , verbose = False): """ Compare the current grid with the other grid. """ if not isinstance(other , EclGrid): raise TypeError("The other argument must be an EclGrid instance") return self._equal( other , include_lgr , include_nnc , verbose) def dualGrid(self): """Is this grid dual porosity model?""" return self._dual_grid( ) def getDims(self): """A tuple of four elements: (nx , ny , nz , nactive).""" return ( self.getNX( ) , self.getNY( ) , self.getNZ( ) , self.getNumActive( ) ) def getNX(self): """ The number of elements in the x direction""" return self._get_nx( ) def getNY(self): """ The number of elements in the y direction""" return self._get_ny( ) def getNZ(self): """ The number of elements in the z direction""" return self._get_nz( ) def getGlobalSize(self): """Returns the total number of cells in this grid""" return self._get_global_size( ) def getNumActive(self): """The number of active cells in the grid.""" return self._get_active( ) def getNumActiveFracture(self): """The number of active cells in the grid.""" return self._get_active_fracture( ) def getBoundingBox2D(self , layer = 0 , lower_left = None , upper_right = None): if 0 <= layer <= self.getNZ(): x = ctypes.c_double() y = ctypes.c_double() z = ctypes.c_double() if lower_left is None: i1 = 0 j1 = 0 else: i1,j1 = lower_left if not 0 < i1 < self.getNX(): raise ValueError("lower_left i coordinate invalid") if not 0 < j1 < self.getNY(): raise ValueError("lower_left j coordinate invalid") if upper_right is None: i2 = self.getNX() j2 = self.getNY() else: i2,j2 = upper_right if not 1 < i2 <= self.getNX(): raise ValueError("upper_right i coordinate invalid") if not 1 < j2 <= self.getNY(): raise ValueError("upper_right j coordinate invalid") if not i1 < i2: raise ValueError("Must have lower_left < upper_right") if not j1 < j2: raise ValueError("Must have lower_left < upper_right") self._get_corner_xyz( i1 , j1 , layer , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z) ) p0 = (x.value , y.value ) self._get_corner_xyz( i2 , j1 , layer , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z) ) p1 = (x.value , y.value ) self._get_corner_xyz( i2 , j2 , layer , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z) ) p2 = (x.value , y.value ) self._get_corner_xyz( i1 , j2 , layer , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z) ) p3 = (x.value , y.value ) return (p0,p1,p2,p3) else: raise ValueError("Invalid layer value:%d Valid range: [0,%d]" % (layer , self.getNZ())) def getName(self): """ Name of the current grid, returns a string. For the main grid this is the filename given to the constructor when loading the grid; for an LGR this is the name of the LGR. If the grid instance has been created with the create() classmethod this can be None. """ n = self._get_name() return str(n) if n else '' def global_index( self , active_index = None, ijk = None): """ Will convert either active_index or (i,j,k) to global index. """ return self.__global_index( active_index = active_index , ijk = ijk ) def __global_index( self , active_index = None , global_index = None , ijk = None): """ Will convert @active_index or @ijk to global_index. This method will convert @active_index or @ijk to a global index. Exactly one of the arguments @active_index, @global_index or @ijk must be supplied. The method is used extensively internally in the EclGrid class; most methods which take coordinate input pass through this method to normalize the coordinate representation. """ set_count = 0 if not active_index is None: set_count += 1 if not global_index is None: set_count += 1 if ijk: set_count += 1 if not set_count == 1: raise ValueError("Exactly one of the kewyord arguments active_index, global_index or ijk must be set") if not active_index is None: global_index = self._get_global_index1A( active_index ) elif ijk: nx = self.getNX() ny = self.getNY() nz = self.getNZ() i,j,k = ijk if not 0 <= i < nx: raise IndexError("Invalid value i:%d Range: [%d,%d)" % (i , 0 , nx)) if not 0 <= j < ny: raise IndexError("Invalid value j:%d Range: [%d,%d)" % (j , 0 , ny)) if not 0 <= k < nz: raise IndexError("Invalid value k:%d Range: [%d,%d)" % (k , 0 , nz)) global_index = self._get_global_index3( i,j,k) else: if not 0 <= global_index < self.getGlobalSize(): raise IndexError("Invalid value global_index:%d Range: [%d,%d)" % (global_index , 0 , self.getGlobalSize())) return global_index def get_active_index( self , ijk = None , global_index = None): """ Lookup active index based on ijk or global index. Will determine the active_index of a cell, based on either @ijk = (i,j,k) or @global_index. If the cell specified by the input arguments is not active the function will return -1. """ gi = self.__global_index( global_index = global_index , ijk = ijk) return self._get_active_index1( gi) def get_active_fracture_index( self , ijk = None , global_index = None): """ For dual porosity - get the active fracture index. """ gi = self.__global_index( global_index = global_index , ijk = ijk) return self._get_active_fracture_index1( gi ) def get_global_index1F( self , active_fracture_index): """ Will return the global index corresponding to active fracture index. """ return self._get_global_index1F( active_fracture_index ) def cell_invalid( self , ijk = None , global_index = None , active_index = None): """ Tries to check if a cell is invalid. Cells which are used to represent numerical aquifers are typically located in UTM position (0,0); these cells have completely whacked up shape and size, and should **NOT** be used in calculations involving real world coordinates. To protect against this a heuristic is used identify such cells and mark them as invalid. There might be other sources than numerical aquifers to this problem. """ gi = self.__global_index( global_index = global_index , ijk = ijk , active_index = active_index) return self._invalid_cell( gi ) def validCellGeometry(self, ijk = None , global_index = None , active_index = None): """Checks if the cell has valid geometry. There are at least two reasons why a cell might have invalid gemetry: 1. In the case of GRID files it is not necessary to supply the geometry for all the cells; in that case this function will return false for cells which do not have valid coordinates. 2. Cells which are used to represent numerical aquifers are typically located in UTM position (0,0); these cells have completely whacked up shape and size; these cells are identified by a heuristic - which might fail If the validCellGeometry( ) returns false for a particular cell functions which calculate cell volumes, real world coordinates and so on - should not be used. """ gi = self.__global_index( global_index = global_index , ijk = ijk , active_index = active_index) return self._valid_cell( gi ) def active( self , ijk = None , global_index = None): """ Is the cell active? See documentation og get_xyz() for explanation of parameters @ijk and @global_index. """ gi = self.__global_index( global_index = global_index , ijk = ijk) active_index = self._get_active_index1( gi) if active_index >= 0: return True else: return False def get_global_index( self , ijk = None , active_index = None): """ Lookup global index based on ijk or active index. """ gi = self.__global_index( active_index = active_index , ijk = ijk) return gi def get_ijk( self, active_index = None , global_index = None): """ Lookup (i,j,k) for a cell, based on either active index or global index. The return value is a tuple with three elements (i,j,k). """ i = ctypes.c_int() j = ctypes.c_int() k = ctypes.c_int() gi = self.__global_index( active_index = active_index , global_index = global_index) self._get_ijk1( gi , ctypes.byref(i) , ctypes.byref(j) , ctypes.byref(k)) return (i.value , j.value , k.value) def get_xyz( self, active_index = None , global_index = None , ijk = None): """ Find true position of cell center. Will return world position of the center of a cell in the grid. The return value is a tuple of three elements: (utm_x , utm_y , depth). The cells of a grid can be specified in three different ways: (i,j,k) : As a tuple of i,j,k values. global_index : A number in the range [0,nx*ny*nz). The global index is related to (i,j,k) as: global_index = i + j*nx + k*nx*ny active_index : A number in the range [0,nactive). For many of the EclGrid methods a cell can be specified using any of these three methods. Observe that one and only method is allowed: OK: pos1 = grid.get_xyz( active_index = 100 ) pos2 = grid.get_xyz( ijk = (10,20,7 )) Crash and burn: pos3 = grid.get_xyz( ijk = (10,20,7 ) , global_index = 10) pos4 = grid.get_xyz() All the indices in the EclGrid() class are zero offset, this is in contrast to ECLIPSE which has an offset 1 interface. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) x = ctypes.c_double() y = ctypes.c_double() z = ctypes.c_double() self._get_xyz1( gi , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z)) return (x.value , y.value , z.value) def getNodePos(self , i , j , k): """Will return the (x,y,z) for the node given by (i,j,k). Observe that this method does not consider cells, but the nodes in the grid. This means that the valid input range for i,j and k are are upper end inclusive. To get the four bounding points of the lower layer of the grid: p0 = grid.getNodePos(0 , 0 , 0) p1 = grid.getNodePos(grid.getNX() , 0 , 0) p2 = grid.getNodePos(0 , grid.getNY() , 0) p3 = grid.getNodePos(grid.getNX() , grid.getNY() , 0) """ if not 0 <= i <= self.getNX(): raise IndexError("Invalid I value:%d - valid range: [0,%d]" % (i , self.getNX())) if not 0 <= j <= self.getNY(): raise IndexError("Invalid J value:%d - valid range: [0,%d]" % (j , self.getNY())) if not 0 <= k <= self.getNZ(): raise IndexError("Invalid K value:%d - valid range: [0,%d]" % (k , self.getNZ())) x = ctypes.c_double() y = ctypes.c_double() z = ctypes.c_double() self._get_corner_xyz( i,j,k , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z)) return (x.value , y.value , z.value) def getCellCorner(self , corner_nr , active_index = None , global_index = None , ijk = None): """ Will look up xyz of corner nr @corner_nr lower layer: upper layer 2---3 6---7 | | | | 0---1 4---5 """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) x = ctypes.c_double() y = ctypes.c_double() z = ctypes.c_double() self._get_cell_corner_xyz1( gi , corner_nr , ctypes.byref(x) , ctypes.byref(y) , ctypes.byref(z)) return (x.value , y.value , z.value) def getNodeXYZ(self , i,j,k): """ This function returns the position of Vertex (i,j,k). The coordinates are in the inclusive interval [0,nx] x [0,ny] x [0,nz]. """ nx = self.getNX() ny = self.getNY() nz = self.getNZ() corner = 0 if i == nx: i -= 1 corner += 1 if j == ny: j -= 1 corner += 2 if k == nz: k -= 1 corner += 4 if self._ijk_valid( i , j , k): return self.getCellCorner( corner , global_index = i + j*nx + k*nx*ny ) else: raise IndexError("Invalid coordinates: (%d,%d,%d) " % (i,j,k)) def getLayerXYZ(self , xy_corner , layer): nx = self.getNX() (j , i) = divmod(xy_corner , nx + 1) k = layer return self.getNodeXYZ(i,j,k) def distance( self , global_index1 , global_index2): dx = ctypes.c_double() dy = ctypes.c_double() dz = ctypes.c_double() self._get_distance( global_index1 , global_index2 , ctypes.byref(dx) , ctypes.byref(dy) , ctypes.byref(dz)) return (dx.value , dy.value , dz.value) def depth( self , active_index = None , global_index = None , ijk = None): """ Depth of the center of a cell. Returns the depth of the center of the cell given by @active_index, @global_index or @ijk. See method get_xyz() for documentation of @active_index, @global_index and @ijk. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) return self._get_depth( gi ) def top( self , i , j ): """ Top of the reservoir; in the column (@i , @j). Returns average depth of the four top corners. """ return self._get_top( i , j ) def top_active( self, i, j ): """ Top of the active part of the reservoir; in the column (@i , @j). Raises ValueError if (i,j) column is inactive. """ for k in range(self.getNZ()): a_idx = self.get_active_index(ijk=(i,j,k)) if a_idx >= 0: return self._get_top1A(a_idx) raise ValueError('No active cell in column (%d,%d)' % (i,j)) def bottom( self , i , j ): """ Bottom of the reservoir; in the column (@i , @j). """ return self._get_bottom( i , j ) def locate_depth( self , depth , i , j ): """ Will locate the k value of cell containing specified depth. Will scan through the grid column specified by the input arguments @i and @j and search for a cell containing the depth given by input argument @depth. The return value is the k value of cell containing @depth. If @depth is above the top of the reservoir the function will return -1, and if @depth is below the bottom of the reservoir the function will return -nz. """ return self._locate_depth( depth , i , j) def find_cell( self , x , y , z , start_ijk = None): """ Lookup cell containg true position (x,y,z). Will locate the cell in the grid which contains the true position (@x,@y,@z), the return value is as a triplet (i,j,k). The underlying C implementation is not veeery efficient, and can potentially take quite long time. If you provide a good intial guess with the parameter @start_ijk (a tuple (i,j,k)) things can speed up quite substantially. If the location (@x,@y,@z) can not be found in the grid, the method will return None. """ start_index = 0 if start_ijk: start_index = self.__global_index( ijk = start_ijk ) global_index = self._get_ijk_xyz( x , y , z , start_index) if global_index >= 0: i = ctypes.c_int() j = ctypes.c_int() k = ctypes.c_int() self._get_ijk1( global_index, ctypes.byref(i), ctypes.byref(j), ctypes.byref(k) ) return (i.value, j.value, k.value) return None def cell_contains( self , x , y , z , active_index = None , global_index = None , ijk = None): """ Will check if the cell contains point given by world coordinates (x,y,z). See method get_xyz() for documentation of @active_index, @global_index and @ijk. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) return self._cell_contains( gi , x,y,z) def findCellXY(self , x, y , k): """Will find the i,j of cell with utm coordinates x,y. The @k input is the layer you are interested in, the allowed values for k are [0,nz]. If the coordinates (x,y) are found to be outside the grid a ValueError exception is raised. """ if 0 <= k <= self.getNZ(): i = ctypes.c_int() j = ctypes.c_int() ok = self._get_ij_xy( x,y,k , ctypes.byref(i) , ctypes.byref(j)) if ok: return (i.value , j.value) else: raise ValueError("Could not find the point:(%g,%g) in layer:%d" % (x,y,k)) else: raise IndexError("Invalid layer value:%d" % k) @staticmethod def d_cmp(a,b): return cmp(a[0] , b[0]) def findCellCornerXY(self , x, y , k): """Will find the corner nr of corner closest to utm coordinates x,y. The @k input is the layer you are interested in, the allowed values for k are [0,nz]. If the coordinates (x,y) are found to be outside the grid a ValueError exception is raised. """ i,j = self.findCellXY(x,y,k) if k == self.getNZ(): k -= 1 corner_shift = 4 else: corner_shift = 0 nx = self.getNX() x0,y0,z0 = self.getCellCorner( corner_shift , ijk = (i,j,k)) d0 = math.sqrt( (x0 - x)*(x0 - x) + (y0 - y)*(y0 - y)) c0 = i + j*(nx + 1) x1,y1,z1 = self.getCellCorner( 1 + corner_shift , ijk = (i,j,k)) d1 = math.sqrt( (x1 - x)*(x1 - x) + (y1 - y)*(y1 - y)) c1 = i + 1 + j*(nx + 1) x2,y2,z2 = self.getCellCorner( 2 + corner_shift , ijk = (i,j,k)) d2 = math.sqrt( (x2 - x)*(x2 - x) + (y2 - y)*(y2 - y)) c2 = i + (j + 1)*(nx + 1) x3,y3,z3 = self.getCellCorner( 3 + corner_shift , ijk = (i,j,k)) d3 = math.sqrt( (x3 - x)*(x3 - x) + (y3 - y)*(y3 - y)) c3 = i + 1 + (j + 1)*(nx + 1) l = [(d0 , c0) , (d1,c1) , (d2 , c2) , (d3,c3)] l.sort( EclGrid.d_cmp ) return l[0][1] def cell_regular(self, active_index = None , global_index = None , ijk = None): """ The ECLIPSE grid models often contain various degenerate cells, which are twisted, have overlapping corners or what not. This function gives a moderate sanity check on a cell, essentially what the function does is to check if the cell contains it's own centerpoint - which is actually not as trivial as it sounds. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) return self._cell_regular( gi ) def cell_volume( self, active_index = None , global_index = None , ijk = None): """ Calculate the volume of a cell. Will calculate the total volume of the cell. See method get_xyz() for documentation of @active_index, @global_index and @ijk. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) return self._get_cell_volume( gi) def cell_dz( self , active_index = None , global_index = None , ijk = None): """ The thickness of a cell. Will calculate the (average) thickness of the cell. See method get_xyz() for documentation of @active_index, @global_index and @ijk. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index ) return self._get_cell_thickness( gi ) def getCellDims(self , active_index = None , global_index = None , ijk = None): """Will return a tuple (dx,dy,dz) for cell dimension. The dx and dy values are best effor estimates of the cell size along the i and j directions respectively. The three values are guaranteed to satisfy: dx * dy * dz = dV See method get_xyz() for documentation of @active_index, @global_index and @ijk. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index ) dx = self._get_cell_dx( gi ) dy = self._get_cell_dy( gi ) dz = self._get_cell_thickness( gi ) return (dx,dy,dz) def getNumLGR(self): """ How many LGRs are attached to this main grid? How many LGRs are attached to this main grid; the grid instance doing the query must itself be a main grid. """ return self._num_lgr( ) def has_lgr( self , lgr_name ): """ Query if the grid has an LGR with name @lgr_name. """ if self._has_lgr( lgr_name ): return True else: return False def get_lgr( self , lgr_name ): """ Get EclGrid instance with LGR content. Return an EclGrid instance based on the LGR named @lgr_name. The LGR grid instance is in most questions like an ordinary grid instance; the only difference is that it can not be used for further queries about LGRs. If the grid does not contain an LGR with this name the method will return None. """ if self._has_lgr( lgr_name ): lgr = self._get_lgr( name ) lgr.setParent( self ) return lgr else: raise KeyError("No such LGR:%s" % lgr_name) def get_cell_lgr( self, active_index = None , global_index = None , ijk = None): """ Get EclGrid instance located in cell. Will query the current grid instance if the cell given by @active_index, @global_index or @ijk has been refined with an LGR. Will return None if the cell in question has not been refined, the return value can be used for further queries. See get_xyz() for documentation of the input parameters. """ gi = self.__global_index( ijk = ijk , active_index = active_index , global_index = global_index) lgr = self._get_cell_lgr( gi ) if lgr: lgr.setParent( self ) return lgr else: raise IndexError("No LGR defined for this cell") def grid_value( self , kw , i , j , k): """ Will evalute @kw in location (@i,@j,@k). The ECLIPSE properties and solution vectors are stored in restart and init files as 1D vectors of length nx*nx*nz or nactive. The grid_value() method is a minor convenience function to convert the (@i,@j,@k) input values to an appropriate 1D index. Depending on the length of kw the input arguments are converted either to an active index or to a global index. If the length of kw does not fit with either the global size of the grid or the active size of the grid things will fail hard. """ return self._grid_value( kw , i , j , k) def load_column( self , kw , i , j , column): """ Load the values of @kw from the column specified by (@i,@j). The method will scan through all k values of the input field @kw for fixed values of i and j. The size of @kw must be either nactive or nx*ny*nz. The input argument @column should be a DoubleVector instance, observe that if size of @kw == nactive k values corresponding to inactive cells will not be modified in the @column instance; in that case it is important that @column is initialized with a suitable default value. """ self._load_column( kw , i , j , column) def createKW( self , array , kw_name , pack): """ Creates an EclKW instance based on existing 3D numpy object. The method create3D() does the inverse operation; creating a 3D numpy object from an EclKW instance. If the argument @pack is true the resulting keyword will have length 'nactive', otherwise the element will have length nx*ny*nz. """ if array.ndim == 3: dims = array.shape if dims[0] == self.getNX() and dims[1] == self.getNY() and dims[2] == self.getNZ(): dtype = array.dtype if dtype == numpy.int32: type = EclDataType.ECL_INT elif dtype == numpy.float32: type = EclDataType.ECL_FLOAT elif dtype == numpy.float64: type = EclDataType.ECL_DOUBLE else: sys.exit("Do not know how to create ecl_kw from type:%s" % dtype) if pack: size = self.getNumActive() else: size = self.getGlobalSize() if len(kw_name) > 8: # Silently truncate to length 8 - ECLIPSE has it's challenges. kw_name = kw_name[0:8] kw = EclKW( kw_name , size , type ) active_index = 0 global_index = 0 for k in range( self.getNZ() ): for j in range( self.getNY() ): for i in range( self.getNX() ): if pack: if self.active( global_index = global_index ): kw[active_index] = array[i,j,k] active_index += 1 else: if dtype == numpy.int32: kw[global_index] = int( array[i,j,k] ) else: kw[global_index] = array[i,j,k] global_index += 1 return kw raise ValueError("Wrong size / dimension on array") def coarse_groups(self): """ Will return the number of coarse groups in this grid. """ return self._num_coarse_groups( ) def in_coarse_group(self , global_index = None , ijk = None , active_index = None): """ Will return True or False if the cell is part of coarse group. """ global_index = self.__global_index( active_index = active_index , ijk = ijk , global_index = global_index) return self._in_coarse_group1( global_index ) def create3D( self , ecl_kw , default = 0): """ Creates a 3D numpy array object with the data from @ecl_kw. Observe that 3D numpy object is a copy of the data in the EclKW instance, i.e. modification to the numpy object will not be reflected in the ECLIPSE keyword. The methods createKW() does the inverse operation; creating an EclKW instance from a 3D numpy object. Alternative: Creating the numpy array object is not very efficient; if you only need a limited number of elements from the ecl_kw instance it might be wiser to use the grid_value() method: value = grid.grid_value( ecl_kw , i , j , k ) """ if len(ecl_kw) == self.getNumActive() or len(ecl_kw) == self.getGlobalSize(): array = numpy.ones( [ self.getGlobalSize() ] , dtype = ecl_kw.dtype) * default kwa = ecl_kw.array if len(ecl_kw) == self.getGlobalSize(): for i in range(kwa.size): array[i] = kwa[i] else: data_index = 0 for global_index in range(self.getGlobalSize()): if self.active( global_index = global_index ): array[global_index] = kwa[data_index] data_index += 1 array = array.reshape( [self.getNX() , self.getNY() , self.getNZ()] , order = 'F') return array else: err_msg_fmt = 'Keyword "%s" has invalid size %d; must be either nactive=%d or nx*ny*nz=%d' err_msg = err_msg_fmt % (ecl_kw, len(ecl_kw), self.getNumActive(), self.getGlobalSize()) raise ValueError(err_msg) def save_grdecl(self , pyfile, output_unit = EclUnitTypeEnum.ECL_METRIC_UNITS): """ Will write the the grid content as grdecl formatted keywords. Will only write the main grid. """ cfile = CFILE( pyfile ) self._fprintf_grdecl2( cfile , output_unit) def save_EGRID( self , filename , output_unit = EclUnitTypeEnum.ECL_METRIC_UNITS): """ Will save the current grid as a EGRID file. """ self._fwrite_EGRID2( filename, output_unit ) def save_GRID( self , filename , output_unit = EclUnitTypeEnum.ECL_METRIC_UNITS): """ Will save the current grid as a EGRID file. """ self._fwrite_GRID2( filename, output_unit ) def write_grdecl( self , ecl_kw , pyfile , special_header = None , default_value = 0): """ Writes an EclKW instance as an ECLIPSE grdecl formatted file. The input argument @ecl_kw must be an EclKW instance of size nactive or nx*ny*nz. If the size is nactive the inactive cells will be filled with @default_value; hence the function will always write nx*ny*nz elements. The data in the @ecl_kw argument can be of type integer, float, double or bool. In the case of bool the default value must be specified as 1 (True) or 0 (False). The input argument @pyfile should be a valid python filehandle opened for writing; i.e. pyfile = open("PORO.GRDECL" , "w") grid.write_grdecl( poro_kw , pyfile , default_value = 0.0) grid.write_grdecl( permx_kw , pyfile , default_value = 0.0) pyfile.close() """ if len(ecl_kw) == self.getNumActive() or len(ecl_kw) == self.getGlobalSize(): cfile = CFILE( pyfile ) self._fwrite_grdecl( ecl_kw , special_header , cfile , default_value ) else: raise ValueError("Keyword: %s has invalid size(%d), must be either nactive:%d or nx*ny*nz:%d" % (ecl_kw.getName() , len(ecl_kw) , self.getNumActive() , self.getGlobalSize())) def exportACTNUM(self): actnum = IntVector( initial_size = self.getGlobalSize() ) self._init_actnum( actnum.getDataPtr() ) return actnum def compressedKWCopy(self, kw): if len(kw) == self.getNumActive(): return kw.copy( ) elif len(kw) == self.getGlobalSize(): kw_copy = EclKW( kw.getName() , self.getNumActive() , kw.data_type) self._compressed_kw_copy( kw_copy , kw) return kw_copy else: raise ValueError("The input keyword must have nx*n*nz or nactive elements. Size:%d invalid" % len(kw)) def globalKWCopy(self, kw , default_value): if len(kw) == self.getGlobalSize( ): return kw.copy( ) elif len(kw) == self.getNumActive(): kw_copy = EclKW( kw.getName() , self.getGlobalSize() , kw.data_type) kw_copy.assign( default_value ) self._global_kw_copy( kw_copy , kw) return kw_copy else: raise ValueError("The input keyword must have nx*n*nz or nactive elements. Size:%d invalid" % len(kw)) def exportACTNUMKw(self): actnum = EclKW("ACTNUM" , self.getGlobalSize() , EclDataType.ECL_INT) self._init_actnum( actnum.getDataPtr() ) return actnum def createVolumeKeyword(self , active_size = True): """Will create a EclKW initialized with cell volumes. The purpose of this method is to create a EclKW instance which is initialized with all the cell volumes, this can then be used to perform volume summation; i.e. to calculate the total oil volume: soil = 1 - sgas - swat cell_volume = grid.createVolumeKeyword() tmp = cell_volume * soil oip = tmp.sum( ) The oil in place calculation shown above could easily be implemented by iterating over the soil kw, however using the volume keyword has two advantages: 1. The calculation of cell volumes is quite time consuming, by storing the results in a kw they can be reused. 2. By using the compact form 'oip = cell_volume * soil' the inner loop iteration will go in C - which is faster. By default the kw will only have values for the active cells, but by setting the optional variable @active_size to False you will get volume values for all cells in the grid. """ return self._create_volume_keyword( active_size ) def export_coord(self): return self._export_coord() def export_zcorn(self): return self._export_zcorn() def export_actnum(self): return self._export_actnum() def export_mapaxes(self): if not self._use_mapaxes(): return None return self._export_mapaxes()
class EclFile(BaseCClass): TYPE_NAME = "ecl_file" _open = EclPrototype("void* ecl_file_open( char* , int )", bind=False) _get_file_type = EclPrototype( "ecl_file_enum ecl_util_get_file_type( char* , bool* , int*)", bind=False) _writable = EclPrototype("bool ecl_file_writable( ecl_file )") _save_kw = EclPrototype( "void ecl_file_save_kw( ecl_file , ecl_kw )") _close = EclPrototype("void ecl_file_close( ecl_file )") _iget_restart_time = EclPrototype( "time_t ecl_file_iget_restart_sim_date( ecl_file , int )") _iget_restart_days = EclPrototype( "double ecl_file_iget_restart_sim_days( ecl_file , int )") _get_restart_index = EclPrototype( "int ecl_file_get_restart_index( ecl_file , time_t)") _get_src_file = EclPrototype( "char* ecl_file_get_src_file( ecl_file )") _replace_kw = EclPrototype( "void ecl_file_replace_kw( ecl_file , ecl_kw , ecl_kw , bool)") _fwrite = EclPrototype( "void ecl_file_fwrite_fortio( ecl_file , fortio , int)") _has_report_step = EclPrototype( "bool ecl_file_has_report_step( ecl_file , int)") _has_sim_time = EclPrototype( "bool ecl_file_has_sim_time( ecl_file , time_t )") _get_global_view = EclPrototype( "ecl_file_view_ref ecl_file_get_global_view( ecl_file )") @staticmethod def get_filetype(filename): fmt_file = ctypes.c_bool() report_step = ctypes.c_int() file_type = EclFile._get_file_type(filename, ctypes.byref(fmt_file), ctypes.byref(report_step)) if file_type in [ EclFileEnum.ECL_RESTART_FILE, EclFileEnum.ECL_SUMMARY_FILE ]: report_step = report_step.value else: report_step = None if file_type in [ EclFileEnum.ECL_OTHER_FILE, EclFileEnum.ECL_DATA_FILE ]: fmt_file = None else: fmt_file = fmt_file.value return (file_type, report_step, fmt_file) @classmethod def restart_block(cls, filename, dtime=None, report_step=None): raise NotImplementedError( "The restart_block implementation has been removed - open file normally and use EclFileView." ) @classmethod def contains_report_step(cls, filename, report_step): """ Will check if the @filename contains @report_step. This classmethod works by opening the file @filename and searching through linearly to see if an ecl_kw with value corresponding to @report_step can be found. Since this is a classmethod it is invoked like this: import ecl.ecl.ecl as ecl .... if ecl.EclFile.contains_report_step("ECLIPSE.UNRST" , 20): print "OK - file contains report step 20" else: print "File does not contain report step 20" If you have already loaded the file into an EclFile instance you should use the has_report_step() method instead. """ obj = EclFile(filename) return obj.has_report_step(report_step) @classmethod def contains_sim_time(cls, filename, dtime): """ Will check if the @filename contains simulation at @dtime. This classmethod works by opening the file @filename and searching through linearly to see if a result block at the time corresponding to @dtime can be found. Since this is a classmethod it is invoked like this: import ecl.ecl.ecl as ecl .... if ecl.EclFile.contains_sim_time("ECLIPSE.UNRST" , datetime.datetime( 2007 , 10 , 10) ): print "OK - file contains 10th of October 2007" else: print "File does not contain 10th of October 2007" If you have already loaded the file into an EclFile instance you should use the has_sim_time() method instead. """ obj = EclFile(filename) return obj.has_sim_time(dtime) @property def report_list(self): report_steps = [] try: seqnum_list = self["SEQNUM"] for s in seqnum_list: report_steps.append(s[0]) except KeyError: # OK - we did not have seqnum; that might be because this # a non-unified restart file; or because this is not a # restart file at all. fname = self.getFilename() matchObj = re.search("\.[XF](\d{4})$", fname) if matchObj: report_steps.append(int(matchObj.group(1))) else: raise TypeError( 'Tried get list of report steps from file "%s" - which is not a restart file' % fname) return report_steps @classmethod def file_report_list(cls, filename): """ Will identify the available report_steps from @filename. """ file = EclFile(filename) return file.report_list def __repr__(self): fn = self.getFilename() wr = ', read/write' if self._writable() else '' return self._create_repr('"%s"%s' % (fn, wr)) def __init__(self, filename, flags=0): """ Loads the complete file @filename. Will create a new EclFile instance with the content of file @filename. The file @filename must be in 'restart format' - otherwise it will be crash and burn. The optional argument flags can be an or'ed combination of the flags: ecl.ECL_FILE_WRITABLE : It is possible to update the content of the keywords in the file. ecl.ECL_FILE_CLOSE_STREAM : The underlying FILE * is closed when not used; to save number of open file descriptors in cases where a high number of EclFile instances are open concurrently. When the file has been loaded the EclFile instance can be used to query for and get reference to the EclKW instances constituting the file, like e.g. SWAT from a restart file or FIPNUM from an INIT file. """ c_ptr = self._open(filename, flags) if c_ptr is None: raise IOError('Failed to open file "%s"' % filename) else: super(EclFile, self).__init__(c_ptr) self.global_view = self._get_global_view() self.global_view.setParent(self) def save_kw(self, kw): """ Will write the @kw back to file. This function should typically be used in situations like this: 1. Create an EclFile instance around an ECLIPSE output file. 2. Extract a keyword of interest and modify it. 3. Call this method to save the modifications to disk. There are several restrictions to the use of this function: 1. The EclFile instance must have been created with the optional read_only flag set to False. 2. You can only modify the content of the keyword; if you try to modify the header in any way (i.e. size, datatype or name) the function will fail. 3. The keyword you are trying to save must be exactly the keyword you got from this EclFile instance, otherwise the function will fail. """ if self._writable(): self._save_kw(kw) else: raise IOError('save_kw: the file "%s" has been opened read only.' % self.getFilename()) def __len__(self): return len(self.global_view) def close(self): if self: self._close() self._invalidateCPointer() def free(self): self.close() def block_view(self, kw, kw_index): if not kw in self: raise KeyError('No such keyword "%s".' % kw) ls = self.global_view.numKeywords(kw) idx = kw_index if idx < 0: idx += ls if 0 <= idx < ls: return self.global_view.blockView(kw, idx) raise IndexError('Index out of range, must be in [0, %d), was %d.' % (ls, kw_index)) def block_view2(self, start_kw, stop_kw, start_index): return self.global_view.blockView2(start_kw, stop_kw, start_index) def restart_view(self, seqnum_index=None, report_step=None, sim_time=None, sim_days=None): return self.global_view.restartView(seqnum_index, report_step, sim_time, sim_days) def select_block(self, kw, kw_index): raise NotImplementedError( "The select_block implementation has been removed - use EclFileView" ) def select_global(self): raise NotImplementedError( "The select_global implementation has been removed - use EclFileView" ) def select_restart_section(self, index=None, report_step=None, sim_time=None): raise NotImplementedError( "The select_restart_section implementation has been removed - use EclFileView" ) """ Will select a restart section as the active section. You must specify a report step with the @report_step argument, a true time with the @sim_time argument or a plain index to select restart block. If none of arguments are given exception TypeError will be raised. If present the @sim_time argument should be a datetime instance. If the restart section you ask for can not be found the method will raise a ValueError exeception. To protect against this you can query first with the has_report_step(), has_sim_time() or num_report_steps() methods. This method should be used when you have already loaded the complete file; if you only want to load a section from the file you can use the classmethod restart_block(). The method will return 'self' which can be used to aid readability. """ def select_last_restart(self): raise NotImplementedError( "The select_restart_section implementation has been removed - use EclFileView" ) """ Will select the last SEQNUM block in restart file. Works by searching for the last SEQNUM keyword; the SEQNUM Keywords are only present in unified restart files. If this is a non-unified restart file (or not a restart file at all), the method will do nothing and return False. """ def __getitem__(self, index): """ Implements [] operator; index can be integer or key. Will look up EclKW instances from the current EclFile instance. The @index argument can either be an integer, in which case the method will return EclKW number @index, or alternatively a keyword string, in which case the method will return a list of EclKW instances with that keyword: restart_file = ecl_file.EclFile("ECLIPSE.UNRST") kw9 = restart_file[9] swat_list = restart_file["SWAT"] The keyword based lookup can be combined with an extra [] to get EclKW instance nr: swat9 = restart_file["SWAT"][9] Will return the 10'th SWAT keyword from the restart file. The following example will iterate over all the SWAT keywords in a restart file: restart_file = ecl_file.EclFile("ECLIPSE.UNRST") for swat in restart_file["SWAT"]: .... """ if isinstance(index, int): ls = len(self) idx = index if idx < 0: idx += ls if 0 <= idx < ls: return self.global_view[idx] else: raise IndexError('Index must be in [0, %d), was: %d.' % (ls, index)) return self.global_view[index] def iget_kw(self, index, copy=False): """ Will return EclKW instance nr @index. In the files loaded with the EclFile implementation the ECLIPSE keywords come sequentially in a long series, an INIT file might have the following keywords: INTEHEAD LOGIHEAD DOUBHEAD PORV DX DY DZ PERMX PERMY PERMZ MULTX MULTY ..... The iget_kw() method will give you a EclKW reference to keyword nr @index. This functionality is also available through the index operator []: file = EclFile( "ECLIPSE.INIT" ) permx = file.iget_kw( 7 ) permz = file[ 9 ] Observe that the returned EclKW instance is only a reference to the data owned by the EclFile instance. The method iget_named_kw() which lets you specify the name of the keyword you are interested in is in general more useful than this method. """ kw = self[index] if copy: return EclKW.copy(kw) else: return kw def iget_named_kw(self, kw_name, index, copy=False): return self.global_view.iget_named_kw(kw_name, index) def restart_get_kw(self, kw_name, dtime, copy=False): """Will return EclKW @kw_name from restart file at time @dtime. This function assumes that the current EclFile instance represents a restart file. It will then look for keyword @kw_name exactly at the time @dtime; @dtime is a datetime instance: file = EclFile( "ECLIPSE.UNRST" ) swat2010 = file.restart_get_kw( "SWAT" , datetime.datetime( 2000 , 1 , 1 )) By default the returned kw instance is a reference to the ecl_kw still contained in the EclFile instance; i.e. the kw will become a dangling reference if the EclFile instance goes out of scope. If the optional argument @copy is True the returned kw will be a true copy. If the file does not have the keyword at the specified time the function will raise IndexError(); if the file does not have the keyword at all - KeyError will be raised. """ index = self._get_restart_index(CTime(dtime)) if index >= 0: if self.num_named_kw(kw_name) > index: kw = self.iget_named_kw(kw_name, index) if copy: return EclKW.copy(kw) else: return kw else: if self.has_kw(kw_name): raise IndexError('Does not have keyword "%s" at time:%s.' % (kw_name, dtime)) else: raise KeyError('Keyword "%s" not recognized.' % kw_name) else: raise IndexError('Does not have keyword "%s" at time:%s.' % (kw_name, dtime)) def replace_kw(self, old_kw, new_kw): """ Will replace @old_kw with @new_kw in current EclFile instance. This method can be used to replace one of the EclKW instances in the current EclFile. The @old_kw reference must be to the actual EclKW instance in the current EclFile instance (the final comparison is based on C pointer equality!), i.e. it must be a reference (not a copy) from one of the ??get_kw?? methods of the EclFile class. In the example below we replace the SWAT keyword from a restart file: swat = file.iget_named_kw( "SWAT" , 0 ) new_swat = swat * 0.25 file.replace_kw( swat , new_swat ) The C-level ecl_file_type structure takes full ownership of all installed ecl_kw instances; mixing the garbage collector into it means that this is quite low level - and potentially dangerous! """ # We ensure that this scope owns the new_kw instance; the # new_kw will be handed over to the ecl_file instance, and we # can not give away something we do not alreeady own. if not new_kw.data_owner: new_kw = EclKW.copy(new_kw) # The ecl_file instance will take responsability for freeing # this ecl_kw instance. new_kw.data_owner = False self._replace_kw(old_kw, new_kw, False) @property def size(self): """ The number of keywords in the current EclFile object. """ return len(self) @property def unique_size(self): """ The number of unique keyword (names) in the current EclFile object. """ return self.global_view.uniqueSize() def keys(self): """ Will return a list of unique kw names - like keys() on a dict. """ header_dict = {} for index in range(len(self)): kw = self[index] header_dict[kw.getName()] = True return header_dict.keys() @property def headers(self): """ Will return a list of the headers of all the keywords. """ header_list = [] for index in range(self.size): kw = self[index] header_list.append(kw.header) return header_list @property def report_steps(self): """ Will return a list of all report steps. The method works by iterating through the whole restart file looking for 'SEQNUM' keywords; if the current EclFile instance is not a restart file it will not contain any 'SEQNUM' keywords and the method will simply return an empty list. """ steps = [] seqnum_list = self["SEQNUM"] for kw in self["SEQNUM"]: steps.append(kw[0]) return steps @property def report_dates(self): """ Will return a list of the dates for all report steps. The method works by iterating through the whole restart file looking for 'SEQNUM/INTEHEAD' keywords; the method can probably be tricked by other file types also containing an INTEHEAD keyword. """ if self.has_kw('SEQNUM'): dates = [] for index in range(self.num_named_kw('SEQNUM')): dates.append(self.iget_restart_sim_time(index)) return dates elif 'INTEHEAD' in self: # This is a uber-hack; should export the ecl_rsthead # object as ctypes structure. intehead = self["INTEHEAD"][0] year = intehead[66] month = intehead[65] day = intehead[64] date = datetime.datetime(year, month, day) return [date] return None @property def dates(self): """ Will return a list of the dates for all report steps. """ return self.report_dates def num_named_kw(self, kw): """ The number of keywords with name == @kw in the current EclFile object. """ return self.global_view.numKeywords(kw) def has_kw(self, kw, num=0): """ Check if current EclFile instance has a keyword @kw. If the optional argument @num is given it will check if the EclFile has at least @num occurences of @kw. """ return self.num_named_kw(kw) > num def __contains__(self, kw): """ Check if the current file contains keyword @kw. """ return self.has_kw(kw) def has_report_step(self, report_step): """ Checks if the current EclFile has report step @report_step. If the EclFile in question is not a restart file, you will just get False. If you want to check if the file contains the actual report_step before loading the file, you should use the classmethod contains_report_step() instead. """ return self._has_report_step(report_step) def num_report_steps(self): """ Returns the total number of report steps in the restart file. Works by counting the number of 'SEQNUM' instances, and will happily return 0 for a non-restart file. Observe that the report_steps present in a unified restart file are in general not consecutive, i.e. the last report step will typically be much higher than the return value from this function. """ return len(self["SEQNUM"]) def has_sim_time(self, dtime): """ Checks if the current EclFile has data for time @dtime. The implementation goes through all the INTEHEAD headers in the EclFile, i.e. it can be fooled (and probably crash and burn) if the EclFile instance in question is has INTEHEAD keyword(s), but is still not a restart file. The @dtime argument should be a normal python datetime instance. """ return self._has_sim_time(CTime(dtime)) def iget_restart_sim_time(self, index): """ Will locate restart block nr @index and return the true time as a datetime instance. """ ct = CTime(self._iget_restart_time(index)) return ct.datetime() def iget_restart_sim_days(self, index): """ Will locate restart block nr @index and return the number of days (in METRIC at least ...) since the simulation started. """ return self._iget_restart_days(index) def get_filename(self): """ Name of the file currently loaded. """ fn = self._get_src_file() return str(fn) if fn else '' def fwrite(self, fortio): """ Will write current EclFile instance to fortio stream. ECLIPSE is written in Fortran; and a "special" handle for Fortran IO must be used when reading and writing these files. This method will write the current EclFile instance to a FortIO stream already opened for writing: import ecl.ecl.ecl as ecl ... fortio = ecl.FortIO( "FILE.XX" ) file.fwrite( fortio ) fortio.close() """ self._fwrite(fortio, 0)
class EclRFTFile(BaseCClass): TYPE_NAME = "ecl_rft_file" _load = EclPrototype("void* ecl_rft_file_alloc_case( char* )", bind = False) _iget = EclPrototype("ecl_rft_ref ecl_rft_file_iget_node( ecl_rft_file , int )") _get_rft = EclPrototype("ecl_rft_ref ecl_rft_file_get_well_time_rft( ecl_rft_file , char* , time_t)") _has_rft = EclPrototype("bool ecl_rft_file_case_has_rft( char* )", bind = False) _free = EclPrototype("void ecl_rft_file_free( ecl_rft_file )") _get_size = EclPrototype("int ecl_rft_file_get_size__( ecl_rft_file , char* , time_t)") _get_num_wells = EclPrototype("int ecl_rft_file_get_num_wells( ecl_rft_file )") """ The EclRFTFile class is used to load an ECLIPSE RFT file. The EclRFTFile serves as a container which can load and hold the content of an ECLIPSE RFT file. The RFT files will in general contain data for several wells and several times in one large container. The EclRFTClass class contains methods get the the RFT results for a specific time and/or well. The EclRFTFile class can in general contain a mix of RFT and PLT measurements. The class does not really differentiate between these. """ def __init__(self , case): c_ptr = self._load( case ) super(EclRFTFile , self).__init__(c_ptr) def __len__(self): return self._get_size( None , CTime(-1)) def __getitem__(self, index): if isinstance(index, int): if 0 <= index < len(self): rft = self._iget(index) rft.setParent( self ) return rft else: raise IndexError("Index '%d' must be in range: [0, %d]" % (index, len(self) - 1)) else: raise TypeError("Index must be integer type") def size(self, well=None, date=None): """ The number of elements in EclRFTFile container. By default the size() method will return the total number of RFTs/PLTs in the container, but by specifying the optional arguments date and/or well the function will only count the number of well measurements matching that time or well name. The well argument can contain wildcards. rftFile = ecl.EclRFTFile( "ECLIPSE.RFT" ) print "Total number of RFTs : %d" % rftFile.size( ) print "RFTs matching OP* : %d" % rftFile.size( well = "OP*" ) print "RFTs at 01/01/2010 : %d" % rftFile.size( date = datetime.date( 2010 , 1 , 1 )) """ if date: cdate = CTime( date ) else: cdate = CTime( -1 ) return self._get_size( well , cdate) def get_num_wells(self): """ Returns the total number of distinct wells in the RFT file. """ return self._get_num_wells( ) def get_headers(self): """ Returns a list of two tuples (well_name , date) for the whole file. """ header_list = [] for i in (range(self._get_size( None , CTime(-1)))): rft = self.iget( i ) header_list.append( (rft.getWellName() , rft.getDate()) ) return header_list def iget(self , index): """ Will lookup RFT @index - equivalent to [@index]. """ return self[index] def get(self , well_name , date ): """ Will look up the RFT object corresponding to @well and @date. Raise Exception if not found. """ if self.size( well = well_name , date = date) == 0: raise KeyError("No RFT for well:%s at %s" % (well_name , date)) rft = self._get_rft( well_name , CTime( date )) rft.setParent( self ) return rft def free(self): self._free( ) def __repr__(self): w = len(self) return self._create_repr('wells = %d' % w)
class EclSMSPECNode(BaseCClass): """ Small class with some meta information about a summary variable. The summary variables have different attributes, like if they represent a total quantity, a rate or a historical quantity. These quantities, in addition to the underlying values like WGNAMES, KEYWORD and NUMS taken from the the SMSPEC file are stored in this structure. """ TYPE_NAME = "smspec_node" _node_is_total = EclPrototype("bool smspec_node_is_total( smspec_node )") _node_is_historical = EclPrototype( "bool smspec_node_is_historical( smspec_node )") _node_is_rate = EclPrototype("bool smspec_node_is_rate( smspec_node )") _node_unit = EclPrototype("char* smspec_node_get_unit( smspec_node )") _node_wgname = EclPrototype("char* smspec_node_get_wgname( smspec_node )") _node_keyword = EclPrototype( "char* smspec_node_get_keyword( smspec_node )") _node_num = EclPrototype("int smspec_node_get_num( smspec_node )") _node_need_num = EclPrototype("bool smspec_node_need_nums( smspec_node )") _gen_key1 = EclPrototype("char* smspec_node_get_gen_key1( smspec_node )") _gen_key2 = EclPrototype("char* smspec_node_get_gen_key2( smspec_node )") _var_type = EclPrototype( "ecl_sum_var_type smspec_node_get_var_type( smspec_node )") _cmp = EclPrototype("int smspec_node_cmp( smspec_node , smspec_node)") def __init__(self): super(EclSMSPECNode, self).__init__(0) # null pointer raise NotImplementedError("Class can not be instantiated directly!") def cmp(self, other): if isinstance(other, EclSMSPECNode): return self._cmp(other) else: raise TypeError("Other argument must be of type EclSMSPECNode") def __lt__(self, other): return self.cmp(other) < 0 def __gt__(self, other): return self.cmp(other) > 0 def __eq__(self, other): return self.cmp(other) == 0 def __hash__(self, other): return hash(self._gen_key1()) @property def unit(self): """ Returns the unit of this node as a string. """ return self._node_unit() @property def wgname(self): """ Returns the WGNAME property for this node. Many variables do not have the WGNAME property, i.e. the field related variables like FOPT and the block properties like BPR:10,10,10. For these variables the function will return None, and not the ECLIPSE dummy value: ":+:+:+:+". """ return self._node_wgname() @property def keyword(self): """ Returns the KEYWORD property for this node. The KEYWORD property is the main classification property in the ECLIPSE SMSPEC file. The properties of a variable can be read from the KEWYORD value; see table 3.4 in the ECLIPSE file format reference manual. """ return self._node_keyword() @property def num(self): return self.getNum() def get_key1(self): """ Returns the primary composite key, i.e. like 'WOPR:OPX' for this node. """ return self._gen_key1() def get_key2(self): """Returns the secondary composite key for this node. Most variables have only one composite key, but in particular nodes which involve (i,j,k) coordinates will contain two forms: getKey1() => "BPR:10,11,6" getKey2() => "BPR:52423" Where the '52423' in getKey2() corresponds to i + j*nx + k*nx*ny. """ return self._gen_key2() def var_type(self): return self._var_type() def get_num(self): """ Returns the NUMS value for this keyword; or None. Many of the summary keywords have an integer stored in the vector NUMS as an attribute, i.e. the block properties have the global index of the cell in the nums vector. If the variable in question makes use of the NUMS value this property will return the value, otherwise it will return None: sum.smspec_node("FOPT").num => None sum.smspec_node("BPR:1000").num => 1000 """ if self._node_need_num(): return self._node_num() else: return None def is_rate(self): """ Will check if the variable in question is a rate variable. The conecpt of rate variabel is important (internally) when interpolation values to arbitrary times. """ return self._node_is_rate() def is_total(self): """ Will check if the node corresponds to a total quantity. The question of whether a variable corresponds to a 'total' quantity or not can be interesting for e.g. interpolation purposes. The actual question whether a quantity is total or not is based on a hardcoded list in smspec_node_set_flags() in smspec_node.c; this list again is based on the tables 2.7 - 2.11 in the ECLIPSE fileformat documentation. """ return self._node_is_total() def is_historical(self): """ Checks if the key corresponds to a historical variable. The check is only based on the last character; all variables ending with 'H' are considered historical. """ return self._node_is_historical()
class FortIO(BaseCClass): TYPE_NAME = "fortio" READ_MODE = 1 WRITE_MODE = 2 READ_AND_WRITE_MODE = 3 APPEND_MODE = 4 _open_reader = EclPrototype("void* fortio_open_reader(char*, bool, bool)", bind=False) _open_writer = EclPrototype("void* fortio_open_writer(char*, bool, bool)", bind=False) _open_readwrite = EclPrototype("void* fortio_open_readwrite(char*, bool, bool)", bind=False) _open_append = EclPrototype("void* fortio_open_append(char*, bool, bool)", bind=False) _guess_fortran = EclPrototype("bool fortio_looks_like_fortran_file(char*, bool)", bind=False) _write_record = EclPrototype("void fortio_fwrite_record(fortio, char*, int)") _get_position = EclPrototype("long fortio_ftell(fortio)") _seek = EclPrototype("void fortio_fseek(fortio, long, int)") _close = EclPrototype("bool fortio_fclose(fortio)") _truncate = EclPrototype("bool fortio_ftruncate(fortio, long)") _filename = EclPrototype("char* fortio_filename_ref(fortio)") def __init__(self, file_name, mode=READ_MODE, fmt_file=False, endian_flip_header=True): """Will open a new FortIO handle to @file_name - default for reading. The newly created FortIO handle will open the underlying FILE* for reading, but if you pass the flag mode=FortIO.WRITE_MODE the file will be opened for writing. Observe that the flag @endian_flip_header will only affect the interpretation of the block size markers in the file, endian flipping of the actual data blocks must be handled at a higher level. When you are finished working with the FortIO instance you can manually close it with the close() method, alternatively that will happen automagically when it goes out of scope. Small example script opening a restart file, and then writing all the pressure keywords to another file: import sys from ecl.ecl import FortIO, EclFile rst_file = EclFile(sys.argv[1]) fortio = FortIO("PRESSURE", mode=FortIO.WRITE_MODE) for kw in rst_file: if kw.name() == "PRESSURE": kw.write(fortio) fortio.close() See the documentation of openFortIO() for an alternative method based on a context manager and the with statement. """ read_modes = (FortIO.READ_MODE, FortIO.APPEND_MODE, FortIO.READ_AND_WRITE_MODE) if mode in read_modes and not os.path.exists(file_name): raise IOError('No such file "%s".' % file_name) if mode == FortIO.READ_MODE: c_pointer = self._open_reader(file_name, fmt_file, endian_flip_header) elif mode == FortIO.WRITE_MODE: c_pointer = self._open_writer(file_name, fmt_file, endian_flip_header) elif mode == FortIO.READ_AND_WRITE_MODE: c_pointer = self._open_readwrite(file_name, fmt_file, endian_flip_header) elif mode == FortIO.APPEND_MODE: c_pointer = self._open_append(file_name, fmt_file, endian_flip_header) else: raise UserWarning("Unknown mode: %d" % mode) self.__mode = mode if not c_pointer: raise IOError('Failed to open FortIO file "%s".' % file_name) super(FortIO, self).__init__(c_pointer) def close(self): if self: self._close() self._invalidateCPointer() def get_position(self): """ @rtype: long """ return self._get_position() def truncate(self, size=None): """Will truncate the file to new size. If the method is called without a size argument the stream will be truncated to the current position. """ if size is None: size = self.getPosition() if not self._truncate(size): raise IOError("Truncate of fortran filehandle:%s failed" % self.filename()) def filename(self): return self._filename() def seek(self, position, whence=0): # SEEK_SET = 0 # SEEK_CUR = 1 # SEEK_END = 2 self._seek(position, whence) @classmethod def is_fortran_file(cls, filename, endian_flip=True): """@rtype: bool @type filename: str Will use heuristics to try to guess if @filename is a binary file written in fortran style. ASCII files will return false, even if they are structured as ECLIPSE keywords. """ return cls._guess_fortran(filename, endian_flip) def free(self): self.close()