Skip to content

Commit

Permalink
adds the functions for managing equations
Browse files Browse the repository at this point in the history
  • Loading branch information
TorkelE committed Apr 1, 2024
1 parent 6c12728 commit 4330069
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 71 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ RuntimeGeneratedFunctions = "0.5.12"
Setfield = "1"
StructuralIdentifiability = "0.5.1"
SymbolicUtils = "1.0.3"
Symbolics = "5.14"
Symbolics = "5.27"
Unitful = "1.12.4"
julia = "1.9"

Expand Down
11 changes: 9 additions & 2 deletions docs/src/api/catalyst_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,17 @@ species
nonspecies
reactionparams
reactions
has_diff_equations
is_reaction
is_alg_equation
is_diff_equation
alg_equations
diff_equations
has_alg_equations
alg_equations
has_diff_equations
get_alg_eqs
get_diff_eqs
has_alg_eqs
has_diff_eqs
numspecies
numparams
numreactions
Expand Down
136 changes: 88 additions & 48 deletions src/networkapi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,54 +97,6 @@ function reactions(network)
[rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])]
end

"""
diff_equations(network)
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).
Notes:
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
"""
function diff_equations(network)
eqs = equations(network)
filter!(!isreaction, eqs)
systems = filter_nonrxsys(network)
isempty(systems) && (return rxs)
[rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])]
end

"""
has_diff_equations(network)
Given a [`ReactionSystem`](@ref), check whether it contain any differential equations (i.e. in addition to those generated through reactions).
Notes:
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
"""
function has_diff_equations(network)
return !isempty(diff_equations(network))
end

"""
alg_equations(network)
Given a [`ReactionSystem`](@ref), return a vector of all `Equations` in the system that are algebraic equations (does not contain any derivatives).
Notes:
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
"""
function alg_equations(network)
eqs = equations(network)
filter!(!isreaction, eqs)
filter!(!isreaction, eqs)
systems = filter_nonrxsys(network)
isempty(systems) && (return rxs)
[rxs; reduce(vcat, namespace_reactions.(systems); init = Reaction[])]
end

"""
has_alg_equations(network)
Given a [`ReactionSystem`](@ref), check whether it contain any algebraic equations.
Notes:
- If `ModelingToolkit.get_systems(network)` is not empty, will allocate.
"""
function has_alg_equations(network)
return !isempty(alg_equations(network))
end

"""
speciesmap(network)
Expand Down Expand Up @@ -633,6 +585,94 @@ end
symmap_to_varmap(sys, symmap) = symmap
#error("symmap_to_varmap requires a Dict, AbstractArray or Tuple to map Symbols to values.")


### Equation Handling Accessors ###

"""
is_reaction(eq::Equation)
Returns `true` if the input is a `Reaction`, else false.
"""
function is_reaction(eq::Equation)
return eq isa Reaction
end

"""
is_alg_equation(eq::Equation)
Returns `true` if the input is an algebraic equation that does not contain any differentials.
"""
function is_alg_equation(eq)
return isdefined(eq, :lhs) && isdefined(eq, :rhs) && !is_diff_equation(eq)
end

"""
is_diff_equation(eq::Equation)
Returns `true` if the input is an that contains at least one differential.
"""
function is_diff_equation(eq)
isdefined(eq, :lhs) && occursin(is_derivative, wrap(eq.lhs)) && (return true)
isdefined(eq, :rhs) && occursin(is_derivative, wrap(eq.rhs)) && (return true)
return false
end

"""
alg_equations(rs)
Returns all the algebraic equations (that does not contain differnetials) in `rs` and its subsystems.
"""
alg_equations(rs) = filter(is_alg_equation, equations(rs))

"""
diff_equations(rs)
Returns all the differnetials equations (equations that contain differnetials) in `rs` and its subsystems.
"""
diff_equations(rs) = filter(is_diff_equation, equations(rs))

"""
has_alg_equations(rs)
Returns true if `rs` (or any of its subsystems) has an algebraic equation (that does not contain a differential).
"""
has_alg_equations(rs) = any(is_alg_equation, equations(rs))

"""
has_diff_equations(rs)
Returns true if `rs` (or any of its subsystems) has a differnetials equations (equations that contain differnetials) .
"""
has_diff_equations(rs) = any(is_diff_equation, equations(rs))

"""
get_alg_eqs(rs)
Returns all the algebraic equations (that does not contain differnetials) in `rs` and (but not its subsystems).
"""
get_alg_eqs(rs) = filter(is_alg_equation, get_eqs(rs))

"""
diff_equations(rs)
Returns all the differnetial equations (equations that contain differnetials) in `rs` (but not its subsystems).
"""
get_diff_eqs(rs) = filter(is_diff_equation, get_eqs(rs))

"""
has_alg_equations(rs)
Returns true if `rs` has an algebraic equation (that does not contain a differential). Does not consider subsystems.
"""
has_alg_eqs(rs) = any(is_alg_equation, get_eqs(rs))

"""
has_diff_equations(rs)
Returns true if `rs` has a differnetial equations (equations that contain differnetials). Does not consider subsystems.
"""
has_diff_eqs(rs) = any(is_diff_equation, get_eqs(rs))

######################## reaction complexes and reaction rates ###############################

"""
Expand Down
49 changes: 40 additions & 9 deletions src/reaction_network.jl
Original file line number Diff line number Diff line change
Expand Up @@ -816,15 +816,35 @@ function make_observed_eqs(observables_expr)
end
end

