(Actually 0.8.0.1 because Hackage complained about 0.8 in a way that
`cabal check`

didn't warn about before uploading.)

A new release of **mandulia** (a zooming visualization
of the Mandelbrot Set as many Julia Sets, with Lua scripting support)
is installable from
Hackage:

cabal update cabal install mandulia

After you have installed it, you can try it out by running:

mandulia main

and hit the 'a' key to enter attract mode or use the key controls listed in the README for interactive navigation. 'F11' toggles full screen mode, and 'ESC' quits.

mandulia-0.8.0.1 has no new features, the only changes are to make it work with the current state of Hackage 12 years later:

- base-4.6 has modifyIORef'
- containers-0.5 has Data.Map.Strict
- hslua-0.4 and above changed API, so restrict to older versions

Tested and working with latest stable versions of GHC from ghc-8.0.2 up, only ghc-9.4.2 needs --allow-newer=text because OpenGLRaw has an outdated dependency. Older versions of GHC may work, but I haven't tried. I have freeglut3-dev installed on Debian Bookworm (current testing distribution), but I don't know what other system dependencies are needed.

Source code is GPLv3+ licensed, Git repository at mandulia on code.mathr.co.uk, or you can download mandulia-0.8.0.1.tar.gz.

]]>In my previous post I had a problem with some simple Haskell code that exploded in memory. This morning I worked out why, and how to fix it, using the technique of difference lists. The problematic code was:

import Control.Monad (replicateM) shapes bias p = [ s | m <- [0 .. p] , s <- replicateM (fromInteger m + 2) [1 .. p] , p == sum (zipWith (*) (map (bias +) s) (tail s)) ] main = mapM_ (print . length . shapes 0) [1..10]

To see how memory explodes, here's my session:

$ ghc -O2 replicateM.hs -rtsopts ... $ timeout 60 ./replicateM +RTS -h 1 3 5 10 14 27 37 $ hp2pretty replicateM.hp

and here is the SVG graph output by hp2pretty:

The memory is going up dramatically, and while the program ran for a whole minute wall-clock, subtracting the garbage collection time gives less than 17 seconds of useful work.

I had a hunch that the problem is `replicateM`

(the rest
of the code is so simple that it almost obviously can't be anything
else). I'm using for lists, where it does Cartesian product (output all
possible combinations of one item from each of cnt0 copies of a list f).
Let's look at the definition in the source code:

replicateM cnt0 f = loop cnt0 where loop cnt | cnt <= 0 = pure [] | otherwise = liftA2 (:) f (loop (cnt - 1))-- base-4.16.1.0:Control.Monad.replicateM

To intuitively understand liftA2, one can desugar it to do-notation,
which changes the type unless
`{-# LANGUAGE ApplicativeDo #-}`

is enabled:

replicateM cnt0 f = loop cnt0 where loop cnt | cnt <= 0 = pure [] | otherwise = do x <- f xs <- loop (cnt - 1) pure (x : xs)

but to see where the leak is coming from we can evaluate the critical
part of the original version, specifically for the `[String]`

type:

> result = words "hello world how are you" > mapM_ putStrLn $ liftA2 (:) "¿¡" result ¿hello ¿world ¿how ¿are ¿you ¡hello ¡world ¡how ¡are ¡you

Immediately one can see that it needs all of `result`

in
order to print it once with a prefix, but then it needs it all again to
print it with the second prefix. So because `loop (cnt - 1)`

is passed as an argument to `liftA2`

, its value will be
shared in the same way as the `result`

list of strings was.
And sharing in this way forces the value to be kept in memory, and can
only start to be freed when the last prefix has started being emitted.

The problem in `replicateM`

is compounded, because the same
happens in each recursive call to `loop`

, although the
retained values get smaller deeper down so its not such a huge deal.
The problem is at the top of the recursion, where it has to store a list
of length ((length f)^(n-1)), which can get pretty large.

So that's the issue, how to fix it? Difference lists traditionally
work by turning appends like `((x ++ y) ++ z) ++ w`

, which is
expensive because (++) cost is the length of the left hand side, and left
nesting means the left hand side gets longer and longer, into something
like
`(((prepend x . prepend y) . prepend z) . prepend w) empty`

which is much more efficient because the cost of function composition
(.) is constant and prepend costs the length of its argument. In this
example the cost of the first is X + X + Y + X + Y + Z = 3 X + 2 Y + Z,
while the second is X + Y + Z + W (and the + W could be avoided by using
it at the end instead of prepending w to empty).

Here's what I came up with, I'm not 100% sure why it works better, and I'm not sure if it is even correct for anything apart from m = [], and my first attempt output items reversed, and maybe it has similarities to foldl' vs foldr in terms of accumulator as well as using difference lists, but anyway:

{-# LANGUAGE ApplicativeDo #-} replicateM' :: Applicative m => Int -> m a -> m [a] replicateM' n ls = go n (pure id) where go n fs | n <= 0 = do f <- fs pure (f []) | otherwise = go (n - 1) gs where gs = do f <- fs l <- ls pure (f . (l:))

Here's the heap profile graph for the same program, just using this replicateM' to replace replicateM:

What a difference! Tiny constant memory, as it should be, and almost none of the time is spent garbage collecting.

]]>`hp2pretty`

is a tool I wrote to graph Haskell heap profiling output,
to help see where your program is using memory.

Install with `cabal update && cabal install hp2pretty`

,
run with `hp2pretty --help`

(assuming cabal installed it somewhere in your PATH -
otherwise try `~/.cabal/bin`

).

