How to Build a Model - 1(如何构建一个模型-1)

  本文档介绍了通过SimpleModel类来创建Repast模型。其目的是为了介绍用Repast写基于代理的模型。如果你有写基于代理模型的经验,你也应该看看 How to Build a Repast Model - 2

  注意:在 repast/demo/life这个目录下的示范模型“Life”就是一个利用SimpleModel类来创建Repast模型的例子。

 

概述

  基于代理的仿真通常分两个阶段进行。第一步是为了运行仿真的准备阶段,第二是仿真的真正运行。在Repast仿真中,仿真的运行按照时间分成一步步的。每一步的行为的发生都利用前面行为的结果作为它们的基础。因此,比如我们构建一个两个人的囚徒困境的仿真,初始配置需要创建这两个人,并且给每个人提供初始化的策略。每秒或者每一步,每个玩家玩游戏,他们当前的行为依赖他们的策略或者先前的行为结果。你需要做的就是构建一个简单的Repast 模型来描述在配置阶段做的准备工作,还有每一秒发生的事情。SimpleModel为你做这些事情提供了接口。

 

SimpleModel

  Repast仿真通常需要至少两个类。一个代理类描述了代理们的行为,一个协同配置和模型的运行的模型类。你可以用SimpleModel作为你的基类,实现它来符合你的目的。在Java或者.NET中如此实现时很常见的通过继承SimpleModel成为了你的模型类的基类。例如:

C#:

using SimpleModel = uchicago.src.sim.engine.SimpleModel;

public class MyModel : SimpleModel
{

...

}

Java:

import uchicago.src.sim.engine.SimpleModel;

public class MyModel extends SimpleModel {

...

}

 

  在第一行我们引入了SimpleModel,下一行用我们的模型类继承了SimpleModel类。在写我们的仿真模型的时候,真如上边所说,我们想要描述怎样设置我们的模型,然后描述每单位时间内发生的事情。SimpleModel提供了这样的方法,我们重写这些方法来具体到我们的模型中。这两个方法是setup() 和 buildModel()。我们这样用: 

C#:

using SimpleModel = uchicago.src.sim.engine.SimpleModel;

public class MyModel : SimpleModel
{


  public static final int TIT_FOR_TAT = 0;
  public static final int ALWAYS_DEFECT = 1;
  private int p1Strategy = TIT_FOR_TAT;
  private int p2Strategy = ALWAYS_DEFECT;

  ...

  public override void setup()
  {
      base.setup();
      p1Strategy = TIT_FOR_TAT;
      p2Strategy = ALWAYS_DEFECT;
  }

  public override void buildModel()
  {
      Player p1 = new Player(p1Strategy);
      Player p2 = new Player(p2Strategy);
      p1.OtherPlayer = p2;
      p2.OtherPlayer = p1;
      agentList.Add(p1);
      agentList.Add(p2);
  }
}

Java:

import uchicago.src.sim.engine.SimpleModel;

public class MyModel extends SimpleModel {


   public static final int TIT_FOR_TAT = 0;
   public static final int ALWAYS_DEFECT = 1; 
   private int p1Strategy = TIT_FOR_TAT;
   private int p2Strategy = ALWAYS_DEFECT;

   ...

   public void setup() {
       super.setup();
       p1Strategy = TIT_FOR_TAT;
       p2Strategy = ALWAYS_DEFECT;
   }

