field.py 17.8 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 of 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

Martin Reinecke's avatar
Martin Reinecke committed
48
    copy: bool
49
    """
50

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

Martin Reinecke's avatar
Martin Reinecke committed
54
        dtype = self._infer_dtype(dtype=dtype, val=val)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
55
        if isinstance(val, Field):
Martin Reinecke's avatar
Martin Reinecke committed
56
            if self._domain != val._domain:
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
57
                raise ValueError("Domain mismatch")
58
            self._val = dobj.from_object(val.val, dtype=dtype, copy=copy)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
59
        elif (np.isscalar(val)):
Martin Reinecke's avatar
Martin Reinecke committed
60
            self._val = dobj.full(self._domain.shape, dtype=dtype,
61
                                  fill_value=val)
62
        elif isinstance(val, dobj.data_object):
Martin Reinecke's avatar
Martin Reinecke committed
63
            if self._domain.shape == val.shape:
64
                self._val = dobj.from_object(val, dtype=dtype, copy=copy)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
65
66
67
            else:
                raise ValueError("Shape mismatch")
        elif val is None:
Martin Reinecke's avatar
Martin Reinecke committed
68
            self._val = dobj.empty(self._domain.shape, dtype=dtype)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
69
70
        else:
            raise TypeError("unknown source type")
csongor's avatar
csongor committed
71

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
    @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
94
        return Field.full(field._domain, val, dtype)
95
96
97
98
99
100
101

    @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
102
        return Field.zeros(field._domain, dtype)
103
104
105
106
107
108
109

    @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
110
        return Field.ones(field._domain, dtype)
111
112
113
114
115
116
117

    @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
118
        return Field.empty(field._domain, dtype)
119

Martin Reinecke's avatar
Martin Reinecke committed
120
    @staticmethod
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
121
    def _infer_domain(domain, val=None):
122
        if domain is None:
123
            if isinstance(val, Field):
Martin Reinecke's avatar
Martin Reinecke committed
124
                return val._domain
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
125
            if np.isscalar(val):
126
                return DomainTuple.make(())  # empty domain tuple
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
127
            raise TypeError("could not infer domain from value")
Martin Reinecke's avatar
Martin Reinecke committed
128
        return DomainTuple.make(domain)
129

Martin Reinecke's avatar
Martin Reinecke committed
130
131
    @staticmethod
    def _infer_dtype(dtype, val):
Martin Reinecke's avatar
Martin Reinecke committed
132
133
134
135
        if dtype is not None:
            return dtype
        if val is None:
            raise ValueError("could not infer dtype")
Martin Reinecke's avatar
Martin Reinecke committed
136
137
        if isinstance(val, Field):
            return val.dtype
Martin Reinecke's avatar
Martin Reinecke committed
138
        return np.result_type(val)
139

Martin Reinecke's avatar
Martin Reinecke committed
140
141
    @staticmethod
    def from_random(random_type, domain, dtype=np.float64, **kwargs):
142
143
144
145
        """ Draws a random field with the given parameters.

        Parameters
        ----------
Martin Reinecke's avatar
Martin Reinecke committed
146
        random_type : str
147
148
            'pm1', 'normal', 'uniform' are the supported arguments for this
            method.
Theo Steininger's avatar
Theo Steininger committed
149

Martin Reinecke's avatar
Martin Reinecke committed
150
        domain : DomainTuple
151
            The domain of the output random field
Theo Steininger's avatar
Theo Steininger committed
152

153
154
        dtype : type
            The datatype of the output random field
Theo Steininger's avatar
Theo Steininger committed
155

156
157
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
158
        Field