The changes in this release are contributed by David Hewson. Previously,
`hp2pretty`

would read the whole
`input.hp`

file into memory, and go through it
twice (once to collect statistics about what needs to be plotted, and the
second time to do the actual plotting). This worked fine for small to medium
files, but for huge files that don't fit into available free memory this
causes problems (operating system swapping pages out to slow disks, or worse).

Now the trick is to open and read the file twice instead of keeping it in memory, and using the Lazy variant of Text allows the data to be garbage collected as soon as it has been processed each time through the file, greatly reducing peak memory consumption. Altogether it also seems to speed things up for less huge files (I didn't yet figure out exactly why), even though the real aim was to make processing very huge files possible in the first place.

You can get the latest from git here:

git clone https://code.mathr.co.uk/hp2pretty.git

or install from Hackage.

]]>**rounded** provides properly rounded floating point numbers
of arbitrary precision. It does so by wrapping the GNU
MPFR library. Phantom types
carry the information about the precision and rounding mode, letting you treat
properly rounded floating point numbers as instances of `Num`

or
`Floating`

, like any other numeric type in Haskell. Unlike other
attempts to port MPFR to Haskell, this library does not require you to cripple
`Integer`

performance or link your code in an unnatural way.

The new releases are twofold, v0.x is for MPFR 3.1 or above, and v1.x requires MPFR 4.0 or above. When in doubt, depend on the v0.x branch, because both will be supported for the foreseeable future and there's not that much new in MPFR 4.0 (at least, only two additional functions are exposed by rounded so far, though that may increase).

The main changes versus the previous release are bindings for a lot more of the MPFR API (I think all unary functions and binary functions are included now). There are still functions missing, mostly those with less common types, so pull requests adding them are welcome. There is also a fix to a memory corrupting crash in the long-double support: a foreign function was imported with an argument missing so the pointer used to write flags to was running wild.

rounded was originally written around 7 years ago by Edward Kmett and Daniel Peebles, but it couldn't be made to work properly until GHC's integer-gmp implementation was changed later (something to do with GMP custom memory allocation vs GHC's garbage collector). I did the work to rip out the semi-broken C-- code and replace it with a more straight-forward (though perhaps less performant) Foreign Function Interface (FFI) binding, and took over the maintainership about a year ago.

You can get it from Hackage or Github, and I have a mirror of the repository on my own code hosting.

]]>Pau Ros took some great pictures of my exhibition opening, part of Sonic Electronics Festival:

The exhibition is open until 27th April. Check the Chalton Gallery website for spacetime coordinates.

]]>I have an exhibition coming up April 2019 in London, UK.

Claude Heiland-Allen

Digital Art - Computer Graphics - Free/Libre Open Source Software

Chalton Gallery, 96 Chalton Street, Camden, London UK NW1 1HJ

Opening Thursday 11 April 2019, 6pm.

Concert Thursday 18 April 2019, 7pm.

Exhibition opens 12-27 April 2019.

Tuesdays: 8 am to 3 pm

Wednesday to Saturday: 11:30 am to 5:45 pm

*Digital print 120x60cm, framed*

Prismatic is rendered using a physics-based ray-tracer for spherically curved space. In spherical space the light ray geodesics eventually wrap around, meeting at the opposite pole to the observer. To compound the sphericity a projection is used that wraps the whole sphere-of-view from a point into a long strip.

The scene contains spheres of three different transparent materials (water, glass, quartz) symmetrically arranged at the vertices of a 24-cell. The equatorial plane is filled with a glowing opaque checkerboard, this acts as a light source with a daylight spectrum.

The 3D spherical space is embedded in 4D Euclidean (flat) space. Represent ray directions by points on the “equator” around the ray source, and use trigonometry to transform these ray directions appropriately when tracing the rays through curved space. The code is optimized to use simpler functions like square root and arithmetic instead of costly sines and cosines.

The materials are all physically based, with refractive index varying with simulated light wavelength, which gives rainbow effects when different colours are refracted by different angles. To get the final image requires tracing a monochrome image at many different wavelengths, which are then combined into the XYZ colour space using tristimulus response curves for the light receptors in the human eye.

*Digital prints 20x30cm, 16 pieces, unframed*

The concept for Wedged is “playing Tetris optimally badly”. Badly in that no row is complete, and optimally in that each row has at most one empty cell, and the rectangle is filled. Additional aesthetic constraints are encoded in the source code to generate more pleasing images.

Starting from an empty rectangle, block off one cell in each row, subject to the constraint that blocked cells in nearby rows shouldn’t be too close to each other, and the blocked cells should be roughly evenly distributed between columns. Some of these blockings might be duplicates (taking into account mirroring and rotation), so pick only one from each equivalence class.

Starting from the top left empty cell in each of these boards, fill it with pieces that fit. Fitting means that the piece is entirely within the board, not overlapping any blocked cells or other pieces. There are some additional constraints to improve aesthetic appearance and reduce the number of output images: there should not be too many pieces of the same colour in the board, all adjacent pieces should be a different colour, and no piece should be able to slide into the space left when blocked cells are removed (this applies only to the long thin blue pieces, the other pieces can’t move due to the earlier constraint on nearby blocked cells).

The filling process has some other aesthetic constraints: the board must be diverse (there must be a wide range of distinct colours in each row and column), the complete board must have a roughly even number of pieces of each colour, and there shouldn’t be any long straight line boundaries between multiple pieces. The complete boards might have duplicates under symmetries (in the case that the original blocking arrangement was symmetrical), so pick only one from each equivalence class.

