Java tutorial
/* * Copyright (C) 2011-2016 Rinde van Lon, iMinds-DistriNet, KU Leuven * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.rinde.rinsim.central; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import javax.annotation.Nullable; import javax.measure.Measure; import javax.measure.quantity.Duration; import javax.measure.quantity.Length; import javax.measure.quantity.Velocity; import javax.measure.unit.Unit; import com.github.rinde.rinsim.central.GlobalStateObject.VehicleStateObject; import com.github.rinde.rinsim.core.Simulator; import com.github.rinde.rinsim.core.SimulatorAPI; import com.github.rinde.rinsim.core.model.ModelProvider; import com.github.rinde.rinsim.core.model.pdp.PDPModel; import com.github.rinde.rinsim.core.model.pdp.PDPModel.VehicleParcelActionInfo; import com.github.rinde.rinsim.core.model.pdp.Parcel; import com.github.rinde.rinsim.core.model.pdp.Vehicle; import com.github.rinde.rinsim.core.model.road.RoadModels; import com.github.rinde.rinsim.core.model.time.Clock; import com.github.rinde.rinsim.core.model.time.TimeModel; import com.github.rinde.rinsim.geom.Point; import com.github.rinde.rinsim.pdptw.common.PDPRoadModel; import com.github.rinde.rinsim.pdptw.common.StatisticsDTO; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultiset; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; import com.google.common.math.DoubleMath; /** * @author Rinde van Lon * */ public final class Solvers { private Solvers() { } /** * Creates a builder for creating {@link SimSolver} instances. For more * information see {@link AdapterBuilder}. * @param sol The solver to use internally. * @return The builder. */ public static AdapterBuilder<SimSolver> solverBuilder(Solver sol) { return new AdapterBuilder<>(sol); } /** * Creates a builder for creating {@link SimulationConverter} instances. * @return The builder. */ public static AdapterBuilder<SimulationConverter> converterBuilder() { return new AdapterBuilder<>(null); } /** * Computes a {@link StatisticsDTO} instance for the given * {@link GlobalStateObject} and routes. For each vehicle in the state the * specified route is used and its arrival times, tardiness and travel times * are computed. The resulting {@link StatisticsDTO} has the same properties * as performing a simulation with the same state. However, since the current * state may be half-way a simulation, it is possible that the returned * statistics describe only a partial simulation. As a result * {@link StatisticsDTO#totalDeliveries} does not necessarily equal * {@link StatisticsDTO#totalPickups}. * @param state The state which represents a simulation. * @param routes Specifies the route the vehicles are currently following, * must be of same size as the number of vehicles (one route per * vehicle). If this is <code>null</code> the * {@link VehicleStateObject#getRoute()} field must be set instead * for <b>each</b> vehicle. * @return The statistics that will be generated when executing this * simulation. */ public static ExtendedStats computeStats(GlobalStateObject state, @Nullable ImmutableList<ImmutableList<Parcel>> routes) { final Optional<ImmutableList<ImmutableList<Parcel>>> r = Optional.fromNullable(routes); if (r.isPresent()) { checkArgument(state.getVehicles().size() == r.get().size(), "Exactly one route should be supplied for every vehicle in state. %s " + "vehicle(s) in state, received %s route(s).", state.getVehicles().size(), r.get().size()); } double totalDistance = 0; int totalDeliveries = 0; int totalPickups = 0; long pickupTardiness = 0; long deliveryTardiness = 0; long overTime = 0; final long startTime = state.getTime(); long maxTime = 0; int movedVehicles = 0; final Set<Parcel> parcels = newHashSet(); final ImmutableList.Builder<ImmutableList<Long>> arrivalTimesBuilder = ImmutableList.builder(); for (int i = 0; i < state.getVehicles().size(); i++) { final VehicleStateObject vso = state.getVehicles().get(i); checkArgument(r.isPresent() || vso.getRoute().isPresent(), "Vehicle routes must either be specified as an argument or must be part" + " of the state object."); final ImmutableList.Builder<Long> truckArrivalTimesBuilder = ImmutableList.builder(); truckArrivalTimesBuilder.add(state.getTime()); ImmutableList<Parcel> route; if (r.isPresent()) { route = r.get().get(i); } else { route = vso.getRoute().get(); } parcels.addAll(route); long time = state.getTime(); Point vehicleLocation = vso.getLocation(); final Measure<Double, Velocity> speed = Measure.valueOf(vso.getDto().getSpeed(), state.getSpeedUnit()); final Set<Parcel> seen = newHashSet(); for (int j = 0; j < route.size(); j++) { final Parcel cur = route.get(j); final boolean inCargo = vso.getContents().contains(cur) || seen.contains(cur); seen.add(cur); if (vso.getDestination().isPresent() && j == 0) { checkArgument(vso.getDestination().asSet().contains(cur), "If a vehicle has a destination, the first position in the route " + "must equal this. Expected %s, is %s.", vso.getDestination().get(), cur); } boolean firstAndServicing = false; if (j == 0 && vso.getRemainingServiceTime() > 0) { // we are already at the service location firstAndServicing = true; truckArrivalTimesBuilder.add(time); time += vso.getRemainingServiceTime(); } else { // vehicle is not there yet, go there first, then service final Point nextLoc = inCargo ? cur.getDeliveryLocation() : cur.getPickupLocation(); final Measure<Double, Length> distance = Measure .valueOf(Point.distance(vehicleLocation, nextLoc), state.getDistUnit()); totalDistance += distance.getValue(); vehicleLocation = nextLoc; final long tt = DoubleMath.roundToLong( RoadModels.computeTravelTime(speed, distance, state.getTimeUnit()), RoundingMode.CEILING); time += tt; } if (inCargo) { // check if we are early if (cur.getDeliveryTimeWindow().isBeforeStart(time)) { time = cur.getDeliveryTimeWindow().begin(); } if (!firstAndServicing) { truckArrivalTimesBuilder.add(time); time += cur.getDeliveryDuration(); } // delivering if (cur.getDeliveryTimeWindow().isAfterEnd(time)) { final long tardiness = time - cur.getDeliveryTimeWindow().end(); deliveryTardiness += tardiness; } totalDeliveries++; } else { // check if we are early if (cur.getPickupTimeWindow().isBeforeStart(time)) { time = cur.getPickupTimeWindow().begin(); } if (!firstAndServicing) { truckArrivalTimesBuilder.add(time); time += cur.getPickupDuration(); } // picking up if (cur.getPickupTimeWindow().isAfterEnd(time)) { final long tardiness = time - cur.getPickupTimeWindow().end(); pickupTardiness += tardiness; } totalPickups++; } } // go to depot final Measure<Double, Length> distance = Measure .valueOf(Point.distance(vehicleLocation, vso.getDto().getStartPosition()), state.getDistUnit()); totalDistance += distance.getValue(); final long tt = DoubleMath.roundToLong( RoadModels.computeTravelTime(speed, distance, state.getTimeUnit()), RoundingMode.CEILING); time += tt; // check overtime if (vso.getDto().getAvailabilityTimeWindow().isAfterEnd(time)) { overTime += time - vso.getDto().getAvailabilityTimeWindow().end(); } maxTime = Math.max(maxTime, time); truckArrivalTimesBuilder.add(time); arrivalTimesBuilder.add(truckArrivalTimesBuilder.build()); if (time > startTime) { // time has progressed -> the vehicle has moved movedVehicles++; } } final int totalParcels = parcels.size(); final int totalVehicles = state.getVehicles().size(); final long simulationTime = maxTime - startTime; return new ExtendedStats(totalDistance, totalPickups, totalDeliveries, totalParcels, totalParcels, pickupTardiness, deliveryTardiness, 0, simulationTime, true, totalVehicles, overTime, totalVehicles, movedVehicles, state.getTimeUnit(), state.getDistUnit(), state.getSpeedUnit(), arrivalTimesBuilder.build()); } public static Callable<ImmutableList<ImmutableList<Parcel>>> createSolverCallable(Solver solver, GlobalStateObject state) { return new SolverCallable(solver, state); } public static MeasureableSolver timeMeasurementDecorator(Solver s) { return new TimeMeasurementSolverDecorator(s); } static GlobalStateObject convert(PDPRoadModel rm, PDPModel pm, Collection<Vehicle> vehicles, Set<Parcel> availableParcels, Measure<Long, Duration> time, Optional<ImmutableList<ImmutableList<Parcel>>> currentRoutes, boolean fixRoutes) { final ImmutableMap.Builder<VehicleStateObject, Vehicle> vbuilder = ImmutableMap.builder(); @Nullable Iterator<ImmutableList<Parcel>> routeIterator = null; if (currentRoutes.isPresent()) { checkArgument(currentRoutes.get().size() == vehicles.size(), "The number of routes (%s) must equal the number of vehicles (%s).", currentRoutes.get().size(), vehicles.size()); routeIterator = currentRoutes.get().iterator(); } final ImmutableSet.Builder<Parcel> availableDestParcels = ImmutableSet.builder(); for (final Vehicle v : vehicles) { final ImmutableSet<Parcel> contentsMap = ImmutableSet.copyOf(pm.getContents(v)); @Nullable ImmutableList<Parcel> route = null; if (routeIterator != null) { route = routeIterator.next(); } final VehicleStateObject vehicleState = convertToVehicleState(rm, pm, v, contentsMap, route, availableDestParcels); vbuilder.put(vehicleState, v); } final ImmutableSet<Parcel> availableDestMap = availableDestParcels.build(); final Set<Parcel> toAdd = Sets.difference(availableParcels, availableDestMap); final ImmutableSet<Parcel> availableParcelsKeys = ImmutableSet.<Parcel>builder().addAll(availableParcels) .addAll(toAdd).build(); final ImmutableMap<VehicleStateObject, Vehicle> vehicleMap = vbuilder.build(); GlobalStateObject gso = GlobalStateObject.create(availableParcelsKeys, vehicleMap.keySet().asList(), time.getValue().longValue(), time.getUnit(), rm.getSpeedUnit(), rm.getDistanceUnit()); if (fixRoutes) { gso = fixRoutes(gso); } return gso; } static GlobalStateObject fixRoutes(GlobalStateObject state) { boolean firstVehicle = true; final ImmutableList.Builder<VehicleStateObject> vehicleList = ImmutableList.builder(); for (int i = 0; i < state.getVehicles().size(); i++) { final VehicleStateObject vso = state.getVehicles().get(i); checkArgument(vso.getRoute().isPresent()); final List<Parcel> route = new ArrayList<>(vso.getRoute().get()); final Multiset<Parcel> routeContents = LinkedHashMultiset.create(route); for (final Parcel p : routeContents.elementSet()) { if (vso.getContents().contains(p)) { // should occur only once if (routeContents.count(p) > 1) { // remove route.remove(p); checkArgument(routeContents.count(p) == 2); } } else { // should occur twice if (routeContents.count(p) < 2) { route.add(p); } else { checkArgument(routeContents.count(p) == 2); } } } if (firstVehicle) { final Set<Parcel> unassigned = GlobalStateObjects.unassignedParcels(state); route.addAll(unassigned); route.addAll(unassigned); firstVehicle = false; } vehicleList.add(VehicleStateObject.create(vso.getDto(), vso.getLocation(), vso.getContents(), vso.getRemainingServiceTime(), vso.getDestination().orNull(), ImmutableList.copyOf(route))); } return GlobalStateObject.create(state.getAvailableParcels(), vehicleList.build(), state.getTime(), state.getTimeUnit(), state.getSpeedUnit(), state.getDistUnit()); } // TODO check for bugs static VehicleStateObject convertToVehicleState(PDPRoadModel rm, PDPModel pm, Vehicle vehicle, ImmutableSet<Parcel> contents, @Nullable ImmutableList<Parcel> route, ImmutableSet.Builder<Parcel> availableDestBuilder) { final boolean isIdle = pm.getVehicleState(vehicle) == PDPModel.VehicleState.IDLE; long remainingServiceTime = 0; @Nullable Parcel destination = null; if (!isIdle) { final VehicleParcelActionInfo vpai = pm.getVehicleActionInfo(vehicle); destination = vpai.getParcel(); remainingServiceTime = vpai.timeNeeded(); } else if (!rm.isVehicleDiversionAllowed()) { // check whether the vehicle is already underway to parcel destination = rm.getDestinationToParcel(vehicle); } // destinations which are not yet picked up should be put in the builder if (destination != null && !pm.getParcelState(destination).isPickedUp()) { availableDestBuilder.add(destination); } return VehicleStateObject.create(vehicle.getDTO(), rm.getPosition(vehicle), contents, remainingServiceTime, destination == null ? null : destination, route); } /** * Converter that converts simulations into {@link GlobalStateObject} * instances which are needed to call {@link Solver#solve(GlobalStateObject)}. * @author Rinde van Lon */ public interface SimulationConverter { /** * Converts the simulation into a {@link GlobalStateObject} object. * @param args {@link SolveArgs}. * @return {@link GlobalStateObject}. */ GlobalStateObject convert(SolveArgs args); } /** * Builder for specifying parameters used in {@link SimSolver} and * {@link SimulationConverter}. * @author Rinde van Lon */ public static final class SolveArgs { Optional<ImmutableSet<Parcel>> parcels; Optional<ImmutableList<ImmutableList<Parcel>>> currentRoutes; boolean fixRoutes; private SolveArgs() { parcels = Optional.absent(); currentRoutes = Optional.absent(); } /** * @return {@link SolveArgs} builder. */ public static SolveArgs create() { return new SolveArgs(); } /** * Indicates that receivers of this object should use all parcels it knows. * @return This, as per the builder pattern. */ public SolveArgs useAllParcels() { parcels = Optional.absent(); return this; } /** * Indicates that receivers of this object should use only the parcels that * are specified. * @param ps The parcels to use. * @return This, as per the builder pattern. */ public SolveArgs useParcels(Iterable<? extends Parcel> ps) { parcels = Optional.of(ImmutableSet.<Parcel>copyOf(ps)); return this; } /** * Indicates that receivers of this object should use no current routes for * the vehicles it knows about. * @return This, as per the builder pattern. */ public SolveArgs noCurrentRoutes() { currentRoutes = Optional.absent(); return this; } /** * Indicates that receivers of this object should use the specified current * routes for the vehicles it knows about. The number of specified route * needs to match the number of known vehicles. * @param cr The current routes to use. * @return This, as per the builder pattern. */ public SolveArgs useCurrentRoutes(ImmutableList<ImmutableList<Parcel>> cr) { currentRoutes = Optional.of(cr); return this; } public SolveArgs useEmptyRoutes(int numVehicles) { final ImmutableList.Builder<ImmutableList<Parcel>> builder = ImmutableList.builder(); for (int i = 0; i < numVehicles; i++) { builder.add(ImmutableList.<Parcel>of()); } currentRoutes = Optional.of(builder.build()); return this; } /** * Indicates that the supplied routes should be fixed. Unassigned parcels * will be assigned to the first vehicle, incorrect parcel occurrences in * routes are corrected. * @return This, as per the builder pattern. */ public SolveArgs fixRoutes() { fixRoutes = true; return this; } } /** * Builder for creating adapters for {@link Solver}s that need to solve * simulation instances. For creating an adapter four different pieces of * information are required, each can be supplied to this builder via a * variety of methods which are listed below. * <ul> * <li>{@link PDPRoadModel} - can be supplied directly, via a * {@link ModelProvider} or via {@link Simulator} instance</li> * <li>{@link PDPModel} - can be supplied directly, via a * {@link ModelProvider} or via {@link Simulator} instance</li> * <li>{@link SimulatorAPI} - can be supplied directly or via a * {@link Simulator} instance.</li> * <li>A number of {@link Vehicle}s - can be supplied directly or if not * supplied all vehicles available in the {@link PDPRoadModel} instance will * be used.</li> * </ul> * @author Rinde van Lon * @param <T> The type of adapter to produce. */ public static class AdapterBuilder<T extends SimulationConverter> { @Nullable Simulator simulator; @Nullable Clock clock; @Nullable ModelProvider modelProvider; @Nullable PDPRoadModel roadModel; @Nullable PDPModel pdpModel; final List<Vehicle> vehicles; final Optional<Solver> solver; AdapterBuilder(@Nullable Solver s) { solver = Optional.fromNullable(s); vehicles = newArrayList(); } /** * @param sim The {@link Simulator} to provide to the adapter. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(Simulator sim) { simulator = sim; return this; } /** * @param mp The {@link ModelProvider} to use for extracting the models. * Calls to this method take precedence over * {@link #with(Simulator)}. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(ModelProvider mp) { modelProvider = mp; return this; } /** * @param rm The {@link PDPRoadModel} to use in the adapter. Calls to this * method take precedence over {@link #with(ModelProvider)} and * {@link #with(Simulator)}. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(PDPRoadModel rm) { roadModel = rm; return this; } /** * @param pm The {@link PDPModel} to use in the adapter. Calls to this * method take precedence over {@link #with(ModelProvider)} and * {@link #with(Simulator)}. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(PDPModel pm) { pdpModel = pm; return this; } /** * @param c The {@link Clock} to use in the adapter. Calls to this method * take precedence over {@link #with(Simulator)}. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(Clock c) { clock = c; return this; } /** * Adds the specified vehicle to the resulting adapter, the vehicle will be * included in the resulting adapter. When no vehicles are supplied, the * adapter will use all vehicles in {@link PDPRoadModel}. * @param dv The {@link Vehicle} to add. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(Vehicle dv) { vehicles.add(dv); return this; } /** * Adds the specified vehicles to the resulting adapter, the vehicles will * be included in the resulting adapter. When no vehicles are supplied, the * adapter will use all vehicles in {@link PDPRoadModel}. * @param dv The {@link Vehicle}s to include. * @return This, as per the builder pattern. */ public AdapterBuilder<T> with(Iterable<? extends Vehicle> dv) { Iterables.addAll(vehicles, dv); return this; } /** * Builds the adapter. * @return The newly created adapter. */ @SuppressWarnings("unchecked") public T build() { PDPRoadModel rm = roadModel; PDPModel pm = pdpModel; if (rm == null || pm == null) { // in this case we need a model provider @Nullable ModelProvider mp = modelProvider; if (mp == null) { checkArgument(simulator != null, "Attempt to find a model provider failed. Either provide the " + "models directly, provide a model provider or a " + "simulator."); mp = simulator.getModelProvider(); } if (rm == null) { rm = mp.getModel(PDPRoadModel.class); } if (pm == null) { pm = mp.getModel(PDPModel.class); } } Clock c = clock; if (c == null && simulator != null) { c = simulator.getModelProvider().getModel(TimeModel.class); } if (c != null && rm != null && pm != null) { return (T) new SimSolver(solver, rm, pm, c, vehicles); } throw new IllegalArgumentException("Not all required components could be found, PDPRoadModel: " + rm + ", PDPModel: " + pm + ", Clock: " + c); } /** * Builds an adapter which can deal with only one vehicle. * @return A new created adapter. */ public T buildSingle() { checkArgument(vehicles.size() == 1); return build(); } } /** * * * @author Rinde van Lon */ public static class ExtendedStats extends StatisticsDTO { private static final long serialVersionUID = 3682772955122186862L; final ImmutableList<ImmutableList<Long>> arrivalTimes; ExtendedStats(double dist, int pick, int del, int parc, int accP, long pickTar, long delTar, long compT, long simT, boolean finish, int atDepot, long overT, int total, int moved, Unit<Duration> time, Unit<Length> distUnit, Unit<Velocity> speed, ImmutableList<ImmutableList<Long>> pArrivalTimes) { super(dist, pick, del, parc, accP, pickTar, delTar, compT, simT, finish, atDepot, overT, total, moved, time, distUnit, speed); arrivalTimes = pArrivalTimes; } public ImmutableList<ImmutableList<Long>> getArrivalTimes() { return arrivalTimes; } } static class TimeMeasurementSolverDecorator implements MeasureableSolver { private final Solver delegate; private final List<SolverTimeMeasurement> measurements; TimeMeasurementSolverDecorator(Solver deleg) { delegate = deleg; measurements = new ArrayList<>(); } @Override public List<SolverTimeMeasurement> getTimeMeasurements() { return Collections.unmodifiableList(measurements); } @Override public ImmutableList<ImmutableList<Parcel>> solve(GlobalStateObject state) throws InterruptedException { final long start = System.nanoTime(); final ImmutableList<ImmutableList<Parcel>> result = delegate.solve(state); final long duration = System.nanoTime() - start; measurements.add(SolverTimeMeasurement.create(state, duration)); return result; } } static class SolverCallable implements Callable<ImmutableList<ImmutableList<Parcel>>> { final Solver solver; final GlobalStateObject snapshot; SolverCallable(Solver sol, GlobalStateObject snap) { solver = sol; snapshot = snap; } @Override public ImmutableList<ImmutableList<Parcel>> call() throws Exception { return solver.solve(snapshot); } } }