Sign in

username or email:

password:



Not a member?
Forgot your password?

Search code



Search tips

Ads

See Also

Embedded SystemsFPGA

DSP Code Sharing > Heuristic multiuser waterfilling algorithm

Heuristic multiuser waterfilling algorithm

Language: C

Processor: Not Relevant

Submitted by Markus Nentwig on Nov 4 2010

Licensed under a Creative Commons Attribution 3.0 Unported License

Heuristic multiuser waterfilling algorithm


 

A heuristic implementation of power-allocation by multi-user waterfilling (a communications problem when scheduling transmissions from a basestation to several receivers with frequency varying channels).
Not particularly clever, but relatively straightforward and easy to extend and modify.

The main documentation can be found in the blog entry .

 
/* **********************************************************
 * Purpose::
 * heuristic multi-user waterfilling algorithm implementation
 * example invocation included; compile and run with
 * gcc -Wall (thisFile.c); ./a.out
 *
 * PLEASE NOTE:
 * This is a quick rewrite that hasn't been extensively tested.
 * Please verify independently that the code does what it says.
 */


/* **********************************************************
 * Header file
 * **********************************************************/


#ifndef WATERFILL_H
#define WATERFILL_H
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

/* **********************************************************
 * Waterfilling algorithm data structure
 * In C++ this would become an object.
 * Usage:
 * wfalg_new(...);
 * while (need to invoke it multiple times on changing data){
 *   wfalg_run(...);
 *   (process results)
 * }
 * wfalg_free(...); now the results become invalid  
 * **********************************************************/

typedef struct wfalg_t_ wfalg_t;

/* Purpose:
 * Create data structure for multiuser waterfiller "object"
 * Use for multiple invocations on varying data
 *
 * Parameters:
 * nRB: Number of "resource blocks" (frequency bins)
 *
 * nUser: number of simultaneous transmissions to allocate
 *
 * pmode: 0 conventional multi-user waterfilling: Per-resource power based on
 * channel quality estimate
 *        otherwise: select resources based on channel quality, but distribute the
 * per-user power evenly
 */

wfalg_t* wfalg_new(int /*nRB*/, int /*nUser*/, int /*mode*/);

/* Purpose:
 * Invoke multi-user waterfilling for the following parameters:
 * Rmax: maximum number of RBs that may be allocated to each individual user.
 *    in "ideal" waterfilling, a single user with a very good channel hogs up
 *    most of the resources. While this (ideally) maximizes sum throughput, there are
 *    usually other criteria that are equally or more important.
 *    The caller can limit the number of resources for a particular user, and thus
 *    control the "fairness" of allocation.
 *    Set Rmax >= nRB for each user to disable this limitation.
 *
 * SNR_threshold: Do not allocate a resource if the signal-to-noise ratio falls below the
 *    threshold. For example, even heavily coded QPSK doesn't lead to any meaningful
 *    throughput when the signal-to-noise ratio is below -3 dB
 *    if so, choose SNR_threshold=10*log10(-3/10) = 0.5
 *
 * Tinv: inverse of channel quality, noise-to-signal ratio N/S where "signal" includes
 *    the channel |H(f)| with f the frequency of a resource.
 *    Matrix: first user resource 0..nRB-1, second user resources 0..nRB-1, last user
 *
 * porder: Process users in the given order (array of user indices 0..nUser-1 with length nUser)
 *    The order is important in pathological cases, such as "more users than RBs."
 *    If so, the scheduling decision is effectively made by the caller, via the processing order:
 *    first come, first served, and the later ones get nothing.
 *
 * After execution,
 *   - resultAlloc points to a size-nRB array containing the index of the allocated user for each resource block.
     - resultPower points to a size-nRB array with the power allocated to a resource.
 * All powers for user j sum up to pUser[j]
 * The number of resources where resultAlloc==j is <= Rmax[j].
 * There is only one storage space per wfalg_t "object". Earlier results become invalid with
 * the next invocation.
 */

