# -*- coding: utf-8 -*-
"""
GStools subpackage providing a class for standard spatial random fields.
.. currentmodule:: gstools.field.srf
The following classes are provided
.. autosummary::
SRF
"""
# pylint: disable=C0103
from __future__ import division, absolute_import, print_function
import numpy as np
from gstools.field.generator import RandMeth, IncomprRandMeth
from gstools.field.tools import (
check_mesh,
make_isotropic,
unrotate_mesh,
reshape_axis_from_struct_to_unstruct,
reshape_field_from_unstruct_to_struct,
)
from gstools.field.base import Field
from gstools.tools.geometric import pos2xyz, xyz2pos
from gstools.field.upscaling import var_coarse_graining, var_no_scaling
from gstools.field.condition import ordinary, simple
from gstools.krige.tools import set_condition
__all__ = ["SRF"]
GENERATOR = {
"RandMeth": RandMeth,
"IncomprRandMeth": IncomprRandMeth,
"VectorField": IncomprRandMeth,
"VelocityField": IncomprRandMeth,
}
UPSCALING = {
"coarse_graining": var_coarse_graining,
"no_scaling": var_no_scaling,
}
CONDITION = {"ordinary": ordinary, "simple": simple}
[docs]class SRF(Field):
"""A class to generate spatial random fields (SRF).
Parameters
----------
model : :any:`CovModel`
Covariance Model of the spatial random field.
mean : :class:`float`, optional
mean value of the SRF
upscaling : :class:`str`, optional
Method to be used for upscaling the variance at each point
depending on the related element volume.
See the ``point_volumes`` keyword in the :any:`SRF.__call__` routine.
At the moment, the following upscaling methods are provided:
* "no_scaling" : No upscaling is applied to the variance.
See: :any:`var_no_scaling`
* "coarse_graining" : A volume depended variance is
calculated by the upscaling technique coarse graining.
See: :any:`var_coarse_graining`
Default: "no_scaling"
generator : :class:`str`, optional
Name of the field generator to be used.
At the moment, the following generators are provided:
* "RandMeth" : The Randomization Method.
See: :any:`RandMeth`
* "IncomprRandMeth" : The incompressible Randomization Method.
This is the original algorithm proposed by Kraichnan 1970
See: :any:`IncomprRandMeth`
* "VectorField" : an alias for "IncomprRandMeth"
* "VelocityField" : an alias for "IncomprRandMeth"
Default: "RandMeth"
**generator_kwargs
Keyword arguments that are forwarded to the generator in use.
Have a look at the provided generators for further information.
"""
def __init__(
self,
model,
mean=0.0,
upscaling="no_scaling",
generator="RandMeth",
**generator_kwargs
):
super(SRF, self).__init__(model, mean)
# initialize private attributes
self._generator = None
self._upscaling = None
self._upscaling_func = None
# condition related
self._cond_pos = None
self._cond_val = None
self._krige_type = None
# initialize attributes
self.raw_field = None
self.krige_field = None
self.err_field = None
self.krige_var = None
self.set_generator(generator, **generator_kwargs)
self.upscaling = upscaling
if self._value_type is None:
raise ValueError(
"Unknown field value type, "
+ "specify 'scalar' or 'vector' before calling SRF."
)
[docs] def __call__(
self, pos, seed=np.nan, point_volumes=0.0, mesh_type="unstructured"
):
"""Generate the spatial random field.
The field is saved as `self.field` and is also returned.
Parameters
----------
pos : :class:`list`
the position tuple, containing main direction and transversal
directions
seed : :class:`int`, optional
seed for RNG for reseting. Default: keep seed from generator
point_volumes : :class:`float` or :class:`numpy.ndarray`
If your evaluation points for the field are coming from a mesh,
they are probably representing a certain element volume.
This volume can be passed by `point_volumes` to apply the
given variance upscaling. If `point_volumes` is ``0`` nothing
is changed. Default: ``0``
mesh_type : :class:`str`
'structured' / 'unstructured'
Returns
-------
field : :class:`numpy.ndarray`
the SRF
"""
# internal conversation
x, y, z = pos2xyz(pos, max_dim=self.model.dim)
self.pos = xyz2pos(x, y, z)
self.mesh_type = mesh_type
# update the model/seed in the generator if any changes were made
self.generator.update(self.model, seed)
# format the positional arguments of the mesh
check_mesh(self.model.dim, x, y, z, mesh_type)
mesh_type_changed = False
if self.model.do_rotation:
if mesh_type == "structured":
mesh_type_changed = True
mesh_type_old = mesh_type
mesh_type = "unstructured"
x, y, z, axis_lens = reshape_axis_from_struct_to_unstruct(
self.model.dim, x, y, z
)
x, y, z = unrotate_mesh(self.model.dim, self.model.angles, x, y, z)
y, z = make_isotropic(self.model.dim, self.model.anis, y, z)
# generate the field
self.raw_field = self.generator.__call__(x, y, z, mesh_type)
# reshape field if we got an unstructured mesh
if mesh_type_changed:
mesh_type = mesh_type_old
self.raw_field = reshape_field_from_unstruct_to_struct(
self.model.dim, self.raw_field, axis_lens
)
# apply given conditions to the field
if self.condition:
(
cond_field,
krige_field,
err_field,
krigevar,
info,
) = self.cond_func(self)
# store everything in the class
self.field = cond_field
self.krige_field = krige_field
self.err_field = err_field
self.krige_var = krigevar
if "mean" in info: # ordinary krging estimates mean
self.mean = info["mean"]
else:
self.field = self.raw_field + self.mean
# upscaled variance
if not np.isscalar(point_volumes) or not np.isclose(point_volumes, 0):
scaled_var = self.upscaling_func(self.model, point_volumes)
self.field -= self.mean
self.field *= np.sqrt(scaled_var / self.model.sill)
self.field += self.mean
return self.field
[docs] def set_condition(
self, cond_pos=None, cond_val=None, krige_type="ordinary"
):
"""Condition a given spatial random field with measurements.
Parameters
----------
cond_pos : :class:`list`
the position tuple of the conditions
cond_val : :class:`numpy.ndarray`
the values of the conditions
krige_type : :class:`str`, optional
Used kriging type for conditioning.
Either 'ordinary' or 'simple'.
Default: 'ordinary'
Notes
-----
When using "ordinary" as ``krige_type``, the ``mean`` attribute of the
spatial random field will be overwritten with the estimated mean.
"""
if cond_pos is not None:
self._cond_pos, self._cond_val = set_condition(
cond_pos, cond_val, self.model.dim
)
else:
self._cond_pos = self._cond_val = None
self._krige_type = krige_type
if krige_type not in CONDITION:
raise ValueError(
"gstools.SRF: Unknown kriging method: " + krige_type
)
[docs] def del_condition(self):
"""Delete Conditions."""
self._cond_pos = None
self._cond_val = None
self._krige_type = None
@property
def cond_pos(self):
""":class:`list`: The position tuple of the conditions."""
return self._cond_pos
@property
def cond_val(self):
""":class:`list`: The values of the conditions."""
return self._cond_val
@property
def condition(self):
""":any:`bool`: State if conditions ar given."""
return self._cond_pos is not None
[docs] def cond_func(self, *args, **kwargs):
"""Conditioning method applied to the field."""
if self.condition:
return CONDITION[self._krige_type](*args, **kwargs)
return None
[docs] def upscaling_func(self, *args, **kwargs):
"""Upscaling method applied to the field variance."""
return self._upscaling_func(*args, **kwargs)
[docs] def set_generator(self, generator, **generator_kwargs):
"""Set the generator for the field.
Parameters
----------
generator : :class:`str`, optional
Name of the generator to use for field generation.
Default: "RandMeth"
**generator_kwargs
keyword arguments that are forwarded to the generator in use.
"""
if generator in GENERATOR:
gen = GENERATOR[generator]
self._generator = gen(self.model, **generator_kwargs)
self._value_type = self._generator.value_type
else:
raise ValueError("gstools.SRF: Unknown generator: " + generator)
@property
def generator(self):
""":any:`callable`: The generator of the field.
Default: :any:`RandMeth`
"""
return self._generator
@property
def upscaling(self): # pragma: no cover
""":class:`str`: Name of the upscaling method.
See the ``point_volumes`` keyword in the :any:`SRF.__call__` routine.
Default: "no_scaling"
"""
return self._upscaling
@upscaling.setter
def upscaling(self, upscaling):
if upscaling in UPSCALING:
self._upscaling = upscaling
self._upscaling_func = UPSCALING[upscaling]
else:
raise ValueError(
"gstools.SRF: Unknown upscaling method: " + upscaling
)
def __repr__(self):
"""Return String representation."""
return "SRF(model={0}, mean={1}, generator={2}".format(
self.model, self.mean, self.generator
)
if __name__ == "__main__": # pragma: no cover
import doctest
doctest.testmod()