Banner
KH-Model-290

KH-Model

Spring 2017

PSC/ANB 290


Introduction

Using models to understand systems and processes such as human mate choice involves working from the simple to the more complex.  The reason we must do this is that if we start with a model that is too complex, it can be almost as hard to analyze and understand as mate choice involving people.  So, we will start by re-creating Kalick & Hamilton’s (1986) original model.  Once we have the original model, we can explore it and add more realistic features piece by piece and determine how different features change the behavior of the family of models we build.

Powerpoint introduction

We will begin by creating a package “KH-Model-PramSweep”, setting the build path project to MASON and MASONex, a package “kh_model”, and creating our basic classes.

Todays Project:  KH-Model-PramSweep


Environment

First we will create an Environment Class that extends SimStateSweep.



package kh_model;


import sweep.SimStateSweep;


public class Environment extends SimStateSweep {
	public CorrelationProbe probe = null;

	public Environment(long seed, Class observer) {
		super(seed, observer);
		// TODO Auto-generated constructor stub
	}
	
	public void makeAgents(){
		
	}
	
	public void start(){
		super.start();
		make2DSpace(false, gridWidth, gridHeight);
		makeAgents();
		if(observer != null){
			if(objSpace)
				observer.initObjectGrid(objectSpace);
			else
				observer.initSparseGrid(sparseSpace);
			((Experimenter)observer).setProbe(probe);
		}
	}
}

Agents

Next, let’s create a class to define our Agents.



package kh_model;

import sim.engine.SimState;
import sim.engine.Steppable;

public class Agent implements Steppable {

	public void step(SimState state) {
		
	}
}


Observer

One problem we have yet to address is how we get data out of agent-based models other than graphically observing agent behavior.  On each step in a simulation, agents are randomly lined up in the schedule and there step method is called.  This is repeated over and over again until the simulation is stopped.  One approach to the problem of collecting data is to introduce a new agent that can observe other agents, record and save the data we want.

In MASONex, there is a basic Observer class agent that we will extend to make an Experimenter.



package kh_model;

import observer.Observer;
import sim.engine.SimState;
import sweep.ParameterSweeper;
import sweep.SimStateSweep;

public class Experimenter extends Observer {
	
	public CorrelationProbe probe = null;

	
	public Experimenter(String fileName, String folderName, SimStateSweep state, ParameterSweeper sweeper,
			String precision, String[] headers) {
		super(fileName, folderName, state, sweeper, precision, headers);
		// TODO Auto-generated constructor stub
	}
	
	public CorrelationProbe getProbe() {
		return probe;
	}

	public void setProbe(CorrelationProbe probe) {
		this.probe = probe;
	}
        public void step(SimState state){
		super.step(state);
	}
}

Probes

Another issue is how to collect data when you want it.  An observer can collect data on the state of agents when its step method is called, but agents perform interactions that may be missed or agents maybe removed before they can be observed.  To handle this problem, we create probes that can be inserted any where we like in agents.  These probes collect data and the Experimenter/Observer can read these probes and save the data.

Kalick & Hamilton’s (1986) compared their simulation results to correlations reported in the literature. So we need to calculate correlations for the simulation results.

To create a class that can calculate Pearson correlations, we could implement the following class for a probe:



package mateChoice;

import sim.util.Bag;
import sim.util.Double2D;


public class CorrelationProbe {
	double sXY = 0;
	double sX = 0;
	double sY =0;
	double sX2 = 0;
	double sY2 = 0;
	double n = 0;
	double x = 0;
	double y = 0;
	boolean headers = false;
	
/**
 * Use this method add two data points (doubles) to an on going collection of data.  Correlation is
 * initialized to 0 data. It is assumed that the use always enters the x and y data points in the same
 * order.  For example, if data are on females and males, then if x = females and y = males, then the
 * data are always added as getData(x(female), y(male)).
 * @param x
 * @param y
 */
	public void getData(double x, double y){
		sXY += x*y;
		sX += x;
		sY += y;
		sX2 += x*x;
		sY2 += y*y;
		n++;
		this.x = x;
		this.y = y;
	}
	
