field.py 17.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
Theo Steininger's avatar
Theo Steininger committed
13
#
Martin Reinecke's avatar
Martin Reinecke committed
14
# Copyright(C) 2013-2018 Max-Planck-Society
Theo Steininger's avatar
Theo Steininger committed
15
16
17
#
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik
# and financially supported by the Studienstiftung des deutschen Volkes.
18

Martin Reinecke's avatar
Martin Reinecke committed
19
from __future__ import division
Martin Reinecke's avatar
Martin Reinecke committed
20
from builtins import range
csongor's avatar
csongor committed
21
import numpy as np
Martin Reinecke's avatar
Martin Reinecke committed
22
from . import utilities
Martin Reinecke's avatar
Martin Reinecke committed
23
from .domain_tuple import DomainTuple
Martin Reinecke's avatar
Martin Reinecke committed
24
from functools import reduce
25
from . import dobj
26

Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
27
28
__all__ = ["Field", "sqrt", "exp", "log", "conjugate"]

29

Martin Reinecke's avatar
Martin Reinecke committed
30
class Field(object):
Theo Steininger's avatar
Theo Steininger committed
31
32
    """ The discrete representation of a continuous field over multiple spaces.

Martin Reinecke's avatar
Martin Reinecke committed
33
    In NIFTy, Fields are used to store data arrays and carry all the needed
34
    metainformation (i.e. the domain) for operators to be able to work on them.
Theo Steininger's avatar
Theo Steininger committed
35

36
37
    Parameters
    ----------
Martin Reinecke's avatar
Martin Reinecke committed
38
    domain : None, DomainTuple, tuple(Domain), or Domain
Theo Steininger's avatar
Theo Steininger committed
39

Martin Reinecke's avatar
Martin Reinecke committed
40
    val : None, Field, data_object, or scalar
41
        The values the array should contain after init. A scalar input will
Martin Reinecke's avatar
Martin Reinecke committed
42
43
        fill the whole array with this scalar. If a data_object is provided,
        its dimensions must match the domain's.
Theo Steininger's avatar
Theo Steininger committed
44

45
    dtype : type
Martin Reinecke's avatar
updates    
Martin Reinecke committed
46
        A numpy.type. Most common are float and complex.
Theo Steininger's avatar
Theo Steininger committed
47

48
49
50
51
    copy: boolean

    Attributes
    ----------
Martin Reinecke's avatar
Martin Reinecke committed
52
    val : data_object
Theo Steininger's avatar
Theo Steininger committed
53

Martin Reinecke's avatar
Martin Reinecke committed
54
    domain : DomainTuple
Martin Reinecke's avatar
Martin Reinecke committed
55

56
57
58
    dtype : type
        Contains the datatype stored in the Field.
    """
59

Martin Reinecke's avatar
stage1    
Martin Reinecke committed
60
    def __init__(self, domain=None, val=None, dtype=None, copy=False):
Martin Reinecke's avatar
Martin Reinecke committed
61
        self._domain = self._infer_domain(domain=domain, val=val)
62

Martin Reinecke's avatar
Martin Reinecke committed
63
        dtype = self._infer_dtype(dtype=dtype, val=val)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
64
        if isinstance(val, Field):
Martin Reinecke's avatar
Martin Reinecke committed
65
            if self._domain != val._domain:
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
66
                raise ValueError("Domain mismatch")
67
            self._val = dobj.from_object(val.val, dtype=dtype, copy=copy)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
68
        elif (np.isscalar(val)):
Martin Reinecke's avatar
Martin Reinecke committed
69
            self._val = dobj.full(self._domain.shape, dtype=dtype,
70
                                  fill_value=val)
71
        elif isinstance(val, dobj.data_object):
Martin Reinecke's avatar
Martin Reinecke committed
72
            if self._domain.shape == val.shape:
73
                self._val = dobj.from_object(val, dtype=dtype, copy=copy)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
74
75
76
            else:
                raise ValueError("Shape mismatch")
        elif val is None:
Martin Reinecke's avatar
Martin Reinecke committed
77
            self._val = dobj.empty(self._domain.shape, dtype=dtype)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
