Heatbugs

Welcome to the provisional home page of the Heatbugs simulation model! The purpose of this page is to provide provisional access to the Java code of the heatbugs model until the regular VSEit home page is ready for use. Below you find the complete listings of all classes needed to get the Heatbugs model run within VSEit, version 0.97. Classes are:

The model was implemented using the Java based VSEit framework. For latest information about VSEit, please visit www.vse.de.

Name of Model: Heatbugs
Author: Kai-H. Brassel
Date: 01/07/01
VSEit Version: 0.97
Start Simulation: Netscape Navigator 4.x (on Windows 95/98/ME/NT4/2000, Linux, and Solaris)
Microsoft Explorer 5.x (on Windows 95/98/ME/NT4/2000)
Other browsers and platforms (e.g. Netscape Navigator 6 on Windows 95/98/ME/NT4/2000 and Linux)

By clicking at the appropriate link above the should simulation will automatically be downloaded and executed as an applet in your browser. If not already installed, your browser attempts to download and install a plug-in for the required Java version, too. If you encounter problems with the automatic installation performed by your browser, download the Java Runtime Environment (JRE) for Java 2, version 1.3.1, from http://java.sun.com/j2se/1.3/jre/index.html. Then close your browser, install the JRE, and try again.

If the applet starts up, choose File > New Simulation...from the main menu and provide any name for the new simulation run, you are going to create. Then, issue command Simulation > Run and watch the show.


Class Model

package de.vseit.heatbugs;

import java.util.Date;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import de.vseit.simulation.Simulation;
import de.vseit.simulation.Clock;
import de.vseit.simulation.SimulationTimeFormat;
import de.vseit.simulation.InitializeModelException;
import de.vseit.showit.DataCollection;
import de.vseit.network.IntegerAttribute;
import de.vseit.network.Multiplicity;
import de.vseit.network.InputPane;


public class Model extends Simulation
{
   // ----- entity type definitions -----   

   static final String HEAT_RANGE = "0 to " + HeatSpace.MAX_TEMP;
   public static final IntegerAttribute NUM_BUGS = new IntegerAttribute(
      "Number of heatbugs: ",Multiplicity.MANDATORY,true,null,"> 0",null);
   public static final IntegerAttribute IDEAL_TEMP_MIN = new IntegerAttribute(
      "Minimal ideal temperature of bugs: ",Multiplicity.MANDATORY,true,null,
      HEAT_RANGE,null);
   public static final IntegerAttribute IDEAL_TEMP_MAX = new IntegerAttribute(
      "Maximal ideal temperature of bugs: ",Multiplicity.MANDATORY,true,null,
      HEAT_RANGE,null);
   public static final IntegerAttribute OUTPUT_HEAT_MIN = new IntegerAttribute(
      "Minimal output heat of bugs: ",Multiplicity.MANDATORY,true,null,
      HEAT_RANGE,null);
   public static final IntegerAttribute OUTPUT_HEAT_MAX = new IntegerAttribute(
      "Maximal output heat of bugs: ",Multiplicity.MANDATORY,true,null,
      HEAT_RANGE,null);

   public void initializeEntity()
   {
      super.initializeEntity();
      entity().add(PAUSE_TIMES,new Date(500));

      entity().set(NUM_BUGS,30);
      entity().set(IDEAL_TEMP_MIN,17000);
      entity().set(IDEAL_TEMP_MAX,31000);
      entity().set(OUTPUT_HEAT_MIN,3000);
      entity().set(OUTPUT_HEAT_MAX,10000);
   }

   public void acceptEntity(InputPane inputs)
   {
      super.acceptEntity(inputs);

      if (inputs.get(NUM_BUGS) <= 0) inputs.set(NUM_BUGS,1);
      if (inputs.get(IDEAL_TEMP_MIN) > inputs.get(IDEAL_TEMP_MAX))
      {
         inputs.markError(IDEAL_TEMP_MIN);
         inputs.markError(IDEAL_TEMP_MAX);
      }
      if (inputs.get(OUTPUT_HEAT_MIN) > inputs.get(OUTPUT_HEAT_MAX))
      {
         inputs.markError(OUTPUT_HEAT_MIN);
         inputs.markError(OUTPUT_HEAT_MAX);
      }

      int maxT = HeatSpace.MAX_TEMP;
      if (inputs.get(IDEAL_TEMP_MIN) < 0) inputs.set(IDEAL_TEMP_MIN,0);
      if (inputs.get(IDEAL_TEMP_MIN) > maxT) inputs.set(IDEAL_TEMP_MIN,maxT);
      if (inputs.get(IDEAL_TEMP_MAX) < 0) inputs.set(IDEAL_TEMP_MAX,0);
      if (inputs.get(IDEAL_TEMP_MAX) > maxT) inputs.set(IDEAL_TEMP_MAX,maxT);
      if (inputs.get(OUTPUT_HEAT_MIN) < 0) inputs.set(OUTPUT_HEAT_MIN,0);
      if (inputs.get(OUTPUT_HEAT_MIN) > maxT) inputs.set(OUTPUT_HEAT_MIN,maxT);
      if (inputs.get(OUTPUT_HEAT_MAX) < 0) inputs.set(OUTPUT_HEAT_MAX,0);
      if (inputs.get(OUTPUT_HEAT_MAX) > maxT) inputs.set(OUTPUT_HEAT_MAX,maxT);
   }

