ExVrp.Model (ExVrp v0.4.2)

Copy Markdown View Source

High-level builder for constructing VRP problems.

The Model provides a fluent API for defining depots, vehicle types, clients, and routing constraints. A model must have at least one depot and one vehicle type before it can be solved.

Basic Example

model =
  ExVrp.Model.new()
  |> ExVrp.Model.add_depot(x: 0, y: 0)
  |> ExVrp.Model.add_vehicle_type(num_available: 2, capacity: [100])
  |> ExVrp.Model.add_client(x: 1, y: 1, delivery: [10])
  |> ExVrp.Model.add_client(x: 2, y: 2, delivery: [20])
  |> ExVrp.Model.add_client(x: 3, y: 1, delivery: [15])

{:ok, result} = ExVrp.solve(model)

Custom Distance/Duration Matrices

By default, Euclidean distances are computed from coordinates. You can provide custom matrices instead (one per vehicle profile):

# Matrix rows/columns: [depot, client1, client2, ...]
distances = [
  [0, 100, 200],
  [100, 0, 150],
  [200, 150, 0]
]

model =
  ExVrp.Model.new()
  |> ExVrp.Model.add_depot(x: 0, y: 0)
  |> ExVrp.Model.add_client(x: 1, y: 0, delivery: [10])
  |> ExVrp.Model.add_client(x: 2, y: 0, delivery: [20])
  |> ExVrp.Model.add_vehicle_type(num_available: 2, capacity: [100])
  |> ExVrp.Model.set_distance_matrices([distances])
  |> ExVrp.Model.set_duration_matrices([distances])

Multi-Dimensional Capacity

Vehicles and clients can have multiple capacity dimensions (e.g. weight and volume):

model
|> ExVrp.Model.add_vehicle_type(num_available: 3, capacity: [1000, 50])
|> ExVrp.Model.add_client(x: 1, y: 1, delivery: [200, 10])

Client Groups

Client groups allow mutually exclusive alternatives — only one client from the group will be visited:

{model, group} = ExVrp.Model.add_client_group(model, required: false)
model =
  model
  |> ExVrp.Model.add_client(x: 1, y: 1, group: group, required: false, prize: 100)
  |> ExVrp.Model.add_client(x: 2, y: 2, group: group, required: false, prize: 150)

Same-Vehicle Groups

Force specific clients onto the same route:

[c1, c2] = model.clients
model = ExVrp.Model.add_same_vehicle_group(model, [c1, c2])

Validation

Models are validated automatically before solving. You can also validate explicitly:

case ExVrp.Model.validate(model) do
  :ok -> :ready
  {:error, reasons} -> IO.inspect(reasons)
end

Summary

Functions

Adds a client to the model.

Adds a new client group to the model.

Adds a depot to the model.

Adds a same-vehicle constraint group to the model.

Adds a vehicle type to the model.

Creates a new empty model.

Returns the number of clients in the model.

Returns the number of depots in the model.

Returns the total number of locations (depots + clients) in the model.

Returns the number of vehicle types in the model.

Returns the total number of vehicles in the model.

Sets custom distance matrices.

Sets custom duration matrices.

Converts the model to ProblemData for the solver.

Validates the model and returns any errors.

Types

t()

@type t() :: %ExVrp.Model{
  client_groups: [ExVrp.ClientGroup.t()],
  clients: [ExVrp.Client.t()],
  depots: [ExVrp.Depot.t()],
  distance_matrices: [[[non_neg_integer()]]],
  duration_matrices: [[[non_neg_integer()]]],
  same_vehicle_groups: [ExVrp.SameVehicleGroup.t()],
  vehicle_types: [ExVrp.VehicleType.t()]
}

Functions

add_client(model, opts)

@spec add_client(
  t(),
  keyword()
) :: t()

Adds a client to the model.

See ExVrp.Client.new/1 for available options.

Options

Example

model
|> ExVrp.Model.add_client(x: 1, y: 2, delivery: [10])