78
79
        else:
            raise TypeError("unknown source type")
csongor's avatar
csongor committed
80

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
    @staticmethod
    def full(domain, val, dtype=None):
        if not np.isscalar(val):
            raise TypeError("val must be a scalar")
        return Field(DomainTuple.make(domain), val, dtype)

    @staticmethod
    def ones(domain, dtype=None):
        return Field(DomainTuple.make(domain), 1., dtype)

    @staticmethod
    def zeros(domain, dtype=None):
        return Field(DomainTuple.make(domain), 0., dtype)

    @staticmethod
    def empty(domain, dtype=None):
        return Field(DomainTuple.make(domain), None, dtype)

    @staticmethod
    def full_like(field, val, dtype=None):
        if not isinstance(field, Field):
            raise TypeError("field must be of Field type")
Martin Reinecke's avatar
Martin Reinecke committed
103
        return Field.full(field._domain, val, dtype)
104
105
106
107
108
109
110

    @staticmethod
    def zeros_like(field, dtype=None):
        if not isinstance(field, Field):
            raise TypeError("field must be of Field type")
        if dtype is None:
            dtype = field.dtype
Martin Reinecke's avatar
Martin Reinecke committed
111
        return Field.zeros(field._domain, dtype)
112
113
114
115
116
117
118

    @staticmethod
    def ones_like(field, dtype=None):
        if not isinstance(field, Field):
            raise TypeError("field must be of Field type")
        if dtype is None:
            dtype = field.dtype
Martin Reinecke's avatar
Martin Reinecke committed
119
        return Field.ones(field._domain, dtype)
120
121
122
123
124
125
126

    @staticmethod
    def empty_like(field, dtype=None):
        if not isinstance(field, Field):
            raise TypeError("field must be of Field type")
        if dtype is None:
            dtype = field.dtype
Martin Reinecke's avatar
Martin Reinecke committed
127
        return Field.empty(field._domain, dtype)
128

Martin Reinecke's avatar
Martin Reinecke committed
129
    @staticmethod
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
130
    def _infer_domain(domain, val=None):
131
        if domain is None:
132
            if isinstance(val, Field):
Martin Reinecke's avatar
Martin Reinecke committed
133
                return val._domain
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
134
            if np.isscalar(val):
135
                return DomainTuple.make(())  # empty domain tuple
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
136
            raise TypeError("could not infer domain from value")
Martin Reinecke's avatar
Martin Reinecke committed
137
        return DomainTuple.make(domain)
138

Martin Reinecke's avatar
Martin Reinecke committed
139
140
    @staticmethod
    def _infer_dtype(dtype, val):
Martin Reinecke's avatar
Martin Reinecke committed
141
142
143
144
        if dtype is not None:
            return dtype
        if val is None:
            raise ValueError("could not infer dtype")
Martin Reinecke's avatar
Martin Reinecke committed
145
146
        if isinstance(val, Field):
            return val.dtype
Martin Reinecke's avatar
Martin Reinecke committed
147
        return np.result_type(val)
148

Martin Reinecke's avatar
Martin Reinecke committed
149
150
    @staticmethod
    def from_random(random_type, domain, dtype=np.float64, **kwargs):
151
152
153
154
155
156
157
        """ Draws a random field with the given parameters.

        Parameters
        ----------
        random_type : String
            'pm1', 'normal', 'uniform' are the supported arguments for this
            method.
Theo Steininger's avatar
Theo Steininger committed
158

Martin Reinecke's avatar
Martin Reinecke committed
159
        domain : DomainTuple
160
            The domain of the output random field
Theo Steininger's avatar
Theo Steininger committed
161

162
163
        dtype : type
            The datatype of the output random field
Theo Steininger's avatar
Theo Steininger committed
164

165
166
167
168
169
        Returns
        -------
        out : Field
            The output object.
        """
Martin Reinecke's avatar
Martin Reinecke committed
170
        domain = DomainTuple.make(domain)
Martin Reinecke's avatar
Martin Reinecke committed
171
172
173
        return Field(domain=domain,
                     val=dobj.from_random(random_type, dtype=dtype,
                                          shape=domain.shape, **kwargs))
