Class Organisation

Last updated on 16th July 2024

Sequence Organisation

Models very quickly grow to have lots of actions with splits in a single sequence, and adding the logic for the Agents in the same place makes the sequence of actions hard to follow.

Actions should be created by methods inside the agent the action is for, as is done in the example below. The methods should have clear names indicating what the purpose of the action is so that the sequence of steps becomes a sequence of step names rather than a sequence of lots of hard to read code.

Creating an action inside an agent class (Java)

  // Function sendVacancyMessage returns an Action
  public static Action<Flat> sendVacancyMessage() {
    return Action.create(
        Flat.class,
        flat -> {
          if (!flat.isOccupied()) {
            flat.getLinks(Links.VacancyLink).send(Messages.Vacancy.class, flat.size)
          }
        });
  }

// The sequence calls the method instead of defining the logic.
public class myModel extends AgentBasedModel<GlobalState> {

  @Override
  public void step() {
    run(
    	Action.create(Flat.sendVacancyMessage())
    );
  }
}  

As can be seen below the code becomes easy to read because the steps are all defined inside the agents.

Running a number of methods that return actions (Java)

run(
    Split.create(
      Flat.sendVacancyMessage(),
      Household.bidIfHomeless(),
      Sequence.create(
        Household.giveUpFlat(),
        Flat.notifyBucketOfVacancy()
      ),
      Bucket.processBidsAndVacancies()
     ...
     );
  )

Sequence Organisation (Java - Advanced)

Once the actions are all being defined inside the agent classes, as the agents grow and the number of methods being defined for the agents increases, there starts to be lots of duplication in the code that can be avoided. To avoid this, when an Agent class is created, it should be created with a static action method.

Static method that returns an action (Java)

public class Flat extends Agent<GlobalState> {
  // Static action method
  public static Action<Flat> action(SerializableConsumer<Flat> action) {
    return Action.create(Flat.class, action)
  }
}  

This action method can now be used when creating the methods that return actions.

(Java)

public class Flat extends Agent<GlobalState> {

  public static Action<Flat> action(SerializableConsumer<Flat> action) {
    return Action.create(Flat.class, action);
  }

  public Action<Flat> sendVacancyMessage() {
    return action(
     flat -> {
       if (!flat.isOccupied()) {
         flat.getLinks(Links.VacancyLink).send(Messages.Vacancy.class, flat.size)
       }
     });
   }
}  

An alternative way of using this action method is from inside the Sequence, defining the methods that return actions as methods that can be called from inside the actions.

(Java)

public class Flat extends Agent<GlobalState> {

  public static Action<Flat> action(SerializableConsumer<Flat> action) {
    return Action.create(Flat.class, action);
  }

  public void sendVacancyMessage() {
    if (!flat.isOccupied()) {
      flat.getLinks(Links.VacancyLink).send(Messages.Vacancy.class, flat.size)
    }
  }
}  

// Use the static action method from inside the sequence
run(
    Split.create(
      Flat.action(Flat::sendVacancyMessage),
     ...
  )
);

Message Organisation

When creating classes for the messages being sent, all message classes (used by the entire model) should be defined in a shared message class.

Creating them inside Agents, the Model or loose as their own separate class should be avoided.

When sending messages that contain only a single, integer, long, double, float, boolean, or merely an empty body use one of the message classes provided.

Creating static member message classes (Java)

public class Messages {
  //extend Message.Double
  public static class Vacancy extends Message.Double {}

  public static class Bid  extends Message.Double{}
}

For messages that require sending more than a single primitive field, there are two approaches.

If you want to send a complex data type that has already been defined, extend Message.Object<T>, remembering to provide the type of the object you are sending. This allows access to methods on the object when the receiving agent gets the message. For example, perhaps your agent needs to communicate information regarding a price quote, and you already have a predefined price quote object.

public class Seller extends Agent<GlobalState> {

  //Action sends an object.
  public static Action<Seller> sendPriceQuote() {
    return Action.create(
        Seller.class,
        seller -> {
          seller
              .getLinks(Links.BankLink.class)
              .send(Messages.PriceQuoteMessage.class, new PriceQuoteMessage(...))
        });
  }
}

Where the message extends Message.Object, typed with the object we wish to send.

Message extending Message.Object(Java)

public class Messages {
  public static final class PriceQuoteMessage extends Message.Object<PriceQuote> {}
}

However, if the agent needs to pass on just a few fields, with no specific behaviour, extend Message like so.

public class Seller extends Agent<GlobalState> {

  public static Action<Seller> sendPriceQuote() {
    return Action.create(
        Seller.class,
        seller -> {
          seller
              .getLinks(Links.BankLink.class)
              .send(
                  Messages.PriceQuoteMessage.class,
                  message -> {
                    message.price = 3;
                    message.reducedPrice = 2.3;
                  })
        });
  }
}

Where the message now has the form.

Message extending Message (Java)

public class Messages {
  public final class PriceQuoteMessage extends Message {
    double price;
    double reducedPrice;
  }
}

Agents with Shared Behaviour (Inheritance)

If multiple agent types need to share some common functionality, while having their own unique functionality and uses, it may be helpful to introduce inheritance, in which a superclass is created to hold the shared functionality.

For example, a Hotel and a Flat have some common functionality, but they also have some differences. Create an Accommodation class, for Hotel and Flat to extend. Accommodation can be used now when specifying an action for all Hotels and Flats.

public class Accomodation extends Agent<GlobalState> {
  public int size;
  public Action<Accomodation> sendVacancyMessage() {
      return Action.create(
          Accomodation.class,
          accomodation -> {
            // if vacant, send message ...
      });
   }
}

public class Flat extends Accomodation {
	public Action<Flat> registerNewHousehold() {
      return Action.create(
          Flat.class,
          flat -> {
            // new household registration ...
      });
   }
}  

public class Hotel extends Accomodation {
	public Action<Hotel> registerBooking() {
      return Action.create(
          Hotel.class,
          hotel -> {
            // new booking ...
      });
   }
}

public class myModel extends AgentBasedModel<GlobalState> {

  @Override
  public void step() {
    run(
      // Some steps are for both Hotel and Flat
      Accomodation.sendVacancyMessage(),
      Household.requestHome(),
      Split.create(
        // Some steps are only for Hotel/Flat
        Flat.registerNewHousehold(),
      	Hotel.registerBooking()
      )  
    );
  }
}

External Classes Organisation

Functionality, or methods that are not agent specific but are for general tasks that are needed by one or more agents or by the model shouldn't be placed together with the agents, but should be in a class of their own.

Examples of this may be a data loader that an agent might use to load data to spawn new agents with, or a distribution class which builds distributions based on some input/loaded data.

Static methods should be created for these, so the methods can be accessed easily from anywhere in the model

public class Distribution {
  public static EmpiricalDistribution loadDistribution(){
    // load distribution
  }
}

public class Flat extends AgentAge {
	public Action<Flat> registerNewHousehold() {
    return Action.create(
      Flat.class,
      flat -> {
        EmpiricalDistribution distribution = Distribution.loadDistribution();
         // use distribution
      });
   }
}