home / skills / plurigrid / asi / catalyst-chemical
This skill models chemical reaction networks with Catalyst.jl, enabling unit-aware ODEs, subsystem coupling, and autopoietic analysis to improve CRN design.
npx playbooks add skill plurigrid/asi --skill catalyst-chemicalReview the files below or copy the command above to add this skill to your agents.
---
name: catalyst-chemical
description: Chemical reaction network modeling with Catalyst.jl. Autopoietic systems,
unit-aware ODEs, and interacting subsystems.
source: SciML/Catalyst.jl + GitHub issues research
license: MIT
gf3_category: ZERO
---
# Catalyst Chemical Computing Skill
> **Addresses**: [Catalyst.jl #1341](https://github.com/SciML/Catalyst.jl/issues/1341) (DynamicQuantities units), [#1336](https://github.com/SciML/Catalyst.jl/issues/1336) (interacting subsystems)
## What is Catalyst.jl?
Catalyst.jl is a **symbolic modeling package for chemical reaction networks (CRNs)**. It enables:
- Declarative DSL for reaction systems
- Automatic conversion to ODEs, SDEs, jump processes
- Hierarchical composition of reaction networks
- Integration with ModelingToolkit.jl ecosystem
## Core Syntax
```julia
using Catalyst
# Define a simple reaction network
rn = @reaction_network begin
k1, A + B --> C # A + B → C with rate k1
k2, C --> A + B # C → A + B with rate k2
k3, C --> D # C → D with rate k3
end
# Convert to ODE system
osys = convert(ODESystem, rn)
# Solve
using DifferentialEquations
u0 = [:A => 1.0, :B => 1.0, :C => 0.0, :D => 0.0]
ps = [:k1 => 1.0, :k2 => 0.5, :k3 => 0.1]
tspan = (0.0, 10.0)
prob = ODEProblem(rn, u0, tspan, ps)
sol = solve(prob, Tsit5())
```
## Interacting Subsystems (Issue #1336)
Pattern for coupling reaction networks through observables:
```julia
using Catalyst, ModelingToolkit
# Subsystem 1: Gene expression
@reaction_network gene_expr begin
@parameters α β
@species mRNA(t) Protein(t)
α, ∅ --> mRNA # Transcription
β, mRNA --> mRNA + Protein # Translation
1.0, mRNA --> ∅ # mRNA decay
0.1, Protein --> ∅ # Protein decay
end
# Subsystem 2: Metabolic network (depends on Protein from subsystem 1)
@reaction_network metabolism begin
@parameters v_max K_m
@species Substrate(t) Product(t)
# Michaelis-Menten kinetics with enzyme = Protein
(v_max * Protein * Substrate) / (K_m + Substrate), Substrate --> Product
0.05, Product --> ∅
end
# Compose via shared variable
function compose_systems(gene::ReactionSystem, metab::ReactionSystem)
# Connect Protein from gene_expr to metabolism
@named coupled = compose(gene, metab)
# Add coupling equation: Protein in metabolism = Protein in gene_expr
eqs = [metab.Protein ~ gene.Protein]
extend(coupled, eqs)
end
coupled_system = compose_systems(gene_expr, metabolism)
```
## DynamicQuantities Integration (Issue #1341)
Unit-aware reaction modeling:
```julia
using Catalyst, DynamicQuantities
# Define units
const mM = u"mmol/L"
const s⁻¹ = u"s^-1"
const mM_s⁻¹ = u"mmol/L/s"
# Reaction network with units
rn_units = @reaction_network begin
@parameters k1::typeof(1.0mM_s⁻¹) k2::typeof(1.0s⁻¹)
@species A(t)::typeof(1.0mM) B(t)::typeof(1.0mM)
k1, ∅ --> A # Zero-order production
k2, A --> B # First-order conversion
end
# Solve with unit-aware initial conditions
u0_units = [:A => 1.0mM, :B => 0.0mM]
ps_units = [:k1 => 0.1mM_s⁻¹, :k2 => 0.05s⁻¹]
tspan_units = (0.0u"s", 100.0u"s")
# Note: Full unit propagation through ODE solve is WIP
```
## Autopoietic Closure Detection
Identify self-maintaining organizations in CRNs:
```julia
"""
Check if a set of species forms an autopoietic organization.
An organization is autopoietic if:
1. It is closed under the reaction network (all products are in the set)
2. It is self-maintaining (can regenerate all consumed species)
"""
function is_autopoietic(rn::ReactionSystem, species_set::Set{Symbol})
reactions = Catalyst.reactions(rn)
# Check closure: all products must be in set
for rx in reactions
# Check if any reactant is in our set
reactant_syms = [Symbol(sp) for sp in rx.substrates]
if !isempty(intersect(reactant_syms, species_set))
# Then all products must also be in set
product_syms = [Symbol(sp) for sp in rx.products]
if !issubset(product_syms, species_set)
return false
end
end
end
# Check self-maintenance: each species can be produced
for sp in species_set
can_produce = false
for rx in reactions
if Symbol(sp) in [Symbol(p) for p in rx.products]
can_produce = true
break
end
end
if !can_produce
return false
end
end
return true
end
# Example: check if {A, B, C} is autopoietic
species = Set([:A, :B, :C])
is_autopoietic(rn, species)
```
## GF(3) Conservation in CRNs
Map reaction network dynamics to balanced ternary:
```julia
"""
Assign GF(3) trits to reactions for conservation checking.
- Production reactions (∅ → X): PLUS (+1)
- Degradation reactions (X → ∅): MINUS (-1)
- Conversion reactions (X → Y): ZERO (0)
"""
function reaction_trits(rn::ReactionSystem)
trits = Dict{Reaction, Int}()
for rx in Catalyst.reactions(rn)
n_substrates = length(rx.substrates)
n_products = length(rx.products)
if n_substrates == 0 && n_products > 0
trits[rx] = 1 # PLUS: production
elseif n_substrates > 0 && n_products == 0
trits[rx] = -1 # MINUS: degradation
else
trits[rx] = 0 # ZERO: conversion
end
end
# Verify conservation
total = sum(values(trits))
balanced = total % 3 == 0
return trits, balanced
end
```
## Spatial Reaction-Diffusion
```julia
using Catalyst, MethodOfLines, DomainSets
# Reaction-diffusion PDE
@parameters D_A D_B
@variables x t A(x,t) B(x,t)
# Spatial domain
domain = [x ∈ Interval(0.0, 1.0)]
# Reaction network in each spatial point
rn_spatial = @reaction_network begin
k1, A + B --> 2B # Autocatalysis
k2, B --> A # Reversion
end
# Add diffusion terms
∂t = Differential(t)
∂x = Differential(x)
∂xx = ∂x ∘ ∂x
eqs = [
∂t(A) ~ D_A * ∂xx(A) + reaction_rate(rn_spatial, :A),
∂t(B) ~ D_B * ∂xx(B) + reaction_rate(rn_spatial, :B),
]
# Solve with MethodOfLines.jl
```
## ImperativeAffect Events (Issue #1335)
Custom event handling for stochastic CRNs:
```julia
using Catalyst, JumpProcesses
# Define reaction network with events
rn_events = @reaction_network begin
@parameters k_div k_death threshold
@species Cells(t) Resources(t)
k_div * Resources, Cells --> 2Cells # Division uses resources
k_death, Cells --> ∅ # Cell death
1.0, ∅ --> Resources # Resource replenishment
end
# Custom affect: cell division only when resources > threshold
function division_affect!(integrator)
if integrator[:Resources] > integrator.p[:threshold]
integrator[:Cells] += 1
integrator[:Resources] -= 1
end
end
# Attach to jump process
# (Full ImperativeAffect DSL support is WIP per issue #1335)
```
## Links
- [Catalyst.jl](https://github.com/SciML/Catalyst.jl)
- [Documentation](https://docs.sciml.ai/Catalyst/stable/)
- [ModelingToolkit.jl](https://github.com/SciML/ModelingToolkit.jl)
- [Chemical Organization Theory](https://en.wikipedia.org/wiki/Chemical_organization_theory)
## Commands
```bash
just catalyst-demo # Run Catalyst demonstration
just catalyst-autopoietic # Autopoietic closure detection
just catalyst-spatial # Reaction-diffusion example
just catalyst-gf3 # GF(3) conservation check
```
---
*GF(3) Category: ZERO (Coordination/Ergodic) | Chemical computing for multi-agent dynamics*This skill provides tooling and patterns for modeling chemical reaction networks with Catalyst.jl, including unit-aware ODEs, interacting subsystems, autopoietic closure detection, GF(3) conservation checks, and spatial reaction–diffusion setups. It focuses on practical recipes: composing reaction systems, attaching observables for coupling, handling units with DynamicQuantities, and simple algorithms for organization detection and conservation classification.
The skill exposes idiomatic Catalyst.jl workflows: declare reaction networks with the @reaction_network DSL, convert networks to ODE/SDE/jump problems, and compose subsystems via shared variables or explicit coupling equations. It demonstrates unit-typed parameters and species using DynamicQuantities, provides a lightweight autopoietic membership checker that verifies closure and self-maintenance, and supplies patterns for mapping reactions to GF(3) trits for parity-like conservation analysis. Spatial models are created by combining local reaction rates with diffusion terms and solved with a method-of-lines approach.
Can Catalyst handle unit-aware ODE solves end-to-end?
Partial support is shown: parameters and species can carry DynamicQuantities types, but full unit propagation through all solver internals may be limited; convert units to compatible numeric values when required for solver compatibility.
How do I couple two reaction systems so one observes a species in the other?
Compose the systems with @named or compose, then add algebraic equality equations that bind the observed species in one subsystem to the source species in the other before extending the coupled system.