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

Sdk

Input And Output

Overview of working with input and output data.

Traditional modeling and optimization tools require translating decision data (i.e. inputs) and business logic to some other form before calling some optimization algorithm. In contrast, Nextmv's tools are designed to operate directly on business data and produce decisions that are actionable by software systems. This makes decisions more interpretable and easier to test. It also makes integration with data warehouses and business intelligence platforms significantly easier.

Nextmv SDK allows you to write a custom input schema to use Nextmv with any input data. See below for more information on writing custom input and output data.

Define input structure

To read input data into a model on Nextmv, you will need to define a Go struct, detailing the structure of your input data.

For example, take the following JSON sample data below for a routing problem. It has a driver's name, their capacity and location, and a vector of pickup and delivery requests to route for them.

{
  "Driver": "Mooney",
  "Capacity": 3,
  "Location": [-122.066889, 37.386274],
  "Requests": [
    [
      [-122.08024, 37.393735],
      [-122.094032, 37.393879]
    ],
    [
      [-122.060621, 37.399051],
      [-122.066038, 37.396881]
    ],
    [
      [-122.058902, 37.394507],
      [-122.055139, 37.390682]
    ]
  ]
}
Copy

The input data structure can be defined with the following Go struct.

type input struct {
  Driver   string
  Capacity int
  Location [2]float64
  Requests [][2][2]float64
}
Copy

Customize JSON formatting

You can change the way the names that Go structures use by default are read in and written out using json annotations on your structs.

type input struct {
  Driver   string       `json:"driver`
  Capacity int          `json:"capacity"`
  Location [2]float64   `json:"location"`
  Requests [][2][2]float64 `json:"requests"`
}
Copy

With these additions, a model can now read data that looks like this:

{
  "driver": "Mooney",
  "capacity": 3,
  "location": [-122.066889, 37.386274],
  "requests": [
    [
      [-122.08024, 37.393735],
      [-122.094032, 37.393879]
    ],
    [
      [-122.060621, 37.399051],
      [-122.066038, 37.396881]
    ],
    [
      [-122.058902, 37.394507],
      [-122.055139, 37.390682]
    ]
  ]
}
Copy

If you need finer-grained control over how data are read in and decisions are written, you can provide UnmarshalJSON and MarshalJSON methods for the types as shown in the Go docs.

As an example, we can change the way our state type is written when solutions are found. Say we implement a duration method for state that estimates the time to complete a path and returns a time.Duration. We add that to our model output with a MarshalJSON method. We also add the request list from the input.

func (s state) MarshalJSON() ([]byte, error) {
  m := map[string]interface{}{
      "path":     s.Path,
      "requests": s.input.requests,
      "time":     s.duration().String(),
  }
}
Copy

Define decision state

Next, define a decision (or system) state for your model. In the example above, we keep the current path as a slice of integers to refer to the locations by index.

type state struct {
    Path []int
    input input
}
Copy

Other data can be stored in your state, such as the final location of the driver or the cost of the route, depending on your requirements.

Standard output

Since Path is exported, Nextmv's solver will automatically serialize it as the model output. All that is needed is to hook up the input type to a runner, construct a state, and return a solver operating on that state through different methods, including the Feasible, Next and any other methods required. See the solver overview for more information on the Feasible and Next methods.

Once you go build your model into a binary, it will read input structures from standard input and write state structures to standard output. An alternative to using standard input and output is to use the -hop.runner.input.path and -hop.runner.output.path command-line flags. See build and run your app for more on next steps and the dispatch app for a full example.

Output statistics

Output statistics are provided about the results of the solver for a specific problem.

Below is an example of output statistics for a solver result.

{
"statistics": {
  "bounds": {
      "lower": 0,
      "upper": 15281
  },
  "search": {
      "generated": 51257,
      "filtered": 28076,
      "expanded": 23181,
      "reduced": 0,
      "restricted": 18838,
      "deferred": 4343,
      "explored": 22979,
      "solutions": 9
  },
  "time": {
      "elapsed": "64.30884ms",
      "elapsed_seconds": "0.061638153",
      "start": "2021-03-02T22:29:11.853297-05:00"
  },
  "value": 15281
  }
}
Copy

The first field under the statistics is bounds, which contains the upper and lower bounds. In cases where the solver is maximizing or minimizing an objective, the upper and lower bounds are continuously updated after generating a new state.

If the bounds are the same in value that means that the solver has mathematically proven optimality of its best solution. When those are not the same value, the solver terminated early while searching the state space proving optimality. In that case, the solver will return the best found to date, but hasn't explored enough states to prove that it is the mathematical optimal. You can read more about the Bounds method here.

In the search object, you'll notice that there are values for generated, expanded, deferred, reduced, explored, and filtered.

The solver's search mechanism is a state exploration. It projects states forward from the root state to determine what decisions you should make. Every transition in that search produces a new state which the solver then explores.

Generated states are all the states the solver has created.

Filtered states are those that have been removed from the search because they have been bounded out. The solver has determined that it cannot, from a particular set of decisions, produce a solution that will be better than the best one it has found so far.

Expanded states are all the states that are not Filtered.

Reduced states are states that have been removed from the search because a Reducer was able to determine that there are states that strictly dominate them.

Deferred states are states saved for later exploration if there's time. This is how the solver manages explosion of the state space. The solver won't explore every state sequentially. It will try to explore the most fruitful ones first and defer the others for later exploration if there's time.

Restricted states are the states that have not been deferred, but instead will be investigated immediately.

Explored states are all states Hop has fully explored. Those states are not able to produce more child states.

Each of these categories contain both feasible and infeasible states.

Page last updated

Go to on-page nav menu