source: branches/source_numpy_conversion/anuga/utilities/numerical_tools.py @ 6768

Last change on this file since 6768 was 5948, checked in by rwilson, 16 years ago

More NumPy? changes.

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