forked from: forked from: 人工知能(遺伝的アルゴリズム GA)で巡回セールスマン問題を解く

by tepe forked from forked from: 人工知能(遺伝的アルゴリズム GA)で巡回セールスマン問題を解く (diff: 111)
人工知能(遺伝的アルゴリズム GA)で巡回セールスマン問題を解く
...
@author sabotenbrother
♥0 | Line 416 | Modified 2013-10-09 16:09:01 | MIT License
play

ActionScript3 source code

/**
 * Copyright tepe ( http://wonderfl.net/user/tepe )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/34RK
 */

// forked from tepe's forked from: 人工知能(遺伝的アルゴリズム GA)で巡回セールスマン問題を解く
// forked from postnum's 人工知能(遺伝的アルゴリズム GA)で巡回セールスマン問題を解く
package  
{
    import flash.display.Sprite;
    import flash.events.Event;
    
    import com.bit101.components.PushButton;
    import com.bit101.components.HUISlider;
    import com.bit101.components.VUISlider;
    import com.bit101.components.Label;
    import com.bit101.components.Text;
    
    [SWF(width = '465', height = '465', backgroundColor = '#FFFFFF', frameRate = '120')]
    
    /**
     * 人工知能(遺伝的アルゴリズム GA)で巡回セールスマン問題を解く
     * ...
     * @author sabotenbrother
     */
    public class World extends Sprite
    {
        private var node:Node;
        private var salesperson:Salesperson;
        
        private var nodeNum:int = 20;
        private var salespersonNum:int = 60;
        
        private var startFlag:Boolean = false;
        
        // GUIパーツ
        private var startButton:PushButton;
        private var stopButton:PushButton;
        private var clearButton:PushButton;
        private var randomButton:PushButton;
        private var circleButton:PushButton;
        private var setValueButton:PushButton;
        
        private var nodeLabel:Label;
        private var salespersonLabel:Label;
        
        private var nodeHUISlider:HUISlider;
        private var salespersonHUISlider:HUISlider;
        private var mutationProbHUISlider:HUISlider;
        private var mutationVolumeHUISlider:HUISlider;
        private var aliveVolumeHUISlider:HUISlider;
        
        private var totalLengthText:Text;
        private var generationLengthText:Text;
        
        public function World():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(event:Event = null):void 
        {
            createWorld();
        }
        
        /**
         * 世界を創造する
         */
        public function createWorld():void
        {
            // 巡回セールスマン(遺伝子作成)
            salesperson = new Salesperson(nodeNum, salespersonNum);
            
            // GUI作成
            startButton = new PushButton(this, 0, 0, "start", onStart);
            startButton.width = 60;
            
            stopButton = new PushButton(this, 60, 0, "stop", onStop);
            stopButton.width = 60;
            
            clearButton = new PushButton(this, 120, 0, "clear", onClear);
            clearButton.width = 60;
            
            randomButton = new PushButton(this, 190, 0, "random", random);
            randomButton.width = 60;
            circleButton = new PushButton(this, 250, 0, "circle", circlePattern);
            circleButton.width = 60;

            setValueButton = new PushButton(this, 320, 0, "setValue", setValue);
            setValueButton.width = 60;

            nodeLabel = new Label(this, 10, 40, "node");
            salespersonLabel = new Label(this, 10, 60, "saleperson");
            
            nodeHUISlider = new HUISlider(this, 60, 40, "");
            nodeHUISlider.width = 150;
            nodeHUISlider.value = nodeNum;
            nodeHUISlider.minimum = 10;
            nodeHUISlider.maximum = 30;
            nodeHUISlider.tick = 1;
            
            salespersonHUISlider = new HUISlider(this, 60, 60, "");
            salespersonHUISlider.width = 150;
            salespersonHUISlider.value = salespersonNum;
            salespersonHUISlider.minimum = 10;
            salespersonHUISlider.maximum = 180;
            salespersonHUISlider.tick = 1;

            mutationProbHUISlider = new HUISlider(this, 290, 40, "");
            mutationProbHUISlider.width = 150;
            mutationProbHUISlider.value = salesperson.mutationProb;
            mutationProbHUISlider.minimum = 0;
            mutationProbHUISlider.maximum = 1.00;
            mutationProbHUISlider.tick = 0.1;
            
            mutationVolumeHUISlider = new HUISlider(this, 290, 60, "");
            mutationVolumeHUISlider.width = 150;
            mutationVolumeHUISlider.value = salesperson.mutationVolume;
            mutationVolumeHUISlider.minimum = 0;
            mutationVolumeHUISlider.maximum = 1.00;
            mutationVolumeHUISlider.tick = 0.1;

            aliveVolumeHUISlider = new HUISlider(this, 290, 80, "");
            aliveVolumeHUISlider.width = 150;
            aliveVolumeHUISlider.value = salesperson.aliveVolume;
            aliveVolumeHUISlider.minimum = 0;
            aliveVolumeHUISlider.maximum = 1.00;
            aliveVolumeHUISlider.tick = 0.1;

            var muttionProbLabel:Label = new Label(this, 210, 40, "mutationProb");
            var mutationVolumeLabel:Label = new Label(this, 210, 60, "mutationVolume");
            var aliveVolumeLabel:Label = new Label(this, 210, 80, "aliveVolume");

            var generationLabel:Label = new Label(this, 10, 420, "Generation");
            var totalLengthLabel:Label = new Label(this, 10, 440, "TotalLength");
            
            generationLengthText = new Text(this, 80, 420, "");
            generationLengthText.width  = 200;
            generationLengthText.height = 20;
            
            totalLengthText = new Text(this, 80, 440, "");
            totalLengthText.width  = 200;
            totalLengthText.height = 20;

            // ノード作成
            node = new Node(nodeNum);
            addChild(node);
        }
        
        
        /**
         * 交叉を開始する
         * 
         * @param    event
         */
        private function onStart(event:Event):void 
        {
            if (!startFlag) {
                salesperson.generation = 0;
                
                salesperson.mutationProb   = mutationProbHUISlider.value;
                salesperson.mutationVolume = mutationVolumeHUISlider.value;
                salesperson.aliveVolume    = aliveVolumeHUISlider.value;
                
                salesperson.nextGeneration(node);
    
                node.dragFalse();
                startFlag = true;
                addEventListener(Event.ENTER_FRAME, nextGeneration);
            }
        }
        
        /**
         * 次の世代へ進める
         * 
         * @param    event
         */
        private function nextGeneration(event:Event):void 
        {
            node.drawRoute(salesperson);
            salesperson.nextGeneration(node);
            generationLengthText.text = String(salesperson.generation);
            totalLengthText.text = String(salesperson.gene[0].score);
        }
        
        /**
         * 交叉を停止する
         * 
         * @param    event
         */
        private function onStop(event:Event):void 
        {
            removeEventListener(Event.ENTER_FRAME, nextGeneration);
        }
        
        /**
         * クリアする
         * 
         * @param    event
         */
        private function onClear(event:Event):void
        {
            node.clearRoute(salesperson);
            node.dragTrue();
            startFlag = false;
            
            salesperson.generation = 0;
            generationLengthText.text = String('');
            totalLengthText.text = String('');
        }
        
        /**
         * nodeの位置をランダムにする
         * 
         * @param    event
         */
        private function random(event:Event):void
        {
            if (!startFlag) {
                node.randomize();
            }
        }
        
        /**
         * nodeをサークル上に配置する
         * 
         * @param    event
         */
        private function circlePattern(event:Event):void
        {
            if (!startFlag) {
                node.circlePattern();
            }
        }
        
        /**
         * パラメータを再設定する
         * 
         * @param    event
         */
        private function setValue(event:Event):void
        {
            if (!startFlag) {
                salesperson.mutationProb   = mutationProbHUISlider.value;        // 突然変異の確立
                salesperson.mutationVolume = mutationVolumeHUISlider.value;        // 突然変異の量
                salesperson.aliveVolume    = aliveVolumeHUISlider.value;        // 生存率
                
                salesperson = new Salesperson(nodeHUISlider.value, salespersonHUISlider.value);    // 巡回セールスマン(遺伝子作成)
                node.setNode(nodeHUISlider.value);                
            }

        }
        
    }

}