	public void printColumnHeaders(){
		System.out.println("pairs\t"+"mean1\t"+"mean2\t"+"correlation");
	}
	/**
	 * Prints out the number of data pairs, the means for the first and second pair and
	 * the correlation. These are running computations and can be printed out at any
	 * time during the data collection process.
	 */
	public void printData(){
		if(!headers){
			printColumnHeaders();
			headers = true;
		}
		if(n>0)
			System.out.println(n +"\t"+ sX/n +"\t"+ sY/n+"\t"+correlation());
		else
			System.out.println(0 +"\t"+ 0 +"\t"+ 0+"\t"+0);
	}
	
	public Double2D means(){
		return new Double2D(sX/n, sY/n);
	}

	public double correlation(){
		double r = (sXY - (sX*sY)/n)/Math.sqrt((sX2-(sX*sX)/n)*(sY2-(sY*sY)/n));
		return r;
	}

	public static double mean(Bag array){
		double x =0;

		for(int i=0; i< array.numObjs;i++){
			x += (Double)array.get(i);
		}

		return x/(double)array.numObjs;
	}

	public static double ssq(Bag array, double mean){
		double x=0;
		for(int i=0; i< array.numObjs;i++){
			double y = (Double)array.get(i)-mean;
			x += y*y;
		}

		return x;
	}

	public static double sumXY(Bag arrayX, Bag arrayY, double meanX, double meanY){
		double xy = 0;
		for(int i =0; i< arrayX.numObjs;i++){
			double x = (Double)arrayX.get(i)-meanX;
			double y = (Double)arrayY.get(i)-meanY;
			xy += x*y;
		}
		return xy;
	}

	public static double correlation(Bag arrayX, Bag arrayY){
		if(arrayX.numObjs != arrayY.numObjs){
			System.err.println("Arrays are not equal!");
			return -100;
		}
		double r = 0;
		double meanX = mean(arrayX);
		double meanY = mean(arrayY);
		double ssqX = ssq(arrayX, meanX);
		double ssqY = ssq(arrayY, meanY);
		double sXY = sumXY(arrayX,arrayY,meanX,meanY);

		r = sXY/Math.sqrt(ssqX * ssqY);
		return r;
	}
}


AgentGUI

As we have seen, creating graphical user interfaces in MASON is messy, however in MASONex, I have created a GUIStateSweep class that allows the user to quickly implement a graphical user interface.  The initialize method takes the following arguments:

 Class simstatesweep  --> This is the simulation environment class you created by extending SimStateSweep
 Class observer       --> This is the Observer class agent you created by extending Observer
 Class theGUIsubClass --> This is the GUIStateSweep class you created by extending GUIStateSweep
 int gridWidth        --> This is the width of the display window
 int gridHeight       --> This is the Height of the display window
 Color backdrop       --> This is the color of the display window
 Color agentDefaultColor --> The color of agents if the default display for agents is true
 boolean agentPortrayal --> If true, then the default color in the previous parameter is used
 boolean objspace     --> If true and ObjectGrid2Dex is used else a SparseGrid2Dex is used.

We can also add some files that will be used in the about tab in the graphical user interface:
Extras.zip


package kh_model;

import java.awt.Color;
import sweep.GUIStateSweep;
import sweep.SimStateSweep;

public class AgentsGUI extends GUIStateSweep {
	
	
	public static void main(String[] args) {
		initialize (Environment.class,Experimenter.class,AgentsGUI.class, 
                                 400, 400, Color.WHITE, Color.BLUE,false,false);
	}

	public AgentsGUI(SimStateSweep state, int gridWidth, int gridHeight, Color backdrop, Color agentDefaultColor,
			boolean agentPortrayal) {
		super(state, gridWidth, gridHeight, backdrop, agentDefaultColor, agentPortrayal);
		// TODO Auto-generated constructor stub
	}

}


Agents: Decision Rules


Kalick & Hamilton’s (1986) had two main decision rule that we will implement (they had a third, but it is odd to say the least). In their general form we have an equation for choosing the most attractive partner and one for choosing the most similar.

 

 

 

