//  SW-WANG.C
//  Equilibration using Swendsen-Wang technique
//  © 2021 Peter J. Meyer

#include "iss.h"

#define STACK_POINTER_CHECK false

#if TIMING_DIAGNOSTICS
clock_t create_v_cluster_clock = 0;
clock_t flip_sw_clusters_clock = 0;
clock_t flip_sw_cluster_clock  = 0;
clock_t another_sw_spin_clock  = 0;
clock_t flip_sw_spin_clock     = 0;
#endif

unsigned int num_virtual_bonds_opened = 0;

static short int s_i, s_j, s_k, s_l, s_r;
static int s_spin_value, s_new_spin_value;

char sw_map_filepath[128];           //  pathname of map file

void create_virtual_bonds(void);
static void flip_swendsen_wang_clusters(void);
static void flip_swendsen_wang_cluster(void);
static void flip_swendsen_wang_spin(void);
static int another_swendsen_wang_spin(void);

//  Swendsen-Wang method has two parts:
//  1.  At the beginning of each Monte Carlo step go through the lattice sequentially
//  and create virtual bonds between same-spin-value sites with a probability p,
//  where p = 1 - exp(-2J/temperature).
//  2.  Then for each virtual cluster assign a random spin value to all sites
//  within that cluster.
/*----------------------------------*/
void perform_swendsen_wang_sweep(void)
{
int context;

create_virtual_bonds();

if ( map_file_requested )
    {
    context = 2;
    strcpy(sw_map_filepath,map_filepath);
    *(strstr(sw_map_filepath,".MAP")+3) = '0'+context;
    display_map(sw_map_filepath,2);
    }

flip_swendsen_wang_clusters();

if ( map_file_requested )
    {
    context = 3;
    strcpy(sw_map_filepath,map_filepath);
    *(strstr(sw_map_filepath,".MAP")+3) = '0'+context;
    display_map(sw_map_filepath,3);
    }
}

