Skip to content

Commit 0cf2781

Browse files
committed
adds the functions for managing equations
1 parent a299c84 commit 0cf2781

File tree

6 files changed

+148
-70
lines changed

6 files changed

+148
-70
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ RuntimeGeneratedFunctions = "0.5.12"
5454
Setfield = "1"
5555
StructuralIdentifiability = "0.5.1"
5656
SymbolicUtils = "1.0.3"
57-
Symbolics = "5.14"
57+
Symbolics = "5.27"
5858
Unitful = "1.12.4"
5959
julia = "1.9"
6060

docs/src/api/catalyst_api.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,17 @@ species
157157
nonspecies
158158
reactionparams
159159
reactions
160-
has_diff_equations
160+
is_reaction
161+
is_alg_equation
162+
is_diff_equation
163+
alg_equations
161164
diff_equations
162165
has_alg_equations
163-
alg_equations
166+
has_diff_equations
167+
get_alg_eqs
168+
get_diff_eqs
169+
has_alg_eqs
170+
has_diff_eqs
164171
numspecies
165172
numparams
166173
numreactions

src/networkapi.jl

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -97,54 +97,6 @@ function reactions(network)
9797
[rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])]
9898
end
9999

100-
"""
101-
diff_equations(network)
102-
Given a [`ReactionSystem`](@ref), return a vector of all `Equations` in the system that are differential equations (contains a derivative with respect to any variable).
103-
Notes:
104-
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
105-
"""
106-
function diff_equations(network)
107-
eqs = equations(network)
108-
filter!(!isreaction, eqs)
109-
systems = filter_nonrxsys(network)
110-
isempty(systems) && (return rxs)
111-
[rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])]
112-
end
113-
114-
"""
115-
has_diff_equations(network)
116-
Given a [`ReactionSystem`](@ref), check whether it contain any differential equations (i.e. in addition to those generated through reactions).
117-
Notes:
118-
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
119-
"""
120-
function has_diff_equations(network)
121-
return !isempty(diff_equations(network))
122-
end
123-
124-
"""
125-
alg_equations(network)
126-
Given a [`ReactionSystem`](@ref), return a vector of all `Equations` in the system that are algebraic equations (does not contain any derivatives).
127-
Notes:
128-
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
129-
"""
130-
function alg_equations(network)
131-
eqs = equations(network)
132-
filter!(!isreaction, eqs)
133-
filter!(!isreaction, eqs)
134-
systems = filter_nonrxsys(network)
135-
isempty(systems) && (return rxs)
136-
[rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])]
137-
end
138-
139-
"""
140-
has_alg_equations(network)
141-
Given a [`ReactionSystem`](@ref), check whether it contain any algebraic equations.
142-
Notes:
143-
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
144-
"""
145-
function has_alg_equations(network)
146-
return !isempty(alg_equations(network))
147-
end
148100

149101
"""
150102
speciesmap(network)
@@ -633,6 +585,94 @@ end
633585
symmap_to_varmap(sys, symmap) = symmap
634586
#error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.")
635587

588+
589+
### Equation Handling Accessors ###
590+
591+
"""
592+
is_reaction(eq::Equation)
593+
594+
Returns `true` if the input is a `Reaction`, else false.
595+
"""
596+
function is_reaction(eq::Equation)
597+
return eq isa Reaction
598+
end
599+
600+
"""
601+
is_alg_equation(eq::Equation)
602+
603+
Returns `true` if the input is an algebraic equation that does not contain any differentials.
604+
"""
605+
function is_alg_equation(eq)
606+
return isdefined(eq, :lhs) && isdefined(eq, :rhs) && !is_diff_equation(eq)
607+
end
608+
609+
"""
610+
is_diff_equation(eq::Equation)
611+
612+
Returns `true` if the input is an that contains at least one differential.
613+
"""
614+
function is_diff_equation(eq)
615+
isdefined(eq, :lhs) && occursin(is_derivative, wrap(eq.lhs)) && (return true)
616+
isdefined(eq, :rhs) && occursin(is_derivative, wrap(eq.rhs)) && (return true)
617+
return false
618+
end
619+
620+
"""
621+
alg_equations(rs)
622+
623+
Returns all the algebraic equations (that does not contain differnetials) in `rs` and its subsystems.
624+
"""
625+
alg_equations(rs) = filter(is_alg_equation, equations(rs))
626+
627+
"""
628+
diff_equations(rs)
629+
630+
Returns all the differnetials equations (equations that contain differnetials) in `rs` and its subsystems.
631+
"""
632+
diff_equations(rs) = filter(is_diff_equation, equations(rs))
633+
634+
"""
635+
has_alg_equations(rs)
636+
637+
Returns true if `rs` (or any of its subsystems) has an algebraic equation (that does not contain a differential).
638+
"""
639+
has_alg_equations(rs) = any(is_alg_equation, equations(rs))
640+
641+
"""
642+
has_diff_equations(rs)
643+
644+
Returns true if `rs` (or any of its subsystems) has a differnetials equations (equations that contain differnetials) .
645+
"""
646+
has_diff_equations(rs) = any(is_diff_equation, equations(rs))
647+
648+
"""
649+
get_alg_eqs(rs)
650+
651+
Returns all the algebraic equations (that does not contain differnetials) in `rs` and (but not its subsystems).
652+
"""
653+
get_alg_eqs(rs) = filter(is_alg_equation, get_eqs(rs))
654+
655+
"""
656+
diff_equations(rs)
657+
658+
Returns all the differnetial equations (equations that contain differnetials) in `rs` (but not its subsystems).
659+
"""
660+
get_diff_eqs(rs) = filter(is_diff_equation, get_eqs(rs))
661+
662+
"""
663+
has_alg_equations(rs)
664+
665+
Returns true if `rs` has an algebraic equation (that does not contain a differential). Does not consider subsystems.
666+
"""
667+
has_alg_eqs(rs) = any(is_alg_equation, get_eqs(rs))
668+
669+
"""
670+
has_diff_equations(rs)
671+
672+
Returns true if `rs` has a differnetial equations (equations that contain differnetials). Does not consider subsystems.
673+
"""
674+
has_diff_eqs(rs) = any(is_diff_equation, get_eqs(rs))
675+
636676
######################## reaction complexes and reaction rates ###############################
637677

638678
"""

