Commit 88ed077d authored by csongor's avatar csongor
Browse files

WIP: move field class to new file

parent c3a33c67
Pipeline #3923 skipped
......@@ -38,9 +38,9 @@ from config import about,\
from d2o import distributed_data_object, d2o_librarian
from nifty_cmaps import ncmap
from nifty_field import field
from nifty_core import space,\
point_space,\
field
point_space
from nifty_random import random
from nifty_simple_math import *
......
......@@ -42,8 +42,10 @@ from matplotlib.ticker import LogFormatter as lf
from d2o import STRATEGIES as DISTRIBUTION_STRATEGIES
from nifty.nifty_core import space,\
point_space,\
field
point_space
from nifty.nifty_field import field
from nifty.config import about,\
nifty_configuration as gc,\
dependency_injector as gdi
......
......@@ -23,7 +23,7 @@
import numpy as np
from numpy import pi
from nifty.config import about
from nifty.nifty_core import field
from nifty.nifty_field import field
from nifty.nifty_simple_math import sqrt, exp, log
......
......@@ -150,12 +150,9 @@ from d2o import distributed_data_object,\
from nifty_paradict import space_paradict,\
point_space_paradict
from nifty.config import about,\
nifty_configuration as gc,\
dependency_injector as gdi
from nifty.config import about
from nifty_random import random
import nifty.nifty_utilities as utilities
POINT_DISTRIBUTION_STRATEGIES = DISTRIBUTION_STRATEGIES['global']
......@@ -1597,1312 +1594,3 @@ class point_space(space):
string += 'discrete: ' + str(self.discrete) + "\n"
string += 'distances: ' + str(self.distances) + "\n"
return string
class field(object):
"""
.. ____ __ __ __
.. / _/ /__/ / / / /
.. / /_ __ _______ / / ____/ /
.. / _/ / / / __ / / / / _ /
.. / / / / / /____/ / /_ / /_/ /
.. /__/ /__/ \______/ \___/ \______| class
Basic NIFTy class for fields.
Parameters
----------
domain : space
The space wherein valid arguments live.
val : {scalar, ndarray}, *optional*
Defines field values, either to be given by a number interpreted
as a constant array, or as an arbitrary array consistent with the
space defined in domain or to be drawn from a random distribution
controlled by kwargs.
codomain : space, *optional*
The space wherein the operator output lives (default: domain).
Other Parameters
----------------
random : string
Indicates that the field values should be drawn from a certain
distribution using a pseudo-random number generator.
Supported distributions are:
- "pm1" (uniform distribution over {+1,-1} or {+1,+i,-1,-i}
- "gau" (normal distribution with zero-mean and a given standard
deviation or variance)
- "syn" (synthesizes from a given power spectrum)
- "uni" (uniform distribution over [vmin,vmax[)
dev : scalar
Sets the standard deviation of the Gaussian distribution
(default=1).
var : scalar
Sets the variance of the Gaussian distribution, outranking the dev
parameter (default=1).
spec : {scalar, list, array, field, function}
Specifies a power spectrum from which the field values should be
synthesized (default=1). Can be given as a constant, or as an
array with indvidual entries per mode.
log : bool
Flag specifying if the spectral binning is performed on logarithmic
scale or not; if set, the number of used bins is set
automatically (if not given otherwise); by default no binning
is done (default: None).
nbin : integer
Number of used spectral bins; if given `log` is set to ``False``;
integers below the minimum of 3 induce an automatic setting;
by default no binning is done (default: None).
binbounds : {list, array}
User specific inner boundaries of the bins, which are preferred
over the above parameters; by default no binning is done
(default: None).
vmin : scalar
Sets the lower limit for the uniform distribution.
vmax : scalar
Sets the upper limit for the uniform distribution.
Attributes
----------
domain : space
The space wherein valid arguments live.
val : {scalar, ndarray}, *optional*
Defines field values, either to be given by a number interpreted
as a constant array, or as an arbitrary array consistent with the
space defined in domain or to be drawn from a random distribution
controlled by the keyword arguments.
codomain : space, *optional*
The space wherein the operator output lives (default: domain).
"""
def __init__(self, domain=None, val=None, codomain=None, comm=gc[
'default_comm'], copy=False, dtype=np.dtype('float64'), datamodel='not',
**kwargs):
"""
Sets the attributes for a field class instance.
Parameters
----------
domain : space
The space wherein valid arguments live.
val : {scalar,ndarray}, *optional*
Defines field values, either to be given by a number interpreted
as a constant array, or as an arbitrary array consistent with the
space defined in domain or to be drawn from a random distribution
controlled by the keyword arguments.
codomain : space, *optional*
The space wherein the operator output lives (default: domain).
Returns
-------
Nothing
"""
# If the given val was a field, try to cast it accordingly to the given
# domain and codomain, etc...
if isinstance(val, field):
self._init_from_field(f=val,
domain=domain,
codomain=codomain,
comm=comm,
copy=copy,
dtype=dtype,
datamodel=datamodel,
**kwargs)
else:
self._init_from_array(val=val,
domain=domain,
codomain=codomain,
comm=comm,
copy=copy,
dtype=dtype,
datamodel=datamodel,
**kwargs)
def _init_from_field(self, f, domain, codomain, comm, copy, dtype,
datamodel,
**kwargs):
# check domain
if domain is None:
domain = f.domain
# check codomain
if codomain is None:
if self.check_codomain(domain, f.codomain):
codomain = f.codomain
else:
codomain = self.get_codomain(domain)
# Check if the given field lives in a space which is compatible to the
# given domain
if f.domain != domain:
# Try to transform the given field to the given domain/codomain
f = f.transform(new_domain=domain,
new_codomain=codomain)
self._init_from_array(domain=domain,
val=f.val,
codomain=codomain,
comm=comm,
copy=copy,
dtype=dtype,
datamodel=datamodel,
**kwargs)
def _init_from_array(self, val, domain, codomain, comm, copy, dtype,
datamodel, **kwargs):
if dtype is None:
dtype = np.dtype('float64')
self.dtype = dtype
self.comm = self._parse_comm(comm)
if datamodel not in DISTRIBUTION_STRATEGIES['global']:
about.warnings.cprint("WARNING: datamodel set to default.")
self.datamodel = \
gc['default_distribution_strategy']
else:
self.datamodel = datamodel
# check domain
self.domain = self.check_valid_domain(domain=domain)
# check codomain
if codomain is None:
codomain = self.get_codomain(domain)
elif not self.check_codomain(domain=domain, codomain=codomain):
raise ValueError(about._errors.cstring(
"ERROR: The given codomain is not compatible to the domain."))
self.codomain = codomain
if val is None:
if kwargs == {}:
val = self._map(lambda: self.cast((0,)))
else:
val = self._map(lambda: self.domain.get_random_values(
codomain=self.codomain,
**kwargs))
self.set_val(new_val=val, copy=copy)
def _parse_comm(self, comm):
# check if comm is a string -> the name of comm is given
# -> Extract it from the mpi_module
if isinstance(comm, str):
if gc.validQ('default_comm', comm):
result_comm = getattr(gdi[gc['mpi_module']], comm)
else:
raise ValueError(about._errors.cstring(
"ERROR: The given communicator-name is not supported."))
# check if the given comm object is an instance of default Intracomm
else:
if isinstance(comm, gdi[gc['mpi_module']].Intracomm):
result_comm = comm
else:
raise ValueError(about._errors.cstring(
"ERROR: The given comm object is not an instance of the " +
"default-MPI-module's Intracomm Class."))
return result_comm
def check_valid_domain(self, domain):
if not isinstance(domain, np.ndarray):
raise TypeError(about._errors.cstring(
"ERROR: The given domain is not a list."))
for d in domain:
if not isinstance(d, space):
raise TypeError(about._errors.cstring(
"ERROR: Given domain is not a space."))
elif d.dtype != self.dtype:
raise AttributeError(about._errors.cstring(
"ERROR: The dtype of a space in the domain missmatches "
"the field's dtype."))
return domain
def check_codomain(self, domain, codomain):
if codomain is None:
return False
if domain.shape == codomain.shape:
return np.all(map((lambda d, c: d._check_codomain(c)), domain,
codomain))
else:
return False
def get_codomain(self, domain):
if domain.shape == (1,):
return np.array(domain[0].get_codomain())
else:
# TODO implement for multiple domain get_codomain need
# calc_transform
return np.empty((0,))
def __len__(self):
return int(self.get_dim(split=True)[0])
def apply_scalar_function(self, function, inplace=False):
if inplace:
working_field = self
else:
working_field = self.copy_empty()
data_object = self._map(
lambda z: self.domain.apply_scalar_function(z, function, inplace),
self.get_val())
working_field.set_val(data_object)
return working_field
def copy(self, domain=None, codomain=None):
copied_val = self._map(
lambda z: self.domain.unary_operation(z, op='copy'),
self.get_val())
new_field = self.copy_empty(domain=domain, codomain=codomain)
new_field.set_val(new_val=copied_val)
return new_field
def _fast_copy_empty(self):
# make an empty field
new_field = EmptyField()
# repair its class
new_field.__class__ = self.__class__
# copy domain, codomain, ishape and val
for key, value in self.__dict__.items():
if key != 'val':
new_field.__dict__[key] = value
else:
new_field.__dict__[key] = \
self.domain.unary_operation(self.val, op='copy_empty')
return new_field
def copy_empty(self, domain=None, codomain=None, dtype=None, comm=None,
datamodel=None, **kwargs):
if domain is None:
domain = self.domain
if codomain is None:
codomain = self.codomain
if dtype is None:
dtype = self.dtype
if comm is None:
comm = self.comm
if datamodel is None:
datamodel = self.datamodel
if (domain is self.domain and
codomain is self.codomain and
dtype == self.dtype and
comm == self.comm and
datamodel == self.datamodel and
kwargs == {}):
new_field = self._fast_copy_empty()
else:
new_field = field(domain=domain, codomain=codomain, dtype=dtype,
comm=comm, datamodel=datamodel, **kwargs)
return new_field
def set_val(self, new_val=None, copy=False):
"""
Resets the field values.
Parameters
----------
new_val : {scalar, ndarray}
New field values either as a constant or an arbitrary array.
"""
if new_val is not None:
if copy:
new_val = self._map(
lambda z: self.domain.unary_operation(z, 'copy'),
new_val)
self.val = self._map(lambda z: self.domain.cast(z), new_val)
return self.val
def get_val(self):
return self.val
# TODO: Add functionality for boolean indexing.
def __getitem__(self, key):
if np.isscalar(key) == True or isinstance(key, slice):
key = (key, )
if self.ishape == ():
return self.domain.getitem(self.get_val(), key)
else:
gotten = self.get_val()[key[:len(self.ishape)]]
try:
is_data_container = (gotten.dtype.type == np.object_)
except(AttributeError):
is_data_container = False
if len(key) > len(self.ishape):
if is_data_container:
gotten = self._map(
lambda z: self.domain.getitem(
z, key[len(self.ishape):]),
gotten)
else:
gotten = self.domain.getitem(gotten,
key[len(self.ishape):])
return gotten
def __setitem__(self, key, value):
if np.isscalar(key) or isinstance(key, slice):
key = (key, )
if self.ishape == ():
return self.domain.setitem(self.get_val(), value, key)
else:
if len(key) > len(self.ishape):
gotten = self.get_val()[key[:len(self.ishape)]]
try:
is_data_container = (gotten.dtype.type == np.object_)
except(AttributeError):
is_data_container = False
if is_data_container:
gotten = self._map(
lambda z1, z2: self.domain.setitem(
z1, z2, key[len(self.ishape):]),
gotten, value)
else:
gotten = self.domain.setitem(gotten, value,
key[len(self.ishape):])
else:
dummy = np.empty(self.ishape)
gotten = self.val.__setitem__(key, self.cast(
value, ishape=np.shape(dummy[key])))
return gotten
def get_shape(self):
if len(self.domain) > 1:
global_shape = reduce(lambda x, y: x.get_shape()+y.get_shape(),
self.domain)
else:
global_shape = self.domain[0].get_shape()
if isinstance(global_shape, tuple):
return global_shape
else:
return ()
def get_dim(self, split=False):
"""
Computes the (array) dimension of the underlying space.
Parameters
----------
split : bool
Sets the output to be either split up per axis or
in form of total number of field entries in all
dimensions (default=False)
Returns
-------
dim : {scalar, ndarray}
Dimension of space.
"""
return np.prod(self.get_shape())
def get_dof(self, split=False):
dim = self.get_dim()
if np.issubdtype(self.dtype, np.complex):
return 2*dim
else:
return dim
def _map(self, function, *args):
return utilities.field_map(self.get_shape(), function, *args)
def cast(self, x=None, dtype=None):
if dtype is not None:
dtype = np.dtype(dtype)
if dtype is None:
dtype = self.dtype
casted_x = self._cast_to_d2o(x, dtype=dtype)
return self._complement_cast(casted_x)
def _cast_to_d2o(self, x, dtype=None, shape=None, **kwargs):
"""
Computes valid field values from a given object, trying
to translate the given data into a valid form. Thereby it is as
benevolent as possible.
Parameters
----------
x : {float, numpy.ndarray, nifty.field}
Object to be transformed into an array of valid field values.
Returns
-------
x : numpy.ndarray, distributed_data_object
Array containing the field values, which are compatible to the
space.
Other parameters
----------------
verbose : bool, *optional*
Whether the method should raise a warning if information is
lost during casting (default: False).
"""
if isinstance(x, field):
x = x.get_val()
if dtype is None:
dtype = self.dtype
if shape is None:
shape = self.get_shape()
# Case 1: x is a distributed_data_object
if isinstance(x, distributed_data_object):
to_copy = False
# Check the shape
if np.any(np.array(x.shape) != np.array(shape)):
# Check if at least the number of degrees of freedom is equal
if x.get_dim() == self.get_dim():
try:
temp = x.copy_empty(global_shape=shape)
temp.set_local_data(x, copy=False)
except:
# If the number of dof is equal or 1, use np.reshape...
about.warnings.cflush(
"WARNING: Trying to reshape the data. This " +
"operation is expensive as it consolidates the " +
"full data!\n")
temp = x
temp = np.reshape(temp, shape)
# ... and cast again
return self._cast_to_d2o(temp,
dtype=dtype,
**kwargs)
else:
raise ValueError(about._errors.cstring(
"ERROR: Data has incompatible shape!"))
# Check the dtype
if x.dtype != dtype:
if x.dtype > dtype:
about.warnings.cflush(
"WARNING: Datatypes are of conflicting precision " +
"(own: " + str(dtype) + " <> foreign: " +
str(x.dtype) + ") and will be casted! Potential " +
"loss of precision!\n")
to_copy = True
# Check the distribution_strategy
if x.distribution_strategy != self.datamodel:
to_copy = True
if to_copy:
temp = x.copy_empty(dtype=dtype,
distribution_strategy=self.datamodel)
temp.set_data(to_key=(slice(None),),
data=x,
from_key=(slice(None),))
temp.hermitian = x.hermitian
x = temp
return x
# Case 2: x is something else
# Use general d2o casting
else:
x = distributed_data_object(x,
global_shape=self.get_shape(),
dtype=dtype,
distribution_strategy=self.datamodel)
# Cast the d2o
return self.cast(x, dtype=dtype)
def _complement_cast(self, x):
#TODO implement complement cast for multiple spaces.
return x
def set_domain(self, new_domain=None, force=False):
"""
Resets the codomain of the field.
Parameters
----------
new_codomain : space
The new space wherein the transform of the field should live.
(default=None).
"""
# check codomain
if new_domain is None:
new_domain = self.codomain.get_codomain()
elif not force:
assert(self.codomain.check_codomain(new_domain))
self.domain = new_domain
return self.domain
def set_codomain(self, new_codomain=None, force=False):
"""
Resets the codomain of the field.
Parameters
----------
new_codomain : space
The new space wherein the transform of the field should live.
(default=None).
"""
# check codomain
if new_codomain is None:
new_codomain = self.domain.get_codomain()
elif not force:
assert(self.domain.check_codomain(new_codomain))