174

Martin Reinecke's avatar
Martin Reinecke committed
175
176
177
    def fill(self, fill_value):
        self._val.fill(fill_value)

Theo Steininger's avatar
Theo Steininger committed
178
179
    @property
    def val(self):
Martin Reinecke's avatar
stage1    
Martin Reinecke committed
180
        """ Returns the data object associated with this Field.
Martin Reinecke's avatar
PEP8    
Martin Reinecke committed
181
        No copy is made.
182
        """
Martin Reinecke's avatar
Martin Reinecke committed
183
        return self._val
csongor's avatar
csongor committed
184

Martin Reinecke's avatar
Martin Reinecke committed
185
186
187
188
    @property
    def dtype(self):
        return self._val.dtype

Martin Reinecke's avatar
Martin Reinecke committed
189
190
191
192
    @property
    def domain(self):
        return self._domain

193
194
    @property
    def shape(self):
Theo Steininger's avatar
Theo Steininger committed
195
        """ Returns the total shape of the Field's data array.
Theo Steininger's avatar
Theo Steininger committed
196

197
198
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
199
200
        Integer tuple containing the dimensions of the spaces in domain.
        """
Martin Reinecke's avatar
Martin Reinecke committed
201
        return self._domain.shape
csongor's avatar
csongor committed
202

203
    @property
Martin Reinecke's avatar
Martin Reinecke committed
204
    def size(self):
Theo Steininger's avatar
Theo Steininger committed
205
        """ Returns the total number of pixel-dimensions the field has.
Theo Steininger's avatar
Theo Steininger committed
206

Theo Steininger's avatar
Theo Steininger committed
207
        Effectively, all values from shape are multiplied.
Theo Steininger's avatar
Theo Steininger committed
208

209
210
211
212
213
        Returns
        -------
        out : int
            The dimension of the Field.
        """
Martin Reinecke's avatar
Martin Reinecke committed
214
        return self._domain.size
csongor's avatar
csongor committed
215

Theo Steininger's avatar
Theo Steininger committed
216
217
    @property
    def real(self):
Martin Reinecke's avatar
Martin Reinecke committed
218
        """ The real part of the field (data is not copied)."""
219
220
        if not np.issubdtype(self.dtype, np.complexfloating):
            raise ValueError(".real called on a non-complex Field")
Martin Reinecke's avatar
Martin Reinecke committed
221
        return Field(self._domain, self.val.real)
Theo Steininger's avatar
Theo Steininger committed
222
223
224

    @property
    def imag(self):
Martin Reinecke's avatar
Martin Reinecke committed
225
        """ The imaginary part of the field (data is not copied)."""
226
227
        if not np.issubdtype(self.dtype, np.complexfloating):
            raise ValueError(".imag called on a non-complex Field")
Martin Reinecke's avatar
Martin Reinecke committed
228
        return Field(self._domain, self.val.imag)
Theo Steininger's avatar
Theo Steininger committed
229

Martin Reinecke's avatar
Martin Reinecke committed
230
    def copy(self):
231
        """ Returns a full copy of the Field.
Theo Steininger's avatar
Theo Steininger committed
232

Martin Reinecke's avatar
Martin Reinecke committed
233
        The returned object will be an identical copy of the original Field.
Theo Steininger's avatar
Theo Steininger committed
234

235
236
237
238
239
        Returns
        -------
        out : Field
            The output object. An identical copy of 'self'.
        """
Martin Reinecke's avatar
Martin Reinecke committed
240
        return Field(val=self, copy=True)
csongor's avatar
csongor committed
241

242
243
    def scalar_weight(self, spaces=None):
        if np.isscalar(spaces):
Martin Reinecke's avatar
Martin Reinecke committed
244
            return self._domain[spaces].scalar_dvol()
245
246

        if spaces is None:
Martin Reinecke's avatar
Martin Reinecke committed
247
            spaces = range(len(self._domain))
Martin Reinecke's avatar
Martin Reinecke committed
248
        res = 1.
249
        for i in spaces:
Martin Reinecke's avatar
Martin Reinecke committed
250
            tmp = self._domain[i].scalar_dvol()
251
252
253
254
255
            if tmp is None:
                return None
            res *= tmp
        return res

Martin Reinecke's avatar
Martin Reinecke committed
256
257
258
259
260
261
262
263
264
265
266
    def total_volume(self, spaces=None):
        if np.isscalar(spaces):
            return self._domain[spaces].total_volume()

        if spaces is None:
            spaces = range(len(self._domain))
        res = 1.
        for i in spaces:
            res *= self._domain[i].total_volume()
        return res

267
    def weight(self, power=1, spaces=None, out=None):
Theo Steininger's avatar
Theo Steininger committed
268
        """ Weights the pixels of `self` with their invidual pixel-volume.
269
270
271
272

        Parameters
        ----------
        power : number
Theo Steininger's avatar
Theo Steininger committed
273
            The pixels get weighted with the volume-factor**power.
Theo Steininger's avatar
Theo Steininger committed
274

Theo Steininger's avatar
Theo Steininger committed
275
276
        spaces : tuple of ints
            Determines on which subspace the operation takes place.
Theo Steininger's avatar
Theo Steininger committed
277

278
279
280
281
282
        out : Field or None
            if not None, the result is returned in a new Field
            otherwise the contents of "out" are overwritten with the result.
            "out" may be identical to "self"!

283
284
285
        Returns
        -------
        out : Field
Theo Steininger's avatar
Theo Steininger committed
286
            The weighted field.
287
        """
288
289
290
291
292
        if out is None:
            out = self.copy()
        else:
            if out is not self:
                out.copy_content_from(self)
csongor's avatar
csongor committed
293

Martin Reinecke's avatar
Martin Reinecke committed
294
        spaces = utilities.parse_spaces(spaces, len(self._domain))
csongor's avatar
csongor committed
295

296
297
        fct = 1.
        for ind in spaces:
Martin Reinecke's avatar
Martin Reinecke committed
298
            wgt = self._domain[ind].dvol()
299
300
301
            if np.isscalar(wgt):
                fct *= wgt
            else:
Martin Reinecke's avatar
Martin Reinecke committed
302
                new_shape = np.ones(len(self.shape), dtype=np.int)
Martin Reinecke's avatar
Martin Reinecke committed
303
304
                new_shape[self._domain.axes[ind][0]:
                          self._domain.axes[ind][-1]+1] = wgt.shape
305
                wgt = wgt.reshape(new_shape)
Martin Reinecke's avatar
Martin Reinecke committed
306
307
                if dobj.distaxis(self._val) >= 0 and ind == 0:
                    # we need to distribute the weights along axis 0
Martin Reinecke's avatar
fixes    
Martin Reinecke committed
308
                    wgt = dobj.local_data(dobj.from_global_data(wgt))
309
310
                lout = dobj.local_data(out.val)
                lout *= wgt**power
311
        fct = fct**power
Martin Reinecke's avatar
Martin Reinecke committed
312
        if fct != 1.:
313
            out *= fct
314

315
        return out
csongor's avatar
csongor committed
316

Martin Reinecke's avatar
Martin Reinecke committed
317
    def vdot(self, x=None, spaces=None):
Theo Steininger's avatar
Theo Steininger committed
318
        """ Computes the volume-factor-aware dot product of 'self' with x.
Theo Steininger's avatar
Theo Steininger committed
319

320
321
322
        Parameters
        ----------
        x : Field
323
            x must live on the same domain as `self`.
Theo Steininger's avatar
Theo Steininger committed
324

325
326
327
        spaces : None, int or tuple of ints (default: None)
            The dot product is only carried out over the sub-domains in this
            tuple. If None, it is carried out over all sub-domains.
Theo Steininger's avatar
Theo Steininger committed
328

329
330
        Returns
        -------
331
332
        out : float, complex, either scalar (for full dot products)
                              or Field (for partial dot products)
333
        """
334
335
336
        if not isinstance(x, Field):
            raise ValueError("The dot-partner must be an instance of " +
                             "the NIFTy field class")
