com.github.rinde.dynurg.Generator.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rinde.dynurg.Generator.java

Source

/*
 * Copyright (C) 2014 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.dynurg;

import static com.github.rinde.rinsim.util.StochasticSuppliers.constant;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newLinkedHashMap;

import java.io.File;
import java.io.IOException;
import java.math.RoundingMode;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.measure.unit.NonSI;
import javax.measure.unit.SI;

import org.apache.commons.math3.random.MersenneTwister;
import org.apache.commons.math3.random.RandomGenerator;
import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
import org.eclipse.swt.SWT;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;

import com.github.rinde.logistics.pdptw.mas.TruckConfiguration;
import com.github.rinde.logistics.pdptw.mas.comm.AuctionCommModel;
import com.github.rinde.logistics.pdptw.mas.comm.SolverBidder;
import com.github.rinde.logistics.pdptw.mas.route.SolverRoutePlanner;
import com.github.rinde.logistics.pdptw.solver.CheapestInsertionHeuristic;
import com.github.rinde.rinsim.core.Simulator;
import com.github.rinde.rinsim.core.model.pdp.Depot;
import com.github.rinde.rinsim.core.model.pdp.PDPScenarioEvent;
import com.github.rinde.rinsim.core.model.pdp.Parcel;
import com.github.rinde.rinsim.core.model.pdp.TimeWindowPolicy.TimeWindowPolicies;
import com.github.rinde.rinsim.core.model.pdp.Vehicle;
import com.github.rinde.rinsim.core.pdptw.ParcelDTO.Builder;
import com.github.rinde.rinsim.experiment.Experiment;
import com.github.rinde.rinsim.geom.Point;
import com.github.rinde.rinsim.pdptw.common.DynamicPDPTWProblem.StopConditions;
import com.github.rinde.rinsim.pdptw.common.ObjectiveFunction;
import com.github.rinde.rinsim.pdptw.common.TimeLinePanel;
import com.github.rinde.rinsim.scenario.Scenario;
import com.github.rinde.rinsim.scenario.Scenario.ProblemClass;
import com.github.rinde.rinsim.scenario.Scenario.SimpleProblemClass;
import com.github.rinde.rinsim.scenario.ScenarioController.UICreator;
import com.github.rinde.rinsim.scenario.ScenarioIO;
import com.github.rinde.rinsim.scenario.gendreau06.Gendreau06ObjectiveFunction;
import com.github.rinde.rinsim.scenario.generator.Depots;
import com.github.rinde.rinsim.scenario.generator.IntensityFunctions;
import com.github.rinde.rinsim.scenario.generator.Locations;
import com.github.rinde.rinsim.scenario.generator.Locations.LocationGenerator;
import com.github.rinde.rinsim.scenario.generator.Models;
import com.github.rinde.rinsim.scenario.generator.Parcels;
import com.github.rinde.rinsim.scenario.generator.ScenarioGenerator;
import com.github.rinde.rinsim.scenario.generator.ScenarioGenerator.TravelTimes;
import com.github.rinde.rinsim.scenario.generator.TimeSeries;
import com.github.rinde.rinsim.scenario.generator.TimeSeries.TimeSeriesGenerator;
import com.github.rinde.rinsim.scenario.generator.TimeWindows.TimeWindowGenerator;
import com.github.rinde.rinsim.scenario.generator.Vehicles;
import com.github.rinde.rinsim.scenario.measure.Metrics;
import com.github.rinde.rinsim.scenario.measure.MetricsIO;
import com.github.rinde.rinsim.ui.View;
import com.github.rinde.rinsim.ui.renderers.PDPModelRenderer;
import com.github.rinde.rinsim.ui.renderers.PlaneRoadModelRenderer;
import com.github.rinde.rinsim.ui.renderers.RoadUserRenderer;
import com.github.rinde.rinsim.ui.renderers.UiSchema;
import com.github.rinde.rinsim.util.StochasticSupplier;
import com.github.rinde.rinsim.util.StochasticSuppliers;
import com.github.rinde.rinsim.util.TimeWindow;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.io.Files;
import com.google.common.math.DoubleMath;
import com.google.common.primitives.Longs;

/**
 * Generator that creates the dataset needed for the dynamism and urgency
 * experiment.
 * @author Rinde van Lon <rinde.vanlon@cs.kuleuven.be>
 */
public class Generator {
    // all times are in ms unless otherwise indicated
    private static final long TICK_SIZE = 1000L;
    private static final double VEHICLE_SPEED_KMH = 50d;
    private static final int NUM_VEHICLES = 10;

    // n x n (km)
    private static final double AREA_WIDTH = 10;

    private static final long SCENARIO_HOURS = 12L;
    private static final long SCENARIO_LENGTH = SCENARIO_HOURS * 60 * 60 * 1000L;
    private static final int NUM_ORDERS = 360;

    private static final long HALF_DIAG_TT = 509117L;
    private static final long ONE_AND_HALF_DIAG_TT = 1527351L;
    private static final long TWO_DIAG_TT = 2036468L;

    private static final long PICKUP_DURATION = 5 * 60 * 1000L;
    private static final long DELIVERY_DURATION = 5 * 60 * 1000L;

    private static final long INTENSITY_PERIOD = 60 * 60 * 1000L;

    private static final int TARGET_NUM_INSTANCES = 50;

    // These parameters influence the dynamism selection settings
    private static final double DYN_STEP_SIZE = 0.05;
    private static final double DYN_BANDWIDTH = 0.01;

    private static final String DATASET_DIR = "files/dataset/";

    public static void main(String[] args) {
        final RandomGenerator rng = new MersenneTwister(123L);
        generateWithDistinctLocations(rng);
        // generateWithFixedLocations(rng);

        // run( "files/archive/dataset-v2-20140724/0-0.05#0.scen");
    }

    @SuppressWarnings("unused")
    private static void run(final String fileName) {
        final Scenario scen;
        try {
            scen = ScenarioIO.read(new File(fileName).toPath());
        } catch (final IOException e) {
            throw new IllegalStateException(e);
        }
        final ObjectiveFunction objFunc = Gendreau06ObjectiveFunction.instance();
        Experiment.build(Gendreau06ObjectiveFunction.instance()).addScenario(scen)
                .addConfiguration(new TruckConfiguration(
                        SolverRoutePlanner.supplier(CheapestInsertionHeuristic.supplier(objFunc)),
                        SolverBidder.supplier(objFunc, CheapestInsertionHeuristic.supplier(objFunc)),
                        ImmutableList.of(AuctionCommModel.supplier())))

                .withThreads(1).showGui(new UICreator() {
                    @Override
                    public void createUI(Simulator sim) {
                        final UiSchema schema = new UiSchema(false);
                        schema.add(Vehicle.class, SWT.COLOR_RED);
                        schema.add(Depot.class, SWT.COLOR_CYAN);
                        schema.add(Parcel.class, SWT.COLOR_BLUE);
                        View.create(sim).with(new PlaneRoadModelRenderer())
                                .with(new RoadUserRenderer(schema, false)).with(new PDPModelRenderer())
                                .with(new TimeLinePanel()).setTitleAppendix(fileName).show();
                    }
                }).perform();
    }

    /**
     * Generates all scenarios. Each scenario has a randomly generated location
     * list.
     * @param rng The master random number generator.
     */
    public static void generateWithDistinctLocations(RandomGenerator rng) {
        generate(rng, Locations.builder().min(0d).max(AREA_WIDTH).buildUniform());
    }

