public static void makeDistanceTimeCapDims(Ivr7.Model m) { m.Dimensions = new Ivr7.DimensionConfiguration(); m.Dimensions.timeConfig = new Ivr7.InternalDimension { Id = "time", measurementUnit = Ivr7.InternalDimension.eMeasurementUnit.Minutes, slackMax = (float)1e6, tardyMax = 0 }; m.Dimensions.distanceConfig = new Ivr7.InternalDimension { Id = "distance", measurementUnit = Ivr7.InternalDimension.eMeasurementUnit.Kilometres, slackMax = 0, tardyMax = 0 }; m.Dimensions.capacityDimensions.Add( new Ivr7.CapacityDimension { Id = "capacity", Units = "kg", slackMax = 0, tardyMax = 0 } ); }
public static void makeJobTimeCap(Ivr7.Model m, List <dataRow> d, List <int> srcs, List <int> dests) { m.Jobs.Clear(); if (srcs.Count != dests.Count) { throw new System.Exception("Expected srcs.Count == dests.Count"); } for (int i = 0; i < srcs.Count; i++) { int si = srcs[i]; int di = dests[i]; Ivr7.Job j = new Ivr7.Job(); j.Id = "job_" + d[di].id; j.pickupTask = new Ivr7.Job.Task(); j.pickupTask.taskId = "pickup_" + d[di].id; j.pickupTask.locationId = d[si].id; j.pickupTask.Attributes.Add( new Ivr7.Job.Task.Attribute { dimensionId = "time", Quantity = d[di].pickupTime }); j.pickupTask.Attributes.Add( new Ivr7.Job.Task.Attribute { dimensionId = "capacity", Quantity = d[di].quanity } ); j.dropoffTask = new Ivr7.Job.Task(); j.dropoffTask.taskId = "dropoff_" + d[di].id; j.dropoffTask.locationId = d[di].id; // careful here, in C# the object is passed by ref. // so instantiate a new set of attributes, don't reuse the object's created // prior to this. j.dropoffTask.Attributes.Add( new Ivr7.Job.Task.Attribute { dimensionId = "time", Quantity = d[di].dropoffTime }); j.dropoffTask.Attributes.Add( new Ivr7.Job.Task.Attribute { dimensionId = "capacity", Quantity = -d[di].quanity } ); j.Penalty = 10000; m.Jobs.Add(j); } }
public static void makeLocations(Ivr7.Model m, List <dataRow> d) { m.Locations.Clear(); foreach (var l in d) { m.Locations.Add(new Ivr7.Location { Id = l.id, Geocode = new Ivr7.Geocode { Longitude = l.X, Latitude = l.Y } }); } }
public void Run() { var api = new ApiHelper <Ivr7.SolveRequest, Ivr7.SolutionResponse>("ivr7-kt461v8eoaif", configFile); // so here we're going to build the model var m = new Ivr7.Model(); // initialise the model container // we're going to reuse the helpers described in the ivr7basic example. Please see that for an initial reference. // We want "Open Routing" which basically means that if a vehicle finishes it's day at any node, // it is then not supposed to cost the return trip home. So as a start, we need to separate the // depot (Guiness Storehouse) from the vehicle-home location by name - it's okay if it's at the // same point (long/lat), but we're going to separate the identifier so we can control the // distance/time matrix nicely. ivr7helper.makeDistanceTimeCapDims(m); // adds distance, time & capacity ivr7helper.makeLocations(m, data); // adds all the locations to the model // we're going to add an exta location; the "vehicle-site" m.Locations.Add(new Ivr7.Location { Id = "vehicle-site", Geocode = m.Locations[0].Geocode // use the geocode of the Guiness storehouse. }); ivr7helper.makeJobTimeCap(m, data, ivr7helper.Rep(0, data.Count - 1), ivr7helper.Seq(1, data.Count)); m.vehicleCostClasses.Add(ivr7helper.makeVccSimple("vcc1", 1000, 0.01f, 0.01f, 0.01f, 1, 3)); m.vehicleClasses.Add(ivr7helper.makeVcSimple("vc1", 1, 1, 1, 1)); for (int i = 0; i < 4; i++) { m.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 "vehicle-site", // start location for the vehicle //NOTE the change here. "vehicle-site", // end location for the vehicle 7 * 60, // start time: 7 AM 18 * 60 // end time: 6 PM )); } // so we've created vehicles which need to start/end at "vehicle-site". Now we can make // the last change which is to override the distance between locations and the "vehicle-site". // we're only going to modify the distances FROM locations TO "vehicle-site". If you wanted to // do complete line-haul outsourcing-style modelling, you could also do this for "vehicle-site" // TO all alocations. For now, we'll just demonstrate the open routing case. // you have two ways of doing this. Upload it via the data-api, or upload it as part of the model. bool dataUpload = true; // it's nice to illustrate this if you've enabled the services on your key // but you can set this to false to get a feel for the other code path if needed. if (dataUpload) { var ts = new IVRData.TransitSet(); for (int i = 0; i < m.Locations.Count; i++) { ts.Transits.Add(new IVRData.TransitSet.TransitValue { fromId = m.Locations[i].Id, toId = "vehicle-site", Value = 0 }); } var datamodel = new IVRData.CachedTransitSet { transitSet = ts }; var data_api = new ApiHelper <IVRData.CachedTransitSet, object>("ivrdata-o43e0dvs78zq", configFile); var transitModelID = data_api.Post(datamodel); // now create the additional transit generators and link them to the data that has been uploaded var tgen_d = new Ivr7.TransitGenerator { Id = "custom_distance", requestId = transitModelID // Note, we're telling the API where to find the Transit-set data. }; var tgen_t = new Ivr7.TransitGenerator { Id = "custom_time", requestId = transitModelID // we can use the same one here as before, why? because it's all zero :-) // so we could upload another column of zeros, but there isn't much point. }; m.transitGenerators.Add(tgen_d); m.transitGenerators.Add(tgen_t); } else { // embed the zero elements in the matrix in the payload directly (rather than through a data-upload) var ts = new Ivr7.TransitSet(); for (int i = 0; i < m.Locations.Count; i++) { ts.Transits.Add(new Ivr7.TransitSet.TransitValue { fromId = m.Locations[i].Id, toId = "vehicle-site", Value = 0 }); } var tgen_d = new Ivr7.TransitGenerator { Id = "custom_distance", transitSet = ts // or we're explicily providing all the data. }; var tgen_t = new Ivr7.TransitGenerator { Id = "custom_time", transitSet = ts // or we're explicily providing all the data. }; m.transitGenerators.Add(tgen_d); m.transitGenerators.Add(tgen_t); } // now the last step, we need to tell the vehicles that they should use these // transit generators. Note. we're appending the transit generators here, so keeping // the roadnetwork distance/time in the list (the order of the list IS important when // layering matricies). There are 4 attributes in the vehicle-class list now. m.vehicleClasses[0].Attributes.Add(new Ivr7.VehicleClass.Attribute { dimensionId = "time", transitGeneratorId = "custom_time", transitCoef = 1.0f, locationCoef = 1.0f, taskCoef = 1.0f }); m.vehicleClasses[0].Attributes.Add(new Ivr7.VehicleClass.Attribute { dimensionId = "distance", transitGeneratorId = "custom_distance", transitCoef = 1.0f, }); // so now we have a roadnetwork distance + time generator // followed by a custom time and custom distance generator. // the api will execute the transit generators in the order they appear and will // override previous values with new values (if they exist). So in this case, it will // build a transit matrix using the road network, then overlay the custom matrix which // was uploaded with our data. var sr = new Ivr7.SolveRequest(); sr.Model = m; // could have instantiated the solve request up-front if we'd wanted to 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) ivr7helper.printSolution(Solution); // lets confirm that the distances and times between the last stop and the vehicle-site are indeed zero. Dictionary <int, string> stopToLocation = new Dictionary <int, string>(); foreach (var r in Solution.Routes) { foreach (var s in r.Stops) { stopToLocation.Add(s.Id, s.locationId); } foreach (var e in r.interStops) { if (stopToLocation[e.fromStopId] != "vehicle-site" && stopToLocation[e.toStopId] == "vehicle-site") { foreach (var a in e.Attributes) { if (a.dimId == "time" || a.dimId == "distance") { if (a.endValue - a.startValue != 0) { throw new Exception("distance and/or time is supposed to be zero!"); } } } } } } // for visualisations see the R/python notebook for plots on the same example. // You'll see in the visuals that the stops that are farthest from the depot are typically // selected to be the route (because it results in the largest saving). return; }
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 var m = 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(m); // adds distance, time & capacity ivr7helper.makeLocations(m, data); // adds all the locations to the model ivr7helper.makeJobTimeCap(m, data, ivr7helper.Rep(0, data.Count - 1), ivr7helper.Seq(1, data.Count)); m.vehicleCostClasses.Add(ivr7helper.makeVccSimple("vcc1", 1000, 0.01f, 0.01f, 0.01f, 1, 3)); m.vehicleClasses.Add(ivr7helper.makeVcSimple("vc1", 1, 1, 1, 1)); for (int i = 0; i < 4; i++) { m.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 )); } // okay, so that's a basic model. Lets now use the objects, but submit them to the api // through a different mechanism. var data_api = new ApiHelper <IVRData.CachedModel, object>("ivrdata-o43e0dvs78zq", configFile); // so the data api allows us to push just the data, without anything else. var dataModel = new IVRData.CachedModel(); dataModel.Model = ApiHelper <object, object> .SerialiseObject <Ivr7.Model>(m); // epic: we just saved our model as a byte stream into this data payload. string modelID = data_api.Post(dataModel); var sr = new Ivr7.SolveRequest(); // we can now make a solve request which references the model we've uploaded sr.modelID = modelID; // tell the solve request to use the model we uploaded. 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) ivr7helper.printSolution(Solution); // this also means that because we have a model which is versioned separately from the // solve request, we can use the solve request with the task-sequence and have that apply // to a model. So lets extract the task sequence from the solved model. foreach (var r in Solution.Routes) { 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.Routes.Add(ts); //NOTE: We're adding the tasks to the solve request, not the model - the task sequence will be // applied to the model we've referenced in this example } } sr.solveType = Ivr7.SolveRequest.SolveType.Evaluate; // now evaluate this sequence with the model reference (i.e. a minimal data-send) requestId = api.Post(sr); var EvalSolution = api.Get(requestId); // just print the infeasibilities ivr7helper.printSolution(EvalSolution); if (Math.Abs(EvalSolution.Objective - Solution.Objective) > 0.01f) { throw new Exception("Evaluation not identical to original solution value?"); } // so this is pretty nice when we think about it. It means that if you want to evaluate // several permutations (i.e. modifications on a UI) then you don't have to resend the model each // time, you can send it only when the master data is modified (i.e. times, locations, tasks etc) // and then just use an evaluate solve request against a particular task-sequence. // for visualisations see the R/python notebook for plots on the same example. return; }