Read 227 times | Created 2014-07-18 07:18:48 | Updated 2014-07-18 07:18:48 | | |

 

<?php
//=================================
class Particle
{
    /**
     * @var WeightVector Position
     */
    protected $x;

    /**
     * @var WeightVector Velocity
     */
    protected $v;
    /**
     * @var WeightVector Personal best
     */
    protected $p;
    /**
     * @var number Personal best value
     */
    protected $p_val = -1000000;

    /**
     * @var callable Fitness function - takes WeightVector as a parameter (eg current position)
     */
    protected $f;
    protected $fitness;
    protected $defaults = array(
        'phi1' => 1,
        'phi2' => 1,
        'inertia' => 1,
        'vMax' => null,
    );

    /**
     * @var Swarm
     */
    public $swarm;

    public function __construct(array $constants, WeightVector $x, WeightVector $v, Swarm $swarm, callable $f)
    {
        $constants = array_merge($this->defaults, $constants);
        $this->phi1 = $constants['phi1'];
        $this->phi2 = $constants['phi2'];
        $this->vMax = $constants['vMax'];
        $this->inertia = $constants['inertia'];
       $this->x = $x;
        $this->fitness = $f($x);
        $this->v = $v;
        $this->swarm = $swarm;
        $this->f = $f;
        $this->p = $x;
        $this->p_val = $f($x);
    }

    public function fitness()
    {
        return $this->fitness;
    }

    /**
     * Get/set best solution
     *
     * @param WeightVector $v
     * @return WeightVector
     */
    public function best(WeightVector $v = null)
    {
        if ($v) {
            $fitness = $this->f;
            $this->p = $v;
            $this->p_val = $fitness($v);
        }
        return $this->p;
    }

    public function bestVal()
    {
        return $this->p_val;
    }

    /**
     * Get/set current position vector
     *
     * @param WeightVector $x
     * @return WeightVector
     */
    public function position(WeightVector $x = null)
    {
        if ($x) {
            $fitness = $this->f;
            $this->x = $x;
            $this->fitness = $fitness($x);
        }
        return $this->x;
    }

    public function go()
    {
        if ($this->fitness() > $this->p_val) {
            $this->best($this->x);
        }

        if ($this->fitness() > $this->swarm->bestVal()) {
            $this->swarm->best($this);
        }

        $phi1 = $this->phi1 * lcg_value();
        $phi2 = $this->phi2 * lcg_value();
        $pg = $this->swarm->best()->position();

        // v 0 i = v i + j 1 _ (p i - x i ) + j 2 _ (p g - x i )
        $this->v = $this->v->scalarMultiply($this->inertia)
            ->add(
                $this->p->subtract($this->x)->scalarMultiply($phi1)
            )
            ->add(
                $pg->subtract($this->x)->scalarMultiply($phi2)
            )->constrain(-$this->vMax, $this->vMax);

        // x i = x i + v i
        $this->position($this->x->add($this->v));
    }

    public function __toString()
    {
        return $this->x . " f(x) -> " . $this->fitness();
    }
}
//=================================
class Swarm
{
    public $phi_1;
    public $phi_2;
    protected $vMax = 0.5;
    /**
     * @var WeightVector Global best
     */
    protected $g;
    /**
     * @var number Global best value
     */
    protected $g_val = -10000000;
    /**
     * @var callable Fitness function
     */
    protected $f;

    /**
     * @var Particle[]
     */
    public $particles = array();

    protected $constants;

    public function __construct(array $constants, $numParticles, $n, callable $fitnessFunc)
    {
        $this->f = $fitnessFunc;
        for ($i = 0; $i < $numParticles; $i++) {
            $particle = $this->createParticle($constants, $n, $fitnessFunc);
            $this->particles[] = $particle;
            if ($particle->fitness() > $this->bestVal()) {
                $this->best($particle);
            }
        }
    }

    protected function createParticle(array $constants, $n, callable $fitnessFunc)
    {
        return new Particle($constants, $this->createVector($n, -5, 15), $this->createVector($n, -1, 1), $this, $fitnessFunc);
    }

    protected function createVector($n, $min, $max)
    {
        return new WeightVector(
            array_map(
                function () use ($min, $max) {
                    return ($max - $min) * lcg_value() + $min;
                },
                array_fill(0, $n, 0)
            )
        );
    }

    public function go()
    {
        foreach ($this->particles as $particle) {
            $particle->go();
        }
    }

    /**
     * Get or set the particle with the global best for the swarm
     *
     * @param Particle $p Supply parameter to update
     * @return Particle
     */
    public function best(Particle $p = null)
    {
        if ($p) {
            $this->g = $p;
            $this->g_val = $p->bestVal();
        }
        return $this->g;
    }

    /**
     * @return number The fitness of the global best
     */
    public function bestVal()
    {
        return $this->g_val;
    }

    public function __toString()
    {
        return array_reduce(
            $this->particles,
            function ($aggr, $p) {
                return $aggr . "$p" . ($p === $this->best() ? ' <- global best' : '') . "n";
            }
        );
    }
}
//=================================
class WeightVector
{
    public $weights = array();
    public $n;
    public function __construct(array $weights)
    {
        $this->weights = array_values($weights);
        $this->n = count($this->weights);
    }
    /**
     * @param WeightVector $that
     * @return WeightVector
     * @throws Exception
     */
    public function add(WeightVector $that)
    {
        if ($this->n !== $that->n) {
            throw new Exception("Vector lengths don't match");
        }
        return new self(
            array_map(
                function ($a, $b) {
                    return $a + $b;
                },
                $this->weights,
                $that->weights
            )
        );
    }
    /**
     * @return WeightVector
     * @throws Exception
     */
    public function negate()
    {
        return new self(
            array_map(
                function ($a) {
                    return -$a;
                },
                $this->weights
            )
        );
    }

    public function scalarMultiply($val)
    {
        return new self(
            array_map(
                function ($a) use ($val) {
                    return $val * $a;
                },
                $this->weights
            )
        );
    }

    /**
     * @param WeightVector $that
     * @return WeightVector
     * @throws Exception
     */
    public function subtract(WeightVector $that)
    {
        return $this->add($that->negate());
    }

    /**
     * Constrain absolute values in each dimension to <= given value
     *
     * @param float $minVal A minimum value
     * @param float $maxVal A maximum value
     * @return WeightVector
     */
    public function constrain($minVal, $maxVal)
    {
        return new self(
            array_map(
                function ($x) use ($minVal, $maxVal) {
                    return $x < $minVal ? $minVal :
                        ($x > $maxVal ? $maxVal : $x);
                },
                $this->weights
            )
        );
    }

    public function __toString()
    {
        return json_encode($this->weights);
    }
}

//====================================
$constants = array(
    'phi1' => 0.15,
    'phi2' => 0.15,
    'vMax' => 0.5,
    'inertia' => 0.97,
);
$fitnessFunc = function (WeightVector $x) {
    // insert your fitness function here
    // too lazy to think of a good example now!
    // high values are good, low values are bad
    return 0;
};
$swarm = new Swarm($constants, 10, 2, $fitnessFunc);
$iterations=100;
for ($i = 0; $i < $iterations; $i++) {
    $swarm->go();
}

echo "$swarm";