    /**
     * Generates all scenarios. Each scenario has exactly the same location list.
     * @param rng The master random number generator.
     */
    public static void generateWithFixedLocations(RandomGenerator rng) {
        final List<Point> locations = Locations.builder().min(0d).max(AREA_WIDTH).buildUniform()
                .generate(rng.nextLong(), NUM_ORDERS * 2);

        generate(rng, Locations.builder().min(0d).max(AREA_WIDTH).buildFixed(locations));
    }

    private static void generate(RandomGenerator rng, LocationGenerator lg) {
        final List<Long> urgencyLevels = Longs.asList(0, 5, 10, 15, 20, 25, 30, 35, 40, 45);

        final ImmutableMap.Builder<GeneratorSettings, ScenarioGenerator> generatorsMap = ImmutableMap.builder();

        for (final long urg : urgencyLevels) {
            System.out.print("create " + urg);
            final long urgency = urg * 60 * 1000L;
            // The office hours is the period in which new orders are accepted, it
            // is defined as [0,officeHoursLength).
            final long officeHoursLength;
            if (urgency < HALF_DIAG_TT) {
                officeHoursLength = SCENARIO_LENGTH - TWO_DIAG_TT - PICKUP_DURATION - DELIVERY_DURATION;
            } else {
                officeHoursLength = SCENARIO_LENGTH - urgency - ONE_AND_HALF_DIAG_TT - PICKUP_DURATION
                        - DELIVERY_DURATION;
            }

            final double numPeriods = officeHoursLength / (double) INTENSITY_PERIOD;

            final Map<String, String> props = newLinkedHashMap();
            props.put("expected_num_orders", Integer.toString(NUM_ORDERS));
            props.put("time_series", "sine Poisson ");
            props.put("time_series.period", Long.toString(INTENSITY_PERIOD));
            props.put("time_series.num_periods", Double.toString(numPeriods));
            props.put("pickup_duration", Long.toString(PICKUP_DURATION));
            props.put("delivery_duration", Long.toString(DELIVERY_DURATION));
            props.put("width_height", String.format("%1.1fx%1.1f", AREA_WIDTH, AREA_WIDTH));
            final GeneratorSettings sineSettings = new GeneratorSettings(TimeSeriesType.SINE, urg, SCENARIO_LENGTH,
                    officeHoursLength, props);

            System.out.print(" non-homogenous Poisson");
            // NON-HOMOGENOUS
            final TimeSeriesGenerator sineTsg = TimeSeries.nonHomogenousPoisson(officeHoursLength,
                    IntensityFunctions.sineIntensity().area(NUM_ORDERS / numPeriods).period(INTENSITY_PERIOD)
                            .height(StochasticSuppliers.uniformDouble(-.99, 3d))
                            .phaseShift(StochasticSuppliers.uniformDouble(0, INTENSITY_PERIOD))
                            .buildStochasticSupplier());

            System.out.print(" homogenous Poisson");
            // HOMOGENOUS
            props.put("time_series", "homogenous Poisson");
            props.put("time_series.intensity", Double.toString((double) NUM_ORDERS / (double) officeHoursLength));
            props.remove("time_series.period");
            props.remove("time_series.num_periods");
            final TimeSeriesGenerator homogTsg = TimeSeries.homogenousPoisson(officeHoursLength, NUM_ORDERS);
            final GeneratorSettings homogSettings = new GeneratorSettings(TimeSeriesType.HOMOGENOUS, urg,
                    SCENARIO_LENGTH, officeHoursLength, props);

            System.out.print(" normal");
            // NORMAL
            props.put("time_series", "normal");
            props.remove("time_series.intensity");
            final TimeSeriesGenerator normalTsg = TimeSeries.normal(officeHoursLength, NUM_ORDERS, 2.4 * 60 * 1000);
            final GeneratorSettings normalSettings = new GeneratorSettings(TimeSeriesType.NORMAL, urg,
                    SCENARIO_LENGTH, officeHoursLength, props);

            System.out.print(" uniform");
            // UNIFORM
            props.put("time_series", "uniform");
            final StochasticSupplier<Double> maxDeviation = StochasticSuppliers.normal().mean(1 * 60 * 1000)
                    .std(1 * 60 * 1000).lowerBound(0).upperBound(15d * 60 * 1000).buildDouble();
            final TimeSeriesGenerator uniformTsg = TimeSeries.uniform(officeHoursLength, NUM_ORDERS, maxDeviation);
            final GeneratorSettings uniformSettings = new GeneratorSettings(TimeSeriesType.UNIFORM, urg,
                    SCENARIO_LENGTH, officeHoursLength, props);
            System.out.println(".");

            generatorsMap.put(sineSettings, createGenerator(SCENARIO_LENGTH, urgency, sineTsg, lg));
            generatorsMap.put(homogSettings, createGenerator(SCENARIO_LENGTH, urgency, homogTsg, lg));
            generatorsMap.put(normalSettings, createGenerator(SCENARIO_LENGTH, urgency, normalTsg, lg));
            generatorsMap.put(uniformSettings, createGenerator(SCENARIO_LENGTH, urgency, uniformTsg, lg));
        }

        final ImmutableMap<GeneratorSettings, ScenarioGenerator> scenarioGenerators = generatorsMap.build();

        System.out.println("num generators: " + scenarioGenerators.size());
        for (final Entry<GeneratorSettings, ScenarioGenerator> entry : scenarioGenerators.entrySet()) {

            final GeneratorSettings generatorSettings = entry.getKey();
            System.out.println("URGENCY: " + generatorSettings.urgency + " " + generatorSettings.timeSeriesType);

            if (generatorSettings.timeSeriesType == TimeSeriesType.SINE) {
                createScenarios(rng, generatorSettings, entry.getValue(), .0, .46, 10);
            } else if (generatorSettings.timeSeriesType == TimeSeriesType.HOMOGENOUS) {
                createScenarios(rng, generatorSettings, entry.getValue(), .49, .56, 2);
            } else if (generatorSettings.timeSeriesType == TimeSeriesType.NORMAL) {
                createScenarios(rng, generatorSettings, entry.getValue(), .59, .66, 2);
            } else if (generatorSettings.timeSeriesType == TimeSeriesType.UNIFORM) {
                createScenarios(rng, generatorSettings, entry.getValue(), .69, 1, 7);
            } else {
                throw new IllegalArgumentException();
            }
        }
        System.out.println("DONE.");
    }