   public void buildModel() {
       Player p1 = new Player(p1Strategy);
       Player p2 = new Player(p2Strategy);
       p1.setOtherPlayer(p2);
       p2.setOtherPlayer(p1);
       agentList.add(p1);
       agentList.add(p2);
  }
}

 

  这里我们用setup() 和 buildModel()来实现我们的目的。在setup()中我们首先调用base.setup()来确保SimpleModel执行了它自己必须的配置,然后我们设定玩家的策略。这里的假设是策略可能会通过用户的交互行为或者前面仿真执行的过程来改变策略的默认值。我们把setup()作为设置模型变量恢复到合理的模型值的方法。通常setup()应该破坏掉模型将要准备的下一步执行状态,setup()在仿真第一次启动时,更频繁的无论什么时候点击setuo按钮。

  setup()破坏掉仿真的地方,我们用buildModel()来创建我么仿真模型要用的对象。所以这时你需要创建代理,并且把它添加到主代理类列表中,即agentList中,agentLiat是SimpleModel为了实现此目的而提供的一个ArraryList。这里的例子假设我们的玩家的构建是依赖初始化策略和交互的其它玩家的。

  执行顺序是setup()首先被调用,然后是buildModel()。然而像上边提到的,setup()在仿真启动时,或者是点击了setup按钮时,都会执行,buildModel()直到仿真运行的时候才会被调用,也就是说除非模型执行,step或者initialize按钮被点击了。(请看下边更详细的什么按钮按下对应执行的方法。)这就给用户通过图形用户接口来执行改变任何的参数提供了可能。

  现在setup步骤完成了,我们需要定义每单位时间内仿真发生的行为。SimpleModel提供了三个方法。它们是preStep()、step()和postStep()。每个时间点它们按照preStep()->step()->postStep()的顺序执行着。这样做的目的是在step()中分离出必要的预处理和后处理。在我们的例子中,我们没有预处理和后处理的需求,因此我们仅使用step()方法。

C#:

using SimpleModel = uchicago.src.sim.engine.SimpleModel;

public class MyModel : SimpleModel
{
    public static final int TIT_FOR_TAT = 0;
    public static final int ALWAYS_DEFECT = 1;
    private int p1Strategy = TIT_FOR_TAT;
    private int p2Strategy = ALWAYS_DEFECT;
  
    ... 
  
    public override void setup()
    {
        base.setup();
        p1Strategy = TIT_FOR_TAT;
        p2Strategy = ALWAYS_DEFECT;
    }
  
    public override void buildModel()
    {
        Player p1 = new Player(p1Strategy);
        Player p2 = new Player(p2Strategy);
        p1.OtherPlayer = p2;
        p2.OtherPlayer = p1;
        agentList.Add(p1);
        agentList.Add(p2);
    }

    public override void step()
    {
        foreach (Player p in agentList)
        {
            p.play();
        }
    }
}

Java:

import uchicago.src.sim.engine.SimpleModel;

public class MyModel extends SimpleModel { 
    public static  final int TIT_FOR_TAT = 0; 
    public static  final int ALWAYS_DEFECT = 1;
    private int p1Strategy = TIT_FOR_TAT;
    private int p2Strategy = ALWAYS_DEFECT;

     ...

    public void setup() {
        super.setup();
        p1Strategy = TIT_FOR_TAT;
        p2Strategy = ALWAYS_DEFECT;
    }

    public void buildModel() {
        Player p1 = new Player(p1Strategy);
        Player p2 = new Player(p2Strategy);
        p1.setOtherPlayer(p2);
        p2.setOtherPlayer(p1);
        agentList.add(p1);
        agentList.add(p2);
    }

    public void step() {
        int size = agentList.size();
        for (int i = 0; i < size; i++) {
          Player p = (Player)agentList.get(i);
          p.play();
        }
    }
}

 

  这里的setup()方法调出主代理类列表中的每个玩家,调用每个玩家的paly()方法。这里假设当我们调用play()方法时,玩家就会和其它的玩家玩。在step()方法中便利所有的代理玩家来执行每个玩家相应方法是典型的做法。当模型执行时step()方法将每时每刻的执行。

  不同的游戏可能需要预处理或者后处理方法。例如,假设一个需要和很多玩家合作的游戏,每个玩家和邻居玩家交互后收到一个反馈,然后又根据最优策略丢给它的一个邻居玩家。这种情况下,每一个正常玩家都应该像上边说的在step方法中定义,邻居玩家分发信息和策略设置应该在postStep()方法中发生。

  在你的模型中step()方法的运用具有多种选择性,你可以使用自动步骤机制,如果你选择这么做的话,SimpleModel将遍历所有的代理类然后调用代理类中定义的step()方法。为了能这样做,你的agent类必须实现Stepable 接口。这个接口只有一个方法step()。请注意:step()是你的agent类中的方法不是model类中的方法。这种自动步骤执行方法替代了上边描述的model类中的step()方法。为了使用自动步骤机制,你可以在你model的构造函数中将autoStep实例变量设置为true。你可以设置随机实例变量为true或者false依赖你是否希望在执行自动步骤执行前通过它来随机agentList,preStep()和postStep()像上边说的一样执行,在autoStep()前或者后调用。不管我们用不用preStep()或者postStep()或者autoStep(),加入setup()、buildMOdel()和step() (或者 autoStep())像上文中说的对于创建一个仿真都是必要的。

  需要注意的是更多的复杂的agent调度行为,modle事件等等包含动态调度通过Repas-r都是可以实现的。SinmpleModel就是为了简化调度,上边的描述正反映了这个目的。同时你可以利用SimpleModel 模型的调度实例变量来控制模型的调度。更多有关调度的信息请看 How to Use a Schedule

  不同的工具栏上的按钮和代码实际执行之间的关系如下。当点击setup按钮,setup()方法中的代码会执行;点击initialize按钮,buildModel方法中的代码会执行;当点击step按钮时,buildModel()方法会执行,并且preStep()、step()和postStep()会立即顺序的执行;当点击start按钮时,buildModel()一直执行,preStep()、step和postStep()一直重复的顺序执行,直到用户点击stop或者pause按钮。