159
160
            The output object.
        """
Martin Reinecke's avatar
Martin Reinecke committed
161
        domain = DomainTuple.make(domain)
Martin Reinecke's avatar
Martin Reinecke committed
162
163
164
        return Field(domain=domain,
                     val=dobj.from_random(random_type, dtype=dtype,
                                          shape=domain.shape, **kwargs))
165

Martin Reinecke's avatar
Martin Reinecke committed
166
167
168
    def fill(self, fill_value):
        self._val.fill(fill_value)

Martin Reinecke's avatar
Martin Reinecke committed
169
170
171
172
173
174
175
    def lock(self):
        dobj.lock(self._val)

    @property
    def locked(self):
        return dobj.locked(self._val)

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

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

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

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

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

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

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

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

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

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

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

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

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

Martin Reinecke's avatar
Martin Reinecke committed
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
    def locked_copy(self):
        """ Returns a read-only version of the Field.

        If `self` is locked, returns `self`. Otherwise returns a locked copy
        of `self`.

        Returns
        -------
        Field
            A read-only version of 'self'.
        """
        if self.locked:
            return self
        res = Field(val=self, copy=True)
        res.lock()
        return res

258
259
    def scalar_weight(self, spaces=None):
        if np.isscalar(spaces):
Martin Reinecke's avatar
Martin Reinecke committed
260
            return self._domain[spaces].scalar_dvol
261
262

        if spaces is None:
Martin Reinecke's avatar
Martin Reinecke committed
263
            spaces = range(len(self._domain))
Martin Reinecke's avatar
Martin Reinecke committed
264
        res = 1.
265
        for i in spaces:
Martin Reinecke's avatar
Martin Reinecke committed
266
            tmp = self._domain[i].scalar_dvol
267
268
269
270
271
            if tmp is None:
                return None
            res *= tmp
        return res

Martin Reinecke's avatar
Martin Reinecke committed
272
273
    def total_volume(self, spaces=None):
        if np.isscalar(spaces):
Martin Reinecke's avatar
Martin Reinecke committed
274
            return self._domain[spaces].total_volume
Martin Reinecke's avatar
Martin Reinecke committed
275
276
277
278
279

        if spaces is None:
            spaces = range(len(self._domain))
        res = 1.
        for i in spaces:
Martin Reinecke's avatar
Martin Reinecke committed
280
            res *= self._domain[i].total_volume
Martin Reinecke's avatar
Martin Reinecke committed
281
282
        return res

283
    def weight(self, power=1, spaces=None, out=None):
Theo Steininger's avatar
Theo Steininger committed
284
        """ Weights the pixels of `self` with their invidual pixel-volume.
285
286
287
288

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

Martin Reinecke's avatar
Martin Reinecke committed
291
        spaces : int or tuple of int
Theo Steininger's avatar
Theo Steininger committed
292
            Determines on which subspace the operation takes place.
Theo Steininger's avatar
Theo Steininger committed
293

294
295
296
297
298
        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"!

299
300
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
301
        Field
Theo Steininger's avatar
Theo Steininger committed
302
            The weighted field.
303
        """
304
305
306
307
308
        if out is None:
            out = self.copy()
        else:
            if out is not self:
                out.copy_content_from(self)
csongor's avatar
csongor committed
309

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

312
313
        fct = 1.
        for ind in spaces:
Martin Reinecke's avatar
Martin Reinecke committed
314
            wgt = self._domain[ind].dvol
315
316
317
            if np.isscalar(wgt):
                fct *= wgt
            else:
Martin Reinecke's avatar
Martin Reinecke committed
318
                new_shape = np.ones(len(self.shape), dtype=np.int)
Martin Reinecke's avatar
Martin Reinecke committed
319
320
                new_shape[self._domain.axes[ind][0]:
                          self._domain.axes[ind][-1]+1] = wgt.shape
321
                wgt = wgt.reshape(new_shape)
Martin Reinecke's avatar
Martin Reinecke committed
322
323
                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
324
                    wgt = dobj.local_data(dobj.from_global_data(wgt))
325
326
                lout = dobj.local_data(out.val)
                lout *= wgt**power
327
        fct = fct**power
Martin Reinecke's avatar
Martin Reinecke committed
328
        if fct != 1.:
329
            out *= fct
330

331
        return out
csongor's avatar
csongor committed
332

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

336
337
338
        Parameters
        ----------
        x : Field
339
            x must live on the same domain as `self`.
Theo Steininger's avatar
Theo Steininger committed
340

Martin Reinecke's avatar
Martin Reinecke committed
341
        spaces : None, int or tuple of int (default: None)
342
343
            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
344

345
346
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
347
        float, complex, either scalar (for full dot products)
348
                              or Field (for partial dot products)
349
        """