/**
 * セールスマン(GAでコントロール)
 * ...
 * @author sabotenbrother
 */
class Salesperson 
{
    private var _gene:Array = new Array();    // 遺伝子情報
    private var _nodeNum:int;
    private var _personNum:int;
    
    private var _generation:int = 0;            // 世代
    private    var _mutationProb:Number   = 0.7;    // 突然変異の確立
    private    var _mutationVolume:Number = 0.2;    // 突然変異の量
    private    var _aliveVolume:Number  = 0.2;        // 生存率

    private var mask:Array = new Array();        // 一様交叉用のマスクパターン
    
    public function Salesperson(nodeNum:int, personNum:int)
    {
        _nodeNum = nodeNum;
        _personNum = personNum;
        _gene = generateGene(nodeNum, personNum);
        
        // マスクパターンの生成 000111000111...
        for (var i:int = 0; i < _nodeNum; i++) {
            mask.push(Math.floor((i / 3) % 2));
        }
    }

    /**
     * 遺伝子を作る
     * 
     * @param    nodeNum        ノードの数だけ gene を持つ
     * @param    personNum    セールスパーソンの数
     * @return    遺伝子情報(route, score, rank)
     */
    public function generateGene(nodeNum:int, personNum:int):Array{
        var rtnArr:Array = new Array();
        
        for (var i:int = 0; i < personNum; i++){
            rtnArr[i] = generateGene2(nodeNum);
        }
        
        return rtnArr;
    }
    
