Skip to content

Commit

Permalink
remove undefined behaviour from the fitness function getters
Browse files Browse the repository at this point in the history
  • Loading branch information
KRM7 committed Nov 6, 2023
1 parent 871b4d4 commit 18a6857
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 66 deletions.
91 changes: 49 additions & 42 deletions src/core/fitness_function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#ifndef GA_CORE_FITNESS_FUNCTION_HPP
#define GA_CORE_FITNESS_FUNCTION_HPP

#include "population.hpp"
#include "candidate.hpp"
#include "../utility/bounded_value.hpp"
#include "../utility/utility.hpp"
#include <functional>
Expand All @@ -13,35 +13,28 @@
namespace gapp
{
/**
* The base class of the fitness functions used in the algorithms.
* The fitness functions take a candidate solution (chromosome) as a parameter
* and return a fitness vector after evaluating the chromosome.
*
* This should be used as the base class for fitness functions if the chromosome length
* is not known at compile time.
* If the chromosome length is known at compile, use FitnessFunction as the base class instead.
* The base class of the fitness functions used in the GAs.
* Contains all of the properties of a fitness function that are not dependent
* on the gene type.
*
* @tparam T The gene type expected by the fitness function.
* Fitness function implementations should not be derived directly from this class,
* but instead from FitnessFunctionBase or FitnessFunction.
*/
template<typename T>
class FitnessFunctionBase
class FitnessFunctionInfo
{
public:
/** The gene type of the chromosomes that can be evaluated by the fitness function. */
using GeneType = T;

/**
* Create a fitness function.
*
* @param chrom_len The chromosome length that is expected by the fitness function,
* and will be used for the candidate solutions in the algorithm. \n
* Must be at least 1, and a value must be given even if the chromosome lengths are variable,
* as it will be used to generate the initial population.
* @param dynamic Should be true if the fitness vector returned for a chromosome will not
* and will be used for the candidate solutions in the GA. \n
* Must be at least 1, and a value must be specified even if the chromosome lengths are variable,
* as it will still be used to generate the initial population.
* @param is_dynamic Should be true if the fitness vector returned for a chromosome will not
* always be the same for the same chromosome (eg. it changes over time or isn't deterministic).
*/
constexpr FitnessFunctionBase(Positive<size_t> chrom_len, bool dynamic = false) noexcept :
chrom_len_(chrom_len), dynamic_(dynamic)
constexpr FitnessFunctionInfo(Positive<size_t> chrom_len, bool is_dynamic = false) noexcept :
chrom_len_(chrom_len), is_dynamic_(is_dynamic)
{}

/** @returns The chromosome length the fitness function expects. */
Expand All @@ -50,7 +43,42 @@ namespace gapp

/** @returns True if the fitness function is dynamic. */
[[nodiscard]]
constexpr bool dynamic() const noexcept { return dynamic_; }
constexpr bool is_dynamic() const noexcept { return is_dynamic_; }

/** Destructor. */
virtual ~FitnessFunctionInfo() = default;

protected:

FitnessFunctionInfo(const FitnessFunctionInfo&) = default;
FitnessFunctionInfo(FitnessFunctionInfo&&) = default;
FitnessFunctionInfo& operator=(const FitnessFunctionInfo&) = default;
FitnessFunctionInfo& operator=(FitnessFunctionInfo&&) = default;

private:
Positive<size_t> chrom_len_;
bool is_dynamic_ = false;
};

/**
* The base class of the fitness functions used in the GAs.
* The fitness functions take a candidate solution (chromosome) as a parameter
* and return a fitness vector after evaluating the chromosome.
*
* This should be used as the base class for fitness functions if the chromosome length
* is not known at compile time.
* If the chromosome length is known at compile, use FitnessFunction as the base class instead.
*
* @tparam T The gene type expected by the fitness function.
*/
template<typename T>
class FitnessFunctionBase : public FitnessFunctionInfo
{
public:
/** The gene type of the chromosomes that can be evaluated by the fitness function. */
using GeneType = T;

using FitnessFunctionInfo::FitnessFunctionInfo;

/**
* Compute the fitness value of a chromosome.
Expand All @@ -63,23 +91,9 @@ namespace gapp
*/
FitnessVector operator()(const Chromosome<T>& chrom) const { return invoke(chrom); }


/** Destructor. */
virtual ~FitnessFunctionBase() = default;

protected:

FitnessFunctionBase(const FitnessFunctionBase&) noexcept = default;
FitnessFunctionBase(FitnessFunctionBase&&) noexcept = default;
FitnessFunctionBase& operator=(const FitnessFunctionBase&) noexcept = default;
FitnessFunctionBase& operator=(FitnessFunctionBase&&) noexcept = default;

private:
/** The implementation of the fitness function. Should be thread-safe. */
virtual FitnessVector invoke(const Chromosome<T>& chrom) const = 0;

Positive<size_t> chrom_len_;
bool dynamic_ = false;
};

/**
Expand Down Expand Up @@ -109,13 +123,6 @@ namespace gapp
constexpr FitnessFunction(bool dynamic = false) noexcept :
FitnessFunctionBase<T>(ChromLen, dynamic)
{}

protected:

FitnessFunction(const FitnessFunction&) noexcept = default;
FitnessFunction(FitnessFunction&&) noexcept = default;
FitnessFunction& operator=(const FitnessFunction&) noexcept = default;
FitnessFunction& operator=(FitnessFunction&&) noexcept = default;
};

} // namespace gapp
Expand Down
10 changes: 3 additions & 7 deletions src/core/ga_base.decl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,18 +187,14 @@ namespace gapp
GA(Positive<size_t> population_size, AlgorithmType algorithm, CrossoverType crossover, MutationType mutation, StoppingType stop_condition = {});


/** @returns The fitness function used. Undefined if no fitness function is set. */
/** @returns The fitness function used. A nullptr is returned if no fitness function is set. */
[[nodiscard]]
const FitnessFunctionBase<T>& fitness_function() const& noexcept;
const FitnessFunctionBase<T>* fitness_function() const& noexcept final;

/** @returns The chromosome length of the candidates of the population. */
/** @returns The chromosome length used. Returns 0 if the chromosome length is unspecified (ie. no fitness function was set yet). */
[[nodiscard]]
size_t chrom_len() const noexcept final;

/** @returns True if a dynamic fitness function is used. */
[[nodiscard]]
bool dynamic_fitness() const noexcept final;


/** @returns The lower and upper bounds for each of the chromosomes' genes (the ranges are inclusive). @see Bounds */
[[nodiscard]]
Expand Down
16 changes: 4 additions & 12 deletions src/core/ga_base.impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,23 +105,15 @@ namespace gapp


template<typename T>
inline const FitnessFunctionBase<T>& GA<T>::fitness_function() const& noexcept
inline const FitnessFunctionBase<T>* GA<T>::fitness_function() const& noexcept
{
GAPP_ASSERT(fitness_function_, "No fitness function is set for the GA.");

return *fitness_function_;
return fitness_function_.get();
}

template<typename T>
inline size_t GA<T>::chrom_len() const noexcept
{
return fitness_function().chrom_len();
}

template<typename T>
inline bool GA<T>::dynamic_fitness() const noexcept
{
return fitness_function().dynamic();
return fitness_function() ? fitness_function()->chrom_len() : 0_sz;
}


Expand Down Expand Up @@ -458,7 +450,7 @@ namespace gapp
/* If the fitness function is static, and the solution has already
* been evaluted sometime earlier (in an earlier generation), there
* is no point doing it again. */
if (!sol.is_evaluated || fitness_function_->dynamic())
if (!sol.is_evaluated || fitness_function_->is_dynamic())
{
std::atomic_ref{ num_fitness_evals_ }.fetch_add(1, std::memory_order_release);

Expand Down
11 changes: 6 additions & 5 deletions src/core/ga_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#define GA_CORE_GA_INFO_HPP

#include "population.hpp"
#include "fitness_function.hpp"
#include "../utility/bounded_value.hpp"
#include "../utility/utility.hpp"
#include "../metrics/metric_set.hpp"
Expand Down Expand Up @@ -95,18 +96,18 @@ namespace gapp
size_t max_gen() const noexcept { return max_gen_; }


/** @returns The chromosome length used for the candidates of the population. */
/** @returns The fitness function used. A nullptr is returned if no fitness function is set. */
[[nodiscard]]
virtual const FitnessFunctionInfo* fitness_function() const& noexcept = 0;

/** @returns The chromosome length used. Returns 0 if the chromosome length is unspecified (ie. no fitness function was set yet). */
[[nodiscard]]
virtual size_t chrom_len() const noexcept = 0;

/** @returns The number of objectives of the fitness function. */
[[nodiscard]]
size_t num_objectives() const noexcept { GAPP_ASSERT(num_objectives_); return num_objectives_; }

/** @returns True if a dynamic fitness function is used. */
[[nodiscard]]
virtual bool dynamic_fitness() const noexcept = 0;


/**
* @returns The fitness matrix of the population.
Expand Down

0 comments on commit 18a6857

Please sign in to comment.