source: anuga_core/source/anuga/pyvolution/quantity.py @ 3514

Last change on this file since 3514 was 3514, checked in by duncan, 18 years ago

Hi all,
I'm doing a change in the anuga structure, moving the code to

\anuga_core\source\anuga

After you have done an svn update, the PYTHONPATH has to be changed to;
PYTHONPATH = anuga_core/source/

This is part of changes required to make installation of anuga quicker and reducing the size of our sandpits.

If any imports are broken, try fixing them. With adding anuga. to them for example. If this seems to have really broken things, email/phone me.

Cheers
Duncan

File size: 45.0 KB
Line 
1"""Class Quantity - Implements values at each triangular element
2
3To create:
4
5   Quantity(domain, vertex_values)
6
7   domain: Associated domain structure. Required.
8
9   vertex_values: N x 3 array of values at each vertex for each element.
10                  Default None
11
12   If vertex_values are None Create array of zeros compatible with domain.
13   Otherwise check that it is compatible with dimenions of domain.
14   Otherwise raise an exception
15"""
16
17
18class Quantity:
19
20    def __init__(self, domain, vertex_values=None):
21
22        from anuga.pyvolution.neighbour_mesh import Mesh
23        from Numeric import array, zeros, Float
24
25        msg = 'First argument in Quantity.__init__ '
26        msg += 'must be of class Mesh (or a subclass thereof)'
27        assert isinstance(domain, Mesh), msg
28
29        if vertex_values is None:
30            N = domain.number_of_elements
31            self.vertex_values = zeros((N, 3), Float)
32        else:
33            self.vertex_values = array(vertex_values).astype(Float)
34
35            N, V = self.vertex_values.shape
36            assert V == 3,\
37                   'Three vertex values per element must be specified'
38
39
40            msg = 'Number of vertex values (%d) must be consistent with'\
41                  %N
42            msg += 'number of elements in specified domain (%d).'\
43                   %domain.number_of_elements
44
45            assert N == domain.number_of_elements, msg
46
47        self.domain = domain
48
49        #Allocate space for other quantities
50        self.centroid_values = zeros(N, Float)
51        self.edge_values = zeros((N, 3), Float)
52
53        #Intialise centroid and edge_values
54        self.interpolate()
55
56
57
58    #Methods for operator overloading
59    def __len__(self):
60        return self.centroid_values.shape[0]
61
62
63    def __neg__(self):
64        """Negate all values in this quantity giving meaning to the
65        expression -Q where Q is an instance of class Quantity
66        """
67
68        Q = Quantity(self.domain)
69        Q.set_values(-self.vertex_values)
70        return Q
71
72
73    def __add__(self, other):
74        """Add to self anything that could populate a quantity
75
76        E.g other can be a constant, an array, a function, another quantity
77        (except for a filename or points, attributes (for now))
78        - see set_values for details
79        """
80
81        Q = Quantity(self.domain)
82        Q.set_values(other)
83
84        result = Quantity(self.domain)
85        result.set_values(self.vertex_values + Q.vertex_values)
86        return result
87
88    def __radd__(self, other):
89        """Handle cases like 7+Q, where Q is an instance of class Quantity
90        """
91        return self + other
92
93
94    def __sub__(self, other):
95        return self + -other  #Invoke __neg__
96
97    def __mul__(self, other):
98        """Multiply self with anything that could populate a quantity
99
100        E.g other can be a constant, an array, a function, another quantity
101        (except for a filename or points, attributes (for now))
102        - see set_values for details
103
104        Note that if two quantitites q1 and q2 are multiplied,
105        vertex values are multiplied entry by entry
106        while centroid and edge values are re-interpolated.
107        Hence they won't be the product of centroid or edge values
108        from q1 and q2.
109        """
110
111        Q = Quantity(self.domain)
112        Q.set_values(other)
113
114        result = Quantity(self.domain)
115        result.set_values(self.vertex_values * Q.vertex_values)
116        return result
117
118    def __rmul__(self, other):
119        """Handle cases like 3*Q, where Q is an instance of class Quantity
120        """
121        return self * other
122
123    def __pow__(self, other):
124        """Raise quantity to (numerical) power
125
126        As with __mul__ vertex values are processed entry by entry
127        while centroid and edge values are re-interpolated.
128
129        Example using __pow__:
130          Q = (Q1**2 + Q2**2)**0.5
131
132        """
133
134        result = Quantity(self.domain)
135        result.set_values(self.vertex_values**other)
136        return result
137
138
139
140    def interpolate(self):
141        """Compute interpolated values at edges and centroid
142        Pre-condition: vertex_values have been set
143        """
144
145        N = self.vertex_values.shape[0]
146        for i in range(N):
147            v0 = self.vertex_values[i, 0]
148            v1 = self.vertex_values[i, 1]
149            v2 = self.vertex_values[i, 2]
150
151            self.centroid_values[i] = (v0 + v1 + v2)/3
152
153        self.interpolate_from_vertices_to_edges()
154
155
156    def interpolate_from_vertices_to_edges(self):
157        #Call correct module function
158        #(either from this module or C-extension)
159        interpolate_from_vertices_to_edges(self)
160
161
162
163
164    #New leaner interface to setting values
165    def set_values(self,
166                   numeric = None,    # List, numeric array or constant
167                   quantity = None,   # Another quantity
168                   function = None,   # Callable object: f(x,y)
169                   geospatial_data = None, #Arbitrary dataset
170                   points = None, values = None, data_georef = None, #Input for fit (obsoleted by use of geo_spatial object)
171                   filename = None, attribute_name = None, #Input from file
172                   alpha = None,
173                   location = 'vertices',
174                   indices = None,
175                   verbose = False,
176                   use_cache = False):
177
178        """Set values for quantity based on different sources.
179
180        numeric:
181          Compatible list, Numeric array (see below) or constant.
182          If callable it will treated as a function (see below)
183          If instance of another Quantity it will be treated as such.
184          If geo_spatial object it will be treated as such
185
186        quantity:
187          Another quantity (compatible quantity, e.g. obtained as a
188          linear combination of quantities)
189
190        function:
191          Any callable object that takes two 1d arrays x and y
192          each of length N and returns an array also of length N.
193          The function will be evaluated at points determined by
194          location and indices in the underlying mesh.
195
196        geospatial_data:
197          Arbitrary geo spatial dataset in the form of the class
198          Geospatial_data. Mesh points are populated using
199          fit_interpolate.fit fitting
200
201        points:
202          Nx2 array of data points for use with fit_interpolate.fit
203          If points are present, an N array of attribute
204          values corresponding to
205          each data point must be present.
206
207        values:
208          If points is specified, values is an array of length N containing
209          attribute values for each point.
210
211        data_georef:
212          If points is specified, geo_reference applies to each point.
213
214        filename:
215          Name of a .pts file containing data points and attributes for
216          use with fit_interpolate.fit.
217
218        attribute_name:
219          If specified, any array matching that name
220          will be used. from file or geospatial_data.
221          Otherwise a default will be used.
222
223        alpha:
224          Smoothing parameter to be used with fit_interpolate.fit.
225          See module fit_interpolate.fit for further details about alpha.
226          Alpha will only be used with points, values or filename.
227          Otherwise it will be ignored.
228
229
230        location: Where values are to be stored.
231                  Permissible options are: vertices, edges, centroids
232                  Default is 'vertices'
233
234                  In case of location == 'centroids' the dimension values must
235                  be a list of a Numerical array of length N,
236                  N being the number of elements.
237                  Otherwise it must be of dimension Nx3
238
239
240                  The values will be stored in elements following their
241                  internal ordering.
242
243                  If location is not 'unique vertices' Indices is the
244                  set of element ids that the operation applies to.
245                  If location is 'unique vertices' Indices is the set
246                  of vertex ids that the operation applies to.
247
248                  If selected location is vertices, values for
249                  centroid and edges will be assigned interpolated
250                  values.  In any other case, only values for the
251                  specified locations will be assigned and the others
252                  will be left undefined.
253
254        verbose: True means that output to stdout is generated
255
256        use_cache: True means that caching of intermediate results is
257                   attempted for fit_interpolate.fit.
258
259
260
261
262        Exactly one of the arguments
263          numeric, quantity, function, points, filename
264        must be present.
265        """
266
267        from anuga.geospatial_data.geospatial_data import Geospatial_data
268        from types import FloatType, IntType, LongType, ListType, NoneType
269        from Numeric import ArrayType
270
271        #General input checks
272        L = [numeric, quantity, function, geospatial_data, points, filename]
273        msg = 'Exactly one of the arguments '+\
274              'numeric, quantity, function, geospatial_data, points, or filename '+\
275              'must be present.'
276        assert L.count(None) == len(L)-1, msg
277
278
279        if location not in ['vertices', 'centroids', 'edges',
280                            'unique vertices']:
281            msg = 'Invalid location: %s' %location
282            raise msg
283
284
285        msg = 'Indices must be a list or None'
286        assert type(indices) in [ListType, NoneType, ArrayType], msg
287
288
289        if not(points is None and values is None and data_georef is None):
290            from warnings import warn
291
292            msg = 'Using points, values or data_georef with set_quantity '
293            msg += 'is obsolete. Please use a Geospatial_data object instead.'
294            warn(msg, DeprecationWarning)
295
296
297
298        #Determine which 'set_values_from_...' to use
299
300        if numeric is not None:
301            if type(numeric) in [FloatType, IntType, LongType]:
302                self.set_values_from_constant(numeric,
303                                              location, indices, verbose)
304            elif type(numeric) in [ArrayType, ListType]:
305                self.set_values_from_array(numeric,
306                                           location, indices, verbose)
307            elif callable(numeric):
308                self.set_values_from_function(numeric,
309                                              location, indices, verbose)
310            elif isinstance(numeric, Quantity):
311                self.set_values_from_quantity(numeric,
312                                              location, indices, verbose)
313            elif isinstance(numeric, Geospatial_data):
314                self.set_values_from_geospatial_data(numeric,
315                                                     alpha,
316                                                     location, indices,
317                                                     verbose = verbose,
318                                                     use_cache = use_cache)
319            else:
320                msg = 'Illegal type for argument numeric: %s' %str(numeric)
321                raise msg
322
323        elif quantity is not None:
324            self.set_values_from_quantity(quantity,
325                                          location, indices, verbose)
326        elif function is not None:
327            msg = 'Argument function must be callable'
328            assert callable(function), msg
329            self.set_values_from_function(function,
330                                          location, indices, verbose)
331        elif geospatial_data is not None:
332                self.set_values_from_geospatial_data(geospatial_data,
333                                                     alpha,
334                                                     location, indices,
335                                                     verbose = verbose,
336                                                     use_cache = use_cache)
337        elif points is not None:
338            print 'The usage of points in set_values will be deprecated.' +\
339                  'Please use the geospatial_data object.'
340
341            msg = 'When points are specified, associated values must also be.'
342            assert values is not None, msg
343            self.set_values_from_points(points, values, alpha,
344                                        location, indices,
345                                        data_georef = data_georef,
346                                        verbose = verbose,
347                                        use_cache = use_cache)
348        elif filename is not None:
349            self.set_values_from_file(filename, attribute_name, alpha,
350                                      location, indices,
351                                      verbose = verbose,
352                                      use_cache = use_cache)
353        else:
354            raise 'This can\'t happen :-)'
355
356
357        #Update all locations in triangles
358        if location == 'vertices' or location == 'unique vertices':
359            #Intialise centroid and edge_values
360            self.interpolate()
361
362        if location == 'centroids':
363            #Extrapolate 1st order - to capture notion of area being specified
364            self.extrapolate_first_order()
365
366
367
368    #Specific functions for setting values
369    def set_values_from_constant(self, X,
370                                 location, indices, verbose):
371        """Set quantity values from specified constant X
372        """
373
374
375        if location == 'centroids':
376            if (indices ==  None):
377                self.centroid_values[:] = X
378            else:
379                #Brute force
380                for i in indices:
381                    self.centroid_values[i,:] = X
382
383        elif location == 'edges':
384            if (indices ==  None):
385                self.edge_values[:] = X
386            else:
387                #Brute force
388                for i in indices:
389                    self.edge_values[i,:] = X
390
391        elif location == 'unique vertices':
392            if (indices ==  None):
393                self.edge_values[:] = X
394            else:
395
396                #Go through list of unique vertices
397                for unique_vert_id in indices:
398                    triangles = self.domain.vertexlist[unique_vert_id]
399
400                    #In case there are unused points
401                    if triangles is None: continue
402
403                    #Go through all triangle, vertex pairs
404                    #and set corresponding vertex value
405                    for triangle_id, vertex_id in triangles:
406                        self.vertex_values[triangle_id, vertex_id] = X
407
408                    #Intialise centroid and edge_values
409                    self.interpolate()
410        else:
411            if (indices ==  None):
412                self.vertex_values[:] = X
413            else:
414                #Brute force
415                for i_vertex in indices:
416                    self.vertex_values[i_vertex,:] = X
417
418
419
420
421
422
423    def set_values_from_array(self, values,
424                              location, indices, verbose):
425        """Set values for quantity
426
427        values: Numeric array
428        location: Where values are to be stored.
429        Permissible options are: vertices, edges, centroid, unique vertices
430        Default is 'vertices'
431
432        indices - if this action is carried out on a subset of
433        elements or unique vertices
434        The element/unique vertex indices are specified here.
435
436        In case of location == 'centroid' the dimension values must
437        be a list of a Numerical array of length N, N being the number
438        of elements.
439
440        Otherwise it must be of dimension Nx3
441
442        The values will be stored in elements following their
443        internal ordering.
444
445        If selected location is vertices, values for centroid and edges
446        will be assigned interpolated values.
447        In any other case, only values for the specified locations
448        will be assigned and the others will be left undefined.
449        """
450
451        from Numeric import array, Float, Int, allclose
452
453        values = array(values).astype(Float)
454
455        if indices is not None:
456            indices = array(indices).astype(Int)
457            msg = 'Number of values must match number of indices'
458            assert values.shape[0] == indices.shape[0], msg
459
460        N = self.centroid_values.shape[0]
461
462        if location == 'centroids':
463            assert len(values.shape) == 1, 'Values array must be 1d'
464
465            if indices is None:
466                msg = 'Number of values must match number of elements'
467                assert values.shape[0] == N, msg
468
469                self.centroid_values = values
470            else:
471                msg = 'Number of values must match number of indices'
472                assert values.shape[0] == indices.shape[0], msg
473
474                #Brute force
475                for i in range(len(indices)):
476                    self.centroid_values[indices[i]] = values[i]
477
478        elif location == 'edges':
479            assert len(values.shape) == 2, 'Values array must be 2d'
480
481            msg = 'Number of values must match number of elements'
482            assert values.shape[0] == N, msg
483
484            msg = 'Array must be N x 3'
485            assert values.shape[1] == 3, msg
486
487            self.edge_values = values
488
489        elif location == 'unique vertices':
490            assert len(values.shape) == 1 or allclose(values.shape[1:], 1),\
491                   'Values array must be 1d'
492
493            self.set_vertex_values(values.flat, indices = indices)
494        else:
495            if len(values.shape) == 1:
496                self.set_vertex_values(values, indices = indices)
497                #if indices == None:
498                    #Values are being specified once for each unique vertex
499                #    msg = 'Number of values must match number of vertices'
500                #    assert values.shape[0] == self.domain.coordinates.shape[0], msg
501                 #   self.set_vertex_values(values)
502                #else:
503                #    for element_index, value in map(None, indices, values):
504                #        self.vertex_values[element_index, :] = value
505
506            elif len(values.shape) == 2:
507                #Vertex values are given as a triplet for each triangle
508
509                msg = 'Array must be N x 3'
510                assert values.shape[1] == 3, msg
511
512                if indices == None:
513                    self.vertex_values = values
514                else:
515                    for element_index, value in map(None, indices, values):
516                        self.vertex_values[element_index] = value
517            else:
518                msg = 'Values array must be 1d or 2d'
519                raise msg
520
521    def set_values_from_quantity(self, q,
522                                 location, indices, verbose):
523        """Set quantity values from specified quantity instance q
524
525        Location is ignored
526        """
527
528
529        A = q.vertex_values
530
531        from Numeric import allclose
532        msg = 'Quantities are defined on different meshes. '+\
533              'This might be a case for implementing interpolation '+\
534              'between different meshes.'
535        assert allclose(A.shape, self.vertex_values.shape), msg
536
537        self.set_values(A, location='vertices',
538                        indices=indices,
539                        verbose=verbose)
540
541
542    def set_values_from_function(self, f,
543                                 location, indices, verbose):
544        """Set values for quantity using specified function
545
546        f: x, y -> z Function where x, y and z are arrays
547        location: Where values are to be stored.
548                  Permissible options are: vertices, centroid, edges,
549                  unique vertices
550                  Default is "vertices"
551        """
552
553        #FIXME: Should check that function returns something sensible and
554        #raise a meaningfull exception if it returns None for example
555
556        #FIXME: Should supply absolute coordinates
557
558        from Numeric import take
559
560        if (indices is None):
561            indices = range(len(self))
562            is_subset = False
563        else:
564            is_subset = True
565
566        if location == 'centroids':
567            P = take(self.domain.centroid_coordinates, indices)
568            if is_subset:
569                self.set_values(f(P[:,0], P[:,1]),
570                                location = location,
571                                indices = indices)
572            else:
573                self.set_values(f(P[:,0], P[:,1]), location = location)
574        elif location == 'vertices':
575            P = self.domain.vertex_coordinates
576            if is_subset:
577                #Brute force
578                for e in indices:
579                    for i in range(3):
580                        self.vertex_values[e,i] = f(P[e,2*i], P[e,2*i+1])
581            else:
582                for i in range(3):
583                    self.vertex_values[:,i] = f(P[:,2*i], P[:,2*i+1])
584        else:
585            raise 'Not implemented: %s' %location
586
587
588
589    def set_values_from_geospatial_data(self, geospatial_data, alpha,
590                                        location, indices,
591                                        verbose = False,
592                                        use_cache = False):
593
594        #FIXME: Use this function for the time being. Later move code in here
595
596        points = geospatial_data.get_data_points(absolute = False)
597        values = geospatial_data.get_attributes()
598        data_georef = geospatial_data.get_geo_reference()
599
600
601
602        self.set_values_from_points(points, values, alpha,
603                                    location, indices,
604                                    data_georef = data_georef,
605                                    verbose = verbose,
606                                    use_cache = use_cache)
607
608
609
610    def set_values_from_points(self, points, values, alpha,
611                               location, indices,
612                               data_georef = None,
613                               verbose = False,
614                               use_cache = False):
615        """
616        Set quantity values from arbitray data points using
617        fit_interpolate.fit
618        """
619
620
621        from Numeric import Float
622        from anuga.utilities.numerical_tools import ensure_numeric
623        #from anuga.pyvolution.least_squares import fit_to_mesh
624        from anuga.fit_interpolate.fit import fit_to_mesh
625        from anuga.coordinate_transforms.geo_reference import Geo_reference
626
627
628        points = ensure_numeric(points, Float)
629        values = ensure_numeric(values, Float)
630
631        if location != 'vertices':
632            msg = 'set_values_from_points is only defined for '+\
633                  'location=\'vertices\''
634            raise ms
635
636        coordinates = self.domain.coordinates
637        triangles = self.domain.triangles
638
639
640        #Take care of georeferencing
641        if data_georef is None:
642            data_georef = Geo_reference()
643
644
645        mesh_georef = self.domain.geo_reference
646
647        #print mesh_georef
648        #print data_georef
649        #print points
650
651
652        #Call fit_interpolate.fit function
653        args = (coordinates, triangles, points, values)
654        kwargs = {'data_origin': data_georef.get_origin(),
655                  'mesh_origin': mesh_georef.get_origin(),
656                  'alpha': alpha,
657                  'verbose': verbose}
658
659        #print kwargs
660
661        if use_cache is True:
662            try:
663                from caching import cache
664            except:
665                msg = 'Caching was requested, but caching module'+\
666                      'could not be imported'
667                raise msg
668
669            vertex_attributes = cache(fit_to_mesh,
670                                      args, kwargs,
671                                      verbose=verbose,
672                                      compression=False)
673        else:
674
675            vertex_attributes = apply(fit_to_mesh,
676                                      args, kwargs)
677
678        #Call underlying method using array values
679        self.set_values_from_array(vertex_attributes,
680                                   location, indices, verbose)
681
682
683
684    def set_values_from_file(self, filename, attribute_name, alpha,
685                             location, indices,
686                             verbose = False,
687                             use_cache = False):
688        """Set quantity based on arbitrary points in .pts file
689        using attribute_name selects name of attribute
690        present in file.
691        If not specified try to use whatever is available in file.
692        """
693
694        from load_mesh.loadASCII import import_points_file
695        from anuga.geospatial_data.geospatial_data import points_dictionary2geospatial_data
696
697        from types import StringType
698        msg = 'Filename must be a text string'
699        assert type(filename) == StringType, msg
700
701
702        #Read from (NetCDF) file
703        #FIXME (Ole): This function should really return a Geospatial_data object.
704        points_dict = import_points_file(filename)
705        points = points_dict['pointlist']
706        attributes = points_dict['attributelist']
707
708        if attribute_name is None:
709            names = attributes.keys()
710            attribute_name = names[0]
711
712        msg = 'Attribute_name must be a text string'
713        assert type(attribute_name) == StringType, msg
714
715
716        if verbose:
717            print 'Using attribute %s from file %s' %(attribute_name, filename)
718            print 'Available attributes: %s' %(names)
719
720        #try:
721        #    z = attributes[attribute_name]
722        #except:
723        #    msg = 'Could not extract attribute %s from file %s'\
724        #          %(attribute_name, filename)
725        #    raise msg
726
727
728        #Take care of georeferencing
729        if points_dict.has_key('geo_reference') and \
730               points_dict['geo_reference'] is not None:
731            data_georef = points_dict['geo_reference']
732        else:
733            data_georef = None
734
735
736
737        #Call underlying method for geospatial data
738        geospatial_data = points_dictionary2geospatial_data(points_dict)
739        geospatial_data.set_default_attribute_name(attribute_name)
740
741        self.set_values_from_geospatial_data(geospatial_data,
742                                             alpha,
743                                             location, indices,
744                                             verbose = verbose,
745                                             use_cache = use_cache)
746
747        #Call underlying method for points
748        #self.set_values_from_points(points, z, alpha,
749        #                            location, indices,
750        #                            data_georef = data_georef,
751        #                            verbose = verbose,
752        #                            use_cache = use_cache)
753
754
755
756    def get_values(self, location='vertices', indices = None):
757        """get values for quantity
758
759        return X, Compatible list, Numeric array (see below)
760        location: Where values are to be stored.
761                  Permissible options are: vertices, edges, centroid
762                  and unique vertices. Default is 'vertices'
763
764        In case of location == 'centroids' the dimension values must
765        be a list of a Numerical array of length N, N being the number
766        of elements. Otherwise it must be of dimension Nx3
767
768        The returned values with be a list the length of indices
769        (N if indices = None).  Each value will be a list of the three
770        vertex values for this quantity.
771
772        Indices is the set of element ids that the operation applies to.
773
774        The values will be stored in elements following their
775        internal ordering.
776
777        """
778        from Numeric import take
779
780        if location not in ['vertices', 'centroids', 'edges', 'unique vertices']:
781            msg = 'Invalid location: %s' %location
782            raise msg
783
784        import types, Numeric
785        assert type(indices) in [types.ListType, types.NoneType,
786                                 Numeric.ArrayType],\
787                                 'Indices must be a list or None'
788
789        if location == 'centroids':
790            if (indices ==  None):
791                indices = range(len(self))
792            return take(self.centroid_values,indices)
793        elif location == 'edges':
794            if (indices ==  None):
795                indices = range(len(self))
796            return take(self.edge_values,indices)
797        elif location == 'unique vertices':
798            if (indices ==  None):
799                indices=range(self.domain.coordinates.shape[0])
800            vert_values = []
801            #Go through list of unique vertices
802            for unique_vert_id in indices:
803                triangles = self.domain.vertexlist[unique_vert_id]
804
805                #In case there are unused points
806                if triangles is None:
807                    msg = 'Unique vertex not associated with triangles'
808                    raise msg
809
810                # Go through all triangle, vertex pairs
811                # Average the values
812                sum = 0
813                for triangle_id, vertex_id in triangles:
814                    sum += self.vertex_values[triangle_id, vertex_id]
815                vert_values.append(sum/len(triangles))
816            return Numeric.array(vert_values)
817        else:
818            if (indices ==  None):
819                indices = range(len(self))
820            return take(self.vertex_values,indices)
821
822
823
824    def set_vertex_values(self, A, indices = None):
825        """Set vertex values for all unique vertices based on input array A
826        which has one entry per unique vertex, i.e.
827        one value for each row in array self.domain.coordinates or
828        one value for each row in vertexlist.
829
830        indices is the list of vertex_id's that will be set.
831
832        This function is used by set_values_from_array
833        """
834
835        from Numeric import array, Float
836
837        #Assert that A can be converted to a Numeric array of appropriate dim
838        A = array(A, Float)
839
840        #print 'SHAPE A', A.shape
841        assert len(A.shape) == 1
842
843        if indices == None:
844            assert A.shape[0] == self.domain.coordinates.shape[0]
845            vertex_list = range(A.shape[0])
846        else:
847            assert A.shape[0] == len(indices)
848            vertex_list = indices
849
850        #Go through list of unique vertices
851        for i_index, unique_vert_id in enumerate(vertex_list):
852            triangles = self.domain.vertexlist[unique_vert_id]
853
854            if triangles is None: continue #In case there are unused points
855
856            #Go through all triangle, vertex pairs
857            #touching vertex unique_vert_id and set corresponding vertex value
858            for triangle_id, vertex_id in triangles:
859                self.vertex_values[triangle_id, vertex_id] = A[i_index]
860
861        #Intialise centroid and edge_values
862        self.interpolate()
863
864
865    def smooth_vertex_values(self, value_array='field_values',
866                             precision = None):
867        """ Smooths field_values or conserved_quantities data.
868        TODO: be able to smooth individual fields
869        NOTE:  This function does not have a test.
870        FIXME: NOT DONE - do we need it?
871        FIXME: this function isn't called by anything.
872               Maybe it should be removed..-DSG
873        """
874
875        from Numeric import concatenate, zeros, Float, Int, array, reshape
876
877
878        A,V = self.get_vertex_values(xy=False,
879                                     value_array=value_array,
880                                     smooth = True,
881                                     precision = precision)
882
883        #Set some field values
884        for volume in self:
885            for i,v in enumerate(volume.vertices):
886                if value_array == 'field_values':
887                    volume.set_field_values('vertex', i, A[v,:])
888                elif value_array == 'conserved_quantities':
889                    volume.set_conserved_quantities('vertex', i, A[v,:])
890
891        if value_array == 'field_values':
892            self.precompute()
893        elif value_array == 'conserved_quantities':
894            Volume.interpolate_conserved_quantities()
895
896
897    #Method for outputting model results
898    #FIXME: Split up into geometric and numeric stuff.
899    #FIXME: Geometric (X,Y,V) should live in mesh.py
900    #FIXME: STill remember to move XY to mesh
901    def get_vertex_values(self,
902                          xy=True,
903                          smooth = None,
904                          precision = None,
905                          reduction = None):
906        """Return vertex values like an OBJ format
907
908        The vertex values are returned as one sequence in the 1D float array A.
909        If requested the coordinates will be returned in 1D arrays X and Y.
910
911        The connectivity is represented as an integer array, V, of dimension
912        M x 3, where M is the number of volumes. Each row has three indices
913        into the X, Y, A arrays defining the triangle.
914
915        if smooth is True, vertex values corresponding to one common
916        coordinate set will be smoothed according to the given
917        reduction operator. In this case vertex coordinates will be
918        de-duplicated.
919
920        If no smoothings is required, vertex coordinates and values will
921        be aggregated as a concatenation of values at
922        vertices 0, vertices 1 and vertices 2
923
924
925        Calling convention
926        if xy is True:
927           X,Y,A,V = get_vertex_values
928        else:
929           A,V = get_vertex_values
930
931        """
932
933        from Numeric import concatenate, zeros, Float, Int, array, reshape
934
935
936        if smooth is None:
937            smooth = self.domain.smooth
938
939        if precision is None:
940            precision = Float
941
942        if reduction is None:
943            reduction = self.domain.reduction
944
945        #Create connectivity
946
947        if smooth == True:
948
949            V = self.domain.get_vertices()
950            N = len(self.domain.vertexlist)
951            A = zeros(N, precision)
952
953            #Smoothing loop
954            for k in range(N):
955                L = self.domain.vertexlist[k]
956
957                #Go through all triangle, vertex pairs
958                #contributing to vertex k and register vertex value
959
960                if L is None: continue #In case there are unused points
961
962                contributions = []
963                for volume_id, vertex_id in L:
964                    v = self.vertex_values[volume_id, vertex_id]
965                    contributions.append(v)
966
967                A[k] = reduction(contributions)
968
969
970            if xy is True:
971                X = self.domain.coordinates[:,0].astype(precision)
972                Y = self.domain.coordinates[:,1].astype(precision)
973
974                return X, Y, A, V
975            else:
976                return A, V
977        else:
978            #Don't smooth
979            #obj machinery moved to general_mesh
980
981            # Create a V like [[0 1 2], [3 4 5]....[3*m-2 3*m-1 3*m]]
982            # These vert_id's will relate to the verts created below
983            #m = len(self.domain)  #Number of volumes
984            #M = 3*m        #Total number of unique vertices
985            #V = reshape(array(range(M)).astype(Int), (m,3))
986
987            V = self.domain.get_triangles(obj=True)
988            #FIXME use get_vertices, when ready
989
990            A = self.vertex_values.flat
991
992            #Do vertex coordinates
993            if xy is True:
994                C = self.domain.get_vertex_coordinates()
995
996                X = C[:,0:6:2].copy()
997                Y = C[:,1:6:2].copy()
998
999                return X.flat, Y.flat, A, V
1000            else:
1001                return A, V
1002
1003
1004    def extrapolate_first_order(self):
1005        """Extrapolate conserved quantities from centroid to
1006        vertices for each volume using
1007        first order scheme.
1008        """
1009
1010        qc = self.centroid_values
1011        qv = self.vertex_values
1012
1013        for i in range(3):
1014            qv[:,i] = qc
1015
1016
1017    def get_integral(self):
1018        """Compute the integral of quantity across entire domain
1019        """
1020        integral = 0
1021        for k in range(self.domain.number_of_elements):
1022            area = self.domain.areas[k]
1023            qc = self.centroid_values[k]
1024            integral += qc*area
1025
1026        return integral
1027
1028
1029
1030
1031class Conserved_quantity(Quantity):
1032    """Class conserved quantity adds to Quantity:
1033
1034    boundary values, storage and method for updating, and
1035    methods for (second order) extrapolation from centroid to vertices inluding
1036    gradients and limiters
1037    """
1038
1039    def __init__(self, domain, vertex_values=None):
1040        Quantity.__init__(self, domain, vertex_values)
1041
1042        from Numeric import zeros, Float
1043
1044        #Allocate space for boundary values
1045        L = len(domain.boundary)
1046        self.boundary_values = zeros(L, Float)
1047
1048        #Allocate space for updates of conserved quantities by
1049        #flux calculations and forcing functions
1050
1051        N = domain.number_of_elements
1052        self.explicit_update = zeros(N, Float )
1053        self.semi_implicit_update = zeros(N, Float )
1054
1055
1056    def update(self, timestep):
1057        #Call correct module function
1058        #(either from this module or C-extension)
1059        return update(self, timestep)
1060
1061
1062    def compute_gradients(self):
1063        #Call correct module function
1064        #(either from this module or C-extension)
1065        return compute_gradients(self)
1066
1067
1068    def limit(self):
1069        #Call correct module function
1070        #(either from this module or C-extension)
1071        limit(self)
1072
1073
1074    def extrapolate_second_order(self):
1075        #Call correct module function
1076        #(either from this module or C-extension)
1077        extrapolate_second_order(self)
1078
1079
1080def update(quantity, timestep):
1081    """Update centroid values based on values stored in
1082    explicit_update and semi_implicit_update as well as given timestep
1083
1084    Function implementing forcing terms must take on argument
1085    which is the domain and they must update either explicit
1086    or implicit updates, e,g,:
1087
1088    def gravity(domain):
1089        ....
1090        domain.quantities['xmomentum'].explicit_update = ...
1091        domain.quantities['ymomentum'].explicit_update = ...
1092
1093
1094
1095    Explicit terms must have the form
1096
1097        G(q, t)
1098
1099    and explicit scheme is
1100
1101       q^{(n+1}) = q^{(n)} + delta_t G(q^{n}, n delta_t)
1102
1103
1104    Semi implicit forcing terms are assumed to have the form
1105
1106       G(q, t) = H(q, t) q
1107
1108    and the semi implicit scheme will then be
1109
1110      q^{(n+1}) = q^{(n)} + delta_t H(q^{n}, n delta_t) q^{(n+1})
1111
1112
1113    """
1114
1115    from Numeric import sum, equal, ones, exp, Float
1116
1117    N = quantity.centroid_values.shape[0]
1118
1119
1120    #Divide H by conserved quantity to obtain G (see docstring above)
1121
1122
1123    for k in range(N):
1124        x = quantity.centroid_values[k]
1125        if x == 0.0:
1126            #FIXME: Is this right
1127            quantity.semi_implicit_update[k] = 0.0
1128        else:
1129            quantity.semi_implicit_update[k] /= x
1130
1131
1132    #Semi implicit updates
1133    denominator = ones(N, Float)-timestep*quantity.semi_implicit_update
1134
1135    if sum(less(denominator, 1.0)) > 0.0:
1136        msg = 'denominator < 1.0 in semi implicit update. Call Stephen :-)'
1137        raise msg
1138
1139    if sum(equal(denominator, 0.0)) > 0.0:
1140        msg = 'Zero division in semi implicit update. Call Stephen :-)'
1141        raise msg
1142    else:
1143        #Update conserved_quantities from semi implicit updates
1144        quantity.centroid_values /= denominator
1145
1146#    quantity.centroid_values = exp(timestep*quantity.semi_implicit_update)*quantity.centroid_values
1147
1148    #Explicit updates
1149    quantity.centroid_values += timestep*quantity.explicit_update
1150
1151def interpolate_from_vertices_to_edges(quantity):
1152    """Compute edge values from vertex values using linear interpolation
1153    """
1154
1155    for k in range(quantity.vertex_values.shape[0]):
1156        q0 = quantity.vertex_values[k, 0]
1157        q1 = quantity.vertex_values[k, 1]
1158        q2 = quantity.vertex_values[k, 2]
1159
1160        quantity.edge_values[k, 0] = 0.5*(q1+q2)
1161        quantity.edge_values[k, 1] = 0.5*(q0+q2)
1162        quantity.edge_values[k, 2] = 0.5*(q0+q1)
1163
1164
1165
1166def extrapolate_second_order(quantity):
1167    """Extrapolate conserved quantities from centroid to
1168    vertices for each volume using
1169    second order scheme.
1170    """
1171
1172    a, b = quantity.compute_gradients()
1173
1174    X = quantity.domain.get_vertex_coordinates()
1175    qc = quantity.centroid_values
1176    qv = quantity.vertex_values
1177
1178    #Check each triangle
1179    for k in range(quantity.domain.number_of_elements):
1180        #Centroid coordinates
1181        x, y = quantity.domain.centroid_coordinates[k]
1182
1183        #vertex coordinates
1184        x0, y0, x1, y1, x2, y2 = X[k,:]
1185
1186        #Extrapolate
1187        qv[k,0] = qc[k] + a[k]*(x0-x) + b[k]*(y0-y)
1188        qv[k,1] = qc[k] + a[k]*(x1-x) + b[k]*(y1-y)
1189        qv[k,2] = qc[k] + a[k]*(x2-x) + b[k]*(y2-y)
1190
1191
1192def compute_gradients(quantity):
1193    """Compute gradients of triangle surfaces defined by centroids of
1194    neighbouring volumes.
1195    If one edge is on the boundary, use own centroid as neighbour centroid.
1196    If two or more are on the boundary, fall back to first order scheme.
1197    """
1198
1199    from Numeric import zeros, Float
1200    from utilitites.numerical_tools import gradient
1201
1202    centroid_coordinates = quantity.domain.centroid_coordinates
1203    surrogate_neighbours = quantity.domain.surrogate_neighbours
1204    centroid_values = quantity.centroid_values
1205    number_of_boundaries = quantity.domain.number_of_boundaries
1206
1207    N = centroid_values.shape[0]
1208
1209    a = zeros(N, Float)
1210    b = zeros(N, Float)
1211
1212    for k in range(N):
1213        if number_of_boundaries[k] < 2:
1214            #Two or three true neighbours
1215
1216            #Get indices of neighbours (or self when used as surrogate)
1217            k0, k1, k2 = surrogate_neighbours[k,:]
1218
1219            #Get data
1220            q0 = centroid_values[k0]
1221            q1 = centroid_values[k1]
1222            q2 = centroid_values[k2]
1223
1224            x0, y0 = centroid_coordinates[k0] #V0 centroid
1225            x1, y1 = centroid_coordinates[k1] #V1 centroid
1226            x2, y2 = centroid_coordinates[k2] #V2 centroid
1227
1228            #Gradient
1229            a[k], b[k] = gradient(x0, y0, x1, y1, x2, y2, q0, q1, q2)
1230
1231        elif number_of_boundaries[k] == 2:
1232            #One true neighbour
1233
1234            #Get index of the one neighbour
1235            for k0 in surrogate_neighbours[k,:]:
1236                if k0 != k: break
1237            assert k0 != k
1238
1239            k1 = k  #self
1240
1241            #Get data
1242            q0 = centroid_values[k0]
1243            q1 = centroid_values[k1]
1244
1245            x0, y0 = centroid_coordinates[k0] #V0 centroid
1246            x1, y1 = centroid_coordinates[k1] #V1 centroid
1247
1248            #Gradient
1249            a[k], b[k] = gradient2(x0, y0, x1, y1, q0, q1)
1250        else:
1251            #No true neighbours -
1252            #Fall back to first order scheme
1253            pass
1254
1255
1256    return a, b
1257
1258
1259
1260def limit(quantity):
1261    """Limit slopes for each volume to eliminate artificial variance
1262    introduced by e.g. second order extrapolator
1263
1264    This is an unsophisticated limiter as it does not take into
1265    account dependencies among quantities.
1266
1267    precondition:
1268    vertex values are estimated from gradient
1269    postcondition:
1270    vertex values are updated
1271    """
1272
1273    from Numeric import zeros, Float
1274
1275    N = quantity.domain.number_of_elements
1276
1277    beta_w = quantity.domain.beta_w
1278
1279    qc = quantity.centroid_values
1280    qv = quantity.vertex_values
1281
1282    #Find min and max of this and neighbour's centroid values
1283    qmax = zeros(qc.shape, Float)
1284    qmin = zeros(qc.shape, Float)
1285
1286    for k in range(N):
1287        qmax[k] = qmin[k] = qc[k]
1288        for i in range(3):
1289            n = quantity.domain.neighbours[k,i]
1290            if n >= 0:
1291                qn = qc[n] #Neighbour's centroid value
1292
1293                qmin[k] = min(qmin[k], qn)
1294                qmax[k] = max(qmax[k], qn)
1295
1296
1297    #Diffences between centroids and maxima/minima
1298    dqmax = qmax - qc
1299    dqmin = qmin - qc
1300
1301    #Deltas between vertex and centroid values
1302    dq = zeros(qv.shape, Float)
1303    for i in range(3):
1304        dq[:,i] = qv[:,i] - qc
1305
1306    #Phi limiter
1307    for k in range(N):
1308
1309        #Find the gradient limiter (phi) across vertices
1310        phi = 1.0
1311        for i in range(3):
1312            r = 1.0
1313            if (dq[k,i] > 0): r = dqmax[k]/dq[k,i]
1314            if (dq[k,i] < 0): r = dqmin[k]/dq[k,i]
1315
1316            phi = min( min(r*beta_w, 1), phi )
1317
1318        #Then update using phi limiter
1319        for i in range(3):
1320            qv[k,i] = qc[k] + phi*dq[k,i]
1321
1322
1323
1324from anuga.utilities import compile
1325if compile.can_use_C_extension('quantity_ext.c'):
1326    #Replace python version with c implementations
1327
1328    from quantity_ext import limit, compute_gradients,\
1329    extrapolate_second_order, interpolate_from_vertices_to_edges, update
Note: See TracBrowser for help on using the repository browser.