source: trunk/anuga_core/source/anuga/utilities/numerical_tools.py @ 9271

Last change on this file since 9271 was 8820, checked in by steve, 12 years ago

Getting rid of tests for importing C code

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