From 2be692b842178ec559cb3d623145126b4a15b255 Mon Sep 17 00:00:00 2001 From: Reuben Gardos Reid <5456207+ReubenJ@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:59:50 +0200 Subject: [PATCH] feat: add `default_target_function` Create a default target function as described in Eq. 3 from the QN paper (https://doi.org/10.1186/1752-0509-1-4). --- src/GraphDynamicalSystems.jl | 3 ++- src/qualitative_networks.jl | 51 ++++++++++++++++++++++++++++++++++++ test/qn_test.jl | 29 ++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/GraphDynamicalSystems.jl b/src/GraphDynamicalSystems.jl index baa69dd..684c110 100644 --- a/src/GraphDynamicalSystems.jl +++ b/src/GraphDynamicalSystems.jl @@ -23,6 +23,7 @@ export QualitativeNetwork, get_domain, target_functions, interpret, - create_qn_system + create_qn_system, + default_target_function end diff --git a/src/qualitative_networks.jl b/src/qualitative_networks.jl index b90a8b5..49b21f0 100644 --- a/src/qualitative_networks.jl +++ b/src/qualitative_networks.jl @@ -158,6 +158,57 @@ function build_qn_grammar( return g end +""" + $TYPEDSIGNATURES + +Construct a default target function for an entity in a QN from a list of +`activators` and `inhibitors`. + +Follows the definition given in Eq. 3 of ["Qualitative networks: a symbolic +approach to analyze biological signaling +networks"](https://doi.org/10.1186/1752-0509-1-4). + +## Examples + +Say we have a component `X` and it has an lower bound on its state value of 0, +an upper bound of 4, activators `A`, `B`, `C`, and inhibitors `D`, `E`, `F`, +then the following example constructs an expression for its default target +function. + +```jldoctest +julia> default_target_function(0, 4, [:A, :B, :C], [:D, :E, :F]) +:(max(0, (A + B + C) / 3 - (D + E + F) / 3)) +``` + +""" +function default_target_function( + lower_bound::Integer, + upper_bound::Integer, + activators::AbstractVector = [], + inhibitors::AbstractVector = [], +) + sum_only_or_nothing = x -> if length(x) == 0 + nothing + elseif length(x) == 1 + :($(only(x))) + elseif length(x) > 1 + :($(Expr(:call, :+, x...)) / $(length(x))) + end + + expr_activators = sum_only_or_nothing(activators) + expr_inhibitors = sum_only_or_nothing(inhibitors) + + if isnothing(expr_activators) && isnothing(expr_inhibitors) + error("Constructing a default target function for a QN with no \ + activators or inhibitors.") + elseif isnothing(expr_activators) # no activators, special case mentioned in paper + return :($upper_bound - $expr_inhibitors) + elseif isnothing(expr_inhibitors) + return :($expr_activators) + else + return :(max($lower_bound, $expr_activators - $expr_inhibitors)) + end +end struct Entity{I} target_function::Any diff --git a/test/qn_test.jl b/test/qn_test.jl index 70dd73a..3819732 100644 --- a/test/qn_test.jl +++ b/test/qn_test.jl @@ -112,3 +112,32 @@ end basins = basins_of_attraction(mapper, grid) end + +@testitem "Construct default target functions" begin + lower_bound = 0 + upper_bound = 4 + activators = [:A, :B, :C] + inhibitors = [:D, :E, :F] + + @test default_target_function(lower_bound, upper_bound, activators, inhibitors) == + :(max($lower_bound, (A + B + C) / 3 - (D + E + F) / 3)) + + activators = [:A, :B] + inhibitors = [:D] + + @test default_target_function(lower_bound, upper_bound, activators, inhibitors) == + :(max($lower_bound, (A + B) / 2 - D)) + + activators = [] + inhibitors = [:D] + + @test default_target_function(lower_bound, upper_bound, activators, inhibitors) == + :($upper_bound - D) + + activators = [:A] + inhibitors = [] + + @test default_target_function(lower_bound, upper_bound, activators, inhibitors) == :(A) + + @test_throws r"no activators or inhibitors" default_target_function(0, 4) +end