Comparison tool for optimization results

Illustration on a dark street grid: two routes over the same six stops, a clean hexagon marked 12.4 km and, after an arrow, a crossed version marked 16.8 km with a red point where the lines cross

This guide describes the comparison tool of JOpt: you change an existing optimization result, for example by swapping the order of two nodes, and compare the original and the modified result by cost and by constraint violations. It is written for developers who use JOpt as a Java library and need to explain or check a result. If you are new to JOpt.TourOptimizer, start with the first optimization tutorial.

Table of contents

Before you start

What it does

An optimized result can look worse to a person than it is. The optimizer weighs constraints such as time windows, skill requirements and visitor restrictions, while customers often prefer routes that are geographically short and look clean on a map. Two concerns come up often:

  • Geographical crossings: a route does not follow the shortest path, because a constraint requires another order.
  • Route splitting: the optimizer spreads the work over more than one route to reduce overtime.

The comparison tool lets you test such a concern in four steps:

  1. Modify the optimization result, for example by swapping the visit order of two locations or by moving nodes to another route.
  2. Compare the original and the modified result.
  3. Weigh the trade-offs, for example a shorter distance against a late arrival.
  4. Generate a report that lists the cost differences and the constraint violations.

Example: Duisburg before Krefeld

In the node exchange example, the optimization result visits Duisburg before Krefeld, although the opposite order looks shorter on the map. A customer may ask why Duisburg comes first. In the example, Duisburg has a shorter opening time window than the other nodes.

The figures below are sample values.

The original result visits Duisburg before Krefeld. The total distance is 272 km, and there are no late violations.

Map of the original route from Aachen: Duisburg is visited before Krefeld, then Essen, Wuppertal, Koeln and Dueren; arrows mark Duisburg and Krefeld

The modified result swaps Duisburg and Krefeld. The total distance is 238 km, 34 km less, but the visit at Duisburg ends 24.5 minutes after Duisburg closes.

Map of the modified route from Aachen: Krefeld is visited before Duisburg, then Essen, Wuppertal, Koeln and Dueren
MetricOriginal routeModified routeDifference
Total distance272 km238 km-34 km
Late violations0 min24.5 min+24.5 min
Total cost306.71316.39+9.68

The original result has the lower cost: it avoids the late visit and accepts the longer distance.

When to use it

Use the comparison tool when a person questions a result, for example:

  • "Why does the route cross this region multiple times?"
  • "Can't the optimizer keep all jobs in a single route?"
  • "Would swapping two locations reduce travel time?"

The optimizer looks for the solution with the lowest cost, which customers may perceive differently. With the comparison tool, a customer modifies a solution as their intuition suggests and compares it with the optimizer's result. The report shows why the optimizer chose its result. If the modified solution has the lower cost, the original run may have had too little computation time, or the model may miss a constraint or weigh one wrongly.

Other changes to compare: a different node visit order, a route split or a different distribution of work.

How to use it

The code below follows the comparison examples. exchangeNodes, createModResult, applyTasksAndCreateModResult and compareResults are helper methods of the examples.

Step 1: Modify an existing result

The helper exchangeNodes swaps the order of two locations. It wraps an ExchangeOptimizableNodesModificationTask in a list of IModificationTask and applies the list with the helper applyTasksAndCreateModResult, which calls OptimizationResultModificationWrapper.getNewResult(opti, tasks). A MoveOptimizableNodeModificationTask moves nodes to another route instead (the node move example).

private static IOptimizationResult exchangeNodes(
    IOptimization opti, String nodeOneId, String nodeTwoId)
    throws InterruptedException, ExecutionException, InvalidLicenceException, IOException,
    ConvertException, SerializationException {
  IModificationTask exchangeTask =
      new ExchangeOptimizableNodesModificationTask(nodeOneId, nodeTwoId);

  List<IModificationTask> tasks = new ArrayList<>();
  tasks.add(exchangeTask);

  return applyTasksAndCreateModResult(opti, tasks);
}

Called with Krefeld and Duisburg, it swaps the two nodes of the original route.

Step 2: Create the modified result

After the optimization run, the example creates the modified result with createModResult(this). The run itself prints the original result under RESULT ORG; createModResult prints the modified one:

private static IOptimizationResult createModResult(IOptimization opti)
    throws InterruptedException, ExecutionException, InvalidLicenceException, IOException,
    ConvertException, SerializationException {
  // Customers often ask why Duisburg comes before Krefeld; the swap shows what the other order
  // costs.
  IOptimizationResult modResult = exchangeNodes(opti, "Krefeld", "Duisburg");

  String banner = "#########################  RESULT MOD  #########################";
  System.out.println("\n\n\n" + banner + "\n\n\n");
  System.out.println(modResult);

  return modResult;
}

The node move example takes the original result as a second argument, because it reads the nodes of the second route from it.

Step 3: Compare the original and the modified result

JobAdvantagesController.compare compares both results and returns a JobAdvantageResult if they are structurally comparable. A CostAdvantageInterpreter turns it into a text report:

protected static void compareResults(
    IOptimizationResult orgResult, IOptimizationResult modResult)
    throws ResultStructureNotMatchingException {
  Optional<JobAdvantageResult> comparisonResultOptional =
      JobAdvantagesController.compare(orgResult, modResult);

  if (comparisonResultOptional.isPresent()) {
    JobAdvantageResult comparisonResult = comparisonResultOptional.get();

    ICostAdvantagesInterpreter myInterpreter = new CostAdvantageInterpreter();

    System.out.println(
        "\n\n\n#########################  COMPARISON RESULT  #########################\n\n\n");
    System.out.println(myInterpreter.generateTextReport(comparisonResult));
  } else {
    System.out.println("Comparison failed");
  }
}

The comparison report

After the comparison, the report summarizes the differences in five numbered parts: the job result (the cost of each whole run), the violation summaries, the restrictions sorted by their cost difference, the restrictions with the largest positive and the largest negative difference, and the routes behind those two. The job result of the node exchange example (Compare results), without its two ident lines:

Cost Base    : 306.71521617219287
Cost Compare : 316.3991459910722
Cost Diff    : -9.683929818879335
Result       : Base job has lower cost.

The route part of the report lists the distance as the restriction where the modified result has the lower cost (Cost Diff : 34.33032257339144) and the lateness as the one where the original is better (Cost Diff : -46.18157322560417): the modified route is 34.33 km shorter, but its late visit at Duisburg costs more than the distance saves. Cost Diff is the cost of the base result minus the cost of the compared result, so the table above shows the same difference as +9.68.

Limits

The comparison needs two structurally comparable results, such as an original result and a result modified from it. JobAdvantagesController.compare declares a ResultStructureNotMatchingException, and the code above prints "Comparison failed" when no JobAdvantageResult comes back.

Authors

DNA Evolutions GmbH


Added: 2026-01-03 (JOpt core 7.5.2-j17) | Last updated: 2026-10-23