Σημειωματάριο Τρίτης, 24 Φεβ. 2015

Ας αρχίσουμε σήμερα γράφοντας μια συνάρτηση anapoda η οποία παίρνει ένα όρισμα τύπου string και επιστρέφει την ίδια λέξη γραμμένη ανάποδα. Βλέπουμε εδώ ότι μπορούμε να διανύσουμε τα γράμματα ενός string ακριβώς όπως διανύουμε τα στοιχεία μιας λίστας.

Ξεκινάμε ορίζοντας μια μεταβλητή ret μέσα στη συνάρτηση που να είναι αρχικά ίση με την κενή λέξη ("" ή ''). Στη συνέχεια διανύουμε τη λέξη από τα αριστερά προς τα δεξιά και για κάθε γράμμα x της λέξης που παίρνουμε το προσθέτουμε στη μεταβλητή ret από μπροστά με την εντολή ret = x + ret. Έτσι στο τέλος της ανακύκλωσης το ret έχει μέσα τα περιεχόμενα του ορίσματος s αλλά γραμμένα ανάποδα.

In [26]:
def anapoda(s):
    "Γυρνάει το string s γραμμένο ανάποδα"
    ret=""
    for x in s:
       ret = x + ret
    return ret

print anapoda("o58763")
36785o

Τοπικές και καθολικές (local and global) μεταβλητές

Εδώ προσθέσαμε στο προηγούμενο πρόγραμμα μια εντολή στο τέλος, print ret, δοκιμάζοντας να τυπώσουμε τη μεταβλητή ret που χρησιμοποιείται στη συνάρτηση anapoda. Παρατηρούμε ότι το πρόγραμμα βγάζει σφάλμα, NameError: name 'ret' is not defined.

Αυτό το σφάλμα σημαίνει ότι πάμε να χρησιμοποιήσουμε τη μεταβλητή ret (να την τυπώσουμε) χωρίς να έχει αυτή η μεταβλητή οριστεί, να έχει δηλ. ήδη πάρει τιμή κάπου.

Αυτό φαίνεται περίεργο μια και όταν εκτελείται η εντολή print ret η συνάρτηση anapoda έχει ήδη εκτελεστεί και η μεταβλητή ret έχει ήδη πάρει τιμή.

Η εξήγηση γι' αυτό είναι η εξής.

Στην python το κάθε όνομα (μεταβλητής, συνάρτησης, κλπ) χρησιμεύει ως αναφορά προς ένα αντικείμενο που είναι αποθηκευμένο στη μνήμη. Έτσι λοιπόν υπάρχει μια αντιστοιχία ονόματος, αντικειμένου (namespace, symbol table) όσο εκτελείται το πρόγραμμά μας και η αντιστοιχία αυτή χρησιμοποιείται από τον python interpreter για να βρει το αντικείμενο στη μνήμη στο οποίο αναφέρεται ένα όνομα και είτε να διαβάσει την τιμή του είτε να την αλλάξει.

Όμως υπάρχουν παραπάνω από μια τέτοιες αντιστοιχίσεις.

Στο πρόγραμμα που έχουμε εδώ υπάρχει η αντιστοίχιση που χρησιμοποιεί το κυρίως πρόγραμμα (καθολική ή global) αλλά όταν εκτελείται η συνάρτηση anapoda έχει και αυτή τη δικιά της αντιστοίχιση ονομάτων, αντικειμένων (τοπική ή local). Ένα όνομα εισάγεται σε μια αντιστοίχιση την πρώτη φορά που του δίνουμε τιμή. Επίσης όταν η python δει ένα όνομα του οποίου η τιμή ζητείται τότε πάει και το ψάχνει πρώτα στην τοπική αντιστοίχιση (αν υπάρχει) και μετά στην καθολική αντιστοίχιση.

Ας δούμε λοιπόν λεπτομερώς το γιατί εμφανίζεται το σφάλμα αυτό.

