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.
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(); } } }

