AI-ParticleSwarmOptimization

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

Revision history for Perl module Win32::MSI::HighLevel

1.000 Sat Oct 18 16:49:30 2008
    - original version; created by ExtUtils::ModuleMaker 0.49

1.002 Sun Oct 19 12:10:02 2008
    - Fix manifest

1.003 Thu Oct 23 08:20:23 2008
    - Changed module name from AI::PSO::OO to AI::ParticleSwarmOptimization
      on suggestion of PAUSE admins

1.004
    - call srand in init if -seedRand value was provided
    - added plateau early exit code contributed by Kevin Balbi

1.005
    - Remove POD test files from distribution
    - Remove bogus print left over from dev testing
    - Fix possible undef warning

1.006
    - Fix Makefile.pl / Makefile.PL issue that caused *nix installs to fail!

LICENSE  view on Meta::CPAN

Terms of Perl itself

a) the GNU General Public License as published by the Free
   Software Foundation; either version 1, or (at your option) any
   later version, or
b) the "Artistic License"

---------------------------------------------------------------------------

The General Public License (GPL)
Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
verbatim copies of this license document, but changing it is not allowed.

LICENSE  view on Meta::CPAN


The intent of this document is to state the conditions under which a Package
may be copied, such that the Copyright Holder maintains some semblance of
artistic control over the development of the package, while giving the users of the
package the right to use and distribute the Package in a more-or-less customary
fashion, plus the right to make reasonable modifications.

Definitions:

-    "Package" refers to the collection of files distributed by the Copyright
     Holder, and derivatives of that collection of files created through textual
     modification.
-    "Standard Version" refers to such a Package if it has not been modified,
     or has been modified in accordance with the wishes of the Copyright
     Holder.
-    "Copyright Holder" is whoever is named in the copyright or copyrights for
     the package.
-    "You" is you, if you're thinking about copying or distributing this Package.
-    "Reasonable copying fee" is whatever you can justify on the basis of
     media cost, duplication charges, time of people involved, and so on. (You
     will not be required to justify it to the Copyright Holder, but only to the
     computing community at large as a market that must bear the fee.)
-    "Freely Available" means that no fee is charged for the item itself, though
     there may be fees involved in handling the item. It also means that
     recipients of the item may redistribute it under the same conditions they
     received it.

1. You may make and give away verbatim copies of the source form of the
Standard Version of this Package without restriction, provided that you duplicate
all of the original copyright notices and associated disclaimers.

2. You may apply bug fixes, portability fixes and other modifications derived from
the Public Domain or from the Copyright Holder. A Package modified in such a
way shall still be considered the Standard Version.

3. You may otherwise modify your copy of this Package in any way, provided
that you insert a prominent notice in each changed file stating how and when
you changed that file, and provided that you do at least ONE of the following:

     a) place your modifications in the Public Domain or otherwise
     make them Freely Available, such as by posting said modifications
     to Usenet or an equivalent medium, or placing the modifications on
     a major archive site such as ftp.uu.net, or by allowing the
     Copyright Holder to include your modifications in the Standard
     Version of the Package.

     b) use the modified Package only within your corporation or
     organization.

     c) rename any non-standard executables so the names do not
     conflict with standard executables, which must also be provided,
     and provide a separate manual page for each non-standard
     executable that clearly documents how it differs from the Standard
     Version.

     d) make other distribution arrangements with the Copyright Holder.

4. You may distribute the programs of this Package in object code or executable
form, provided that you do at least ONE of the following:

     a) distribute a Standard Version of the executables and library
     files, together with instructions (in the manual page or equivalent)
     on where to get the Standard Version.

     b) accompany the distribution with the machine-readable source of
     the Package with your modifications.

     c) accompany any non-standard executables with their
     corresponding Standard Version executables, giving the
     non-standard executables non-standard names, and clearly
     documenting the differences in manual pages (or equivalent),
     together with instructions on where to get the Standard Version.

     d) make other distribution arrangements with the Copyright Holder.