Όταν αρχίζει να εκτελείται το κυρίως πρόγραμμα το πρώτο πράγμα που κάνει είναι να καλέσει τη συνάρτηση anapoda("o58763"). Μέχρι στιγμής το μόνο όνομα που έχει μπει στην καθολική αντιστοίχιση είναι το όνομα anapoda, το οποίο η python έχει καταγράψει στην καθολική αντιστοίχιση ως όνομα που αναφέρεται σε μια συνάρτηση. Μόλις μεταφερθεί ο έλεγχος στη συνάρτηση anapoda ανοίγει η νέα τοπική ανιστοίχιση της συνάρτησης αυτής, αρχικά κενή, και μπαίνει μέσα πρώτα το όνομα s το οποίο είναι το όνομα της παραμέτρου. Όταν αρχίσει η κλήση της anapoda στην πρώτη εντολή παίρνει τιμή η μεταβλητή ret. Τότε η python εισάγει την ret στην τρέχουσα αντιστοίχιση, η οποία είναι η αντιστοίχιση της συνάρτησης anapoda και όχι η καθολική αντιστοίχιση. Μόλις αρχίσει να εκτελείται η ανακύκλωση τότε εισάγεται και το όνομα x στην αντιστοίχιση και δεν εισάγονται άλλα. Λίγο πριν τελειώσει δηλ. η κλήση της συνάρτησης anapoda και επιστρέψει ο έλεγχος στο κυρίως πρόγραμμα η τοπική αντιστοίχιση ονομάτων,αντικειμένων της συνάρτησης anapoda περιλαμβάνει τα ονόματα s, x και ret, ενώ η καθολική αντιστοίχιση περιλαμβάνει μόνο το όνομα anapoda.

Αφού επιστρέψει ο έλεγχος στο κυρίως πρόγραμμα (λίγο πριν τυπωθεί το αποτέλεσμα που επιστρέφει η συνάρτηση) η τοπική αντιστοίχιση της συνάρτησης anapoda διαγράφεται από τη μνήμη. Τώρα πλέον υπάρχει μόνο η καθολική αντιστοίχιση και αυτή δεν περιέχει μέσα καμία αναφορά στο όνομα ret. Έτσι, όταν στην επόμενη εντολή αναζητείται η τιμή της ret ώστε να εκτυπωθεί, τότε αυτή δε μπορεί να βρεθεί στην υπάρχουσα αντιστοίχιση και έτσι προκύπτει το σφάλμα.

In [5]:
def anapoda(s):
    "Γυρνάει το string s γραμμένο ανάποδα"
    ret=""
    for x in s:
       ret = x + ret
    return ret

print anapoda("o58763")
print ret
  
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-5-194e9ee10feb> in <module>()
      7 
      8 print anapoda("o58763")
----> 9 print ret
     10 

NameError: name 'ret' is not defined
36785o

Στο επόμενο βλέπουμε μια παραλλαγή του παραπάνω προγράμματος.

Εδώ ορίζουμε πρώτα την μεταβλητή name την οποία χρησιμοποιούμε μέσα στη συνάρτηση anapoda για να τυπώσουμε ένα μήνυμα.

Μετά την εντολή name="Mihalis" (κυρίως πρόγραμμα) ο καθολικός πίνακας αντιστοίχισης ονομάτων, αντικειμένων περιέχει μόνο το όνομα name. Επίσης μπαίνει και το όνομα anapoda και δείχνει προς τον ορισμό της συνάρτησης. Η εκτέλεση του κυρίως προγράμματος συνεχίζει με την εντολή print anapoda("sdfsdf") η οποία καλεί τη συνάρτηση anapoda. Μόλις αρχίσει η εκτέλεση της συνάρτησης αυτής δημιουργείται η τοπική αντιστοίχιση ονομάτων, αντικειμένων. Σε αυτή την αντιστοίχιση μπαίνει το όνομα s, της παραμέτρου της συνάρτησης και επίσης τα ονόματα x και ret, μεταβλητές στις οποίες η συνάρτηση δίνει τιμές (αυτό πάντα συνεπάγεται ότι μια τέτοια μεταβλητή μπαίνει στην τοπικκή αντιστοίχιση). Η πρώτη εντολή της συνάρτησης που εκτελείται είναι η print "Hello",name. Εκεί η python συναντάει το όνομα name, του οποίου την τιμή θα πρέπει να αναζητήσει. Η αρχή γίνεται από την τοπική αντιστοίχιση όπου όμως το όνομα name δεν υπάρχει και έτσι συνεχίζει η python να ψάχνει το όνομα στην αμέσως ανώτερη αντιστοίχιση, την καθολική, όπου και βρίσκει το όνομα και την τιμή του (Mihalis). Η τιμή αυτή τυπώνεται και η εκτέλεση της συνάρτησης συνεχίζει όπως και στο προηγούμενο παράδειγμα.

