/// <summary> /// Executes a solver procedure. /// </summary> /// <param name="problem"></param> /// <returns></returns> internal override MaxTimeSolution Solve(MaxTimeProblem problem) { MaxTimeCalculator calculator = new MaxTimeCalculator(problem); // get the seed customers. ICollection<int> seeds = _seed_selector.SelectSeeds( problem, _k); double[] weights = new double[seeds.Count]; // start the seed routes. List<int> selectable_customers = problem.Customers; MaxTimeSolution routes = new MaxTimeSolution( problem.Size, true); foreach (int seed in seeds) { routes.Add(seed); selectable_customers.Remove(seed); } if (!routes.IsValid()) { throw new Exception(); } // keep a list of cheapest insertions. IInsertionCosts costs = new BinaryHeapInsertionCosts(); // keep looping until all customers have been placed. while (selectable_customers.Count > 0) { // try and place into every route. CheapestInsertionResult best_result = new CheapestInsertionResult(); best_result.Increase = float.MaxValue; int best_route_idx = -1; CheapestInsertionResult best_result_above_max = new CheapestInsertionResult(); best_result_above_max.Increase = float.MaxValue; int best_route_above_max_idx = -1; for (int route_idx = 0; route_idx < routes.Count; route_idx++) { IRoute current_route = routes.Route(route_idx); // choose the next customer. CheapestInsertionResult result = CheapestInsertionHelper.CalculateBestPlacement(problem, current_route, selectable_customers, costs); if (result.Customer == result.CustomerAfter) { throw new Exception(); } // get the current weight double weight = weights[route_idx]; if (result.Increase < best_result.Increase) { if (weight + result.Increase + calculator.DeliveryTime < problem.Max.Value) { // route will still be inside bounds. best_result = result; best_route_idx = route_idx; } else { // route will become above max. if (result.Increase < best_result_above_max.Increase) { best_result_above_max = result; best_route_above_max_idx = route_idx; } } } } // do the placement if a placement is found without max violation. // else do the placement in the above max route. CheapestInsertionResult placement_result = new CheapestInsertionResult(); placement_result.Increase = double.MaxValue; int placement_result_idx = -1; if (best_route_idx >= 0) { // best placement found. placement_result = best_result; placement_result_idx = best_route_idx; } else { // best placement found but only above max. placement_result = best_result_above_max; placement_result_idx = best_route_above_max_idx; } // do the actual placement. weights[placement_result_idx] = calculator.CalculateOneRouteIncrease( weights[placement_result_idx], placement_result.Increase); selectable_customers.Remove(placement_result.Customer); //routes.Route(placement_result_idx).InsertAfterAndRemove( // placement_result.CustomerBefore, placement_result.Customer, placement_result.CustomerAfter); routes.Route(placement_result_idx).InsertAfter( placement_result.CustomerBefore, placement_result.Customer); if (!routes.IsValid()) { throw new Exception(); } } return routes; }
/// <summary> /// Tries to relocate customers using the source the same as the CrossExchange but places customers using cheapest insertion. /// </summary> /// <param name="problem"></param> /// <param name="solution"></param> /// <param name="route1_idx"></param> /// <param name="route2_idx"></param> /// <param name="max"></param> /// <returns></returns> public bool Improve(MaxTimeProblem problem, MaxTimeSolution solution, int route1_idx, int route2_idx, double max) { int max_window = 10; IRoute route1 = solution.Route(route1_idx); IRoute route2 = solution.Route(route2_idx); double total_before = problem.Time(solution.Route(route1_idx)) + problem.Time(solution.Route(route2_idx)); int route1_customers = route1.Count; int route2_customers = route2.Count; double[] route1_cumul = problem.TimeCumul(route1); double[] route2_cumul = problem.TimeCumul(route2); // build all edge weights. List<Edge> route1_edges = new List<Edge>(route1.Edges()); List<Edge> route2_edges = new List<Edge>(route2.Edges()); double[] route1_weights = new double[route1_edges.Count]; for (int idx = 0; idx < route1_edges.Count; idx++) { Edge edge = route1_edges[idx]; route1_weights[idx] = problem.WeightMatrix[edge.From][edge.To]; } double[] route2_weights = new double[route2_edges.Count]; for (int idx = 0; idx < route2_edges.Count; idx++) { Edge edge = route2_edges[idx]; route2_weights[idx] = problem.WeightMatrix[edge.From][edge.To]; } List<EdgePair> route2_pairs = new List<EdgePair>(); for (int i_idx = 0; i_idx < route2_edges.Count - 2; i_idx++) { Edge i = route2_edges[i_idx]; double i_weight = route2_weights[i_idx]; double weight_before_i = route2_cumul[i_idx]; int k_idx_max = route2_edges.Count; if (k_idx_max > i_idx + 2 + max_window) { k_idx_max = i_idx + 2 + max_window; } for (int k_idx = i_idx + 2; k_idx < k_idx_max; k_idx++) { Edge k = route2_edges[k_idx]; double k_weight = route2_weights[k_idx]; double weight_after_k = route2_cumul[route2_cumul.Length - 1] - route2_cumul[k_idx + 1]; double weight_between_route = route2_cumul[k_idx] - route2_cumul[i_idx + 1]; route2_pairs.Add(new EdgePair() { First = i, FirstWeight = i_weight, Second = k, SecondWeight = k_weight, Between = new List<int>(route2.Between(i.To, k.From)), WeightTotal = i_weight + k_weight, WeightAfter = weight_after_k, WeightBefore = weight_before_i, WeightBetween = weight_between_route, CustomersBetween = k_idx - i_idx }); } } // try to relocate all and find best pair. double route1_weight = route1_cumul[route1_cumul.Length - 1]; EdgePair best = null; CheapestInsertionResult best_result = new CheapestInsertionResult(); double best_extra = double.MaxValue; foreach (EdgePair pair2 in route2_pairs) { // calculate cheapest insertion. CheapestInsertionResult result = CheapestInsertionHelper.CalculateBestPlacement(problem, route1, pair2.First.To, pair2.Second.From); double extra_route2 = problem.WeightMatrix[pair2.First.From][pair2.Second.To]; // check if the result has a net-decrease. if (result.Increase + extra_route2 < pair2.WeightTotal - 0.01) { // there is a net decrease. // calculate the real increase. double new_weight = problem.Time(route1_weight + result.Increase + pair2.WeightBetween, route1_customers + pair2.CustomersBetween); // check the max. if (new_weight < max && new_weight < best_extra) { // the route is smaller than max. best_extra = new_weight; best_result = result; best = pair2; } } } if (best != null) { if (route2.Last == best.Second.To) { //throw new Exception(); } route2.ReplaceEdgeFrom(best.First.From, best.Second.To); int previous = best_result.CustomerBefore; foreach (int customer in best.Between) { route1.ReplaceEdgeFrom(previous, customer); previous = customer; } route1.ReplaceEdgeFrom(previous, best_result.CustomerAfter); // check validity. if (!route2.IsValid()) { throw new Exception(); } if (!route1.IsValid()) { throw new Exception(); } if (route1.Count + route2.Count != route1_customers + route2_customers) { throw new Exception(); } double total_after = problem.Time(solution.Route(route1_idx)) + problem.Time(solution.Route(route2_idx)); if (total_after >= total_before) { throw new Exception("this is not an improvement!"); } return true; } return false; }
/// <summary> /// Returns the customer that least increases the length of the given route. /// </summary> /// <param name="problem"></param> /// <param name="route"></param> /// <param name="customers"></param> /// <param name="costs"></param> /// <returns></returns> public static CheapestInsertionResult CalculateBestPlacement( IProblemWeights problem, IRoute route, ICollection<int> customers, IInsertionCosts costs) { // initialize the best placement result. CheapestInsertionResult best = new CheapestInsertionResult(); best.Increase = float.MaxValue; // loop over all customers in the route. if (route.Count > 0) { // there have to be at least two customers. IEnumerator<int> route_enumerator = route.GetEnumerator(); if (!route_enumerator.MoveNext()) { // this route is empty throw new ArgumentException("Route needs to be initialized with at least two customers!"); } int customer_before = route_enumerator.Current; int customer_after = -1; while (route_enumerator.MoveNext()) { // keep moving! customer_after = route_enumerator.Current; InsertionCost cost = costs.PopCheapest(customer_before, customer_after); bool found = false; while(!found) { // test if the costs are empty. if (cost == null) { // re-initialize the costs with all customers under consideration. foreach (int customer in customers) { costs.Add(customer_before, customer_after, customer, (float)problem.WeightMatrix[customer_before][customer] + (float)problem.WeightMatrix[customer][customer_after] - (float)problem.WeightMatrix[customer_before][customer_after]); } // pop the cheapest again! cost = costs.PopCheapest(customer_before, customer_after); } else if (customers.Contains(cost.Customer)) { // the customer is found! found = true; } else { // pop the cheapest again! cost = costs.PopCheapest(customer_before, customer_after); } } if (cost.Cost < best.Increase) { // the costs is better than the current cost! best = new CheapestInsertionResult() { Customer = cost.Customer, CustomerAfter = customer_after, CustomerBefore = customer_before, Increase = cost.Cost }; if (best.Increase == 0) { // immidiately return if smaller than epsilon. return best; } } // set the after to the before. customer_before = route_enumerator.Current; } // if the round is a round try first-last. if (route.IsRound) { // the route is a round! customer_after = route.First; InsertionCost cost = costs.PopCheapest(customer_before, customer_after); bool found = false; while (!found) { // test if the costs are empty. if (cost == null) { // re-initialize the costs with all customers under consideration. foreach (int customer in customers) { costs.Add(customer_before, customer_after, customer, (float)problem.WeightMatrix[customer_before][customer] + (float)problem.WeightMatrix[customer][customer_after] - (float)problem.WeightMatrix[customer_before][customer_after]); } // pop the cheapest again! cost = costs.PopCheapest(customer_before, customer_after); } else if (customers.Contains(cost.Customer)) { // the customer is found! found = true; } else { // pop the cheapest again! cost = costs.PopCheapest(customer_before, customer_after); } } if (cost.Cost < best.Increase) { // the costs is better than the current cost! best = new CheapestInsertionResult() { Customer = cost.Customer, CustomerAfter = customer_after, CustomerBefore = customer_before, Increase = cost.Cost }; if (best.Increase == 0) { // immidiately return if smaller than epsilon. return best; } } } } else { // route needs to be initialized. throw new ArgumentException("Route needs to be initialized with at least two customers!"); } // return result. return best; }
/// <summary> /// Searches for the best place to insert the given two customers abstracting the distance between them. /// </summary> /// <param name="problem"></param> /// <param name="route"></param> /// <param name="from"></param> /// <param name="to"></param> /// <returns></returns> public static CheapestInsertionResult CalculateBestPlacement( IProblemWeights problem, IRoute route, int from, int to) { // initialize the best placement result. CheapestInsertionResult result = new CheapestInsertionResult(); result.Customer = -1; // this property is useless here! result.CustomerAfter = -1; result.CustomerBefore = -1; result.Increase = double.MaxValue; if (!route.IsEmpty) { double new_weight = double.MaxValue; double old_weight = 0; int previous = -1; int first = -1; foreach (int current in route) { if (previous >= 0) { // only if the previous is known. // calculate the new weights. new_weight = problem.Weight(previous, from) + (problem.Weight(to, current)); // calculate the old weights. old_weight = problem.Weight(previous, current); // calculate the difference. double difference = new_weight - old_weight; if (result.Increase > difference) { result.CustomerAfter = current; result.CustomerBefore = previous; result.Increase = difference; } } else { // store the first city for later. first = current; } // go to the next loop. previous = current; } // test last-to-first if the route is a round. if (route.IsRound) { // calculate the new weights. new_weight = problem.Weight(previous, from) + (problem.Weight(to, first)); // calculate the old weights. old_weight = problem.Weight(previous, first); // calculate the difference. double difference = new_weight - old_weight; if (result.Increase > difference) { result.CustomerBefore = previous; result.CustomerAfter = first; result.Increase = difference; } } } else { // route needs to be initialized. throw new ArgumentException("Route needs to be initialized with at least one customer!"); } // return result. return result; }
/// <summary> /// Searches for the best place to insert the given customer. /// </summary> /// <param name="problem"></param> /// <param name="route"></param> /// <param name="customer"></param> /// <returns></returns> public static CheapestInsertionResult CalculateBestPlacement( IProblemWeights problem, IRoute route, int customer) { // initialize the best placement result. double[][] weights = problem.WeightMatrix; CheapestInsertionResult result = new CheapestInsertionResult(); result.Customer = customer; result.CustomerAfter = -1; result.CustomerBefore = -1; result.Increase = double.MaxValue; double difference = double.MaxValue; if (!route.IsEmpty) { double new_weight = double.MaxValue; double old_weight = 0; int previous = -1; int first = -1; foreach (int current in route) { if (previous >= 0) { // the previous customer exists. // only if the previous is known. // calculate the old weights. //old_weight = problem.Weight(previous, current); old_weight = weights[previous][current]; // calculate the new weights. //new_weight = problem.Weight(previous, customer); new_weight = weights[previous][customer]; //new_weight = new_weight + // problem.Weight(customer, current); new_weight = new_weight + weights[customer][current]; // calculate the difference. difference = new_weight - old_weight; if (result.Increase > difference) { result.CustomerAfter = current; result.CustomerBefore = previous; result.Increase = difference; // if the difference is equal to or smaller than epsilon we have search enough. if (difference == 0) { // result is the best we will be able to get. return result; } } } else { // store the first city for later. first = current; } // go to the next loop. previous = current; } // set the pervious to the last. //previous = route.Last; // test last-to-first if the route is a round. if (route.IsRound) { // calculate the new weights. //new_weight = problem.Weight(previous, customer) // + (problem.Weight(customer, first)); new_weight = weights[previous][customer] + weights[customer][first]; // calculate the old weights. //old_weight = problem.Weight(previous, first); old_weight = weights[previous][first]; // calculate the difference. difference = new_weight - old_weight; if (result.Increase > difference) { result.CustomerAfter = first; result.CustomerBefore = previous; result.Increase = difference; } } } else { // route needs to be initialized. throw new ArgumentException("Route needs to be initialized with at least one customer!"); } // return result. return result; }
/// <summary> /// Executes a solver procedure. /// </summary> /// <param name="problem"></param> /// <returns></returns> internal override MaxTimeSolution Solve(MaxTimeProblem problem) { // create the solution. MaxTimeSolution solution = new MaxTimeSolution(problem.Size, true); // keep placing customer until none are left. List<int> customers = new List<int>(problem.Customers); double max = problem.Max.Value - (problem.Max.Value * _delta_percentage); // keep a list of cheapest insertions. IInsertionCosts costs = new BinaryHeapInsertionCosts(); double percentage = _threshold_percentage; while (customers.Count > 0) { // try and distribute the remaining customers if there are only a few left. if (customers.Count < problem.Size * percentage) { bool succes = true; while (succes && customers.Count > 0) { succes = false; CheapestInsertionResult best = new CheapestInsertionResult(); best.Increase = float.MaxValue; int best_idx = -1; for (int route_idx = 0; route_idx < solution.Count; route_idx++) { IRoute route = solution.Route(route_idx); CheapestInsertionResult result = CheapestInsertionHelper.CalculateBestPlacement(problem, route, customers); if (best.Increase > result.Increase) { best = result; best_idx = route_idx; } } IRoute best_route = solution.Route(best_idx); double route_time = problem.Time(best_route); if (route_time + best.Increase < max) { // insert the customer. best_route.InsertAfter(best.CustomerBefore, best.Customer); customers.Remove(best.Customer); this.Improve(problem, solution, max, best_idx); succes = true; } } } // select a customer using some heuristic. if (customers.Count > 0) { // select a customer using some heuristic. int customer = -1; if (_use_seed) { // use a seeding heuristic. customer = this.SelectSeed(problem, problem.MaxTimeCalculator, solution, customers); } else { // just select a random customer. customer = customers[Tools.Math.Random.StaticRandomGenerator.Get().Generate(customers.Count)]; } customers.Remove(customer); // start a route r. IRoute current_route = solution.Add(customer); solution[solution.Count - 1] = 0; while (customers.Count > 0) { //OsmSharp.Tools.Output.OutputStreamHost.WriteLine("{0}/{1} placed!", // customers.Count, problem.Size); // calculate the best placement. CheapestInsertionResult result; if (_use_seed_cost) { // use the seed cost; the cost to the seed customer. result = CheapestInsertionHelper.CalculateBestPlacement(problem, current_route, customers, customer, _lambda); // calculate the 'real' increase. result.Increase = (problem.WeightMatrix[result.CustomerBefore][result.Customer] + problem.WeightMatrix[result.Customer][result.CustomerAfter]) - problem.WeightMatrix[result.CustomerBefore][result.CustomerAfter]; } else { // just use cheapest insertion. result = CheapestInsertionHelper.CalculateBestPlacement(problem, current_route, customers, costs); } // calculate the new weight. solution[solution.Count - 1] = problem.Time(solution.Route(solution.Count - 1)); double potential_weight = problem.MaxTimeCalculator.CalculateOneRouteIncrease(solution[solution.Count - 1], result.Increase); // cram as many customers into one route as possible. if (potential_weight < max) { // insert the customer, it is customers.Remove(result.Customer); current_route.InsertAfter(result.CustomerBefore, result.Customer); // free some memory in the costs list. costs.Remove(result.CustomerBefore, result.CustomerAfter); // update the cost of the route. solution[solution.Count - 1] = potential_weight; // improve if needed. if (((problem.Size - customers.Count) % _k) == 0) { // an improvement is descided. // apply the inter-route improvements. int count_before = solution.Route(solution.Count - 1).Count; solution[solution.Count - 1] = this.ImproveIntraRoute(problem, current_route, solution[solution.Count - 1]); if (!solution.IsValid()) { throw new Exception(); } int count_after = solution.Route(solution.Count - 1).Count; // also to the inter-improvements. current_route = this.Improve(problem, solution, max, solution.Count - 1); } } else {// ok we are done! this.Improve(problem, solution, max, solution.Count - 1); // break the route. break; } } //else //{// ok we are done! // solution[solution.Count - 1] = this.ImproveIntraRoute(problem, // current_route, solution[solution.Count - 1]); // if (!solution.IsValid()) // { // throw new Exception(); // } // int count_after = solution.Route(solution.Count - 1).Count; // this.Improve(problem, solution, max, solution.Count - 1); // // break the route. // break; //} } } // remove empty routes. for (int route_idx = solution.Count - 1; route_idx >= 0; route_idx--) { if (solution.Route(route_idx).IsEmpty) { solution.Remove(route_idx); } } return solution; }