5. You may charge a reasonable copying fee for any distribution of this Package.
You may charge any fee you choose for support of this Package. You may not
charge a fee for this Package itself. However, you may distribute this Package in
aggregate with other (possibly commercial) programs as part of a larger
(possibly commercial) software distribution provided that you do not advertise
this Package as a product of your own.

6. The scripts and library files supplied as input to or produced as output from
the programs of this Package do not automatically fall under the copyright of this

META.json  view on Meta::CPAN

{
   "abstract" : "OO Perl implementation of Particle Swarm Optimization",
   "author" : [
      "Peter Jaquiery (grandpa@cpan.org)"
   ],
   "dynamic_config" : 1,
   "generated_by" : "ExtUtils::MakeMaker version 6.58, CPAN::Meta::Converter version 2.110930001",
   "license" : [
      "perl_5"
   ],
   "meta-spec" : {
      "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
      "version" : "2"
   },
   "name" : "AI-ParticleSwarmOptimization",
   "no_index" : {
      "directory" : [
         "t",
         "inc"
      ]
   },
   "prereqs" : {
      "build" : {
         "requires" : {
            "ExtUtils::MakeMaker" : 0
         }
      },
      "configure" : {
         "requires" : {
            "ExtUtils::MakeMaker" : 0
         }
      },
      "runtime" : {
         "requires" : {
            "Math::Random::MT" : 0
         }
      }
   },
   "release_status" : "stable",
   "version" : "1.006"
}

META.yml  view on Meta::CPAN

---
abstract: 'OO Perl implementation of Particle Swarm Optimization'
author:
  - 'Peter Jaquiery (grandpa@cpan.org)'
build_requires:
  ExtUtils::MakeMaker: 0
configure_requires:
  ExtUtils::MakeMaker: 0
dynamic_config: 1
generated_by: 'ExtUtils::MakeMaker version 6.58, CPAN::Meta::Converter version 2.110930001'
license: perl
meta-spec:
  url: http://module-build.sourceforge.net/META-spec-v1.4.html
  version: 1.4
name: AI-ParticleSwarmOptimization
no_index:
  directory:
    - t
    - inc
requires:
  Math::Random::MT: 0
version: 1.006

Makefile.PL  view on Meta::CPAN

#!/usr/bin/perl
use ExtUtils::MakeMaker;

# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    dist         => {TARFLAGS => '--create --verbose --mode=0700 --file '},
    NAME         => 'AI::ParticleSwarmOptimization',
    LICENSE      => 'perl',
    VERSION_FROM => 'lib/AI/ParticleSwarmOptimization.pm', # finds \$VERSION
    AUTHOR       => 'Peter Jaquiery (grandpa@cpan.org)',
    ABSTRACT     => 'OO Perl implementation of Particle Swarm Optimization',
    PREREQ_PM    => {
        'Math::Random::MT' => '0',
    },
);

Samples/PSOPlatTest.pl  view on Meta::CPAN

#!/usr/bin/perl
use strict;
use warnings;
use lib '..\lib';    # For development testing
use AI::ParticleSwarmOptimization;
use Math::Random::MT qw();

++$|;
my $pso = AI::ParticleSwarmOptimization->new (
    -fitFunc           => \&calcFit,
    -dimensions        => 3,
    -iterations        => 500,
    -exitPlateau       => 1,
    -exitPlateauDP     => 3,
    -exitPlateauBurnin => 100,
    -exitPlateauWindow => 60,
);

$pso->init ();

my $fitValue = $pso->optimize ();
my ($best) = $pso->getBestParticles (1);
my ($fit, @values) = $pso->getParticleBestPos ($best);
my $iters = $pso->getIterationCount ();
print $pso->getSeed();

printf ",# Fit %.5f at (%s) after %d iterations\n",
    $fit, join (', ', map {sprintf '%.4f', $_} @values), $iters;


sub calcFit {
    my @values = @_;
    my $offset = int (-@values / 2);
    my $sum;

    $sum += ($_ - $offset++)**2 for @values;
    return $sum;
}

Samples/PSOTest.pl  view on Meta::CPAN

