field.py 21.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
#
14
# Copyright(C) 2013-2019 Max-Planck-Society
Theo Steininger's avatar
Theo Steininger committed
15
#
16
# NIFTy is being developed at the Max-Planck-Institut fuer Astrophysik.
17

Martin Reinecke's avatar
Martin Reinecke committed
18
from functools import reduce
csongor's avatar
csongor committed
19
import numpy as np
20
21

from . import dobj, utilities
Martin Reinecke's avatar
Martin Reinecke committed
22
from .domain_tuple import DomainTuple
23

24

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

28
    Stores data arrays and carries all the needed meta-information (i.e. the
29
    domain) for operators to be able to operate on them.
Theo Steininger's avatar
Theo Steininger committed
30

31
32
    Parameters
    ----------
33
    domain : DomainTuple
Philipp Arras's avatar
Docs  
Philipp Arras committed
34
        The domain of the new Field.
35
36
37
    val : data_object
        This object's global shape must match the domain shape
        After construction, the object will no longer be writeable!
Martin Reinecke's avatar
Martin Reinecke committed
38

Martin Reinecke's avatar
Martin Reinecke committed
39
40
    Notes
    -----
Martin Reinecke's avatar
Martin Reinecke committed
41
    If possible, do not invoke the constructor directly, but use one of the
42
    many convenience functions for instantiation!
43
    """
44

45
46
    _scalar_dom = DomainTuple.scalar_domain()

47
48
49
    def __init__(self, domain, val):
        if not isinstance(domain, DomainTuple):
            raise TypeError("domain must be of type DomainTuple")
Martin Reinecke's avatar
Martin Reinecke committed
50
        if type(val) is not dobj.data_object:
Martin Reinecke's avatar
Martin Reinecke committed
51
            if np.isscalar(val):
Martin Reinecke's avatar
Martin Reinecke committed
52
                val = dobj.full(domain.shape, val)
Martin Reinecke's avatar
Martin Reinecke committed
53
54
            else:
                raise TypeError("val must be of type dobj.data_object")
55
        if domain.shape != val.shape:
Martin Reinecke's avatar
Martin Reinecke committed
56
            raise ValueError("shape mismatch between val and domain")
57
58
        self._domain = domain
        self._val = val
59
        dobj.lock(self._val)
Martin Reinecke's avatar
Martin Reinecke committed
60

Martin Reinecke's avatar
Martin Reinecke committed
61
62
63
64
    @staticmethod
    def scalar(val):
        return Field(Field._scalar_dom, val)

65
66
67
68
69
70
71
    # prevent implicit conversion to bool
    def __nonzero__(self):
        raise TypeError("Field does not support implicit conversion to bool")

    def __bool__(self):
        raise TypeError("Field does not support implicit conversion to bool")

72
    @staticmethod
73
    def full(domain, val):
Martin Reinecke's avatar
Martin Reinecke committed
74
75
76
77
78
        """Creates a Field with a given domain, filled with a constant value.

        Parameters
        ----------
        domain : Domain, tuple of Domain, or DomainTuple
Philipp Arras's avatar
Docs  
Philipp Arras committed
79
            Domain of the new Field.
Martin Reinecke's avatar
Martin Reinecke committed
80
        val : float/complex/int scalar
Philipp Arras's avatar
Docs  
Philipp Arras committed
81
            Fill value. Data type of the field is inferred from val.
Martin Reinecke's avatar
Martin Reinecke committed
82
83
84
85

        Returns
        -------
        Field
Philipp Arras's avatar
Docs  
Philipp Arras committed
86
            The newly created Field.
Martin Reinecke's avatar
Martin Reinecke committed
87
        """
88
89
        if not np.isscalar(val):
            raise TypeError("val must be a scalar")
90
91
92
        if not (np.isreal(val) or np.iscomplex(val)):
            raise TypeError("need arithmetic scalar")
        domain = DomainTuple.make(domain)
Martin Reinecke's avatar
Martin Reinecke committed
93
        return Field(domain, val)
94

95
    @staticmethod
96
    def from_global_data(domain, arr, sum_up=False):
Martin Reinecke's avatar
Martin Reinecke committed
97
98
99
100
101
        """Returns a Field constructed from `domain` and `arr`.

        Parameters
        ----------
        domain : DomainTuple, tuple of Domain, or Domain
Philipp Arras's avatar
Docs  
Philipp Arras committed
102
            The domain of the new Field.
