# Taxi Matching Problem¶

Let's solve the taxi matching problem as an example of a problem that uses both an objective function and constraints.

The taxi matching problem is the problem of minimizing the cost of dispatching a taxi given multiple customers and multiple taxi locations respectively.

The cost of dispatching a taxi can be defined in various ways, but for simplicity, we will assume that it is the total distance between the taxi and the customer. By matching taxis and customers, we can decide where to dispatch the taxi so as to minimize the total distance between each taxi and the destination customer.

## Formulation of the Problem¶

First of all, the assumption of the problem here is that there are $N$ customers and the same number of $N$ taxis. Suppose that we are given the coordinates $(c_{i,x}, c_{i,y})$ of the customers and $(t_{j,x}, t_{j,y})$ of the taxis with indices $i, j = 0, 1, \cdots, N -1$. From these coordinates, let the distance between customer $i$ and taxi $j$ be the following:

$$ d_{ij} = \sqrt{(c_{i,x} - t_{j,x})^2 + (c_{i,y} - t_{j,y})^2} $$

### Decision Variable¶

The relation between customer $i$ and taxi $j$ can be divided into the following two patterns:

- Customer $i$ is assigned a taxi $j$
- Taxi $j$ is not assigned to customer $i$

We use the binary variable $q_{ij}$ to represent these two states.

- When taxi $j$ is assigned to customer $i$, $q_{ij} = 1$
- When no taxi $j$ is assigned to customer $i$, $q_{ij} = 0$

Customer \ Taxi | $0$ | $1$ | ... | $N-1$ |
---|---|---|---|---|

$0$ | $q_{0,0}$ | $q_{0,1}$ | ... | $q_{0,N-1}$ |

$1$ | $q_{1,0}$ | $q_{1,1}$ | ... | $q_{1,N-1}$ |

$\vdots$ | $\vdots$ | $\vdots$ | ... | $\vdots$ |

$N -1$ | $q_{N-1,0}$ | $q_{N-1,1}$ | ... | $q_{N-1,N-1}$ |

### Objective Function¶

Using the above binary variables, the objective function, which is the total distance between the
matched
customer and the taxi, is given by following:

Since the variable $q_{ij}$ means that customer $i$ and taxi $j$ are matched when $1$, we only add up
the
distances that result in $q_{ij} = 1$.

$$ \sum_{i, j=0}^{N-1}d_{ij}q_{ij} $$

### Constraint¶

The next step is to define the constraints.

First, since we always assign one taxi to one customer, we need the following constraint for customer $i$.

$$ \sum_{j=0}^{N -1}q_{ij} = 1 $$

In addition, since one taxi is always assigned to one customer, we also need the following constraint for taxi $j$:

$$ \sum_{i=0}^{N -1}q_{ij} = 1 $$

## Implementing the Problem¶

Since we need the coordinates of the customers and the taxies as input data, we will create a function that randomly generates the coordinates of the customers and the taxies and calculates the distances for all combinations of customers and taxies.

```
import numpy as np
# Randomly generate the coordinates of the customers and the taxies, and calculate the distances between the customers and the taxies
def gen_random_locations(num_customers: int, num_taxies: int):
rng = np.random.default_rng()
# Customer coordinates
loc_customers = rng.random(size=(num_customers, 2))
# Taxi coordinates
loc_taxies = rng.random(size=(num_taxies, 2))
# Construct a distance matrix between customer i and taxi j, distances[i, j]
distances = (
(loc_customers[:, np.newaxis, 0] - loc_taxies[np.newaxis, :, 0]) ** 2
+ (loc_customers[:, np.newaxis, 1] - loc_taxies[np.newaxis, :, 1]) ** 2
) ** 0.5
return loc_customers, loc_taxies, distances
```

For visualization purposes, we also create a function that, given the coordinates of a customer and a taxi, plot those coordinates.

```
%matplotlib inline
import matplotlib.pyplot as plt
# Visualize the location of customers and taxis
def show_plot(loc_customers: np.ndarray, loc_taxies: np.ndarray):
markersize = 100
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 10))
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.scatter(
loc_customers[:, 0],
loc_customers[:, 1],
label="Customers",
marker="o",
color="red",
s=markersize,
)
ax.scatter(
loc_taxies[:, 0],
loc_taxies[:, 1],
label="Taxies",
marker="^",
color="blue",
s=markersize,
)
ax.legend(loc="upper right")
plt.show()
```

Determine `N`

corresponding to the number of customers and the number of taxis, and
generate
their coordinates and distances with the `gen_random_locations`

function we defined
earlier.
Plot the generated results to visualize the locations of customers and taxis.

```
N = 5
loc_customers, loc_taxies, distances = gen_random_locations(
num_customers=N, num_taxies=N
)
show_plot(loc_customers, loc_taxies)
```

### Building a Binary Polynomial Model¶

Next, we define the QUBO variables that we will need. Since we want to have $N$ taxis for each $N$ customer, we define the QUBO variable as a two-dimensional array of $N\times N$ as follows:

```
from amplify import VariableGenerator
# Generate a binary decision variable array
gen = VariableGenerator()
q = gen.array("Binary", shape=(N, N))
```

Using these QUBO variables, the objective function is obtained as follows:

```
from amplify import sum
cost = (distances * q).sum()
```

The next step is to define the constraints.

The two constraints described at the beginning are represented as follows using the
`one_hot`

function, and they are added up to construct a constraint object.

```
from amplify import one_hot
customer_has_one_taxi = one_hot(q, axis=1)
taxi_has_one_customer = one_hot(q, axis=0)
constraints = customer_has_one_taxi + taxi_has_one_customer
```