# With group assignment
{model, group} = Model.add_client_group(model, required: false)
model = Model.add_client(model, x: 1, y: 1, group: group)

Raises

add_client_group(model, opts \\ [])

@spec add_client_group(
  t(),
  keyword()
) :: {t(), non_neg_integer()}

Adds a new client group to the model.

Returns {model, group_index} where group_index can be passed to add_client/2 to dynamically add clients to the group.

Options

  • :required - Whether at least one client must be visited (default: true)
  • :mutually_exclusive - Whether only one client can be visited (default: not required)
  • :name - Group name for identification (default: "")

Example

{model, group} = Model.add_client_group(model, required: false)
model = Model.add_client(model, x: 1, y: 1, group: group)
model = Model.add_client(model, x: 2, y: 2, group: group)

add_depot(model, opts)

@spec add_depot(
  t(),
  keyword()
) :: t()

Adds a depot to the model.

See ExVrp.Depot.new/1 for available options.

Note: When adding a depot after clients have been added, all client group indices are recalculated to account for the new depot shifting client indices.

Example

model
|> ExVrp.Model.add_depot(x: 0, y: 0)

add_same_vehicle_group(model, clients, opts \\ [])

@spec add_same_vehicle_group(t(), [ExVrp.Client.t()], keyword()) :: t()

Adds a same-vehicle constraint group to the model.

All clients in this group that are visited must be served by the same vehicle. It is allowed to visit only a subset of the group (or none at all), but any visited clients must share a route.

Parameters

  • model - The model to add the group to
  • clients - List of Client structs that must be served by the same vehicle

Options

  • :name - Free-form name for the group (default: "")

Returns

The updated model with the new same-vehicle group added.

Example

model =
  Model.new()
  |> Model.add_depot(x: 0, y: 0)
  |> Model.add_client(x: 1, y: 1)
  |> Model.add_client(x: 2, y: 2)
  |> Model.add_vehicle_type(num_available: 2)

[c1, c2] = model.clients
model = Model.add_same_vehicle_group(model, [c1, c2], name: "group1")

Raises

add_vehicle_type(model, opts)

@spec add_vehicle_type(
  t(),
  keyword()
) :: t()

Adds a vehicle type to the model.

See ExVrp.VehicleType.new/1 for available options.

Example

model
|> ExVrp.Model.add_vehicle_type(num_available: 3, capacity: [100])

new()

@spec new() :: t()

Creates a new empty model.

num_clients(model)

@spec num_clients(t()) :: non_neg_integer()

Returns the number of clients in the model.

num_depots(model)

@spec num_depots(t()) :: non_neg_integer()

Returns the number of depots in the model.

num_locations(model)

@spec num_locations(t()) :: non_neg_integer()

Returns the total number of locations (depots + clients) in the model.

num_vehicle_types(model)

@spec num_vehicle_types(t()) :: non_neg_integer()

Returns the number of vehicle types in the model.

num_vehicles(model)

@spec num_vehicles(t()) :: non_neg_integer()

Returns the total number of vehicles in the model.

set_distance_matrices(model, matrices)

@spec set_distance_matrices(t(), [[[non_neg_integer()]]]) :: t()

Sets custom distance matrices.

If not provided, Euclidean distances are computed from coordinates.

Example

model
|> ExVrp.Model.set_distance_matrices([matrix1, matrix2])

set_duration_matrices(model, matrices)

@spec set_duration_matrices(t(), [[[non_neg_integer()]]]) :: t()

Sets custom duration matrices.

If not provided, distances are used as durations.

Example

model
|> ExVrp.Model.set_duration_matrices([matrix1, matrix2])

to_problem_data(model)

@spec to_problem_data(t()) :: {:ok, reference()} | {:error, term()}

Converts the model to ProblemData for the solver.

This is called internally by ExVrp.solve/2.

validate(model)

@spec validate(t()) :: :ok | {:error, [String.t()]}

Validates the model and returns any errors.

Returns :ok if valid, {:error, reasons} otherwise.