    static void createScenarios(RandomGenerator rng, GeneratorSettings generatorSettings,
            ScenarioGenerator generator, double dynLb, double dynUb, int levels) {
        final List<Scenario> scenarios = newArrayList();

        final Multimap<Double, Scenario> dynamismScenariosMap = LinkedHashMultimap.create();
        while (scenarios.size() < levels * TARGET_NUM_INSTANCES) {
            final Scenario scen = generator.generate(rng, "temp");
            Metrics.checkTimeWindowStrictness(scen);
            final StatisticalSummary urgency = Metrics.measureUrgency(scen);

            final long expectedUrgency = generatorSettings.urgency * 60000L;
            if (Math.abs(urgency.getMean() - expectedUrgency) < 0.01 && urgency.getStandardDeviation() < 0.01) {

                final int numParcels = Metrics.getEventTypeCounts(scen).count(PDPScenarioEvent.ADD_PARCEL);
                if (numParcels == NUM_ORDERS) {

                    final double dynamism = Metrics.measureDynamism(scen, generatorSettings.officeHours);
                    System.out.print(String.format("%1.3f ", dynamism));
                    if ((dynamism % DYN_STEP_SIZE < DYN_BANDWIDTH
                            || dynamism % DYN_STEP_SIZE > DYN_STEP_SIZE - DYN_BANDWIDTH) && dynamism <= dynUb
                            && dynamism >= dynLb) {

                        final double targetDyn = Math.round(dynamism / DYN_STEP_SIZE) * DYN_STEP_SIZE;

                        final int numInstances = dynamismScenariosMap.get(targetDyn).size();

                        if (numInstances < TARGET_NUM_INSTANCES) {

                            final String instanceId = "#" + Integer.toString(numInstances);
                            dynamismScenariosMap.put(targetDyn, scen);

                            final String problemClassId = String.format("%d-%1.2f",
                                    (long) (urgency.getMean() / 60000), targetDyn);
                            System.out.println();
                            System.out.println(" > ACCEPT " + problemClassId);
                            final String fileName = DATASET_DIR + problemClassId + instanceId;
                            try {
                                Files.createParentDirs(new File(fileName));
                                writePropertiesFile(scen, urgency, dynamism, problemClassId, instanceId,
                                        generatorSettings, fileName);
                                MetricsIO.writeLocationList(Metrics.getServicePoints(scen),
                                        new File(fileName + ".points"));
                                MetricsIO.writeTimes(scen.getTimeWindow().end, Metrics.getArrivalTimes(scen),
                                        new File(fileName + ".times"));

                                final ProblemClass pc = new SimpleProblemClass(problemClassId);
                                final Scenario finalScenario = Scenario.builder(pc).copyProperties(scen)
                                        .problemClass(pc).instanceId(instanceId).build();

                                ScenarioIO.write(finalScenario, new File(fileName + ".scen").toPath());
                            } catch (final IOException e) {
                                throw new IllegalStateException(e);
                            }
                            scenarios.add(scen);
                        }
                    }
                }
            }
        }
    }

