// Dijkstra.c

// OpenMP example program:  Dijkstra shortest-path finder in a
// bidirectional graph; finds the shortest path from vertex 0 to all
// others

// usage:  dijkstra nv print

// where nv is the size of the graph, and print is 1 if graph and min
// distances are to be printed out, 0 otherwise

// for simplicity, assume the number of threads evenly divides the
// number of vertices

#include <omp.h>
#include <stdio.h>
#include <stdlib.h>

#define largeint -1  // largest unsigned int

// finds closest to 0 among notdone, among s through e
void findmymin(int *notdone,int *mind,int s,int e,unsigned *d,int *v)
{  int i;
   *d = largeint; 
   for (i = s; i <= e; i++)
      if (notdone[i] && mind[i] < *d)  {
         *d = mind[i];
         *v = i;
      }
}

// for each i in [s,e], ask whether a shorter path to i exists, through
// mv
void updatemind(int mv, int nv, int *ohd, int *mind, int s, int e)
{  int i;
   for (i = s; i <= e; i++)
      if (mind[mv] + ohd[mv*nv+i] < mind[i])  
         mind[i] = mind[mv] + ohd[mv*nv+i];
}

void dowork(int nv, int *ohd, int *mind)
{  
   int *notdone, // vertices not checked yet
       nth,  // number of threads
       chunk,  // number of vertices handled by each thread
       md,  // current min distance over all threads
       mv,  // vertex which achieves that min
       i;

   notdone = malloc(nv*sizeof(int)); 
   for (i = 1; i < nv; i++)  {
      notdone[i] = 1;
      mind[i] = ohd[i];
   }

   int step;  // whole procedure goes nv steps

   #pragma omp parallel  
   {  int startv,endv,  // start, end vertices for my thread
          mymv,  // vertex which attains the min value in my chunk
          me = omp_get_thread_num();  
          unsigned mymd,  // min value found by this thread
                   md;  // overall minimum distance

      #pragma omp single   
      {  nth = omp_get_num_threads();  // must call from inside parallel block
         if (nv % nth != 0) {
            printf("nv must be divisible by nth\n");
            exit(1);
         }
         chunk = nv/nth;  
      }

      startv = me * chunk; 
      endv = startv + chunk - 1;
      for (step = 0; step < nv; step++)  {

         // find closest vertex to 0 among notdone for this thread's
         // group
         #pragma omp single 
         {  md = largeint; mv = 0;  }
         findmymin(notdone,mind,startv,endv,&mymd,&mymv);

         // update overall min if mine is smaller
         #pragma omp critical  
         {  if (mymd < md)  
               {  md = mymd; mv = mymv;  }
         }

         #pragma omp barrier 
         // by now we know the overal min dist and the vertex that
         // achieves it

         // mark new vertex as done 
         #pragma omp single 
         {  notdone[mv] = 0;  }

         // now update this threads's section of mind, the collection of
         // current shortest distances from vertex to vertex i: is there
         // a new shortest path from 0 to vertex i, that first takes a
         // path to mv and then one hop to i?
         updatemind(mv,nv,ohd,mind,startv,endv);

         #pragma omp barrier 
      }
   }
}

int main(int argc, char **argv)
{  int i,j,print,tmp;
   double startime,endtime;

   // nv = number of vertices, 
   int nv;

   // ohd = one-hop distances from each vertex to each vertex; absence
   // of an edge means distance of largeint; "ohd[i][j]" is ohd[i*nv+j]
   unsigned int *ohd; 

   // current minimum distances
   int *mind = malloc(nv*sizeof(int));

   // make test case
   nv = atoi(argv[1]);
   ohd = malloc(nv*nv*sizeof(unsigned int));
   // random graph
   for (i = 0; i < nv; i++)  
      for (j = i; j < nv; j++)  {
         if (j == i) ohd[i*nv+i] = 0;
         else  {
            ohd[nv*i+j] = rand() % 20;
            ohd[nv*j+i] = ohd[nv*i+j];
         }
      }
   
   startime = omp_get_wtime();
   dowork(nv,ohd,mind);  
   endtime = omp_get_wtime();

   printf("elapsed time:  %f\n",endtime-startime);
   print = atoi(argv[2]);
   if (print)  {
      printf("graph weights:\n");
      for (i = 0; i < nv; i++)  {
         for (j = 0; j < nv; j++)  
            printf("%u  ",ohd[nv*i+j]);
         printf("\n");
      }
      printf("minimum distances:\n");
      for (i = 1; i < nv; i++)
         printf("%u\n",mind[i]);
   }
}