#!/usr/bin/perl
use strict;
use warnings;
use lib '..\lib'; # For development testing
use AI::ParticleSwarmOptimization;

++$|;
my $pso = AI::ParticleSwarmOptimization->new ();

$pso->setParams (
    -fitFunc    => \&calcFit,
    -dimensions => 3,
    -iterations => 100,
    );

for (0 .. 9) {
    $pso->init () unless $_ % 5;

    my $fitValue      = $pso->optimize ();
    my ($best)        = $pso->getBestParticles (1);
    my ($fit, @values) = $pso->getParticleBestPos ($best);
    my $iters = $pso->getIterationCount();

    printf "Fit %.4f at (%s) after %d iterations\n",
        $fit, join (', ', map {sprintf '%.4f', $_} @values), $iters;
}


sub calcFit {
    my @values = @_;
    my $offset = int (-@values / 2);
    my $sum;

    $sum += ($_ - $offset++) ** 2 for @values;
    return $sum;
}

lib/AI/ParticleSwarmOptimization.pm  view on Meta::CPAN

our @EXPORT  = qw();
$AI::ParticleSwarmOptimization::VERSION = '1.006';

use constant kLogBetter     => 1;
use constant kLogStall      => 2;
use constant kLogIter       => 4;
use constant kLogDetail     => 8;
use constant kLogIterDetail => (kLogIter | kLogDetail);

sub new {
    my ($class, %params) = @_;
    my $self = bless {}, $class;

    $self->setParams (%params);
    return $self;
}


sub setParams {
    my ($self, %params) = @_;

    if (defined $params{-fitFunc}) {
        # Process required parameters - -fitFunc and -dimensions
        if ('ARRAY' eq ref $params{-fitFunc}) {
            ($self->{fitFunc}, @{$self->{fitParams}}) = @{$params{-fitFunc}};
        } else {
            $self->{fitFunc} = $params{-fitFunc};
        }

        $self->{fitParams} ||= [];
    }

    $self->{prtcls} = []    # Need to reinit if num dimensions changed
        if defined $params{-dimensions}
            and defined $self->{dimensions}
            and $params{-dimensions} != $self->{dimensions};

    $self->{$_} = $params{"-$_"} for grep {exists $params{"-$_"}} qw/
        dimensions
        exitFit
        exitPlateau
        exitPlateauDP
        exitPlateauWindow
        exitPlateauBurnin
        inertia
        iterations
        meWeight
        numNeighbors
        numParticles
        posMax
        posMin
        randSeed
        randStartVelocity
        stallSpeed
        themWeight
        verbose
        /;

    die "-dimensions must be greater than 0\n"
        if exists $params{-dimensions} && $params{-dimensions} <= 0;

    if (defined $self->{verbose} and 'ARRAY' eq ref $self->{verbose}) {
        my @log = map {lc} @{$self->{verbose}};
        my %logTypes = (
            better => kLogBetter,
            stall  => kLogStall,
            iter   => kLogIter,
            detail => kLogDetail,
        );

        $self->{verbose} = 0;
        exists $logTypes{$_} and $self->{verbose} |= $logTypes{$_} for @log;
    }

    $self->{numParticles} ||= $self->{dimensions} * 10
        if defined $self->{dimensions};
    $self->{numNeighbors} ||= int sqrt $self->{numParticles}
        if defined $self->{numParticles};
    $self->{iterations}        ||= 1000;
    $self->{exitPlateauDP}     ||= 10;
    $self->{exitPlateauWindow} ||= $self->{iterations} * 0.1;
    $self->{exitPlateauBurnin} ||= $self->{iterations} * 0.5;
    $self->{posMax} = 100 unless defined $self->{posMax};
    $self->{posMin} = -$self->{posMax} unless defined $self->{posMin};
    $self->{meWeight}   ||= 0.5;
    $self->{themWeight} ||= 0.5;
    $self->{inertia}    ||= 0.9;
    $self->{verbose}    ||= 0;

    return 1;
}


