If any aspects of what follows are confusing, it may help to play with the polyomino tiler a bit to get a better idea for what it's doing.
There are a couple aspects of the problem that combine to make it challenging:
So the algorithm needs to be generic enough to not get bogged down in all the edge cases resulting from these two facts.
The two key components of the algorithm are:
The main preprocessing step is to calculate all possible placements of all available pieces. When hundreds of piece variants are available this step can be lengthy, and the result can require a lot of memory. But the result is essentially a bipartite graph (or, in database terms, a many-to-many relationship) between placements and grid points.
For example, given this 2x3 grid:
and restricting ourselves to the L-shaped trominoes to keep the visuals tractable, the placement graph looks like this:
Notice that all of the geometric details have been abstracted away; this means that the rest of the algorithm could just as easily be applied to grids of triangles, or hexagons, or hyperbolic grids, or higher dimensions.
Given the bipartite graph, the problem of tiling the grid can be reformulated as the problem of finding a subset of the placements that, as a whole, is connected to every one of the grid points exactly one time.
The search algorithm is a backtracking algorithm that at each step chooses an untiled grid point and a random placement associated with that grid point; it adds that placement to the solution set and then tries to tile the remainder of the grid (taking note of which placements are invalidated by the one selected). If it fails to tile the rest of the grid, then it tries the next placement. If it runs out of placements, then the grid must be untileable.
For example, if we decide to start tiling the above grid from grid point A, the search tree might look like this:
Depending on the order that we search the children, we could either end up with the ABC-DEF tiling or the ABD-CEF tiling; if we visit the ACD branch first, we'll have to backtrack since there's no solution in that direction.
The reason the tree is so sparse is that each time we descend we remove placements from the graph that conflict with the one chosen. E.g., when we descend to ABC, the graph is updated to look like this:
This basic search algorithm is specialized with several heuristics/optimizations.
Since every placement we select makes several other placements impossible, it's easy to get to a point where there is some grid point that no longer has any possible placements. This is the most common sign of untileability, and it means we need to backtrack and try something different.
For example, if we descended down the ACD branch of the search tree, the graph would look like this, and the existence of grid points with no placements indicates that there's no solution.
A given set of polyominoes places restrictions on what grids can be tiled purely based on their size. This is solely a function of the set of distinct sizes of the polyominoes.
For example, with the tetrominoes, only grids whose size is a multiple of four can be tiled. If the polyominoes have sizes 4 and 6, then only grids whose size is an even number greater than two can be tiled. This kind of analysis is closely related to the coin problem.
Further, if a grid consists of disconnected subcomponents, then we can analyze the tileability of each one separately, and conclude that the whole grid can't be tileable if any of the subcomponents is not tileable.
For example, this grid has been disconnected, and even though the size of both pieces together is a multiple of three and so could theoretically be tiled by trominoes, the individual subcomponents do not have a multiple of three grid points, and so neither of them are tileable.
If the grid at any point becomes disconnected, we immediately restrict the algorithm to the smallest subcomponent and try to tile that completely before moving on to the next one. The logic here is that if the disconnected grid as a whole can't be tiled, there's a decent chance that the smallest subcomponent can't be tiled. The smallest subcomponent is also likely the one that we can finish searching the fastest, and so this hopefully leads to a more aggressive pruning of the search space.
For example, this grid has a small component and a large component, and the small component can't be tiled with trominoes (can you figure out why?); tiling the smaller one first means we find out about this problem quickly, rather than waiting until the whole large subcomponent has been tiled.
Within a subcomponent, we select the next grid point to tile based on the number of remaining placements it has. It is not uncommon for there to be a grid tile with only a single option, and in those cases it's important to place that piece immediately, since it likely decreases the size of the search space by eliminating other placements (and potentially causing more grid points to have only one option).
When there's not a grid point with a single placement, we still prioritize grid points with the fewest number of placements, since these are intuitively the most "difficult" parts of the grid to tile, which makes them more likely to be rendered untileable by other placements.
For example, in this grid the untiled grid point in the bottom left corner has only one possible placement with trominoes; tiling it lets us eliminate seven other placements that conflict with the forced placement.
The last heuristic is a little crude but still useful. The motivation is that being able to split the grid into subcomponents is useful (since they can be analyzed independently), and so if we have the opportunity to split it by placing a piece, we should do so. So the precise rule is that, when there aren't any single-placement grid points (which are higher priority), and there is a single grid point that splits the grid (in graph theory terms, a vertex whose removal disconnects the graph), then we search placements for that grid point next.
For example, there are a couple grid points that disconnect the bottom right portion of this grid from the rest of it; either of these could be chosen for the next search step.
This approach could probably be generalized to something that tends to select grid points that make the grid easier to split, rather than only acting on this principle when it can be achieved in a single step. However, this would likely be somewhat in tension with the prioritization of the most restricted grid points, so I'm not sure how it would work out.
Since the polyomino tiling problem is NP-complete, it's not surprising that there's a variety of scenarios where the algorithm gets stuck. Some of these are artifacts of the particular algorithm, and it could conceivably be tweaked to handle those special cases. But I think the algorithm as it is is a decent balance of broad efficacy and simplicity.
The randomness in the algorithm (the ordering of placements for a particular grid point, and to a limited extent the prioritization of grid points) means that sometimes more progress can be made simply by restarting the search.
There are a variety of directions the algorithm could be tweaked; I mentioned one earlier about working more aggressively to split the grid.
Another idea is to optimize the order that placements for a particular grid point are tried. Perhaps placements could be prioritized based on the number of options available for the other grid points involved.
So to tile an arbitrary grid with an arbitrary set of polyominoes, we:
In hindsight, I believe the initial step of transforming to a graph could be retargeted to transforming to an instance of the SAT problem, in which case a generic SAT solver could be used for the rest of the algorithm. It would be interesting to learn which of the heuristics in my algorithm have analogs in general SAT solvers, and which are particular to tiling problems.
I've been working on some static visualizations of hash functions, starting with SHA1 and MD5 (images here and here, respectively). The two sample images show the process of computing the respective hash functions on the input "denny" (with ASCII encoding -- i.e., the input in hex is "64656E6E79"). A portion of the MD5 image is included below:
Notes about these two particular algorithms and their visualizations:
I'm hoping this sort of diagram can
Several things I think are worth exploring:
General feedback and suggestions welcome.
Big-endian looks more natural to me, so I display the hex value for words with the higher-order bytes first -- e.g., the word corresponding to the integer 100 is shown as "00000064". This means that internally both algorithms look the same (in particular the bit-rotations and addition-mod-32 operate the same way), but for MD5 the input and output words will have their bytes ordered the opposite of what you would expect. For the input "denny" where the first word comes from the substring "denn", the first word in SHA1 is "64656E6E" while the first word in MD5 is "6E6E6564". You'll also notice this if you try to compare the MD5 output shown with the value given by any normal MD5 utility.
There's a quasi-paradox about interesting numbers which says that all the natural numbers must be interesting. If they weren't, then there would be a smallest uninteresting number, which is a very interesting thing to be. So that's immediately a contradiction and we're forced to conclude that all the natural numbers are interesting. This is just a bit of fun with self-referential definitions, but it suggests an interesting exercise with trying to formally define "interesting".
We can start with some reasonable attributes that qualify a number as interesting, and use them to find the first handful of uninteresting numbers. For example, we might start by saying that 0 and 1 are interesting for being the additive and multiplicative identities (respectively), and that all primes are interesting. By this definition the first ten uninteresting numbers are:
4, 6, 8, 9, 10, 12, 14, 15, 16, 18, ...
Then we can look at the first few and try to find something interesting about them that we can add to our list of attributes. For example 4 is a square, or more generally it is nk where k > 1. Removing those sorts of numbers gives us:
6, 10, 12, 14, 15, 18, 20, 21, 22, 24, ...
6 is 2×3, which makes it a semiprime. We may as well say semiprimes are interesting, which also takes out 10 and 14 and 15 and 21 and 22, leaving us with:
12, 18, 20, 24, 28, 30, 40, 42, 44, 45, ...
12 is 3×4, which makes it the product of at least two consecutive numbers, so that's mildly interesting as well. This also captures 20 and 24 and 30 and 42, so now we have:
18, 28, 40, 44, 45, 48, 50, 52, 54, 63, ...
18 is right between a pair of twin primes, and 28 is a perfect number, so let's grab both of those:
40, 44, 45, 48, 50, 52, 54, 63, 66, 68, ...
These numbers are getting rather boring, so let's stop there. But conceivably you could take this arbitrarily far. As long as the set of numbers with any given attribute have an asymptotic density of 0 (i.e., become arbitrarily rare as you go up), there will always be more uninteresting numbers to handle. And as long as the attributes you pick are efficiently computable, the computer should always be able to find them for you.
The Quantum Circuit model of computation is the primary model used by theoretical computer scientists to study quantum computers (in the same way that the Turing Machine is used to study classical computers). It's definitely important if you're studying computation, but can also be useful for understanding aspects of quantum mechanics more generally.
My experience was that quantum circuits were mostly dealt with algebraically. I had a hard time visualizing the quantum states I was dealing with, partly because they exist in a high-dimensional space and partly because they involve complex numbers. I've attempted to tame them a little bit by building a quantum circuit simulator, using a circular display for the complex amplitudes. For example, these are the representations for the numbers 1, 0, and (i-1)/2:
Combine that with the ability to move time forward and backward through the circuit, and the result is something that for me goes a lot further toward conveying the effects of the various quantum gates than the standard algebraic approach.
I've posted it here. It includes a circuit editor, permalinks for created circuits, and transition diagrams for individual gates. Unfortunately I don't have time at the moment for a full explanation of quantum computation, but hopefully for those unfamiliar with it the simulator can be a helpful supplement to other explanations.
Considering the strong correlation between my 20-month blog silence and the amount of time I've spent playing with baby toys in the interim, it seems appropriate to start back with a post about those baby toys.
To start with there are a whole slew of these broken colored rings lying around, presumably intended to be linked together in a chain.
I noticed that the oblong shape allows them to be assembled into Borromean rings.
Then there are all of these stereotypical wooden cubes.
It's probably pretty common to probe the limits of top-heavy structures. It occurred to me to try a single block with two blocks atop it, three blocks on the next layer, and so on as high as possible.
Each successive step seemed equally dubious.
After eight layers I ran out of the standard style of blocks and had to fall back on the alternate.
I tried adding an eleventh layer but everything started falling over at that point.
An unsuspecting friend recently reported that her five-year-old son is a fan of big numbers in general, and one number in particular. His favorite number is One Googol and One Hundred (or 10100 + 100), which I will here on out refer to as the number “Samuel” to keep my sentences to a reasonable length. He wanted to be able to count to Samuel in the same way that you might count to other numbers by skipping a regular amount, such as counting to 1000 by tens.
As I started thinking through the various options, I realized that the answer depended on the prime factorization of Samuel, since any number by which you could count to Samuel would have to be a divisor of Samuel. One obvious divisor of Samuel is 100. Counting to Samuel by 100’s is certainly not feasible, but counting to Samuel by “Samuel divided by one hundred”s (or 1098 + 1 or one hundred untrigintillion and one) is, and that was my first suggestion. But then I wondered if the full factorization of Samuel would give other options. Factoring 100-digit numbers isn’t something that I know how to get my computer to do, but fortunately Wolfram Alpha was up to the task.
The result revealed that there are actually quite a handful of options for counting to Samuel in a reasonable amount of time, so I thought I would present them here, just for the joy of looking at really big numbers written out in words. These are all the numbers you can count to Samuel by in fewer than a thousand steps, sorted by and labeled by number of steps:
Once again Algebraic Topology compelled me to write code. This time it was the Ham Sandwich Theorem, when I realized that it wouldn’t be too hard to demonstrate the two dimensional version of the theorem with another interactive doohickey.
The interpretation from which the Ham Sandwich Theorem gets its name says that if you have two slices of bread and a slice of ham between them, then no matter what their respective shapes or volumes or how they are positioned relative to each other, it’s always possible to make a single straight slice through the sandwich resulting in all three pieces being exactly bisected.
The Ham Sandwich Theorem applies to any N solids in N-dimensional space, so the 2D version says that any two sets of geometric shapes can be simultaneously bisected by a line. I said “two sets” rather than “two shapes” because the theorem actually doesn’t rely on the shapes even being connected. So we can talk about a collection of red and blue circles, as in the image below:
No matter how the circles are arranged, and no matter how many of them there are, there will always be at least one line that bisects both sets by area simultaneously. The interactive version lets you add, remove, and rearrange the circles.
I’ve been spending some of my free time the last week and a half watching Norman Wildberger’s Lectures on Algebraic Topology. One of the things I’ve learned is that an m-by-n rectangle with integer dimensions can be partitioned into 2×m×n triangles with area ½ and corners at integral points. This fact by itself is rather obvious, given the most regular partitioning shown below:
The part that surprised me is that the sides of the triangles can be arbitrarily long, allowing a variety of different shapes, and the whole thing still works out. Here’s another way to partition the 5-by-5 rectangle:
The fact that they all have the same area is a consequence of Pick’s Theorem. I wanted to see what sorts of partitionings you get by starting with a bare rectangle and adding lines at random. And that’s exactly where the image above came from.
Here are a couple more, just for fun.
I’ve been doing some academic research lately that involves computing the probability that a network that grows by adding links in a particular way will end up with certain properties. I went about this by generating all graphs up to a particular order, and so I used unlabeled graphs, which seemed good enough and meant I had far fewer graphs to deal with.
Eventually I decided that it would be more realistic to at least pretend that the computation was being done on labeled graphs (by making sure that I got the same answer either way). This led me to a discovery that wasn’t too surprising by itself, but I was surprised that I hadn’t thought of it earlier: there are (unlabeled) graphs for which adding two completely different edges will produce the same graph. For illustration I found the smallest example:
The first thing to notice about this graph is that it’s completely asymmetric: The pink and orange vertices have the distinction of being the only degree-4 and degree-2 vertices respectively. The blue vertex is distinguished as being the only vertex adjacent to both the pink and orange. The green vertex is the only degree-1 vertex adjacent to the pink, and the yellow and blue vertices are clearly distinct from each other as they have different degrees. So there is no symmetry in this graph whatsoever, which means that any edge we could conceivably add to it would be “different” from any other edge we could add.
So there are two different edges we can add to this graph and end up with graphs that are isomorphic to each other. Specifically, by adding either edge we will end up with this graph:
Another way to say it is that our original colored graph is a subgraph of this graph in two different ways. This can be easily illustrated by repositioning the vertices so they match the supergraph in two different ways:
You should be able to confirm visually that these two are the same as the original graph, and that they can each be transformed into the supergraph by adding a single edge, which is drawn dashed.
So this is the smallest pair of graphs that has this property, and in fact the only example on six vertices. I’m not sure how common they are. It is not a straightforward thing to google.
Another cool thing is that, apparently just by coincidence, the smaller graph and the larger graph are complements of each other. So if we take the original graph and reverse all the links like so:
we end up with the same graph that we got by adding an edge: