Repository: SciML/Catalyst.jl
Pull request #1142: Refactoring network analysis docs
Author: login: vyudu
Created: 2024-12-02T19:07:18Z | Merged: 2025-04-14T21:30:45Z

Probably worth discussing more but my thought here is to make Network Analysis its own section of the Catalyst docs, with subsections for the ODE decompositions, structural properties and chemical reaction network theory, and a third section for advanced stuff that links to CatalystNetworkAnalysis. 

Added sections for the new API functions in #1134 and building symbolic functions from the ODEs to the ODE docs. Added a section on complex/detailed balance to the CRNT docs. 

Need to: add a section on the deficiency one theorem, bring in the CatalystNetworkAnalysis docs, add code examples to the complex/detailed balance section (including stuff about the precision limitations of `isdetailedbalanced`)


Diff:
diff --git a/docs/pages.jl b/docs/pages.jl
index a8d8fa0cb7..bdf42fc987 100644
--- a/docs/pages.jl
+++ b/docs/pages.jl
@@ -17,7 +17,6 @@ pages = Any[
         "model_creation/model_file_loading_and_export.md",
         "model_creation/model_visualisation.md",
         "model_creation/reactionsystem_content_accessing.md",
-        "model_creation/network_analysis.md",
         "model_creation/chemistry_related_functionality.md",
         "Examples" => Any[
             "model_creation/examples/basic_CRN_library.md",
@@ -40,6 +39,11 @@ pages = Any[
             "model_simulation/examples/interactive_brusselator_simulation.md"
         ]
     ],
+    "Network Analysis" => Any[
+        "network_analysis/odes.md",
+        "network_analysis/crn_theory.md",
+        "network_analysis/network_properties.md"
+    ],
     "Steady state analysis" => Any[
         "steady_state_functionality/homotopy_continuation.md",
         "steady_state_functionality/nonlinear_solve.md",
@@ -69,6 +73,9 @@ pages = Any[
         "spatial_modelling/spatial_jump_simulations.md"
     ],
     "FAQs" => "faqs.md",
-    "API" => "api.md",
+    "API" => Any[
+                 "api/core_api.md",
+                 "api/network_analysis_api.md"
+                ],
     "Developer Documentation" => "devdocs/dev_guide.md"
 ]
diff --git a/docs/src/api.md b/docs/src/api/core_api.md
similarity index 92%
rename from docs/src/api.md
rename to docs/src/api/core_api.md
index efda7cb172..849c6ac805 100644
--- a/docs/src/api.md
+++ b/docs/src/api/core_api.md
@@ -86,7 +86,7 @@ ReactionSystem
 ```
 
 ## [Options for the `@reaction_network` DSL](@id api_dsl_options)
-We have [previously described](@ref dsl_advanced_options) how options allow one to supply additional information to a [`ReactionSystem`](@ref) created with the DSL. Here follows a list
+We have [previously described](@ref dsl_advanced_options) how options permit the user to supply non-reaction information to [`ReactionSystem`](@ref) created through the DSL. Here follows a list
 of all options currently available.
 - [`parameters`](@ref dsl_advanced_options_declaring_species_and_parameters): Allows the designation of a set of symbols as system parameters.
 - [`species`](@ref dsl_advanced_options_declaring_species_and_parameters): Allows the designation of a set of symbols as system species.
@@ -100,7 +100,6 @@ of all options currently available.
 - [`continuous_events`](@ref constraint_equations_events): Allows the creation of continuous events.
 - [`discrete_events`](@ref constraint_equations_events): Allows the creation of discrete events.
 - [`combinatoric_ratelaws`](@ref faq_combinatoric_ratelaws): Takes a single option (`true` or `false`), which sets whether to use combinatorial rate laws.
-- [`require_declaration`](@ref dsl_advanced_options_require_dec): Turns off all inference of parameters, species, variables, the default differential, and observables (requiring these to be explicitly declared using e.g. `@species`).
 
 ## [ModelingToolkit and Catalyst accessor functions](@id api_accessor_functions)
 A [`ReactionSystem`](@ref) is an instance of a
@@ -124,8 +123,7 @@ direct access to the corresponding internal fields of the `ReactionSystem`)
   entries in `get_species(rn)` correspond to the first `length(get_species(rn))`
   components in `get_unknowns(rn)`.
 * `ModelingToolkit.get_ps(rn)` is a vector that collects all the parameters
-  defined *within* reactions in `rn`. This includes initialisation parameters (which
-  are added to the system by ModelingToolkit, and not the user).
+  defined *within* reactions in `rn`.
 * `ModelingToolkit.get_eqs(rn)` is a vector that collects all the
   [`Reaction`](@ref)s and `Symbolics.Equation` defined within `rn`, ordering all
   `Reaction`s before `Equation`s.
@@ -240,32 +238,6 @@ ModelingToolkit.compose
 Catalyst.flatten
 ```
 
-## Network analysis and representations
-Note, currently API functions for network analysis and conservation law analysis
-do not work with constant species (currently only generated by SBMLToolkit).
-
-```@docs
-conservationlaws
-conservedquantities
-conservedequations
-conservationlaw_constants
-ReactionComplexElement
-ReactionComplex
-reactioncomplexmap
-reactioncomplexes
-incidencemat
-complexstoichmat
-complexoutgoingmat
-incidencematgraph
-linkageclasses
-deficiency
-subnetworks
-linkagedeficiencies
-isreversible
-isweaklyreversible
-reset_networkproperties!
-```
-
 ## Network comparison
 ```@docs
 ==(rn1::Reaction, rn2::Reaction)
diff --git a/docs/src/api/network_analysis_api.md b/docs/src/api/network_analysis_api.md
new file mode 100644
index 0000000000..a4e3b21121
--- /dev/null
+++ b/docs/src/api/network_analysis_api.md
@@ -0,0 +1,42 @@
+# [Network analysis and representations](@id api_network_analysis)
+```@meta
+CurrentModule = Catalyst
+```
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+For more information about these functions, please see the sections of the docs on
+[network ODE representation](@ref network_analysis_odes) and [chemical reaction network theory](@ref network_analysis_structural_aspects).
+
+```@docs
+conservationlaws
+conservedquantities
+conservedequations
+conservationlaw_constants
+ReactionComplexElement
+ReactionComplex
+reactioncomplexmap
+reactioncomplexes
+incidencemat
+incidencematgraph
+complexstoichmat
+complexoutgoingmat
+fluxmat
+adjacencymat
+laplacianmat
+massactionvector
+linkageclasses
+deficiency
+linkagedeficiencies
+satisfiesdeficiencyone
+satisfiesdeficiencyzero
+subnetworks
+isreversible
+isweaklyreversible
+iscomplexbalanced
+isdetailedbalanced
+robustspecies
+reset_networkproperties!
+```
diff --git a/docs/src/index.md b/docs/src/index.md
index 419b905ec7..deb373d3ac 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -23,7 +23,7 @@ etc).
 - The [Catalyst.jl API](@ref api) provides functionality for building networks programmatically and for composing multiple networks together.
 - Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref simulation_intro), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ensemble_simulations). Plot recipes are available for [visualization of all solutions](@ref simulation_plotting).
 - Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref dsl_description_stoichiometries_decimal) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref parametric_stoichiometry) for all system types.
-- A [network analysis suite](@ref network_analysis) permits the computation of linkage classes, deficiencies, reversibility, and other network properties.
+- A [network analysis suite](@ref network_analysis_structural_aspects) permits the computation of linkage classes, deficiencies, reversibility, and other network properties.
 - [Conservation laws can be detected and utilized](@ref conservation_laws) to reduce system sizes, and to generate non-singular Jacobians (e.g. during conversion to ODEs, SDEs, and steady state equations).
 - Catalyst reaction network models can be [coupled with differential and algebraic equations](@ref constraint_equations_coupling_constraints) (which are then incorporated during conversion to ODEs, SDEs, and steady state equations).
 - Models can be [coupled with events](@ref constraint_equations_events) that affect the system and its state during simulations.
diff --git a/docs/src/model_creation/network_analysis.md b/docs/src/model_creation/network_analysis.md
deleted file mode 100644
index 8085de1b60..0000000000
--- a/docs/src/model_creation/network_analysis.md
+++ /dev/null
@@ -1,496 +0,0 @@
-# [Network Analysis in Catalyst](@id network_analysis)
-
-In this tutorial we introduce several of the Catalyst API functions for network
-analysis. A complete summary of the exported functions is given in the API
-section
-[`Network-Analysis-and-Representations`](https://docs.sciml.ai/Catalyst/stable/api/catalyst_api/#Network-Analysis-and-Representations).
-
-Note, currently API functions for network analysis and conservation law analysis
-do not work with constant species (currently only generated by SBMLToolkit).
-
-## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
-We first load Catalyst and construct our model of the repressilator
-```@example s1
-using Catalyst
-repressilator = @reaction_network Repressilator begin
-       hillr(P₃,α,K,n), ∅ --> m₁
-       hillr(P₁,α,K,n), ∅ --> m₂
-       hillr(P₂,α,K,n), ∅ --> m₃
-       (δ,γ), m₁ <--> ∅
-       (δ,γ), m₂ <--> ∅
-       (δ,γ), m₃ <--> ∅
-       β, m₁ --> m₁ + P₁
-       β, m₂ --> m₂ + P₂
-       β, m₃ --> m₃ + P₃
-       μ, P₁ --> ∅
-       μ, P₂ --> ∅
-       μ, P₃ --> ∅
-end
-```
-In the [Introduction to Catalyst](@ref introduction_to_catalyst)
-tutorial we showed how the above network could be visualized as a
-species-reaction graph. There, species are represented by the nodes of the graph
-and edges show the reactions in which a given species is a substrate or product.
-```@example s1
-using Catalyst, GraphMakie, NetworkLayout, CairoMakie
-g = plot_network(repressilator)
-```
-
-We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
-the reaction rate equation ODE model for the repressilator is
-```math
-\begin{aligned}
-\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
-\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
-\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
-\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
-\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
-\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
-\end{aligned}
-```
-
-## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
-In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
-be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
-we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
-```math
-\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
-```
-For the repressilator, ``\mathbf{x}(t)`` is just
-```@example s1
-x = species(repressilator)
-```
-The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
-```math
-\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
-```
-where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
-stoichiometric coefficient of species ``m`` in reaction ``k``.
-``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
-``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
-for the first reaction of the repressilator above, the rate law is
-```math
-v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
-```
-We can calculate each of these in Catalyst via
-```@example s1
-N = netstoichmat(repressilator)
-```
-and by using the [`oderatelaw`](@ref) function
-```@example s1
-rxs = reactions(repressilator)
-ν = oderatelaw.(rxs)
-```
-Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
-broadcasting to apply it to each element of `rxs`.
-
-Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
-generates by converting to an `ODESystem`
-```@example s1
-osys = convert(ODESystem, repressilator)
-
-# for display purposes we just pull out the right side of the equations
-odes = [eq.rhs for eq in equations(osys)]
-```
-whereas our matrix-vector representation gives
-```@example s1
-odes2 = N * ν
-```
-Let's check these are equal symbolically
-```@example s1
-isequal(odes, odes2)
-```
-
-## [Reaction complex representation](@id network_analysis_reaction_complexes)
-We now introduce a further decomposition of the RRE ODEs, which has been used to
-facilitate analysis of a variety of reaction network properties. Consider a simple
-reaction system like
-```@example s1
-rn = @reaction_network begin
- k*A, 2*A + 3*B --> A + 2*C + D
- b, C + D --> 2*A + 3*B
-end
-```
-We can think of the first reaction as converting the *reaction complex*,
-``2A+3B`` to the complex ``A+2C+D`` with rate ``kA``. Suppose we order our
-species the same way as Catalyst does, i.e.
-```math
-\begin{pmatrix}
-x_1(t)\\
-x_2(t)\\
-x_3(t)\\
-x_4(t)
-\end{pmatrix} =
-\begin{pmatrix}
-A(t)\\
-B(t)\\
-C(t)\\
-D(t)
-\end{pmatrix},
-```
-which should be the same as
-```@example s1
-species(rn)
-```
-We can describe a given reaction complex by the stoichiometric coefficients of
-each species within the complex. For the reactions in `rn` these vectors would
-be
-```math
-\begin{align*}
-2A+3B = \begin{pmatrix}
-2\\
-3\\
-0\\
-0
-\end{pmatrix}, &&
-A+2C+D = \begin{pmatrix}
-1\\
-0\\
-2\\
-1
-\end{pmatrix},
- &&
-C+D = \begin{pmatrix}
-0\\
-0\\
-1\\
-1
-\end{pmatrix}
-\end{align*}
-```
-Catalyst can calculate these representations as the columns of the complex
-stoichiometry matrix,
-```@example s1
-Z = complexstoichmat(rn)
-```
-If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
-giving the stoichiometric coefficient of species ``m`` within complex ``c``.
-
-We can use this representation to provide another representation of the RRE
-ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
-is called the incidence matrix of the reaction network,
-```@example s1
-B = incidencemat(rn)
-```
-Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
-appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
-substrate of reaction ``k``.
-
-Using our decomposition of ``N``, the RRE ODEs become
-```math
-\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
-```
-Let's verify that ``N = Z B``,
-```@example s1
-N = netstoichmat(rn)
-N == Z*B
-```
-
-Reaction complexes give an alternative way to visualize a reaction network
-graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
-a network and then show how they are related. For example,
-```@example s1
-using Catalyst, GraphMakie, NetworkLayout, CairoMakie
-plot_complexes(rn)
-```
-
-while for the repressilator we find
-```@example s1
-plot_complexes(repressilator)
-```
-
-Here ∅ represents the empty complex, black arrows show reactions converting
-substrate complexes into product complexes where the rate is just a number or
-parameter, and red arrows indicate the conversion of substrate complexes into
-product complexes where the rate is an expression involving chemical species.
-
-## [Aspects of reaction network structure](@id network_analysis_structural_aspects)
-The reaction complex representation can be exploited via [Chemical Reaction
-Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory)
-to provide insight into possible steady state and time-dependent properties of
-RRE ODE models and stochastic chemical kinetics models. We'll now illustrate
-some of the types of network properties that Catalyst can determine, using the
-reaction complex representation in these calculations.
-
-Consider the following reaction network.
-```@example s1
-rn = @reaction_network begin
-    (k1,k2), A + B <--> C
-    k3, C --> D+E
-    (k4,k5), D+E <--> F
-    (k6,k7), 2A <--> B+G
-    k8, B+G --> H
-    k9, H --> 2A
-end
-```
-with graph
-```@example s1
-plot_complexes(rn)
-```
-
-
-#### [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
-The preceding reaction complex graph shows that `rn` is composed of two
-disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
-``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
-``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
-of the reaction network. The function [`linkageclasses`](@ref) will calculate
-these for a given network, returning a vector of the integer indices of reaction
-complexes participating in each set of linkage-classes. Note, indices of
-reaction complexes can be determined from the ordering returned by
-[`reactioncomplexes`](@ref).
-```@example s1
-# we must first calculate the reaction complexes -- they are cached in rn
-reactioncomplexes(rn)
-
-# now we can calculate the linkage classes
-lcs = linkageclasses(rn)
-```
-It can often be convenient to obtain the disconnected sub-networks as distinct
-`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
-```@example s1
-subnets = subnetworks(rn)
-
-# check the reactions in each subnetwork
-reactions.(subnets)
-```
-The graphs of the reaction complexes in the two sub-networks are then
-```@example s1
-  plot_complexes(subnets[1])
-```
-
-and,
-```@example s1
- plot_complexes(subnets[2])
-```
-
-#### [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
-A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
-Theorem [^1], allows us to use knowledge of the net stoichiometry matrix and the
-linkage classes of a *mass action* RRE ODE system to draw conclusions about the
-system's possible steady states. In this section we'll see how Catalyst can
-calculate a network's deficiency.
-
-The rank, ``r``, of a reaction network is defined as the dimension of the
-subspace spanned by the net stoichiometry vectors of the reaction-network [^1],
-i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
-to the number of independent species in a chemical reaction network. That is, if
-we calculate the linear conservation laws of a network, and use them to
-eliminate the dependent species of the network, we will have ``r`` independent
-species remaining. For our current example the conservation laws are given by
-```@example s1
-# first we calculate the conservation laws -- they are cached in rn
-conservationlaws(rn)
-
-# then we display them as equations for the dependent variables
-conservedequations(rn)
-show(stdout, MIME"text/plain"(), ans) # hide
-```
-Here the parameters `Γ[i]` represent the constants of the three
-conservation laws, and we see that there are three dependent species that could
-be eliminated. As
-```@example s1
-numspecies(rn)
-```
-we find that there are five independent species. Let's check this is correct:
-```@example s1
-using LinearAlgebra
-rank(netstoichmat(rn)) == 5
-```
-So we know that the rank of our reaction network is five. An extended section discussing how to
-utilise conservation law elimination during chemical reaction network modelling can be found
-[here](@ref conservation_laws).
-
-The deficiency, ``\delta``, of a reaction network is defined as
-```math
-\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
-```
-For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
-```@example s1
-# first we calculate the reaction complexes of rn and cache them in rn
-reactioncomplexes(rn)
-
-# then we can calculate the deficiency
-δ = deficiency(rn)
-```
-Quoting Feinberg [^1]
-> Deficiency zero networks are ones for which the reaction vectors [i.e. net
-> stoichiometry vectors] are as independent as the partition of complexes into
-> linkage classes will allow.
-
-#### [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
-A reaction network is *reversible* if the "arrows" of the reactions are
-symmetric so that every reaction is accompanied by its reverse reaction.
-Catalyst's API provides the [`isreversible`](@ref) function to determine whether
-a reaction network is reversible. As an example, consider
-```@example s1
-rn = @reaction_network begin
-  (k1,k2),A <--> B
-  (k3,k4),A + C <--> D
-  (k5,k6),D <--> B+E
-  (k7,k8),B+E <--> A+C
-end
-
-# calculate the set of reaction complexes
-reactioncomplexes(rn)
-
-# test if the system is reversible
-isreversible(rn)
-```
-Consider another example,
-```@example s1
-rn = @reaction_network begin
-  (k1,k2),A <--> B
-  k3, A + C --> D
-  k4, D --> B+E
-  k5, B+E --> A+C
-end
-reactioncomplexes(rn)
-isreversible(rn)
-```
-```@example s1
-plot_complexes(rn)
-```
-
-It is evident from the preceding graph that the network is not reversible.
-However, it satisfies a weaker property in that there is a path from each
-reaction complex back to itself within its associated subgraph. This is known as
-*weak reversibility*. One can test a network for weak reversibility by using
-the [`isweaklyreversible`](@ref) function:
-```@example s1
-# need subnetworks from the reaction network first
-subnets = subnetworks(rn)
-isweaklyreversible(rn, subnets)
-```
-Every reversible network is also weakly reversible, but not every weakly
-reversible network is reversible.
-
-#### [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
-Knowing the deficiency and weak reversibility of a mass action chemical reaction
-network ODE model allows us to make inferences about the corresponding
-steady state behavior. Before illustrating how this works for one example, we
-need one last definition.
-
-Recall that in the matrix-vector representation for the RRE ODEs, the entries,
-``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
-``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
-column of this matrix, this vector corresponds to the change in the species
-state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
-occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
-integrating the ODE
-```math
-\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
-```
-we find
-```math
-\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
-```
-which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
-linear combination of the stoichiometry vectors, i.e.
-```math
-\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
-```
-In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
-``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
-``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
-solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
-subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
-physical values for the solution, ``\mathbf{x}(t)``, must be in the set
-```math
-(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
-```
-This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
-The key property of stoichiometric compatibility classes is that they are
-invariant under the RRE ODE's dynamics, i.e. a solution will always remain
-within the subspace given by the stoichiometric compatibility class. Finally, we
-note that the *positive* stoichiometric compatibility class generated by
-$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
-\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
-``\mathbb{R}^M`` with strictly positive components.
-
-With these definitions we can now see how knowing the deficiency and weak
-reversibility of the network can tell us about its steady state behavior.
-Consider the previous example, which we know is weakly reversible. Its
-deficiency is
-```@example s1
-deficiency(rn)
-```
-We also verify that the system is purely mass action (though it is apparent
-from the network's definition):
-```@example s1
-all(rx -> ismassaction(rx, rn), reactions(rn))
-```
-We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
-system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
-a mass action network with deficiency zero satisfies
-1. If the network is weakly reversible, then independent of the reaction rate
-   constants the RRE ODEs have exactly one equilibrium solution within each
-   positive stoichiometric compatibility class. That equilibrium is locally
-   asymptotically stable.
-2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
-   positive equilibrium solution.
-
-See [^1] for a more precise statement, proof, and additional examples.
-
-We can therefore conclude that for any initial condition that is positive, and
-hence in some positive stoichiometric compatibility class, `rn` will have
-exactly one equilibrium solution which will be positive and locally
-asymptotically stable.
-
-As a final example, consider the following network from [^1]
-```@example s1
-rn = @reaction_network begin
-  (k1,k2),A <--> 2B
-  (k3,k4), A + C <--> D
-  k5, B+E --> C + D
-end
-reactioncomplexes(rn)
-subnets = subnetworks(rn)
-isma = all(rx -> ismassaction(rx,rn), reactions(rn))
-def = deficiency(rn)
-iswr = isweaklyreversible(rn, subnets)
-isma,def,iswr
-```
-which we see is mass action and has deficiency zero, but is not weakly
-reversible. As such, we can conclude that for any choice of rate constants the
-RRE ODEs cannot have a positive equilibrium solution.
-
-## [Caching of Network Properties in `ReactionSystems`](@id network_analysis_caching_properties)
-When calling many of the network API functions, Catalyst calculates and caches
-in `rn` a variety of information. For example the first call to
-```julia
-rcs,B = reactioncomplexes(rn)
-```
-calculates, caches, and returns the reaction complexes, `rcs`, and the incidence
-matrix, `B`, of `rn`. Subsequent calls simply return `rcs` and `B` from the
-cache.
-
-Similarly, the first call to
-```julia
-N = netstoichmat(rn)
-```
-calculates, caches and returns the net stoichiometry matrix. Subsequent calls
-then simply return the cached value of `N`. Caching such information means users
-do not need to manually know which subsets of network properties are needed for
-a given calculation (like the deficiency). Generally only
-```julia
-rcs,B = reactioncomplexes(rn)    # must be called once to cache rcs and B
-any_other_network_property(rn)
-```
-should work to calculate a desired network property, with the API doc strings
-indicating when `reactioncomplexes(rn)` must be called at least once before a
-given function is used.
-
-Because of the caching of network properties, subsequent calls to most API
-functions will be fast, simply returning the previously calculated and cached
-values. In some cases it may be desirable to reset the cache and recalculate
-these properties. This can be done by calling
-```julia
-Catalyst.reset_networkproperties!(rn)
-```
-Network property functions will then recalculate their associated properties and
-cache the new values the next time they are called.
-
----
-## References
-[^1]: [Feinberg, M. *Foundations of Chemical Reaction Network Theory*, Applied Mathematical Sciences 202, Springer (2019).](https://link.springer.com/book/10.1007/978-3-030-03858-8?noAccess=true)
diff --git a/docs/src/network_analysis/crn_theory.md b/docs/src/network_analysis/crn_theory.md
new file mode 100644
index 0000000000..657ed4efbd
--- /dev/null
+++ b/docs/src/network_analysis/crn_theory.md
@@ -0,0 +1,375 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+There is an API function [`satisfiesdeficiencyzero`](@ref) that will let us check
+all these conditions easily:
+
+```@example s1
+satisfiesdeficiencyzero(def0_rn)
+```
+
+## Deficiency One Theorem
+Very analogous to the deficiency zero theorem is the deficiency one theorem. The deficiency one theorem applies to a network with the following properties: 
+1. The deficiency of each *linkage class* of the network is at most 1, 
+2. The sum of the linkage class deficiencies is the total deficiency of the network, and
+3. Each linkage class has at most one terminal linkage class, which is a linkage class that is 1) strongly connected, and 2) has no outgoing reactions.
+
+For the set of reactions $A \to B, B \to A, B \to C$, $\{A, B, C\}$ is a linkage class,
+and $\{A, B\}$ is a strong linkage class (since A is reachable from B and vice versa).
+However, $\{A, B\}$ is not a terminal linkage class, because the reaction $B \to C$ goes
+to a complex outside the linkage class. 
+
+If these conditions are met, then the network will have at most one steady state in each 
+stoichiometric compatibility class for any choice of rate constants and parameters. Unlike 
+the deficiency zero theorem, networks obeying the deficiency one theorem are not guaranteed 
+to have stable solutions.
+
+Let's look at an example network.
+
+```@example s1
+def1_network = @reaction_network begin
+    (k1, k2), A <--> 2B
+    k3, C --> 2D
+    k4, 2D --> C + E
+    (k5, k6), C + E <--> E + 2D
+end
+plot_complexes(def1_network)
+```
+
+We can see from the complex graph that there are two linkage classes, the deficiency of the bottom one is zero and the deficiency of the top one is one.
+The total deficiency is one:
+
+```@example s1
+deficiency(def1_network)
+```
+
+And there is only one terminal linkage class in each, so our network satisfies all three conditions.
+As in the deficiency zero case, there is the API function [`satisfiesdeficiencyone`](@ref)
+for quickly checking these conditions:
+
+```@example s1
+satisfiesdeficiencyone(def1_network)
+```
+
+## [Complex and Detailed Balance](@id network_analysis_complex_and_detailed_balance)
+A reaction network's steady state is **complex-balanced** if the total production of each 
+*complex* is zero at the steady state. A reaction network's steady state is **detailed balanced** 
+if every reaction is balanced by its reverse reaction at the steady-state (this corresponds to 
+the usual notion of chemical equilibrium; note that this requires every reaction be reversible). 
+
+Note that detailed balance at a given steady state implies complex balance for that steady state,
+i.e. detailed balance is a stronger property than complex balance.
+
+Remarkably, having just one positive steady state that is complex (detailed) balance implies that 
+complex (detailed) balance holds for *every* positive steady state, so we say that a network 
+is complex (detailed) balanced if any one of its steady states are complex (detailed) balanced. 
+Additionally, there will be exactly one steady state in every positive stoichiometric 
+compatibility class, and this steady state is asymptotically stable. (For proofs of these results,
+please consult Martin Feinberg's *Foundations of Chemical Reaction Network Theory*[^1]). So 
+knowing that a network is complex balanced is really quite powerful.
+
+Let's check whether the deficiency 0 reaction network that we defined above is complex balanced by providing a set of rates:
+```@example s1
+rates = Dict([:k1 => 2.4, :k2 => 4., :k3 => 10., :k4 => 5.5, :k5 => 0.4])
+iscomplexbalanced(rn, rates)
+```
+
+We can do a similar check for detailed balance.
+```@example s1
+isdetailedbalanced(rn, rates)
+```
+
+The reason that the deficiency zero theorem puts such strong restrictions on the steady state 
+properties of the reaction network is because it implies that the reaction network will be 
+complex balanced for any set of rate constants and parameters. The fact that this holds 
+from a purely structural property of the graph, regardless of kinetics, is what makes it so 
+useful. But in some cases it might be desirable to check complex balance on its own, as for 
+higher deficiency networks.
+
+```@docs; canonical=false
+iscomplexbalanced
+isdetailedbalanced
+```
+
+## [Concentration Robustness](@id network_analysis_concentration_robustness)
+Certain reaction networks have species that do not change their concentration,
+regardless of whether the system is perturbed to a different stoichiometric compatibility
+class. This is a very useful property to have in biological contexts, where it might
+be important to keep the concentration of a critical species relatively stable in
+the face of changes in its environment. 
+
+Determining every species with concentration-robustness in a network is in general very difficult. However, there are certain cases where there are sufficient conditions
+that can be checked relatively easily. One example is for deficiency one networks.
+
+**Theorem (a sufficient condition for concentration robustness for deficiency one networks)**: If there are two *non-terminal* reaction complexes that differ only in species ``s``, then the system is absolutely concentration robust with respect to ``s``. 
+
+This is the check provided by the API function `robustspecies(rn)`. More general concentration robustness analysis can be done using the forthcoming CatalystNetworkAnalysis package.
+
+```@docs; canonical=false
+robustspecies
+```
+
+---
+## References
+[^1]: [Feinberg, M. *Foundations of Chemical Reaction Network Theory*, Applied Mathematical Sciences 202, Springer (2019).](https://link.springer.com/book/10.1007/978-3-030-03858-8?noAccess=true)
diff --git a/docs/src/network_analysis/network_properties.md b/docs/src/network_analysis/network_properties.md
new file mode 100644
index 0000000000..b3c9cc8f2f
--- /dev/null
+++ b/docs/src/network_analysis/network_properties.md
@@ -0,0 +1,35 @@
+# [Caching of Network Properties in `ReactionSystems`](@id network_analysis_caching_properties)
+When calling many of the network API functions, Catalyst calculates and caches
+in `rn` a variety of information. For example the first call to
+```julia
+rcs,B = reactioncomplexes(rn)
+```
+calculates, caches, and returns the reaction complexes, `rcs`, and the incidence
+matrix, `B`, of `rn`. Subsequent calls simply return `rcs` and `B` from the
+cache.
+
+Similarly, the first call to
+```julia
+N = netstoichmat(rn)
+```
+calculates, caches and returns the net stoichiometry matrix. Subsequent calls
+then simply return the cached value of `N`. Caching such information means users
+do not need to manually know which subsets of network properties are needed for
+a given calculation (like the deficiency). Generally only
+```julia
+rcs,B = reactioncomplexes(rn)    # must be called once to cache rcs and B
+any_other_network_property(rn)
+```
+should work to calculate a desired network property, with the API doc strings
+indicating when `reactioncomplexes(rn)` must be called at least once before a
+given function is used.
+
+Because of the caching of network properties, subsequent calls to most API
+functions will be fast, simply returning the previously calculated and cached
+values. In some cases it may be desirable to reset the cache and recalculate
+these properties. This can be done by calling
+```julia
+Catalyst.reset_networkproperties!(rn)
+```
+Network property functions will then recalculate their associated properties and
+cache the new values the next time they are called.
diff --git a/docs/src/network_analysis/odes.md b/docs/src/network_analysis/odes.md
new file mode 100644
index 0000000000..adf8fa7e4d
--- /dev/null
+++ b/docs/src/network_analysis/odes.md
@@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst, CairoMakie, GraphMakie, NetworkLayout
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+
+```@example s1
+g = plot_network(repressilator)
+```
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```@example s1
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t).
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$ denotes the mass-action product of the substrate complex $y$ from the $y \rightarrow y'$ reaction. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In summary, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t) \\ 
+&= Z A_k \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+All three of the objects introduced in this section (the flux matrix, mass-action vector, and Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
+For example, `massactionvector` will return a numerical output if a set of species concentrations is supplied using a dictionary, tuple, or vector of Symbol-value pairs.
+```@example s1
+concmap = Dict([:A => 3., :B => 5., :C => 2.4, :D => 1.5])
+massactionvector(rn, concmap)
+```
+
+`fluxmat` and `laplacianmat` will return numeric matrices if a set of rate constants and other parameters are supplied the same way.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+fluxmat(rn, parammap)
+```
+
+```@example s1
+laplacianmat(rn, parammap)
+```
+
+## Symbolic ODE functions
+In some cases it might be useful to generate the function defining the system of ODEs as a symbolic Julia function that can be used for further analysis. This can be done using Symbolics' [`build_function`](https://docs.sciml.ai/Symbolics/stable/getting_started/#Building-Functions), which takes a symbolic expression and a set of desired arguments, and converts it into a Julia function taking those arguments.
+
+Let's build the full symbolic function corresponding to our ODE system. `build_function` will return two expressions, one for a function that outputs a new vector for the result, and one for a function that modifies the input in-place. Either expression can then be evaluated to return a Julia function.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+K = fluxmat(rn, parammap)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6.]
+f(c)
+```
+The generated `f` now corresponds to the $f(\mathbf{x}(t))$ on the right-hand side of $\frac{d\mathbf{x}(t)}{dt} = f(\mathbf{x}(t))$. Given a vector of species concentrations $c$, `f` will return the rate of change of each species. Steady state concentration vectors `c_ss` will satisfy `f(c_ss) = zeros(length(species(rn)))`.
+
+Above we have generated a numeric rate matrix to substitute the rate constants into the symbolic expressions. We could have used a symbolic rate matrix, but then we would need to define the parameters `k, b`, so that the function `f` knows what `k` and `b` in its output refer to.
+```@example s1
+@parameters k b 
+K = fluxmat(rn)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6.]
+f(c)
+```
+
+Alternatively, if we use a symbolic rate matrix, we could define our function to take in both species concentrations and parameter values as arguments:
+```@example s1
+K = fluxmat(rn)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn), parameters(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6]; ks = [12., 4.]
+f(c, ks)
+```
+
+Note also that `f` can take any vector with the right dimension (i.e. the number of species), not just a vector of `Number`, so it can be used to build, e.g. a vector of polynomials in Nemo for commutative algebraic methods.
+
+## Properties of matrix null spaces
+The null spaces of the matrices discussed in this section often have special meaning. Below we will discuss some of these properties.
+
+Recall that we may write the net stoichiometry matrix ``N = ZB``, where `Z` is the complex stoichiometry matrix and `B` is the incidence matrix of the graph.
+
+[Conservation laws](@ref conservation_laws) arise as left null eigenvectors of the net stoichiometry matrix ``N``, and cycles arise as right null eigenvectors of the stoichiometry matrix. A cycle may be understood as a sequence of reactions that leaves the overall species composition unchanged. These do not necessarily have to correspond to actual cycles in the graph.
+
+[Complex balance](@ref network_analysis_complex_and_detailed_balance) can be compactly formulated as the following: a set of steady state reaction fluxes is complex-balanced if it is in the nullspace of the incidence matrix ``B``.
+
+## API Section for matrices and vectors
+We have that: 
+- ``N`` is the `netstoichmat`
+- ``Z`` is the `complexstoichmat`
+- ``B`` is the `incidencemat`
+- ``K`` is the `fluxmat`
+- ``A_k`` is the `laplacianmat`
+- ``\Phi`` is the `massactionvector`
+
+```@docs; canonical=false
+netstoichmat
+complexstoichmat
+incidencemat
+fluxmat
+laplacianmat
+massactionvector
+```
+
+---
+## References
+[^1]: [Feinberg, M. *Foundations of Chemical Reaction Network Theory*, Applied Mathematical Sciences 202, Springer (2019).](https://link.springer.com/book/10.1007/978-3-030-03858-8?noAccess=true)
diff --git a/src/Catalyst.jl b/src/Catalyst.jl
index c8c2af25c6..57c244977e 100644
--- a/src/Catalyst.jl
+++ b/src/Catalyst.jl
@@ -110,11 +110,13 @@ export @reaction_network, @network_component, @reaction, @species
 # Network analysis functionality.
 include("network_analysis.jl")
 export reactioncomplexmap, reactioncomplexes, incidencemat
-export complexstoichmat, laplacianmat, fluxmat, massactionvector, complexoutgoingmat
+export complexstoichmat, laplacianmat, fluxmat, massactionvector, complexoutgoingmat, adjacencymat
 export incidencematgraph, linkageclasses, stronglinkageclasses,
        terminallinkageclasses, deficiency, subnetworks
 export linkagedeficiencies, isreversible, isweaklyreversible
 export conservationlaws, conservedquantities, conservedequations, conservationlaw_constants
+export satisfiesdeficiencyone, satisfiesdeficiencyzero
+export iscomplexbalanced, isdetailedbalanced, robustspecies
 
 # registers CRN specific functions using Symbolics.jl
 include("registered_functions.jl")
diff --git a/src/network_analysis.jl b/src/network_analysis.jl
index 6a508269ad..ee78f7eced 100644
--- a/src/network_analysis.jl
+++ b/src/network_analysis.jl
@@ -195,10 +195,11 @@ end
 @doc raw"""
     laplacianmat(rn::ReactionSystem, pmap=Dict(); sparse=false)
 
-    Return the negative of the graph Laplacian of the reaction network. The ODE system of a chemical reaction network can be factorized as ``\frac{dx}{dt} = Y A_k Φ(x)``, where ``Y`` is the [`complexstoichmat`](@ref) and ``A_k`` is the negative of the graph Laplacian, and ``Φ`` is the [`massactionvector`](@ref). ``A_k`` is an n-by-n matrix, where n is the number of complexes, where ``A_{ij} = k_{ij}`` if a reaction exists between the two complexes and 0 otherwise.
-    Returns a symbolic matrix by default, but will return a numerical matrix if parameter values are specified via pmap.
+Return the negative of the graph Laplacian of the reaction network. The ODE system of a chemical reaction network can be factorized as ``\frac{dx}{dt} = Y A_k Φ(x)``, where ``Y`` is the [`complexstoichmat`](@ref) and ``A_k`` is the negative of the graph Laplacian, and ``Φ`` is the [`massactionvector`](@ref). ``A_k`` is an n-by-n matrix, where n is the number of complexes, where ``A_{ij} = k_{ij}`` if a reaction exists between the two complexes and 0 otherwise.
+
+Returns a symbolic matrix by default, but will return a numerical matrix if parameter values are specified via pmap. 
 
-    **Warning**: Unlike other Catalyst functions, the `laplacianmat` function will return a `Matrix{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs, and to ensure that multiplying the sparse form of the matrix will work.
+**Warning**: Unlike other Catalyst functions, the `laplacianmat` function will return a `Matrix{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs, and to ensure that multiplying the sparse form of the matrix will work.
 """
 function laplacianmat(rn::ReactionSystem, pmap::Dict = Dict(); sparse = false)
     D = incidencemat(rn; sparse)
@@ -209,10 +210,10 @@ end
 @doc raw"""
     fluxmat(rn::ReactionSystem, pmap = Dict(); sparse=false)
 
-    Return an r×c matrix ``K`` such that, if complex ``j`` is the substrate complex of reaction ``i``, then ``K_{ij} = k``, the rate constant for this reaction. Mostly a helper function for the network Laplacian, [`laplacianmat`](@ref). Has the useful property that ``\frac{dx}{dt} = S*K*Φ(x)``, where S is the [`netstoichmat`](@ref) or net stoichiometry matrix and ``Φ(x)`` is the [`massactionvector`](@ref).
+Return an r×c matrix ``K`` such that, if complex ``j`` is the substrate complex of reaction ``i``, then ``K_{ij} = k``, the rate constant for this reaction. Mostly a helper function for the network Laplacian, [`laplacianmat`](@ref). Has the useful property that ``\frac{dx}{dt} = S*K*Φ(x)``, where S is the [`netstoichmat`](@ref) or net stoichiometry matrix and ``Φ(x)`` is the [`massactionvector`](@ref).
     Returns a symbolic matrix by default, but will return a numerical matrix if rate constants are specified as a `Tuple`, `Vector`, or `Dict` of symbol-value pairs via `pmap`.
 
-    **Warning**: Unlike other Catalyst functions, the `fluxmat` function will return a `Matrix{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs, and to ensure that multiplying the sparse form of the matrix will work.
+**Warning**: Unlike other Catalyst functions, the `fluxmat` function will return a `Matrix{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs, and to ensure that multiplying the sparse form of the matrix will work.
 """
 function fluxmat(rn::ReactionSystem, pmap::Dict = Dict(); sparse=false)
     deps = Set()
@@ -286,11 +287,13 @@ end
 """
     massactionvector(rn::ReactionSystem, scmap = Dict(); combinatoric_ratelaws = true)
 
-    Return the vector whose entries correspond to the "mass action products" of each complex. For example, given the complex A + B, the corresponding entry of the vector would be ``A*B``, and for the complex 2X + Y, the corresponding entry would be ``X^2*Y``. The ODE system of a chemical reaction network can be factorized as ``\frac{dx}{dt} = Y A_k Φ(x)``, where ``Y`` is the [`complexstoichmat`](@ref) and ``A_k`` is the negative of the [`laplacianmat`](@ref). This utility returns ``Φ(x)``.
-    Returns a symbolic vector by default, but will return a numerical vector if species concentrations are specified as a tuple, vector, or dictionary via scmap.
-    If the `combinatoric_ratelaws` option is set, will include prefactors for that (see [introduction to Catalyst's rate laws](@ref introduction_to_catalyst_ratelaws). Will default to the default for the system.
+Return the vector whose entries correspond to the "mass action products" of each complex. For example, given the complex A + B, the corresponding entry of the vector would be ``A*B``, and for the complex 2X + Y, the corresponding entry would be ``X^2*Y``. The ODE system of a chemical reaction network can be factorized as ``\frac{dx}{dt} = Y A_k Φ(x)``, where ``Y`` is the [`complexstoichmat`](@ref) and ``A_k`` is the negative of the [`laplacianmat`](@ref). This utility returns ``Φ(x)``.
+
+Returns a symbolic vector by default, but will return a numerical vector if species concentrations are specified as a tuple, vector, or dictionary via scmap.
+
+If the `combinatoric_ratelaws` option is set, will include prefactors for that (see [introduction to Catalyst's rate laws](@ref introduction_to_catalyst_ratelaws). Will default to the default for the system.
 
-    **Warning**: Unlike other Catalyst functions, the `massactionvector` function will return a `Vector{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs.
+**Warning**: Unlike other Catalyst functions, the `massactionvector` function will return a `Vector{Num}` in the symbolic case. This is to allow easier computation of the matrix decomposition of the ODEs.
 """
 function massactionvector(rn::ReactionSystem, scmap::Dict = Dict(); combinatoric_ratelaws = Catalyst.get_combinatoric_ratelaws(rn))
     r = numreactions(rn)
@@ -1224,7 +1227,7 @@ end
 """
     satisfiesdeficiencyone(rn::ReactionSystem)
 
-    Check if a reaction network obeys the conditions of the deficiency one theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class.
+Check if a reaction network obeys the conditions of the deficiency one theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class.
 """
 function satisfiesdeficiencyone(rn::ReactionSystem)
     all(r -> ismassaction(r, rn), reactions(rn)) ||
@@ -1246,7 +1249,7 @@ end
 """
     satisfiesdeficiencyzero(rn::ReactionSystem)
 
-    Check if a reaction network obeys the conditions of the deficiency zero theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class, this equilibrium is asymptotically stable, and this equilibrium is complex balanced.
+Check if a reaction network obeys the conditions of the deficiency zero theorem, which ensures that there is only one equilibrium for every positive stoichiometric compatibility class, this equilibrium is asymptotically stable, and this equilibrium is complex balanced.
 """
 function satisfiesdeficiencyzero(rn::ReactionSystem)
     all(r -> ismassaction(r, rn), reactions(rn)) ||


Reviews:
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-06T16:59:44Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-06T17:01:37Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-06T17:03:50Z
user: login: vyudu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-10T00:57:08Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:46:41Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:48:57Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:49:38Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:52:18Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:52:57Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:54:17Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:55:29Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:56:27Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T21:58:21Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:01:31Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:03:24Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:07:06Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:07:24Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:09:32Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:13:33Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:16:10Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:17:54Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:18:02Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:18:17Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:18:24Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:21:38Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T22:25:40Z
user: login: vyudu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T03:27:49Z
user: login: isaacsas
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-12T01:01:27Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T18:11:36Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T18:12:52Z
user: login: TorkelE
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T18:18:13Z
user: login: vyudu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T21:18:44Z

Review comments:
diff_hunk: @@ -0,0 +1,4 @@
+# [Advanced Analysis using CatalystNetworkAnalysis](@id catalyst_network_analysis)
path: docs/src/network_analysis/advanced_analysis.md
user: login: TorkelE
body: Just checking, is this a wip? If not, having a direct link to that package would definitely be a good start.
created_at: 2025-02-06T16:59:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,352 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: Maybe we should add a link to the intro page that Sam wrote describing how these ODEs are generated?
created_at: 2025-02-06T17:01:37Z
author_association: MEMBER
diff_hunk: @@ -1,219 +1,26 @@
-# [Network Analysis in Catalyst](@id network_analysis)
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
path: docs/src/network_analysis/crnt.md
user: login: TorkelE
body: I'd probably call the file "crn_theory.md", more descriptive at a first glance for people unfamiliar with it.
(but feel free to keep current name as well!)
created_at: 2025-02-06T17:03:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,4 @@
+# [Advanced Analysis using CatalystNetworkAnalysis](@id catalyst_network_analysis)
path: docs/src/network_analysis/advanced_analysis.md
user: login: vyudu
body: Do I need a release for the package to be able to publish docs for it? 
created_at: 2025-02-10T00:57:07Z
author_association: MEMBER
diff_hunk: @@ -86,9 +86,9 @@ ReactionSystem
 ```
 
 ## [Options for the `@reaction_network` DSL](@id api_dsl_options)
-We have [previously described](@ref dsl_advanced_options) how options allow one to supply additional information to a [`ReactionSystem`](@ref) created with the DSL. Here follows a list
+We have [previously described](@ref dsl_advanced_options) how options permits the user to supply non-reaction information to [`ReactionSystem`](@ref) created through the DSL. Here follows a list
 of all options currently available.
-- [`parameters`](@ref dsl_advanced_options_declaring_species_and_parameters): Allows the designation of a set of symbols as system parameters.
+- [`parameters`]:(@ref dsl_advanced_options_declaring_species_and_parameters) Allows the designation of a set of symbols as system parameter.
path: docs/src/api/core_api.md
user: login: TorkelE
body: these two changes seems wrong?
created_at: 2025-04-02T21:46:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,41 @@
+# [Network analysis and representations](@id api_network_analysis)
+```@meta
+CurrentModule = Catalyst
+```
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
path: docs/src/api/network_analysis_api.md
user: login: TorkelE
body: We allow the definition of constant species in Catalyst (https://docs.sciml.ai/Catalyst/stable/model_creation/dsl_advanced/#dsl_advanced_options_constant_species)?

Also, maybe say SBML generally (I think currently SBMLImporters is the more reliable SBML importer. I think i both covers more cases and also handles jump systems a lot better) 
created_at: 2025-04-02T21:48:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,41 @@
+# [Network analysis and representations](@id api_network_analysis)
+```@meta
+CurrentModule = Catalyst
+```
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+For more information about these functions, please see the sections of the docs on
+[network ODE representation](@ref network_analysis_odes) and [chemical reaction network theory](@ref network_analysis_structural_aspects).
+
+```@docs
+conservationlaws
+conservedquantities
+conservedequations
+conservationlaw_constants
+ReactionComplexElement
+ReactionComplex
+reactioncomplexmap
+reactioncomplexes
+incidencemat
+incidencematgraph
+complexstoichmat
+complexoutgoingmat
+fluxmat
+adjacencymat
+laplacianmat
+massactionvector
+linkageclasses
+deficiency
+linkagedeficiencies
+satisfiesdeficiencyone
+satisfiesdeficiencyzero
+subnetworks
+isreversible
+isweaklyreversible
+iscomplexbalanced
+isdetailedbalanced
+robustspecies
+reset_networkproperties!
+```
path: docs/src/api/network_analysis_api.md
user: login: TorkelE
body: Might be useful (for some, although personally I do not use API that way really) to split into sections? Only if there actually is an easy split that makes sense.
created_at: 2025-04-02T21:49:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,35 @@
+# [Caching of Network Properties in `ReactionSystems`](@id network_analysis_caching_properties)
+When calling many of the network API functions, Catalyst calculates and caches
+in `rn` a variety of information. For example the first call to
+```julia
+rcs,B = reactioncomplexes(rn)
+```
path: docs/src/network_analysis/network_properties.md
user: login: TorkelE
body: Could this be made dynamic by creating a simple `rn` model and then applying these on it? The output doesn't;t really matter, so can add `nothing # hide` at the end of each block.
created_at: 2025-04-02T21:52:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: same as previous comment
created_at: 2025-04-02T21:52:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Introduction to Catalyst](@ref introduction_to_catalyst)
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: Since when we initially wrote this, we have a dedicated tutorial and section for this kind of visualisation: https://docs.sciml.ai/Catalyst/stable/model_creation/model_visualisation/#visualisation_graphs. Might make sense to link that directly instead?
created_at: 2025-04-02T21:54:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Introduction to Catalyst](@ref introduction_to_catalyst)
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: (admitedly that repressilator is implemented using a slightly simplified network)
created_at: 2025-04-02T21:55:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Introduction to Catalyst](@ref introduction_to_catalyst)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation ODE model for the repressilator is
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: first mention of the RRE, maybe change this to "reaction rate equation (RRE)" and use rre later on?
created_at: 2025-04-02T21:56:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Introduction to Catalyst](@ref introduction_to_catalyst)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2*A + 3*B --> A + 2*C + D
+ b, C + D --> 2*A + 3*B
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: Think, generally, we have used `k, 2A + 3B --> A + 2C + D` (no `*`) across the docs. But feel free to use your preferred notation.
created_at: 2025-04-02T21:58:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Introduction to Catalyst](@ref introduction_to_catalyst)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2*A + 3*B --> A + 2*C + D
+ b, C + D --> 2*A + 3*B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants.
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: I might add a parenthesis or something here explaining that `==` does not work for symbolic variables (i.e. `Num`s). Don't think this is common knowledge to many new-to intermediate users.
created_at: 2025-04-02T22:01:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,359 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (currently only generated by SBMLToolkit).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Introduction to Catalyst](@ref introduction_to_catalyst)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2*A + 3*B --> A + 2*C + D
+ b, C + D --> 2*A + 3*B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants.
+
+In sum, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+&= Z A_k \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+All three of the objects introduced in this section (the flux matrix, mass-action vector, Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
+For example, `massactionvector` will return a numerical output if a set of species concentrations is supplied using a dictionary, tuple, or vector of Symbol-value pairs.
+```@example s1
+concmap = Dict([:A => 3., :B => 5., :C => 2.4, :D => 1.5])
+massactionvector(rn, concmap)
+```
+
+`fluxmat` and `laplacianmat` will return numeric matrices if a set of rate constants and other aprameters are supplied the same way.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+fluxmat(rn, parammap)
+```
+
+```@example s1
+laplacianmat(rn, parammap)
+```
+
+# Symbolic ODE functions
path: docs/src/network_analysis/odes.md
user: login: TorkelE
body: I think so far (not sure by chance or planning, but I stick to it these days) we only use `# Title` for the first header of each doc page. Then subsequent ones uses `## Sub heading` and `### Subsub heading` and such.
created_at: 2025-04-02T22:03:24Z
author_association: MEMBER
diff_hunk: @@ -681,10 +681,12 @@ end
 # Reads the variables options. Outputs a list of the variables inferred from the equations,
 # as well as the equation vector. If the default differential was used, update the `diffsexpr`
 # expression so that this declares this as well.
-function read_equations_option!(diffsexpr, options, syms_unavailable, tiv; requiredec = false)
+function read_equations_option!(
+        diffsexpr, options, syms_unavailable, tiv; requiredec = false)
path: src/dsl.jl
user: login: TorkelE
body: I'd probably make the line break after `tiv;`, if possible.
created_at: 2025-04-02T22:07:06Z
author_association: MEMBER
diff_hunk: @@ -739,7 +741,8 @@ end
 # Reads the observables options. Outputs an expression for creating the observable variables,
 # a vector containing the observable equations, and a list of all observable symbols (this
 # list contains both those declared separately or inferred from the `@observables` option` input`).
-function read_observables_option(options, all_ivs, us_declared, all_syms; requiredec = false)
+function read_observables_option(
+        options, all_ivs, us_declared, all_syms; requiredec = false)
path: src/dsl.jl
user: login: TorkelE
body: I'd probably make the line break after `all_syms;`, if possible.
created_at: 2025-04-02T22:07:24Z
author_association: MEMBER
diff_hunk: @@ -1461,7 +1472,8 @@ Notes:
 - Returns a new `ReactionSystem` and does not modify `rs`.
 - By default, the new `ReactionSystem` will have the same name as `sys`.
 """
-function ModelingToolkit.compose(sys::ReactionSystem, systems::AbstractArray; name = nameof(sys))
+function ModelingToolkit.compose(
+        sys::ReactionSystem, systems::AbstractArray; name = nameof(sys))
path: src/reactionsystem.jl
user: login: TorkelE
body: I'd probably put the line break after `systems::AbstractArray;`, if possible.
created_at: 2025-04-02T22:09:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: I'd probably remove this one, doesn't feel like something we need everywhere, given the niche ness of the situation.

If it is something that can yield a silent error, it might make sense to add it as a warning later on. Howeverstarting the tutorial with a note about something that most users won't know about or encounter seems irrelevant. And if it is restricted to SBMLToolkit the warning might be better there.
created_at: 2025-04-02T22:13:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
+
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. deficiency) to dynamical properties of the reaction system
+(e.g. complex balance). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: Here I'd make the `deficiency` and `complex balance` links to corresponding sections (if nothing else to mark that these are terms we do not necessarily expect the reader to know at this stage).
created_at: 2025-04-02T22:16:10Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
+
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. deficiency) to dynamical properties of the reaction system
+(e.g. complex balance). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem [^1], allows us to use knowledge of the net stoichiometry matrix and the
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: No space in Theorem [^1]
created_at: 2025-04-02T22:17:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
+
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. deficiency) to dynamical properties of the reaction system
+(e.g. complex balance). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem [^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network [^1],
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: No space in Theorem [^1]
created_at: 2025-04-02T22:18:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
+
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. deficiency) to dynamical properties of the reaction system
+(e.g. complex balance). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem [^1], allows us to use knowledge of the net stoichiometry matrix and the
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: I.e. `Theorem[^1]`
created_at: 2025-04-02T22:18:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
+
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. deficiency) to dynamical properties of the reaction system
+(e.g. complex balance). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem [^1], allows us to use knowledge of the net stoichiometry matrix and the
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: (I think)
created_at: 2025-04-02T22:18:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,332 @@
+> Note, currently API functions for network analysis and conservation law analysis
+> do not work with constant species (currently only generated by SBMLToolkit).
+
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. deficiency) to dynamical properties of the reaction system
+(e.g. complex balance). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem [^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network [^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+```@docs; canonical=false
+satisfiesdeficiencyzero
+```
+
+## Deficiency One Theorem
+Very analogous to the deficiency zero theorem is the deficiency one theorem. The deficiency one theorem applies to a network with the following properties: 
+1. The deficiency of each *linkage class* of the network is at most 1, 
+2. The sum of the linkage class deficiencies is the total deficiency of the network, and
+3. Each linkage class has at most one terminal linkage class, which is a linkage class that is 1) strongly connected, and 2) has no outgoing reactions.
+
+For the set of reactions $A \to B, B \to A, B \to C$, $\{A, B, C\}$ is a linkage class, and $\{A, B\}$ is a strong linkage class (since A is reachable from B and vice versa). However, $\{A, B\}$ is not a terminal linkage class, because the reaction $B \to C$ goes to a complex outside the linkage class. 
+
+If these conditions are met, then the network will have at most one steady state in each stoichiometric compatibility class for any choice of rate constants and parameters.
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: no line break
created_at: 2025-04-02T22:21:38Z
author_association: MEMBER
diff_hunk: @@ -25,6 +24,12 @@ pages = Any[
             "model_creation/examples/smoluchowski_coagulation_equation.md"
         ]
     ],
