Basic elements of JOpt

Illustration on a dark street grid: a white van stands at the center of lime routes that connect five map pins, each with a small clock beside it

This tutorial introduces the basic elements for defining an optimization input with JOpt in Java: nodes, resources, the connections between them, the optimization properties and the license. It is written for developers who use JOpt as a Java library. If you run JOpt.TourOptimizer as a Docker container instead, you define the same elements in the JSON format that the container's Swagger UI shows; see TourOptimizer REST server.

Table of contents

Before you start

The code on this page uses these imports:

Show the imports
/*
 * Other imports - Java and third-party
 */
import static java.time.Month.MAY;
import static javax.measure.MetricPrefix.KILO;
import static tech.units.indriya.unit.Units.METRE;

import tech.units.indriya.quantity.Quantities;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;


import javax.measure.Quantity;
import javax.measure.quantity.Length;


/*
 * JOpt imports
 */
import com.dna.jopt.framework.body.IOptimization;
import com.dna.jopt.framework.body.Optimization;
import com.dna.jopt.framework.exception.caught.InvalidLicenceException;
import com.dna.jopt.framework.outcomewrapper.IOptimizationResult;
import com.dna.jopt.member.unit.hours.IWorkingHours;
import com.dna.jopt.member.unit.hours.IOpeningHours;
import com.dna.jopt.member.unit.hours.WorkingHours;
import com.dna.jopt.member.unit.hours.OpeningHours;
import com.dna.jopt.member.unit.hours.TimeWindow;
import com.dna.jopt.member.unit.node.INode;
import com.dna.jopt.member.unit.node.geo.TimeWindowGeoNode;
import com.dna.jopt.member.unit.resource.CapacityResource;
import com.dna.jopt.member.unit.resource.IResource;
import helper.ExampleLicenseHelper;

Nodes, resources and restrictions

JOpt finds an optimized distribution of nodes and resources. A resource can visit a node and work on it. In a pickup and delivery problem, for example, a resource can be a truck that carries goods and either delivers them to a node or picks them up from a node. A real problem adds restrictions such as:

  1. Do the working hours of the resource match the opening hours of the node?
  2. Is the resource allowed to visit the node, for example because of skills or of customer wishes such as "We want the same driver every day"? May it pick up dangerous goods?
  3. Does the resource already have overtime?
  4. Is the resource close enough to the node to reach it in time?
  5. Does the route look convincing after the optimization?

Nodes

JOpt has several node types, and a resource can visit each of them. This tutorial uses the TimeWindowGeoNode, which implements the interface INode. The Nodes page describes the other types.

Parameters of a TimeWindowGeoNode

A TimeWindowGeoNode needs these input parameters:

  1. A unique node id as a String.
  2. A geographical coordinate: the latitude and longitude of the node.
  3. A list of opening hours in which the node can be visited.
  4. A visit duration: the time that the task at the node takes.
  5. A relative importance value as an integer.

Node id

Each element, not only a node, needs a unique id that identifies it.

Node coordinates

In the simplest form, a geo-coordinate is two double values for latitude and longitude:

// Geo-coordinate of Cologne in Germany
double latitude  = 50.9333;
double longitude = 6.95;

OpeningHours

A node can hold a list of opening hours. Each OpeningHours mainly represents a time window with a start and an end. An OpeningHours is implementing the interface IOpeningHours which is extending the interface IDutyHours.

Define a start and an end:

ZonedDateTime start 
    = ZonedDateTime.of(2020, MAY.getValue(), 7, 8, 0, 0, 0, ZoneId.of("Europe/Berlin"));
    
ZonedDateTime end = 
    ZonedDateTime.of(2020, MAY.getValue(), 7, 17, 0, 0, 0, ZoneId.of("Europe/Berlin"));

Construct the OpeningHours from start and end:

IOpeningHours firstHour = new OpeningHours(start, end);

We can also define a TimeWindow object first, and construct the hour using it.

TimeWindow secondTimeWindow = new TimeWindow(start,end);

Then create the OpeningHours object from it:

IOpeningHours secondHour = new OpeningHours(secondTimeWindow);

Collect the opening hours in a list:

List<IOpeningHours> myOpeningHours = new ArrayList<>();
myOpeningHours.add(oneHour);
myOpeningHours.add(anotherHour);

Visit duration

A visit at a node usually means a task that takes a certain time:

Duration visitDuration = Duration.ofMinutes(20);

Importance

In real problems, violations often cannot be avoided. For example, of two nodes only one can be visited in time. A node with a higher importance is less likely to be violated. Importance is relative: a high value only has an effect when other nodes have a lower one. If all nodes have the same importance, high or low, the optimizer treats them the same.

Construct the node

The constructor takes the values above:

 INode koeln = new TimeWindowGeoNode("Cologne", latitude, longitude, myOpeningHours, visitDuration, 1);

Resources

The primary resource type is the CapacityResource, which implements the interface IResource.

Parameters of a CapacityResource

A CapacityResource needs these input parameters:

  1. A unique resource id as a String.
  2. A geographical coordinate: the latitude and longitude where the resource is located.
  3. A list of working hours in which the resource can be used.
  4. The maximal working time of the resource.
  5. The maximal driving distance of the resource.

Resource id

Each resource needs a unique id that identifies it, for example John.

Resource coordinates

In the simplest form, a geo-coordinate is two double values for latitude and longitude:

// Geo-coordinate of Cologne in Germany
double latitude  = 50.9333;
double longitude = 6.95;

