Skip to content

Examples

The app/ directory contains ready-to-run example programs. Build and run any example with:

fpm run <name> --profile="release"

Quick start: Rosenbrock function

The Rosenbrock function is a classic test problem with a narrow curved valley leading to a global minimum at \((x, y) = (1, 1)\), where \(f = 0\):

\[f(x, y) = (1 - x)^2 + 100\,(y - x^2)^2\]

This example demonstrates evolve_population with blend crossover and Gaussian mutation, and shows how to retrieve the best individual from each generation.

program rosenbrock_example

  use evortran__util_kinds,        only : wp
  use evortran__individuals_float, only : individual
  use evortran__evolutions_float,  only : evolve_population

  implicit none

  type(individual) :: best_ind
  type(individual), allocatable :: bests(:)

  real(wp), parameter :: xmin = -2.0_wp, xmax = 2.0_wp
  real(wp), parameter :: ymin = -1.0_wp, ymax = 3.0_wp

  best_ind = evolve_population( &
    300, 2, rosenbrock,         &
    mating='blend',             &
    elite_size=3,               &
    max_generations=1000,       &
    fitness_target=1.0e-7_wp,  &
    mutate='gaussian',          &
    mutate_prob=0.2_wp,         &
    mutate_gene_prob=0.4_wp,   &
    mutate_gaussian_sigma=0.5_wp, &
    verbose=.true.,             &
    fittest_inds_from_gen=bests)

  write(*,*) 'Minimum at:'
  write(*,*) '  x =', xmin + best_ind%genes(1) * (xmax - xmin)
  write(*,*) '  y =', ymin + best_ind%genes(2) * (ymax - ymin)
  write(*,*) '  f =', best_ind%get_fitness()

contains

  pure subroutine rosenbrock(ind, f)
    class(individual), intent(in) :: ind
    real(wp), intent(out) :: f
    real(wp) :: x, y
    x = xmin + ind%genes(1) * (xmax - xmin)
    y = ymin + ind%genes(2) * (ymax - ymin)
    f = (1.0_wp - x)**2 + 100.0_wp * (y - x**2)**2
  end subroutine

end program

Run the packaged version with:

fpm run rosenbrock --profile="release"

Key points:

  • Genes are in [0, 1] (default); the mapping to \((x, y)\) is done inside the fitness function.
  • fittest_inds_from_gen collects the best individual after every generation, useful for convergence plots.
  • verbose=.true. shows a live display of the current generation and fitness.

Multiple minima: Himmelblau function with migration

Himmelblau's function has four equal global minima at different locations in \([-5, 5]^2\):

\[f(x, y) = (x^2 + y - 11)^2 + (x + y^2 - 7)^2\]

A single population tends to converge to one minimum. Using evolve_migration with multiple populations allows simultaneous discovery of all minima.

program himmelblau_example

  use evortran__util_kinds,        only : wp
  use evortran__individuals_float, only : individual
  use evortran__migrations_float,  only : evolve_migration

  implicit none

  type(individual) :: best_ind
  type(individual), allocatable :: fittest_per_pop(:)
  integer :: i

  best_ind = evolve_migration( &
    pop_number=20,             &
    epoches=1,                 &
    pop_size=50,               &
    gene_length=2,             &
    fit_func=himmelblau,       &
    mating='sbx',              &
    elite_size=1,              &
    lower_lim=-5.0_wp,        &
    upper_lim=5.0_wp,         &
    max_generations=100,       &
    fittest_inds_final_pops=fittest_per_pop)

  write(*,*) 'Global best: f =', best_ind%get_fitness()
  write(*,*) 'At: x =', best_ind%genes(1), '  y =', best_ind%genes(2)

  write(*,*) 'Best individual per population:'
  do i = 1, 20
    write(*,'(i3,3es14.5)') i, &
      fittest_per_pop(i)%genes(1), fittest_per_pop(i)%genes(2), &
      fittest_per_pop(i)%get_fitness()
  end do

contains

  pure subroutine himmelblau(ind, f)
    class(individual), intent(in) :: ind
    real(wp), intent(out) :: f
    real(wp) :: x, y
    x = ind%genes(1)
    y = ind%genes(2)
    f = (x**2 + y - 11.0_wp)**2 + (x + y**2 - 7.0_wp)**2
  end subroutine

end program

Run the packaged version:

fpm run himmelblau --profile="release"

Key points:

  • lower_lim/upper_lim are set, so ind%genes directly holds \((x, y)\).
  • 20 populations of 50 individuals each run in parallel (OpenMP), then exchange their best individual after each epoch.
  • fittest_inds_final_pops returns the best individual from each population at the end, giving multiple candidate minima.

High-dimensional problem: Rastrigin function

The Rastrigin function is highly multi-modal with many local minima:

\[f(\mathbf{x}) = An + \sum_{i=1}^{n} \left[ x_i^2 - A \cos(2\pi x_i) \right], \quad A = 10\]

with global minimum \(f = 0\) at \(\mathbf{x} = \mathbf{0}\).

program rastrigin_example

  use evortran__util_kinds,        only : wp
  use evortran__individuals_float, only : individual
  use evortran__evolutions_float,  only : evolve_population

  implicit none

  type(individual) :: best_ind
  integer, parameter :: ndim = 10
  real(wp), parameter :: pi = 4.0_wp * atan(1.0_wp)
  real(wp), parameter :: A  = 10.0_wp

  best_ind = evolve_population(   &
    10000, ndim, rastrigin,       &
    lower_lim=-5.12_wp,          &
    upper_lim=5.12_wp,           &
    mating='blend',              &
    selection='rank',            &
    selection_size=100,          &
    elite_size=100,              &
    fitness_target=1.0e-10_wp,  &
    mutate_prob=0.1_wp,         &
    mutate_gene_prob=0.1_wp)

  write(*,*) 'Minimum: f =', best_ind%get_fitness()
  write(*,*) 'At: x =', best_ind%genes

contains

  pure subroutine rastrigin(ind, f)
    class(individual), intent(in) :: ind
    real(wp), intent(out) :: f
    integer :: i
    f = A * size(ind%genes)
    do i = 1, size(ind%genes)
      f = f + ind%genes(i)**2 - A * cos(2.0_wp * pi * ind%genes(i))
    end do
  end subroutine

end program

Run the packaged multi-dimensional scan:

fpm run rastrigin --profile="release"

Key points:

  • lower_lim/upper_lim map genes directly to \([-5.12, 5.12]\).
  • Large elite_size (100 out of 10000) and rank selection help retain good solutions in the heavily multi-modal landscape.
  • The packaged rastrigin example runs dimensions 2–20 and logs function call counts to a CSV file.