[Canonical page](<https://docs.dhtmlx.com/planning-engine/rust/api/project-evaluate/>)
[Full documentation index](<https://docs.dhtmlx.com/planning-engine/llms.txt>)


# Project::evaluate

Scores alternative change sets against the solved baseline of a [`Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md>). The project is never modified. Use it to compare options, such as which resource should take a task, before committing one with [`apply`](<https://docs.dhtmlx.com/planning-engine/rust/api/project-apply.md>).

## Signature

```rust
pub fn evaluate(
    &self,
    candidates: &[Candidate],
    options: &EvaluateOptions,
) -> Result<EvaluateResult, Error>
```

Takes `&self`. Synchronous and CPU-only.

## Parameters

| Name | Type | Description |
|---|---|---|
| `candidates` | `&[`[`Candidate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/candidate.md>)`]` | The change sets to score |
| `options` | `&`[`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-options.md>) | Baseline check, leveling stage, focus tasks and `clear_first` |

## Behavior

Each candidate runs on a disposable copy of the reference state (the baseline, or its cleared copy with `options.clear_first`):

1. `apply` the candidate's changes.
2. `solve(None)` and measure the scheduled stage.
3. With `options.level` set, `level` with those options and measure the leveled stage.

Every candidate starts from the reference state, never from the previous candidate. A failure inside one candidate goes into that candidate's `error` and doesn't fail the call. The result has scores, not dates.

## Returns

An [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-result.md>) with the baseline metrics and one result per candidate, in input order.

## Errors

| Error | When |
|---|---|
| [`Error::Project(ProjectError::NoBaseline)`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/errors/project-error.md>) | The project is unsolved, including after `apply` |
| `Error::Project(ProjectError::StaleBaseline)` | `options.baseline_id` or any candidate's `changes.baseline_id` doesn't name the current baseline |
| [`Error::Input`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/errors/error.md>) | An unknown task in `focus` (path `focus[i]`); `options.level` is invalid for the baseline |

## evaluate_with

```rust
#[cfg(not(target_family = "wasm"))]
pub fn evaluate_with(
    &self,
    candidates: &[Candidate],
    options: &EvaluateOptions,
    execution: Execution,
) -> Result<EvaluateResult, Error>
```

Same as `evaluate`, with a choice of [`Execution`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/execution.md>). `Execution::Parallel` runs candidates on scoped worker threads. The results are identical to `evaluate` and keep input order. A call-level error is the first one in input order. Errors are the same as for `evaluate`.

:::note
`evaluate_with` isn't compiled for wasm targets. Use `evaluate` there.
:::

## Example

Compare leaving the plan as it is with moving task `B` to an idle resource.

```rust
use plen_core::{
    AssignmentData, AssignmentUpdate, Candidate, DateTime, EvaluateOptions, Patch, Project,
    ProjectData, ResourceData, SchedulingChanges, SolveOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let monday = DateTime::from_ymd_hm(2026, 8, 10, 8, 0).ok_or("invalid date")?;
    let task = |id: &str| TaskData {
        start_date: Some(monday),
        duration: Some(2.0),
        ..TaskData::new(id)
    };
    let resource = |id: &str| ResourceData { id: id.into(), capacity: None, calendar_id: None };
    let assign = |id: &str, task: &str| AssignmentData {
        id: Some(id.into()),
        task: task.into(),
        resource: "dev1".into(),
        value: 1.0,
        offset: None,
        duration: None,
    };
    let data = ProjectData {
        tasks: vec![task("A"), task("B")],
        resources: vec![resource("dev1"), resource("dev2")],
        assignments: vec![assign("aA", "A"), assign("aB", "B")],
        ..Default::default()
    };
    let mut project = Project::default();
    project.solve(Some(data), &SolveOptions::default())?;

    let mut moved = AssignmentUpdate::new("aB");
    moved.resource = Patch::Value("dev2".into());
    let mut to_dev2 = SchedulingChanges::default();
    to_dev2.assignments.update.push(moved);

    let candidates = [
        Candidate { id: "stay".into(), changes: SchedulingChanges::default() },
        Candidate { id: "dev2".into(), changes: to_dev2 },
    ];
    let result = project.evaluate(&candidates, &EvaluateOptions::default())?;

    for candidate in &result.candidates {
        println!("{}: cost {:?}", candidate.id, candidate.cost); // stay: 2, dev2: 0
    }
    Ok(())
}
```

## Related

- [`Project`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/project/project.md#evaluate>) - the type and its other methods
- [`Candidate`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/candidate.md>) and [`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-options.md>) - the input
- [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/rust/api/core/evaluate/evaluate-result.md>) - the returned scores
- [Evaluating Alternatives](<https://docs.dhtmlx.com/planning-engine/rust/resources-and-assignment/evaluating-alternatives.md>) - metrics, cost, failed candidates and committing the winner
