source: sasview/DataLoader/data_info.py @ 2733188

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 2733188 was 2733188, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

dataloader: make sure the data are kept as numpy arrays.

  • Property mode set to 100644
File size: 26.3 KB
Line 
1"""
2    Module that contains classes to hold information read from
3    reduced data files.
4   
5    A good description of the data members can be found in
6    the CanSAS 1D XML data format:
7   
8    http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
9"""
10
11"""
12This software was developed by the University of Tennessee as part of the
13Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
14project funded by the US National Science Foundation.
15
16If you use DANSE applications to do scientific research that leads to
17publication, we ask that you acknowledge the use of the software with the
18following sentence:
19
20"This work benefited from DANSE software developed under NSF award DMR-0520547."
21
22copyright 2008, University of Tennessee
23"""
24
25#TODO: Keep track of data manipulation in the 'process' data structure.
26#TODO: This module should be independent of plottables. We should write
27#        an adapter class for plottables when needed.
28
29#from sans.guitools.plottables import Data1D as plottable_1D
30from data_util.uncertainty import Uncertainty
31import numpy
32import math
33
34class plottable_1D:
35    """
36        Data1D is a place holder for 1D plottables.
37    """
38    # The presence of these should be mutually exclusive with the presence of Qdev (dx)
39    x = None
40    y = None
41    dx = None
42    dy = None
43    ## Slit smearing length
44    dxl = None
45    ## Slit smearing width
46    dxw = None
47   
48    # Units
49    _xaxis = ''
50    _xunit = ''
51    _yaxis = ''
52    _yunit = ''
53   
54    def __init__(self,x,y,dx=None,dy=None,dxl=None,dxw=None):
55        self.x = numpy.asarray(x)
56        self.y = numpy.asarray(y)
57        if dx is not None: self.dx = numpy.asarray(dx)
58        if dy is not None: self.dy = numpy.asarray(dy)
59        if dxl is not None: self.dxl = numpy.asarray(dxl)
60        if dxw is not None: self.dxw = numpy.asarray(dxw)
61
62    def xaxis(self, label, unit):
63        self._xaxis = label
64        self._xunit = unit
65       
66    def yaxis(self, label, unit):
67        self._yaxis = label
68        self._yunit = unit
69
70class plottable_2D:
71    """
72        Data2D is a place holder for 2D plottables.
73    """
74    xmin = None
75    xmax = None
76    ymin = None
77    ymax = None
78    data = None
79    err_data = None
80   
81    # Units
82    _xaxis = ''
83    _xunit = ''
84    _yaxis = ''
85    _yunit = ''
86    _zaxis = ''
87    _zunit = ''
88   
89    def __init__(self, data=None, err_data=None):
90        self.data = numpy.asarray(data)
91        self.err_data = numpy.asarray(err_data)
92       
93    def xaxis(self, label, unit):
94        self._xaxis = label
95        self._xunit = unit
96       
97    def yaxis(self, label, unit):
98        self._yaxis = label
99        self._yunit = unit
100           
101    def zaxis(self, label, unit):
102        self._zaxis = label
103        self._zunit = unit
104
105           
106class Vector:
107    """
108        Vector class to hold multi-dimensional objects
109    """
110    ## x component
111    x = None
112    ## y component
113    y = None
114    ## z component
115    z = None
116   
117    def __init__(self, x=None, y=None, z=None):
118        """
119            Initialization. Components that are not
120            set a set to None by default.
121           
122            @param x: x component
123            @param y: y component
124            @param z: z component
125        """
126        self.x = x
127        self.y = y
128        self.z = z
129       
130    def __str__(self):
131        return "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
132       
133
134class Detector:
135    """
136        Class to hold detector information
137    """
138    ## Name of the instrument [string]
139    name = None
140    ## Sample to detector distance [float] [mm]
141    distance = None
142    distance_unit = 'mm'
143    ## Offset of this detector position in X, Y, (and Z if necessary) [Vector] [mm]
144    offset = None
145    offset_unit = 'm'
146    ## Orientation (rotation) of this detector in roll, pitch, and yaw [Vector] [degrees]
147    orientation = None
148    orientation_unit = 'degree'
149    ## Center of the beam on the detector in X and Y (and Z if necessary) [Vector] [mm]
150    beam_center = None
151    beam_center_unit = 'mm'
152    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
153    pixel_size = None
154    pixel_size_unit = 'mm'
155    ## Slit length of the instrument for this detector.[float] [mm]
156    slit_length = None
157    slit_length_unit = 'mm'
158   
159    def __init__(self):
160        """
161            Initialize class attribute that are objects...
162        """
163        self.offset      = Vector()
164        self.orientation = Vector()
165        self.beam_center = Vector()
166        self.pixel_size  = Vector()
167       
168   
169    def __str__(self):
170        _str  = "Detector:\n"
171        _str += "   Name:         %s\n" % self.name
172        _str += "   Distance:     %s [%s]\n" % \
173            (str(self.distance), str(self.distance_unit))
174        _str += "   Offset:       %s [%s]\n" % \
175            (str(self.offset), str(self.offset_unit))
176        _str += "   Orientation:  %s [%s]\n" % \
177            (str(self.orientation), str(self.orientation_unit))
178        _str += "   Beam center:  %s [%s]\n" % \
179            (str(self.beam_center), str(self.beam_center_unit))
180        _str += "   Pixel size:   %s [%s]\n" % \
181            (str(self.pixel_size), str(self.pixel_size_unit))
182        _str += "   Slit length:  %s [%s]\n" % \
183            (str(self.slit_length), str(self.slit_length_unit))
184        return _str
185
186class Aperture:
187    ## Name
188    name = None
189    ## Type
190    type = None
191    ## Size name
192    size_name = None
193    ## Aperture size [Vector]
194    size = None
195    size_unit = 'mm'
196    ## Aperture distance [float]
197    distance = None
198    distance_unit = 'mm'
199   
200    def __init__(self):
201        self.size = Vector()
202   
203class Collimation:
204    """
205        Class to hold collimation information
206    """
207    ## Name
208    name = None
209    ## Length [float] [mm]
210    length = None
211    length_unit = 'mm'
212    ## Aperture
213    aperture = None
214   
215    def __init__(self):
216        self.aperture = []
217   
218    def __str__(self):
219        _str = "Collimation:\n"
220        _str += "   Length:       %s [%s]\n" % \
221            (str(self.length), str(self.length_unit))
222        for item in self.aperture:
223            _str += "   Aperture size:%s [%s]\n" % \
224                (str(item.size), str(item.size_unit))
225            _str += "   Aperture_dist:%s [%s]\n" % \
226                (str(item.distance), str(item.distance_unit))
227        return _str
228
229class Source:
230    """
231        Class to hold source information
232    """ 
233    ## Name
234    name = None
235    ## Radiation type [string]
236    radiation = None
237    ## Beam size name
238    beam_size_name = None
239    ## Beam size [Vector] [mm]
240    beam_size = None
241    beam_size_unit = 'mm'
242    ## Beam shape [string]
243    beam_shape = None
244    ## Wavelength [float] [Angstrom]
245    wavelength = None
246    wavelength_unit = 'A'
247    ## Minimum wavelength [float] [Angstrom]
248    wavelength_min = None
249    wavelength_min_unit = 'nm'
250    ## Maximum wavelength [float] [Angstrom]
251    wavelength_max = None
252    wavelength_max_unit = 'nm'
253    ## Wavelength spread [float] [Angstrom]
254    wavelength_spread = None
255    wavelength_spread_unit = 'percent'
256   
257    def __init__(self):
258        self.beam_size = Vector()
259       
260   
261    def __str__(self):
262        _str  = "Source:\n"
263        _str += "   Radiation:    %s\n" % str(self.radiation)
264        _str += "   Shape:        %s\n" % str(self.beam_shape)
265        _str += "   Wavelength:   %s [%s]\n" % \
266            (str(self.wavelength), str(self.wavelength_unit))
267        _str += "   Waveln_min:   %s [%s]\n" % \
268            (str(self.wavelength_min), str(self.wavelength_min_unit))
269        _str += "   Waveln_max:   %s [%s]\n" % \
270            (str(self.wavelength_max), str(self.wavelength_max_unit))
271        _str += "   Waveln_spread:%s [%s]\n" % \
272            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
273        _str += "   Beam_size:    %s [%s]\n" % \
274            (str(self.beam_size), str(self.beam_size_unit))
275        return _str
276   
277   
278"""
279    Definitions of radiation types
280"""
281NEUTRON  = 'neutron'
282XRAY     = 'x-ray'
283MUON     = 'muon'
284ELECTRON = 'electron'
285   
286class Sample:
287    """
288        Class to hold the sample description
289    """
290    ## Short name for sample
291    name = ''
292    ## ID
293    ID = ''
294    ## Thickness [float] [mm]
295    thickness = None
296    thickness_unit = 'mm'
297    ## Transmission [float] [fraction]
298    transmission = None
299    ## Temperature [float] [C]
300    temperature = None
301    temperature_unit = 'C'
302    ## Position [Vector] [mm]
303    position = None
304    position_unit = 'mm'
305    ## Orientation [Vector] [degrees]
306    orientation = None
307    orientation_unit = 'degree'
308    ## Details
309    details = None
310   
311    def __init__(self):
312        self.position    = Vector()
313        self.orientation = Vector()
314        self.details     = []
315   
316    def __str__(self):
317        _str  = "Sample:\n"
318        _str += "   ID:           %s\n" % str(self.ID)
319        _str += "   Transmission: %s\n" % str(self.transmission)
320        _str += "   Thickness:    %s [%s]\n" % \
321            (str(self.thickness), str(self.thickness_unit))
322        _str += "   Temperature:  %s [%s]\n" % \
323            (str(self.temperature), str(self.temperature_unit))
324        _str += "   Position:     %s [%s]\n" % \
325            (str(self.position), str(self.position_unit))
326        _str += "   Orientation:  %s [%s]\n" % \
327            (str(self.orientation), str(self.orientation_unit))
328       
329        _str += "   Details:\n"
330        for item in self.details:
331            _str += "      %s\n" % item
332           
333        return _str
334 
335class Process:
336    """
337        Class that holds information about the processes
338        performed on the data.
339    """
340    name = ''
341    date = ''
342    description= ''
343    term = None
344    notes = None
345   
346    def __init__(self):
347        self.term = []
348        self.notes = []
349   
350    def __str__(self):
351        _str  = "Process:\n"
352        _str += "   Name:         %s\n" % self.name
353        _str += "   Date:         %s\n" % self.date
354        _str += "   Description:  %s\n" % self.description
355        for item in self.term:
356            _str += "   Term:         %s\n" % item
357        for item in self.notes:
358            _str += "   Note:         %s\n" % item
359        return _str
360   
361 
362class DataInfo:
363    """
364        Class to hold the data read from a file.
365        It includes four blocks of data for the
366        instrument description, the sample description,
367        the data itself and any other meta data.
368    """
369    ## Title
370    title      = ''
371    ## Run number
372    run        = None
373    ## Run name
374    run_name   = None
375    ## File name
376    filename   = ''
377    ## Notes
378    notes      = None
379    ## Processes (Action on the data)
380    process    = None
381    ## Instrument name
382    instrument = ''
383    ## Detector information
384    detector   = None
385    ## Sample information
386    sample     = None
387    ## Source information
388    source     = None
389    ## Collimation information
390    collimation = None
391    ## Additional meta-data
392    meta_data  = None
393    ## Loading errors
394    errors = None
395           
396    def __init__(self):
397        """
398            Initialization
399        """
400        ## Title
401        self.title      = ''
402        ## Run number
403        self.run        = []
404        self.run_name   = {}
405        ## File name
406        self.filename   = ''
407        ## Notes
408        self.notes      = []
409        ## Processes (Action on the data)
410        self.process    = []
411        ## Instrument name
412        self.instrument = ''
413        ## Detector information
414        self.detector   = []
415        ## Sample information
416        self.sample     = Sample()
417        ## Source information
418        self.source     = Source()
419        ## Collimation information
420        self.collimation = []
421        ## Additional meta-data
422        self.meta_data  = {}
423        ## Loading errors
424        self.errors = []       
425       
426    def __str__(self):
427        """
428            Nice printout
429        """
430        _str =  "File:            %s\n" % self.filename
431        _str += "Title:           %s\n" % self.title
432        _str += "Run:             %s\n" % str(self.run)
433        _str += "Instrument:      %s\n" % str(self.instrument)
434        _str += "%s\n" % str(self.sample)
435        _str += "%s\n" % str(self.source)
436        for item in self.detector:
437            _str += "%s\n" % str(item)
438        for item in self.collimation:
439            _str += "%s\n" % str(item)
440        for item in self.process:
441            _str += "%s\n" % str(item)
442        for item in self.notes:
443            _str += "%s\n" % str(item)
444
445        return _str
446           
447    # Private method to perform operation. Not implemented for DataInfo,
448    # but should be implemented for each data class inherited from DataInfo
449    # that holds actual data (ex.: Data1D)
450    def _perform_operation(self, other, operation): return NotImplemented
451
452    def __add__(self, other):
453        """
454            Add two data sets
455           
456            @param other: data set to add to the current one
457            @return: new data set
458            @raise ValueError: raised when two data sets are incompatible
459        """
460        def operation(a, b): return a+b
461        return self._perform_operation(other, operation)
462       
463    def __radd__(self, other):
464        """
465            Add two data sets
466           
467            @param other: data set to add to the current one
468            @return: new data set
469            @raise ValueError: raised when two data sets are incompatible
470        """
471        def operation(a, b): return b+a
472        return self._perform_operation(other, operation)
473       
474    def __sub__(self, other):
475        """
476            Subtract two data sets
477           
478            @param other: data set to subtract from the current one
479            @return: new data set
480            @raise ValueError: raised when two data sets are incompatible
481        """
482        def operation(a, b): return a-b
483        return self._perform_operation(other, operation)
484       
485    def __rsub__(self, other):
486        """
487            Subtract two data sets
488           
489            @param other: data set to subtract from the current one
490            @return: new data set
491            @raise ValueError: raised when two data sets are incompatible
492        """
493        def operation(a, b): return b-a
494        return self._perform_operation(other, operation)
495       
496    def __mul__(self, other):
497        """
498            Multiply two data sets
499           
500            @param other: data set to subtract from the current one
501            @return: new data set
502            @raise ValueError: raised when two data sets are incompatible
503        """
504        def operation(a, b): return a*b
505        return self._perform_operation(other, operation)
506       
507    def __rmul__(self, other):
508        """
509            Multiply two data sets
510           
511            @param other: data set to subtract from the current one
512            @return: new data set
513            @raise ValueError: raised when two data sets are incompatible
514        """
515        def operation(a, b): return b*a
516        return self._perform_operation(other, operation)
517       
518    def __div__(self, other):
519        """
520            Divided a data set by another
521           
522            @param other: data set that the current one is divided by
523            @return: new data set
524            @raise ValueError: raised when two data sets are incompatible
525        """
526        def operation(a, b): return a/b
527        return self._perform_operation(other, operation)
528       
529    def __rdiv__(self, other):
530        """
531            Divided a data set by another
532           
533            @param other: data set that the current one is divided by
534            @return: new data set
535            @raise ValueError: raised when two data sets are incompatible
536        """
537        def operation(a, b): return b/a
538        return self._perform_operation(other, operation)           
539           
540class Data1D(plottable_1D, DataInfo):
541    """
542        1D data class
543    """
544    x_unit = '1/A'
545    y_unit = '1/cm'
546   
547    def __init__(self, x, y, dx=None, dy=None):
548        DataInfo.__init__(self)
549        plottable_1D.__init__(self, x, y, dx, dy)
550       
551       
552    def __str__(self):
553        """
554            Nice printout
555        """
556        _str =  "%s\n" % DataInfo.__str__(self)
557   
558        _str += "Data:\n"
559        _str += "   Type:         %s\n" % self.__class__.__name__
560        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
561        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
562        _str += "   Length:       %g\n" % len(self.x)
563
564        return _str
565
566    def is_slit_smeared(self):
567        """
568            Check whether the data has slit smearing information
569           
570            @return: True is slit smearing info is present, False otherwise
571        """
572        def _check(v):           
573            if (v.__class__==list or v.__class__==numpy.ndarray) \
574                and len(v)>0 and min(v)>0:
575                return True
576           
577            return False
578       
579        return _check(self.dxl) or _check(self.dxw)
580       
581    def clone_without_data(self, length=0, clone=None):
582        """
583            Clone the current object, without copying the data (which
584            will be filled out by a subsequent operation).
585            The data arrays will be initialized to zero.
586           
587            @param length: length of the data array to be initialized
588            @param clone: if provided, the data will be copied to clone
589        """
590        from copy import deepcopy
591       
592        if clone is None or not issubclass(clone.__class__, Data1D):
593            x  = numpy.zeros(length) 
594            dx = numpy.zeros(length) 
595            y  = numpy.zeros(length) 
596            dy = numpy.zeros(length) 
597            clone = Data1D(x, y, dx=dx, dy=dy)
598       
599        clone.title       = self.title
600        clone.run         = self.run
601        clone.filename    = self.filename
602        clone.notes       = deepcopy(self.notes) 
603        clone.process     = deepcopy(self.process) 
604        clone.detector    = deepcopy(self.detector) 
605        clone.sample      = deepcopy(self.sample) 
606        clone.source      = deepcopy(self.source) 
607        clone.collimation = deepcopy(self.collimation) 
608        clone.meta_data   = deepcopy(self.meta_data) 
609        clone.errors      = deepcopy(self.errors) 
610       
611        return clone
612
613    def _validity_check(self, other):
614        """
615            Checks that the data lengths are compatible.
616            Checks that the x vectors are compatible.
617            Returns errors vectors equal to original
618            errors vectors if they were present or vectors
619            of zeros when none was found.
620           
621            @param other: other data set for operation
622            @return: dy for self, dy for other [numpy arrays]
623            @raise ValueError: when lengths are not compatible
624        """
625        dy_other = None
626        if isinstance(other, Data1D):
627            # Check that data lengths are the same
628            if len(self.x) != len(other.x) or \
629                len(self.y) != len(other.y):
630                raise ValueError, "Unable to perform operation: data length are not equal"
631           
632            # Here we could also extrapolate between data points
633            for i in range(len(self.x)):
634                if self.x[i] != other.x[i]:
635                    raise ValueError, "Incompatible data sets: x-values do not match"
636           
637            # Check that the other data set has errors, otherwise
638            # create zero vector
639            dy_other = other.dy
640            if other.dy==None or (len(other.dy) != len(other.y)):
641                dy_other = numpy.zeros(len(other.y))
642           
643        # Check that we have errors, otherwise create zero vector
644        dy = self.dy
645        if self.dy==None or (len(self.dy) != len(self.y)):
646            dy = numpy.zeros(len(self.y))           
647           
648        return dy, dy_other
649
650    def _perform_operation(self, other, operation):
651        """
652        """
653        # First, check the data compatibility
654        dy, dy_other = self._validity_check(other)
655        result = self.clone_without_data(len(self.x))
656       
657        for i in range(len(self.x)):
658            result.x[i] = self.x[i]
659            if self.dx is not None and len(self.x)==len(self.dx):
660                result.dx[i] = self.dx[i]
661           
662            a = Uncertainty(self.y[i], dy[i]**2)
663            if isinstance(other, Data1D):
664                b = Uncertainty(other.y[i], dy_other[i]**2)
665            else:
666                b = other
667           
668            output = operation(a, b)
669            result.y[i] = output.x
670            result.dy[i] = math.sqrt(math.fabs(output.variance))
671        return result
672       
673class Data2D(plottable_2D, DataInfo):
674    """
675        2D data class
676    """
677    ## Units for Q-values
678    Q_unit = '1/A'
679   
680    ## Units for I(Q) values
681    I_unit = '1/cm'
682   
683    ## Vector of Q-values at the center of each bin in x
684    x_bins = None
685   
686    ## Vector of Q-values at the center of each bin in y
687    y_bins = None
688   
689   
690    def __init__(self, data=None, err_data=None):
691        self.y_bins = []
692        self.x_bins = []
693        DataInfo.__init__(self)
694        plottable_2D.__init__(self, data, err_data)
695        if len(self.detector)>0:
696            raise RuntimeError, "Data2D: Detector bank already filled at init"
697
698    def __str__(self):
699        _str =  "%s\n" % DataInfo.__str__(self)
700       
701        _str += "Data:\n"
702        _str += "   Type:         %s\n" % self.__class__.__name__
703        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
704        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
705        leny = 0
706        if len(self.data)>0:
707            leny = len(self.data[0])
708        _str += "   Length:       %g x %g\n" % (len(self.data), leny)
709       
710        return _str
711 
712    def clone_without_data(self, length=0, clone=None):
713        """
714            Clone the current object, without copying the data (which
715            will be filled out by a subsequent operation).
716            The data arrays will be initialized to zero.
717           
718            @param length: length of the data array to be initialized
719            @param clone: if provided, the data will be copied to clone
720        """
721        from copy import deepcopy
722       
723        if clone is None or not issubclass(clone.__class__, Data2D): 
724            data     = numpy.zeros(length) 
725            err_data = numpy.zeros(length) 
726            clone = Data2D(data, err_data)
727           
728        clone.title       = self.title
729        clone.run         = self.run
730        clone.filename    = self.filename
731        clone.notes       = deepcopy(self.notes) 
732        clone.process     = deepcopy(self.process) 
733        clone.detector    = deepcopy(self.detector) 
734        clone.sample      = deepcopy(self.sample) 
735        clone.source      = deepcopy(self.source) 
736        clone.collimation = deepcopy(self.collimation) 
737        clone.meta_data   = deepcopy(self.meta_data) 
738        clone.errors      = deepcopy(self.errors) 
739       
740        return clone
741 
742 
743    def _validity_check(self, other):
744        """
745            Checks that the data lengths are compatible.
746            Checks that the x vectors are compatible.
747            Returns errors vectors equal to original
748            errors vectors if they were present or vectors
749            of zeros when none was found.
750           
751            @param other: other data set for operation
752            @return: dy for self, dy for other [numpy arrays]
753            @raise ValueError: when lengths are not compatible
754        """
755        err_other = None
756        if isinstance(other, Data2D):
757            # Check that data lengths are the same
758            if numpy.size(self.data) != numpy.size(other.data):
759                raise ValueError, "Unable to perform operation: data length are not equal"
760               
761            # Check that the scales match
762            #TODO: matching scales?     
763           
764            # Check that the other data set has errors, otherwise
765            # create zero vector
766            #TODO: test this
767            err_other = other.err_data
768            if other.err_data==None or (numpy.size(other.err_data) != numpy.size(other.data)):
769                err_other = numpy.zeros([numpy.size(other.data,0), numpy.size(other.data,1)])
770           
771        # Check that we have errors, otherwise create zero vector
772        err = self.err_data
773        if self.err_data==None or (numpy.size(self.err_data) != numpy.size(self.data)):
774            err = numpy.zeros([numpy.size(self.data,0), numpy.size(self.data,1)])
775           
776        return err, err_other
777 
778 
779    def _perform_operation(self, other, operation):
780        """
781            Perform 2D operations between data sets
782           
783            @param other: other data set
784            @param operation: function defining the operation
785        """
786        # First, check the data compatibility
787        dy, dy_other = self._validity_check(other)
788   
789        result = self.clone_without_data([numpy.size(self.data,0), numpy.size(self.data,1)])
790       
791        for i in range(numpy.size(self.data,0)):
792            for j in range(numpy.size(self.data,1)):
793                result.data[i][j] = self.data[i][j]
794                if self.err_data is not None and numpy.size(self.data)==numpy.size(self.err_data):
795                    result.err_data[i][j] = self.err_data[i][j]
796               
797                a = Uncertainty(self.data[i][j], dy[i][j]**2)
798                if isinstance(other, Data2D):
799                    b = Uncertainty(other.data[i][j], dy_other[i][j]**2)
800                else:
801                    b = other
802               
803                output = operation(a, b)
804                result.data[i][j] = output.x
805                result.err_data[i][j] = math.sqrt(math.fabs(output.variance))
806        return result
807   
808 
809 
Note: See TracBrowser for help on using the repository browser.