DNA //evolutions

Open Assessor

Custom business rules without forking the solver

Every dispatch operation has rules that are unique to the business. "High-priority jobs must be visited early." "A route entering territory X must end in territory X." "No more than two hazardous-goods stops per route." These rules are too specific for a generic optimizer to include as built-in features. In traditional solvers, implementing them requires deep knowledge of the solver's internals, months of development, and ongoing maintenance when the solver is updated.

JOpt.TourOptimizer provides the Open Assessor as its primary extensibility mechanism. It allows injecting custom cost contributions, custom violations, and custom scoring logic into the optimization process through a clean, stable interface. The integration pattern is the same regardless of the rule: implement a restriction class, wire it into the optimization scheme, run the optimizer. The rule participates in every solution evaluation. No solver modification required.

The architecture is deliberately simple. A custom rule can be implemented in minutes by anyone who can write a Java method. The restriction receives the current route and node context, applies the business logic, and returns cost adjustments and violation details. The optimizer treats these custom contributions exactly like its built-in cost components.

Open Assessor node-level architecture

Figure 1: Node-level Open Assessor. Custom logic evaluates each node in route context and injects cost or violations.


Overview


Two levels: node and route

Open Assessor operates at two distinct levels:

Node-level restrictions evaluate each visited node inside its route context. They have access to the node, the previous element (sequence context), the planned arrival time, the chosen opening-hours window, and the route's cost controller. This is the right level for rules about individual tasks: deadlines, priority-based placement, sequence preferences, per-node compliance checks.

Route-level restrictions evaluate properties of the entire route. They have access to the complete list of assigned nodes, total distance, total working time, and the route's cost controller. This is the right level for rules about the route as a whole: maximum number of specific task types, territory consistency, minimum/maximum stop counts, compliance policies that span the full shift.

Open Assessor route-level architecture

Figure 2: Route-level Open Assessor. Custom logic evaluates the entire route and injects cost or violations.

Both levels can be used simultaneously. A single optimization can have multiple node-level and multiple route-level restrictions active at the same time.

The integration pattern

Every Open Assessor restriction follows the same three-layer pattern:

1) Restriction implementation

A Java class extending the appropriate base class:

  • AbstractCustomNodeLevelRestriction for node-level rules
  • AbstractCustomRouteLevelRestriction for route-level rules

The class overrides the invokeRestriction(...) method, which receives the current context and returns cost adjustments and optional violation details.

2) Optimization scheme wiring

The restriction is attached to the optimization through a custom optimization scheme. In the scheme's postCreate() method, you instantiate your restriction and call attachCustomNodeLevelRestriction(...) or attachCustomRouteLevelRestriction(...).

public class MyCustomScheme extends DefaultOptimizationScheme {

    public MyCustomScheme(IOptimization optimization) {
        super(optimization);
    }

    @Override
    public void postCreate() {
        ICustomNodeLevelRestriction myRule =
            new MyBusinessRule(this.getOptimization().getPropertyProvider());
        this.attachCustomNodeLevelRestriction(myRule);
    }
}

3) Activation

The custom scheme is set on the optimization instance before the run:

opti.setOptimizationScheme(new MyCustomScheme(opti));

That is the entire integration. No solver modification. No recompilation of the engine. The restriction is evaluated on every solution candidate, contributing to the cost function alongside all built-in components.

Node-level restrictions

A node-level restriction is invoked for every visited node during solution evaluation. The method signature provides rich context:

public IEntityRestrictionResult invokeRestriction(
    Optional<ILogicEntityRoute> previousRoute,
    ILogicEntityRoute route,
    INode node,
    INode prevElement,
    EvaluatedNodeDataHolder holder,
    IEntityCostAssessor costAssessor,
    boolean resultRequested)

Key inputs available to your business logic:

ParameterWhat it provides
routeThe current route (access to cost controller, all elements)
nodeThe node being evaluated
prevElementThe previous node in the sequence
holderEvaluated timing data: planned arrival time, chosen opening-hours window
costAssessorThe entity cost assessor for cost adjustment scaling
resultRequestedWhether the caller needs a structured violation report

This context is sufficient for implementing rules such as:

  • "High-priority nodes must be visited close to opening start"
  • "Nodes of type X must not be visited after 11:00 AM"
  • "Penalize sequences where node A immediately follows node B"
  • "Apply exponential late-penalty beyond a custom deadline"

Route-level restrictions

A route-level restriction is invoked once per route during solution evaluation:

public IEntityRestrictionResult invokeRestriction(
    Optional<ILogicEntityRoute> previousRoute,
    ILogicEntityRoute route,
    IEntityCostAssessor costAssessor,
    boolean resultRequested)

The route object provides access to the full list of optimizable elements, total cost, total distance, working-time consumption, and the cost/violation controller. This is sufficient for rules such as:

  • "A route must not contain more than 2 long-haul legs"
  • "If a route enters territory X, it must end in territory X"
  • "A route must visit at least one replenishment point if load exceeds threshold"
  • "Penalize routes with too many direction changes"
  • "Prefer routes that maintain technician-customer pairing"

