anyLogistix
Expand
Font size

Phase 7. Create a Rate Matrix Calculator for regions

In the previous phase we exported the modified calculator and checked its calculations.

In this phase we will introduce some more advanced modifications to the calculator, and we will also show another way of providing data to the calculator.

Let us assume, that we want the calculator to consider more than just one value that we can define in the parameter. We could, of course, create more parameters, but we would eventually face the problem of having too many parameters. Managing this lot of parameters may become a challenge. Also, we do not necessarily know the exact number of parameters we want to send to the calculator. A better way would be to use the anyLogistix external tables.

In case of transportation costs there is the Rate Matrix calculator, which uses the external table to estimates expenses.

Let us recreate this calculator to have a better understanding of the steps we need to get through to use the external table as an input data for the extension. Additionally, we will improve the default Rate Matrix calculator to make it work with regions. And of course, the transportation costs will be defined for to each region.

Creating a new calculator

This time we are not going to change the pre-defined extension. We will use the existing transportation cost calculator template as the basis for the new extension.

Create a new calculator template

  1. In anyLogistix select Extensions > Create New... .

  2. Click the Policies tab in the opened dialog, check the Transportation Cost Calculator, and click OK.
    The AnyLogic extensions editor will open with the selected object available in the Projects view.
  3. Double-click the TCCTemplate Java class to open it. We will use this class to define the custom calculator.

    If you see an error, use the following workaround: close the graphical editor tab of this class. Now go to the Properties view, enable and then disable the Ignore option. Try opening the class again.

Let us now rename the calculator to later safely export it.