By adding the objective function and constraints, the final binary polynomial model can be obtained as follows.

Here, the weight of the constraints relative to the objective function is important. Just to conclude, it is enough to set the maximum value of $d_{ij}$, and we will not go into the discussion of how strong it should be.

```
# Set the weight
constraints *= np.amax(distances) # type: ignore
# Combine objective function and constraints
model = cost + constraints
```

### Running the Ising Machine¶

Set the client of the Ising machine to `FixstarsClient`

, and also create a solver to solve
the
problem as follows:

```
from amplify import FixstarsClient, solve
from datetime import timedelta
# Set the client
client = FixstarsClient()
client.parameters.timeout = timedelta(milliseconds=1000) # Timeout is 1 second
# client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # If you are using it in a local environment, please enter the access token for Amplify AE
# Solve the problem
result = solve(model, client)
```

The obtained solution can be checked as follows.

The final solution can be obtained by using the `evaluate`

function and assigning the
solution
to the variable array defined in the beginning.

```
# If result is empty, the constraint condition was not satisfied and the solution cannot be found.
if len(result) == 0:
raise RuntimeError("Given constraint conditions are not satisfied")
# The value of objective function at the optimal solution (the sum of distances between the customers and taxies)
print(f"objective = {result.best.objective}")
# The values of decision variables at the optimal solution
solution = q.evaluate(result.best.values)
print(f"solution = {solution}")
```

The array of decision variables indicates that if there is $1$ in the $j$th column of the $i$th row, the taxi $j$ will be assigned to customer $i$. Thus, we can get the information about which taxi to match to which customer as follows:

```
customers = np.arange(N, dtype=int)
taxies = (solution @ np.arange(N)).astype(int)
matches = list(zip(customers, taxies))
matches
```

Finally, we visualize the obtained data of matching customers and taxis.

```
def plot_matching(loc_customers, loc_taxies, matches):
markersize = 100
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(10, 10))
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.scatter(
loc_customers[:, 0],
loc_customers[:, 1],
label="Customers",
marker="o",
color="red",
s=markersize,
)
ax.scatter(
loc_taxies[:, 0],
loc_taxies[:, 1],
label="Taxies",
marker="^",
color="blue",
s=markersize,
)
for i, j in matches:
xc, yc = loc_customers[i]
xt, yt = loc_taxies[j]
plt.plot([xc, xt], [yc, yt], color="green", linestyle="--")
plt.legend(loc="upper right")
plt.show()
plot_matching(loc_customers=loc_customers, loc_taxies=loc_taxies, matches=matches)
```

## The Case Where the Number of Customers is Less Than the Number of Taxis¶

In this section, we consider the taxi matching problem when the number of customers is smaller than the number of taxis. In this case, we need to formulate constraints that take into account both the case where the number of customers assigned to each taxi is zero and the case where the number of customers assigned to each taxi is one. Such constraints can be formulated using inequality constraints.

Given $N_c$ customers and $N_t$ taxis ($N_c < N_t$) and their coordinates, let $d_{ij}$ be the distance between customer $i$ and taxi $j$ as before.

### Objective Function¶

The objective function is the same as before, but we consider that $N_c$ and $N_t$ are different values.

$$ \sum_{i=0}^{N_c-1}\sum_{j=0}^{N_t - 1}d_{ij}q_{ij} $$

### Constraint¶

Since there are more taxis than customers, every customer can be matched with one taxi. Therefore, for customer $i$, the following holds. $$ \sum_{j=0}^{N_{\rm t}-1}q_{ij} = 1 $$

On the other hand, for a taxi, there may be no customers at all. Therefore, we impose a constraint by inequality, taking into account both the cases where the number of customers is zero and the case where the number of customers is one. The following holds for taxi $j$. $$ \sum_{i=0}^{N_{\rm c} -1}q_{ij} \le 1 $$

```
import numpy as np
from amplify import VariableGenerator, sum, less_equal, one_hot, FixstarsClient, solve
num_customers = 5 # Number of customers
num_taxies = 8 # Number of taxis
# Generate customers' coordinates, taxis' coordinates, and distance matrix between customers and taxis
loc_customers, loc_taxies, distances = gen_random_locations(num_customers, num_taxies)
# Create a QUBO variable array
gen = VariableGenerator()
q = gen.array("Binary", shape=(num_customers, num_taxies))
# Objective function
cost = (distances * q).sum()
############################################################################################
# Constraint
# Use equal_to for an equality constraint, less_equal for an inequality constraint
############################################################################################
customer_has_one_taxi = one_hot(q, axis=1)
taxi_has_one_or_less_customer = less_equal(q, 1, axis=0)
constraints = customer_has_one_taxi + taxi_has_one_or_less_customer
############################################################################################
# Construct a logical model by adding the objective function and constraint objects
constraints *= np.amax(distances) # type: ignore
model = cost + constraints
# Set up the client
client = FixstarsClient()
client.parameters.timeout = timedelta(milliseconds=1000) # Timeout is 1 second
# client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # If you are using it in a local environment, please enter the access token for Amplify AE
# solve a problem
result = solve(model, client)
# If result is empty, the constraint condition is not satisfied.
if len(result) == 0:
raise RuntimeError("Given constraint conditions are not satisfied")
solution = q.evaluate(result.best.values)
customers = np.arange(num_customers, dtype=int)
taxies = (solution @ np.arange(num_taxies)).astype(int)
matches = list(zip(customers, taxies)) # Index of customers and taxis to be matched
# Plotting the matching of customers and taxis
plot_matching(loc_customers=loc_customers, loc_taxies=loc_taxies, matches=matches)
```

```
```