[Canonical page](<https://docs.dhtmlx.com/planning-engine/net/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/net/api/core/project/project.md>). The project isn't 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/net/api/project-apply.md>).

## Signature

```csharp
public EvaluateResult Evaluate(IReadOnlyList<Candidate> candidates, EvaluateOptions options)
```

Synchronous and CPU-only. Reads the project without changing it. [`Project`](<https://docs.dhtmlx.com/planning-engine/net/api/core/project/project.md>) isn't thread-safe, and `Evaluate` with `ClearFirst` updates the project's internal copy-on-write flags, so don't call it on one instance from several threads at once.

## Parameters

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

## Behavior

Each candidate runs on a disposable copy of the reference state. The reference state is the baseline, or its cleared copy with `options.ClearFirst`:

1. [`Apply`](<https://docs.dhtmlx.com/planning-engine/net/api/project-apply.md>) the candidate's changes.
2. [`Solve(null, …)`](<https://docs.dhtmlx.com/planning-engine/net/api/project-solve.md>) and measure the scheduled stage.
3. With `options.Level` set, [`Level`](<https://docs.dhtmlx.com/planning-engine/net/api/project-level.md>) with those options and measure the leveled stage. A cyclic scheduled stage skips this step.

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/net/api/core/evaluate/evaluate-result.md>) with the baseline metrics and one result per candidate, in input order.

## Errors

| Error | When |
|---|---|
| [`ProjectException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/project-exception.md>) `NoBaseline` | The project is unsolved, including after `Apply` |
| `ProjectException` `StaleBaseline` | `options.BaselineId` or any candidate's `Changes.BaselineId` doesn't name the current baseline |
| [`InputException`](<https://docs.dhtmlx.com/planning-engine/net/api/core/errors/input-exception.md>) | An unknown task in `options.Focus` (`Path == "focus[i]"`), or `options.Level` is invalid for the baseline |
| `ArgumentNullException` | `candidates` or `options` is `null` |

## EvaluateWith

```csharp
public EvaluateResult EvaluateWith(IReadOnlyList<Candidate> candidates, EvaluateOptions options, Execution execution)
```

Same as `Evaluate`, with a choice of [`Execution`](<https://docs.dhtmlx.com/planning-engine/net/api/core/evaluate/execution.md>). `Execution.Parallel(threads)` scores candidates on thread-pool workers, `threads` of them at most (`null` uses `Environment.ProcessorCount`). The call still blocks until every candidate is done. Peak memory is one candidate copy per worker.

The results are identical to `Evaluate` and keep input order. A call-level error is the first one in input order, and later candidates may already have run. Errors are the same as for `Evaluate`.

## Example

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

```csharp
using Plen;

var monday = Moment.FromYmdHm(2026, 8, 10, 8, 0) ?? throw new InvalidOperationException("invalid date");
var data = new ProjectData
{
    Tasks =
    [
        new TaskData("A") { StartDate = monday, Duration = 2.0 },
        new TaskData("B") { StartDate = monday, Duration = 2.0 },
    ],
    Resources = [new ResourceData { Id = "dev1" }, new ResourceData { Id = "dev2" }],
    Assignments =
    [
        new AssignmentData { Id = "aA", Task = "A", Resource = "dev1", Value = 1.0 },
        new AssignmentData { Id = "aB", Task = "B", Resource = "dev1", Value = 1.0 },
    ],
};
var project = new Project();
project.Solve(data, new SolveOptions());

var toDev2 = new SchedulingChanges();
toDev2.Assignments.Update.Add(new AssignmentUpdate("aB") { Resource = Patch<Id>.Of("dev2") });

Candidate[] candidates =
[
    new() { Id = "stay", Changes = new SchedulingChanges() },
    new() { Id = "dev2", Changes = toDev2 },
];
var result = project.Evaluate(candidates, new EvaluateOptions());

foreach (var candidate in result.Candidates)
    Console.WriteLine($"{candidate.Id}: cost {candidate.Cost}"); // stay: cost 2, dev2: cost 0
```

## Related

- [`Candidate`](<https://docs.dhtmlx.com/planning-engine/net/api/core/evaluate/candidate.md>) - one change set to score
- [`EvaluateOptions`](<https://docs.dhtmlx.com/planning-engine/net/api/core/evaluate/evaluate-options.md>) - the options of the call
- [`EvaluateResult`](<https://docs.dhtmlx.com/planning-engine/net/api/core/evaluate/evaluate-result.md>) - the returned scores
- [Evaluating alternatives](<https://docs.dhtmlx.com/planning-engine/net/resources-and-assignment/evaluating-alternatives.md>) - comparing candidates and committing the winner
- [Call from async code](<https://docs.dhtmlx.com/planning-engine/net/how-to/call-from-async-code.md>) - running `Evaluate` off the calling thread