+    "Network Analysis" => Any[
+        "network_analysis/odes.md",
+        "network_analysis/crn_theory.md",
+        "network_analysis/network_properties.md",
+        "network_analysis/advanced_analysis.md"
path: docs/pages.jl
user: login: TorkelE
body: Thinking about this order, having the ODE section first makes sense as it nicely feeds into the next one. On the other hand, most users will likely have more use of the subsequent sections, which makes it make sense to have that earlier.

Maybe keep it for now, and then as this gets more fleshed out we can make a "Introduction to Network" analysis section, which basically explains what this is all about, when it can be useful, and where to find the useful bits. 
(i.e. a new user that know very little should likely first know about the network analysis summary function, and maybe be directed to know about conservation laws).
created_at: 2025-04-02T22:25:40Z
author_association: MEMBER
diff_hunk: @@ -25,6 +24,12 @@ pages = Any[
             "model_creation/examples/smoluchowski_coagulation_equation.md"
         ]
     ],
+    "Network Analysis" => Any[
+        "network_analysis/odes.md",
+        "network_analysis/crn_theory.md",
+        "network_analysis/network_properties.md",
+        "network_analysis/advanced_analysis.md"
path: docs/pages.jl
user: login: vyudu
body: Yeah this sounds like a good idea, will keep it on my plate.
created_at: 2025-04-09T03:27:49Z
author_association: MEMBER
diff_hunk: @@ -19,6 +19,7 @@ Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
 LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
 Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
 ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
+MultiDocumenter = "87ed4bf0-c935-4a67-83c3-2a03bee4197c"
path: docs/Project.toml
user: login: isaacsas
body: ```suggestion
```
created_at: 2025-04-11T20:33:46Z
author_association: MEMBER
diff_hunk: @@ -2,6 +2,7 @@ using Documenter
 using Catalyst, ModelingToolkit
 # Add packages for plotting
 using GraphMakie, CairoMakie
+#using MultiDocumenter
path: docs/make.jl
user: login: isaacsas
body: ```suggestion
```
created_at: 2025-04-11T20:34:02Z
author_association: MEMBER
diff_hunk: @@ -86,7 +86,7 @@ ReactionSystem
 ```
 
 ## [Options for the `@reaction_network` DSL](@id api_dsl_options)
-We have [previously described](@ref dsl_advanced_options) how options allow one to supply additional information to a [`ReactionSystem`](@ref) created with the DSL. Here follows a list
+We have [previously described](@ref dsl_advanced_options) how options permits the user to supply non-reaction information to [`ReactionSystem`](@ref) created through the DSL. Here follows a list
path: docs/src/api/core_api.md
user: login: isaacsas
body: ```suggestion
We have [previously described](@ref dsl_advanced_options) how options permit the user to supply non-reaction information to [`ReactionSystem`](@ref) created through the DSL. Here follows a list
```
created_at: 2025-04-11T20:35:38Z
author_association: MEMBER
path: docs/src/network_analysis/advanced_analysis.md
user: login: isaacsas
body: Just delete this file and the inclusion of it in pages.jl. We can add back when you are ready to fill it in.
created_at: 2025-04-11T20:36:56Z
author_association: MEMBER
diff_hunk: @@ -48,5 +49,32 @@ makedocs(sitename = "Catalyst.jl",
     pagesonly = true,
     warnonly = [:missing_docs])
 
+#clonedir = mktempdir()
+
+#docs = [MultiDocumenter.MultiDocRef(
+#        upstream = joinpath(@__DIR__, "build"),
+#        path = "docs",
+#        name = "Catalyst",
+#        fix_canonical_url = false,
+#        ),
+#        MultiDocumenter.MultiDocRef(
+#        upstream = joinpath(clonedir, "build"),
+#        path = "NetworkAnalysis",
+#        name = "CatalystNetworkAnalysis",
+#        giturl = "https://github.com/SciML/CatalystNetworkAnalysis.jl",
+#        )]
+
path: docs/make.jl
user: login: isaacsas
body: ```suggestion
```
created_at: 2025-04-12T00:28:14Z
author_association: MEMBER
diff_hunk: @@ -48,5 +49,32 @@ makedocs(sitename = "Catalyst.jl",
     pagesonly = true,
     warnonly = [:missing_docs])
 
+#clonedir = mktempdir()
+
+#docs = [MultiDocumenter.MultiDocRef(
+#        upstream = joinpath(@__DIR__, "build"),
+#        path = "docs",
+#        name = "Catalyst",
+#        fix_canonical_url = false,
+#        ),
+#        MultiDocumenter.MultiDocRef(
+#        upstream = joinpath(clonedir, "build"),
+#        path = "NetworkAnalysis",
+#        name = "CatalystNetworkAnalysis",
+#        giturl = "https://github.com/SciML/CatalystNetworkAnalysis.jl",
+#        )]
+
 deploydocs(repo = "github.com/SciML/Catalyst.jl.git";
     push_preview = true)
+
+#outpath = joinpath(@__DIR__, "build")
+#MultiDocumenter.make(outpath, docs;
+#                     assets_dir = "docs/src/assets",
+#                     search_engine = MultiDocumenter.SearchConfig(index_versions = [
+#                                                                      "stable",
+#                                                                  ],
+#                                                                  engine = MultiDocumenter.FlexSearch),
+#                     brand_image = MultiDocumenter.BrandImage("https://sciml.ai",
+#                                                              joinpath("assets",
+#                                                                       "logo.png")))
+#
path: docs/make.jl
user: login: isaacsas
body: ```suggestion
```
created_at: 2025-04-12T00:28:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: Why is this no longer dynamic? Generally we should keep everything as dynamic as possible. 
created_at: 2025-04-12T00:33:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: Make dynamic.
created_at: 2025-04-12T00:34:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: Also, this is the older version of the graph and not the version we currently have in our docs.
created_at: 2025-04-12T00:36:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
&= Z B \mathbf{v}(\mathbf{x}(t),t).
```
created_at: 2025-04-12T00:40:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
In this section we discuss a further decomposition of the ODEs. Recall the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
```
created_at: 2025-04-12T00:40:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
where $\mathbf{x}^y = \prod_s x_s^{y_s}$, denotes the mass-action product of the substrate complex $y$ from the $y \rightarrow y'$ reaction. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
```
created_at: 2025-04-12T00:43:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In sum, we have that
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
In summary, we have that
```
created_at: 2025-04-12T00:43:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In sum, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
&= Z B K \Phi(\mathbf{x}(t),t) \\
```
created_at: 2025-04-12T00:43:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In sum, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+&= Z A_k \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+All three of the objects introduced in this section (the flux matrix, mass-action vector, Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
All three of the objects introduced in this section (the flux matrix, mass-action vector, and Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
```
created_at: 2025-04-12T00:43:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In sum, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+&= Z A_k \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+All three of the objects introduced in this section (the flux matrix, mass-action vector, Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
+For example, `massactionvector` will return a numerical output if a set of species concentrations is supplied using a dictionary, tuple, or vector of Symbol-value pairs.
+```@example s1
+concmap = Dict([:A => 3., :B => 5., :C => 2.4, :D => 1.5])
+massactionvector(rn, concmap)
+```
+
+`fluxmat` and `laplacianmat` will return numeric matrices if a set of rate constants and other aprameters are supplied the same way.
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: ```suggestion
`fluxmat` and `laplacianmat` will return numeric matrices if a set of rate constants and other parameters are supplied the same way.
```
created_at: 2025-04-12T00:44:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In sum, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+&= Z A_k \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+All three of the objects introduced in this section (the flux matrix, mass-action vector, Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
+For example, `massactionvector` will return a numerical output if a set of species concentrations is supplied using a dictionary, tuple, or vector of Symbol-value pairs.
+```@example s1
+concmap = Dict([:A => 3., :B => 5., :C => 2.4, :D => 1.5])
+massactionvector(rn, concmap)
+```
+
+`fluxmat` and `laplacianmat` will return numeric matrices if a set of rate constants and other aprameters are supplied the same way.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+fluxmat(rn, parammap)
+```
+
+```@example s1
+laplacianmat(rn, parammap)
+```
+
+## Symbolic ODE functions
+In some cases it might be useful to generate the function defining the system of ODEs as a symbolic Julia function that can be used for further analysis. This can be done using Symbolics' [`build_function`](https://docs.sciml.ai/Symbolics/stable/getting_started/#Building-Functions), which takes a symbolic expression and a set of desired arguments, and converts it into a Julia function taking those arguments.
+
+Let's build the full symbolic function corresponding to our ODE system. `build_function` will return two expressions, one for a function that outputs a new vector for the result, and one for a function that modifies the input in-place. Either expression can then be evaluated to return a Julia function.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+K = fluxmat(rn, parammap)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6.]
+f(c)
+```
+The generated `f` now corresponds to the $f(\mathbf{x}(t))$ on the right-hand side of $\frac{d\mathbf{x}(t)}{dt} = f(\mathbf{x}(t))$. Given a vector of species concentrations $c$, `ode_func` will return the rate of change of each species. Steady state concentration vectors `c_ss` will satisfy `f(c_ss) = zeros(length(species(rn)))`.
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: What is `ode_func` here? Do you mean `f`?
created_at: 2025-04-12T00:46:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,362 @@
+# [Decomposing the Reaction Network ODEs](@id network_analysis_odes)
+
+In this tutorial we will discuss the specific mathematical 
+structure of the [ODEs that arise from the mass-action dynamics](@ref math_models_in_catalyst_rre_odes)
+of chemical reaction networks, and decompose them as a product
+of matrices that describe the network.  A complete summary of 
+the exported functions is given in the API section [Network Analysis and Representations](@ref api_network_analysis). Please consult Feinberg's *Foundations of Chemical Reaction
+Network Theory*[^1] for more discussion about the concepts on this page.
+
+Note, currently API functions for network analysis and conservation law analysis
+do not work with constant species (which are generated by SBML, and can be [declared
+in Catalyst as well](@ref dsl_advanced_options_constant_species).
+
+## [Network representation of the Repressilator `ReactionSystem`](@id network_analysis_repressilator_representation)
+We first load Catalyst and construct our model of the repressilator
+```@example s1
+using Catalyst
+repressilator = @reaction_network Repressilator begin
+       hillr(P₃,α,K,n), ∅ --> m₁
+       hillr(P₁,α,K,n), ∅ --> m₂
+       hillr(P₂,α,K,n), ∅ --> m₃
+       (δ,γ), m₁ <--> ∅
+       (δ,γ), m₂ <--> ∅
+       (δ,γ), m₃ <--> ∅
+       β, m₁ --> m₁ + P₁
+       β, m₂ --> m₂ + P₂
+       β, m₃ --> m₃ + P₃
+       μ, P₁ --> ∅
+       μ, P₂ --> ∅
+       μ, P₃ --> ∅
+end
+```
+In the [Model Visualization](@ref visualisation_graphs)
+tutorial we showed how the above network could be visualized as a
+species-reaction graph. There, species are represented by the nodes of the graph
+and edges show the reactions in which a given species is a substrate or product.
+```julia
+g = Graph(repressilator)
+```
+![Repressilator solution](../assets/repressilator.svg)
+
+We also showed in the [Introduction to Catalyst](@ref introduction_to_catalyst) tutorial that
+the reaction rate equation (RRE) ODE model for the repressilator is
+```math
+\begin{aligned}
+\frac{dm_1(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_3}\left( t \right) \right)^{n}} - \delta {m_1}\left( t \right) + \gamma \\
+\frac{dm_2(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_1}\left( t \right) \right)^{n}} - \delta {m_2}\left( t \right) + \gamma \\
+\frac{dm_3(t)}{dt} =& \frac{\alpha K^{n}}{K^{n} + \left( {P_2}\left( t \right) \right)^{n}} - \delta {m_3}\left( t \right) + \gamma \\
+\frac{dP_1(t)}{dt} =& \beta {m_1}\left( t \right) - \mu {P_1}\left( t \right) \\
+\frac{dP_2(t)}{dt} =& \beta {m_2}\left( t \right) - \mu {P_2}\left( t \right) \\
+\frac{dP_3(t)}{dt} =& \beta {m_3}\left( t \right) - \mu {P_3}\left( t \right)
+\end{aligned}
+```
+
+## [Matrix-vector reaction rate equation representation](@id network_analysis_matrix_vector_representation)
+In general, reaction rate equation (RRE) ODE models for chemical reaction networks can
+be represented as a first-order system of ODEs in a compact matrix-vector notation. Suppose
+we have a reaction network with ``K`` reactions and ``M`` species, labelled by the state vector
+```math
+\mathbf{x}(t) = \begin{pmatrix} x_1(t) \\ \vdots \\ x_M(t)) \end{pmatrix}.
+```
+For the repressilator, ``\mathbf{x}(t)`` is just
+```@example s1
+x = species(repressilator)
+```
+The RRE ODEs satisfied by $\mathbf{x}(t)$ are then
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t),t),
+```
+where ``N`` is a constant ``M`` by ``K`` matrix with ``N_{m k}`` the net
+stoichiometric coefficient of species ``m`` in reaction ``k``.
+``\mathbf{v}(\mathbf{x}(t),t)`` is the rate law vector, with
+``v_k(\mathbf{x}(t),t)`` the rate law for the ``k``th reaction. For example,
+for the first reaction of the repressilator above, the rate law is
+```math
+v_1(\mathbf{x}(t),t) = \frac{\alpha K^{n}}{K^{n} + \left( P_3(t) \right)^{n}}.
+```
+We can calculate each of these in Catalyst via
+```@example s1
+N = netstoichmat(repressilator)
+```
+and by using the [`oderatelaw`](@ref) function
+```@example s1
+rxs = reactions(repressilator)
+ν = oderatelaw.(rxs)
+```
+Note, as [`oderatelaw`](@ref) takes just one reaction as input we use
+broadcasting to apply it to each element of `rxs`.
+
+Let's check that this really gives the same ODEs as Catalyst. Here is what Catalyst
+generates by converting to an `ODESystem`
+```@example s1
+osys = convert(ODESystem, repressilator)
+
+# for display purposes we just pull out the right side of the equations
+odes = [eq.rhs for eq in equations(osys)]
+```
+whereas our matrix-vector representation gives
+```@example s1
+odes2 = N * ν
+```
+Let's check these are equal symbolically
+```@example s1
+isequal(odes, odes2)
+```
+
+## [Reaction complex representation](@id network_analysis_reaction_complexes)
+We now introduce a further decomposition of the RRE ODEs, which has been used to
+facilitate analysis of a variety of reaction network properties. Consider a simple
+reaction system like
+```@example s1
+rn = @reaction_network begin
+ k, 2A + 3B --> A + 2C + D
+ b, C + D --> 2A + 3B
+end
+```
+We can think of the first reaction as converting the *reaction complex*,
+``2A+3B`` to the complex ``A+2C+D`` with rate ``k``. Suppose we order our
+species the same way as Catalyst does, i.e.
+```math
+\begin{pmatrix}
+x_1(t)\\
+x_2(t)\\
+x_3(t)\\
+x_4(t)
+\end{pmatrix} =
+\begin{pmatrix}
+A(t)\\
+B(t)\\
+C(t)\\
+D(t)
+\end{pmatrix},
+```
+which should be the same as
+```@example s1
+species(rn)
+```
+We can describe a given reaction complex by the stoichiometric coefficients of
+each species within the complex. For the reactions in `rn` these vectors would
+be
+```math
+\begin{align*}
+2A+3B = \begin{pmatrix}
+2\\
+3\\
+0\\
+0
+\end{pmatrix}, &&
+A+2C+D = \begin{pmatrix}
+1\\
+0\\
+2\\
+1
+\end{pmatrix},
+ &&
+C+D = \begin{pmatrix}
+0\\
+0\\
+1\\
+1
+\end{pmatrix}
+\end{align*}
+```
+Catalyst can calculate these representations as the columns of the complex
+stoichiometry matrix,
+```@example s1
+Z = complexstoichmat(rn)
+```
+If we have ``C`` complexes, ``Z`` is a ``M`` by ``C`` matrix with ``Z_{m c}``
+giving the stoichiometric coefficient of species ``m`` within complex ``c``.
+
+We can use this representation to provide another representation of the RRE
+ODEs. The net stoichiometry matrix can be factored as ``N = Z B``, where ``B``
+is called the incidence matrix of the reaction network,
+```@example s1
+B = incidencemat(rn)
+```
+Here ``B`` is a ``C`` by ``K`` matrix with ``B_{c k} = 1`` if complex ``c``
+appears as a product of reaction ``k``, and ``B_{c k} = -1`` if complex ``c`` is a
+substrate of reaction ``k``.
+
+Using our decomposition of ``N``, the RRE ODEs become
+```math
+\frac{dx}{dt} = Z B \mathbf{v}(\mathbf{x}(t),t).
+```
+Let's verify that ``N = Z B``,
+```@example s1
+N = netstoichmat(rn)
+N == Z*B
+```
+
+Reaction complexes give an alternative way to visualize a reaction network
+graph. Catalyst's [`plot_complexes`](@ref) command will calculate the complexes of
+a network and then show how they are related. For example, we can run
+```@example s1
+plot_complexes(rn)
+```
+
+while for the repressilator we find
+```julia
+plot_complexes(repressilator)
+```
+
+Here ∅ represents the empty complex, black arrows show reactions converting
+substrate complexes into product complexes where the rate is just a number or
+parameter, and red arrows indicate the conversion of substrate complexes into
+product complexes where the rate is an expression involving chemical species.
+
+# Full decomposition of the reaction network ODEs (flux matrix and mass-action vector)
+So far we have covered two equivalent descriptions of the chemical reaction network ODEs: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= Z B \mathbf{v}(\mathbf{x}(t),t)
+\end{align}
+```
+
+In this section we discuss a further decomposition of the ODEs. Recall that the reaction rate vector $\mathbf{v}$, which is a vector of length $R$ whose elements are the rate expressions for each reaction. Its elements can be written as 
+```math
+\mathbf{v}_{y \rightarrow y'} = k_{y \rightarrow y'} \mathbf{x}^y,
+```
+where $\mathbf{x}^y = \prod_s x_s^{y_s}$, the mass-action product of the complex $y$, where $y$ is the substrate complex of the reaction $y \rightarrow y'$. We can define a new vector called the mass action vector $\Phi(\mathbf{x}(t))$, a vector of length $C$ whose elements are the mass action products of each complex: 
+```@example s1
+Φ = massactionvector(rn)
+```
+
+An important thing to note is this function assumes [combinatoric ratelaws](@ref introduction_to_catalyst_ratelaws), meaning that mass-action products will get rescaled by factorial factors. For instance, note that the mass-action product for the complex `2A + 3B` has a factor of 1/12, corresponding to 1/(2! 3!). This option can be turned off with `combinatoric_ratelaws = false`.
+
+```@example s1
+Φ_2 = massactionvector(rn; combinatoric_ratelaws = false)
+```
+
+Then the reaction rate vector $\mathbf{v}$ can be written as 
+```math
+\mathbf{v}(\mathbf{x}(t)) = K \Phi(\mathbf{x}(t))
+```
+where $K$ is an $R$-by-$C$ matrix called the flux matrix, where $K_{rc}$ is the rate constant of reaction $r$ if $c$ is the index of the substrate complex of reaction $r$, and 0 otherwise. In Catalyst, the API function for $K$ is `fluxmat`: 
+```@example s1
+K = fluxmat(rn)
+```
+
+Since we have that $\mathbf{v} = K\Phi$, we can rewrite the above decompositions as follows: 
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+The final matrix to discuss is the product of $A_k = BK$, which is a $C$-by-$C$ matrix that turns out to be exactly the negative of the [graph Laplacian](https://en.wikipedia.org/wiki/Laplacian_matrix) of the weighted graph whose nodes are reaction complexes and whose edges represent reactions, weighted by the rate constants. The API function for $A_k$ is the `laplacianmat`: 
+```@example s1
+A_k = laplacianmat(rn)
+```
+We can check that 
+```@example s1
+isequal(A_k, B * K)
+```
+
+Note that we have used `isequal` instead of `==` here because `laplacianmat`
+returns a `Matrix{Num}`, since some of its entries are symbolic rate constants
+(symbolic variables and `Num`s cannot be compared using `==`, since `a == b`
+is interpreted as a symbolic expression).
+
+In sum, we have that
+```math
+\begin{align}
+\frac{d\mathbf{x}}{dt} &= N \mathbf{v}(\mathbf{x}(t),t) \\
+&= N K \Phi(\mathbf{x}(t),t) \\
+&= Z B K \Phi(\mathbf{x}(t),t). 
+&= Z A_k \Phi(\mathbf{x}(t),t). 
+\end{align}
+```
+
+All three of the objects introduced in this section (the flux matrix, mass-action vector, Laplacian matrix) will return symbolic outputs by default, but can be made to return numerical outputs if values are specified. 
+For example, `massactionvector` will return a numerical output if a set of species concentrations is supplied using a dictionary, tuple, or vector of Symbol-value pairs.
+```@example s1
+concmap = Dict([:A => 3., :B => 5., :C => 2.4, :D => 1.5])
+massactionvector(rn, concmap)
+```
+
+`fluxmat` and `laplacianmat` will return numeric matrices if a set of rate constants and other aprameters are supplied the same way.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+fluxmat(rn, parammap)
+```
+
+```@example s1
+laplacianmat(rn, parammap)
+```
+
+## Symbolic ODE functions
+In some cases it might be useful to generate the function defining the system of ODEs as a symbolic Julia function that can be used for further analysis. This can be done using Symbolics' [`build_function`](https://docs.sciml.ai/Symbolics/stable/getting_started/#Building-Functions), which takes a symbolic expression and a set of desired arguments, and converts it into a Julia function taking those arguments.
+
+Let's build the full symbolic function corresponding to our ODE system. `build_function` will return two expressions, one for a function that outputs a new vector for the result, and one for a function that modifies the input in-place. Either expression can then be evaluated to return a Julia function.
+```@example s1
+parammap = Dict([:k => 12., :b => 8.])
+K = fluxmat(rn, parammap)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6.]
+f(c)
+```
+The generated `f` now corresponds to the $f(\mathbf{x}(t))$ on the right-hand side of $\frac{d\mathbf{x}(t)}{dt} = f(\mathbf{x}(t))$. Given a vector of species concentrations $c$, `ode_func` will return the rate of change of each species. Steady state concentration vectors `c_ss` will satisfy `f(c_ss) = zeros(length(species(rn)))`.
+
+Above we have generated a numeric rate matrix to substitute the rate constants into the symbolic expressions. We could have used a symbolic rate matrix, but then we would need to define the parameters `k, b`, so that the function `f` knows what `k` and `b` in its output refer to.
+```@example s1
+@parameters k b 
+K = fluxmat(rn)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6.]
+f(c)
+```
+
+Alternatively, if we use a symbolic rate matrix, we could define our function to take in both species concentrations and parameter values as arguments:
+```@example s1
+K = fluxmat(rn)
+odes = N * K * Φ
+f_oop_expr, f_iip_expr = Symbolics.build_function(odes, species(rn), parameters(rn))
+f = eval(f_oop_expr)
+
+c = [3., 5., 2., 6]; ks = [12., 4.]
+f(c, ks)
+```
+
+Note also that `f` can take any vector with the right dimension (i.e. the number of species), not just a vector of `Number`, so it can be used to build, e.g. a vector of polynomials in Nemo for commutative algebraic methods.
+
+## Properties of matrix null spaces
+The null spaces of the matrices discussed in this section often have special meaning. Below we will discuss some of these properties.
+
+Recall that we may write the net stoichiometry matrix ``N = YB``.
path: docs/src/network_analysis/odes.md
user: login: isaacsas
body: What is `Y` here? That seems like new notation from above.
created_at: 2025-04-12T00:48:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,349 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+```@docs; canonical=false
+satisfiesdeficiencyzero
+```
path: docs/src/network_analysis/crn_theory.md
user: login: isaacsas
body: I'd add a dynamic example of using this function above, right after we check the conditions manually (i.e. say that Catalyst provides `satisfiesdeficiencyzero` to automatically check these conditions, and add a code block showing it gives the correct answer on that example). Then you can just link the API doc where you reference it.
created_at: 2025-04-12T00:56:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,349 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+```@docs; canonical=false
+satisfiesdeficiencyzero
+```
+
+## Deficiency One Theorem
+Very analogous to the deficiency zero theorem is the deficiency one theorem. The deficiency one theorem applies to a network with the following properties: 
+1. The deficiency of each *linkage class* of the network is at most 1, 
+2. The sum of the linkage class deficiencies is the total deficiency of the network, and
+3. Each linkage class has at most one terminal linkage class, which is a linkage class that is 1) strongly connected, and 2) has no outgoing reactions.
+
+For the set of reactions $A \to B, B \to A, B \to C$, $\{A, B, C\}$ is a linkage class,
+and $\{A, B\}$ is a strong linkage class (since A is reachable from B and vice versa).
+However, $\{A, B\}$ is not a terminal linkage class, because the reaction $B \to C$ goes
+to a complex outside the linkage class. 
+
+If these conditions are met, then the network will have at most one steady state in each 
+stoichiometric compatibility class for any choice of rate constants and parameters. Unlike 
+the deficiency zero theorem, networks obeying the deficiency one theorem are not guaranteed 
+to have stable solutions.
+
+```@docs; canonical=false
+satisfiesdeficiencyone
+```
+
path: docs/src/network_analysis/crn_theory.md
user: login: isaacsas
body: Again, why not give a simple dynamic example to illustrate using this function and just link the API doc on it?
created_at: 2025-04-12T00:57:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,349 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+```@docs; canonical=false
+satisfiesdeficiencyzero
+```
+
+## Deficiency One Theorem
+Very analogous to the deficiency zero theorem is the deficiency one theorem. The deficiency one theorem applies to a network with the following properties: 
+1. The deficiency of each *linkage class* of the network is at most 1, 
+2. The sum of the linkage class deficiencies is the total deficiency of the network, and
+3. Each linkage class has at most one terminal linkage class, which is a linkage class that is 1) strongly connected, and 2) has no outgoing reactions.
+
+For the set of reactions $A \to B, B \to A, B \to C$, $\{A, B, C\}$ is a linkage class,
+and $\{A, B\}$ is a strong linkage class (since A is reachable from B and vice versa).
+However, $\{A, B\}$ is not a terminal linkage class, because the reaction $B \to C$ goes
+to a complex outside the linkage class. 
+
+If these conditions are met, then the network will have at most one steady state in each 
+stoichiometric compatibility class for any choice of rate constants and parameters. Unlike 
+the deficiency zero theorem, networks obeying the deficiency one theorem are not guaranteed 
+to have stable solutions.
+
+```@docs; canonical=false
+satisfiesdeficiencyone
+```
+
+## [Complex and Detailed Balance](@id network_analysis_complex_and_detailed_balance)
+A reaction network's steady state is **complex-balanced** if the total production of each 
+*complex* is zero at the steady state. A reaction network's steady state is **detailed balanced** 
+if every reaction is balanced by its reverse reaction at the steady-state (this corresponds to 
+the usual notion of chemical equilibrium; note that this requires every reaction be reversible). 
+
+Note that detailed balance at a given steady state implies complex balance for that steady state,
+i.e. detailed balance is a stronger property than complex balance.
+
+Remarkably, having just one positive steady state that is complex (detailed) balance implies that 
+complex (detailed) balance obtains at *every* positive steady state, so we say that a network 
path: docs/src/network_analysis/crn_theory.md
user: login: isaacsas
body: ```suggestion
complex (detailed) balance holds for *every* positive steady state, so we say that a network 
```
created_at: 2025-04-12T00:59:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,349 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+```@docs; canonical=false
+satisfiesdeficiencyzero
+```
+
+## Deficiency One Theorem
+Very analogous to the deficiency zero theorem is the deficiency one theorem. The deficiency one theorem applies to a network with the following properties: 
+1. The deficiency of each *linkage class* of the network is at most 1, 
+2. The sum of the linkage class deficiencies is the total deficiency of the network, and
+3. Each linkage class has at most one terminal linkage class, which is a linkage class that is 1) strongly connected, and 2) has no outgoing reactions.
+
+For the set of reactions $A \to B, B \to A, B \to C$, $\{A, B, C\}$ is a linkage class,
+and $\{A, B\}$ is a strong linkage class (since A is reachable from B and vice versa).
+However, $\{A, B\}$ is not a terminal linkage class, because the reaction $B \to C$ goes
+to a complex outside the linkage class. 
+
+If these conditions are met, then the network will have at most one steady state in each 
+stoichiometric compatibility class for any choice of rate constants and parameters. Unlike 
+the deficiency zero theorem, networks obeying the deficiency one theorem are not guaranteed 
+to have stable solutions.
+
+```@docs; canonical=false
+satisfiesdeficiencyone
+```
+
+## [Complex and Detailed Balance](@id network_analysis_complex_and_detailed_balance)
+A reaction network's steady state is **complex-balanced** if the total production of each 
+*complex* is zero at the steady state. A reaction network's steady state is **detailed balanced** 
+if every reaction is balanced by its reverse reaction at the steady-state (this corresponds to 
+the usual notion of chemical equilibrium; note that this requires every reaction be reversible). 
+
+Note that detailed balance at a given steady state implies complex balance for that steady state,
+i.e. detailed balance is a stronger property than complex balance.
+
+Remarkably, having just one positive steady state that is complex (detailed) balance implies that 
+complex (detailed) balance obtains at *every* positive steady state, so we say that a network 
+is complex (detailed) balanced if any one of its steady states are complex (detailed) balanced. 
+Additionally, there will be exactly one steady state in every positive stoichiometric 
+compatibility class, and this steady state is asymptotically stable. (For proofs of these results,
+please consult Martin Feinberg's *Foundations of Chemical Reaction Network Theory*[^1]). So 
+knowing that a network is complex balanced is really quite powerful.
+
+Let's check whether the deficiency 0 reaction network that we defined above is complex balanced by providing a set of rates:
+```@example s1
+rates = Dict([:k1 => 2.4, :k2 => 4., :k3 => 10., :k4 => 5.5, :k5 => 0.4])
+iscomplexbalanced(rn, rates)
+```
+
+We can do a similar check for detailed balance.
+```@example s1
+isdetailedbalanced(rn, rates)
+```
+
+The reason that the deficiency zero theorem puts such strong restrictions on the steady state 
+properties of the reaction network is because it implies that the reaction network will be 
+complex balanced for any set of rate constants and parameters. The fact that this holds 
+from a purely structural property of the graph, regardless of kinetics, is what makes it so 
+useful. But in some cases it might be desirable to check complex balance on its own, as for 
+higher deficiency networks.
+
+```@docs; canonical=false
+iscomplexbalanced
+isdetailedbalanced
+```
+
+## [Concentration Robustness](@id network_analysis_concentration_robustness)
+Certain reaction networks have species that do not change their concentration,
+regardless of the system is perturbed to a different stoichiometric compatibility
path: docs/src/network_analysis/crn_theory.md
user: login: isaacsas
body: ```suggestion
regardless of whether the system is perturbed to a different stoichiometric compatibility
```
created_at: 2025-04-12T01:00:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,349 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
+end
+
+# calculate the set of reaction complexes
+reactioncomplexes(rn)
+
+# test if the system is reversible
+isreversible(rn)
+```
+Consider another example,
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  k3, A + C --> D
+  k4, D --> B+E
+  k5, B+E --> A+C
+end
+reactioncomplexes(rn)
+isreversible(rn)
+```
+```@example s1
+plot_complexes(rn)
+```
+
+It is evident from the preceding graph that the network is not reversible.
+However, it satisfies a weaker property in that there is a path from each
+reaction complex back to itself within its associated subgraph. This is known as
+*weak reversibility*. One can test a network for weak reversibility by using
+the [`isweaklyreversible`](@ref) function:
+```@example s1
+# need subnetworks from the reaction network first
+subnets = subnetworks(rn)
+isweaklyreversible(rn, subnets)
+```
+Every reversible network is also weakly reversible, but not every weakly
+reversible network is reversible.
+
+## [Deficiency Zero Theorem](@id network_analysis_structural_aspects_deficiency_zero_theorem)
+Knowing the deficiency and weak reversibility of a mass action chemical reaction
+network ODE model allows us to make inferences about the corresponding
+steady state behavior. Before illustrating how this works for one example, we
+need one last definition.
+
+Recall that in the matrix-vector representation for the RRE ODEs, the entries,
+``N_{m k}``, of the stoichiometry matrix, ``N``, give the net change in species
+``m`` due to reaction ``k``. If we let ``\mathbf{N}_k`` denote the ``k``th
+column of this matrix, this vector corresponds to the change in the species
+state vector, ``\mathbf{x}(t)``, due to reaction ``k``, i.e. when reaction ``k``
+occurs ``\mathbf{x}(t) \to \mathbf{x}(t) + \mathbf{N}_k``. Moreover, by
+integrating the ODE
+```math
+\frac{d\mathbf{x}}{dt} = N \mathbf{v}(\mathbf{x}(t)) = \sum_{k=1}^{K} v_k(\mathbf{x}(t)) \, \mathbf{N}_k
+```
+we find
+```math
+\mathbf{x}(t) = \mathbf{x}(0) + \sum_{k=1}^K \left(\int_0^t v_k(\mathbf{x})(s) \, ds\right) \mathbf{N}_k,
+```
+which demonstrates that ``\mathbf{x}(t) - \mathbf{x}(0)`` is always given by a
+linear combination of the stoichiometry vectors, i.e.
+```math
+\mathbf{x}(t) - \mathbf{x}(0) \in \operatorname{span}\{\mathbf{N}_k \}.
+```
+In particular, this says that ``\mathbf{x}(t)`` lives in the translation of the
+``\operatorname{span}\{\mathbf{N}_k \}`` by ``\mathbf{x}(0)`` which we write as
+``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})``. In fact, since the
+solution should stay non-negative, if we let $\bar{\mathbb{R}}_+^{M}$ denote the
+subset of vectors in $\mathbb{R}^{M}$ with non-negative components, the possible
+physical values for the solution, ``\mathbf{x}(t)``, must be in the set
+```math
+(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\}) \cap \bar{\mathbb{R}}_+^{M}.
+```
+This set is called the stoichiometric compatibility class of ``\mathbf{x}(t)``.
+The key property of stoichiometric compatibility classes is that they are
+invariant under the RRE ODE's dynamics, i.e. a solution will always remain
+within the subspace given by the stoichiometric compatibility class. Finally, we
+note that the *positive* stoichiometric compatibility class generated by
+$\mathbf{x}(0)$ is just ``(\mathbf{x}(0) + \operatorname{span}\{\mathbf{N}_k\})
+\cap \mathbb{R}_+^{M}``, where ``\mathbb{R}_+^{M}`` denotes the vectors in
+``\mathbb{R}^M`` with strictly positive components.
+
+With these definitions we can now see how knowing the deficiency and weak
+reversibility of the network can tell us about its steady state behavior.
+Consider the previous example, which we know is weakly reversible. Its
+deficiency is
+```@example s1
+deficiency(rn)
+```
+We also verify that the system is purely mass action (though it is apparent
+from the network's definition):
+```@example s1
+all(rx -> ismassaction(rx, rn), reactions(rn))
+```
+We can therefore apply the Deficiency Zero Theorem to draw conclusions about the
+system's steady state behavior. The Deficiency Zero Theorem (roughly) says that
+a mass action network with deficiency zero satisfies
+1. If the network is weakly reversible, then independent of the reaction rate
+   constants the RRE ODEs have exactly one equilibrium solution within each
+   positive stoichiometric compatibility class. That equilibrium is locally
+   asymptotically stable.
+2. If the network is not weakly reversible, then the RRE ODEs cannot admit a
+   positive equilibrium solution.
+
+See [^1] for a more precise statement, proof, and additional examples.
+
+We can therefore conclude that for any initial condition that is positive, and
+hence in some positive stoichiometric compatibility class, `rn` will have
+exactly one equilibrium solution which will be positive and locally
+asymptotically stable.
+
+As a final example, consider the following network from [^1]
+```@example s1
+def0_rn = @reaction_network begin
+  (k1,k2),A <--> 2B
+  (k3,k4), A + C <--> D
+  k5, B+E --> C + D
+end
+reactioncomplexes(def0_rn)
+subnets = subnetworks(def0_rn)
+isma = all(rx -> ismassaction(rx,def0_rn), reactions(def0_rn))
+def = deficiency(def0_rn)
+iswr = isweaklyreversible(def0_rn, subnets)
+isma,def,iswr
+```
+which we see is mass action and has deficiency zero, but is not weakly
+reversible. As such, we can conclude that for any choice of rate constants the
+RRE ODEs cannot have a positive equilibrium solution.
+
+```@docs; canonical=false
+satisfiesdeficiencyzero
+```
+
+## Deficiency One Theorem
+Very analogous to the deficiency zero theorem is the deficiency one theorem. The deficiency one theorem applies to a network with the following properties: 
+1. The deficiency of each *linkage class* of the network is at most 1, 
+2. The sum of the linkage class deficiencies is the total deficiency of the network, and
+3. Each linkage class has at most one terminal linkage class, which is a linkage class that is 1) strongly connected, and 2) has no outgoing reactions.
+
+For the set of reactions $A \to B, B \to A, B \to C$, $\{A, B, C\}$ is a linkage class,
+and $\{A, B\}$ is a strong linkage class (since A is reachable from B and vice versa).
+However, $\{A, B\}$ is not a terminal linkage class, because the reaction $B \to C$ goes
+to a complex outside the linkage class. 
+
+If these conditions are met, then the network will have at most one steady state in each 
+stoichiometric compatibility class for any choice of rate constants and parameters. Unlike 
+the deficiency zero theorem, networks obeying the deficiency one theorem are not guaranteed 
+to have stable solutions.
+
+```@docs; canonical=false
+satisfiesdeficiencyone
+```
+
+## [Complex and Detailed Balance](@id network_analysis_complex_and_detailed_balance)
+A reaction network's steady state is **complex-balanced** if the total production of each 
+*complex* is zero at the steady state. A reaction network's steady state is **detailed balanced** 
+if every reaction is balanced by its reverse reaction at the steady-state (this corresponds to 
+the usual notion of chemical equilibrium; note that this requires every reaction be reversible). 
+
+Note that detailed balance at a given steady state implies complex balance for that steady state,
+i.e. detailed balance is a stronger property than complex balance.
+
+Remarkably, having just one positive steady state that is complex (detailed) balance implies that 
+complex (detailed) balance obtains at *every* positive steady state, so we say that a network 
+is complex (detailed) balanced if any one of its steady states are complex (detailed) balanced. 
+Additionally, there will be exactly one steady state in every positive stoichiometric 
+compatibility class, and this steady state is asymptotically stable. (For proofs of these results,
+please consult Martin Feinberg's *Foundations of Chemical Reaction Network Theory*[^1]). So 
+knowing that a network is complex balanced is really quite powerful.
+
+Let's check whether the deficiency 0 reaction network that we defined above is complex balanced by providing a set of rates:
+```@example s1
+rates = Dict([:k1 => 2.4, :k2 => 4., :k3 => 10., :k4 => 5.5, :k5 => 0.4])
+iscomplexbalanced(rn, rates)
+```
+
+We can do a similar check for detailed balance.
+```@example s1
+isdetailedbalanced(rn, rates)
+```
+
+The reason that the deficiency zero theorem puts such strong restrictions on the steady state 
+properties of the reaction network is because it implies that the reaction network will be 
+complex balanced for any set of rate constants and parameters. The fact that this holds 
+from a purely structural property of the graph, regardless of kinetics, is what makes it so 
+useful. But in some cases it might be desirable to check complex balance on its own, as for 
+higher deficiency networks.
+
+```@docs; canonical=false
+iscomplexbalanced
+isdetailedbalanced
+```
+
+## [Concentration Robustness](@id network_analysis_concentration_robustness)
+Certain reaction networks have species that do not change their concentration,
+regardless of the system is perturbed to a different stoichiometric compatibility
+class. This is a very useful property to have in biological contexts, where it might
+be important to keep the concentration of a critical species relatively stable in
+the face of changes in its environment. 
+
+Determining every species with concentration-robustness in a network is in general very difficult. However, there are certain cases where there are sufficient conditions
+that can be checked relatively easily. One example is for deficiency one networks.
+
+**Theorem (a sufficient condition for concentration robustness for deficiency one networks)**: If there are two *non-terminal* reaction complexes that differ only in species ``s``, then the system is absolutely concentration robust with respect to ``s``. 
+
+This is the check provided by the API function `robustspecies(rn)`. More general concentration robustness analysis can be done using the [CatalystNetworkAnalysis](@ref catalyst_network_analysis) package.
path: docs/src/network_analysis/crn_theory.md
user: login: isaacsas
body: Give a dynamic example of using this?
created_at: 2025-04-12T01:01:19Z
author_association: MEMBER
diff_hunk: @@ -23,7 +23,7 @@ etc).
 - The [Catalyst.jl API](@ref api) provides functionality for building networks programmatically and for composing multiple networks together.
 - Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref simulation_intro), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ensemble_simulations). Plot recipes are available for [visualization of all solutions](@ref simulation_plotting).
 - Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref dsl_description_stoichiometries_decimal) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref parametric_stoichiometry) for all system types.