Έπειτα εκτελείται η εντολή name="Manolis" στο κυρίως πρόγραμμα και η συνάρτηση ξανακαλείται με τα αποτελέσματα που βλέπετε.

In [9]:
name="Mihalis"

def anapoda(s):
    "Γυρνάει το string s γραμμένο ανάποδα"
    print "Hello",name
    ret=""
    for x in s:
       ret = x + ret
    return ret


print anapoda("sdfsdf")
name="Manolis"
print anapoda("sdfsdf")
Hello Mihalis
fdsfds
Hello Manolis
fdsfds

Προσέξτε τι γίνεται αν η εντολή name = "Manolis" μεταφερθεί μέσα στη συνάρτηση anapoda από το κυρίως πρόγραμμα όπου βρισκόταν. Αφελώς περιμένουμε ότι με αυτή την αλλαγή αυτό που θα συμβεί είναι ότι στην πρώτη κλήση της anapoda η τιμή της μεταβλητής name θα είναι "Mihalis" και στη δεύτερη "Manolis", αφού η τιμή της name θα έχει αλλάξει στην πρώτη κλήση της anapoda.

Όμως αυτό δε συμβαίνει και το πρόγραμμα βγάζει σφάλμα. Παρατηρείστε το μήνυμα παρακάτω:

UnboundLocalError: local variable 'name' referenced before assignment

Για τη μεταβλητή name, μας λέει το μήνυμα, αναζητούμε πρώτα την τιμή της (referenced) πριν της δώσουμε τιμή. Επίσης η python θεωρεί ότι η μεταβλητή name είναι τοπική.

Γιατι γίνεται αυτό; Γιατί δεν καταλαβαίνει η python ότι αναφερόμαστε στην καθολική μεταβλητή name που ορίστηκε ήδη στην πρώτη γραμμή του προγράμματος; Η απάντηση είναι ότι είναι ο κανόνας στην python όποτε πάμε να δώσουμε τιμή σε μια μεταβλητή μέσα σε μια συνάρτηση τότε αυτή η τιμή δίνεται στη μεταβλητή όπως αυτή φαίνεται στην τοπική αντιστοίχιση και αν η μεταβλητή αυτή δεν υπάρχει στην τοπική αντιστοίχιση τότε δημιουργείται μια νέα αναφορά σε αυτό το όνομα στην τοπική αντιστοίχιση. Η τοπική αντιστοίχιση της συνάρτησης anapoda δημιουργείται με την κλήση της συνάρτησης και η συνάρτηση διαβάζεται από την python πριν εκτελεστεί. Δημιουργείται δηλ. η πλήρης αντιστοίχιση της συνάρτησης anapoda πριν η συνάρτηση εκτελεστεί. Αφού υπάρχει ανάθεση στη μεταβλητή name μέσα στη συνάρτηση ο κανόνας της python μας κάνει να τη θεωρούμε τοπική μεταβλητή. Άρα η αναφορά στη name στην εντολή print "Hello",name θεωρείται αναφορά στην τοπική μεταβλητή name, η οποία όμως δεν έχει ακόμη πάρει τιμή, εξ ου και το σφάλμα.

In [2]:
name="Mihalis"

def anapoda(s):
    "Γυρνάει το string s γραμμένο ανάποδα"
    print "Hello",name
    name = "Manolis"
    ret=""
    for x in s:
       ret = x + ret
    return ret


print anapoda("sdfsdf")
print anapoda("sdfsdf")
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-2-22206d7d78dd> in <module>()
     11 
     12 
---> 13 print anapoda("sdfsdf")
     14 print anapoda("sdfsdf")

<ipython-input-2-22206d7d78dd> in anapoda(s)
      3 def anapoda(s):
      4     "Γυρνάει το string s γραμμένο ανάποδα"
----> 5     print "Hello",name
      6     name = "Manolis"
      7     ret=""

UnboundLocalError: local variable 'name' referenced before assignment
 Hello

Στο επόμενο βλέπουμε πώς διορθώνεται το πρόβλημα που παρατηρήσαμε στο προηγούμενο κελλί.

