Skip to content

WIP - add aliases and alias elimination for ReactionSystems#1449

Open
isaacsas wants to merge 8 commits intoSciML:masterfrom
isaacsas:alias_elimination
Open

WIP - add aliases and alias elimination for ReactionSystems#1449
isaacsas wants to merge 8 commits intoSciML:masterfrom
isaacsas:alias_elimination

Conversation

@isaacsas
Copy link
Copy Markdown
Member

@isaacsas isaacsas commented Mar 29, 2026

NOTE: I still need to give this a comprehensive line by line review as I have primarily carefully reviewed/edited the plan for this and the implementation of select functions within the plan. So this is currently very WIP.

Alias Elimination for ReactionSystems

This PR adds support for declaring symbol aliases on ReactionSystems and automatically eliminating them during model conversion and problem construction.

Motivation

When composing multiple ReactionSystems, users often need to declare that a species or parameter in one subsystem is the same as in another. Previously, this required algebraic equations for unknowns and had no built-in mechanism for parameters. This PR provides a first-class alias system with automatic resolution.

How it works

An alias A ~ B declares that A (the LHS) is eliminated in favor of B (the RHS, the canonical/surviving symbol). During model conversion, all references to A are substituted with B throughout reactions, equations, events, initial conditions, and observables. Eliminated unknowns become observables so they remain accessible in solutions via sol[A].

Basic usage

# Via the DSL
rn = @reaction_network begin
    @aliases begin
        A ~ B       # species alias: A eliminated, B survives
        k1 ~ k2     # parameter alias: k1 eliminated, k2 survives
    end
    k1, A + C --> D
    k2, B --> E
end

# Aliases are eliminated automatically during problem construction
prob = ODEProblem(rn, [A => 1.0, C => 0.5, D => 0.0, E => 0.0], (0.0, 10.0),
    [k1 => 0.3, k2 => 0.5])
# User can provide eliminated symbols in u0/p — they are remapped automatically
# Via the programmatic API
@species A(t) B(t) C(t)
@parameters k1 k2
@named rn = ReactionSystem(
    [Reaction(k1, [A], [B]), Reaction(k2, [B], [C])], t, [A, B, C], [k1, k2];
    aliases = [A ~ B])

# Explicit elimination
flat = Catalyst.flatten(complete(rn))
elim = Catalyst.eliminate_aliases(flat)
# Cross-subsystem aliases via compose
@named sub1 = ReactionSystem([Reaction(k1, [A], [B])], t, [A, B], [k1])
@named sub2 = ReactionSystem([Reaction(k2, [C], [D])], t, [C, D], [k2])
composed = compose(sub1, [sub2]; aliases = [sub1.A ~ sub2.C])

Supported alias types (v1)

  • Ordinary species ↔ ordinary species
  • BC-species ↔ BC-species
  • Constant species ↔ constant species
  • Non-species unknowns (variables) ↔ non-species unknowns
  • Parameters ↔ parameters (with strict metadata matching: both must agree on type, units, time-dependency)

Key features

  • Automatic elimination during all model conversions (ode_model, sde_model, jump_model, hybrid_model, ss_ode_model) and problem constructors (ODEProblem, SDEProblem, JumpProblem, HybridProblem, NonlinearProblem, SteadyStateProblem)
  • eliminate_aliases kwarg (true by default) on all conversion/problem functions. Set to false to materialize aliases as algebraic constraints (ODE/SDE only)
  • Automatic input remapping in problem constructors: users can provide eliminated symbols in u0/p maps (both Dict and Vector{Pair})
  • remap_alias_inputs(u0_or_p, sys) public function for manual remapping when using the *_model*Problem(::System, ...) workflow
  • Composition support: compose(...; aliases=...), extend (unions aliases), flatten (collects with namespacing)
  • Chain resolution: A ~ B, B ~ C correctly resolves both A and B to canonical C
  • Cycle detection with informative errors
  • No-op reaction removal: reactions that collapse to zero net stoichiometry after substitution (e.g. A → B with A ~ B) are dropped
  • Stoichiometry merging: 2A + B → C with A ~ B becomes 3B → C
  • isequivalent compares aliases (set equality)
  • Serialization guard (errors with informative message; deferred alongside tstops/brownians/poissonians)
  • Non-symbolic jump guard: imperative (function-based) ConstantRateJump/VariableRateJump are now rejected at ReactionSystem construction time