and

 

 

 

 

where A is attractiveness with other, self, and maximum attractiveness are indicated by subscripts and d is the number of dates with the maximum and the number of actual dates are indicated by subscripts. Let’s implement these rules in our model. These two rule can be written in Java as:


KH Model

Next, let’s create a method that implements Kalick and Hamilton human mate choice.

 


Agent

Now we have all the code for the Agent class:



package kh_model;

import sim.engine.SimState;
import sim.engine.Steppable;
import java.awt.Color;
import sim.engine.Stoppable;
import sim.portrayal.simple.OvalPortrayal2D;
import sim.util.Bag;
import sweep.GUIStateSweep;
import sweep.SimStateSweep;

public class Agent implements Steppable {
	int x;
	int y;
	boolean female;
	double attractiveness;
	double maxAttractivenss;
	double choosiness;
	double maxDates;
	double d=0;
	boolean similar;
	CorrelationProbe probe;
	Stoppable stop;

	
	public Agent(Environment state, int x, int y, boolean female, double attractivenessA) {
		super();
		this.x = x;
		this.y = y;
		this.female = female;
		this.attractiveness = attractivenessA;
		this.maxAttractivenss = state.maxAttractiveness;
		float value =(float)(this.attractiveness/this.maxAttractivenss);
		if(female)
			setColor(state, (float)1, (float)0, (float)0, value);
		else
			setColor(state, (float)0, (float)0, (float)1, value);
		this.similar = state.similar;
		this.choosiness = state.choosiness;
		this.maxDates = state.maxDates;
		this.probe = state.probe;
	}
	
	public void setColor(SimStateSweep state, float red, float green, float blue, float opacity){
		Color c = new Color(red,green,blue,opacity);
		OvalPortrayal2D o = new OvalPortrayal2D(c);
		GUIStateSweep guiState = (GUIStateSweep)state.gui;
		if(state.objSpace)
			guiState.agentsPortrayalObject.setPortrayalForObject(this, o);
		else
			guiState.agentsPortrayalSparseGrid.setPortrayalForObject(this, o);
	}	
	
	public double chooseTheBest(Agent other){
		return  Math.pow(other.attractiveness, choosiness)/ Math.pow(maxAttractivenss, choosiness);
	}
	
	public double chooseSimilar(Agent other){
		return Math.pow(maxAttractivenss - Math.abs(other.attractiveness 
                  - this.attractiveness),choosiness)/Math.pow(maxAttractivenss, choosiness);
	}

	public double closingTimeRule(double p){
		if(maxDates <= d){
			return 1; //no more choosiness
		}
		else{
			return Math.pow(p, (maxDates-d)/(maxDates));
		}
	}
	
	public void removeSelf(Environment state){
		stop.stop(); //remove the agent from the schedule
		state.sparseSpace.remove(this); //take it out of space, it ends up in the garbage
	}
	
	public void findDate(Environment state){
		Bag all = new Bag(state.sparseSpace.getAllObjects());
		all.shuffle(state.random); //randomly shuffle them
		Agent other = null;
		for(int i = 0;i< all.numObjs;i++){
			other = (Agent)all.objs[i];
			if(other.female != this.female){
				break;
			}
			else{
				other = null;
			}
		}
	
		double p1 = 0; //choosing agent
		double p2 = 0; //selected agent
		if(other != null){
			if(similar){
				p1 = chooseSimilar(other);
				p2 = other.chooseSimilar(this);
			}
			else{
				p1 = chooseTheBest(other);
				p2 = other.chooseTheBest(this);
			}
			p1 = closingTimeRule(p1); //correct for closing time rule
			p2 = other.closingTimeRule(p2);
			d++;// increment d
			other.d++;// increment d
			
			//make joint decision
			double p = p1 * p2; //joint probability
			if(state.random.nextBoolean(p)){
				if(this.female){ //(female, male)
					probe.getData(this.attractiveness, other.attractiveness);
				}
				else{
					probe.getData(other.attractiveness, this.attractiveness);
				}
				this.removeSelf(state);
				other.removeSelf(state);
				//probe.printData();
			}
			
		}
	}