    public function generateGene2(nodeNum:int):Object{
        var result:Object = new Object();
        var arr:Array = new Array();
            
        for (var j:int = 0; j < nodeNum; j++) arr[j] = j;//ノード番号割り当て
        arr = shuffle(arr);//経路を決定
        result = {route: arr, score: 0, rank: 0};
        return result;
    }

    
    /**
     * 次の世代へ進める
     */
    public function nextGeneration(node:Node):void
    {
        _generation++;
        var aliveNum:int = Math.floor(gene.length * _aliveVolume) + 2;    // 親の遺伝子ふたつは必ず残す
        
        crossbreed(node, aliveNum);
    }
    
    //突然変異の発生率を調整する
    private function func():void{
        //上位スコアの平均
        //平均スコアとトップの差:差が少ないなら突然変異の発生率を高める
    }

    
    /**
     * 遺伝子を一様交叉させる
     * マスクパターンは000111000111000111...
     * 
     * @param    node ノード情報
     * @param    aliveNum 親以外に残す遺伝子の数
     */
    private function crossbreed(node:Node, aliveNum:int):void {
        
        // 遺伝子情報を一時的に保管するための配列を作成
        var _geneTmp:Array = new Array();
        _geneTmp = generateGene(_nodeNum, 1);
        _geneTmp[0].route = new Array();
//------------------------------
        //スコア順に並べ替える
        rankingSort(node);
        
        var genePosArr:Array = new Array();        // 遺伝子の配列位置
//------------------------------
        // 父親の遺伝子を入力
        for (var i:int = 0; i < _nodeNum; i++) {
            if(mask[i]==1)continue;
            //ランキング1位の_gene[0]を父親とする
            _geneTmp[0].route[i] = _gene[0].route[i];            
        }
//------------------------------
        // 母親の遺伝子を入力
        // ランキング1位との交配対象をスコア上位1/3から選択
        var mo:int = 1+Math.floor(Math.random()*_gene.length);
        for (var j:int = 0; j < _nodeNum; j++) {
            if(mask[j]==0)continue;            
            
            // 遺伝子情報を持っていない場合だけ入力(既に持っている遺伝子情報であれば入力しない)
            if (_geneTmp[0].route.indexOf(_gene[mo].route[j], 0) == -1) {
                //_gene[mo]を母親とする
                _geneTmp[0].route[j] = _gene[mo].route[j];
            } else {
                genePosArr.push(j);    // 埋められなかった場所を記憶する
            }
        }
//------------------------------        
        var nothingGene:Array = new Array();    // 欠けている遺伝子情報を格納する配列
        
        // 欠けている遺伝子情報を調べる(どの数字がないか調べる)
        for (var k:int = 0; k < _nodeNum; k++) {
            if (_geneTmp[0].route.indexOf(k, 0) == -1) nothingGene.push(k);
        }
        
        nothingGene = shuffle(nothingGene);    // 位置をシャッフル
        
        // 欠けている遺伝子を補う
        for (var l:int = 0; l < genePosArr.length; l++) {
            _geneTmp[0].route[genePosArr[l]] = nothingGene[l];
        }
//---淘汰プロセス--------------------------
        //スコアの低いのに交配して生成したものを上書き
        // 交叉させた遺伝子情報を子供たちに与える
        for (var m:int = 2 + aliveNum; m < _gene.length; m++) {
            _gene[m].route = _geneTmp[0].route.concat();    // 配列は参照渡しになるので deepcopy する
        }
//---突然変異プロセス----------------------        
        // mutationProb の確立で遺伝子に突然変異を起こす
        if (Math.random() < _mutationProb) {
            mutate(aliveNum);
            //mutate(10);
        }
        
    }