sub init {
    my ($self) = @_;

    die "-fitFunc must be set before init or optimize is called"
        unless $self->{fitFunc} and 'CODE' eq ref $self->{fitFunc};
    die
        "-dimensions must be set to 1 or greater before init or optimize is called"
        unless $self->{dimensions} and $self->{dimensions} >= 1;

    my $seed =
        int (exists $self->{randSeed} ? $self->{randSeed} : rand (0xffffffff));

    $self->{rndGen} = Math::Random::MT->new ($seed);
    $self->{usedRandSeed} = $seed;

    $self->{prtcls}         = [];
    $self->{bestBest}       = undef;
    $self->{bestBestByIter} = undef;
    $self->{bestsMean}      = 0;
    $self->_initParticles ();
    $self->{iterCount} = 0;

    # Normalise weights.
    my $totalWeight =
        $self->{inertia} + $self->{themWeight} + $self->{meWeight};

    $self->{inertia}    /= $totalWeight;
    $self->{meWeight}   /= $totalWeight;
    $self->{themWeight} /= $totalWeight;

    die "-posMax must be greater than -posMin"
        unless $self->{posMax} > $self->{posMin};
    $self->{$_} > 0 or die "-$_ must be greater then 0" for qw/numParticles/;

    $self->{deltaMax} = ($self->{posMax} - $self->{posMin}) / 100.0;

    return 1;
}


sub optimize {
    my ($self, $iterations) = @_;

    $iterations ||= $self->{iterations};
    $self->init () unless $self->{prtcls};
    return $self->_swarm ($iterations);
}


sub getBestParticles {
    my ($self, $num) = @_;
    my @bests  = 0 .. $self->{numParticles} - 1;
    my $prtcls = $self->{prtcls};

    @bests = sort {$prtcls->[$a]{bestFit} <=> $prtcls->[$b]{bestFit}} @bests;
    $num ||= 1;
    return @bests[0 .. $num - 1];
}


sub getParticleBestPos {
    my ($self, $prtcl) = @_;

    return undef if $prtcl >= $self->{numParticles};
    $prtcl = $self->{prtcls}[$prtcl];

    return ($prtcl->{bestFit}, @{$prtcl->{bestPos}});
}


sub getIterationCount {
    my ($self) = @_;

    return $self->{iterCount};
}


sub getSeed {
    my ($self) = @_;

    return $self->{usedRandSeed};
}


sub _initParticles {
    my ($self) = @_;

    for my $id (0 .. $self->{numParticles} - 1) {
        $self->{prtcls}[$id]{id} = $id;
        $self->_initParticle ($self->{prtcls}[$id]);
    }
}


sub _initParticle {
    my ($self, $prtcl) = @_;

    # each particle is a hash of arrays with the array sizes being the
    # dimensionality of the problem space
    for my $d (0 .. $self->{dimensions} - 1) {
        $prtcl->{currPos}[$d] =
            $self->_randInRange ($self->{posMin}, $self->{posMax});

        $prtcl->{velocity}[$d] =
              $self->{randStartVelocity}
            ? $self->_randInRange (-$self->{deltaMax}, $self->{deltaMax})
            : 0;
    }

    $prtcl->{currFit} = $self->_calcPosFit ($prtcl->{currPos});
    $self->_calcNextPos ($prtcl);

    unless (defined $prtcl->{bestFit}) {
        $prtcl->{bestPos}[$_] =
            $self->_randInRange ($self->{posMin}, $self->{posMax})
            for 0 .. $self->{dimensions} - 1;
        $prtcl->{bestFit} = $self->_calcPosFit ($prtcl->{bestPos});
    }
}


sub _calcPosFit {
    my ($self, $pos) = @_;

    return $self->{fitFunc}->(@{$self->{fitParams}}, @$pos);
}


