YangLab-um / BiologicalOscillations.jl

A package for researchers working with biological oscillations
https://yanglab-um.github.io/BiologicalOscillations.jl/
MIT License
1 stars 2 forks source link

Add function to find all cycles and their types in any networks #25

Closed biphy closed 9 months ago

biphy commented 9 months ago

A function to find all cycles from a given connectivity matrix is added.

ftavella commented 9 months ago

Oh this is a great function to have!

ftavella commented 9 months ago

I'm copying here the functions that I removed from my PR in case they are useful:

"""
    single_addition_loop_length_and_type(connectivity::AbstractMatrix, loop_start::AbstractVector)

    Returns the length and type of a feedback loop given its start node for a single addition to a negative-feedback-only network.

# Arguments (Required)
- `connectivity::AbstractMatrix`: Connectivity matrix of a network
- `loop_start::AbstractVector`: Vector containing the coordinates of the start node of a feedback loop

# Returns
- `loop_properties::DataFrame`: DataFrame containing the length and type of a feedback loop
"""
function calculate_loop_length_and_type(connectivity::AbstractMatrix, loop_start::AbstractVector)
    nodes = size(connectivity, 1)
    feedback_sign = connectivity[loop_start...]
    loop_length = 1
    loop_type = "unknown"
    initial_node = loop_start[1]
    current_node = loop_start[2]
    next_node = loop_start[2]
    # Backtrace the loop
    iterations = 0
    while next_node != initial_node
        current_node = next_node
        next_node = findall(connectivity[next_node, :] .!= 0)[1]
        feedback_sign *= connectivity[current_node, next_node]
        loop_length += 1
        iterations += 1
        if iterations > nodes
            error("Loop length is larger than the number of nodes")
        end
    end

    if feedback_sign == 1
        loop_type = "positive"
    elseif feedback_sign == -1
        loop_type = "negative"
    end
    loop_properties = DataFrame(length = loop_length, type = loop_type)
    return loop_properties
end

"""
    classify_single_addition(reference_connectivity::AbstractMatrix, one_added_connectivity::AbstractMatrix)

    Returns the coherence and feedback loop type of a single addition to a reference network.

# Arguments (Required)
- `reference_connectivity::AbstractMatrix`: Connectivity matrix used as a reference for comparison
- `one_added_connectivity::AbstractMatrix`: Connectivity matrix with one addition with respect to the reference connectivity

# Returns
- `addition_properties::DataFrame`: DataFrame containing the coherence and feedback loop type of a single addition to a reference network
"""
function classify_single_addition(reference_connectivity::AbstractMatrix, one_added_connectivity::AbstractMatrix)
    # Check that the reference connectivity is a negative feedback network
    if !is_negative_feedback_network(reference_connectivity)
        error("Reference connectivity should be a negative feedback network")
    end
    added_edge_indices = findall(reference_connectivity .!= one_added_connectivity)[1]
    loop_start = [added_edge_indices[1], added_edge_indices[2]]
    loop_properties = calculate_loop_length_and_type(one_added_connectivity, loop_start)

    node_inputs = one_added_connectivity[added_edge_indices[1], :]
    node_coherence = calculate_node_coherence(node_inputs)
    if node_coherence.coherent[1] == 1 && node_coherence.incoherent[1] == 0
        loop_coherence = "coherent"
    elseif node_coherence.incoherent[1] == 1 && node_coherence.coherent[1] == 0
        loop_coherence = "incoherent"
    else
        loop_coherence = "unknown"
    end

    addition_properties = DataFrame(loop_coherence = loop_coherence, 
                                    loop_length = loop_properties.length, 
                                    loop_type = loop_properties.type)
    return addition_properties
end

and the tests I wrote for them

