Mahoney Map

AMG: A Mahoney map is like a Karnaugh map [L1 ] with a more straightforward generation process. Either can be used for simplifying logical expressions, particularly for making a simplified logical expression given a truth table.

Someday I will put code here, but for now I will just demonstrate the process of constructing a Mahoney map.

Let's start with the simple single-variable case. I will call this variable A. Make a table like this:

0 1

The numbers 0 and 1 represent /A and A, respectively, where /A is pronounced "not-A". (Man, I wish I could type an overbar!)

Now let's add a second variable B. Mirror the table vertically and add a new digit to the left-hand side, which will represent /B and B. Put 0 on the bottom row and 1 on the top row. 00: /A/B, 01: /AB, 10: A/B, 11: AB.

10 11
00 01

A third variable C can be added by mirroring horizontally and adding a third digit to the left-hand side. Put 0 on the left two columns and 1 on the right two columns.

010 011 111 110
000 001 101 100

Now a fourth variable D! Mirror vertically and add a fourth digit on the left-hand side. Put 0 on the bottom two rows and 1 on the top two rows.

1000 1001 1101 1100
1010 1011 1111 1110
0010 0011 0111 0110
0000 0001 0101 0100

Now that things are out of the realm of the trivial, let's just grab a cell at random and explain its meaning. Take 1011. What that really means is "D and (not C) and B and A", which can also be written "D/CBA". To actually use this to convert a truth table to a logical expression, circle every cell that corresponds to a "true" entry in the truth table. If the function of variables A, B, C, and D is true when "(D and (not C) and B and A)" is true, circle 1011. Depending on the truth table, there will hopefully be several adjacent circled entries, forming rows, columns, or rectangles of width and/or height 2 or 4 (but not 3; it's not a power of two). Circle these. The table wraps around the edges, so the grouping circles can wrap too. Just like how each individual cell corresponds to a single expression (such as D/CBA = 1011), the larger grouping circles correspond to expressions, also known as minterms. For example, if 1000, 1001, 1010, and 1011 are circled, that corresponds to D/C = 10xx, where "x" means "I don't care". Let's say there are large circles: 1000+1001+1010+1011 and 0111+0101. This corresponds to the expression "((D and (not C)) or ((not D) and C and A))", also written "D/C+/DCA". In order of decreasing precedence, a prefixed "/" is "not", juxtaposition is "and", and "+" is "or".

We can keep going. Just alternate between mirroring horizontally and vertically. However, after four variables things start to get tricky. Why is that? First let me explain why the above stuff isn't tricky. :^) Since the tables wrap*, each cell is adjacent to N other cells, where N is the number of variables. Adjacent cells differ in only one variable, which in my notation is one digit (or bit). But since this Wiki only allows me to create two-dimensional tables (lousy stupid wiki!), I can't actually draw a table where a cell visibly has five neighbors. So you will have to use your imagination. Pretend that the right four columns in this next table are stacked on top of the left four columns. Pretend that you have this page printed out and folded with a vertical crease down the middle, so that the rightmost column is touching the leftmost column.

* Actually they don't wrap, but the effect is same. Cells on opposite edges are adjacent because the table is "folded" so that they touch. The problem with thinking in terms of wrapping is that it may cause you to fail to notice some adjacencies when working with five or more variables.

01000 01001 01101 01100 11100 11101 11001 11000
01010 01011 01111 01110 11110 11111 11011 11010
00010 00011 00111 00110 10110 10111 10011 10010
00000 00001 00101 00100 10100 10101 10001 10000

And again!

100000 100001 100101 100100 110100 110101 110001 110000
100010 100011 100111 100110 110110 110111 110011 110010
101010 101011 101111 101110 111110 111111 111011 111010
101000 101001 101101 101100 111100 111101 111001 111000
001000 001001 001101 001100 011100 011101 011001 011000
001010 001011 001111 001110 011110 011111 011011 011010
000010 000011 000111 000110 010110 010111 010011 010010
000000 000001 000101 000100 010100 010101 010001 010000

Now not only are the right four columns adjacent to the left four columns, but the top four rows are adjacent to the bottom four rows. The consequence of this is that not only are you grouping adjacent cells into rows and columns, but also into stacks. For example, the second and seventh cells on the top row are adjacent; they differ only in the second bit (E). They can be grouped. But it's not visually obvious, and now that there are six variables, it's getting hard to imagine in terms of folding. This is where the notation stops being usable, and it makes sense to pick a different approach to simplifying your expression.

But! With a nifty Tk program I could make the table highlight adjacent cells to help overcome the limitations of the notation. Maybe someday I will write one!


Lars H: The natural domain for an n-variable function is an n-dimensional hypercube, but working directly with that is difficult already for n=3. Karnaugh maps compresses two dimensions of the hypercube into one dimension of the table by using the observation that a 2-dimensional hypercube — a square — is also a cycle of length 4, so by changing

  1-2
  | |      to    -1-2-3-4-
  4-3

in the drawing, one can depict an n-dimensional hypercube in n/2 dimensions. That way, things work well all the way up to 4 variables. In order to get further, one needs to employ an additional trick.

AMG: In a Mahoney map, this kind of compression happens with every other pair of variables. Take the third table:

010 011 111 110
000 001 101 100

Replace the second variable (B) with don't-care:

0x0 0x1 1x1 1x0
0x0 0x1 1x1 1x0

The two rows are now the same, so drop one. Also drop the don't-care term and understand that the bit assignment is now CA:

00 01 11 10

Now compare with the second table, mentally substituting C for B as the first bit and flipping vertically to match your top-to-bottom convention:

00 01
10 11

See the resemblance? In the above two tables, replace 00 with 1, 01 with 2, 11 with 3, and 10 with 4 to get your two tables.

Why the vertical flip? I used bottom-to-top so that all-bits-zero would be in the lower-left corner, similar to the origin in the first quadrant of the Cartesian plane. But you used top-to-bottom, presumably to match conventional reading order. It doesn't matter which is used.