//  J #defined in ISS_DEF.H as 1.0.
//  According to Coddington
//  sw_probability = 1 - exp(-J/temperature);  
//  For the critical temperature this gives sw_probability = 0.3564.
//  According to Gould & Tobochnik 
//  sw_probability = 1 - exp(-2*J/temperature),
//  which gives sw_probability = 0.5858.
//  Comparison with results from internal energy and specific heat,
//  as measured with Glauber dynamics, shows that Gould & Tobochnik are correct.
/*---------------------------*/
void create_virtual_bonds(void)
{
int i, j, k, l, r;
#if TIMING_DIAGNOSTICS
clock_t start_clock, end_clock;

start_clock = clock();
#endif

nn_i = nn_j = nn_k = nn_l = 0;

mark_all_sites_unvisited();         //  S&B.C

switch ( dimensionality )
    {
    case 2:
    //  Erase v_bonds2[][]
    memset(&v_bonds2[0][0],0,num_sites);

    for ( i=0; i<size; i++ )
        {
        for ( j=0; j<size; j++ )
            {
            if ( ! ( s_spin_value = spin_at_site(i,j) ) )
            //  spin_at_site() returns 0 if site not occupied.
                continue;

            MARK_SITE_AS_VISITED_2(i,j);     //  S&B.C

            //  Does this site have non-visited nearest neighbour spins?
            if ( !bonds2[i][j] )
                continue;
                //  because it has none
            
            get_nn_sites(i,j);               //  Q&A.C
            //  nn_sites[][] now holds the indices 
            //  of the nearest neighbour sites in all directions  
            //  (including those that cross the lattice boundaries).
            //  (no. of directions = coord_num)

            for ( r=0; r<coord_num; r++ )
                {
                if ( bonds2[i][j] & bit[r] )   //  if bond exists in direction r
                    {
                    nn_i = nn_sites[r][0];
                    nn_j = nn_sites[r][1];

                    if ( SITE_IS_VISITED_2(nn_i,nn_j) )     //  ISS_DEF.C
                        continue;   //  because this possible virtual bond 
                                    //  has been considered already.

                    if ( s_spin_value != spin_at_occupied_site(nn_i,nn_j) )
                        continue;   //  because virtual bonds opened 
                                    //  only between spins with same spin value.
                    
                    if ( PRNG1() < v_bond_probability )
                        {
                        //  Open the virtual bond.
                        v_bonds2[i][j] |= bit[r];

                        //  Mark the virtual bond as open at the other site.
                        v_bonds2[nn_i][nn_j] |= bit[inverse_direction((i+j)%2,r)];   
                        
                        num_virtual_bonds_opened++;    
                        }
                    }                    
                }
            }
        }
    break;

    case 3:
    //  Erase v_bonds3[][][]
    memset(&v_bonds3[0][0][0],0,num_sites);

    for ( i=0; i<size; i++ )
        {
        for ( j=0; j<size; j++ )
            {
            for ( k=0; k<size; k++ )
                {
                if ( ! ( s_spin_value = spin_at_site(i,j,k) ) )
                //  spin_at_site() returns 0 if site not occupied.
                    continue;

                MARK_SITE_AS_VISITED_3(i,j,k);     //  S&B.C

                //  Does this site have non-visited nearest neighbour spins?
                if ( !bonds3[i][j][k] )
                    continue;
                    //  because it has none
            
                get_nn_sites(i,j,k);               //  Q&A.C
                //  nn_sites[][] now holds the indices 
                //  of the nearest neighbour sites in all directions  
                //  (including those that cross the lattice boundaries).
                //  (no. of directions = coord_num)

                for ( r=0; r<coord_num; r++ )
                    {
                    if ( bonds3[i][j][k] & bit[r] )   //  if bond exists in direction r
                        {
                        nn_i = nn_sites[r][0];
                        nn_j = nn_sites[r][1];
                        nn_k = nn_sites[r][2];

                        if ( SITE_IS_VISITED_3(nn_i,nn_j,nn_k) )     //  ISS_DEF.C
                            continue;   //  because this possible virtual bond 
                                        //  has been considered already.

                        if ( s_spin_value != spin_at_occupied_site(nn_i,nn_j,nn_k) )
                            continue;   //  because virtual bonds opened 
                                        //  only between spins with same spin value.
                    
                        if ( PRNG1() < v_bond_probability )
                            {
                            //  Open the virtual bond.
                            v_bonds3[i][j][k] |= bit[r];

                            //  Mark the virtual bond as open at the other site.
                            v_bonds3[nn_i][nn_j][nn_k] 
                                |= bit[inverse_direction((i+j+k)%2,r)];     
                            
                            num_virtual_bonds_opened++;    
                            }
                        }                    
                    }
                }
            }
        }    
    break;

    case 4:
    //  Erase v_bonds4[][][][]
    memset(&v_bonds4[0][0][0][0],0,num_sites);

    for ( i=0; i<size; i++ )
        {
        for ( j=0; j<size; j++ )
            {
            for ( k=0; k<size; k++ )
                {
                for ( l=0; l<size; l++ )
                    {
                    if ( ! ( s_spin_value = spin_at_site(i,j,k,l) ) )
                    //  spin_at_site() returns 0 if site not occupied.
                        continue;

                    MARK_SITE_AS_VISITED_4(i,j,k,l);     //  S&B.C

                    //  Does this site have non-visited nearest neighbour spins?
                    if ( !bonds4[i][j][k][l] )
                        continue;
                        //  because it has none
            
                    get_nn_sites(i,j,k,l);               //  Q&A.C
                    //  nn_sites[...] now holds the indices 
                    //  of the nearest neighbour sites in all directions  
                    //  (including those that cross the lattice boundaries).
                    //  (no. of directions = coord_num)

                    for ( r=0; r<coord_num; r++ )
                        {
                        if ( bonds4[i][j][k][l] & bit[r] )   //  if bond exists in direction r
                            {
                            nn_i = nn_sites[r][0];
                            nn_j = nn_sites[r][1];
                            nn_k = nn_sites[r][2];
                            nn_l = nn_sites[r][3];

                            if ( SITE_IS_VISITED_4(nn_i,nn_j,nn_k,nn_l) )     
                                continue;   //  because this possible virtual bond 
                                            //  has been considered already.

                            if ( s_spin_value != spin_at_occupied_site(nn_i,nn_j,nn_k,nn_l) )
                                continue;   //  because virtual bonds opened 
                                            //  only between spins with same spin value.
                    
                            if ( PRNG1() < v_bond_probability )
                                {
                                //  Open the virtual bond.
                                v_bonds4[i][j][k][l] |= bit[r];

                                //  Mark the virtual bond as open at the other site.
                                v_bonds4[nn_i][nn_j][nn_k][nn_l] 
                                    |= bit[inverse_direction((i+j+k+l)%2,r)];     
                            
                                num_virtual_bonds_opened++;    
                                }
                            }                    
                        }
                    }                    
                }
            }
        }
    break;

    }

#if TIMING_DIAGNOSTICS
end_clock = clock();
create_v_cluster_clock += end_clock - start_clock;
#endif
}

