dynamics_NB_OGA.f90 Source File


This file depends on

sourcefile~~dynamics_nb_oga.f90~~EfferentGraph sourcefile~dynamics_nb_oga.f90 dynamics_NB_OGA.f90 sourcefile~dynamics_base.f90 dynamics_base.f90 sourcefile~dynamics_nb_oga.f90->sourcefile~dynamics_base.f90 sourcefile~kinds.f90 kinds.f90 sourcefile~dynamics_nb_oga.f90->sourcefile~kinds.f90 sourcefile~network.f90 network.f90 sourcefile~dynamics_nb_oga.f90->sourcefile~network.f90 sourcefile~dynamics_base.f90->sourcefile~kinds.f90 sourcefile~dynamics_base.f90->sourcefile~network.f90 sourcefile~network.f90->sourcefile~kinds.f90

Files dependent on this one

sourcefile~~dynamics_nb_oga.f90~~AfferentGraph sourcefile~dynamics_nb_oga.f90 dynamics_NB_OGA.f90 sourcefile~dynamics_chooser.f90 dynamics_chooser.f90 sourcefile~dynamics_chooser.f90->sourcefile~dynamics_nb_oga.f90 sourcefile~dynamics.f90 dynamics.f90 sourcefile~dynamics.f90->sourcefile~dynamics_chooser.f90 sourcefile~common.f90 common.f90 sourcefile~common.f90->sourcefile~dynamics.f90

Source Code

module hyperSIS_dynamics_NB_OGA_mod
    use hyperSIS_kinds_mod
    use hyperSIS_network_mod
    use hyperSIS_dynamics_base_mod
    use datastructs_mod, only: dynamical_list_t
    use datastructs_mod, only: sampler_base_t
    use datastructs_mod, only: choose_sampler
    use datastructs_mod, only: log_unit, log_write, LOGGER_OK, LOG_ERROR, LOG_WARNING, LOG_INFO, LOG_DEBUG
    implicit none
    private

    character(len=*), parameter :: ALGORITHM_NAME = 'NB_OGA'

    type, extends(state_compartment_base_t) :: state_compartment_t
        integer(kind=i4), allocatable :: num_infected_nodes_per_edge(:) ! number of nodes in that state in that edge
        real(kind=dp), allocatable :: max_rate_per_node(:) ! maximum rate per node, used for the sampler
    end type

    type, extends(net_state_base_t) :: net_state_t
        type(state_compartment_t) :: infected ! Infected nodes, base type
        ! NB-OGA specific variables
        real(kind=dp) :: total_infection_attempt_rate ! Rate of infection attempts
        real(kind=dp) :: total_healing_rate ! Rate of healing
        class(sampler_base_t), allocatable :: possibly_quiescent_nodes_sampler ! Sampler for the infection attempts: susceptible nodes in active or inactive edges
    contains
        procedure :: init => net_state_init
        procedure :: add_infected => net_state_add_infected
        procedure :: remove_infected => net_state_remove_infected
        ! NB-OGA will be the default algorithm
        procedure :: dynamics_init => net_state_dynamics_init
        procedure :: dynamics_update_dt => net_state_dynamics_update_dt
        procedure :: dynamics_step => net_state_dynamics_step
        procedure :: calculate_rates => net_state_calculate_rates
        procedure :: print_debug_quantities => net_state_print_debug_quantities
        procedure :: activate_edge => net_state_activate_edge

        procedure :: export_edges_states => net_state_export_edges_states

        procedure :: get_num_infected => net_state_get_num_infected
    end type

    public :: net_state_t

contains

    function net_state_get_num_infected(this) result(n)
        ! Get the number of infected nodes
        class(net_state_t), intent(in) :: this
        integer(kind=i4) :: n

        n = this%infected%num_nodes

    end function net_state_get_num_infected

    subroutine net_state_export_edges_states(this, net, filename)
        ! Export the edges states to a file
        class(net_state_t) :: this
        type(network_t), intent(in) :: net
        character(len=*), intent(in) :: filename
        integer(kind=i4) :: funit, i

        open(newunit=funit, file=filename, status='unknown', action='write', position='append')
        write(funit, fmt_general, advance='no') this%time
        do i = 1, net%num_edges
            if (this%infected%is_edge_active(i)) write(funit, fmt_general, advance='no') i
        end do
        write(funit, *) ! new line
        close(funit)

    end subroutine

    subroutine net_state_init(this, net, params, sampler_choice)
        ! It will initialize the net_state with all nodes in empty state
        class(net_state_t) :: this
        type(network_t), intent(in) :: net
        class(dyn_parameters_t), intent(in) :: params
        integer(kind=i4) :: node_id, edge_id, edge_pos, edge_order
        character(len=*), intent(in) :: sampler_choice

        call log_write(LOG_INFO, 'Dynamics: '//trim(adjustl(ALGORITHM_NAME)), .false.)
        call log_write(LOG_INFO, 'Sampler: '//trim(adjustl(sampler_choice)))

        ! allocate sampler
        call choose_sampler(this%possibly_quiescent_nodes_sampler, sampler_choice)

        this%params = params
        this%infected%num_nodes = 0

        allocate(this%node_state(net%num_nodes))
        allocate(this%infected%num_infected_nodes_per_edge(net%num_edges))
        allocate(this%infected%is_edge_active(net%num_edges))

        allocate(this%infected%max_rate_per_node(net%num_nodes))

        this%infected%max_rate_per_node = 0.0_dp
        do node_id = 1, net%num_nodes
            do edge_pos = 1, net%nodes(node_id)%degree
                edge_id = net%nodes(node_id)%edges(edge_pos)
                edge_order = net%edges(edge_id)%order
                this%infected%max_rate_per_node(node_id) = this%infected%max_rate_per_node(node_id) + this%params%beta(edge_order)
            end do
        end do

        this%node_state = 0
        this%infected%num_infected_nodes_per_edge = 0
        this%infected%is_edge_active = .false.

        ! initialize dynamical lists
        call this%infected%nodes%init(net%num_nodes)

        ! initialize the infection attempts sampler
        select case (trim(adjustl(sampler_choice)))
            case ('rejection_maxheap_composition')
                block
                    real(kind=dp) :: min_weight, max_weight, weight
                    integer(kind=i4) :: node_id

                    min_weight = huge(min_weight)
                    max_weight = 0.0_dp

                    do node_id = 1, net%num_nodes
                        weight = this%infected%max_rate_per_node(node_id)
                        if (weight < min_weight) min_weight = weight
                        if (weight > max_weight) max_weight = weight
                    end do

                    call this%possibly_quiescent_nodes_sampler%init(net%num_nodes, min_weight, max_weight)
                end block
            case ('rejection_two_classes', 'rejection_maxheap_two_classes')
                block
                    real(kind=dp) :: threshold, threshold_sum, threshold_std, weight
                    integer(kind=i4) :: node_id

                    threshold_sum = 0.0_dp
                    threshold_std = 0.0_dp
                    do node_id = 1, net%num_nodes
                        weight = this%infected%max_rate_per_node(node_id)
                        threshold_sum = threshold_sum + weight
                        threshold_std = threshold_std + weight**2
                    end do

                    threshold_std = sqrt(threshold_std / net%num_nodes - (threshold_sum / net%num_nodes)**2)

                    threshold = threshold_sum / net%num_nodes + threshold_std

                    write(*, fmt_general) 'Initializing with threshold: ', threshold

                    call this%possibly_quiescent_nodes_sampler%init(net%num_nodes, threshold)
                end block
            case default
                call this%possibly_quiescent_nodes_sampler%init(net%num_nodes)
        end select

    end subroutine

    subroutine net_state_dynamics_init(this, net)
        ! Initialize the NB-OGA dynamics
        class(net_state_t) :: this
        class(network_t), intent(in) :: net ! compatibility with the other algorithms

        ! Set the time to zero
        this%time = 0.0_dp

        call this%calculate_rates(net)

    end subroutine

    subroutine net_state_calculate_rates(this, net)
        ! Calculate the rates of infection and healing
        class(net_state_t) :: this
        class(network_t), intent(in) :: net

        ! Initial healing rate is the number of infected nodes times the healing rate
        this%total_healing_rate = this%infected%nodes%n_used * this%params%alpha

        ! Loop over the active edges and calculate the total infection attempt rate
        this%total_infection_attempt_rate = this%params%beta_scale * this%possibly_quiescent_nodes_sampler%sum()

    end subroutine net_state_calculate_rates

    function net_state_dynamics_update_dt(this, net, gen) result(res)
        use rndgen_mod
        class(net_state_t) :: this
        class(network_t), intent(in) :: net
        type(rndgen) :: gen
        logical :: res

        res = .true.

        if (this%infected%num_nodes == 0) then
            ! no infected nodes, nothing to do
            print*, 'No infected nodes, nothing to do.'
            res = .false.
            return
        end if

        call this%calculate_rates(net)

        this%total_rate = this%total_infection_attempt_rate + this%total_healing_rate

        ! Calculate the time step
        this%dt = -log(1.0_dp - gen%rnd()) / this%total_rate

        ! Update the time
        this%time = this%time + this%dt

    end function

    subroutine net_state_dynamics_step(this, net, gen)
        use rndgen_mod
        ! Perform a step of the NB-OGA dynamics
        class(net_state_t) :: this
        class(network_t), intent(in) :: net
        type(rndgen) :: gen
        integer(kind=i4) :: node_pos, node_id
        integer(kind=i4) :: edge_pos, edge_id, edge_order
        real(kind=dp) :: evaluated_rate
        logical :: is_node_quiescent

        if (gen%rnd() < this%total_healing_rate/this%total_rate) then
            ! healing will happen
            ! we select a random infected node

            node_pos = gen%int(1, this%infected%nodes%n_used)
            node_id = this%infected%nodes%list(node_pos)

            ! remove it from the infected state and move to susceptible state
            call this%remove_infected(net, node_id, node_pos, 0_i2)
        else
            ! infection will happen
            ! for that, we select a random quiescent node

            node_id = this%possibly_quiescent_nodes_sampler%sample(gen)

            ! now we need to check if the node is indeed in a state that can be infected
            evaluated_rate = 0.0_dp
            is_node_quiescent = .false.
            do edge_pos = 1, net%nodes(node_id)%degree
                edge_id = net%nodes(node_id)%edges(edge_pos)
                edge_order = net%edges(edge_id)%order

                if (this%infected%is_edge_active(edge_id)) then
                    ! the edge is active, so the node is quiescent
                    is_node_quiescent = .true.
                    evaluated_rate = evaluated_rate + this%params%beta(edge_order)
                end if
            end do

            ! first phantom process: if the node is not quiescent, we remove it from the sampler
            if (.not. is_node_quiescent) then
                call this%possibly_quiescent_nodes_sampler%remove(node_id)
                return
            end if

            ! now we know the quiescent node, we try to infect it
            if (gen%rnd() < 1.0_dp*(evaluated_rate) / this%possibly_quiescent_nodes_sampler%weights(node_id) ) then
                ! we will infect that node
                call this%add_infected(net, node_id)
            else
                ! otherwise, we do nothing! Phantom process :)
            end if

        end if

    end subroutine

    ! @@@@@@@@@@@@@@@@ NB_OGA - auxiliary functions @@@@@@@@@@@@@@@@@@
    subroutine check_infected_edge_condition(this, net, edge_id, edge_order)
        ! Subroutine to check if the edge is active in the infected compartment
        class(net_state_t) :: this
        type(network_t), intent(in) :: net
        integer(kind=i4), intent(in) :: edge_id, edge_order

        if ( &
            (this%infected%num_infected_nodes_per_edge(edge_id) >= this%params%theta(edge_order)) & ! it has to have at least theta nodes in that state
            .and. (this%infected%num_infected_nodes_per_edge(edge_id) /= net%edges(edge_id)%order + 1) & ! it has to have at least one node in another state
            ) then
            ! it will be active
                if (.not. this%infected%is_edge_active(edge_id)) then
                    !print(fmt_general), 'Activating edge', edge_id, 'of order', edge_order, 'with', this%infected%num_infected_nodes_per_edge(edge_id), 'nodes in that state'
                    call this%activate_edge(net, edge_id, edge_order)
                endif
                ! otherwise, it is already active
        else
            ! only change the edge status, do nothing in the list
            this%infected%is_edge_active(edge_id) = .false.
        end if

    end subroutine

    subroutine net_state_add_infected(this, net, node_id)
        ! Subroutine to add infected node in the list
        ! We assume that it was not infected before, be careful!
        class(net_state_t) :: this
        type(network_t), intent(in) :: net
        integer(kind=i4), intent(in) :: node_id
        integer(kind=i4) :: edge_pos, edge_id, edge_order

        ! DEBUG
        !if (this%node_state(node_id) == 1_i2) stop 'Node already infected!'

        this%node_state(node_id) = 1_i2 ! add to that state

        ! add node to the list
        this%infected%num_nodes = this%infected%num_nodes + 1
        call this%infected%nodes%add(node_id)

        ! remove it from sampler, since it is now infected
        call this%possibly_quiescent_nodes_sampler%remove(node_id)

        ! fill the edges list
        ! if the edge is not active, add it. Otherwise, keep as is
        do edge_pos = 1, net%nodes(node_id)%degree
            edge_id = net%nodes(node_id)%edges(edge_pos)
            edge_order = net%edges(edge_id)%order
            ! add one node to that state in that edge
            this%infected%num_infected_nodes_per_edge(edge_id) = this%infected%num_infected_nodes_per_edge(edge_id) + 1
            ! if the edge is not active, it can become active
            ! else, if the edge is active, it can become inactive
            call check_infected_edge_condition(this, net, edge_id, edge_order)
        end do

    end subroutine

    subroutine net_state_remove_infected(this, net, node_id, node_pos, new_state)
        ! Subroutine to remove infected node in the list
        class(net_state_t) :: this
        type(network_t), intent(in) :: net
        integer(kind=i4), intent(in) :: node_id, node_pos
        integer(kind=i4) :: edge_pos, edge_id, edge_order
        integer(kind=i2), intent(in) :: new_state

        ! DEBUG
        !if (this%node_state(node_id) /= 1_i2) stop 'Node was not infected!'
        !if (this%infected%nodes%list(node_pos) /= node_id) stop 'Node is not the correct one!'

        this%node_state(node_id) = new_state ! add to that state

        ! remove node from the list of infected
        this%infected%num_nodes = this%infected%num_nodes - 1
        call this%infected%nodes%remove(node_pos)

        ! update the status of the edges
        do edge_pos = 1, net%nodes(node_id)%degree
            edge_id = net%nodes(node_id)%edges(edge_pos)
            edge_order = net%edges(edge_id)%order
            this%infected%num_infected_nodes_per_edge(edge_id) = this%infected%num_infected_nodes_per_edge(edge_id) - 1
            ! the number of active edges can be reduced or increased
            ! we check the condition
            call check_infected_edge_condition(this, net, edge_id, edge_order)
        end do

    end subroutine

    subroutine net_state_print_debug_quantities(this)
        ! Print debug quantities of the network state
        class(net_state_t) :: this

        print(fmt_general), 'Number of infected nodes:', this%infected%num_nodes
        print(fmt_general), 'Number of active edges:', count(this%infected%is_edge_active)
        !print(fmt_general), 'Number of active edges in the list:', this%infected%possibly_active_edges%n_used
        !print(fmt_general), 'Maximum order of possibly active edges:', this%infected%possibly_active_max_order
        print(fmt_general), 'Total infection attempt rate:', this%total_infection_attempt_rate
        print(fmt_general), 'Total healing rate:', this%total_healing_rate
        print(fmt_general), 'Infection attempt probability:', &
            this%total_infection_attempt_rate / (this%total_infection_attempt_rate + this%total_healing_rate)
        print(fmt_general), ''

    end subroutine

    subroutine net_state_activate_edge(this, net, edge_id, edge_order)
        ! Subroutine to activate the edge in the compartment
        class(net_state_t) :: this
        class(network_t), intent(in) :: net
        integer(kind=i4), intent(in) :: edge_id, edge_order
        integer(kind=i4) :: node_id, node_pos

        ! DEBUG
        !if (this%infected%is_edge_active(edge_id)) stop 'Edge is already active!'

        this%infected%is_edge_active(edge_id) = .true.

        ! update the possibily quiescent nodes sampler
        do node_pos = 1, net%edges(edge_id)%order + 1
            node_id = net%edges(edge_id)%nodes(node_pos)
            ! since we are already here, we can check if the node is quiescent or not
            if (this%node_state(node_id) == 0_i2) then
                ! the node is susceptible, so we add it to the possibly quiescent nodes sampler
                call this%possibly_quiescent_nodes_sampler%set_weight(node_id, this%infected%max_rate_per_node(node_id)) ! safe, since it will only change the weight
            ! code below is not necessary, since I already remove it when it is infected (add_infected)
            !else if (this%node_state(node_id) == 1_i2) then
            !    ! the node is infected, so we remove it from the possibly quiescent nodes sampler
            !    call this%possibly_quiescent_nodes_sampler%remove(node_id)
            end if
        end do

    end subroutine
    !/@@@@@@@@@@@@@@@@ NB_OGA @@@@@@@@@@@@@@@@@@

end module