	public void step(SimState state) {
		findDate((Environment) state);

	}
}

Environment

And all of the code for the environment:



package kh_model;

import sim.util.Int2D;
import sweep.SimStateSweep;


public class Environment extends SimStateSweep {
	public CorrelationProbe probe = null;
	public int females = 1000;
	public int males = 1000;
	public boolean similar = true;
	public double maxAttractiveness = 10;
	public double choosiness = 3;
	public double maxDates = 50;

	public Environment(long seed, Class observer) {
		super(seed, observer);
		// TODO Auto-generated constructor stub
	}
	
	public int getFemales() {
		return females;
	}

	public void setFemales(int females) {
		this.females = females;
	}

	public int getMales() {
		return males;
	}

	public void setMales(int males) {
		this.males = males;
	}

	public boolean isSimilar() {
		return similar;
	}

	public void setSimilar(boolean similar) {
		this.similar = similar;
	}

	public double getMaxAttractiveness() {
		return maxAttractiveness;
	}

	public void setMaxAttractiveness(double maxAttractiveness) {
		this.maxAttractiveness = maxAttractiveness;
	}

	public double getChoosiness() {
		return choosiness;
	}

	public void setChoosiness(double choosiness) {
		this.choosiness = choosiness;
	}

	public double getMaxDates() {
		return maxDates;
	}

	public void setMaxDates(double maxDates) {
		this.maxDates = maxDates;
	}

	public Int2D findUniqueLocation(){
		int x = random.nextInt(gridWidth);
		int y = random.nextInt(gridHeight);
		while(sparseSpace.getObjectsAtLocation(x, y) != null){
			x = random.nextInt(gridWidth);
			y = random.nextInt(gridHeight);
		}
		return new Int2D(x,y);
	}
	
	public void makeAgents(){
		for(int i= 0;i<females;i++){
			Int2D location = findUniqueLocation();
			double attractiveness = (int)(random.nextDouble() * maxAttractiveness) + 1;
			Agent a = new Agent(this,location.x, location.y,true, attractiveness);
			a.stop = schedule.scheduleRepeating(a);
			sparseSpace.setObjectLocation(a, location);
		}
		
		for(int i= 0;i<males;i++){
			Int2D location = findUniqueLocation();
			double attractiveness = (int)(random.nextDouble() * maxAttractiveness) + 1;
			Agent a = new Agent(this,location.x, location.y,false, attractiveness);
			a.stop = schedule.scheduleRepeating(a);
			sparseSpace.setObjectLocation(a, location);
		}
	}
	
	public void start(){
		super.start();
		make2DSpace(false, gridWidth, gridHeight);
		probe = new CorrelationProbe();
		makeAgents();
		if(observer != null){
			if(objSpace)
				observer.initObjectGrid(objectSpace);
			else
				observer.initSparseGrid(sparseSpace);
			((Experimenter)observer).setProbe(probe);
		}
	}
}


Experimenter

And all of the code for the environment:



package kh_model;

import observer.Observer;
import sim.engine.SimState;
import sweep.ParameterSweeper;
import sweep.SimStateSweep;

public class Experimenter extends Observer {
	
	public CorrelationProbe probe = null;

	
	public Experimenter(String fileName, String folderName, SimStateSweep state, ParameterSweeper sweeper,
			String precision, String[] headers) {
		super(fileName, folderName, state, sweeper, precision, headers);
		// TODO Auto-generated constructor stub
	}
	
	public CorrelationProbe getProbe() {
		return probe;
	}

	public void setProbe(CorrelationProbe probe) {
		this.probe = probe;
	}
	
	public boolean nextInterval(){
		if(probe.n > 1){
			data.add(probe.n);
			data.add(probe.sX/probe.n);
			data.add(probe.sY/probe.n);
			data.add(probe.correlation());
		}
		else{
			data.add(0);
			data.add(0);
			data.add(0);
			data.add(0);
		}
		
		return true;
	}

	public void step(SimState state){
		super.step(state);
		if(getdata){ //Get data is true at each collection interval
			nextInterval();
		}
	}

}