350
351
352
        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
353

Martin Reinecke's avatar
Martin Reinecke committed
354
        if x._domain != self._domain:
355
            raise ValueError("Domain mismatch")
Theo Steininger's avatar
Theo Steininger committed
356

Martin Reinecke's avatar
Martin Reinecke committed
357
        ndom = len(self._domain)
358
359
360
        spaces = utilities.parse_spaces(spaces, ndom)

        if len(spaces) == ndom:
Martin Reinecke's avatar
Martin Reinecke committed
361
            return dobj.vdot(self.val, x.val)
362
363
        # 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
364
        return (self.conjugate()*x).sum(spaces=spaces)
Theo Steininger's avatar
Theo Steininger committed
365

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

Theo Steininger's avatar
Theo Steininger committed
369
370
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
371
        float
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
372
            The L2-norm of the field values.
csongor's avatar
csongor committed
373
        """
374
        return np.sqrt(np.abs(self.vdot(x=self)))
csongor's avatar
csongor committed
375

Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
376
    def conjugate(self):
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
377
        """ Returns the complex conjugate of the field.
Theo Steininger's avatar
Theo Steininger committed
378

379
380
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
381
382
        Field
            The complex conjugated field.
csongor's avatar
csongor committed
383
        """
Martin Reinecke's avatar
Martin Reinecke committed
384
        return Field(self._domain, self.val.conjugate())
csongor's avatar
csongor committed
385

Theo Steininger's avatar
Theo Steininger committed
386
    # ---General unary/contraction methods---
387

Theo Steininger's avatar
Theo Steininger committed
388
389
    def __pos__(self):
        return self.copy()
390

Theo Steininger's avatar
Theo Steininger committed
391
    def __neg__(self):
392
        return Field(self._domain, -self.val)
csongor's avatar
csongor committed
393

Theo Steininger's avatar
Theo Steininger committed
394
    def __abs__(self):
395
        return Field(self._domain, dobj.abs(self.val))
csongor's avatar
csongor committed
396

397
    def _contraction_helper(self, op, spaces):
Theo Steininger's avatar
Theo Steininger committed
398
        if spaces is None:
399
            return getattr(self.val, op)()
400

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

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

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

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

Theo Steininger's avatar
Theo Steininger committed
411
412
413
        # 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
414
        else:
Martin Reinecke's avatar
Martin Reinecke committed
415
            return_domain = tuple(dom
Martin Reinecke's avatar
Martin Reinecke committed
416
                                  for i, dom in enumerate(self._domain)
Theo Steininger's avatar
Theo Steininger committed
417
                                  if i not in spaces)
418

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

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

424
    def integrate(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
425
426
427
428
429
        swgt = self.scalar_weight(spaces)
        if swgt is not None:
            res = self.sum(spaces)
            res *= swgt
            return res
430
431
432
        tmp = self.weight(1, spaces=spaces)
        return tmp.sum(spaces)

433
434
    def prod(self, spaces=None):
        return self._contraction_helper('prod', spaces)
csongor's avatar
csongor committed
435

436
437
    def all(self, spaces=None):
        return self._contraction_helper('all', spaces)
csongor's avatar
csongor committed
438

439
440
    def any(self, spaces=None):
        return self._contraction_helper('any', spaces)
csongor's avatar
csongor committed
441

442
443
    def min(self, spaces=None):
        return self._contraction_helper('min', spaces)
csongor's avatar
csongor committed
444

445
446
    def max(self, spaces=None):
        return self._contraction_helper('max', spaces)
csongor's avatar
csongor committed
447

448
    def mean(self, spaces=None):
449
450
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('mean', spaces)
Martin Reinecke's avatar
Martin Reinecke committed
451
452
453
        # MR FIXME: not very efficient
        tmp = self.weight(1)
        return tmp.sum(spaces)*(1./tmp.total_volume(spaces))
csongor's avatar
csongor committed
454

455
    def var(self, spaces=None):
456
457
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('var', spaces)
Martin Reinecke's avatar
Martin Reinecke committed
458
459
460
461
462
463
464
465
466
        # 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
467

468
    def std(self, spaces=None):
469
470
471
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('std', spaces)
        return sqrt(self.var(spaces))
csongor's avatar
csongor committed
472

473
474
475
    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
476
        if other._domain != self._domain:
477
            raise ValueError("domains are incompatible.")
Martin Reinecke's avatar
tweaks    
Martin Reinecke committed
478
        dobj.local_data(self.val)[()] = dobj.local_data(other.val)[()]
479

480
    def _binary_helper(self, other, op):
csongor's avatar
csongor committed
481
        # if other is a field, make sure that the domains match
482
        if isinstance(other, Field):
Martin Reinecke's avatar
Martin Reinecke committed
483
            if other._domain != self._domain:
484
                raise ValueError("domains are incompatible.")
Martin Reinecke's avatar
Martin Reinecke committed
485
            tval = getattr(self.val, op)(other.val)
Martin Reinecke's avatar
Martin Reinecke committed
486
            return self if tval is self.val else Field(self._domain, tval)
csongor's avatar
csongor committed
487

488
489
        if np.isscalar(other) or isinstance(other, dobj.data_object):
            tval = getattr(self.val, op)(other)
Martin Reinecke's avatar
Martin Reinecke committed
490
            return self if tval is self.val else Field(self._domain, tval)
491

Martin Reinecke's avatar
Martin Reinecke committed
492
        return NotImplemented
csongor's avatar
csongor committed
493
494

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

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

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

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

    def __rsub__(self, other):
Theo Steininger's avatar
Theo Steininger committed
507
        return self._binary_helper(other, op='__rsub__')
csongor's avatar
csongor committed
508
509

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

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

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

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

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

Martin Reinecke's avatar
Martin Reinecke committed
524
525
526
    def __truediv__(self, other):
        return self._binary_helper(other, op='__truediv__')

csongor's avatar
csongor committed
527
    def __rdiv__(self, other):
Theo Steininger's avatar
Theo Steininger committed
528
        return self._binary_helper(other, op='__rdiv__')
csongor's avatar
csongor committed
529

Martin Reinecke's avatar
Martin Reinecke committed
530
531
532
    def __rtruediv__(self, other):
        return self._binary_helper(other, op='__rtruediv__')

csongor's avatar
csongor committed
533
    def __idiv__(self, other):
534
        return self._binary_helper(other, op='__idiv__')
535

csongor's avatar
csongor committed
536
    def __pow__(self, other):
Theo Steininger's avatar
Theo Steininger committed
537
        return self._binary_helper(other, op='__pow__')
csongor's avatar
csongor committed
538
539

    def __rpow__(self, other):
Theo Steininger's avatar
Theo Steininger committed
540
        return self._binary_helper(other, op='__rpow__')
csongor's avatar
csongor committed
541
542

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

Theo Steininger's avatar
Theo Steininger committed
545
    def __repr__(self):
Martin Reinecke's avatar
Martin Reinecke committed
546
        return "<nifty4.Field>"
Theo Steininger's avatar
Theo Steininger committed
547
548

    def __str__(self):
Martin Reinecke's avatar
Martin Reinecke committed
549
        return "nifty4.Field instance\n- domain      = " + \
550
               self._domain.__str__() + \
551
               "\n- val         = " + repr(self.val)
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
552
553
554
555
556
557
558
559


# 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
560
        if not isinstance(out, Field) or x._domain != out._domain:
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
561
562
563
564
            raise ValueError("Bad 'out' argument")
        function(x.val, out=out.val)
        return out
    else:
Martin Reinecke's avatar
Martin Reinecke committed
565
        return Field(domain=x._domain, val=function(x.val))
Martin Reinecke's avatar
cleanup    
Martin Reinecke committed
566
567
568
569
570
571
572
573
574
575
576
577
578
579


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
580
581
582
583
def tanh(x, out=None):
    return _math_helper(x, dobj.tanh, out)


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