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.
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 exposedLink 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 = 0Creating 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 0Link 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 := recoveredRemoving 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-linkModels that use this
Two catalog models exercise the link surface — one as a minimal proof, one as a full epidemic.
Model
Network Example
The smallest link demo: stochastic construction with create-link-with, proving links are first-class agents.
Open model →Model
Virus on a Network
Contagion over link-neighbors: susceptible, infected, and recovered states spreading through a contact network built in setup.
Open model →Model
Directed Diffusion
A numeric field flows only along outgoing links, making source and target roles visible in the graph.
Open model →