Reason#

Why the planner placed a task where it did. It's read from Mutation.reason, which the planner fills only when the call ran with reasons = true in SolveOptions or PreviewOptions. The planner creates the values; caller code only reads them.

Declaration#

package org.dhtmlx.plen;

public abstract class Reason extends ValueRecord {
  public static class Asap extends Reason {
    public Id linkId;
    public Id sourceId;
    public Asap(Id linkId, Id sourceId)
  }

  public static class Alap extends Reason {
    public Id linkId;
    public Id targetId;
    public Alap(Id linkId, Id targetId)
  }

  public static class SummaryTranslation extends Reason {
    public Id summaryId;
    public SummaryTranslation(Id summaryId)
  }

  public static class Constraint extends Reason {}

  public static class Preferred extends Reason {}

  public Reason copy()
}

The five nested classes are the variants. Match a value with instanceof patterns, or with a switch over type patterns that ends in a default case. Variants compare by field values through equals and hashCode. copy() returns a shallow copy of the same variant.

Variants#

Variant Description
Asap(linkId, sourceId) A predecessor pushed the task later. linkId is the driving link, sourceId the driving predecessor.
Alap(linkId, targetId) A successor pulled the task earlier, in backward planning. linkId is the driving link, targetId the driving successor.
SummaryTranslation(summaryId) The task moved together with its summary. summaryId is the summary that moved.
Constraint The task's own constraint placed it.
Preferred The task stayed at, or returned to, its own input date.

Links, constraints and summaries covers how links, constraints and summaries decide a task's dates.

Example#

import java.util.List;
import org.dhtmlx.plen.*;

public class ReasonExample {
  public static void main(String[] args) throws Exception {
    Moment monday = Moment.fromYmdHm(2026, 1, 5, 8, 0);

    ProjectData data = new ProjectData()
        .withTasks(List.of(
            new TaskData(Id.str("design")).withStartDate(monday).withDuration(2.0),
            new TaskData(Id.str("build")).withStartDate(monday).withDuration(3.0)))
        .withLinks(List.of(
            new LinkData().withId(Id.str("l1"))
                .withSource(Id.str("design"))
                .withTarget(Id.str("build"))));

    ScheduleResult result = Engine.schedule(data, new SolveOptions().withReasons(true));

    for (Mutation update : result.updates) {
      String why;
      if (update.reason instanceof Reason.Asap r) {
        why = "pushed by " + r.sourceId + " through link " + r.linkId;
      } else if (update.reason instanceof Reason.Alap r) {
        why = "pulled by " + r.targetId + " through link " + r.linkId;
      } else if (update.reason instanceof Reason.SummaryTranslation r) {
        why = "moved with summary " + r.summaryId;
      } else if (update.reason instanceof Reason.Constraint) {
        why = "placed by its constraint";
      } else if (update.reason instanceof Reason.Preferred) {
        why = "kept at its own date";
      } else {
        why = "no reason recorded";
      }
      System.out.println(update.id + ": " + why);
      // build: pushed by design through link l1
    }
  }
}