sub _swarm {
    my ($self, $iterations) = @_;

    for my $iter (1 .. $iterations) {
        ++$self->{iterCount};
        last if defined $self->_moveParticles ($iter);

        $self->_updateVelocities ($iter);
        next if !$self->{exitPlateau} || !defined $self->{bestBest};

        if ($iter >= $self->{exitPlateauBurnin} - $self->{exitPlateauWindow}) {
            my $i = $iter % $self->{exitPlateauWindow};

            $self->{bestsMean} -= $self->{bestBestByIter}[$i]
                if defined $self->{bestBestByIter}[$i];
            $self->{bestsMean} += $self->{bestBestByIter}[$i] =
                $self->{bestBest} / $self->{exitPlateauWindow};
        }

        next if $iter <= $self->{exitPlateauBurnin};

        #Round to the specified number of d.p.
        my $format  = "%.$self->{exitPlateauDP}f";
        my $mean    = sprintf $format, $self->{bestsMean};
        my $current = sprintf $format, $self->{bestBest};

        #Check if there is a sufficient plateau - stopping iterations if so
        last if $mean == $current;
    }

    return $self->{bestBest};
}


sub _moveParticles {
    my ($self, $iter) = @_;

    print "Iter $iter\n" if $self->{verbose} & kLogIter;

    for my $prtcl (@{$self->{prtcls}}) {
        @{$prtcl->{currPos}} = @{$prtcl->{nextPos}};
        $prtcl->{currFit} = $prtcl->{nextFit};

        my $fit = $prtcl->{currFit};

        if ($self->_betterFit ($fit, $prtcl->{bestFit})) {
            # Save position - best fit for this particle so far
            $self->_saveBest ($prtcl, $fit, $iter);
        }

        return $fit if defined $self->{exitFit} and $fit < $self->{exitFit};
        next if !($self->{verbose} & kLogIterDetail);

        printf "Part %3d fit %8.2f", $prtcl->{id}, $fit
            if $self->{verbose} >= 2;
        printf " (%s @ %s)",
            join (', ', map {sprintf '%5.3f', $_} @{$prtcl->{velocity}}),
            join (', ', map {sprintf '%5.2f', $_} @{$prtcl->{currPos}})
            if $self->{verbose} & kLogDetail;
        print "\n";
    }

    return undef;
}


sub _saveBest {
    my ($self, $prtcl, $fit, $iter) = @_;

    # for each dimension, set the best position as the current position
    @{$prtcl->{bestPos}} = @{$prtcl->{currPos}};

    $prtcl->{bestFit} = $fit;
    return if !$self->_betterFit ($fit, $self->{bestBest});

    if ($self->{verbose} & kLogBetter) {
        my $velSq;

        $velSq += $_**2 for @{$prtcl->{velocity}};
        printf "#%05d: Particle $prtcl->{id} best: %.4f (vel: %.3f)\n",
            $iter, $fit, sqrt ($velSq);
    }

    $self->{bestBest} = $fit;
}


sub _betterFit {
    my ($self, $new, $old) = @_;

    return !defined ($old) || ($new < $old);
}


sub _updateVelocities {
    my ($self, $iter) = @_;

    for my $prtcl (@{$self->{prtcls}}) {
        my $bestN = $self->{prtcls}[$self->_getBestNeighbour ($prtcl)];
        my $velSq;

        for my $d (0 .. $self->{dimensions} - 1) {
            my $meFactor =
                $self->_randInRange (-$self->{meWeight}, $self->{meWeight});
            my $themFactor =
                $self->_randInRange (-$self->{themWeight}, $self->{themWeight});
            my $meDelta   = $prtcl->{bestPos}[$d] - $prtcl->{currPos}[$d];
            my $themDelta = $bestN->{bestPos}[$d] - $prtcl->{currPos}[$d];

            $prtcl->{velocity}[$d] =
                $prtcl->{velocity}[$d] * $self->{inertia} +
                $meFactor * $meDelta +
                $themFactor * $themDelta;
            $velSq += $prtcl->{velocity}[$d]**2;
        }

        my $vel = sqrt ($velSq);
        if (!$vel or $self->{stallSpeed} and $vel <= $self->{stallSpeed}) {
            $self->_initParticle ($prtcl);
            printf "#%05d: Particle $prtcl->{id} stalled (%6f)\n", $iter, $vel
                if $self->{verbose} & kLogStall;
        }

        $self->_calcNextPos ($prtcl);
    }
}