Rename calculator

  1. In the @CustomTypeClass annotation change the name value to Phase 7 Rate matrix calculator for regions.
  2. Now change the class name to Phase7RateMatrixRegions. This step is required to avoid conflicts when exporting the customized calculator to anyLogistix.

    When done, it should look like this

    @CustomTypeClass(name = "Phase 7 Rate matrix calculator for regions")
    public class Phase7RateMatrixRegions implements ITransportationCostCalculator{
    

As you remember, we will be using external table as the source of the data for the calculator. We need a parameter to get the name of this external table. We can delete the template's example parameter, and add 2 new parameters.

Add new parameters

  1. Add the new Amount unit parameter, it will define the value's measurement unit:
    @Parameter(name = "Amount unit", type = EditorType.MeasurementUnitVolumeEditor)
        private MeasurementUnit unit;
  2. Now add the External table name parameter, and declare the matrixName variable of String type within it. The variable will keep the name of the external table.
    @Parameter(name = "External table name", type = EditorType.ExternalTableCombo)
        private String matrixName;

Now we need the map object to store the values from the external table and provide easy access to these values.

The structure of the external table has markers for the first column and the top row. These markers determine the coordinates of the value, we want to get from the table. So, we need to create a Map object that will contain another Map for each of the keys. The key type is String for both Maps, the value type for the external MAP is the internal Map, and for the internal Map the value type is Double. The name of the Map object is costMatrix.

Add the Map object

  1. Declare a new costMatrix Map object that will contain another Map for each row of the external table.
    private Map < String, Map < String, Double >> costMatrix;

Updating the cost calculator for the simulation-based experiments

Since the calculator that we are creating estimates costs between the From and To locations, we will use the extended cost() method for SIM calculations.
The method we have previously used is not needed, so we can simply make it return 0.

Update the cost() methods

  1. Set the cost() method to return 0
    @Override
        public double cost(IShipment shipment, double distance, VehicleType vehicleType) {
        return 0;
        }
  2. Define the actual cost calculation in the extended version of the cost() method.
    @Override
        public double cost(IShipment shipment, double distance, VehicleType vehicleType, IFacility from, IFacility to) {
        return getTotalVolume(shipment) * getRate(from.getLocation().getLocation(), to.getLocation().getLocation());
        }

We still need the getTotalVolume() method to calculate the number of products in the shipment. The received value must be multiplied by the rate of the from-to locations combination from the external table. The exact rate value is returned by the getRate() method. We are calling this method for the from and to locations as arguments.

Add the getRate() method

  1. Create the getRate() method
    private double getRate(Location from, Location to) {
        //The first thing this method does is checking if the costMatrix Map object has been initialized (whether the init() method has been called).
        //If the costMatrix was not initialized, the matrix is filled with data from the external table by the init() method.
        if (costMatrix == null) {
        init(from.getScenario());
        }
        //Now that we are confident, that the costMatrix obtains the values from the external table, we can find the required value.
        //We get the inner HashMap corresponding to the From location and assign it to the new declared tosRate Map.
        Map < String, Double> tosRate = costMatrix.get(from.getName());
        //We need to make sure, that we received the required Map we wanted.
        //If not, it will mean that there is no From location in the external table, and no rates defined for it.
        if (tosRate == null) {
        //If the inner Map was not found, we simply return 0 as the rate, because we do not have the exact value for it.
        return 0;
        }
        //Now we call the getRegion() method to get the region name, which contains the To location.
        //The same way we may use the getCode() method to make this calculator estimate transportation costs based on the ZIP code of the locations.
        //In this case make sure that the code is defined for all locations used in the Paths table.
        //Otherwise the transportation cost will be 0.
        String toRegionName = to.getRegion();
        //The next step is getting the actual rate from the tosRate HashMap using the name of the previously found region as a Key value.
        Double value = tosRate.get(toRegionName);
        //Here we check again if we were able to find the Key in the HashMap, and if the value was right.
        if (value == null) {
        return 0;
        }
        //With the last step we return the received rate.
        return value.doubleValue();
    }

At this point we know the name of the external table to use, and we have created the Map that stores the value from this table. Now we need to insert the values from the external table to the Map object.

Initialize Map

  1. Add the init() method, which will insert the values from the external table to the Map object.
    private void init(Scenario scenario) {
            //Here we specify the exact type of Map we want to use, the HashMap.
    	costMatrix = new HashMap<>();
            //Then we create the ExternalTableData object to get the external table with the name that was received through the matrixName parameter.
    	ExternalTableData data = scenario.getExternalTables().stream()
    		.filter(t -> t.getTableName().equals(matrixName)).findFirst()
    		.orElse(null);
            //Now we need to get through the rows of the external table and put it into the outside HashMap Key value. We use the for loop to iterate through the rows.
    	for (int i = 0; i < data.rowsNumber(); i++) {
                    //We store the value from the first column of the row (name of the From location) in the just created from variable of String type.
    		String from = data.getStringValue(i, 0);
                    //Then we put the value the from variable stores to the costMatrix HashMap as the Key value, and create the new HashMap as a value for this Key.
    		costMatrix.put(from, new HashMap<>());
                    //Now we need to get through the columns and place all the values from the iterated row into the HashMap that we have just created.
                    //To iterate through the columns, we use the for loop again.
    		for (int j = 1; j < data.columnsNumber(); j++) {
                            //Here we assign the value of the column header (name of the To location) to the new String variable named to.
    			String to = data.getColumnData(j).getName();
                            //Now the cost value corresponding to the To location is assigned to the rate variable.
    			double rate = data.getDoubleValue(i, j);
                            //And finally, the to and rate values are placed into the inner HashMap that corresponds to the from location.
    			costMatrix.get(from).put(to, rate);
    		}
    	}
    }

This way all the values from the external table will be inserted into the HashMap object.

Updating the cost calculator for the network optimization experiment

For the Network optimization we will use the same getRate() and init() methods that we have used for the Simulation-based experiments, to get the value from the external table. And we will also use the extended version of the cost calculations method. The flowCost() and the vehicleFlowCost() methods are not required here, so we will make them return 0.

Update the flowCost() and vehicleFlowCost() methods

  1. Make the two methods return 0.
    @Override
    public double flowCost(Product product, double flowSize, double distance, VehicleType vehicleType) {
    	return 0;
    }
    @Override
    public double vehicleFlowCost(double flowSize, double distance, VehicleType vehicleType){
    	return 0;
    }
    

We also need the extended flowCost() method. It allows us to use the starting and finishing points of the flow.

Update the flowCost() method

  1. Update the method by taking the flowSize value, converting it to the units defined by the parameter, and multiplying it by the rate received from the external table.
    @Override
        public double flowCost(Product product, double flowSize, double distance, VehicleType vehicleType, IFacilityData from, IFacilityData to){
        return flowSize * product.convertToUnit(unit) * getRate(from.getLocation(), to.getLocation());
        }

The last thing we need to do is to change the getDescription() method to have the name of the external table in the anyLogistix user interface.

Update the getDescription() method

  1. Update the getDescription() method as shown below
    @Override
        public String getDescription() {
        return "Calculated based on " + matrixName + ", Unit: " + unit;
        }

Finally, that's it. We have configured the calculator. In the next phase we will check it to see if works as we want it to work.

How can we improve this article?