*Sound installation*

Generative techno. Dynamo creates music from carefully controlled randomness, using numbers to invent harmonies, melodies, and rhythms. Dynamo is a Pure-data patch which plays new techno tracks forever. It is a generative system, and not a DJ mix.

When it is time to generate a new track, Dynamo first picks some high level parameters like tempo, density, and the scale of notes to use. Then it fills in the details, such as the specific rhythms of each instrument and which notes to play in which order. Finally an overall sequence is applied to form the large scale musical structure.

Pure-data is deterministic, which makes Dynamo deterministic. To avoid the same output each time the patch is started, entropy is injected from outside the Pure-data environment.

*Audio-visual installation*

Sliding tile puzzles have existed for over a century. The 15-puzzle craze in 1880 offered a cash prize for a problem with no solution. In the Puzzle presented here the computer is manipulating the tiles. No malicious design, but insufficient specification means that no solution can be found; the automaton forever explores the state space but finds every way to position the tiles as good as the last…

Each tile makes a sound, and each possible position has a processing effect associated with it. Part of the Puzzle is to watch and listen carefully, to see and hear and try to pick apart what it is that the computer is doing, to reverse-engineer the machinery inside from its outward appearance. The video is built using eight squares, each coloured tile is textured with the whole Puzzle, descending into an infinite fractal cascade. The control algorithm is a Markov Chain that avoids repetition.

Puzzle is implemented in Pure-data, using GEM for video and pdlua for the tile-control logic.

*Interactive installation*

A graph is a set of nodes and links between them. In GraphGrow the term is overloaded: there are visible graphs of nodes and links on the tablet computer, and a second implicit graph with links between the rules.

The visible graphs give the name of GraphGrow - a fractal is grown from a seed graph by replacing each visible link with its corresponding rule graph, recursively. The correspondence is by colour: a yellow link corresponds to the graph with yellow background, and so on. The implicit graph between rules thus *directs* the expansion. The implict graph is also a *directed graph* (even more terminological overloading!).

The rule graphs are constrained, with two fixed nodes at left and right. When growing a graph, each link is replaced with the corresponding rule graph with the left-hand fixed node of the rule mapped to the start point of the link and the right-hand fixed node of the rule mapped to the end point of the link. The mapping is restricted to uniform scaling, rotation and translation. The fixed nodes are coloured white on the tablet.

The fractal is projected, along with rhythmic drones amplified through speakers. Both are generated from the graph data. Dragging the brightly coloured nodes on the tablet in each of the four rule graphs, allows the gallery visitor to explore a subspace of graph-directed iterated function system of similarities.

*Video installation*

Fractals are mathematical objects exhibiting detail at all scales. Escape-time fractals are plotted by iterating recurrence relations parameterised by pixel coordinates from a seed value until the values exceed an escape radius or until an arbitrary limit on iteration count is reached (this is to ensure termination, as some pixels may not escape at all). The colour of each pixel is determined by the distance of the point from the fractal structure: pixels near the fractal are coloured black and pixels far from the fractal are coloured white, or the reverse.

Escape-time fractals are generated by formulas, for example the Mandelbrot set emerges from *z* → *z*^{2} + *c* and the Burning Ship emerges from *x* + *i**y* → (|*x*| + *i*|*y*|)^{2} + *c*, where *c* is the coordinates of each pixel. Hybrid fractals combine different formulas into one more complicated formula: for example one might perform one iteration of the Mandelbrot set formula, then one iteration of the Burning Ship formula, then two more iterations of the Mandelbrot set formula, repeating this sequence in a loop.

Claude Heiland-Allen is an artist from London interested in the complex emergent behaviour of simple systems, unusual geometries, and mathematical aesthetics.

From 2005 through 2011 Claude was a member of the GOTO10 collective, whose mission was to promote Free/Libre Open Source Software in Art. GOTO10 projects included the make art Festival (Poitiers, France), the Puredyne GNU/Linux distribution, and the GOSUB10 netlabel. Since 2011 he has continued as an unaffiliated independent artist and researcher.

Claude has performed, exhibited and presented internationally, including in the United Kingdom (London, Cambridge, Winchester, Lancaster, Oxford, Sheffield), the Netherlands (Leiden, Amsterdam), Austria (Linz, Graz), Germany (Cologne, Berlin), France (Toulouse, Poitiers, Paris), Spain (Gijon), Norway (Bergen), Slovenia (Maribor), Finland (Helsinki), and Canada (Montreal).

Claude’s larger artistic projects include RDEX (an exploration of digitally simulated reaction diffusion chemistry) and clive (a minimal environment for live-coding audio in the C programming language). As a software developer, Claude has developed several programs and libraries used by the wider free software community, including pdlua (extending the Puredata multimedia environment with the Lua programming language), buildtorrent (a program to create .torrent files), and hp2pretty (a program to graph Haskell heap profiling output).

]]>Last week I implemented (in Haskell, using lazy ST with each STRef paired with Natural so that I can have Ord) the algorithm presented in this paper:

Images of Julia sets that you can trust

L. H. de Figueiredo, D. Nehab, J. Stolfi, and J. B. Oliveira

Last updated on January 8, 2013 at 10:45am.

Abstract:We present an algorithm for computing images of quadratic Julia sets that can be trusted in the sense that they contain numerical guarantees against sampling artifacts and rounding errors in floating-point arithmetic. We use cell mapping and color propagation in graphs to avoid function iteration and rounding errors. As a result, our algorithm avoids point sampling and can robustly classify entire rectangles in the complex plane as being on either side of the Julia set. The union of the regions that cannot be so classified is guaranteed to contain the Julia set. Our algorithm computes a refinable quadtree decomposition of the complex plane adapted to the Julia set which can be used for rendering and for approximating geometric properties such as the area of the filled Julia set and the fractal dimension of the Julia set.

