Violation#

The bound of a task's dates that its links and its constraint disagree on. A Violation arrives in the violation field of Conflict::ConstraintViolation, next to the ConstraintType that set the bound.

Declaration#

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Violation {
    EarliestStart,
    LatestStart,
    EarliestEnd,
    LatestEnd,
}

Variants#

Variant Description
EarliestStart The links want the task to start before the earliest start its constraint allows (Snet, Mso). Reported in backward scheduling.
LatestStart The links want the task to start after the latest start its constraint allows (Snlt, Mso). Reported in forward scheduling.
EarliestEnd The links want the task to end before the earliest end its constraint allows (Fnet, Mfo). Reported in backward scheduling.
LatestEnd The links want the task to end after the latest end its constraint allows (Fnlt, Mfo). Reported in forward scheduling.

What required and actual hold for each variant is described on Conflict.

Serialization#

With the serde feature, Violation serializes as a kebab-case string: "earliest-start", "latest-start", "earliest-end", "latest-end". See Serialize results to JSON.

Example#

use plen_core::{
    Conflict, ConstraintType, DateTime, LinkData, LinkType, Project, ProjectData,
    SchedulingConfig, SolveOptions, TaskData, Violation,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 1, 5, 8, 0).ok_or("invalid date")?;
    let tuesday = DateTime::from_ymd_hm(2026, 1, 6, 8, 0).ok_or("invalid date")?;

    let mut design = TaskData::new("design");
    design.start_date = Some(monday);
    design.duration = Some(2.0);

    // `build` must start by Tuesday, but `design` ends later.
    let mut build = TaskData::new("build");
    build.start_date = Some(monday);
    build.duration = Some(3.0);
    build.constraint_type = Some(ConstraintType::Snlt);
    build.constraint_date = Some(tuesday);

    let data = ProjectData {
        config: SchedulingConfig {
            apply_constraints: true,
            ..Default::default()
        },
        tasks: vec![design, build],
        links: vec![LinkData {
            id: "l1".into(),
            source: "design".into(),
            target: "build".into(),
            link_type: LinkType::Fs,
            lag: 0.0,
            lag_unit: None,
        }],
        ..Default::default()
    };

    let mut project = Project::default();
    let result = project.solve(Some(data), &SolveOptions::default())?;

    for conflict in &result.conflicts {
        if let Conflict::ConstraintViolation { task, violation, .. } = conflict {
            let bound = match violation {
                Violation::EarliestStart => "earliest start",
                Violation::LatestStart => "latest start",
                Violation::EarliestEnd => "earliest end",
                Violation::LatestEnd => "latest end",
            };
            println!("{task}: links cross the {bound}");
        }
    }
    Ok(())
}