[Edu-sig] Brute force solutions

David Handy david at handysoftware.com
Wed Sep 21 15:33:29 CEST 2005


I'm sorry, but I couldn't help taking Kirby's findphi() function as a
personal challenge.

At the cost of roughly doubling the complexity of the code (19 lines instead
of ten lines in the function body), I was able to improve the performance by
a factor of more than 6500, while basically still using the same
"brute-force" approach of guessing a number, adjusting the guess by a delta,
and noticing which number gets the lowest error.

Why bother optimizing, when phi is a constant?  Because using my approach,
you can add two digits more precision to the answer with less than 30% more
time, whereas the original approach would cost 100x (10000% as much) time
for 2 more digits of precision. This refines Kirby's statement that "you get
greater accuracy for more cycles" - it doesn't have to be a linear trade-off.

(Yes, I know the original version wasn't claimed to be optimized, but it was
crying to be optimized...)

# phi.py

def findphi(step = 0.01):
    """
        step
         ->
    -----|---------- (a+b)=1
      a       b

    Lengthen a stepwise, while testing how closely
    b/a approaches 1/b, and return b/a for the least
    difference.
    """
    diff = b = 1.0
    a = phi = 0
    while a < b:
        a += step
        b = 1 - a
        e = abs(b/a - 1/b)
        if e < diff:
            phi = b/a
            diff = e
    return phi


def findphi2(tolerance=0.01):
    a = tolerance
    N = 8.0
    step = (0.5 - a) / N
    last_e = INFINITY = 1e+30
    while True:
        b = 1.0 - a
        e = abs(b/a - 1/b)
        if e < tolerance:
            break
        if e > last_e:
            a -= 2.0 * step # tricky! must go back 2 steps
            if a < tolerance:
                a = tolerance
            step /= N
            last_e = INFINITY
        else:
            a = a + step
            last_e = e
    return b / a


def test():
    import timeit
    print "Slow method -- result:", findphi(0.000001),
    n = 10
    timer1 = timeit.Timer(stmt='findphi(0.000001)', 
                          setup='from phi import findphi')
    t1 = timer1.timeit(number=n)
    print "time:", t1/n, "seconds"
    print "Faster method -- result:", findphi2(0.000001),
    timer2 = timeit.Timer(stmt='findphi2(0.000001)', 
                          setup='from phi import findphi2')
    n = 1000
    t2 = timer2.timeit(number=n)
    print "time:", t2/n, "seconds"
    print "2 digits more precision -- result:", findphi2(0.00000001),
    timer2 = timeit.Timer(stmt='findphi2(0.00000001)', 
                          setup='from phi import findphi2')
    n = 1000
    t2 = timer2.timeit(number=n)
    print "time:", t2/n, "seconds"

if __name__ == '__main__':
    test()

# end-of-file


david at arno2:~$ python phi.py
Slow method -- result: 1.61803406588 time: 0.755390405655 seconds
Faster method -- result: 1.6180333003 time: 0.000112377882004 seconds
2 digits more precision -- result: 1.61803398295 time: 0.000145354032516
seconds
david at arno2:~$ python -c "print 0.755390405655/0.000112377882004"
6721.87793705


On Tue, Sep 20, 2005 at 04:58:32PM -0700, Kirby Urner wrote:
> 
> Per my 'Pentagon math' thread, I think the golden ratio (phi) is an
> important one to explore in K-12.[1]  It's one of those key nodes in our
> curriculum network.
> 
> Given a regular pentagon, the ratio of a diagonal to an edge is phi.  In
> other words, that famous pentacle pattern is phi-edged, vis-?-vis a
> containing pentagon of edges 1.[2]
> 
> Although we have many closed form algebraic methods for deriving phi,
> computers give us this power to solve through brute force.  Some may frown
> on using such "mindless methods" but I'm thinking to go ahead and introduce
> them -- not exclusively, but as an exhibit along the way.
> 
> In the Python function below, we're looking for a way to break a line of
> unit length into two segments, the shorter 'a' and the longer 'b', such that
> the ratio b:a is as close as feasible to the ratio of the whole to b (1:b).
> 
> 
> We move 'a' to the right by tiny increments, and keep track of which value
> minimizes the difference between these two ratios:
> 
>  >>> def findphi(step = 0.01):
> 	"""
> 	    step
> 	     ->
> 	-----|---------- (a+b)=1
> 	  a       b
> 
> 	Lengthen a stepwise, while testing how closely 
>       b/a approaches 1/b, and return b/a for the least 
>       difference.
> 	"""
> 	diff = b = 1.0
> 	a = phi = 0
> 	while a < b:
> 		a += step		
> 		b = 1 - a
> 		e = abs(b/a - 1/b)
> 		if e < diff:
> 			phi = b/a
> 			diff = e
> 	return phi
> 
>  >>> findphi(0.000001)
>  1.6180340658818939
> 
> Some lessons that might be learned from this approach:
> 
> (1) when working with floats, you want to compare differences, not check for
> equalities, e.g. looking for when b/a == 1/b would be a bad idea.
> 
> (2) you get greater accuracy in exchange for more cycles
> 
> (3) visualization helps
> 
> The last point is especially important.  The code itself says nothing about
> lines.  The diagram is what explains the logic behind the algorithm -- which
> is why it's included right in the comments, as primitive ASCII art (bad
> form, or a good idea?).
> 
> Kirby
> 
> [1] 
> http://mathforum.org/kb/message.jspa?messageID=3867841&tstart=0
> 
> [2] 
> http://altreligion.about.com/library/glossary/symbols/bldefswiccasymbols.htm
> 
> PS:  happy birthday Dawn Wicca (9/20/2005)
> 
> 
> _______________________________________________
> Edu-sig mailing list
> Edu-sig at python.org
> http://mail.python.org/mailman/listinfo/edu-sig
> 

-- 
David Handy
Computer Programming is Fun!
Beginning Computer Programming with Python
http://www.handysoftware.com/cpif/

----- End forwarded message -----

-- 
David Handy
Computer Programming is Fun!
Beginning Computer Programming with Python
http://www.handysoftware.com/cpif/


More information about the Edu-sig mailing list