# Checks if the `@default_noise_scaling` option is used. If so, read its input and adds it as a
# default metadata value to the `default_reaction_metadata` vector.
function check_default_noise_scaling!(default_reaction_metadata, options)
if haskey(options, :default_noise_scaling)
if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used.
error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
# Reads the variables options. Outputs:
# `vars_extracted`: A vector with extracted variables (lhs in pure differential equations only).
# `dtexpr`: If a differentialequation is defined, the default derrivative (D ~ Differential(t)) must be defined.
# `equations`: a vector with the equations provided.
function read_equations_options(options, variables_declared)
# Prepares the equations. First, extracts equations from provided option (converting to block form if requried).
# Next, uses MTK's `parse_equations!` function to split input into a vector with the equations.
eqs_input = haskey(options, :equations) ? options[:equations].args[3] : :(begin end)
eqs_input = option_block_form(eqs_input)
equations = Expr[]
ModelingToolkit.parse_equations!(Expr(:block), equations, Dict{Symbol, Any}(), eqs_input)

# Loops through all equations, checks for lhs of the form `D(X) ~ ...`.
# When this is the case, the variable X and differential D are extracted (for automatic declaration).
# Also performs simple error checks.
vars_extracted = []
add_default_diff = false
for eq in equations
((eq.head != :call) || (eq.args[1] != :~)) && error("Malformed equation: \"$eq\". Equation's left hand and right hand sides should be separated by a \"~\".")
(eq.args[2] isa Symbol || eq.args[2].head != :call) && continue
if (eq.args[2].args[1] == :D) && (eq.args[2].args[2] isa Symbol) && (length(eq.args[2].args) == 2)
diff_var = eq.args[2].args[2]
in(diff_var, forbidden_symbols_error) && error("A forbidden symbol ($(diff_var)) was used as an variable in this differential equation: $eq")
add_default_diff = true
in(diff_var, variables_declared) || push!(vars_extracted, diff_var)
end
push!(default_reaction_metadata.args, :(:noise_scaling => $(options[:default_noise_scaling].args[3])))
end

return vars_extracted, add_default_diff, equations
end

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

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

# Checks if the `@default_noise_scaling` option is used. If so, read its input and adds it as a
# default metadata value to the `default_reaction_metadata` vector.
function check_default_noise_scaling!(default_reaction_metadata, options)
if haskey(options, :default_noise_scaling)
if (length(options[:default_noise_scaling].args) != 3) # Becasue of how expressions are, 3 is used.
error("@default_noise_scaling should only have a single input, this appears not to be the case: \"$(options[:default_noise_scaling])\"")
end
push!(default_reaction_metadata.args, :(:noise_scaling => $(options[:default_noise_scaling].args[3])))
end
end

### Functionality for expanding function call to custom and specific functions ###

#Recursively traverses an expression and replaces special function call like "hill(...)" with the actual corresponding expression.
Expand Down
6 changes: 3 additions & 3 deletions src/reactionsystem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,9 @@ struct ReactionSystem{V <: NetworkProperties} <:
name, systems, defaults, connection_type, nps, cls, cevs, devs,
metadata, complete; checks::Bool = true)

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

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

if isempty(sivs) && (checks == true || (checks & MT.CheckUnits) > 0)
Expand Down
16 changes: 8 additions & 8 deletions test/dsl/dsl_options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -657,7 +657,7 @@ let

# Checks that the internal structures have the correct lengths.
@test length(species(rn)) == 1
@test length(states(rn)) == 3
@test length(unknowns(rn)) == 3
@test length(reactions(rn)) == 2
@test length(equations(rn)) == 4
@test !has_diff_equations(rn)
Expand All @@ -666,9 +666,9 @@ let
@test isequal(alg_equations(rn), [X + 5 ~ k*S, 3Y + X ~ S + X*d])

# Checks that the internal structures contain the correct stuff, and are correctly sorted.
@test isspecies(states(rn)[1])
@test !isspecies(states(rn)[2])
@test !isspecies(states(rn)[3])
@test isspecies(unknowns(rn)[1])
@test !isspecies(unknowns(rn)[2])
@test !isspecies(unknowns(rn)[3])
@test equations(rn)[1] isa Reaction
@test equations(rn)[2] isa Reaction
@test equations(rn)[3] isa Equation
Expand Down Expand Up @@ -769,7 +769,7 @@ let

# Checks that the internal structures have the correct lengths.
@test length(species(rn)) == 1
@test length(states(rn)) == 3
@test length(unknowns(rn)) == 3
@test length(reactions(rn)) == 2
@test length(equations(rn)) == 4
@test has_diff_equations(rn)
Expand All @@ -778,9 +778,9 @@ let
@test length(alg_equations(rn)) == 1

# Checks that the internal structures contain the correct stuff, and are correctly sorted.
@test isspecies(states(rn)[1])
@test !isspecies(states(rn)[2])
@test !isspecies(states(rn)[3])
@test isspecies(unknowns(rn)[1])
@test !isspecies(unknowns(rn)[2])
@test !isspecies(unknowns(rn)[3])
@test equations(rn)[1] isa Reaction
@test equations(rn)[2] isa Reaction
@test equations(rn)[3] isa Equation
Expand Down

0 comments on commit 4330069

Please sign in to comment.