src/reaction_network.jl

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -816,15 +816,35 @@ function make_observed_eqs(observables_expr)
816816
end
817817
end
818818

819-
# Checks if the `@default_noise_scaling` option is used. If so, read its input and adds it as a
820-
# default metadata value to the `default_reaction_metadata` vector.
821-
function check_default_noise_scaling!(default_reaction_metadata, options)
822-
if haskey(options, :default_noise_scaling)
823-
if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used.
824-
error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
819+
# Reads the variables options. Outputs:
820+
# `vars_extracted`: A vector with extracted variables (lhs in pure differential equations only).
821+
# `dtexpr`: If a differentialequation is defined, the default derrivative (D ~ Differential(t)) must be defined.
822+
# `equations`: a vector with the equations provided.
823+
function read_equations_options(options, variables_declared)
824+
# Prepares the equations. First, extracts equations from provided option (converting to block form if requried).
825+
# Next, uses MTK's `parse_equations!` function to split input into a vector with the equations.
826+
eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end)
827+
eqs_input = option_block_form(eqs_input)
828+
equations = Expr[]
829+
ModelingToolkit.parse_equations!(Expr(:block), equations, Dict{Symbol, Any}(), eqs_input)
830+
831+
# Loops through all equations, checks for lhs of the form `D(X) ~ ...`.
832+
# When this is the case, the variable X and differential D are extracted (for automatic declaration).
833+
# Also performs simple error checks.
834+
vars_extracted = []
835+
add_default_diff = false
836+
for eq in equations
837+
((eq.head != :call) || (eq.args[1] != :~)) && error("Malformed equation: \"$eq\". Equation's left hand and right hand sides should be separated by a \"~\".")
838+
(eq.args[2] isa Symbol || eq.args[2].head != :call) && continue
839+
if (eq.args[2].args[1] == :D) && (eq.args[2].args[2] isa Symbol) && (length(eq.args[2].args) == 2)
840+
diff_var = eq.args[2].args[2]
841+
in(diff_var, forbidden_symbols_error) && error("A forbidden symbol ($(diff_var)) was used as an variable in this differential equation: $eq")
842+
add_default_diff = true
843+
in(diff_var, variables_declared) || push!(vars_extracted, diff_var)
825844
end
826-
push!(default_reaction_metadata.args, :(:noise_scaling => $(options[:default_noise_scaling].args[3])))
827845
end
846+
847+
return vars_extracted, add_default_diff, equations
828848
end
829849

830850
# Creates an expression declaring differentials.
@@ -850,8 +870,8 @@ end
850870
# Read the events (continious or discrete) provided as options to the DSL. Returns an expression which evalutes to these.
851871
function read_events_option(options, event_type::Symbol)
852872
# Prepares the events, if required to, converts them to block form.
853-
(event_type in [:continuous_events]) || error("Trying to read an unsupported event type.")
854-
events_input = haskey(options, event_type) ? options[event_type].args[3] : :(begin end)
873+
(event_type in [:continuous_events, :discrete_events]) || error("Trying to read an unsupported event type.")
874+
events_input = haskey(options, event_type) ? options[event_type].args[3] : MacroTools.striplines(:(begin end))
855875
events_input = option_block_form(events_input)
856876

