network.f90 Source File


This file depends on

sourcefile~~network.f90~~EfferentGraph sourcefile~network.f90 network.f90 sourcefile~kinds.f90 kinds.f90 sourcefile~network.f90->sourcefile~kinds.f90

Files dependent on this one

sourcefile~~network.f90~~AfferentGraph sourcefile~network.f90 network.f90 sourcefile~common.f90 common.f90 sourcefile~common.f90->sourcefile~network.f90 sourcefile~network_io.f90 network_io.f90 sourcefile~common.f90->sourcefile~network_io.f90 sourcefile~dynamics.f90 dynamics.f90 sourcefile~common.f90->sourcefile~dynamics.f90 sourcefile~dynamics_base.f90 dynamics_base.f90 sourcefile~dynamics_base.f90->sourcefile~network.f90 sourcefile~dynamics_hb_oga.f90 dynamics_HB_OGA.f90 sourcefile~dynamics_hb_oga.f90->sourcefile~network.f90 sourcefile~dynamics_hb_oga.f90->sourcefile~dynamics_base.f90 sourcefile~dynamics_nb_oga.f90 dynamics_NB_OGA.f90 sourcefile~dynamics_nb_oga.f90->sourcefile~network.f90 sourcefile~dynamics_nb_oga.f90->sourcefile~dynamics_base.f90 sourcefile~network_io.f90->sourcefile~network.f90 sourcefile~dynamics.f90->sourcefile~dynamics_base.f90 sourcefile~dynamics_chooser.f90 dynamics_chooser.f90 sourcefile~dynamics.f90->sourcefile~dynamics_chooser.f90 sourcefile~dynamics_chooser.f90->sourcefile~dynamics_base.f90 sourcefile~dynamics_chooser.f90->sourcefile~dynamics_hb_oga.f90 sourcefile~dynamics_chooser.f90->sourcefile~dynamics_nb_oga.f90

Source Code

module hyperSIS_network_mod
    use hyperSIS_kinds_mod
    use datastructs_mod, only: dynamical_list_t, fixed_list_t
    use datastructs_mod, only: log_unit, log_write, LOGGER_OK, LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG
    implicit none
    private

    !> Interface for the constructor of the network type.
    interface network
        module procedure network_new
    end interface

    !> Interface for the constructor of the hyperedge type.
    !> It receives the order OR a list of nodes and returns a new hyperedge object.
    interface hyperedge
        module procedure hyperedge_new, hyperedge_new_from_nodes_list
    end interface

    !> Hyperedge object to represent a hyperedge in the network.
    !> It contains an ID, order m, and list of m + 1 nodes belonging to the hyperedge.
    type hyperedge_t
        integer(kind=i4) :: order = -1
        integer(kind=i4), allocatable :: nodes(:), dual_edges(:)
    end type

    !> Node object to represent a node in the network.
    !> It contains an ID, degree, and list of hyperedges that the node belongs to.
    type node_t
        integer(kind=i4) :: degree = 0
        integer(kind=i4), allocatable :: edges(:), dual_nodes(:)
    end type

    type network_props_t
        logical :: cleaned_null_edges = .false. ! .true. if all edges have at least a min_order
        logical :: removed_invalid_nodes_and_edges = .false. ! .true. if all nodes and edges are valid
        logical :: checked_topology_consistency = .false. ! .true. if the topology is consistent (nodes are connected to edges and vice versa)
        integer(kind=i4) :: min_order = -1 ! minimum order of the edges
    end type

    !> Network object to represent the entire hypergraph.
    !> It contains the number of nodes, number of edges, and arrays of nodes and edges objects.
    type :: network_t
        integer(kind=i4) :: num_nodes = 0
        integer(kind=i4) :: num_edges = 0
        integer(kind=i4) :: max_order = 0
        type(node_t), allocatable :: nodes(:)
        type(hyperedge_t), allocatable :: edges(:)
        type(network_props_t) :: props

        ! extra properties
        type(fixed_list_t), allocatable :: nodes_per_degree(:), edges_per_order(:)
    contains
        procedure :: init => network_init

        procedure :: build_edges_from_nodes => network_build_edges_from_nodes
        procedure :: build_nodes_from_edges => network_build_nodes_from_edges

        procedure :: print_nodes_and_edges => network_print_nodes_and_edges

        procedure :: clear_null_edges => network_clear_null_edges ! we can only remove the null edges (order -1)
        procedure :: check_topology_consistency => network_check_topology_consistency ! we can only check the topology consistency
        procedure :: remove_invalid_nodes_and_edges => network_remove_invalid_nodes_and_edges
        procedure :: clear_and_check_all => network_clear_and_check_all ! we can only check the topology consistency
        procedure :: reset_props => network_reset_props

        procedure :: destroy => network_destroy
        final :: network_finalizer
    end type

    public :: network, hyperedge
    public :: network_t, hyperedge_t, node_t

