source: anuga_core/source/anuga/utilities/numerical_tools.py @ 4668

Last change on this file since 4668 was 4551, checked in by ole, 18 years ago

Introduced maximal_inundation_elevation (runup) for sww files

File size: 9.3 KB
Line 
1#!/usr/bin/env python
2"""Auxiliary numerical tools
3
4"""
5
6from math import acos, pi, sqrt
7from warnings import warn
8
9#Establish which Numeric package to use
10#(this should move to somewhere central)
11#try:
12#    from scipy import ArrayType, array, sum, innerproduct, ravel, sqrt,
13# searchsorted, sort, concatenate, Float, arange   
14#except:
15#    #print 'Could not find scipy - using Numeric'
16#    from Numeric import ArrayType, array, sum, innerproduct, ravel, sqrt,
17#searchsorted, sort, concatenate, Float, arange
18
19from Numeric import ArrayType, array, sum, innerproduct, ravel, sqrt,\
20     searchsorted, sort, concatenate, Float, arange   
21
22# Getting an infinite number to use when using Numeric
23#INF = (array([1])/0.)[0]
24
25NAN = (array([1])/0.)[0]
26# Note, INF is used instead of NAN (Not a number), since Numeric has no NAN
27# if we use a package that has NAN, this should be updated to use NAN.
28
29# Static variable used by get_machine_precision
30machine_precision = None
31
32
33def safe_acos(x):
34    """Safely compute acos
35
36    Protect against cases where input argument x is outside the allowed
37    interval [-1.0, 1.0] by no more than machine precision
38    """
39
40    error_msg = 'Input to acos is outside allowed domain [-1.0, 1.0].'+\
41                'I got %.12f' %x
42    warning_msg = 'Changing argument to acos from %.18f to %.1f' %(x, sign(x))
43
44    eps = get_machine_precision() # Machine precision
45    if x < -1.0:
46        if x < -1.0 - eps:
47            raise ValueError, errmsg
48        else:
49            warn(warning_msg)
50            x = -1.0
51
52    if x > 1.0:
53        if x > 1.0 + eps:
54            raise ValueError, errmsg
55        else:
56            print 'NOTE: changing argument to acos from %.18f to 1.0' %x
57            x = 1.0
58
59    return acos(x)
60
61
62def sign(x):
63    if x > 0: return 1
64    if x < 0: return -1
65    if x == 0: return 0   
66
67
68def is_scalar(x):
69    """True if x is a scalar (constant numeric value)
70    """
71
72    from types import IntType, FloatType
73    if type(x) in [IntType, FloatType]:
74        return True
75    else:
76        return False
77
78def angle(v1, v2=None):
79    """Compute angle between 2D vectors v1 and v2.
80   
81    If v2 is not specified it will default
82    to e1 (the unit vector in the x-direction)
83
84    The angle is measured as a number in [0, 2pi] from v2 to v1.
85    """
86 
87    # Prepare two Numeric vectors
88    if v2 is None:
89        v2 = [1.0, 0.0] # Unit vector along the x-axis
90       
91    v1 = ensure_numeric(v1, Float)
92    v2 = ensure_numeric(v2, Float)   
93   
94    # Normalise
95    v1 = v1/sqrt(sum(v1**2))
96    v2 = v2/sqrt(sum(v2**2))
97
98    # Compute angle
99    p = innerproduct(v1, v2)
100    c = innerproduct(v1, normal_vector(v2)) # Projection onto normal
101                                            # (negative cross product)
102       
103    theta = safe_acos(p)
104           
105   
106    # Correct if v1 is in quadrant 3 or 4 with respect to v2 (as the x-axis)
107    # If v2 was the unit vector [1,0] this would correspond to the test
108    # if v1[1] < 0: theta = 2*pi-theta   
109    # In general we use the sign of the projection onto the normal.
110    if c < 0: 
111       #Quadrant 3 or 4
112       theta = 2*pi-theta       
113       
114    return theta
115
116   
117def anglediff(v0, v1):
118    """Compute difference between angle of vector v0 (x0, y0) and v1 (x1, y1).
119    This is used for determining the ordering of vertices,
120    e.g. for checking if they are counter clockwise.
121
122    Always return a positive value
123    """
124
125    from math import pi
126
127    a0 = angle(v0)
128    a1 = angle(v1)
129
130    #Ensure that difference will be positive
131    if a0 < a1:
132        a0 += 2*pi
133
134    return a0-a1
135
136def normal_vector(v):
137    """Normal vector to v.
138
139    Returns vector 90 degrees counter clockwise to and of same length as v
140    """
141   
142    return array([-v[1], v[0]], Float)
143
144   
145#def crossproduct_length(v1, v2):
146#    return v1[0]*v2[1]-v2[0]*v1[1]
147   
148       
149def mean(x):
150    """Mean value of a vector
151    """
152    return(float(sum(x))/len(x))
153
154
155def cov(x, y=None):
156    """Covariance of vectors x and y.
157
158    If y is None: return cov(x, x)
159    """
160   
161    if y is None:
162        y = x
163
164    x = ensure_numeric(x)
165    y = ensure_numeric(y)
166    msg = 'Lengths must be equal: len(x) == %d, len(y) == %d' %(len(x), len(y))
167    assert(len(x)==len(y)), msg
168
169    N = len(x) 
170    cx = x - mean(x) 
171    cy = y - mean(y) 
172
173    p = innerproduct(cx,cy)/N
174    return(p)
175
176
177def err(x, y=0, n=2, relative=True):
178    """Relative error of ||x-y|| to ||y||
179       n = 2:    Two norm
180       n = None: Max norm
181
182       If denominator evaluates to zero or
183       if y is omitted or
184       if keyword relative is False,
185       absolute error is returned
186    """
187
188    x = ensure_numeric(x)
189    if y:
190        y = ensure_numeric(y)       
191
192    if n == 2:
193        err = norm(x-y)
194        if relative is True:
195            try:
196                err = err/norm(y)
197            except:
198                pass
199
200    else:
201        err = max(abs(x-y))
202        if relative is True:
203            try:
204                err = err/max(abs(y))   
205            except:
206                pass
207         
208    return err
209 
210
211def norm(x):
212    """2-norm of x
213    """
214 
215    y = ravel(x)
216    p = sqrt(innerproduct(y,y))
217    return p
218   
219 
220def corr(x, y=None):
221    """Correlation of x and y
222    If y is None return autocorrelation of x
223    """
224
225    from math import sqrt
226    if y is None:
227        y = x
228
229    varx = cov(x)
230    vary = cov(y)
231
232    if varx == 0 or vary == 0:
233        C = 0
234    else: 
235        C = cov(x,y)/sqrt(varx * vary)   
236
237    return(C)
238
239
240       
241def ensure_numeric(A, typecode = None):
242    """Ensure that sequence is a Numeric array.
243    Inputs:
244        A: Sequence. If A is already a Numeric array it will be returned
245                     unaltered
246                     If not, an attempt is made to convert it to a Numeric
247                     array
248        A: Scalar.   Return 0-dimensional array of length 1, containing that value
249        A: String.   Array of ASCII values
250        typecode: Numeric type. If specified, use this in the conversion.
251                                If not, let Numeric decide
252
253    This function is necessary as array(A) can cause memory overflow.
254    """
255
256    if typecode is None:
257        if type(A) == ArrayType:
258            return A
259        else:
260            return array(A)
261    else:
262        if type(A) == ArrayType:
263            if A.typecode == typecode:
264                return array(A)  #FIXME: Shouldn't this just return A?
265            else:
266                return array(A,typecode)
267        else:
268            return array(A,typecode)
269
270
271
272
273def histogram(a, bins, relative=False):
274    """Standard histogram straight from the Numeric manual
275
276    If relative is True, values will be normalised againts the total and
277    thus represent frequencies rather than counts.
278    """
279
280    n = searchsorted(sort(a), bins)
281    n = concatenate( [n, [len(a)]] )
282
283    hist = n[1:]-n[:-1]
284
285    if relative is True:
286        hist = hist/float(sum(hist))
287       
288    return hist
289
290def create_bins(data, number_of_bins = None):
291    """Safely create bins for use with histogram
292    If data contains only one point or is constant, one bin will be created.
293    If number_of_bins in omitted 10 bins will be created
294    """
295
296    mx = max(data)
297    mn = min(data)
298
299    if mx == mn:
300        bins = array([mn])
301    else:
302        if number_of_bins is None:
303            number_of_bins = 10
304           
305        bins = arange(mn, mx, (mx-mn)/number_of_bins)
306
307    return bins
308
309def get_machine_precision():
310    """Calculate the machine precision for Floats
311
312    Depends on static variable machine_precision in this module
313    as this would otherwise require too much computation.
314    """
315
316    global machine_precision
317   
318    if machine_precision is None:
319        epsilon = 1.
320        while epsilon/2 + 1. > 1.:
321            epsilon /= 2
322
323        machine_precision = epsilon
324
325    return machine_precision   
326
327####################################################################
328#Python versions of function that are also implemented in numerical_tools_ext.c
329#
330
331def gradient_python(x0, y0, x1, y1, x2, y2, q0, q1, q2):
332    """
333    """
334
335    det = (y2-y0)*(x1-x0) - (y1-y0)*(x2-x0)
336    a = (y2-y0)*(q1-q0) - (y1-y0)*(q2-q0)
337    a /= det
338
339    b = (x1-x0)*(q2-q0) - (x2-x0)*(q1-q0)
340    b /= det
341
342    return a, b
343
344
345def gradient2_python(x0, y0, x1, y1, q0, q1):
346    """Compute radient based on two points and enforce zero gradient
347    in the direction orthogonal to (x1-x0), (y1-y0)
348    """
349
350    #Old code
351    #det = x0*y1 - x1*y0
352    #if det != 0.0:
353    #    a = (y1*q0 - y0*q1)/det
354    #    b = (x0*q1 - x1*q0)/det
355
356    #Correct code (ON)
357    det = (x1-x0)**2 + (y1-y0)**2
358    if det != 0.0:
359        a = (x1-x0)*(q1-q0)/det
360        b = (y1-y0)*(q1-q0)/det
361       
362    return a, b       
363
364
365##############################################
366#Initialise module
367
368from anuga.utilities import compile
369if compile.can_use_C_extension('util_ext.c'):
370    from util_ext import gradient, gradient2
371else:
372    gradient = gradient_python
373    gradient2 = gradient2_python   
374
375
376if __name__ == "__main__":
377    pass
378
379
380   
381def angle_obsolete(v):
382    """Compute angle between e1 (the unit vector in the x-direction)
383    and the specified vector v.
384   
385    Return a number in [0, 2pi]
386    """
387    from math import acos, pi, sqrt
388 
389    # Normalise v
390    v = ensure_numeric(v, Float)
391    v = v/sqrt(sum(v**2))
392   
393    # Compute angle
394    theta = acos(v[0])
395     
396    if v[1] < 0: 
397       #Quadrant 3 or 4
398        theta = 2*pi-theta
399   
400    return theta
401   
Note: See TracBrowser for help on using the repository browser.