#!/usr/bin/perl

use warnings;
use strict;
use lib "..";

#Incluimos las librerías de OPEAL que nso van a hacer falta
use EasyAlgorithm;
use DeltaTerm;
use EZFullAlgo;
use BinaryIndi;
use GenerationalTerm;

my $numBits = shift || 64; #Precición en el problema de la marea (Número de bits de la solución)
my $popSize = shift || 100; #Número de individuos que consituyen una población
my $numGens = shift || undef ; #Si no se le pasa un tercer parámetro, utilizamos un terminador delta, que en este caso, terminaremos  cuando encontremos el óptimo


#----------------------------------------------------------#
#Definimos la función de fitnes, que es la función Marea
my $rr = sub {
  my $chrom = shift;
  my $str = $chrom->Chrom();
  my $fitness = 1;

  #extraemos los dos números binarios
  my $l2=length($str)/2;
  my $x=eval("0b".substr ($str, 0, $l2));
  my $y=eval("0b".substr ($str,    $l2));

  #Los normalizamos y los pasamos al rango [0,1]
  my $max=(2 ** ($l2) )-1;
  $x=$x/$max;
  $y=$y/$max;

  #Calculamos la función marea
  my $sqrt=sqrt( ($x*$x) + ($y*$y) );
  $fitness= sin( $sqrt ) / $sqrt  if ($sqrt !=0 );

  #Y devolvemos el fitness calculado
  return $fitness;

};
#----------------------------------------------------------#

#Definimos los operadores de variación
my $m = new MutationOne; #Cambia a un sólo bit
my $c = new Crossover; #Cruce en 2 puntos clásico

#Usamos estos operadores para definir una generación del algoritmo. Lo cual
# no es realmente necesario ya que este algoritmo define ambos operadores por
# defecto. Los parámetros son la función de fitness, la tasa de selección y los
# operadores de variación.
my $generation = new EasyAlgorithm( $rr, 0.2, [$m, $c] );

#Ya sólo nos queda definir el algoritmo a partir de la generación que hemos creado y elegir
# un terminador
my $term;
if ( $numGens ) {
  #Si se indica un número determinado de generaciones, utilizamos un terminador generacional
  $term = new GenerationalTerm  $numGens ;
} else {
  #Si no se indica un número determinado de generaciones, utilizamos un terminador Delta, donde el fitness
  # a alcanzar es uno y la variacion (delta) es 0.
  $term = new DeltaTerm  1, 0 ;
}

#Definimos el algoritmo
my $fullAlgo = new EZFullAlgo  $generation, $term, 1 ;

#Definimos la población inicial
my @pop;
for ( 0..$popSize ) {
  my $indi = new BinaryIndi $numBits ; #Creamos individuos aleatorios
  push( @pop, $indi ); #Los añadimos a la población
}

#Ejecutamos el algoritmo
$fullAlgo->apply( \@pop );

#leemos el mejor resultado
my $str = $pop[0]->Chrom();

#extraemos los dos números binarios
my $l2=length($str)/2;
my $x=eval("0b".substr ($str, 0, $l2));
my $y=eval("0b".substr ($str,    $l2));

#Cogemos las cadenas binarias para mostrarlas
my $sub1=substr ($str, 0, $l2);
my $sub2=substr ($str,    $l2);

#Los normalizamos y los pasamos al rango [0,1]
my $max=(2 ** ($l2) )-1;
$x=$x/$max;
$y=$y/$max;

#Mostramos los resultados obtenidos
print "El mejor es:\n\t ",$pop[9]->asString(),"\n\t x=", $sub2, " ($x)\n\t y=" ,$sub1, " ($y) \n\t Fitness: ",$pop[0]->Fitness(),"\n";
