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

Last change on this file since 4218 was 3945, checked in by ole, 18 years ago

One large step towards major cleanup. This has mainly to do with
the way vertex coordinates are handled internally.

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