Martin Reinecke's avatar
Martin Reinecke committed
103
104
105
        arr : numpy.ndarray
            The data content to be used for the new Field.
            Its shape must match the shape of `domain`.
106
107
108
109
110
        sum_up : bool, optional
            If True, the contents of `arr` are summed up over all MPI tasks
            (if any), and the sum is used as data content.
            If False, the contens of `arr` are used directly, and must be
            identical on all MPI tasks.
Martin Reinecke's avatar
Martin Reinecke committed
111
        """
112
113
        return Field(DomainTuple.make(domain),
                     dobj.from_global_data(arr, sum_up))
114

Martin Reinecke's avatar
Martin Reinecke committed
115
116
    @staticmethod
    def from_local_data(domain, arr):
117
        return Field(DomainTuple.make(domain),
Martin Reinecke's avatar
Martin Reinecke committed
118
                     dobj.from_local_data(domain.shape, arr))
Martin Reinecke's avatar
Martin Reinecke committed
119

120
    def to_global_data(self):
Martin Reinecke's avatar
Martin Reinecke committed
121
122
123
124
125
126
127
        """Returns an array containing the full data of the field.

        Returns
        -------
        numpy.ndarray : array containing all field entries.
            Its shape is identical to `self.shape`.
        """
128
129
        return dobj.to_global_data(self._val)

130
131
132
133
134
    def to_global_data_rw(self):
        """Returns a modifiable array containing the full data of the field.

        Returns
        -------
Philipp Arras's avatar
Docs  
Philipp Arras committed
135
136
137
        numpy.ndarray
            Array containing all field entries, which can be modified. Its
            shape is identical to `self.shape`.
138
139
140
        """
        return dobj.to_global_data_rw(self._val)

Martin Reinecke's avatar
Martin Reinecke committed
141
142
    @property
    def local_data(self):
Martin Reinecke's avatar
Martin Reinecke committed
143
144
145
146
147
        """numpy.ndarray : locally residing field data

        Returns a handle to the part of the array data residing on the local
        task (or to the entore array if MPI is not active).
        """
Martin Reinecke's avatar
Martin Reinecke committed
148
149
        return dobj.local_data(self._val)

Martin Reinecke's avatar
Martin Reinecke committed
150
    def cast_domain(self, new_domain):
Martin Reinecke's avatar
Martin Reinecke committed
151
152
153
154
155
156
157
158
159
160
161
        """Returns a field with the same data, but a different domain

        Parameters
        ----------
        new_domain : Domain, tuple of Domain, or DomainTuple
            The domain for the returned field. Must be shape-compatible to
            `self`.

        Returns
        -------
        Field
Philipp Arras's avatar
Philipp Arras committed
162
            Field defined on `new_domain`, but with the same data as `self`.
Martin Reinecke's avatar
Martin Reinecke committed
163
        """
164
        return Field(DomainTuple.make(new_domain), self._val)
165

Martin Reinecke's avatar
Martin Reinecke committed
166
167
    @staticmethod
    def from_random(random_type, domain, dtype=np.float64, **kwargs):
168
        """Draws a random field with the given parameters.
169
170
171

        Parameters
        ----------
Martin Reinecke's avatar
Martin Reinecke committed
172
173
        random_type : 'pm1', 'normal', or 'uniform'
            The random distribution to use.
Martin Reinecke's avatar
Martin Reinecke committed
174
        domain : DomainTuple
Philipp Arras's avatar
Docs  
Philipp Arras committed
175
            The domain of the output random Field.
176
        dtype : type
Philipp Arras's avatar
Docs  
Philipp Arras committed
177
            The datatype of the output random Field.
Theo Steininger's avatar
Theo Steininger committed
178

179
180
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
181
        Field
Martin Reinecke's avatar
Martin Reinecke committed
182
            The newly created Field.
183
        """
Martin Reinecke's avatar
Martin Reinecke committed
184
        domain = DomainTuple.make(domain)
Martin Reinecke's avatar
Martin Reinecke committed
185
186
187
        return Field(domain=domain,
                     val=dobj.from_random(random_type, dtype=dtype,
                                          shape=domain.shape, **kwargs))
188

Theo Steininger's avatar
Theo Steininger committed
189
190
    @property
    def val(self):
Philipp Arras's avatar
Docs  
Philipp Arras committed
191
        """dobj.data_object : the data object storing the field's entries.
