diff --git a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DefaultJobDurationCalculator.java b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DefaultJobDurationCalculator.java new file mode 100644 index 00000000000..42ddbc4068a --- /dev/null +++ b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DefaultJobDurationCalculator.java @@ -0,0 +1,37 @@ +package org.matsim.freightDemandGeneration; + +import org.matsim.api.core.v01.Scenario; + +public class DefaultJobDurationCalculator implements JobDurationCalculator { + @Override + public double calculateServiceDuration(Integer serviceTimePerUnit, int demandForThisService) { + return getDefaultCalculation(serviceTimePerUnit, demandForThisService); + } + + @Override + public double calculatePickupDuration(Integer pickupDurationPerUnit, int demandForThisShipment) { + return getDefaultCalculation(pickupDurationPerUnit, demandForThisShipment); + } + + @Override + public double calculateDeliveryDuration(Integer deliveryDurationPerUnit, int demandForThisShipment) { + return getDefaultCalculation(deliveryDurationPerUnit, demandForThisShipment); + } + + @Override + public void recalculateJobDurations(Scenario scenario) { + // do nothing + } + + /** + * @param timePerUnit time per unit + * @param demandForThisService demand for this service + * @return default calculation + */ + private int getDefaultCalculation(int timePerUnit, int demandForThisService) { + if (demandForThisService == 0) + return timePerUnit; + else + return timePerUnit * demandForThisService; + } +} diff --git a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DemandReaderFromCSV.java b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DemandReaderFromCSV.java index e39eedd9ad4..63179128cc4 100644 --- a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DemandReaderFromCSV.java +++ b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/DemandReaderFromCSV.java @@ -324,16 +324,18 @@ public String getTypeOfDemand() { * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file * @param population Population * @param shapeCategory Column name in the shape file for the data connection in the csv files - * @throws IOException if the csv file cannot be read + * @param jobDurationCalculator Calculator for the job duration + * @throws IOException if the csv file cannot be read */ static void readAndCreateDemand(Scenario scenario, Path csvLocationDemand, ShpOptions.Index indexShape, boolean combineSimilarJobs, - CoordinateTransformation crsTransformationNetworkAndShape, Population population, String shapeCategory) throws IOException { + CoordinateTransformation crsTransformationNetworkAndShape, Population population, String shapeCategory, + JobDurationCalculator jobDurationCalculator) throws IOException { Set demandInformation = readDemandInformation(csvLocationDemand); checkNewDemand(scenario, demandInformation, indexShape, shapeCategory); createDemandForCarriers(scenario, indexShape, demandInformation, population, combineSimilarJobs, - crsTransformationNetworkAndShape); + crsTransformationNetworkAndShape, jobDurationCalculator); } /** @@ -538,42 +540,45 @@ static void checkNewDemand(Scenario scenario, Set dema /** * Creates for every demand information the services/shipments for the carriers * - * @param scenario Scenario - * @param indexShape ShpOptions.Index for the shape file - * @param demandInformation Set with the demand information - * @param population Population - * @param combineSimilarJobs boolean if the jobs of the same carrier with same location and time will be combined - * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file + * @param scenario Scenario + * @param indexShape ShpOptions.Index for the shape file + * @param demandInformation Set with the demand information + * @param population Population + * @param combineSimilarJobs boolean if the jobs of the same carrier with same location and time will be combined + * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file + * @param jobDurationCalculator Calculator for the job duration */ static void createDemandForCarriers(Scenario scenario, ShpOptions.Index indexShape, - Set demandInformation, Population population, boolean combineSimilarJobs, - CoordinateTransformation crsTransformationNetworkAndShape) { + Set demandInformation, Population population, boolean combineSimilarJobs, + CoordinateTransformation crsTransformationNetworkAndShape, JobDurationCalculator jobDurationCalculator) { for (DemandInformationElement newDemandInformationElement : demandInformation) { log.info("Create demand for carrier {}", newDemandInformationElement.getCarrierName()); if (newDemandInformationElement.getTypeOfDemand().equals("service")) - createServices(scenario, newDemandInformationElement, indexShape, population, combineSimilarJobs, - crsTransformationNetworkAndShape); + createServices(scenario, newDemandInformationElement, indexShape, population, + crsTransformationNetworkAndShape, jobDurationCalculator); else if (newDemandInformationElement.getTypeOfDemand().equals("shipment")) - createShipments(scenario, newDemandInformationElement, indexShape, population, combineSimilarJobs, - crsTransformationNetworkAndShape); + createShipments(scenario, newDemandInformationElement, indexShape, population, + crsTransformationNetworkAndShape, jobDurationCalculator); } - + if (combineSimilarJobs) + combineSimilarJobs(scenario); + jobDurationCalculator.recalculateJobDurations(scenario); } /** * Creates the services. * - * @param scenario Scenario - * @param newDemandInformationElement single DemandInformationElement - * @param indexShape ShpOptions.Index - * @param population Population - * @param combineSimilarJobs boolean if the jobs of the same carrier with same location and time will be combined - * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file + * @param scenario Scenario + * @param newDemandInformationElement single DemandInformationElement + * @param indexShape ShpOptions.Index + * @param population Population + * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file + * @param jobDurationCalculator Calculator for the job duration */ private static void createServices(Scenario scenario, DemandInformationElement newDemandInformationElement, - ShpOptions.Index indexShape, Population population, boolean combineSimilarJobs, - CoordinateTransformation crsTransformationNetworkAndShape) { + ShpOptions.Index indexShape, Population population, + CoordinateTransformation crsTransformationNetworkAndShape, JobDurationCalculator jobDurationCalculator) { int countOfLinks = 1; int distributedDemand = 0; @@ -643,8 +648,8 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) { Link link = findNextUsedLink(scenario, indexShape, possibleLinksForService, numberOfJobs, areasForServiceLocations, locationsOfServices, usedServiceLocations, possiblePersonsForService, nearestLinkPerPerson, crsTransformationNetworkAndShape, i); - double serviceTime = newDemandInformationElement.getFirstJobElementTimePerUnit(); int demandForThisLink = 1; + double serviceTime = jobDurationCalculator.calculateServiceDuration(newDemandInformationElement.getFirstJobElementTimePerUnit(), demandForThisLink); usedServiceLocations.add(link.getId().toString()); Id idNewService = Id.create( createJobId(scenario, newDemandInformationElement, link.getId(), null), @@ -673,13 +678,19 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) { countOfLinks++; Carrier thisCarrier = CarriersUtils.getCarriers(scenario).getCarriers() .get(Id.create(newDemandInformationElement.getCarrierName(), Carrier.class)); - int numberOfJobsForDemand = calculateNumberOfJobsForDemand(thisCarrier, demandForThisLink); - for (int i = 0; i < numberOfJobsForDemand; i++) { - int singleDemandForThisLink = demandForThisLink / numberOfJobsForDemand; - if (i == numberOfJobsForDemand - 1) - singleDemandForThisLink = demandForThisLink - (numberOfJobsForDemand - 1) * singleDemandForThisLink; - double serviceTime = newDemandInformationElement.getFirstJobElementTimePerUnit() - * singleDemandForThisLink; + int handledDemand = 0; + //the number of jobs on this link is calculated based on the available vehicles + double largestPossibleDemandSize = getLargestVehicleCapacity(thisCarrier); + while (handledDemand < demandForThisLink) { + int singleDemandForThisLink; + if (demandForThisLink - handledDemand < largestPossibleDemandSize) + singleDemandForThisLink = demandForThisLink - handledDemand; + else + singleDemandForThisLink = (int)largestPossibleDemandSize; + handledDemand = handledDemand + singleDemandForThisLink; + double serviceTime = jobDurationCalculator.calculateServiceDuration( + newDemandInformationElement.getFirstJobElementTimePerUnit(), singleDemandForThisLink); + Id idNewService = Id.create( createJobId(scenario, newDemandInformationElement, link.getId(), null), CarrierService.class); @@ -718,16 +729,18 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) { int demandForThisLink = calculateDemandForThisLink(demandToDistribute, numberOfJobs, distributedDemand, i); Carrier thisCarrier = CarriersUtils.getCarriers(scenario).getCarriers() .get(Id.create(newDemandInformationElement.getCarrierName(), Carrier.class)); - int numberOfJobsForDemand = calculateNumberOfJobsForDemand(thisCarrier, demandForThisLink); - for (int j = 0; j < numberOfJobsForDemand; j++) { - int singleDemandForThisLink = demandForThisLink / numberOfJobsForDemand; - if (j == numberOfJobsForDemand - 1) - singleDemandForThisLink = demandForThisLink - (numberOfJobsForDemand - 1) * singleDemandForThisLink; - double serviceTime; - if (singleDemandForThisLink == 0) - serviceTime = newDemandInformationElement.getFirstJobElementTimePerUnit(); + int handledDemand = 0; + //the number of jobs on this link is calculated based on the available vehicles + double largestPossibleDemandSize = getLargestVehicleCapacity(thisCarrier); + while (handledDemand < demandForThisLink || demandToDistribute == 0) { + int singleDemandForThisLink; + if (demandForThisLink - handledDemand < largestPossibleDemandSize) + singleDemandForThisLink = demandForThisLink - handledDemand; else - serviceTime = newDemandInformationElement.getFirstJobElementTimePerUnit() * demandForThisLink; + singleDemandForThisLink = (int)largestPossibleDemandSize; + handledDemand = handledDemand + singleDemandForThisLink; + double serviceTime = jobDurationCalculator.calculateServiceDuration( + newDemandInformationElement.getFirstJobElementTimePerUnit(), singleDemandForThisLink); usedServiceLocations.add(link.getId().toString()); Id idNewService = Id.create( @@ -740,28 +753,28 @@ else if (samplingOption.equals("changeNumberOfLocationsWithDemand")) { CarriersUtils.getCarriers(scenario).getCarriers() .get(Id.create(newDemandInformationElement.getCarrierName(), Carrier.class)).getServices() .put(thisService.getId(), thisService); + if (demandToDistribute == 0) + break; } } distributedDemand = distributedDemand + demandForThisLink; } } - if (combineSimilarJobs) - reduceNumberOfJobsIfSameCharacteristics(scenario, newDemandInformationElement); } /** * Creates the shipments of a carrier. * - * @param scenario Scenario - * @param newDemandInformationElement single DemandInformationElement - * @param indexShape ShpOptions.Index for the shape file - * @param population Population - * @param combineSimilarJobs boolean if the jobs of the same carrier with same location and time will be combined - * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file + * @param scenario Scenario + * @param newDemandInformationElement single DemandInformationElement + * @param indexShape ShpOptions.Index for the shape file + * @param population Population + * @param crsTransformationNetworkAndShape CoordinateTransformation for the network and shape file + * @param jobDurationCalculator Calculator for the job duration */ private static void createShipments(Scenario scenario, DemandInformationElement newDemandInformationElement, - ShpOptions.Index indexShape, Population population, boolean combineSimilarJobs, - CoordinateTransformation crsTransformationNetworkAndShape) { + ShpOptions.Index indexShape, Population population, + CoordinateTransformation crsTransformationNetworkAndShape, JobDurationCalculator jobDurationCalculator) { int countOfLinks = 1; int distributedDemand = 0; @@ -900,7 +913,7 @@ else if (population == null) if (!usedDeliveryLocations.contains(linkDelivery.getId().toString())) usedDeliveryLocations.add(linkDelivery.getId().toString()); - createSingleShipment(scenario, newDemandInformationElement, linkPickup, linkDelivery, demandForThisLink); + createSingleShipment(scenario, newDemandInformationElement, linkPickup, linkDelivery, demandForThisLink, jobDurationCalculator); } } else // creates a demand on each link, demand depends on the length of the link @@ -968,7 +981,7 @@ else if (numberOfPickupLocations != null) { if (demandForThisLink > 0) { createSingleShipment(scenario, newDemandInformationElement, linkPickup, linkDelivery, - demandForThisLink); + demandForThisLink, jobDurationCalculator); } distributedDemand = distributedDemand + demandForThisLink; } @@ -998,52 +1011,52 @@ else if (numberOfPickupLocations != null) { usedDeliveryLocations.add(linkDelivery.getId().toString()); createSingleShipment(scenario, newDemandInformationElement, linkPickup, linkDelivery, - demandForThisLink); + demandForThisLink, jobDurationCalculator); distributedDemand = distributedDemand + demandForThisLink; } } - if (combineSimilarJobs) - reduceNumberOfJobsIfSameCharacteristics(scenario, newDemandInformationElement); } - /** Creates a single shipment. + /** + * Creates a single shipment. + * * @param scenario Scenario * @param newDemandInformationElement single DemandInformationElement * @param linkPickup Link for the pickup * @param linkDelivery Link for the delivery * @param demandForThisLink Demand for this link + * @param jobDurationCalculator Calculator for the job duration */ private static void createSingleShipment(Scenario scenario, DemandInformationElement newDemandInformationElement, - Link linkPickup, Link linkDelivery, int demandForThisLink) { + Link linkPickup, Link linkDelivery, int demandForThisLink, JobDurationCalculator jobDurationCalculator) { Carrier thisCarrier = CarriersUtils.getCarriers(scenario).getCarriers() .get(Id.create(newDemandInformationElement.getCarrierName(), Carrier.class)); - int numberOfJobsForDemand = calculateNumberOfJobsForDemand(thisCarrier, demandForThisLink); - + double largestPossibleDemandSize = getLargestVehicleCapacity(thisCarrier); + int handledDemand = 0; TimeWindow timeWindowPickup = newDemandInformationElement.getFirstJobElementTimeWindow(); TimeWindow timeWindowDelivery = newDemandInformationElement.getSecondJobElementTimeWindow(); - for (int i = 0; i < numberOfJobsForDemand; i++) { + while (handledDemand < demandForThisLink || demandForThisLink == 0) { Id idNewShipment = Id.create(createJobId(scenario, newDemandInformationElement, linkPickup.getId(), linkDelivery.getId()), CarrierShipment.class); - double serviceTimePickup; - double serviceTimeDelivery; - int singleDemandForThisLink = Math.round ((float) demandForThisLink / numberOfJobsForDemand); - if (i == numberOfJobsForDemand - 1) - singleDemandForThisLink = demandForThisLink - (numberOfJobsForDemand - 1) * singleDemandForThisLink; - if (singleDemandForThisLink == 0) { - serviceTimePickup = newDemandInformationElement.getFirstJobElementTimePerUnit(); - serviceTimeDelivery = newDemandInformationElement.getSecondJobElementTimePerUnit(); - } else { - serviceTimePickup = newDemandInformationElement.getFirstJobElementTimePerUnit() * singleDemandForThisLink; - serviceTimeDelivery = newDemandInformationElement.getSecondJobElementTimePerUnit() * singleDemandForThisLink; - } + int singleDemandForThisLink; + if (demandForThisLink - handledDemand < largestPossibleDemandSize) + singleDemandForThisLink = demandForThisLink - handledDemand; + else + singleDemandForThisLink = (int)largestPossibleDemandSize; + handledDemand = handledDemand + singleDemandForThisLink; + double serviceTimePickup = jobDurationCalculator.calculatePickupDuration(newDemandInformationElement.getFirstJobElementTimePerUnit(), singleDemandForThisLink); + double serviceTimeDelivery = jobDurationCalculator.calculateDeliveryDuration(newDemandInformationElement.getSecondJobElementTimePerUnit(), singleDemandForThisLink); + CarrierShipment thisShipment = CarrierShipment.Builder .newInstance(idNewShipment, linkPickup.getId(), linkDelivery.getId(), singleDemandForThisLink) .setPickupServiceTime(serviceTimePickup).setPickupTimeWindow(timeWindowPickup) .setDeliveryServiceTime(serviceTimeDelivery).setDeliveryTimeWindow(timeWindowDelivery) .build(); thisCarrier.getShipments().put(thisShipment.getId(), thisShipment); + if (demandForThisLink == 0) + break; } } @@ -1051,21 +1064,16 @@ private static void createSingleShipment(Scenario scenario, DemandInformationEle * Method calculates the number of jobs for a demand on one link based on the largest vehicle capacity of the carrier. * * @param thisCarrier the carrier of a job - * @param demandForThisLink Demand for this link * @return Number of jobs for this demand */ - private static int calculateNumberOfJobsForDemand(Carrier thisCarrier, int demandForThisLink) { + private static double getLargestVehicleCapacity(Carrier thisCarrier) { double largestVehicleCapacity = 0; for (CarrierVehicle vehicle : thisCarrier.getCarrierCapabilities().getCarrierVehicles().values()) { if (vehicle.getType().getCapacity().getOther() > largestVehicleCapacity) { largestVehicleCapacity = vehicle.getType().getCapacity().getOther(); } } - if (demandForThisLink > largestVehicleCapacity) { - log.info("Demand {} is larger than the largest vehicle capacity ({}). Splitting demand into multiple jobs.", demandForThisLink, largestVehicleCapacity); - return (int) Math.ceil((double) demandForThisLink / largestVehicleCapacity); - } - return 1; + return largestVehicleCapacity; } /** @@ -1160,109 +1168,102 @@ private static int calculateDemandBasedOnLinkLength(int countOfLinks, int distri * If jobs of a carrier have the same characteristics (time window, location), * they will be combined to one job. * - * @param scenario Scenario - * @param newDemandInformationElement single DemandInformationElement + * @param scenario Scenario */ - private static void reduceNumberOfJobsIfSameCharacteristics(Scenario scenario, - DemandInformationElement newDemandInformationElement) { + private static void combineSimilarJobs(Scenario scenario) { log.warn( "The number of Jobs will be reduced if jobs have the same characteristics (e.g. time, location, carrier)"); - int connectedJobs = 0; - if (newDemandInformationElement.getTypeOfDemand().equals("shipment")) { - HashMap, CarrierShipment> shipmentsToRemove = new HashMap<>(); - ArrayList shipmentsToAdd = new ArrayList<>(); - Carrier thisCarrier = CarriersUtils.getCarriers(scenario).getCarriers() - .get(Id.create(newDemandInformationElement.getCarrierName(), Carrier.class)); - for (Id baseShipmentId : thisCarrier.getShipments().keySet()) { - if (!shipmentsToRemove.containsKey(baseShipmentId)) { - CarrierShipment baseShipment = thisCarrier.getShipments().get(baseShipmentId); - HashMap, CarrierShipment> shipmentsToConnect = new HashMap<>(); - shipmentsToConnect.put(baseShipmentId, baseShipment); - for (Id thisShipmentId : thisCarrier.getShipments().keySet()) { - if (!shipmentsToRemove.containsKey(thisShipmentId)) { - CarrierShipment thisShipment = thisCarrier.getShipments().get(thisShipmentId); - if (baseShipment.getId() != thisShipment.getId() + for (Carrier thisCarrier : CarriersUtils.getCarriers(scenario).getCarriers().values()) { + if (!thisCarrier.getShipments().isEmpty()) { + int shipmentsBeforeConnection = thisCarrier.getShipments().size(); + HashMap, CarrierShipment> shipmentsToRemove = new HashMap<>(); + ArrayList shipmentsToAdd = new ArrayList<>(); + for (Id baseShipmentId : thisCarrier.getShipments().keySet()) { + if (!shipmentsToRemove.containsKey(baseShipmentId)) { + CarrierShipment baseShipment = thisCarrier.getShipments().get(baseShipmentId); + HashMap, CarrierShipment> shipmentsToConnect = new HashMap<>(); + shipmentsToConnect.put(baseShipmentId, baseShipment); + for (Id thisShipmentId : thisCarrier.getShipments().keySet()) { + if (!shipmentsToRemove.containsKey(thisShipmentId)) { + CarrierShipment thisShipment = thisCarrier.getShipments().get(thisShipmentId); + if (baseShipment.getId() != thisShipment.getId() && baseShipment.getFrom() == thisShipment.getFrom() && baseShipment.getTo() == thisShipment.getTo() && baseShipment.getPickupTimeWindow() == thisShipment.getPickupTimeWindow() && baseShipment.getDeliveryTimeWindow() == thisShipment.getDeliveryTimeWindow()) - shipmentsToConnect.put(thisShipmentId, thisShipment); + shipmentsToConnect.put(thisShipmentId, thisShipment); + } } - } - Id idNewShipment = baseShipment.getId(); - int demandForThisLink = 0; - double serviceTimePickup = 0; - double serviceTimeDelivery = 0; - for (CarrierShipment carrierShipment : shipmentsToConnect.values()) { - demandForThisLink = demandForThisLink + carrierShipment.getSize(); - serviceTimePickup = serviceTimePickup + carrierShipment.getPickupServiceTime(); - serviceTimeDelivery = serviceTimeDelivery + carrierShipment.getDeliveryServiceTime(); - shipmentsToRemove.put(carrierShipment.getId(), carrierShipment); - connectedJobs++; - } - CarrierShipment newShipment = CarrierShipment.Builder + Id idNewShipment = baseShipment.getId(); + int demandForThisLink = 0; + double serviceTimePickup = 0; + double serviceTimeDelivery = 0; + for (CarrierShipment carrierShipment : shipmentsToConnect.values()) { + demandForThisLink = demandForThisLink + carrierShipment.getSize(); + serviceTimePickup = serviceTimePickup + carrierShipment.getPickupServiceTime(); + serviceTimeDelivery = serviceTimeDelivery + carrierShipment.getDeliveryServiceTime(); + shipmentsToRemove.put(carrierShipment.getId(), carrierShipment); + } + CarrierShipment newShipment = CarrierShipment.Builder .newInstance(idNewShipment, baseShipment.getFrom(), baseShipment.getTo(), demandForThisLink) .setPickupServiceTime(serviceTimePickup) .setPickupTimeWindow(baseShipment.getPickupTimeWindow()) .setDeliveryServiceTime(serviceTimeDelivery) .setDeliveryTimeWindow(baseShipment.getDeliveryTimeWindow()).build(); + shipmentsToAdd.add(newShipment); + } + } + for (CarrierShipment id : shipmentsToRemove.values()) + thisCarrier.getShipments().remove(id.getId(), id); - shipmentsToAdd.add(newShipment); - connectedJobs++; + for (CarrierShipment carrierShipment : shipmentsToAdd) { + thisCarrier.getShipments().put(carrierShipment.getId(), carrierShipment); } - } - for (CarrierShipment id : shipmentsToRemove.values()) - thisCarrier.getShipments().remove(id.getId(), id); - for (CarrierShipment carrierShipment : shipmentsToAdd) { - thisCarrier.getShipments().put(carrierShipment.getId(), carrierShipment); + log.warn("Number of reduced shipments for carrier {}: {}", thisCarrier.getId().toString(), shipmentsBeforeConnection - thisCarrier.getShipments().size()); } - log.warn("Number of reduced shipments: {}", connectedJobs); - } - if (newDemandInformationElement.getTypeOfDemand().equals("service")) { - HashMap, CarrierService> servicesToRemove = new HashMap<>(); - ArrayList servicesToAdd = new ArrayList<>(); - Carrier thisCarrier = CarriersUtils.getCarriers(scenario).getCarriers() - .get(Id.create(newDemandInformationElement.getCarrierName(), Carrier.class)); - for (Id baseServiceId : thisCarrier.getServices().keySet()) { - if (!servicesToRemove.containsKey(baseServiceId)) { - CarrierService baseService = thisCarrier.getServices().get(baseServiceId); - HashMap, CarrierService> servicesToConnect = new HashMap<>(); - servicesToConnect.put(baseServiceId, baseService); - for (Id thisServiceId : thisCarrier.getServices().keySet()) { - if (!servicesToRemove.containsKey(thisServiceId)) { - CarrierService thisService = thisCarrier.getServices().get(thisServiceId); - if (baseService.getId() != thisService.getId() + if (!thisCarrier.getServices().isEmpty()) { + int servicesBeforeConnection = thisCarrier.getServices().size(); + HashMap, CarrierService> servicesToRemove = new HashMap<>(); + ArrayList servicesToAdd = new ArrayList<>(); + for (Id baseServiceId : thisCarrier.getServices().keySet()) { + if (!servicesToRemove.containsKey(baseServiceId)) { + CarrierService baseService = thisCarrier.getServices().get(baseServiceId); + HashMap, CarrierService> servicesToConnect = new HashMap<>(); + servicesToConnect.put(baseServiceId, baseService); + for (Id thisServiceId : thisCarrier.getServices().keySet()) { + if (!servicesToRemove.containsKey(thisServiceId)) { + CarrierService thisService = thisCarrier.getServices().get(thisServiceId); + if (baseService.getId() != thisService.getId() && baseService.getLocationLinkId() == thisService.getLocationLinkId() && baseService - .getServiceStartTimeWindow() == thisService.getServiceStartTimeWindow()) - servicesToConnect.put(thisServiceId, thisService); + .getServiceStartTimeWindow() == thisService.getServiceStartTimeWindow()) + servicesToConnect.put(thisServiceId, thisService); + } } - } - Id idNewService = baseService.getId(); - int demandForThisLink = 0; - double serviceTimeService = 0; - for (CarrierService carrierService : servicesToConnect.values()) { - demandForThisLink = demandForThisLink + carrierService.getCapacityDemand(); - serviceTimeService = serviceTimeService + carrierService.getServiceDuration(); - servicesToRemove.put(carrierService.getId(), carrierService); - connectedJobs++; - } - CarrierService newService = CarrierService.Builder + Id idNewService = baseService.getId(); + int demandForThisLink = 0; + double serviceTimeService = 0; + for (CarrierService carrierService : servicesToConnect.values()) { + demandForThisLink = demandForThisLink + carrierService.getCapacityDemand(); + serviceTimeService = serviceTimeService + carrierService.getServiceDuration(); + servicesToRemove.put(carrierService.getId(), carrierService); + } + CarrierService newService = CarrierService.Builder .newInstance(idNewService, baseService.getLocationLinkId()) .setServiceDuration(serviceTimeService) .setServiceStartTimeWindow(baseService.getServiceStartTimeWindow()) .setCapacityDemand(demandForThisLink).build(); - servicesToAdd.add(newService); - connectedJobs++; + servicesToAdd.add(newService); + } } + for (CarrierService id : servicesToRemove.values()) + thisCarrier.getServices().remove(id.getId(), id); + for (CarrierService carrierService : servicesToAdd) { + thisCarrier.getServices().put(carrierService.getId(), carrierService); + } + log.warn("Number of reduced services for carrier {}: {}", thisCarrier.getId().toString(), servicesBeforeConnection - thisCarrier.getServices().size()); } - for (CarrierService id : servicesToRemove.values()) - thisCarrier.getServices().remove(id.getId(), id); - for (CarrierService carrierService : servicesToAdd) { - thisCarrier.getServices().put(carrierService.getId(), carrierService); - } - log.warn("Number of reduced shipments: {}", connectedJobs); } } diff --git a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGeneration.java b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGeneration.java index 368dddaad69..ac3c44cb5a6 100644 --- a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGeneration.java +++ b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGeneration.java @@ -62,6 +62,8 @@ + " * these files are given in the example project. See: TODO", showDefaultValues = true) public class FreightDemandGeneration implements MATSimAppCommand { + private final JobDurationCalculator jobDurationCalculator; + private enum CarrierInputOptions { readCarrierFile, createCarriersFromCSV, addCSVDataToExistingCarrierFileData } @@ -154,6 +156,16 @@ private enum OptionsOfVRPSolutions { @CommandLine.Option(names = "--defaultJspritIterations", description = "Set the default number of jsprit iterations.") private int defaultJspritIterations; + public FreightDemandGeneration() { + this.jobDurationCalculator = new DefaultJobDurationCalculator(); + log.info("Using default {} for job duration calculation", jobDurationCalculator.getClass().getSimpleName()); + } + + public FreightDemandGeneration(JobDurationCalculator jobDurationCalculator) { + this.jobDurationCalculator = jobDurationCalculator; + log.info("Using {} for job duration calculation", jobDurationCalculator.getClass().getSimpleName()); + } + public static void main(String[] args) { System.exit(new CommandLine(new FreightDemandGeneration()).execute(args)); } @@ -353,7 +365,7 @@ private void createDemand(DemandGenerationOptions selectedDemandGenerationOption case createDemandFromCSV -> // creates the demand by using the information given in the read csv file DemandReaderFromCSV.readAndCreateDemand(scenario, csvLocationDemand, indexShape, combineSimilarJobs, - crsTransformationNetworkAndShape, null, shapeCategory); + crsTransformationNetworkAndShape, null, shapeCategory, jobDurationCalculator); case createDemandFromCSVAndUsePopulation -> { /* * Option creates the demand by using the information given in the read csv file @@ -399,14 +411,14 @@ private void createDemand(DemandGenerationOptions selectedDemandGenerationOption case useHolePopulation: // uses the hole population as possible demand locations DemandReaderFromCSV.readAndCreateDemand(scenario, csvLocationDemand, indexShape, - combineSimilarJobs, crsTransformationNetworkAndShape, population, shapeCategory); + combineSimilarJobs, crsTransformationNetworkAndShape, population, shapeCategory, jobDurationCalculator); break; case usePopulationInShape: // uses only the population with home location in the given shape file FreightDemandGenerationUtils.reducePopulationToShapeArea(population, shp.createIndex(populationCRS, "_")); DemandReaderFromCSV.readAndCreateDemand(scenario, csvLocationDemand, indexShape, - combineSimilarJobs, crsTransformationNetworkAndShape, population, shapeCategory); + combineSimilarJobs, crsTransformationNetworkAndShape, population, shapeCategory, jobDurationCalculator); break; default: throw new RuntimeException("No valid population option selected!"); diff --git a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGenerationUtils.java b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGenerationUtils.java index c60a8a39390..ede28ace76d 100644 --- a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGenerationUtils.java +++ b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/FreightDemandGenerationUtils.java @@ -152,7 +152,7 @@ static void createDemandLocationsFile(Controler controler) { writer.flush(); } catch (IOException e) { - e.printStackTrace(); + log.error("Could not write job locations file under " + "/outputLocationFile.xml.gz"); } log.info("Wrote job locations file under " + "/outputLocationFile.xml.gz"); } diff --git a/contribs/application/src/main/java/org/matsim/freightDemandGeneration/JobDurationCalculator.java b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/JobDurationCalculator.java new file mode 100644 index 00000000000..85d30908fa0 --- /dev/null +++ b/contribs/application/src/main/java/org/matsim/freightDemandGeneration/JobDurationCalculator.java @@ -0,0 +1,39 @@ +package org.matsim.freightDemandGeneration; + +import org.matsim.api.core.v01.Scenario; + +public interface JobDurationCalculator { + /** + * Calculates the duration of a service in seconds. + * + * @param serviceDurationPerUnit in seconds + * @param demandForThisService amount of demand for this service + * @return duration in seconds + */ + double calculateServiceDuration(Integer serviceDurationPerUnit, int demandForThisService); + + /** + * Calculates the duration of a pickup in seconds. + * + * @param pickupDurationPerUnit in seconds + * @param demandForThisShipment amount of demand for this shipment + * @return duration in seconds + */ + double calculatePickupDuration(Integer pickupDurationPerUnit, int demandForThisShipment); + + /** + * Calculates the duration of a delivery in seconds. + * + * @param deliveryDurationPerUnit in seconds + * @param demandForThisShipment amount of demand for this shipment + * @return duration in seconds + */ + double calculateDeliveryDuration(Integer deliveryDurationPerUnit, int demandForThisShipment); + + /** + * Recalculates the job durations for all jobs in the scenario. The devault implementation does nothing. + * + * @param scenario scenario + */ + void recalculateJobDurations(Scenario scenario); +} diff --git a/contribs/application/src/test/java/org/matsim/freightDemandGeneration/DemandReaderFromCSVTest.java b/contribs/application/src/test/java/org/matsim/freightDemandGeneration/DemandReaderFromCSVTest.java index c27b251afc4..9cd21c16d9d 100644 --- a/contribs/application/src/test/java/org/matsim/freightDemandGeneration/DemandReaderFromCSVTest.java +++ b/contribs/application/src/test/java/org/matsim/freightDemandGeneration/DemandReaderFromCSVTest.java @@ -79,10 +79,10 @@ void demandCreationWithSampleWithChangeNumberOfLocations() throws IOException { String populationLocation = utils.getPackageInputDirectory() + "testPopulation.xml"; Population population = PopulationUtils.readPopulation(populationLocation); FreightDemandGenerationUtils.preparePopulation(population, 0.5, 1.0, "changeNumberOfLocationsWithDemand"); - + Boolean combineSimilarJobs = false; // run methods createDemandAndCheckCarrier(carrierCSVLocation, scenario, freightCarriersConfigGroup, indexShape, demandCSVLocation, shapeCategory, - population); + population, combineSimilarJobs); Network network = scenario.getNetwork(); @@ -147,10 +147,11 @@ void demandCreationWithSampleWithDemandOnLocation() throws IOException { String populationLocation = utils.getPackageInputDirectory() + "testPopulation.xml"; Population population = PopulationUtils.readPopulation(populationLocation); FreightDemandGenerationUtils.preparePopulation(population, 0.5, 1.0, "changeDemandOnLocation"); + Boolean combineSimilarJobs = false; - createDemandAndCheckCarrier(carrierCSVLocation, scenario, freightCarriersConfigGroup, indexShape, demandCSVLocation, shapeCategory, population); + createDemandAndCheckCarrier(carrierCSVLocation, scenario, freightCarriersConfigGroup, indexShape, demandCSVLocation, shapeCategory, + population, combineSimilarJobs); - // check carrier 1 Network network = scenario.getNetwork(); checkCarrier1and2(scenario, network, indexShape); @@ -194,6 +195,74 @@ void demandCreationWithSampleWithDemandOnLocation() throws IOException { } } + @Test + void demandCreationWithSampleWithDemandOnLocationWithCombiningJobs() throws IOException { + // read inputs + Config config = ConfigUtils.createConfig(); + config.network().setInputFile( + "https://raw.githubusercontent.com/matsim-org/matsim-libs/master/examples/scenarios/freight-chessboard-9x9/grid9x9.xml"); + Scenario scenario = ScenarioUtils.loadScenario(config); + FreightCarriersConfigGroup freightCarriersConfigGroup = ConfigUtils.addOrGetModule(scenario.getConfig(), + FreightCarriersConfigGroup.class); + freightCarriersConfigGroup.setCarriersVehicleTypesFile(utils.getPackageInputDirectory() + "testVehicleTypes.xml"); + Path carrierCSVLocation = Path.of(utils.getPackageInputDirectory() + "testCarrierCSV.csv"); + Path demandCSVLocation = Path.of(utils.getPackageInputDirectory() + "testDemandCSV.csv"); + Path shapeFilePath = Path.of(utils.getPackageInputDirectory() + "testShape/testShape.shp"); + ShpOptions shp = new ShpOptions(shapeFilePath, "WGS84", null); + String shapeCategory = "Ortsteil"; + ShpOptions.Index indexShape = shp.createIndex("Ortsteil"); + String populationLocation = utils.getPackageInputDirectory() + "testPopulation.xml"; + Population population = PopulationUtils.readPopulation(populationLocation); + FreightDemandGenerationUtils.preparePopulation(population, 0.5, 1.0, "changeDemandOnLocation"); + Boolean combineSimilarJobs = true; + + createDemandAndCheckCarrier(carrierCSVLocation, scenario, freightCarriersConfigGroup, indexShape, demandCSVLocation, shapeCategory, + population, combineSimilarJobs); + + Network network = scenario.getNetwork(); + + checkCarrier1and2WithCombiningJobs(scenario, network, indexShape); + int countDemand; + Object2IntMap countShipmentsWithCertainDemand; + Map> locationsPerShipmentElement; + + // check carrier 3 + Carrier testCarrier3 = CarriersUtils.getCarriers(scenario).getCarriers() + .get(Id.create("testCarrier3", Carrier.class)); + Assertions.assertEquals(0, testCarrier3.getServices().size()); + Assertions.assertEquals(2, testCarrier3.getShipments().size()); + countShipmentsWithCertainDemand = new Object2IntOpenHashMap<>(); + locationsPerShipmentElement = new HashMap<>(); + countDemand = 0; + for (CarrierShipment shipment : testCarrier3.getShipments().values()) { + countShipmentsWithCertainDemand.merge((Integer) shipment.getSize(), 1, Integer::sum); + countDemand = countDemand + shipment.getSize(); + Assertions.assertEquals(10, shipment.getSize()); + Assertions.assertEquals(4000, shipment.getPickupServiceTime(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(2500, shipment.getDeliveryServiceTime(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(TimeWindow.newInstance(8000, 50000), shipment.getPickupTimeWindow()); + Assertions.assertEquals(TimeWindow.newInstance(10000, 60000), shipment.getDeliveryTimeWindow()); + locationsPerShipmentElement.computeIfAbsent("ShipmentElement1_pickup", (k) -> new HashSet<>()) + .add(shipment.getFrom().toString()); + locationsPerShipmentElement.computeIfAbsent("ShipmentElement1_delivery", (k) -> new HashSet<>()) + .add(shipment.getTo().toString()); + } + Assertions.assertEquals(20, countDemand); + Assertions.assertEquals(2, countShipmentsWithCertainDemand.getInt(10)); + Assertions.assertEquals(1, locationsPerShipmentElement.get("ShipmentElement1_pickup").size()); + Assertions.assertEquals(2, locationsPerShipmentElement.get("ShipmentElement1_delivery").size()); + for (String locationsOfShipmentElement : locationsPerShipmentElement.get("ShipmentElement1_delivery")) { + Link link = network.getLinks().get(Id.createLinkId(locationsOfShipmentElement)); + Assertions.assertTrue( + FreightDemandGenerationUtils.checkPositionInShape(link, null, indexShape, null, null)); + Assertions.assertTrue(FreightDemandGenerationUtils.checkPositionInShape(link, null, indexShape, + new String[] { "area1" }, null)); + Assertions.assertFalse(FreightDemandGenerationUtils.checkPositionInShape(link, null, indexShape, + new String[] { "area2" }, null)); + } + } + + @Test void demandCreationNoSampling() throws IOException { // read inputs @@ -213,10 +282,11 @@ void demandCreationNoSampling() throws IOException { String populationLocation = utils.getPackageInputDirectory() + "testPopulation.xml"; Population population = PopulationUtils.readPopulation(populationLocation); FreightDemandGenerationUtils.preparePopulation(population, 0.5, 0.5, "changeDemandOnLocation"); + Boolean combineSimilarJobs = false; // run methods createDemandAndCheckCarrier(carrierCSVLocation, scenario, freightCarriersConfigGroup, indexShape, demandCSVLocation, shapeCategory, - population); + population, combineSimilarJobs); // check carrier 1 Network network = scenario.getNetwork(); @@ -367,7 +437,8 @@ void csvDemandReader() throws IOException { private static void createDemandAndCheckCarrier(Path carrierCSVLocation, Scenario scenario, FreightCarriersConfigGroup freightCarriersConfigGroup, ShpOptions.Index indexShape, Path demandCSVLocation, String shapeCategory, - Population population) throws IOException { + Population population, Boolean combineSimilarJobs) throws IOException { + JobDurationCalculator jobDurationCalculator = new DefaultJobDurationCalculator(); // run methods Set allNewCarrierInformation = CarrierReaderFromCSV .readCarrierInformation(carrierCSVLocation); @@ -375,8 +446,8 @@ private static void createDemandAndCheckCarrier(Path carrierCSVLocation, Scenari indexShape, 1, null); Set demandInformation = DemandReaderFromCSV.readDemandInformation(demandCSVLocation); DemandReaderFromCSV.checkNewDemand(scenario, demandInformation, indexShape, shapeCategory); - DemandReaderFromCSV.createDemandForCarriers(scenario, indexShape, demandInformation, population, false, - null); + DemandReaderFromCSV.createDemandForCarriers(scenario, indexShape, demandInformation, population, combineSimilarJobs, + null, jobDurationCalculator); Assertions.assertEquals(3, CarriersUtils.getCarriers(scenario).getCarriers().size()); Assertions.assertTrue( CarriersUtils.getCarriers(scenario).getCarriers().containsKey(Id.create("testCarrier1", Carrier.class))); @@ -491,4 +562,89 @@ private static void checkCarrier1and2(Scenario scenario, Network network, ShpOpt Assertions.assertEquals(1, locationsPerShipmentElement.get("ShipmentElement2_pickup").size()); Assertions.assertEquals(2, locationsPerShipmentElement.get("ShipmentElement2_delivery").size()); } + + /** + * Results after combing jobs. + * + * @param scenario the scenario + * @param network the network + * @param indexShape the index of the shape + */ + private static void checkCarrier1and2WithCombiningJobs(Scenario scenario, Network network, ShpOptions.Index indexShape) { + Carrier testCarrier1 = CarriersUtils.getCarriers(scenario).getCarriers() + .get(Id.create("testCarrier1", Carrier.class)); + Assertions.assertEquals(8, testCarrier1.getServices().size()); + Assertions.assertEquals(0, testCarrier1.getShipments().size()); + Object2IntMap countServicesWithCertainDemand = new Object2IntOpenHashMap<>(); + Map> locationsPerServiceElement = new HashMap<>(); + int countDemand = 0; + for (CarrierService service : testCarrier1.getServices().values()) { + countServicesWithCertainDemand.merge((Integer) service.getCapacityDemand(), 1, Integer::sum); + countDemand = countDemand + service.getCapacityDemand(); + if (service.getCapacityDemand() == 0) { + Assertions.assertEquals(180, service.getServiceDuration(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(TimeWindow.newInstance(3000, 13000), service.getServiceStartTimeWindow()); + locationsPerServiceElement.computeIfAbsent("serviceElement1", (k) -> new HashSet<>()) + .add(service.getLocationLinkId().toString()); + } else { + Assertions.assertEquals(service.getCapacityDemand() * 100, service.getServiceDuration(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(TimeWindow.newInstance(5000, 20000), service.getServiceStartTimeWindow()); + locationsPerServiceElement.computeIfAbsent("serviceElement2", (k) -> new HashSet<>()) + .add(service.getLocationLinkId().toString()); + } + } + Assertions.assertEquals(12, countDemand); + Assertions.assertEquals(4, countServicesWithCertainDemand.getInt(0)); + Assertions.assertEquals(4, locationsPerServiceElement.get("serviceElement1").size()); + for (String locationsOfServiceElement : locationsPerServiceElement.get("serviceElement1")) { + Link link = network.getLinks().get(Id.createLinkId(locationsOfServiceElement)); + Assertions.assertTrue( + FreightDemandGenerationUtils.checkPositionInShape(link, null, indexShape, null, null)); + Assertions.assertFalse(FreightDemandGenerationUtils.checkPositionInShape(link, null, indexShape, + new String[] { "area1" }, null)); + Assertions.assertTrue(FreightDemandGenerationUtils.checkPositionInShape(link, null, indexShape, + new String[] { "area2" }, null)); + } + Assertions.assertEquals(4, locationsPerServiceElement.get("serviceElement2").size()); + Assertions.assertTrue(locationsPerServiceElement.get("serviceElement2").contains("i(2,0)")); + + // check carrier 2 + Carrier testCarrier2 = CarriersUtils.getCarriers(scenario).getCarriers() + .get(Id.create("testCarrier2", Carrier.class)); + Assertions.assertEquals(0, testCarrier2.getServices().size()); + Assertions.assertEquals(6, testCarrier2.getShipments().size()); + Object2IntMap countShipmentsWithCertainDemand = new Object2IntOpenHashMap<>(); + Map> locationsPerShipmentElement = new HashMap<>(); + countDemand = 0; + for (CarrierShipment shipment : testCarrier2.getShipments().values()) { + countShipmentsWithCertainDemand.merge((Integer) shipment.getSize(), 1, Integer::sum); + countDemand = countDemand + shipment.getSize(); + if (shipment.getSize() == 0) { + Assertions.assertEquals(300, shipment.getPickupServiceTime(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(350, shipment.getDeliveryServiceTime(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(TimeWindow.newInstance(10000, 45000), shipment.getPickupTimeWindow()); + Assertions.assertEquals(TimeWindow.newInstance(11000, 44000), shipment.getDeliveryTimeWindow()); + locationsPerShipmentElement.computeIfAbsent("ShipmentElement1_pickup", (k) -> new HashSet<>()) + .add(shipment.getFrom().toString()); + locationsPerShipmentElement.computeIfAbsent("ShipmentElement1_delivery", (k) -> new HashSet<>()) + .add(shipment.getTo().toString()); + } else { + Assertions.assertEquals(shipment.getSize() * 200, shipment.getPickupServiceTime(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(shipment.getSize() * 200, shipment.getDeliveryServiceTime(), MatsimTestUtils.EPSILON); + Assertions.assertEquals(TimeWindow.newInstance(11000, 44000), shipment.getPickupTimeWindow()); + Assertions.assertEquals(TimeWindow.newInstance(20000, 40000), shipment.getDeliveryTimeWindow()); + locationsPerShipmentElement.computeIfAbsent("ShipmentElement2_pickup", (k) -> new HashSet<>()) + .add(shipment.getFrom().toString()); + locationsPerShipmentElement.computeIfAbsent("ShipmentElement2_delivery", (k) -> new HashSet<>()) + .add(shipment.getTo().toString()); + } + } + Assertions.assertEquals(15, countDemand); + Assertions.assertEquals(4, countShipmentsWithCertainDemand.getInt(0)); + Assertions.assertEquals(4, locationsPerShipmentElement.get("ShipmentElement1_pickup").size()); + Assertions.assertEquals(1, locationsPerShipmentElement.get("ShipmentElement1_delivery").size()); + Assertions.assertTrue(locationsPerShipmentElement.get("ShipmentElement1_delivery").contains("i(2,0)")); + Assertions.assertEquals(1, locationsPerShipmentElement.get("ShipmentElement2_pickup").size()); + Assertions.assertEquals(2, locationsPerShipmentElement.get("ShipmentElement2_delivery").size()); + } }