void wfalg_run(wfalg_t* /*obj*/, const double* /*pUser*/, const int* /*Rmax*/, const int* /*porder*/, const double* /*TinvMatrix*/, double /*SNR_threshold*/, int** /*resultAlloc*/, double** /*resultPower*/);

/* Purpose:
 * Release allocated memory.
 * obj becomes invalid.
 * any space provided by wfalg_run becomes invalid.
 */

void wfalg_free(wfalg_t* /* obj */);
#endif

/* **********************************************************
 * .c file
 * **********************************************************/


/* Data structure for multiple invocations without unnecessary malloc / free */
struct wfalg_t_ {
  /* parameters */
  int nRB;
  int nUser;
 
  /* Storage space for results */
  int* RB_alloc;
  double* RB_power;
 
  /* Internal temporary storage space
   * all Tinv values for the single user that is
   * processed by waterfill() */

  double* temp_singleUserTinv;
 
  /* internal temporary storage: How many resources are allocated per user? */
  int* temp_userNResources;
  int* order;
 
  /* Mode switch parameter */
  int mode;  
};

wfalg_t* wfalg_new(int nRB, int nUser, int mode){

  wfalg_t* obj=(wfalg_t*)malloc(sizeof(wfalg_t)); assert(obj);  
  obj->nUser=nUser;
  obj->nRB=nRB;
  obj->mode=mode;

  obj->RB_alloc=(int*)malloc(sizeof(int) * nRB); assert(obj->RB_alloc);
  obj->RB_power=(double*)malloc(sizeof(double) * nRB); assert(obj->RB_power);
  obj->temp_singleUserTinv=(double*)malloc(sizeof(double) * nRB); assert(obj->temp_singleUserTinv);
  obj->temp_userNResources=(int*)malloc(sizeof(int) * nUser); assert(obj->temp_userNResources);
  memset((void*)obj->RB_power, 0, nRB * sizeof(double));
 
  return obj;
}

void wfalg_free(wfalg_t* obj){
  free(obj->RB_alloc);
  free(obj->RB_power);
  free(obj->temp_singleUserTinv);
  free(obj->temp_userNResources);
  free(obj);
}

/* ************************************************************************************
 * Conventional multi-user waterfilling on the set of resources where
 * RB_alloc[]==userindex
 * expects temp_singleUserTinv to be filled with the Tinv for the allocating user!
 * ************************************************************************************/

static void waterfill(wfalg_t* obj, int userindex, double pUser){
  const int nRB=obj->nRB;

  const double E0=pUser;
  const double threshold=1e-12* E0;
 
  /* ************************************************************************************
   * Count resource allocated to this user
   * ************************************************************************************/

  int nRB_user=0;
  int j_RB;
  for(j_RB=0; j_RB < nRB; ++j_RB){
    if (*(obj->RB_alloc+j_RB)==userindex){
      ++nRB_user;
    }
  }
  if (nRB_user == 0){
    /* no resources allocated - can't waterfill */
    return;
  }
 
  double E=E0;
  double waterlevel=0;      
  while (E > threshold){
    /* ************************************************************************************
     * Distribute remaining energy evenly over all RBs
     * since some of them have greater-than-zero Tinv, this approximation will always stay
     * below target (standard waterfilling algorithm)
     * ************************************************************************************/

    E *= 0.999;
    waterlevel += E/(double)nRB_user;
   
    /* ************************************************************************************
     * Determine energy that remains with current allocation
     * ************************************************************************************/

    E=E0;

    /* Note: temp_singleUserTinv contains the Tinv for the user who allocates the RB.
     * This avoids many repeated table lookups from the nUser*nRB Tinv matrix. */

    double* pTinv=obj->temp_singleUserTinv;

    double* pRB_power=obj->RB_power;
    int* palloc=obj->RB_alloc;

    int j_RB;
    for(j_RB=0; j_RB < nRB; ++j_RB){    
      int alloc=*(palloc++);
      double Tinv=*(pTinv++);
     
      /* resource is allocated to current user */
      if (alloc==userindex){
       
        /* determine actual per-resource energy */
        double d=waterlevel - Tinv;
        d=(d > 0.0 ? d : 0.0);
        E -= d;
        *(pRB_power)=d;
      } /* if allocated */
     
      ++pRB_power;

    } /* for RB */
  } /* while energy remains */

  assert(E >= 0.0);
}

