Spatial Interaction Modelling: Structuring into methods
[Practical 5 of 11 - Part 2]


Factoring Out Model Errors:

We have now refactored the original spatial interaction model code into more sensible blocks. Ones to load the data and ones to run the model in separate classes. Within those classes we have factored the code into separate blocks to load different parts of the data or calculate different sections of the model equation.

This is all good progress but it has introduced a series of errors. These can be seen by the white on red highlighted exclamation marks on the class, package and project symbols and on the class tab in the text editor area. The exact lines where the compile-time errors have been identified are shown in the right hand scroll bar of the text editor area by the red bars, the little black line is the location of your cursor. This can be seen in Figure 16.

Refactoring the errors, identifying the locations.
Figure 16

Lets correct the first error in the class, this is on line 17 in Figure 16. This error occurs because we are trying to access the length attribute of the origins array. However, we no longer have an origins array. The first thing is to gain access to the data where the array holding the origin data can be accessed.

When we calibrate models we have the requirement to run them across a dataset many times but we only want to load the data once as this can be a processor intensive operation taking some time to complete. So we don't want to put the call to load the data within the model. The best way to handle the data is as a parameter which is preloaded and passed into our model when it is constructed.

To do this lets create a constructor for our model which takes a parameter of type DataHandler. Make the constructor public access. To be explicit that we don't want anyone to make a Model object without giving it some data to work from we will also declare an empty constructor that takes in no parameters and give that private access.

So now we can create a Model object and provide a DataHandler loaded with data. But none of the other methods can access it, we need to expose it at the class level. Declare an instance variable of type DataHandler and call it data with private access. Inside our constructor simply set the instance level variable data to equal the parameter data.

Creating the constructors for Model.
Figure 17

Now we have some data we can use this to get to the length of the origins array. Replace the code origins.length in the for loop with a call to the origins array in the data object. We then use the .length to access the length property of the array that is returned.

