예제 #1
0
    def reset_seed(self, seed=np.nan):
        """
        Recalculate the random amplitudes and wave numbers with the given seed.

        Parameters
        ----------
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`

        Notes
        -----
        Even if the given seed is the present one, modes will be recalculated.
        """
        if seed is None or not np.isnan(seed):
            self._seed = seed
        self._rng = RNG(self._seed)
        # normal distributed samples for randmeth
        self._z_1 = self._rng.random.normal(size=self._mode_no)
        self._z_2 = self._rng.random.normal(size=self._mode_no)
        # sample uniform on a sphere
        sphere_coord = self._rng.sample_sphere(self.model.dim, self._mode_no)
        # sample radii acording to radial spectral density of the model
        if self.sampling == "inversion" or (self.sampling == "auto"
                                            and self.model.has_ppf):
            pdf, cdf, ppf = self.model.dist_func
            rad = self._rng.sample_dist(size=self._mode_no,
                                        pdf=pdf,
                                        cdf=cdf,
                                        ppf=ppf,
                                        a=0)
        else:
            rad = self._rng.sample_ln_pdf(
                ln_pdf=self.model.ln_spectral_rad_pdf,
                size=self._mode_no,
                sample_around=1.0 / self.model.len_rescaled,
            )
        # get fully spatial samples by multiplying sphere samples and radii
        self._cov_sample = rad * sphere_coord
예제 #2
0
    def test_rng_normal_consistency(self):
        rng = RNG(21021997)
        z1_refs = [-1.93013270, 0.46330478]
        z2_refs = [-0.25536086, 0.98298696]

        z1 = self.rng.random.normal(size=self.few_modes)
        z2 = self.rng.random.normal(size=self.few_modes)
        self.assertAlmostEqual(z1[0], z1_refs[0])
        self.assertAlmostEqual(z1[1], z1_refs[1])
        self.assertAlmostEqual(z2[0], z2_refs[0])
        self.assertAlmostEqual(z2[1], z2_refs[1])
        self.rng.seed = self.seed
        z1 = self.rng.random.normal(size=self.few_modes)
        z2 = self.rng.random.normal(size=self.few_modes)
        self.assertAlmostEqual(z1[0], z1_refs[0])
        self.assertAlmostEqual(z1[1], z1_refs[1])
        self.assertAlmostEqual(z2[0], z2_refs[0])
        self.assertAlmostEqual(z2[1], z2_refs[1])
