use xlog_solve::{
Clause, Literal, Objective, SolveInstance, SolveProof, SolveStats, SolveStatus, Solver,
SolverConfig, SolverState,
};
#[test]
fn test_3sat_satisfiable() {
let clauses: Vec<Clause> = vec![
Clause::new(vec![
Literal::positive(0),
Literal::positive(1),
Literal::negative(2),
]),
Clause::new(vec![
Literal::negative(0),
Literal::positive(2),
Literal::positive(3),
]),
Clause::new(vec![
Literal::positive(1),
Literal::negative(3),
Literal::positive(4),
]),
Clause::new(vec![
Literal::negative(1),
Literal::positive(4),
Literal::negative(5),
]),
Clause::new(vec![
Literal::positive(2),
Literal::positive(5),
Literal::positive(6),
]),
Clause::new(vec![
Literal::negative(2),
Literal::negative(6),
Literal::positive(7),
]),
Clause::new(vec![
Literal::positive(3),
Literal::positive(7),
Literal::negative(8),
]),
Clause::new(vec![
Literal::negative(3),
Literal::positive(8),
Literal::positive(9),
]),
Clause::new(vec![
Literal::positive(4),
Literal::negative(9),
Literal::positive(0),
]),
Clause::new(vec![
Literal::negative(4),
Literal::positive(0),
Literal::negative(1),
]),
];
let instance = SolveInstance::new(10, clauses);
let solver_config = {
let mut config = SolverConfig::default();
config.max_iterations = 5000;
config.learning_rate = 0.15;
config.momentum = 0.9;
config.discretize_threshold = 0.5;
config
};
let solver = Solver::with_config_cpu(solver_config);
let result = solver.solve(instance.clone());
match result.status {
SolveStatus::Sat => {
if let SolveProof::Satisfying { assignment, .. } = &result.proof {
assert!(
instance.is_satisfied(assignment),
"Reported SAT but assignment doesn't satisfy all clauses"
);
} else {
panic!("SAT status but no Satisfying proof");
}
}
SolveStatus::Unknown => {
if let SolveProof::Approximate {
satisfied_clauses,
total_clauses,
assignment,
..
} = &result.proof
{
let ratio = *satisfied_clauses as f64 / *total_clauses as f64;
assert!(
ratio > 0.7,
"Should satisfy at least 70% of clauses, got {:.1}%",
ratio * 100.0
);
let actual_satisfied = instance.count_satisfied(assignment) as u32;
assert_eq!(
actual_satisfied, *satisfied_clauses,
"Reported satisfaction count doesn't match actual"
);
} else {
panic!("Unknown status but no Approximate proof");
}
}
SolveStatus::Unsat => {
panic!("CLS incorrectly reported UNSAT for a satisfiable instance");
}
SolveStatus::Optimal(_) => {
panic!("Unexpected Optimal status for SAT instance");
}
}
println!("Solver stats: {:?}", result.stats);
}
#[test]
fn test_3sat_dense() {
let clauses: Vec<Clause> = vec![
Clause::ternary(
Literal::positive(0),
Literal::positive(1),
Literal::positive(2),
),
Clause::ternary(
Literal::negative(0),
Literal::positive(1),
Literal::positive(3),
),
Clause::ternary(
Literal::positive(0),
Literal::negative(1),
Literal::positive(4),
),
Clause::ternary(
Literal::negative(0),
Literal::negative(1),
Literal::positive(2),
),
Clause::ternary(
Literal::positive(1),
Literal::positive(2),
Literal::negative(3),
),
Clause::ternary(
Literal::negative(1),
Literal::positive(2),
Literal::positive(4),
),
Clause::ternary(
Literal::positive(0),
Literal::negative(2),
Literal::positive(3),
),
Clause::ternary(
Literal::negative(0),
Literal::negative(2),
Literal::negative(4),
),
Clause::ternary(
Literal::positive(2),
Literal::positive(3),
Literal::positive(4),
),
Clause::ternary(
Literal::negative(2),
Literal::negative(3),
Literal::positive(0),
),
Clause::ternary(
Literal::positive(3),
Literal::negative(4),
Literal::positive(1),
),
Clause::ternary(
Literal::negative(3),
Literal::positive(4),
Literal::negative(0),
),
Clause::ternary(
Literal::positive(0),
Literal::positive(3),
Literal::negative(4),
),
Clause::ternary(
Literal::negative(1),
Literal::negative(3),
Literal::negative(4),
),
Clause::ternary(
Literal::positive(1),
Literal::positive(4),
Literal::negative(2),
),
];
let instance = SolveInstance::new(5, clauses);
let solver = Solver::with_config_cpu(SolverConfig::thorough());
let result = solver.solve(instance.clone());
match result.status {
SolveStatus::Sat => {
if let Some(assignment) = result.assignment() {
assert!(instance.is_satisfied(assignment));
}
}
SolveStatus::Unknown => {
if let SolveProof::Approximate {
satisfied_clauses,
total_clauses,
..
} = result.proof
{
let ratio = satisfied_clauses as f64 / total_clauses as f64;
assert!(ratio > 0.5, "Should satisfy at least 50% of dense clauses");
}
}
_ => {}
}
}
#[test]
fn test_pigeonhole_unsat_2_1() {
let instance = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0)]), Clause::new(vec![Literal::positive(1)]), Clause::new(vec![Literal::negative(0), Literal::negative(1)]),
],
);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
assert!(
matches!(result.status, SolveStatus::Unsat | SolveStatus::Unknown),
"Pigeonhole should be UNSAT or Unknown, got {:?}",
result.status
);
if result.status == SolveStatus::Unknown {
if let SolveProof::Approximate {
satisfied_clauses,
total_clauses,
..
} = result.proof
{
assert!(
satisfied_clauses < total_clauses,
"Should not satisfy all clauses in UNSAT instance"
);
}
}
}
#[test]
fn test_pigeonhole_unsat_3_2() {
let clauses = vec![
Clause::new(vec![Literal::positive(0), Literal::positive(1)]), Clause::new(vec![Literal::positive(2), Literal::positive(3)]), Clause::new(vec![Literal::positive(4), Literal::positive(5)]), Clause::new(vec![Literal::negative(0), Literal::negative(2)]),
Clause::new(vec![Literal::negative(0), Literal::negative(4)]),
Clause::new(vec![Literal::negative(2), Literal::negative(4)]),
Clause::new(vec![Literal::negative(1), Literal::negative(3)]),
Clause::new(vec![Literal::negative(1), Literal::negative(5)]),
Clause::new(vec![Literal::negative(3), Literal::negative(5)]),
];
let instance = SolveInstance::new(6, clauses);
let solver = Solver::new_cpu();
let result = solver.solve(instance);
assert!(
matches!(result.status, SolveStatus::Unsat | SolveStatus::Unknown),
"3-2 Pigeonhole should be UNSAT or Unknown"
);
}
#[test]
fn test_graph_coloring_triangle_3colors() {
let mut clauses = Vec::new();
clauses.push(Clause::ternary(
Literal::positive(0),
Literal::positive(1),
Literal::positive(2),
));
clauses.push(Clause::ternary(
Literal::positive(3),
Literal::positive(4),
Literal::positive(5),
));
clauses.push(Clause::ternary(
Literal::positive(6),
Literal::positive(7),
Literal::positive(8),
));
for v in 0..3 {
let base = v * 3;
clauses.push(Clause::binary(
Literal::negative(base),
Literal::negative(base + 1),
));
clauses.push(Clause::binary(
Literal::negative(base),
Literal::negative(base + 2),
));
clauses.push(Clause::binary(
Literal::negative(base + 1),
Literal::negative(base + 2),
));
}
for k in 0..3u32 {
clauses.push(Clause::binary(
Literal::negative(k),
Literal::negative(3 + k),
));
}
for k in 0..3u32 {
clauses.push(Clause::binary(
Literal::negative(3 + k),
Literal::negative(6 + k),
));
}
for k in 0..3u32 {
clauses.push(Clause::binary(
Literal::negative(k),
Literal::negative(6 + k),
));
}
let instance = SolveInstance::new(9, clauses);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
match result.status {
SolveStatus::Sat => {
if let Some(assignment) = result.assignment() {
assert!(
instance.is_satisfied(assignment),
"Coloring assignment should satisfy all constraints"
);
}
}
SolveStatus::Unknown => {
if let SolveProof::Approximate {
satisfied_clauses,
total_clauses,
..
} = result.proof
{
let ratio = satisfied_clauses as f64 / total_clauses as f64;
assert!(
ratio > 0.8,
"Should satisfy at least 80% of graph coloring clauses"
);
}
}
_ => panic!("Unexpected status for 3-colorable graph"),
}
}
#[test]
fn test_graph_coloring_triangle_2colors_unsat() {
let clauses = vec![
Clause::binary(Literal::positive(0), Literal::positive(1)),
Clause::binary(Literal::positive(2), Literal::positive(3)),
Clause::binary(Literal::positive(4), Literal::positive(5)),
Clause::binary(Literal::negative(0), Literal::negative(1)),
Clause::binary(Literal::negative(2), Literal::negative(3)),
Clause::binary(Literal::negative(4), Literal::negative(5)),
Clause::binary(Literal::negative(0), Literal::negative(2)),
Clause::binary(Literal::negative(1), Literal::negative(3)),
Clause::binary(Literal::negative(2), Literal::negative(4)),
Clause::binary(Literal::negative(3), Literal::negative(5)),
Clause::binary(Literal::negative(0), Literal::negative(4)),
Clause::binary(Literal::negative(1), Literal::negative(5)),
];
let instance = SolveInstance::new(6, clauses);
let solver = Solver::new_cpu();
let result = solver.solve(instance);
assert!(
matches!(result.status, SolveStatus::Unsat | SolveStatus::Unknown),
"Triangle 2-coloring should be UNSAT or Unknown"
);
}
#[test]
fn test_solver_determinism() {
let instance = SolveInstance::new(
5,
vec![
Clause::new(vec![Literal::positive(0), Literal::positive(1)]),
Clause::new(vec![Literal::negative(0), Literal::positive(2)]),
Clause::new(vec![
Literal::positive(1),
Literal::negative(2),
Literal::positive(3),
]),
Clause::new(vec![
Literal::negative(1),
Literal::positive(3),
Literal::positive(4),
]),
Clause::new(vec![Literal::negative(3), Literal::negative(4)]),
],
);
let solver_config = {
let mut config = SolverConfig::default();
config.max_iterations = 1000;
config.learning_rate = 0.1;
config.momentum = 0.9;
config.discretize_threshold = 0.5;
config
};
let solver = Solver::with_config_cpu(solver_config);
let result1 = solver.solve(instance.clone());
let result2 = solver.solve(instance.clone());
let result3 = solver.solve(instance.clone());
assert_eq!(
result1.status, result2.status,
"Solver should produce same status on repeated runs"
);
assert_eq!(
result2.status, result3.status,
"Solver should produce same status on repeated runs"
);
assert_eq!(
result1.assignment(),
result2.assignment(),
"Solver should produce same assignment on repeated runs"
);
assert_eq!(
result2.assignment(),
result3.assignment(),
"Solver should produce same assignment on repeated runs"
);
assert_eq!(
result1.stats.iterations, result2.stats.iterations,
"Solver should use same number of iterations on repeated runs"
);
}
#[test]
fn test_solver_state_deterministic_init() {
let state1 = SolverState::new(10);
let state2 = SolverState::new(10);
assert_eq!(
state1.assignments, state2.assignments,
"State initialization should be deterministic"
);
assert_eq!(state1.velocities, state2.velocities);
assert_eq!(state1.gradients, state2.gradients);
}
#[test]
fn test_solver_config_effects() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::negative(0), Literal::positive(1)]),
Clause::new(vec![Literal::negative(1), Literal::positive(2)]),
],
);
let fast_config = {
let mut config = SolverConfig::default();
config.max_iterations = 100;
config.learning_rate = 0.3;
config.momentum = 0.8;
config.discretize_threshold = 0.5;
config
};
let fast_solver = Solver::with_config_cpu(fast_config);
let fast_result = fast_solver.solve(instance.clone());
let thorough_config = SolverConfig::thorough();
let thorough_solver = Solver::with_config_cpu(thorough_config);
let thorough_result = thorough_solver.solve(instance.clone());
assert!(
matches!(fast_result.status, SolveStatus::Sat | SolveStatus::Unknown),
"Fast solver should complete"
);
assert!(
matches!(
thorough_result.status,
SolveStatus::Sat | SolveStatus::Unknown
),
"Thorough solver should complete"
);
assert!(
fast_result.stats.iterations <= thorough_config.max_iterations,
"Fast config should not exceed its max iterations"
);
}
#[test]
fn test_discretize_threshold_effects() {
let instance = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0), Literal::positive(1)]),
Clause::new(vec![Literal::negative(0), Literal::negative(1)]),
],
);
let low_threshold_config = {
let mut config = SolverConfig::default();
config.discretize_threshold = 0.3;
config
};
let low_solver = Solver::with_config_cpu(low_threshold_config);
let low_result = low_solver.solve(instance.clone());
let high_threshold_config = {
let mut config = SolverConfig::default();
config.discretize_threshold = 0.7;
config
};
let high_solver = Solver::with_config_cpu(high_threshold_config);
let high_result = high_solver.solve(instance.clone());
for result in [low_result, high_result] {
match result.status {
SolveStatus::Sat => {
if let Some(assignment) = result.assignment() {
assert!(instance.is_satisfied(assignment));
}
}
SolveStatus::Unknown => {
}
_ => panic!("Unexpected status"),
}
}
}
#[test]
fn test_learning_rate_effects() {
let instance = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
],
);
let slow_config = {
let mut config = SolverConfig::default();
config.learning_rate = 0.01;
config.max_iterations = 10000;
config
};
let slow_solver = Solver::with_config_cpu(slow_config);
let slow_result = slow_solver.solve(instance.clone());
let normal_config = {
let mut config = SolverConfig::default();
config.learning_rate = 0.1;
config.max_iterations = 10000;
config
};
let normal_solver = Solver::with_config_cpu(normal_config);
let normal_result = normal_solver.solve(instance.clone());
assert!(
slow_result.is_sat() || slow_result.status == SolveStatus::Unknown,
"Slow learner should still work"
);
assert!(
normal_result.is_sat() || normal_result.status == SolveStatus::Unknown,
"Normal learner should still work"
);
if slow_result.is_sat() && normal_result.is_sat() {
}
}
#[test]
fn test_solver_large_instance() {
let mut clauses = Vec::new();
for i in 0..100u32 {
let v1 = i % 50;
let v2 = (i * 7) % 50;
let v3 = (i * 13) % 50;
clauses.push(Clause::ternary(
Literal::new(v1, i % 2 == 0),
Literal::new(v2, i % 3 == 0),
Literal::new(v3, i % 5 == 0),
));
}
let instance = SolveInstance::new(50, clauses);
let solver_config = {
let mut config = SolverConfig::default();
config.max_iterations = 10000;
config
};
let solver = Solver::with_config_cpu(solver_config);
let result = solver.solve(instance.clone());
match result.status {
SolveStatus::Sat => {
if let Some(assignment) = result.assignment() {
assert!(instance.is_satisfied(assignment));
}
}
SolveStatus::Unknown => {
if let SolveProof::Approximate {
satisfied_clauses,
total_clauses,
..
} = result.proof
{
let ratio = satisfied_clauses as f64 / total_clauses as f64;
assert!(ratio > 0.5, "Should satisfy at least 50% of clauses");
}
}
_ => {}
}
assert!(
result.stats.iterations > 0,
"Should have performed iterations"
);
}
#[test]
fn test_solver_underconstrained() {
let clauses: Vec<Clause> = (0..10)
.map(|i| {
Clause::ternary(
Literal::positive(i * 10),
Literal::positive(i * 10 + 1),
Literal::positive(i * 10 + 2),
)
})
.collect();
let instance = SolveInstance::new(100, clauses);
let solver = Solver::with_config_cpu(SolverConfig::fast());
let result = solver.solve(instance.clone());
match result.status {
SolveStatus::Sat => {
if let Some(assignment) = result.assignment() {
assert_eq!(
assignment.len(),
100,
"Assignment should have 100 variables"
);
assert!(instance.is_satisfied(assignment));
}
}
SolveStatus::Unknown => {
if let SolveProof::Approximate {
satisfied_clauses,
total_clauses,
..
} = result.proof
{
assert!(
satisfied_clauses >= total_clauses - 1,
"Underconstrained should satisfy almost all clauses"
);
}
}
_ => panic!("Unexpected status for underconstrained instance"),
}
}
#[test]
fn test_maxsat_weighted() {
let instance = SolveInstance::with_weights(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::negative(0)]),
Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
],
vec![10.0, 1.0, 5.0],
);
assert_eq!(instance.objective, Objective::MaxSat);
assert_eq!(instance.total_weight(), 16.0);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
if let Some(assignment) = result.assignment() {
let weighted_sat = instance.weighted_satisfaction(assignment);
assert!(
weighted_sat >= 10.0,
"Should satisfy at least the high-weight clause"
);
}
}
#[test]
fn test_satisfaction_ratio() {
let instance = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
Clause::new(vec![Literal::negative(0)]),
Clause::new(vec![Literal::negative(1)]),
],
);
let assignment1 = vec![true, true];
let assignment2 = vec![false, false];
let assignment3 = vec![true, false];
assert_eq!(instance.count_satisfied(&assignment1), 2);
assert_eq!(instance.count_satisfied(&assignment2), 2);
assert_eq!(instance.count_satisfied(&assignment3), 2);
let ratio = instance.satisfaction_ratio(&assignment1);
assert!(
(ratio - 0.5).abs() < 0.001,
"Satisfaction ratio should be 0.5"
);
}
#[test]
fn test_proof_checksum_verification() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
Clause::new(vec![Literal::positive(2)]),
],
);
let solver = Solver::new_cpu();
let result = solver.solve(instance);
if result.is_sat() {
assert_eq!(
result.verify_proof(),
Some(true),
"Proof checksum should be valid"
);
if let SolveProof::Satisfying { checksum, .. } = &result.proof {
assert_ne!(*checksum, 0, "Checksum should be non-zero");
}
}
}
#[test]
fn test_proof_checksum_uniqueness() {
use xlog_solve::compute_checksum;
let checksum1 = compute_checksum(&[true, false, true]);
let checksum2 = compute_checksum(&[false, true, false]);
let checksum3 = compute_checksum(&[true, true, true]);
assert_ne!(
checksum1, checksum2,
"Different assignments should have different checksums"
);
assert_ne!(
checksum2, checksum3,
"Different assignments should have different checksums"
);
assert_ne!(
checksum1, checksum3,
"Different assignments should have different checksums"
);
}
#[test]
fn test_solver_statistics() {
let instance = SolveInstance::new(
5,
vec![
Clause::new(vec![Literal::positive(0), Literal::positive(1)]),
Clause::new(vec![Literal::negative(1), Literal::positive(2)]),
Clause::new(vec![Literal::negative(2), Literal::positive(3)]),
Clause::new(vec![Literal::negative(3), Literal::positive(4)]),
],
);
let config = {
let mut config = SolverConfig::default();
config.max_iterations = 500;
config
};
let solver = Solver::with_config_cpu(config);
let result = solver.solve(instance);
assert!(
result.stats.iterations > 0,
"Should have performed iterations"
);
assert!(
result.stats.iterations <= 500,
"Should not exceed max iterations"
);
}
#[test]
fn test_stats_helpers() {
let stats = SolveStats {
iterations: 1000,
duration_us: 2_500_000, peak_memory: 1_048_576, };
assert_eq!(stats.duration_ms(), 2500);
assert!((stats.duration_secs() - 2.5).abs() < 0.001);
assert!((stats.iterations_per_sec() - 400.0).abs() < 0.1);
}
#[test]
fn test_empty_instance() {
let instance = SolveInstance::new(0, vec![]);
let solver = Solver::new_cpu();
let result = solver.solve(instance);
assert!(result.is_sat(), "Empty instance should be SAT");
}
#[test]
fn test_empty_clause_instance() {
let instance = SolveInstance::new(0, vec![Clause::new(vec![])]);
let solver = Solver::new_cpu();
let result = solver.solve(instance);
assert!(
!result.is_sat(),
"Instance with empty clause should not be SAT"
);
}
#[test]
fn test_minimal_sat() {
let instance = SolveInstance::new(1, vec![Clause::new(vec![Literal::positive(0)])]);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
assert!(result.is_sat(), "Single positive literal should be SAT");
if let Some(assignment) = result.assignment() {
assert!(assignment[0], "Variable should be true");
assert!(instance.is_satisfied(assignment));
}
}
#[test]
fn test_minimal_unsat() {
let instance = SolveInstance::new(
1,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::negative(0)]),
],
);
let solver = Solver::new_cpu();
let result = solver.solve(instance);
assert!(
matches!(result.status, SolveStatus::Unsat | SolveStatus::Unknown),
"Contradictory clauses should be UNSAT or Unknown"
);
}
#[test]
fn test_tautological_clause() {
let instance = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0), Literal::negative(0)]), Clause::new(vec![Literal::positive(1)]), ],
);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
assert!(
result.is_sat(),
"Tautological clause should not prevent SAT"
);
if let Some(assignment) = result.assignment() {
assert!(instance.is_satisfied(assignment));
}
}
#[test]
fn test_instance_construction() {
let mut instance = SolveInstance::new(
3,
vec![Clause::new(vec![
Literal::positive(0),
Literal::positive(1),
])],
);
assert_eq!(instance.num_vars, 3);
assert_eq!(instance.num_clauses(), 1);
assert!(instance.validate());
instance.add_clause(Clause::new(vec![Literal::positive(2)]));
assert_eq!(instance.num_clauses(), 2);
instance.add_weighted_clause(Clause::new(vec![Literal::negative(0)]), 2.5);
assert_eq!(instance.num_clauses(), 3);
assert!(instance.weights.is_some());
}
#[test]
fn test_literal_dimacs_roundtrip() {
for var in 0..10 {
let pos = Literal::positive(var);
let neg = Literal::negative(var);
let pos_dimacs = pos.to_dimacs();
let neg_dimacs = neg.to_dimacs();
assert!(pos_dimacs > 0);
assert!(neg_dimacs < 0);
let pos_back = Literal::from_dimacs(pos_dimacs);
let neg_back = Literal::from_dimacs(neg_dimacs);
assert_eq!(pos, pos_back);
assert_eq!(neg, neg_back);
}
}
#[test]
fn test_literal_packed_roundtrip() {
for var in 0..100 {
let pos = Literal::positive(var);
let neg = Literal::negative(var);
let pos_packed = pos.to_packed();
let neg_packed = neg.to_packed();
let pos_back = Literal::from_packed(pos_packed);
let neg_back = Literal::from_packed(neg_packed);
assert_eq!(pos, pos_back);
assert_eq!(neg, neg_back);
}
}
#[test]
fn test_implication_chain() {
let n = 10;
let mut clauses = Vec::new();
clauses.push(Clause::unit(Literal::positive(0)));
for i in 0..n - 1 {
clauses.push(Clause::binary(
Literal::negative(i as u32),
Literal::positive((i + 1) as u32),
));
}
let instance = SolveInstance::new(n as u32, clauses);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
assert!(result.is_sat(), "Implication chain should be SAT");
if let Some(assignment) = result.assignment() {
for (i, value) in assignment.iter().enumerate().take(n) {
assert!(*value, "Variable {} should be true due to implications", i);
}
assert!(instance.is_satisfied(assignment));
}
}
#[test]
fn test_xor_chain() {
let mut clauses = Vec::new();
for i in 0..3u32 {
let a = i;
let b = i + 1;
clauses.push(Clause::binary(Literal::positive(a), Literal::positive(b)));
clauses.push(Clause::binary(Literal::negative(a), Literal::negative(b)));
}
clauses.push(Clause::unit(Literal::positive(0)));
let instance = SolveInstance::new(4, clauses);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
assert!(result.is_sat(), "XOR chain should be SAT");
if let Some(assignment) = result.assignment() {
assert!(assignment[0]);
assert!(!assignment[1]);
assert!(assignment[2]);
assert!(!assignment[3]);
assert!(instance.is_satisfied(assignment));
}
}
#[test]
fn test_at_most_one() {
let n = 5;
let mut clauses = Vec::new();
clauses.push(Clause::new((0..n as u32).map(Literal::positive).collect()));
for i in 0..n {
for j in i + 1..n {
clauses.push(Clause::binary(
Literal::negative(i as u32),
Literal::negative(j as u32),
));
}
}
let instance = SolveInstance::new(n as u32, clauses);
let solver = Solver::new_cpu();
let result = solver.solve(instance.clone());
assert!(result.is_sat(), "At-most-one should be SAT");
if let Some(assignment) = result.assignment() {
let count = assignment.iter().filter(|&&x| x).count();
assert_eq!(count, 1, "Exactly one variable should be true");
assert!(instance.is_satisfied(assignment));
}
}