← Home
petri-net ode biochemistry mass-action

Enzyme Kinetics: Michaelis-Menten from Three Transitions

The Michaelis-Menten equation says why an enzyme-catalyzed reaction saturates: past some substrate concentration, adding more substrate stops speeding it up. The textbook derivation starts from four rate equations, assumes the enzyme-substrate complex sits at a quasi-steady state, and does the algebra. A 4-place Petri net with mass-action rates gives you those four rate equations without anyone typing them, and the solver integrates them as they stand — no steady-state assumption. The Michaelis-Menten law is still an approximation of that system, and it still comes from the algebra; what the net changes is that the thing being approximated is the model itself, not a hand transcription of it.

Try the interactive demo at pilot.pflow.xyz/enzyme-kinetics. Its source is frontends/enzyme-kinetics/main.js in petri-pilot, which builds the net in buildModel; the same net with the same default rates is declared for the generated service in services/enzyme-kinetics.json. If the ODE view of a Petri net is new, Declarative Differential Models is the background.

The Reaction

An enzyme catalyzes the conversion of substrate to product in three steps:

E + S  ⇌  ES  →  E + P
  1. Binding: enzyme and substrate combine to form a complex
  2. Unbinding: the complex falls apart (reverse reaction)
  3. Catalysis: the complex converts substrate to product, releasing the enzyme

The enzyme is not consumed—it cycles between free and bound states.

The Petri Net

Model Structure

Four places, three transitions:

Place Initial Role
substrate 100 Free substrate molecules
enzyme 10 Free enzyme molecules
complex 0 Enzyme-substrate complex
product 0 Converted product
Transition Rate Arcs
bind k1 = 0.01 substrate + enzyme → complex
unbind k-1 = 0.1 complex → substrate + enzyme
catalyze kcat = 0.5 complex → product + enzyme

The arc structure encodes the chemistry directly. bind consumes a free enzyme and both unbind and catalyze give one back — the enzyme is a catalyst because every path out of complex returns it.

Mass Action, Then Michaelis-Menten

With mass-action kinetics, each transition fires at a rate proportional to the product of its input concentrations:

flux(bind)     = k1 × [S] × [E]
flux(unbind)   = k-1 × [ES]
flux(catalyze) = kcat × [ES]

The ODE system follows directly from the Petri net topology:

d[S]/dt  = -k1·[S]·[E] + k-1·[ES]
d[E]/dt  = -k1·[S]·[E] + k-1·[ES] + kcat·[ES]
d[ES]/dt =  k1·[S]·[E] - k-1·[ES] - kcat·[ES]
d[P]/dt  =  kcat·[ES]

We didn't write these equations into the simulation. The demo builds the net as JSON-LD and hands it to pflow-xyz's petri-solver.js, whose buildODEFunction derives each derivative from the arcs — flux is the rate times the product of the input markings. (The service definition does carry the four equations as metadata, for people to read; the solver never sees them.)

Set d[ES]/dt ≈ 0 — the quasi-steady-state assumption — use [E] + [ES] = [E]total, and the reaction rate simplifies to:

v = Vmax × [S] / (Km + [S])

where Km = (k-1 + kcat) / k1 and Vmax = kcat × [E]total. That step is algebra, and the demo doesn't do it numerically: its Km / Vmax readout is those two formulas applied to the slider values (computePredictions in main.js), not something fitted to the simulated curve. The approximation is good when [E]total is small against [S] + Km — at the defaults, 10 against 160 — and the net's ODE is what it approximates.

What the Simulation Reveals

Concentration Curves

The demo re-integrates on every slider change (Tsit5, fixed step dt = 0.05, over a 30-second default window) and shows four readouts:

Readout Default Value What It Is
Time to 50% conversion ~17s First time free substrate falls to half its initial value
Peak reaction rate ~2.95 Largest finite-difference d[P]/dt in the run
Final yield ~70% Product as a percentage of initial substrate at the end of the window
Km / Vmax 60 / 5.0 Computed from the rate constants, not measured from the run

The first three come out of the integration; the figures are the demo's readout at the default sliders, and we haven't re-run them for this revision. There is one check you can do without running anything. d[P]/dt = kcat × [ES], and [ES] can never exceed the 10 enzyme molecules there are, so the peak is strictly bounded by Vmax = 5. The Michaelis-Menten approximation is tighter: at [S] = 100 it predicts 5 × 100 / 160 ≈ 3.1, and substrate only falls from there. A peak of ~2.95 sits just under that, which is what you'd expect when some substrate is already tied up in complex by the time the rate peaks.

The concentration chart shows the familiar pattern: substrate depletes as product accumulates, with the enzyme-substrate complex rising quickly then falling as substrate runs out.

The rate chart plots d[P]/dt against time, with a dashed line at Vmax. The page labels it a saturation curve, but it isn't one — the saturation curve is rate against substrate concentration — and its shape is different: the rate rises while the complex forms, peaks below Vmax, then falls as substrate runs out.

The Saturation Effect

Why does the rate saturate? The Petri net makes it visible.

At low substrate, most enzyme is free. Increasing [S] means more binding events, so the rate climbs linearly. But at high substrate, almost all enzyme is tied up in complex. Adding more substrate can't speed things up—there's no free enzyme to bind with.

