Project::solve#

Schedules a Project and accepts the scheduled dates as its baseline. Call it with Some(data) to load a project and with None to reschedule after apply. Every other read-only call (preview, evaluate, suggest_assignments and the rest) needs a solved project.

For a one-shot run without keeping state, use schedule.

Signature#

pub fn solve(
    &mut self,
    data: Option<ProjectData>,
    options: &SolveOptions,
) -> Result<Arc<ScheduleResult>, Error>

Takes &mut self. Synchronous and CPU-only.

Parameters#

Name Type Description
data Option<ProjectData> Replacement data, or None to schedule the current state
options &SolveOptions Subtree, analytics and reporting switches

Behavior#

  • Some(data) validates the replacement data, installs it, schedules it and accepts the result. The previous data is discarded. How data is validated or repaired depends on ProjectOptions::validation.
  • None schedules the current state when it has unsolved edits. When the project is already solved with equal options, it returns the cached result without scheduling again. The cache key is the whole SolveOptions value.

Tasks touched by successful apply calls since the last solve appear in updates as direct updates, even when the planner leaves them where the edit put them. A cyclic project returns unchanged dates and keeps those tasks pending.

The result's baseline_id names the accepted baseline. It changes only when the data or the dates change, so solving an unchanged state keeps it.

Returns#

The ScheduleResult behind an Arc, shared with the project's cache.

Errors#

Error When
Error::Input Some(data) fails validation. The previous state and cached result are kept.
Error::Project(ProjectError::NeedsResync) None after an apply rejected an operation and no solve(Some(data)) followed

Example#

Load a project, commit an edit and reschedule it.

use plen_core::{
    DateTime, Patch, Project, ProjectData, SchedulingChanges, SolveOptions, TaskData, TaskUpdate,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 8, 10, 8, 0).ok_or("invalid date")?;
    let mut task = TaskData::new("a");
    task.start_date = Some(monday);
    task.duration = Some(2.0);
    let data = ProjectData {
        tasks: vec![task],
        ..Default::default()
    };

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

    // Nothing changed: the cached result comes back with the same token.
    let again = project.solve(None, &options)?;
    assert_eq!(again.baseline_id, loaded.baseline_id);

    let mut update = TaskUpdate::new("a");
    update.duration = Patch::Value(3.0);
    let mut changes = SchedulingChanges::default();
    changes.tasks.update.push(update);
    project.apply(&changes)?;

    let result = project.solve(None, &options)?;
    for update in &result.updates {
        if let (Some(start), Some(end)) = (update.start_date, update.end_date) {
            println!("task {}: {start} .. {end}", update.id); // task a: 2026-08-10 08:00 .. 2026-08-13 08:00
        }
    }
    Ok(())
}