Υπάρχει τρόπος μέσα σε μια συνάρτηση να δηλώσουμε ρητά ότι μια μεταβλητή δεν είναι τοπική αλλά καθολική, ώστε αυτή να αναζητηθεί στην καθολική αντιστοίχιση. Η εντολή αυτή είναι η global name μέσα στη συνάρτηση anapoda. Τώρα το πρόγραμμα τρέχει κανονικά.

In [5]:
name="Mihalis"

def anapoda(s):
    "Γυρνάει το string s γραμμένο ανάποδα"
    global name
    print "Hello",name
    name = "Manolis"
    ret=""
    for x in s:
       ret = x + ret
    return ret


print anapoda("sdfsdf")
print anapoda("sdfsdf")
Hello Mihalis
fdsfds
Hello Manolis
fdsfds

Εδώ βλέπουμε πώς μπορούμε να εντάξουμε σε μια συνάρτηση ένα μηχανισμό με τον οποίο μετράμε το πόσες φορές καλέστηκε η συνάρτηση, από οπουδήποτε. Χρησιμοποιούμε μια καθολική μεταβλητή numcalls, την οποία δηλώνουμε ως global μέσα στη συνάρτηση και την οποία αυξάνουμε κατά ένα κάθε φορά που εκτελείται η συνάρτηση αυτή.

In [18]:
numcalls = 0

def anapoda(s):
    "Γυρνάει το string s γραμμένο ανάποδα"
    global numcalls
    numcalls += 1
    ret=""
    for x in s:
       ret = x + ret
    return ret

print anapoda("o58763")
print anapoda("43856945")
print "Function was called",numcalls,"times"
36785o
54965834
Function was called 2 times

Εδώ βλέπουμε το πώς η τροποποίηση της μεταβλητής x μέσα στη συνάρτηση f αφήνει αμετάβλητη την καθολική μεταβλητή x. Εφόσον μέσα στη συνάρτηση ανατίθεται τιμή στην x, και αυτή δεν έχει δηλωθεί global, η x θεωρείται τοπική μεταβλητή και η ανάθεση x=2 μέσα στην f δεν επηρεάζει την τιμή της καθολικής μεταβλητής x που είναι αυτή που τυπώνεται με την εντολή print x.

In [11]:
def f(x):
    x=2
    return x

x=5
f(x)
print x
5

Όπως και στο προηγούμενο παράδειγμα βλέπουμε πώς η συνάρτηση g δεν επηρεάζει την καθολική μεταβλητή x.

In [12]:
def g(x):
    x += 1
    return

x=2
g(x)
print x
2

Ένα πρόγραμμα για εύρεση (προσεγγιστικά) ρίζας μιας μη γραμμικής εξίσωσης

Στο επόμενο κελλί βλέπουμε ένα πρόγραμμα του οποίου σκοπός είναι η προσεγγιστική εύρεση μιας ρίζας της εξίσωσης cos(x) = sin^2(x). Αν γράψουμε f(x) = cos(x) - sin^2(x) τότε ψάχνουμε για ένα x στο διάστημα [0,π/2] έτσι ώστε f(x)=0. Η συνάρτηση f(x) είναι συνεχής και f(0) = 1, f(π/2) = -1, άρα σίγουρα υπάρχει μια ρίζα στο διάστημα [0, π/2] από το Θεώρημα Ενδιάμεσης Τιμής.

Σημαντικό είναι να τονίσουμε ότι δεν ψάχνουμε για ακριβή αλλά για προσεγγιστική λύση. Σε μη γραμμικές εξισώσεις αυτό είναι η συνηθισμένη κατάσταση. Ακόμη και για πολύ απλές εξισώσεις, όπως η x^2-2=0, η ρίζα δεν είναι καθόλου απαραίτητο ότι μπορεί να παρασταθεί ακριβώς στον υπολογιστή ως αριθμός κινητής υποδιαστολής (floating point number, αυτή είναι η συνηθισμένη παράσταση πραγματικών αριθμών στον υπολογιστή).

Το να βρούμε λοιπόν τη ρίζα της εξίσωσης f(x) = 0 σημαίνει ότι θα βρούμε ένα αριθμό x ο οποίος είναι εγγυημένα κοντά σε μια πραγματική ρίζα. Πόσο κοντά είναι σε μας να το προσδιορίσουμε και στο πρόγραμμα παρακάτω έχουμε θέσει ως σκοπό να μην είμαστε μακρύτερα από 1e-9 (10 στη δύναμη -9).