Keywords:Fractals, Julia sets, adaptive refinement, cellular models, cell mapping, computer-assisted proofs

You can find my code in my mandelbrot-graphics repository. I reproduced most of the results, I coloured with black interior, white exterior, red unknown (Julia set is inside the red region), and the quad tree cell boundaries in grey:

The last two examples above show how it fails at parabolic Julia sets.

I also implemented a trustworthy Mandelbrot set, based on the idea that if the neighbourhood of the origin in the Julia is all exterior, then the point cannot be in the Mandelbrot set, and if any interior exists in the Julia set, then the point must be in the Mandelbrot set. Now replace 'point' in those two clauses with "closed 2D square", and use the property of the algorithm in the paper that means the proofs for interiorhood and exteriorhood of the Julia set range over the interval.

It's far too slow to be practical, if pretty pictures were the goal! The red zone of unknown doesn't shrink much with each depth increment.

]]>Define the iterated quadratic polynomial:

\[ f_c^0(z) = z \\ f_c^{n+1}(z) = f_c(f_c^n(z))^2 + c \]

The Mandelbrot set is those \(c\) for which \(f_c^n(0)\) remains bounded for all \(n\). Misiurewicz points are dense in the boundary of the Mandelbrot set. They are strictly preperiodic, which means they satisfy this polynomial equation:

\[ f_c^{q+p}(0) = f_c^{q}(0) \\ p > 0 \\ q > 0\]

and moreover the period \(p\) and the preperiod \(q\) of a Misiurewicz point \( c \in M_{q,p} \) are the lowest values that make the equation true. For example, \(-2 \in M_{2,1}\) and \(i \in M_{2,2}\), which can be verified by iterating the polynomial (exercise: do that).

Misiurewicz points are algebraic integers (a subset of the algebraic numbers), which means they are the roots of a monic polynomial with integer coefficients. A monic polynomial is one with leading coefficient \(1\), for example \(c^2+c\). Factoring a monic polynomial gives monic polynomials as factors. Factoring over the complex numbers \(\mathbb{C}\) gives the \(M_{q,p}\) in linear factors, factoring over the integers \(\mathbb{Z}\) can give irreducible polynomials of degree greater than \(1\). For example, here's the equation for \(M_{2,2}\):

\[c^3\,\left(c+1\right)^2\,\left(c+2\right)\,\left(c^2+1\right)\]

Note that the repeated root \(0\) corresponds to a hyperbolic component of period \(1\) (the nucleus of the top level cardioid of the Mandelbrot set), and the repeated root \(-1\) corresponds to the period \(2\) circle to the left. And \(-2 \in M_{2,1}\), so the "real" equation we are interested in is the last term, \(c^2+1\), which is irreducible over the integers, but has complex roots \(\pm i\). There are two roots, so \(\left|M_{2,2}\right| = 2\).

So, a first **attempt** at enumerating Misiurewicz points works like this:

-- using numeric-prelude and MemoTrie from Hackage type P = MathObj.Polynomial.T Integer -- h with all factors g removed divideAll :: P -> P -> P divideAll h g | isZero h = h | isOne g = h | isZero g = error "/0" | otherwise = case h `divMod` g of (di, mo) | isZero mo -> di `divideAll` g | otherwise -> h -- h with all factors in the list removed divideAlls :: P -> [P] -> P divideAlls h [] = h divideAlls h (g:gs) = divideAlls (h `divideAll` g) gs -- the variable for the polynomials c :: P c = fromCoeffs [ 0, 1 ] -- the base quadratic polynomial f :: P -> P f z = z^2 + c -- the iterated quadratic polynomial fn :: Int -> P fn = memo fn_ where fn_ 0 = 0 ; fn_ n = f (fn (n - 1)) -- the raw M_{q,p} polynomial m_raw :: Int -> Int -> P m_raw = memo2 m_raw_ where m_raw_ q p = fn (q + p) - fn q -- the M_{q,p} polynomial with lower (pre)periods removed m :: Int -> Int -> P m = memo2 m_ where m_ q p = m_raw q p `divideAlls` [ mqp | q' <- [ 0 .. q ] , p' <- [ 1 .. p ] , q' + p' < q + p , p `mod` p' == 0 , let mqp = m q' p' , not (isZero mqp) ] -- |M_{q,p}| d :: Int -> Int -> Int d q p = case degree (m q p) of Just k -> k ; Nothing -> -1

This is using numeric-prelude and MemoTrie from Hackage, but with a reimplemented divMod for monic polynomials that doesn't try to divide by an Integer (which will always be \(1\) for monic polynomials). The core polynomial divMod from numeric-prelude needs a Field for division, and the integers don't form a field.

Tabulating this **attempt** at \(\left|M_{q,p}\right|\) (`d q p`

)
for various small \(q,p\) gives:

q | p | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|

1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |

0 | 1 | 1 | 3 | 6 | 15 | 27 | 63 | 120 | 252 | 495 | 1023 | 2010 | 4095 | 8127 | 16365 | 32640 |

1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |

2 | 1 | 2 | 6 | 12 | 30 | 54 | 126 | 240 | 504 | 990 | 2046 | 4020 | 8190 | 16254 | ||

3 | 3 | 3 | 12 | 24 | 60 | 108 | 252 | 480 | 1008 | 1980 | 4092 | 8040 | 16380 | |||

4 | 7 | 8 | 21 | 48 | 120 | 216 | 504 | 960 | 2016 | 3960 | 8184 | 16080 | ||||

5 | 15 | 15 | 48 | 90 | 240 | 432 | 1008 | 1920 | 4032 | 7920 | 16368 | |||||

6 | 31 | 32 | 96 | 192 | 465 | 864 | 2016 | 3840 | 8064 | 15840 | ||||||

7 | 63 | 63 | 189 | 384 | 960 | 1701 | 4032 | 7680 | 16128 | |||||||

8 | 127 | 128 | 384 | 768 | 1920 | 3456 | 8001 | 15360 | ||||||||

9 | 255 | 255 | 768 | 1530 | 3840 | 6912 | 16128 | |||||||||

10 | 511 | 512 | 1533 | 3072 | 7680 | 13824 | ||||||||||

11 | 1023 | 1023 | 3072 | 6144 | 15345 | |||||||||||

12 | 2047 | 2048 | 6144 | 12288 | ||||||||||||

13 | 4095 | 4095 | 12285 | |||||||||||||

14 | 8191 | 8192 | ||||||||||||||

15 | 16383 |

\(|M_{0,p}|\) is known to be A000740. \(|M_{2,p}|\) appears to be A038199. \(|M_{q,1}|\) appears to be A000225. \(|M_{q,2}|\) appears to be A166920.

**HOWEVER there is a fatal flaw**. The polynomials might
not be irreducible, which means that `divideAlls`

might not be
removing all of the lower (pre)period roots! A proper solution would be
to port the code to a computer algebra system that can factor polynomials
into irreducible polynomials. Or alternatively, mathematically prove that
the polynomials in question will always be irreducible (as far as I know
this is an open question, verified only for \(M_{0,p}\) up to \(p = 10\),
according to
Corollary 5.6 (Centers of Components as Algebraic Numbers)).

You can download my full Haskell code.

**UPDATE** I wrote some Sage code (Python-based) with an
improved algorithm (I think it's perfect now). The values all matched the
original table, and I extended it with further values and links to OEIS.
All the polynomials in question are irreducible, up to the \(p + q < 16\)
limit. No multiplicities greater than one were reported. Code:

@parallel(16) def core(q, p, allroots): mpq = 0 roots = set() R.<x> = ZZ[] w = 0*x for i in range(q): w = w^2 + x wq = w for i in range(p): w = w^2 + x wqp = w f = wqp - wq r = f.factor() for i in r: m = i[0] k = i[1] if not (m in allroots) and not (m in roots): roots.add(m) mpq += m.degree() if k > 1: print(("multiplicity > 1", k, "q", q, "p", p, "degree", m.degree())) return (q, p, mpq, roots) allroots = set() for n in range(16): print(n) res = sorted(list(core([(q, n - q, allroots) for q in range(n)]))) for r in res: t = r[1] q = t[0] p = t[1] mpq = t[2] roots = t[3] print((q, p, mpq, len(roots), [root.degree() for root in roots])) allroots |= roots

**UPDATE2** I bumped the table to \(q + p < 17\). I
ran into some OOM-kills, so I had to run it with less parallelism to get
it to finish.

**UPDATE3** I found a simple function that fits all the
data in the table, but I don't know if it is correct or will break for
larger values. Code (the function is called `f`

):

import Math.NumberTheory.ArithmeticFunctions (divisors, moebius, runMoebius) -- arithmoi import Data.Set (toList) -- containers mu :: Integer -> Integer mu = runMoebius . moebius mqps :: [[Integer]] mqps = [[1,1,3,6,15,27,63,120,252,495,1023,2010,4095,8127,16365,32640] ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ,[1,2,6,12,30,54,126,240,504,990,2046,4020,8190,16254] ,[3,3,12,24,60,108,252,480,1008,1980,4092,8040,16380] ,[7,8,21,48,120,216,504,960,2016,3960,8184,16080] ,[15,15,48,90,240,432,1008,1920,4032,7920,16368] ,[31,32,96,192,465,864,2016,3840,8064,15840] ,[63,63,189,384,960,1701,4032,7680,16128] ,[127,128,384,768,1920,3456,8001,15360] ,[255,255,768,1530,3840,6912,16128] ,[511,512,1533,3072,7680,13824] ,[1023,1023,3072,6144,15345] ,[2047,2048,6144,12288] ,[4095,4095,12285] ,[8191,8192] ,[16383] ] m :: Integer -> Integer -> Integer m q p = mqps !! fromInteger q !! fromInteger (p - 1) f :: Integer -> Integer -> Integer f 0 p = sum [ mu (p `div` d) * 2 ^ (d - 1) | d <- toList (divisors p) ] f 1 _ = 0 f q 1 = 2 ^ (q - 1) - 1 f q p = (2 ^ (q - 1) - if q `mod` p == 1 then 1 else 0) * f 0 p check :: Bool check = and [ f q p == m q p | n <- [1 .. 16], p <- [1 .. n], let q = n - p ] main :: IO () main = print check

**UPDATE4** Progress! I found a paper with the answer:

Misiurewicz Points for Polynomial Maps and Transversality

Benjamin Hutz, Adam Towsley

Corollary 3.3.The number of \((m,n)\) Misiurewicz points for \(f_{d,c}\) is \[ M_{m,n} = \begin{cases} \sum_{k \mid n} \mu\left(n \over k \right) d^{k-1} & m = 0 \\ (d^m - d^{m-1} - d + 1) \sum_{k \mid n} \mu\left(n \over k \right) d^{k-1} & m \ne 0 \text{ and } n \mid (m - 1) \\ (d^m - d^{m-1}) \sum_{k \mid n} \mu\left(n \over k \right) d^{k-1} & \text{otherwise} \end{cases} \]

They have \(f_{d,c}(z) = z^d + c\), so this result is more general than the case \(d = 2\) I was researching in this post. The formula I came up with is the same, with minor notational differences.

]]>Back in 2011 I played around with word edit graphs based on the spelling of words. Recently I've been playing with the same concept applied to the pronunciation of words. I used the Haskell package pronounce to load the CMUDict pronouncing dictionary, but most of my code didn't really use the API of the library - mostly because it uses a reader monad while I wanted to use a random monad for algorithmic poetry generation, which didn't happen yet (but maybe I'll send patches to use a transformer instead of a plain reader).