    //経路の評価
    private function getTotalLength(node:Node, geneNum:int):Number{
        var totalLength:Number = 0;
        //ノード数ループ
        for (var i:int = 0; i < _nodeNum - 1; i++) {
            totalLength += node.getDistance(_gene[geneNum].route[i], _gene[geneNum].route[i + 1]);//距離
        }
        totalLength += node.getDistance(_gene[geneNum].route[i], _gene[geneNum].route[0]);//始めと終わりをつなぐ距離
        return totalLength;//スコア決定
    }


    /**
     * 遺伝子に優越をつける
     * 各ノード間の距離が短い順にソートする(距離が短い方が優秀)
     * 
     * @param    node ノード情報
     */
    private function rankingSort(node:Node):void {
        //組数ループ
        for (var j:int = 0; j < _personNum; j++) {     
            _gene[j].score = getTotalLength(node,j);//評価
        }
        //スコア順に並べ替え
        _gene.sortOn('score', Array.NUMERIC);
    }
    
    /**
     * 突然変異させる
     * 
     * @param    aliveNum 突然変異させない遺伝子の数
     */
    private function mutate(aliveNum:int):void {
        var flipVolume:int = Math.round(_nodeNum * mutationVolume);    // 何個の遺伝子を組み換えるか

        for (var i:int = aliveNum; i < _gene.length; i++) {
            for (var j:int = 0; j < flipVolume; j++) {
                var firstPos:int  = Math.round(Math.random() * (_nodeNum - 1));
                var secondPos:int = Math.round(Math.random() * (_nodeNum - 1));
                var tmp:int;
                
                tmp = _gene[i].route[firstPos];
                _gene[i].route[firstPos]  = _gene[i].route[secondPos];
                _gene[i].route[secondPos] = tmp;
            }
        }
    }
    
    /**
     * 遺伝子情報をシャッフルする
     * 
     * @param    arr    遺伝子情報を持った配列
     * @return    シャッフルした配列
     */
    private function shuffle(arr:Array):Array
    {
        var num:int = arr.length;    // 配列の数

        for (var i:int = 0; i < num; i++) {
            var rundomNum:int = Math.floor(Math.random() * num);
            //入れ替え
            var temp:int = arr[rundomNum];
            arr[rundomNum] = arr[i];
            arr[i] = temp;
        }
        
        return arr;
    }
    
    
    //gene
    public function get gene():Array {
        return _gene;
    }
    
    //mutationProb
    public function get mutationProb():Number {
        return _mutationProb;
    }
    public function set mutationProb(value:Number):void {
        _mutationProb = value;
    }
    
    //mutationVolume
    public function get mutationVolume():Number {
        return _mutationVolume;
    }
    public function set mutationVolume(value:Number):void {
        _mutationVolume = value;
    }

    //aliveVolume    
    public function get aliveVolume():Number {
        return _aliveVolume;
    }    
    public function set aliveVolume(value:Number):void {
        _aliveVolume = value;
    }
    
    //gneration
    public function get generation():int{
        return _generation;
    }
    public function set generation(value:int):void{
        _generation = value;
    }
    
}

import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Rectangle;

/**
 * ノード = 各都市
 * ...
 * @author sabotenbrother
 */
class Node extends Sprite
{
    private var nodeArr:Array = new Array();
    private var blocker:Sprite = new Sprite();
    private var line:Sprite = new Sprite();
    private var boundary:Sprite = new Sprite();
    
    private const radius:int = 6;

    public function Node(nodeNum:int) 
    {
        initNode(nodeNum);
    }
    
    /**
     * nodeを引数の数だけ初期化
     * 
     * @param    nodeNum    ノードの数
     */
    public function initNode(nodeNum:int):void
    {
        for (var i:int = 0; i < nodeNum; i++) {
            var sp:Sprite = new Sprite();
            nodeArr.push(sp);

            nodeArr[i].graphics.beginFill(0x000000);
            nodeArr[i].graphics.drawCircle(0, 0, radius);
            nodeArr[i].graphics.endFill();
            nodeArr[i].buttonMode = true;
            
            nodeArr[i].x = radius + Math.floor(Math.random() * (465 - radius * 2));
            nodeArr[i].y = 110 + radius + Math.floor(Math.random() * (300 - radius * 2));
            nodeArr[i].addEventListener(MouseEvent.MOUSE_DOWN, startDragging);
            addChild(nodeArr[i]);
        }
        
        // ノード表示区域境界線用のスプライト
        boundary.graphics.lineStyle(1, 0x0);
        boundary.graphics.drawRect(0, 110, 464, 300);
        boundary.graphics.endFill();
        addChild(boundary);
        
        // ルート表示用のスプライト
        addChild(line);
        
        // 非選択用のレクタングル
        blocker.graphics.beginFill(0x000000);
        blocker.graphics.drawRect(0, 110, 465, 300);
        blocker.graphics.endFill();
        blocker.alpha = 0.1;
        blocker.visible = false;
        addChild(blocker);
    }

