A small library of Python functions for anxious humans.

The Python code in this book is, with a small number of carefully marked exceptions, real. You can copy it into a file. You can run it. The functions will do what they say they do, give or take the specifics of your Python installation.

This appendix collects the working code from across the chapters into one place, so that you have, in a single document, the executable parts of the toolkit. I have added a few small utility functions that did not earn a place in any single chapter but that, over the years, have turned out to be useful enough in my own daily life to include here. The whole thing is, in total, well under two hundred lines of Python. You could, if you wanted, paste it into a single file called anxious_toolkit.py and have, by the end of an afternoon, a working library you can import from any project.

A note on what is here and what is not. The functions below are the ones that actually run. The book also contains, in Chapters 10, 11, 18, and 19, code that is deliberately broken or that uses Python syntax as a notation for procedures whose implementation is, on inspection, performed by your brain rather than by an interpreter. That code is not in this appendix, because it would not, on import, do anything useful. If you want it, it is in the chapters where it lives. This appendix is the runnable parts.

The functions are organized roughly in the order they appeared in the book. Each one has a short note pointing at the chapter where the underlying concept was introduced. Read the chapter first, if you have not. The function alone, without the concept, is a tool without a use.

∗ ∗ ∗

From the diagnosis.

brain_threat_response(stimulus) — from Chapter 1.

The brain's threat-detection function, miscalibrated. Returns 'leopard' for every input, because the legacy code was written for an environment that has not existed for a hundred thousand years.

def brain_threat_response(stimulus):
    """
    The threat-response function as installed by evolution,
    running on the modern nervous system. Notice that it
    does not actually examine the input.
    """
    return 'leopard'
∗ ∗ ∗

From the toolkit.

how_is_life_going(history) — from Chapter 4.

Returns both the function value and its derivative. The anxious mind, by default, returns only the second and tells you it is the first.

def how_is_life_going(history):
    """
    history: a list of numeric values representing your life
             at evenly spaced moments. Higher is better.
    Returns:  a tuple (position, velocity).
    """
    if not history:
        return (None, None)
    position = history[-1]
    if len(history) < 2:
        return (position, None)
    velocity = history[-1] - history[-2]
    return (position, velocity)
gradient_descent(start, landscape, step_size, steps) — from Chapter 5.

Roll downhill until you stop. Will find a local minimum and refuse to leave, even if a deeper valley is available somewhere over the next hill.

def gradient_descent(start, landscape, step_size, steps):
    position = start
    for _ in range(steps):
        slope = (landscape(position + 0.001)
                 - landscape(position - 0.001)) / 0.002
        position = position - step_size * slope
        if abs(slope) < 1e-6:
            break
    return position
chase_happiness(target, position, learning_rate, iterations) — from Chapter 6.

Closes the gap to the target by a fixed proportion at each step. Gets arbitrarily close. Never arrives. Run it with a learning rate above 1 to simulate the overshooter.

def chase_happiness(target, position, learning_rate, iterations):
    history = [position]
    for _ in range(iterations):
        gap = target - position
        position = position + learning_rate * gap
        history.append(position)
    return history
find_eigen_directions(transformation) — from Chapter 7. Requires numpy.

Given a transformation, find the directions in which it does not rotate. These are the eigenvectors. They are what life cannot turn.

import numpy as np

def find_eigen_directions(transformation):
    """
    transformation: a 2x2 numpy array
    Returns:        a list of (eigenvalue, eigenvector) pairs.
    """
    eigenvalues, eigenvectors = np.linalg.eig(transformation)
    return list(zip(eigenvalues, eigenvectors.T))
bayesian_update(prior, likelihood_if_true, likelihood_if_false) — from Chapter 8.

One Bayesian update. Strong priors yield slowly, then all at once. The collapse is mathematically guaranteed if the evidence is real and you keep producing it.

def bayesian_update(prior, likelihood_if_true, likelihood_if_false):
    """
    prior:               belief in the hypothesis, 0..1
    likelihood_if_true:  P(evidence | hypothesis_true)
    likelihood_if_false: P(evidence | hypothesis_false)
    Returns the updated belief after the evidence.
    """
    numerator = likelihood_if_true * prior
    denominator = (likelihood_if_true * prior
                   + likelihood_if_false * (1 - prior))
    return numerator / denominator
