diff --git a/CHANGELOG.md b/CHANGELOG.md index 51fa960f..4b5b7f6c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to **GSTools** will be documented in this file. +## [1.3.1] - Pure Pink - 2021-06 + +### Enhancements +- Standalone use of Field class [#166](https://github.com/GeoStat-Framework/GSTools/issues/166) +- add social badges in README [#169](https://github.com/GeoStat-Framework/GSTools/issues/169), [#170](https://github.com/GeoStat-Framework/GSTools/issues/170) + +### Bugfixes +- use `oldest-supported-numpy` to build cython extensions [#165](https://github.com/GeoStat-Framework/GSTools/pull/165) + + ## [1.3.0] - Pure Pink - 2021-04 ### Topics @@ -263,7 +273,8 @@ All notable changes to **GSTools** will be documented in this file. First release of GSTools. -[Unreleased]: https://github.com/GeoStat-Framework/gstools/compare/v1.3.0...HEAD +[Unreleased]: https://github.com/GeoStat-Framework/gstools/compare/v1.3.1...HEAD +[1.3.1]: https://github.com/GeoStat-Framework/gstools/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/GeoStat-Framework/gstools/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/GeoStat-Framework/gstools/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/GeoStat-Framework/gstools/compare/v1.1.1...v1.2.0 diff --git a/README.md b/README.md index 010ae06c..42ff648e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,13 @@ GSTools-LOGO

+

Get in Touch!

+

+GH-Discussions +Slack-Swung +Gitter-GSTools +Email +

