tiny.smt.proof
Defined in tiny.smt.
A plain-text format for the evidence behind one unsatisfiable answer, with a writer and a reader.
API (3)
Actions
Public operations.
parse: Returns asat.ProofArtifactthat allocates withallocatorand holds the records of the proof textsource, so code reads stored evidence and then checks the result withvalid.write: Writesartifactin the proof format: the format line, the header, onebrecord per starting clause, onearecord, and onerrecord per step.
Types and contracts
Public types and contracts.
ParseError: Errorsparsereturns for a malformed text, besides the allocator's, so code that reports why a proof text was refused switches on them.
Source
Source: lib/smt/src/proof.zig
zig
//! A plain-text format for the evidence behind one unsatisfiable answer, with a writer and a//! reader. The text writes each literal as a signed variable number counted from 1, as the DIMACS//! CNF clause format does.//!//! A caller that reports an unsatisfiable answer needs to keep its evidence after the solver is//! gone, and to hand it to a checker that does not trust the solver. A reader of such a text needs//! every count and literal checked before a checker walks it. Each proof step is a clause that//! follows from the clauses before it by unit propagation, so the evidence has to carry the//! starting clauses, the assumptions and the steps in order.//!//! The text holds the clauses, assumptions and steps behind one unsatisfiable answer (*proof//! artifact*, `sat.ProofArtifact`). The first line names the format and its version, 1. The header//! line `p rup V C A S` gives the variable count and the numbers of starting clauses, assumptions//! and steps. After the header, each line holds one letter, a list of literals, then 0 (*record*).//! A `b` record is a starting clause, the single `a` record lists the assumptions, and each `r`//! record is one step. The starting clauses are every clause the solver held when the solve began,//! learned clauses it kept included. The reader checks the format and the counts, and//! `sat.ProofArtifact.valid` checks the steps.const std = @import("std");const sat = @import("sat/root.zig");/// Errors `parse` returns for a malformed text, besides the allocator's, so code that reports why a/// proof text was refused switches on them.pub const ParseError = error{ /// The first non-blank line does not name this format and version 1, or the text is empty. InvalidMagic, /// A `p` line whose kind differs from `rup`, whose counts are other than unsigned numbers, or /// that holds other than four counts. InvalidHeader, /// A second `p` line. DuplicateHeader, /// A record comes before any header, or the text has no header. MissingHeader, /// A line that starts with a letter other than `b`, `a` or `r`, a second `a` record, or a token /// after the closing 0 of a record. InvalidRecord, /// A record token other than a 32-bit signed integer. InvalidLiteral, /// A record with no closing 0. UnterminatedRecord, /// The number of `b` records differs from the header's clause count. ClauseCountMismatch, /// The number of literals in the `a` record differs from the header's assumption count. AssumptionCountMismatch, /// The number of `r` records differs from the header's step count. StepCountMismatch, /// A literal names a variable at or above the header's variable count. InvalidVariable,};const magic = "tiny-smt-proof";const version = "1";/// Writes `artifact` in the proof format: the format line, the header, one `b` record per starting/// clause, one `a` record, and one `r` record per step. Code keeps with it the evidence of an/// unsatisfiable answer, after reading the artifact from the solver's `lastProofArtifact`. The call/// always writes the `a` record, as `a 0` when there are no assumptions. The call returns only the/// writer's errors.pub fn write(writer: *std.Io.Writer, artifact: *const sat.ProofArtifact) std.Io.Writer.Error!void { try writer.print("{s} {s}\n", .{ magic, version }); try writer.print("p rup {d} {d} {d} {d}\n", .{ artifact.variable_count, artifact.clauses.items.len, artifact.assumptions.items.len, artifact.steps.items.len, }); for (artifact.clauses.items) |clause| { try writeLiteralRecord(writer, "b", clause.literals); } try writeLiteralRecord(writer, "a", artifact.assumptions.items); for (artifact.steps.items) |step| { try writeLiteralRecord(writer, "r", step.literals); }}/// Returns a `sat.ProofArtifact` that allocates with `allocator` and holds the records of the proof/// text `source`, so code reads stored evidence and then checks the result with `valid`. The caller/// owns the artifact and frees it with `deinit`. Lines are trimmed of spaces, tabs and carriage/// returns, and blank lines are skipped. A record is one line: it cannot span lines, and the format/// has no comment lines. The `a` record is optional, and a text without it has no assumptions. The/// call returns each `ParseError` tag as that tag describes. The literal -2147483648 makes it/// panic. The parser checks the format and the counts, and it does not check that the steps follow.pub fn parse(allocator: std.mem.Allocator, source: []const u8) !sat.ProofArtifact { var artifact = sat.ProofArtifact.init(allocator, 0); errdefer artifact.deinit(); var magic_seen = false; var header_seen = false; var expected_clauses: usize = 0; var expected_assumptions: usize = 0; var expected_steps: usize = 0; var clauses_seen: usize = 0; var assumptions_seen: usize = 0; var steps_seen: usize = 0; var assumption_record_seen = false; var lines = std.mem.splitScalar(u8, source, '\n'); while (lines.next()) |raw_line| { const line = std.mem.trim(u8, raw_line, " \t\r"); if (line.len == 0) continue; var tokens = std.mem.tokenizeAny(u8, line, " \t\r"); const first = tokens.next() orelse continue; if (!magic_seen) { if (!std.mem.eql(u8, first, magic)) return ParseError.InvalidMagic; const found_version = tokens.next() orelse return ParseError.InvalidMagic; if (!std.mem.eql(u8, found_version, version)) return ParseError.InvalidMagic; if (tokens.next() != null) return ParseError.InvalidMagic; magic_seen = true; continue; } if (std.mem.eql(u8, first, "p")) { if (header_seen) return ParseError.DuplicateHeader; const kind = tokens.next() orelse return ParseError.InvalidHeader; if (!std.mem.eql(u8, kind, "rup")) return ParseError.InvalidHeader; artifact.variable_count = try parseHeaderCount(tokens.next() orelse return ParseError.InvalidHeader); expected_clauses = try parseHeaderCount(tokens.next() orelse return ParseError.InvalidHeader); expected_assumptions = try parseHeaderCount(tokens.next() orelse return ParseError.InvalidHeader); expected_steps = try parseHeaderCount(tokens.next() orelse return ParseError.InvalidHeader); if (tokens.next() != null) return ParseError.InvalidHeader; header_seen = true; continue; } if (!header_seen) return ParseError.MissingHeader; if (std.mem.eql(u8, first, "b")) { const literals = try parseLiteralRecord(allocator, artifact.variable_count, &tokens); defer allocator.free(literals); try artifact.appendClause(literals); clauses_seen += 1; } else if (std.mem.eql(u8, first, "a")) { if (assumption_record_seen) return ParseError.InvalidRecord; const literals = try parseLiteralRecord(allocator, artifact.variable_count, &tokens); defer allocator.free(literals); try artifact.appendAssumptions(literals); assumptions_seen += literals.len; assumption_record_seen = true; } else if (std.mem.eql(u8, first, "r")) { const literals = try parseLiteralRecord(allocator, artifact.variable_count, &tokens); defer allocator.free(literals); try artifact.appendStep(literals); steps_seen += 1; } else { return ParseError.InvalidRecord; } } if (!magic_seen) return ParseError.InvalidMagic; if (!header_seen) return ParseError.MissingHeader; if (clauses_seen != expected_clauses) return ParseError.ClauseCountMismatch; if (assumptions_seen != expected_assumptions) return ParseError.AssumptionCountMismatch; if (steps_seen != expected_steps) return ParseError.StepCountMismatch; return artifact;}fn writeLiteralRecord(writer: *std.Io.Writer, prefix: []const u8, literals: []const sat.Literal) std.Io.Writer.Error!void { try writer.writeAll(prefix); for (literals) |literal| { try writer.print(" {d}", .{literal.toDimacs()}); } try writer.writeAll(" 0\n");}fn parseHeaderCount(token: []const u8) !usize { return std.fmt.parseInt(usize, token, 10) catch ParseError.InvalidHeader;}fn parseLiteralRecord(allocator: std.mem.Allocator, variable_count: usize, tokens: anytype) ![]sat.Literal { var literals: std.ArrayList(sat.Literal) = .empty; errdefer literals.deinit(allocator); var terminated = false; while (tokens.next()) |token| { const value = std.fmt.parseInt(i32, token, 10) catch return ParseError.InvalidLiteral; if (value == 0) { terminated = true; if (tokens.next() != null) return ParseError.InvalidRecord; break; } const literal = sat.Literal.fromDimacs(value) catch return ParseError.InvalidLiteral; if (literal.variable() >= variable_count) return ParseError.InvalidVariable; try literals.append(allocator, literal); } if (!terminated) return ParseError.UnterminatedRecord; return literals.toOwnedSlice(allocator);}test "proof artifact format roundtrips valid unsat evidence" { var solver = sat.Solver.init(std.testing.allocator); defer solver.deinit(); const a = try solver.addVariable(); const b = try solver.addVariable(); try solver.addClause(&.{ sat.Literal.positive(a), sat.Literal.positive(b) }); try solver.addClause(&.{ sat.Literal.negative(a), sat.Literal.positive(b) }); try solver.addClause(&.{ sat.Literal.positive(a), sat.Literal.negative(b) }); try solver.addClause(&.{ sat.Literal.negative(a), sat.Literal.negative(b) }); try std.testing.expectEqual(sat.Status.unsat, try solver.solve()); var artifact = (try solver.lastProofArtifact(std.testing.allocator)).?; defer artifact.deinit(); var out: std.Io.Writer.Allocating = .init(std.testing.allocator); defer out.deinit(); try write(&out.writer, &artifact); var parsed = try parse(std.testing.allocator, out.written()); defer parsed.deinit(); try std.testing.expectEqual(artifact.variable_count, parsed.variable_count); try std.testing.expectEqual(artifact.clauses.items.len, parsed.clauses.items.len); try std.testing.expectEqual(artifact.assumptions.items.len, parsed.assumptions.items.len); try std.testing.expectEqual(artifact.steps.items.len, parsed.steps.items.len); try std.testing.expect(try parsed.valid());}test "proof artifact parser rejects mismatched counts" { try std.testing.expectError(ParseError.ClauseCountMismatch, parse(std.testing.allocator, \\tiny-smt-proof 1 \\p rup 1 1 0 1 \\r 0 \\ ));}test "proof artifact parser rejects unknown variables" { try std.testing.expectError(ParseError.InvalidVariable, parse(std.testing.allocator, \\tiny-smt-proof 1 \\p rup 1 0 0 1 \\r 2 0 \\ ));}Source: lib/smt/src/root.zig:98
zig
pub const proof = @import("proof.zig");Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 11 |
| Version | 26.7.0 |
| Revision | daab053ee433 |