예제 #3
0
class RandMeth(object):
    r"""Randomization method for calculating isotropic spatial random fields.

    Parameters
    ----------
    model : :any:`CovModel`
        Covariance model
    mode_no : :class:`int`, optional
        Number of Fourier modes. Default: ``1000``
    seed : :class:`int` or :any:`None`, optional
        The seed of the random number generator.
        If "None", a random seed is used. Default: :any:`None`
    verbose : :class:`bool`, optional
        Be chatty during the generation.
        Default: :any:`False`
    **kwargs
        Placeholder for keyword-args

    Notes
    -----
    The Randomization method is used to generate isotropic
    spatial random fields characterized by a given covariance model.
    The calculation looks like:

    .. math::
       u\left(x\right)=
       \sqrt{\frac{\sigma^{2}}{N}}\cdot
       \sum_{i=1}^{N}\left(
       Z_{1,i}\cdot\cos\left(\left\langle k_{i},x\right\rangle \right)+
       Z_{2,i}\cdot\sin\left(\left\langle k_{i},x\right\rangle \right)
       \right)

    where:

        * :math:`N` : fourier mode number
        * :math:`Z_{j,i}` : random samples from a normal distribution
        * :math:`k_i` : samples from the spectral density distribution of
          the covariance model
    """
    def __init__(self,
                 model,
                 mode_no=1000,
                 seed=None,
                 verbose=False,
                 **kwargs):
        if kwargs:
            print("gstools.RandMeth: **kwargs are ignored")
        # initialize atributes
        self._mode_no = int(mode_no)
        self._verbose = bool(verbose)
        # initialize private atributes
        self._model = None
        self._seed = None
        self._rng = None
        self._z_1 = None
        self._z_2 = None
        self._cov_sample = None
        self._value_type = "scalar"
        # set model and seed
        self.update(model, seed)

    def __call__(self, x, y=None, z=None, mesh_type="unstructured"):
        """Calculate the random modes for the randomization method.

        This method  calls the `summate_*` Cython methods, which are the
        heart of the randomization method.

        Parameters
        ----------
        x : :class:`float`, :class:`numpy.ndarray`
            The x components of the pos. tuple.
        y : :class:`float`, :class:`numpy.ndarray`, optional
            The y components of the pos. tuple.
        z : :class:`float`, :class:`numpy.ndarray`, optional
            The z components of the pos. tuple.
        mesh_type : :class:`str`, optional
            'structured' / 'unstructured'

        Returns
        -------
        :class:`numpy.ndarray`
            the random modes
        """
        if mesh_type == "unstructured":
            pos = _reshape_pos(x, y, z, dtype=np.double)

            summed_modes = summate_unstruct(self._cov_sample, self._z_1,
                                            self._z_2, pos)
        else:
            x, y, z = _set_dtype(x, y, z, dtype=np.double)
            summed_modes = summate_struct(self._cov_sample, self._z_1,
                                          self._z_2, x, y, z)

        nugget = self._set_nugget(summed_modes.shape)

        return np.sqrt(self.model.var / self._mode_no) * summed_modes + nugget

    def _set_nugget(self, shape):
        """
        Generate normal distributed values for the nugget simulation.

        Parameters
        ----------
        shape : :class:`tuple`
            the shape of the summed modes
        Returns
        -------
        nugget : :class:`numpy.ndarray`
            the nugget in the same shape as the summed modes
        """
        if self.model.nugget > 0:
            nugget = np.sqrt(
                self.model.nugget) * self._rng.random.normal(size=shape)
        else:
            nugget = 0.0
        return nugget

    def update(self, model=None, seed=np.nan):
        """Update the model and the seed.

        If model and seed are not different, nothing will be done.

        Parameters
        ----------
        model : :any:`CovModel` or :any:`None`, optional
            covariance model. Default: :any:`None`
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`
        """
        # check if a new model is given
        if isinstance(model, CovModel):
            if self.model != model:
                self._model = dcp(model)
                if seed is None or not np.isnan(seed):
                    self.reset_seed(seed)
                else:
                    self.reset_seed(self._seed)
            # just update the seed, if its a new one
            elif seed is None or not np.isnan(seed):
                self.seed = seed
        # or just update the seed, when no model is given
        elif model is None and (seed is None or not np.isnan(seed)):
            if isinstance(self._model, CovModel):
                self.seed = seed
            else:
                raise ValueError(
                    "gstools.field.generator.RandMeth: no 'model' given")
        # if the user tries to trick us, we beat him!
        elif model is None and np.isnan(seed):
            if (isinstance(self._model, CovModel) and self._z_1 is not None
                    and self._z_2 is not None
                    and self._cov_sample is not None):
                if self.verbose:
                    print("RandMeth.update: Nothing will be done...")
            else:
                raise ValueError("gstools.field.generator.RandMeth: " +
                                 "neither 'model' nor 'seed' given!")
        # wrong model type
        else:
            raise ValueError(
                "gstools.field.generator.RandMeth: 'model' is not an " +
                "instance of 'gstools.CovModel'")

    def reset_seed(self, seed=np.nan):
        """
        Recalculate the random amplitudes and wave numbers with the given seed.

        Parameters
        ----------
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`

        Notes
        -----
        Even if the given seed is the present one, modes will be recalculated.
        """
        if seed is None or not np.isnan(seed):
            self._seed = seed
        self._rng = RNG(self._seed)
        # normal distributed samples for randmeth
        self._z_1 = self._rng.random.normal(size=self._mode_no)
        self._z_2 = self._rng.random.normal(size=self._mode_no)
        # sample uniform on a sphere
        sphere_coord = self._rng.sample_sphere(self.model.dim, self._mode_no)
        # sample radii acording to radial spectral density of the model
        if self.model.has_ppf:
            pdf, cdf, ppf = self.model.dist_func
            rad = self._rng.sample_dist(size=self._mode_no,
                                        pdf=pdf,
                                        cdf=cdf,
                                        ppf=ppf,
                                        a=0)
        else:
            rad = self._rng.sample_ln_pdf(
                ln_pdf=self.model.ln_spectral_rad_pdf,
                size=self._mode_no,
                sample_around=1.0 / self.model.len_scale,
            )
        # get fully spatial samples by multiplying sphere samples and radii
        self._cov_sample = rad * sphere_coord

    @property
    def seed(self):
        """:class:`int`: Seed of the master RNG.

        Notes
        -----
        If a new seed is given, the setter property not only saves the
        new seed, but also creates new random modes with the new seed.
        """
        return self._seed

    @seed.setter
    def seed(self, new_seed):
        if new_seed is not self._seed:
            self.reset_seed(new_seed)

    @property
    def model(self):
        """:any:`CovModel`: Covariance model of the spatial random field."""
        return self._model

    @model.setter
    def model(self, model):
        self.update(model)

    @property
    def mode_no(self):
        """:class:`int`: Number of modes in the randomization method."""
        return self._mode_no

    @mode_no.setter
    def mode_no(self, mode_no):
        if int(mode_no) != self._mode_no:
            self._mode_no = int(mode_no)
            self.reset_seed(self._seed)

    @property
    def verbose(self):
        """:class:`bool`: Verbosity of the generator."""
        return self._verbose

    @verbose.setter
    def verbose(self, verbose):
        self._verbose = bool(verbose)

    @property
    def name(self):
        """:class:`str`: Name of the generator."""
        return self.__class__.__name__

    @property
    def value_type(self):
        """:class:`str`: Type of the field values (scalar, vector)."""
        return self._value_type

    def __str__(self):
        """Return String representation."""
        return self.__repr__()

    def __repr__(self):
        """Return String representation."""
        return "RandMeth(model={0}, mode_no={1}, seed={2})".format(
            repr(self.model), self._mode_no, self.seed)
