Defining new neighborhoods

The standard model provides the following neighborhoods: Simple variations and combinations of these can be typed directly into the shell.  The following code defines a new NeighborhoodConstructor that returns one of three randomly chosen neighborhoods of different shapes and sizes. (Trying typing it in and see how it works.)
bsh% mixed_nbhd = new NeighborhoodConstructor() {
NeighborhoodConstructor n1 = new SquareNeighborhood(1);
NeighborhoodConstructor n2 = new RoundNeighborhood(2);
NeighborhoodConstructor n3 = new SquareNeighborhood(3);
LinkedList getNeighborhood(int i, int j, int w, int h, Agent[][] world, boolean b) {
double r = Math.random();
if (r < 0.333)
return n1.getNeighborhood(i,j,w,h,world,b);
else if (r < 0.666)
return n2.getNeighborhood(i,j,w,h,world,b);
else
return n3.getNeighborhood(i,j,w,h,world,b);
}
};
bsh% SetDefaultNeighborhood( mixed_nbhd );
Run the model forward a number of steps to see if you can tell the difference.  You can always check to verify that non-uniform neighborhoods are being used by setting the strategies of all neighbors of several randomly chosen people to certain distinct values.

For the game of divide-the-cake, the above mixed neighborhood converges to demand-half.  We can verify that mixed neighborhoods are used by picking 5 random people from the population and setting all of their neighbors to the strategy of Demand 4:
SetStrategies(ExpandedNeighborhood(RandomSubset(population, 5), 1), 4);



Before the above command
After the above command



Implementing an entirely new neighborhood in the shell results in a serious performance hit; a much more efficient way to create new neighborhoods (as well as new learning rules) is to write an extension class and load it into the model once it has started.

This is easier than it sounds. The following will guide you through the process of creating a new neighborhood, installing it, and using it in your model. It is assumed that you have a working version of the Java compiler installed. If not, go to Sun's web site and download a Java compiler for your computer (it's free).

Creating a new neighborhood

Neighborhoods are created using a NeighborhoodConstructor. A NeighborhoodConstructor is any java class that defines the function getNeighborhood. (There are some arguments to that method, which we're ignoring for now.)

Suppose you want X-shaped neighborhoods. One solution is the following:

public class XNeighborhood implements NeighborhoodConstructor {
int length;

public XNeighborhood(int length) {
this.length = length;
}

public LinkedList getNeighborhood(int i,
int j,
int width,
int height,
Agent[][] world,
boolean wrap)
{
LinkedList nbhd = new LinkedList();
int offset;
for (offset=1; offset<=length; offset++) {
nbhd.add( world[ (i+offset+width) % width ][ (j+offset+height) % height] );
nbhd.add( world[ (i+offset+width) % width ][ (j-offset+height) % height] );
nbhd.add( world[ (i-offset+width) % width ][ (j+offset+height) % height] );
nbhd.add( world[ (i-offset+width) % width ][ (j-offset+height) % height] );
}
return nbhd;
}
}

That's it. Compiling this and loading it into the model will give you X-shaped neighborhoods of any size. If you don't have a Java compiler installed but want to see how loading an extension class works, you can download a copy of XNeighborhood.class and follow the instructions on loading an extension class.

The class header and variables

public class XNeighborhood implements NeighborhoodConstructor {
int length;
This says we are defining a new class named "XNeighborhood" which implements the interface "NeighborhoodConstructor". In Java, implementing an interface is a promise that certain functions of the right kind are defined. The NeighborhoodConstructor interface only requires one function to be defined: getNeighborhood. It must return a linked list containing the neighbors.

There's no reason why we have to fix the size of the X. The member variable "length" is the length of one of the stems of the X.

The class constructor

    public XNeighborhood(int length) {
this.length = length;
}
When creating a new instance of the XNeighborhood, we have to pass in the length of a stem. This value is stored in the variable "length". When we are done, the following command will create a new X-shaped neighborhood with a stem size of 3:
bsh% x_nbhd = new XNeighborhood(3);

The getNeighborhood method

The getNeighborhood method takes a lot of arguments. They are:

i The x-coordinate of the agent whose neighborhood we are creating.
j The y-coordinate of the agent whose neighborhood we are creating.
width The width of the lattice.
height    The height of the lattice.
world A two-dimensional array of agents. This provides direct access to the lattice (and hence the entire population).
wrap If "true", then the user has requested that neighborhoods wrap at the edge of the lattice; if "false," then the user has requested that neighborhoods do not wrap --- i.e., that the lattice has definite bounds. This value can be ignored (as this example does).

Constructing the X-shaped neighborhood is straightforward: add agents in the northwest, northeast, southwest, and southeast corners, moving out from the starting position at (i,j). If you haven't seen "(i+offset+width) % width" before, its meaning probably isn't clear. That bit of code addsoffset to i, taking care to wrap around the edges of the lattice. ("N % M" returns the value of N modulo M.) With this code we are explicitly ignoring the user's request to wrap or not wrap at the edge of the lattice. Modifying this example so that it respects the user's request is left as an exercise.

The linked list of neighbors that will be returned is created in the line

LinkedList nbhd = new LinkedList();
Neighbors are added to the list by calling the "add(...)" method. The majority of the getNeighborhood method consists of a for-loop that picks the right neighbors out of the lattice and adds them to the list.
public LinkedList getNeighborhood(int i,
int j,
int width,
int height,
Agent[][] world,
boolean wrap)
{
LinkedList nbhd = new LinkedList();
int offset;
for (offset=1; offset<=length; offset++) {
nbhd.add( world[ (i+offset+width) % width ][ (j+offset+height) % height] );
nbhd.add( world[ (i+offset+width) % width ][ (j-offset+height) % height] );
nbhd.add( world[ (i-offset+width) % width ][ (j+offset+height) % height] );
nbhd.add( world[ (i-offset+width) % width ][ (j-offset+height) % height] );
}
return nbhd;
}

Compiling and loading the code into the model

Save the above code to a file named "XNeighborhood.java", adding
import java.util.*;
to the start of the file. If you like, you can download a copy of XNeighborhood.java. Save it in the same directory where net.jar is located.

From your operating system's command line (not the BeanShell command line), switch to the directory containing XNeighborhood.java and type:

javac -classpath "./net.jar" XNeighborhood.java
If the gods of computation favor you, your machine should whir and chunk away for a few seconds and then stop. A new file named "XNeighborhood.class" has been created.

Move "XNeighborhood.class" into the same directory as net.jar.

Type addClassPath("."); at the BeanShell prompt. Once you have done this, the new class is available to the model. If you quit the model and restart, you will need to type addClassPath(".") each time to load the extension classes. This minor headache will be fixed in future versions.

Using the new neighborhood

Type:
bsh% x_nbhd = new XNeighborhood(3);
bsh% SetDefaultNeighborhood(x_nbhd);
and then create a new model.

Some other sample neighborhoods

You can download sample code for a RandomNeighborhood and a BowTieNeighborhood.