Exemple #1
0
    public void Run()
    {
        var api = new ApiHelper <Ivr7.SolveRequest, Ivr7.SolutionResponse>("ivr7-kt461v8eoaif", configFile);

        // so here we're going to build the model

        // create a solve request
        Ivr7.SolveRequest sr = new Ivr7.SolveRequest();

        sr.Model = new Ivr7.Model(); // initialise the model container

        // we're going to reuse the helpers described in the ivr7basic example. Please see that for a reference.
        ivr7helper.makeDistanceTimeCapDims(sr.Model); // adds distance, time & capacity
        ivr7helper.makeLocations(sr.Model, data);     // adds all the locations to the model
        ivr7helper.makeJobTimeCap(sr.Model, data, ivr7helper.Rep(0, data.Count - 1), ivr7helper.Seq(1, data.Count));
        sr.Model.vehicleCostClasses.Add(ivr7helper.makeVccSimple("vcc1", 1000, 0.01f, 0.01f, 0.01f, 1, 3));
        sr.Model.vehicleClasses.Add(ivr7helper.makeVcSimple("vc1", 1, 1, 1, 1));
        for (int i = 0; i < 4; i++)
        {
            sr.Model.Vehicles.Add(ivr7helper.makeVehicleCap("vehicle_" + i, // unique id for the vehicle.
                                                            "vc1",          // the vehicle class
                                                            "vcc1",         // the vehicle cost class
                                                            2000,           // the capacity of the vehicle
                                                            data[0].id,     // start location for the vehicle
                                                            data[0].id,     // end location for the vehicle
                                                            7 * 60,         // start time: 7 AM
                                                            18 * 60         // end time: 6 PM
                                                            ));
        }

        sr.solveType = Ivr7.SolveRequest.SolveType.Optimise; // Optimise the solve request.

        // now it's just sending the model to the api

        string requestId       = api.Post(sr);       // send the model to the api
        var    initialSolution = api.Get(requestId); // get the response (which it typed, so that's cool)

        // so we can do a few things here, we can add constraints which weren't in the original
        // model, evaluate the same sequence and see if any constraints are broken?
        // sure, this sounds like fun. Lets add some time windows to all the locations and see
        // what that does.
        foreach (var l in sr.Model.Locations)
        {
            var a = new Ivr7.Location.Attribute()
            {
                dimensionId    = "time",
                arrivalWindows = { new Ivr7.Window {
                                       Start = 8 * 60,
                                       End   = 14 * 60
                                   } }
            };
            l.Attributes.Add(a);
        }


        // okay, so now we've added a 08:00 - 14:00 window on all the locations.
        // in order to evaluate our current solution against this new solution
        // we need to convert our current solution to a task sequence (which is done
        // by vehicle)
        // so the only catch here is that the shift-start and shift-end nodes are implicitly already there
        // so all we really need to do is pull out the nodes inbetween. So we filter on only the tasks we're
        // scheduling in the last solution we received.

        foreach (var r in initialSolution.Routes)
        {
            // so the definition is, for a vehicle: list the tasks that vehicle should perform
            // each item in the list is another vehicle. vehicles with no tasks can be omitted
            List <string> tasklist = new List <string>();
            for (int i = 1; i < r.Stops.Count - 1; i++)
            {
                tasklist.Add(r.Stops[i].taskId);
            }
            if (tasklist.Count > 0)
            {
                var ts = new Ivr7.TaskSequence {
                    vehicleId = r.vehicleId
                };
                foreach (var t in tasklist)
                {
                    ts.taskIds.Add(t);
                }
                sr.Model.taskSequences.Add(ts);
            }
        }

        sr.solveType = Ivr7.SolveRequest.SolveType.Evaluate; // now evaluate this sequence with the new constraints

        requestId = api.Post(sr);                            // send the new model to the api
        Solution  = api.Get(requestId);                      // get the response (which it typed, so that's cool)

        // just print the infeasibilities
        ivr7helper.printSolution(Solution, false, false, false, true);

        // so here we should have some constraints which have been broken.
        // We get told which dimension is related (if the constraint is related to a dimension)
        // we also get told which type of constraint (if known) and the degree to which the constraint is broken.

        //  if the constraints are tardy constraints being broken, this means the task starts AFTER the
        //  allowable window. the limit will be often be zero, the value will be the amount by which the
        //  vehicle is late. we can check the arrival time of the task to verify this.
        HashSet <string> infeasibleTasks = new HashSet <string>();

        foreach (var t in Solution.Infeasibilities)
        {
            infeasibleTasks.Add(t.taskId);
        }
        foreach (var r in initialSolution.Routes)
        { // now step through the initial solution and confirm this.
            foreach (var s in r.Stops)
            {
                if (infeasibleTasks.Contains(s.taskId))
                {
                    foreach (var a in s.Attributes)
                    {
                        if (a.dimId == "time")
                        {
                            if (!(a.startValue > 14 * 60))
                            {
                                throw new Exception("Hmmm. a stop was marked as infeasible but it's arrival time looks okay?");
                                // don't worry, this won't happen unless the solver is broken, or you're checking against
                                // the incorrect solution reference.
                            }
                        }
                    }
                }
            }
        }
        // we could try other things: how about we take out all tasks which are infeasible?
        // lets modify the solve request to take out these stops.
        foreach (var ts in sr.Model.taskSequences)
        {
            foreach (var tskId in ts.taskIds.ToArray())
            {
                if (infeasibleTasks.Contains(tskId))
                {
                    ts.taskIds.Remove(tskId); // lol. this is O(n) on the .net list (which is actually an array). For this example it's fine,
                                              // but in general, it's better a linkedlist for these kinds of operators. Although you need a prettty
                                              // massive model to really feel the performance impact of this.
                }
            }
        }
        // okay, but kinda obviously, the pickups were mostly feasible on their time windows, it was
        // just the dropoffs that were a problem. So now we have pickups without a dropoff?
        // what will happen?
        // lets re-run the model and check for infeasibilities
        requestId = api.Post(sr);       // send the new model to the api
        Solution  = api.Get(requestId); // get the response (which it typed, so that's cool)

        // just print the infeasibilities
        ivr7helper.printSolution(Solution, false, false, false, true);
        // so this is again, very intuitive. We find that there are a whole bunch of precendence
        // constraints which are then broken, cumul-pair constraints and task-pair constraints.
        // this is because there's a relation between the pickup and dropoff and either they're BOTH scheduled
        // or BOTH unscheduled. Having one task assigned to a vehicle in the schedule without the other breaks
        // a bunch of constraints.
        // so lets apply the same trick and remove these stops.
        foreach (var t in Solution.Infeasibilities)
        {
            infeasibleTasks.Add(t.taskId);
        }

        foreach (var ts in sr.Model.taskSequences)
        {
            foreach (var tskId in ts.taskIds.ToArray())
            {
                if (infeasibleTasks.Contains(tskId))
                {
                    ts.taskIds.Remove(tskId);
                }
            }
        }

        requestId = api.Post(sr);       // send the new model to the api
        Solution  = api.Get(requestId); // get the response (which it typed, so that's cool)
        // just print the infeasibilities
        ivr7helper.printSolution(Solution, false, false, false, true);
        // great, this is actually an empty table now, which means there aren't any infeasibilities left in the schedule
        // but at what cost did that come? Quite a lot. the solution now is very expensive.
        // We could simply copy the solution, then switch the model back to optimise and re-run it

        var prevSolution = Solution;

        sr.solveType = Ivr7.SolveRequest.SolveType.Optimise;
        requestId    = api.Post(sr);
        Solution     = api.Get(requestId);
        if (prevSolution.Objective < Solution.Objective)
        {
            throw new Exception("Whoa, this doesn't make any sense :-)");
        }

        ivr7helper.printSolution(Solution);
        // with no infeasibilities.
        // for visualisations see the R/python notebook for plots on the same example.

        return;
    }