   // ----- end of entity type definitions -----


   static
   {
      TIME_FORMAT = new SimulationTimeFormat();
      INIT_TIME.initTimeFormat(TIME_FORMAT);
      STOP_TIME.initTimeFormat(TIME_FORMAT);
      PAUSE_TIMES.initTimeFormat(TIME_FORMAT);

      DataCollection.initializeDataClasses(new Class[]{MacroData.class});
   }

   HeatSpace space;
   ArrayList bugs;

   Model(MainWindow mainWindow)
   {
      super(mainWindow);

      bugs = new ArrayList();
      space = new HeatSpace(this);
      repository.insert(space,120,20,2);
         // insert heat space on level 2, so that it will always stay behind
         // the heat bugs "living" on level 1 (the default level)

      Clock clock = new Clock("Step",1,1000,1)
      {
         public void execute()
         {
            // simulate diffusion and evaporation in the heat space
            space.step();
            // simulate one cycle of each heat bug's life
            Collections.shuffle(bugs,rnd0.getGenerator());
            for (int i=0; i < bugs.size(); i++)
               ((Heatbug)bugs.get(i)).step();

            // data sampling
            observations.addData(Model.this,new MacroData(Model.this));
         }
      };
      repository.insert(clock,10,120);
   }

   protected void initializeModel() throws InitializeModelException
   {
      observations.createDataSample(this,MacroData.class);

      // trim number of heatbugs
      int n = entity().get(NUM_BUGS) - bugs.size();
      if (n > 0)
         for (int i=0; i < n; i++)
         {
            Heatbug bug = new Heatbug(this);
            bugs.add(bug);
            repository.insert(bug);
         }
      else if (n < 0)
         for (int i=0; i < -n; i++)
            repository.delete((Heatbug)bugs.remove(bugs.size()-1));

      // initialize heat bugs randomly
      Point coords = new Point();
      Iterator iter = bugs.iterator();
      while (iter.hasNext())
      {
         Heatbug bug = (Heatbug)iter.next();
         do
         {
            coords.x = rnd0.nextUniform(0,space.width-1);
            coords.y = rnd0.nextUniform(0,space.height-1);
         } while (space.isOccupiedAt(coords));
         bug.entity().set(bug.IDEAL_TEMP,rnd0.nextUniform(
            entity().get(IDEAL_TEMP_MIN),entity().get(IDEAL_TEMP_MAX)));
         bug.entity().set(bug.OUTPUT_HEAT,rnd0.nextUniform(
            entity().get(OUTPUT_HEAT_MIN),entity().get(OUTPUT_HEAT_MAX)));
         bug.placeAt(coords);
      }
   }

   protected void stopSimulationRun()
   {
      space.step();
      super.stopSimulationRun();
   }

   protected void resetSimulationRun()
   {
      space.resetSimulationRun();
   }
}

Class Heatbug

package de.vseit.heatbugs;

import java.awt.geom.Ellipse2D;
import java.awt.Color;
import java.awt.Point;
import java.util.Observable;
import java.util.ArrayList;
import de.vseit.network.*;


public class Heatbug extends AbstractEntityClient
{
   // provide the first HeatSpace.NUM_NEIGHBOURS integers as constants to
   // avoid repeating allocation of new integer objects in step
   private static final Integer[] INTEGERS =
      new Integer[HeatSpace.NUM_NEIGHBOURS];
   {
      for (int d=0; d < HeatSpace.NUM_NEIGHBOURS; d++)
         INTEGERS[d] = new Integer(d);
   }

   // ----- entity type definitions -----

   public static final Multiplicity MULTIPLICITY = Multiplicity.NONE;

