numbbo / coco

Numerical Black-Box Optimization Benchmarking Framework
https://numbbo.github.io/coco
Other
263 stars 91 forks source link

Constraints on variables in a custom functions #2294

Closed Yash-Pisat closed 5 months ago

Yash-Pisat commented 5 months ago

So, I have added the custom function in the new suite. I have 2 questions:

1) Is there a way to define constraints/bounds on each variable of the functions? Example: I have 10 variables, and each variables have specific bounds, is it possible to define those bounds in the function definition itself?

2) I have to start with random points initially and then run a Bayesian optimization loop. So when I generate those random points (x values), I need to evaluate those points on the functions (Y values) and store them. I want to use the Y values as a current max for the Bayesian optimization loop. I can run the Bayesian optimization on the custom function, but Is it possible to pass the initial random points to the newly added function in the suite?

Any guidance would be highly appreciated, Thank you.

nikohansen commented 5 months ago

re. 1., the C code has the functions coco_problem_get_smallest_values_of_interest(problem) and coco_problem_get_largest_values_of_interest(problem) which returns the respective fields of the problem struct which hence have to have been assigned accordingly, the Python code they are accessed via the properties problem.lower_bounds and problem.upper_bounds.

re. 2., why don't you think you could pass them as any other solution which is evaluated?

Yash-Pisat commented 5 months ago

re.1. ---> Now I understood how the upper bounds and lower bounds are assigned.

double lower_bounds[10] = {15, 0.3, 0, 0, 0, 0, 0, 0.049, 1.4, 80};
double upper_bounds[10] = {55, 1.4, 1.2, 0.6, 0.25, 0.3, 0.12, 0.45, 3, 100};

/* Allocates the viscosity problem */
static coco_problem_t *f_viscosity_allocate(const size_t number_of_variables) {
    coco_problem_t *problem = coco_problem_allocate_from_scalars("Viscosity Function",
                                                                 f_viscosity_evaluate, NULL,
                                                                 number_of_variables, lower_bounds, upper_bounds, 0);
    coco_problem_set_id(problem, "viscosity-%zu", number_of_variables);
    //f_viscosity_evaluate(problem, problem->best_parameter, problem->best_value);

    return problem;
}

In the above code I am passing lower_bounds and upper_bounds instead of -5 and 5.

I am getting this error in this part:

static coco_problem_t *coco_problem_allocate_from_scalars(const char *problem_name,
                                                          coco_evaluate_function_t evaluate_function,
                                                          coco_problem_free_function_t problem_free_function,
                                                          const size_t number_of_variables,
                                                          const double smallest_value_of_interest,
                                                          const double largest_value_of_interest,
                                                          const double best_parameter) {
  size_t i;
  coco_problem_t *problem = coco_problem_allocate(number_of_variables, 1, 0);

  problem->problem_name = coco_strdup(problem_name);
  problem->number_of_variables = number_of_variables;
  problem->number_of_objectives = 1;
  problem->number_of_constraints = 0;
  problem->evaluate_function = evaluate_function;
  problem->problem_free_function = problem_free_function;

  for (i = 0; i < number_of_variables; ++i) {
    problem->smallest_values_of_interest[i] = smallest_value_of_interest;
    problem->largest_values_of_interest[i] = largest_value_of_interest;
    problem->best_parameter[i] = best_parameter;
  }
  problem->number_of_integer_variables = 0;
  return problem;
}

The problem here is that 'smallest_value_of_interest' and 'largest_value_of_interest' accept only single values and I am trying to pass an array. So I tried to add '[i]' after 'smallest_value_of_interest' and 'largest_value_of_interest', but as soon as I compiled the whole package, this change is reverted back.

re.2 I figured out that I can pass the values as I do for other solutions, Thankyou

Thank you for your reply

ttusar commented 5 months ago

Re 1: The coco_problem_allocate_from_scalars function does exactly what its name suggests - allocates the problem using scalar values (it assumes that the lower and upper bounds are the same for all problems). The most simple work-around is to use the coco_problem_allocate_from_scalars function for allocation (with some dummy default values for the lower and upper bounds) and then update lower and upper bounds afterwards.

Yash-Pisat commented 5 months ago

@ttusar Thank you for your reply. Currently, I am giving the dummy values as 0 and 1 for lower bounds and upper bounds respectively. Where should I update the lower and upper bounds exactly?

/**
 * @file f_poly.c
 * @brief Implementation of the polynomial function and problem.
 */

#include <stdio.h>
#include <assert.h>
#include <math.h> // For pow function
#include "coco.h"
#include "coco_problem.c"
#include "suite_bbob_legacy_code.c"
#include "transform_obj_shift.c"
#include "transform_vars_shift.c"
#include "transform_obj_norm_by_dim.c"

// Define the specific bounds for each variable
double lower_bounds[10] = {15, 0.3, 0, 0, 0, 0, 0, 0.049, 1.4, 80};
double upper_bounds[10] = {55, 1.4, 1.2, 0.6, 0.25, 0.3, 0.12, 0.45, 3, 100};