Martin Reinecke's avatar
Martin Reinecke committed
192

Martin Reinecke's avatar
Martin Reinecke committed
193
194
        Notes
        -----
Martin Reinecke's avatar
Martin Reinecke committed
195
196
        This property is intended for low-level, internal use only. Do not use
        from outside of NIFTy's core; there should be better alternatives.
197
        """
Martin Reinecke's avatar
Martin Reinecke committed
198
        return self._val
csongor's avatar
csongor committed
199

Martin Reinecke's avatar
Martin Reinecke committed
200
201
    @property
    def dtype(self):
Martin Reinecke's avatar
Martin Reinecke committed
202
        """type : the data type of the field's entries"""
Martin Reinecke's avatar
Martin Reinecke committed
203
204
        return self._val.dtype

Martin Reinecke's avatar
Martin Reinecke committed
205
206
    @property
    def domain(self):
Martin Reinecke's avatar
Martin Reinecke committed
207
        """DomainTuple : the field's domain"""
Martin Reinecke's avatar
Martin Reinecke committed
208
209
        return self._domain

210
211
    @property
    def shape(self):
Martin Reinecke's avatar
Martin Reinecke committed
212
        """tuple of int : the concatenated shapes of all sub-domains"""
Martin Reinecke's avatar
Martin Reinecke committed
213
        return self._domain.shape
csongor's avatar
csongor committed
214

215
    @property
Martin Reinecke's avatar
Martin Reinecke committed
216
    def size(self):
Martin Reinecke's avatar
Martin Reinecke committed
217
        """int : total number of pixels in the field"""
Martin Reinecke's avatar
Martin Reinecke committed
218
        return self._domain.size
csongor's avatar
csongor committed
219

Theo Steininger's avatar
Theo Steininger committed
220
221
    @property
    def real(self):
Martin Reinecke's avatar
Martin Reinecke committed
222
        """Field : The real part of the field"""
Martin Reinecke's avatar
Martin Reinecke committed
223
224
225
        if utilities.iscomplextype(self.dtype):
            return Field(self._domain, self._val.real)
        return self
Theo Steininger's avatar
Theo Steininger committed
226
227
228

    @property
    def imag(self):
Martin Reinecke's avatar
Martin Reinecke committed
229
        """Field : The imaginary part of the field"""
Martin Reinecke's avatar
Martin Reinecke committed
230
        if not utilities.iscomplextype(self.dtype):
231
            raise ValueError(".imag called on a non-complex Field")
Martin Reinecke's avatar
Martin Reinecke committed
232
        return Field(self._domain, self._val.imag)
Theo Steininger's avatar
Theo Steininger committed
233