Here's some output:

And here's the code, mostly copy/paste of the 2011 code:

{-# LANGUAGE OverloadedStrings #-} module Main (main) where import Prelude hiding (Word) import Text.Pronounce import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Map (Map) import qualified Data.Map as M import Data.Set (Set) import qualified Data.Set as S import Control.Monad (forM_, when) import Data.Char (isDigit, isLower) import Data.List (inits, tails) import Data.Maybe (fromMaybe) import System.Environment (getArgs) type Dict = (Map Word [Pron], Map Pron [Word]) invert :: (Ord k, Ord v) => Map k [v] -> Map v [k] invert m = M.fromListWith (++) [(v, [k]) | (k, vs) <- M.toList m, v <- vs] type Phon = T.Text type Pron = T.Text type Word = T.Text toPron :: Dict -> Word -> [Pron] toPron (dict, _) word = fromMaybe [] (M.lookup word dict) fromPron :: Dict -> Pron -> [Word] fromPron (_, dict) pron = fromMaybe [] (M.lookup pron dict) editPron :: Pron -> [Pron] editPron pron = map T.unwords (delete1 s ++ replace1 s ++ insert1 s) where s = T.words pron delete1 :: [Phon] -> [[Phon]] delete1 [] = [] delete1 (x:xs) = xs : ((x:) <$> delete1 xs) replace1 :: [Phon] -> [[Phon]] replace1 [] = [[]] replace1 (x:xs) = [ y:xs | y <- {- phonemes -} if x `elem` vowels then vowels else consonants ] ++ [ x:ys | ys <- replace1 xs ] insert1 :: [Phon] -> [[Phon]] insert1 xs = [ ys ++ [p] ++ zs | (ys, zs) <- zip (inits xs) (tails xs) , p <- phonemes ] edit :: Dict -> Word -> [Word] edit dict word = do pron <- toPron dict word pron <- editPron pron take 1 $ fromPron dict pron data Graph = Graph { nodes :: Set Word , edges :: Set (Word, Word) , border :: Set Word , unused :: Set Word } graph :: Set Word -> Word -> Graph graph ns s = Graph { nodes = S.singleton s , edges = S.empty , border = S.singleton s , unused = S.delete s ns } gGrow :: (Word -> [Word]) -> Bool -> Graph -> Graph -> Maybe (Set (Word, Word)) gGrow f phase source sink | (not . S.null) inter = Just . (if phase then gReverse else id) $ gJoin inter (edges source) (edges sink) | S.null sourceNew = Nothing | otherwise = gGrow f (not phase) sink source' where inter = (nodes source `S.intersection` nodes sink) neighbours g = let (n, e) = unzip . concatMap (step f) . S.toList . border $ g ns = unused g `S.intersection` S.fromList n es = ((`S.member` unused g) . snd) `S.filter` S.fromList e in (ns, es) (sourceNew, sourceEdge) = neighbours source source' = source { nodes = nodes source `S.union` sourceNew , border = sourceNew , edges = edges source `S.union` sourceEdge , unused = unused source `S.difference` sourceNew } gJoin :: Set Word -> Set (Word, Word) -> Set (Word, Word) -> Set (Word, Word) gJoin inter source sink = gPrune inter source `S.union` gReverse (gPrune inter sink) gPrune :: Set Word -> Set (Word, Word) -> Set (Word, Word) gPrune ns es | S.null ns = S.empty | otherwise = let keep = S.filter ((`S.member` ns) . snd) es in keep `S.union` gPrune (S.map fst keep) (es `S.difference` keep) gReverse :: Set (Word, Word) -> Set (Word, Word) gReverse = S.map swap swap :: (Word, Word) -> (Word, Word) swap (x, y) = (y, x) step :: (Word -> [Word]) -> Word -> [(Word, (Word, Word))] step f w = (\s -> (s, (w, s))) <$> f w main :: IO () main = do args <- map T.pack <$> getArgs cmu <- M.map (map (T.filter (not . isDigit))) <$> stdDict usr <- readDict cmu <- pure $ M.intersection cmu (M.fromList [(w,()) | w <- S.toList usr ]) dict <- pure $ M.keysSet cmu f <- pure $ edit (cmu, invert cmu) T.putStrLn $ "digraph G {" T.putStrLn $ "node[shape=\"box\",style=\"filled\",fillcolor=\"#ff8888\"];" <> T.unwords (map (T.pack . (++";") . show) args) <> " node[shape=\"ellipse\",fillcolor=\"#cccccc\"];" when (all (`M.member` cmu) args) $ forM_ (zip args (tail args)) $ \(src, snk) -> case gGrow f False (graph dict src) (graph dict snk) of Nothing -> T.putStrLn $ T.pack (" " <> show src <> "; " <> show snk <> ";") Just es -> forM_ (S.toList es) $ \(a, b) -> T.putStrLn $ T.pack (" " <> show a <> " -> " <> show b <> ";") putStrLn "}" readDict :: IO (Set Word) readDict = do d <- T.readFile dictFile return . S.fromList . map T.toUpper . filter (\w -> T.all isLower w && 1<T.length w) . T.lines $ d where dictFile = "/usr/share/dict/words" vowels, consonants, phonemes :: [T.Text] vowels = T.words "AA AE AH AO AW AY EH ER EY IH IY OW OY UH UW" consonants = T.words "B CH D DH F G HH JH K L M N NG P R S SH T TH V W Y Z ZH" phonemes = vowels ++ consonants

I also implemented the O(V^{3})
Floyd-Warshall algorithm
for finding the longest shortest paths in the entire pronunciation adjacency
graph of the intersection between the CMUDict and the all-lower-case words
in /usr/share/dict/words, in C running on 16 cores it still took over 4 hours
to run. I should have spent the 8 hours (because I ran it twice, with slightly
different adjacency classes) finding and implementing a faster algorithm,
apparently it can be done in O(E V α(E, V)) which is much much better:

Computing shortest paths with comparisons and additions.

Pettie, Seth; Ramachandran, Vijaya (2002).

Proceedings of the thirteenth annual ACM-SIAM symposium on Discrete algorithms. pp. 267–276. ISBN 0-89871-513-X.

AbstractWe present an undirected all-pairs shortest paths (APSP) algorithm which runs on a pointer machine in time O(mnα(m,n)) while making O(mnlog α(m, n)) comparisons and additions, where m and n are the number of edges and vertices, respectively, and α(m, n) is Tarjan's inverse-Ackermann function. This improves upon all previous comparison & addition-based APSP algorithms when the graph is sparse, i.e., when m = o(n log n).At the heart of our APSP algorithm is a new single-source shortest paths algorithm which runs in time O(mα(m, n) + n log log r) on a pointer machine, where r is the ratio of the maximum-to-minimum edge length. So long as r < 2no(1) this algorithm is faster than any implementation of Dijkstra's classical algorithm in the comparison-addition model.For directed graphs we give an O(m + n log r)-time comparison & addition-based SSSP algorithm on a pointer machine. Similar algorithms assuming integer weights or the RAM model were given earlier.

The longest shortest paths are of length 26, here's the output images:

Here's the C code (note: needs 7GB RAM for this (hardcoded) size graph):

#include <stdint.h> #include <stdio.h> #define INFINITY 65535 #define V 42447 uint16_t a[2][V][V]; int diameter(void) { for (int k = 0; k < V; ++k) { fprintf(stderr, "\r%d", k); int src = k & 1; int dst = 1 - src; #pragma omp parallel for for (int i = 0; i < V; ++i) for (int j = 0; j < V; ++j) a[dst][i][j] = a[src][i][j]; #pragma omp parallel for for (int i = 0; i < V; ++i) { uint32_t ik = a[src][i][k]; if (ik < INFINITY) { for (int j = 0; j < V; ++j) { uint32_t kj = a[src][k][j]; if (kj < INFINITY) { uint32_t ij = a[src][i][j]; uint32_t d = ik + kj; if (ij > d) { a[dst][i][j] = d; } } } } } } int src = V & 1; int m = 0; #pragma omp parallel for reduction(max:m) for (int i = 0; i < V; ++i) { int mi = 0; for (int j = 0; j < V; ++j) { int x = a[src][i][j]; if (x < INFINITY) mi = x > mi ? x : mi; } m = mi > m ? mi : m; } return m; } int main() { fread(&a[0][0][0], sizeof(a[0][0][0]) * V * V, 1, stdin); int m = diameter(); printf("%d\n", m); int src = V & 1; for (int i = 0; i < V; ++i) for (int j = i; j < V; ++j) if (a[src][i][j] == m) printf("%d %d\n", i, j); return 0; }

And supporting Haskell code:

adjMatrix :: Set Text -> (Text -> [Text]) -> ST s (STUArray s (Int, Int) Word16) adjMatrix dict f = do v <- pure $ S.size dict - 1 a <- newArray ((0,0),(v,v)) maxBound forM_ ([0 .. v] `zip` S.toList dict) $ \(i, p) -> do writeArray a (i, i) 0 forM_ (f (S.elemAt i dict)) $ \w -> do let j = S.findIndex w dict when (i /= j) $ do writeArray a (i, j) 1 writeArray a (j, i) 1 return a floydWarshall :: Set Text -> (Text -> [Text]) -> IO (Int, [(Text, Text)]) floydWarshall dict f = do let a :: UArray (Int, Int) Word16 a = runSTUArray (adjMAtrix dict f) v = Data.Vector.Storable.fromListN (S.size dict^2) (A.elems a) withCreateProcess ((proc "./a.out" []) { std_in = CreatePipe, std_out = Inherit, std_err = Inherit } ) $ \(Just hin) _ _ ph -> do forkIO $ V.unsafeWith v (\p -> hPutBuf hin p (sizeOf (0 :: Word16) * S.size dict ^ 2)) waitForProcess ph return (maxBound, []) -- get stdout and paste (modified) into code like this -- didn't manage to get parsing working properly {- let e x = S.elemAt x dict f x y = (e x, e y) return (26, [f 15021 30928 ,f 15021 33278 ,f 15021 33279 ,f 15021 35870 ,f 15021 37719 ,f 28802 30928 ,f 28802 33278 ,f 28802 33279 ,f 30667 30928 ,f 30667 33278 ,f 30667 33279 ]) -}

If there are any other (better) pronouncing dictionaries out there, let me know - especially if they have nice free/open licenses - as the CMUDict has some idiosyncracies.

]]>hp2pretty is a tool I wrote to graph Haskell heap profiling output, to help see where your program is using memory.