    static void writePropertiesFile(Scenario scen, StatisticalSummary urgency, double dynamism,
            String problemClassId, String instanceId, GeneratorSettings settings, String fileName) {
        final DateTimeFormatter formatter = ISODateTimeFormat.dateHourMinuteSecondMillis();

        final ImmutableMap.Builder<String, Object> properties = ImmutableMap.<String, Object>builder()
                .put("problem_class", problemClassId).put("id", instanceId).put("dynamism", dynamism)
                .put("urgency_mean", urgency.getMean()).put("urgency_sd", urgency.getStandardDeviation())
                .put("creation_date", formatter.print(System.currentTimeMillis()))
                .put("creator", System.getProperty("user.name")).put("day_length", settings.dayLength)
                .put("office_opening_hours", settings.officeHours);

        properties.putAll(settings.properties);

        final ImmutableMultiset<Enum<?>> eventTypes = Metrics.getEventTypeCounts(scen);
        for (final Multiset.Entry<Enum<?>> en : eventTypes.entrySet()) {
            properties.put(en.getElement().name(), en.getCount());
        }

        try {
            Files.write(Joiner.on("\n").withKeyValueSeparator(" = ").join(properties.build()),
                    new File(fileName + ".properties"), Charsets.UTF_8);
        } catch (final IOException e) {
            throw new IllegalStateException(e);
        }
    }