simulate_mood(days, mean=6.0, std=1.5, seed=0) — from Chapter 9.

Generate a sequence of daily moods drawn from a normal distribution. Useful for watching regression to the mean happen in numbers.

import random

def simulate_mood(days, mean=6.0, std=1.5, seed=0):
    rng = random.Random(seed)
    return [rng.gauss(mean, std) for _ in range(days)]
make_change_greedy(target, coins) — from Chapter 12.

Greedy: take the biggest coin that fits, repeat. Fast. Often wrong.

def make_change_greedy(target, coins):
    coins = sorted(coins, reverse=True)
    used = []
    for coin in coins:
        while target >= coin:
            target -= coin
            used.append(coin)
    return used
make_change_dp(target, coins) — from Chapter 12.

Dynamic programming: consider, at each step, what future moves a current choice preserves. Slower. Correct.

def make_change_dp(target, coins):
    best = [0] + [None] * target
    choice = [None] * (target + 1)
    for amount in range(1, target + 1):
        for coin in coins:
            if coin <= amount and best[amount - coin] is not None:
                candidate = best[amount - coin] + 1
                if best[amount] is None or candidate < best[amount]:
                    best[amount] = candidate
                    choice[amount] = coin
    used = []
    a = target
    while a > 0:
        used.append(choice[a])
        a -= choice[a]
    return used
simulate_disorder(n_particles, n_steps, seed) — from Chapter 13.

Watch a small system drift from order to disorder as the second law of thermodynamics does what it does.

def simulate_disorder(n_particles=20, n_steps=500, seed=42):
    rng = random.Random(seed)
    positions = [1] * n_particles
    history = []
    for step in range(n_steps):
        i = rng.randrange(n_particles)
        positions[i] = rng.choice([0, 1])
        history.append(sum(positions))
    return history
simulate_observation(duration, base, freq, cost, seed) — from Chapter 14.

A toy demonstration that observing a sensitive variable changes it. Watch the average drop as the observation frequency rises.

def simulate_observation(duration=100, base=6.0, freq=0.0,
                          cost=0.4, seed=0):
    rng = random.Random(seed)
    value = base
    history = []
    for _ in range(duration):
        value += rng.gauss(0, 0.3)
        value = max(0, min(10, value))
        if rng.random() < freq:
            value -= cost
        history.append(value)
    return sum(history) / len(history)
nearest_neighbor_tsp(cities, start=0) — from Chapter 17.

A heuristic for the traveling salesman problem. Not optimal. Finishes. The two are not the same thing.

import math

def distance(a, b):
    return math.hypot(a[0] - b[0], a[1] - b[1])


def tour_length(cities, route):
    total = 0
    for i in range(len(route)):
        total += distance(cities[route[i]],
                          cities[route[(i + 1) % len(route)]])
    return total


def nearest_neighbor_tsp(cities, start=0):
    n = len(cities)
    unvisited = set(range(n))
    route = [start]
    unvisited.remove(start)
    current = start
    while unvisited:
        nxt = min(unvisited,
                  key=lambda c: distance(cities[current], cities[c]))
        route.append(nxt)
        unvisited.remove(nxt)
        current = nxt
    return tuple(route), tour_length(cities, route)
∗ ∗ ∗

From the constructive practices.

mark_and_sweep(heap, roots) — from Chapter 20.

Identify, in a graph of references, what is still reachable from the roots. Everything else is garbage. This function decides by reference tracing, not by emotional valence.

def mark_and_sweep(heap, roots):
    live = set()
    queue = list(roots)
    while queue:
        obj_id = queue.pop()
        if obj_id in live:
            continue
        live.add(obj_id)
        for ref in heap[obj_id]['refs']:
            if ref not in live:
                queue.append(ref)
    return {obj_id: obj for obj_id, obj in heap.items()
            if obj_id in live}
∗ ∗ ∗

Small additions that did not earn their own chapter.