参数

  然而setup()、buildModel()和step都是我们创建模型所需要的,这样的模型不是很有趣。它对于执行来说没有很多的变化性,我们不能很容易的改变模型的初始化条件。我们能够通过为模型创建参数来提供可变性。

  一个模型的参数通过属性来定义。这些组合将变量的读和写结合到一个单一的逻辑结构中。在Java或其它的语言中这件事是通过set、get方法对来完成了,.NET把set和get放到了一起处理。在Java中你可能会有getAgentProperty()、setAgentProperty(),但是在.NET中你仅仅有agentProperty。(注意:get/set方法在某些情况下还是要用的,特别是当set或get方法比较复杂时),所以,玩家1策略参数就像这样: 

C#:

using SimpleModel = uchicago.src.sim.engine.SimpleModel;

public class MyModel : SimpleModel
{
    public static final int TIT_FOR_TAT = 0;
    public static final int ALWAYS_DEFECT = 1;

    private int p1Strategy = TIT_FOR_TAT;
    private int p2Strategy = ALWAYS_DEFECT;


     public int P1Strategy
    {
        set { p1Strategy = value; }
        get { return p1Strategy; }
    }


    public override void setup()
    {
        base.setup();
        p1Strategy = TIT_FOR_TAT;
        p2Strategy = ALWAYS_DEFECT;
    }
  
    public override void buildModel()
    {
        Player p1 = new Player(p1Strategy);
        Player p2 = new Player(p2Strategy);
        p1.OtherPlayer = p2;
        p2.OtherPlayer = p1;
        agentList.Add(p1);
        agentList.Add(p2);
    }

    public override void step()
    {
        foreach (Player p in agentList)
        {
             p.play();
        }
    }
}

Java:

import uchicago.src.sim.engine.SimpleModel;

public class MyModel extends SimpleModel {
    public static  final int TIT_FOR_TAT = 0;
    public static  final int ALWAYS_DEFECT = 1; 
    private int  p1Strategy = TIT_FOR_TAT;
    private int p2Strategy = ALWAYS_DEFECT;

    public void setP1Strategy(int val) {
        p1Strategy = val;
    }

    public int getP1Strategy() {
        return p1Strategy;
    }


    public void setup() {
        super.setup();
        p1Strategy = TIT_FOR_TAT;
        p2Strategy = ALWAYS_DEFECT;
    }

    public void buildModel() {
        Player p1 = new Player(p1Strategy);
        Player p2 = new Player(p2Strategy);
        p1.setOtherPlayer(p2);
        p2.setOtherPlayer(p1);
        agentList.add(p1);
        agentList.add(p2);
    }

    public void step() {
        int size = agentList.size();
        for (int i = 0; i < size; i++) {
           Player p = (Player)agentList.get(i);
           p.play();
        }
    }
}

  

  参数的名称就是属性名。因此这里的参数名就是P1Strategy。创建参数的最后一块就是让Repast意识到它。你通过将参数名加到参数字符串数组中就可以实现了。可以在model的构造函数中执行。例如:

 