    static ScenarioGenerator createGenerator(long scenarioLength, long urgency, TimeSeriesGenerator tsg,
            LocationGenerator lg) {
        return ScenarioGenerator.builder()
                // global
                .timeUnit(SI.MILLI(SI.SECOND)).scenarioLength(scenarioLength).tickSize(TICK_SIZE)
                .speedUnit(NonSI.KILOMETERS_PER_HOUR).distanceUnit(SI.KILOMETER)
                .stopCondition(Predicates.and(StopConditions.VEHICLES_DONE_AND_BACK_AT_DEPOT,
                        StopConditions.TIME_OUT_EVENT))

                // parcels
                .parcels(Parcels.builder()
                        .announceTimes(TimeSeries.filter(tsg, TimeSeries.numEventsPredicate(NUM_ORDERS)))
                        .pickupDurations(constant(PICKUP_DURATION)).deliveryDurations(constant(DELIVERY_DURATION))
                        .neededCapacities(constant(0)).locations(lg)
                        .timeWindows(new CustomTimeWindowGenerator(urgency)
                        // TimeWindows.builder()
                        // .pickupUrgency(constant(urgency))
                        // // .pickupTimeWindowLength(StochasticSuppliers.uniformLong(5
                        // // * 60 * 1000L,))
                        // .deliveryOpening(constant(0L))
                        // .minDeliveryLength(constant(10 * 60 * 1000L))
                        // .deliveryLengthFactor(constant(3d))
                        // .build()
                        ).build())

                // vehicles
                .vehicles(Vehicles.builder().capacities(constant(1)).centeredStartPositions()
                        .creationTimes(constant(-1L)).numberOfVehicles(constant(NUM_VEHICLES))
                        .speeds(constant(VEHICLE_SPEED_KMH)).timeWindowsAsScenario().build())

                // depots
                .depots(Depots.singleCenteredDepot())

                // models
                .addModel(Models.roadModel(VEHICLE_SPEED_KMH, true))
                .addModel(Models.pdpModel(TimeWindowPolicies.TARDY_ALLOWED)).build();
    }

    static class GeneratorSettings {
        final TimeSeriesType timeSeriesType;
        final long urgency;
        final long dayLength;
        final long officeHours;
        final ImmutableMap<String, String> properties;

        GeneratorSettings(TimeSeriesType type, long urg, long dayLen, long officeH, Map<String, String> props) {
            timeSeriesType = type;
            urgency = urg;
            dayLength = dayLen;
            officeHours = officeH;
            properties = ImmutableMap.copyOf(props);
        }
    }

    enum TimeSeriesType {
        SINE, HOMOGENOUS, NORMAL, UNIFORM;
    }

    static class CustomTimeWindowGenerator implements TimeWindowGenerator {
        private static final long MINIMAL_PICKUP_TW_LENGTH = 10 * 60 * 1000L;
        private static final long MINIMAL_DELIVERY_TW_LENGTH = 10 * 60 * 1000L;

        private final long urgency;
        private final StochasticSupplier<Double> pickupTWopening;
        private final StochasticSupplier<Double> deliveryTWlength;
        private final StochasticSupplier<Double> deliveryTWopening;
        private final RandomGenerator rng;

        public CustomTimeWindowGenerator(long urg) {
            urgency = urg;
            pickupTWopening = StochasticSuppliers.uniformDouble(0d, 1d);
            deliveryTWlength = StochasticSuppliers.uniformDouble(0d, 1d);
            deliveryTWopening = StochasticSuppliers.uniformDouble(0d, 1d);
            rng = new MersenneTwister();
        }

