I’m working on some prototypes for a (hopefully) upcoming computational project which concerns the growth rings of trees, and simulating them in code – their accretion, the environmental factors which influence their shape, colour, distribution, width and so on – is proving to be an interesting little challenge. I’ve been using Processing (which is essentially a breed of Java) as I’ve been getting familiar with it elsewhere recently; burrowing my way through Daniel Shiffman’s excellent Nature of Code, a book about the simulation of natural processes. More than anything, Processing makes it very, very simple to draw a circle, and – fundamentally – mess about with it in consistent ways.
Shiffman’s book mostly concerns itself with slightly more elemental components of the natural world: gravity, entropy, friction and oscillation, which in simulation get frustratingly ornery. When I have thirty ellipse()s flying about according to equations that I didn’t understand at GCSE, and still clearly don’t, the margin for error is quite wide, and bugs difficult to reverse-engineer. In comparison, the first pass of generating a cross-section of a tree was reassuringly post-hoc: an array of an arbitary number of concentric circles drawn to the screen, each a standard factor larger than the previous; nothing to sustain beyond the first 30 step()s or so.
The next pass was to add perhaps the most obvious variable to what was, at the moment, little more than an icon: a variable width of ring. Most of us probably remember an early lesson at school where we were taught to read a tree stump; to accordion out thirty or forty or three hundred years of history from all that close-packed subtext. First we were taught to count the rings, and then how the width of each ring corresponded to the conditions that the tree experienced in that year: a colder year, for example, will result in less growth than a warm year. Translating this into code seemed as simple as changing the width and height of each ellipse(), but as always the fact of the code itself – its own nature – introduced an additional, irritating step. Just setting a random size for each ring, even within an arbitrary range, can lead to puzzling scenarios where a ring actually grows inside the previous year’s ring, as if the tree had just given up and decided to make a wide turn and head back for the taproot. Every time I write more code that is designed to be mimetic – to represent the functionality of real-world phenomena – I am reminded of the fact that physical absurdities and impossibilities are perfectly consistent and internally logical in code, as long as all the semi-colons are in the right place. In my recent teaching I’ve been explaining this to students by reminding them of the tale of the golem: a mindless servant who perfectly and silently performs the wishes of its master to the letter, even if that literal intepretation leads to calamity. (A slightly more chilling, and contemporary, version of this can be found in AI researcher Robert Miles’ version of the stamp-collector thought experiment).
For me, the computer’s binary literalism was a bit less dangerous. On each round of the for() loop generating each ring, I had to store the base width of both the previous and next rings and use those as constraints on the size of the current ring. After this, the simulation instantly took on an organicism, as computer simulations often do when you add in a touch of bounded pseudo-randomness. Rather than thirty years of Platonic summers, the tree now was telling a more interesting life-history: a promising start, some lean middle years, and a stability, and confidence in its field, as it approached the day of its felling.
There are more variables to consider. At the moment, this tree is still an icon: perfectly cylindrical, or at least as perfectly as Processing can render it. The other, numerous variables that influence a sapling into an individual tree – the noise of cellular growth – are still missing. Luckily, Perlin noise came to the rescue, implemented as a native function in Processing. I wasn’t sure, at first, how best to implement it in order to deform the shape of each ring, but eventually managed to crib and adapt this example by Peter Hoffman to give me the effect I wanted. I understood Hoffman’s example enough to strip out some of the extraneous controls, and to work out how the noise() function uses the two variables nInt and nAmp to determine how ‘noisy’ (how jagged, how alpine) the tree becomes: functioning as a seed which influences each ring, reverberating out from the first year’s growth to the last cortex.
While visually this looks acceptably like a tree-ring – at least to me, who has never studied them too closely – it’s not much of a simulation. Not only are those two values, nInt and nAmp, entirely arbitrary, they are also identically employed in each ring. While the tree appears organic, if you look closely at the third picture, you can see that every ring is an identical (if expanded) copy of the previous. If you saw this tree standing in the forest you would have no idea that it was a bark-deep simulacrum. Each ring reverberates perfectly from its seed, with no impression of the conditions of any year intervening, no echoes or ripples of past traumas and comforts – the carving of a pair of names in the bark, a forest fire, radioactive poisoning, early shade, a prevailing wind, galleries of beetles in the heartwood – in subsequent summers and winters.
I tried a half-hearted initial pass at regenerating the random nInt and nAmp for each ring, but again had the Golem problem: rings overleaving and overlapping, looking more like a tattered crepe rose than anything else. I think the next step is to do some reading, both online and out on the hillside beside my house where the Wildlife Trust felled a big beech last year,and then pick apart Hoffman’s example a little more. There are surprisingly few simulations of tree ring growth surviving out there on the Internet, though the generation of whole trees is one of those omnipresent but overlooked middlewares of videogame development that might bear a bit more fruit.
Whatever balance I choose between xylem-level specificity and forest-level abstraction in this simulation, I want this project to speak to what (I think) must be one of the greatest narrative satisfactions of practising dendrochronology – the plotting of the vagaries of life beyond the tree onto the graph which the tree’s cross-section provides: the opening of the leaves of the accordion. In essence, what I am trying to do is the dendrochronologist’s opposite: a compression of a thousand stories into a fibrous impression. A successful simulation would represent the enfolding of mysteries, asked of the tree through its witnessing. Why did you grow to the south-south east? Why was your fifth year so hard? Who burnt you at the end of your first decade? Why – the epistemic why of what Marie-Laure Ryan sees as the prototype of digital narrative – did you stop growing?