solverforge-solver 0.15.0

Solver engine for SolverForge
Documentation
#[test]
fn default_scalar_assignment_construction_batches_required_fill() {
    let solver_scope = solve_default_assignment(coverage_plan(
        2,
        vec![
            coverage_slot(true, 0, None, &[0, 1]),
            coverage_slot(true, 1, None, &[0, 1]),
        ],
    ));

    let slots = &solver_scope.working_solution().slots;
    assert!(slots.iter().all(|slot| slot.assigned.is_some()));
    assert_eq!(
        solver_scope.current_score().copied(),
        Some(HardSoftScore::of(0, 0))
    );
    assert_eq!(solver_scope.stats().moves_generated, 1);
    assert_eq!(solver_scope.stats().moves_accepted, 1);
    assert_eq!(solver_scope.stats().moves_applied, 1);
    assert_eq!(solver_scope.stats().step_count, 1);
}

#[test]
fn required_assignment_construction_completes_under_expired_time_limit() {
    let solver_scope = solve_default_assignment_with_expired_time_limit(coverage_plan(
        2,
        vec![
            coverage_slot(true, 0, None, &[0, 1]),
            coverage_slot(true, 1, None, &[0, 1]),
            coverage_slot(true, 2, None, &[0, 1]),
        ],
    ));

    assert!(solver_scope
        .working_solution()
        .slots
        .iter()
        .all(|slot| slot.assigned.is_some()));
    assert_eq!(
        solver_scope.current_score().copied(),
        Some(HardSoftScore::of(0, 0))
    );
    assert_eq!(solver_scope.stats().moves_applied, 1);
}

#[test]
fn required_assignment_construction_batches_under_expired_time_limit() {
    let values = (0..80).collect::<Vec<_>>();
    let solver_scope = solve_default_assignment_with_expired_time_limit(coverage_plan(
        values.len(),
        vec![coverage_slot(true, 0, None, &values)],
    ));

    assert!(solver_scope.working_solution().slots[0].assigned.is_some());
    assert_eq!(
        solver_scope.current_score().copied(),
        Some(HardSoftScore::of(0, 0))
    );
    assert_eq!(solver_scope.stats().moves_generated, 1);
    assert_eq!(solver_scope.stats().moves_evaluated, 1);
}

#[test]
fn scalar_assignment_construction_cursor_batches_required_entity_values() {
    let model = assignment_model_with_limits(ScalarGroupLimits {
        group_candidate_limit: Some(4),
        max_moves_per_step: Some(1),
        max_augmenting_depth: Some(3),
        ..ScalarGroupLimits::new()
    });
    let plan = coverage_plan(
        2,
        vec![
            coverage_slot(true, 0, None, &[0, 1]),
            coverage_slot(true, 1, None, &[0, 1]),
        ],
    );
    let crate::builder::ScalarGroupBindingKind::Assignment(assignment) =
        model.scalar_groups()[0].kind
    else {
        panic!("test model should contain an assignment-backed scalar group");
    };
    let options =
        crate::phase::construction::grouped_scalar::ScalarAssignmentMoveOptions::for_construction(
            model.scalar_groups()[0].limits,
        );
    let mut cursor =
        crate::phase::construction::grouped_scalar::ScalarAssignmentMoveCursor::required_construction(
            assignment, plan, options,
        );
    let mut moves = Vec::new();
    while let Some(mov) = cursor.next_move() {
        moves.push(mov);
    }

    assert_eq!(moves.len(), 1);
    for mov in moves {
        assert_eq!(mov.reason(), "scalar_assignment_required");
        assert_eq!(
            mov.edits().len(),
            2,
            "required construction cursor must preserve the fast whole-phase fill"
        );
    }
}

#[test]
fn scalar_assignment_construction_ignores_repair_move_cap() {
    let model = assignment_model_with_limits(ScalarGroupLimits {
        group_candidate_limit: Some(4),
        max_moves_per_step: Some(1),
        max_augmenting_depth: Some(3),
        ..ScalarGroupLimits::new()
    });
    let plan = coverage_plan(
        2,
        vec![
            coverage_slot(true, 0, None, &[0, 1]),
            coverage_slot(true, 1, None, &[0, 1]),
        ],
    );
    let crate::builder::ScalarGroupBindingKind::Assignment(assignment) =
        model.scalar_groups()[0].kind
    else {
        panic!("test model should contain an assignment-backed scalar group");
    };
    let options =
        crate::phase::construction::grouped_scalar::ScalarAssignmentMoveOptions::for_construction(
            model.scalar_groups()[0].limits,
        );
    let mut cursor =
        crate::phase::construction::grouped_scalar::ScalarAssignmentMoveCursor::required_construction(
            assignment, plan, options,
        );
    let mut edited_slots = 0;
    while let Some(mov) = cursor.next_move() {
        edited_slots += mov.edits().len();
    }

    assert_eq!(edited_slots, 2);
}