久章智能JZLOG
转载 · Repost · WECHAT ARTICLE

Excerpts from the paper|Diversified adaptive ejection pool method based on rule switching to solve the team orientation problem with capacity constraints (Java code attached)

The Team Orienteering Problem (TOP) is a combinatorial optimization problem in the field of operations research and optimization. A team of multiple individuals is asked to visit a set of locations within a given area. Each location has a score that represents the value or benefit of visiting that location. The goal of the problem is to determine the best route for each team member, so that they can visit a part of the location respectively, and the most optimal route under the given time constraint.

1. Introduction: Team Orientation Problem

The Team Orienteering Problem (TOP) is a combinatorial optimization problem in the field of operations research and optimization. A team of multiple individuals is asked to visit a set of locations within a given area. Each location has a score that represents the value or benefit of visiting that location. The problem goal is to determine the best route for each team member so that they visit a portion of the location individually, maximize the total score under a given time constraint, and return to the starting point. The complexity of the problem is high and heuristic algorithms are often used to solve it. It is widely used in practical fields such as tourism planning and resource allocation.

The Capacitated Team Orienteering Problem (CTOP) with capacity constraints was first proposed by Archetti et al. It is a variant of TOP that adds vehicle capacity constraints and path length constraints.

2. Problem description and terminology explanation

CTOP is defined on an undirected graph, where is the set of vertices and E is the given set of edges. Vertex 1 is called the warehouse, and the set of other vertices represents the set of customers. Every customer has service needs and profits. Each edge has a travel time. We assume that the distance satisfies the triangle inequality. In addition, we give the capacity Q and the maximum driving distance of m homogeneous vehicles. Each vehicle starts from the depot, serves multiple customers and obtains related profits, and returns to the depot.

The problem is to design m routes to maximize the total profit of vehicle collection; the total service demand of customers on a route must not exceed the vehicle capacity Q, and the total length of each route can be at most. Furthermore, a client's profits can be collected at most once.

Ejection Pool (EP, Ejection Pool): is a buffer and memory pool used to temporarily store released data or processes. In this article, the ejection pool is a priority queue containing unserved customers. In each iteration, we insert the highest priority customer from the ejection pool into an existing path. "Adaptive" indicates that the ejection pool may dynamically adjust its behavior based on certain conditions or rules.

Toggle-rule diversification: In order to better enable the search process to escape the local optimum, three measures are taken in this article, which are used when the quality of the solution obtained by multiple iterations has not improved.

used in this articleEjection pool framework with adaptive strategies and diverse mechanisms for switching between two priority rulesA new heuristic algorithm is proposed for CTOP.

3. Algorithm process

Overall approach:

ADEPT-RD method

The general process of Adaptive Ejection Pool with Toggle-Rule Diversification (ADEPT-RD) is as follows:

In each iteration, the highest priority customer in the ejection pool is inserted into a known route. At this point the route may violate capacity or maximum travel distance constraints, so we perform a local search to restore the feasibility of the route. If the search fails, we remove some customers from the route to make the route eligible, and at the same time penalize the priority value, i.e. reduce the priority, and insert these customers into the ejection pool. When it is found that the search process exceeds the maximum number of dvtenture (diversification tenure), it is deemed to have fallen into a local optimum. We reset the customer priority, perturb the current solution and change the ejection pool priority rules to prevent falling into a local optimum.

The pseudo code is as follows:

Explanation of pseudocode:

Explanation of subroutines

1. Insert function Insert(S,i)

In each iteration of ADEPT-RD, the customer i with the highest priority is selected from the EP and inserted into the current solution S. Add i to a randomly chosen path at a randomly chosen position in S. If the route is feasible skip this process, if not we need to try to restore feasibility using local search.

We define a cost function related to S to determine whether the path is feasible

The first part of the cost function measures the violation of the capacity constraint, and the second part measures the violation of the travel distance constraint. Informally, the cost of a solution is the weighted sum of capacity and travel distance violations for all routes in the solution.

Note that the solution to S is infeasible. In this case, we use a simple local search to try to restore feasibility using three neighborhood operators: relocate operator, crossover operator, and 3-opt operator; these are commonly used operators in vehicle routing problems (VRP).

For customer i in route r, let i represent the customer immediately following i in route r, and i + represent the customer immediately following i in route r. We use notation to indicate that customer i precedes customer j in the route.

relocate operator

Delete the customer and insert customer i immediately after customer. This is achieved by removing arc segments and adding arc segments, as shown in Fig.1

crossover operator

Four customers located on two routes exchange paths (i+,...,j) and (k+,...,l). Delete arc segments and add arc segments to achieve this. As shown below

3-opt operator

This operator includes two operations. For three customers, delete the arc segments first. We can construct a new path S' in two ways:

The feasibility of the solutions obtained by the three operators can be verified in O (1) time. Let, and represent the neighborhoods of the relocate, 3-opt, and crossover operators respectively, that is, the set of all solutions that can be generated from S with a single call to the corresponding operator.

After preliminary experiments, we found that the value of when agrees well with our test data.

2.MakeFeasible function

The Insert process attempts to add a customer to the solution S to generate a feasible solution. If a feasible solution cannot be generated, the subroutine MakeFeasible is used to make the solution feasible.

The MakeFeasible procedure removes a set of customers from each path r of the solution S, making the path r feasible and minimizing it (thereby maximizing the valuation of the remaining customers). At the same time, the valuation of the removed customer is reduced and the EP is returned.

If the customer's profit density is smaller (more capacity is needed to obtain the same profit), or the customer is often rejected (it is difficult to coexist with other customers to form a viable route), the customer's valuation is lower. Therefore, our selection criteria favor reserving capacity for customers that are highly profit-intensive and easy to handle.

Remove customer's selection

For example, let (1, 2, 3, 4, 5) be the customers in the infeasible path r, sorted in increasing order of valuation. Initialize priority queue PQ = ( { 1 } , { 2 } , { 3 } , { 4 } , { 5 }). In the first iteration, we remove the first element (i.e. { 1 }) and check if the removal of node 1 causes r to become feasible. If not, we add {1, 2}, {1, 3}, {1, 4} and {1, 5} to the PQ and continue. If the priority queue PQ is implemented as a heap, each iteration requires O (log n) times, where n is the number of customers in the route.

3. Diversification process

A successful local search process requires a way to escape the local optimum. In the ADEPT-RD method, when we find that the process is likely to fall into a local optimum, we use three measures to spread out the search neighborhood.

4.PostOpt function (post-optimization function)

In the final stage, the optimal solution S* is further improved through the Post-optimization process. For each customer i in EP, the PostOpt process considers each path r in the solution in turn. Find every customer subset in r whose total profit is less than. The subsets form a set, which are arranged in order of increasing total profit. After deleting the customer, check whether i can be inserted into r.

Parameter settings

dvTenure (diversification tenure) is set to, where n is the number of customers, m is the number of vehicles, Q is the vehicle capacity, and is the maximum driving distance of the vehicle.

The number of iterations maxIter is set to 10dvTenure.

4. Algorithm advantages

The algorithm experimental results show that in the step of reducing the valuation of ejected customers in the makefeasible function, the solution obtained under the adaptive mechanism () is better than the solution obtained by disabling the adaptive mechanism (). The solution obtained using the two sorting rules of RA and RB is better than the solution obtained by only one priority rule. The solution obtained by the perturbation process is better than the solution without perturbation process.

This article also compares ADEPT-RD with the branch pricing algorithm (an exact algorithm), VNS (variable domain search algorithm), TSF (tabu search algorithm that only considers feasible solutions), and TSA (tabu search algorithm that considers admissible solutions). Results ADEPT-RD improved the results of VNS by 0.095%, the results of TSF by 0.215%, and the results of TSA by 0.284%. The time spent is much better than the other three algorithms (exact algorithm). (see table1)

At the same time, the number of optimal solutions found by the four heuristic methods was counted. ADEPT-RD obtained the optimal solution in 120 test instances, which is far better than the other three heuristic algorithms. (see table2)

5. Code sharing

This article has a large amount of code. For specific reference papers, calculation examples and code downloads, please go to the message area.

When running the code, you need to set the command line parameter array args in the main method in advance and enter the parameters in the IDE, such as Data/p03.cri result/p03-2-100-100.cri 2 100.0 100.0 1000 30.0 3123 or enter the parameters on the command line (p03-2-100-100.cri means file name - number of vehicles - vehicle capacity - maximum driving distance)

public static void main(String[] args) throws IOException{
// TODO Auto-generated method stub
//para the parameters
if(args.length!=8){
System.out.println("input error!");
System.exit(0);
}//Determine whether the args[] parameter is configured correctly
// Parse command line parameters
String input_file=args[0];//Input file path
String output_file=args[1];//Output file path
int m=Integer.parseInt(args[2]);//Number of vehicles
double Q=Double.parseDouble(args[3]);//Maximum capacity of each vehicle
double Tmax=Double.parseDouble(args[4]);//The longest distance traveled
int iterations=Integer.parseInt(args[5]);//Number of iterations
double time=Double.parseDouble(args[6])*1000000000;// parameter time, unit converted to nanoseconds
int seed=Integer.parseInt(args[7]);//Random seed
...
...
}

Core code AlgFinalEx class analysis The three internal classes Com, Com2 and Com3 are used for comparison and sorting in the priority queue. The definitions of these classes are used to support some strategies in the algorithm. The Struct class contains the path (Route) and the list of paths to be inserted. The EI method and EI2 method correspond to RA and RB rules respectively. RB uses queue to implement the FIFO principle, which will not be explained in detail here.

