percolation.f90 Source File


This file depends on

sourcefile~~percolation.f90~~EfferentGraph sourcefile~percolation.f90 percolation.f90 sourcefile~utilities.f90 utilities.f90 sourcefile~percolation.f90->sourcefile~utilities.f90 sourcefile~hk.f90 hk.f90 sourcefile~percolation.f90->sourcefile~hk.f90

Files dependent on this one

sourcefile~~percolation.f90~~AfferentGraph sourcefile~percolation.f90 percolation.f90 sourcefile~randomwalk.f90 randomwalk.f90 sourcefile~randomwalk.f90->sourcefile~percolation.f90

Contents

Source Code


Source Code

module percolation
    !! A module with procedures for calculating various properties which
    !! are interesting when doing numerical percolation experiments.
    use utilities
    use hk
    implicit none

    private :: has_spanning_cluster_one_sample, spanning_density_one_sample, &
               label_recursively, growcluster

    real(kind=dp), parameter :: pc = 0.592746
        !! Known critical probability for a two-dimensional site-percolating
        !! system.
    contains
        function create_binary_matrix(p, L) result(binary_matrix)
            !! Create a random, binary (`logical`) matrix,
            !! which can be used in percolation experiments.

            logical, dimension(:,:), allocatable :: binary_matrix
                !! Randomly created matrix, where each element is `.true.`
                !! or `.false.` if a randomly generated number is smaller
                !! or greater than p.

            real(kind=dp), intent(in) :: p
                !! Probability for each matrix element to be `.true.`.

            integer, intent(in) :: L

            real(kind=dp), dimension(:,:), allocatable :: p_matrix
            allocate(p_matrix(L,L))

            call random_seed()
            call random_number(p_matrix)

            binary_matrix = p_matrix < p
        end function

        subroutine label(matrix, labelled_matrix, num_labels)
            !! Alternative interface to the Hoshen-Kopelman algorithm
            !! from the hk module, which uses a binary matrix created
            !! by [[create_binary_matrix]].

            logical, dimension(:,:), intent(in) :: matrix
                !! Binary matrix where clusters will be identified.

            integer, dimension(:,:), allocatable, intent(out) :: labelled_matrix
                !! Integer matrix which will store the labels of each cluster.
                !! Reallocated if necessary.

            integer, intent(out) :: num_labels
                !! Overwritten with the total number of disjoint clusters.

            integer :: L

            L = size(matrix,1)

            if(.not. allocated(labelled_matrix)) then
                allocate(labelled_matrix(L,L))
            else if(size(labelled_matrix,1) /= L) then
                deallocate(labelled_matrix)
                allocate(labelled_matrix(L,L))
            endif
            where(matrix)
                labelled_matrix = 1
            else where
                labelled_matrix = 0
            end where
            num_labels = hoshen_kopelman(labelled_matrix)
        end subroutine

        function find_sizes(labelled_matrix, num_labels) result(sizes)
            !! Count the number of sites belonging to each cluster
            !! in the labelled matrix.

            integer, dimension(:), allocatable :: sizes
                !! Array of cluster sizes. The i'th element is the size of
                !! the cluster with label i.

            integer, dimension(:,:), intent(in) :: labelled_matrix
                !! Integer matrix with labelled clusters, resulting from
                !! the Hoshen-Kopelman algorithm.

            integer, intent(in) :: num_labels
                !! The known number of clusters.

            integer :: L,i,j

            L = size(labelled_matrix,1)
            allocate(sizes(num_labels))
            sizes(:) = 0

            do j=1,L
                do i=1,L
                    associate (label => labelled_matrix(i,j))
                        if(label /= 0) then
                            sizes(label) = sizes(label) + 1
                        end if
                    end associate
                end do
            end do
        end function

        subroutine cluster_number_density(p, L, num_samples, bin_mids, results, binsize_base)
            !! Calculate the number density of clusters with different sizes.
            !! The cluster number density is defined as
            !! \\begin{equation}
            !!      n(s,p) = \sum_{\text{MC-samples}}
            !!                   \frac{\text{number of clusters with size }s}
            !!                        {L^2\cdot\text{number of MC-samples}}.
            !! \\end{equation}
            !! Direct calculations will usually give bad results, as there
            !! will be very few
            !! clusters with large sizes compared to the numbers of clusters
            !! with small sizes. This is circumvented by doing logarithmic
            !! binning and averaging, i.e.
            !! \\begin{equation}
            !!      n\left([s+\Delta s),p\right) = \sum_{\text{MC-samples}}
            !!                   \frac{\text{number of clusters with size }s
            !!                         \in[s,s+\Delta s)}
            !!                        {\Delta s\cdot L^2\cdot\text{number of MC-samples}},
            !!      \label{eq:nsp}
            !! \\end{equation}
            !! where \\(\Delta s\\) are logarithmically distributed. After execution,
            !! **bin_mids** will contain the centres of the bins.

            integer, intent(in) :: L
                !! Percolating systems will be \\(L\times L\\).

            integer, intent(in) :: num_samples
                !! Results will be averaged over this number of Monte Carlo-samples.
                !! Sampling is parallelised.

            real(kind=dp), intent(in) :: p
                !! Probability for a given site to allow transport.

            real(kind=dp), intent(in), optional :: binsize_base
                !! The edges of the logarithmically distributed bins will be
                !! integer powers of this number. Default: 1.5.

            real(kind=dp), dimension(:), intent(out), allocatable :: bin_mids
                !! Centres of bins.

            real(kind=dp), dimension(:), intent(out), allocatable :: results
                !! Cluster number density in \eqref{eq:nsp}.

            integer :: num_bins, i, j, num_labels, sizeindex, spanning_label
            real(kind=dp) :: a, loga

            logical, dimension(:,:), allocatable :: binary_matrix
            integer, dimension(:,:), allocatable :: label_matrix
            integer, dimension(:), allocatable :: clustersizes, histogram
            real(kind=dp), dimension(:), allocatable :: bin_edges, bin_sizes

            if(present(binsize_base)) then
                a = binsize_base
            else
                a = 1.5d0
            end if

            !/cndstart/!
            loga = log(a)

            num_bins  = ceiling(log(1.0d0*L**2)/loga)
            bin_edges = a**[(i, i=0 ,num_bins)]
            bin_mids  = 0.5*(bin_edges(1:num_bins) + bin_edges(2:num_bins+1))
            bin_sizes = bin_edges(2:num_bins+1) - bin_edges(1:num_bins)

            allocate(histogram(1:num_bins))
            histogram = 0

            !$omp  parallel do &
            !$omp& private(binary_matrix, label_matrix, num_labels, &
            !$omp&         clustersizes, spanning_label, sizeindex) &
            !$omp& reduction(+:histogram)
            do i = 1, num_samples
                binary_matrix = create_binary_matrix(p, L)
                call label(binary_matrix, label_matrix, num_labels)
                clustersizes = find_sizes(label_matrix, num_labels)
                spanning_label = find_spanning_cluster(label_matrix, num_labels)

                do j = 1, num_labels
                    if(j /= spanning_label) then
                        sizeindex = floor(log(1.0d0*clustersizes(j))/loga) + 1
                        histogram(sizeindex) = histogram(sizeindex) + 1
                    end if
                end do
            end do
            !$omp end parallel do

            results = histogram/(L**2 * num_samples * bin_sizes)
            !/cndend/!
        end subroutine

        function find_spanning_cluster(labelled_matrix, num_labels) result(spanning_label)
            !! Find the label of the percolating cluster, i.e. the one spanning from
            !! one side of the system to the opposing side.

            integer :: spanning_label
                !! Label of the percolating cluster. -1 if no percolating cluster is found.

            integer, dimension(:,:), intent(in) :: labelled_matrix
                !! Labelled matrix of clusters from [[hoshen_kopelman]]/[[label]].

            integer, intent(in) :: num_labels
                !! Known number of clusters.

            integer :: L

            L = size(labelled_matrix,1)

            spanning_label = find_intersection(labelled_matrix(:,1), labelled_matrix(:,L), num_labels)

            if (spanning_label == -1) then
                spanning_label = find_intersection(labelled_matrix(1,:), labelled_matrix(L,:), num_labels)
            end if
        end function

        function spanning_density_one_sample(p, L) result(spanning_density)
            real(kind=dp) :: spanning_density
            real(kind=dp), intent(in) :: p
            integer, intent(in) :: L

            logical, dimension(:,:), allocatable :: binary_matrix
            integer, dimension(:,:), allocatable :: labelled_matrix

            integer :: num_labels, spanning_label

            binary_matrix = create_binary_matrix(p,L)
            call label(binary_matrix, labelled_matrix, num_labels)

            spanning_label = find_spanning_cluster(labelled_matrix, num_labels)

            if (spanning_label == -1) then
                spanning_density = 0
                return
            end if

            spanning_density = count(labelled_matrix == spanning_label)/real(L**2,kind=dp)
        end function

        function spanning_density(p, L, num_samples)
            !! Density of the spanning/percolating cluster, i.e.
            !! the number of sites on the percolating cluster divided by \\(L^2\\).
            !! Averaged over **num_samples** Monte Carlo samples (with OpenMP).

            real(kind=dp) :: spanning_density
                !! The number of sites on the spanning cluster divided by \\(L^2\\).

            real(kind=dp), intent(in) :: p
                !! The probability for a site to allow transport.

            integer, intent(in) :: L
                !! The size of the system.

            integer, intent(in) :: num_samples
                !! The number of Monte Carlo samples.


            integer :: i
            real(kind=dp), dimension(:), allocatable :: results

            allocate(results(num_samples))

            !$omp parallel do
            do i=1,num_samples
                results(i) = spanning_density_one_sample(p, L)
            end do
            !$omp end parallel do

            spanning_density = sum(results)/num_samples
        end function

        function has_spanning_cluster_one_sample(p, L) result(has_spanning)
            logical :: has_spanning
            real(kind=dp), intent(in) :: p
            integer, intent(in) :: L

            logical, dimension(:,:), allocatable :: binary_matrix
            integer, dimension(:,:), allocatable :: labelled_matrix

            integer :: num_labels, spanning_label

            binary_matrix = create_binary_matrix(p,L)
            call label(binary_matrix, labelled_matrix, num_labels)

            spanning_label = find_spanning_cluster(labelled_matrix, num_labels)

            has_spanning = spanning_label /= -1
        end function

        function spanning_probability(p, L, num_samples)
            !! Calculate the probability of having a spanning/percolating cluster, given
            !! a system size **L** and probability for a site to have transport **p**.

            real(kind=dp) :: spanning_probability
                !! The probability of having a percolating cluster, calculated as the number
                !! of times a percolating cluster is found, divided by the number of attempts
                !! (**num_samples**).

            real(kind=dp), intent(in) :: p
                !! Probability for a site to allow transport.

            integer, intent(in) :: L
                !! Size of the system.

            integer, intent(in) :: num_samples
                !! Number of Monte Carlo samples.


            integer :: i
            logical, dimension(:), allocatable :: results

            allocate(results(num_samples))

            !$omp parallel do
            do i=1,num_samples
                results(i) = has_spanning_cluster_one_sample(p, L)
            end do
            !$omp end parallel do

            spanning_probability = count(results)/real(num_samples,kind=dp)
        end function

        function spanning_probability_inverse(x, L, num_samples, tolerance) result(p_x)
            !! Find the inverse of [[spanning_probability]] by use
            !! of the bisection method.

            real(kind=dp), intent(in) :: x
                !! The value of [[spanning_probability]] for which the inverse
                !! is calculated.

            real(kind=dp), intent(in) :: tolerance
                !! Tolerance of approximation. The return value is within
                !! **tolerance**/2 of the correct (but numerical) value.

            integer, intent(in) :: L
                !! Size of the system.

            integer, intent(in) :: num_samples
                !! Number of Monte Carlo samples to use when evaluating [[spanning_probability]].

            real(kind=dp) :: p_x
                !! The inverse of [[spanning_probability]]. If [[spanning_probability]] is
                !! denoted \\(\Pi(p,L)\\), this function returns \\(p\\) such
                !! that \\(\Pi(p,L)=x\\).


            real(kind=dp) :: lower, upper, lowerPI, upperPI, mid, midPI

            !/invPIstart/!
            lower = 0
            upper = 1
            lowerPI = 0
            upperPI = 1

            do while(upper - lower > tolerance)
                mid = (lower + upper)/2
                midPI = spanning_probability(mid, L, num_samples)
                if(midPI > x) then
                    upper = mid
                    upperPI = midPI
                else
                    lower = mid
                    lowerPI = midPI
                end if
            end do
            p_x = mid
            !/invPIend/!

        end function

        !/labelsubroutinestart/!
        subroutine label_recursively(matrix, labelled_matrix, num_labels)
            logical, dimension(:,:), allocatable, intent(in) :: matrix
            integer, dimension(:,:), allocatable, intent(inout) :: labelled_matrix
            integer, intent(inout) :: num_labels
            integer :: L, i, j

            L = size(matrix,1)
            num_labels = 0

            if(.not. allocated(labelled_matrix)) then
                allocate(labelled_matrix(L,L))
            end if

            labelled_matrix = 0

            do j=1,L
                do i=1,L
                    if(matrix(i,j) .and. labelled_matrix(i,j) == 0) then
                        num_labels = num_labels + 1
                        call growcluster(matrix,labelled_matrix,i,j,num_labels)
                    end if
                end do
            end do
        end subroutine
        !/labelsubroutineend/!

        !/growclustersubroutinestart/!
        recursive subroutine growcluster(matrix, labelled_matrix, i, j, label)
            logical, dimension(:,:), allocatable, intent(in) :: matrix
            integer, dimension(:,:), allocatable, intent(inout) :: labelled_matrix
            integer, intent(in) :: i, j, label
            integer :: L

            L = size(matrix,1)

            labelled_matrix(i,j) = label

            if(i<L) then
                if(matrix(i+1,j) .and. labelled_matrix(i+1,j)==0) then
                    call growcluster(matrix, labelled_matrix, i+1, j, label)
                end if
            end if
            if(j<L) then
                if(matrix(i,j+1) .and. labelled_matrix(i,j+1)==0) then
                    call growcluster(matrix, labelled_matrix, i, j+1, label)
                end if
            end if
            if(i>1) then
                if(matrix(i-1,j) .and. labelled_matrix(i-1,j)==0) then
                    call growcluster(matrix, labelled_matrix, i-1, j, label)
                end if
            end if
            if(j>1) then
                if(matrix(i,j-1) .and. labelled_matrix(i,j-1)==0) then
                    call growcluster(matrix, labelled_matrix, i, j-1, label)
                end if
            end if
        end subroutine
        !/growclustersubroutineend/!
end module percolation