Simulated Annealing with C

Solving Optimization Problems with C

We will look at how to develop Simulated Annealing algorithm in C to find the best solution for an optimization problem.

The problem we are facing is that we need to construct a list from a given set of numbers (domain) provided that the list doesn’t have any duplicates and the sum of the list is equal to 13.

The full code can be found in the GitHub repo: https://github.com/MNoorFawi/simulated-annealing-in-c

Defining the Problem

We have a domain which is the following list of numbers:

// include the needed libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// defining the available domain from which we get the answer
int domain[20] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};

Our target is to construct a list of 4 members with no duplicates, i.e. unique numbers, and the sum of the list should be 13

Let’s define a couple of macros for these conditions

#define CHOICE 20 // length of the domain
#define TARGET 13 // the target
#define LEN 4 // the length of the desired array

Now we define some helper functions that will help in our program

// returns random integer from 0 to n - 1
int rand_int(int n) {
  int r, rand_max = RAND_MAX - (RAND_MAX % n);
  while ((r = rand()) >= rand_max);
  return r / (rand_max / n);
}

// print array
void print_arr(int * a, int l) {
  int i = 0;
  for (; i < l; ++i)
    printf("%i ", a[i]);
}

Cool!

Now as we have defined the conditions, let’s get into the most critical part of the algorithm. The cost function!

Cost Function

The Cost Function is the most important part in any optimization algorithm. The algorithm searches different solutions in order to minimize the cost function of the current solution until it reaches the stop criteria.  The cost function is problem-oriented, which means we should define it according to the problem at hand, that’s why it is so important.

Our cost function for this problem is kind of simple. We can actually divide into two smaller functions; one to calculate the sum of the suggested list while the other checks for duplication.

// the cost func here is to see if the array meets the target
int cost(int * a) {
  int i, sum = 0;
  for (i = 0; i < LEN; i++)
    sum += a[i];
  return sum;
}

// to ensure that all the values in the array are unique
int is_dup(int * a, int len) {
  int i, j;
  for (i = 0; i < len - 1; ++i) {
    for (j = i + 1; j < len; ++j) {
      if (a[i] == a[j])
        return 1; // duplicates is found
    }
  }
  return 0;
}

We have now everything ready for the algorithm to start looking for the best solution.

Simulated Annealing

Simulated annealing algorithm is an optimization method which is inspired by the slow cooling of metals. The algorithm starts with a random solution to the problem. It has a variable called temperature, which starts very high and gradually gets lower (cool down).

In each iteration, the algorithm chooses a random number from the current solution and changes it in a given direction. The cost is calculated before and after the change, and the two costs are compared. If the new cost is lower, the new solution becomes the current solution, just like any other optimization algorithm. However, if the cost is higher, the algorithm can still accept the current solution with a certain probability. This is to avoid the local minimum.

As the picture shows, the simulated annealing algorithm, like optimization algorithms, searches for the global minimum which has the least value of the cost function that we are trying to minimize.

I prefer simulated annealing over gradient descent, as it can avoid the local minima while gradient descent can get stuck in it.

Now let’s define the algorithm:

// simulated annealing algorithm
void siman(int * current_sol, int( * cost_func)(int * ), float temp, float cool) {
  int i, j;
  double current_cost, new_cost, p;
  int new_sol[LEN];

  // populate the initial solution randomly from the domain
  for (i = 0; i < LEN; ++i)
    current_sol[i] = domain[rand_int(CHOICE)];

  // loop until temperature is cooled down totally
  while (temp > 0.01) {
    // choose a random element to update each iteration
    i = rand_int(LEN);
    // copy current_sol into new_sol
    memcpy(new_sol, current_sol, sizeof(int) * LEN);
    // update an element randomly from domain
    new_sol[i] = domain[rand_int(CHOICE)];
    // if array has duplicates skip
    if (is_dup(new_sol, LEN))
      continue;

    // calculate cost of current solution and cost of new solution to compare
    current_cost = cost_func(current_sol);
    new_cost = cost_func(new_sol);
    // a probability to help not looping forever
    p = exp((-new_cost - current_cost) / temp);

    // check if new cost has narrower gap with TARGET than the current cost
    if (abs(TARGET - new_cost) < abs(TARGET - current_cost) || rand() < p) {
      // if so then update current_sol to be the new solution
      memcpy(current_sol, new_sol, LEN * sizeof(int));
      printf("\ncurrent cost is: %f\n", new_cost);
      puts("current solution is:");
      print_arr(current_sol, LEN);
      puts("");
    }
    // cool down
    temp = temp * cool;
  }
}

Perfect! We developed everything for the problem. Now let’s develop the program to test the algorithm

C Main Function

We can easily now define a simple main() function and compile the code. But as you see, the siman function has arguments, temp and cool, that can usually be the same every run. So it would be better if we can make these arguments have default values.

C doesn’t support neither named nor default arguments. But with a little workaround, we can overcome this limitation and make our algorithm accept named arguments with default values.

We first define a struct which contains all the arguments:

typedef struct {
  int* current_sol;
  int( * cost_func)(int * );
  float temp, cool;
}
func_args;

Then, we define a wrapper function that checks for certain arguments, the default ones, if they are provided or not to assign the default values to them:

void siman_wrapper(func_args input) {
  /* our wrapper checks only for temp and cool 
  only and assign default values */
  float temp = input.temp ? input.temp : 100.0;
  float cool = input.cool ? input.cool : 0.99;
  printf("\n Temperature = %.2f & Cool = %.2f\n", temp, cool);
  return siman(input.current_sol, input.cost_func, temp, cool);
}

Now we define a macro that the program will use, let’s say the macro will be the interface for the algorithm. The macro will convert input into the struct type and pass it to the wrapper which in turn checks the default arguments and then pass it to our siman algorithm.

// macro for named args in a func
#define simulated_annealing(...) siman_wrapper((func_args) {__VA_ARGS__})

Now comes the definition of our main program:

int main() {
  printf("\n\tTarget is an array of length %i unique values\n\tsum should be %i\n\tfrom numbers between 1 and %i\n",
    LEN, TARGET, CHOICE);
  // the array to be filled with possible answers
  int * current_sol = (int * ) malloc(LEN * sizeof(int));
  simulated_annealing(current_sol, cost);
  return 0;
}

Compile and Run

At this point, we have done with developing, it is time to test that everything works well.

First we compile our program: I assume that you added all code in one file as in the github repo. However, you should feel free to have the project more structured into a header and .c files.

gcc siman.c -o siman -lm

‘-lm’ to include ‘math’ library

Then, we run the program and see the results:

./siman

        Target is an array of length 4 unique values
        sum should be 13
        from numbers between 1 and 20

 Temperature = 100.00 & Cool = 0.99
 
current cost is: 45.000000
current solution is:
17 8 16 4

current cost is: 44.000000
current solution is:
17 8 15 4

...
...

current cost is: 12.000000
current solution is:
1 2 6 3

current cost is: 13.000000
current solution is:
1 2 6 4

Great! It works so well.

You can also check how to develop simulated annealing algorithm in python to solve resource allocation

Share

Leave a Reply

Your email address will not be published. Required fields are marked *