   // attributes
   public static final IntegerAttribute IDEAL_TEMP = new IntegerAttribute(
      "Ideal Temperature: ",Multiplicity.MANDATORY,true,null,Model.HEAT_RANGE,
      null);
   public static final IntegerAttribute OUTPUT_HEAT = new IntegerAttribute(
      "Output Heat: ",Multiplicity.MANDATORY,true,null,Model.HEAT_RANGE,null);
   public static final DoubleAttribute UNHAPPINESS = new DoubleAttribute(
      "Unhappiness: ",Multiplicity.OPTIONAL,false,null,null,null);

   private static final EntityShape SHAPE =
      new EntityShape(new Ellipse2D.Float(1,1,10,10),1);

   static void setBugsSize(int cellSize)
   {
      SHAPE.setShape(new Ellipse2D.Float(0,0,cellSize-3,cellSize-1));
   }

    public EntityShape shape()
   {
      SHAPE.setInnerColor(color);
      return SHAPE;
   }

   public void acceptEntity(InputPane inputs)
   {
      int maxT = HeatSpace.MAX_TEMP;
      if (inputs.get(IDEAL_TEMP) < 0) inputs.set(IDEAL_TEMP,0);
      if (inputs.get(IDEAL_TEMP) > maxT) inputs.set(IDEAL_TEMP,maxT);
      if (inputs.get(OUTPUT_HEAT) < 0) inputs.set(OUTPUT_HEAT,0);
      if (inputs.get(OUTPUT_HEAT) > maxT) inputs.set(OUTPUT_HEAT,maxT);
   }
   
   public void update(Observable sender, Object aspect)
   {
      if (aspect == IDEAL_TEMP)
      {
         float brightness = (float)entity().get(IDEAL_TEMP)/HeatSpace.MAX_TEMP;
         color = new Color(brightness,brightness,0);
         entity().changed("look");
      }
   }

   // ----- end of entity type definitions -----
   
   // instance variables
   private Model model;
   private HeatSpace myWorld;
   private Point gridPos;   // may only modified via moveTo() and placeAt()
   private Color color;
   private ArrayList bestPlaces;


   Heatbug(Model m)
   {
      model = m;
      myWorld = model.space;
      gridPos = new Point();
      bestPlaces = new ArrayList(HeatSpace.NUM_NEIGHBOURS);
   }

   void step()
   {
      Point coords = new Point();
      // update unhappiness
      int ideal = entity().get(IDEAL_TEMP);
      int heatDiff = StrictMath.abs(ideal-myWorld.getHeatAt(gridPos));
      entity().set(UNHAPPINESS,(double)heatDiff / HeatSpace.MAX_TEMP);

      // random moves would be implemented here

      // looking for a free and better place
      bestPlaces.clear();
      for (int d=0; d < HeatSpace.NUM_NEIGHBOURS; d++)
      {
         coords.setLocation(gridPos);
         coords = myWorld.neighbour(coords,d);
         if (!myWorld.isOccupiedAt(coords))
         {
            int diff = StrictMath.abs(ideal-myWorld.getHeatAt(coords));
            if (diff < heatDiff)
            {
               heatDiff = diff;
               bestPlaces.clear();
               bestPlaces.add(INTEGERS[d]);
            }
            else if (diff == heatDiff)
               bestPlaces.add(INTEGERS[d]);
         }
      }

      coords.setLocation(gridPos);
      if (!bestPlaces.isEmpty())
      {
         int d = model.rnd0.nextUniform(0,bestPlaces.size()-1);
         d = ((Integer)bestPlaces.get(d)).intValue();
         coords = myWorld.neighbour(coords,d);
      }

      // update position and heat
      moveTo(coords);
      myWorld.addHeat(coords,entity().get(OUTPUT_HEAT));
   }

   // Assumes that the target cell is free
   void moveTo(Point coords)
   {
      myWorld.setOccupiedAt(gridPos,false);
      placeAt(coords);
   }

   // The bug to be placed was not in the world before.
   // Assumes that the target cell is not occupied
   void placeAt(Point coords)
   {
      // logical placement
      gridPos.setLocation(coords);
      myWorld.setOccupiedAt(gridPos,true);

      // graphical placement 
      Point zero = model.repository.getPosition(myWorld);
      int cs = myWorld.cellSize;
      model.repository.setPosition(
         this,zero.x+1 + coords.x*cs,zero.y + coords.y*cs);
   }
}

Class HeatSpace

package de.vseit.heatbugs;

import java.awt.Rectangle;
import java.awt.Point;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.GridBagConstraints;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.util.Observable;
import java.util.ArrayList;
import java.util.HashSet;
import de.vseit.network.*;


public class HeatSpace extends AbstractEntityClient
{
// ----- entity type definitions -----

   public static final Multiplicity MULTIPLICITY = Multiplicity.MANDATORY;