Known limitations (v1)

Unsupported alias types (rejected with informative error):

  • Brownian, Poissonian, compound species, binding key, and observable aliasing
  • Cross-category aliasing (e.g. species ↔ parameter)
  • MassActionJump in systems with aliases (stoichiometry remapping deferred)
  • Imperative (function-based) jumps

Other limitations:

  • Serialization of systems with aliases is not yet supported
  • eliminate_aliases=false is not supported for jump systems
  • The *_model*Problem(::System, ...) workflow requires manual remap_alias_inputs calls
  • Per-expression substitution guards are structural only (category-level skips when submaps are empty); no expression-level early-exit
  • system_to_reactionsystem reverse conversion produces an empty aliases field (the original alias declarations are cleared after elimination, though the substitution maps survive in metadata for remap_alias_inputs)

Files changed

  • New: src/alias_elimination.jl — AliasClass EnumX, classification, validation, resolution, elimination, remapping, conversion helper
  • New: test/reactionsystem_core/alias_elimination.jl — 85 tests across 21 test sets
  • Modified: src/reactionsystem.jlaliases field, constructors, accessors, flatten/compose/extend, isequivalent, non-symbolic jump guard
  • Modified: src/reactionsystem_conversions.jleliminate_aliases kwarg threading, prepare_aliases_for_conversion, input remapping in all problem constructors, metadata forwarding, has_alg_equations fixes
  • Modified: src/dsl.jl@aliases option
  • Modified: src/Catalyst.jl — include, exports
  • Modified: src/reactionsystem_serialisation/serialise_reactionsystem.jl — serialization guard
  • Modified: test/runtests.jl — added test set
  • Modified: HISTORY.md — feature documentation with known limitations
  • Modified: docs/src/devdocs/dev_guide.md — ReactionSystem field addition checklist

isaacsas and others added 8 commits March 29, 2026 18:33
Add species/variable/parameter aliasing with automatic elimination during
model conversion and problem construction. Aliases declared via `lhs ~ rhs`
are resolved by substitution, with eliminated unknowns becoming observables
and eliminated parameters removed throughout.

Key features:
- `aliases` field on ReactionSystem with `@aliases` DSL option
- AliasClass EnumX for type-safe classification and validation
- Cycle detection via DFS with path compression
- Stoichiometry merging, no-op reaction removal, IC transfer
- `eliminate_aliases` kwarg on all model/problem constructors
- `remap_alias_inputs` for user input remapping (Dict + Vector{Pair})
- Composition support (compose/extend/flatten)
- isequivalent comparison and serialization guard
- 85 tests across 21 test sets
- Developer docs: ReactionSystem field addition checklist
- Comprehensive HISTORY.md with known limitations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Alias substitution maps survive in metadata after round-trip through
MT.System; only the alias declarations field is cleared.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The @enumx AliasClass macro generates a submodule that ExplicitImports.jl
cannot analyze, same as PhysicalScale.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Symbols appearing only in aliases (not in reactions/equations) were not
being added to the system unknowns/parameters during construction.
This adds a collect_vars! loop over aliases in make_ReactionSystem_internal,
matching the existing pattern for observables, events, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
has_aliases now follows the MTK convention of indicating type-level
support (always true for ReactionSystem). The new aliases_present
function performs the value-level recursive check for whether any
alias equations exist in the system tree.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ss_ode_model: use fullrs instead of rs for bindings computation and
differential equation check. sde_model: use flatrs instead of rs for
completeness and spatial checks in legacy noise path. jump_model: make
eliminate_aliases an explicit kwarg instead of extracting from kwargs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant