/* L-24 MCS 572 Friday 10 March 2006 : parallel knapsack problem,
 * with a static load assignment; run this program on 8 processors,
 * terminates as soon as one good sum is found. */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "mpi.h"

#define tag 100 /* tag for sending a number */

int terminate ( int myid, int p, int n, double weight[n], int choice[n],
                double sum, int cnt );
/*
 * DESCRIPTION :
 *   Once a good sum has been found, this routine is called,
 *   and the termination message is send to all other nodes. */

int pack ( int myid, int np, int n, int k, double weight[n], int choice[n],
           double sum, double min_sum, double max_sum, int cnt, int max_items,
int (*process) ( int i, int p, int m, double *w, int *c, double s, int nb ) );

/* DESCRIPTION :
 *   Exhaustive search to select items to pack a knapsack.
 *
 * ON ENTRY :
 *   myid       identification label of the node;
 *   np         total number of nodes;
 *   n          total number of items to consider;
 *   k          start enumeration at item k;
 *   weight     weight of every item;
 *   choice     current choice: choice[i] == 1 if i is chosen;
 *   sum        sum of weights of the current choice;
 *   min_sum    minimal sum of weight to meet requirement;
 *   max_sum    sum may not exceed this value;
 *   cnt        current number of items selected;
 *   max_item   cnt may not exceed this value;
 *   process    is called whenever a new good sum has been found.
 *
 * ON RETURN :
 *   1 if search must continue, 0 if search must stop. */

int main ( int argc, char *argv[] )
{
   int n,i,max,*c,p,myid;
   double *w,min_sum,max_sum,s;

   MPI_Init(&argc,&argv);
   MPI_Comm_size(MPI_COMM_WORLD,&p);
   MPI_Comm_rank(MPI_COMM_WORLD,&myid);

   if(myid==0)
   {
      srand(time(NULL));

      printf("Give number of items : "); scanf("%d",&n);

      w = (double*)calloc(n,sizeof(double));
      for(i=0; i<n; i++) w[i] = ((double) rand())/RAND_MAX;

      printf("The weights :\n");
      for(i=0; i<n; i++) printf("%.15lf\n",w[i]);

      printf("Give the minimal sum of weights : "); scanf("%lf",&min_sum);
      printf("Give the maximal sum of weights : "); scanf("%lf",&max_sum);
      printf("Give the maximal number of items : "); scanf("%d",&max);
   }
   MPI_Bcast(&n,1,MPI_INT,0,MPI_COMM_WORLD);
   if(myid!=0) w = (double*)calloc(n,sizeof(double));
   MPI_Bcast(w,n,MPI_DOUBLE,0,MPI_COMM_WORLD);
   MPI_Bcast(&min_sum,1,MPI_DOUBLE,0,MPI_COMM_WORLD);
   MPI_Bcast(&max_sum,1,MPI_DOUBLE,0,MPI_COMM_WORLD);
   MPI_Bcast(&max,1,MPI_INT,0,MPI_COMM_WORLD);
   c = (int*)calloc(n,sizeof(int));
   for(i=0; i<n; i++) c[i] = 0;
   switch(myid)
   {
      case 1: c[0] = 1; s = w[0];
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,1,max,terminate); break;
      case 2: c[0] = 1; c[1] = 1; s = w[0]+w[1];
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,2,max,terminate); break;
      case 3: c[0] = 1; c[2] = 1; s = w[0]+w[2];
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,2,max,terminate); break;
      case 4: c[0] = 1; c[1] = 1; c[2] = 1; s = w[0]+w[1]+w[2];
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,3,max,terminate); break;
      case 5: c[1] = 1; s = w[1];
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,0,max,terminate); break;
      case 6: c[1] = 1; c[2] = 1; s = w[1]+w[2];
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,2,max,terminate); break;
      case 7: c[2] = 1; s = w[2]; 
              pack(myid,p,n,3,w,c,s,min_sum,max_sum,1,max,terminate); break;
      default: pack(myid,p,n,3,w,c,0.0,min_sum,max_sum,0,max,terminate);
   }
   MPI_Finalize();
   return 0;
}

int pack ( int myid, int np, int n, int k, double weight[n], int choice[n],
           double sum, double min_sum, double max_sum, int cnt, int max_items,
int (*process) ( int i, int p, int m, double *w, int *c, double s, int nb ) )
{
   MPI_Status status;
   int flag = 0;

   if((k < n) && (sum <= max_sum) && (cnt < max_items))
   {
      int i;

      for(i=k; i<n; i++)
      {
         MPI_Iprobe(MPI_ANY_SOURCE,tag,MPI_COMM_WORLD,&flag,&status);
         if(flag==1)
         {
            int t;
            MPI_Recv(&t,1,MPI_INT,MPI_ANY_SOURCE,tag,MPI_COMM_WORLD,
                     &status);
            printf("Node %d received terminating signal from %d.\n",myid,t);
            fflush(stdout);
            return 0;
         }

         if(sum + weight[i] <= max_sum)      /* we pick the item */
         {
            choice[i] = 1;                   /* add item to the bag */

            if(sum + weight[i] >= min_sum)   /* found good choice */
            {
               if(process(myid,np,n,weight,choice,sum+weight[i],cnt+1) == 0)
                  return 0;                  /* interrupt the search */
            }
	    else
            {
               if(pack(myid,np,n,i+1,weight,choice,sum+weight[i],
                       min_sum,max_sum,cnt+1,max_items,process) == 0)
                  return 0;                  /* interrupt the search */
            }
            choice[i] = 0;                   /* skip the item */
         }
      }
   }
   return 1;                                 /* continue the search */
}

int terminate ( int myid, int p, int n, double weight[n], int choice[n],
                double sum, int cnt )
{
   int j;

   printf("Node %d has found a selection of %d items:\n",myid,cnt);
   for(j=0; j<n; j++)
      if(choice[j] == 1) printf(" %d",j);
   printf("\nwith sum of weights: %.15lf\n",sum);
   fflush(stdout);

   for(j=0; j<p; j++)
      if(j!=myid)
      {
         printf("Node %d is sending terminating signal to %d\n",myid,j);
         fflush(stdout);
         MPI_Send(&myid,1,MPI_INT,j,tag,MPI_COMM_WORLD);
      }

   return 0;
}