Km is the substrate concentration at half-maximum rate: a low Km means the enzyme is half-saturated at low substrate, a high Km means it needs a lot. It is often read as binding affinity, but that reading only holds when kcat ≪ k-1. At the defaults kcat is five times k-1, and Km = 60 against a dissociation constant k-1 / k1 = 10 — most of Km here is turnover, not loose binding.

In the Petri net, both effects are the competition for complex tokens. unbind and catalyze drain the same place, and either one empties it; when k-1 is large relative to kcat the complex mostly falls apart, when kcat is large it mostly turns over, and in both cases you need more substrate to keep the enzyme occupied.

The demo doesn't plot the hyperbola itself. To trace it you'd step the substrate slider and note the peak rate each time, which is roughly the initial rate at that [S] — a sweep we haven't automated (see the end of the post).

Parameter Exploration

The sliders auto-simulate on every change. Some experiments to try:

High enzyme, low substrate (e0=50, s0=20): There's plenty of enzyme to go around, so the reaction runs much faster. This is also where the Km / Vmax readout stops meaning much. With more enzyme than substrate the quasi-steady-state assumption fails, and the Vmax of 25 it reports is unreachable: the complex can hold at most the 20 substrate molecules there are, so d[P]/dt never exceeds 0.5 × 20 = 10.

Low k1 (k1=0.001): Binding becomes the bottleneck. Km goes to (0.1 + 0.5) / 0.001 = 600, past the substrate slider's maximum of 500, so the demo can't put this enzyme at half-saturation at all.

High kcat (kcat=2.0): The enzyme turns over faster. Vmax goes to 20, and the 50% conversion time drops. But Km also rises, to 210 — faster catalysis empties the complex faster, so more substrate is needed to keep it populated.

Equal k-1 and kcat (km1=0.5, kcat=0.5): The complex is equally likely to unbind or catalyze. Km = (0.5 + 0.5) / 0.01 = 100—higher than default. The enzyme is less efficient because half the binding events are "wasted" on unbinding.

Conservation Laws

The Petri net enforces a conservation law automatically:

[E] + [ES] = [E]total = constant

Free enzyme plus bound enzyme always equals the initial enzyme count. Nothing in the demo enforces this; it follows from the arcs. The enzyme token circulates through enzyme → complex → enzyme but never leaves the system.

Similarly:

[S] + [ES] + [P] = [S]initial

Substrate is either free, bound in complex, or converted to product. Each transition leaves that sum unchanged: bind takes one substrate and makes one complex, unbind reverses it, catalyze turns one complex into one product.

These are P-invariants of the Petri net — weight vectors over the places whose weighted token count no firing can change. Over (substrate, enzyme, complex, product) they are w = (0, 1, 1, 0) and w = (1, 0, 1, 1), and each satisfies wᵀC = 0. We checked that by hand, transition by transition, against the nine arcs in the service definition. The coffee shop model gets its conservation laws the same way, one per ingredient (coffee_beans + beans_used = 1000).

Beyond chemistry, tentatively

The shape — a limited resource cycling between free and occupied, with throughput capped by how much of it there is — looks familiar from elsewhere: processes and cores, callers and agents, jobs and machines. We haven't built any of those as nets, and the mapping is looser than swapping labels. A call center has no obvious unbind (a caller abandoning mid-service, maybe), and a context switch is overhead on the core rather than a reversible binding. What should carry over is the enzyme invariant — free plus busy is constant — and the ceiling that comes with it: throughput can't exceed rate × servers, for the same reason d[P]/dt can't exceed kcat × [E]total.

View in pflow editor → · more ODE demos

Not shown yet

The net is small enough to understand completely, and what it establishes is narrower than the earlier version of this post claimed. It generates the mass-action ODE system without anyone writing it, it carries two conservation laws in its arcs, and the demo's readout at the default setting is consistent with Michaelis-Menten (the peak-rate check above). It does not, by itself, produce the Michaelis-Menten equation; that is still the quasi-steady-state algebra above. The worked case that would close the gap is a sweep: integrate the net across a range of initial substrate, take the initial rate at each, and plot it against Vmax × [S] / (Km + [S]). It should match closely at e0 = 10 and drift as e0 approaches [S] + Km. Nothing in the demo or the repo runs that sweep yet, so that is a prediction, not a result.

This topic is covered in depth in Chapter 9: Enzyme Kinetics of Petri Nets as a Universal Abstraction.

What Changed

For readers of the February version (revised 2026-10-04):

  1. Michaelis-Menten is not derived by the solver. The earlier text said the equation was "derived not by algebra, but by running the ODE solver." The demo's Km / Vmax readout is the closed-form formula applied to the sliders, and the rate chart is d[P]/dt against time, not the saturation curve. The post now says what the net produces (the ODE system) and what still comes from the quasi-steady-state algebra.
  2. Catalyst wording. The enzyme is not an input of catalyze; it is consumed by bind and returned by unbind or catalyze.
  3. Km is not binding affinity at these rates. With kcat = 5 × k-1, Km = 60 is mostly turnover; the dissociation constant is 10.
  4. Bounds and validity. Peak rate "approaches Vmax" became a stated bound; the high-enzyme experiment now notes that the quasi-steady-state assumption fails there and its reported Vmax is unreachable.
  5. Analogies hedged. The CPU / call center / manufacturing list claimed an identical net structure; none of those nets exists, and the post now says so.

Related: Declarative Differential Models · Coffee Shop Model

×

Follow on Mastodon