MathPets language

Graph

Links are first-class agents that connect pets. They have their own fields, their own update blocks, and their own state — the network layer of a model.

Chapter

Graph

See it live in the guide

Links as agents

Links are agents in the same sense patches and pets are. They have fields, they live in named breeds, they can be counted and filtered, and they have their own update blocks. The difference is that each link refers to two endpoint pets — that's the whole point.

When you don't need links, ignore them — a model without a links:section doesn't pay for them. When you do, they become the most natural way to encode contact graphs, supply chains, neural networks, or any relational layer on top of moving agents.

pets:  people:    state: susceptible | infected | recovered = susceptible links:  link contacts undirected:    state: calm | exposed = calm monitors:  contact-count: number = count contacts  exposed-count: number = count contacts where exposed

Link breeds

Declare a link breed inside links: with link breed-name <direction>:. Direction is undirected by default if omitted, or explicit directed. An undirected link is symmetric; a directed link distinguishes a source endpoint from a target.

Link field declarations look like pet fields: typed, with an initial value. The link breed name is itself an agentset — you can count contacts, filter with contacts where (...), update with contacts:, and so on.

links:  link contacts undirected:    state: calm | exposed = calm    weight: number = 1   link follows directed:    strength: number = 0

Creating links

Links are created from a pet update. create-link-with(breed, partner) creates an undirected link between the current pet and partner. create-link-to(breed, target) creates a directed link from the current pet to target.

The canonical idiom for stochastic graph construction combines three language features: a pet update, one-of other breedto pick a partner that isn't self, and repeat n: to run several construction rounds in setup. Three short lines build a recognizable random graph.

setup:  create people population  people:    set-random-position   repeat 3:    people:      where (random-float(100) < link-chance):        let partner = one-of other people        create-link-with(contacts, partner)

Querying links

The standard aggregate operators work on link breeds the way they do on patches and pets: count, any, sum, mean, min, max. Use them in monitors for network-wide summaries.

Filters operate on link fields and on the link's endpoint states. You can ask "how many contacts touch an infected person?" from the link side rather than the pet side, which is often the cleaner shape.

monitors:  contact-count: number = count contacts  active-contacts: number = count contacts where exposed  average-weight: number = mean contacts report (weight) # whole-network densitymonitors:  density: number = if (count people > 0)    then (count contacts * 2 / count people)    else 0

Link updates

A link update runs the body once per link of the breed. Inside the block, the link's own fields are accessible as bare names; end1 and end2 are the two endpoint pets. On a directed link, end1 is the source and end2 is the target.

Endpoint fields are reachable through normal field access: end1.state reads the state of the source pet. This lets a link decide its own state from what its endpoints are doing.

step:  contacts:    state := calm     where ((end1.state = infected and end2.state = susceptible)        or (end2.state = infected and end1.state = susceptible)):      state := exposed stop when (count people where infected = 0)

From a pet

Inside a pet update, link-neighbors(link-breed) returns the pets connected to the current pet through that breed — an agentset of neighbors via links, suitable for filtering and counting like any other.

When you want the links themselves instead of the neighbors, links-with(link-breed) gives you the incident links. Useful when you need to update or query link fields based on the current pet.

Directed breeds also expose in-link-neighbors(breed) and out-link-neighbors(breed), plus in-links(breed) and out-links(breed). Numeric pet fields can flow along a graph with diffuse(value, rate) over breed.

step:  people:    where susceptible:      let neighbors = link-neighbors(contacts)      let infected-neighbor-count = count neighbors where infected       where (infected-neighbor-count > 0          and random-float(100) < virus-spread-chance):        state := infected     where infected:      where (random-float(100) < recovery-chance):        state := recovered

Removing links

die-link removes the current link from the world inside a link update. The endpoint pets stay; only the connection goes away. Like die on a pet, it takes no arguments — bare name.

When you need to remove all links touching a particular pet (for example, a death event), iterate links-with(breed) from a pet update and act on each.

# stochastic edge pruningstep:  contacts where (weight < threshold):    die-link

Models that use this

Two catalog models exercise the link surface — one as a minimal proof, one as a full epidemic.