sub _calcNextPos {
    my ($self, $prtcl) = @_;

    for my $d (0 .. $self->{dimensions} - 1) {
        $prtcl->{nextPos}[$d] = $prtcl->{currPos}[$d] + $prtcl->{velocity}[$d];
        if ($prtcl->{nextPos}[$d] < $self->{posMin}) {
            $prtcl->{nextPos}[$d]  = $self->{posMin};
            $prtcl->{velocity}[$d] = 0;
        } elsif ($prtcl->{nextPos}[$d] > $self->{posMax}) {
            $prtcl->{nextPos}[$d]  = $self->{posMax};
            $prtcl->{velocity}[$d] = 0;
        }
    }

    $prtcl->{nextFit} = $self->_calcPosFit ($prtcl->{nextPos});
}


sub _randInRange {
    my ($self, $min, $max) = @_;
    return $min + $self->{rndGen}->rand ($max - $min);
}


sub _getBestNeighbour {
    my ($self, $prtcl) = @_;
    my $bestNFitness;
    my $bestNIndex;

    for my $neighbor (0 .. $self->{numNeighbors} - 1) {
        my $prtclNIndex = ($prtcl + $neighbor) % $self->{numParticles};

        if (!defined ($bestNFitness)
            || $self->{prtcls}[$prtclNIndex]{bestFit} < $bestNFitness)
        {
            $bestNFitness = $self->{prtcls}[$prtclNIndex]{bestFit};
            $bestNIndex   = $prtclNIndex;
        }
    }

    return $bestNIndex;
}


1;


=head1 NAME

AI::ParticleSwarmOptimization - Particle Swarm Optimization (object oriented)

=head1 SYNOPSIS

    use AI::ParticleSwarmOptimization;

    my $pso = AI::ParticleSwarmOptimization->new (
        fitFunc    => \&calcFit,
        dimensions => 3,
        );
    my $fitValue       = $pso->optimize ();
    my ($best)         = $pso->getBestParticles (1);
    my ($fit, @values) = $pso->getParticleBestPos ($best);

    printf "Fit %.4f at (%s)\n",
        $fit, join ', ', map {sprintf '%.4f', $_} @values;


    sub calcFit {
        my @values = @_;
        my $offset = int (-@values / 2);
        my $sum;

        $sum += ($_ - $offset++) ** 2 for @values;
        return $sum;
    }

=head1 Description

The Particle Swarm Optimization technique uses communication of the current best
position found between a number of particles moving over a hyper surface as a
technique for locating the best location on the surface (where 'best' is the
minimum of some fitness function). For a Wikipedia discussion of PSO see
http://en.wikipedia.org/wiki/Particle_swarm_optimization.

This pure Perl module is an implementation of the Particle Swarm Optimization

lib/AI/ParticleSwarmOptimization.pm  view on Meta::CPAN

fitness value becomes equal or less than I<-exitFit>.

=item I<-fitFunc>: required

I<-fitFunc> is a reference to the fitness function used by the search. If extra
parameters need to be passed to the fitness function an array ref may be used
with the code ref as the first array element and parameters to be passed into
the fitness function as following elements. User provided parameters are passed
as the first parameters to the fitness function when it is called:

    my $pso = AI::ParticleSwarmOptimization->new (
        fitFunc    => [\&calcFit, $context],
        dimensions => 3,
        );

    ...

    sub calcFit {
        my ($context, @values) = @_;
        ...
        return $fitness;
        }

In addition to any user provided parameters the list of values representing the
current particle position in the hyperspace is passed in. There is one value per
hyperspace dimension.

=item I<-inertia>: positive or zero number, optional

Determines what proportion of the previous velocity is carried forward to the
next iteration. Defaults to 0.9

lib/AI/ParticleSwarmOptimization.pm  view on Meta::CPAN

Takes an optional count.

Returns a list containing the best $n particle numbers. If $n is not specified
only the best particle number is returned.

=item B<getParticleBestPos ($particleNum)>

