The Art of Omission
Whether in painting, fiction, film, landscape architecture, or paper folding, art is often said to be the art of omission. Simplicity breeds elegance, and engages the reader at a deep, aesthetic level.
A prime example is the famous six-word story written by Ernest Hemingway:
For sale: baby shoes, never worn.
He called it his best work, and rightfully so. To say so much with this simple picture is a monumental feat that authors have been trying to recreate since Hemingway’s day. Unsurprisingly, some mathematicians (for whom the art of proof had better not omit anything!) want to apply their principles to describe elegance.
Computation and Complexity
This study of artistic elegance will be from a computational perspective, and it will be based loosely on the paper of the same name. While we include the main content of the paper in a condensed form, we will deviate in two important ways: we alter an axiom with justification, and we provide a working implementation for the reader’s use. We do not require extensive working knowledge of theoretical computation, but the informed reader should be aware that everything here is theoretically performed on a Turing machine, but the details are unimportant.
So let us begin with the computational characterization of simplicity. Unfortunately, due to our own lack of knowledge of the subject, we will overlook the underlying details and take them for granted. [At some point in the future, we will provide a primer on Kolmogorov complexity. We just ordered a wonderful book on it, and can’t wait to dig into it!]
Here we recognize that all digital images are strings of bits, and so when we speak of the complexity of a string, in addition to meaning strings in general, we specifically mean the complexity of an image.
Definition: The Kolmogorov complexity of a string is the length of the shortest program which generates it.
In order to specify “length” appropriately, we must fix some universal description language, so that all programs have the same frame of reference. Any Turing-complete programming language will do, so let us choose Python for the following examples. More specifically, there exists a universal Turing machine $ U$, for which any program on any machine may be translated (compiled) into an equivalent program for $ U$ by a program of fixed size. Hence, the measure of Kolmogorov complexity, when a fixed machine is specified (in this case Python), is objective over the class of all outputs.
Here is a simple example illustrating Kolmogorov complexity: consider the string of one hundred zeros. This string is obviously not very “complex,” in the sense that one could write a very short program to generate it. In Python:
print "0" * 100
One can imagine that a compiler which optimizes for brevity would output rather short assembly code as well, with a single print instruction and a conditional branch, and some constants. On the other hand, we want to call a string like
“00111010010000101101001110101000111101”
complex, because it follows no apparent pattern. Indeed, in Python the shortest program to output this string is just to print the string itself:
print "00111010010000101101001110101000111101"
And so we see that this random string of ones and zeros has a higher Kolmogorov complexity than the string of all zeros. In other words, the boring string of all zeros is “simple,” while the other is “complicated.”
Kolmogorov himself proved that there is no algorithm to compute Kolmogorov complexity (the number itself) for any input. In other words, the problem of determining exact Kolmogorov complexity is undecidable (by reduction from the halting problem; see the Turing machines primer). So we will not try in vain to actually get a number for the Kolmogorov complexity of arbitrary programs, although it is easy to count the lengths of these provably short examples, and instead we speak of complexity in terms of bounds and relativity.
Kolmogorov Meets Picasso
To apply this to art, we want to ask, “for a given picture, what is the length of the shortest program that outputs it?” This will tell us whether a picture is simple or complex. Unfortunately for us, most pictures are neither generated by programs, nor do they have obvious programmatic representations. More feasibly, we can ask, “can we come up with pictures which have low Kolmogorov complexity and are also beautiful?” This is truly a tough task.
To do so, we must first invent an encoding for pictures, and write a program to interpret the encoding. That’s the easy part. Then, the true test, we must paint a beautiful picture.
We don’t pretend to be capable of such artistry. However, there are some who have created an encoding based on circles and drawn very nice pictures with it. Here we will present those pictures as motivation, and then develop a very similar encoding method, providing the code and examples for the reader to play with.
Jürgen Schmidhuber, a long time proponent of low-complexity art, spent a very long time (on the order of thousands of sketches) creating drawings using his circle encoding method, and here are some of his results:
Marvelous. Our creations will be much uglier. But we admit, one must start somewhere, and it might as well be where we feel most comfortable: mathematics and programming.
Magnificence Meets Method
There are many possible encodings for drawings. We will choose one which is fairly easy to implement, and based on intersecting circles. The strokes in a drawing are arcs of these circles. We call the circles used to generate drawings legal circles, while the arcs are legal arcs. Here is an axiomatic specification of how to generate legal circles:
- Arbitrarily define the a circle $ C$ with radius 1 as legal. All other circles are generated with respect to this circle. Define a second legal circle whose center is on $ C$, and also has radius 1.
- Wherever two legal circles of equal radius intersect, a third circle of equal radius is centered at the point of intersection.
- Every legal circle of radius $ r$ has at its center another legal circle of radius $ r/2$.
A legal arc is then simply any arc of a legal circle, and a legal drawing is any list of legal arcs, where each arc has a width corresponding to some fixed set of values. Now we generate all circles which intersect the interior of the base circle $ C$, and sort them first by radius, then by $ x$ coordinate, then $ y$ coordinate. Now given a specified order on the circles, we may number them from 1 to $ n$, and specify a particular circle by its index in the list. In this way, we have defined a coordinate space of arcs, with points of the form (center, thickness, arc-start, arc-end), where the arc-start and art-end coordinates are measured in radians.
We describe the programmatic construction of these circles later. For now, here is the generated picture of all circles which intersect the unit circle up to radius $ 2^{-5}$:
In addition, we provide an animation showing the different layers:
And another animation displaying the list circles sorted by index in increasing order. For an animated GIF, this file has a large size (5MB), and so we link to it separately.
As we construct smaller and smaller circles, the interior of the base circle is covered up by a larger proportion of legally usable area. By using obscenely small circles, we may theoretically construct any drawing. On the other hand, what we care about is how much information is needed to do so.
Because of our nice well ordering on circles, those circles with very small radii will have huge indices! Indeed, there are about four circles of radius $ 2^{-i-1}$ for each circle of radius $ 2^{-i}$ in any fixed area. Then, we can measure the complexity of a drawing by how many characters its list of legal arcs requires. Clearly, a rendition of Starry Night would have a large number of high-indexed circles, and hence have high Kolmogorov complexity. (On second thought, I wonder how hard it would be to get a rough sketch of a Starry-Night-esque picture in this circle encoding…it might not be all that complex).
Note that Schmidhuber defines things slightly differently. In particular, he requires that the endpoints of a legal arc must be the intersection points of two other legal arcs, making the arc-start and arc-end coordinates integers instead of radian measures. We respectfully disagree with this axiom, and we explain why here:
Of the two arcs in the picture to the left, which would you say is more complex, the larger or the smaller? We observe that two arcs of the same circle, regardless of how long or short they are, should not be significantly different in complexity.
Schmidhuber, on the other hand, implicitly claims that arcs which begin or terminate at non-standard locations (locations which only correspond to the intersections of sufficiently small circles) should be deemed more complex. But this can be a difference as small as $ \pi/100$, and it drastically alters the complexity. We consider this specification unrealistic, at least to the extent to which human beings consider complexity in art. So we stick to radians.
Indeed, our model does alter the complexity for some radian measures, simply because finely specifying fractions requires more bits than integral values. But the change in complexity is hardly as drastic.
In addition, Schmidhuber allows for region shading between legal arcs. Since we did not find an easy way to implement this in Mathematica, we skipped it as extraneous.
Such Stuff as Programs are Made of
We implemented this circle encoding in Mathematica. The reader is encouraged to download and experiment with the full notebook, available from this blog’s Github page. We will explain the important bits here.
First, we have a function to compute all the circles whose centers lie on a given circle:
borderCircleCenters[{x_, y_}, r_] := Table[{x + r Cos[i 2 Pi/6], y + r Sin[i 2 Pi/6]}, {i, 0, 5}];
We arbitrarily picked the first legal circle to be the unit circle, defined with center (0,0), while the second has center (1,0). This made generating all legal circles a relatively simple search task. In addition, we recognize that any arbitrary second chosen circle is simply a rotation of this chosen configuration, so one may rotate their final drawing to accommodate for a different initialization step.
Second, we have the brute-force search of all circles. We loop through all circles in a list, generating the six border circles appropriately, and then filtering out the ones we need, repeating until we have all the circles which intersect the interior of the unit circle. Note our inefficiencies: we search out as far as radius 2 to find small circles which do not necessarily intersect the unit circle, and we calculate the border circles of each circle many times. On the other hand, finding all circles as small as radius $ 2^{-5}$ takes about a minute on an Intel Atom processor, which is not so slow to need excessive tuning for a prototype’s sake.
getAllCenters[r_] := Module[{centers, borderCenters, searchR, ord, rt}, ord[{a_, b_}, {c_, d_}] := If[a < c, True, b < d]; centers = {{0, 0}}; rt = Power[r, 1/2]; While[Norm[centers[[-1]]] <= Min[2, 1 + rt], borderCenters = Map[borderCircleCenters[#, r] &, centers]; centers = centers \[Union] Flatten[borderCenters, 1]]; Sort[Select[centers, Norm[#] < 1 + r &], ord] ];
Finally, we have a function to extract from the resulting list of all centers the center and radius of a given index, and a function to convert a coordinate to its graphical representation:
(* extracts a pair {center, radius} given the index of the circle *) indexToCenterRadius[layeredCenters_, index_] := Module[{row, length, counter}, row = 1; length = Length[layeredCenters[[row]]]; counter = index; While[counter > length, counter -= length; row++; length = Length[layeredCenters[[row]]]; ]; {layeredCenters[[row, counter]], 1/2^(row - 1)} ]; drawArc[{index_, thickness_, arcStart_, arcEnd_}] := Module[{center, radius}, {center, radius} = indexToCenterRadius[allCenters, index]; Graphics[{Thickness[thickness], Circle[center, radius, {arcStart, arcEnd}]}, ImagePadding -> 5, PlotRange -> {{-1, 1}, {-1, 1}}, ImageSize -> {400, 400}] ];
And a front-end style function, which takes a list of coordinates and draws the resulting picture:
paint[coordinates_] := Show[Map[drawArc, coordinates]];
Any omitted details (at least one global variable name) are clarified in the notebook.
Now, with our paintbrush in hand, we unveil our very first low-complexity piece of art. Behold! Surprised Mr. Moustache Witnessing a Collapsing Soufflé:
It’s coordinates are:
{{7, 0.005, 0, 2 Pi}, {197, 0.002, 0, 2 Pi}, {299, 0.002, 0, 2 Pi}, {783, 0.002, 0, 2 Pi}, {2140, 0.001, 0, 2 Pi}, {3592, 0.001, 0, 2 Pi}, {22, 0.004, 8 Pi/6, 10 Pi/6}, {29, 0.004, 4 Pi/3, 5 Pi/3}, {21, 0.004, Pi/3, 2 Pi/3}, {28, 0.004, Pi/3, 2 Pi/3}}
Okay, so it’s lame, and took all of ten minutes to create (guess-and-check on the indices is quick, thanks to Mathematica’s interpreter). But it has low Kolmogorov complexity! And that’s got to count for something, right?
Even if you disagree with our obviously inspired artistic genius, the Mathematica framework for creating such drawings is free and available for anyone to play with. So please, should you have any artistic talent at all (and access to Mathematica), we would love to see your low-complexity art! If we somehow come across three days of being locked in a room with access to nothing but a computer and a picture of Starry Night, we might attempt to recreate a sketch of it for this blog. But until then, we will explore other avenues.
Happy sketching!
Addendum: Note that the outstanding problem here is how to algorithmically take a given picture (or specification of what one wants to draw), and translate it into this system of coordinates. As of now, no such algorithm is known, and hence we call the process of making a drawing art. We may attempt to find such a method in the future, but it is likely hard, and if we produced an algorithm even a quarter as good as we might hope, we would likely publish a paper first, and blog about it second.
What about fractals?
Technically this circle construction is a fractal (if we drew larger circles and smaller circles ad infinitum), but we are selecting pieces of the fractal with the goal of constructing a specific picture. The difference here is that we have well-defined curves to choose from, whereas in something like Mandelbrot’s set, it’s a gradient. Of course, there are other fractals which draw specific pictures, like the Barnsley fern, but these sorts of constructions have a disadvantage for our purposes because each algorithm is specific to the object being created.
With this construction we have a distinct analytical advantage. Any drawing can be drawn, so our framework is universal. And when we restrict our attention to this particular “style” of art, any drawing can be compared to any other drawing in terms of complexity. We could theoretically construct both the Mandelbrot set and the Barnsley fern using the coordinate system, but our real problem is to find those drawings which have very low complexity in this framework and are still beautiful.
So, where’s your drawing? 🙂
This is a nice attempt to compare the Kolmogorov complexity of images, but what happens if you try to compare the complexities of the rather simple geometric figures of a suare and a circle?
Your system will tell you that the square has an infinite complexity (which is not true) and the circle is rather simple. Your results are biased by the way you encode your information. If you encoded your pictures with parts of straight lines instead of parts of circles the comparision of a square and a circle would give you the opposite results (square rather simple but the circle infinitely complex).
Now saying that you could just combine the circles-system and the lines-system would not get you anywhere: now circles and squares are both simple (as they are supposed to be) but for example a Mandelbrot fractal (with low Kolmogorov complexity) would still be graded infinitely complex.
If you want any usable results you need to use math or any equivalent strong language to encode your picture information. And then again the encoding of a picture is not unique any more and you need to make sure that any image you draw/construct is encoded in the simplest way possible which is equally hard as computing the Kolmogorov complexity.
This is not mean as a rant on your article. I love it and your whole blog. It makes me think and teaches me! Thanks a lot for the effort you put into your articles.
Maximilian
You make some very good points, and the square counterexample clearly came from a mathematical mind! And I think the only rebuttal is this: the circle framework is not designed to be useful. In fact, it is not hard to see that determining the correct Kolmogorov complexity of any image is undecidable, since any string can be interpreted as the pixel information of an image.
So it is not fruitful to search for such a system, because no system exists. Here’s a relatively simple example, however, of a more expressive system that encapsulates both circles and squares: Bezier curves. However, this system is just more complex, and it sidesteps the point of the article.
That is, this is a question about aesthetics: are designs with provably low Kolmogorov complexity more beautiful than those with higher Kolmogorov complexity (with respect to a universal encoding system)?
Whatever you believe, Kolmogorov complexity is pretty fascinating stuff. I’m planning to look at it in some more depth in the coming months. In particular it has shown up in a number of machine learning applications.
I agree that some of my arguments stated the obvious. I just like to write down my though process and make everything as easy to understand as possible.
A system based on beziers would certeinly be worth to look into. Especially since they are not that hard to implement.
I am looking forward to your future articles about Kolmogorov complexity because I feel that it is very important even though I do not (yet) see how it could be at least approximated and used in any way.
I’m a bit late to the paty, but if you want some /really/ good low-complexity art, check old, good demoscene stuff.
> Indeed, in Python the shortest program to output this string is just to print the string itself
On this, I have to disagree:
print ’00’+bin(62557317693)[2:]
will produce “00111010010000101101001110101000111101” just fine.
For Kolmogorov complexity, your program’s length would include the code required to implement “bin,” as well as the code required to implement array indexing.