Book Review: The Apollo Guidance Computer: Architecture and Operation

Like most programmers I have spend my entire career working with general purpose “do anything” computers. Every machine I’ve touched from $3 micro-controllers to $3000 servers has been based around the same basic design: A processor that supports universal math and logic combined with a chunk of flexible memory that can hold whatever code and data you want to put in it.

Need your universal computer to do something new? Just load different code into the memory and suddenly your spreadsheet machine is a web server or a fluid simulator.

But what if your computer only ever had to do one very specific thing? What if you your only goal was to get a spaceship to the moon with near-perfect reliability? And what if you had an entire team of engineers willing to build you a custom computer from scratch? How would your one-thing-only computer be different from a generic off the shelf processor and memory combo?

That is the question answered by The Apollo Guidance Computer: Architecture and Operation, a four hundred page book dedicated entirely to the design and programming of the computer that sent the first man to the moon.

As you’ve probably guessed the aspect of the book I found most interesting was how profoundly weird the guidance computer was compared to the generic mass produced machines we use for 99.999% of computation.

For example, normal computers have flexible memory that lets the user program whatever they want. After all, the manufacturer has no idea whether their customers are going to be building stop lights, washing machines or air conditioners. But the guidance computer was only ever going to be used for flight calculations. It didn’t need to be flexible. But what it did need to be was reliable in the harsh, slightly radioactive void of space. So instead of programmable memory it stored it’s code in the form of wires woven around and through hundreds and hundreds of tiny magnets, creating an extremely reliable form of read only memory.

Or what about mathematics. In normal computers a lot of silicon and logic is spent on making sure processors can gracefully and accurately work with numbers both extremely large and extremely small. But implementing that sort of mathematical power in a machine with as many constraints as the guidance computer would have been difficult.

The solution? To not solve the problem. After carefully studying the physics problems they needed to solve it became apparent that they never needed to multiply a very very large number by a very very small number. That meant they didn’t need a full range of flexible numbers. They could instead save processor complexity and instruction space by just doing relatively simple math and leaving it up to the programmers to just remember which equations were working in dozens of kilometers instead of fractions of seconds. Magnitude is relative and as long as the computer fires the thrusters at the right moment it doesn’t really matter whether that it was doing 10 +3 instead of 10,000,000 + 3,000,000.

These design decisions, and hundreds of other, were meticulously described over the course of hundreds of pages of thorough technical description. Technical enough that following along might be difficult if you’ve never taken a class or done some readings on the fundamentals of computer architecture.

So overall a very niche book, but for someone with the right background it’s a true pleasure to read. It’s always interesting learning about the technical accomplishments of NASA and thinking about the design decisions of a very specific computer really helps highlight that modern computer architecture, while very powerful, isn’t actually mandatory or universal.

Let’s Program Some Weird Markov Chains Part 7: Overly Abstract Algorithmic Art

For our final weird Markov Chain experiment we will be working with images. This is the definitely least likely project to result in something interesting. After all, Markove Chains are designed to model and simulate sets of linear data where the next item in the chain is heavily dependent on the items that came before. How are we even going to apply that idea to a 2D image?

I suppose there are a lot of ways you could handle this but what I settled on revolves around the question “What pixels tend to group together?” or more specifically “If I have a 2 x 2 square of pixels and I know what the top left, top right, and lower left pixels are, can I predict what the lower right pixel should be?”

That’s enough to turn an image into a Markov Model. We just loop through every 2 x 2 square in the entire image, use the first three pixels as a key and then keep a count of how often different colors show up for the fourth and final pixel.

We can then use this model to create new images by starting with some randomized corner pixels and then filling in the gap by randomly choosing a pixel based on our model. Adding that one extra pixel creates a new corner with a new gap that we can once again probabilistically paint until we have an entire image that hopefully somewhat reflects the original we based our model on.

What should that model be though? Obviously it needs to be something old enough to be public domain. So how about… this?

A low resolution black and white version of the Mona Lisa

The low resolution might catch you by surprise, but it’s an important pre-processing step. There are so many different colors of pixels that trying to build a Markov Model of a full color picture would probably just produce hundreds of thousands of unique keys each with one unique next pixel. For a more meaningful model we need something a little more generic, something that captures general patterns instead of hyper specific patterns. We achieve this by flattening the colors of the original to a limited number of shades of grey.

We can then turn this into a model and use that model to generate new images with some fairly simple code:

from PIL import Image
from random import randint

