I'm interested in algorithmically generating structures that have a natural feel. I experimented with growing trees. They won't save the planet, but they're pretty. To me the most natural choice to model a natural tree was... a tree. Every node in the tree is a segment of the natural tree that is characterized by a thickness, two points and its' children.

class Node():
    def __init__(self, a, b):
        self.children = []
        self.thickness = 1
        self.a = a
        self.b = b

The tree grows by branching, that is attaching new nodes. Every iteration, each leaf node branches. There is also a chance that a non-leaf node will spawn a new branch. This is inverse proportional to the square of the node's thickness, so new branches tend to spawn from thin branches at the top and not from the thick trunk. The tree is updated from the leafs to the trunk.

    def is_leaf(self):
        return len(self.children) == 0

    def update(self):
        for child in self.children:
            child.update()
        # Leaves always grow
        if self.is_leaf():
            self.branch()
        # Random branching, dependent on thickness
        elif random(50) < 1/self.thickness**2:
            self.branch()
        self.update_thickness()

When generating a new branch, a copy of the Node's shape is made, rotated by a random angle and attached to the top of the Node. Instead of a purely random walk, a mean regression is used to guide the tree towards the sun.

    def angle(self):
        return PVector.angleBetween(PVector(0,-1), self.b - self.a)

    def branch(self):
        # New Branch
        my_shape = self.b - self.a
        if my_shape.x == 0:
            current_angle = 0
        else:
            current_angle = math.tan(my_shape.x/my_shape.y) 
        mean_regression = 0.02
        angle =  mean_regression * current_angle + (1 - mean_regression) * deg2rad(random(-10, 10))
        new_shape = my_shape.rotate(angle)
        branch = Node(self.b, self.b + new_shape)

        self.children.append(branch)

Trees tend to conserve cross-sectional surface. This means that the sum of cut surfaces remains roughly constant over the height of the tree. I use this heuristic to adjust the thickness of each node in relation to newly spawned branches.

    def update_thickness(self):
        sum_squared_thicknesses = 1
        for child in self.children:
            sum_squared_thicknesses += child.thickness**2
        self.thickness = math.sqrt(sum_squared_thicknesses)

I use Processing as a simple method of drawing the trees. Here is the full code:

# tree.py
import math

def deg2rad(deg):
    return math.pi/180*deg

def random_choice(iterable):
    return iterable[int(random(len(iterable)))]

class Node():
    def __init__(self, a, b):
        self.children = []
        self.thickness = 1
        self.a = a
        self.b = b
        
    def angle(self):
        return PVector.angleBetween(PVector(0,-1), self.b - self.a)

    def is_leaf(self):
        return len(self.children) == 0

    def update_thickness(self):
        sum_squared_thicknesses = 1
        for child in self.children:
            sum_squared_thicknesses += child.thickness**2
        self.thickness = math.sqrt(sum_squared_thicknesses)

    def branch(self):
        # New Branch
        my_shape = self.b - self.a
        if my_shape.x == 0:
            current_angle = 0
        else:
            current_angle = math.tan(my_shape.x/my_shape.y) 
        mean_regression = 0.02
        angle =  mean_regression * current_angle + (1 - mean_regression) * deg2rad(random(-10, 10))
        new_shape = my_shape.rotate(angle)
        branch = Node(self.b, self.b + new_shape)

        self.children.append(branch)

    def update(self):
        for child in self.children:
            child.update()
        # Leaves always grow
        if self.is_leaf():
            self.branch()
        # Random branching, dependent on thickness
        elif random(50) < 1/self.thickness**2:
            self.branch()
        self.update_thickness()

        
    def draw(self):
        strokeWeight(self.thickness)
        line(self.a.x, self.a.y, self.b.x, self.b.y)
        for child in self.children:
            child.draw()
        
# main.py
import tree

root = None 

def setup():
    global root
    size(1000, 1000)
    background(255)
    frameRate(4) # Slow it down, so we can watch the tree grow
    root = tree.Node(PVector(width/2, height), PVector(width/2, height - 10))

def draw():
    global root
    background(255)
    root.draw()
    root.update()