容量制約付き配送計画問題
メモ
インストールが必要: この機能には、MATLAB Support Package for Quantum Computing が必要です。
容量制約付き配送計画は、ナップサック問題と巡回セールスマン問題を組み合わせた問題です。この問題では、車両 (または複数の車両) が地理的に分散した顧客群を訪問します。車両には容量制約があり、その容量とは車両がそれぞれの顧客に配送できる数量を指します。この問題には中央のデポがあり、車両は顧客群への訪問、すなわちルートの完了後にデポへ戻らなければなりません。この問題の目的は、顧客群を訪問するルートの総距離をコストとし、そのコストを最小化しながら顧客を訪問することです。
次の図は、単一の地点であるデポから出発する 4 つのルートを示しています。これらのルートは最適解ではありません。少なくともノード 2 とノード 3 は逆の順序で訪問すべきだからです。ノード 2 とノード 3 を含むルートには自己交差があり、このような交差は最適な巡回経路では発生しません。

容量制約付き配送計画問題を解くには、Feld および共著者が示す手順に従います[1]。Feld は複数の解法を示していますが、この例ではそのうちの 1 つのみを使用します。
1 回のルートで車両が訪問する顧客群を表すクラスターを作成します。このステップはナップサック問題です。
各クラスターに対して巡回セールスマン問題を解きます。
クラスター作成問題は古典的アルゴリズムを使用して解きます。巡回セールスマン問題は QUBO 問題として定式化して解きます。QUBO 問題は、内部でタブー探索アルゴリズムを使用する solve 関数を使用して解きます。
問題データと変数
24 人の顧客についてランダムな整数地点を作成し、各地点の需要量を 100 ~ 2500 の範囲で 100 刻みのランダムな整数値とします。追加の 1 地点であるデポは、座標 [0,0] に配置します。
rng(1) % For reproducibility numCustomers = 24; % Depot at [0 0] makes 25 locations depot = [0 0]; % Depot at the origin loc = [depot; randi([-50,50],numCustomers,2)]; % Integers from -50 to 50, 24-by-2 demand = 100*randi([1,25],numCustomers,1); capacity = 6000;
地点と需要量をプロットします。
% Plot the locations with demands overlaid figure; scatter(loc(:,1),loc(:,2),'filled','SizeData',25); hold on text(loc(:,1),loc(:,2),["Depot"; num2str(demand)]); title("Customer Locations and Demands");

座標ベクトルから顧客間の距離の行列を作成します。ピタゴラスの定理とユークリッド距離を使用します。
numLocations = numCustomers + 1; [X,Y] = meshgrid(1:numLocations); dist = hypot(loc(X(:),1)- loc(Y(:),1),loc(X(:),2) - loc(Y(:),2)); dist = reshape(dist,numLocations,numLocations);
問題データからサイト 1 のデポを除外して、この例の残りで使用する変数を設定します。
customerCoords = loc(2:end,:); costMatrix = dist; vehicleCapacity = capacity;
クラスターの作成
Feld [1]では、クラスターを作成するための次の 2 ステップの方法が示されています。
問題の座標と容量制約に基づいて初期クラスターを作成します。
初期クラスターを改善して、より短い巡回セールスマン ルートを得ます。
この例では、次の反復的な方法を使用して初期クラスターを作成します。
デポから最も離れた顧客から開始します。
クラスター内の顧客の現在の平均位置に最も近い顧客を選びながら、顧客を 1 人ずつ反復的に追加します。つまり、クラスターに追加する 2 人目の顧客は 1 人目に最も近い顧客とし、3 人目は最初の 2 人の平均位置に最も近い顧客とし、その後も同様に追加します。
次の顧客を追加すると容量制約を超える場合は、その時点でクラスターへの追加を停止します。この時点で最初のステップに戻り、新しいクラスターの作成を開始します。
クラスターは、customers、center、および demand のフィールドをもつ構造体として作成します。addToCluster 補助関数を使用して顧客をクラスターに追加します。
clusters = []; % CLUSTER GENERATION % Select the first node of the first cluster [~,idx] = max(costMatrix(1,:)); currentCluster.Customers = idx; currentCluster.Center = customerCoords(idx,:); currentCluster.Demand = demand(idx); unclustered.Coords = customerCoords; unclustered.Customers = 1:numCustomers; unclustered.Coords(idx,:) = []; unclustered.Customers(idx) = []; % While not all the nodes have beed added while ~isempty(unclustered.Customers) % Find the next closest node to the cluster distance = sqrt(sum((currentCluster.Center - unclustered.Coords).^2,2)); [~,nextIdx] = min(distance); next = unclustered.Customers(nextIdx); % Remove the node from the unclustered list unclustered.Coords(nextIdx,:) = []; unclustered.Customers(nextIdx) = []; % If the customer can be added without violating capacity constraints, % add the customer to the current cluster; otherwise, start a new cluster if currentCluster.Demand + demand(next) <= vehicleCapacity currentCluster = addToCluster(currentCluster,next,customerCoords,demand); else clusters = [clusters, currentCluster]; currentCluster.Customers = next; currentCluster.Center = customerCoords(next,:); currentCluster.Demand = demand(next); end end clusters = [clusters, currentCluster];
初期クラスターを作成した後、それらを改善できます。そのためには、新しいクラスターの容量制約を超えない範囲で、顧客を現在のクラスターよりも新しいクラスターの平均位置に近づけられる場合、その顧客を別のクラスターに再割り当てします。可能なステップがなくなるまで、または改善ステップを 10 回実行するまで、クラスター改善ステップの実行を続けます。
クラスターの改善には addToCluster 補助関数と removeFromCluster 補助関数を使用します。
% CLUSTER IMPROVEMENT iterations = 0; % For each cluster while iterations < 10 for i = 1:numel(clusters) % And each customer within the cluster for customer = clusters(i).Customers % Calculate the customer's distance from the center d_i = sqrt(sum((clusters(i).Center - customerCoords(customer,:)).^2)); % For each alternative cluster for j = [1:i-1, i+1:numel(clusters)] % Calculate the customer's distance to the center of the % alternative cluster d_j = sqrt(sum((clusters(j).Center - customerCoords(customer,:)).^2)); % Move the customer to the alternative cluster if it is closer and % capacity constraints are met if d_j < d_i && clusters(j).Demand + demand(customer) < vehicleCapacity % Remove the customer from the original cluster % and add the customer to the new cluster clusters(i) = removeFromCluster(clusters(i), ... customer, customerCoords, demand); clusters(j) = addToCluster(clusters(j), ... customer, customerCoords, demand); break end end end end iterations = iterations + 1; end
デポをサイト 1、最初の顧客をサイト 2 とするように、顧客ラベルを元の問題に合わせて調整します。
nRoutes = numel(clusters); for i = 1:nRoutes clusters(i).Customers = clusters(i).Customers + 1; end
巡回セールスマン問題の作成と求解
各クラスターは、デポから出発してデポへ戻る 1 つの車両ルートで訪問する顧客群を表します。各ルートについて、車両は可能な限り短い距離を移動する必要があります。この問題は、各クラスターに対する巡回セールスマン問題 (TSP) です。
各クラスターについて、そのクラスター内の顧客の距離行列を作成します。複数の TSP とその解を構造体にまとめます。各構造体には、その TSP に対応する顧客、顧客間の距離、および最短距離のルートである TSP の解が含まれます。
QUBO 問題へ変換して解くための TSP を作成します。
TSPsolutions = cell(nRoutes,1);
Routes = cell(nRoutes,1);
customerCoords = loc; % Return depot to list各顧客クラスターについてペアワイズ距離を計算し、対応する TSP を定式化します。convertTSPtoQUBO 補助関数を使用して、各 TSP を QUBO 問題へ変換します。solvemyTSP 補助関数を使用して TSP を解きます。
for rt = 1:nRoutes % Compute pairwise distance cluster = clusters(rt); coords = [depot; customerCoords(cluster.Customers,:)]; M = height(coords); [X,Y] = meshgrid(1:M); dist = hypot(coords(X(:),1)- coords(Y(:),1),coords(X(:),2) - coords(Y(:),2)); % Collect TSP data tsp.CostMatrix = reshape(dist,M,M); tsp.Customers = cluster.Customers; % Solve this TSP TSPsolutions{rt} = solvemyTSP(tsp); Routes{rt} = TSPsolutions{rt}.Route; end
plotClusters 補助関数を使用して得られたルートをプロットします。
plotClusters(clusters,loc,Routes)

