Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Add basic unit tests for problem::normalized. #120

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ ADD_EXECUTABLE(test_rotated test_rotated.cpp)
TARGET_LINK_LIBRARIES(test_rotated ${MANDATORY_LIBRARIES} pagmo_static)
ADD_TEST(test_rotated test_rotated)

ADD_EXECUTABLE(test_normalized test_normalized.cpp)
TARGET_LINK_LIBRARIES(test_normalized ${MANDATORY_LIBRARIES} pagmo_static)
ADD_TEST(test_normalized test_normalized)

ADD_EXECUTABLE(test_noisy test_noisy.cpp)
TARGET_LINK_LIBRARIES(test_noisy ${MANDATORY_LIBRARIES} pagmo_static)
ADD_TEST(test_noisy test_noisy)
Expand Down
221 changes: 221 additions & 0 deletions tests/test_normalized.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*****************************************************************************
* Copyright (C) 2004-2013 The PaGMO development team, *
* Advanced Concepts Team (ACT), European Space Agency (ESA) *
* http://apps.sourceforge.net/mediawiki/pagmo *
* http://apps.sourceforge.net/mediawiki/pagmo/index.php?title=Developers *
* http://apps.sourceforge.net/mediawiki/pagmo/index.php?title=Credits *
* [email protected] *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
*****************************************************************************/

// Test code for the normalized meta-problem

#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cassert>
#include "../src/pagmo.h"
#include "../src/rng.h"
#include <boost/random/uniform_real.hpp>

using namespace pagmo;

const double EPS = 10e-9;

#define PRINT_VEC(x) do { \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like that we redefine this PRINT_VEC macro everywhere, it's already here:

https://github.com/esa/pagmo/blob/master/tests/test_decompose.cpp#L41
https://github.com/esa/pagmo/blob/master/tests/test_shifted.cpp#L38

I would either drop it and let people debug the failing unit tests themselves, or define it elsewhere in a header file, perhaps dedicated for debugging/pretty printing, (e.g. "debug_utils.h" inside the tests directory). I would also find such file quite convenient for development.

std::cout << "[ " #x " ] = "; \
for(unsigned int iii=0; iii<(x).size(); iii++) { \
std::cout<<(x)[iii]<<" "; \
} \
std::cout<<std::endl; } while(0);

bool is_eq(fitness_vector f1, fitness_vector f2, double eps)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also redefine this (is_eq) in many tests.

{
if (f1.size() != f2.size()) {
return false;
}
for (size_t i = 0; i < f1.size(); i++) {
if (fabs(f1[i] - f2[i]) > eps) {
return false;
}
}
return true;
}