Returns a list containing the best value of the fit and the vector of its point
in hyper space.

    my ($fit, @vector) = $pso->getParticleBestPos (3)

=item B<getIterationCount ()>

Return the number of iterations performed. This may be useful when the
I<-exitFit> criteria has been met or where multiple calls to I<optimize> have
been made.

=back

=head1 BUGS

lib/AI/ParticleSwarmOptimization.pm  view on Meta::CPAN

http://en.wikipedia.org/wiki/Particle_swarm_optimization

=head1 ACKNOWLEDGEMENTS

This module is an evolution of the AI::PSO module created by Kyle Schlansker.

Plateau management code added in version 1.004 contributed by Kevin Balbi.

=head1 AUTHOR

    Peter Jaquiery
    CPAN ID: GRANDPA
    grandpa@cpan.org

=head1 COPYRIGHT AND LICENSE

This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

The full text of the license can be found in the LICENSE file included with this
module.

=cut

t/01_pso_oo.t  view on Meta::CPAN

Test AI::ParticleSwarmOptimization

=cut

plan (tests => 27);

ok (my $pso = AI::ParticleSwarmOptimization->new (), 'Constructor');
mustDie ('$pso->setParams (-fitFunc => 1)', 'Bad -fitFunc');
ok ($pso->setParams (-fitFunc => \&fitFunc,), 'Good -fitFunc (setParams)');
ok ($pso = AI::ParticleSwarmOptimization->new (-fitFunc => \&fitFunc,),
    'Good -fitFunc (new)');
ok ($pso->setParams (-fitFunc => [\&fitFunc, 1]), 'Good -fitFunc (array)');

mustDie ('$pso->setParams (-dimensions => 0)',  '-dimensions 0');
mustDie ('$pso->setParams (-dimensions => -1)', '-dimensions -1');
ok ($pso->setParams (-dimensions => 1), '-dimensions good');

for my $param (qw/numParticles/) {
    mustDie ("$pso->setParams (-$param => 0); $pso->init ()",  "-$param zero");
    mustDie ("$pso->setParams (-$param => -1); $pso->init ()", "-$param neg");
    ok (($pso->setParams (-$param => 1), $pso->init ()), "-$param good");
}

for my $param (
    qw/inertia iterations meWeight numNeighbors stallSpeed themWeight/)
{
    mustDie ("$pso->setParams (-$param => -1); $pso->init ()", "-$param neg");
    ok (($pso->setParams (-$param => 1), $pso->init ()), "-$param good");
}

mustDie (
    '$pso->setParams (-posMax => 0); $pso->setParams (-posMin => 0); $pso->init ()',
    '-posMax == -posMin'
);
mustDie (
    '$pso->setParams (-posMax => -1); $pso->setParams (-posMin => 0); $pso->init ()',
    '-posMax < -posMin'
);
ok (
    '$pso->setParams (-posMax => -1); $pso->setParams (-posMin => -2); $pso->init ()',
    '-posMax > -posMin'
   );

# Calculation tests.
$pso = AI::ParticleSwarmOptimization->new (
    -randSeed          => 2626813951,# Fit 0.00006 at (-1.0051, -0.0005, 1.0058) after 258 iterations
    -fitFunc           => \&calcFit,
    -dimensions        => 3,
    -iterations        => 500,
    -exitPlateau       => 1,
    -exitPlateauDP     => 3,
    -exitPlateauBurnin => 100,
    -exitPlateauWindow => 60,
);
my $fitValue = $pso->optimize ();
my $iters = $pso->getIterationCount ();

ok ($iters > 100 && $iters < 350, 'Low plateau ok');

sub fitFunc {
}


sub calcFit {
    my @values = @_;
    my $offset = int (-@values / 2);
    my $sum;

    $sum += ($_ - $offset++)**2 for @values;
    return $sum;
}


sub mustDie {
    my ($test, $name) = @_;

    eval $test;
    ok (defined $@, $name);
}



( run in 0.279 second using v1.01-cache-2.11-cpan-4d50c553e7e )