Custom Solution — Architecture, Warm-Start Patterns, and Practical Integration
A custom solution (also called warm start, seed solution, or initial entity) is an explicitly constructed solution that you inject into the optimizer before the search starts.
This capability is central for production deployments where you want the solver to:
- continue from an existing plan (yesterday’s routes, dispatcher plan, manual plan),
- re-optimize partially after disruptions (new jobs, cancellations, resource changes),
- run controlled “what-if” scenarios without restarting from scratch,
- increase end-user acceptance by starting from a familiar plan and improving it incrementally.
In JOpt.TourOptimizer, a “solution” is an IEntity that contains one or more routes (ILogicEntityRoute), each route referencing a resource and a sequence of optimizable elements (nodes).
References
Examples in this package
The solution architecture in JOpt (mental model)
Think of the optimizer as two layers:
1) Problem definition layer (static inputs)
You define:
- nodes (
INode) and their constraints (opening hours, skills, relations, …), - resources (
IResource) with working hours and capacities, - cost model and optimization properties,
- optional connection data (distance/time).
2) Solution layer (dynamic arrangement)
You define (or let the solver create):
- an
IEntityconsisting of- multiple
ILogicEntityRouteroutes, - each with a visiting resource + working-hours index,
- and an ordered list of optimizable elements.
- multiple
A simplified view:
Optimization (problem container)
└── IEntity (a solution candidate)
├── Route 0 (ILogicEntityRoute)
│ ├── Visiting Resource: Jack (working day index 0)
│ ├── Start / Termination (derived from resource)
│ └── Optimizable elements: [NodeA, NodeB, NodeC, ...]
└── Route 1 (ILogicEntityRoute)
├── Visiting Resource: Jack (working day index 1)
└── Optimizable elements: [NodeX, NodeY, ...]
A custom solution means: you construct the right-hand side (the IEntity) explicitly and set it as the initial entity.
How to inject a custom solution (the essential sequence)
Both examples follow the same high-level sequence:
- Configure license and properties
- Create an initial entity (
IEntity) - Inject it via
setInitialEntity(entity) - Start optimization
Minimal schematic:
IEntity initial = createInitialSolution();
setInitialEntity(initial);
startRunAsync().get();
Why the properties matter
In the examples, assisted construction is disabled:
props.setProperty("JOpt.Assisted", "FALSE");
Interpretation:
- you are explicitly telling the optimizer: “do not build your own construction solution; I am providing the starting point.”
- this is typically what you want for true warm-start workflows.
Building an IEntity correctly (what “valid” means)
The solver can repair certain inconsistencies, but a robust custom solution should be structurally consistent.
Route must be bound to a resource and a working-hours index
In CustomSolutionExample, two routes are bound to the same resource but different working days:
firstRoute.setCurrentVisitingResource(myRes, 0);
secondRoute.setCurrentVisitingResource(myRes, 1);
This is the recommended way to model:
- multi-day planning,
- or multiple shifts for the same person/vehicle.
Route start and termination should be defined
The examples use:
route.setRouteStart(myRes);
route.setRouteTermination(myRes);
This ensures the route has a valid start and end reference.
(How “end” is interpreted depends on your open/closed route modeling; see the OpenClosedRoute example and documentation.)
Add the optimizable elements in the planned order
You then insert nodes in the exact order you want to seed:
route.addAllToOptimizableElements(nodesForThisRoute);
Finally:
IEntity entity = new Entity();
entity.addRoute(firstRoute);
entity.addRoute(secondRoute);
What a custom solution is (and what it is not)
It is a starting point, not a hard constraint
A custom solution does not freeze the plan by default. It only provides the initial state from which search begins.
If you want parts of the solution to be immutable (e.g., “these nodes are fixed at the beginning”), use architectural features such as:
- Pillar/Captured nodes,
- specific relations,
- or other structural modeling tools (depending on the rule you need).
Hard constraints are not enforced by “high cost”
A recurring mistake is to simulate feasibility constraints by “making violations very expensive”. In JOpt, hard constraints must be satisfied by architecture—not by cost inflation.
- If a constraint is truly mandatory, it must be modeled as a hard constraint and/or via structural constructs.
- Costs are for optimizing within the feasible space, not for “buying” infeasibility.
This is particularly important when custom solutions are involved:
- you should seed a solution that is feasible (or close to feasible),
- and let the optimizer improve it within the valid constraint architecture.
Adding new nodes/resources after injecting a custom solution
CustomSolutionWithAdditionalNodesAndResourcesExample demonstrates a critical production workflow:
- you start with a custom initial entity (your “current plan”),
- then you add new elements (late orders, new resources),
- and re-optimize.
After setting the initial entity, the example calls:
addReassignResources(getAdditionalResources());
addReassignNodes(getAdditionalNodes());
What addReassignNodes(...) / addReassignResources(...) are for
These helper methods are designed for incremental plan evolution:
- add a node/resource to the optimization container,
- ensure it becomes part of the search space,
- and “reassign” it into a state where the solver can place it appropriately.
This is the practical bridge between:
- “I already have a plan” and
- “the world changed; optimize again”.
Duplicate IDs are rejected (important!)
The example explicitly adds duplicates to demonstrate behavior:
- a node with an existing ID (“Essen”) is rejected,
- a resource with an existing ID (“Jack”) is rejected.
Production implication:
- IDs must be globally unique across nodes/resources in a run.
- When importing external plans, apply a deterministic ID strategy:
- stable IDs for existing objects,
- unique IDs for new objects,
- never reuse an existing ID for a different object instance.
Warm-start patterns used in real systems
Pattern A — Continue yesterday’s plan
Use case:
- daily scheduling where most jobs repeat.
Workflow:
- Load yesterday’s result (or store it as an entity snapshot)
- Convert to an
IEntitywith routes + node order setInitialEntity(entity)- Run a shorter optimization budget (because you already have a good baseline)
Benefits:
- faster convergence,
- higher similarity to previous plan,
- better planner trust.
Pattern B — Insert late jobs (“re-optimization under change”)
Use case:
- new job arrives at 10:30 while routes are already planned.
Workflow:
- Seed with current plan (
setInitialEntity) - Add new nodes via
addReassignNodes(...) - Run with limited compute budget
- Optionally compare baseline vs modified plan (see CompareResult tooling)
Benefits:
- controlled deviation,
- clear “delta” to the plan,
- supports continuous planning.
Pattern C — Add (or remove) resources
Use case:
- a driver calls in sick; an extra driver becomes available.
Workflow:
- Seed with existing plan
- Add/remove resources
- Let the optimizer rebalance
Note:
- If resources change, always validate:
- working hours coverage,
- skill constraints,
- territory constraints,
- and feasibility of previously assigned jobs.
Pattern D — Human-in-the-loop acceptance workflow
Use case:
- dispatcher wants to keep “most of my plan” but allow optimization.
Workflow:
- Seed with dispatcher plan
- Run short improvement phase
- Show a diff report (comparison tool)
- Iterate
This is one of the most effective adoption strategies in practice.
Engineering best practices (high-leverage details)
1) Decide whether you want “repair” or “strict feasibility”
If your initial plan may be infeasible:
- you can still seed it, but you should expect repair dynamics and warnings.
If you require strict feasibility:
- validate feasibility before seeding (at least for obvious hard constraints),
- or seed a partially empty route structure and let the solver construct within constraints.
2) Keep your custom entity minimal and consistent
Avoid overfitting the seed with incidental details that can confuse search.
Good seed characteristics:
- correct resource/day assignment,
- sensible route order,
- realistic start/termination,
- no duplicated elements,
- consistent IDs.
3) Control similarity vs improvement
If end-user acceptance is key:
- use a shorter compute budget and/or a more conservative operator set,
- seed from the human plan,
- and evaluate deltas with the comparison tool.
If pure optimality is key:
- allow full optimization budget,
- still seed to accelerate convergence.
4) Instrument results and warnings
Custom seeds can surface modeling mismatches quickly.
Always log:
onWarning(...),onError(...),- progress strings,
- final KPIs.
The examples demonstrate these hooks.
Practical checklist for implementing custom solutions
- IDs
- Node IDs and Resource IDs must be unique and stable.
- Resource/WorkingHours assignment
- Every route must have a resource and a correct working-hours index.
- Start/Termination
- Set route start and termination (or use your chosen open/closed route rules consistently).
- Node order
- Add nodes in the exact sequence you want as seed.
- Properties
- Decide whether assisted construction should be disabled.
- Decide compute budget and operator aggressiveness.
- Incremental changes
- Add new nodes/resources via reassign helpers rather than rebuilding everything.
- Explainability
- If users propose changes, compare baseline vs modified solutions and provide a report.
Summary
- A custom solution in JOpt is an explicitly constructed
IEntitycontaining routes and node order. - You inject it via
setInitialEntity(...)to warm-start optimization. - This enables production-grade workflows: continuation, incremental re-optimization, human-in-the-loop planning, and improved acceptance.
- Additional nodes/resources can be integrated after seeding using
addReassignNodes(...)andaddReassignResources(...), with strict ID uniqueness requirements. - Mandatory feasibility rules must be enforced by architecture (hard constraints / structural modeling), not by “high cost”.
Custom Node Backup Connector — Custom Distance/Time Calculation for Missing Connections
JOpt.TourOptimizer can operate with externally provided connections (distance + driving time) or it can auto-generate connections when they are missing. The mechanism responsible for filling missing NodeEdgeConnectorItems is the Backup Connector.
Extract Build Info — Diagnostics, Reproducibility, and Support-Ready Bug Reports
When you run JOpt.TourOptimizer in production (services, batch pipelines, planning UIs), you will eventually need to answer questions like: