Coverage for sparkle/solver/verifiers.py: 91%
57 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 10:17 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 10:17 +0000
1"""Classes for verifying solutions."""
3from __future__ import annotations
4from pathlib import Path
5import subprocess
7from sparkle.types import SolverStatus
10class SolutionVerifier:
11 """Solution verifier base class."""
13 def verify(
14 self: SolutionVerifier, instance: Path, output: dict, solver_call: list[str]
15 ) -> SolverStatus:
16 """Verify the solution."""
17 raise NotImplementedError
20class SATVerifier(SolutionVerifier):
21 """Class to handle the SAT verifier."""
23 sat_verifier_path = (
24 Path(__file__).parent.parent / "Components/Sparkle-SAT-verifier/SAT"
25 )
27 def __str__(self: SATVerifier) -> str:
28 """Return the name of the SAT verifier."""
29 return SATVerifier.__name__
31 @staticmethod
32 def verify(instance: Path, output: dict, solver_call: list[str]) -> SolverStatus:
33 """Run a SAT verifier and return its status."""
34 if SolverStatus(output["status"]) == SolverStatus.TIMEOUT:
35 return SolverStatus.TIMEOUT
36 raw_result = Path([s for s in solver_call if s.endswith(".rawres")][0])
37 return SATVerifier.call_sat_raw_result(instance, raw_result)
39 @staticmethod
40 def sat_verify_output(sat_output: str) -> SolverStatus:
41 """Return the status of the SAT verifier.
43 Four statuses are possible: "SAT", "UNSAT", "WRONG", "UNKNOWN"
44 """
45 return_code = int(sat_output.splitlines()[-1])
46 if return_code == 11: # SAT code
47 return SolverStatus.SAT
48 if return_code == 10: # UNSAT code
49 return SolverStatus.UNSAT
50 # Wrong code OR Unknown code
51 if return_code == 0 and "Wrong solution." in sat_output:
52 return SolverStatus.WRONG
53 # Should not occur
54 return SolverStatus.UNKNOWN
56 @staticmethod
57 def call_sat_raw_result(instance: Path, raw_result: Path) -> SolverStatus:
58 """Run a SAT verifier to determine correctness of a result.
60 Args:
61 instance: path to the instance
62 raw_result: path to the result to verify
64 Returns:
65 The status of the solver on the instance
66 """
67 sat_verify = subprocess.run(
68 [SATVerifier.sat_verifier_path, instance, raw_result], capture_output=True
69 )
70 return SATVerifier.sat_verify_output(sat_verify.stdout.decode())
73class SolutionFileVerifier(SolutionVerifier):
74 """Class to handle the file verifier."""
76 def __init__(self: SolutionFileVerifier, csv_file: Path) -> None:
77 """Initialize the verifier by building dictionary from csv.
79 Args:
80 csv_file: path to the csv file. Requires lines to be of the form:
81 instance,objective,solution
82 """
83 self.csv_file = csv_file
84 lines = [
85 line.split(",", maxsplit=2)
86 for line in self.csv_file.read_text().splitlines()
87 ]
88 self.solutions = {
89 instance: (objective, solution) for instance, objective, solution in lines
90 }
92 def __str__(self: SATVerifier) -> str:
93 """Return the name of the SAT verifier."""
94 return SolutionFileVerifier.__name__
96 def verify(
97 self: SolutionFileVerifier,
98 instance: Path,
99 solver_output: dict[str, object],
100 solver_call: list[str],
101 ) -> SolverStatus:
102 """Verify the solution.
104 Args:
105 instance: instance to verify, solution found by instance name as key
106 solver_output: outcome of the solver to verify
107 solver_call: list of solver calls
109 Returns:
110 The status of the solver on the instance
111 """
112 # If the solver ran out of time, we cannot verify a solution
113 if SolverStatus(solver_output["status"]) == SolverStatus.TIMEOUT:
114 return SolverStatus.TIMEOUT
115 instance = instance.name
116 if instance not in self.solutions:
117 return SolverStatus.UNKNOWN
119 objective, solution = self.solutions[instance]
120 if objective not in solver_output:
121 return SolverStatus.UNKNOWN
123 outcome = solver_output[objective]
124 if solution in SolverStatus._value2member_map_: # SAT type status
125 solution, outcome = SolverStatus(solution), SolverStatus(outcome)
126 if solution == SolverStatus.UNKNOWN or solution == outcome:
127 # Verify that the presented solution is correct
128 return SATVerifier.verify(instance, solver_output, solver_call)
129 else:
130 return SolverStatus.WRONG
132 if solution != outcome:
133 return SolverStatus.WRONG
134 return SolverStatus.SUCCESS
137# Define a mapping so we can translate between names and classes
138mapping = {
139 SATVerifier.__name__: SATVerifier,
140 SolutionFileVerifier.__name__: SolutionFileVerifier,
141}