How cost and violations are injected

Both node-level and route-level restrictions inject their results through the route's RouteCostAndViolationController. A typical restriction that detects a violation performs three updates:

// 1. Increment the constraint violation counter
controller.setNumConstraintViolations(
    controller.getNumConstraintViolations() + 1);

// 2. Track the injected restriction cost separately
controller.setCostInjectedRestriction(
    controller.getCostInjectedRestriction() + penaltyCost);

// 3. Add the cost to the total route cost
controller.addCost(penaltyCost);

This triple update ensures that:

  • The violation is visible in reporting and comparison tools
  • Injected cost is tracked separately from built-in cost components (for diagnostics)
  • The cost contributes to the total optimization objective, steering the solver toward rule-compliant solutions

Structured violation reporting

When resultRequested is true, the restriction should return an IEntityRestrictionResult containing a structured violation entry:

IViolation violation = MY_VIOLATION_TYPE.copy();
violation.setValue("Node arrived 15 minutes after custom deadline");
myRestrictionResult.addViolation(violation);

Structured violations are not just for internal debugging. They can be surfaced in planning UIs, included in acceptance reports, logged for audits, and used in comparison tooling ("why is solution A better than B?"). This makes Open Assessor an explainability feature, not only an optimization feature.

Practical examples

JOpt provides several example restrictions that demonstrate different rule patterns:

Deadline-style rule: "M-nodes must not be late"

Nodes whose IDs start with "M" must be served before a custom deadline (11:00 AM). The restriction computes the time deviation and applies an exponential cost multiplier so that "more late" becomes disproportionately expensive. This models real service-level requirements where marginal lateness is tolerable but significant lateness is operationally painful.

Priority-based early placement

High-importance nodes should be visited close to the start of their opening window. The restriction reads node importance, computes the gap between planned arrival and opening start, and assigns a penalty scaled by importance. This aligns the schedule with planner intuition: "do the important tasks first."

Importance-order enforcement

Within a route, nodes should appear in descending importance order. If a lower-importance node appears before a higher-importance node, the restriction assigns a penalty proportional to the importance difference. This is useful when many nodes share similar locations and sequencing differences are otherwise arbitrary.

Route-level: odd number of elements

A route must have an odd number of elements. While artificial, this demonstrates the route-level pattern: count elements, check a condition, inject cost if violated, return a structured violation. The same pattern applies to any route-scope business rule.

Soft vs. hard: modeling guidance

Open Assessor restrictions inject penalty costs and increment violation counters. This is a soft steering mechanism: the optimizer prefers satisfying the rule, but it may violate the preference if other objectives dominate.

If a rule must be strictly satisfied:

  • Model it as a native hard constraint when the feature exists (e.g. Pillar Nodes for time-window SLAs, ZoneCodes for territory enforcement).
  • Use architectural constructs that prevent infeasible configurations from being generated.
  • Reserve Open Assessor for sophisticated preferences, acceptance-driven shaping, and domain-specific scoring.

The guiding principle: hard constraints must be fulfilled by architecture, not by high cost. Costs optimize within the feasible space. Open Assessor is strongest for shaping the solution within that space according to business preferences.

Production best practices

Keep restriction evaluation fast and deterministic. Restrictions are evaluated frequently during the optimization run. Avoid network calls, heavy IO, or non-deterministic logic inside invokeRestriction(...). If you need expensive computations, precompute features and cache derived values by node or route ID.

Make costs comparable and stable. Because custom costs interact with built-in cost components, keep penalty magnitudes reasonable. Use cost adjusters for consistent scaling. Prefer explicit non-linear scaling (exponential) over arbitrary "huge constants" for deadline-style rules.

Produce high-quality violation payloads. When resultRequested is true, return clear violation type identifiers, contextual values (minutes late, threshold, route ID), and human-readable messages. This directly improves debugging speed, customer trust, and rollout success.

Wire via optimization schemes, not ad-hoc. Attach restrictions in the scheme's postCreate() method. This centralizes configuration, ensures consistent application, and makes the setup reproducible across runs and environments.

Start with one rule, validate, then add more. Each restriction adds evaluation overhead and interacts with the cost function. Roll out incrementally: implement one rule, verify it produces the expected behavior, then add the next.


Closing Words

Open Assessor is the mechanism that unlocks the "last 10%" of customer-specific business logic. The rules that make your operation unique (compliance policies, acceptance criteria, domain-specific scoring, operational preferences) can be implemented as first-class cost contributions without modifying the solver. The integration pattern is simple, stable, and identical for every rule: implement a class, wire it into the scheme, run the optimizer.

The deliberate simplicity of this architecture opens a path toward even more accessible rule definition in the future. Because restrictions are self-contained methods that receive context and return cost adjustments, they could be expressed in scripting languages or visual rule builders, allowing end customers to define custom rules without writing Java.

For the full interactive documentation, see the Open Assessor section in Special Features.


Authors

A product by dna-evolutions ©