contains

    subroutine network_reset_props(net)
        class(network_t), intent(inout) :: net

        net%props%cleaned_null_edges = .false.
        net%props%removed_invalid_nodes_and_edges = .false.
        net%props%checked_topology_consistency = .false.
        net%props%min_order = -1

    end subroutine network_reset_props

    subroutine network_clear_and_check_all(net, min_order)
        class(network_t), intent(inout) :: net
        integer(kind=i4), intent(in), optional :: min_order

        net%max_order = maxval(net%edges(:)%order)

        if ((.not. net%props%cleaned_null_edges) .or. (min_order /= net%props%min_order)) call net%clear_null_edges(min_order)
        if (.not. net%props%removed_invalid_nodes_and_edges) call net%remove_invalid_nodes_and_edges()
        if (.not. net%props%checked_topology_consistency) call net%check_topology_consistency()

    end subroutine network_clear_and_check_all

    subroutine network_remove_invalid_nodes_and_edges(net)
        class(network_t), intent(inout) :: net

        ! It will remove the nodes with degree 0
        ! and edges with order -1
        ! It will be necessary to reindex everything

        ! Check nodes with degree = 0
        block
            integer(kind=i4), allocatable :: new_nodes_indexes(:)
            integer(kind=i4), allocatable :: new_edges_indexes(:)
            integer(kind=i4) :: new_num_nodes, new_num_edges, i, j, node_id, edge_id
            type(node_t), allocatable :: new_nodes(:)
            type(hyperedge_t), allocatable :: new_edges(:)

            allocate(new_nodes_indexes(net%num_nodes))
            allocate(new_nodes(net%num_nodes))

            new_num_nodes = 0
            new_nodes_indexes = -1
            do node_id = 1, net%num_nodes
                if (net%nodes(node_id)%degree /= 0) then
                    new_num_nodes = new_num_nodes + 1
                    new_nodes_indexes(node_id) = new_num_nodes

                    block
                        integer(kind=i4), allocatable :: new_edges_list(:)
                        integer(kind=i4) :: new_degree

                        allocate(new_edges_list(net%nodes(node_id)%degree))

                        new_degree = 0
                        do j = 1, net%nodes(node_id)%degree
                            edge_id = net%nodes(node_id)%edges(j)
                            if (net%edges(edge_id)%order /= -1) then ! valid edge
                                new_degree = new_degree + 1
                                new_edges_list(new_degree) = edge_id
                            end if
                        end do

                        deallocate(net%nodes(node_id)%edges)
                        net%nodes(node_id)%degree = new_degree
                        net%nodes(node_id)%edges = new_edges_list(1:new_degree)

                        new_nodes(new_num_nodes) = net%nodes(node_id)
                    end block
                end if
            end do

            deallocate(net%nodes)
            allocate(net%nodes(new_num_nodes))
            net%num_nodes = new_num_nodes
            net%nodes = new_nodes(1:new_num_nodes)
            deallocate(new_nodes)

            call log_write(LOG_DEBUG, 'New number of nodes:', net%num_nodes)

            ! Now, we need to reindex the nodes in each edge, and remove those with order -1
            allocate(new_edges_indexes(net%num_edges))
            allocate(new_edges(net%num_edges))

            new_num_edges = 0
            new_edges_indexes = -1
            do edge_id = 1, net%num_edges
                if (net%edges(edge_id)%order /= -1) then
                    new_num_edges = new_num_edges + 1
                    new_edges_indexes(edge_id) = new_num_edges

                    ! reindex the nodes in the edge
                    do i = 1, net%edges(edge_id)%order + 1
                        node_id = net%edges(edge_id)%nodes(i)
                        if (new_nodes_indexes(node_id) == -1) error stop 'should not happen! [node_id]'
                        net%edges(edge_id)%nodes(i) = new_nodes_indexes(node_id)
                    end do

                    new_edges(new_num_edges) = net%edges(edge_id)
                end if
            end do

            deallocate(net%edges)
            allocate(net%edges(new_num_edges))
            net%num_edges = new_num_edges
            net%edges = new_edges(1:new_num_edges)
            deallocate(new_edges)

            call log_write(LOG_DEBUG, 'New number of edges:', net%num_edges)

            ! Finally, update the edge_ids in the nodes
            do node_id = 1, net%num_nodes
                do i = 1, net%nodes(node_id)%degree
                    edge_id = net%nodes(node_id)%edges(i)
                    if (new_edges_indexes(edge_id) == -1) error stop 'should not happen! [edge_id]'
                    net%nodes(node_id)%edges(i) = new_edges_indexes(edge_id)
                end do
            end do
        end block

        net%props%checked_topology_consistency = .false.
        net%props%removed_invalid_nodes_and_edges = .true.
    end subroutine

    subroutine network_clear_null_edges(net, min_order_input)
        class(network_t), intent(inout) :: net
        integer(kind=i4), intent(in), optional :: min_order_input
        integer(kind=i4) :: min_order, i, j, edge_id, node_id, count
        integer(kind=i4), allocatable :: edges_with_smaller_order(:)

        if (present(min_order_input)) then
            min_order = min_order_input
        else
            min_order = 1 ! default is to have at least two nodes
        end if

        ! loop over the edges
        ! if the order is less than min_order, remove the edge
        ! and update the respective nodes
        count = 0
        do i = 1, net%num_edges
            if (net%edges(i)%order < min_order) then
                count = count + 1
            end if
        end do
        allocate(edges_with_smaller_order(count))
        count = 0
        do i = 1, net%num_edges
            if (net%edges(i)%order < min_order) then
                count = count + 1
                edges_with_smaller_order(count) = i
            end if
        end do

        call log_write(LOG_DEBUG, 'Number of edges with order smaller than', min_order, .false.)
        call log_write(LOG_DEBUG, 'is', size(edges_with_smaller_order))

        do i = 1, size(edges_with_smaller_order)
            edge_id = edges_with_smaller_order(i)

            ! we will loop over the nodes and delete the respective position there and shrink the list
            do j = 1, net%edges(edge_id)%order + 1
                node_id = net%edges(edge_id)%nodes(j)
                call clear_node_id_edges_list()
            end do

            ! remove edge
            net%edges(edge_id)%order = -1
            deallocate(net%edges(edge_id)%nodes)
        end do

        net%props%min_order = min_order
        net%props%cleaned_null_edges = .true.
        net%props%checked_topology_consistency = .false.

    contains

        subroutine clear_node_id_edges_list()
            integer(kind=i4) :: i, j, new_degree
            integer(kind=i4), allocatable :: new_edges(:)

            new_degree = net%nodes(node_id)%degree
            do i = 1, net%nodes(node_id)%degree
                if (net%nodes(node_id)%edges(i) == edge_id) then
                    net%nodes(node_id)%edges(i) = net%nodes(node_id)%edges(new_degree)
                    new_degree = new_degree - 1
                end if
            end do

            net%nodes(node_id)%degree = new_degree

            allocate(new_edges(new_degree))
            new_edges(1:new_degree) = net%nodes(node_id)%edges(1:new_degree)
            call move_alloc(new_edges, net%nodes(node_id)%edges)

        end subroutine

    end subroutine network_clear_null_edges

    subroutine network_check_topology_consistency(net)
        class(network_t), intent(inout) :: net
        integer(kind=i4) :: node_id, edge_id, i, j
        logical :: is_consistent

        is_consistent = .true.

        ! loop over nodes and check if they are in their respective edges
        do node_id = 1, net%num_nodes
            if (.not. allocated(net%nodes(node_id)%edges)) then
                if (.not. net%nodes(node_id)%degree == 0) then
                    call log_write(LOG_WARNING, 'Node', node_id, .false.)
                    call log_write(LOG_WARNING, 'has degree', net%nodes(node_id)%degree, .false.)
                    call log_write(LOG_WARNING, 'but no edges')
                    is_consistent = .false.
                    cycle
                end if
            end if
            do i = 1, net%nodes(node_id)%degree
                edge_id = net%nodes(node_id)%edges(i)
                ! check if the node is in the edge
                if (findloc(net%edges(edge_id)%nodes, node_id, dim=1) == 0) then
                    call log_write(LOG_WARNING, 'Node', node_id, .false.)
                    call log_write(LOG_WARNING, 'is not in edge', edge_id)
                    is_consistent = .false.
                end if
            end do
        end do

        ! loop over edges and check if they are in their respective nodes
        do edge_id = 1, net%num_edges
            if (.not. allocated(net%edges(edge_id)%nodes)) then
                if (.not. net%edges(edge_id)%order == -1) then
                    call log_write(LOG_WARNING, 'Edge', edge_id, .false.)
                    call log_write(LOG_WARNING, 'has order', net%edges(edge_id)%order, .false.)
                    call log_write(LOG_WARNING, 'but no nodes')
                    is_consistent = .false.
                    cycle
                end if
            end if
            do j = 1, net%edges(edge_id)%order + 1
                node_id = net%edges(edge_id)%nodes(j)
                ! check if the edge is in the node
                if (findloc(net%nodes(node_id)%edges, edge_id, dim=1) == 0) then
                    call log_write(LOG_WARNING, 'Edge', edge_id, .false.)
                    call log_write(LOG_WARNING, 'is not in node', node_id)
                    is_consistent = .false.
                end if
            end do
        end do

        net%props%checked_topology_consistency = is_consistent

        if (.not. is_consistent) then
            call log_write(LOG_ERROR, 'Topology is not consistent')
            error stop ''
        else
            call log_write(LOG_DEBUG, 'Topology is consistent')
        end if

    end subroutine network_check_topology_consistency

    subroutine network_init(net, num_nodes, num_edges)
        class(network_t) :: net
        integer(kind=i4), intent(in) :: num_nodes, num_edges

        net%num_nodes = num_nodes
        net%num_edges = num_edges
        allocate(net%nodes(num_nodes))
        allocate(net%edges(num_edges))

        net%nodes(:)%degree = 0
        net%edges(:)%order = -1
    end subroutine

    !> Constructor for the network type.
    !> It initializes the number of nodes and edges, and allocates memory for the nodes and edges arrays.
    !> @param net The network object to be initialized.
    !> @param num_nodes The number of nodes in the network.
    !> @param num_edges The number of edges in the network.
    !> @note This subroutine allocates memory for the nodes and edges arrays based on the provided sizes.
    !> @note The arrays are allocated with the sizes specified by num_nodes and num_edges.
    function network_new(num_nodes, num_edges) result(net)
        integer(kind=i4), intent(in) :: num_nodes, num_edges
        type(network_t) :: net

        call net%init(num_nodes, num_edges)

    end function network_new

    !> Constructor for the hyperedge type.
    !> It initializes the ID and order of the hyperedge, and allocates memory for the nodes array.
    !> @param order The order of the hyperedge.
    !> @note This subroutine allocates memory for the nodes array based on the provided size.
    elemental function hyperedge_new(order) result(edge)
        integer(kind=i4), intent(in) :: order
        type(hyperedge_t) :: edge

        edge%order = order
        allocate(edge%nodes(order + 1))
    end function hyperedge_new

    !> Constructor for the hyperedge type from a list of nodes.
    function hyperedge_new_from_nodes_list(nodes) result(edge)
        integer(kind=i4), intent(in) :: nodes(:)
        type(hyperedge_t) :: edge

        edge%order = size(nodes) - 1
        allocate(edge%nodes(size(nodes)))
        edge%nodes = nodes
    end function hyperedge_new_from_nodes_list

    !> Subroutine to build edges from nodes in the network.
    !> It iterates over each node and assigns the corresponding edges to the hyperedges.
    !> @param net The network object containing the nodes and edges.
    !> @note This subroutine modifies the edges of the network based on the nodes' connections.
    !> @note The edges are built by iterating over each node and assigning the corresponding edges to the hyperedges.
    !> @note The last_edge_index array is used to keep track of the last index for each hyperedge.
    subroutine network_build_edges_from_nodes(net)
        class(network_t) :: net
        integer(kind=i4) :: id_edge, i, j
        integer(kind=i4) :: last_edge_index(net%num_edges)

        last_edge_index = 0

        ! Loop over each node in the network
        do i = 1, net%num_nodes
            ! Loop over each hyperedge that the node belongs to
            do j = 1, net%nodes(i)%degree
                ! Add the node to the corresponding hyperedge
                id_edge = net%nodes(i)%edges(j)
                net%edges(id_edge)%nodes(last_edge_index(id_edge) + 1) = i
                last_edge_index(id_edge) = last_edge_index(id_edge) + 1
            end do
        end do

    end subroutine

    subroutine network_build_nodes_from_edges(net)
        class(network_t) :: net
        integer(kind=i4) :: node_id, edge_id, i
        integer(kind=i4) :: last_node_index(net%num_nodes)

        last_node_index = 0

        ! Loop over each hyperedge in the network
        do edge_id = 1, net%num_edges
            ! Loop over each node that belongs to the hyperedge
            do i = 1, net%edges(edge_id)%order + 1
                ! Add the hyperedge to the corresponding node
                node_id = net%edges(edge_id)%nodes(i)
                net%nodes(node_id)%edges(last_node_index(node_id) + 1) = edge_id
                last_node_index(node_id) = last_node_index(node_id) + 1
            end do
        end do

    end subroutine network_build_nodes_from_edges

    subroutine network_finalizer(net)
        type(network_t), intent(inout) :: net
        call net%destroy()
    end subroutine network_finalizer

    subroutine network_destroy(net)
        class(network_t), intent(inout) :: net
        integer(kind=i4) :: i

        ! Deallocate the edges and nodes arrays
        do i = 1, net%num_nodes
            if (allocated(net%nodes(i)%edges)) deallocate(net%nodes(i)%edges)
            if (allocated(net%nodes(i)%dual_nodes)) deallocate(net%nodes(i)%dual_nodes)
        end do
        do i = 1, net%num_edges
            if (allocated(net%edges(i)%nodes)) deallocate(net%edges(i)%nodes)
            if (allocated(net%edges(i)%dual_edges)) deallocate(net%edges(i)%dual_edges)
        end do

        if (allocated(net%nodes)) deallocate(net%nodes)
        if (allocated(net%edges)) deallocate(net%edges)

        net%num_nodes = 0
        net%num_edges = 0

    end subroutine network_destroy

    subroutine network_print_nodes_and_edges(net)
        class(network_t), intent(in) :: net
        integer(kind=i4) :: i

        ! Print each node
        write(*,fmt_general) 'Nodes:'
        do i = 1, net%num_nodes
            if (net%nodes(i)%degree > 0) then
                write(*,fmt_general) 'Node', i, 'degree:', net%nodes(i)%degree, 'edges:', net%nodes(i)%edges
            else
                write(*,fmt_general) 'Node', i, 'degree:', net%nodes(i)%degree, ' (isolated)'
            end if
        end do

        ! Print each hyperedge
        write(*,fmt_general) 'Hyperedges:'
        do i = 1, net%num_edges
            if (net%edges(i)%order >= 0) then
                write(*,fmt_general) 'Hyperedge', i, 'order:', net%edges(i)%order, '(', net%edges(i)%order +1, 'nodes )', &
                                                         'nodes:', net%edges(i)%nodes
            else
                write(*,fmt_general) 'Hyperedge', i, 'order:', net%edges(i)%order, ' (isolated)'
            end if
        end do

    end subroutine

end module