Το πρόγραμμα δουλεύει ως εξής. Ξεκινάμε από ένα διάστημα [a,b] = [0,π/2] το οποίο σίγουρα περιέχει μια ρίζα της f(x). Επανειλημμένως χωρίζουμε το διάστημα [a,b] σε δύο ίσα διαστήματα, το αριστερό και το δεξί μισό. Εφόσον τα πρόσημα της f(x) είναι διαφορετικά στα δύο άκρα του [a,b] είμαστε σίγουροι ότι ένα από τα δύο αυτά μισά διαστήματα έχει επίσης ετερόσημες τιμές της f(x) στα δύο άκρα του και άρα περιέχει ρίζα της f(x). Το κάνουμε αυτό συνεχώς έως ότου το μήκος του διαστήματος γίνει μικρότερο από 1e-9. Έχουμε σε αυτό το σημείο βρει ένα διάστημα που περιέχει μια ρίζα της f(x) και άρα οποιοδήποτε σημείο του διαστήματος αυτού, π.χ. το αριστερό του άκρο, αποτελεί ικανοποιητική προσέγγιση μιας ρίζας της f(x).

Στην πρώτη γραμμή του προγράμματος κάνουμεimport τη βιβλιοθήκη math την οποία χρειαζόμαστε για τις συναρτήσεις cos και sin. Η πρώτη συνάρτηση που ορίζουμε είναι ακριβώς η f(x) της οποίας μια ρίζα ψάχνουμε.

Η συνάρτηση newinterval που ορίζεται μετά παίρνει δύο ορίσματα, τους αριθμούς A, B που πρέπει πάντα να είναι το αριστερό και το δεξί άκρο ενός διαστήματος στα άκρα του οποίου η f(x) παίρνει ετερόσημες τιμές. Η συνάρτηση αυτή επιστρέφει τα δύο άκρα ενός νέου διαστήματος με τις ίδιες ιδιότητες αλλά με το μισό μήκος. Το διάστημα αυτό είναι είτε το αριστερό μισό είτε το δεξί μισό του διαστήματος [A,B]. Στη συνάρτηση newinterval πρώτα το μέσο του διαστήματος με την εντολή middle = (A+B)/2.0 και έπειτα υπολογίζουμε τις τιμές της f(x) στο αριστερό άκρο (leftvalue) και στο μέσο (midvalue). Αν οι δύο αυτές τιμές είναι ετερόσημες (αυτό ελέγχεται στοif ελέγχοντας αν το γινόμενό τους είναι <=0) τότε επιστρέφουμε το αριστερό διάστημα με την πρώτη εντολή return, αλλιώς επιστρέφουμε το δεξί μισό διάστημα (σίγουρα σε ένα από τα δύο μισά διαστήματα θα έχουμε ετερόσημες τιμές στα άκρα). Προσέξτε ότι η συνάρτηση επιστρέφει δύο τιμές, το αριστερό και το δεξί άκρο του νέου διαστήματος.

Στο κυρίως πρόγραμμα θέτουμε κατ' αρχήν το αρχικό μας διάστημα να είναι το [0, π/2] με τις δύο πρώτες εντολές. Αυτό που κάνουμε στην ανακύκλωση while είναι ότι όσο το διάστημα στο οποίο έχουμε εγκλωβίσει τη ρίζα έχει μήκος b-a μεγαλύτερο από το 1e-9 καλούμε τη συνάρτηση newinterval και βρίσκουμε το νέο μας διάστημα (με μισό μήκος κάθε φορά) το οποίο επίσης αποθηκεύουμε στις ίδιες μεταβλητές a, b. Όταν η ανακύκλωση σταματήσει τυπώνουμε τα δύο άκρα του διαστήματος που βρήκαμε.

Τυπώνουμε επίσης τη μεταβλητή numfcalls η οποία μετράει το πόσες φορές καλέσαμε τη συνάρτηση f(x) σε όλον αυτό τον υπολογισμό. Η μεταβλητή numfcalls δηλώνεται ως global μέσα στην f(x) όπου και αυξάνεται κατά 1 κάθε φορά που η συνάρτηση καλείται. Η συνάρτηση καλέστηκε 62 φορές.