234
    def scalar_weight(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
235
236
237
238
239
        """Returns the uniform volume element for a sub-domain of `self`.

        Parameters
        ----------
        spaces : int, tuple of int or None
Philipp Arras's avatar
Docs  
Philipp Arras committed
240
            Indices of the sub-domains of the field's domain to be considered.
Martin Reinecke's avatar
Martin Reinecke committed
241
242
243
244
245
            If `None`, the entire domain is used.

        Returns
        -------
        float or None
Philipp Arras's avatar
Docs  
Philipp Arras committed
246
            If the requested sub-domain has a uniform volume element, it is
Martin Reinecke's avatar
Martin Reinecke committed
247
248
            returned. Otherwise, `None` is returned.
        """
249
        return self._domain.scalar_weight(spaces)
250

Martin Reinecke's avatar
Martin Reinecke committed
251
    def total_volume(self, spaces=None):
252
        """Returns the total volume of the field's domain or of a subspace of it.
Martin Reinecke's avatar
Martin Reinecke committed
253
254
255
256

        Parameters
        ----------
        spaces : int, tuple of int or None
Philipp Arras's avatar
Docs  
Philipp Arras committed
257
            Indices of the sub-domains of the field's domain to be considered.
258
            If `None`, the total volume of the whole domain is returned.
Martin Reinecke's avatar
Martin Reinecke committed
259
260
261
262

        Returns
        -------
        float
263
            the total volume of the requested (sub-)domain.
Martin Reinecke's avatar
Martin Reinecke committed
264
        """
265
        return self._domain.total_volume(spaces)
Martin Reinecke's avatar
Martin Reinecke committed
266

267
    def weight(self, power=1, spaces=None):
268
        """Weights the pixels of `self` with their invidual pixel volumes.
269
270
271
272

        Parameters
        ----------
        power : number
273
            The pixel values get multiplied with their volume-factor**power.
Theo Steininger's avatar
Theo Steininger committed
274

Martin Reinecke's avatar
Martin Reinecke committed
275
276
277
        spaces : None, int or tuple of int
            Determines on which sub-domain the operation takes place.
            If None, the entire domain is used.
Theo Steininger's avatar
Theo Steininger committed
278

279
280
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
281
        Field
Theo Steininger's avatar
Theo Steininger committed
282
            The weighted field.
283
        """
284
        aout = self.local_data.copy()
csongor's avatar
csongor committed
285

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

288
289
        fct = 1.
        for ind in spaces:
Martin Reinecke's avatar
Martin Reinecke committed
290
            wgt = self._domain[ind].dvol
291
292
293
            if np.isscalar(wgt):
                fct *= wgt
            else:
Martin Reinecke's avatar
Martin Reinecke committed
294
                new_shape = np.ones(len(self.shape), dtype=np.int)
Martin Reinecke's avatar
Martin Reinecke committed
295
296
                new_shape[self._domain.axes[ind][0]:
                          self._domain.axes[ind][-1]+1] = wgt.shape
297
                wgt = wgt.reshape(new_shape)
Martin Reinecke's avatar
Martin Reinecke committed
298
299
                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
300
                    wgt = dobj.local_data(dobj.from_global_data(wgt))
301
                aout *= wgt**power
302
        fct = fct**power
Martin Reinecke's avatar
Martin Reinecke committed
303
        if fct != 1.:
304
            aout *= fct
305

306
        return Field.from_local_data(self._domain, aout)
csongor's avatar
csongor committed
307

308
    def outer(self, x):
309
        """Computes the outer product of 'self' with x.
310
311
312
313
314
315

        Parameters
        ----------
        x : Field

        Returns
Philipp Arras's avatar
Docs  
Philipp Arras committed
316
317
318
        -------
        Field
            Defined on the product space of self.domain and x.domain.
319
320
321
        """
        if not isinstance(x, Field):
            raise TypeError("The multiplier must be an instance of " +
322
                            "the Field class")
323
324
325
        from .operators.outer_product_operator import OuterProduct
        return OuterProduct(self, x.domain)(x)

Martin Reinecke's avatar
Martin Reinecke committed
326
    def vdot(self, x=None, spaces=None):
327
        """Computes the dot product of 'self' with x.
Theo Steininger's avatar
Theo Steininger committed
328

329
330
331
        Parameters
        ----------
        x : Field
Philipp Arras's avatar
Philipp Arras committed
332
            x must be defined on the same domain as `self`.
Theo Steininger's avatar
Theo Steininger committed
333

Philipp Arras's avatar
Philipp Arras committed
334
        spaces : None, int or tuple of int
335
336
            The dot product is only carried out over the sub-domains in this
            tuple. If None, it is carried out over all sub-domains.
Philipp Arras's avatar
Philipp Arras committed
337
            Default: None.
Theo Steininger's avatar
Theo Steininger committed
338

339
340
        Returns
        -------
Philipp Arras's avatar
Philipp Arras committed
341
        float, complex, either scalar (for full dot products) or Field (for partial dot products).
342
        """
343
        if not isinstance(x, Field):
344
            raise TypeError("The dot-partner must be an instance of " +
345
                            "the Field class")
Theo Steininger's avatar
Theo Steininger committed
346

Martin Reinecke's avatar
Martin Reinecke committed
347
        if x._domain != self._domain:
348
            raise ValueError("Domain mismatch")
Theo Steininger's avatar
Theo Steininger committed
349

Martin Reinecke's avatar
Martin Reinecke committed
350
        ndom = len(self._domain)
351
352
353
        spaces = utilities.parse_spaces(spaces, ndom)

        if len(spaces) == ndom:
Martin Reinecke's avatar
Martin Reinecke committed
354
            return dobj.vdot(self._val, x._val)
355
356
        # 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
357
        return (self.conjugate()*x).sum(spaces=spaces)
Theo Steininger's avatar
Theo Steininger committed
358

Martin Reinecke's avatar
Martin Reinecke committed
359
    def norm(self, ord=2):
360
        """Computes the L2-norm of the field values.
csongor's avatar
csongor committed
361

Martin Reinecke's avatar
Martin Reinecke committed
362
363
        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
364
365
        ord : int
            Accepted values: 1, 2, ..., np.inf. Default: 2.
366
367
368
369

        Returns
        -------
        float
Martin Reinecke's avatar
Martin Reinecke committed
370
            The L2-norm of the field values.
371
        """
Martin Reinecke's avatar
Martin Reinecke committed
372
        return dobj.norm(self._val, ord)
373

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

377
378
        Returns
        -------
Martin Reinecke's avatar
Martin Reinecke committed
379
380
        Field
            The complex conjugated field.
csongor's avatar
csongor committed
381
        """
Martin Reinecke's avatar
Martin Reinecke committed
382
        if utilities.iscomplextype(self._val.dtype):
Martin Reinecke's avatar
Martin Reinecke committed
383
384
            return Field(self._domain, self._val.conjugate())
        return self
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
    def __pos__(self):
389
        return self
390

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

Theo Steininger's avatar
Theo Steininger committed
394
    def __abs__(self):
Martin Reinecke's avatar
Martin Reinecke committed
395
        return Field(self._domain, 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:
Martin Reinecke's avatar
Martin Reinecke committed
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
Martin Reinecke's avatar
Martin Reinecke committed
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

419
            return Field(DomainTuple.make(return_domain), data)
csongor's avatar
csongor committed
420

421
    def sum(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
422
423
424
425
        """Sums up over the sub-domains given by `spaces`.

        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
426
        spaces : None, int or tuple of int
Martin Reinecke's avatar
Martin Reinecke committed
427
            The summation is only carried out over the sub-domains in this
Martin Reinecke's avatar
cleanup  
Martin Reinecke committed
428
            tuple. If None, it is carried out over all sub-domains.
Martin Reinecke's avatar
Martin Reinecke committed
429
430
431
432
433
434
435

        Returns
        -------
        Field or scalar
            The result of the summation. If it is carried out over the entire
            domain, this is a scalar, otherwise a Field.
        """
436
        return self._contraction_helper('sum', spaces)
csongor's avatar
csongor committed
437

438
    def integrate(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
439
440
441
442
443
444
445
        """Integrates over the sub-domains given by `spaces`.

        Integration is performed by summing over `self` multiplied by its
        volume factors.

        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
446
        spaces : None, int or tuple of int
Martin Reinecke's avatar
Martin Reinecke committed
447
448
449
450
451
452
453
454
455
            The summation is only carried out over the sub-domains in this
            tuple. If None, it is carried out over all sub-domains.

        Returns
        -------
        Field or scalar
            The result of the integration. If it is carried out over the
            entire domain, this is a scalar, otherwise a Field.
        """
Martin Reinecke's avatar
Martin Reinecke committed
456
457
458
        swgt = self.scalar_weight(spaces)
        if swgt is not None:
            res = self.sum(spaces)
Martin Reinecke's avatar
fixes  
Martin Reinecke committed
459
            res = res*swgt
Martin Reinecke's avatar
Martin Reinecke committed
460
            return res
461
462
463
        tmp = self.weight(1, spaces=spaces)
        return tmp.sum(spaces)

464
    def prod(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
465
466
467
468
        """Computes the product over the sub-domains given by `spaces`.

        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
469
        spaces : None, int or tuple of int
Martin Reinecke's avatar
Martin Reinecke committed
470
471
            The operation is only carried out over the sub-domains in this
            tuple. If None, it is carried out over all sub-domains.
Philipp Arras's avatar
Philipp Arras committed
472
            Default: None.
Martin Reinecke's avatar
Martin Reinecke committed
473
474
475
476
477
478
479

        Returns
        -------
        Field or scalar
            The result of the product. If it is carried out over the entire
            domain, this is a scalar, otherwise a Field.
        """
480
        return self._contraction_helper('prod', spaces)
csongor's avatar
csongor committed
481

482
483
    def all(self, spaces=None):
        return self._contraction_helper('all', spaces)
csongor's avatar
csongor committed
484

485
486
    def any(self, spaces=None):
        return self._contraction_helper('any', spaces)
csongor's avatar
csongor committed
487

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
#     def min(self, spaces=None):
#         """Determines the minimum over the sub-domains given by `spaces`.
#
#         Parameters
#         ----------
#         spaces : None, int or tuple of int (default: None)
#             The operation is only carried out over the sub-domains in this
#             tuple. If None, it is carried out over all sub-domains.
#
#         Returns
#         -------
#         Field or scalar
#             The result of the operation. If it is carried out over the entire
#             domain, this is a scalar, otherwise a Field.
#         """
#         return self._contraction_helper('min', spaces)
#
#     def max(self, spaces=None):
#         """Determines the maximum over the sub-domains given by `spaces`.
#
#         Parameters
#         ----------
#         spaces : None, int or tuple of int (default: None)
#             The operation is only carried out over the sub-domains in this
#             tuple. If None, it is carried out over all sub-domains.
#
#         Returns
#         -------
#         Field or scalar
#             The result of the operation. If it is carried out over the entire
#             domain, this is a scalar, otherwise a Field.
#         """
#         return self._contraction_helper('max', spaces)
csongor's avatar
csongor committed
521

522
    def mean(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
523
524
525
526
527
528
529
        """Determines the mean over the sub-domains given by `spaces`.

        ``x.mean(spaces)`` is equivalent to
        ``x.integrate(spaces)/x.total_volume(spaces)``.

        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
530
        spaces : None, int or tuple of int
Martin Reinecke's avatar
Martin Reinecke committed
531
            The operation is only carried out over the sub-domains in this
Martin Reinecke's avatar
cleanup  
Martin Reinecke committed
532
            tuple. If None, it is carried out over all sub-domains.
Martin Reinecke's avatar
Martin Reinecke committed
533
534
535
536
537
538
539

        Returns
        -------
        Field or scalar
            The result of the operation. If it is carried out over the entire
            domain, this is a scalar, otherwise a Field.
        """
540
541
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('mean', spaces)
Martin Reinecke's avatar
Martin Reinecke committed
542
        # MR FIXME: not very efficient
543
544
        # MR FIXME: do we need "spaces" here?
        tmp = self.weight(1, spaces)
Martin Reinecke's avatar
Martin Reinecke committed
545
        return tmp.sum(spaces)*(1./tmp.total_volume(spaces))
csongor's avatar
csongor committed
546

547
    def var(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
548
549
550
551
        """Determines the variance over the sub-domains given by `spaces`.

        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
552
        spaces : None, int or tuple of int
Martin Reinecke's avatar
Martin Reinecke committed
553
554
            The operation is only carried out over the sub-domains in this
            tuple. If None, it is carried out over all sub-domains.
Philipp Arras's avatar
Philipp Arras committed
555
            Default: None.
Martin Reinecke's avatar
Martin Reinecke committed
556
557
558
559
560
561
562

        Returns
        -------
        Field or scalar
            The result of the operation. If it is carried out over the entire
            domain, this is a scalar, otherwise a Field.
        """
563
564
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('var', spaces)
Martin Reinecke's avatar
Martin Reinecke committed
565
566
        # MR FIXME: not very efficient or accurate
        m1 = self.mean(spaces)
Martin Reinecke's avatar
Martin Reinecke committed
567
        if utilities.iscomplextype(self.dtype):
568
            sq = abs(self-m1)**2
Martin Reinecke's avatar
Martin Reinecke committed
569
        else:
570
571
            sq = (self-m1)**2
        return sq.mean(spaces)
csongor's avatar
csongor committed
572

573
    def std(self, spaces=None):
Martin Reinecke's avatar
Martin Reinecke committed
574
575
576
577
578
579
580
        """Determines the standard deviation over the sub-domains given by
        `spaces`.

        ``x.std(spaces)`` is equivalent to ``sqrt(x.var(spaces))``.

        Parameters
        ----------
Philipp Arras's avatar
Philipp Arras committed
581
        spaces : None, int or tuple of int
Martin Reinecke's avatar
Martin Reinecke committed
582
583
            The operation is only carried out over the sub-domains in this
            tuple. If None, it is carried out over all sub-domains.
Philipp Arras's avatar
Philipp Arras committed
584
            Default: None.
Martin Reinecke's avatar
Martin Reinecke committed
585
586
587
588
589
590
591

        Returns
        -------
        Field or scalar
            The result of the operation. If it is carried out over the entire
            domain, this is a scalar, otherwise a Field.
        """
592
        from .sugar import sqrt
593
594
595
        if self.scalar_weight(spaces) is not None:
            return self._contraction_helper('std', spaces)
        return sqrt(self.var(spaces))
csongor's avatar
csongor committed
596

Theo Steininger's avatar
Theo Steininger committed
597
    def __repr__(self):
Martin Reinecke's avatar
5->6  
Martin Reinecke committed
598
        return "<nifty6.Field>"
Theo Steininger's avatar
Theo Steininger committed
599
600

    def __str__(self):
Martin Reinecke's avatar
5->6  
Martin Reinecke committed
601
        return "nifty6.Field instance\n- domain      = " + \
602
               self._domain.__str__() + \
Martin Reinecke's avatar
Martin Reinecke committed
603
               "\n- val         = " + repr(self._val)
Martin Reinecke's avatar
cleanup  
Martin Reinecke committed
604

Martin Reinecke's avatar
more  
Martin Reinecke committed
605
    def extract(self, dom):
Martin Reinecke's avatar
Martin Reinecke committed
606
        if dom != self._domain:
Martin Reinecke's avatar
more  
Martin Reinecke committed
607
608
609
            raise ValueError("domain mismatch")
        return self

610
611
612
    def extract_part(self, dom):
        if dom != self._domain:
            raise ValueError("domain mismatch")
Martin Reinecke's avatar
more  
Martin Reinecke committed
613
614
615
        return self

    def unite(self, other):
Martin Reinecke's avatar
Martin Reinecke committed
616
617
618
619
        return self+other

    def flexible_addsub(self, other, neg):
        return self-other if neg else self+other
620

621
    def sigmoid(self):
Martin Reinecke's avatar
Martin Reinecke committed
622
623
        return 0.5*(1.+self.tanh())

Martin Reinecke's avatar
Martin Reinecke committed
624
    def clip(self, min=None, max=None):
Martin Reinecke's avatar
tweaks  
Martin Reinecke committed
625
626
        min = min.local_data if isinstance(min, Field) else min
        max = max.local_data if isinstance(max, Field) else max
Martin Reinecke's avatar
Martin Reinecke committed
627
        return Field(self._domain, dobj.clip(self._val, min, max))
628
629
630
631

    def one_over(self):
        return 1/self

Martin Reinecke's avatar
Martin Reinecke committed
632
633
634
635
    def _binary_op(self, other, op):
        # if other is a field, make sure that the domains match
        f = getattr(self._val, op)
        if isinstance(other, Field):
Martin Reinecke's avatar
Martin Reinecke committed
636
            if other._domain != self._domain:
Martin Reinecke's avatar
Martin Reinecke committed
637
638
639
640
641
                raise ValueError("domains are incompatible.")
            return Field(self._domain, f(other._val))
        if np.isscalar(other):
            return Field(self._domain, f(other))
        return NotImplemented
Martin Reinecke's avatar
Martin Reinecke committed
642

Martin Reinecke's avatar
Martin Reinecke committed
643

Martin Reinecke's avatar
Martin Reinecke committed
644
645
646
for op in ["__add__", "__radd__",
           "__sub__", "__rsub__",
           "__mul__", "__rmul__",
647
648
649
           "__truediv__", "__rtruediv__",
           "__floordiv__", "__rfloordiv__",
           "__pow__", "__rpow__",
650
651
652
           "__lt__", "__le__", "__gt__", "__ge__", "__eq__", "__ne__"]:
    def func(op):
        def func2(self, other):
Martin Reinecke's avatar
Martin Reinecke committed
653
            return self._binary_op(other, op)
654
655
        return func2
    setattr(Field, op, func(op))
656
657
658
659
660
661
662
663
664

for op in ["__iadd__", "__isub__", "__imul__", "__idiv__",
           "__itruediv__", "__ifloordiv__", "__ipow__"]:
    def func(op):
        def func2(self, other):
            raise TypeError(
                "In-place operations are deliberately not supported")
        return func2
    setattr(Field, op, func(op))
Martin Reinecke's avatar
Martin Reinecke committed
665

666
667
for f in ["sqrt", "exp", "log", "sin", "cos", "tan", "sinh", "cosh", "tanh",
          "absolute", "sinc", "sign", "log10", "log1p", "expm1"]:
Martin Reinecke's avatar
Martin Reinecke committed
668
669
    def func(f):
        def func2(self):
Martin Reinecke's avatar
Martin Reinecke committed
670
            return Field(self._domain, getattr(dobj, f)(self.val))
Martin Reinecke's avatar
Martin Reinecke committed
671
672
        return func2
    setattr(Field, f, func(f))