tiny.smt.dimacs
Defined in tiny.smt.
Reads and writes Boolean formulas in conjunctive normal form as plain text.
API (3)
Actions
Public operations.
parse: Returns a new solver that allocates withallocatorand holds the variables and clauses of the DIMACS textsource, so code can load a formula written by another tool, then solve the returned solver.write: Writessolveras DIMACS CNF: ap cnf V Cheader, then one line per clause of its literals and a closing 0, so code can hand a solver's formula to another tool.
Types and contracts
Public types and contracts.
ParseError: The errorsparsereturns for a malformed text, besides the allocator's and the solver's, so code that reports why a DIMACS text was refused can switch on them.
Source
Source: lib/smt/src/dimacs.zig
zig
//! Reads and writes Boolean formulas in conjunctive normal form as plain text. The format is DIMACS//! CNF: a `p cnf V C` header gives the variable and clause counts, `c` lines are comments, and each//! clause lists its literals as signed variable numbers, 1-based, ending with 0.//!//! Other SAT tools and benchmark collections exchange formulas as text, so a caller needs to load//! such a file into a solver and to print a solver's clauses for another tool. A text written//! elsewhere may be malformed, or may disagree with its own header about how many clauses it holds.//!//! Variable 1 of the text is the solver's variable 0, and a negative number is the negated literal.//! The reader treats the header's clause count as a check and its variable count as a floor: a//! clause may name a variable past the count, and the solver grows to hold it. The writer prints//! every clause the solver holds, learned clauses the solver kept included, so a text written after//! a solve can hold more clauses than were added.const std = @import("std");const sat = @import("sat/root.zig");/// The errors `parse` returns for a malformed text, besides the allocator's and the solver's, so/// code that reports why a DIMACS text was refused can switch on them. A header count that fails to/// parse as a number returns the error of `std.fmt.parseInt`, which this set leaves out. `parse`/// returns an inferred error set.pub const ParseError = error{ /// A `p` line whose kind differs from `cnf` or that lacks a count. InvalidHeader, /// A clause token other than a 32-bit signed integer, or a last clause missing a closing 0. InvalidLiteral, /// The number of clauses differs from the header's count. ClauseCountMismatch,};/// Returns a new solver that allocates with `allocator` and holds the variables and clauses of the/// DIMACS text `source`, so code can load a formula written by another tool, then solve the/// returned solver. The caller owns the solver and frees it with `deinit`. Lines are trimmed of/// spaces, tabs and carriage returns, and blank lines and lines starting with `c` are skipped. A/// `p cnf V C` line adds V variables and sets C as the expected clause count. The parser accepts a/// text without a header, and a second header adds its variables and replaces the expected count. A/// clause may span lines, and one line may hold more than one clause: each 0 ends one. A lone 0 is/// the empty clause. The call returns `InvalidHeader`, `InvalidLiteral` and `ClauseCountMismatch`/// as their tags describe, and the errors of `std.fmt.parseInt` for a header count that fails to/// parse as a number. The literal -2147483648 makes the call panic.pub fn parse(allocator: std.mem.Allocator, source: []const u8) !sat.Solver { var solver = sat.Solver.init(allocator); errdefer solver.deinit(); var expected_clauses: ?usize = null; var clauses_seen: usize = 0; var clause: std.ArrayList(sat.Literal) = .empty; defer clause.deinit(allocator); 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 or line[0] == 'c') continue; var tokens = std.mem.tokenizeAny(u8, line, " \t\r"); const first = tokens.next() orelse continue; if (std.mem.eql(u8, first, "p")) { const kind = tokens.next() orelse return ParseError.InvalidHeader; if (!std.mem.eql(u8, kind, "cnf")) return ParseError.InvalidHeader; const variable_count = try std.fmt.parseInt(usize, tokens.next() orelse return ParseError.InvalidHeader, 10); expected_clauses = try std.fmt.parseInt(usize, tokens.next() orelse return ParseError.InvalidHeader, 10); var index: usize = 0; while (index < variable_count) : (index += 1) _ = try solver.addVariable(); continue; } var current: ?[]const u8 = first; while (current) |token| { const value = std.fmt.parseInt(i32, token, 10) catch return ParseError.InvalidLiteral; if (value == 0) { try solver.addClause(clause.items); clause.clearRetainingCapacity(); clauses_seen += 1; } else { try clause.append(allocator, try sat.Literal.fromDimacs(value)); } current = tokens.next(); } } if (clause.items.len > 0) return ParseError.InvalidLiteral; if (expected_clauses) |expected| { if (expected != clauses_seen) return ParseError.ClauseCountMismatch; } return solver;}/// Writes `solver` as DIMACS CNF: a `p cnf V C` header, then one line per clause of its literals/// and a closing 0, so code can hand a solver's formula to another tool. An empty clause added to/// the solver appears once, as a line holding only 0, and the header counts it. The function writes/// every clause the solver holds, learned clauses the solver kept included. The call returns only/// the writer's errors.pub fn write(writer: *std.Io.Writer, solver: *const sat.Solver) std.Io.Writer.Error!void { const empty_clauses: usize = @intFromBool(solver.has_empty_clause); try writer.print("p cnf {d} {d}\n", .{ solver.variableCount(), solver.clauseCount() + empty_clauses, }); if (solver.has_empty_clause) { try writer.writeAll("0\n"); } for (solver.clauses.items) |clause| { for (clause.literals) |literal| { try writer.print("{d} ", .{literal.toDimacs()}); } try writer.writeAll("0\n"); }}test "DIMACS writer preserves the empty clause" { var solver = sat.Solver.init(std.testing.allocator); defer solver.deinit(); _ = try solver.addVariable(); try solver.addClause(&.{sat.Literal.positive(0)}); try solver.addClause(&.{}); var buffer: [64]u8 = undefined; var stream = std.Io.Writer.fixed(&buffer); try write(&stream, &solver); const text = stream.buffered(); try std.testing.expect(std.mem.indexOf(u8, text, "p cnf 1 2\n") != null); try std.testing.expect(std.mem.indexOf(u8, text, "\n0\n") != null); var parsed = try parse(std.testing.allocator, text); defer parsed.deinit(); try std.testing.expectEqual(sat.Status.unsat, try parsed.solve());}test "DIMACS parser builds satisfiable formula" { var solver = try parse(std.testing.allocator, \\c example \\p cnf 2 2 \\1 2 0 \\-1 0 \\ ); defer solver.deinit(); try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "DIMACS writer roundtrips clauses" { 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.negative(b) }); var buffer: [256]u8 = undefined; var stream = std.Io.Writer.fixed(&buffer); try write(&stream, &solver); const text = stream.buffered(); try std.testing.expect(std.mem.indexOf(u8, text, "p cnf 2 1\n") != null); try std.testing.expect(std.mem.indexOf(u8, text, "1 -2 0\n") != null);}Source: lib/smt/src/root.zig:97
zig
pub const dimacs = @import("dimacs.zig");Audit
| Definitions | 4 |
|---|---|
| Public names | 4 |
| Members | 3 |
| Version | 26.7.0 |
| Revision | daab053ee433 |