What follows are three small helper functions that draw on the concepts in the book but did not, on their own, justify a chapter. They are the kind of thing that is easier to keep around as code than as a habit, because code, unlike a habit, does not require you to remember it on a Tuesday at three in the morning. You can call the code. You can read the docstring. The code will respond the same way regardless of your mood, which is, on inspection, exactly the property the chapters have been arguing makes mathematics worth keeping around as company.

expected_value_of_fear(stages) — builds on Chapter 3.

Given a chain of conditional probabilities representing the steps in a feared cascade, returns the overall probability of the feared outcome. Useful for one-in-three-thousand calculations on the back of a notional envelope.

def expected_value_of_fear(stages):
    """
    stages: a list of conditional probabilities in [0, 1],
            each one the probability of the next step given
            the previous one. Example for a feared career
            consequence:
              [0.33,  # they noticed the awkward email
               0.20,  # given noticed, they formed bad opinion
               0.10,  # given bad opinion, they still remember
               0.05]  # given remembered, it affects something

    Returns:
        the overall probability of the feared end state.
    """
    p = 1.0
    for s in stages:
        p *= s
    return p
cascade_check(current_thought, original_seed) — builds on Chapter 11.

Decides, by a simple heuristic, whether the current thought has drifted far enough from its original seed to count as a "but what if" cascade rather than a real piece of reasoning. The implementation is, on inspection, a piece of self-honesty that the function cannot perform for you.

def cascade_check(current_thought, original_seed):
    """
    current_thought, original_seed: short strings describing
        what your mind is currently chewing on and what you
        actually started with.

    Returns:
        'on topic' if the two appear to be discussing the
        same concrete situation, 'cascade' if the current
        thought is significantly more abstract than the seed.

    Note: the abstraction check is by word overlap, which is
    a crude heuristic. The actual diagnosis is the one you
    perform when you read the two strings side by side and
    notice how much further the current thought has drifted.
    """
    seed_words = set(original_seed.lower().split())
    thought_words = set(current_thought.lower().split())
    overlap = len(seed_words & thought_words)
    if overlap >= 2:
        return 'on topic'
    return 'cascade'
prior_strength(years_held, contradictory_observations) — builds on Chapter 8.

A rough estimate of how strongly you are still holding a particular Bayesian prior, given how long you have held it and how much contradictory evidence you have generated. Not a measurement. A prompt to think about one.

def prior_strength(years_held, contradictory_observations):
    """
    years_held: how long you have held the belief, in years
    contradictory_observations: how many concrete pieces of
        evidence against it you can name without effort

    Returns:
        a rough estimate of your current posterior, on a
        scale of 0 to 1, where 1 means you still hold the
        original belief with full confidence.

    The estimate is not a measurement. It is a structured
    prompt that converts an internal sense into a number
    you can argue with.
    """
    if years_held <= 0:
        return 0.5
    base = 1.0 - (1.0 / (1.0 + 0.1 * years_held))
    decay_per_observation = 0.1
    posterior = base - decay_per_observation * contradictory_observations
    return max(0.0, min(1.0, posterior))
∗ ∗ ∗

This is the toolkit. It is not, in any sense, the entire toolkit. The chapters contain the rest. The chapters are the part you read. This appendix is the part you import.

A small note before the appendix closes. I have, over the years, been asked some version of the following question by people who have read drafts of this material: does writing this stuff down as code, in a Python file, actually help, or is it just a way of feeling productive?

The honest answer is that the helping is, in my experience, real but indirect. Reading the code does not, on its own, calm a panic attack. Running the simulations does not, by itself, lift a depression. The code is not a piece of magic. The code is a small piece of intellectual companionship, available at any hour, in a notation that does not negotiate with the anxious mind on the anxious mind's preferred terms. The code is a friend who does not flinch. The code is a friend who does not get tired. The code is a friend whose answer, on the seven hundredth visit, will be exactly what it was on the first.

For a particular kind of mind, this kind of company has been, in my experience, more useful than I expected. For your kind of mind, it may or may not be. The only way to find out is to copy the code into a file, run it once, and see whether the act of doing so changes anything inside you that nothing else has been able to change. If yes, the appendix has earned its keep. If no, no harm done. The functions will be where they are. You can come back to them later.

← Epilogue                   Appendix B                   Appendix C                   Appendix D                   Appendix E