rinde.sim.pdptw.central.Solvers Maven / Gradle / Ivy
The newest version!
/**
*
*/
package rinde.sim.pdptw.central;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.common.collect.Sets.newLinkedHashSet;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
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.SI;
import javax.measure.unit.Unit;
import rinde.sim.core.Simulator;
import rinde.sim.core.SimulatorAPI;
import rinde.sim.core.graph.Point;
import rinde.sim.core.model.ModelProvider;
import rinde.sim.core.model.pdp.PDPModel;
import rinde.sim.core.model.pdp.PDPModel.ParcelState;
import rinde.sim.core.model.pdp.PDPModel.VehicleParcelActionInfo;
import rinde.sim.core.model.pdp.Parcel;
import rinde.sim.pdptw.central.GlobalStateObject.VehicleStateObject;
import rinde.sim.pdptw.common.DefaultParcel;
import rinde.sim.pdptw.common.DefaultVehicle;
import rinde.sim.pdptw.common.PDPRoadModel;
import rinde.sim.pdptw.common.ParcelDTO;
import rinde.sim.pdptw.common.StatisticsDTO;
import rinde.sim.pdptw.common.VehicleDTO;
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.Maps;
import com.google.common.math.DoubleMath;
/**
* @author Rinde van Lon
*
*/
public final class Solvers {
private Solvers() {}
/**
* Creates a builder for creating {@link SimulationSolver} instances. For more
* information see {@link AdapterBuilder}.
* @param sol The solver to use internally.
* @return The builder.
*/
public static AdapterBuilder solverBuilder(Solver sol) {
return new AdapterBuilder(sol);
}
/**
* Creates a builder for creating {@link SimulationConverter} instances.
* @return The builder.
*/
public static AdapterBuilder converterBuilder() {
return new AdapterBuilder(null);
}
/**
* Computes the duration which is required to travel the specified distance
* with the given velocity. Note: although time is normally a long, we use
* double here instead. Converting it to long in this method would introduce
* rounding in a too early stage.
* @param speed The travel speed.
* @param distance The distance to travel.
* @param outputTimeUnit The time unit to use for the output.
* @return The time it takes to travel the specified distance with the
* specified speed.
*/
public static double computeTravelTime(Measure speed,
Measure distance, Unit outputTimeUnit) {
// meters
return Measure.valueOf(distance.doubleValue(SI.METER)
// divided by m/s
/ speed.doubleValue(SI.METERS_PER_SECOND),
// gives seconds
SI.SECOND)
// convert to desired unit
.doubleValue(outputTimeUnit);
}
/**
* 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 null
the
* {@link VehicleStateObject#route} field must be set instead for
* each vehicle.
* @return The statistics that will be generated when executing this
* simulation.
*/
public static StatisticsDTO computeStats(GlobalStateObject state,
@Nullable ImmutableList> routes) {
final Optional>> r = Optional
.fromNullable(routes);
if (r.isPresent()) {
checkArgument(
state.vehicles.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.vehicles.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.time;
long maxTime = 0;
int movedVehicles = 0;
final Set parcels = newHashSet();
final ImmutableList.Builder> arrivalTimesBuilder = ImmutableList
.builder();
for (int i = 0; i < state.vehicles.size(); i++) {
final VehicleStateObject vso = state.vehicles.get(i);
checkArgument(r.isPresent() || vso.route.isPresent());
final ImmutableList.Builder truckArrivalTimesBuilder = ImmutableList
.builder();
truckArrivalTimesBuilder.add(state.time);
ImmutableList route;
if (r.isPresent()) {
route = r.get().get(i);
} else {
route = vso.route.get();
}
parcels.addAll(route);
long time = state.time;
Point vehicleLocation = vso.location;
final Measure speed = Measure.valueOf(vso.speed,
state.speedUnit);
final Set seen = newHashSet();
for (int j = 0; j < route.size(); j++) {
final ParcelDTO cur = route.get(j);
final boolean inCargo = vso.contents.contains(cur)
|| seen.contains(cur);
seen.add(cur);
if (vso.destination != null && j == 0) {
checkArgument(
vso.destination == cur,
"If a vehicle has a destination, the first position in the route must equal this. Expected %s, is %s.",
vso.destination, cur);
}
boolean firstAndServicing = false;
if (j == 0 && vso.remainingServiceTime > 0) {
// we are already at the service location
firstAndServicing = true;
truckArrivalTimesBuilder.add(time);
time += vso.remainingServiceTime;
} else {
// vehicle is not there yet, go there first, then service
final Point nextLoc = inCargo ? cur.destinationLocation
: cur.pickupLocation;
final Measure distance = Measure.valueOf(
Point.distance(vehicleLocation, nextLoc), state.distUnit);
totalDistance += distance.getValue();
vehicleLocation = nextLoc;
final long tt = DoubleMath.roundToLong(
computeTravelTime(speed, distance, state.timeUnit),
RoundingMode.CEILING);
time += tt;
}
if (inCargo) {
// check if we are early
if (cur.deliveryTimeWindow.isBeforeStart(time)) {
time = cur.deliveryTimeWindow.begin;
}
if (!firstAndServicing) {
truckArrivalTimesBuilder.add(time);
time += cur.deliveryDuration;
}
// delivering
if (cur.deliveryTimeWindow.isAfterEnd(time)) {
final long tardiness = time - cur.deliveryTimeWindow.end;
deliveryTardiness += tardiness;
}
totalDeliveries++;
} else {
// check if we are early
if (cur.pickupTimeWindow.isBeforeStart(time)) {
time = cur.pickupTimeWindow.begin;
}
if (!firstAndServicing) {
truckArrivalTimesBuilder.add(time);
time += cur.pickupDuration;
}
// picking up
if (cur.pickupTimeWindow.isAfterEnd(time)) {
final long tardiness = time - cur.pickupTimeWindow.end;
pickupTardiness += tardiness;
}
totalPickups++;
}
}
// go to depot
final Measure distance = Measure.valueOf(
Point.distance(vehicleLocation, vso.startPosition), state.distUnit);
totalDistance += distance.getValue();
final long tt = DoubleMath.roundToLong(
computeTravelTime(speed, distance, state.timeUnit),
RoundingMode.CEILING);
time += tt;
// check overtime
if (vso.availabilityTimeWindow.isAfterEnd(time)) {
overTime += time - vso.availabilityTimeWindow.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.vehicles.size();
final long simulationTime = maxTime - startTime;
return new ExtendedStats(totalDistance, totalPickups, totalDeliveries,
totalParcels, totalParcels, pickupTardiness, deliveryTardiness, 0,
simulationTime, true, totalVehicles, overTime, totalVehicles,
movedVehicles, state.timeUnit, state.distUnit, state.speedUnit,
arrivalTimesBuilder.build());
}
/**
* Converts the specified collection containing {@link DefaultParcel}s into an
* {@link ImmutableList} of {@link ParcelDTO}s.
* @param parcels The parcels to convert.
* @return A list of {@link ParcelDTO}s in the same order as in the provided
* collection.
*/
public static ImmutableList toDtoList(
Collection parcels) {
final ImmutableList.Builder builder = ImmutableList.builder();
for (final DefaultParcel dp : parcels) {
builder.add(dp.dto);
}
return builder.build();
}
/**
* Converts the specified collection of collections containing
* {@link DefaultParcel}s into a list of lists containing {@link ParcelDTO}s.
* @param routes The collection of collections of parcels to convert.
* @return A list of lists of {@link ParcelDTO}s in the same order as the
* provided lists.
*/
public static ImmutableList> toDtoLists(
Collection extends Collection> routes) {
final ImmutableList.Builder> newRoutes = ImmutableList
.builder();
for (final Collection route : routes) {
newRoutes.add(toDtoList(route));
}
return newRoutes.build();
}
// converts the routes received from Solver.solve(..) into a format which is
// expected by the simulator
static List> convertRoutes(StateContext cont,
List extends List> routes) {
final ImmutableList.Builder> routesBuilder = ImmutableList
.builder();
for (final List route : routes) {
final Queue newRoute = newLinkedList();
for (final ParcelDTO dto : route) {
checkArgument(cont.parcelMap.containsKey(dto),
"Parcel %s is not known in the context.", dto);
newRoute.add(cont.parcelMap.get(dto));
}
routesBuilder.add(newRoute);
}
return routesBuilder.build();
}
static Collection conv(Collection input) {
final List l = newArrayList();
for (final Parcel p : input) {
checkArgument(p instanceof DefaultParcel);
l.add((DefaultParcel) p);
}
return l;
}
static StateContext convert(PDPRoadModel rm, PDPModel pm,
Collection vehicles,
Collection availableParcels, Measure time,
Optional>> currentRoutes) {
final ImmutableMap vehicleMap = toVehicleMap(vehicles);
final ImmutableList.Builder vbuilder = ImmutableList
.builder();
final ImmutableMap.Builder allParcels = ImmutableMap
.builder();
@Nullable
Iterator> 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 ImmutableMap.Builder availableDestParcels = ImmutableMap
.builder();
for (final DefaultVehicle v : vehicles) {
final ImmutableMap contentsMap = contentsToMap(
pm, v);
@Nullable
ImmutableList route = null;
if (routeIterator != null) {
route = routeIterator.next();
}
vbuilder.add(convertToVehicleState(rm, pm, v, contentsMap, route,
availableDestParcels));
allParcels.putAll(contentsMap);
}
final ImmutableMap availableMap = toMap(availableParcels);
final ImmutableMap availableDestMap = availableDestParcels
.build();
final Map toAdd = Maps.difference(availableMap,
availableDestMap).entriesOnlyOnRight();
allParcels.putAll(availableMap);
allParcels.putAll(toAdd);
final ImmutableSet availableParcelsKeys = ImmutableSet
. builder()
.addAll(availableMap.keySet())
.addAll(toAdd.keySet()).build();
return new StateContext(new GlobalStateObject(availableParcelsKeys,
vbuilder.build(), time.getValue().longValue(), time.getUnit(),
rm.getSpeedUnit(), rm.getDistanceUnit()), vehicleMap,
allParcels.build());
}
static ImmutableMap contentsToMap(PDPModel pm,
DefaultVehicle vehicle) {
// this is ok since we actually check the type
@SuppressWarnings({ "unchecked", "rawtypes" })
final Set ps = Collections.checkedSet(
(Set) newLinkedHashSet(pm.getContents(vehicle)), DefaultParcel.class);
return toMap(ps);
}
// TODO check for bugs
static VehicleStateObject convertToVehicleState(PDPRoadModel rm, PDPModel pm,
DefaultVehicle vehicle, ImmutableMap contents,
@Nullable ImmutableList route,
ImmutableMap.Builder availableDestBuilder) {
final boolean isIdle = pm.getVehicleState(vehicle) == PDPModel.VehicleState.IDLE;
long remainingServiceTime = 0;
@Nullable
DefaultParcel destination = null;
if (!isIdle) {
final VehicleParcelActionInfo vpai = pm.getVehicleActionInfo(vehicle);
destination = ((DefaultParcel) 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.put(destination.dto, destination);
}
@Nullable
ImmutableList r = null;
if (route != null) {
r = toDtoList(route);
}
return new VehicleStateObject(vehicle.getDTO(), rm.getPosition(vehicle),
contents.keySet(), remainingServiceTime, destination == null ? null
: destination.dto, r);
}
static ImmutableMap toMap(
Collection parcels) {
final ImmutableMap.Builder parcelMapBuilder = ImmutableMap
.builder();
for (final DefaultParcel dp : parcels) {
parcelMapBuilder.put(dp.dto, dp);
}
return parcelMapBuilder.build();
}
static ImmutableMap toVehicleMap(
Collection vehicles) {
final ImmutableMap.Builder vehicleMapBuilder = ImmutableMap
.builder();
for (final DefaultVehicle dp : vehicles) {
vehicleMapBuilder.put(dp.getDTO(), dp);
}
return vehicleMapBuilder.build();
}
/**
* Converter that converts simulations into {@link StateContext} instances
* which are needed to call {@link Solver#solve(GlobalStateObject)}.
* @author Rinde van Lon
*/
public interface SimulationConverter {
/**
* Converts the simulation into a {@link StateContext} object.
* @param args {@link SolveArgs}.
* @return {@link StateContext}.
*/
StateContext convert(SolveArgs args);
}
/**
* Builder for specifying parameters used in {@link SimulationSolver} and
* {@link SimulationConverter}.
* @author Rinde van Lon
*/
public static final class SolveArgs {
Optional> parcels;
Optional>> currentRoutes;
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(Collection ps) {
parcels = Optional.of(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> cr) {
currentRoutes = Optional.of(cr);
return this;
}
}
/**
* Adapter for {@link Solver}s.
* @author Rinde van Lon
*/
public static class SimulationSolver implements SimulationConverter {
final Optional solver;
final SimulatorAPI simulator;
final PDPRoadModel roadModel;
final PDPModel pdpModel;
final List vehicles;
SimulationSolver(Optional s, PDPRoadModel rm, PDPModel pm,
SimulatorAPI sim, List vs) {
solver = s;
simulator = sim;
roadModel = rm;
pdpModel = pm;
vehicles = vs;
}
/**
* Calls the {@link Solver} to solve the problem as defined by the current
* simulation state.
* @param args {@link SolveArgs} specifying what information to include.
* @return A list containing routes for each vehicle known to this solver.
*/
public List> solve(SolveArgs args) {
return solve(convert(args));
}
/**
* Calls the {@link Solver} to solve the problem as defined by the current
* simulation state.
* @param state The {@link StateContext} that specifies the current
* simulation state.
* @return A list of routes, one for each vehicle.
*/
public List> solve(StateContext state) {
return Solvers.convertRoutes(state, solver.get().solve(state.state));
}
@Override
public StateContext convert(SolveArgs args) {
final Collection vs = vehicles.isEmpty() ? roadModel
.getObjectsOfType(DefaultVehicle.class) : vehicles;
final Collection ps = args.parcels.isPresent() ? args.parcels
.get()
: conv(pdpModel.getParcels(ParcelState.ANNOUNCED,
ParcelState.AVAILABLE, ParcelState.PICKING_UP));
return Solvers.convert(roadModel, pdpModel, vs, ps, time(),
args.currentRoutes);
}
Measure time() {
return Measure.valueOf(simulator.getCurrentTime(),
simulator.getTimeUnit());
}
}
/**
* 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.
*
* - {@link PDPRoadModel} - can be supplied directly, via a
* {@link ModelProvider} or via {@link Simulator} instance
* - {@link PDPModel} - can be supplied directly, via a
* {@link ModelProvider} or via {@link Simulator} instance
* - {@link SimulatorAPI} - can be supplied directly or via a
* {@link Simulator} instance.
* - A number of {@link DefaultVehicle}s - can be supplied directly or if
* not supplied all vehicles available in the {@link PDPRoadModel} instance
* will be used.
*
*
* @param The type of adapter to produce.
* @author Rinde van Lon
*/
public static class AdapterBuilder {
Simulator simulator;
SimulatorAPI simulatorApi;
ModelProvider modelProvider;
PDPRoadModel roadModel;
PDPModel pdpModel;
final List vehicles;
final Optional 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 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 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 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 with(PDPModel pm) {
pdpModel = pm;
return this;
}
/**
* @param sim The {@link SimulatorAPI} to use in the adapter. Calls to this
* method take precedence over {@link #with(Simulator)}.
* @return This, as per the builder pattern.
*/
public AdapterBuilder with(SimulatorAPI sim) {
simulatorApi = sim;
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 DefaultVehicle} to add.
* @return This, as per the builder pattern.
*/
public AdapterBuilder with(DefaultVehicle 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 DefaultVehicle}s to include.
* @return This, as per the builder pattern.
*/
public AdapterBuilder with(List extends DefaultVehicle> dv) {
vehicles.addAll(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
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);
}
}
SimulatorAPI sapi = simulatorApi;
if (sapi == null) {
sapi = simulator;
}
if (sapi != null && rm != null && pm != null) {
return (T) new SimulationSolver(solver, rm, pm, sapi, vehicles);
} else {
throw new IllegalArgumentException(
"Not all required components could be found, PDPRoadModel: " + rm
+ ", PDPModel: " + pm + ", SimulatorAPI: " + sapi);
}
}
/**
* Builds an adapter which can deal with only one vehicle.
* @return A new created adapter.
*/
public T buildSingle() {
checkArgument(vehicles.size() == 1);
return build();
}
}
/**
* Value object containing representing the state of a simulation. It contains
* a {@link GlobalStateObject} (the actual state) and two maps with references
* to the original vehicles and parcels. Using these maps the state object can
* be translated back to the original simulation objects.
* @author Rinde van Lon
*/
public static class StateContext {
/**
* A reference to the {@link GlobalStateObject}.
*/
public final GlobalStateObject state;
/**
* A mapping of {@link VehicleDTO} to {@link DefaultVehicle}.
*/
public final ImmutableMap vehicleMap;
/**
* A mapping of {@link ParcelDTO} to {@link DefaultParcel}.
*/
public final ImmutableMap parcelMap;
StateContext(GlobalStateObject state,
ImmutableMap vehicleMap,
ImmutableMap parcelMap) {
this.state = state;
this.vehicleMap = vehicleMap;
this.parcelMap = parcelMap;
}
}
// only used for testing
static class ExtendedStats extends StatisticsDTO {
private static final long serialVersionUID = 3682772955122186862L;
final ImmutableList> 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 time,
Unit distUnit, Unit speed,
ImmutableList> arrivalTimes) {
super(dist, pick, del, parc, accP, pickTar, delTar, compT, simT, finish,
atDepot, overT, total, moved, time, distUnit, speed);
this.arrivalTimes = arrivalTimes;
}
}
}