   // attributes
   public static final IntegerAttribute DIMENSION = new IntegerAttribute(
      "Dimension: ",new Multiplicity(2,2),true,null,"width * height",null);
   public static final IntegerAttribute CELL_SIZE = new IntegerAttribute(
      "Size of a cell: ",Multiplicity.MANDATORY,true,null,"5 to 30 pixel",null);
   public static final DoubleAttribute DIFFUSION = new DoubleAttribute(
      "Diffusion constant: ",Multiplicity.MANDATORY,true,null,
      "0.0 to 1.0",null);
   public static final DoubleAttribute EVAPORATION = new DoubleAttribute(
      "Evaporation rate: ",Multiplicity.MANDATORY,true,null,
      "0.0 to 1.0",null);

   public static final EntityShape SHAPE =
      new EntityShape(new Rectangle(0,0,3,3),0);
   public EntityShape shape()
   {
      return SHAPE;
   }


   public void initializeEntity()
   {
      super.initializeEntity();

      entity().set(DIMENSION, new int[]{40,40});   
      entity().set(CELL_SIZE,8);
      entity().set(DIFFUSION,1.0);
      entity().set(EVAPORATION,0.01);
   }

   public void installCustomComponent(JPanel panel)
   {
      JComponent ca = new JComponent()
      {
         public void paint(Graphics g)
         {
            super.paint(g);
            for (int y=0; y < height; y++)
               for (int x=0; x < width; x++)
               {
                  g.setColor(colorOfHeat(space[x][y]));
                  g.fillRect(x*cellSize,y*cellSize,cellSize,cellSize);
               }
         }
      };
      GridBagConstraints gbc = new GridBagConstraints();
      gbc.fill = GridBagConstraints.BOTH;
      gbc.weightx = 1.0;
      gbc.weighty = 1.0;
      panel.add(ca,gbc);
   }

   public double entityViewWidth() {return width*cellSize;}
   public double entityViewHeight() {return height*cellSize;}

   public void acceptEntity(InputPane inputs)
   {
      super.acceptEntity(inputs);

      // allow for changing dimensions of space only if simulation is not
      // initialized, active or ready (but new or resetted)
      if (inputs.isChanged(DIMENSION))
         if (model.isNew())
         {
            // prohibit a to small 
            int w = inputs.get(DIMENSION,0);
            int h = inputs.get(DIMENSION,1);
            if (w < 3 || h < 3)
               inputs.set(DIMENSION,new int[]
                  {StrictMath.max(3,w),StrictMath.max(3,h)});
         }
         else
            inputs.reset(DIMENSION);
   
      if (inputs.get(CELL_SIZE) < 5) inputs.set(CELL_SIZE,5);
      if (inputs.get(CELL_SIZE) > 30) inputs.set(CELL_SIZE,30);
      if (inputs.get(DIFFUSION) < 0) inputs.set(DIFFUSION,0);
      if (inputs.get(DIFFUSION) > 1) inputs.set(DIFFUSION,1);
      if (inputs.get(EVAPORATION) < 0) inputs.set(EVAPORATION,0);
      if (inputs.get(EVAPORATION) > 1) inputs.set(EVAPORATION,1);

   }

   public void update(Observable sender, Object aspect)
   {
      super.update(sender,aspect);
      if (aspect == DIMENSION)
      {
         width = entity().get(DIMENSION,0);
         height = entity().get(DIMENSION,1);
         space = new short[width][height];
         spaceNext = new short[width][height];
         occupied = new boolean[width][height];
            // These three arrays are initialized correctly by default with
            // zeros and false values (see ...)
         entity().changed("shape");
      }
      if (aspect == CELL_SIZE)
      {
         cellSize = entity().get(CELL_SIZE);
         entity().changed("shape");
         Heatbug.setBugsSize(cellSize);
         for (int i=0; i < model.bugs.size(); i++)
            ((Heatbug)model.bugs.get(i)).entity().changed("shape");
      }
   }

// ----- end of entity type definitions -----


   static final int MAX_TEMP = Short.MAX_VALUE;
   private static final int NUM_COLORS = 64;

   // encoding of relative neighbour position in this sequence:
   // northwest, north, northeast, east, southeast, south, southwest, west
   private static final Point[] NEIGHBOURS = new Point[]
   {
      new Point(-1,-1),
      new Point( 0,-1),
      new Point( 1,-1),
      new Point( 1, 0),
      new Point( 1, 1),
      new Point( 0, 1),
      new Point(-1, 1),
      new Point(-1, 0)
   };
   static final int NUM_NEIGHBOURS = NEIGHBOURS.length;