/* picks the best unallocated RB for the given user */
static int find_best_RB(wfalg_t* obj, int userindex, const double* thisUserTinv){
  /* find RB with lowest Tinv for this user */
  int i_RB;
  int bestRB_index=-1;
  double bestRB_Tinv=9e99;
  int valid=0;
  const double* pTinv=thisUserTinv;
  for (i_RB=0; i_RB < obj->nRB; ++i_RB){
    double Tinv=*(pTinv++);
    int alloc=*(obj->RB_alloc+i_RB);
    if ((alloc < 0) && (Tinv < bestRB_Tinv)){
      bestRB_index=i_RB;
      bestRB_Tinv=Tinv;
      valid=1;
    }    
  }
 
  if (!valid){
    bestRB_index=-1;
  }

  return bestRB_index; /* -1 means: none */
}

static int try_alloc_one_RB(wfalg_t* obj, int userindex, double pThisUser, const double* thisUserTinv, double SNR_threshold){
  int RBindex=find_best_RB(obj, userindex, thisUserTinv);
  if (RBindex >= 0){
   
    /* channel quality on resource RBindex */
    double Tinv=*(thisUserTinv+RBindex);
   
    /* allocate RB, at least temporarily */
    *(obj->RB_alloc+RBindex)=userindex;

    double Tinv2=Tinv;
    if (obj->mode){
      /* Equal power allocation - disregard "floor" level in waterfilling
       * We'll use the original Tinv for the SNR check later */

      Tinv2=0.0;
    }
   
    *(obj->temp_singleUserTinv+RBindex) = Tinv2;

    waterfill(obj, userindex, pThisUser);

    /* check SNR of last RB */
    double P=*(obj->RB_power + RBindex);
    double SNR=P/Tinv;
    if (SNR >= SNR_threshold){
      return 1; /* success */
    } else {
      /* too low power to be useful.
       * undo allocation. */

      *(obj->RB_alloc+RBindex)=-1;      
    }
  }
  return 0; /* failure */
}

static void zero_unallocated_RBs(const wfalg_t* obj){
  int* ap=obj->RB_alloc;  
  double* pp=obj->RB_power;
  int iRB;
  for (iRB=0; iRB < obj->nRB; ++iRB){
    *pp=(*ap >= 0 ? *pp : 0.0);
    ++pp;
    ++ap;
  }
}