Install with `cabal update && cabal install hp2pretty`

,
run with `hp2pretty --help`

(assuming cabal installed it somewhere
in your `PATH`

- otherwise try `~/.cabal/bin`

).

Inspired by an emailed bug report (with patch, my favourite kind, although
I didn't apply it in this case due to some weird issues in Firefox) about
the key being truncated when the +RTS -L option is used for long cost center
names, I worked on making the key and title output more flexible. The
workflow to generate a report with a detached key is via a simple text file,
listing the SVG for the key label box, and the corresponding SCC name. This
format is easy to parse with bash (for example), and I wrote scripts to output
LaTeX for PDF and also simple HTML5. A Makefile build system glues it
together. See the `examples/`

subfolder of `cabal unpack hp2pretty`

.

The LaTeX output is made easier by the svg package, which handles the calls to inkscape to convert files automatically. I learnt about it and found a complete example on TeX stackexchange.

You can download the example output report.pdf or browse the example output report.html.

]]>Today I presented GULCII, my Graphical Untyped Lambda Calculus Interactive Interpreter, at the University of Edinburgh Informatics department. It went well I think. The first go around in the morning I missed some slides about de Bruijn indexes but the afternoon audience seemed to be familiar with it. First I performed with GULCII, the same set as the FARM conference music evening, testing an equivalence between Church encoding and Scott encoding of natural numbers. Then I presented some slides about those encoding schemes of data in untyped lambda calculus, and a bit about how GULCII works, some shortcomings, and ideas for future developments.