/*-----------------------------------------*/
static void flip_swendsen_wang_clusters(void)
{
int i, j, k, l;
#if TIMING_DIAGNOSTICS
clock_t start_clock, end_clock;

start_clock = clock();
#endif

mark_all_sites_unvisited();

clear_stack();

switch ( dimensionality )
    {
    case 2:
    for ( i=0; i<size; i++ )
        {
        for ( j=0; j<size; j++ )
            {
            if ( !SITE_IS_OCCUPIED_2(i,j) )
                continue;

            if ( SITE_IS_VISITED_2(i,j) )
                continue;
            
            //  About to trace out a new cluster.

            s_spin_value = spin_at_occupied_site(i,j);

            //  Pick a random spin value.
            if ( model_is_ising )
                s_new_spin_value = ( PRNG1() < 0.5 ? UP_SPIN : DOWN_SPIN );
            else    //  model is q-state Potts
                s_new_spin_value = (int)(PRNG1()*q) + 1;

            s_i = i;
            s_j = j;

            ct_size_of_cluster = 0;

            flip_swendsen_wang_cluster();
            //  Need to call this function even if spin value unchanged
            //  so as to mark sites in this cluster as visited.

            if ( ct_size_of_cluster > max_cluster_size )
                max_cluster_size = ct_size_of_cluster;

            #if STACK_POINTER_CHECK
            check_stack_is_empty(__FILE__);
            #endif

            num_spins_visited_this_sweep += ct_size_of_cluster;

            num_clusters_traced_this_sweep++;
            }
        }
    break;

    case 3:
    for ( i=0; i<size; i++ )
        {
        for ( j=0; j<size; j++ )
            {
            for ( k=0; k<size; k++ )
                {
                if ( !SITE_IS_OCCUPIED_3(i,j,k) )
                    continue;

                if ( SITE_IS_VISITED_3(i,j,k) )
                    continue;
            
                //  About to trace out a new cluster.

                s_spin_value = spin_at_occupied_site(i,j,k);

                //  Pick a random spin value.
                if ( model_is_ising )
                    s_new_spin_value = ( PRNG1() < 0.5 ? UP_SPIN : DOWN_SPIN );
                else    //  model is q-state Potts
                    s_new_spin_value = (int)(PRNG1()*q) + 1;

                s_i = i;
                s_j = j;
                s_k = k;

                ct_size_of_cluster = 0;

                flip_swendsen_wang_cluster();
                //  Need to call this function even if spin value unchanged
                //  so as to mark sites in this cluster as visited.

                if ( ct_size_of_cluster > max_cluster_size )
                    max_cluster_size = ct_size_of_cluster;

                #if STACK_POINTER_CHECK
                check_stack_is_empty(__FILE__);
                #endif

                num_spins_visited_this_sweep += ct_size_of_cluster;

                num_clusters_traced_this_sweep++;
                }
            }
        }
    break;

    case 4:
    for ( i=0; i<size; i++ )
        {
        for ( j=0; j<size; j++ )
            {
            for ( k=0; k<size; k++ )
                {
                for ( l=0; l<size; l++ )
                    {
                    if ( !SITE_IS_OCCUPIED_4(i,j,k,l) )
                        continue;

                    if ( SITE_IS_VISITED_4(i,j,k,l) )
                        continue;
            
                    //  About to trace out a new cluster.

                    s_spin_value = spin_at_occupied_site(i,j,k,l);

                    //  Pick a random spin value.
                    if ( model_is_ising )
                        s_new_spin_value = ( PRNG1() < 0.5 ? UP_SPIN : DOWN_SPIN );
                    else    //  model is q-state Potts
                        s_new_spin_value = (int)(PRNG1()*q) + 1;

                    s_i = i;
                    s_j = j;
                    s_k = k;
                    s_l = l;

                    ct_size_of_cluster = 0;

                    flip_swendsen_wang_cluster();
                    //  Need to call this function even if spin value unchanged
                    //  so as to mark sites in this cluster as visited.

                    if ( ct_size_of_cluster > max_cluster_size )
                        max_cluster_size = ct_size_of_cluster;

                    #if STACK_POINTER_CHECK
                    check_stack_is_empty(__FILE__);
                    #endif

                    num_spins_visited_this_sweep += ct_size_of_cluster;

                    num_clusters_traced_this_sweep++;
                    }
                }
            }
        }
    break;
    }

if ( num_spins_visited_this_sweep != (unsigned int)num_spins )
    {
    printf("\nError in SW-WANG.C: "
        "num_spins_visited_this_sweep != num_spins\n");
    exit(1);
    }

#if TIMING_DIAGNOSTICS
end_clock = clock();
flip_sw_clusters_clock += end_clock - start_clock;
#endif
}

