Banner
The KH-Model

The KH-Model

Winter 2016

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.

Let’s download and install the complete version:

particle_to_agents_MC.zip

Now that we are building professional-level models, they are more complicated and to type in the code during class would take too long.

Reuse

As I said towards the beginning of this course, using an object-oriented language such as Java is not just intuitive for developing agent-based models, but it also allows us to reuse code that we have already developed.  In the case of human mate choice, not only do we want to replicate and analyze a parameterized version of Kalick and Hamilton’s (1986) agent-based mate choice model, but we would also like to investigate the role of space and movement on mate choice.  So, far we have spent a lot of time developing a particle model in which agents can move randomly, aggregate, and coordinate their behavior.  It would be great if we could use what we have already programmed and add on the code we need for a Kalick and Hamilton style human mate choice model.

The first place to start is to extend the Particle class to an Agent class.


package particles;

public class Agent extends Particle{

public void step(SimState state){
		MateChoiceEnvironment e = (MateChoiceEnvironment)state;
		switch (e.simulationType){
		case MateChoiceEnvironment.AGGREGATE_COORDINATE:
			super.step(state);
			break;
		case MateChoiceEnvironment.KH:
			kh_model(state);
			break;
		case MateChoiceEnvironment.KHM:
			super.step(state);
			khm_model(state);
			break;
		default:
			kh_model(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:


 

	public double chooseBest(Agent other){
		double closingTime = 0;
		if(dateNumber <= maxDates){ 
                     closingTime = ((double)maxDates -(double)dateNumber)/(double)maxDates; 
                 } 
                 else { 
                     closingTime = 0; 
                 } 
                 double pBest = Math.pow(other.attractiveness/maxAttractiveness, r*closingTime); 
                if(pBest > 1){
			pBest = 1;
		}
		else if(pBest < 0){
			pBest = 0;
		}
		return pBest;
	}

	public double chooseSimilar(Agent agent1, Agent agent2){
		double closingTime = 0;
		if(dateNumber <= maxDates){ 
                     closingTime = ((double)maxDates 
                          -(double)dateNumber)/(double)maxDates; 
                } 
                else { 
                    closingTime = 0; 
                } 
                double pSimilar = Math.pow((maxAttractiveness - 
                        Math.abs(agent2.attractiveness - agent1.attractiveness)
                          )/maxAttractiveness, r*closingTime); 
                if(pSimilar > 1)
                {
			pSimilar = 1;
		}
		else if(pSimilar < 0){
			pSimilar = 0;
		}
		return pSimilar;
	}

 


KH Model

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


 

       public void kh_model(SimState state){
		MateChoiceEnvironment e = (MateChoiceEnvironment)state;
		Agent f, m;
		boolean match = false; //assume a failed match
		if(this.sexID == MateChoiceEnvironment.FEMALE){
			if(e.malePop.numObjs>0)
				m = (Agent)e.malePop.get(e.random.nextInt(e.malePop.numObjs));//get a random male
			else
				m=null;
			f = this;
		}
		else {
			if(e.femalePop.numObjs>0)
				f = (Agent)e.femalePop.get(e.random.nextInt(e.femalePop.numObjs));//get a random female
			else
				f=null;
			m = this;
		}

		if(chooseTheBest && (f != null && m != null)){
			match = e.random.nextBoolean(chooseBest(f)) && e.random.nextBoolean(chooseBest(m));
		}
		else if(f != null && m != null) {//choose similar
			match = e.random.nextBoolean(chooseSimilar(f,m));
		}

		if(match){
			PC.getData(f.attractiveness, m.attractiveness); //get data
			e.malePop.remove(m);
			m.removeAgent(state);
			e.femalePop.remove(f);
			f.removeAgent(state);	
		}
		else if(f != null && m != null) { //update only if there was a date successful or not
			f.dateNumber++;
			m.dateNumber++;	
		}
	}

 


KH Model with Space and Movement

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


 

      public Bag sortBySex(SimState state, Bag neighbors, int sexID){
		Bag dates = new Bag();
		for(int i=0;i<neighbors.numObjs;i++){ 
                     Agent a = (Agent)neighbors.objs[i]; 
                     if(a.sexID == sexID){ 
                            dates.add(a); 
                     } 
                 } 
                 return dates; 
     } 

       public void khm_model(SimState state){ 
             MateChoiceEnvironment e = (MateChoiceEnvironment)state; 
             Agent f, m; 
             boolean match = false; //assume a failed match 
             if(this.sexID == MateChoiceEnvironment.FEMALE){ 
                  Bag neighbors = e.particleSpace.getNeighborsMaxDistance(x, y, dateSearchRadius, !boundaries, null, null, null); 
                  Bag dates = sortBySex(state,neighbors,MateChoiceEnvironment.MALE); 
                  if(dates.numObjs>0)
			m = (Agent)dates.get(e.random.nextInt(dates.numObjs));//get a random male
			else
				m=null;
			f = this;
		}
		else {//else male
			Bag neighbors = e.particleSpace.getNeighborsMaxDistance(x, y,dateSearchRadius, !boundaries, null, null, null);
			Bag dates = sortBySex(state,neighbors,MateChoiceEnvironment.FEMALE);
		if(dates.numObjs>0)
			f = (Agent)dates.get(e.random.nextInt(dates.numObjs));//get a random female
			else
				f = null;
			m = this;
		}

		if(chooseTheBest && (f != null && m != null)){
			match = e.random.nextBoolean(chooseBest(f)) && e.random.nextBoolean(chooseBest(m));
		}
		else if(f != null && m != null) {//choose similar
			match = e.random.nextBoolean(chooseSimilar(f,m));
		}

		if(match){
			PC.getData(f.attractiveness, m.attractiveness); //get data
			e.malePop.remove(m);
			m.removeAgent(state);
			e.femalePop.remove(f);
			f.removeAgent(state);	
		}
		else if(f != null && m != null) { //update only if there was a date successful or not
			f.dateNumber++;
			m.dateNumber++;	
		}
	}

Graphical Representation of Agents

Next it is useful to represent agents in a meaningful way.  To do this, we will color-code males (blue) and females (red).  To color-code attractiveness, we will make agents more transparent (reduce alpha) the less attractive they are.


    public OvalPortrayal2D setColor(SimState state){
		MateChoiceEnvironment e = (MateChoiceEnvironment)state;
		OvalPortrayal2D o = null;
		if(sexID == e.MALE){
			Color c = new Color((float)0,(float)0,(float)1,(float)
					((float)this.attractiveness/(float)maxAttractiveness));
			o = new OvalPortrayal2D(c);
			e.particlesPortrayal.setPortrayalForObject(this, o);   
		}
		else{
			Color c = new Color((float)1,(float)0,(float)0,(float)
					((float)this.attractiveness/(float)maxAttractiveness));
			o = new OvalPortrayal2D(c);
			e.particlesPortrayal.setPortrayalForObject(this, o);    
		}
		return o;
	}



Agent

Now we have all the code for the Agent class:


package particles;

import java.awt.Color;

import sim.engine.SimState;
import sim.engine.Stoppable;
import sim.portrayal.simple.OvalPortrayal2D;
import sim.util.Bag;

public class Agent extends Particle{
	public int sexID; //An agent's biological sex
	public double attractiveness;
	public double maxAttractiveness;
	public OvalPortrayal2D myPortrayal2D;
	public int dateNumber = 0;
	public int maxDates;
	public double r;
	public boolean chooseTheBest;
	public Stoppable event = null;
	PearsonCorrelation PC;
	public int dateSearchRadius;


	public void setPC(PearsonCorrelation pC) {
		PC = pC;
	}

	public void setDateSearchRadius(int dateSearchRadius) {
		this.dateSearchRadius = dateSearchRadius;
	}

	public void setChooseTheBest(boolean chooseTheBest) {
		this.chooseTheBest = chooseTheBest;
	}

	public void setR(double r) {
		this.r = r;
	}

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

	public void setSexID(int sexID) {
		this.sexID = sexID;
	}

	public void setAttractiveness(double attractiveness) {
		this.attractiveness = attractiveness;
	}

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

	public OvalPortrayal2D setColor(SimState state){
		MateChoiceEnvironment e = (MateChoiceEnvironment)state;
		OvalPortrayal2D o = null;
		if(sexID == e.MALE){
			Color c = new Color((float)0,(float)0,(float)1,(float)
					((float)this.attractiveness/(float)maxAttractiveness));
			o = new OvalPortrayal2D(c);
			e.particlesPortrayal.setPortrayalForObject(this, o);   
		}
		else{
			Color c = new Color((float)1,(float)0,(float)0,(float)
					((float)this.attractiveness/(float)maxAttractiveness));
			o = new OvalPortrayal2D(c);
			e.particlesPortrayal.setPortrayalForObject(this, o);    
		}
		return o;
	}

	public double chooseBest(Agent other){
		double closingTime = 0;
		if(dateNumber <= maxDates){ 
                      closingTime = ((double)maxDates 
                          -(double)dateNumber)/(double)maxDates; 
                 } 
                else { closingTime = 0; 
                } 
                     double pBest = Math.pow(other.attractiveness/maxAttractiveness, 
                      r*closingTime); 
                if(pBest > 1){
			pBest = 1;
		}
		else if(pBest < 0){
			pBest = 0;
		}
		return pBest;
	}

	public double chooseSimilar(Agent agent1, Agent agent2){
		double closingTime = 0;
		if(dateNumber <= maxDates){ 
                     closingTime = ((double)maxDates -
                         (double)dateNumber)/(double)maxDates; 
                } 
                else { 
                     closingTime = 0; 
                } 
                     double pSimilar = Math.pow((maxAttractiveness -
                        Math.abs(agent2.attractiveness 
                            - agent1.attractiveness))/maxAttractiveness, 
                           r*closingTime); 
                if(pSimilar > 1){
			pSimilar = 1;
		}
		else if(pSimilar < 0){ 
                       pSimilar = 0; 
                } 
                      return pSimilar; 
} 

      public void removeAgent(SimState state){ 
        MateChoiceEnvironment e = (MateChoiceEnvironment)state; 
        this.event.stop(); 
        e.particleSpace.remove(this); 
       } 

       public void kh_model(SimState state){ 
        MateChoiceEnvironment e = (MateChoiceEnvironment)state; 
        Agent f, m; 
        boolean match = false; //assume a failed match 
        if(this.sexID == MateChoiceEnvironment.FEMALE){ 
               if(e.malePop.numObjs>0)
		m = (Agent)e.malePop.get(e.random.nextInt(e.malePop.numObjs));//get a random male
		else
			m=null;
		f = this;
		}
		else {
			if(e.femalePop.numObjs>0)
				f = (Agent)e.femalePop.get(e.random.nextInt(e.femalePop.numObjs));//get a random female
			else
			f=null;
			m = this;
		}

		if(chooseTheBest && (f != null && m != null)){
			match = e.random.nextBoolean(chooseBest(f)) && e.random.nextBoolean(chooseBest(m));
		}
		else if(f != null && m != null) {//choose similar
			match = e.random.nextBoolean(chooseSimilar(f,m));
		}

		if(match){
			PC.getData(f.attractiveness, m.attractiveness); //get data
			e.malePop.remove(m);
			m.removeAgent(state);
			e.femalePop.remove(f);
			f.removeAgent(state);	
		}
		else if(f != null && m != null) { //update only if there was a date successful or not
			f.dateNumber++;
			m.dateNumber++;	
		}
	}

	public Bag sortBySex(SimState state, Bag neighbors, int sexID){
		Bag dates = new Bag();
		for(int i=0;i<neighbors.numObjs;i++){ 
                      Agent a = (Agent)neighbors.objs[i]; 
                      if(a.sexID == sexID){ 
                          dates.add(a); 
                      }
               } 
               return dates; 
        } 

       public void khm_model(SimState state){ 
              MateChoiceEnvironment e = (MateChoiceEnvironment)state; 
              Agent f, m; 
              boolean match = false; //assume a failed match 
              if(this.sexID == MateChoiceEnvironment.FEMALE){ 
                   Bag neighbors = e.particleSpace.getNeighborsMaxDistance(x, y, dateSearchRadius, !boundaries, null, null, null); 
                   Bag dates = sortBySex(state,neighbors,MateChoiceEnvironment.MALE); 
              if(dates.numObjs>0)
		    m = (Agent)dates.get(e.random.nextInt(dates.numObjs));//get a random male
	      else
		    m=null;
		f = this;
		}
		else {//else male
			Bag neighbors = e.particleSpace.getNeighborsMaxDistance(x, y, dateSearchRadius, !boundaries, null, null, null);
			Bag dates = sortBySex(state,neighbors,MateChoiceEnvironment.FEMALE);
			if(dates.numObjs>0)
				f = (Agent)dates.get(e.random.nextInt(dates.numObjs));//get a random female
			else
				f = null;
			m = this;
		}

		if(chooseTheBest && (f != null && m != null)){
			match = e.random.nextBoolean(chooseBest(f)) && e.random.nextBoolean(chooseBest(m));
		}
		else if(f != null && m != null) {//choose similar
			match = e.random.nextBoolean(chooseSimilar(f,m));
		}

		if(match){
			PC.getData(f.attractiveness, m.attractiveness); //get data
			e.malePop.remove(m);
			m.removeAgent(state);
			e.femalePop.remove(f);
			f.removeAgent(state);	
		}
		else if(f != null && m != null) { //update only if there was a date successful or not
			f.dateNumber++;
			m.dateNumber++;	
		}
	}

	public void step(SimState state){
		MateChoiceEnvironment e = (MateChoiceEnvironment)state;
		switch (e.simulationType){
		case MateChoiceEnvironment.AGGREGATE_COORDINATE:
			super.step(state);
			break;
		case MateChoiceEnvironment.KH:
			kh_model(state);
			break;
		case MateChoiceEnvironment.KHM:
			super.step(state);
			khm_model(state);
			break;
		default:
			kh_model(state);
		}
	}

}

Observer

How do we collect data on virtual males and females?  Let’s create a new class called the “Observer”, which is an agent that does the data collection. One way to build an observer is this:


 

package particles;

import sim.engine.SimState;
import sim.engine.Steppable;
import sim.engine.Stoppable;
import sim.util.Bag;
import sim.util.Double2D;

public class Observer implements Steppable{
	Bag malePop;
	Bag femalePop;
	MateChoiceEnvironment se;
	int paired = 0;
	Stoppable event;
	PearsonCorrelation correlation;


	public void step(SimState state) {
		printDataKH();
		testEndKH( state);
	}

	public void printDataKH(){
		if(se.schedule.getTime()<1){ 
                    System.out.println("Time r pairs meanF meanM"); 
                     } 
                Double2D c = correlation.means(); 
                String r = new String().format("%.3f",correlation.correlation()); 
                String meanf = new String().format("%.3f",c.x); 
                String meanm = new String().format("%.3f",c.y); 
                System.out.println(se.schedule.getTime()+" "+r+ " "+ 
                   correlation.n+ " "+ meanf+ " "+meanm); 
        } 
        public void testEndKH(SimState state){ 
             if(malePop.numObjs == 0 || femalePop.numObjs ==0){ 
                   event.stop(); 
                   if(malePop.numObjs > 0){
			for(int k=0; k<malePop.numObjs;k++){ 
                                Agent a = (Agent)malePop.objs[k]; 
                                a.removeAgent(state); 
                            }
                    } 
                  if(femalePop.numObjs > 0){
			for(int k=0; k<femalePop.numObjs;k++){
				Agent a = (Agent)femalePop.objs[k];
				a.removeAgent(state);
				}
			}
		}
	}

	public Observer(MateChoiceEnvironment state, PearsonCorrelation correlation){

		this.malePop = state.malePop;
		this.femalePop = state.malePop;
		event = state.schedule.scheduleRepeating(this, 100, 1);
		se = state;
		this.correlation = correlation;
	}

	public void removeAgent(Agent a){
		a.event.stop();
		se.particleSpace.remove(a);
	}

}



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 particles;

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

public class PearsonCorrelation {
	double sXY = 0;
	double sX = 0;
	double sY =0;
	double sX2 = 0;
	double sY2 = 0;
	double n = 0;

	public void getData(double x, double y){
		sXY += x*y;
		sX += x;
		sY += y;
		sX2 += x*x;
		sY2 += y*y;
		n++;
	}

	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;
	}

}

 


MateChoiceEnvironment: Start Method

Now, let’s go to the MateChoiceEnvironment, and see what we need to add and change.


       public void start(){
		super.start();
		particleSpace = new SparseGrid2D(gridWidth, gridHeight); //create a 2D
		femalePop = new Bag(females);
		malePop = new Bag(males);
		PearsonCorrelation correlation = new PearsonCorrelation();
		Observer ex = new Observer(this,correlation);
                   //space for our agents.

                makeMalesandFemales(femalePop,malePop,correlation );
}