public class AlgFinalEx {
...
...
public Solution squeeze(Solution solution, int id) throws IOException{
//Randomly select a path and location to insert a customer, and perform a local search to optimize the solution

Random random=new Random(solution.GetData().seed);
int rid=random.nextInt(solution.GetSize());
Route route=solution.GetRoute(rid);
int place=random.nextInt(route.GetSize()-1)+1;
route.Insert(place, id);
route.Update();
solution.Update();

LocalSearch ls=new LocalSearch(solution);
solution=ls.Search(solution);

return (Solution)solution.clone();
}

public void flash(Solution solution, ArrayList<Com2> pool){
//Let some users in the path be randomly replaced to introduce diversity
value[0]=0;
for(int i=1;i<=data.N;i++)
value[i]=data.p[i]/Math.pow(data.q[i],1);
value[data.N+1]=0;

Purturb purturb=new Purturb();
purturb.Dversify(solution, data.purturb_time);
}
public boolean direct_insert(Solution solution, int id){
//Try to insert the customer directly into the existing path, if possible insert it.
//scan for a feasible place to insert the customer
for(int i=0;i<solution.GetSize();i++){
Route route=solution.GetRoute(i);
//check the capacity constraint
if(route.GetLoad()+data.q[id]>data.Q)
continue;
//find the minimum cost insert place
double min_cost=Double.MAX_VALUE;
int insert_place=0;
for(int j=0;j<route.GetSize()-1;j++){
int pre=route.GetCustomer(j);
int next=route.GetCustomer(j+1);
double cost=route.GetLength()+data.t[pre][id]+
data.t[id][next]-data.t[pre][next];
if(cost<min_cost){
min_cost=cost;
insert_place=j;
}
}
//check Tmax constraint
if(min_cost<data.Tmax){
route.Insert(insert_place+1, id);
route.Update();
solution.SetRoute(i, route);
solution.Update();
return true;
}
}
return false;
}
...
...
public Solution intensify(Solution solution){
// Mark the customers who are already in the solution, iset is used to store the customer index of the removed customer subset
int[] check=new int[data.N+1];
for(int i=0;i<solution.GetSize();i++){
Route route=solution.GetRoute(i);
for(int j=1;j<route.GetSize()-1;j++)
check[route.GetCustomer(j)]=1;
}
//Priority queue is used to store customers who are not in the solution
PriorityQueue<Com3> qpool=new PriorityQueue<Com3>();
// Initialize the queue and add customers who are not in the solution to the queue
for(int i=1;i<=data.N;i++){
if(check[i]==1)
continue;
else if(direct_insert(solution,i)==false)
qpool.add((Com3) new Com3(i,data.p[i]));
}

// Try all cases of inserting customers through greedy strategy
while(qpool.isEmpty()==false){
int id=qpool.poll().id;
for(int i=0;i<solution.GetSize();i++){
Route route=solution.GetRoute(i);
PriorityQueue<Com> queue=new PriorityQueue<Com>();
// Initialize the queue and add the position where the customer may insert into the queue.
for(int j=1;j<route.GetSize()-1;j++){
if(data.p[route.GetCustomer(j)]>=data.p[id])
continue;
ArrayList<Integer> temp=new ArrayList<Integer>();
temp.add(j);
queue.add((Com) new Com(temp,data.p[route.GetCustomer(j)]));
}
while(queue.isEmpty()==false){
Com com=queue.poll();
ArrayList<Integer> iset=com.set;

Collections.sort(iset);
Route troute=(Route) route.clone();
//Delete the customer to make room for insertion
for(int j=iset.size()-1;j>=0;j--)
troute.Remove(iset.get(j));
troute.Update();
// Check capacity constraints
if(troute.GetLoad()+data.q[id]>data.Q){
for(int j=iset.get(iset.size()-1);j<route.GetSize()-1;j++){
if(iset.contains(j)==false){
// Check the profit gain caused by replacing this customer
if(com.profit+data.p[route.GetCustomer(j)]>=data.p[id])
continue;
ArrayList<Integer> copy=(ArrayList<Integer>) iset.clone();
copy.add(j);
queue.add((Com)new Com(copy,com.profit+data.p[route.GetCustomer(j)]));
}
}
continue;
}
// Check time window constraints
// Find the insertion position
double min_cost=Double.MAX_VALUE;
int insert_place=-1;
for(int j=0;j<troute.GetSize()-1;j++)
{
int now=troute.GetCustomer(j);
int next=troute.GetCustomer(j+1);
double length=troute.GetLength()+data.t[now][id]+data.t[id][next]-data.t[now][next];
if(length<min_cost){
min_cost=length;
insert_place=j;
}
}
if(min_cost>=data.Tmax){
for(int j=iset.get(iset.size()-1);j<route.GetSize()-1;j++){
if(iset.contains(j)==false){
if(com.profit+data.p[route.GetCustomer(j)]>=data.p[id])
continue;
ArrayList<Integer> copy=(ArrayList<Integer>) iset.clone();
copy.add(j);
queue.add((Com)new Com(copy,com.profit+data.p[route.GetCustomer(j)]));
}
}
continue;
}
//Change iset to include pop-up customers from other routes
ArrayList<Integer> ejection=new ArrayList<Integer>();
while(qpool.isEmpty()==false)
ejection.add(qpool.poll().id);
for(int j=0;j<iset.size();j++)
ejection.add(route.GetCustomer(iset.get(j)));
//Insert customer at calculated position
troute.Insert(insert_place+1, id);
troute.Update();
// Create a new solution to update route configuration
Solution new_solution=(Solution) solution.clone();
new_solution.SetRoute(i, troute);
new_solution.Update();
return new_solution;
}
}
}
return null;
}
public Solution search(Solution solution, ArrayList<Integer> iset) throws IOException{
GetTime gettime=new GetTime();
Solution best_solution=(Solution) solution.clone();//Create a backup of the best solution
//ArrayList<Solution> solution_pool=new ArrayList<Solution>();
ArrayList<Com2> pool=new ArrayList<Com2>();
Data data=solution.GetData();
is_reserve = false; // The initialization flag is false to retain the solution.
int time = 0; // Initialize the number of iterations
int u_time = 0; // Initialize the number of times to perform local searches
int r_time = 0; // Initialize the number of times to perform the solution-preserving operation
double thresh_hold = 0; // Initialization threshold
//intialize the pool
for(int i=0;i<iset.size();i++){
//check the feasibility
int id=iset.get(i);//Get the current customer id
if(data.q[id]>data.Q||data.t[0][id]+data.t[id][0]>data.Tmax)
continue;
pool.add((Com2)new Com2(id));
}
Collections.sort(pool);//Sort objects in the pool
int indicator=0;
int last_improve=0;
data.noimprove_iterations=0; // Initialize the number of consecutive iterations without improvement
while(pool.isEmpty()==false&&++time<data.max_iteration){

double t=gettime.getCpuTime();
if(t-data.start_time>data.running_time)
break;//If the running time exceeds the limit, end the search
//Output output=new Output();
int id=pool.get(0).id;//Get the first customer ID in the pool
pool.remove(0);//Remove the first customer in the pool

solution=squeeze(solution,id);

if(solution.isFeasible()==true){
if(solution.GetProfit()>best_solution.GetProfit()){
best_solution=(Solution) solution.clone();
System.out.println(best_solution.GetProfit());
data.best_solution_time=gettime.getCpuTime();
data.optimal_iteration=time;
if(time-last_improve>data.noimprove_iterations)
data.noimprove_iterations=time-last_improve;
last_improve=time;
if(data.A1==true)
thresh_hold=best_solution.GetProfit();
}
continue;
}
// If the solution is not feasible, try insertion and optimization through the EI2 method
for(int i=0;i<solution.GetSize();i++){
Route route=solution.GetRoute(i);
if(route.isFeasible()==true)
continue;
Struct result=EI2(route);
solution.SetRoute(i, result.route);//Update path
for(int j=0;j<result.set.size();j++){
int tid=result.set.get(j);
value[tid]/=data.adapter;
pool.add(new Com2(tid));//Put the customers removed from the queue into EP
}
}

solution.Update();

if(solution.GetProfit()>thresh_hold){
u_time=0;
r_time=0;
thresh_hold=solution.GetProfit();
}
else{
u_time++;
r_time++;
}

//update the best solution
if(solution.GetProfit()>best_solution.GetProfit()){
best_solution=(Solution) solution.clone();
System.out.println(best_solution.GetProfit());
data.best_solution_time=gettime.getCpuTime();
data.optimal_iteration=time;
if(time-last_improve>data.noimprove_iterations)
data.noimprove_iterations=time-last_improve;
last_improve=time;
}

//update the capacity_reserve
capacity_reserve=0;
for(int i=0;i<solution.GetSize();i++){
Route route=solution.GetRoute(i);
if(capacity_reserve<data.Q-route.GetLoad())
capacity_reserve=data.Q-route.GetLoad();
}

if(u_time==data.flash_tenure){
u_time=0;
flash(solution,pool);//Perform diversified operations
}
if(r_time==data.reserve_tenure){
r_time=0;

indicator++;
if(indicator%2==0){
is_reserve=false;
capacity_reserve=0;
}
else if(indicator%2==1){
is_reserve=true;
}
}
if(data.RB==true&&indicator%2==1){
capacity_reserve=0;
}
Collections.sort(pool);
}
best_solution.Update();

if(best_solution.GetProfit()<data.optimal_value){
//for(int i=0;i<solution_pool.size();i++){
//Solution new_solution=intensify(solution_pool.get(i));
Solution new_solution=intensify(best_solution);
// If the profit of the final solution is less than the value of the optimal solution, try to intensify the optimization through the intensify method, corresponding to the postopt method
if(new_solution!=null&&new_solution.GetProfit()>best_solution.GetProfit()){
best_solution=(Solution) new_solution.clone();
System.out.println("intisify: "+best_solution.GetProfit());
}
//}
}
return best_solution;
}
...
}

To download the code related to this article, please go to the message area

Reference papers:

Zhixing Luo, Brenda Cheang, Andrew Lim, Wenbin Zhu,An adaptive ejection pool with toggle-rule diversification approach for the capacitated team orienteering problem,European Journal of Operational Research,Volume 229, Issue 3,2013,Pages 673-682.ISSN 0377-2217.

Machine-translated from the Chinese original 论文拾萃|基于规则切换多样化的自适应弹射池方法解决带容量约束的团队定向问题(附Java代码) (published 2023-09-18). WeChat original ↗