# -*- 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
from functools import partial
import numpy as np
from gstools.covmodel.base import CovModel
from gstools.field.generator import RandMeth
from gstools.field.tools import (
reshape_input,
check_mesh,
make_isotropic,
unrotate_mesh,
reshape_axis_from_struct_to_unstruct,
reshape_field_from_unstruct_to_struct,
)
from gstools.tools.geometric import pos2xyz
from gstools.field.upscaling import var_coarse_graining, var_no_scaling
__all__ = ["SRF"]
GENERATOR = {"RandMeth": RandMeth}
UPSCALING = {
"coarse_graining": var_coarse_graining,
"no_scaling": var_no_scaling,
}
[docs]class SRF(object):
"""A class to generate spatial random fields (SRF).
Parameters
----------
model : :any:`CovModel`
Covariance Model to use for the field.
mean : :class:`float`, optional
mean value of the SRF
var_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 generator to use for field generation.
At the moment, the following generators are provided:
* "RandMeth" : The Randomization Methode.
See: :any:`RandMeth`
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
):
# initialize private attributes
self._model = None
self._generator = None
self._upscaling = None
self._upscaling_func = None
# initialize attributes
self.field = None
self.mean = mean
self.model = model
self.set_generator(generator, **generator_kwargs)
self.upscaling = upscaling
[docs] def __call__(
self,
pos,
seed=np.nan,
force_moments=False,
point_volumes=0.0,
mesh_type="unstructured",
):
"""Generate the spatial random field.
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
force_moments : :class:`bool`
Force the generator to exactly match mean and variance.
Default: ``False``
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 volumes 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)
# 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.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)
x, y, z = reshape_input(x, y, z, mesh_type)
# generate the field
field = self.generator.__call__(x, y, z)
# reshape field if we got an unstructured mesh
if mesh_type_changed:
mesh_type = mesh_type_old
field = reshape_field_from_unstruct_to_struct(
self.model.dim, field, axis_lens
)
# force variance and mean to be exactly as given (if wanted)
if force_moments:
var_in = np.var(field)
mean_in = np.mean(field)
rescale = np.sqrt(self.model.sill / var_in)
field = rescale * (field - mean_in)
# upscaled variance
scaled_var = self.upscaling_func(self.model, point_volumes)
# rescale and shift the field to the mean
self.field = np.sqrt(scaled_var / self.model.sill) * field + self.mean
return self.field
[docs] def structured(self, *args, **kwargs):
"""Generate an SRF on a structured mesh
See :any:`SRF.__call__`
"""
call = partial(self.__call__, mesh_type="structured")
return call(*args, **kwargs)
[docs] def unstructured(self, *args, **kwargs):
"""Generate an SRF on an unstructured mesh
See :any:`SRF.__call__`
"""
call = partial(self.__call__, mesh_type="unstructured")
return call(*args, **kwargs)
[docs] def upscaling_func(self, *args, **kwargs):
"""The 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)
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 for the variance at each
point depending on the related element volume.
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
)
@property
def model(self):
""":any:`CovModel`: The covariance model of the spatial random field.
"""
return self._model
@model.setter
def model(self, model):
if isinstance(model, CovModel):
self._model = model
else:
raise ValueError(
"gstools.SRF: 'model' is not an instance of 'gstools.CovModel'"
)
@property
def do_rotation(self):
""":any:`bool`: State if a rotation should be performed
depending on the model.
"""
return not np.all(np.isclose(self.model.angles, 0.0))
def __str__(self):
return self.__repr__()
def __repr__(self):
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()