2025-02-09
Recently I had some excess of procrastination potential (exam phase), so I finally got around to a fun idea I had many months ago. The following binary term calculates Pi to infinite precision:
00010100011010000000010
11100111000011001011111
01111 00000
01110 01110
01011 11110
11010 00000
10111 10000
01010 000001110
01110 0111010
…well, almost, let me explain :)
There’s this fractal called a Sierpiński Carpet (also known as its 3D variant, the Menger sponge). It works by dividing some area into (typically ) parts, and repeating this step on every subarea except the one in the center. Doing this infinite times will result in a fractal.
There’s also this concept called Lambda Screen that I’ve created. It can render fractals and other images using a recursive, quad-tree-like, purely functional product type. Specifically,
tl, tr, bl, and
br represent the top-left, top-right, bottom-left, and
bottom-right quadrants of the image. Each of these terms can either
reduce to another screen, or a pixel.Rendering of terms will stop at a certain depth by drawing the unreduced terms as grey in the current screen context. The outermost term also gets an empty screen as additional argument to allow a kind of monadic IO – we ignore this here using .
By strategic use of fixed-point recursion, you can write some beautifully elegant terms such as this 51-bit term (in Binary Lambda Calculus (BLC), currently the smallest in a codegolf challenge):
How does this relate to ? Well, there’s this researcher Ed Pegg who posted something fascinating in a blog post (archive) around 9 years ago. When you increase by two per “layer” of the Sierpiński Carpet, the total area of the fractal will eventually approach ! (no, not factorial)
This might seem counterintuitive at first, but it actually makes perfect sense. When splitting a screen and recursing it on itself in each of its sub-screens, then the total recursive area will represent a variation of the Wallis product
So, if I can write a minimal lambda term that generates such fractal, we will only have to measure the recursive (grey) area that the Lambda Screen renderer draws and multiply it by in order to get an approximation of . The only problem is that Lambda Screen does not support arbitrary area splits of . This was a simple change in the renderer though that doesn’t even affect the behavior of existing quad-tree terms:
This allows us to draw the following Sierpiński Carpet:
Now there are three remaining open problems:
The solution to the first problem is quite elegant: A Church numeral is a right-associative -fold composition, while a (single-color) screen is a left-associative -fold composition applied to some color. So, in order to convert between the two, you can left fold the Church numeral itself like so:
and thus creating a screen of arbitrary size becomes trivial: In our case, the screen will of course contain itself recursively instead of a single color, but the difference doesn’t matter here – it’s still the same argument.
Since taking the center element of such a functional structure is difficult and ugly, the second problem requires a little trick: We don’t actually care that the non-recursive term in each layer is precisely at the center – as long as it’s always exactly a single term. So, instead we map the first element of the structure we previously generated to a fixed color while keeping the rest.
We first eta-expand the screen term:
Then the first element applied to can be mapped to by injecting a rewrite via :
where we ignore the first argument and replace it while we keep the rest of the left-associative application (the other screens) as-is (argument ).
We also need to implement the actual Church numeral logic, such that we always generate sub-screens when splitting a layer with sub-screens. We can do this by giving the initial term an argument , incrementing it two times at every layer, and – before we convert the Church numeral to a screen with sub-screens using the technique shown above – multiply by itself.
All the above will need to be mapped to the term itself – only that it will now take the incremented Church numeral as argument. All of this comes together in a single term:
Now onto the final problem – making the term tiny! This is done using incredibly complex and highly confidential techniques (i.e. carefully beta-reducing the non-recursive parts with some off-the-shelf strong reducer) and results in the following final term (in bruijn notation):
π [[0 0] [[[1 (1 [0 (3 3 [[1 (1 (4 1 0))]])]) [[2 [[0]] 0]]]]] (+3u)]Converting the term to BLC will result in the 122-bit program from the beginning. Unfortunately I was not able to golf the Church three into the inner term, so the initialization number alone takes of the size!
Here’s the rendering of the term (this may crash your tab if you’re using a phone, chrome, safari, or something else that I don’t use)
While this doesn’t look like an incremented Sierpiński Carpet anymore, it’s actually fully equivalent to a quadrant of Ed Pegg’s design – just with the stable, non-recursive area at the top left.
So you probably also want to know how to get a concrete approximation of from this and how quickly it converges.
The simplest way is to count the area that you’re drawing black within the renderer, calculate to get the recursive area (grey), and multiply this number by four.1 This way you will get the following approximations, based on how many layers deep you go:
| layers | |
|---|---|
| 1 | 3.55555 |
| 2 | 3.41333 |
| 3 | 3.34367 |
| 4 | 3.30239 |
| 5 | 3.27510 |
| 6 | 3.25572 |
| 7 | 3.24125 |
| 8 | 3.23003 |
| 9 | 3.22108 |
| 10 | 3.21378 |
| 11 | 3.20770 |
| 12 | 3.20257 |
| 13 | 3.19818 |
| 14 | 3.19438 |
| 15 | 3.19105 |
| 16 | 3.18812 |
| 17 | 3.18552 |
| 18 | 3.18319 |
| 19 | 3.18110 |
| 20 | 3.17921 |
| 21 | 3.17749 |
| 22 | 3.17592 |
| 23 | 3.17448 |
| 24 | 3.17316 |
| 25 | 3.17194 |
| 26 | 3.17081 |
| 27 | 3.16976 |
| 28 | 3.16879 |
| 29 | 3.16788 |
| 30 | 3.16702 |
| 31 | 3.16623 |
| 32 | 3.16548 |
| 33 | 3.16477 |
| 34 | 3.16411 |
| 35 | 3.16348 |
| 36 | 3.16289 |
| 37 | 3.16232 |
| 38 | 3.16179 |
| 39 | 3.16128 |
| 40 | 3.16080 |
| 41 | 3.16034 |
| 42 | 3.15991 |
| 43 | 3.15949 |
| 44 | 3.15909 |
| 45 | 3.15871 |
| 46 | 3.15834 |
| 47 | 3.15799 |
| 48 | 3.15766 |
| 49 | 3.15733 |
| 50 | 3.15703 |
| 51 | 3.15673 |
| 52 | 3.15644 |
| 53 | 3.15617 |
| 54 | 3.15590 |
| 55 | 3.15564 |
| 56 | 3.15540 |
| 57 | 3.15516 |
| 58 | 3.15493 |
| 59 | 3.15470 |
| 60 | 3.15449 |
| 61 | 3.15428 |
| 62 | 3.15408 |
| 63 | 3.15388 |
| 64 | 3.15369 |
| 65 | 3.15351 |
| 66 | 3.15333 |
| 67 | 3.15316 |
| 68 | 3.15299 |
| 69 | 3.15283 |
| 70 | 3.15267 |
| 71 | 3.15251 |
| 72 | 3.15236 |
| 73 | 3.15222 |
| 74 | 3.15208 |
| 75 | 3.15194 |
| 76 | 3.15180 |
| 77 | 3.15167 |
| 78 | 3.15155 |
| 79 | 3.15142 |
| 80 | 3.15130 |
| 81 | 3.15118 |
| 82 | 3.15106 |
| 83 | 3.15095 |
| 84 | 3.15084 |
| 85 | 3.15073 |
| 86 | 3.15063 |
| 87 | 3.15053 |
| 88 | 3.15042 |
| 89 | 3.15033 |
| 90 | 3.15023 |
| 91 | 3.15014 |
| 92 | 3.15004 |
| 93 | 3.14995 |
| 94 | 3.14987 |
| 95 | 3.14978 |
| 96 | 3.14969 |
| 97 | 3.14961 |
| 98 | 3.14953 |
| 99 | 3.14945 |
| 100 | 3.14937 |
As you can see, this converges quite slowly. Notably, I’ve never gotten deeper than layer 5 using my JavaScript implementation of Lambda Screen, so you probably won’t get some new world record approximation using this technique. You can see the measurement of the rendering above in your browser’s console, it will probably be around 3.302 (layer 4).
I’ve also implemented a Haskell higher-order reducer for this specific type of problem which counts the total area drawn for any term that uses a perfect square amount of sub-screens. I did some experiments with parallel reduction, but it also does not get much further than the JavaScript renderer.
One problem is that at every level we have to “lower” down the HOAS encoding in order to count the area and continue on all sub-screens – I think this might impact sharing of reductions.
This also leads me to a challenge I have for you! Please let me know if you come up with a reducer that measures the area of such fractals more efficiently. Due to its inherent parallelism and sharing of redexes I believe that optimal reduction could be the optimal approach to this problem. My experiments with the HVM optimal runtime failed though, since the term constantly duplicates non-affine terms.
Also let me know if you find smaller encodings of the fractal (and π in general!), I’m fairly certain that it could be golfed below 100 bit.
You can find the Haskell program in a GitHub Gist – have fun!
Contact me via email. Support on Ko-fi. Subscribe on RSS. Follow on Mastodon.
动态网自由门 天安門 天安门 法輪功 李洪志 Free Tibet 六四天安門事件 The Tiananmen Square protests of 1989 天安門大屠殺 The Tiananmen Square Massacre 反右派鬥爭 The Anti-Rightist Struggle 大躍進政策 The Great Leap Forward 文化大革命 The Great Proletarian Cultural Revolution 人權 Human Rights 民運 Democratization 自由 Freedom 獨立 Independence 多黨制 Multi-party system 台灣 臺灣 Taiwan Formosa 中華民國 Republic of China 西藏 土伯特 唐古特 Tibet 達賴喇嘛 Dalai Lama 法輪功 Falun Dafa 新疆維吾爾自治區 The Xinjiang Uyghur Autonomous Region 諾貝爾和平獎 Nobel Peace Prize 劉暁波 Liu Xiaobo 民主 言論 思想 反共 反革命 抗議 運動 騷亂 暴亂 騷擾 擾亂 抗暴 平反 維權 示威游行 李洪志 法輪大法 大法弟子 強制斷種 強制堕胎 民族淨化 人體實驗 肅清 胡耀邦 趙紫陽 魏京生 王丹 還政於民 和平演變 激流中國 北京之春 大紀元時報 九評論共産黨 獨裁 專制 壓制 統一 監視 鎮壓 迫害 侵略 掠奪 破壞 拷問 屠殺 活摘器官 誘拐 買賣人口 遊進 走私 毒品 賣淫 春畫 賭博 六合彩 天安門 天安门 法輪功 李洪志 Winnie the Pooh 劉曉波动态网自由门
Imprint · AI Statement · RSS