857877
# Goes throgh the events, checks for errors, and adds them to the output vector.
@@ -874,6 +894,17 @@ function read_events_option(options, event_type::Symbol)
874894
return events_expr
875895
end
876896

897+
# Checks if the `@default_noise_scaling` option is used. If so, read its input and adds it as a
898+
# default metadata value to the `default_reaction_metadata` vector.
899+
function check_default_noise_scaling!(default_reaction_metadata, options)
900+
if haskey(options, :default_noise_scaling)
901+
if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used.
902+
error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
903+
end
904+
push!(default_reaction_metadata.args, :(:noise_scaling => $(options[:default_noise_scaling].args[3])))
905+
end
906+
end
907+
877908
### Functionality for expanding function call to custom and specific functions ###
878909

879910
#Recursively traverses an expression and replaces special function call like "hill(...)" with the actual corresponding expression.

src/reactionsystem.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ struct ReactionSystem{V <: NetworkProperties} <:
553553

554554
# Filters away any potential obervables from `states` and `spcs`.
555555
obs_vars = [obs_eq.lhs for obs_eq in observed]
556-
states = filter(state -> !any(isequal(state, obs_var) for obs_var in obs_vars), states)
556+
unknowns = filter(state -> !any(isequal(state, obs_var) for obs_var in obs_vars), unknowns)
557557
spcs = filter(spc -> !any(isequal(spc, obs_var) for obs_var in obs_vars), spcs)
558558

559559
# unit checks are for ODEs and Reactions only currently
@@ -563,7 +563,7 @@ struct ReactionSystem{V <: NetworkProperties} <:
563563
check_parameters(ps, iv)
564564
nonrx_eqs = Equation[eq for eq in eqs if eq isa Equation]
565565
!isempty(nonrx_eqs) && check_equations(nonrx_eqs, iv)
566-
check_equations(equations(cevs), iv)
566+
!isempty(cevs) && check_equations(equations(cevs), iv)
567567
end
568568

569569
if isempty(sivs) && (checks == true || (checks & MT.CheckUnits) > 0)

test/dsl/dsl_options.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ let
702702

703703
# Checks that the internal structures have the correct lengths.
704704
@test length(species(rn)) == 1
705-
@test length(states(rn)) == 3
705+
@test length(unknowns(rn)) == 3
706706
@test length(reactions(rn)) == 2
707707
@test length(equations(rn)) == 4
708708
@test !has_diff_equations(rn)
@@ -711,9 +711,9 @@ let
711711
@test isequal(alg_equations(rn), [X + 5 ~ k*S, 3Y + X ~ S + X*d])
712712

713713
# Checks that the internal structures contain the correct stuff, and are correctly sorted.
714-
@test isspecies(states(rn)[1])
715-
@test !isspecies(states(rn)[2])
716-
@test !isspecies(states(rn)[3])
714+
@test isspecies(unknowns(rn)[1])
715+
@test !isspecies(unknowns(rn)[2])
716+
@test !isspecies(unknowns(rn)[3])
717717
@test equations(rn)[1] isa Reaction
718718
@test equations(rn)[2] isa Reaction
719719
@test equations(rn)[3] isa Equation
@@ -814,7 +814,7 @@ let
814814

815815
# Checks that the internal structures have the correct lengths.
816816
@test length(species(rn)) == 1
817-
@test length(states(rn)) == 3
817+
@test length(unknowns(rn)) == 3
818818
@test length(reactions(rn)) == 2
819819
@test length(equations(rn)) == 4
820820
@test has_diff_equations(rn)
@@ -823,9 +823,9 @@ let
823823
@test length(alg_equations(rn)) == 1
824824

825825
# Checks that the internal structures contain the correct stuff, and are correctly sorted.
826-
@test isspecies(states(rn)[1])
827-
@test !isspecies(states(rn)[2])
828-
@test !isspecies(states(rn)[3])
826+
@test isspecies(unknowns(rn)[1])
827+
@test !isspecies(unknowns(rn)[2])
828+
@test !isspecies(unknowns(rn)[3])
829829
@test equations(rn)[1] isa Reaction
830830
@test equations(rn)[2] isa Reaction
831831
@test equations(rn)[3] isa Equation

0 commit comments

Comments
 (0)