"""
Geosoft vector arrays (vector of array elements)
:Classes:
=============== =========================
:class:`GXva` vector of array elements
=============== =========================
VA and VV classes are related based on a key called a *fiducial*,
which has a start value and increment between values. The :meth:`refid` method can be used to resample vector
data to the same fiducial so that vector-to-vector operations can be performed.
.. seealso:: :mod:`geosoft.gxpy.vv`, :mod:`geosoft.gxapi.GXVA`, :mod:`geosoft.gxapi.GXVV`
.. note::
Regression tests provide usage examples:
`va tests <https://github.com/GeosoftInc/gxpy/blob/master/geosoft/gxpy/tests/test_gxva.py>`_
"""
from collections.abc import Sequence
import numpy as np
import geosoft
import geosoft.gxapi as gxapi
from . import utility as gxu
__version__ = geosoft.__version__
def _t(s):
return geosoft.gxpy.system.translate(s)
[docs]class VAException(geosoft.GXRuntimeError):
"""
Exceptions from :mod:`geosoft.gxpy.va`.
.. versionadded:: 9.1
"""
pass
[docs]class GXva(Sequence):
"""
VA class wrapper.
:param array: 2D numpy array, None for an empty VA
:param dtype: numpy data type, default np.float
:param width: array width, default is determined from array.
:param fid: fid tuple (start,increment), default (0.0, 1.0)
:param unit_of_measure: the unit of measurement for the data
Maximum number of elements must be less that 2^31 - 1
.. versionchanged:: 9.3 added unit_of_measure
.. versionchanged:: 9.2 allow construction directly from numpy array
.. versionadded:: 9.1
"""
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.__del__()
def __del__(self):
if hasattr(self, '_gxva'):
self._gxva = None
def __len__(self):
return self._gxva.len()
[docs] def __init__(self, array=None, width=None, dtype=None, fid=(0.0, 1.0), unit_of_measure=''):
if array is not None:
if not isinstance(array, np.ndarray):
array = np.array(array)
if dtype is None:
dtype = array.dtype
if array.ndim != 2:
raise VAException(_t('array must have 2 dimensions'))
if width is None:
width = array.shape[1]
if width is None or (width < 2):
raise VAException('width must be >= 2')
self._gxtype = gxu.gx_dtype(dtype)
if self._gxtype < 0:
raise VAException(_t("VA of strings is not supported."))
self._dtype = gxu.dtype_gx(self._gxtype)
self._width = width
self._gxva = gxapi.GXVA.create_ext(self._gxtype, 0, self._width)
self.fid = fid
self._start, self._incr = self.fid
self._next = 0
self._unit_of_measure = unit_of_measure
if array is not None and array.size > 0:
self.set_data(array, fid)
def __iter__(self):
return self
def __next__(self):
if self._next >= self.length:
self._next = 0
self._start, self._incr = self.fid
raise StopIteration
else:
i = self._next
self._next += 1
return self.np[i], self._start + self._incr * i
def __getitem__(self, item):
self._start, self._incr = self.fid
return self.np[item], self._start + self._incr * item
@property
def unit_of_measure(self):
""" data unit of measurement"""
return self._unit_of_measure
@unit_of_measure.setter
def unit_of_measure(self, uom):
self._unit_of_measure = str(uom)
@property
def fid(self):
"""
fid tuple (start,increment), can be set
.. versionadded:: 9.1
"""
return self._gxva.get_fid_start(), self._gxva.get_fid_incr()
@fid.setter
def fid(self, fid):
self._gxva.set_fid_start(fid[0])
self._gxva.set_fid_incr(fid[1])
[docs] def refid(self, fid, length):
"""
Resample VA to a new fiducial and length
:param fid: (start,incr)
:param length: length
.. versionadded:: 9.1
"""
self._gxva.re_fid(fid[0], fid[1], length)
self.fid = fid
@property
def length(self):
"""
number of elements in the VA, can be set.
.. versionadded:: 9.1
.. versionchanged:: 9.3 can be set
"""
return self.__len__()
@length.setter
def length(self, length):
self.refid(self.fid, length)
@property
def width(self):
"""
width of each row(element) in the VA
.. versionadded:: 9.1
"""
return self._width
@property
def dimensions(self):
"""
VA dimensions (length, width)
.. versionadded:: 9.2
"""
return (self.length, self._width)
@property
def gxtype(self):
"""
GX data type
.. versionadded:: 9.1
"""
return self._gxtype
@property
def dtype(self):
"""
numpy data type
.. versionadded:: 9.1
"""
return self._dtype
@property
def np(self):
"""
Numpy array of VA data, in the data type of the VA. Use :meth:`get_data` to get a numpy array
in another `dtype`. Array will be 2-dimensional.
.. versionadded:: 9.2
"""
return self.get_data()[0]
@property
def gxva(self):
"""
The :class:`geosoft.gxapi.GXVA` instance handle.
..versionadded:: 9.3
"""
return self._gxva
[docs] def get_data(self, dtype=None, start=0, n=None, start_col=0, n_col=None):
"""
Return a numpy array of data from a va.
:param start: index of first value, must be >=0
:param n: number of values wanted
:param start_col: index of the first column wanted
:param n_col: number of columns
:param dtype: numpy data type wanted
.. versionadded:: 9.1
"""
if dtype is None:
dtype = self._dtype
else:
dtype = np.dtype(dtype)
if self.length == 0:
return np.array([[], []], dtype=dtype)
# strings not supported
if gxu.gx_dtype(dtype) < 0:
raise VAException(_t('VA string elements are not supported.'))
if n is None:
n = self.length - start
else:
n = min((self.length - start), n)
if (n <= 0) or (start < 0):
raise VAException(_t('Cannot get (start,n) ({},{}) from va of length {}').format(start, n, self.length))
if n_col is None:
n_col = self._width - start_col
else:
n_col = min((self._width - start_col), n_col)
if (n_col <= 0) or (start_col < 0):
raise VAException(_t('Cannot get columns (start,n) ({},{}) from VA of width {}').
format(start_col, n_col, self._width))
npd = self._gxva.get_array_np(start, start_col, n, n_col, dtype).reshape(-1, n_col)
# float dummies to nan
if npd.dtype == np.float32 or npd.dtype == np.float64:
npd[npd == gxu.gx_dummy(npd.dtype)] = np.nan
fid = self.fid
start = fid[0] + start * fid[1]
return npd, (start, fid[1])
[docs] def set_data(self, npdata, fid=(0.0, 1.0)):
"""
Copy numpy data into a VA.
:param npdata: numpy data array (must be 2D)
:param fid: fid tuple (start,increment), default (0.0,1.0)
Maximum number of elements must be less that 2^31 - 1
.. versionadded:: 9.1
"""
if npdata.size == 0:
self.length = 0
if fid:
self.fid = fid
return
try:
npd = npdata.reshape((-1, self._width))
except ValueError:
raise VAException(_t('Numpy data does not align with VA data width ({}).').format(self._width))
max_length = gxapi.iMAX // self._width
if npdata.shape[0] > max_length:
raise VAException(_t('Array length {} too long. Maximum is {} for width {}').format(npdata.shape[0], max_length, self._width))
if npdata.dtype == np.float32 or npdata.dtype == np.float64:
if np.isnan(npdata).any():
npdata = npdata.copy()
npdata[np.isnan(npdata)] = gxu.gx_dummy(npdata.dtype)
self._gxva.set_ln(npd.shape[0])
self._gxva.set_array_np(0, 0, npd)
self.fid = fid