WorkingHours

A WorkingHours is implementing the interface IWorkingHours which is extending the interface IDutyHours.

WorkingHours and OpeningHours share the same interface IDutyHours.

You create working hours the same way as opening hours, from a start and an end (see OpeningHours):

IOpeningHours firstHour = new OpeningHours(start, end);

Or:

TimeWindow secondTimeWindow = new TimeWindow(start,end);
IOpeningHours secondHour = new OpeningHours(secondTimeWindow);

Then collect them in a list:

List<IWorkingHours> myWorkingHours = new ArrayList<>();
myWorkingHours.add(oneHour);
myWorkingHours.add(anotherHour);

Maximal working time

Each resource needs a maximal working time. It is a soft constraint: the optimizer uses it as a guideline, and a solution can show overtime for one or more resources. For example, a maximal working time of 8 hours means that the resource should not work more than 8 hours within each of its working hours.

Duration maxWorkingTime = Duration.ofHours(10);

A WorkingHours object has a start and an end. Time after the end of the working hours counts against the maximal working time, and the working hours are a frame for matching resources with the opening hours of the nodes, so a route can end after its working hours without a violation. The start of the frame matters when a resource uses a flexible start time.

Maximal distance

Each resource needs a maximal distance. Like the maximal working time, it is a soft constraint and a guideline for the optimizer: a solution can show over-distance for one or more resources. For example, a maximal distance of 500 kilometers means that the resource should not drive more than 500 kilometers within each of its working hours.

Quantity<Length> maxDistance =  Quantities.getQuantity(500, KILO(METRE));

Construct the resource

The constructor takes the values above:

 IResource john = new CapacityResource("John", latitude, longitude,
    maxWorkingTime, maxDistance, johnWorkingHours);

Element connections

A NodeEdgeConnectorItem defines the distance and the driving time between two elements, which are nodes or resources. JOpt.TourOptimizer takes connections as input, but they are optional. If a connection is missing, or if there are no connections at all, JOpt calculates them with the flat-surface formula. With the backup connector you can define your own formula instead, for example the Haversine formula.

The following code creates two connections between Cologne and Munich, one in each direction. The two directions are not necessarily symmetric, so two elements usually need two connections:


 INodeConnectorItem connectionCologneMunich = new NodeEdgeConnectorItem();
    connectionCologneMunich.setFromOptimizationElement(cologne);
    connectionCologneMunich.setToOptimizationElement(munich);
    connectionCologneMunich.setDistance(Quantities.getQuantity(600, KILO(METRE)));
    connectionCologneMunich.setDrivingTime(Duration.ofMinutes(240));
    
 INodeConnectorItem connectionMunichCologne = new NodeEdgeConnectorItem();
    connectionMunichCologne.setFromOptimizationElement(munich);
    connectionMunichCologne.setToOptimizationElement(cologne);
    connectionMunichCologne.setDistance(Quantities.getQuantity(580, KILO(METRE)));
    connectionMunichCologne.setDrivingTime(Duration.ofMinutes(220));
    
 // Define a list for the connections
 List<INodeConnectorItem> connectionItems = new ArrayList<>();
 
 connectionItems.add(connectionCologneMunich);
 connectionItems.add(connectionMunichCologne);

The connections go into an INodeEdgeConnector, a wrapper object that holds them:


 // Create a connector
 INodeEdgeConnector myNodeConnector = new NodeEdgeConnector();

 // Add connections to connector
 myNodeConnector.putNodeConnections(connectionItems);

 // Set NodeEdgeConnector
 optimization.setNodeConnector(myNodeConnector);

Properties for the optimization run

Optimization properties tune an optimization for a use case. Typical problems that need tuning:

  • The optimization takes too long. How do I tune the number of iterations?
  • I want the optimizer to put more weight on late arrivals at nodes than on the maximal working time of resources.
  • I want to skip some optimization steps.

Step (1) Adding properties of the first optimization tutorial shows how to set properties. The optimization properties page lists the property keys by category, with their defaults.

License for the optimization

An optimization above a certain number of elements (nodes and resources together) needs a license; see License guide: element limits. The license is a JSON definition. If you run the optimizer as a dependency, you can save the definition as a text file and set it at startup, or set it as a String:


 // Via String
 myOpti.setLicenseJSON(myJsonLicenseString);
 
 ...
 
 // Or via file
 myOpti.setLicenseJSON(myJsonLicenseFile);

A sample license file; the values are examples and the key is a placeholder:


{
  "version" : "10.4",
  "identifier" : "TEST-",
  "description" : "Key provided to [email protected] by DNA evolutions GmbH",
  "contact" : "[email protected]",
  "modules" : [ {
    "Module:" : "Date",
    "creation" : "2020-05-25",
    "due" : "2099-05-08"
  }, {
    "Module:" : "Elements",
    "max" : 100
  } ],
  "key" : "TEST-AVERYUNIQUEKEY"
}
Changing any JSON-key (deleting, modifying, or adding) of the license will deactivate the license. The only allowed modification is a formatting step, like prettifying the JSON.

In case you are using the Docker version of JOpt.TourOptimizer, you will have to send this JSON definition along with your request.

License agreement

The license agreement and further information about license plans are on the DNA Evolutions website.

Examples on GitHub

GitHub logo

Java-TourOptimizer-Examples on GitHub is a collection of Java examples. It is a working Maven project that you can clone and use as a base for starting with JOpt.TourOptimizer.

Authors

DNA Evolutions GmbH


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