We can do all of this on one line as in Figure 18, or we could return the origin array into a declared array and use that local variable to access the attribute. The two approaches are the equivalent. To use the second approach we would use the code double[] origins = data.getOrigin(); and then use the local variable origins to access the array length attribute in the for loop with for( int i = 0; i < origins.length; i++){.

Note, after you have typed the name of the data object and used the . operator you are provided with a list of accessible options. This is called intelisense and is another way that the IDE tries to make our lives easier... We don't have to remember everything!

Accessing the origins length attribute.
Figure 18

We can use the same approach for the destination loop as shown in Figure 19. The only difference here is that instead of calling the getOrigin() method we call the getDestination().

Accessing the destinations length attribute.
Figure 19

We can also alter the code to access the values. For the origin and destination values we must remember that we are getting an array back so using the accessor method will not provide one double value but an array object. To get to the individual value we need to still use the index of the element.

There are two ways to approach this. The first is to get a local reference to the array by creating a variable pointing to it. Remember that getting a local reference just puts another label on the object it does not create another object.

So we could do double[] origins = data.getOrigin(); and then use the local variable like this double tij = ai * origins[i] * Math.pow(destinations[j], alpha) * Math.exp(distances[i][j] * beta);

What we are going to do is a little more elegant than that. We will access the data directly from the data object. We get the array object using data.getOrigin() and then access the required element using the [i]. So the call becomes data.getOrigin()[i];.

We can use the same approach for both origin and destination values. See Figure 20.

Accessing the origin and destination values.
Figure 20

Accessing the distance value is much more straight forward. We changed the accessor so that it returns a single double value dependent on the indexes being correct. However, the index bounds we use to control the loops are not the distance array bounds so we need to check and make sure we are not entering incorrect index values.

To check the index values we can test the distance return value. If it is -1.0 then we know things have gone wrong and to check our input data and we will report this to the screen in a helpful way.

First of all we get the distance value for this origin and destination pair using double distance = data.getDistance(i, j);. We can test the value and if it is greater than -1.0 do the calculation otherwise report the failure to the screen. Wrap the equation in an if(){}else{} to do this. If you get stuck the code is shown in Figure 21.

Accessing the distance values.
Figure 21

We are nearly there. If you look at line 63 in Figure 21 you will see that we have introduced another error. This is a scope error. The variable tij is now created inside the if statement, but we are trying to use it outside of the if statement. The variable only exists within the brace block where it is created, so by the time the code gets to line 63 the variable no longer exists.

This is relatively easy to fix but it is important you understand what is going on here. To fix it we can simply declare the variable above the if block and then use it inside and outside. Alter the code to move the variable declaration as in Figure 22.

Fixing a scope error.
Figure 22

Figure 22 shows that there is only one error left to solve in this section of code. We no longer calculate the balancing factor in the same scope as the model equation so the ai variable is no longer available.

To solve this we are going to create an instance variable for ai and set this within our balancing factor code. First create an instance variable for ai of type double with private access as shown in Figure 23.

Fixing the ai scope error.
Figure 23

One problem remains with this solution, the ai balancing factor will always equal 0.0 because it is never calculated! We need to put some calls in to calculate the balancing factor. Place two calls to the method calculateBalancingFactors with the parameters configured as shown in Figure 24

Calling calculateBalancingFactors
Figure 24

Factoring Out Balancing Factor Errors:

We have six errors in the balancing factor code and the same approach can be use here to fix three of these as we used in the main model code. To begin with replace the two calls to the destinations array with data.getDestination() as shown in Figure 25.

Fixing destinations in the balancing factor method
Figure 25

We need to access the distance between the origin and destination next. However, we don't know which origin or destination the model is on. The destination is less important in this case, but in other model configurations that too would be required so we will set both. For this reason we will set both indexes so that the balancing factor code can access them.

To enable the balancing factor code to access the current origin / destination in the model code we will adjust the for-loop counters i and j. First create two instance variables called i and j with private access and of type int as shown in Figure 26.

Making the origin and destination indexes accessible
Figure 26

Next the declaration of the i and j for loop counters are removed in the calculate method as shown in Figure 27. Change the for loops from
for(int i = 0; i < data.getOrigin().length; i++){
to
for(; i < data.getOrigin().length; i++){
and from
for(int j = 0; j < data.getDestination().length; j++){
to
for(; j < data.getDestination().length; j++){

The origin and destination cycles now use the instance level counters instead of creating there own. This means that the index of the origin and destination can be accessed anywhere in the class.

This means that the j value will not automatically reset on each origin cycle. Therefore, this must be done manually. The code on line 90 in Figure 27 demonstrates this. Insert this line after the closing brace of the destination cycle but before the closing brace of the origin cycle.

Caution must be exercised in this situation, reading from the i and j variables is fine, but if we alter their values it will impact on the cycling within the for-loops!

Using the instance origin and destination indexes
Figure 27

Now the distance can be accessed using the instance level index i and the local destination index j. Replace the array reference with the accessor for the distance value data.getDistance(i, j) as highlighted in Figure 28.

We do not need to check the return of the distance value as this is done and any discrepancies reported in the main model equation code.

Getting the distance in the balancing factor method
Figure 28

Having a local level and instance level variable j is both confusing and bad practice, although quite legal in Java. You can specify access to the instance level variable using the keyword this and access the local variable in the normal way by direct reference.

To keep our code tidy we are going to change the local variable name to jLocal. In the left hand margin of the text editor you will see a little yellow light bulb, left click on this and you will see the options similar to the ones in Figure 29 presented. Select Rename the local variable. You can then type in the new variable name jLocal and the variable name will be changed throughout the method. When you have finished typing the new name press enter.

Renaming the local j variable
Figure 29

There are two alterations left to do in this class.

  1. Make the alpha and beta parameters accessible to the whole class.
  2. Make the ai variable accessible to the whole class.

We have already created an instance variable ai. The only change we need to make is to delete the keyword double from the highlighted line in Figure 30 removing the declaration of a local variable. The balancing factor will now be stored in the instance level variable.

Making ai instance level
Figure 30

To make the alpha and beta parameters class level we first need to create two instance variables to hold the values. Create two private instance level variables of type double, one called alpha and the other called beta as shown in Figure 31.

Making alpha and beta instance level
Figure 31

In the method calculate when the alpha and beta parameters are passed in we simply assign them to the class level variables we just declared, Figure 32. The keyword this is used to specify which are the instance variables.

We have now refactored our classes.

Assigning alpha and beta to instance level variables
Figure 32

Running the Refactored Model:

To run the refactored model we need to add a few lines of code to the main method in the SpatialInteractionModel class.

Below the equation parameters create a new instance of the DataHandler class called dh. Call the loadData() method on the new DataHandler object dh.

Below these lines create a new instance of the Model class called model using the dh object as the parameter. Finally, call the calculate method on the object model passing in the alpha and beta parameters.

Once you have done this your code should look like that in Figure 33. You can now run the model by right clicking on the SpatialInteractionModel class in the project area and selecting Run File as you did previously.

Note: the results from running the refactored model with the same parameter configuration will be the same as running the code at the beginning of the practical. It is the structure of the code that has been altered not the functionality.

Code in the main method to run the model
Figure 33

Summary:


Continue