各ルートで必要となる容量を表示します。
d = arrayfun(@(x)x.Demand,clusters)
d =
5000 5800 5400 4600 5700各ルートの需要量は、容量上限である 6000 未満です。
補助関数
次のコードは addToCluster 補助関数を作成します。この補助関数は updateCluster 補助関数を使用していることに注意してください。
function cluster = addToCluster(cluster,next,customerCoordinates,demandVector) cluster.Customers = [cluster.Customers, next]; cluster = updateCluster(cluster,next,customerCoordinates,demandVector,1); end
次のコードは removeFromCluster 補助関数を作成します。この補助関数は updateCluster 補助関数を使用していることに注意してください。
function cluster = removeFromCluster(cluster,next,customerCoordinates,demandVector) cluster.Customers(cluster.Customers == next) = []; cluster = updateCluster(cluster,next,customerCoordinates,demandVector,-1); end
次のコードは updateCluster 補助関数を作成します。
function cluster = updateCluster(cluster,next,customerCoordinates,demandVector,s) % Update the cluster as well as the lists of available coordinates and % customers currentN = numel(cluster.Customers); previousN = currentN - s; % Update center newX = (cluster.Center(1)*previousN + s*customerCoordinates(next,1))/(currentN); newY = (cluster.Center(2)*previousN + s*customerCoordinates(next,2))/(currentN); cluster.Center = [newX,newY]; % Update demand cluster.Demand = cluster.Demand + s*demandVector(next); end
次のコードは plotClusters 補助関数を作成します。
function plotClusters(clusters,customerCoords,Routes) % Plot the result of the clustering algorithm in 2D space. Plot the % routes as well if they are provided. % Plot the depot f = figure; scatter(customerCoords(1,1), customerCoords(1,2),"filled"); ax = f.CurrentAxes; text(customerCoords(1,1), customerCoords(1,2),"Depot"); hold on % Plot each cluster and label customers for i = 1:numel(clusters) customer = clusters(i).Customers; scatter(customerCoords(customer,1),... customerCoords(customer,2),"filled",SizeData=25); % Locations text(customerCoords(customer,1),... customerCoords(customer,2),num2str(customer')); % Labels end % If the routes are provided, plot them if nargin > 2 % Reset color index - 1 is for the depot, so start at 2 colorIdx = 1; for k = 1:numel(Routes) route = Routes{k}; % Advance color index for the next route colorIdx = colorIdx + 1; % Append depot as the start of the route cu = [1 clusters(k).Customers]; tr = cu(route); % Route in original indices for customer = 1:numel(tr) - 1 ax.ColorOrderIndex = colorIdx; % Keep current route color plot(customerCoords(tr(customer:customer+1),1),... customerCoords(tr(customer:customer+1),2)); end ax.ColorOrderIndex = colorIdx; plot(customerCoords([tr(end),tr(1)],1),... customerCoords([tr(end),tr(1)],2)); end end drawnow hold off end
次のコードは convertTSPtoQUBO 補助関数を作成します。
function QP = convertTSPtoQUBO(dist) % QP = CONVERTTSPTOQUBO(DIST) returns a QUBO problem from the traveling salesperson % problem specified by the distance matrix DIST. DIST is an N-by-N % nonnegative matrix where DIST(i,j) is the distance between locations % i and j. % Copyright 2023 The MathWorks, Inc. N = size(dist,1); % Create constraints on routes A = eye(N); B = ones(N); Q0 = kron(A,B); Q1 = kron(B,A); % Create upper diagonal matrices of distances v = ones(N-1,1); A2 = diag(v,1); Q2 = kron(B,A2); % Q2 has a diagonal just above the main diagonal in each block C = kron(dist,B); Q2 = Q2.*C; % Q2 has an upper diagonal dist(i,j) % Create dist(j,i) in the upper-right corner of each block E = zeros(N); E(1,N) = 1; Q3 = kron(B,E); % Q3 has a 1 in the upper-right corner of each block CP = kron(dist',B); % dist' for D(j,i) Q3 = Q3.*CP; % Q3 has dist(j,i) in the upper-right corner of each block % Add the multipliers M = max(max(dist)); QN = sparse(M*(Q0 + Q1)*N^2 + Q2 + Q3); % Symmetrize QN = (QN + QN.')/2; % Include the constant and linear terms c = -4*ones(N^2,1)*M*N^2; d = 2*N*M*N^2; QP = qubo(QN,c,d); end
次のコードは solvemyTSP 補助関数を作成します。この補助関数は solveTSPwithTabu 補助関数と convertSolutionToRoute 補助関数を使用していることに注意してください。
function TSPsolution = solvemyTSP(tsp) % solvemyTSP solves the TSP by first converting it to a QUBO problem % and then using tabu search to find a solution. % Inputs: % tsp: Structure with fields % customers: the customers for the tsp % costMatrix: the cost matrix for the tsp % Outputs: % TSP_solution: Structure with fields % Route: The order of the customers in the best route found. For example, % if there are 5 customers in this tsp, the Route might be [2 3 1 5 4] % for customers [13 6 3 12 7]. % customers: The customers in the current tsp, taken directly % from the tsp input % quboFval: The fval returned by the qubo algorithm n = numel(tsp.Customers); % If the number of customers is less than 3, the solution is trivial and % there is no need to solve the QUBO. if n < 3 TSPsolution.Route = 1:n; TSPsolution.Customers = tsp.Customers; TSPsolution.QuboFval = tsp.CostMatrix(1,2) + tsp.CostMatrix(2,1); return end % Solve with tabu search TSPsol = solveTSPwithTabu(tsp.CostMatrix); % Convert the QUBO solution to a route TSPsolution.Route = convertSolutionToRoute(TSPsol); TSPsolution.QuboFval = TSPsol.BestFunctionValue; TSPsolution.Customers = tsp.Customers; end
次のコードは solveTSPwithTabu 補助関数を作成します。この補助関数は convertTSPtoQUBO 補助関数と checkTSPConstraints 補助関数を使用していることに注意してください。
function solution = solveTSPwithTabu(costMatrix) % Solves the TSP with tabu search. Convert the TSP to a QUBO % according to the penalty term and return the best feasible solution. % Convert the TSP problem to QUBO Q = convertTSPtoQUBO(costMatrix); x = solve(Q); validSolutions = checkTSPConstraints(x); % If no valid solution is found yet, try again up to 10 times if validSolutions == false it = 1; while (validSolutions == false) && it < 10 x = solve(Q); validSolutions = checkTSPConstraints(x); it = it + 1; end end if validSolutions == false solution = []; else solution = x; end end
次のコードは checkTSPConstraints 補助関数を作成します。
function validSolutions = checkTSPConstraints(x) % The solutions are encoded as [x11, x12, x13, ... x21, x23, ...]' % where xij indicates that customer i is visited in the jth position of the % route. To establish that the constraints are satisfied, verify % that each customer is visited only once and each position in the route % has only one customer assigned to it. N = size(x.BestX,1); n = sqrt(N); y = reshape(x.BestX,n,n); % Ensure each customer has only one position assigned r = all(sum(y,1) == 1); % Ensure each route position (row) on has one customer assigned s = all(sum(y,2) == 1); validSolutions = r && s; end
次のコードは convertSolutionToRoute 補助関数を作成します。
function Route = convertSolutionToRoute(sol) % The solutions are encoded as [x11, x12, x13, ... x21, x23, ...]' % where xij indicates that customer i is visited in the jth position of the % route. Decode the solution into a route. solution = sol.BestX; n = sqrt(numel(solution)); selected_indices = find(solution); order = mod(selected_indices - 1, n) + 1; [~, node_order] = sort(order); nodes = 1:n; Route = nodes(node_order); end
参照
[1] Feld, Sebastian, Christoph Roch, Thomas Gabor, Christian Seidel, Florian Neukart, Isabella Galter, Wolfgang Mauerer, and Claudia Linnhoff-Popien. A Hybrid Solution Method for the Capacitated Vehicle Routing Problem Using a Quantum Annealer. Available at https://arxiv.org/abs/1811.07403.
参考
関数
オブジェクト
qubo|tabuSearch|qaoa