        @Override
        public void generate(long seed, Builder parcelBuilder, TravelTimes travelTimes, long endTime) {
            rng.setSeed(seed);
            final long orderAnnounceTime = parcelBuilder.getOrderAnnounceTime();
            final Point pickup = parcelBuilder.getPickupLocation();
            final Point delivery = parcelBuilder.getDeliveryLocation();

            final long pickupToDeliveryTT = travelTimes.getShortestTravelTime(pickup, delivery);
            final long deliveryToDepotTT = travelTimes.getTravelTimeToNearestDepot(delivery);

            // compute range of possible openings
            long pickupOpening;
            if (urgency > MINIMAL_PICKUP_TW_LENGTH) {

                // possible values range from 0 .. n
                // where n = urgency - MINIMAL_PICKUP_TW_LENGTH
                pickupOpening = orderAnnounceTime + DoubleMath.roundToLong(
                        pickupTWopening.get(rng.nextLong()) * (urgency - MINIMAL_PICKUP_TW_LENGTH),
                        RoundingMode.HALF_UP);
            } else {
                pickupOpening = orderAnnounceTime;
            }
            final TimeWindow pickupTW = new TimeWindow(pickupOpening, orderAnnounceTime + urgency);
            parcelBuilder.pickupTimeWindow(pickupTW);

            // find boundaries
            final long minDeliveryOpening = pickupTW.begin + parcelBuilder.getPickupDuration() + pickupToDeliveryTT;

            final long maxDeliveryClosing = endTime - deliveryToDepotTT - parcelBuilder.getDeliveryDuration();
            long maxDeliveryOpening = maxDeliveryClosing - MINIMAL_DELIVERY_TW_LENGTH;
            if (maxDeliveryOpening < minDeliveryOpening) {
                maxDeliveryOpening = minDeliveryOpening;
            }

            final double openingRange = maxDeliveryOpening - minDeliveryOpening;
            final long deliveryOpening = minDeliveryOpening + DoubleMath
                    .roundToLong(deliveryTWopening.get(rng.nextLong()) * openingRange, RoundingMode.HALF_DOWN);

            final long minDeliveryClosing = Math
                    .min(Math.max(pickupTW.end + parcelBuilder.getPickupDuration() + pickupToDeliveryTT,
                            deliveryOpening + MINIMAL_DELIVERY_TW_LENGTH), maxDeliveryClosing);

            final double closingRange = maxDeliveryClosing - minDeliveryClosing;
            final long deliveryClosing = minDeliveryClosing + DoubleMath
                    .roundToLong(deliveryTWlength.get(rng.nextLong()) * closingRange, RoundingMode.HALF_DOWN);

            final long latestDelivery = endTime - deliveryToDepotTT - parcelBuilder.getDeliveryDuration();

            // final long minDeliveryTWlength = MINIMAL_DELIVERY_TW_LENGTH;
            // // Math
            // // .max(MINIMAL_DELIVERY_TW_LENGTH,
            // // pickupTW.end + parcelBuilder.getPickupDuration()
            // // + pickupToDeliveryTT);
            // final long maxDeliveryTWlength = latestDelivery - minDeliveryOpening;
            //
            // double factor = maxDeliveryTWlength - minDeliveryTWlength;
            // if (factor < 0d) {
            // factor = 0;
            // }
            // long deliveryTimeWindowLength = minDeliveryTWlength
            // + DoubleMath.roundToLong(deliveryTWlength.get(rng.nextLong())
            // * factor, RoundingMode.HALF_UP);
            //
            // // delivery TW may not close before this time:
            // final long minDeliveryClosing = pickupTW.end
            // + parcelBuilder.getPickupDuration() + pickupToDeliveryTT;
            //
            // if (minDeliveryOpening < minDeliveryClosing - deliveryTimeWindowLength)
            // {
            // minDeliveryOpening = minDeliveryClosing - deliveryTimeWindowLength;
            // }
            //
            // final long deliveryOpening;
            // if (deliveryTimeWindowLength >= maxDeliveryTWlength) {
            // deliveryOpening = minDeliveryOpening;
            // deliveryTimeWindowLength = maxDeliveryTWlength;
            // } else {
            // deliveryOpening = minDeliveryOpening
            // + DoubleMath.roundToLong(deliveryTWopening.get(rng.nextLong())
            // * (maxDeliveryTWlength - deliveryTimeWindowLength),
            // RoundingMode.HALF_UP);
            // }
            //
            // if (deliveryOpening + deliveryTimeWindowLength > latestDelivery) {
            // deliveryTimeWindowLength = latestDelivery - deliveryOpening;
            // }

            final TimeWindow deliveryTW = new TimeWindow(deliveryOpening, deliveryClosing);

            checkArgument(deliveryOpening >= minDeliveryOpening);
            checkArgument(deliveryOpening + deliveryTW.length() <= latestDelivery);
            checkArgument(pickupTW.end + parcelBuilder.getPickupDuration() + pickupToDeliveryTT <= deliveryOpening
                    + deliveryTW.length());

            parcelBuilder.deliveryTimeWindow(deliveryTW);
        }
    }
}