예제 #4
0
 def setUp(self):
     self.seed = 19031977
     self.rng = RNG(self.seed)
     self.many_modes = 1000000
     self.few_modes = 100
예제 #5
0
class TestRNG(unittest.TestCase):
    def setUp(self):
        self.seed = 19031977
        self.rng = RNG(self.seed)
        self.many_modes = 1000000
        self.few_modes = 100

    def test_rng_normal_consistency(self):
        rng = RNG(21021997)
        z1_refs = [-1.93013270, 0.46330478]
        z2_refs = [-0.25536086, 0.98298696]

        z1 = self.rng.random.normal(size=self.few_modes)
        z2 = self.rng.random.normal(size=self.few_modes)
        self.assertAlmostEqual(z1[0], z1_refs[0])
        self.assertAlmostEqual(z1[1], z1_refs[1])
        self.assertAlmostEqual(z2[0], z2_refs[0])
        self.assertAlmostEqual(z2[1], z2_refs[1])
        self.rng.seed = self.seed
        z1 = self.rng.random.normal(size=self.few_modes)
        z2 = self.rng.random.normal(size=self.few_modes)
        self.assertAlmostEqual(z1[0], z1_refs[0])
        self.assertAlmostEqual(z1[1], z1_refs[1])
        self.assertAlmostEqual(z2[0], z2_refs[0])
        self.assertAlmostEqual(z2[1], z2_refs[1])

    def test_sample_sphere_1d(self):
        dim = 1
        sphere_coord = self.rng.sample_sphere(dim, self.few_modes)
        self.assertEqual(sphere_coord.shape, (dim, self.few_modes))
        sphere_coord = self.rng.sample_sphere(dim, self.many_modes)
        self.assertAlmostEqual(np.mean(sphere_coord), 0.0, places=3)

    def test_sample_sphere_2d(self):
        dim = 2
        sphere_coord = self.rng.sample_sphere(dim, self.few_modes)
        np.testing.assert_allclose(
            np.ones(self.few_modes),
            sphere_coord[0, :]**2 + sphere_coord[1, :]**2,
        )
        sphere_coord = self.rng.sample_sphere(dim, self.many_modes)
        self.assertAlmostEqual(np.mean(sphere_coord), 0.0, places=3)

    def test_sample_sphere_3d(self):
        dim = 3
        sphere_coord = self.rng.sample_sphere(dim, self.few_modes)
        self.assertEqual(sphere_coord.shape, (dim, self.few_modes))
        np.testing.assert_allclose(
            np.ones(self.few_modes),
            sphere_coord[0, :]**2 + sphere_coord[1, :]**2 +
            sphere_coord[2, :]**2,
        )
        sphere_coord = self.rng.sample_sphere(dim, self.many_modes)
        self.assertAlmostEqual(np.mean(sphere_coord), 0.0, places=3)

    def test_sample_dist(self):
        model = Gaussian(dim=1, var=3.5, len_scale=8.0)
        pdf, cdf, ppf = model.dist_func
        rad = self.rng.sample_dist(size=self.few_modes,
                                   pdf=pdf,
                                   cdf=cdf,
                                   ppf=ppf,
                                   a=0)
        self.assertEqual(rad.shape[0], self.few_modes)

        model = Gaussian(dim=2, var=3.5, len_scale=8.0)
        pdf, cdf, ppf = model.dist_func
        rad = self.rng.sample_dist(size=self.few_modes,
                                   pdf=pdf,
                                   cdf=cdf,
                                   ppf=ppf,
                                   a=0)
        self.assertEqual(rad.shape[0], self.few_modes)

        model = Gaussian(dim=3, var=3.5, len_scale=8.0)
        pdf, cdf, ppf = model.dist_func
        rad = self.rng.sample_dist(size=self.few_modes,
                                   pdf=pdf,
                                   cdf=cdf,
                                   ppf=ppf,
                                   a=0)
        self.assertEqual(rad.shape[0], self.few_modes)
예제 #6
0
class RandMeth:
    r"""Randomization method for calculating isotropic random fields.

    Parameters
    ----------
    model : :any:`CovModel`
        Covariance model
    mode_no : :class:`int`, optional
        Number of Fourier modes. Default: ``1000``
    seed : :class:`int` or :any:`None`, optional
        The seed of the random number generator.
        If "None", a random seed is used. Default: :any:`None`
    verbose : :class:`bool`, optional
        Be chatty during the generation.
        Default: :any:`False`
    sampling : :class:`str`, optional
        Sampling strategy. Either

            * "auto": select best strategy depending on given model
            * "inversion": use inversion method
            * "mcmc": use mcmc sampling

    **kwargs
        Placeholder for keyword-args

    Notes
    -----
    The Randomization method is used to generate isotropic
    spatial random fields characterized by a given covariance model.
    The calculation looks like [Hesse2014]_:

    .. math::
       u\left(x\right)=
       \sqrt{\frac{\sigma^{2}}{N}}\cdot
       \sum_{i=1}^{N}\left(
       Z_{1,i}\cdot\cos\left(\left\langle k_{i},x\right\rangle \right)+
       Z_{2,i}\cdot\sin\left(\left\langle k_{i},x\right\rangle \right)
       \right)

    where:

        * :math:`N` : fourier mode number
        * :math:`Z_{j,i}` : random samples from a normal distribution
        * :math:`k_i` : samples from the spectral density distribution of
          the covariance model

    References
    ----------
    .. [Hesse2014] Heße, F., Prykhodko, V., Schlüter, S., and Attinger, S.,
           "Generating random fields with a truncated power-law variogram:
           A comparison of several numerical methods",
           Environmental Modelling & Software, 55, 32-48., (2014)
    """
    def __init__(
        self,
        model,
        mode_no=1000,
        seed=None,
        verbose=False,
        sampling="auto",
        **kwargs,
    ):
        if kwargs:
            warnings.warn("gstools.RandMeth: **kwargs are ignored")
        # initialize atributes
        self._mode_no = int(mode_no)
        self._verbose = bool(verbose)
        # initialize private atributes
        self._model = None
        self._seed = None
        self._rng = None
        self._z_1 = None
        self._z_2 = None
        self._cov_sample = None
        self._value_type = "scalar"
        # set sampling strategy
        self._sampling = None
        self.sampling = sampling
        # set model and seed
        self.update(model, seed)

    def __call__(self, pos, add_nugget=True):
        """Calculate the random modes for the randomization method.

        This method  calls the `summate_*` Cython methods, which are the
        heart of the randomization method.

        Parameters
        ----------
        pos : (d, n), :class:`numpy.ndarray`
            the position tuple with d dimensions and n points.
        add_nugget : :class:`bool`
            Whether to add nugget noise to the field.

        Returns
        -------
        :class:`numpy.ndarray`
            the random modes
        """
        pos = np.asarray(pos, dtype=np.double)
        summed_modes = summate(self._cov_sample, self._z_1, self._z_2, pos)
        nugget = self.get_nugget(summed_modes.shape) if add_nugget else 0.0
        return np.sqrt(self.model.var / self._mode_no) * summed_modes + nugget

    def get_nugget(self, shape):
        """
        Generate normal distributed values for the nugget simulation.

        Parameters
        ----------
        shape : :class:`tuple`
            the shape of the summed modes

        Returns
        -------
        nugget : :class:`numpy.ndarray`
            the nugget in the same shape as the summed modes
        """
        if self.model.nugget > 0:
            nugget = np.sqrt(
                self.model.nugget) * self._rng.random.normal(size=shape)
        else:
            nugget = 0.0
        return nugget

    def update(self, model=None, seed=np.nan):
        """Update the model and the seed.

        If model and seed are not different, nothing will be done.

        Parameters
        ----------
        model : :any:`CovModel` or :any:`None`, optional
            covariance model. Default: :any:`None`
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`
        """
        # check if a new model is given
        if isinstance(model, CovModel):
            if self.model != model:
                self._model = dcp(model)
                if seed is None or not np.isnan(seed):
                    self.reset_seed(seed)
                else:
                    self.reset_seed(self._seed)
            # just update the seed, if its a new one
            elif seed is None or not np.isnan(seed):
                self.seed = seed
        # or just update the seed, when no model is given
        elif model is None and (seed is None or not np.isnan(seed)):
            if isinstance(self._model, CovModel):
                self.seed = seed
            else:
                raise ValueError(
                    "gstools.field.generator.RandMeth: no 'model' given")
        # if the user tries to trick us, we beat him!
        elif model is None and np.isnan(seed):
            if (isinstance(self._model, CovModel) and self._z_1 is not None
                    and self._z_2 is not None
                    and self._cov_sample is not None):
                if self.verbose:
                    print("RandMeth.update: Nothing will be done...")
            else:
                raise ValueError("gstools.field.generator.RandMeth: "
                                 "neither 'model' nor 'seed' given!")
        # wrong model type
        else:
            raise ValueError(
                "gstools.field.generator.RandMeth: 'model' is not an "
                "instance of 'gstools.CovModel'")

    def reset_seed(self, seed=np.nan):
        """
        Recalculate the random amplitudes and wave numbers with the given seed.

        Parameters
        ----------
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`

        Notes
        -----
        Even if the given seed is the present one, modes will be recalculated.
        """
        if seed is None or not np.isnan(seed):
            self._seed = seed
        self._rng = RNG(self._seed)
        # normal distributed samples for randmeth
        self._z_1 = self._rng.random.normal(size=self._mode_no)
        self._z_2 = self._rng.random.normal(size=self._mode_no)
        # sample uniform on a sphere
        sphere_coord = self._rng.sample_sphere(self.model.dim, self._mode_no)
        # sample radii acording to radial spectral density of the model
        if self.sampling == "inversion" or (self.sampling == "auto"
                                            and self.model.has_ppf):
            pdf, cdf, ppf = self.model.dist_func
            rad = self._rng.sample_dist(size=self._mode_no,
                                        pdf=pdf,
                                        cdf=cdf,
                                        ppf=ppf,
                                        a=0)
        else:
            rad = self._rng.sample_ln_pdf(
                ln_pdf=self.model.ln_spectral_rad_pdf,
                size=self._mode_no,
                sample_around=1.0 / self.model.len_rescaled,
            )
        # get fully spatial samples by multiplying sphere samples and radii
        self._cov_sample = rad * sphere_coord

    @property
    def sampling(self):
        """:class:`str`: Sampling strategy."""
        return self._sampling

    @sampling.setter
    def sampling(self, sampling):
        if sampling not in ["auto", "inversion", "mcmc"]:
            raise ValueError(f"RandMeth: sampling not in {SAMPLING}.")
        self._sampling = sampling

    @property
    def seed(self):
        """:class:`int`: Seed of the master RNG.

        Notes
        -----
        If a new seed is given, the setter property not only saves the
        new seed, but also creates new random modes with the new seed.
        """
        return self._seed

    @seed.setter
    def seed(self, new_seed):
        if new_seed is not self._seed:
            self.reset_seed(new_seed)

    @property
    def model(self):
        """:any:`CovModel`: Covariance model of the spatial random field."""
        return self._model

    @model.setter
    def model(self, model):
        self.update(model)

    @property
    def mode_no(self):
        """:class:`int`: Number of modes in the randomization method."""
        return self._mode_no

    @mode_no.setter
    def mode_no(self, mode_no):
        if int(mode_no) != self._mode_no:
            self._mode_no = int(mode_no)
            self.reset_seed(self._seed)

    @property
    def verbose(self):
        """:class:`bool`: Verbosity of the generator."""
        return self._verbose

    @verbose.setter
    def verbose(self, verbose):
        self._verbose = bool(verbose)

    @property
    def name(self):
        """:class:`str`: Name of the generator."""
        return self.__class__.__name__

    @property
    def value_type(self):
        """:class:`str`: Type of the field values (scalar, vector)."""
        return self._value_type

    def __repr__(self):
        """Return String representation."""
        return (f"{self.name}(model={self.model}, "
                f"mode_no={self._mode_no}, seed={self.seed})")