C#:

using SimpleModel = uchicago.src.sim.engine.SimpleModel;

public class MyModel : SimpleModel
{

    ...

   public MyModel()
   {
       params = new String[] {"P1Strategy"};
   }

   ...

}

Java:

import uchicago.src.sim.engine.SimpleModel;

public class MyModel extends SimpleModel {

  ...

    public MyModel() {
        params = new String[] {"P1Strategy"};
    }

  ...

}

 

  参数变量是SimpleModel提供的。它是一个包含你的参数名的数组。

 

  一旦你创建了你的参数获取的方法并且参数名加到了数组参数中,你将在仿真运行过程中的参数panel中看到你的参数显示。这个参数的值就是通过get获取方法返回的。参数通过在输入框中输入一个新的值来设置。改变是通过注册事件的方式当按回车键或者文本框失去焦点时。这个新输入的值就成了参数谁置方法的参数。着重需要注意的是获取方法是非常重要的,参数存的真实值不能被参数机制直接看到(大笑我的理解是需要加密,不知道对不对)。

  也有利用check boxes, combo boxes, buttons等来显示参数。更多信息请参考 How to create PropertyDescriptors

 

命名你的模型

  你可以通过name实例变量给模型命名。在你的model的构造函数中给这个变量分配正确的值。例如:

 

C#:

using SimpleModel = uchicago.src.sim.engine.SimpleModel;

public class MyModel : SimpleModel
{

  ...

    public MyModel()
    {
         name = "Example Model";
    }

  ...

}

Java:

import uchicago.src.sim.engine.SimpleModel;

public class MyModel extends SimpleModel {

  ...

    public MyModel() {
        name = "Example Model";
    }

  ...

}

  这个名称将会是你的模型工具栏窗口的标题。

 

SimpleModel方法和实例变量

  SimpleModel有一些其它的方法,属性和变量供你的model类来用。

 

  Methods": 

  setStoppingTime(long time)能用来设置当前运行的仿真每一步骤的执行暂停时间。

  getNextIntFromTo(int from, int to)返回下一个从from到to包括from和to之间的整数。

  getNextDoubleFromTo(double from, double to)返回下一个从from到to不包括from和to之间的双精度浮点数。

  atPause()仿真暂停的时候将执行这个方法,你需要重写这个方法。

  atEnd()仿真结束的时候执行这个方法,你需要重写这个方法。 

  在Java框架中还有一个方法

  setRngSeed(long seed)用来设置默认随机种子数。注意:默认的种子数是1。

 

  Properties: 

  RngSeed(long) 能用来设置默认随机种子数。注意:默认的种子数是1。

 

  Instance Variables: 

  Schedule schedule 这个能向调度增加你的行为。更多信息请看 How to Use a Schedule

  boolean isGui 如果仿真实运行在图形用户接口则为true,反之false。

  long startAt 开始执行preStep()、step()和postStep()的时间默认为1。

 

增加显示等

  显示,数据记录等能够纳入到基于SimpleModel的模型中,就像其它任何的模型一样。更多信息请看How To Index

  唯一需要注意的是显示通常在一个buildDisplay()方法中创建,虽然这不是必要的。在你的代码中引入这样的一个方法,你可以先添加方法,然后再buildModel()中调用它。例如:

 

public class MyModel extends SimpleModel {

    private DisplaySurface dsurf;

    ...

    private void buildDisplay() {
        ...
    }

    public void buildModel() {
        ...
        buildDisplay();
    }
}

  你然后将你的显示代码放到buildDisplsy()中并确保它在模型开始启动时被调用。或者你也可以将你所有的代码放到buildModel()中,但是显示部分的代码分离出来看起来更清晰。

关于构建模型和命令行方式运行的更多地信息,请看 How to Build a Repast Model – 2

 

 

今天的讲解就到此,谢谢您的阅读,下次再见。

如果您觉得这篇博客对您有所启发,不妨点击一下右下角的【推荐】按钮。

如果您对本博客内容感兴趣,请继续关注我,我是Bull Li。

posted @ 2014-03-01 18:29  Buller Lee  阅读(710)  评论(0)    收藏  举报