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 @@
+Get in Touch!
+
+
+
+
+
+
## 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()