solverforge 0.19.3

SolverForge - A constraint solver in Rust
Documentation
use solverforge::__internal::PlanningSolution as PlanningSolutionTrait;
use solverforge::{
    CandidateTraceConfig, CandidateTraceExternalDigest, QualifiedCandidateTraceRunProvenance,
    SoftScore, Solvable, SolverEvent, SolverManager, SolverRuntime, SolverTelemetry,
    SolverTelemetryDetail, SolverTerminalReason,
};

#[derive(Clone, Debug)]
struct ManualRuntimeSolution {
    value: i64,
    score: Option<SoftScore>,
}

impl PlanningSolutionTrait for ManualRuntimeSolution {
    type Score = SoftScore;

    fn score(&self) -> Option<Self::Score> {
        self.score
    }

    fn set_score(&mut self, score: Option<Self::Score>) {
        self.score = score;
    }
}

impl Solvable for ManualRuntimeSolution {
    fn solve(
        mut self,
        runtime: SolverRuntime<Self>,
        _provenance: Option<QualifiedCandidateTraceRunProvenance>,
    ) {
        let score = SoftScore::of(self.value);
        self.set_score(Some(score));

        runtime.emit_best_solution(self.clone(), Some(score), score, SolverTelemetry::default());
        runtime.emit_progress(Some(score), Some(score), SolverTelemetry::default());
        runtime.emit_completed(
            self,
            Some(score),
            score,
            SolverTelemetry::default(),
            SolverTerminalReason::Completed,
        );
    }
}

#[test]
fn candidate_trace_configuration_and_provenance_are_facade_root_types() {
    let config = CandidateTraceConfig::new(std::num::NonZeroUsize::new(4).unwrap());
    assert_eq!(config.max_entries.get(), 4);

    let digest = |byte| CandidateTraceExternalDigest::sha256([byte; 32]);
    let provenance = QualifiedCandidateTraceRunProvenance::externally_attested(
        digest(1),
        digest(2),
        digest(3),
        digest(4),
        digest(5),
        "facade-only-test",
    )
    .expect("complete external provenance qualifies");
    assert_eq!(provenance.input_provenance().schema_digest, digest(1),);

    let _detail: Option<SolverTelemetryDetail<SoftScore>> = None;
}

#[test]
fn manual_solvable_impl_can_emit_public_runtime_events() {
    static MANAGER: SolverManager<ManualRuntimeSolution> = SolverManager::new();

    let (job_id, mut receiver) = MANAGER
        .solve(ManualRuntimeSolution {
            value: 9,
            score: None,
        })
        .expect("job should start");

    match receiver.blocking_recv().expect("best solution event") {
        SolverEvent::BestSolution { metadata, solution } => {
            assert_eq!(metadata.current_score, Some(SoftScore::of(9)));
            assert_eq!(solution.score(), Some(SoftScore::of(9)));
        }
        other => panic!("unexpected event: {other:?}"),
    }

    match receiver.blocking_recv().expect("progress event") {
        SolverEvent::Progress { metadata } => {
            assert_eq!(metadata.best_score, Some(SoftScore::of(9)));
        }
        other => panic!("unexpected event: {other:?}"),
    }

    match receiver.blocking_recv().expect("completed event") {
        SolverEvent::Completed { metadata, solution } => {
            assert_eq!(
                metadata.terminal_reason,
                Some(SolverTerminalReason::Completed)
            );
            assert_eq!(solution.score(), Some(SoftScore::of(9)));
        }
        other => panic!("unexpected event: {other:?}"),
    }

    MANAGER.delete(job_id).expect("delete completed job");
}