"""XTGeo blockedwell module"""
from __future__ import annotations
from typing import Any
import pandas as pd
from xtgeo.common._xyz_enum import _AttrName
from xtgeo.common.log import null_logger
from . import _blockedwell_roxapi
from .well1 import Well
logger = null_logger(__name__)
# =============================================================================
# METHODS as wrappers to class init + import
[docs]
def blockedwell_from_file(
bwfile,
fformat=None,
mdlogname=None,
zonelogname=None,
strict=False,
lognames="all",
lognames_strict=False,
):
"""Make an instance of a BlockedWell directly from file import.
Args:
bwfile (str): Name of file
fformat (str): File format: rms_ascii, csv, hdf5.
If None (default), auto-detect from file extension or file signature.
mdlogname (str): See :meth:`Well.from_file`
zonelogname (str): See :meth:`Well.from_file`
strict (bool): See :meth:`Well.from_file`
lognames: Name or list of lognames to import, default is "all"
lognames_strict: If True, require all logs in lognames to be present
Example::
>>> import xtgeo
>>> well3 = xtgeo.blockedwell_from_file(well_dir + '/OP_1.bw')
"""
return BlockedWell._read_file(
bwfile,
fformat=fformat,
mdlogname=mdlogname,
zonelogname=zonelogname,
strict=strict,
lognames=lognames,
lognames_strict=lognames_strict,
)
[docs]
def blockedwell_from_roxar(
project, gname, bwname, wname, lognames=None, ijk=True, realisation=0
):
"""This makes an instance of a BlockedWell directly from Roxar RMS.
For arguments, see :meth:`BlockedWell.from_roxar`.
Example::
# inside RMS:
import xtgeo
mylogs = ['ZONELOG', 'GR', 'Facies']
mybw = xtgeo.blockedwell_from_roxar(project, 'Simgrid', 'BW', '31_3-1',
lognames=mylogs)
"""
# TODO: replace this with proper class method
obj = BlockedWell(
*([0.0] * 3),
"",
pd.DataFrame(
{
_AttrName.XNAME.value: [],
_AttrName.YNAME.value: [],
_AttrName.ZNAME.value: [],
}
),
)
_blockedwell_roxapi.import_bwell_roxapi(
obj,
project,
gname,
bwname,
wname,
lognames=lognames,
ijk=ijk,
realisation=realisation,
)
obj._ensure_consistency()
return obj
# =============================================================================
# CLASS
[docs]
class BlockedWell(Well):
"""Class for a blocked well in the XTGeo framework, subclassed from the
Well class.
Similar to Wells, the blocked well logs are stored as Pandas dataframe,
which make manipulation easy and fast.
For blocked well logs, the numbers of rows cannot be changed if you want to
save the result in RMS, as this is derived from the grid. Also the blocked well
icon must exist before save.
The well trajectory are here represented as logs, and XYZ have magic names as
default: X_UTME, Y_UTMN, Z_TVDSS, which are the three first Pandas columns.
Other geometry logs has also 'semi-magic' names:
M_MDEPTH or Q_MDEPTH: Measured depth, either real/true (M...) or
quasi computed/estimated (Q...). The Quasi computations may be incorrect for
all uses, but sufficient for some computations.
Similar for M_INCL, Q_INCL, M_AZI, Q_AZI.
I_INDEX, J_INDEX, K_INDEX: They are grid indices. For practical reasons
they are treated as a CONT logs, since the min/max grid indices usually are
unknown, and hence making a code index is not trivial.
All Pandas values (yes, discrete also!) are stored as float32 or float64
format, and undefined values are Nan. Integers are stored as Float due
to the lacking support for 'Integer Nan' (currently lacking in Pandas,
but may come in later Pandas versions).
Note there is a method that can return a dataframe (copy) with Integer
and Float columns, see :meth:`get_filled_dataframe`.
The instance can be made either from file or::
>>> well1 = xtgeo.blockedwell_from_file(well_dir + '/OP_1.bw') # RMS ascii well
If in RMS, instance can be made also from RMS icon::
well4 = xtgeo.blockedwell_from_roxar(
project,
'gridname',
'bwname',
'wellname',
)
"""
[docs]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._gridname = None
@classmethod
def _read_file(
cls,
wfile,
fformat=None,
**kwargs,
):
"""Import blocked well from file (internal method).
Uses BlockedWellData instead of WellData for proper grid index handling.
Args:
wfile: Name of file as string or pathlib.Path
fformat: File format. If None, auto-detect.
**kwargs: Additional arguments passed to import functions
Returns:
BlockedWell instance
"""
from xtgeo.io._file import FileWrapper
from . import _blockedwell_io_factory
wfile = FileWrapper(wfile)
fmt = wfile.fileformat(fformat)
kwargs = _blockedwell_io_factory._blocked_data_reader_factory(fmt)(
wfile, **kwargs
)
return cls(**kwargs)
[docs]
def to_file(
self,
bwfile,
fformat=None,
compression: str | None = "lzf",
):
"""Export BlockedWell to file.
Args:
bwfile: File name or pathlib.Path or stream
fformat: File format ('rms_ascii'/'rmswell', 'csv', 'hdf'/'hdf5'/'h5').
If None (default), uses 'rms_ascii'.
compression: Compression for HDF5 format only. Default is 'lzf'.
Returns:
Path to file (or file object if stream was provided)
Example::
>>> mybw = xtgeo.blockedwell_from_file(well_dir + '/OP_1.bw')
>>> mybw.to_file(outdir + '/OP_1_copy.bw')
.. versionchanged:: 4.0 Added explicit to_file method for BlockedWell
"""
from xtgeo.common.exceptions import InvalidFileFormatError
from xtgeo.io._file import FileFormat, FileWrapper
from xtgeo.io._welldata import WellFileFormat
from . import _blockedwell_io_factory
bwfile = FileWrapper(bwfile, mode="wb", obj=self)
bwfile.check_folder(raiseerror=OSError)
self._ensure_consistency()
if fformat is None or fformat in (
"rms_ascii",
"rms_asc",
"rmsasc",
"rmswell",
):
blockedwelldata = _blockedwell_io_factory._blockedwell_to_blockedwelldata(
self
)
blockedwelldata.to_file(bwfile.name, fformat=WellFileFormat.RMS_ASCII)
elif fformat in FileFormat.CSV.value:
blockedwelldata = _blockedwell_io_factory._blockedwell_to_blockedwelldata(
self
)
blockedwelldata.to_file(bwfile.name, fformat=WellFileFormat.CSV)
elif fformat in FileFormat.HDF.value:
blockedwelldata = _blockedwell_io_factory._blockedwell_to_blockedwelldata(
self
)
blockedwelldata.to_file(
bwfile.name, fformat=WellFileFormat.HDF5, compression=compression
)
else:
extensions = FileFormat.extensions_string(
[FileFormat.RMSWELL, FileFormat.CSV, FileFormat.HDF]
)
raise InvalidFileFormatError(
f"File format {fformat} is invalid for BlockedWell. "
f"Supported formats are {extensions}."
)
return bwfile.file
@property
def gridname(self):
"""Returns or set (rename) the grid name that the blocked wells
belongs to."""
return self._gridname
@gridname.setter
def gridname(self, newname):
if isinstance(newname, str):
self._gridname = newname
else:
raise ValueError("Input name is not a string.")
[docs]
def copy(self):
newbw = super().copy()
newbw._gridname = self._gridname
return newbw
[docs]
def to_roxar(
self,
project: Any,
gridname: str,
bwname: str,
wname: str,
lognames: str | list[str] = "all",
realisation: int = 0,
ijk: bool = False,
) -> None:
"""Set (export) a single blocked well item inside roxar project.
Note this method works only when inside RMS, or when RMS license is
activated. RMS will store blocked wells as a Gridmodel feature, not as a
well.
Note:
When project is file path (direct access, outside RMS) then
``to_roxar()`` will implicitly do a project save. Otherwise, the project
will not be saved until the user do an explicit project save action.
Args:
project: Magic object 'project' or file path to project
gridname: Name of GridModel icon in RMS
bwname: Name of Blocked Well icon in RMS, usually 'BW'
wname: Name of well, as shown in RMS.
lognames: List of lognames to include, or use 'all' for
all current blocked logs for this well (except index logs). Default is
"all".
realisation: Realisation index (0 is default)
ijk: If True, then also write special index logs if they exist,
such as I_INDEX, J_INDEX, K_INDEX, etc. Default is False
.. versionadded: 2.12
"""
_blockedwell_roxapi.export_bwell_roxapi(
self,
project,
gridname,
bwname,
wname,
lognames=lognames,
ijk=ijk,
realisation=realisation,
)