Exemple #2
0
    public void Run()
    {
        var api = new ApiHelper <Ivr7.SolveRequest, Ivr7.SolutionResponse>("ivr7-kt461v8eoaif", configFile);

        // so here we're going to build the model

        // create a solve request
        Ivr7.SolveRequest sr = new Ivr7.SolveRequest();

        sr.Model = new Ivr7.Model(); // initialise the model container

        // we're going to reuse the helpers described in the ivr7basic example. Please see that for a reference.

        ivr7helper.makeDistanceTimeCapDims(sr.Model); // adds distance, time & capacity
        ivr7helper.makeLocations(sr.Model, data);     // adds all the locations to the model

        // we're going to add time windows to the locations. 08:00 - 14:00
        foreach (var l in sr.Model.Locations)
        {
            var a = new Ivr7.Location.Attribute()
            {
                dimensionId    = "time",
                arrivalWindows = { new Ivr7.Window {
                                       Start = 8 * 60,
                                       End   = 14 * 60
                                   } }
            };
            l.Attributes.Add(a);
        }

        ivr7helper.makeJobTimeCap(sr.Model, data, ivr7helper.Rep(0, data.Count - 1), ivr7helper.Seq(1, data.Count));
        // Two vehicle cost classes, one which is cheaper on time, one which is cheaper on distance, one
        // which is more expensive if used (1000 vs 1200).
        sr.Model.vehicleCostClasses.Add(ivr7helper.makeVccSimple("vcc1", 1000, 0.01f, 0.01f, 0.01f, 1, 3));
        sr.Model.vehicleCostClasses.Add(ivr7helper.makeVccSimple("vcc2", 1200, 0.1f, 0.1f, 0.1f, 0.6f, 2.5f));

        sr.Model.vehicleClasses.Add(ivr7helper.makeVcSimple("vc1", 1, 1, 1, 1));

        // now we can just specify the vehicles.
        // lets provide 2 x 2 ton vehicles and 2 x 3 ton vehicles. Although this is probably more than we need.
        // the reason for this is that we're modelling a full-blown pickup+dropoff model, so if there's
        // time to reload, a vehicle can return to the depot and grab more goodies!

        for (int i = 0; i < 4; i++)
        {
            string vcc = "vcc1";
            float  cap = 2000;
            if (i > 1)
            {
                vcc = "vcc2";
                cap = 3000;
            }
            sr.Model.Vehicles.Add(ivr7helper.makeVehicleCap("vehicle_" + i, // unique id for the vehicle.
                                                            "vc1",          // the vehicle class
                                                            vcc,            // the vehicle cost class
                                                            cap,            // the capacity of the vehicle
                                                            data[0].id,     // start location for the vehicle
                                                            data[0].id,     // end location for the vehicle
                                                            7 * 60,         // start time: 7 AM
                                                            18 * 60         // end time: 6 PM
                                                            ));
        }

        // Lunch breaks.
        // so this is a touch more complex, we want to link our transit-rule to the time
        // dimension, and when a certain amount has accumulated on the dimension, we trigger the rule.
        sr.Model.transitRules.Add(ivr7helper.makeLunchBreakRule("lunch_break_rule", "lunchy_munchy_", 12 * 60, 60));

        // now link the transit rule to the vehicle classes
        sr.Model.vehicleClasses[0].transitRuleIds.Add("lunch_break_rule");

        sr.solveType = Ivr7.SolveRequest.SolveType.Optimise; // Optimise the solve request.

        // now it's just sending the model to the api

        string requestId = api.Post(sr); // send the model to the api

        Solution = api.Get(requestId);   // get the response (which it typed, so that's cool)

        // lets pull out the total distance
        lineString ls            = new lineString();
        double     totalDistance = 0.0;
        int        stopCount     = 0;

        foreach (var r in Solution.Routes)
        {
            foreach (var e in r.interStops)
            {
                foreach (var a in e.Attributes)
                {
                    if (a.dimId == "distance")
                    {
                        totalDistance += (a.endValue - a.startValue); // the difference between the start and end value
                    }
                }
                foreach (var g in e.routeSegments)
                {
                    ls.Add(new double[] { g.Longitude, g.Latitude }); // these are the road-network edges if you want to visualise the route in
                                                                      //leaflet or on a map.
                }
            }
        }
        Console.WriteLine(string.Format("Total Cost: {0:0.00} \t ", Solution.Objective));
        Console.WriteLine(string.Format("Total distance: {0:0.00} km\t Stops: " + stopCount, totalDistance));

        ivr7helper.printSolution(Solution);
        // the maximum quantity assigned to each vehicle is <= 2000 (the capacity dimension).
        // the majority of the cost is coming in the distance dimension (because of the way we've configured the vehicle cost class)

        // for visualisations see the R/python notebook for plots on the same example.

        return;
    }