inconvergent

A few days ago I wrote about a possible strategy for getting more interesting generative results. Basically I suggested that you need a way to introduce errors into your system, as well as a way for the system to correct itself.

Below I will illustrate one possible interpretation of this using my experimental generative system called snek.

SNEK is a data structure for dealing with geometry (vertices and edges), combined with a method for generating and applying alterations to that structure.

By design these alterations do not depend on the current state of the snek instance. At the end of a context the alterations are applied to the instance according to a set of rules designed to keep the data structure intact. Specifically this means that nonsensical alterations are discarded. This feature is important, as we will see now.

Consider the following example, which is a part of the rules that make up the "Drift" algorithm from @sandpaintbot.

; context start
(snek:with (snk)
  ; pick a random vertex, v, and create
  ; a new edge between v and xy
  (snek:with-rnd-vert (snk v)
    ; xy placed relative to position of v
    (snek:append-edge? v xy :rel t))
  (snek:with-rnd-vert (snk v)
    (snek:with-rnd-vert (snk w)
      ; create an edge between arbitrary
      ; vertices v and w
      (snek:join-verts? w v)))))
  ; context end
  ; alterations have been applied

If the (snek:join-verts? ...) alteration happens to be created with w equal to v then that alteration won't make sense. In this case it will be gracefully discarded.

Drift
A variation of "Drift" by @sandpaintbot on dark background.

Since the alterations are independent of the snek instance, and since the snek structure will gracefully ignore faulty alterations, there is nothing stopping us from arbitrarily changing the alterations before they are applied.

It is not a stretch to imagine this as mutating the alterations. For instance we can randomly mutate an alteration by a given probability. In code it can look like the example below. Here mut contains the existing mutations as well as the mutation probability.

; context start
(snek:with (snk)
  ; mutate alterations
  (snek:mutate (mut)
    ; remaining code is exactly as above
    (snek:with-rnd-vert (snk v)
      (snek:append-edge? v xy :rel t))
    (snek:with-rnd-vert (snk v)
      (snek:with-rnd-vert (snk w)
        (snek:join-verts? w v))))))

The mutations can be controlled by whatever rules you can come up with. For instance, you can randomly change the index (or indices) affected by an alteration. In the above example this will probably not have much of a visible effect—we are already selecting indices at random. However, if we were to say that all mutated alterations would have (one of) their affected indices replaced by 0, then we would certainly see something.

In the below image we can see an example of a slightly more complex mutation rule. I will leave it up to those who are interested to speculate on what is going on.

Mutated Drift
A mutated variation of the same algorithm from @sandpaintbot.

All things considered, there is no real reason why this "error process" can't be incorporated into the original algorithm. As such, what we have really done is create a new algorithm by combining the original with an error process.

This somewhat basic example might seem a little contrived, but I do believe there is a value in being able to apply these kinds of mutations. And as such, I will explore it further.


  1. I recommend reading the post before continuing.
  2. Alterations are immutable, so technically we do not change alterations, we create new ones.
  3. It is my opinion that "the algorithm" must be the combination of all the components that created a given result.