A week before the talk I discovered a bad bug in the evaluator ("exp two two" with Church numerals was not equal to "four"), but I managed to rewrite it in time, using the "scope extrusion" rules from the Lambdascope paper. However, sharing is still broken, so the next version will probably switch to using Lambdascope as a library, so that full lazy graph reduction will work properly. The bug had been there since 2011, and is visible in the video of my FARM performance.

You can download the slides from my presentation,
or view the Pandoc Markdown for LaTex Beamer
source code.
I might upload some video of the second talk soon. GULCII itself is
on Hackage so you can install
it with `cabal install gulcii`

.

Incidentally the brickwork opposite the Informatics Forum reminded me of Donky Kong, with its ladders:

]]>I last played live with GULCII in 2011 at LiWoLi and NOHUP Bow Arts Open around the same time. With further tweaks and enhancements, I've been rehearsing a short code recital for the FARM conference's Evening Of Algorithmic Arts at the Old Fire Station in Oxford, UK, this coming Saturday 9th September. Book your free ticket online and come along, it should be a good one with many performances.

GULCII (Graphical Untyped Lambda Calculus Interactive Interpreter) is an untyped lambda calculus interpreter supporting interactive modification of a running program with graphical display of graph reduction. Lambda calculus is a minimal prototypical functional programming language developed by Alonzo Church in the 1930s. Church encoding uses folds to represent data as higher-order functions. Dana Scott's encoding composes algebraic data types as functions. Each has strengths and weaknesses.

The performance is a code recital, with the internal state of the interpreter visualized and sonified. Part 1 introduces Church encoding. Part 2 develops Scott encoding. An interlude takes in two non-terminating loops, each with their own intrinsic computational rhythm. Finally, Part 3 tests an equivalence between Church and Scott numerals.

The performance involves functional programming in two ways. Firstly, the live recital of lambda calculus code into the interpreter for evaluation. Secondly, the interpreter itself is written in the functional programming language Haskell.

Regarding related work, Henning Thielemann's
"live-sequencer"
has a similar approach to code evaluation with names rebindable to terms during
runtime. Sonification of data has a long history, including a mention by Douglas
Adams in "*Dirk Gently’s Holistic Detective Agency*" (1987), while
sonification of machines is perhaps more niche. I was inspired by Valentina
Vuksic's
"Sei Personaggi Part 2",
in which "*the magnetic fields of the memory modules (RAM) are being
picked up acoustically while the experimental computer drama is moving on*";
the Puredyne GNU/Linux startup sound, "`cat /dev/mem > /dev/dsp`

";
and also Dave Griffith's "Betablocker"
live-coding acid techno machine.

I'll be at the daytime FARM conference too, if you're at ICFP and want to say hi. Long beard, glasses, probably drinking coffee.

**EDIT:** it went really well, I'm pleased. Had some nice
feedback too, particularly a suggestion that I could have finished up by
defining:

churchPred = \n . toChurch (scottPred (fromChurch n))

I didn't catch the name of the suggester, but I think I'll use this in future, it's a very nice punchline. Thanks, mystery functional programmer! I'll try to improve the sonfication too, it is rather minimal.

Since getting back home I worked a bit on the code, cleaning it up for a 0.3 release on Hackage (not quite ready yet) - I also fixed an infelicity with the evaluator, now it reduces the right-hand branch of an apply node when the left-hand branch is irreducible (and the apply node itself is not a lambda applied to something, in which case it would beta reduce). This means it can make progress in more cases, for example (with Scott-encoded lists and numerals):

iterate succ zero

The version I performed with would get stuck at
`cons zero `

.*stuff*