   private Model model;
   int width;
   int height;
   int cellSize;
   private short[][] space;
   private short[][] spaceNext;
   private boolean[][] occupied;
   private Color[] colorMap;


   public HeatSpace(Model m)
   {
      model = m;

      // colors for displaying different amounts of heat in the heat space
      colorMap = new Color[NUM_COLORS];
      for (int i=0; i < NUM_COLORS; i++)
      {
         float c = i/(NUM_COLORS-1f);
         colorMap[i] = new Color(c,0,0);
      }
   }


   private Color colorOfHeat(int heat)
   {
      return colorMap[heat*(NUM_COLORS-1)/MAX_TEMP];
   }

   int getHeatAt(Point coords)
   {
      return space[coords.x][coords.y];
   }

   void addHeat(Point coords, int someHeat)
   {
      space[coords.x][coords.y] =
         (short)StrictMath.min(space[coords.x][coords.y]+someHeat,MAX_TEMP);
   }

   Point neighbour(Point coords, int direction)
   {
      coords.x = (coords.x + NEIGHBOURS[direction].x + width) % width;
      coords.y = (coords.y + NEIGHBOURS[direction].y + height) % height;
      return coords;
   }
   
   void setOccupiedAt(Point coords, boolean flag)
   {
      occupied[coords.x][coords.y] = flag;
   }

   boolean isOccupiedAt(Point coords)
   {
      return occupied[coords.x][coords.y];
   }


   void resetSimulationRun()
   {
      for (int y=0; y < height; y++)
         for (int x=0; x < width; x++)
         {
            space[x][y] = 0;
            occupied[x][y] = false;
         }
      entity().changed("look");
   }

   void step()
   {
      double diff = entity().get(DIFFUSION);
      double evap = 1-entity().get(EVAPORATION);
      Point coords = new Point();
      for (int y=0; y < height; y++)
         for (int x=0; x < width; x++)
         {
            int heatAround = 0;
            for (int d=0; d < NUM_NEIGHBOURS; d++)
            {
               coords.x = x;
               coords.y = y;
               coords = neighbour(coords,d);
               heatAround += space[coords.x][coords.y];
            }
            heatAround = heatAround / NUM_NEIGHBOURS;
            int oldHeat = space[x][y];
            spaceNext[x][y] = (short)StrictMath.round(
               evap*(oldHeat + diff*(heatAround-oldHeat)));
         }
      short[][] tmp = space;
      space = spaceNext;
      spaceNext = tmp;
      entity().changed("look");
   }
}

Class MacroData

package de.vseit.heatbugs;

import java.util.Date;
import java.util.Iterator;

public class MacroData extends Object
{
   public Date time;
   public double averageUnhappiness;

   protected MacroData(final Model model)
   {
      time = model.currentTime();

      averageUnhappiness = 0;
      Iterator iter = model.bugs.iterator();
      while (iter.hasNext())
      {
         Heatbug bug = (Heatbug)iter.next();
         averageUnhappiness += bug.entity().get(Heatbug.UNHAPPINESS);
      }
      averageUnhappiness = averageUnhappiness / model.bugs.size();
   }
}

Class MainWindow

package de.vseit.heatbugs;

import de.vseit.simulation.MainPane;
import de.vseit.simulation.Simulation;
import javax.swing.JApplet;
import javax.swing.JFrame;


public class MainWindow extends MainPane
{
   protected MainWindow(JFrame myFrame, String[] args)
   {
      super(myFrame,args);
   }

   protected MainWindow(JApplet callingApplet)
   {
      super(callingApplet);
   }

   protected Simulation getNewSimulation()
   {
      return new Model(this);
   }

   protected void installCustomWindowTypes()
   {
   }

   public String getModelName()
   {
      return "Heatbugs";
   }

   public Class[] getRepositoryClasses()
   {
      return new Class[]{Heatbug.class,HeatSpace.class};
   }
}

Class Application

package de.vseit.heatbugs;

import de.vseit.simulation.MainPane;
import javax.swing.JFrame;

public class Application extends JFrame
{
   static void main(String[] args)
   {
      new Application(args);
   }

   Application(String[] args)
   {
      this.setBounds(20,20,970,720);
      MainPane p = new MainWindow(this,args);
      this.setRootPane(p);
      this.setTitle(p.getModelName());
      this.show();
   }
}

Class Applet

package de.vseit.heatbugs;

import de.vseit.simulation.MainPane;
import de.vseit.simulation.VSEitApplet;


public class Applet extends VSEitApplet
{
   public void init()
   {
      super.init();
      this.setRootPane(new MainWindow(this));
   }
}