# source:branches/numpy/anuga/caching/test_caching.py@6410

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

numpy changes.

File size: 26.0 KB
Line
1
2import unittest
3
4from copy import deepcopy
5
6from anuga.caching import *
7from anuga.caching.dummy_classes_for_testing import Dummy, Dummy_memorytest
8
9import numpy as num
10
11
12# Define a test function to be cached
13#
14def f(a,b,c,N,x=0,y='abcdefg'):
15  """f(a,b,c,N)
16     Do something time consuming and produce a complex result.
17  """
18
19  import string
20
21  B = []
22  for n in range(N):
23    s = str(n+2.0/(n + 4.0))+'.a'*10
24    B.append((a,b,c,s,n,x,y))
25  return(B)
26
27def f_numeric(A, B):
28  """Operation on numeric arrays
29  """
30
31  return 3.1*A + B + 1
32
33
34def f_object(A, B):
35  """Operation of objecs of class Dummy
36  """
37
38  return A.value+B.value, A.another+B.another
39
40
41def f_generic(A):
42  return A
43
44def clear_and_create_cache(Dummy, verbose=False):
45
46  a = cache(Dummy, 'clear', verbose=verbose)
47  a = cache(Dummy, args=(9,10),
48            verbose=verbose)
49
50
51def retrieve_cache(Dummy, verbose=False):
52  if verbose: print 'Check that cache is there'
53
54  X = cache(Dummy, args=(9,10), test=1,
55            verbose=verbose)
56
58  assert X is not None, msg
59
60
61
62
63
64class Test_Caching(unittest.TestCase):
65    def setUp(self):
66        set_option('verbose', 0)  #Why
67
68        pass
69
70    def tearDown(self):
71        pass
72
73    def test_simple(self):
74        """Check set_option (and switch stats off)
75        """
76
77        set_option('savestat', 0)
78        assert options['savestat'] == 0
79        set_option('verbose', 0)
80        assert options['verbose'] == 0
81
82
83    def test_basic_caching(self):
84
85        verbose=False
86        # Make some test input arguments
87        #
88        N = 5000  #Make N fairly small here
89
90        a = [1,2]
91        b = ('Thou shalt count the number three',4)
92        c = {'Five is right out': 6, (7,8): 9}
93        x = 3
94        y = 'holy hand granate'
95
96        # Test caching
97        #
98
99        comprange = 2
100
101        for comp in range(comprange):
102
103            # Evaluate and store
104            #
105            T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, evaluate=1, \
106                       compression=comp, verbose=verbose)
107
108            # Retrieve
109            #
110            T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, compression=comp)
111
112            # Reference result
113            #
114            T3 = f(a,b,c,N,x=x,y=y)  # Compute without caching
115
116
117            assert T1 == T2, 'Cached result does not match computed result'
118            assert T2 == T3, 'Cached result does not match computed result'
119
120
121
122    def test_caching_of_numeric_arrays(self):
123        """test_caching_of_numeric_arrays
124
125        Test that numeric arrays can be recognised by caching even if their id's are different
126        """
127
128        verbose = False
129
130        # Make some test input arguments
131        A0 = num.arange(5)
132        B0 = num.array([1.1, 2.2, 0.0, -5, -5])
133
134        A1 = A0.copy()
135        B1 = B0.copy()
136
137        # Check that their ids are different
138        assert id(A0) != id(A1)
139        assert id(B0) != id(B1)
140
141
142        # Test caching
143        comprange = 2
144        for comp in range(comprange):
145
146            # Evaluate and store
147            T1 = cache(f_numeric, (A0, B0), evaluate=1,
148                       compression=comp, verbose=verbose)
149
150            # Retrieve
151            T2 = cache(f_numeric, (A1, B1),
152                       compression=comp, test=1, verbose=verbose)
153
154            # Check for presence of cached result
155            msg = 'Different array objects with same contents were not recognised'
156            assert T2 is not None, msg
157
158            # Reference result
159            T3 = f_numeric(A0, B0) # Compute without caching
160
161
162            assert num.alltrue(T1 == T2), 'Cached result does not match computed result'
163            assert num.alltrue(T2 == T3), 'Cached result does not match computed result'
164
165
166    def test_hash_collision(self):
167        """Test that hash collisons are dealt with correctly"""
168
169        verbose = False
170
171        # Make test input arguments
172        A0 = num.arange(5)*1.0
173        B = ('x', 15)
174
175        # Create different A that hashes to the same address (having the same average)
176        A1 = num.array([2.0, 2.0, 2.0, 2.0, 2.0])
177
178        assert myhash(A0) == myhash(A1)
179
180
181        # Test caching
182        comprange = 2
183        for comp in range(comprange):
184
185            # Clear
186            cache(f_numeric, (A0, A0), clear=1,
187                  compression=comp, verbose=verbose)
188            cache(f_numeric, (A1, A1), clear=1,
189                  compression=comp, verbose=verbose)
190
191
192            # Evaluate and store
193            T1 = cache(f_numeric, (A0, A0), evaluate=1,
194                       compression=comp, verbose=verbose)
195
196
197            # Check that A1 doesn't trigger retrieval of the previous result
198            # even though it hashes to the same address
199            T2 = cache(f_numeric, (A1, A1),
200                       compression=comp, verbose=verbose)
201
202            T1_ref = f_numeric(A0, A0)
203            T2_ref = f_numeric(A1, A1)
204
205            assert num.alltrue(T1 == T1_ref)
206            assert num.alltrue(T2 == T2_ref)
207
208
209    def test_caching_of_dictionaries(self):
210        """test_caching_of_dictionaries
211
212        Real example from ANUGA that caused some
213        hashing problems
214        """
215
216
217        verbose = False #True
218
219        D = {'point_attributes': None,
220             'use_cache': True,
221             'vertex_coordinates': None,
222             'verbose': True,
224             'acceptable_overshoot': 1.01,
225             'mesh': None,
226             'data_origin': None,
227             'alpha': 0.02,
228             'mesh_origin': None,
229             'attribute_name': None,
230             'triangles': None}
231
232        DD = deepcopy(D) # Mangles the dictionary ordering
233
234        assert myhash(DD) == myhash(D)
235
236        # Also test caching now that we are at it
237        comprange = 2
238        for comp in range(comprange):
239
240            # Evaluate and store using D
241            T1 = cache(f_generic, D, evaluate=1,
242                       compression=comp, verbose=verbose)
243
244            # Retrieve using copy (DD)
245            T2 = cache(f_generic, DD,
246                       compression=comp, test=1, verbose=verbose)
247
248            # Check for presence of cached result
250            assert T2 is not None, msg
251
252            # Reference result
253            T3 = f_generic(D) # Compute without caching
254
255
256            msg = 'Cached result does not match computed result'
257
258            # Compare dictionaries
259            for key in T1:
260                assert T1[key] == T2[key]
261                assert T2[key] == T3[key]
262
263
264
265
266
267    def test_caching_of_objects(self):
268        """test_caching_of_objects
269
270        Test that Objecs can be recognised as input variabelse
271        by caching even if their id's are different
272        """
273
274
275        verbose = False
276
277        # Make some test input arguments
278        A0 = Dummy(5, 7)
279        B0 = Dummy(2.2, -5)
280
281        A0.new_attribute = 'x'
282        B0.new_attribute = 'x'
283
284        A1 = deepcopy(A0)
285        B1 = deepcopy(B0)
286
287        # Check that their ids are different
288        assert id(A0) != id(A1)
289        assert id(B0) != id(B1)
290
291
292        # Test caching
293        comprange = 2
294        for comp in range(comprange):
295
296            # Evaluate and store
297            T1 = cache(f_object, (A0, B0), evaluate=1,
298                       compression=comp, verbose=verbose)
299
300            # Retrieve
301            T2 = cache(f_object, (A1, B1),
302                       compression=comp,
303                       test=1,
304                       verbose=verbose)
305
306            # Check for presence of cached result
307            msg = 'Different objects with same attributes were not recognised'
308            assert T2 is not None, msg
309
310            # Reference result
311            T3 = f_object(A0, B0) # Compute without caching
312
313
314            assert T1 == T2, 'Cached result does not match computed result'
315            assert T2 == T3, 'Cached result does not match computed result'
316
317
318    def test_caching_of_circular_structures(self):
319        """test_caching_of_circular_structures
320
321        Test that Caching doesn't recurse infinitely in case of
322        circular or self-referencing structures
323        """
324
325        verbose = False
326
327        # Create input argument
328        A = Dummy(5, 7)
329        B = {'x': 10, 'A': A}
330        C = [B, 15]
331        A.value = C # Make it circular
332        A.x = [1,2,C,5,A] # More circular and self referential
333
334        AA = deepcopy(A)
335
336        # Test caching
337        comprange = 2
338        for comp in range(comprange):
339
340            # Evaluate and store
341            T1 = cache(f_generic, A,
342                       evaluate=1,
343                       compression=comp, verbose=verbose)
344
345            # Retrieve
346            T2 = cache(f_generic, AA,
347                       compression=comp,
348                       test=1, verbose=verbose)
349
350            # Check for presence of cached result
352            assert T2 is not None, msg
353
354            # Reference result
355            T3 = f_generic(A) # Compute without caching
356
357
358            msg = 'Cached result does not match computed result'
359            assert str(T1) == str(T2), msg
360            assert str(T2) == str(T3), msg
361
362
363    def test_caching_of_callable_objects(self):
364        """test_caching_of_callable_objects(self)
365
366        Test that caching will discern between calls of
367        two different instances of a callable object with the same input.
368
369        This cause problems with add_quantity in ANUGA in changeset:6225
370        where a previous instance of Polygon_function was picked up.
371        """
372
373        class call:
374
375          def __init__(self, a, b):
376            self.a = a
377            self.b = b
378
379          def __call__(self, x):
380            return self.a*x + self.b
381
382
383        f1 = call(2, 3)
384        f2 = call(5, 7)
385
386
387        # Check that hash value of callable objects don't change
388        if os.name == 'posix' and os.uname()[4] in ['x86_64', 'ia64']:
389          # 64 bit hash values
390          f1hash = 1914027059797211698
391          f2hash = 1914027059807087171
392        else:
393          f1hash = -758136387
394          f2hash = -11221564
395
396        assert myhash(f1) == f1hash
397        assert myhash(f2) == f2hash
398
399        bc1 = get_bytecode(f1)
400        bc2 = get_bytecode(f2)
401
402        #print 'bc1', bc1
403        #print 'bc2', bc2
404
405        msg = 'Byte code should be different'
406        assert bc1 != bc2, msg
407
408
409        x = num.arange(10).astype(num.float)
410
411        ref1 = f1(x)
412        ref2 = f2(x)
413
414        # Clear cache for f1(x) and f2(x) and verify that all is clear
415        cache(f1, x, clear=True, verbose=False)
416        flag = cache(f1, x, test=True, verbose=False)
417        assert flag is None
418
419        cache(f2, x, clear=True, verbose=False)
420        flag = cache(f2, x, test=True, verbose=False)
421        assert flag is None
422
423        # Run f1(x) and cache
424        res1 = cache(f1, x, verbose=False)
425        assert num.allclose(res1, ref1)
426
427        # Test that f1(x) has been cached correctly
428        res1 = cache(f1, x, test=True, verbose=False)
429        assert num.allclose(res1, ref1)
430
431        # Test that f2(x) is still clear
432        cache(f2, x, clear=True, verbose=False)
433        flag = cache(f2, x, test=True, verbose=False)
434        assert flag is None
435
436        # Run f2(x) and test result
437        res2 = cache(f2, x, verbose=False)
438        msg = 'Wrong result for f2(x)'
439        assert num.allclose(res2, ref2), msg
440
441
442
443
444    def test_uniqueness_of_hash_values(self):
445        """test_uniqueness_of_hash_values(self):
446
447        Test that Caching can handle a realistic
448        complex structure by hashing it consistently and
449        uniquely.
450        """
451
452        verbose = False
453
454        # Create input argument
455        A = Dummy(5, 7)
456        B = {'x': 10, 'A': A}
457        C = [B, num.array([1.2, 3, 5, 0.1])]
458        A.value = C # Make it circular
459
460        # Create identical but separate object
461        AA = Dummy(None, None)
462        BB = {'A': AA, 'x': 10}
463        CC = [BB, num.array([1.200, 3.000, 5.00, 1.0/10])]
464        AA.value = CC # Make it circular
465        AA.another = 3+4
466
467
468        assert myhash(A) == myhash(AA)
469
470
471
472        # Also test caching now that we are at it
473        comprange = 2
474        for comp in range(comprange):
475
476            # Evaluate and store using A
477            T1 = cache(f_generic, A, evaluate=1,
478                       compression=comp, verbose=verbose)
479
480            # Retrieve using copy (AA)
481            T2 = cache(f_generic, AA,
482                       compression=comp, test=1, verbose=verbose)
483
484            # Check for presence of cached result
486            assert T2 is not None, msg
487
488            # Reference result
489            T3 = f_generic(A) # Compute without caching
490
491
492            msg = 'Cached result does not match computed result'
493            assert str(T1) == str(T2), msg
494            assert str(T2) == str(T3), msg
495
496
497
498    def test_caching_of_simple_circular_dictionaries(self):
499        """test_caching_of_circular_structures
500
501        Test that Caching doesn't recurse infinitely in case of
502        circular or self-referencing structures
503        """
504
505        verbose = False #True
506
507        # Create input argument
508        A = {'x': 10, 'B': None}
509        B = [A, 15]
510        A['B'] = B # Make it circular
511
512        # Test caching
513        comprange = 2
514        for comp in range(comprange):
515
516            # Evaluate and store
517            T1 = cache(f_generic, A, evaluate=1,
518                       compression=comp, verbose=verbose)
519
520
521            # Retrieve
522            T2 = cache(f_generic, A,
523                       compression=comp, test=1, verbose=verbose)
524
525            # Check for presence of cached result
527            assert T2 is not None, msg
528
529            # Reference result
530            T3 = f_generic(A) # Compute without caching
531
532
533            msg = 'Cached result does not match computed result'
534            assert str(T1) == str(T2), msg
535            assert str(T2) == str(T3), msg
536
537
538
539    def test_cachefiles(self):
540        """Test existence of cachefiles
541        """
542        N = 5000  #Make N fairly small here
543
544        a = [1,2]
545        b = ('Thou shalt count the number three',4)
546        c = {'Five is right out': 6, (7,8): 9}
547        x = 3
548        y = 'holy hand granate'
549
550
551        FN = cache(f,(a,b,c,N), {'x':x, 'y':y}, verbose=0, \
552                  return_filename = 1)
553
554
555        assert FN[:2] == 'f['
556
557        CD = checkdir(cachedir)
558        compression = 1
559
560        (datafile,compressed0) = myopen(CD+FN+'_'+file_types[0],"rb",compression)
561        (argsfile,compressed1) = myopen(CD+FN+'_'+file_types[1],"rb",compression)
563
564        datafile.close()
565        argsfile.close()
567
568    def test_test(self):
569        """Test 'test' function when cache is present
570        """
571
572        verbose = False
573
574        N = 5
575
576        a = [1,2]
577        b = ('Thou shalt count the number three',4)
578        c = {'Five is right out': 6, (7,8): 9}
579        x = 3
580        y = 'holy hand granate'
581
582
583        T1 = cache(f, (a,b,c,N), {'x':x, 'y':y},
584                   evaluate=1,
585                   verbose=verbose)
586
587        T2 = cache(f, (a,b,c,N), {'x':x, 'y':y},
588                   test=1,
589                   verbose=verbose)
590
591
592        # Check for presence of cached result
593        msg = 'Different objects with same attributes were not recognised'
594        assert T2 is not None, msg
595
596        assert T1 == T2, "Option 'test' when cache file present failed"
597
598
599    def test_clear(self):
600        """Test that 'clear' works
601        """
602
603        N = 5000  #Make N fairly small here
604
605        a = [1,2]
606        b = ('Thou shalt count the number three',4)
607        c = {'Five is right out': 6, (7,8): 9}
608        x = 3
609        y = 'holy hand granate'
610
611
612        T1 = cache(f,(a,b,c,N), {'x':x, 'y':y}, evaluate = 1)
613
614
615        cache(f, (a,b,c,N), {'x':x, 'y':y}, clear = 1)
616
617
618        # Test 'test' function when cache is absent
619
620
621        T4 = cache(f, (a,b,c,N), {'x':x, 'y':y}, test=1)
622        #print 'T4', T4
623        assert T4 is None, "Option 'test' when cache absent failed"
624
625
626    def test_dependencies(self):
627
628        # Make a dependency file
629        CD = checkdir(cachedir)
630
631        DepFN = CD + 'testfile.tmp'
632        DepFN_wildcard = CD + 'test*.tmp'
633        Depfile = open(DepFN,'w')
634        Depfile.write('We are the knights who say NI!')
635        Depfile.close()
636
637
638        # Test
639        #
640
641        N = 5000  #Make N fairly small here
642
643        a = [1,2]
644        b = ('Thou shalt count the number three',4)
645        c = {'Five is right out': 6, (7,8): 9}
646        x = 3
647        y = 'holy hand granate'
648
649        T1 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)
650        T2 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)
651
652        assert T1 == T2, 'Dependencies do not work'
653
654
655        # Test basic wildcard dependency
656        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN_wildcard)
657
658        assert T1 == T3, 'Dependencies with wildcards do not work'
659
660
661        # Test that changed timestamp in dependencies triggers recomputation
662
663        # Modify dependency file
664        Depfile = open(DepFN,'a')
665        Depfile.write('You must cut down the mightiest tree in the forest with a Herring')
666        Depfile.close()
667
668        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN, test = 1)
669
670        assert T3 is None, 'Changed dependencies not recognised'
671
672        # Test recomputation when dependencies have changed
673        #
674        T3 = cache(f,(a,b,c,N), {'x':x, 'y':y}, dependencies=DepFN)
675        assert T1 == T3, 'Recomputed value with changed dependencies failed'
676
677    #def test_performance(self):
678    #    """Performance test (with statistics)
679    #    Don't really rely on this as it will depend on specific computer.
680    #    """
681    #
682    #    import time
683    #    set_option('savestat', 1)
684    #
685    #    N = 300000   #Should be large on fast computers...
686    #    a = [1,2]
687    #    b = ('Thou shalt count the number three',4)
688    #    c = {'Five is right out': 6, (7,8): 9}
689    #    x = 3
690    #    y = 'holy hand granate'
691    #
692    #
693    #    tt = time.time()
694    #    T1 = cache(f,(a,b,c,N), {'x':x, 'y':y})
695    #    t1 = time.time() - tt
696    #
697    #    tt = time.time()
698    #    T2 = cache(f,(a,b,c,N), {'x':x, 'y':y})
699    #    t2 = time.time() - tt
700
701    #    assert T1 == T2
702    #     assert t1 > t2, 'WARNING: Performance a bit low - this could be specific to current platform. Try to increase N in this test'
703    #    #test_OK('Performance test: relative time saved = %s pct' \
704    #    #        %str(round((t1-t2)*100/t1,2)))
705
706
707    def test_statsfile(self):
708        """Test presence of statistics file
709        """
710        import os, string
711        statsfile  = '.cache_stat'  # Basefilename for cached statistics.
712
713        CD = checkdir(cachedir)
714        DIRLIST = os.listdir(CD)
715        SF = []
716        for FN in DIRLIST:
717            if string.find(FN,statsfile) >= 0:
718                try:
719                    fid = open(CD+FN,'r')
720                    fid.close()
721                except:
722                    raise 'Statistics files cannot be opened'
723
724
725
726    # def test_network_cachedir(self):
727
728#         #set_option('cachedir', 'H:\\.python_cache\\')
729#         set_option('cachedir', 'V:\\2\\cit\\.python_cache\\')
730#         set_option('verbose', 1)
731
732
733#         # Make some test input arguments
734#         #
735#         N = 5000  #Make N fairly small here
736
737#         a = [1,2]
738#         b = ('Thou shalt count the number three',4)
739#         c = {'Five is right out': 6, (7,8): 9}
740#         x = 3
741#         y = 'holy hand granate'
742
743#         # Test caching
744#         #
745
746#         comprange = 2
747
748#         for comp in range(comprange):
749
750#             # Evaluate and store
751#             #
752#             T1 = cache(f, (a,b,c,N), {'x':x, 'y':y}, evaluate=1, \
753#                        compression=comp)
754
755#             # Retrieve
756#             #
757#             T2 = cache(f, (a,b,c,N), {'x':x, 'y':y}, compression=comp)
758
759#             # Reference result
760#             #
761#             T3 = f(a,b,c,N,x=x,y=y)  # Compute without caching
762
763
764#             assert T1 == T2, 'Cached result does not match computed result'
765#             assert T2 == T3, 'Cached result does not match computed result'
766
767
768
769    def Will_fail_test_objects(self):
770      """
771      This test shows how instances can't be effectively cached.
772      myhash uses hash which uses id which uses the memory address.
773
774      This will be a NIL problem once caching can handle instances with different id's and
775      identical content.
776
777      The test is disabled.
778      """
779
780
781
782      verbose = True
783      #verbose = False
784
785      for i in range(2):
786        if verbose: print "clear cache"
787        a = cache(Dummy, 'clear')
788
789        if verbose: print "cache for first time"
790        a = cache(Dummy, args=(9,10), verbose=verbose)
791        hash_value = myhash(a)
792
793        #print "hash_value",hash_value
794        if verbose: print "cache for second time"
795        a = cache(Dummy, args=(9,10), verbose=verbose)
796
797        #print "myhash(a)",myhash(a)
798        assert hash_value == myhash(a)
799
800
801    # This test works in the caching dir and in anuga_core, but not in the
802    # anuga_core/source/anuga dir
803    # This has to do with pickle (see e.g. http://telin.ugent.be/~slippens/drupal/pickleproblem)
804    # The error message is
805    # PicklingError: Can't pickle test_caching.Dummy: it's not the same object as test_caching.Dummy
806    #
807    # This problem was fixed by moving the class into the separate module
808
809    def test_objects_are_created(self):
810      """
811      This test shows how instances can be created from cache
812      as long as input arguments are unchanged.
813
814      Such instances will have different id's and cannot be currently be used as input
815      arguments in subsequent caches. However, this is still useful.
816
817      Do it for all combinations of compression
818
819      """
820
821      verbose = False
822
823      for compression_store in [False, True]:
824        for compression_retrieve in [False, True]:
825
826          if verbose: print 'clear cache'
827          a = cache(Dummy, 'clear')
828
829          if verbose: print 'cache for first time'
830          a_ref = cache(Dummy, args=(9,10),
831                        compression=compression_store,
832                        verbose=verbose)
833
834          if verbose: print 'Check that cache is there'
835          assert cache(Dummy, args=(9,10), test=1,
836                       compression=compression_retrieve,
837                       verbose=verbose)
838
839          if verbose: print 'Check cached result'
840          a = cache(Dummy, args=(9,10),
841                    compression=compression_store,
842                    verbose=verbose)
843          assert a.__dict__ == a_ref.__dict__
844
845
846
847    # NOTE (Ole): This test has been commented out because,
848    #             although the test will pass (not anymore!)
849    #             inside the caching dir and also at the anuga_core level,
850    #             it won't pass at the anuga_core/source/anuga level.
851    # It may have to do with the comments above.
852    #
853    # But this is a very nice test to run occasionally within the caching
854    # area
855    def Xtest_objects_are_created_memory(self):
856      """
857
858      This test shows how instances can be created from cache
859      as long as input arguments are unchanged - even if the class
860      lives in different memory locations.
861
862      This is using cache created in the main program below
863      """
864
865      verbose = True #False
866
867      # Redefine class Dummy_memorytest
868      class Dummy_memorytest:
869        def __init__(self, value, another):
870          self.value = value
871
872      # Make sure that class has been redefined to another address
873      print
876      msg = 'Redefined class ended up at same memory location as '
877      msg += 'original class making this test irrelevant. Try to run '
878      msg += 'it again and see if this error goes away.'
879      msg += 'If it persists contact Ole.Nielsen@ga.gov.au'
880      assert initial_addr != `Dummy_memorytest`, msg
881
882
883      retrieve_cache(Dummy_memorytest, verbose=verbose)
884
885# Cache created for use with 'test_objects_are_created_memory'