Theo Steininger's avatar
Theo Steininger committed
337

Martin Reinecke's avatar
Martin Reinecke committed
338
        if x._domain != self._domain:
339
            raise ValueError("Domain mismatch")
Theo Steininger's avatar
Theo Steininger committed
340

Martin Reinecke's avatar
Martin Reinecke committed
341
        ndom = len(self._domain)
342
343
344
        spaces = utilities.parse_spaces(spaces, ndom)

        if len(spaces) == ndom:
Martin Reinecke's avatar
Martin Reinecke committed
345
            return dobj.vdot(self.val, x.val)
346
347
        # If we arrive here, we have to do a partial dot product.
        # For the moment, do this the explicit, non-optimized way
Martin Reinecke's avatar
Martin Reinecke committed
348
        return (self.conjugate()*x).sum(spaces=spaces)
Theo Steininger's avatar
Theo Steininger committed
349

Theo Steininger's avatar
Theo Steininger committed
350
    def norm(self):
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
351
        """ Computes the L2-norm of the field values.
csongor's avatar
csongor committed
352

Theo Steininger's avatar
Theo Steininger committed
353
354
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
355
        norm : float
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
356
            The L2-norm of the field values.
csongor's avatar
csongor committed
357
        """
358
        return np.sqrt(np.abs(self.vdot(x=self)))
csongor's avatar
csongor committed
359

Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
360
    def conjugate(self):
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
361
        """ Returns the complex conjugate of the field.
Theo Steininger's avatar
Theo Steininger committed
362

363
364
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
365
        The complex conjugated field.
csongor's avatar
csongor committed
366
        """
Martin Reinecke's avatar
Martin Reinecke committed
367
        return Field(self._domain, self.val.conjugate())
csongor's avatar
csongor committed
368

Theo Steininger's avatar
Theo Steininger committed
369
    # ---General unary/contraction methods---
370

Theo Steininger's avatar
Theo Steininger committed
371
372
    def __pos__(self):
        return self.copy()
373

Theo Steininger's avatar
Theo Steininger committed
374
    def __neg__(self):
375
        return Field(self._domain, -self.val)
csongor's avatar
csongor committed
376

Theo Steininger's avatar
Theo Steininger committed
377
    def __abs__(self):
378
        return Field(self._domain, dobj.abs(self.val))
csongor's avatar
csongor committed
379

380
    def _contraction_helper(self, op, spaces):
Theo Steininger's avatar
Theo Steininger committed
381
        if spaces is None:
382
            return getattr(self.val, op)()
383

Martin Reinecke's avatar
Martin Reinecke committed
384
        spaces = utilities.parse_spaces(spaces, len(self._domain))
csongor's avatar
csongor committed
385

Martin Reinecke's avatar
Martin Reinecke committed
386
        axes_list = tuple(self._domain.axes[sp_index] for sp_index in spaces)
387

Martin Reinecke's avatar
Martin Reinecke committed
388
        if len(axes_list) > 0:
Theo Steininger's avatar
Theo Steininger committed
389
            axes_list = reduce(lambda x, y: x+y, axes_list)
csongor's avatar
csongor committed
390

Martin Reinecke's avatar
stage1    
Martin Reinecke committed
391
        # perform the contraction on the data
392
        data = getattr(self.val, op)(axis=axes_list)
csongor's avatar
csongor committed
393

Theo Steininger's avatar
Theo Steininger committed
394
395
396
        # check if the result is scalar or if a result_field must be constr.
        if np.isscalar(data):
            return data
csongor's avatar
csongor committed
397
        else:
Martin Reinecke's avatar
Martin Reinecke committed
398
            return_domain = tuple(dom
Martin Reinecke's avatar
Martin Reinecke committed
399
                                  for i, dom in enumerate(self._domain)
Theo Steininger's avatar
Theo Steininger committed
400
                                  if i not in spaces)
401

Martin Reinecke's avatar
updates    
Martin Reinecke committed
402
            return Field(domain=return_domain, val=data, copy=False)
csongor's avatar
csongor committed
403

404
405
    def sum(self, spaces=None):
        return self._contraction_helper('sum', spaces)