    /**
     * nodeを再設定
     * 
     * @param    nodeNum
     */
    public function setNode(nodeNum:int):void
    {
        var length:int = nodeArr.length;
        
        for (var i:int = 0; i < length; i++) {
            removeChild(nodeArr[0]);
            nodeArr.shift();
        }
        
        for (var j:int = 0; j < nodeNum; j++) {
            var sp:Sprite = new Sprite();
            nodeArr.push(sp);

            nodeArr[j].graphics.beginFill(0x000000);
            nodeArr[j].graphics.drawCircle(0, 0, radius);
            nodeArr[j].graphics.endFill();
            nodeArr[j].buttonMode = true;
            
            nodeArr[j].x = radius + Math.floor(Math.random() * (465 - radius * 2));
            nodeArr[j].y = 110 + radius + Math.floor(Math.random() * (300 - radius * 2));
            nodeArr[j].addEventListener(MouseEvent.MOUSE_DOWN, startDragging);
            addChild(nodeArr[j]);
        }
    }
    
    /**
     * 一番優秀なセールスパーソンのルートを描画
     * 
     * @param    salesperson セールスパーソン
     */
    public function drawRoute(salesperson:Salesperson):void
    {
        line.graphics.clear();
        line.graphics.lineStyle(1, 0x0);

        for (var i:int = 0; i < nodeArr.length - 1; i++) {
            line.graphics.moveTo(nodeArr[salesperson.gene[0].route[i]].x, nodeArr[salesperson.gene[0].route[i]].y);
            line.graphics.lineTo(nodeArr[salesperson.gene[0].route[i + 1]].x, nodeArr[salesperson.gene[0].route[i + 1]].y);
        }
        
        line.graphics.moveTo(nodeArr[salesperson.gene[0].route[nodeArr.length - 1]].x, nodeArr[salesperson.gene[0].route[nodeArr.length - 1]].y);
        line.graphics.lineTo(nodeArr[salesperson.gene[0].route[0]].x, nodeArr[salesperson.gene[0].route[0]].y);
    }
    
    public function clearRoute(saleperson:Salesperson):void
    {
        line.graphics.clear();
    }

    /**
     * node間の距離を測る
     * 
     * @param    pt1    ひとつめのノード配列番号
     * @param    pt2    ふたつめのノード配列番号
     * @return    node間の距離をピクセルで返す
     */
    public function getDistance(pt1:int, pt2:int):Number
    {
        var distance:Number = Math.sqrt(Math.pow(nodeArr[pt1].x - nodeArr[pt2].x, 2) + Math.pow(nodeArr[pt1].y - nodeArr[pt2].y, 2));
        return Math.round(distance);
    }
    
    /**
     * nodeの位置をランダムにする
     */
    public function randomize():void
    {
        for (var i:int = 0; i < nodeArr.length; i++) {
            nodeArr[i].x = 6 + Math.floor(Math.random() * (465 - 12));
            nodeArr[i].y = 110 + 6 + Math.floor(Math.random() * (300 - 12));
        }
    }
    
    /**
     * nodeを円状に配置
     */
    public function circlePattern():void
    {
        var angle:Number = 0.02;
        var radius:int = 130;
        
        for (var i:int = 0; i < nodeArr.length; i++) {
            nodeArr[i].x = (stage.stageWidth / 2) + radius * Math.cos(angle + i / nodeArr.length * Math.PI * 2);
            nodeArr[i].y = 260 + radius * Math.sin(angle + i / nodeArr.length * Math.PI * 2);
        }            
    }
    
    /**
     * 全nodeを移動できるようにする
     */
    public function dragTrue():void
    {
        blocker.visible = false;
    }

    /**
     * 全nodeを移動できないようにする
     */
    public function dragFalse():void
    {
        blocker.visible = true;
    }

    private function startDragging(event:MouseEvent):void 
    {
        event.target.startDrag(false, new Rectangle(0 + radius, 110 + radius, 465 - radius * 2, 300 - radius * 2));
        this.addEventListener(MouseEvent.MOUSE_UP, stopDragging);
    }

    private function stopDragging(event:MouseEvent):void 
    {
        event.target.stopDrag();
        this.removeEventListener(MouseEvent.MOUSE_UP, stopDragging);
    }

}