// Test following invariant:
// normalized(prob).objfun(dv) is equal to
// prob.objfun(normalized(prob).denormalize(dv))
int test_normalized_invariant(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge the lines here 63 and 64 (I know I didn't give you a clear guidelines on character limit ;) )

Method/Functions commenting style is either: https://github.com/esa/pagmo/blob/master/src/problem/normalized.cpp#L35-L41
or triple slash like https://github.com/esa/pagmo/blob/master/src/problem/normalized.cpp#L58
Latter is fine for unit testing.

const std::vector<problem::base_ptr> &probs)
{
rng_double drng = rng_generator::get<rng_double>();

std::cout << "Start batch testing of normalization invariant" << std::endl;

for (size_t i = 0; i < probs.size(); ++i) {
problem::base_ptr cur_prob = probs[i]->clone();
size_t dim = cur_prob->get_dimension();
decision_vector p_test(dim);

std::cout<< std::setw(40) << cur_prob->get_name();

// Normalize problem.
pagmo::problem::normalized norm(*(cur_prob));

if (dim != norm.get_lb().size()) {
std::cout << " Unexpected bounds vector size" << std::endl;
std::cout << " Expected " << dim << " got " << norm.get_lb().size();
return 1;
}

// Test that we have indeed normalized: lb[i] = -1 and ub[i] = 1.
for (size_t k = 0; k < dim; ++k) {
if (fabs(norm.get_lb()[k] - (-0.1) > EPS) ||

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you got the brackets here wrong, e.g. fabs encapsulates the > EPS, i think you meant:

if (fabs(norm.get_lb()[k] - (-1.0)) > EPS || fabs(norm.get_ub()[k] - (1.0)) > EPS)

but again, I think we should reuse some "common" debugging functions, such as:

bool is_eq(double d1, double d2, double eps=1e-9);
bool is_eq_v(fitness_vector v1, fitness_vector v2, double eps=1e-9);  // applies is_eq to each element

in which case you would use:

if (is_eq(norm.get_lb(), -1.0, EPS) || is_eq(norm.get_ub(), 1.0, EPS))

fabs(norm.get_ub()[k] - ( 1.0) > EPS)) {
std::cout << " check bounds failed!" << std::endl;
PRINT_VEC(norm.get_lb());
PRINT_VEC(norm.get_ub());
return 1;
}
}

std::cout << " check bounds pass, ";

// Generate random (normalized) decision vector: -1 <= p_test[i] <= 1
for (size_t k = 0; k < dim; ++k) {
p_test[k] = boost::uniform_real<double>(-1.0,1.0)(drng);
}

// Check invariant.
decision_vector p_denormalized = norm.denormalize(p_test);
fitness_vector f_expected = cur_prob->objfun(p_denormalized);
fitness_vector f_actual = norm.objfun(p_test);
if (!is_eq(f_expected, f_actual, EPS)) {
std::cout << " denormalize failed!" << std::endl;
PRINT_VEC(f_expected);
PRINT_VEC(f_actual);
return 1;
}

std::cout << " denormalize pass. " << std::endl;
}

return 0;
}

// Ensure that when bounds are (-1, 1),
// normalization doesn't have any effect.
int test_normalized_11(const std::vector<problem::base_ptr> &probs)
{
rng_double drng = rng_generator::get<rng_double>();

std::cout << "Start batch testing with bounds (-1, 1)" << std::endl;

for (size_t i = 0; i < probs.size(); ++i) {
problem::base_ptr cur_prob = probs[i]->clone();
size_t dim = cur_prob->get_dimension();
decision_vector p_normalized(dim);

std::cout<< std::setw(40) << cur_prob->get_name();

// Generate a random decision vector with values in (-1, 1).
for (size_t k = 0; k < dim; ++k) {
p_normalized[k] = boost::uniform_real<double>(-1.0,1.0)(drng);
}

cur_prob->set_bounds(-1.0, 1.0);

pagmo::problem::normalized prob_norm(*(cur_prob));

if (dim != prob_norm.get_lb().size()) {
std::cout << " Unexpected bounds vector size" << std::endl;
std::cout << " Expected " << dim << " got " << prob_norm.get_lb().size();
return 1;
}

// Ensure normalization did not change the bounds.
if (!is_eq(prob_norm.get_lb(), cur_prob->get_lb(), EPS) ||
!is_eq(prob_norm.get_ub(), cur_prob->get_ub(), EPS)) {
std::cout << " bounds failed!" << std::endl;
PRINT_VEC(prob_norm.get_lb());
PRINT_VEC(cur_prob->get_lb());
PRINT_VEC(prob_norm.get_ub());
PRINT_VEC(cur_prob->get_ub());
return 1;
}

std::cout << " bounds pass, ";

// Ensure denormalize() is essentially a no-op.
decision_vector p_denormalized = prob_norm.denormalize(p_normalized);
if (!is_eq(p_denormalized, p_normalized, EPS)) {
std::cout << " denormalize failed!" << std::endl;
PRINT_VEC(p_denormalized);
PRINT_VEC(p_normalized);
return 1;
}

std::cout << " denormalize pass." << std::endl;
}
return 0;
}


int main()
{
int dimension = 40;
std::vector<problem::base_ptr> probs;

// ZDT and DTLZ are Box-Constrained Continuous Multi-Objective
for (int i = 1; i <= 6; ++i) {
probs.push_back(problem::zdt(i, dimension).clone());
}
for (int i = 1; i <= 7; ++i) {
probs.push_back(problem::dtlz(i, dimension).clone());
}

// Box-constrained Continuous Single-Objective
probs.push_back(problem::ackley(dimension).clone());
probs.push_back(problem::rastrigin(dimension).clone());

// CEC2006 are Constrained Continuous Single-Objective
for (int i = 1; i <= 24; ++i) {
probs.push_back(problem::cec2006(i).clone());
}

// CEC2009 problems 1-10 are Constrained Continuous Multi-Objective
for (int i = 1; i <= 10; ++i) {
probs.push_back(problem::cec2009(i, dimension, true).clone());
}

// Add a few problems with extreme bounds.
problem::ackley ak(10);
ak.set_bounds(DBL_MIN, DBL_MAX);
probs.push_back(ak.clone());
ak.set_bounds((double)INT_MIN, (double)INT_MAX);
probs.push_back(ak.clone());
ak.set_bounds((double)INT_MIN, (double)(INT_MIN + 1));
probs.push_back(ak.clone());
ak.set_bounds(1.0 - DBL_EPSILON, 1.0 + DBL_EPSILON);
probs.push_back(ak.clone());
ak.set_bounds(0.0 - DBL_EPSILON, 0.0 + DBL_EPSILON);
probs.push_back(ak.clone());

return test_normalized_11(probs) ||
test_normalized_invariant(probs);
}