All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.rinde.rinsim.pdptw.common.PDPRoadModel Maven / Gradle / Ivy

There is a newer version: 4.4.6
Show newest version
/*
 * 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.pdptw.common;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Maps.newHashMap;

import java.util.Map;
import java.util.Queue;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;

import com.github.rinde.rinsim.core.model.DependencyProvider;
import com.github.rinde.rinsim.core.model.ModelBuilder;
import com.github.rinde.rinsim.core.model.ModelProvider;
import com.github.rinde.rinsim.core.model.ModelReceiver;
import com.github.rinde.rinsim.core.model.pdp.Depot;
import com.github.rinde.rinsim.core.model.pdp.PDPModel;
import com.github.rinde.rinsim.core.model.pdp.PDPModel.ParcelState;
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.AbstractRoadModel;
import com.github.rinde.rinsim.core.model.road.ForwardingRoadModel;
import com.github.rinde.rinsim.core.model.road.MoveProgress;
import com.github.rinde.rinsim.core.model.road.MovingRoadUser;
import com.github.rinde.rinsim.core.model.road.RoadModel;
import com.github.rinde.rinsim.core.model.road.RoadUser;
import com.github.rinde.rinsim.core.model.time.TimeLapse;
import com.github.rinde.rinsim.geom.Point;
import com.google.auto.value.AutoValue;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;

/**
 * A decorator for {@link AbstractRoadModel} which provides a more convenient
 * API for PDP problems and it can control whether vehicles are allowed to
 * divert.
 * 

* Pickup and delivery problem convenience API
* This model allows the following: {@code * moveTo(v1,p1,..) * pickup(v1,p1,..) * moveTo(v1,p1,..) * deliver(v1,p1,..) * } This means that you only need the reference to the parcel which you are * interested in, depending on its state the vehicle will automatically move to * the pickup location or the delivery location. *

* Diversion
* This model can optionally disallow vehicle diversion. Vehicle diversion is * defined as a vehicle changing its destination service location before it has * completed servicing that service location. More concretely if a vehicle * v1 is moving to the pickup location of parcel p1 * but then changes its destination to the pickup location of parcel * p2 we say that vehicle v1 has diverted. * * @author Rinde van Lon */ public class PDPRoadModel extends ForwardingRoadModel implements ModelReceiver { final Map destinations; final Multimap destinationHistory; final boolean allowDiversion; Optional pdpModel; PDPRoadModel(AbstractRoadModel rm, boolean diversion) { super(rm); allowDiversion = diversion; destinations = newHashMap(); // does not allow duplicates: WE NEED THIS destinationHistory = LinkedHashMultimap.create(); pdpModel = Optional.absent(); } /** * @return true when diversion is allowed, false * otherwise. See {@link PDPRoadModel} for more information about * diversion. */ public boolean isVehicleDiversionAllowed() { return allowDiversion; } private void checkType(RoadUser ru) { checkArgument(ru instanceof Vehicle || ru instanceof Depot || ru instanceof Parcel, "This RoadModel only allows instances of Vehicle, Depot and Parcel."); } @Override public boolean equalPosition(RoadUser obj1, RoadUser obj2) { return getParcelPos(obj1).equals(getParcelPos(obj2)); } Point getParcelPos(RoadUser obj) { if (!containsObject(obj) && obj instanceof Parcel) { final ParcelState state = pdpModel.get().getParcelState( (Parcel) obj); checkArgument( state == ParcelState.IN_CARGO, "Can only move to parcels which are either on the map or in cargo, " + "state is %s, obj is %s.", state, obj); return ((Parcel) obj).getDeliveryLocation(); } return getPosition(obj); } @Override public void addObjectAt(RoadUser newObj, Point pos) { checkType(newObj); delegate.addObjectAt(newObj, pos); } @Override public void addObjectAtSamePosition(RoadUser newObj, RoadUser existingObj) { checkType(newObj); delegate.addObjectAtSamePosition(newObj, existingObj); } private boolean isAlreadyServiced(DestType type, RoadUser ru) { boolean alreadyServiced = false; final Parcel p = (Parcel) ru; if (type == DestType.PICKUP) { alreadyServiced = pdpModel.get().getParcelState(p) == ParcelState.PICKING_UP || pdpModel.get().getParcelState(p) == ParcelState.IN_CARGO; } else if (type == DestType.DELIVERY) { alreadyServiced = pdpModel.get().getParcelState(p) == ParcelState.DELIVERING || pdpModel.get().getParcelState(p) == ParcelState.DELIVERED; } return alreadyServiced; } @Override public MoveProgress moveTo(MovingRoadUser object, RoadUser destinationRoadUser, TimeLapse time) { DestinationObject newDestinationObject; if (destinationRoadUser instanceof Parcel) { final Parcel dp = (Parcel) destinationRoadUser; final DestType type = containsObject(dp) ? DestType.PICKUP : DestType.DELIVERY; final Point pos = getParcelPos(destinationRoadUser); if (type == DestType.DELIVERY) { checkArgument( pdpModel.get().containerContains((Vehicle) object, (Parcel) destinationRoadUser), "A vehicle can only move to the delivery location of a parcel if it" + " is carrying it."); } newDestinationObject = DestinationObject.create(type, pos, dp); } else { newDestinationObject = DestinationObject.create(DestType.DEPOT, getPosition(destinationRoadUser), destinationRoadUser); } boolean destChange = true; if (destinations.containsKey(object) && !allowDiversion) { final DestinationObject prev = destinations.get(object); final boolean atDestination = getPosition(object).equals(prev.dest()); if (!atDestination && prev.type() != DestType.DEPOT) { // when we haven't reached our destination and the destination // isn't the depot we are not allowed to change destination checkArgument( prev.roadUser() == destinationRoadUser || isAlreadyServiced(prev.type(), prev.roadUser()), "Diversion from the current destination is not allowed. " + "Prev: %s, new: %s.", prev, destinationRoadUser); destChange = false; } else // change destination if (prev.type() == DestType.PICKUP) { // when we are at the prev destination, and it was a pickup, we are // allowed to move if it has been picked up checkArgument( pdpModel.get().getParcelState( (Parcel) prev.roadUser()) != ParcelState.AVAILABLE, "Can not move away before the parcel has been picked up: %s.", prev.roadUser()); } else if (prev.type() == DestType.DELIVERY) { // when we are at the prev destination and it was a delivery, we are // allowed to move to other objects only, and only if the parcel is // delivered checkArgument(prev.roadUser() != destinationRoadUser, "Can not move to the same parcel since we are already there: %s.", prev.roadUser()); checkArgument( pdpModel.get().getParcelState( (Parcel) prev.roadUser()) == ParcelState.DELIVERED, "The parcel needs to be delivered before moving away: %s.", prev.roadUser()); } else { // it is a depot // the destination is only changed in case we are no longer going // towards the depot destChange = newDestinationObject.type() != DestType.DEPOT; } } destinations.put(object, newDestinationObject); if (destChange && newDestinationObject.type() != DestType.DEPOT) { checkArgument( allowDiversion || !destinationHistory .containsEntry(object, newDestinationObject), "It is not allowed to revisit this parcel, it should have been picked" + " up and been delivered already: %s.", newDestinationObject.roadUser()); destinationHistory.put(object, newDestinationObject); } return delegate.moveTo(object, newDestinationObject.dest(), time); } /** * Returns the destination of the specified {@link MovingRoadUser} if * all of the following holds: *

    *
  • The {@link MovingRoadUser} is moving to some destination.
  • *
  • The destination is a {@link Parcel}.
  • *
  • The {@link MovingRoadUser} has not yet started servicing at its * destination.
  • *
* Returns null otherwise. * @param obj The {@link MovingRoadUser} to check its destination for. * @return The parcel the road user is heading to or null * otherwise. */ @Nullable public Parcel getDestinationToParcel(MovingRoadUser obj) { if (destinations.containsKey(obj) && destinations.get(obj).type() != DestType.DEPOT) { final ParcelState parcelState = pdpModel.get().getParcelState( (Parcel) destinations.get(obj).roadUser()); // if it is a pickup destination it must still be available if (destinations.get(obj).type() == DestType.PICKUP && parcelState == ParcelState.AVAILABLE || parcelState == ParcelState.ANNOUNCED // if it is a delivery destination it must still be in cargo || destinations.get(obj).type() == DestType.DELIVERY && parcelState == ParcelState.IN_CARGO) { return (Parcel) destinations.get(obj).roadUser(); } } return null; } /** * {@inheritDoc} * @throws UnsupportedOperationException when diversion is not allowed. */ @Override public MoveProgress moveTo(MovingRoadUser object, Point destination, TimeLapse time) { if (allowDiversion) { return delegate.moveTo(object, destination, time); } else { return unsupported(); } } /** * {@inheritDoc} * @throws UnsupportedOperationException when diversion is not allowed. */ @Override public final MoveProgress followPath(MovingRoadUser object, Queue path, TimeLapse time) { if (allowDiversion) { return delegate.followPath(object, path, time); } else { return unsupported(); } } private MoveProgress unsupported() { throw new UnsupportedOperationException( "This road model doesn't allow diversion and therefore only supports " + "the moveTo(MovingRoadUser,RoadUser,TimeLapse) method."); } @Override public void registerModelProvider(ModelProvider mp) { pdpModel = Optional.of(mp.getModel(PDPModel.class)); } @Override public U get(Class type) { return type.cast(self); } /** * Create a new {@link Builder} instance. * @param delegate The {@link ModelBuilder} that constructs the * {@link RoadModel} that is going to be decorated. * @return A new {@link Builder} that constructs {@link PDPRoadModel} * instances that decorate the delegate. */ @SuppressWarnings("unchecked") public static Builder builder( ModelBuilder delegate) { return Builder.create((ModelBuilder) delegate, false); } /** * Builder for constructing {@link PDPRoadModel} instances. Instances can be * obtained via {@link PDPRoadModel#builder(ModelBuilder)}. * @author Rinde van Lon */ @AutoValue public abstract static class Builder extends ForwardingRoadModel.Builder { private static final long serialVersionUID = -4743379496855573980L; Builder() { setProvidingTypes(RoadModel.class, PDPRoadModel.class); } /** * @return true if vehicle diversion is allowed, * false otherwise. */ public abstract boolean getAllowVehicleDiversion(); /** * Should the model allow vehicle diversion or not. See {@link PDPRoadModel} * for more information about diversion. Default: false. * @param allowDiversion Allow (true) or disallow ( * false) vehicle diversion. * @return This, as per the builder pattern. */ @CheckReturnValue public Builder withAllowVehicleDiversion(boolean allowDiversion) { return create(getDelegateModelBuilder(), allowDiversion); } @Override public PDPRoadModel build(DependencyProvider dependencyProvider) { final AbstractRoadModel rm = (AbstractRoadModel) getDelegateModelBuilder() .build(dependencyProvider); return new PDPRoadModel(rm, getAllowVehicleDiversion()); } @Override public String toString() { return Joiner.on("").join( PDPRoadModel.class.getSimpleName(), ".builder(", getDelegateModelBuilder(), ")"); } static Builder create( ModelBuilder delegateModelBuilder, boolean allowVehicleDiversion) { return new AutoValue_PDPRoadModel_Builder(delegateModelBuilder, allowVehicleDiversion); } } @AutoValue abstract static class DestinationObject { DestinationObject() {} static DestinationObject create(DestType type, Point dest, RoadUser obj) { return new AutoValue_PDPRoadModel_DestinationObject(type, dest, obj); } abstract DestType type(); abstract Point dest(); abstract RoadUser roadUser(); } enum DestType { PICKUP, DELIVERY, DEPOT; } }