-- A [network analysis suite](@ref network_analysis) permits the computation of linkage classes, deficiencies, reversibility, and other network properties.
+- A [network analysis suite](@ref network_analysis_structural_aspects) permits the computation of linkage classes, deficiencies, reversibility, and other network properties.
path: docs/src/index.md
user: login: TorkelE
body: A similar update will be needed on the GitHub main readme page. However, that links to the actual website, so if you want we can update after the docs have been pushed and are avaiable.
created_at: 2025-04-14T18:11:36Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,35 @@
+# [Caching of Network Properties in `ReactionSystems`](@id network_analysis_caching_properties)
path: docs/src/network_analysis/network_properties.md
user: login: TorkelE
body: I'd probably call this file "network_properties_caching" (since that is what it is about).
created_at: 2025-04-14T18:12:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,375 @@
+# [Chemical Reaction Network Theory](@id network_analysis_structural_aspects)
+
+The systems of ODEs or stochastic chemical kinetics models that arise from chemical
+reaction networks can often have their steady-state properties (number of steady states, etc.) known in advance,
+simply by analyzing the graph structure of the network. The subfield of chemistry
+and math studying this relationship is called [Chemical Reaction Network Theory](https://en.wikipedia.org/wiki/Chemical_reaction_network_theory).
+
+In this tutorial we give a broad overview of chemical reaction network theory, building on the discussion of the structure of 
+the reaction network ODEs in the previous section. We also introduce several of the Catalyst API functions for network
+analysis. A complete summary of the exported functions is given in the API
+section
+[Network Analysis and Representations](@ref api_network_analysis).
+
+Broadly, results from chemical reaction network theory relate a purely
+graph-structural property (e.g. [deficiency](@ref network_analysis_structural_aspects_deficiency)) to dynamical properties of the reaction system
+(e.g. [complex balance](@ref network_analysis_complex_and_detailed_balance)). The relevant graph here is the one corresponding to the [reaction complex representation](@ref network_analysis_reaction_complexes)
+of the network, where the nodes represent the reaction complexes and the edges represent reactions.
+Let us illustrate some of the types of network properties that 
+Catalyst can determine.
+
+To begin, consider the following reaction network:
+```@example s1
+using Catalyst
+rn = @reaction_network begin
+    (k1,k2), A + B <--> C
+    k3, C --> D+E
+    (k4,k5), D+E <--> F
+    (k6,k7), 2A <--> B+G
+    k8, B+G --> H
+    k9, H --> 2A
+end
+```
+with graph
+```@example s1
+plot_complexes(rn)
+```
+
+## [Linkage classes and sub-networks of the reaction network](@id network_analysis_structural_aspects_linkage)
+The preceding reaction complex graph shows that `rn` is composed of two
+disconnected sub-graphs, one containing the complexes ``A+B``, ``C``, ``D+E``, and
+``F``, the other containing the complexes ``2A``, ``B + G``, and ``H``. These sets,
+``\{A+B, C, D+E, F\}`` and ``\{2A, B + G,H\}`` are called the "linkage classes"
+of the reaction network. The function [`linkageclasses`](@ref) will calculate
+these for a given network, returning a vector of the integer indices of reaction
+complexes participating in each set of linkage-classes. Note, indices of
+reaction complexes can be determined from the ordering returned by
+[`reactioncomplexes`](@ref).
+```@example s1
+# we must first calculate the reaction complexes -- they are cached in rn
+reactioncomplexes(rn)
+
+# now we can calculate the linkage classes
+lcs = linkageclasses(rn)
+```
+It can often be convenient to obtain the disconnected sub-networks as distinct
+`ReactionSystem`s, which are returned by the [`subnetworks`](@ref) function:
+```@example s1
+subnets = subnetworks(rn)
+
+# check the reactions in each subnetwork
+reactions.(subnets)
+```
+The graphs of the reaction complexes in the two sub-networks are then
+```@example s1
+  plot_complexes(subnets[1])
+```
+
+and,
+```@example s1
+ plot_complexes(subnets[2])
+```
+
+## [Deficiency of the network](@id network_analysis_structural_aspects_deficiency)
+A famous theorem in Chemical Reaction Network Theory, the Deficiency Zero
+Theorem[^1], allows us to use knowledge of the net stoichiometry matrix and the
+linkage classes of a *mass action* [RRE ODE system](@ref network_analysis_matrix_vector_representation) to draw conclusions about the
+system's possible steady states. In this section we'll see how Catalyst can
+calculate a network's deficiency.
+
+The rank, ``r``, of a reaction network is defined as the dimension of the
+subspace spanned by the net stoichiometry vectors of the reaction-network[^1],
+i.e. the span of the columns of the net stoichiometry matrix `N`. It corresponds
+to the number of independent species in a chemical reaction network. That is, if
+we calculate the linear conservation laws of a network, and use them to
+eliminate the dependent species of the network, we will have ``r`` independent
+species remaining. For our current example the conservation laws are given by
+```@example s1
+# first we calculate the conservation laws -- they are cached in rn
+conservationlaws(rn)
+
+# then we display them as equations for the dependent variables
+conservedequations(rn)
+show(stdout, MIME"text/plain"(), ans) # hide
+```
+Here the parameters `Γ[i]` represent the constants of the three
+conservation laws, and we see that there are three dependent species that could
+be eliminated. As
+```@example s1
+numspecies(rn)
+```
+we find that there are five independent species. Let's check this is correct:
+```@example s1
+using LinearAlgebra
+rank(netstoichmat(rn)) == 5
+```
+So we know that the rank of our reaction network is five. An extended section discussing how to
+utilise conservation law elimination during chemical reaction network modelling can be found
+[here](@ref conservation_laws).
+
+The deficiency, ``\delta``, of a reaction network is defined as
+```math
+\delta = \textrm{(number of complexes)} - \textrm{(number of linkage classes)} - \textrm{(rank)}.
+```
+For our network this is ``7 - 2 - 5 = 0``, which we can calculate in Catalyst as
+```@example s1
+# first we calculate the reaction complexes of rn and cache them in rn
+reactioncomplexes(rn)
+
+# then we can calculate the deficiency
+δ = deficiency(rn)
+```
+Quoting Feinberg [^1]
+> Deficiency zero networks are ones for which the reaction vectors [i.e. net
+> stoichiometry vectors] are as independent as the partition of complexes into
+> linkage classes will allow.
+
+
+## [Reversibility of the network](@id network_analysis_structural_aspects_reversibility)
+A reaction network is *reversible* if the "arrows" of the reactions are
+symmetric so that every reaction is accompanied by its reverse reaction.
+Catalyst's API provides the [`isreversible`](@ref) function to determine whether
+a reaction network is reversible. As an example, consider
+```@example s1
+rn = @reaction_network begin
+  (k1,k2),A <--> B
+  (k3,k4),A + C <--> D
+  (k5,k6),D <--> B+E
+  (k7,k8),B+E <--> A+C
path: docs/src/network_analysis/crn_theory.md
user: login: TorkelE
body: Here and in some places there is no space between the rate and the reaction itself (but in other places there is). If this is not intentional you might want to update (but if it is, no worries!).
created_at: 2025-04-14T18:18:13Z
author_association: MEMBER
diff_hunk: @@ -23,7 +23,7 @@ etc).
 - The [Catalyst.jl API](@ref api) provides functionality for building networks programmatically and for composing multiple networks together.
 - Leveraging ModelingToolkit, generated models can be converted to symbolic reaction rate equation ODE models, symbolic Chemical Langevin Equation models, and symbolic stochastic chemical kinetics (jump process) models. These can be simulated using any [DifferentialEquations.jl](https://docs.sciml.ai/DiffEqDocs/stable/) [ODE/SDE/jump solver](@ref simulation_intro), and can be used within `EnsembleProblem`s for carrying out [parallelized parameter sweeps and statistical sampling](@ref ensemble_simulations). Plot recipes are available for [visualization of all solutions](@ref simulation_plotting).
 - Non-integer (e.g. `Float64`) stoichiometric coefficients [are supported](@ref dsl_description_stoichiometries_decimal) for generating ODE models, and symbolic expressions for stoichiometric coefficients [are supported](@ref parametric_stoichiometry) for all system types.
-- A [network analysis suite](@ref network_analysis) permits the computation of linkage classes, deficiencies, reversibility, and other network properties.
+- A [network analysis suite](@ref network_analysis_structural_aspects) permits the computation of linkage classes, deficiencies, reversibility, and other network properties.
path: docs/src/index.md
user: login: vyudu
body: Yes, sounds good
created_at: 2025-04-14T21:18:44Z
author_association: MEMBER

PR comments:
user: login: isaacsas
created_at: 2024-12-20T17:01:04Z
body: Having network analysis be its own section is fine. LMK when you feel this is ready to review.
author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-02T22:41:31Z
body: Can you remove the full repo format? We haven’t been using formatter due to the bugs it introduced in one release last year. While I’m fine with re-enabling it that should really be in a separate PR so we can check nothing is getting broken. (Feel free to run it on the files you are actually editing here though.)
author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-08T23:19:14Z
body: @vyudu do you think you'll be able to clean this up with regards to the formatter changes soon so we can review? We are basically done now with the stuff that was holding back V15.
author_association: MEMBER
user: login: vyudu
created_at: 2025-04-08T23:34:27Z
body: I will do this tonight

On Tue, Apr 8, 2025 at 19:19 Sam Isaacson ***@***.***> wrote:

> @vyudu <https://github.com/vyudu> do you think you'll be able to clean
> this up with regards to the formatter changes soon so we can review? We are
> basically done now with the stuff that was holding back V15.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/SciML/Catalyst.jl/pull/1142#issuecomment-2787851280>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ANAOXYCK2TNR6Y5TIEJAERT2YRKQTAVCNFSM6AAAAABS4DPWQOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOOBXHA2TCMRYGA>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
> *isaacsas* left a comment (SciML/Catalyst.jl#1142)
> <https://github.com/SciML/Catalyst.jl/pull/1142#issuecomment-2787851280>
>
> @vyudu <https://github.com/vyudu> do you think you'll be able to clean
> this up with regards to the formatter changes soon so we can review? We are
> basically done now with the stuff that was holding back V15.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/SciML/Catalyst.jl/pull/1142#issuecomment-2787851280>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ANAOXYCK2TNR6Y5TIEJAERT2YRKQTAVCNFSM6AAAAABS4DPWQOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOOBXHA2TCMRYGA>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-08T23:39:55Z
body: Great, thanks!
author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-08T23:40:23Z
body: (Also, remove the WIP when it is ready to review so we know you are happy with it.)
author_association: MEMBER
user: login: vyudu
created_at: 2025-04-09T03:30:06Z
body: @isaacsas @TorkelE Incorporated most things (except making the network properties page dynamic which I'll leave for later).  
author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-12T01:02:05Z
body: @vyudu some more changes, most minor. After these I am happy to merge and finally release V15!!!
author_association: MEMBER
user: login: vyudu
created_at: 2025-04-13T21:47:33Z
body: @isaacsas Have gone through and made fixes
author_association: MEMBER
user: login: TorkelE
created_at: 2025-04-13T22:13:56Z
body: Haven't read the updated version (going to bed now), bit one thought. Noe that network analysis is its own section, wouldn't it make sense to put the network analysis section after the model simulation one? Simulation ls is arguably the primary aspect of Catalyst, so it would make sense to that it comes directly after the model creation section.
author_association: MEMBER
user: login: vyudu
created_at: 2025-04-13T22:19:05Z
body: I think that'd make sense. Away from computer so can't change it at the
moment but can get to it later tonight.

On Sun, Apr 13, 2025 at 6:14 PM Torkel Loman ***@***.***>
wrote:

> Haven't read the updated version (going to bed now), bit one thought. Noe
> that network analysis is its own section, wouldn't it make sense to put the
> network analysis section after the model simulation one? Simulation ls is
> arguably the primary aspect of Catalyst, so it would make sense to that it
> comes directly after the model creation section.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/SciML/Catalyst.jl/pull/1142#issuecomment-2800149465>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ANAOXYANDPDFVI6CII7FLX32ZLOTTAVCNFSM6AAAAABS4DPWQOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQMBQGE2DSNBWGU>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
> *TorkelE* left a comment (SciML/Catalyst.jl#1142)
> <https://github.com/SciML/Catalyst.jl/pull/1142#issuecomment-2800149465>
>
> Haven't read the updated version (going to bed now), bit one thought. Noe
> that network analysis is its own section, wouldn't it make sense to put the
> network analysis section after the model simulation one? Simulation ls is
> arguably the primary aspect of Catalyst, so it would make sense to that it
> comes directly after the model creation section.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/SciML/Catalyst.jl/pull/1142#issuecomment-2800149465>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ANAOXYANDPDFVI6CII7FLX32ZLOTTAVCNFSM6AAAAABS4DPWQOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQMBQGE2DSNBWGU>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-13T22:32:39Z
body: @vyudu, yes make that update when you chance, along with fixing the doc build error. I'd make that update myself but you seem to have your Catalyst fork setup so others don't have permission to push to it.
author_association: MEMBER
user: login: vyudu
created_at: 2025-04-14T03:29:51Z
body: Hmm not sure why that is, it seems like I have allow edits by maintainers on. But I think I've fixed it, let me know when I should re-run this when the doc build is working again
author_association: MEMBER
user: login: isaacsas
created_at: 2025-04-14T10:33:26Z
body: Doc builds were working yesterday outside this PR.  Are you sure that error isn’t from an update you made?
author_association: MEMBER
user: login: TorkelE
created_at: 2025-04-14T10:58:22Z
body: I think it because the Catalyst version on master have been updated to v15
```
version = "15.0.0"
```
however, in e.g. this PR the docs are marked as for Catlayst v14, which is incompatible with the Catalyst version.
author_association: MEMBER
user: login: TorkelE
created_at: 2025-04-14T11:01:52Z
body: I have updated the doc version here: https://github.com/SciML/Catalyst.jl/pull/1233 Once merged this one should be able to run the docs again.
author_association: MEMBER
user: login: TorkelE
created_at: 2025-04-14T14:44:32Z
body: Tests and docs passes now
author_association: MEMBER