Source code for fluidfft.fft2d.operators

"""Operators 2d (:mod:`fluidfft.fft2d.operators`)

=================================================

.. autoclass:: OperatorsPseudoSpectral2D
   :members:
   :undoc-members:

"""

from math import pi
import warnings

import numpy as np

from transonic import boost
from fluiddyn.util import mpi
from fluiddyn.util.compat import cached_property

from fluidfft import create_fft_object, import_fft_class
from fluidfft.base import OperatorsBase


Ac = "complex128[:,:]"
Af = "float64[:,:]"

try:
    if mpi.nb_proc > 1:
        MPI = mpi.MPI
except TypeError:
    # for Transonic transpilation
    pass


def _make_str_length(length):
    l_over_pi = length / np.pi
    if l_over_pi.is_integer():
        return repr(int(l_over_pi)) + "pi"

    elif round(length) == length:
        return "{}".format(int(length))

    return "{:.3f}".format(length).rstrip("0")


def get_simple_2d_seq_method():
    """Get a simple 2d sequential method"""
    try:
        import pyfftw

    except ImportError:
        return "fft2d.with_fftw2d"
    else:
        del pyfftw
        return "fft2d.with_pyfftw"


def get_simple_2d_mpi_method():
    """Get a simple 2d parallel method"""
    method = "fft2d.mpi_with_fftwmpi2d"
    try:
        import_fft_class(method)
    except ImportError:
        method = "fft2d.mpi_with_fftw1d"
    return method


[docs] @boost class OperatorsPseudoSpectral2D(OperatorsBase): """Perform 2D FFT and operations on data. Parameters ---------- nx : int Global dimension over the x-axis (second dimension for the real arrays). ny : int Global dimension over the y-axis (first dimension for the real arrays). lx : float Length of the domain along the x-axis. ly : float Length of the domain along the y-axis. fft : str or FFT classes Name of module or string characterizing a method. It has to correspond to a module of fluidfft. The first part "fluidfft." of the module "path" can be omitted. coef_dealiasing : float """ KX: Af KY: Af KX_over_K2: Af KY_over_K2: Af _has_to_dealiase: bool where_dealiased: "uint8[][]" nK0_loc: int nK1_loc: int def __init__(self, nx, ny, lx, ly, fft=None, coef_dealiasing=1.0): self.nx_seq = self.nx = nx = int(nx) self.ny_seq = self.ny = ny = int(ny) self.lx = lx = float(lx) self.ly = ly = float(ly) if fft is None or fft == "default": if mpi.nb_proc == 1: fft = get_simple_2d_seq_method() else: fft = get_simple_2d_mpi_method() if isinstance(fft, str): if fft.lower() == "sequential": fft = get_simple_2d_seq_method() if any([fft.startswith(s) for s in ["fluidfft.", "fft2d."]]): opfft = create_fft_object(fft, ny, nx) else: raise ValueError( ( "Cannot instantiate %s. Expected something like " "'sequential', 'fluidfft.fft2d.<method>' or " "'fft2d.<method>'" ) % fft ) elif isinstance(fft, type): opfft = fft(ny, nx) else: opfft = fft self.oper_fft = self.opfft = opfft self.type_fft = opfft.__class__.__module__ # NOTE: Overwrites the value of `np` in the present scope self._numpy_api = np = opfft._numpy_api self.is_transposed = opfft.get_is_transposed() self.fft2 = self.fft = self.opfft.fft self.ifft2 = self.ifft = self.opfft.ifft self.fft_as_arg = opfft.fft_as_arg self.ifft_as_arg = opfft.ifft_as_arg self.shapeX = self.shapeX_loc = opfft.get_shapeX_loc() self.shapeX_seq = opfft.get_shapeX_seq() self.shapeK = self.shapeK_loc = opfft.get_shapeK_loc() self.shapeK_seq = opfft.get_shapeK_seq() self.compute_energy_from_X = opfft.compute_energy_from_X self.compute_energy_from_K = opfft.compute_energy_from_K self.spectrum2D_from_fft = self.compute_2dspectrum self.spectra1D_from_fft = self.compute_1dspectra self.seq_indices_first_K = opfft.get_seq_indices_first_K() self.seq_indices_first_X = opfft.get_seq_indices_first_X() self.deltax = lx / nx self.deltay = ly / ny self.x_seq = self.x = self.deltax * np.arange(nx) self.y_seq = self.y = self.deltay * np.arange(ny) self.deltakx = 2 * pi / lx self.deltaky = 2 * pi / ly x0_adim, x1_adim = opfft.get_x_adim_loc() self.nX0_loc = len(x0_adim) self.nX1_loc = len(x1_adim) self.x = self.x_loc = self.deltax * x1_adim self.y = self.y_loc = self.deltay * x0_adim [self.X, self.Y] = np.meshgrid(self.x_loc, self.y_loc) k0_adim, k1_adim = opfft.get_k_adim_loc() self.nK0_loc = len(k0_adim) self.nK1_loc = len(k1_adim) if self.nK0_loc == 0 or self.nK1_loc == 0: warnings.warn( "The shape for processor {} is {}. ".format( mpi.rank, self.shapeK_loc ) + "This means that it has no data to treat. " "It is not efficient and many of the functions of " "this operators won't work. Unless you know what you do, " "change the fft class, the resolution or " "the number of MPI processes." ) if self.is_transposed: kx_adim = k0_adim ky_adim = k1_adim else: kx_adim = k1_adim ky_adim = k0_adim self.kx = self.deltakx * kx_adim self.ky = self.deltaky * ky_adim self.kx_loc = self.kx self.ky_loc = self.ky if not self.is_transposed: [self.KX, self.KY] = np.meshgrid(self.kx_loc, self.ky_loc) self.dim_kx = 1 self.dim_ky = 0 self.k0 = self.ky self.k1 = self.kx self.nkx_loc = self.nK1_loc self.nky_loc = self.nK0_loc else: [self.KY, self.KX] = np.meshgrid(self.ky_loc, self.kx_loc) self.dim_kx = 0 self.dim_ky = 1 self.k0 = self.kx self.k1 = self.ky self.nkx_loc = self.nK0_loc self.nky_loc = self.nK1_loc assert self.KX.shape == self.shapeK_loc self.KX2 = self.KX**2 self.KY2 = self.KY**2 self.K2 = self.KX2 + self.KY2 self.K4 = self.K2**2 self.K8 = self.K4**2 self.K = np.sqrt(self.K2) self.is_sequential = opfft.get_shapeK_loc() == opfft.get_shapeK_seq() self.rank = mpi.rank self.nb_proc = mpi.nb_proc if mpi.nb_proc > 1: self.comm = mpi.comm if not self.is_sequential: self.gather_Xspace = self.opfft.gather_Xspace self.scatter_Xspace = self.opfft.scatter_Xspace self.nkx_seq = nx // 2 + 1 self.nky_seq = ny self.nky_spectra = ny // 2 + 1 khmax = min(self.deltakx * self.nkx_seq, self.deltakx * self.nky_spectra) self.deltak = max(self.deltakx, self.deltaky) self.nkh = int(khmax / self.deltak) if self.nkh == 0: self.nkh = 1 self.kh_2dspectrum = self.deltak * np.arange(self.nkh) # Initialisation dealiasing kx_max = self.deltakx * (nx // 2) ky_max = self.deltaky * (ny // 2) self.coef_dealiasing = coef_dealiasing if isinstance(coef_dealiasing, bool) and not coef_dealiasing: self._has_to_dealiase = False self.kxmax_dealiasing = kx_max + self.deltakx self.kymax_dealiasing = ky_max + self.deltaky else: self._has_to_dealiase = True self.kxmax_dealiasing = coef_dealiasing * kx_max self.kymax_dealiasing = coef_dealiasing * ky_max CONDKX = abs(self.KX) >= self.kxmax_dealiasing CONDKY = abs(self.KY) >= self.kymax_dealiasing where_dealiased = np.logical_or(CONDKX, CONDKY) self.where_dealiased = np.array(where_dealiased, dtype="uint8") self.indexes_dealiased = np.argwhere(where_dealiased) # for spectra self.nkxE = self.nx_seq // 2 + 1 self.nkxE2 = (self.nx_seq + 1) // 2 self.nkyE = self.ny_seq // 2 + 1 self.nkyE2 = (self.ny_seq + 1) // 2 # print('nkxE, nkxE2', self.nkxE, self.nkxE2) # print('nkyE, nkyE2', self.nkyE, self.nkyE2) self.kxE = self.deltakx * np.arange(self.nkxE) self.kyE = self.deltaky * np.arange(self.nkyE) self.khE = self.kxE self.nkhE = self.nkxE y_loc, x_loc = self.opfft.get_x_adim_loc() y_loc = y_loc * self.deltay x_loc = x_loc * self.deltax self.XX, self.YY = np.meshgrid(x_loc, y_loc) # Some arrays below are made cached_property mainly because setting them are # possible only using numpy arrays and not as dask array. There is also the # added advantage of making the class somewhat lightweight def _get_Kn_not0(self, Kn): Kn_not0 = np.copy(Kn) if mpi.rank == 0 or self.is_sequential: Kn_not0[0, 0] = 10.0e-10 return Kn_not0 @cached_property def K_not0(self): return self._get_Kn_not0(self.K) @cached_property def K2_not0(self): return self._get_Kn_not0(self.K2) @cached_property def K4_not0(self): return self._get_Kn_not0(self.K4) @cached_property def KX_over_K2(self): return self.KX / self.K2_not0 @cached_property def KY_over_K2(self): return self.KY / self.K2_not0
[docs] def mean_global(self, field): """Compute the global average over all processes""" if self.is_sequential: return field.mean() else: result = field.sum() result = mpi.comm.allreduce(result, op=mpi.MPI.SUM) result /= self.nx_seq * self.ny_seq return result
[docs] def sum_wavenumbers(self, field_fft): """Compute the sum over all wavenumbers.""" np = self._numpy_api try: field_fft = np.ascontiguousarray(field_fft) except AttributeError: # Dask does not implement ascontiguousarray. Although the above # expression works as is, if numpy is used as np, it computes # field_fft from a lazy dask array into a numpy array pass return self.opfft.sum_wavenumbers(field_fft)
[docs] def sum_wavenumbers_versatile(self, field_fft): """Compute the sum over all wavenumbers (versatile version). This function should return the same result than :func:`sum_wavenumbers`. It is here mainly to check that the classes are well implemented. """ raise NotImplementedError
[docs] def produce_str_describing_grid(self): """Produce a short string describing the grid.""" return "{}x{}".format(self.nx_seq, self.ny_seq)
[docs] def produce_str_describing_oper(self): """Produce a short string describing the operator.""" str_lx = _make_str_length(self.lx) str_ly = _make_str_length(self.ly) return ("{}x{}_S" + str_lx + "x" + str_ly).format( self.nx_seq, self.ny_seq )
[docs] def produce_long_str_describing_oper(self): """Produce a string describing the operator.""" str_lx = _make_str_length(self.lx) str_ly = _make_str_length(self.ly) return ( "type fft: " + str(self.type_fft) + "\n" + "nx = {0:6d} ; ny = {1:6d}\n".format(self.nx_seq, self.ny_seq) + "lx = " + str_lx + " ; ly = " + str_ly + "\n" )
[docs] def compute_1dspectra(self, energy_fft): """Compute the 1D spectra. Return a dictionary.""" np = self._numpy_api if self.type_fft == "fluidfft.fft2d.with_dask": # Alternate algorithm since dask does not support array mutation # https://github.com/dask/dask/issues/4399#issuecomment-462080036 # https://stackoverflow.com/questions/36142892/item-assignment-to-python-dask-array-objects # computation of E_kx E_kx = 2.0 * energy_fft.sum(self.dim_ky) / self.deltakx # concatenate instead of mutation begin = E_kx[0:1] / 2 middle = E_kx[1:-1] end = E_kx[-1:] if self.nx_seq % 2 == 0: end /= 2 E_kx = np.concatenate([begin, middle, end], axis=0) E_kx = E_kx[: self.nkxE].compute() # computation of E_ky E_ky_tmp = energy_fft[:, 0].copy() E_ky_tmp += 2 * energy_fft[:, 1 : self.nkxE2].sum(1) if self.nx_seq % 2 == 0: E_ky_tmp += energy_fft[:, self.nkxE - 1] nkyE = self.nkyE E_ky = E_ky_tmp[:nkyE] # concatenate begin = E_ky[0:1] middle = ( E_ky[1 : self.nkyE2] + E_ky_tmp[self.nkyE : self.nky_seq][::-1] ) end = E_ky[self.nkyE2 :] E_ky = np.concatenate([begin, middle, end]) E_ky = (E_ky / self.deltaky).compute() elif self.is_sequential and not self.is_transposed: # In this case, self.dim_ky == 0 and self.dim_kx == 1 # note that only the kx >= 0 are in the spectral variables # # computation of E_kx # we sum over all ky # the 2 is here because there are only the kx >= 0 E_kx = 2.0 * energy_fft.sum(self.dim_ky) / self.deltakx E_kx[0] = E_kx[0] / 2 if self.nx_seq % 2 == 0: E_kx[-1] = E_kx[-1] / 2 E_kx = E_kx[: self.nkxE] # computation of E_ky E_ky_tmp = energy_fft[:, 0].copy() E_ky_tmp += 2 * energy_fft[:, 1 : self.nkxE2].sum(1) if self.nx_seq % 2 == 0: E_ky_tmp += energy_fft[:, self.nkxE - 1] nkyE = self.nkyE E_ky = E_ky_tmp[:nkyE] E_ky[1 : self.nkyE2] += E_ky_tmp[self.nkyE : self.nky_seq][::-1] E_ky = E_ky / self.deltaky elif self.is_sequential and self.is_transposed: # In this case, self.dim_ky == 1 and self.dim_kx == 0 # note that only the kx >= 0 are in the spectral variables # # computation of E_kx # we sum over all ky # the 2 is here because there are only the kx >= 0 E_kx = 2.0 * energy_fft.sum(self.dim_ky) / self.deltakx E_kx[0] = E_kx[0] / 2 if self.nx_seq % 2 == 0 and self.shapeK_seq[0] == self.nkxE: E_kx[-1] = E_kx[-1] / 2 E_kx = E_kx[: self.nkxE] # computation of E_ky E_ky_tmp = energy_fft[0, :].copy() E_ky_tmp += 2 * energy_fft[1 : self.nkxE2, :].sum(0) if self.nx_seq % 2 == 0 and self.shapeK_seq[0] == self.nkxE: E_ky_tmp += energy_fft[self.nkxE - 1, :] nkyE = self.nkyE E_ky = E_ky_tmp[:nkyE] E_ky[1 : self.nkyE2] += E_ky_tmp[self.nkyE : self.nky_seq][::-1] E_ky = E_ky / self.deltaky elif self.is_transposed: # Memory is shared along kx (dim 0) # In this case, self.dim_ky == 1 and self.dim_kx == 0 # note that only the kx >= 0 are in the spectral variables # # computation of E_kx # we sum over all ky # the 2 is here because there are only the kx >= 0 E_kx_loc = 2.0 * energy_fft.sum(self.dim_ky) / self.deltakx if self.rank == 0: E_kx_loc[0] = E_kx_loc[0] / 2 if self.shapeK_loc[0] != 1: if ( self.rank == self.nb_proc - 1 and self.nx_seq % 2 == 0 and self.shapeK_seq[0] == self.nkxE ): E_kx_loc[-1] = E_kx_loc[-1] / 2 E_kx = np.zeros(self.nkxE) counts = self.comm.allgather(self.nkx_loc) self.comm.Allgatherv( sendbuf=[E_kx_loc, MPI.DOUBLE], recvbuf=[E_kx, (counts, None), MPI.DOUBLE], ) E_kx = E_kx[: self.nkxE] # computation of E_ky E_ky_tmp = 2 * energy_fft[1:-1, :].sum(0) if self.rank == 0: E_ky_tmp += energy_fft[0, :] else: E_ky_tmp += 2 * energy_fft[0, :] if self.shapeK_loc[0] != 1: if ( self.rank == self.nb_proc - 1 and self.nx_seq % 2 == 0 and self.shapeK_seq[0] == self.nkxE ): E_ky_tmp += energy_fft[-1, :] else: E_ky_tmp += 2 * energy_fft[-1, :] nkyE = self.nkyE E_ky = E_ky_tmp[:nkyE] E_ky[1 : self.nkyE2] += E_ky_tmp[self.nkyE : self.nky_seq][::-1] E_ky = E_ky / self.deltaky E_ky = self.comm.allreduce(E_ky, op=MPI.SUM) elif not self.is_transposed: # In this case, self.dim_ky == 0 and self.dim_ky == 1 # note that only the kx>=0 are in the spectral variables # to obtain the spectrum as a function of kx # we sum over all ky # the 2 is here because there are only the kx>=0 raise NotImplementedError # E_kx = 2.*energy_fft.sum(self.dim_ky)/self.deltakx # E_kx[0] = E_kx[0]/2 # E_kx = self.comm.allreduce(E_kx, op=MPI.SUM) # E_kx = E_kx[:self.nkxE] # # computation of E_ky # E_ky_tmp = energy_fft[:, 0].copy() # E_ky_tmp += 2*energy_fft[:, 1:].sum(1) # E_ky_tmp = np.ascontiguousarray(E_ky_tmp) # # print(self.rank, 'E_ky_tmp', E_ky_tmp, E_ky_tmp.shape) # E_ky_long = np.empty(self.nky_seq) # counts = self.comm.allgather(self.nky_loc) # self.comm.Allgatherv(sendbuf=[E_ky_tmp, MPI.DOUBLE], # recvbuf=[E_ky_long, (counts, None), # MPI.DOUBLE]) # nkyE = self.nkyE # E_ky = E_ky_long[0:nkyE] # E_ky[1:nkyE] = E_ky[1:nkyE] + E_ky_long[self.nky_seq:nkyE:-1] # E_ky = E_ky/self.deltaky return E_kx, E_ky
[docs] def compute_2dspectrum(self, E_fft): """Compute the 2D spectrum.""" K = self.K nk0loc = self.shapeK_loc[0] nk1loc = self.shapeK_loc[1] # the copy is important: no *= ! E_fft = 2 * E_fft if self.is_transposed: if self.rank == 0: E_fft[0, :] /= 2 if ( (self.is_sequential or self.rank == self.nb_proc - 1) and self.nx_seq % 2 == 0 and self.shapeK_seq[0] == self.nkxE ): E_fft[-1, :] /= 2 else: E_fft[:, 0] /= 2 if self.nx_seq % 2 == 0: E_fft[:, -1] /= 2 deltak = self.deltak khE = self.khE nkh = self.nkhE spectrum2D = np.zeros([nkh]) for ik0 in range(nk0loc): for ik1 in range(nk1loc): E0D = E_fft[ik0, ik1] kappa0D = K[ik0, ik1] ikh = int(kappa0D / deltak) if ikh >= nkh - 1: ikh = nkh - 1 spectrum2D[ikh] += E0D else: coef_share = (kappa0D - khE[ikh]) / deltak spectrum2D[ikh] += (1 - coef_share) * E0D spectrum2D[ikh + 1] += coef_share * E0D if not self.is_sequential: spectrum2D = self.comm.allreduce(spectrum2D, op=mpi.MPI.SUM) return spectrum2D / deltak
[docs] def compute_spectrum_kykx(self, energy_fft, folded=True): """Compute a spectrum vs ky, kx. Return a dictionary. Parameters ---------- energy_fft : ndarray[float] A real valued 2D array representing the energy content in each wavenumber. folded : bool Computes a spectra with the (+ky, +kx) and (-ky, +kx) quadrants folded, when ``True``. """ if not np.issubdtype(energy_fft.dtype, np.floating): raise TypeError( "Do you really want to do spectrum_kykx with complex field?" "Perhaps you want to compute a spectrum from energy_fft :" "E_fft = oper.compute_spectrum_kykx(abs(a_complex)**2)" ) if not self.is_transposed: # In this case, self.dim_ky == 0 and self.dim_kx == 1 # note : only the kx >= 0 and ky>=0 are in the spectral variables E_kykxtmp = 2.0 * energy_fft / (self.deltakx * self.deltaky) else: # In this case, self.dim_ky == 1 and self.dim_kx == 0 # note : only the kx >= 0 are in the spectral variables E_kykxtmp = ( 2.0 * np.transpose(energy_fft) / (self.deltakx * self.deltaky) ) if folded: nkyE = self.nkyE else: nkyE = self.ny_seq if self.is_sequential: # # computation of E_kykx E_kykxtmp[:, 0] = E_kykxtmp[:, 0] / 2 if self.nx_seq % 2 == 0 and self.shapeK_seq[self.dim_kx] == self.nkxE: E_kykxtmp[:, -1] = E_kykxtmp[:, -1] / 2 E_kykx = np.zeros([nkyE, self.nkxE]) E_kykx[:nkyE, : self.nkxE] = E_kykxtmp[:nkyE, : self.nkxE] if folded: E_kykx[1 : self.nkyE2, :] += E_kykxtmp[ self.nkyE : self.nky_seq, : ][::-1] elif self.is_transposed: # computation of E_kykx E_kykx_loc = E_kykxtmp if self.rank == 0: E_kykx_loc[:, 0] = E_kykx_loc[:, 0] / 2 if ( self.rank == self.nb_proc - 1 and self.nx_seq % 2 == 0 and self.shapeK_seq[0] == self.nkxE ): E_kykx_loc[:, -1] = E_kykx_loc[:, -1] / 2 E_kykx = np.zeros([nkyE, self.nkxE]) nkx_start = self.seq_indices_first_K[0] E_kykx[:, nkx_start : self.nkx_loc + nkx_start] = E_kykx_loc[ :nkyE, : self.nkx_loc ] if folded: E_kykx[ 1 : self.nkyE2, nkx_start : self.nkx_loc + nkx_start ] += E_kykx_loc[nkyE : self.nky_seq, : self.nkx_loc][::-1] E_kykx = self.comm.allreduce(E_kykx, op=MPI.SUM) elif not self.is_transposed: raise NotImplementedError return E_kykx
[docs] def projection_perp(self, fx_fft, fy_fft): """Project (inplace) a vector perpendicular to the wavevector. The resulting vector is divergence-free. """ KX = self.KX KY = self.KY a = fx_fft - self.KX_over_K2 * (KX * fx_fft + KY * fy_fft) b = fy_fft - self.KY_over_K2 * (KX * fx_fft + KY * fy_fft) fx_fft[:] = a fy_fft[:] = b return a, b
[docs] @boost def rotfft_from_vecfft(self, vecx_fft: Ac, vecy_fft: Ac): """Return the rotational of a vector in spectral space.""" return 1j * (self.KX * vecy_fft - self.KY * vecx_fft)
[docs] @boost def divfft_from_vecfft(self, vecx_fft: Ac, vecy_fft: Ac): """Return the divergence of a vector in spectral space.""" return 1j * (self.KX * vecx_fft + self.KY * vecy_fft)
[docs] @boost def vecfft_from_rotfft(self, rot_fft: Ac): """Return the velocity in spectral space computed from the rotational.""" ux_fft = 1j * self.KY_over_K2 * rot_fft uy_fft = -1j * self.KX_over_K2 * rot_fft return ux_fft, uy_fft
[docs] @boost def vecfft_from_divfft(self, div_fft: Ac): """Return the velocity in spectral space computed from the divergence.""" ux_fft = -1j * self.KX_over_K2 * div_fft uy_fft = -1j * self.KY_over_K2 * div_fft return ux_fft, uy_fft
[docs] @boost def gradfft_from_fft(self, f_fft: Ac): """Return the gradient of f_fft in spectral space.""" px_f_fft = 1j * self.KX * f_fft py_f_fft = 1j * self.KY * f_fft return px_f_fft, py_f_fft
[docs] @boost def dealiasing_variable(self, f_fft: Ac): """Dealiasing a variable.""" if self._has_to_dealiase: for iK0 in range(self.nK0_loc): for iK1 in range(self.nK1_loc): if self.where_dealiased[iK0, iK1]: f_fft[iK0, iK1] = 0.0
def _get_shapeX(self, shape="loc"): if shape.lower() == "loc": return self.shapeX_loc elif shape.lower() == "seq": return self.shapeX_seq else: raise ValueError('shape should be "loc" or "seq"') def _get_shapeK(self, shape="loc"): if shape.lower() == "loc": return self.shapeK_loc elif shape.lower() == "seq": return self.shapeK_seq else: raise ValueError('shape should be "loc" or "seq"')
[docs] def create_arrayX(self, value=None, shape="loc"): """Return a constant array in real space.""" shapeX = self._get_shapeX(shape) return self.opfft.create_arrayX(value, shapeX)
[docs] def create_arrayK(self, value=None, shape="loc"): """Return a constant array in spectral space.""" shapeK = self._get_shapeK(shape) return self.opfft.create_arrayK(value, shapeK)
[docs] def create_arrayX_random(self, shape="loc", min_val=None, max_val=None): """Return a random array in real space.""" shape = self._get_shapeX(shape) np = self._numpy_api values = np.random.random(shape) return self._rescale_random(values, min_val, max_val)
[docs] def create_arrayK_random(self, shape="loc", min_val=None, max_val=None): """Return a random array in real space.""" shape = self._get_shapeK(shape) np = self._numpy_api values = np.random.random(shape) + 1j * np.random.random(shape) return self._rescale_random(values, min_val, max_val)