Source code for deepfield.field.tables.tables

"""Tables component."""
import numpy as np
import pandas as pd
import h5py

from ..base_component import BaseComponent
from ..decorators import apply_to_each_input
from ..parse_utils import read_table, TABLE_INFO
from .table_interpolation import TABLE_INTERPOLATOR
from ..plot_utils import plot_table_1d, plot_table_2d


[docs] class Tables(BaseComponent): """Tables component of geological model."""
[docs] @apply_to_each_input def apply(self, func, attr, *args, inplace=False, **kwargs): raise NotImplementedError()
def _read_buffer(self, buffer, attr, **kwargs): """Read table data from string buffer. Parameters ---------- buffer : buffer String buffer to read from. attr : str Target attribute. Returns ------- comp : Tables Tables with new attribute. """ dtype = kwargs.get('dtype', None) table = read_table(buffer, TABLE_INFO[attr], dtype) setattr(self, attr, _Table(data=table, name=attr)) return self def _load_hdf5(self, path, attrs=None, raise_errors=False, logger=None, **kwargs): """Load tables from HDF5 file. Parameters ---------- path : str Path to file to load data from. attrs : str or array of str, optional Table names to get from file. If not given, loads all. Returns ------- comp : Tables Tables with loaded attributes. """ with h5py.File(path, 'r') as f: grp = f[self.class_name] for attr in grp.keys() if attrs is None else attrs: try: table = pd.read_hdf(path, key='/'.join([grp.name, attr]), mode='r') except KeyError as err: if raise_errors: raise err if logger is not None: logger.info('Attribute %s not found in %s.' % (attr.upper(), grp.name)) continue setattr(self, attr, _Table(data=table, name=attr)) return self def _dump_hdf5(self, path, mode='w', state=False, **kwargs): """Save tables into HDF5 file. Parameters ---------- path : str Path to output file. mode : str Mode to open file. 'w': write, a new file is created (an existing file with the same name would be deleted). 'a': append, an existing file is opened for reading and writing, and if the file does not exist it is created. Default to 'a'. state : bool Dump compoments's state. Returns ------- comp : Tables Tables unchanged. """ _ = kwargs with h5py.File(path, mode) as f: tab = f[self.class_name] if self.class_name in f else f.create_group(self.class_name) if state: for k, v in self.state.as_dict().items(): tab.attrs[k] = v for attr, table in self.items(): pd.DataFrame(table).to_hdf(path, key='/'.join([self.class_name, attr]), mode='a') return self def _dump_ascii(self, path, attrs=None, mode='w', **kwargs): """Save tables into ASCII file. Parameters ---------- path : str Path to output file. attrs : str, array of str or None Array of keywords to dump into file. mode : str Mode to open file. 'w': write, a new file is created (an existing file with the same name would be deleted). 'a': append, an existing file is opened for reading and writing, and if the file does not exist it is created. Default to 'w'. fmt : str or sequence of strs, optional Format to be passed into ``numpy.savetxt`` function. Default to '%f'. Returns ------- out : Tables Tables unchanged. """ _ = kwargs if attrs is None: attrs = self.attributes elif isinstance(attrs, str): attrs = [attrs] with open(path, mode + 'b') as f: for att in attrs: table = getattr(self, att) table.dump_ascii(f) return self
[docs] def pvtg_to_pvdg(self, as_saturated=False, inplace=True): """Transforms PVTG (wet gas) table into PVDG (dry gas) form. Parameters ---------- as_saturated: bool If True, properties of the resulting dry gas will be similar to the properties of given saturated gas. Else, properties will be similar to the properties of given dry gas. inplace: bool If True, replaces PVTG table with a PVDG one. Else, returns PVDG as output. Returns ------- pvdg: _Table or None """ pvtg = self.pvtg pressure = pvtg.index.get_level_values('PRESSURE') rv = pvtg.index.get_level_values('RV') if as_saturated: mask = np.zeros_like(pressure, dtype=bool) for p in sorted(list(set(pressure))): level_rv = rv[pressure == p] sat_rv = np.max(level_rv) level_mask = np.all([pressure == p, rv == sat_rv], axis=0) mask = np.any([mask, level_mask], axis=0) else: mask = rv == 0 pvdg = pvtg.loc[mask] pvdg.index = pvdg.index.get_level_values('PRESSURE') pvdg = _Table(data=pvdg, name='PVDG') if inplace: delattr(self, 'PVTG') setattr(self, 'PVDG', pvdg) return self return pvdg
[docs] def pvt_oil(self, pressure, rs=None): """Caclulate FVF and Viscosity for oil.""" if 'PVCDO' in self.attributes: return self.pvcdo(pressure) if 'PVDO' in self.attributes: return self.pvdo(pressure) return self.pvto(np.vstack((rs, pressure)).T)
class _Table(pd.DataFrame): # pylint: disable=abstract-method """Table component.""" _metadata = ['domain', 'name', '_interpolator'] def __init__(self, data=None, **kwargs): self.name = kwargs.pop('name') if 'name' in kwargs else '' super().__init__(data=data, **kwargs) self.domain = list(self.index.names) if list(self.index.names)[0] is not None else None self._interpolator = None def __call__(self, x): """ Apply table-defined function to x Parameters ---------- x: array-like of shape (n_points, len(table.domain)) Points for function to be computed at Returns ------- values: array-like of shape (n_points, len(table.columns)) """ if self._interpolator is None: if self.name in TABLE_INTERPOLATOR: self._interpolator = TABLE_INTERPOLATOR[self.name](self) else: self._interpolator = TABLE_INTERPOLATOR[None](self) return self._interpolator(x) @property def _constructor(self): return self.__class__ def plot(self, figsize=None): """Plot table.""" if self.domain: if len(self.domain) == 1: plot_table_1d(self, figsize=figsize) elif len(self.domain) == 2: plot_table_2d(self, figsize=figsize) else: raise AttributeError('Can plot functions of 1 and 2 variables. Function of %d variables is given' % len(self.domain)) else: raise AttributeError('The table has no domain. Hence, can not be plotted!') def dump_ascii(self, path_or_buffer): """Dumps table to ASCII format.""" if self.domain is not None: header = self.name + '\n-- ' + '\t'.join(self.domain) + '\t' + '\t'.join(list(self.columns)) else: header = self.name + '\n-- ' + '\t'.join(list(self.columns)) footer = '/\n' round_decimals = 6 if self.domain is not None: if len(self.domain) > 1: outer_idx = None idx_values = [] row_ends = [] for idx in self.index.values: idx = [str(round(i, round_decimals)) for i in idx] if idx[0] == outer_idx: idx[0] = '\t' row_ends.append(0) else: outer_idx = idx[0] row_ends.append(1) idx_values.append(idx) idx_values = np.array(idx_values) row_ends = np.array(row_ends + [1])[1:].astype(bool) else: idx_values = self.index.values.reshape(-1, 1) row_ends = np.zeros(self.shape[0]).astype(bool) row_ends[-1] = 1 x = np.hstack([idx_values, np.round(self.values, round_decimals)]).astype(str) else: row_ends = np.zeros(self.shape[0]).astype(bool) row_ends[-1] = 1 x = np.round(self.values, round_decimals).astype(str) for i in range(x.shape[0]): if row_ends[i]: x[i, -1] += '\t/' np.savetxt(path_or_buffer, x, header=header, footer=footer, delimiter='\t', comments='', fmt='%.18s') return self def to_numpy(self, include_index=False): """ Get numpy representation of a table. """ if include_index: if isinstance(self.index, pd.MultiIndex): index = np.array(self.index.values.tolist()) else: index = self.index.values.reshape(-1, 1) return np.hstack((index, self.values)) return self.values