/*
Flip a Swendsen-Wang cluster (call with 1st spin at i,j):

    Flip spin i,j (if new value different)
    Loop
        While there is a direction r such that there is a nn spin
        in direction r which is part of this virtual cluster
        and which is invisited:
            Push i,j on stack
            Put i,j = location of spin in direction r
            Flip spin i,j
        End While

        If stack is empty
            finish
        End If

        Pop location from stack to i,j
    End Loop

Flip spin:

    If new spin value different, flip the spin
    Mark spin as visited
    Increase cluster count

 */
/*----------------------------------------*/
static void flip_swendsen_wang_cluster(void)
{
#if TIMING_DIAGNOSTICS
clock_t start_clock, end_clock;

start_clock = clock();
#endif

flip_swendsen_wang_spin();

switch ( dimensionality )
    {
    case 2:
    loop
        {
        while ( another_swendsen_wang_spin() )
            {
            //  direction of possible new spin is s_r
            //  and location of possible new spin is (nn_i,nn_j).
            if ( push_onto_stack(s_i,s_j) )
                {
                s_i = nn_i;
                s_j = nn_j;
                flip_swendsen_wang_spin();
                }
            }

        if ( stack_empty() )
            {
            #if TIMING_DIAGNOSTICS
            end_clock = clock();
            flip_sw_cluster_clock += end_clock - start_clock;
            #endif
            return;
            }

        pop_from_stack(&s_i,&s_j);
        }
    break;

    case 3:
    loop
        {
        while ( another_swendsen_wang_spin() )
            {
            //  direction of possible new spin is s_r
            //  and location of possible new spin is (nn_i,nn_j,nn_k).
            if ( push_onto_stack(s_i,s_j,s_k) )
                {
                s_i = nn_i;
                s_j = nn_j;
                s_k = nn_k;
                flip_swendsen_wang_spin();
                }
            }

        if ( stack_empty() )
            {
            #if TIMING_DIAGNOSTICS
            end_clock = clock();
            flip_sw_cluster_clock += end_clock - start_clock;
            #endif
            return;
            }

        pop_from_stack(&s_i,&s_j,&s_k);
        }
    break;

    case 4:
    loop
        {
        while ( another_swendsen_wang_spin() )
            {
            //  direction of possible new spin is s_r
            //  and location of possible new spin is (nn_i,nn_j,nn_k,nn_l).
            if ( push_onto_stack(s_i,s_j,s_k,s_l) )
                {
                s_i = nn_i;
                s_j = nn_j;
                s_k = nn_k;
                s_l = nn_l;
                flip_swendsen_wang_spin();
                }
            }

        if ( stack_empty() )
            {
            #if TIMING_DIAGNOSTICS
            end_clock = clock();
            flip_sw_cluster_clock += end_clock - start_clock;
            #endif
            return;
            }

        pop_from_stack(&s_i,&s_j,&s_k,&s_l);
        }
    break;
    }

#if TIMING_DIAGNOSTICS
end_clock = clock();
flip_sw_cluster_clock += end_clock - start_clock;
#endif
}

