KH-Model
Winter 2017
Psychology 120
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 “human mate choice”, setting the build path project to MASON, a package “mateChoice”, and creating our basic classes. We need to create male and female agents, so our initial environment should look like this:
package mateChoice; import sim.engine.SimState; import sim.field.grid.SparseGrid2D; public class Environment extends SimState { AgentsWithGUI gui; SparseGrid2D space; int gridWidth = 100; int gridHeight = 100; int females = 1000; int males = 1000; double scaleK; //attractiveness scale public Environment(long seed) { super(seed); } public void setGui(AgentsWithGUI gui) { this.gui = gui; } public int getGridWidth() { return gridWidth; } public void setGridWidth(int gridWidth) { this.gridWidth = gridWidth; } public int getGridHeight() { return gridHeight; } public void setGridHeight(int gridHeight) { this.gridHeight = gridHeight; } 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 double getScaleK() { return scaleK; } public void setScaleK(double scaleK) { this.scaleK = scaleK; } public void placeAgents(){ for(int i = 0; i<females;i++){ int x = random.nextInt(gridWidth); int y = random.nextInt(gridHeight); double attractiveness = (double)random.nextInt((int)scaleK) + 1; Agent a = new Agent(this, x, y, true, attractiveness); space.setObjectLocation(a, x,y); schedule.scheduleRepeating(a); } for(int i = 0; i<males;i++){ int x = random.nextInt(gridWidth); int y = random.nextInt(gridHeight); double attractiveness = (double)random.nextInt((int)scaleK) + 1; Agent a = new Agent(this, x, y, false, attractiveness); space.setObjectLocation(a, x,y); schedule.scheduleRepeating(a); } } public void start(){ super.start(); space = new SparseGrid2D(gridWidth,gridHeight); placeAgents(); }
Next, let’s add the ability to control the color representation of agents. To do this, we need access to the AgentsWithGUI class instance. To do this we add to our Environment class:
AgentsWithGUI gui; public void setGui(AgentsWithGUI gui) { this.gui = gui; }
Then we add to the AgentsWithGUI class a line of code passing the AgentsWithGUI instance to the Environment:
public AgentsWithGUI() { super(new Environment(System.currentTimeMillis())); Environment e = (Environment)state; e.setGui(this); }
Finally, we can create an oval portrayal for each agent and use
state.gui.agentsPortrayal.setPortrayalForObject(this, o);
to set a portrayal for each agent:
package mateChoice; import java.awt.Color; import sim.engine.SimState; import sim.engine.Steppable; import sim.engine.Stoppable; import sim.portrayal.simple.OvalPortrayal2D; import sim.util.Bag; public class Agent implements Steppable { int x; int y; boolean female; double attractivenessA; double scaleK; double exponentN; double MaxD; double d=0; boolean similar; 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.attractivenessA = attractivenessA; this.scaleK = state.scaleK; float value =(float)(this.attractivenessA/this.scaleK); if(female) setColor(state, (float)1, (float)0, (float)0, value); else setColor(state, (float)0, (float)0, (float)1, value); } public Agent setColor(Environment state, float red, float green, float blue, float opacity){ Color c = new Color(red,green,blue,opacity); OvalPortrayal2D o = new OvalPortrayal2D(c); state.gui.agentsPortrayal.setPortrayalForObject(this, o); return this; } public void step(SimState state) { } }
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:
double exponentN; double MaxD; double d=0; boolean similar; Stoppable stop; public double chooseTheBest(Agent other){ return Math.pow(other.attractivenessA, exponentN)/ Math.pow(scaleK, exponentN); } public double chooseSimilar(Agent other){ return Math.pow(scaleK - Math.abs(other.attractivenessA - this.attractivenessA),exponentN)/Math.pow(scaleK, exponentN); } public double closingTimeRule(double p){ if(MaxD <= d){ return 1; //no more choosiness } else{ return Math.pow(p, (MaxD-d)/(MaxD)); } }
KH Model
Next, let’s create a method that implements Kalick and Hamilton human mate choice.
public void removeSelf(Environment state){ stop.stop(); //remove the agent from the schedule state.space.remove(this); //take it out of space, it ends up in the garbage } public void findDate(Environment state){ Bag all = state.space.getAllObjects(); int r = state.random.nextInt(all.numObjs);//start search Agent other = null; for(int i = r;i< all.numObjs;i++){ other = (Agent)all.objs[i]; if(other.female != this.female){ break; } else{ other = null; } } if(other == null){ for(int i = 0;i< r;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) state.correlation.getData(this.attractivenessA, other.attractivenessA); } else{ state.correlation.getData(other.attractivenessA, this.attractivenessA); } this.removeSelf(state); other.removeSelf(state); state.correlation.printData(); } } } public void step(SimState state) { findDate((Environment) state); }
Agent
Now we have all the code for the Agent class:
package mateChoice; import java.awt.Color; import sim.engine.SimState; import sim.engine.Steppable; import sim.engine.Stoppable; import sim.portrayal.simple.OvalPortrayal2D; import sim.util.Bag; public class Agent implements Steppable { int x; int y; boolean female; double attractivenessA; double scaleK; double exponentN; double maxD; double d=0; boolean similar; 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.attractivenessA = attractivenessA; this.scaleK = state.scaleK; float value =(float)(this.attractivenessA/this.scaleK); 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.exponentN = state.exponentN; this.maxD = state.maxD; } public Agent setColor(Environment state, float red, float green, float blue, float opacity){ Color c = new Color(red,green,blue,opacity); OvalPortrayal2D o = new OvalPortrayal2D(c); state.gui.agentsPortrayal.setPortrayalForObject(this, o); return this; } public double chooseTheBest(Agent other){ return Math.pow(other.attractivenessA, exponentN)/ Math.pow(scaleK, exponentN); } public double chooseSimilar(Agent other){ return Math.pow(scaleK - Math.abs(other.attractivenessA - this.attractivenessA),exponentN)/Math.pow(scaleK, exponentN); } public double closingTimeRule(double p){ if(maxD <= d){ return 1; //no more choosiness } else{ return Math.pow(p, (maxD-d)/(maxD)); } } public void removeSelf(Environment state){ stop.stop(); //remove the agent from the schedule state.space.remove(this); //take it out of space, it ends up in the garbage } public void findDate(Environment state){ Bag all = state.space.getAllObjects(); int r = state.random.nextInt(all.numObjs);//start search Agent other = null; for(int i = r;i< all.numObjs;i++){ other = (Agent)all.objs[i]; if(other.female != this.female){ break; } else{ other = null; } } if(other == null){ for(int i = 0;i< r;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) state.correlation.getData(this.attractivenessA, other.attractivenessA); } else{ state.correlation.getData(other.attractivenessA, this.attractivenessA); } this.removeSelf(state); other.removeSelf(state); state.correlation.printData(); } } } public void step(SimState state) { findDate((Environment) state); }
Environment
And all of the code for the environment:
package mateChoice; import sim.engine.SimState; import sim.field.grid.SparseGrid2D; public class Environment extends SimState { AgentsWithGUI gui; SparseGrid2D space; Correlation correlation; int gridWidth = 100; int gridHeight = 100; int females = 1000; int males = 1000; boolean similar = true; double scaleK = 10; double exponentN = 3; double maxD = 50; public Environment(long seed) { super(seed); } public void setGui(AgentsWithGUI gui) { this.gui = gui; } public int getGridWidth() { return gridWidth; } public void setGridWidth(int gridWidth) { this.gridWidth = gridWidth; } public int getGridHeight() { return gridHeight; } public void setGridHeight(int gridHeight) { this.gridHeight = gridHeight; } 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 getScaleK() { return scaleK; } public void setScaleK(double scaleK) { this.scaleK = scaleK; } public double getExponentN() { return exponentN; } public void setExponentN(double exponentN) { this.exponentN = exponentN; } public double getMaxD() { return MaxD; } public void setMaxD(double maxD) { maxD = maxD; } public void placeAgents(){ for(int i = 0; i<females;i++){ int x = random.nextInt(gridWidth); int y = random.nextInt(gridHeight); double attractiveness = (double)random.nextInt((int)scaleK) + 1; Agent a = new Agent(this, x, y, true, attractiveness); space.setObjectLocation(a, x,y); a.stop = schedule.scheduleRepeating(a); } for(int i = 0; i<males;i++){ int x = random.nextInt(gridWidth); int y = random.nextInt(gridHeight); double attractiveness = (double)random.nextInt((int)scaleK) + 1; Agent a = new Agent(this, x, y, false, attractiveness); space.setObjectLocation(a, x,y); a.stop = schedule.scheduleRepeating(a); } } public void start(){ super.start(); space = new SparseGrid2D(gridWidth,gridHeight); correlation = new Correlation(); placeAgents(); }
Correlation
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:
package mateChoice; import sim.util.Bag; import sim.util.Double2D; public class Correlation { 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; }