void wfalg_run(wfalg_t* obj, const double* pUser, const int* Rmax, const int* porder, const double* TinvMatrix, double SNR_threshold, int** resultAlloc, double** resultPower){

  /* Deallocate all RBs */
  int* p=obj->RB_alloc;
  int i_RB;  
  for (i_RB=0; i_RB < obj->nRB; ++i_RB){
    *(p++) = -1;
  }
  memset((void*)obj->temp_userNResources, 0, obj->nUser * sizeof(int));

  int nAllocResourcesThisRound=-1; /* any nonzero value to enter the loop */

  /* Repeat until we fail to allocate another resource */
  while (nAllocResourcesThisRound != 0){
    nAllocResourcesThisRound=0;

    /* Iterate over all users ... */
    int jj_user;
    for (jj_user=0; jj_user < obj->nUser; ++jj_user){
      /* ... in the specified order */
      int j_user=*(porder+jj_user);

      /* power budget of current user */
      double p_jUser=*(pUser+j_user);
     
      /* Are we allowed to add another resource to this user? (Rmax constraint) */
      if (*(obj->temp_userNResources+j_user) < *(Rmax+j_user)){
       
        /* Look up the channel quality array for j_user */
        const double* thisUserTinv=TinvMatrix + j_user * obj->nRB;
       
        /* Try to allocate one more resource to this user */
        if (try_alloc_one_RB(obj, j_user, p_jUser, thisUserTinv, SNR_threshold)){
          /* Successfully allocated */
          ++ *(obj->temp_userNResources+j_user); /* count resources allocated to this user */
          ++nAllocResourcesThisRound; /* continue iterating */
        } else {
          /* this user can't allocate any more resources
           * There is no need to try again in future iterations,
           * disregard this user by making the Rmax test fail
           *
           * note, power allocation is not valid at this point */

          *(obj->temp_userNResources+j_user) = *(Rmax+j_user);
        }
      }      
    } /* for j_user */
  } /* while allocations succeed */
 
  /* The previous step fails exactly once per user, making the
   * power allocation invalid.
   *
   * Recalculate.
   * This is single-user waterfilling without any interaction, therefore order does not matter
   * Note that waterfill() requires a valid temp_singleUserTinv (which is the case at this point):
   * For each resource, it contains the Tinv of the allocating user
   */

  int j_user;
  for (j_user=0; j_user < obj->nUser; ++j_user){
    double p_jUser=*(pUser+j_user);    
    waterfill(obj, j_user, p_jUser);
  }
 
  /* Set zero power allocation for resources that aren't allocated to any user */
  zero_unallocated_RBs(obj);
 
  *resultAlloc=obj->RB_alloc;
  *resultPower=obj->RB_power;
}

/* **********************************************************
 * Example use (still .c file)
 * **********************************************************/

#if 1
int main(void){
  const int nUser=5;
  const int nRB=40;

  /* allocate and create random multi-user channel data */
  double* Tinv=(double*)malloc(nUser*nRB*sizeof(double));
  int i; int j;
  double* p=Tinv;
  for (i=0; i < nUser; ++i){
    for (j=0; j < nRB; ++j){
      *(p++)=(double)random() / (double)RAND_MAX;
    }
  }

  /* power per user */
  double pUser[]={1, 2, 3, 4, 5};
   
  /* processing order */
  int order[]={0, 1, 2, 3, 4};
 
  /* maximum number of resources per user */
  const int RMax[]={nRB, nRB, nRB, nRB, nRB};

  /* create waterfiller "object" */
  wfalg_t* wfalg=wfalg_new(nRB, nUser, /* mode */0);

  /* Invoke waterfilling */
  int* RB_alloc;
  double* RB_power;  
  wfalg_run(wfalg, pUser, RMax, order, Tinv, /* SNR_threshold */1.0, &RB_alloc, &RB_power);
 
  /* print result */
  int j_user; int i_RB;
  for (j_user=0; j_user  < nUser; ++j_user){
    printf("user %i ", j_user);
    double sum=0;
    for (i_RB=0; i_RB < nRB; ++i_RB){
      if (*(RB_alloc+i_RB)==j_user){
        double p=*(RB_power+i_RB);
        printf("%i=%lf ", i_RB, p);
        sum += p;
      }
    }
    printf("sum=%lf\n", sum);
  }
 
  /* clean up */
  wfalg_free(wfalg);
  free(Tinv);

  return EXIT_SUCCESS;
}
#endif
 
 
Rate this code snippet:
0
Rating: 0 | Votes: 0
 
   
 
posted by Markus Nentwig
Markus received his Dipl. Ing. degree in electrical engineering / communications in 1999. Work interests include RF transceiver system design, implementation, modeling and verification. He works as senior architect for Renesas Mobile Europe in Finland.


Comments


No comments yet for this code


Add a Comment
You need to login before you can post a comment (best way to prevent spam). ( Not a member? )