예제 #7
0
class RandMeth(object):
    r"""Randomization method for calculating isotropic spatial random fields.

    Parameters
    ----------
    model : :any:`CovModel`
        covariance model
    mode_no : :class:`int`, optional
        number of Fourier modes. Default: ``1000``
    seed : :class:`int` or :any:`None`, optional
        the seed of the random number generator.
        If "None", a random seed is used. Default: :any:`None`
    chunk_tmp_size : :class:`int`, optional
        Number of points (number of coordinates * mode_no)
        to be handled by one chunk while creating the fild.
        This is used to prevent memory overflows while
        generating the field. Default: ``1e7``
    verbose : :class:`bool`, optional
        State if there should be output during the generation.
        Default: :any:`False`
    **kwargs
        Placeholder for keyword-args

    Notes
    -----
    The Randomization method is used to generate isotropic
    spatial random fields characterized by a given covariance model.
    The calculation looks like:

    .. math::
       u\left(x\right)=
       \sqrt{\frac{\sigma^{2}}{N}}\cdot
       \sum_{i=1}^{N}\left(
       Z_{1,i}\cdot\cos\left(\left\langle k_{i},x\right\rangle \right)+
       Z_{2,i}\cdot\sin\left(\left\langle k_{i},x\right\rangle \right)
       \right)

    where:

        * :math:`N` : fourier mode number
        * :math:`Z_{j,i}` : random samples from a normal distribution
        * :math:`k_i` : samples from the spectral density distribution of
          the covariance model
    """
    def __init__(self,
                 model,
                 mode_no=1000,
                 seed=None,
                 chunk_tmp_size=1e7,
                 verbose=False,
                 **kwargs):
        if kwargs:
            print("gstools.RandMeth: **kwargs are ignored")
        # initialize atributes
        self._mode_no = int(mode_no)
        self._chunk_tmp_size = int(chunk_tmp_size)
        self._verbose = bool(verbose)
        # initialize private atributes
        self._model = None
        self._seed = None
        self._rng = None
        self._z_1 = None
        self._z_2 = None
        self._cov_sample = None
        # set model and seed
        self.update(model, seed)

    def __call__(self, x, y=None, z=None):
        """Calculates the random modes for the randomization method.

        Parameters
        ----------
        x : :class:`float`, :class:`numpy.ndarray`
            the x components of the position tuple, the shape has to be
            (len(x), 1, 1) for 3d and accordingly shorter for lower
            dimensions
        y : :class:`float`, :class:`numpy.ndarray`, optional
            the y components of the pos. tupls
        z : :class:`float`, :class:`numpy.ndarray`, optional
            the z components of the pos. tuple

        Returns
        -------
        :class:`numpy.ndarray`
            the random modes
        """
        summed_modes = np.broadcast(x, y, z)
        summed_modes = np.squeeze(np.zeros(summed_modes.shape))
        # make a guess fo the chunk_no according to the input
        tmp_pnt = np.prod(summed_modes.shape) * self._mode_no
        chunk_no_exp = int(
            max(0, np.ceil(np.log2(tmp_pnt / self.chunk_tmp_size))))
        # Test to see if enough memory is available.
        # In case there isn't, divide Fourier modes into 2 smaller chunks
        while True:
            try:
                chunk_no = 2**chunk_no_exp
                chunk_len = int(np.ceil(self._mode_no / chunk_no))
                if self.verbose:
                    print("RandMeth: Generating field with " + str(chunk_no) +
                          " chunks")
                    print("(chunk length " + str(chunk_len) + ")")
                for chunk in range(chunk_no):
                    if self.verbose:
                        print("chunk " + str(chunk + 1) + " of " +
                              str(chunk_no))
                    ch_start = chunk * chunk_len
                    # In case k[d,ch_start:ch_stop] with
                    # ch_stop >= len(k[d,:]) causes errors in
                    # numpy, use the commented min-function below
                    # ch_stop = min((chunk + 1) * chunk_len, self._mode_no-1)
                    ch_stop = (chunk + 1) * chunk_len

                    if self.model.dim == 1:
                        phase = self._cov_sample[0, ch_start:ch_stop] * x
                    elif self.model.dim == 2:
                        phase = (self._cov_sample[0, ch_start:ch_stop] * x +
                                 self._cov_sample[1, ch_start:ch_stop] * y)
                    else:
                        phase = (self._cov_sample[0, ch_start:ch_stop] * x +
                                 self._cov_sample[1, ch_start:ch_stop] * y +
                                 self._cov_sample[2, ch_start:ch_stop] * z)
                    summed_modes += np.squeeze(
                        np.sum(
                            self._z_1[ch_start:ch_stop] * np.cos(phase) +
                            self._z_2[ch_start:ch_stop] * np.sin(phase),
                            axis=-1,
                        ))
            except MemoryError:
                chunk_no_exp += 1
                print("Not enough memory. Dividing Fourier modes into {} "
                      "chunks.".format(2**chunk_no_exp))
            else:
                # we break out of the endless loop if we don't get MemoryError
                break

        # generate normal distributed values for the nugget simulation
        if self.model.nugget > 0:
            nugget = np.sqrt(self.model.nugget) * self._rng.random.normal(
                size=summed_modes.shape)
        else:
            nugget = 0.0

        return np.sqrt(self.model.var / self._mode_no) * summed_modes + nugget

    def update(self, model=None, seed=np.nan):
        """Update the model and the seed.

        If model and seed are not different, nothing will be done.

        Parameters
        ----------
        model : :any:`CovModel` or :any:`None`, optional
            covariance model. Default: :any:`None`
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`
        """
        # check if a new model is given
        if isinstance(model, CovModel):
            if self.model != model:
                self._model = dcp(model)
                if seed is None or not np.isnan(seed):
                    self.reset_seed(seed)
                else:
                    self.reset_seed(self._seed)
            # just update the seed, if its a new one
            elif seed is None or not np.isnan(seed):
                self.seed = seed
        # or just update the seed, when no model is given
        elif model is None and (seed is None or not np.isnan(seed)):
            if isinstance(self._model, CovModel):
                self.seed = seed
            else:
                raise ValueError(
                    "gstools.field.generator.RandMeth: no 'model' given")
        # if the user tries to trick us, we beat him!
        elif model is None and np.isnan(seed):
            if (isinstance(self._model, CovModel) and self._z_1 is not None
                    and self._z_2 is not None
                    and self._cov_sample is not None):
                if self.verbose:
                    print("RandMeth.update: Nothing will be done...")
            else:
                raise ValueError("gstools.field.generator.RandMeth: " +
                                 "neither 'model' nor 'seed' given!")
        # wrong model type
        else:
            raise ValueError(
                "gstools.field.generator.RandMeth: 'model' is not an " +
                "instance of 'gstools.CovModel'")

    def reset_seed(self, seed=np.nan):
        """
        Recalculate the random amplitudes and wave numbers with the given seed.

        Parameters
        ----------
        seed : :class:`int` or :any:`None` or :any:`numpy.nan`, optional
            the seed of the random number generator.
            If :any:`None`, a random seed is used. If :any:`numpy.nan`,
            the actual seed will be kept. Default: :any:`numpy.nan`

        Notes
        -----
        Even if the given seed is the present one, modes will be racalculated.
        """
        if seed is None or not np.isnan(seed):
            self._seed = seed
        self._rng = RNG(self._seed)
        # normal distributed samples for randmeth
        self._z_1 = self._rng.random.normal(size=self._mode_no)
        self._z_2 = self._rng.random.normal(size=self._mode_no)
        # sample uniform on a sphere
        sphere_coord = self._rng.sample_sphere(self.model.dim, self._mode_no)
        # sample radii acording to radial spectral density of the model
        if self.model.has_ppf:
            pdf, cdf, ppf = self.model.dist_func
            rad = self._rng.sample_dist(size=self._mode_no,
                                        pdf=pdf,
                                        cdf=cdf,
                                        ppf=ppf,
                                        a=0)
        else:
            rad = self._rng.sample_ln_pdf(
                ln_pdf=self.model.ln_spectral_rad_pdf,
                size=self._mode_no,
                sample_around=1.0 / self.model.len_scale,
            )
        # get fully spatial samples by multiplying sphere samples and radii
        self._cov_sample = rad * sphere_coord

    @property
    def seed(self):
        """:class:`int`: the seed of the master RNG

        Notes
        -----
        If a new seed is given, the setter property not only saves the
        new seed, but also creates new random modes with the new seed.
        """
        return self._seed

    @seed.setter
    def seed(self, new_seed):
        if new_seed is not self._seed:
            self.reset_seed(new_seed)

    @property
    def model(self):
        """:any:`CovModel`: The covariance model of the spatial random field.
        """
        return self._model

    @model.setter
    def model(self, model):
        self.update(model)

    @property
    def mode_no(self):
        """:class:`int`: The number of modes in the randomization method."""
        return self._mode_no

    @mode_no.setter
    def mode_no(self, mode_no):
        if int(mode_no) != self._mode_no:
            self._mode_no = int(mode_no)
            self.reset_seed(self._seed)

    @property
    def chunk_tmp_size(self):
        """:class:`int`: temporary chunk size"""
        return self._chunk_tmp_size

    @chunk_tmp_size.setter
    def chunk_tmp_size(self, size):
        self._chunk_tmp_size = int(size)

    @property
    def verbose(self):
        """:class:`bool`: verbosity of the generator"""
        return self._verbose

    @verbose.setter
    def verbose(self, verbose):
        self._verbose = bool(verbose)

    @property
    def name(self):
        """:class:`str`: The name of the generator"""
        return self.__class__.__name__

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return "RandMeth(model={0}, mode_no={1}, seed={2})".format(
            repr(self.model), self._mode_no, self.seed)