def getRandomFromProbabilityList(probabilityList):
    total = 0
    for shade, count in probabilityList.items():
        total = total + count
    chosenProbability = randint(0, total -1)
    for shade, count in probabilityList.items():
        if(chosenProbability < count):
            return shade
        chosenProbability = chosenProbability - count

shadeMarkovChain = {}
        
filename = "images\mona_lisa.jpg"
with Image.open(filename) as image: 
    width, height = image.size
    resizeRatio = 64/width
    newWidth = ((int)(width * resizeRatio))
    newHeight = ((int)(height*resizeRatio))
    image = image.resize((newWidth, newHeight))
    image = image.convert('LA')
    image.save("images\output.png")
    
    for x in range(0,newWidth):
        for y in range(0, newHeight):
            oldPixel = image.getpixel((x, y))[0]
            newPixel = oldPixel - oldPixel % 128
            topLeft = -1
            top = -1
            left = -1
            if(x > 0):
                tempPixel = image.getpixel((x-1, y))[0]
                left = tempPixel - tempPixel %128
            if(y > 0):
                tempPixel = image.getpixel((x, y-1))[0]
                top = tempPixel - tempPixel %128
              
            if(x > 0 and y > 0):
                tempPixel = image.getpixel((x-1, y-1))[0]
                topLeft = tempPixel - tempPixel %128
                
            if (topLeft, top, left) not in shadeMarkovChain:
                shadeMarkovChain[(topLeft, top, left)] = {}
            if newPixel not in shadeMarkovChain[(topLeft, top, left)]:
                shadeMarkovChain[(topLeft, top, left)][newPixel] = 1
            else:
                shadeMarkovChain[(topLeft, top, left)][newPixel] += 1
            
            
print(shadeMarkovChain)

print(getRandomFromProbabilityList(shadeMarkovChain[(-1,-1,-1)]))


img = Image.new('LA', (newWidth, newHeight))
for x in range(0,newWidth):
    for y in range(0, newHeight):
        topLeft = -1
        top = -1
        left = -1
        if(x > 0):
            left = img.getpixel((x-1, y))[0]

        if(y > 0):
            top = img.getpixel((x, y-1))[0]
          
        if(x > 0 and y > 0):
            topLeft = img.getpixel((x-1, y-1))[0]

        if(topLeft, top, left) in shadeMarkovChain:
            img.putpixel((x,y),(getRandomFromProbabilityList(shadeMarkovChain[(topLeft, top, left)]), 255))
        else:
            img.putpixel((x,y),(getRandomFromProbabilityList(shadeMarkovChain[(-1, -1, -1)]),255))
img.save("images\markov.png")


The results aren’t exactly that impressive though.

I played around with the program a bit to try and get different results. I pre processed the image less, I pre processed it more, I generated smaller images, I generated larger images, i looked at more neighboring pixels.

Nothing really helped. We wind up with this sort of diagonal storm cloud pattern every time.

Which honestly makes sense. As we mentioned earlier images aren’t really linear. You can’t draw a person just by looking at a few dozen pixels at a time. You need context; you need to know where the entire face is to draw the eyes. You need posture to put the hands in the right place. And while there are certainly generative models that can track this sort of data they are much more complex than the little bit of toy code we have built here.

But I promised you a weird Markov chain and I believe I have delivered.

 

Let’s Program Some Weird Markov Chains Part 6: Listening To Music Reeeeaaaally Closely

Last time we learned how to represent musical chords with a Markov Chain as well as how to use them to programmatically write music. Unfortunately the music sounds really really bad because I have no idea what chords are supposed to sound like or what chords are supposed to follow each other.

That’s why this time we’re going to focus on reading files instead of writing them. That way our program can learn from actual musicians what notes and chords should follow each other. Because I’m certainly never going to figure that stuff out.

Of course, my lack of musical knowledge also limits my ability to write code that analyzes existing music but if I can get even close we should see some interesting results. And then all you out in the audience can do me one better by learning enough about real music and mp3 to write something better than the upcoming prototype.

Let’s start by importing some libraries, writing a couple helper functions and setting up a variable to hold our musical markov chain data.

from mido import Message, MidiFile, MidiTrack
from random import randint
       
def getRandomFromProbabilityList(probabilityList):
    total = 0
    for word, count in probabilityList.items():
        total = total + count
    chosenProbability = randint(0, total -1)
    for word, count in probabilityList.items():
        if(chosenProbability < count):
            return word
        chosenProbability = chosenProbability - count
        
def addChordToTrack(track, musicalTuple):
    track.append(Message('note_on', note=musicalTuple[0], velocity=64, time=500))
    for subNote in musicalTuple[1:]:
        track.append(Message('note_on', note=subNote, velocity=64, time=0))
 
markovChain = {}

As you can see we’ve got our classic function for pulling an option out of a list of weighted options as well as a simple function for taking a set of musical notes and adding them to a midi track as a half second long chord.

Next we need a lot of raw music. I found a nice collection of publicly available midis of public domain classical music. I downloaded several from the piano section, put them in a folder named “piano” and then threw their file names into a big list in the code.

inputFiles = ['piano/alice.mid',
                'piano/alkan-op31-1.mid',
                'piano/andre-sonatine.mid',
                'piano/anna-magdalena-20a.mid',
                'piano/bach-invention-01.mid',
                'piano/bach-invention-07.mid',
                'piano/bach-invention-13.mid',
                'piano/bach-invention-14.mid',
                'piano/bluemtns.mid',
                'piano/bwv_462.mid',
                'piano/BWV-117a.mid',
                'piano/bwv-259.mid',
                'piano/bwv347.mid',
                'piano/BWV-511.mid',
                'piano/cpe-bach-rondo.mid',
                'piano/giselle.mid',
                'piano/lanative.mid',
                'piano/lesgraces.mid',
                'piano/Rumores_de_la-caleta.mid',
                'piano/swallows.mid',]

And then we can loop through all the files, use our midi library to loop through all the chords in the piece and build up a one deep markov chain from them.

Getting the chords from a midi file is a little bit more difficult than getting the words from a book. As we saw last time a midi chord is created by using “time = 0” to make several notes play at the same time as the note before them. So to build a cord we loop through “note_on” type events. If the time is 0 we add it to the current chordTemp item. If the time isn’t 0 it must be the start of a new chord so we push the current chord onto our main list of chords, clear it out, add the new note and then start looping again.

And of course, once we have our big list of every chord and the order it happened in we can build a one deep markov chain just the same we did for words.

for inputFile in inputFiles:
 
    mid = MidiFile(inputFile)

    chordList = []
    chordTemp = []

    for msg in mid:
        info = msg.dict()
        if(info['type'] == 'note_on'):
            if(msg.time != 0):
                chordList.append(tuple(sorted(set(chordTemp))))
                chordTemp=[]
            chordTemp.append(msg.note)
                


    for i in range(len(chordList) - 1):
        if chordList[i] not in markovChain:
            markovChain[chordList[i]] = {}
        if chordList[i+1] not in markovChain[chordList[i]]:
            markovChain[chordList[i]][chordList[i+1]] = 1
        else:
            markovChain[chordList[i]][chordList[i+1]] += 1

The last thing we need is a list of starting points for generating new songs. Once again this is a little tricky. A book has thousands upon thousands of senteces each with an easy to find first word. But music? Much harder to pick out the start of a musical phrase within a midi.

So we’ll just take a shortcut and make a list of every chord in our markov chain that links to at least one other chord. And then we’ll grab one at random to be the start of our song.

possibleStartingChords = list(markovChain.keys())
startingChord = possibleStartingChords[randint(0,len(possibleStartingChords)-1)]       

And from there building a new song is easy peasy. Only difference from working with sentences is that our goal here is to produce a song of a certain length instead of just running until we reach a data point with no next possible value. So we loop through 480 chords, using the markov chain whenever we can and choosing a new random note to continue with everytime we hit a dead end.

newMid = MidiFile()
track = MidiTrack()
newMid.tracks.append(track)

track.append(Message('program_change', program=0, time=0))
            
currentChord = startingChord

for x in range(480):
    if(currentChord in markovChain):
        currentChord = getRandomFromProbabilityList(markovChain[currentChord])
    else:
       currentChord = possibleStartingChords[randint(0,len(possibleStartingChords)-1)] 
    addChordToTrack(track, currentChord)

newMid.save('markov_song.mid')

Run it a few times and give the results a listen. What do you think?

Awful isn’t it?

But awful in a way that at times sounds almost like music.

I think the main problem is that several of the chords don’t sound very good. This is probably because our naïve chord collector doesn’t distinguish between different instruments. Nor does it have any way to distinguish between treble and bass cleft chords that happen to be being played at the same time.

This is going to make me sound pretty lazy, but I’m happy leaving this experiment where it is. It’s not a great program for generating music… but as a proof of concept program written by someone who barely understands music it’s pretty good. Or at least, it’s good enough that another programmer who actually understands music could probably take it, improve and make something cool.

So we’ll call this a sort-of success and next time we’ll move on to our final and least likely to succeed experiment: Writing a Markov chain system for painting pictures..