# `ExVrp.Model`
[🔗](https://github.com/sephianl/ex_vrp/blob/v0.4.2/lib/ex_vrp/model.ex#L1)

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

# `t`

```elixir
@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()]
}
```

# `add_client`

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

Adds a client to the model.

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

## Options

- `:group` - Group index from `add_client_group/2` (optional)
- See `ExVrp.Client.new/1` for other 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

- `ArgumentError` if group index is invalid
- `ArgumentError` if required client is added to mutually exclusive group

# `add_client_group`

```elixir
@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`

```elixir
@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`

```elixir
@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

- `ArgumentError` if any client is not in the model

# `add_vehicle_type`

```elixir
@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`

```elixir
@spec new() :: t()
```

Creates a new empty model.

# `num_clients`

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

Returns the number of clients in the model.

# `num_depots`

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

Returns the number of depots in the model.

# `num_locations`

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

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

# `num_vehicle_types`

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

Returns the number of vehicle types in the model.

# `num_vehicles`

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

Returns the total number of vehicles in the model.

# `set_distance_matrices`

```elixir
@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`

```elixir
@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`

```elixir
@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`

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

Validates the model and returns any errors.

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
