source: anuga_core/source/anuga/abstract_2d_finite_volumes/quantity.py @ 6166

Last change on this file since 6166 was 6166, checked in by rwilson, 15 years ago

Changes: array(A).astype(X) -> array(A, X).

File size: 51.7 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
17from anuga.utilities.numerical_tools import ensure_numeric, is_scalar
18from anuga.utilities.polygon import inside_polygon
19
20from anuga.geospatial_data.geospatial_data import Geospatial_data
21from anuga.fit_interpolate.fit import fit_to_mesh
22from anuga.config import points_file_block_line_size as default_block_line_size
23from anuga.config import epsilon
24
25import Numeric as num
26
27
28class Quantity:
29
30    def __init__(self, domain, vertex_values=None):
31
32        from anuga.abstract_2d_finite_volumes.neighbour_mesh import Mesh
33
34        msg = 'First argument in Quantity.__init__ '
35        msg += 'must be of class Mesh (or a subclass thereof)'
36        assert isinstance(domain, Mesh), msg
37
38        if vertex_values is None:
39            N = len(domain) # number_of_elements
40            self.vertex_values = num.zeros((N, 3), num.Float)
41        else:
42            self.vertex_values = num.array(vertex_values, num.Float)
43
44            N, V = self.vertex_values.shape
45            assert V == 3,\
46                   'Three vertex values per element must be specified'
47
48
49            msg = 'Number of vertex values (%d) must be consistent with'\
50                  %N
51            msg += 'number of elements in specified domain (%d).'\
52                   %len(domain)
53
54            assert N == len(domain), msg
55
56        self.domain = domain
57
58        # Allocate space for other quantities
59        self.centroid_values = num.zeros(N, num.Float)
60        self.edge_values = num.zeros((N, 3), num.Float)
61
62        # Allocate space for Gradient
63        self.x_gradient = num.zeros(N, num.Float)
64        self.y_gradient = num.zeros(N, num.Float)
65
66        # Allocate space for Limiter Phi
67        self.phi = num.zeros(N, num.Float)       
68
69        # Intialise centroid and edge_values
70        self.interpolate()
71
72        # Allocate space for boundary values
73        L = len(domain.boundary)
74        self.boundary_values = num.zeros(L, num.Float)
75
76        # Allocate space for updates of conserved quantities by
77        # flux calculations and forcing functions
78
79        # Allocate space for update fields
80        self.explicit_update = num.zeros(N, num.Float )
81        self.semi_implicit_update = num.zeros(N, num.Float )
82        self.centroid_backup_values = num.zeros(N, num.Float)
83
84        self.set_beta(1.0)
85
86
87
88    # Methods for operator overloading
89    def __len__(self):
90        return self.centroid_values.shape[0]
91
92
93    def __neg__(self):
94        """Negate all values in this quantity giving meaning to the
95        expression -Q where Q is an instance of class Quantity
96        """
97
98        Q = Quantity(self.domain)
99        Q.set_values(-self.vertex_values)
100        return Q
101
102
103    def __add__(self, other):
104        """Add to self anything that could populate a quantity
105
106        E.g other can be a constant, an array, a function, another quantity
107        (except for a filename or points, attributes (for now))
108        - see set_values for details
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 __radd__(self, other):
119        """Handle cases like 7+Q, where Q is an instance of class Quantity
120        """
121        return self + other
122
123
124    def __sub__(self, other):
125        return self + -other  #Invoke __neg__
126
127    def __mul__(self, other):
128        """Multiply self with anything that could populate a quantity
129
130        E.g other can be a constant, an array, a function, another quantity
131        (except for a filename or points, attributes (for now))
132        - see set_values for details
133        """
134
135        if isinstance(other, Quantity):
136            Q = other
137        else:   
138            Q = Quantity(self.domain)
139            Q.set_values(other)
140
141        result = Quantity(self.domain)
142
143        # The product of vertex_values, edge_values and centroid_values
144        # are calculated and assigned directly without using
145        # set_values (which calls interpolate). Otherwise
146        # edge and centroid values wouldn't be products from q1 and q2
147        result.vertex_values = self.vertex_values * Q.vertex_values
148        result.edge_values = self.edge_values * Q.edge_values
149        result.centroid_values = self.centroid_values * Q.centroid_values
150       
151        return result
152
153    def __rmul__(self, other):
154        """Handle cases like 3*Q, where Q is an instance of class Quantity
155        """
156        return self * other
157
158    def __div__(self, other):
159        """Divide self with anything that could populate a quantity
160
161        E.g other can be a constant, an array, a function, another quantity
162        (except for a filename or points, attributes (for now))
163        - see set_values for details
164
165        Zero division is dealt with by adding an epsilon to the divisore
166        FIXME (Ole): Replace this with native INF once we migrate to NumPy
167        """
168
169        if isinstance(other, Quantity):
170            Q = other
171        else:   
172            Q = Quantity(self.domain)
173            Q.set_values(other)
174
175        result = Quantity(self.domain)
176
177        # The quotient of vertex_values, edge_values and centroid_values
178        # are calculated and assigned directly without using
179        # set_values (which calls interpolate). Otherwise
180        # edge and centroid values wouldn't be quotient of q1 and q2
181        result.vertex_values = self.vertex_values/(Q.vertex_values + epsilon)
182        result.edge_values = self.edge_values/(Q.edge_values + epsilon)
183        result.centroid_values = self.centroid_values/(Q.centroid_values + epsilon)
184
185        return result
186
187    def __rdiv__(self, other):
188        """Handle cases like 3/Q, where Q is an instance of class Quantity
189        """
190        return self / other
191
192    def __pow__(self, other):
193        """Raise quantity to (numerical) power
194
195        As with __mul__ vertex values are processed entry by entry
196        while centroid and edge values are re-interpolated.
197
198        Example using __pow__:
199          Q = (Q1**2 + Q2**2)**0.5
200
201        """
202
203        if isinstance(other, Quantity):
204            Q = other
205        else:   
206            Q = Quantity(self.domain)
207            Q.set_values(other)
208
209        result = Quantity(self.domain)
210
211        # The power of vertex_values, edge_values and centroid_values
212        # are calculated and assigned directly without using
213        # set_values (which calls interpolate). Otherwise
214        # edge and centroid values wouldn't be correct
215        result.vertex_values = self.vertex_values ** other
216        result.edge_values = self.edge_values ** other
217        result.centroid_values = self.centroid_values ** other
218
219        return result
220
221    #def __sqrt__(self, other):
222    #    """Define in terms of x**0.5
223    #    """
224    #    pass
225
226    def set_beta(self,beta):
227        """Set default beta value for limiting
228        """
229
230        if beta < 0.0:
231            print 'WARNING: setting beta < 0.0'
232        if beta > 2.0:
233            print 'WARNING: setting beta > 2.0'
234           
235        self.beta = beta
236
237    def get_beta(self):
238        """Get default beta value for limiting
239        """
240
241        return self.beta
242
243    def interpolate(self):
244        """Compute interpolated values at edges and centroid
245        Pre-condition: vertex_values have been set
246        """
247       
248        # FIXME (Ole): Maybe this function
249        # should move to the C-interface?
250        # However, it isn't called by validate_all.py, so it
251        # may not be that important to optimise it?
252       
253        N = self.vertex_values.shape[0]
254        for i in range(N):
255            v0 = self.vertex_values[i, 0]
256            v1 = self.vertex_values[i, 1]
257            v2 = self.vertex_values[i, 2]
258
259            self.centroid_values[i] = (v0 + v1 + v2)/3
260
261        self.interpolate_from_vertices_to_edges()
262
263
264    def interpolate_from_vertices_to_edges(self):
265        # Call correct module function
266        # (either from this module or C-extension)
267        interpolate_from_vertices_to_edges(self)
268
269    def interpolate_from_edges_to_vertices(self):
270        # Call correct module function
271        # (either from this module or C-extension)
272        interpolate_from_edges_to_vertices(self)
273
274
275
276
277    #---------------------------------------------
278    # Public interface for setting quantity values
279    #---------------------------------------------
280    def set_values(self,
281                   numeric=None,    # List, numeric array or constant
282                   quantity=None,   # Another quantity
283                   function=None,   # Callable object: f(x,y)
284                   geospatial_data=None, # Arbitrary dataset
285                   filename=None, attribute_name=None, # Input from file
286                   alpha=None,
287                   location='vertices',
288                   polygon=None,
289                   indices=None,
290                   smooth=False,
291                   verbose=False,
292                   use_cache=False):
293
294        """Set values for quantity based on different sources.
295
296        numeric:
297          Compatible list, Numeric array (see below) or constant.
298          If callable it will treated as a function (see below)
299          If instance of another Quantity it will be treated as such.
300          If geo_spatial object it will be treated as such
301
302        quantity:
303          Another quantity (compatible quantity, e.g. obtained as a
304          linear combination of quantities)
305
306        function:
307          Any callable object that takes two 1d arrays x and y
308          each of length N and returns an array also of length N.
309          The function will be evaluated at points determined by
310          location and indices in the underlying mesh.
311
312        geospatial_data:
313          Arbitrary geo spatial dataset in the form of the class
314          Geospatial_data. Mesh points are populated using
315          fit_interpolate.fit fitting
316
317        filename:
318          Name of a points file containing data points and attributes for
319          use with fit_interpolate.fit.
320
321        attribute_name:
322          If specified, any array matching that name
323          will be used. from file or geospatial_data.
324          Otherwise a default will be used.
325
326        alpha:
327          Smoothing parameter to be used with fit_interpolate.fit.
328          See module fit_interpolate.fit for further details about alpha.
329          Alpha will only be used with points, values or filename.
330          Otherwise it will be ignored.
331
332
333        location: Where values are to be stored.
334                  Permissible options are: vertices, edges, centroids
335                  Default is 'vertices'
336
337                  In case of location == 'centroids' the dimension values must
338                  be a list of a Numerical array of length N,
339                  N being the number of elements.
340                  Otherwise it must be of dimension Nx3
341
342
343                  The values will be stored in elements following their
344                  internal ordering.
345
346                  If location is 'unique vertices' indices refers the set
347                  of node ids that the operation applies to.
348                  If location is not 'unique vertices' indices refers the
349                  set of triangle ids that the operation applies to.
350
351
352                  If selected location is vertices, values for
353                  centroid and edges will be assigned interpolated
354                  values.  In any other case, only values for the
355                  specified locations will be assigned and the others
356                  will be left undefined.
357
358
359        polygon: Restrict update of quantity to locations that fall
360                 inside polygon. Polygon works by selecting indices
361                 and calling set_values recursively.
362                 Polygon mode has only been implemented for
363                 constant values so far.
364
365        indices: Restrict update of quantity to locations that are
366                 identified by indices (e.g. node ids if location
367                 is 'unique vertices' or triangle ids otherwise).
368       
369        verbose: True means that output to stdout is generated
370
371        use_cache: True means that caching of intermediate results is
372                   attempted for fit_interpolate.fit.
373
374
375
376
377        Exactly one of the arguments
378          numeric, quantity, function, filename
379        must be present.
380        """
381
382        from anuga.geospatial_data.geospatial_data import Geospatial_data
383        from types import FloatType, IntType, LongType, ListType, NoneType
384
385        # Treat special case: Polygon situation
386        # Location will be ignored and set to 'centroids'
387        # FIXME (Ole): This needs to be generalised and
388        # perhaps the notion of location and indices simplified
389
390        # FIXME (Ole): Need to compute indices based on polygon
391        # (and location) and use existing code after that.
392       
393        # See ticket:275, ticket:250, ticeket:254 for refactoring plan
394       
395        if polygon is not None:
396            if indices is not None:
397                msg = 'Only one of polygon and indices can be specified'
398                raise Exception, msg
399
400            msg = 'With polygon selected, set_quantity must provide '
401            msg += 'the keyword numeric and it must (currently) be '
402            msg += 'a constant.'
403            if numeric is None:
404                raise Exception, msg           
405            else:
406                # Check that numeric is as constant
407                assert type(numeric) in [FloatType, IntType, LongType], msg
408
409
410            location = 'centroids'
411
412
413            points = self.domain.get_centroid_coordinates(absolute=True)
414            indices = inside_polygon(points, polygon)
415           
416            self.set_values_from_constant(numeric,
417                                          location, indices, verbose)
418
419
420            self.extrapolate_first_order()
421
422            if smooth:
423                self.smooth_vertex_values()
424
425               
426            return
427       
428       
429
430
431
432
433        # General input checks
434        L = [numeric, quantity, function, geospatial_data, filename]
435        msg = 'Exactly one of the arguments '+\
436              'numeric, quantity, function, geospatial_data, '+\
437              'or filename must be present.'
438        assert L.count(None) == len(L)-1, msg
439
440
441        if location == 'edges':
442            msg = 'edges has been deprecated as valid location'
443            raise Exception, msg
444           
445        if location not in ['vertices', 'centroids', 'unique vertices']:
446            msg = 'Invalid location: %s' %location
447            raise Exception, msg
448
449
450        msg = 'Indices must be a list or None'
451        assert type(indices) in [ListType, NoneType, num.ArrayType], msg
452
453
454
455        # Determine which 'set_values_from_...' to use
456
457        if numeric is not None:
458            if type(numeric) in [FloatType, IntType, LongType]:
459                self.set_values_from_constant(numeric,
460                                              location, indices, verbose)
461            elif type(numeric) in [num.ArrayType, ListType]:
462                self.set_values_from_array(numeric,
463                                           location, indices, verbose)
464            elif callable(numeric):
465                self.set_values_from_function(numeric,
466                                              location, indices, verbose)
467            elif isinstance(numeric, Quantity):
468                self.set_values_from_quantity(numeric,
469                                              location, indices, verbose)
470            elif isinstance(numeric, Geospatial_data):
471                self.set_values_from_geospatial_data(numeric,
472                                                     alpha,
473                                                     location, indices,
474                                                     verbose=verbose,
475                                                     use_cache=use_cache)
476            else:
477                msg = 'Illegal type for argument numeric: %s' %str(numeric)
478                raise msg
479
480        elif quantity is not None:
481            self.set_values_from_quantity(quantity,
482                                          location, indices, verbose)
483        elif function is not None:
484            msg = 'Argument function must be callable'
485            assert callable(function), msg
486            self.set_values_from_function(function,
487                                          location, indices, verbose)
488        elif geospatial_data is not None:
489                self.set_values_from_geospatial_data(geospatial_data,
490                                                     alpha,
491                                                     location, indices,
492                                                     verbose=verbose,
493                                                     use_cache=use_cache)
494           
495        elif filename is not None:
496            if hasattr(self.domain, 'points_file_block_line_size'):
497                max_read_lines = self.domain.points_file_block_line_size
498            else:
499                max_read_lines = default_block_line_size
500            self.set_values_from_file(filename, attribute_name, alpha,
501                                      location, indices,
502                                      verbose=verbose,
503                                      max_read_lines=max_read_lines,
504                                      use_cache=use_cache)
505        else:
506            raise Exception, 'This can\'t happen :-)'
507
508
509
510        # Update all locations in triangles
511        if location == 'vertices' or location == 'unique vertices':
512            # Intialise centroid and edge_values
513            self.interpolate()
514
515        if location == 'centroids':
516            # Extrapolate 1st order - to capture notion of area being specified
517            self.extrapolate_first_order()
518
519
520
521    #-------------------------------------------------------------       
522    # Specific internal functions for setting values based on type
523    #-------------------------------------------------------------           
524   
525    def set_values_from_constant(self, X,
526                                 location, indices, verbose):
527        """Set quantity values from specified constant X
528        """
529
530        # FIXME (Ole): Somehow indices refer to centroids
531        # rather than vertices as default. See unit test
532        # test_set_vertex_values_using_general_interface_with_subset(self):
533       
534
535        if location == 'centroids':
536            if indices is None:
537                self.centroid_values[:] = X
538            else:
539                # Brute force
540                for i in indices:
541                    self.centroid_values[i] = X
542
543        #elif location == 'edges':
544        #    if indices is None:
545        #        self.edge_values[:] = X
546        #    else:
547        #        # Brute force
548        #        for i in indices:
549        #            self.edge_values[i] = X
550
551        elif location == 'unique vertices':
552            if indices is None:
553                self.edge_values[:] = X  #FIXME (Ole): Shouldn't this be vertex_values?
554            else:
555
556                # Go through list of unique vertices
557                for unique_vert_id in indices:
558
559                    triangles = self.domain.get_triangles_and_vertices_per_node(node=unique_vert_id)
560                   
561                    # In case there are unused points
562                    if len(triangles) == 0:
563                        continue
564                   
565                    # Go through all triangle, vertex pairs
566                    # and set corresponding vertex value
567                    for triangle_id, vertex_id in triangles:
568                        self.vertex_values[triangle_id, vertex_id] = X
569
570                    # Intialise centroid and edge_values
571                    self.interpolate()
572        else:
573            if indices is None:
574                self.vertex_values[:] = X
575            else:
576                # Brute force
577                for i_vertex in indices:
578                    self.vertex_values[i_vertex] = X
579
580
581
582
583    def set_values_from_array(self, values,
584                              location='vertices',
585                              indices=None,
586                              verbose=False):
587        """Set values for quantity
588
589        values: Numeric array
590        location: Where values are to be stored.
591        Permissible options are: vertices, centroid, unique vertices
592        Default is 'vertices'
593
594        indices - if this action is carried out on a subset of
595        elements or unique vertices
596        The element/unique vertex indices are specified here.
597
598        In case of location == 'centroid' the dimension values must
599        be a list of a Numerical array of length N, N being the number
600        of elements.
601
602        Otherwise it must be of dimension Nx3
603
604        The values will be stored in elements following their
605        internal ordering.
606
607        If selected location is vertices, values for centroid and edges
608        will be assigned interpolated values.
609        In any other case, only values for the specified locations
610        will be assigned and the others will be left undefined.
611        """
612
613        values = num.array(values, num.Float)
614
615        if indices is not None:
616            indices = num.array(indices, num.Int)
617            msg = 'Number of values must match number of indices:'
618            msg += ' You specified %d values and %d indices'\
619                   %(values.shape[0], indices.shape[0])
620            assert values.shape[0] == indices.shape[0], msg
621
622        N = self.centroid_values.shape[0]
623
624        if location == 'centroids':
625            assert len(values.shape) == 1, 'Values array must be 1d'
626
627            if indices is None:
628                msg = 'Number of values must match number of elements'
629                assert values.shape[0] == N, msg
630
631                self.centroid_values = values
632            else:
633                msg = 'Number of values must match number of indices'
634                assert values.shape[0] == indices.shape[0], msg
635
636                # Brute force
637                for i in range(len(indices)):
638                    self.centroid_values[indices[i]] = values[i]
639
640        elif location == 'unique vertices':
641            assert len(values.shape) == 1 or num.allclose(values.shape[1:], 1),\
642                   'Values array must be 1d'
643
644            self.set_vertex_values(values.flat, indices=indices)
645           
646        else:
647            # Location vertices
648            if len(values.shape) == 1:
649                self.set_vertex_values(values, indices=indices)
650
651            elif len(values.shape) == 2:
652                # Vertex values are given as a triplet for each triangle
653
654                msg = 'Array must be N x 3'
655                assert values.shape[1] == 3, msg
656
657                if indices is None:
658                    self.vertex_values = values
659                else:
660                    for element_index, value in map(None, indices, values):
661                        self.vertex_values[element_index] = value
662            else:
663                msg = 'Values array must be 1d or 2d'
664                raise msg
665           
666
667    def set_values_from_quantity(self, q,
668                                 location, indices, verbose):
669        """Set quantity values from specified quantity instance q
670
671        Location is ignored - vertices will always be used here.
672        """
673
674
675        A = q.vertex_values
676
677        msg = 'Quantities are defined on different meshes. '+\
678              'This might be a case for implementing interpolation '+\
679              'between different meshes.'
680        assert num.allclose(A.shape, self.vertex_values.shape), msg
681
682        self.set_values(A, location='vertices',
683                        indices=indices,
684                        verbose=verbose)
685
686
687    def set_values_from_function(self, f,
688                                 location='vertices',
689                                 indices=None,
690                                 verbose=False):
691        """Set values for quantity using specified function
692
693        Input
694       
695        f: x, y -> z Function where x, y and z are arrays
696        location: Where values are to be stored.
697                  Permissible options are: vertices, centroid,
698                  unique vertices
699                  Default is "vertices"
700        indices: 
701
702                 
703        """
704
705        # FIXME: Should check that function returns something sensible and
706        # raise a meaningfull exception if it returns None for example
707
708        # FIXME: Should supply absolute coordinates
709
710
711        # Compute the function values and call set_values again
712        if location == 'centroids':
713            if indices is None:
714                indices = range(len(self))
715               
716            V = num.take(self.domain.get_centroid_coordinates(), indices)
717            self.set_values(f(V[:,0], V[:,1]),
718                            location=location,
719                            indices=indices)
720           
721        elif location == 'vertices':
722
723            M = self.domain.number_of_triangles
724            V = self.domain.get_vertex_coordinates()
725
726            x = V[:,0]; y = V[:,1];                     
727            values = f(x, y)
728
729
730            # FIXME (Ole): This code should replace all the
731            # rest of this function and it would work, except
732            # one unit test in test_region fails.
733            # If that could be resolved this one will be
734            # more robust and simple.
735           
736            #values = num.reshape(values, (M,3))
737            #self.set_values(values,
738            #                location='vertices',
739            #                indices=indices)
740
741
742            # This should be removed
743            if is_scalar(values):
744                # Function returned a constant value
745                self.set_values_from_constant(values,
746                                              location, indices, verbose)
747                return
748
749            # This should be removed           
750            if indices is None:
751                for j in range(3):
752                    self.vertex_values[:,j] = values[j::3]                 
753            else:   
754                # Brute force
755                for i in indices:
756                    for j in range(3):
757                        self.vertex_values[i,j] = values[3*i+j]
758
759
760        else:
761            raise 'Not implemented: %s' %location
762
763
764
765    def set_values_from_geospatial_data(self, geospatial_data, alpha,
766                                        location, indices,
767                                        verbose=False,
768                                        use_cache=False):
769        """ Set values based on geo referenced geospatial data object.
770        """
771
772        points = geospatial_data.get_data_points(absolute=False)
773        values = geospatial_data.get_attributes()
774        data_georef = geospatial_data.get_geo_reference()
775
776
777        from anuga.coordinate_transforms.geo_reference import Geo_reference
778
779
780        points = ensure_numeric(points, num.Float)
781        values = ensure_numeric(values, num.Float)
782
783        if location != 'vertices':
784            msg = 'set_values_from_points is only defined for '+\
785                  'location=\'vertices\''
786            raise ms
787
788        coordinates = self.domain.get_nodes()
789        triangles = self.domain.triangles      #FIXME
790
791
792        # Take care of georeferencing
793        if data_georef is None:
794            data_georef = Geo_reference()
795
796
797        mesh_georef = self.domain.geo_reference
798
799
800        # Call fit_interpolate.fit function
801        # args = (coordinates, triangles, points, values)
802        args = (points, )
803        kwargs = {'vertex_coordinates': coordinates,
804                  'triangles': triangles,
805                  'mesh': None,
806                  'point_attributes': values,
807                  'data_origin': data_georef.get_origin(),
808                  'mesh_origin': mesh_georef.get_origin(),
809                  'alpha': alpha,
810                  'verbose': verbose}
811
812        vertex_attributes = apply(fit_to_mesh,
813                                  args, kwargs)       
814
815        # Call underlying method using array values
816        self.set_values_from_array(vertex_attributes,
817                                   location, indices, verbose)
818
819
820
821    def set_values_from_points(self, points, values, alpha,
822                               location, indices,
823                               data_georef=None,
824                               verbose=False,
825                               use_cache=False):
826        """
827        Set quantity values from arbitray data points using
828        fit_interpolate.fit
829        """
830
831        raise Exception, 'set_values_from_points is obsolete, use geospatial data object instead'
832       
833
834    def set_values_from_file(self, filename, attribute_name, alpha,
835                             location, indices,
836                             verbose=False,
837                             use_cache=False,
838                             max_read_lines=None):
839        """Set quantity based on arbitrary points in a points file
840        using attribute_name selects name of attribute
841        present in file.
842        If attribute_name is not specified, use first available attribute
843        as defined in geospatial_data.
844        """
845
846        from types import StringType
847        msg = 'Filename must be a text string'
848        assert type(filename) == StringType, msg
849
850
851        if location != 'vertices':
852            msg = 'set_values_from_file is only defined for '+\
853                  'location=\'vertices\''
854            raise msg
855
856        if False: 
857            # FIXME (Ole)
858            # Use mesh as defined by domain
859            # This causes problems for caching
860            # due to quantities changing, but
861            # it would work if we only passed in
862            # the appropriate Mesh object.
863            vertex_attributes = fit_to_mesh(filename,
864                                            mesh=self.domain, 
865                                            alpha=alpha,
866                                            attribute_name=attribute_name,
867                                            use_cache=use_cache,
868                                            verbose=verbose,
869                                            max_read_lines=max_read_lines)
870        else:
871            # This variant will cause Mesh object to be recreated
872            # in fit_to_mesh thus doubling up on the neighbour structure
873            nodes = self.domain.get_nodes(absolute=True)
874            triangles = self.domain.triangles     
875            vertex_attributes = fit_to_mesh(filename,
876                                            nodes, triangles, 
877                                            mesh=None,
878                                            alpha=alpha,
879                                            attribute_name=attribute_name,
880                                            use_cache=use_cache,
881                                            verbose=verbose,
882                                            max_read_lines=max_read_lines)
883                                           
884        # Call underlying method using array values
885        self.set_values_from_array(vertex_attributes,
886                                   location, indices, verbose)
887
888   
889   
890    #-----------------------------------------------------   
891    def get_extremum_index(self, mode=None, indices=None):
892        """Return index for maximum or minimum value of quantity (on centroids)
893
894        Optional arguments:
895            mode is either 'max'(default) or 'min'.
896            indices is the set of element ids that the operation applies to.
897
898        Usage:
899            i = get_extreme_index()
900
901        Notes:
902            We do not seek the extremum at vertices as each vertex can
903            have multiple values - one for each triangle sharing it.
904
905            If there are multiple cells with same maximum value, the
906            first cell encountered in the triangle array is returned.
907        """
908
909        V = self.get_values(location='centroids', indices=indices)
910
911        # Always return absolute indices
912        if mode is None or mode == 'max':
913            i = num.argmax(V)
914        elif mode == 'min':   
915            i = num.argmin(V)
916
917           
918        if indices is None:
919            return i
920        else:
921            return indices[i]
922
923
924    def get_maximum_index(self, indices=None):
925        """See get extreme index for details
926        """
927
928        return self.get_extremum_index(mode='max',
929                                       indices=indices)
930
931
932       
933    def get_maximum_value(self, indices=None):
934        """Return maximum value of quantity (on centroids)
935
936        Optional argument:
937            indices is the set of element ids that the operation applies to.
938
939        Usage:
940            v = get_maximum_value()
941
942        Note, we do not seek the maximum at vertices as each vertex can
943        have multiple values - one for each triangle sharing it           
944        """
945
946
947        i = self.get_maximum_index(indices)
948        V = self.get_values(location='centroids') #, indices=indices)
949       
950        return V[i]
951       
952
953    def get_maximum_location(self, indices=None):
954        """Return location of maximum value of quantity (on centroids)
955
956        Optional argument:
957            indices is the set of element ids that the operation applies to.
958
959        Usage:
960            x, y = get_maximum_location()
961
962
963        Notes:
964            We do not seek the maximum at vertices as each vertex can
965            have multiple values - one for each triangle sharing it.
966
967            If there are multiple cells with same maximum value, the
968            first cell encountered in the triangle array is returned.       
969        """
970
971        i = self.get_maximum_index(indices)
972        x, y = self.domain.get_centroid_coordinates()[i]
973
974        return x, y
975
976
977    def get_minimum_index(self, indices=None):
978        """See get extreme index for details
979        """       
980
981        return self.get_extremum_index(mode='min',
982                                       indices=indices)
983
984
985    def get_minimum_value(self, indices=None):
986        """Return minimum value of quantity (on centroids)
987
988        Optional argument:
989            indices is the set of element ids that the operation applies to.
990
991        Usage:
992            v = get_minimum_value()
993
994        See get_maximum_value for more details.   
995        """
996
997
998        i = self.get_minimum_index(indices)
999        V = self.get_values(location='centroids')
1000       
1001        return V[i]
1002       
1003
1004    def get_minimum_location(self, indices=None):
1005        """Return location of minimum value of quantity (on centroids)
1006
1007        Optional argument:
1008            indices is the set of element ids that the operation applies to.
1009
1010        Usage:
1011            x, y = get_minimum_location()
1012
1013
1014        Notes:
1015            We do not seek the maximum at vertices as each vertex can
1016            have multiple values - one for each triangle sharing it.
1017
1018            If there are multiple cells with same maximum value, the
1019            first cell encountered in the triangle array is returned.       
1020        """
1021
1022        i = self.get_minimum_index(indices)
1023        x, y = self.domain.get_centroid_coordinates()[i]
1024
1025        return x, y
1026
1027
1028
1029    def get_interpolated_values(self, interpolation_points,
1030                                use_cache=False,
1031                                verbose=False):
1032        """ Get values at interpolation points
1033       
1034        The argument interpolation points must be given as either a
1035        list of absolute UTM coordinates or a geospatial data object.
1036        """
1037       
1038
1039        # FIXME (Ole): Points might be converted to coordinates relative to mesh origin
1040        # This could all be refactored using the
1041        # 'change_points_geo_ref' method of Class geo_reference.
1042        # The purpose is to make interpolation points relative
1043        # to the mesh origin.
1044        #
1045        # Speed is also a consideration here.
1046       
1047       
1048        # Ensure that interpolation points is either a list of
1049        # points, Nx2 array, or geospatial and convert to Numeric array
1050        if isinstance(interpolation_points, Geospatial_data):       
1051            # Ensure interpolation points are in absolute UTM coordinates
1052            interpolation_points = interpolation_points.get_data_points(absolute=True)
1053               
1054        # Reconcile interpolation points with georeference of domain
1055        interpolation_points = self.domain.geo_reference.get_relative(interpolation_points) 
1056        interpolation_points = ensure_numeric(interpolation_points)
1057
1058       
1059        # Get internal representation (disconnected) of vertex values
1060        vertex_values, triangles = self.get_vertex_values(xy=False,
1061                                                          smooth=False)               
1062   
1063        # Get possibly precomputed interpolation object
1064        I = self.domain.get_interpolation_object()
1065
1066        # Call interpolate method with interpolation points               
1067        result = I.interpolate_block(vertex_values, interpolation_points,
1068                                     use_cache=use_cache,
1069                                     verbose=verbose)
1070                               
1071        return result
1072       
1073       
1074
1075
1076    def get_values(self, 
1077                   interpolation_points=None,
1078                   location='vertices',
1079                   indices=None,
1080                   use_cache=False,
1081                   verbose=False):
1082        """get values for quantity
1083
1084        Extract values for quantity as a Numeric array.       
1085       
1086        Inputs:
1087           interpolation_points: List of x, y coordinates where value is
1088                                 sought (using interpolation). If points
1089                                 are given, values of location and indices
1090                                 are ignored. Assume either absolute UTM
1091                                 coordinates or geospatial data object.
1092       
1093           location: Where values are to be stored.
1094                     Permissible options are: vertices, edges, centroids
1095                     and unique vertices. Default is 'vertices'
1096
1097
1098        The returned values will have the leading dimension equal to length of the indices list or
1099        N (all values) if indices is None.
1100
1101        In case of location == 'centroids' the dimension of returned
1102        values will be a list or a Numerical array of length N, N being
1103        the number of elements.
1104       
1105        In case of location == 'vertices' or 'edges' the dimension of
1106        returned values will be of dimension Nx3
1107
1108        In case of location == 'unique vertices' the average value at
1109        each vertex will be returned and the dimension of returned values
1110        will be a 1d array of length "number of vertices"
1111       
1112        Indices is the set of element ids that the operation applies to.
1113
1114        The values will be stored in elements following their
1115        internal ordering.
1116        """
1117       
1118
1119        # FIXME (Ole): I reckon we should have the option of passing a
1120        #              polygon into get_values. The question becomes how
1121        #              resulting values should be ordered.
1122       
1123        if verbose is True:
1124            print 'Getting values from %s' %location
1125
1126        if interpolation_points is not None:
1127            return self.get_interpolated_values(interpolation_points,
1128                                                use_cache=use_cache,
1129                                                verbose=verbose)
1130       
1131       
1132        # FIXME (Ole): Consider deprecating 'edges' - but not if it is used
1133        # elsewhere in ANUGA.
1134        # Edges have already been deprecated in set_values, see changeset:5521,
1135        # but *might* be useful in get_values. Any thoughts anyone?
1136       
1137        if location not in ['vertices', 'centroids', 'edges',
1138                            'unique vertices']:
1139            msg = 'Invalid location: %s' %location
1140            raise msg
1141
1142        import types
1143        assert type(indices) in [types.ListType, types.NoneType,
1144                                 num.ArrayType],\
1145                                 'Indices must be a list or None'
1146
1147        if location == 'centroids':
1148            if (indices ==  None):
1149                indices = range(len(self))
1150            return num.take(self.centroid_values,indices)
1151        elif location == 'edges':
1152            if (indices ==  None):
1153                indices = range(len(self))
1154            return num.take(self.edge_values,indices)
1155        elif location == 'unique vertices':
1156            if (indices ==  None):
1157                indices=range(self.domain.number_of_nodes)
1158            vert_values = []
1159
1160            # Go through list of unique vertices
1161            for unique_vert_id in indices:
1162                triangles = self.domain.get_triangles_and_vertices_per_node(node=unique_vert_id)
1163                   
1164                # In case there are unused points
1165                if len(triangles) == 0:
1166                    msg = 'Unique vertex not associated with triangles'
1167                    raise msg
1168
1169                # Go through all triangle, vertex pairs
1170                # Average the values
1171               
1172                # FIXME (Ole): Should we merge this with get_vertex_values
1173                sum = 0
1174                for triangle_id, vertex_id in triangles:
1175                    sum += self.vertex_values[triangle_id, vertex_id]
1176                vert_values.append(sum/len(triangles))
1177            return num.array(vert_values)
1178        else:
1179            if (indices is None):
1180                indices = range(len(self))
1181            return num.take(self.vertex_values, indices)
1182
1183
1184
1185    def set_vertex_values(self, A, indices = None):
1186        """Set vertex values for all unique vertices based on input array A
1187        which has one entry per unique vertex, i.e.
1188        one value for each row in array self.domain.nodes.
1189
1190        indices is the list of vertex_id's that will be set.
1191
1192        This function is used by set_values_from_array
1193        """
1194
1195
1196        # Assert that A can be converted to a Numeric array of appropriate dim
1197        A = ensure_numeric(A, num.Float)
1198
1199        # print 'SHAPE A', A.shape
1200        assert len(A.shape) == 1
1201
1202        if indices is None:
1203            assert A.shape[0] == self.domain.get_nodes().shape[0]
1204            vertex_list = range(A.shape[0])
1205        else:
1206            assert A.shape[0] == len(indices)
1207            vertex_list = indices
1208
1209        # Go through list of unique vertices
1210        for i_index, unique_vert_id in enumerate(vertex_list):
1211
1212
1213            triangles = self.domain.get_triangles_and_vertices_per_node(node=unique_vert_id)
1214                   
1215            # In case there are unused points
1216            if len(triangles) == 0: continue
1217
1218            # Go through all triangle, vertex pairs
1219            # touching vertex unique_vert_id and set corresponding vertex value
1220            for triangle_id, vertex_id in triangles:
1221                self.vertex_values[triangle_id, vertex_id] = A[i_index]
1222
1223        # Intialise centroid and edge_values
1224        self.interpolate()
1225
1226
1227    def smooth_vertex_values(self):
1228        """ Smooths vertex values.
1229        """
1230
1231        A,V = self.get_vertex_values(xy=False, smooth=True)
1232        self.set_vertex_values(A)
1233
1234
1235    # Methods for outputting model results
1236    def get_vertex_values(self,
1237                          xy=True,
1238                          smooth=None,
1239                          precision=None):
1240        """Return vertex values like an OBJ format i.e. one value per node.
1241
1242        The vertex values are returned as one sequence in the 1D float array A.
1243        If requested the coordinates will be returned in 1D arrays X and Y.
1244
1245        The connectivity is represented as an integer array, V, of dimension
1246        Mx3, where M is the number of triangles. Each row has three indices
1247        defining the triangle and they correspond to elements in the arrays
1248        X, Y and A.
1249
1250        if smooth is True, vertex values corresponding to one common
1251        coordinate set will be smoothed by taking the average of vertex values for each node.
1252        In this case vertex coordinates will be
1253        de-duplicated corresponding to the original nodes as obtained from
1254        the method general_mesh.get_nodes()
1255
1256        If no smoothings is required, vertex coordinates and values will
1257        be aggregated as a concatenation of values at
1258        vertices 0, vertices 1 and vertices 2. This corresponds to
1259        the node coordinates obtained from the method
1260        general_mesh.get_vertex_coordinates()
1261
1262
1263        Calling convention
1264        if xy is True:
1265           X,Y,A,V = get_vertex_values
1266        else:
1267           A,V = get_vertex_values
1268
1269        """
1270
1271
1272        if smooth is None:
1273            # Take default from domain
1274            try:
1275                smooth = self.domain.smooth
1276            except:
1277                smooth = False
1278
1279        if precision is None:
1280            precision = num.Float
1281           
1282
1283        if smooth is True:
1284            # Ensure continuous vertex values by averaging
1285            # values at each node
1286           
1287            V = self.domain.get_triangles()
1288            N = self.domain.number_of_full_nodes # Ignore ghost nodes if any
1289            A = num.zeros(N, num.Float)
1290            points = self.domain.get_nodes()           
1291           
1292            if 1:
1293                # Fast C version
1294                average_vertex_values(ensure_numeric(self.domain.vertex_value_indices),
1295                                      ensure_numeric(self.domain.number_of_triangles_per_node),
1296                                      ensure_numeric(self.vertex_values),
1297                                      A)
1298                A = A.astype(precision)
1299            else:   
1300
1301                # Slow Python version
1302               
1303                current_node = 0
1304                k = 0 # Track triangles touching on node
1305                total = 0.0
1306                for index in self.domain.vertex_value_indices:
1307                    if current_node == N:
1308                        msg = 'Current node exceeding number of nodes (%d) ' %(N)
1309                        raise msg
1310                   
1311
1312                   
1313                    k += 1
1314                   
1315                    volume_id = index / 3
1316                    vertex_id = index % 3
1317                 
1318                    #assert V[volume_id, vertex_id] == current_node
1319               
1320                    v = self.vertex_values[volume_id, vertex_id]
1321                    total += v
1322
1323                    #print 'current_node=%d, index=%d, k=%d, total=%f' %(current_node, index, k, total)
1324                    if self.domain.number_of_triangles_per_node[current_node] == k:
1325                        A[current_node] = total/k
1326               
1327                   
1328                        # Move on to next node
1329                        total = 0.0
1330                        k = 0
1331                        current_node += 1
1332
1333
1334
1335        else:
1336            # Return disconnected internal vertex values
1337            V = self.domain.get_disconnected_triangles()
1338            points = self.domain.get_vertex_coordinates()
1339            A = self.vertex_values.flat.astype(precision)
1340
1341
1342        # Return   
1343        if xy is True:
1344            X = points[:,0].astype(precision)
1345            Y = points[:,1].astype(precision)
1346           
1347            return X, Y, A, V
1348        else:
1349            return A, V           
1350
1351
1352
1353    def extrapolate_first_order(self):
1354        """Extrapolate conserved quantities from centroid to
1355        vertices and edges for each volume using
1356        first order scheme.
1357        """
1358
1359        qc = self.centroid_values
1360        qv = self.vertex_values
1361        qe = self.edge_values
1362
1363        for i in range(3):
1364            qv[:,i] = qc
1365            qe[:,i] = qc
1366
1367        self.x_gradient *= 0.0
1368        self.y_gradient *= 0.0
1369
1370
1371    def get_integral(self):
1372        """Compute the integral of quantity across entire domain
1373        """
1374        integral = 0
1375        for k in range(len(self.domain)):
1376            area = self.domain.areas[k]
1377            qc = self.centroid_values[k]
1378            integral += qc*area
1379
1380        return integral
1381
1382    def get_gradients(self):
1383        """Provide gradients. Use compute_gradients first
1384        """
1385
1386        return self.x_gradient, self.y_gradient
1387
1388
1389    def update(self, timestep):
1390        # Call correct module function
1391        # (either from this module or C-extension)
1392        return update(self, timestep)
1393
1394    def compute_gradients(self):
1395        # Call correct module function
1396        # (either from this module or C-extension)
1397        return compute_gradients(self)
1398
1399    def limit(self):
1400        # Call correct module depending on whether
1401        # basing limit calculations on edges or vertices
1402        limit_old(self)
1403
1404    def limit_vertices_by_all_neighbours(self):
1405        # Call correct module function
1406        # (either from this module or C-extension)
1407        limit_vertices_by_all_neighbours(self)
1408
1409    def limit_edges_by_all_neighbours(self):
1410        # Call correct module function
1411        # (either from this module or C-extension)
1412        limit_edges_by_all_neighbours(self)
1413
1414    def limit_edges_by_neighbour(self):
1415        # Call correct module function
1416        # (either from this module or C-extension)
1417        limit_edges_by_neighbour(self)               
1418
1419    def extrapolate_second_order(self):
1420        # Call correct module function
1421        # (either from this module or C-extension)
1422        compute_gradients(self)
1423        extrapolate_from_gradient(self)
1424       
1425    def extrapolate_second_order_and_limit_by_edge(self):
1426        # Call correct module function
1427        # (either from this module or C-extension)
1428        extrapolate_second_order_and_limit_by_edge(self)
1429
1430    def extrapolate_second_order_and_limit_by_vertex(self):
1431        # Call correct module function
1432        # (either from this module or C-extension)
1433        extrapolate_second_order_and_limit_by_vertex(self)
1434
1435    def bound_vertices_below_by_constant(self, bound):
1436        # Call correct module function
1437        # (either from this module or C-extension)
1438        bound_vertices_below_by_constant(self, bound)
1439
1440    def bound_vertices_below_by_quantity(self, quantity):
1441        # Call correct module function
1442        # (either from this module or C-extension)
1443
1444        # check consistency
1445        assert self.domain == quantity.domain
1446        bound_vertices_below_by_quantity(self, quantity)                       
1447
1448    def backup_centroid_values(self):
1449        # Call correct module function
1450        # (either from this module or C-extension)
1451        backup_centroid_values(self)
1452
1453    def saxpy_centroid_values(self,a,b):
1454        # Call correct module function
1455        # (either from this module or C-extension)
1456        saxpy_centroid_values(self,a,b)
1457   
1458#Conserved_quantity = Quantity
1459
1460class Conserved_quantity(Quantity):
1461    """Class conserved quantity being removed, use Quantity
1462
1463    """
1464
1465    def __init__(self, domain, vertex_values=None):
1466        #Quantity.__init__(self, domain, vertex_values)
1467
1468        msg = 'ERROR: Use Quantity instead of Conserved_quantity'
1469
1470        raise Exception, msg
1471
1472
1473
1474from anuga.utilities import compile
1475if compile.can_use_C_extension('quantity_ext.c'):   
1476    # Underlying C implementations can be accessed
1477
1478    from quantity_ext import \
1479         average_vertex_values,\
1480         backup_centroid_values,\
1481         saxpy_centroid_values,\
1482         compute_gradients,\
1483         limit_old,\
1484         limit_vertices_by_all_neighbours,\
1485         limit_edges_by_all_neighbours,\
1486         limit_edges_by_neighbour,\
1487         limit_gradient_by_neighbour,\
1488         extrapolate_from_gradient,\
1489         extrapolate_second_order_and_limit_by_edge,\
1490         extrapolate_second_order_and_limit_by_vertex,\
1491         bound_vertices_below_by_constant,\
1492         bound_vertices_below_by_quantity,\
1493         interpolate_from_vertices_to_edges,\
1494         interpolate_from_edges_to_vertices,\
1495         update   
1496else:
1497    msg = 'C implementations could not be accessed by %s.\n ' %__file__
1498    msg += 'Make sure compile_all.py has been run as described in '
1499    msg += 'the ANUGA installation guide.'
1500    raise Exception, msg
1501
1502
1503
Note: See TracBrowser for help on using the repository browser.