In [24]:
import math
numfcalls = 0

def f(x):
    "Υπολογίζει τη συνάρτηση f(x) της οποίας μια ρίζα ψάχνουμε"
    global numfcalls
    numfcalls += 1
    return math.cos(x)-math.sin(x)**2

def newinterval(A, B):
    "Επιστρέφει νέο διάστημα. Απαιτείται στα A, B να έχει διαφορετικά πρόσημα η f"
    middle = (A+B)/2.0
    leftvalue = f(A)
    midvalue = f(middle)
    if leftvalue*midvalue <= 0:
        return A, middle
    return middle, B
    
a=0
b=math.pi/2

while b-a > 1e-9:
    a,b = newinterval(a,b)

print "a=",a,"b=",b
print "f was called", numfcalls, "times"
a= 0.904556893968 b= 0.9045568947
f was called 62 times

Τώρα αλλάζουμε λιγάκι το προηγούμενο πρόγραμμα με σκοπό να ελαττώσουμε όσο γίνεται τον αριθμό των κλήσεων στη συνάρτηση f(x). Ο λόγος είναι ότι σε πολλές περιπτώσεις η συνάρτηση αυτή μπορεί να είναι εξαιρετικά "ακριβή" στον υπολογισμό της, μας κοστίζει δηλ. πολύ χρόνο για να την υπολογίσουμε, οπότε θέλουμε να μην το κάνουμε αν δεν είναι απαραίτητο.

Η βασική παρατήρηση που κάνουμε στο προηγούμενο πρόγραμμα είναι ότι στη συνάρτηση newinterval πολλές φορές υπολογίζουμε την f(x) σε σημεία που την έχουμε ήδη υπολογίσει σε προηγούμενα βήματα. Συγκεκριμένα ο υπολογισμός leftvalue = f(A) που κάνουμε για να υπολογίσουμε τη συνάρτηση στο αριστερό άκρο του διαστήματος είναι σίγουρα ένας υπολογισμός που μπορεί με κάποιο τρόπο να αποφευχθεί μια και η τιμή αυτή έχει ήδη υπολογιστεί σε προηγούμενο βήμα.

Η λύση που δίνουμε είναι ότι αλλάζουμε τον ορισμό της συνάρτησης newinterval και τώρα παίρνει ως ορίσματα όχι μόνο τα άκρα του διαστήματος αλλά και τις τιμές της f(x) στα δύο αυτά άκρα. Η συνάρτηση τώρα επίσης επιστρέφει τις νέες τιμές με τον ίδιο τρόπο, δηλ. τα δύο άκρα και τις δύο τιμές της f(x). Σε κάθε εκτέλεση της newinterval τώρα η συνάρτηση f(x) καλείται μόνο μια φορά ενώ πριν καλούνταν δύο.

Παρατηρείστε ότι το αποτέλεσμα είναι το ίδιο με πριν αλλά με πολύ λιγότερες (περίπου τις μισές) κλήσεις στην f(x).

In [25]:
import math
numfcalls = 0

def f(x):
    global numfcalls
    numfcalls += 1
    return math.cos(x)-math.sin(x)**2

def newinterval(A, B, fA, fB):
    """
     Απαιτείται στα A, B να έχει διαφορετικά πρόσημα η f.
     fA πρέπει να ισούται με την τιμή της f στο A, και fB η τιμή στο B.
     Επιστρέφει το αριστερό ή το δεξί μισό του διαστήματος και τις αντίστοιχες τιμές της
     f στα δύο νέα άκρα. Στο διάστημα που επιστρέφει η f(x) έχει ετερόσημες τιμές στα άκρα.
    """
    middle = (A+B)/2.0
    leftvalue = fA
    midvalue = f(middle)
    rightvalue = fB
    if leftvalue*midvalue <= 0:
        return A, middle, fA, midvalue
    return middle, B, midvalue, fB
    
a=0
b=math.pi/2
fa = f(a)
fb = f(b)

while b-a > 1e-9:
    a,b,fa,fb = newinterval(a,b,fa,fb)

print "a=",a,"b=",b
print "f was called", numfcalls, "times"
a= 0.904556893968 b= 0.9045568947
f was called 33 times