# teaching machines

## Power Set of Venn Diagrams

In our introductory programming class, I try to reframe abstract logic into concrete spatial thinking. One way I do this is through the game Trux Falsy. Another way is through Venn diagrams. I want students to see how the AND operator creates an intersection, how the OR operator creates a union, and how DeMorgan’s Law flips an expression.

To support a visualization exercise in lecture today, I wrote this short Twoville program that generates all possible Venn diagrams for a 2-term boolean expression:



// Parameters
width = 150
height = 100
trueColor = [1, 0.9, 0.4]
falseColor = [0.55, ~, ~]
separationFactor = 0.15

// Derived
separation = separationFactor * width
intersectionAngle = asin(intersectionY / radius) * 2
cellDimensions = [width, height] + padding

with viewport
corner = [0, 0]
size = [width * 4 + padding.x * 3, (height + padding.y) * 4]

to venn(aMinusB, bMinusA, both, neither, column, row)
origin = cellDimensions * [column, row] + [0, padding.y]
centroid = origin + [width, height] * 0.5
left = centroid - [separation, 0]
right = centroid + [separation, 0]

// neither
with rectangle()
corner = origin
size = [width, height]
color = if neither then trueColor else falseColor

// aMinusB
with circle()
center = left
color = if aMinusB then trueColor else falseColor
stroke.size = 1
stroke.color = :black

// bMinusA
with circle()
center = right
color = if bMinusA then trueColor else falseColor
stroke.size = 1
stroke.color = :black

// both
with path()
stroke.size = 1
stroke.color = :black
closed = false
color = if both then trueColor else falseColor
with jump()
position = centroid - [0, intersectionY]
with arc()
center = left
degrees = intersectionAngle
with arc()
center = right
degrees = intersectionAngle

for i to 16
venn(i, i, i, i, i % 4, i / 4)

var twovilleDiv = jQuery('#twoville_demo');
twovilleDiv.closest('pre').replaceWith(twovilleDiv);
document.getElementById('twoville_form_demo').submit();

// Parameters
width = 150
height = 100
trueColor = [1, 0.9, 0.4]
falseColor = [0.55, ~, ~]
separationFactor = 0.15

// Derived
separation = separationFactor * width
intersectionAngle = asin(intersectionY / radius) * 2
cellDimensions = [width, height] + padding

with viewport
corner = [0, 0]
size = [width * 4 + padding.x * 3, (height + padding.y) * 4]

to venn(aMinusB, bMinusA, both, neither, column, row)
origin = cellDimensions * [column, row] + [0, padding.y]
centroid = origin + [width, height] * 0.5
left = centroid - [separation, 0]
right = centroid + [separation, 0]

// neither
with rectangle()
corner = origin
size = [width, height]
color = if neither then trueColor else falseColor

// aMinusB
with circle()
center = left
color = if aMinusB then trueColor else falseColor
stroke.size = 1
stroke.color = :black

// bMinusA
with circle()
center = right
color = if bMinusA then trueColor else falseColor
stroke.size = 1
stroke.color = :black

// both
with path()
stroke.size = 1
stroke.color = :black
closed = false
color = if both then trueColor else falseColor
with jump()
position = centroid - [0, intersectionY]
with arc()
center = left
degrees = intersectionAngle
with arc()
center = right
degrees = intersectionAngle

for i to 16
venn(i, i, i, i, i % 4, i / 4)



There are four regions in a two-term Venn diagram: the aMinusB region, the bMinusA region, the both region, and the neither region. In the code above, the venn function accepts a bit for each of these four regions. Each is colored according to its bit. With 4 regions, each either selected or unselected, we have $2^4 = 16$ possible diagrams.

When I first coded it up, I manually called the venn function with all possible bit configurations for these four regions:

// the parameters: aMinusB, bMinusA, both, neither, column, row

venn(0, 0, 0, 0, 0, 0)
venn(1, 0, 0, 0, 1, 0)
venn(0, 1, 0, 0, 2, 0)
venn(1, 1, 0, 0, 3, 0)

venn(0, 0, 1, 0, 0, 1)
venn(1, 0, 1, 0, 1, 1)
venn(0, 1, 1, 0, 2, 1)
venn(1, 1, 1, 0, 3, 1)

venn(0, 0, 0, 1, 0, 2)
venn(1, 0, 0, 1, 1, 2)
venn(0, 1, 0, 1, 2, 2)
venn(1, 1, 0, 1, 3, 2)

venn(0, 0, 1, 1, 0, 3)
venn(1, 0, 1, 1, 1, 3)
venn(0, 1, 1, 1, 2, 3)
venn(1, 1, 1, 1, 3, 3)

During a refactoring, I decided to simplify this code by iterating through [0, 15] and extracting out the individual bits from the iterator. Twoville doesn’t yet support the shifting and masking operators, which are the usual means of extracting bits, and I was just about to add them. But then I thought, “Why not add the subscript operator to integers?” bitfield would yield bit 0, bitfield would yield bit 1, and so on.

Four lines of code later, I was able to condense my calls down to this:

for i to 16
venn(i, i, i, i, i % 4, i / 4)