/*-------------------------------------*/
static void flip_swendsen_wang_spin(void)
{
#if TIMING_DIAGNOSTICS
clock_t start_clock, end_clock;

start_clock = clock();
#endif

ct_size_of_cluster++;

switch ( dimensionality )
    {
    case 2:
    if ( s_spin_value != s_new_spin_value )
        {
        //  Flip the spin.
        if ( model_is_ising )
            do_ising_spin_flip(s_spin_value,s_i,s_j); 
        else //  model is q-state Potts
            do_q_state_potts_spin_flip(s_spin_value,s_new_spin_value,s_i,s_j); 
        }

    MARK_SITE_AS_VISITED_2(s_i,s_j);
    break;

    case 3:
    if ( s_spin_value != s_new_spin_value )
        {
        //  Flip the spin.
        if ( model_is_ising )
            do_ising_spin_flip(s_spin_value,s_i,s_j,s_k); 
        else //  model is q-state Potts
            do_q_state_potts_spin_flip(s_spin_value,s_new_spin_value,s_i,s_j,s_k); 
        }

    MARK_SITE_AS_VISITED_3(s_i,s_j,s_k);
    break;

    case 4:
    if ( s_spin_value != s_new_spin_value )
        {
        //  Flip the spin.
        if ( model_is_ising )
            do_ising_spin_flip(s_spin_value,s_i,s_j,s_k,s_l); 
        else //  model is q-state Potts
            do_q_state_potts_spin_flip(s_spin_value,s_new_spin_value,s_i,s_j,s_k,s_l); 
        }

    MARK_SITE_AS_VISITED_4(s_i,s_j,s_k,s_l);
    break;
    }

#if TIMING_DIAGNOSTICS
end_clock = clock();
flip_sw_spin_clock += end_clock - start_clock;
#endif
}

//  This returns true if there is a direction r such that
//  there is a nn spin in direction r which is part of this virtual cluster
//  and which is unvisited.
//  In this case the direction of the possible new spin on return is in s_r
//  and the location of the possible new spin is (nn_i,nn_j,...).
/*---------------------------------------*/
static int another_swendsen_wang_spin(void)
{
#if TIMING_DIAGNOSTICS
clock_t start_clock, end_clock;

start_clock = clock();
#endif

switch ( dimensionality )
    {
    case 2:
    if ( v_bonds2[s_i][s_j] )
        {
        //  There is at least one virtual bond at this site.

        get_nn_sites(s_i,s_j);

        for ( s_r=0; s_r<coord_num; s_r++ )
            {
            if ( v_bonds2[s_i][s_j] & bit[s_r] )  
            //  if a virtual bond exists in direction s_r
                {
                nn_i = nn_sites[s_r][0];
                nn_j = nn_sites[s_r][1];
                if ( !SITE_IS_VISITED_2(nn_i,nn_j) )
                    return ( true );
                }
            }
        }
    break;

    case 3:
    if ( v_bonds3[s_i][s_j][s_k] )
        {
        //  There is at least one virtual bond at this site.

        get_nn_sites(s_i,s_j,s_k);

        for ( s_r=0; s_r<coord_num; s_r++ )
            {
            if ( v_bonds3[s_i][s_j][s_k] & bit[s_r] )  
            //  if a virtual bond exists in direction s_r
                {
                nn_i = nn_sites[s_r][0];
                nn_j = nn_sites[s_r][1];
                nn_k = nn_sites[s_r][2];
                if ( !SITE_IS_VISITED_3(nn_i,nn_j,nn_k) )
                    return ( true );
                }
            }
        }
    break;

    case 4:
    if ( v_bonds4[s_i][s_j][s_k][s_l] )
        {
        //  There is at least one virtual bond at this site.

        get_nn_sites(s_i,s_j,s_k,s_l);

        for ( s_r=0; s_r<coord_num; s_r++ )
            {
            if ( v_bonds4[s_i][s_j][s_k][s_l] & bit[s_r] )  
            //  if a virtual bond exists in direction s_r
                {
                nn_i = nn_sites[s_r][0];
                nn_j = nn_sites[s_r][1];
                nn_k = nn_sites[s_r][2];
                nn_l = nn_sites[s_r][3];
                if ( !SITE_IS_VISITED_4(nn_i,nn_j,nn_k,nn_l) )
                    return ( true );
                }
            }
        }
    break;
    }

#if TIMING_DIAGNOSTICS
end_clock = clock();
another_sw_spin_clock += end_clock - start_clock;
#endif

return ( false );
}