/* L-24 MCS 572 Friday 10 March 2006 : parallel knapsack problem,
 * with a static load assignment; run this program on 8 processors. */

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

int count_choice ( int n, double weight[n], int choice[n],
                   double sum, int cnt );
/*
 * DESCRIPTION :
 *   Counts the number of times this function is called, returning 1
 *   each time.  Returns the number of counts when n < 0 on input. */

int pack ( 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 m, double *w, int *c, double s, int nb ) );

/* DESCRIPTION :
 *   Exhaustive search to select items to pack a knapsack.
 *
 * ON ENTRY :
 *   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(n,3,w,c,s,min_sum,max_sum,1,max,count_choice); break;
      case 2: c[0] = 1; c[1] = 1; s = w[0]+w[1];
              pack(n,3,w,c,s,min_sum,max_sum,2,max,count_choice); break;
      case 3: c[0] = 1; c[2] = 1; s = w[0]+w[2];
              pack(n,3,w,c,s,min_sum,max_sum,2,max,count_choice); break;
      case 4: c[0] = 1; c[1] = 1; c[2] = 1; s = w[0]+w[1]+w[2];
              pack(n,3,w,c,s,min_sum,max_sum,3,max,count_choice); break;
      case 5: c[1] = 1; s = w[1];
              pack(n,3,w,c,s,min_sum,max_sum,0,max,count_choice); break;
      case 6: c[1] = 1; c[2] = 1; s = w[1]+w[2];
              pack(n,3,w,c,s,min_sum,max_sum,2,max,count_choice); break;
      case 7: c[2] = 1; s = w[2]; 
              pack(n,3,w,c,s,min_sum,max_sum,1,max,count_choice); break;
      default: pack(n,3,w,c,0.0,min_sum,max_sum,0,max,count_choice);
   }
   printf("Node %d counted %d valid choices.\n",
          myid,count_choice(-1,w,c,0,0));

   MPI_Finalize();
   return 0;
}

int pack ( 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 m, double *w, int *c, double s, int nb ) )
{
   if((k < n) && (sum <= max_sum) && (cnt < max_items))
   {
      int i;

      for(i=k; i<n; i++)
      {
         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(n,weight,choice,sum+weight[i],cnt+1) == 0)
                  return 0;                  /* interrupt the search */
            }
	    else
            {
               if(pack(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 count_choice ( int n, double weight[n], int choice[n],
                   double sum, int cnt )
{
   static int counter = 0;

   if(n < 0) 
      return counter;
   else
   {
      counter++;
      return 1;
   }
}