## Purpose @@ -22,7 +29,7 @@ GeoStatTools provides geostatistical tools for various purposes: - simple, ordinary, universal and external drift kriging - conditioned field generation - incompressible random vector field generation -- (automatted) variogram estimation and fitting +- (automated) variogram estimation and fitting - directional variogram estimation and modelling - data normalization and transformation - many readily provided and even user-defined covariance models diff --git a/docs/source/index.rst b/docs/source/index.rst index bb06049a..90d7b28c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,13 +6,22 @@ GSTools Quickstart :width: 150px :align: center + +**Get in Touch!** + +|GH-Discussions| |Slack-Swung| |Gitter-GSTools| |Email| + + +Purpose +======= + GeoStatTools provides geostatistical tools for various purposes: - random field generation - simple, ordinary, universal and external drift kriging - conditioned field generation - incompressible random vector field generation -- (automatted) variogram estimation and fitting +- (automated) variogram estimation and fitting - directional variogram estimation and modelling - data normalization and transformation - many readily provided and even user-defined covariance models @@ -379,7 +388,27 @@ Optional - `pyvista `_ +Contact +------- + +You can contact us via `info@geostat-framework.org `_. + + License ======= `LGPLv3 `_ + + +.. |GH-Discussions| image:: https://img.shields.io/badge/GitHub-Discussions-f6f8fa?logo=github&style=flat + :alt: GH-Discussions + :target: https://github.com/GeoStat-Framework/GSTools/discussions +.. |Slack-Swung| image:: https://img.shields.io/badge/Swung-Slack-4A154B?style=flat&logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABmJLR0QA%2FwD%2FAP%2BgvaeTAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAB3RJTUUH5AYaFSENGSa5qgAABmZJREFUSMeFlltsVNcVhr%2B1z5m7Zzy%2BxaBwcQrGQOpgCAkKtSBQIqJepKhPBULpQ6sKBVWVKqXtSy%2BR0qYXqa2qRmlDCzjBEZGKUCK1TWqlNiGIEKDQBtf4Fki4OIxnxrex53LOXn2YwbjEtOvlHG3tvX%2Btf%2B21%2Fl%2BYJ1QVEbn1vwLYBWwCVgG1lW0ZoA%2FoAQ6LSP%2BdZ%2BeGzAMiIqK%2Bem0GpxNYVeBj3j2b4NCfM2QnfAAaa11al4fZuCZK24owQJ9v%2BbLryIVbd9wVSNUaEWNVtQPYfXHmAD0T32ZJeBM1Q8d0zzMDUpMwAFgLJU%2BxClURw9NfqedLWxMAHSKyR1WNiNhPAM0B6c%2FbdPORTLuOeUMSNkmMBHgyeo32bwwRDMh8bDM%2BZVl0j6uvPrdYknFnSESWzwUzt%2BkyVlUHx7zh5j%2BmPkXBjosjLkWdominiMQ%2BoiEZxuq8OFRXGXJ5K5%2Fde5nha8VlqjooIlZVBcBUiqeqemjGppd1ptfhSpS8pmmN7GVf4whPNY4Di9m%2BMcR03nK3sBbCQeFbv7gBsExVOyp3l6nz1VtjcM4fTK3Uok5IXtPsrHuPevcBXk8d4dWPX6I%2BsIB9wf1s%2B2Y%2FVbFynUIBIeDeplIECiXl5Iv3kbLogogRgbWukfNumT%2FnlYszBxj3hwXg0cQvqXcfYNu5tVyYPE%2B1G8dXn%2BfW72fH49U8sSlOPGr4SccoF4cKs3WzFrY%2BFCMUNmz%2Ba0aeWR1l15JwJ7DaVPpk1YnJ7xIxtQRNjDXRvTx%2F9ef0Tl0g6SYQhAlvmkH%2Fgv74qUaiTSG8ewJ0%2FGgRK5aG8Cts5ouWDa1RxoDRovK9i9MAq1S12QA7b5ROUdBxBIeQ1ACG49m%2FEXPis7Qk3ChHbx6Qw1dgXVeWB7uyDOctP%2Fx6w2zdrIVIyFCyiq8wXlJOZzyAXQbY%2FGGhC8EAilJ%2BVg7ufxU6IAHeSvewfQEadiDuCr%2B6NE1LU4hwUFAF1xFGRkvEjVDlgiPwVqoEsNkAq0ZKp3EIYrFM2xGm7Uc8u%2FzXjHkTmHIHoCiDM73E3IIsDCtRV3gn7QHQ0hTCt0ooKLw%2FWCAM1AcNISOcHSsBrDRAbc7eQMQBFFciHM18kaZIMz3r%2F0HO5mazytsiw%2FmTtCYiGGCkQlltwkEVjMDVmyUA6oIGR%2BDGjAWoM3f2giHAhH%2BFI5nPsDrWxqWNE9S4tUz5k1S7cQ5df4k9S6qY9JRipXtr4w5WQYH0eHkWrqxy8FTn3AvpmFmIqj%2B76EiQjNfHH1JNWFKc3vABj9V9npw%2FRXfmBNsaoTRnRAQDAgqqMJr1KBWUtUmHaR8WRgzAqAH6FgYexqd4R2Yuns5wcLSFK4U36bj%2FdbbUbGdoZoCi3uS%2Bqtt73TlNWygpqXGfZTGXnKesrwkA9BmgZ0noMZT5R0tQ4hzLfo4rhS46W%2F%2BCAn3T7%2BhDySiWMl2RkHArP8dAesKjPixYVbbUBwB6DHB4QWADIamuHPtkhE0t3ZP7ANhe9zgvXP2dfK0pymRJmQLiEYNW6mEVljYGuDzlkwwaHq51AQ4bERkAetvjP2XCT6H480AJeZsB4N7QYt7OnuSROtRXJV2wNNS4qIJvlbUtERJxhxcv5%2FlNWwygV0QGyzKBv%2FP%2ByFfZXf%2ButoR3UuXcS95mKNgxSjpN3qZZFHwUgFPjx5n2c9wo9ktrtcOZtMeWB2NEw4b2thivPLuIS1M%2BAzmrTy4O4ys7Zv1B5fsnVdWCr7PxYf7vej73ex2YeU1VVY9nu7ShG63vRo%2Fe%2FK1%2B518FbXkjo3OjO1XU2LFRzRZ9VdWDczFQ1VsCOHgpd1G%2FcG6jHrj2vPbn%2BjVdHNfr%2BRH92eXva2MPuvxEQpe%2BHdEnzm%2FQf4%2BrRo%2BldMUbGd393oS2dWU0cDSlw1OequrALVG9Q8rLsquqg2OlzLL2Myu1N5eShgB4CjEnSMSJYrX8Oj0t8UH7NMnX0iSDwmhBWRl3tKs9IcmgGRSRZqtqzFwpL4uWWKvWiMjyZKC24%2F1HbsrLn95Pwk3gCpS0yIw%2Fg6clPC2RLc3QmzvJupoARQsvrItxZmtSkkFz6E6Q%2F2m3PFta44jbCaw%2BO3GK7uybnJs8xfXC1fLYCdTz9NIfsCS0mYVhAHp9ZYdr5J%2F%2F127dxUA2AzuBzRUDWVfZlq4YyG6gs9ImdzWQ%2FwFNRlgCFdG5bAAAAABJRU5ErkJggg%3D%3D + :alt: Slack-Swung + :target: https://swung.slack.com/messages/gstools +.. |Gitter-GSTools| image:: https://img.shields.io/badge/Gitter-GeoStat--Framework-ed1965?logo=gitter&style=flat + :alt: Gitter-GSTools + :target: https://gitter.im/GeoStat-Framework/GSTools +.. |Email| image:: https://img.shields.io/badge/Email-GeoStat--Framework-468a88?style=flat&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSI1MDAiIGhlaWdodD0iNTAwIj48cGF0aCBkPSJNNDQ4IDg4SDUyYy0yNyAwLTQ5IDIyLTQ5IDQ5djIyNmMwIDI3IDIyIDQ5IDQ5IDQ5aDM5NmMyNyAwIDQ5LTIyIDQ5LTQ5VjEzN2MwLTI3LTIyLTQ5LTQ5LTQ5em0xNiA0OXYyMjZsLTIgNy0xMTUtMTE2IDExNy0xMTd6TTM2IDM2M1YxMzdsMTE3IDExN0wzOCAzNzBsLTItN3ptMjE5LTYzYy0zIDMtNyAzLTEwIDBMNjYgMTIxaDM2OHptLTc5LTIzIDQ2IDQ2YTM5IDM5IDAgMCAwIDU2IDBsNDYtNDYgMTAxIDEwMkg3NXoiIHN0eWxlPSJmaWxsOiNmNWY1ZjU7ZmlsbC1vcGFjaXR5OjEiLz48L3N2Zz4= + :alt: Email + :target: mailto:info@geostat-framework.org diff --git a/examples/00_misc/05_standalone_field.py b/examples/00_misc/05_standalone_field.py new file mode 100644 index 00000000..3d25b26b --- /dev/null +++ b/examples/00_misc/05_standalone_field.py @@ -0,0 +1,28 @@ +""" +Standalone Field class +---------------------- + +The :any:`Field` class of GSTools can be used to plot arbitrary data in nD. + +In the following example we will produce 10000 random points in 4D with +random values and plot them. +""" +import numpy as np +import gstools as gs + +rng = np.random.RandomState(19970221) +x0 = rng.rand(10000) * 100.0 +x1 = rng.rand(10000) * 100.0 +x2 = rng.rand(10000) * 100.0 +x3 = rng.rand(10000) * 100.0 +values = rng.rand(10000) * 100.0 + +############################################################################### +# Only thing needed to instantiate the Field is the dimension. +# +# Afterwards we can call the instance like all other Fields +# (:any:`SRF`, :any:`Krige` or :any:`CondSRF`), but with an additional field. + +plotter = gs.field.Field(dim=4) +plotter(pos=(x0, x1, x2, x3), field=values) +plotter.plot() diff --git a/gstools/field/base.py b/gstools/field/base.py index 96abf392..7c83f370 100755 --- a/gstools/field/base.py +++ b/gstools/field/base.py @@ -41,7 +41,7 @@ class Field: Parameters ---------- - model : :any:`CovModel` + model : :any:`CovModel`, optional Covariance Model related to the field. value_type : :class:`str`, optional Value type of the field. Either "scalar" or "vector". @@ -56,15 +56,18 @@ class Field: Trend of the denormalized fields. If no normalizer is applied, this behaves equal to 'mean'. The default is None. + dim : :any:`None` or :class:`int`, optional + Dimension of the field if no model is given. """ def __init__( self, - model, + model=None, value_type="scalar", mean=None, normalizer=None, trend=None, + dim=None, ): # initialize attributes self.pos = None @@ -76,6 +79,7 @@ def __init__( self._mean = None self._normalizer = None self._trend = None + self._dim = dim if dim is None else int(dim) # set properties self.model = model self.value_type = value_type @@ -83,13 +87,40 @@ def __init__( self.normalizer = normalizer self.trend = trend - def __call__(self, *args, **kwargs): - """Generate the field.""" + def __call__( + self, pos, field=None, mesh_type="unstructured", post_process=True + ): + """Generate the field. + + Parameters + ---------- + pos : :class:`list` + the position tuple, containing main direction and transversal + directions + field : :class:`numpy.ndarray` or :any:`None`, optional + the field values. Will be all zeros if :any:`None` is given. + mesh_type : :class:`str`, optional + 'structured' / 'unstructured'. Default: 'unstructured' + post_process : :class:`bool`, optional + Whether to apply mean, normalizer and trend to the field. + Default: `True` + + Returns + ------- + field : :class:`numpy.ndarray` + the field values. + """ + pos, shape = self.pre_pos(pos, mesh_type) + if field is None: + field = np.zeros(shape, dtype=np.double) + else: + field = np.array(field, dtype=np.double).reshape(shape) + return self.post_field(field, process=post_process) def structured(self, *args, **kwargs): """Generate a field on a structured mesh. - See :any:`Field.__call__` + See :any:`__call__` """ call = partial(self.__call__, mesh_type="structured") return call(*args, **kwargs) @@ -97,7 +128,7 @@ def structured(self, *args, **kwargs): def unstructured(self, *args, **kwargs): """Generate a field on an unstructured mesh. - See :any:`Field.__call__` + See :any:`__call__` """ call = partial(self.__call__, mesh_type="unstructured") return call(*args, **kwargs) @@ -105,12 +136,12 @@ def unstructured(self, *args, **kwargs): def mesh( self, mesh, points="centroids", direction="all", name="field", **kwargs ): - """Generate a field on a given meshio or ogs5py mesh. + """Generate a field on a given meshio, ogs5py or PyVista mesh. Parameters ---------- mesh : meshio.Mesh or ogs5py.MSH or PyVista mesh - The given meshio, ogs5py, or PyVista mesh + The given mesh points : :class:`str`, optional The points to evaluate the field at. Either the "centroids" of the mesh cells @@ -128,17 +159,17 @@ def mesh( cell_data. If to few names are given, digits will be appended. Default: "field" **kwargs - Keyword arguments forwareded to `Field.__call__`. + Keyword arguments forwarded to :any:`__call__`. Notes ----- This will store the field in the given mesh under the given name, if a meshio or PyVista mesh was given. - See: https://github.com/nschloe/meshio - See: https://github.com/pyvista/pyvista - - See: :any:`Field.__call__` + See: + - meshio: https://github.com/nschloe/meshio + - ogs5py: https://github.com/GeoStat-Framework/ogs5py + - PyVista: https://github.com/pyvista/pyvista """ return generate_on_mesh(self, mesh, points, direction, name, **kwargs) @@ -176,9 +207,11 @@ def pre_pos(self, pos, mesh_type="unstructured"): # prepend dimension if we have a vector field if self.value_type == "vector": shape = (self.dim,) + shape - if self.model.latlon: + if self.latlon: raise ValueError("Field: Vector fields not allowed for latlon") # return isometrized pos tuple and resulting field shape + if self.model is None: + return pos, shape return self.model.isometrize(pos), shape def post_field(self, field, name="field", process=True, save=True): @@ -299,7 +332,7 @@ def plot( if self.value_type == "scalar": r = plot_field(self, field, fig, ax, **kwargs) elif self.value_type == "vector": - if self.model.dim == 2: + if self.dim == 2: r = plot_vec_field(self, field, fig, ax, **kwargs) else: raise NotImplementedError( @@ -317,12 +350,17 @@ def model(self): @model.setter def model(self, model): - if isinstance(model, CovModel): + if model is not None: + if not isinstance(model, CovModel): + raise ValueError( + "Field: 'model' is not an instance of 'gstools.CovModel'" + ) self._model = model + self._dim = None + elif self._dim is None: + raise ValueError("Field: either needs 'model' or 'dim'.") else: - raise ValueError( - "Field: 'model' is not an instance of 'gstools.CovModel'" - ) + self._model = None @property def mean(self): @@ -365,7 +403,12 @@ def value_type(self, value_type): @property def dim(self): """:class:`int`: Dimension of the field.""" - return self.model.field_dim + return self._dim if self.model is None else self.model.field_dim + + @property + def latlon(self): + """:class:`bool`: Whether the field depends on geographical coords.""" + return False if self.model is None else self.model.latlon @property def name(self): @@ -378,9 +421,13 @@ def _fmt_mean_norm_trend(self): def __repr__(self): """Return String representation.""" - return "{0}(model={1}, value_type='{2}'{3})".format( + if self.model is None: + dim_str = f"dim={self.dim}" + else: + dim_str = f"model={self.model.name}" + return "{0}({1}, value_type='{2}'{3})".format( self.name, - self.model.name, + dim_str, self.value_type, self._fmt_mean_norm_trend(), ) diff --git a/gstools/field/cond_srf.py b/gstools/field/cond_srf.py index 5e1eb85d..609045c8 100644 --- a/gstools/field/cond_srf.py +++ b/gstools/field/cond_srf.py @@ -9,7 +9,7 @@ .. autosummary:: CondSRF """ -# pylint: disable=C0103, W0231, W0221, E1102 +# pylint: disable=C0103, W0231, W0221, W0222, E1102 import numpy as np from gstools.field.generator import RandMeth from gstools.field.base import Field diff --git a/gstools/field/plot.py b/gstools/field/plot.py index 77e604ae..ca3a422b 100644 --- a/gstools/field/plot.py +++ b/gstools/field/plot.py @@ -56,7 +56,7 @@ def plot_field( if fld.dim == 1: return plot_1d(fld.pos, plt_fld, fig, ax, **kwargs) return plot_nd( - fld.pos, plt_fld, fld.mesh_type, fig, ax, fld.model.latlon, **kwargs + fld.pos, plt_fld, fld.mesh_type, fig, ax, fld.latlon, **kwargs ) diff --git a/pyproject.toml b/pyproject.toml index fd18dc4e..bba7e49b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "setuptools>=42", "wheel", "setuptools_scm[toml]>=3.5", - "numpy>=1.14.5,<2.0", + "oldest-supported-numpy", "Cython>=0.28.3,<3.0", ] build-backend = "setuptools.build_meta" diff --git a/tests/test_field.py b/tests/test_field.py new file mode 100644 index 00000000..53e7c336 --- /dev/null +++ b/tests/test_field.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This is the unittest of SRF class. +""" + +import unittest +import numpy as np +import gstools as gs + + +class TestField(unittest.TestCase): + def setUp(self): + self.cov_model = gs.Gaussian(dim=2, var=1.5, len_scale=4.0) + rng = np.random.RandomState(123018) + x = rng.uniform(0.0, 10, 100) + y = rng.uniform(0.0, 10, 100) + self.field = rng.uniform(0.0, 10, 100) + self.pos = np.array([x, y]) + + def test_standalone(self): + fld = gs.field.Field(dim=2) + fld_cov = gs.field.Field(model=self.cov_model) + field1 = fld(self.pos, self.field) + field2 = fld_cov(self.pos, self.field) + self.assertTrue(np.all(np.isclose(field1, field2))) + self.assertTrue(np.all(np.isclose(field1, self.field))) + + def test_raise(self): + # vector field on latlon + fld = gs.field.Field(gs.Gaussian(latlon=True), value_type="vector") + self.assertRaises(ValueError, fld, [1, 2], [1, 2]) + # no pos tuple present + fld = gs.field.Field(dim=2) + self.assertRaises(ValueError, fld.post_field, [1, 2]) + # wrong model type + with self.assertRaises(ValueError): + gs.field.Field(model=3.1415) + # no model and no dim given + with self.assertRaises(ValueError): + gs.field.Field() + # wrong value type + with self.assertRaises(ValueError): + gs.field.Field(dim=2, value_type="complex") + # wrong mean shape + with self.assertRaises(ValueError): + gs.field.Field(dim=3, mean=[1, 2]) + + +if __name__ == "__main__": + unittest.main()