csongor's avatar
csongor committed
406

407
    def integrate(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
408
409
410
411
412
        swgt = self.scalar_weight(spaces)
        if swgt is not None:
            res = self.sum(spaces)
            res *= swgt
            return res
413
414
415
        tmp = self.weight(1, spaces=spaces)
        return tmp.sum(spaces)

416
417
    def prod(self, spaces=None):
        return self._contraction_helper('prod', spaces)
csongor's avatar
csongor committed
418

419
420
    def all(self, spaces=None):
        return self._contraction_helper('all', spaces)
csongor's avatar
csongor committed
421

422
423
    def any(self, spaces=None):
        return self._contraction_helper('any', spaces)
csongor's avatar
csongor committed
424

425
426
    def min(self, spaces=None):
        return self._contraction_helper('min', spaces)
csongor's avatar
csongor committed
427

428
429
    def max(self, spaces=None):
        return self._contraction_helper('max', spaces)
csongor's avatar
csongor committed
430

431
    def mean(self, spaces=None):
432
433
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('mean', spaces)
Martin Reinecke's avatar
Martin Reinecke committed
434
435
436
        # MR FIXME: not very efficient
        tmp = self.weight(1)
        return tmp.sum(spaces)*(1./tmp.total_volume(spaces))
csongor's avatar
csongor committed
437

438
    def var(self, spaces=None):
439
440
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('var', spaces)
Martin Reinecke's avatar
Martin Reinecke committed
441
442
443
444
445
446
447
448
449
        # MR FIXME: not very efficient or accurate
        m1 = self.mean(spaces)
        if np.issubdtype(self.dtype, np.complexfloating):
            sq = abs(self)**2
            m1 = abs(m1)**2
        else:
            sq = self**2
            m1 **= 2
        return sq.mean(spaces) - m1
csongor's avatar
csongor committed
450

451
    def std(self, spaces=None):
452
453
454
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('std', spaces)
        return sqrt(self.var(spaces))
csongor's avatar
csongor committed
455

456
457
458
    def copy_content_from(self, other):
        if not isinstance(other, Field):
            raise TypeError("argument must be a Field")
Martin Reinecke's avatar
Martin Reinecke committed
459
        if other._domain != self._domain:
460
            raise ValueError("domains are incompatible.")
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
461
        dobj.local_data(self.val)[()] = dobj.local_data(other.val)[()]
462

463
    def _binary_helper(self, other, op):
csongor's avatar
csongor committed
464
        # if other is a field, make sure that the domains match
465
        if isinstance(other, Field):
Martin Reinecke's avatar
Martin Reinecke committed
466
            if other._domain != self._domain:
467
                raise ValueError("domains are incompatible.")
Martin Reinecke's avatar
Martin Reinecke committed
468
            tval = getattr(self.val, op)(other.val)
Martin Reinecke's avatar
Martin Reinecke committed
469
            return self if tval is self.val else Field(self._domain, tval)
csongor's avatar
csongor committed
470

471
472
        if np.isscalar(other) or isinstance(other, dobj.data_object):
            tval = getattr(self.val, op)(other)
Martin Reinecke's avatar
Martin Reinecke committed
473
            return self if tval is self.val else Field(self._domain, tval)
474

Martin Reinecke's avatar
Martin Reinecke committed
475
        return NotImplemented
csongor's avatar
csongor committed
476
477

    def __add__(self, other):
Theo Steininger's avatar
Theo Steininger committed
478
        return self._binary_helper(other, op='__add__')
479

480
    def __radd__(self, other):
Theo Steininger's avatar
Theo Steininger committed
481
        return self._binary_helper(other, op='__radd__')
csongor's avatar
csongor committed
482
483

    def __iadd__(self, other):
484
        return self._binary_helper(other, op='__iadd__')
csongor's avatar
csongor committed
485
486

    def __sub__(self, other):
Theo Steininger's avatar
Theo Steininger committed
487
        return self._binary_helper(other, op='__sub__')
csongor's avatar
csongor committed
488
489

    def __rsub__(self, other):
Theo Steininger's avatar
Theo Steininger committed
490
        return self._binary_helper(other, op='__rsub__')
csongor's avatar
csongor committed
491
492

    def __isub__(self, other):
493
        return self._binary_helper(other, op='__isub__')
csongor's avatar
csongor committed
494
495

    def __mul__(self, other):
Theo Steininger's avatar
Theo Steininger committed
496
        return self._binary_helper(other, op='__mul__')
497

498
    def __rmul__(self, other):
Theo Steininger's avatar
Theo Steininger committed
499
        return self._binary_helper(other, op='__rmul__')
csongor's avatar
csongor committed
500
501

    def __imul__(self, other):
502
        return self._binary_helper(other, op='__imul__')
csongor's avatar
csongor committed
503
504

    def __div__(self, other):
Theo Steininger's avatar
Theo Steininger committed
505
        return self._binary_helper(other, op='__div__')
csongor's avatar
csongor committed
506

Martin Reinecke's avatar
Martin Reinecke committed
507
508
509
    def __truediv__(self, other):
        return self._binary_helper(other, op='__truediv__')

csongor's avatar
csongor committed
510
    def __rdiv__(self, other):
Theo Steininger's avatar
Theo Steininger committed
511
        return self._binary_helper(other, op='__rdiv__')
csongor's avatar
csongor committed
512

Martin Reinecke's avatar
Martin Reinecke committed
513
514
515
    def __rtruediv__(self, other):
        return self._binary_helper(other, op='__rtruediv__')

csongor's avatar
csongor committed
516
    def __idiv__(self, other):
517
        return self._binary_helper(other, op='__idiv__')
518

csongor's avatar
csongor committed
519
    def __pow__(self, other):
Theo Steininger's avatar
Theo Steininger committed
520
        return self._binary_helper(other, op='__pow__')
csongor's avatar
csongor committed
521
522

    def __rpow__(self, other):
Theo Steininger's avatar
Theo Steininger committed
523
        return self._binary_helper(other, op='__rpow__')
csongor's avatar
csongor committed
524
525

    def __ipow__(self, other):
526
        return self._binary_helper(other, op='__ipow__')
csongor's avatar
csongor committed
527

Theo Steininger's avatar
Theo Steininger committed
528
    def __repr__(self):
Martin Reinecke's avatar
Martin Reinecke committed
529
        return "<nifty4.Field>"
Theo Steininger's avatar
Theo Steininger committed
530
531
532
533

    def __str__(self):
        minmax = [self.min(), self.max()]
        mean = self.mean()
Martin Reinecke's avatar
Martin Reinecke committed
534
        return "nifty4.Field instance\n- domain      = " + \
535
               self._domain.__str__() + \
536
               "\n- val         = " + repr(self.val) + \
Theo Steininger's avatar
Theo Steininger committed
537
538
               "\n  - min.,max. = " + str(minmax) + \
               "\n  - mean = " + str(mean)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
539
540
541
542
543
544
545
546


# Arithmetic functions working on Fields

def _math_helper(x, function, out):
    if not isinstance(x, Field):
        raise TypeError("This function only accepts Field objects.")
    if out is not None:
Martin Reinecke's avatar
Martin Reinecke committed
547
        if not isinstance(out, Field) or x._domain != out._domain:
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
548
549
550
551
            raise ValueError("Bad 'out' argument")
        function(x.val, out=out.val)
        return out
    else:
Martin Reinecke's avatar
Martin Reinecke committed
552
        return Field(domain=x._domain, val=function(x.val))
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
553
554
555
556
557
558
559
560
561
562
563
564
565
566


def sqrt(x, out=None):
    return _math_helper(x, dobj.sqrt, out)


def exp(x, out=None):
    return _math_helper(x, dobj.exp, out)


def log(x, out=None):
    return _math_helper(x, dobj.log, out)


Martin Reinecke's avatar
Martin Reinecke committed
567
568
569
570
def tanh(x, out=None):
    return _math_helper(x, dobj.tanh, out)


Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
571
572
def conjugate(x, out=None):
    return _math_helper(x, dobj.conjugate, out)