You are viewing Nextmv legacy docs. ⚡️ Go to latest docs ⚡️

Get Started

Decision Stack

Overview of the Nextmv decision stack for your self-hosted app.

Runners

Runners are needed to deploy your app. Runners read data into a state structure, run a solver or simulator, and get decisions as data. See the deployment overview page for more information on prebuilt runners available for use with the Nextmv Decision Stack.

Apps

Nextmv apps are industry-specific applications that can be used as is or extended as needed to meet your needs for your specific use case. For example, see the engines overview page for a list of prebuilt filters and constraints available for use with the Dispatch App for routing a fleet of vehicles.

Apps are available on GitHub (contact support@nextmv.io for access).

In each app, the cmd directory contains the main.go, cmd.sh and supporting data files needed to run the app. Apps provide a starting point for solving real-world problems using our solvers and engines. If you are just getting started, check out our n-queens app.

Engines

Engines are Nextmv's implementations of high-performance algorithms that handle common tasks. These can be used directly or as components in your own decision models similar to plugin architectures.

Engines each have a struct defining its expected input. Engines implement Hop's model.State interface, and can be passed directly to Hop's solver constructors. By convention, engines provide the following exported types and functions for constructing models:

  • Input: a struct defining required model input.
  • DefaultModel: a function that converts an Input, and any other required data, into a Hop model.
  • DefaultSolver: a function that converts an Input, and any other required data, into a Hop solver.
  • DefaultOptions: a function to configure default solver options for a model.

Additionally, for fleet routing problems, engines provide:

  • DefaultAssigner: a function that converts an Input, and any other required data, into a Hop assigner.

Default solver vs model

There are typically 3 usage patterns for engines:

ProblemDataUsage Pattern
StandardAdheres to Input structureInstall and run DefaultSolver directly
StandardCan be transformed into Input structureModify main, build, and run DefaultSolver
ExtensionAdheres to (or can be transformed into) Input structureInstantiate a DefaultModel, extend standard model, build, and run new solver

"Standard" here refers to the standard modeling problem represented by an engine. For example, a Traveling Salesperson Problem (TSP) is our standard vehicle engine that can be used standalone (standard) or nested inside of a knapsack problem (extension).

As mentioned above, a user can install and run the DefaultSolver directly. For example, the package github.com/nextmv-io/code/engines/route provides a default routing model for the Capacitated Vehicle Routing Problem (CVRP).

go install github.com/nextmv-io/code/engines/cmd/route
$(go env GOPATH)/bin/route -hop.runner.input.path input.json \
                           -hop.runner.output.path output.json \
                           -hop.solver.limits.solutions 1
Copy

If the input data does not conform to the required Input format, the main method for the user model can be modified to transform input data into the required format for using the DefaultSolver directly.

package main

import (
    "github.com/nextmv-io/code/hop/run/cli"
    "github.com/nextmv-io/code/engines/route/vehicle"
)

func main() {
    cli.Run(vehicle.DefaultSolver)
}
Copy

The DefaultSolver configuration can be found in vehicle/default.go and shows that a cost matrix is the standard measure for this TSP.

Modelers may, however, choose to use engines as components and build extensions around them rather than using the solver directly. Calling DefaultModel rather than DefaultSolver gives the modeler access to the exported components of the underlying model which allows a user to build and test models without having to dive into the individual model source.

Constraints

Our engines can be configured with prebuilt, commonly used constraints as well as a constraint interface for building custom constraints unique to your business.

You can find a full list of constraints for each model in the Engines' package docs.

Here is an example of a model with capacity, precedence (pickups happen before dropoffs), window (locations must be visited within cost windows), and max length (route must be less than or equal to the max) constraints for routing vehicles.

constraints = append(
      constraints,
      []vehicle.Constraint{
        vehicle.Capacity(capacity, capacityChange),
        vehicle.Precedence(precedence),
        vehicle.Window(startCost, windows, windowMeasure)
        vehicle.MaxLength(routeMeasure, max, true)
      },
    )
Copy

Our clustering models can also have constraints. For example you may want to dictate how many points are in a cluster (cardinality) or their combined weight (capacity). Here is an example using both.

model, err := kmeans.DefaultModel(
    k,
    measure,
    kmeans.Cardinality(5),
    kmeans.Capacity(capacity, weights),
  )
Copy

Measures

Measures tell the model how to access the cost of an arc or connection between two locations. Engines can be used with a predefined collection of measures (e.g., haversine and matrix) which give users flexibility in how routes are compared. Measures also allow you to scale, override, or make cost between locations constant.

In the example below, the measure will tell the Vehicle model that the cost between any two locations is time (haversine distance scaled by 1/speed) as a default. The override cost will be 0 if the driver is going from any location to the end of their route. This is useful for any routing model that does not specify an end location for the driver or does not return to a depot (think real-time gig economy).

See the Measures page and engine package docs for more information on measures and how to use the different measures available with Nextmv.

  // Use speed to get drive time to/from all points and make to a driver start 0
  measure := measure.Override(
    // Calculate drive time, assumes travel speed is 9m/s
    measure.Scale(measure.Indexed(HaversineByPoint(),points), 1.0/9.0),
    measure.Constant(0),
    func(from, to int) bool {
      return driverDomain.Contains(to)
    },
  )
Copy

Solvers and simulators

While you can use Nextmv solvers and simulators directly, we recommend starting with an app or engine first. See the best practices for more information on using Nextmv solvers and simulators with apps and engines.

Page last updated

Go to on-page nav menu