/* Created 8th Feb 2005 by Richard Taylor richard@cfpm.org Demonstration source code for Social Networks Tutorial University of Napoli The Industrial Districts Network Model (Network model) Implementation in Java - RePast */ import uchicago.src.sim.engine.SimModelImpl; import uchicago.src.sim.util.Random; import uchicago.src.sim.util.SimUtilities; import uchicago.src.sim.engine.*; import uchicago.src.sim.gui.*; import uchicago.src.sim.analysis.*; import uchicago.src.reflector.*; import uchicago.src.reflector.BooleanPropertyDescriptor; // network models do not require spaces // however, they do require drawable nodes and edges and other classes // import uchicago.src.sim.space.*; import uchicago.src.sim.network.NetworkFactory; import uchicago.src.sim.network.DefaultNode; import uchicago.src.sim.network.DefaultEdge; import uchicago.src.sim.network.DefaultDrawableEdge; import uchicago.src.sim.network.DefaultDrawableNode; import uchicago.src.sim.network.NetworkRecorder; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.awt.Color; public class IDNetworkModel extends SimModelImpl { // initial starting parameters of the model private int NUM_AGENTS; private int NUM_CLIENTS = 10; private int NUM_SUBCONTRACTORS = 60; private int NUM_CYCLES = 100; private int NUM_LINKS = 120; // the threshold / maximum number of work contracts the subcontractor can accept private int MAX_NUM_CONTRACTS; // Define the non-grid network CircularGraphLayout layout; Network2DDisplay display; int worldXSize = 800; int worldYSize = 800; // A list of all the agents. Convenient for any method that iterates through a list of agents. // At least one list like this is common to most simulations. private ArrayList agentList; // A NetworkRecorder records a network as a matrix or matrices in the appropriate format. // NetworkRecorder takes a list of Nodes and writes the corresponding adjacency matrix to a file. // Three file formats are supported: UCINet's dl, Excel, and ASCII. The actual network matrix // is written out in this format, although Repast records relevant non-matrix information as well. private NetworkRecorder recorder; // The surface on which the agents and their environment are displayed private DisplaySurface dsurf; // Every model must have a schedule private Schedule schedule; // All simulations should have a no argument constructor - this is empty and // thus not strictly necessary public IDNetworkModel() { BooleanPropertyDescriptor bd = new BooleanPropertyDescriptor("Replacement", false); descriptors.put("Replacement", bd); } // Create a method which sets up the DataRecorder and is called from within buildModel() // As of Repast 1.3 this is just as fast as creating your own inner classes. // Exporting data is done through a NetworkRecorder which records a matrix or matrices in one of the // following formats: UCINet's dl, Excel, and ASCII. // The NetworkRecorder takes a list of Nodes and writes the corresponding adjacency matrix to a file. // RePast records relevant non-matrix information as well: a file header containing the constant // parameters for the model and a timestamp, and a block header that contains header information protected void initDataRecorder() { recorder = new NetworkRecorder(NetworkRecorder.DL,"./IDNetwork.dl",this); recorder.record(agentList, "tick: " + getTickCount(), NetworkRecorder.BINARY); recorder.write(); } // setup() should "tear down" the model in preparation for a call to begin(). // should set any objects that are created over the course of the run to null // and dispose of any DisplaySurfaces, graphs, and Schedules // The initial model parameters should be set to their defaults // a Schedule should be created here and if the model is a gui model a DisplaySurface public void setup() { schedule = null; // create the agent list of firms agentList = new ArrayList(); if (dsurf != null) dsurf.dispose(); dsurf = null; System.gc(); // Create and initialise the custom action button setupCustomActions(); // create a schedule with an interval of one. schedule = new Schedule(1); // displays are created dsurf = new DisplaySurface(this, "Industrial District Network Model"); } // any objects you need to create that depend on parameter values can be created here // any parameter manipulation through GUI or parameter files occurs after setup()is called. public void begin() { Random.createUniform(); // 3 build methods which are called in begin() buildModel(); buildDisplay(); // buildSchedule(); //uncomment this line to explore the dynamic simulation model (day2 tutorial) dsurf.display(); } // buildModel()is responsible for creating those parts of the simulation that represent // what is being modelled: the agents and their environment are typically created here // together with any optional data collection objects. private void buildModel () { NUM_AGENTS = NUM_CLIENTS + NUM_SUBCONTRACTORS; // Create the firms. It is neccessary to set the initial x and y values for displaying them as // drawable nodes, although later we shall lay them out according to a graph layout algorithm for (int i = 0; i < NUM_AGENTS; i++) { int x = Random.uniform.nextIntFromTo (0, worldXSize - 1); int y = Random.uniform.nextIntFromTo (0, worldYSize - 1); Firm node = new Firm (x,y); node.setNodeLabel ("Firm - " + i); node.setBorderColor (Color.orange); node.setBorderWidth (2); agentList.add(node); } // Setting up the network is now a matter of making the links among the nodes (i.e. the firms). // Edges must implement the DrawableEdge interface. RePast provides a default implementation of // this that should be fine for most purposes: DefaultDrawableEdge. See docs for more info. // first few agents in the list are the clients: indices 0 to (NUM_CLIENTS-1) // and the remainder are subcontractors: indices NUM_CLIENTS to (NUM_AGENTS-1) // make the links by picking one client and one subcontractor at random // repeat specified number of times Firm client, subcontractor; int count = 0; do { int client_index = Random.uniform.nextIntFromTo (0, NUM_CLIENTS - 1); int sc_index = Random.uniform.nextIntFromTo (NUM_CLIENTS, NUM_AGENTS - 1); client = (Firm) agentList.get(client_index); subcontractor = (Firm) agentList.get(sc_index); if (client.hasEdgeTo(subcontractor)==false) { count ++; // This constructs the edge and makes it a directed link from the client to the subcontractor DefaultDrawableEdge edge = new DefaultDrawableEdge (client, subcontractor); client.addSubcontractor(subcontractor, edge); subcontractor.addClient(client, edge); } } while (count < NUM_LINKS); // call to initDataRecorder records the initial adjacency matrix in the "IDNetwork.dl" file initDataRecorder(); // This method calculates the highest number of contracts obtained by any subcontractor during // the buildModel stage calculateMaxNumContracts(); } // Setting up Repast for drawing consists of several steps: (1) creating what you want to be drawn // Usually, these are agents and their environment. Agents etc. are created in buildModel() // Then in buildDisplay() the display is created and the agentList is also added to the display. // The display itself is then added to a DisplaySurface either as a displayable or as a displayableProbeable. private void buildDisplay() { // Non-grid networks are displayed using the Network2DDisplay class. This network is encapsulated // by a List (or a VectorSpace) of DrawableNonGridNodes whose edges are of the DrawableEdge type. // Typically, this List is not passed directly to the Network2DDisplay for drawing, but is wrapped // in some sort of GraphLayout first. The GraphLayout sets the x and y coordinates of the Drawable // NonGridNodes and then the Network2DDisplay displays the nodes at these coordinates. // Create the NetworkDrawable in the DrawableNode class using init(). // You can use one of the graph layout classes to lay out your network for you, such as CircularGraphLayout. // This passes the list of agents (DefaultDrawableNodes) and the size of the display. // This layout is then passed to Network2DDisplay and handled in the usual manner. // Graph layouts choose from among the following four types: layout = new CircularGraphLayout(agentList, worldXSize, worldYSize); // FruchGraphLayout layout = new FruchGraphLayout(agentList, worldXSize, worldYSize); // KamadaGraphLayout layout = new KamadaGraphLayout(agentList, worldXSize, worldYSize); // RandomGraphLayout layout = new RandomGraphLayout(agentList, worldXSize, worldYSize); display = new Network2DDisplay(layout); // update the layout so that the algorithm will take effect layout.updateLayout(); // add the display to the displaySurface dsurf.addDisplayableProbeable(display, "Network Display"); addSimEventListener(dsurf); } // buildSchedule builds the schedule that changes the simulations state. // Under this scheme, a simulation is a state machine where all transitions // between states are the result of actions initiated by a schedule. // Scheduling consists of setting up method calls on objects to occur at certain times. // These method calls must be wrapped by sub-classes of the BasicAction class. // On day two of the tutorial we shall look at dynamic simulations that use the RePast Scheduling // mechanism to manipulate the network. private void buildSchedule() { // create a new NetworkRecorder to be associated with a separate file for dynamic simulation String fileIdentifier = "IDNetwork_dynamic.dl"; final NetworkRecorder dynamic_recorder; dynamic_recorder = new NetworkRecorder(NetworkRecorder.DL,fileIdentifier,this); // inner class which extends BasicAction. It is used to schedule the recording of network data // (i.e. the adjacency matrix) during the dynamic simulation class DynamicSimulationDataRecorder extends BasicAction { public void execute() { dynamic_recorder.record(agentList, "tick: " + getTickCount(), NetworkRecorder.BINARY); }; } // The methods: removeUnconnectedFirms, addNewContract, and removeRandomFirm // must be scheduled for execution through BasicAction type objects. // Therefore we shall define inner classes for each one which extend the BasicAction class. class removeUnconnectedAction extends BasicAction { public void execute() { removeUnconnectedFirms(); }; } class addContractAction extends BasicAction { public void execute() { addNewContract(); }; } class removeRandomAction extends BasicAction { public void execute() { removeRandomFirm(); }; } // This line schedules the removeUnconnectedFirms method (which is wrapped in a BasicAction) // So that it will execute at tick 1 schedule.scheduleActionAt(1, new removeUnconnectedAction()); // This line schedules one new contract to be added every tick schedule.scheduleActionBeginning(1, new addContractAction()); // Set up the schedule for recording by creating an object of type DynamicSimulationDataRecorder. // Whenever an object of this type is scheduled, its record() method will trigger the recording // of the network data to the file "IDNetwork_dynamic.dl" (specified above) // It records the network(s) represented by the Nodes and their Edges in nodeList and appends // the current tick count to the block header. The last argument refers to the size of the ij // values, the strength of the edge. By default this is 1 if an edge exists otherwise 0. schedule.scheduleActionAtEnd(new DynamicSimulationDataRecorder()); // Alternatively, this line would schedule the recording to take place every 25 ticks ... // schedule.scheduleActionAtInterval(25, new DynamicSimulationDataRecorder()); // When the simulation run ends, call the write/writeToFile method on the recorder object. // This writes the data collected by the recorder to the file specified when it was constructed, // appending the data if data have already been written during this run. // Note that data are stored in memory until the write/writeToFile call at which point it is // flushed. Here we have scheduled the write call to occur at the end of the simulation. /*********** NetworkRecorder uses write method which is identical to writeToFile *********/ schedule.scheduleActionAtEnd(dynamic_recorder, "write"); // Stop the simulation after the specified number of ticks schedule.scheduleActionAt(NUM_CYCLES, this, "stop", Schedule.LAST); System.out.println("reached the end of buildSchedule"); } /************************************************************************************************ The following methods: removeUnconnectedFirms, addNewContract, and removeRandomFirm are supposed to be scheduled for execution during the dynamic simulation. Briefly, removeUnconnectedFirms removes all those remaining isolated after the buildModel stage In BuildModel, calculateMaxNumContracts found the highest number of contracts allocated to a subcontractor. This variable is used for the addNewContract routine ... In the dynamic simulation that is described in buildSchedule ... In addNewContract, the number of contracts undertaken by any firm is not allowed to exceed the max. Instead, if the 'busy' subcontractor is selected for a new link, then that firm itself subcontracts the work to a second subcontractor. removeRandomFirms selects clients or subcontractors at random and removes them, including any links. ************************************************************************************************/ // Assuming that there are no disconnected nodes, we can use this method to make new connections // in which, when they reach a threshold number of accepted contracts, or if the edge is a duplicate, // subcontractors may then themselves subcontract to other partners of the original client firm. // In this case we create a directed link from the first subcontracter to the second subcontractor. private void addNewContract () { Firm client, subcontractor; int pop_size = agentList.size(); int NUM_SUBCONTRACTORS_CLIENTS; int client_index; int NUM_CLIENTS_CLIENTS; do{ // select a random client, (ie. a firm which itself has no clients) client_index = Random.uniform.nextIntFromTo (0, pop_size - 1); client = (Firm) agentList.get(client_index); NUM_CLIENTS_CLIENTS = (int)((ArrayList)client.getClients()).size(); } while (NUM_CLIENTS_CLIENTS != 0); do { // now select a random subcontractor int sc_index = Random.uniform.nextIntFromTo (0, pop_size - 1); subcontractor = (Firm) agentList.get(sc_index); // number of clients of that firm (==0 only if is itself a client or if it is disconnected) NUM_SUBCONTRACTORS_CLIENTS = ((ArrayList) subcontractor.getClients()).size(); } while (NUM_SUBCONTRACTORS_CLIENTS==0); // test whether the subcontractor either (a) already has a contract with the client, or (b)has // already reached the threshold (MAX_NUM_CONTRACTS) if (subcontractor.hasEdgeFrom(client) || NUM_SUBCONTRACTORS_CLIENTS >= MAX_NUM_CONTRACTS) { // then connect the subcontractor as a client, to a second subcontractor // select the second subcontractor at random as before int NUM_2ND_SUBCONTRACTORS_CLIENTS; int sc2_index; Firm subcontractor2; // quick check that it is still possible to add another contract int count = 0; boolean check = false; do { subcontractor2 = (Firm) agentList.get(count); NUM_2ND_SUBCONTRACTORS_CLIENTS = ((ArrayList) subcontractor2.getClients()).size(); if (NUM_2ND_SUBCONTRACTORS_CLIENTS >0 && NUM_2ND_SUBCONTRACTORS_CLIENTS < MAX_NUM_CONTRACTS) check=true; else check=false; if (subcontractor2 == subcontractor) check=false; count++; } while (check == false && count < pop_size); // exit loop when check is set to true, ie.when number of clients lies between 0 // and max, or exit when all agents have been checked and boolean remains false if (check) { do { sc2_index = Random.uniform.nextIntFromTo (0, pop_size - 1); subcontractor2 = (Firm) agentList.get(sc2_index); NUM_2ND_SUBCONTRACTORS_CLIENTS = ((ArrayList) subcontractor2.getClients()).size(); } while (NUM_2ND_SUBCONTRACTORS_CLIENTS==0 || NUM_2ND_SUBCONTRACTORS_CLIENTS==MAX_NUM_CONTRACTS); // add the link, where the first subcontractor is the client to the second subcontractor DefaultDrawableEdge edge = new DefaultDrawableEdge (subcontractor, subcontractor2); subcontractor.addSubcontractor(subcontractor2, edge); subcontractor2.addClient(subcontractor, edge); } } else { // make the link as a normal one from client to subcontractor DefaultDrawableEdge edge = new DefaultDrawableEdge (client, subcontractor); client.addSubcontractor(subcontractor, edge); subcontractor.addClient(client, edge); } layout.updateLayout(); dsurf.updateDisplay(); } // removes all unconnected, i.e. isolated, firms private void removeUnconnectedFirms () { int count = 0; int len = (int) agentList.size(); Firm test_node, partner; ArrayList partners; int i=0; do { test_node = (Firm) agentList.get(i); // partners is a combined list of all subcontractors and all clients partners = (ArrayList) test_node.getSubcontractors(); partners.addAll(test_node.getClients()); int len_partners = partners.size(); // if the agent has no partners then remove it from the agentList // note that in these cases it is not neccessary to remove edges if(len_partners == 0) { agentList.remove(test_node); len--; // length of list has decreased by one count++; // increase count by one } else i++; // if element was not removed then increase the index ready to test the next } while (i0) { len = (int) agentList.size(); int rem_index = Random.uniform.nextIntFromTo (0, len - 1); rem_node = (Firm) agentList.get(rem_index); // remove the node from the agentList using its index agentList.remove(rem_index); len--; // remove the links to any subcontractors partners = (ArrayList) rem_node.getSubcontractors(); int len_sc = partners.size(); // condition that length of list of subcontractors greater than 0 while (len_sc > 0) { // get the first subcontractor then remove it partner = (Firm) partners.get(0); partner.removeClient(rem_node); rem_node.removeSubcontractor(partner); len_sc--; } System.out.println("finised removing subcontractors"); // remove the links from any clients partners = (ArrayList) rem_node.getClients(); int len_clients = partners.size(); while (len_clients>0) { partner = (Firm) partners.get(0); partner.removeSubcontractor(rem_node); rem_node.removeClient(partner); len_clients--; } System.out.println("finised removing clients"); } layout.updateLayout(); dsurf.updateDisplay(); } public void calculateMaxNumContracts(){ // cycle through the agent list and find the highest number of contracts held by any subcontractor Firm node; ArrayList partners; int num; int highest = 0; int len = (int) agentList.size(); for(int i=0; i< len ; i++) { node = (Firm) agentList.get(i); partners = (ArrayList) node.getClients(); num = partners.size(); if (num > highest) highest = num; } MAX_NUM_CONTRACTS = highest; } // To set up a model that will create and show buttons, sliders etc. for carrying out CustomActions // use the ModelManipulator object. Put all the slider etc. creation in a private method in your model, // and call this method as part of your model's setup() method. Note that the first thing you want // to call in this method is modelManipulator.init() private void setupCustomActions() { modelManipulator.init(); modelManipulator.addButton("Simple Network Manipulator", new ActionListener() { public void actionPerformed(ActionEvent evt) { int NUM_NODES_TO_REMOVE = 10; int NUM_LINKS_TO_ADD = 20; Firm rem_node, partner; ArrayList partners; // select the nodes to be removed // these are held in the object rem_node for(int i=0; i< NUM_NODES_TO_REMOVE ; i++) { int len = (int) agentList.size(); int rem_index = Random.uniform.nextIntFromTo (0, len - 1); rem_node = (Firm) agentList.get(rem_index); // remove the node from the agentList using its index agentList.remove(rem_index); // remove the links to any subcontractors partners = (ArrayList) rem_node.getSubcontractors(); int len_sc = partners.size(); // condition that length of list of subcontractors greater than 0 while (len_sc > 0) { // get the first subcontractor then remove it partner = (Firm) partners.get(0); partner.removeClient(rem_node); rem_node.removeSubcontractor(partner); len_sc--; } System.out.println("finised removing subcontractors"); // remove the links from any clients partners = (ArrayList) rem_node.getClients(); int len_clients = partners.size(); while (len_clients>0) { partner = (Firm) partners.get(0); partner.removeSubcontractor(rem_node); rem_node.removeClient(partner); len_clients--; } System.out.println("finised removing clients"); } /* add 10 new links Firm client, subcontractor; int count = 0; do { int client_index = Random.uniform.nextIntFromTo (0, NUM_CLIENTS - 1); int sc_index = Random.uniform.nextIntFromTo (NUM_CLIENTS, NUM_AGENTS - 1); client = (Firm) agentList.get(client_index); subcontractor = (Firm) agentList.get(sc_index); if (client.hasEdgeTo(subcontractor)==false) { count ++; DefaultDrawableEdge edge = new DefaultDrawableEdge (client, subcontractor); client.addSubcontractor(subcontractor, edge); subcontractor.addClient(client, edge); } } while (count < NUM_LINKS); */ layout.updateLayout(); //dsurf.updateDisplay(); } }); } // When a non-batch simulation is started through SimInit, a Controller object is created // to control the running of that model. // If the name of the model was passed in as an argument to SimInit, the Controller calls // getInitParam() on the model and receives a list of initial parameters that can be displayed // In order to display the value of these parameters the controller determines if the model has // implemented get and set methods for that parameter. // A non-batch model uses the same methods to set its initial parameters from a parameter file public String[] getInitParam() { String[] params = {"ID Network Model", "NUM_CLIENTS", "NUM_SUBCONTRACTORS", "NUM_LINKS", "worldXSize", "worldYSize", "NUM_CYCLES"}; return params ; } public int getNUM_CLIENTS() { return NUM_CLIENTS; } public int getNUM_SUBCONTRACTORS() { return NUM_SUBCONTRACTORS; } public int getNUM_CYCLES() { return NUM_CYCLES; } public int getNUM_LINKS() { return NUM_LINKS; } public void setNUM_CLIENTS(int NUM_CLIENTS) { this.NUM_CLIENTS = NUM_CLIENTS; } public void setNUM_SUBCONTRACTORS(int NUM_SUBCONTRACTORS) { this.NUM_SUBCONTRACTORS = NUM_SUBCONTRACTORS; } public void setNUM_LINKS(int NUM_LINKS) { this.NUM_LINKS = NUM_LINKS; } public int getworldXSize() { return worldXSize; } public int getworldYSize() { return worldYSize; } // getSchedule returns the schedule associated with the model - typically just the model's schedule variable. public Schedule getSchedule() { return schedule; } // getName() should return the name of the model - displayed as the title of the tool bar public String getName() { return "IDNetwork Model"; } public double getTickCount() { return schedule.getCurrentTime(); } // Optional main method which may be used to start the model via the command line, i.e. with the // command "java MyClass.class". It should create an instance of your model, an instance of // the SimInit class, and instance of the model itself. It then uses SimInit to load the model // via the loadModel method. The second and third parameters of the loadModel method specify // a parameter file and whether or not the model is a batch model. You can either (1) pass values to // these parameters via the args array or (if you know that the model is a batch model) (2) specify // the method parameters directly. Eg. (1) with a command line of: // "java -cp c:\repast\lib\repast.jar;. MyModel param.pf true", args[0] = param.pf and args[1] = true. // Eg.(2) "init.loadModel(model, "./EnnParams.txt", true);" (code snippet from EnnBatchModel.java) public static void main(String args[]) { System.out.println("Starting App"); //if (args[0]!="") System.out.println("Args Worked!"); IDNetworkModel model = new IDNetworkModel(); SimInit init = new SimInit(); // init.loadModel(model, args[0], false); init.loadModel(model, "", false); } }