/* Sigmoid function */
static double sigmoid(double x) {
    return 1 / (1 + exp(-x));
}

static double f_func_raw(const double *x, const size_t number_of_variables) {
    assert(number_of_variables >= 10);
    double x1 = x[0], x2 = x[1], x3 = x[2], x4 = x[3], x5 = x[4],
           x6 = x[5], x7 = x[6], x8 = x[7], x9 = x[8], x10 = x[9];

    double y = (1000 +
         -0.0001*pow(x1, 3) + -0.0001*pow(x1, 2)*x3 + -0.0003*pow(x1, 2)*x9 + -0.0000*pow(x1, 2)*x10 +
         -0.0030*x1*x2*x9 + -0.0001*x1*x2*x10 + 0.0006*x1*x3*x9 + -0.0002*x1*x3*x10 +
         -0.0002*x1*x4*x10 + -0.0000*x1*x6*x10 + 0.0003*x1*x7*x10 + 0.0073*x1*x8*x9 +
         -0.0005*x1*x8*x10 ;

    return y;
}

/* Transforms the polynomial output based on the given thresholds */
static double transform_output(double y, double lower_lim, double upper_limit, double steepness) {
    if (y < lower_lim) {
        return 0 + (y - 0) * sigmoid(steepness * (y - lower_lim));
    } else if (y > upper_limit) {
        return 10 - (10 - y) * sigmoid(steepness * (upper_limit - y));
    } else {
        return y;
    }
}

double calculate_func(const double *x, const size_t number_of_variables, double lower_lim, double upper_limit, double steepness) {
    double raw_output = f_func_raw(x, number_of_variables);
    return transform_output(raw_output, lower_lim, upper_limit, steepness);
}

static void f_func_evaluate(coco_problem_t *problem, const double *x, double *y) {
    assert(problem->number_of_objectives == 1);
    // Define the additional parameters for calculate_viscosity
    double lower_lim = 0.1; // The desired Lower limit of viscosity is 0, but to keep a smooth transition to zerp we keep this 0.1
    double upper_limit = 9.9; // The desired Upper limit of viscosity is 10, but to keep a smooth transition to zerp we keep this 9.9
    double steepness = 10; // This is used for sigmoid function to decide the smoothness of curve
    y[0] = calculate_func(x, problem->number_of_variables,lower_lim, upper_limit, steepness);
    //assert(y[0] + 1e-13 >= problem->best_value[0]); // SOMETHING THAT NEEDS TO BE DISCUSSED
}

static coco_problem_t *f_func_allocate(const size_t number_of_variables) {
    coco_problem_t *problem = coco_problem_allocate_from_scalars("Viscosity Function",
                                                                 f_func_evaluate, NULL,
                                                                 number_of_variables, 0, 1, 0);
    coco_problem_set_id(problem, "viscosity-%zu", number_of_variables);

    return problem;
}

/* Allocates the problem with transformations */
static coco_problem_t *f_func_problem_allocate(const size_t function,
                                                    const size_t dimension,
                                                    const size_t instance,
                                                    const long rseed,
                                                    const char *problem_id_template,
                                                    const char *problem_name_template) {
    double *xopt, fopt;
    coco_problem_t *problem = NULL;

    // Allocate and compute the optimal solution xopt based on the dimension and random seed
    xopt = coco_allocate_vector(dimension);
    bbob2009_compute_xopt(xopt, rseed, dimension);
    // Compute the optimal function value fopt based on the function ID and instance
    fopt = bbob2009_compute_fopt(function, instance);

    problem = f_func_allocate(dimension);  // Corrected to pass only one parameter
    // Shift the variables of the problem by xopt
    // problem = transform_vars_shift(problem, xopt, 0);

    // If the problem is part of a large-scale test suite, normalize the objective by dimension
    if (coco_strfind(problem_name_template, "BBOB large-scale suite") >= 0) {
        problem = transform_obj_norm_by_dim(problem);
    }
    // Shift the objective function by fopt
    // problem = transform_obj_shift(problem, fopt);

    // Set the problem ID and name using the provided templates and problem characteristics
    coco_problem_set_id(problem, problem_id_template, function, instance, dimension);
    coco_problem_set_name(problem, problem_name_template, function, instance, dimension);

    // Free allocated memory for xopt after using it
    coco_free_memory(xopt);
    return problem;
}

Thankyou in advance.

ttusar commented 5 months ago

I would do it in the f_viscosity_allocate function after you have allocated the problem. Then, something like this should do:

  size_t i;
  for (i = 0; i < number_of_variables; ++i) {
    problem->smallest_values_of_interest[i] = lower_bounds[i];
    problem->largest_values_of_interest[i] = upper_bounds[i];
  }

Here, lower_bounds and upper_bounds are the two global arrays from the top of your code.

Yash-Pisat commented 5 months ago

Thankyou so much! It worked!