This Python program, inspired by an ASCII-art generator by Darius Bacon, generates a PostScript graph showing how the powers of φ, the golden ratio (1+√5)/2, are distributed such that the fractional part of each power falls into the largest remaining hole between the fractional parts of the earlier powers — although in some cases there is more than one such “largest remaining hole”.
#!/usr/bin/python from __future__ import division phi = (1 + 5**.5) / 2 n = 200 print '%!' print '36 dup translate 540 dup scale 0 0 moveto 0 1 lineto 1 1 lineto 1 0 lineto closepath gsave 0.9 setgray fill grestore clip 0 setlinewidth' for i in range(1, n): print 'newpath', i * phi % 1, i/n, 'moveto 0 -2 rlineto stroke' print 'showpage' (End of `golden.py`.) Example usage: : kra...@inexorable:~/devel/inexorable-misc ; ./golden.py > golden.ps : kra...@inexorable:~/devel/inexorable-misc ; evince golden.ps (`evince` is GNOME’s viewer for PostScript files.) The X-axis inside the gray square is the interval from 0 to 1 within which the fractional parts fall; the Y-axis is the power to which φ is raised, starting from 0 at the top of the square and continuing to n = 200 at the bottom. Unfortunately, although the result is visually fairly striking, it isn’t very convincing. So I wrote an overly elaborate program to confirm this property experimentally on some examples, although of course that can be misleading. This program is called `intervals.py`. #!/usr/bin/python # -*- coding: utf-8 -*- """Subdividing an interval according to the golden ratio. Uses a binary tree to represent divisions of the interval to see if the multiples of the golden ratio, mod 1, always fall in the largest undivided interval in small experiments. Up to 32000, they do seem to. This program is somewhat inadequate to answer the question, because of the problem of equal subintervals. The first apparent counterexample is that phi * 5 % 1 is about 0.0902, which is in the interval (0, phi*2%1 = 0.2361), leaving untouched the interval (phi*1%1 = 0.6180, phi*3%1=0.8541) of length, uh, 0.2361. In fact these intervals are exactly equal in size; phi*3 - phi = phi*2, exactly, even outside the modulo-1 system. However, this program is not adequate to deal with multiple “largest” intervals, especially in the presence of arithmetic errors. The best it can do is to compare the length of the interval being cut with the length of the largest interval. Doing so, it doesn’t find a counterexample with an error as large as 1e-12 within the first 4000 multiples of the golden ratio, or as large as 1e-11 within the first 32000. One potential improvement to the program would be to balance the tree upon point insertions. However, for the specific case of multiples of the golden ratio, the tree remains reasonably well balanced without any explicit balancing logic; it reaches a depth of only 17 for 4000 nodes, where 12 would be the theoretical minimum and 4001 the worst case. At 32000 nodes it reaches a depth of 22. I think a 2-3-4 tree would probably be the simplest variant to implement. """ import math, sys class Subdivision: """Represents an interval possibly subdivided into intervals. Can efficiently perform the following operations: - Return a subdivision with an additional point subdividing one of the intervals; - Return its longest interval; - Return its ends; - Return its length; - Return all its division points; - Return the interval within it containing a given point. At least if you get lucky. """ def length(self): return self.right_end() - self.left_end() def points(self): return [self.left_end()] + self.cuts() + [self.right_end()] def __repr__(self): return 'subdivision(%r)' % self.points() class Interval(Subdivision): """Represents an interval not subdivided into intervals. """ def __init__(self, left, right): self.left, self.right = left, right def left_end(self): return self.left def right_end(self): return self.right def longest(self): return self def cut(self, where): return Broken(Interval(self.left, where), Interval(where, self.right)) def find(self, point): assert self.left <= point < self.right return self def cuts(self): return [] def depth(self): return 1 class Broken(Subdivision): """Represents an interval divided into intervals.""" def __init__(self, lefthalf, righthalf): assert lefthalf.right_end() == righthalf.left_end() self.lefthalf, self.righthalf = lefthalf, righthalf ll = lefthalf.longest() rl = righthalf.longest() if ll.length() > rl.length(): self.longest_interval = ll else: self.longest_interval = rl self.stored_depth = 1 + max(lefthalf.depth(), righthalf.depth()) def left_end(self): return self.lefthalf.left_end() def right_end(self): return self.righthalf.right_end() def longest(self): return self.longest_interval def cut(self, where): assert self.left_end() <= where < self.right_end() if where < self.lefthalf.right_end(): return Broken(self.lefthalf.cut(where), self.righthalf) else: return Broken(self.lefthalf, self.righthalf.cut(where)) def find(self, point): assert self.left_end() <= point < self.right_end() if point < self.lefthalf.right_end(): return self.lefthalf.find(point) else: return self.righthalf.find(point) def cuts(self): return self.lefthalf.cuts() + [self.lefthalf.right_end()] + self.righthalf.cuts() def depth(self): return self.stored_depth def phidemo(n=40): phi = (1+math.sqrt(5))/2 interval = Interval(0, 1) max_error = 0 for ii in range(1, n): print "longest:", interval.longest() cut = (phi * ii) % 1 print "cutting at phi * %s = %s" % (ii, cut) found = interval.find(cut) if found != interval.longest(): error = interval.longest().length() - found.length() print "shorter? by", error if error > max_error: max_error = error interval = interval.cut(cut) print "finally:", interval print "depth:", interval.depth() print "max_error:", max_error if __name__ == '__main__': if len(sys.argv) > 1: phidemo(int(sys.argv[1])) else: phidemo() (End of `intervals.py`.) After writing 150 lines of code to test this property, I tried rewriting it in Lua to see how it came out. It came out 4× as fast, and roughly equally clear, I think. The log of the problems I ran into is perhaps interesting when considering human interface questions related to programming languages. This is `intervals.lua`: #!/usr/bin/lua -- Analyze the distribution of the fractional parts of the multiples -- of the golden ratio. -- The original was in Python; I rewrote it in Lua to see if LuaJIT -- could run it faster than Python. The answer: -- YES, running it with 32000 points takes -- 56.5 seconds with CPython 2.6.2, -- 34.3 seconds with Lua 5.1.4, and -- 15.8 seconds with LuaJIT 2.0.0-beta2. -- A 4x speedup isn’t bad, but 40x is probably doable. -- Log of my problems, as a Lua novice: -- * Didn’t have lua-mode installed for Emacs. -- * Tried to use “load('intervals.lua')” instead of -- “loadfile('intervals.lua')()” to test in the REPL. -- * Forgot to return a value from the “interval” function. -- * Couldn’t remember that “does not equal” is spelled “~=”. -- * Couldn't remember that “not” is spelled “not”, not “!”. -- * Tried to use “loadfile('intervals.lua')” instead of -- “print(loadfile('intervals.lua'))” to see syntax errors. -- * Left out parens on a method call: “if foo:bar() < baz:bar then”, -- getting “function arguments expected near 'then'”. -- * Tried to declare a local variable with “var cuts” -- instead of “local cuts”, getting “'=' expected near 'cuts'”. -- * Forgot that I had translated “length” as a function instead of -- a method, getting “attempt to call method 'length' (a nil value)”. -- * Was initializing an instance variable as “rv.longest = foo” instead -- of “rv.longest_interval = foo”, so my "longest" method returned nil, -- so I was getting “attempt to index local 'i' (a nil value)” in a -- totally different function. function concat(t, items) for _, item in ipairs(items) do table.insert(t, item) end end function interval_length(i) return i:right_end() - i:left_end() end function points(i) -- I was concatenating tables with unpack() and then I got this with 32000 items: -- /home/kragen/pkgs/luajit: intervals.lua:9: too many results to unpack -- So now I’m using a concat() function. local p = {i:left_end()} concat(p, i:cuts()) table.insert(p, i:right_end()) return p end function repr(i) return 'subdivision(['..table.concat(points(i), ', ')..'])' end -- a subdivision not subdivided into intervals; a leaf node. function interval(left, right) local rv = {left = left, right = right} function rv:left_end() return self.left end function rv:right_end() return self.right end function rv:longest() return self end function rv:cut(where) return broken(interval(self.left, where), interval(where, self.right)) end function rv:find(point) assert(self.left <= point) assert(point < self.right) return self end function rv:cuts() return {} end function rv:depth() return 1 end return rv end -- a subdivision broken down further into intervals function broken(lefthalf, righthalf) assert(lefthalf:right_end() == righthalf:left_end()) local rv = { lefthalf = lefthalf, righthalf = righthalf } local ll = lefthalf:longest() local rl = righthalf:longest() if interval_length(ll) > interval_length(rl) then rv.longest_interval = ll else rv.longest_interval = rl end rv.stored_depth = 1 + math.max(lefthalf:depth(), righthalf:depth()) function rv:left_end() return self.lefthalf:left_end() end function rv:right_end() return self.righthalf:right_end() end function rv:longest() return self.longest_interval end function rv:cut(where) assert(self:left_end() <= where) assert(where < self:right_end()) if where < self.lefthalf:right_end() then return broken(self.lefthalf:cut(where), self.righthalf) else return broken(self.lefthalf, self.righthalf:cut(where)) end end function rv:find(point) assert(self:left_end() <= point) assert(point < self:right_end()) if point < self.lefthalf:right_end() then return self.lefthalf:find(point) else return self.righthalf:find(point) end end function rv:cuts() local cuts = self.lefthalf:cuts() table.insert(cuts, self.lefthalf:right_end()) concat(cuts, self.righthalf:cuts()) return cuts end function rv:depth() return self.stored_depth end return rv end function phidemo(n) if not n then n = 40 end local phi = (1+math.sqrt(5))/2 local iv = interval(0, 1) local max_error = 0 for ii = 1, n do print("longest: "..repr(iv:longest())) local cut = (phi * ii) % 1 print("cutting at phi * "..ii.." = "..cut) local found = iv:find(cut) if found ~= iv:longest() then local err = interval_length(iv:longest()) - interval_length(found) print("shorter? by "..err) if err > max_error then max_error = err end end iv = iv:cut(cut) end print("finally: "..repr(iv)) print("depth: "..iv:depth()) print("max_error: "..max_error) end if #{...} > 0 then phidemo(...) end (End of `intervals.lua`.) Some of this software is available via git clone http://canonical.org/~kragen/sw/inexorable-misc.git (or in <http://canonical.org/~kragen/sw/inexorable-misc>) in the file `golden.py`. Like everything else posted to kragen-hacks without a notice to the contrary, this software is in the public domain. -- To unsubscribe: http://lists.canonical.org/mailman/listinfo/kragen-hacks