diff --git a/src/xtgeo/well/_welldata.py b/src/xtgeo/well/_welldata.py index 3709cdff6..972231893 100644 --- a/src/xtgeo/well/_welldata.py +++ b/src/xtgeo/well/_welldata.py @@ -20,9 +20,12 @@ similarly of a column is removed, the corresponding entries in wlogtypes and wlogrecords will be deleted. """ +from __future__ import annotations + import math from dataclasses import dataclass, field from enum import Enum, EnumMeta, unique +from typing import Any, Optional import numpy as np import pandas as pd @@ -31,8 +34,6 @@ from xtgeo import XTGeoCLibError # type: ignore[attr-defined] from xtgeo.cxtgeo import _cxtgeo -from . import _well_aux - class _LogTypeMeta(EnumMeta): """For enabling 'in' method, cf https://stackoverflow.com/questions/43634618""" @@ -69,20 +70,20 @@ class _WellData: The wlogstypes is on form {"PHIT": CONT, "FACIES": DISC, ...} - The wlogrecords is somewhat heterogenous, on form: + The wlogrecords is somewhat heterogeneous, on form: {"PHIT": ("unit", "scale"), "FACIES": {0:BG, 2: "SST", 4: "CALC"}} Hence the CONT logs hold a tuple or list with 2 str members, or None, while DISC log holds a dict where the key is an int and the value is a string. Note:: - Callers shall not use properties, but methods, e.g.:: + Callers shall not use properties, but methods, e.g.:: - instance.well = some_new_dataframe # not + instance.well = some_new_dataframe # not - but:: + but - instance.set_dataframe(some_new_dataframe) + instance.set_dataframe(some_new_dataframe) """ data: pd.DataFrame @@ -112,7 +113,12 @@ def _infer_log_dtypes(self): datatypes = {} for name, dtype in dlist.items(): if name in self.wlogtypes: - datatypes[name] = self.wlogtypes[name] # keep as is + datatypes[name] = self.wlogtypes[name] + if "DISC" in datatypes[name]: + datatypes[name] = _LogType.DISC.value + else: + datatypes[name] = _LogType.CONT.value + continue if name in (self.xname, self.yname, self.zname): @@ -205,6 +211,7 @@ def ensure_consistency(self): * When adding one or columns to the dataframe * When removing one or more columns from the dataframe + * ... """ if list(self.data.columns[:3]) != [self.xname, self.yname, self.zname]: @@ -213,6 +220,7 @@ def ensure_consistency(self): f"and '{self.zname}', got {list(self.data.columns[:3])}" ) + # order matters: self._ensure_consistency_wlogtypes() self._ensure_consistency_wlogrecords() self._ensure_consistency_df_dtypes() @@ -232,7 +240,7 @@ def set_wlogtype(self, name: str, wtype: str) -> None: apply_wtype = "DISC" if name not in self.wlogtypes: - raise ValueError(f"No such well log name present: {name}") + raise ValueError(f"No such log name present: {name}") if apply_wtype in _LogType: self.wlogtypes[name] = _LogType(apply_wtype) @@ -247,7 +255,7 @@ def set_wlogrecord(self, name: str, record: dict) -> None: """Set a wlogrecord for a named log.""" if name not in self.wlogtypes: - raise ValueError(f"No such well log name present: {name}") + raise ValueError(f"No such logname: {name}") if self.wlogtypes[name] == _LogType.CONT.value and isinstance( record, (list, tuple) @@ -265,11 +273,13 @@ def set_wlogrecord(self, name: str, record: dict) -> None: record, dict ): raise ValueError( - "Cannot set a log record for a discrete log: input record is " - "not a dictionary" + "Input is not a dictionary. Cannot set a log record for a discrete log" ) else: - raise ValueError("Something went wrong when setting logrecord.") + raise ValueError( + "Something went wrong when setting logrecord: " + f"({self.wlogtypes[name]} {type(record)})." + ) self.ensure_consistency() @@ -401,9 +411,9 @@ def geometrics(self): ) # extract numpies from XYZ trajetory logs - ptr_xv = _well_aux.get_carray(self, "X_UTME") - ptr_yv = _well_aux.get_carray(self, "Y_UTMN") - ptr_zv = _well_aux.get_carray(self, "Z_TVDSS") + ptr_xv = self._get_carray(self.xname) + ptr_yv = self._get_carray(self.yname) + ptr_zv = self._get_carray(self.zname) # get number of rows in pandas nlen = len(self.data.index) @@ -447,7 +457,7 @@ def _convert_np_carr_int(self, np_array): The numpy is always a double (float64), so need to convert first """ - carr = _cxtgeo.new_intarray(self.nrow) + carr = _cxtgeo.new_intarray(len(self.data.index)) np_array = np_array.astype(np.int32) @@ -457,7 +467,7 @@ def _convert_np_carr_int(self, np_array): def _convert_np_carr_double(self, np_array): """Convert numpy 1D array to C array, assuming double type.""" - carr = _cxtgeo.new_doublearray(self.nrow) + carr = _cxtgeo.new_doublearray(len(self.data.index)) _cxtgeo.swig_numpy_to_carr_1d(np_array, carr) @@ -466,8 +476,26 @@ def _convert_np_carr_double(self, np_array): def _convert_carr_double_np(self, carray, nlen=None): """Convert a C array to numpy, assuming double type.""" if nlen is None: - nlen = len(self._wdata.data.index) + nlen = len(self.data.index) nparray = _cxtgeo.swig_carr_to_numpy_1d(nlen, carray) return nparray + + def _get_carray(self, lname: str) -> Optional[Any]: + """Returns the C array pointer (via SWIG) for a given log. + + Type conversion is double if float64, int32 if DISC log. + Returns None if log does not exist. + """ + if lname in self.data: + np_array = self.data[lname].values + else: + return None + + if "DISC" in self.wlogtypes[lname]: + carr = self._convert_np_carr_int(np_array) + else: + carr = self._convert_np_carr_double(np_array) + + return carr diff --git a/src/xtgeo/well/well1.py b/src/xtgeo/well/well1.py index d4cf6cf8b..35e23d555 100644 --- a/src/xtgeo/well/well1.py +++ b/src/xtgeo/well/well1.py @@ -4,7 +4,6 @@ from __future__ import annotations import io -import math from copy import deepcopy from pathlib import Path from typing import Dict, List, Optional, Union