# Test calculate_loop_length_and_type
connectivity = [0 0 0 1 -1;-1 0 0 0 0;0 -1 0 0 0;0 0 -1 0 0;0 0 0 -1 0]
loop_start = [1, 4]
loop_properties = calculate_loop_length_and_type(connectivity, loop_start)
@test loop_properties.length[1] == 4
@test loop_properties.type[1] == "negative"

connectivity = [1 0 0 0 -1;-1 0 0 0 0;0 -1 0 0 0;0 0 -1 0 0;0 0 0 -1 0]
loop_start = [1, 1]
loop_properties = calculate_loop_length_and_type(connectivity, loop_start)
@test loop_properties.length[1] == 1
@test loop_properties.type[1] == "positive"

connectivity = [0 0 0 0 -1;-1 0 0 1 0;0 -1 0 0 0;0 0 -1 0 0;0 0 0 -1 0]
loop_start = [2, 4]
loop_properties = calculate_loop_length_and_type(connectivity, loop_start)
@test loop_properties.length[1] == 3
@test loop_properties.type[1] == "positive"

connectivity = [0 0 0 0 -1;-1 0 0 0 0;0 -1 0 1 0;0 0 -1 0 0;0 0 0 -1 0]
loop_start = [3, 4]
loop_properties = calculate_loop_length_and_type(connectivity, loop_start)
@test loop_properties.length[1] == 2
@test loop_properties.type[1] == "negative"

connectivity = [0 0 0 0 -1;-1 0 0 0 0;0 -1 0 -1 0;0 0 -1 0 0;0 0 0 -1 0]
loop_start = [3, 4]
loop_properties = calculate_loop_length_and_type(connectivity, loop_start)
@test loop_properties.length[1] == 2
@test loop_properties.type[1] == "positive"

# Test classify_single_addition
reference_connectivity = [0 0 -1;-1 0 0;0 -1 0]
one_add_connectivity = [1 0 -1;-1 0 0;0 -1 0]
addition_properties = classify_single_addition(reference_connectivity, one_add_connectivity)
@test addition_properties.loop_type[1] == "positive"
@test addition_properties.loop_length[1] == 1
@test addition_properties.loop_coherence[1] == "incoherent"

one_add_connectivity = [-1 0 -1;-1 0 0;0 -1 0]
addition_properties = classify_single_addition(reference_connectivity, one_add_connectivity)
@test addition_properties.loop_type[1] == "negative"
@test addition_properties.loop_length[1] == 1
@test addition_properties.loop_coherence[1] == "coherent"

one_add_connectivity = [0 1 -1;-1 0 0;0 -1 0]
addition_properties = classify_single_addition(reference_connectivity, one_add_connectivity)
@test addition_properties.loop_type[1] == "negative"
@test addition_properties.loop_length[1] == 2
@test addition_properties.loop_coherence[1] == "incoherent"

one_add_connectivity = [0 -1 -1;-1 0 0;0 -1 0]
addition_properties = classify_single_addition(reference_connectivity, one_add_connectivity)
@test addition_properties.loop_type[1] == "positive"
@test addition_properties.loop_length[1] == 2
@test addition_properties.loop_coherence[1] == "coherent"

reference_connectivity = [0 0 0 0 -1;-1 0 0 0 0;0 -1 0 0 0;0 0 -1 0 0;0 0 0 -1 0]
one_add_connectivity = [0 0 0 1 -1;-1 0 0 0 0;0 -1 0 0 0;0 0 -1 0 0;0 0 0 -1 0]
addition_properties = classify_single_addition(reference_connectivity, one_add_connectivity)
@test addition_properties.loop_type[1] == "negative"
@test addition_properties.loop_length[1] == 4
@test addition_properties.loop_coherence[1] == "incoherent"

# catch error
reference_connectivity_not_nf = [0 0 -1;-1 0 0; 0 -1 1]
one_add_connectivity = [1 0 -1;-1 0 0;0 -1 0]
@test_throws ErrorException classify_single_addition(reference_connectivity_not_nf, one_add_connectivity)