Nothing
# Test the creation of a CnfFormula from CnfClauses
test_that("CnfFormula is created correctly from CnfClauses", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a", "b"))))
clause2 = CnfClause(list(CnfAtom(Y, c("d", "e"))))
formula = CnfFormula(list(clause1, clause2))
expect_s3_class(formula, "CnfFormula")
expect_length(as.list(formula), 2)
clauses = as.list(formula)
expect_true(identical(clauses[[1]], clause1) || identical(clauses[[1]], clause2))
expect_true(identical(clauses[[2]], clause1) || identical(clauses[[2]], clause2))
})
# Test the handling of tautologies and contradictions in CnfFormula
test_that("CnfFormula handles tautologies and contradictions correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
tautology = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b", "c"))))))
contradiction = CnfFormula(list(CnfClause(list(CnfAtom(X, character(0))))))
# Tautology should evaluate to TRUE
expect_true(as.logical(tautology))
# Contradiction should evaluate to FALSE
expect_false(as.logical(contradiction))
# Conjunction of a tautology and a clause should result in the clause
atom = CnfAtom(X, c("a"))
clause = CnfClause(list(atom))
formula = tautology & clause
expect_identical(as.list(formula), list(clause))
# Conjunction of a contradiction with a clause should result in contradiction
formula_ctn = contradiction & clause
expect_s3_class(formula_ctn, "CnfFormula")
expect_false(as.logical(formula_ctn))
})
test_that("CnfClause can be called with CnfClauses", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
atom1 = CnfAtom(X, "a")
atom2 = CnfAtom(Y, "d")
atom3 = CnfAtom(X, "b")
atom4 = CnfAtom(Y, "e")
clause1 = CnfClause(list(atom1))
clause2 = CnfClause(list(atom2))
clause3 = CnfClause(list(atom3))
clause4 = CnfClause(list(atom4))
clause_1_3 = CnfClause(list(atom1, atom3))
clause_2_4 = CnfClause(list(atom2, atom4))
expect_true(all.equal(clause_1_3, CnfClause(list(clause1, clause3))))
expect_true(all.equal(clause_1_3 | clause2, CnfClause(list(clause1, clause3, atom2))))
expect_true(all.equal(CnfClause(list(clause_1_3, clause_2_4)), CnfClause(list(clause1, clause2, clause3, clause4))))
expect_true(all.equal(CnfClause(list(clause_1_3, clause_2_4)), CnfClause(list(atom1, atom2, atom3, atom4))))
})
test_that("CnfFormula can be called with CnfFormulas", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a", "b"))))
clause2 = CnfClause(list(CnfAtom(Y, c("d", "e"))))
clause3 = CnfClause(list(CnfAtom(X, c("b"))))
formula1 = CnfFormula(list(clause1))
formula2 = CnfFormula(list(clause2))
formula3 = CnfFormula(list(clause3))
formula1_2 = CnfFormula(list(clause1, clause2))
formula1_3 = CnfFormula(list(clause1, clause3))
expect_true(all.equal(formula1_2, CnfFormula(list(formula1_2))))
expect_true(all.equal(formula1_2, CnfFormula(list(formula1, formula2))))
expect_true(all.equal(formula1_2 & formula1_3, CnfFormula(list(clause1, clause2, clause3))))
})
# Test conjunction (&) of CnfFormulas
test_that("Conjunction of CnfFormulas works correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a", "b"))))
clause2 = CnfClause(list(CnfAtom(Y, c("d", "e"))))
formula1 = CnfFormula(list(clause1))
formula2 = CnfFormula(list(clause2))
conjunction = formula1 & formula2
expect_s3_class(conjunction, "CnfFormula")
expect_length(as.list(conjunction), 2)
clauses = as.list(conjunction)
expect_true(identical(clauses[[1]], clause1) || identical(clauses[[1]], clause2))
expect_true(identical(clauses[[2]], clause1) || identical(clauses[[2]], clause2))
# Check that conjunction of a tautology with a formula is the formula
tautology = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b", "c"))))))
conjunction_with_tautology = tautology & formula1
expect_identical(as.list(conjunction_with_tautology), list(clause1))
})
# Test disjunction (|) of CnfFormulas
test_that("Disjunction of CnfFormulas works correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a"))))
clause2 = CnfClause(list(CnfAtom(Y, c("d"))))
clause3 = CnfClause(list(CnfAtom(X, c("b"))))
clause4 = CnfClause(list(CnfAtom(Y, c("e"))))
formula1 = CnfFormula(list(clause1, clause2))
formula2 = CnfFormula(list(clause3, clause4))
disjunction = formula1 | formula2
expect_s3_class(disjunction, "CnfFormula")
expect_length(as.list(disjunction), 2)
formula1
formula2
# (X = a & Y = d) | (X = b & Y = e) <=>
# (X = a | X = b) & (Y = d | Y = e) & (X = a | Y = e) & (X = b | Y = d)
## can drop the first two clauses as they are always true whenever the latter two are true
expected_clause_1 = CnfClause(list(CnfAtom(X, "a"), CnfAtom(Y, "e")))
expected_clause_2 = CnfClause(list(CnfAtom(X, "b"), CnfAtom(Y, "d")))
clauses = as.list(disjunction)
expect_true(isTRUE(all.equal(clauses[[1]], expected_clause_1)) || isTRUE(all.equal(clauses[[1]], expected_clause_2)))
expect_true(isTRUE(all.equal(clauses[[1]], expected_clause_2)) || isTRUE(all.equal(clauses[[1]], expected_clause_1)))
# Check that disjunction of a contradiction with a formula is the same formula
contradiction = CnfFormula(list(CnfClause(list(CnfAtom(X, character(0))))))
disjunction_with_contradiction = contradiction | formula1
expect_s3_class(disjunction_with_contradiction, "CnfFormula")
expect_true(
identical(as.list(disjunction_with_contradiction), list(clause1, clause2)) ||
identical(as.list(disjunction_with_contradiction), list(clause2, clause1))
)
})
# Test negation of CnfFormula
test_that("Negation of CnfFormula works correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d"))))
formula = CnfFormula(list(clause))
negated_formula = !formula
expect_s3_class(negated_formula, "CnfFormula")
expect_length(as.list(negated_formula), 2)
expect_true(
identical(as.list(negated_formula), list(CnfClause(list(CnfAtom(X, "c"))), CnfClause(list(CnfAtom(Y, c("e", "f")))))) ||
identical(as.list(negated_formula), list(CnfClause(list(CnfAtom(Y, c("e", "f")))), CnfClause(list(CnfAtom(X, "c")))))
)
# Check that negation of a tautology is a contradiction
tautology = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b", "c"))))))
expect_false(as.logical(!tautology))
# Check that negation of a contradiction is a tautology
contradiction = CnfFormula(list(CnfClause(list(CnfAtom(X, character(0))))))
expect_true(as.logical(!contradiction))
})
# Test complex conjunction and disjunction operations on CnfFormula
test_that("Complex operations on CnfFormula work correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
Z = CnfSymbol(u, "Z", c("g", "h", "i"))
formula1 = (CnfAtom(X, c("a", "b")) | CnfAtom(Y, c("d", "e"))) & CnfAtom(Z, c("g", "h"))
formula2 = !(CnfAtom(X, c("a", "b")) | CnfAtom(Y, c("d", "e")))
expect_s3_class(formula1, "CnfFormula")
expect_s3_class(formula2, "CnfFormula")
# Check that complex formula works correctly with negation and conjunction
negated_formula1 = !formula1
expect_s3_class(negated_formula1, "CnfFormula")
conjunction_formula = formula1 & formula2
expect_s3_class(conjunction_formula, "CnfFormula")
# conjunction_formula is a contradiction, since formula2 is the negation of the
# first part of formula1.
# We don't yet test whether this is obvious enough for the simplification algorithm
# to catch, but negating it should give a tautology that can directly be recognized;
expect_true(!conjunction_formula)
})
# Test print and format methods for CnfFormula
test_that("print and format methods for CnfFormula work correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
formula = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d"))))))
# Test print method
expect_output(print(formula), "CnfFormula:\n \\(X . \\{a, b\\} \\| Y . \\{d\\}\\)|CnfFormula:\n \\(Y . \\{d\\} \\| X . \\{a, b\\}\\)")
# Test format method
expect_equal(format(formula), "CnfFormula(1)")
})
# Test conversion between logical and CnfFormula
test_that("as.logical and as.CnfFormula conversions work correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
formula_true = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b", "c"))))))
formula_false = CnfFormula(list(CnfClause(list(CnfAtom(X, character(0))))))
# Convert CnfFormula to logical
expect_true(as.logical(formula_true))
expect_false(as.logical(formula_false))
# Convert logical to CnfFormula
expect_s3_class(as.CnfFormula(TRUE), "CnfFormula")
expect_true(as.logical(as.CnfFormula(TRUE)))
expect_s3_class(as.CnfFormula(FALSE), "CnfFormula")
expect_false(as.logical(as.CnfFormula(FALSE)))
expect_identical(as.logical(CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b")))))) & CnfFormula(list(CnfClause(list(CnfAtom(Y, c("d"))))))),
NA
)
})
# Test invalid creation of CnfFormula with clauses from different universes
test_that("CnfFormula throws error when clauses are from different universes", {
u1 = CnfUniverse()
u2 = CnfUniverse()
X1 = CnfSymbol(u1, "X", c("a", "b", "c"))
X2 = CnfSymbol(u2, "X", c("a", "b", "c"))
clause1 = CnfClause(list(CnfAtom(X1, c("a", "b"))))
clause2 = CnfClause(list(CnfAtom(X2, c("a", "b"))))
expect_error(CnfFormula(list(clause1, clause2)), "All clauses must be in the same universe")
})
test_that("all.equal recognizes (in)equality for CnfFormula", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
Z = CnfSymbol(u, "Z", c("g", "h", "i"))
# Test equality between identical CnfFormulas
atom1 = CnfAtom(X, c("a", "b"))
atom2 = CnfAtom(Y, c("d", "e"))
clause1 = CnfClause(list(atom1, atom2))
atom3 = CnfAtom(Z, c("g"))
clause2 = CnfClause(list(atom3))
formula1 = CnfFormula(list(clause1, clause2))
formula2 = CnfFormula(list(clause1, clause2))
expect_true(all.equal(formula1, formula2))
# Test equality with the same clauses but in different order
formula2 = CnfFormula(list(clause2, clause1))
expect_true(all.equal(formula1, formula2))
# Test inequality for different CnfFormulas with different atoms
atom4 = CnfAtom(X, c("b", "c"))
clause3 = CnfClause(list(atom4, atom2))
formula2 = CnfFormula(list(clause3, clause2))
expect_string(all.equal(formula1, formula2), pattern = "string mismatch")
# Test equality for CnfFormulas where the values of one symbol in clauses are in a different order
atom5 = CnfAtom(X, c("b", "a")) # same values, different order
clause4 = CnfClause(list(atom5, atom2))
formula2 = CnfFormula(list(clause4, clause2))
expect_true(all.equal(formula1, formula2))
# Test inequality when formulas have different symbols
atom6 = CnfAtom(Z, c("h"))
clause5 = CnfClause(list(atom6))
formula2 = CnfFormula(list(clause1, clause5))
expect_string(all.equal(formula1, formula2), pattern = "string mismatch")
# Test equality for logical CnfFormula (TRUE and FALSE) vs a formula
formula_tautology = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b", "c")))))) # tautology (TRUE)
formula_contradiction = CnfFormula(list(CnfClause(list(CnfAtom(X, character(0)))))) # contradiction (FALSE)
expect_string(all.equal(formula1, formula_tautology), pattern = "not both logicals")
expect_string(all.equal(formula1, formula_contradiction), pattern = "not both logicals")
# Test equality for a logical TRUE formula and another logical TRUE formula
clause_ttl1 = CnfClause(list(CnfAtom(X, c("a", "b", "c"))))
clause_ttl2 = CnfClause(list(CnfAtom(Y, c("d", "e", "f"))))
formula_ttl1 = CnfFormula(list(clause_ttl1))
formula_ttl2 = CnfFormula(list(clause_ttl2))
expect_true(all.equal(formula_ttl1, formula_ttl2))
expect_true(all.equal(formula_ttl1, as.CnfFormula(TRUE)))
# Test equality for a logical FALSE formula and another logical FALSE formula
formula_false1 = CnfFormula(list(CnfClause(list(CnfAtom(X, character(0))))))
formula_false2 = CnfFormula(list(CnfClause(list(CnfAtom(Y, character(0))))))
expect_true(all.equal(formula_false1, formula_false2))
expect_string(all.equal(formula1, formula_false1), pattern = "not both logicals")
expect_true(all.equal(formula_false1, as.CnfFormula(FALSE)))
# Test inequality between a CnfFormula and a non-CnfFormula object
expect_string(all.equal(formula1, "not a CnfFormula"), pattern = "current is not a CnfFormula")
# Test equality between CnfFormulas in different universes
u1 = CnfUniverse()
u2 = CnfUniverse()
X1 = CnfSymbol(u1, "X", c("a", "b", "c"))
X2 = CnfSymbol(u2, "X", c("a", "b", "c"))
atom_u1 = CnfAtom(X1, c("a", "b"))
atom_u2 = CnfAtom(X2, c("a", "b"))
clause_u1 = CnfClause(list(atom_u1))
clause_u2 = CnfClause(list(atom_u2))
formula_u1 = CnfFormula(list(clause_u1))
formula_u2 = CnfFormula(list(clause_u2))
expect_true(all.equal(formula_u1, formula_u2))
# Test inequality when universes differ in length
u3 = CnfUniverse()
X3 = CnfSymbol(u3, "X", c("a", "b", "c", "d"))
atom_u3 = CnfAtom(X3, c("a", "b"))
clause_u3 = CnfClause(list(atom_u3))
formula_u3 = CnfFormula(list(clause_u3))
expect_string(all.equal(formula_u1, formula_u3), pattern = "Lengths \\(3, 4\\) differ")
# Test disjunction and conjunction in CnfFormula
atom7 = CnfAtom(X, c("a"))
atom8 = CnfAtom(Y, c("d"))
clause6 = CnfClause(list(atom7, atom8))
clause7 = CnfClause(list(atom1, atom2))
formula1 = CnfFormula(list(clause6))
formula2 = CnfFormula(list(clause7))
formula_disjunction = formula1 | formula2
formula_conjunction = formula1 & formula2
# Test all.equal on conjunction and disjunction cases
expect_string(paste(all.equal(formula_disjunction, formula1), collapse = "\n"), pattern = "Lengths \\(2, 1\\) differ")
expect_true(all.equal(formula_conjunction, formula1))
# Explicitly constructed object all.equals test
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
Z = CnfSymbol(u, "Z", c("g", "h", "i"))
f1 = structure(list(
list(X = c("a", "b"), Y = c("d")),
list(Z = c("g"))
), universe = u, class = "CnfFormula")
f2 = structure(list(
list(Y = c("d"), X = c("a", "b")),
list(Z = c("g"))
), universe = u, class = "CnfFormula")
f3 = structure(list(
list(X = c("b", "a"), Y = c("d")),
list(Z = c("g"))
), universe = u, class = "CnfFormula")
f4 = structure(list(
list(Y = c("d"), X = c("b", "a")),
list(Z = c("g"))
), universe = u, class = "CnfFormula")
f5 = structure(list(
list(Z = c("g")),
list(X = c("a", "b"), Y = c("d"))
), universe = u, class = "CnfFormula")
f1_unequal = structure(list(
list(X = c("a", "b")),
list(Z = c("g"))
), universe = u, class = "CnfFormula")
for (fx in list(f1, f2, f3, f4, f5)) {
for (fy in list(f1, f2, f3, f4, f5)) {
expect_true(all.equal(fx, fy))
}
}
for (fx in list(f1, f2, f3, f4, f5)) {
expect_string(all.equal(fx, f1_unequal), pattern = "string mismatch|[Ll]ength mismatch")
}
})
# Test unit propagation in CnfFormula
test_that("CnfFormula performs unit propagation correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a", "c")))) # Unit clause: X ∈ {a, c}
clause2 = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e"))))
formula = CnfFormula(list(clause1, clause2))
# Expected: clause2 should be simplified to just X ∈ {a}, Y ∈ {d, e}, since clause1 implies X ∈ {a, c} and c is not in clause2.
expected_formula = CnfFormula(list(clause1, CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d", "e"))))))
expect_true(all.equal(formula, expected_formula))
})
# Test subsumption elimination in CnfFormula
test_that("CnfFormula performs subsumption elimination correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
Z = CnfSymbol(u, "Z", c("g", "h", "i"))
clause1 = CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d"))))
clause2 = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e"))))
formula = CnfFormula(list(clause1, clause2))
# Expected: clause2 is subsumed by clause1, so the result should only contain clause1
expected_formula = CnfFormula(list(clause1))
expect_true(all.equal(formula, expected_formula))
expect_true(all.equal(
CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e")))), CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d", "e")))))),
CnfFormula(list(CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d", "e"))))))
))
expect_true(all.equal(
CnfFormula(list(CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("e", "d")))), CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e")))))),
CnfFormula(list(CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d", "e"))))))
))
expect_true(all.equal(
CnfFormula(list(CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d", "e")))), CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e")), CnfAtom(Z, c("g", "h")))))),
CnfFormula(list(CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d", "e"))))))
))
})
# Test self-subsumption elimination in CnfFormula
test_that("CnfFormula performs self-subsumption elimination correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d")))) # X ∈ {a, b} | Y ∈ {d}
clause2 = CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("e")))) # X ∈ {a} | Y ∈ {e}
# Expected: self-subsumption should simplify the formula
expected_formula = CnfFormula(list(CnfClause(list(CnfAtom(X, c("a", "b")))), CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("e"))))))
expect_true(all.equal(CnfFormula(list(clause1, clause2)), expected_formula))
expect_true(all.equal(CnfFormula(list(clause2, clause1)), expected_formula))
clause1 = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d")))) # X ∈ {a, b} | Y ∈ {d}
clause2 = CnfClause(list(CnfAtom(X, c("a", "c")), CnfAtom(Y, c("d")))) # X ∈ {a, b} | Y ∈ {d}
clause3 = CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("e")))) # X ∈ {a} | Y ∈ {e}
# Expected: self-subsumption should simplify the formula
expected_formula = CnfFormula(list(
CnfClause(list(CnfAtom(X, c("a", "b")))),
CnfClause(list(CnfAtom(X, c("a", "c")))),
CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("e"))))
))
expect_true(all.equal(CnfFormula(list(clause1, clause2, clause3)), expected_formula))
expect_true(all.equal(CnfFormula(list(clause3, clause2, clause1)), expected_formula))
expect_true(all.equal(CnfFormula(list(clause1, clause3, clause2)), expected_formula))
})
# Test hidden subsumption elimination in CnfFormula
test_that("CnfFormula performs hidden subsumption elimination correctly", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
clause1 = CnfClause(list(CnfAtom(X, c("a")), CnfAtom(Y, c("d")))) # X ∈ {a} | Y ∈ {d}
clause2 = CnfClause(list(CnfAtom(X, c("b")), CnfAtom(Y, c("e")))) # X ∈ {b} | Y ∈ {e}
clause3 = CnfClause(list(CnfAtom(Y, c("d", "e")))) # Y ∈ {d, e}
formula = CnfFormula(list(clause1, clause2, clause3))
# Expected: clause3 is implied by clause1 and clause2, so it should be removed
expected_formula = CnfFormula(list(clause1, clause2))
expect_true(all.equal(formula, expected_formula))
})
# Test contradiction recognition in CnfFormula
test_that("CnfFormula recognizes simple contradictions", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
clause1 = CnfClause(list(CnfAtom(X, c("a")))) # X ∈ {a}
clause2 = CnfClause(list(CnfAtom(X, c("b")))) # X ∈ {b}
formula = CnfFormula(list(clause1, clause2))
# Expected: this should be a contradiction
expected_formula = as.CnfFormula(FALSE)
expect_true(all.equal(formula, expected_formula))
})
# Test simplifications in a larger formula
test_that("CnfFormula performs all simplifications in a complex formula", {
u = CnfUniverse()
X = CnfSymbol(u, "X", c("a", "b", "c"))
Y = CnfSymbol(u, "Y", c("d", "e", "f"))
Z = CnfSymbol(u, "Z", c("g", "h", "i"))
clause1 = CnfClause(list(CnfAtom(X, c("b", "c")))) # X ∈ {a}
clause2 = CnfClause(list(CnfAtom(X, c("a", "b")), CnfAtom(Y, c("d", "e")))) # X ∈ {b} | Y ∈ {d, e}
clause3 = CnfClause(list(CnfAtom(Y, c("d", "e")), CnfAtom(Z, c("g", "h")))) # Y ∈ {d, e} | Z ∈ {g, h}
clause4 = CnfClause(list(CnfAtom(Z, c("g", "h")))) # Z ∈ {g, h}
formula = CnfFormula(list(clause1, clause2, clause3, clause4))
# Expected: clause3 should be removed, and unit propagation should simplify clause2
expected_formula = CnfFormula(list(
clause1, clause4,
CnfClause(list(CnfAtom(X, c("b")), CnfAtom(Y, c("d", "e"))))
))
expect_true(all.equal(formula, expected_formula))
})
formula_to_expression = function(formula) {
if (is.logical(formula)) return(c(formula))
clause_exprs = lapply(as.CnfFormula(formula), function(clause) {
atom_exprs = lapply(clause, function(atom) {
substitute(symbol %among% values, list(symbol = as.name(atom$symbol), values = atom$values))
})
Reduce(function(x, y) substitute(x | y, list(x = x, y = y)), atom_exprs)
})
Reduce(function(x, y) substitute(x & y, list(x = x, y = y)), clause_exprs)
}
evaluate_expression = function(expression, assignment) {
substituted = do.call(substitute, list(expression, c(list("%among%" = quote(`%in%`)), assignment)))
eval(substituted, envir = baseenv())
}
test_that("Brute-force test", {
skip_on_cran()
u = CnfUniverse()
W = CnfSymbol(u, "W", c("p", "q", "r"))
X = CnfSymbol(u, "X", c("s", "t", "u"))
Y = CnfSymbol(u, "Y", c("v", "w", "x"))
Z = CnfSymbol(u, "Z", c("y", "z", "a"))
random_atom_expression = function() {
symbol = sample(alist(W, X, Y, Z), 1)[[1]]
values = sample(u[[deparse(symbol)]], sample(2, 1))
substitute(symbol %among% values, list(symbol = symbol, values = values))
}
random_cnf_expression = function(depth, do_cnf = FALSE, ddepth = 0) {
if (depth <= 1) {
# base case: return a random symbol expression call
return(random_atom_expression())
}
# recursively build a random tree of expressions
if (do_cnf) {
op = if (depth <= ddepth) quote(`|`) else quote(`&`)
} else {
op = sample(alist(`&`, `|`), 1)[[1]] # randomly choose between AND and OR
}
left_expr = random_cnf_expression(depth - 1, do_cnf, ddepth)
right_expr = random_cnf_expression(depth - 1, do_cnf, ddepth)
# create a call object for the expression: (left_expr op right_expr)
substitute(op(left_expr, right_expr), list(op = op, left_expr = left_expr, right_expr = right_expr))
}
expression_weight = function(expression) {
if (is.logical(expression)) return(0)
sum(all.names(expression) %in% c("|", "&")) + 1
}
ss = 0
# dti_col <- parallel::mclapply(mc.cores = 16, 1:16, function(ss) {
varnames = names(u)
assignments = expand.grid(lapply(varnames, function(var) u[[var]]), stringsAsFactors = FALSE)
colnames(assignments) = varnames
stats = list(depth = integer(0), expweight = numeric(0), simpweight = numeric(0), was_tautology = logical(0), was_contradiction = logical(0))
set.seed(3 + ss)
for (depth_to_check in 2:11) {
for (repetition in seq_len(200)) {
if (depth_to_check == 10) {
expression = random_cnf_expression(10, TRUE, 4)
} else if (depth_to_check == 11) {
expression = random_cnf_expression(5, TRUE, 2)
} else {
expression = random_cnf_expression(depth_to_check)
}
simplified = formula_to_expression(eval(expression))
stats$depth[[length(stats$depth) + 1]] = depth_to_check
stats$expweight[[length(stats$expweight) + 1]] = expression_weight(expression)
stats$simpweight[[length(stats$simpweight) + 1]] = expression_weight(simplified)
truevals = evaluate_expression(expression, assignments)
simpvals = evaluate_expression(simplified, assignments)
stats$was_tautology[[length(stats$was_tautology) + 1]] = all(truevals)
stats$was_contradiction[[length(stats$was_contradiction) + 1]] = all(!truevals)
if (!all(truevals == simpvals)) {
trueval = truevals[[which(!truevals == simpvals)[1]]]
simpval = simpvals[[which(!truevals == simpvals)[1]]]
assignment = assignments[which(!truevals == simpvals)[1], , drop = FALSE]
} else {
# alibi
trueval = simpval = TRUE
assignment = NULL
}
expect_equal(trueval, simpval,
info = sprintf("Expression: %s\nAssignment:\n%s\nSimplified to: %s",
deparse1(expression),
paste(capture.output(print(assignment)), collapse = "\n"),
deparse1(simplified)
))
}
}
dti <- as.data.table(stats)
# })
# dti <- rbindlist(dti_col, idcol = "seedoffset")
dti[, .(
ew = mean(expweight), sw = mean(simpweight),
etriv = mean(expweight == 0),
striv = mean(simpweight == 0),
could_simplify = mean(expweight > simpweight),
was_tautology = mean(was_tautology),
was_contradiction = mean(was_contradiction),
tautologies_not_recognized = mean(was_tautology & simpweight > 0),
contradictions_not_recognized = mean(was_contradiction & simpweight > 0)
), by = "depth"]
})
test_that("Constructed test cases", {
u = CnfUniverse()
A = CnfSymbol(u, "A", c("a1", "a2", "a3", "a4"))
B = CnfSymbol(u, "B", c("b1", "b2", "b3", "b4"))
C = CnfSymbol(u, "C", c("c1", "c2", "c3", "c4"))
D = CnfSymbol(u, "D", c("d1", "d2", "d3", "d4"))
varnames = names(u)
assignments = expand.grid(lapply(varnames, function(var) u[[var]]), stringsAsFactors = FALSE)
colnames(assignments) = varnames
# greedy self-subsumption eliminatoin fails for the following:
original = quote((A %among% "a1" | B %among% c("b1", "b2")) & (A %among% "a1" | B %among% "b2" | C %among% "c1") & (A %among% "a2" | C %among% "c2"))
simplified = CnfFormula(list(
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b1", "b2")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b2")), CnfAtom(C, c("c1")))),
CnfClause(list(CnfAtom(A, c("a2")), CnfAtom(C, c("c2"))))
))
expect_equal(evaluate_expression(formula_to_expression(simplified), assignments),
evaluate_expression(original, assignments))
## exactly one of the clauses in the following is eliminated
# - 1 (A %among% "a1" | B %among% "b1" | D %among% "d1") &
# 2 (A %among% "a1" | C %among% "c1" | D %among% "d1") &
# 3 (A %among% "a1" | B %among% "b1" | C %among% "c2") &
# 4 (B %among% "b2" | C %among% "c1" | D %among% "d1")
original = quote(
(A %among% "a1" | B %among% "b1" | D %among% "d1") &
(A %among% "a1" | C %among% "c1" | D %among% "d1") &
(A %among% "a1" | B %among% "b1" | C %among% "c2") &
(B %among% "b2" | C %among% "c1" | D %among% "d1")
)
simplified = CnfFormula(list(
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b1")), CnfAtom(D, c("d1")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(C, c("c1")), CnfAtom(D, c("d1")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b1")), CnfAtom(C, c("c2")))),
CnfClause(list(CnfAtom(B, c("b2")), CnfAtom(C, c("c1")), CnfAtom(D, c("d1"))))
))
expect_equal(length(as.list(simplified)), 3)
expect_equal(evaluate_expression(formula_to_expression(simplified), assignments),
evaluate_expression(original, assignments))
# 1 A %among% c("a1", "a2") | B %among% "b1" | C %among% c("c1", "c2")
# 2 B %among% c("b1", "b2") | C %among% c("c1", "c3")
# 3 A %among% c("a1", "a2") | B %among% c("b1", "b3")
# 4 A %among% "a2" | B %among% "b3" | D %among% "d1"
# 5 A %among% "a1" | B %among% "b3" | D %among% c("d2", "d3")
# - clause 3 adds 'b2' to clause 1; clause 2 can then restrict clause 1 to 'c1'
# - in other words: clauses 2 + 3 combine on symbol B to form A %among% c("a1", "a2") | C %among% c("c1", "c3")
# with respect to clause 1 (which conditions on B %among% "b1")
# - however, clauses 4 and 5 eliminate clause 3 through hidden subsumption elimination
# - at least B %among% "b1" can be removed from 1 by combining 4 and 5 over D., even if 3 is not present.
original = quote(
(A %among% c("a1", "a2") | B %among% "b1" | C %among% c("c1", "c2")) &
(B %among% c("b1", "b2") | C %among% c("c1", "c3")) &
(A %among% c("a1", "a2") | B %among% c("b1", "b3")) &
(A %among% "a2" | B %among% "b3" | D %among% "d1") &
(A %among% "a1" | B %among% "b3" | D %among% c("d2", "d3"))
)
simplified = CnfFormula(list(
CnfClause(list(CnfAtom(A, c("a1", "a2")), CnfAtom(B, c("b1")), CnfAtom(C, c("c1", "c2")))),
CnfClause(list(CnfAtom(B, c("b1", "b2")), CnfAtom(C, c("c1", "c3")))),
CnfClause(list(CnfAtom(A, c("a1", "a2")), CnfAtom(B, c("b1", "b3")))),
CnfClause(list(CnfAtom(A, c("a2")), CnfAtom(B, c("b3")), CnfAtom(D, c("d1")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b3")), CnfAtom(D, c("d2", "d3"))))
))
simplified2 = CnfFormula(list(
CnfClause(list(CnfAtom(A, c("a1", "a2")), CnfAtom(B, c("b1")), CnfAtom(C, c("c1", "c2")))),
CnfClause(list(CnfAtom(B, c("b1", "b2")), CnfAtom(C, c("c1", "c3")))),
CnfClause(list(CnfAtom(A, c("a2")), CnfAtom(B, c("b3")), CnfAtom(D, c("d1")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b3")), CnfAtom(D, c("d2", "d3"))))
))
simplified3 = CnfFormula(list(
CnfClause(list(CnfAtom(A, c("a1", "a2")), CnfAtom(C, c("c1", "c2")))),
CnfClause(list(CnfAtom(B, c("b1", "b2")), CnfAtom(C, c("c1", "c3")))),
CnfClause(list(CnfAtom(A, c("a1", "a2")), CnfAtom(B, c("b1", "b3")))),
CnfClause(list(CnfAtom(A, c("a2")), CnfAtom(B, c("b3")), CnfAtom(D, c("d1")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b3")), CnfAtom(D, c("d2", "d3"))))
))
expect_equal(evaluate_expression(formula_to_expression(simplified), assignments),
evaluate_expression(original, assignments))
expect_equal(evaluate_expression(formula_to_expression(simplified2), assignments),
evaluate_expression(original, assignments))
expect_equal(evaluate_expression(formula_to_expression(simplified3), assignments),
evaluate_expression(original, assignments))
# check that 'B' was treated the same in all cases, even if C is still unreliable:
# clause 2 is always unchanged with 'b1', 'b2'; clauses 4 and 5 contribute one 'b3' each.
expect_set_equal(unlist(lapply(as.list(simplified), function(x) unclass(x)$B)), c("b1", "b2", "b3", "b3"))
expect_set_equal(unlist(lapply(as.list(simplified2), function(x) unclass(x)$B)), c("b1", "b2", "b3", "b3"))
expect_set_equal(unlist(lapply(as.list(simplified3), function(x) unclass(x)$B)), c("b1", "b2", "b3", "b3"))
original = quote(
(A %among% "a2" | B %among% "b1") &
(A %among% "a1" | B %among% "b2") &
(A %among% "a1" | B %among% "b1"| C %among% "c1")
)
simplified = CnfFormula(list(
CnfClause(list(CnfAtom(A, c("a2")), CnfAtom(B, c("b1")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b2")))),
CnfClause(list(CnfAtom(A, c("a1")), CnfAtom(B, c("b1")), CnfAtom(C, c("c1"))))
))
expect_equal(evaluate_expression(formula_to_expression(simplified), assignments),
evaluate_expression(original, assignments))
e1 <- (D %among% c("d3", "d1") | B %among% c("b1", "b3")) &
(B %among% c("b1", "b2") | D %among% c("d3")) & ((C %among%
c("c1", "c3") | D %among% c("d3", "d2")) & (B %among%
"b3" | D %among% "d2"))
e2 <- (D %among% c("d3", "d2") & C %among% c("c2", "c3") & (B %among% "b2" & C %among%
c("c1", "c3")) | D %among% "d1" & D %among% c("d2",
"d1") & (C %among% "c3" & D %among% "d1"))
expect_false(is.logical(e1))
expect_false(is.logical(e2))
expect_true(is.logical(e1 & e2)) # this contradiction should be recognized from 2nd order self subsumption elimination
expect_false(e1 & e2)
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.