Quantified Boolean formula manipulation (pyqbf.formula
)#
List of classes#
Class for manipulating PCNF formulas. |
List of methods#
Safely transforms the input to a |
List of constants#
Indicator for an universial quantifier |
|
Indicator for an existential quantifier |
|
Indicator for a free variable |
Module description#
This module provides classes and methods for simple manipulation of quantified boolean formulas in prenex normal form, where the propositional part of the formula is in conjunctive normal form (CNF), i.e. PCNF.
We can see PCNF as an extension of CNF with the addition of a quantifier-prefix.
Therefore, the PCNF
-class inherits all the properties provided by pysat.formula.CNF
with the additional prefix
-attribute. The prefix can be set using the forall()
and exists()
functions
The propositional part can be seen as a set of clauses, where each clause is a set of literals.
Literals are represented using the int
type, where a negative value indicates the negation of the variable
referenced.
The prefix can be seen as an ordered tuple of quantifiers. The order-relation applied to the quantifier is expressed by the order of where the variable occur in the prefix. A negative value indicates forall-quantification whereas a postive value indicates exists.
>>> from pyqbf.formula import PCNF
>>> pcnf = PCNF()
>>> pcnf.append([-1, 2])
>>> pcnf.append([-2, 3])
>>> pcnf.forall(1).exists(2, 3)
>>> print(pcnf.clauses)
[[-1, 2], [-2, 3]]
>>> print(pcnf.prefix)
[-1, 2, 3]
>>> print(pcnf.nv)
3
The default input-format is QDIMACS. Very similar to DIMACS for PySAT, reading and writing can be done the following way:
>>> from pyqbf.formula import PCNF
>>> f1 = PCNF(from_file="some-file-name.qdimacs")
>>> f1.to_file("another-file-name.qdimacs")
>>>
>>> with open('some-file-name.qdimacs', 'r+') as fp:
... f2 = PCNF(from_fp=fp)
...
... fp.seek(0)
... f2.to_fp(fp)
>>>
>>> f3 = PCNF(from_string="p cnf 3 3\na 1 0\ne 2 3 0\n-1 2 0\n-2 3 0\n-3 0\n")
>>> print(f3.clauses)
[[-1, 2], [-2, 3], [-3]]
>>> print(f3.prefix)
[-1, 2, 3]
>>> print(f3.nv)
3
PyQBF furthermore allows easy extensions from the domain of Propositional Logic into QBF. In the following are a few examples.
>>> from pyqbf.formula import PCNF, to_pcnf
>>> f1 = PCNF(from_file="cnf.dimacs") #DIMACS is compatible with QDIMACS
>>> print(f1.prefix) #but the prefix is empty
[]
>>> f1.forall(1).exists(2,3,4).forall(5,6)
>>> print(f1.prefix)
[-1, 2, 3, 4, -5, -6]
>>>
>>> f2 = PCNF(from_file="cnf.dimacs", auto_generate_prefix=True)
>>> print(f2.prefix) #now everything is automatically existentially quantified
[1, 2, 3, 4, 5, 6]
>>> f2.set_quantifier(1, QUANTIFIER_FORALL)
>>> print(f2.prefix)
[-1, 2, 3, 4, 5, 6]
Note
Currently only the pysat.formula.CNF
class is supported.
More complex structures like pysat.formula.WCNF
or pysat.formula.CNFPlus
can not be expressed using quantification at the moment.
Module details#
- class pyqbf.formula.PCNF(*args, **kwargs)#
Bases:
CNF
Class for manipulating PCNF formulas. It can be used for creating formulas, reading them from a file, or writing them to a file.
- Parameters:
from_file (
str
) β a QDIMACS filename to read fromfrom_fp (
SupportsRead[str]
) β a file pointer to read fromfrom_string (
str
) β a string storing a PCNF formula in QDIMACS formatfrom_clauses (
list[list[int]]
) β a list of clauses to bootstrap the formula with. Variables are free by defaultfrom_aiger (
aiger.AIG
(see py-aiger package)) β an AIGER circuit to bootstrap the formula with. Variables are free by defaultfrom_cnf (
pysat.formula.CNF
) β a CNF-formula. Variables are free by defaultauto_generate_prefix (
bool
) β if true, a prefix is automatically generated from the propositional part.
- Variables:
prefix (
list[int]
) β Prefix of the formula containing the quantificationsclauses (
list[list[int]]
) β Propositional part of the formula containing the clausescomments (
list[str]
) β Comments encountered during parsingnv (
int
) β Total count of variables
- append(clause, update_vpool=False)#
Add one more clauses to the PCNF formula. This method additionally updates the number of variables, i.e. variable
self.nv
, used in the formula.The additional keyword argument
update_vpool
can be set toTrue
if the user wants to update for default static pool of variable identifiers stored in classFormula
. In light of the fact that a user may encode their problem manually and add thousands to millions of clauses using this method, the value ofupdate_vpool
is set toFalse
by default.Note
Setting
update_vpool=True
is required if a user wants to combine thisPCNF
formula with other (clausal or non-clausal) formulas followed by the clausification of the result combination. Alternatively, a user may resort to using the methodextend()
instead.- Parameters:
clause (list(int)) β a new clause to add
update_vpool (bool) β update or not the static vpool
>>> from pyqbf.formula import PCNF >>> pcnf = PCNF(from_clauses=[[-1, 2], [3]]) >>> pcnf.append([-3, 4]) >>> print(pcnf.clauses) [[-1, 2], [3], [-3, 4]]
- copy()#
This method can be used for creating a copy of a PCNF object. It creates another object of the
PCNF
class and makes use of thedeepcopy()
functionality to copy the prefix and clauses.- Returns:
A copy of the current instance.
- Return type:
Usage example:
>>> pcnf1 = PCNF(from_clauses=[[-1, 2], [1]]) >>> pcnf1.forall(1).exists(2) >>> pcnf2 = pcnf1.copy() >>> print(pcnf2.prefix) [-1, 2] >>> print(pcnf2.clauses) [[-1, 2], [1]] >>> print(pcnf2.nv) 2
- exists(*vars)#
Adds variables bound by an existential quantifier to the formulaβs prefix.
- Parameters:
vars (
int
) β identifiers of the variables- Returns:
The current
PCNF
for chaining- Return type:
Usage example:
>>> pcnf = PCNF() >>> pcnf.exists(1, 2, "3", 4.0).exists(5) >>> qlist = [6, 7, 8, 9] >>> pcnf.exists(*qlist)
- extend(clauses)#
Add several clauses to PCNF formula. The clauses should be given in the form of list. For every clause in the list, method
append()
is invoked.- Parameters:
clauses (list(list(int))) β a list of new clauses to add
Example:
>>> from pyqbf.formula import PCNF >>> pcnf = PCNF(from_clauses=[[-1, 2], [3]]) >>> pcnf.extend([[-3, 4], [5, 6]]) >>> print(pcnf.clauses) [[-1, 2], [3], [-3, 4], [5, 6]]
- forall(*vars)#
Adds variables bound by an universial quantifier to the formulaβs prefix.
- Parameters:
vars (
int
) β identifiers of the variables- Returns:
The current
PCNF
for chaining- Return type:
Usage example:
>>> pcnf = PCNF() >>> pcnf.forall(1, 2, "3", 4.0).forall(5) >>> qlist = [6, 7, 8, 9] >>> pcnf.forall(*qlist)
- from_aiger(aig, vpool=False)#
Create a PCNF formula by Tseitin-encoding an input AIGER circuit.
The input circuit is expected to be an object of class
aiger.AIG
. Alternatively, it can be specified as anaiger.BoolExpr
, an*.aag
filename or an AIGER string to parse. (Classesaiger.AIG
andaiger.BoolExpr
are defined in the py-aiger package.)- Parameters:
aig (
aiger.AIG
(see py-aiger package)) β an input AIGER circuitvpool (
IDPool
) β pool of variable identifiers (optional)
Example:
>>> import aiger >>> x, y, z = aiger.atom('x'), aiger.atom('y'), aiger.atom('z') >>> expr = ~(x | y) & z >>> print(expr.aig) aag 5 3 0 1 2 2 4 8 10 6 3 5 10 6 8 i0 y i1 x i2 z o0 6c454aea-c9e1-11e9-bbe3-3af9d34370a9 >>> >>> from pyqbf.formula import PCNF >>> pcnf = PCNF(from_aiger=expr.aig) >>> print(pcnf.nv) 5 >>> print(pcnf.clauses) [[3, 2, 4], [-3, -4], [-2, -4], [-4, -1, 5], [4, -5], [1, -5]] >>> print(['{0} <-> {1}'.format(v, pcnf.vpool.obj(v)) for v in pcnf.inps]) ['3 <-> y', '2 <-> x', '1 <-> z'] >>> print(['{0} <-> {1}'.format(v, pcnf.vpool.obj(v)) for v in pcnf.outs]) ['5 <-> 6c454aea-c9e1-11e9-bbe3-3af9d34370a9']
- from_clauses(clauses, by_ref=False)#
This methods copies a list of clauses into a PCNF object. The optional keyword argument
by_ref
(False
by default) indicates whether the clauses should be deep-copied or copied by reference.- Parameters:
clauses (
list(list(int))
) β a list of clausesby_ref (
bool
) β a flag to indicate whether to deep-copy the clauses or copy them by reference
Example:
>>> from pyqbf.formula import PCNF >>> pcnf = PCNF(from_clauses=[[-1, 2], [1, -2], [5]]) >>> print(pcnf.clauses) [[-1, 2], [1, -2], [5]] >>> print(pcnf.nv) 5
- from_file(fname, comment_lead=['c'], compressed_with='use_ext')#
Read a PCNF formula from a file in the QDIMACS format. A file name is expected as an argument. A given file can be compressed by either gzip, bzip2, or lzma.
Note
The parameter
comment_lead
exists due to compatibility withpysat.formula.CNF.from_file()
but does not serve a purpose here- Parameters:
fname (str) β name of a file to parse.
comment_lead (list(str)) β a list of characters leading comment lines
compressed_with (str) β file compression algorithm
Note that the
compressed_with
parameter can beNone
(i.e. the file is uncompressed),'gzip'
,'bzip2'
,'lzma'
, or'use_ext'
. The latter value indicates that compression type should be automatically determined based on the file extension. Using'lzma'
in Python 2 requires thebackports.lzma
package to be additionally installed.Usage example:
>>> from pyqbf.formula import PCNF >>> pcnf1 = PCNF() >>> pcnf1.from_file('some-file.qcnf.gz', compressed_with='gzip') >>> >>> cnf2 = CNF(from_file='another-file.qcnf')
- from_fp(file_pointer, comment_lead=[])#
Read a PCNF formula in QDIMACS format from a file pointer.
Note
The parameter
comment_lead
exists due to compatibility withpysat.formula.CNF.from_fp()
but does not serve a purpose here- Parameters:
file_pointer (
SupportsRead[str]
) β a file pointer to read the formula from.comment_lead (
list[str]
) β a list of characters leading comment lines (for compatibility reason)
Usage example:
>>> with open('some-file.qdimacs', 'r') as fp: ... pcnf1 = PCNF() ... pcnf1.from_fp(fp) >>> >>> with open('another-file.qdimacs', 'r') as fp: ... pcnf2 = PCNF(from_fp=fp)
- from_string(string, comment_lead=['c'])#
Read a PCNF formula from a string. The string should be specified as an argument and should be in the QDIMACS PCNF format. The only default argument is
comment_lead
, which can be used for parsing specific comment lines.- Parameters:
string (str) β a string containing the formula in QDIMACS.
comment_lead (list(str)) β a list of characters leading comment lines
Example:
>>> from pyqbf.formula import PCNF >>> pcnf1 = PCNF() >>> pcnf1.from_string('p cnf 2 2\na 1 0\ne 2 0\n-1 2 0\n1 -2 0') >>> print(pcnf1.clauses) [[-1, 2], [1, -2]] >>> >>> print(pcnf1.prefix) [-1, 2] >>> pcnf2 = PCNF(from_string='p cnf 3 3\n-1 2 0\n-2 3 0\n-3 0\n') >>> print(pcnf2.clauses) [[-1, 2], [-2, 3], [-3]] >>> print(pcnf2.nv) 3) >>> print(pcnf2.prefix) []
- property is_normalized#
Indicates whether the formula was normalized. This is needed for certain solvers like
pyqbf.solvers.Qute
.Usage example:
>>> pcnf = PCNF(from_clauses=[[1, 2, 3], [-1, 2, 3], [-1, -2, -3]]) >>> print(pcnf.is_normalized) False >>> pcnf.normalize() >>> print(pcnf.is_normalized) True
- negate()#
Given a PCNF formula \(\mathcal{F}\), this method creates a PCNF formula \(\neg{\mathcal{F}}\).
The negation of the propositional part is encoded using additional Tseitin variables [1]. Furthermore, the technique proposed by Plaisted and Greenbaum [2] is used to reduce the amount of clauses. For the prenex, the quantifiers are inversed, i.e. existential become universial and vice versa. A new PCNF formula is returned keeping all the newly introduced variables that can be accessed through the
auxvars
variable. All the literals used to encode the negation of the original clauses can be accessed through theenclits
variable.If this function is called twice and the encoding can still be re-constructed, instead of re-encoding with another set of tseitin variables it will reverse the encoding and restore the original formula.
- Returns:
a formula describing the negated version of the input
- Return type:
>>> pos = PCNF(from_clauses=[[-1, 2], [3]]) >>> pos.forall(1).exists(2,3) >>> neg = pos.negate() >>> print(neg.prefix) [1, -2, -3, 4] >>> print(neg.clauses) [[1, -4], [-2, -4], [4, -3]] >>> print(neg.auxvars) [4] >>> print(neg.enclits) [4, -3] >>> pos2 = neg.negate() >>> print(pos2.prefix) [-1, 2, 3] >>> print(pos2.clauses) [[-1, 2], [3]]
- normalize()#
Normalizes the current formula. Free variables are eliminated by adding them to the prefix. Variable-ids are assigned by the order they are defined such that there are no gaps.
Usage example:
>>> pcnf = PCNF(from_clauses=[[-2, 4], [-4, 6], [-6, 2]]) >>> pcnf.forall(6) >>> print(pcnf.nv) 6 >>> pcnf.normalize() # eliminate free variables and re-map ids >>> print(pcnf.prefix) # 2 => 1, 4 => 2, 6 => 3 [1, 2, -3] >>> print(pcnf.clauses) [[-1, 2], [-2, 3], [-3, 1]] >>> print(pcnf.nv) 3
- prefix_from_clauses(quantifier=1)#
Generates the prefix from the clauses specified. The order is determined by the order they occur in the formula
- Parameters:
quantifier (
int
) β the quantifier type the variables should be quantified with. DefaultQUANTIFIER_EXISTS
>>> pcnf = PCNF(from_clauses=[[1, 2], [-4, 3]]) >>> print(pcnf.prefix) [] >>> pcnf.prefix_from_clauses() >>> print(pcnf.prefix) [1, 2, 3, 4] >>> pcnf.prefix_from_clauses(QUANTIFIER_FORALL) >>> print(pcnf.prefix) [-1, -2, -3, -4] >>> # Setting a flag can do this automatically during initialization >>> pcnf2 = PCNF(from_clauses=[[1, 2], [-4, 3]], auto_generate_prefix=True) >>> print(pcnf2.prefix) [1, 2, 3, 4]
- quantify_free_variables(quantifier=1)#
Quantification of free variables by adding them to the prefix.
- Parameters:
quantifier (
int
) β the quantifier type the variables should be quantified with. DefaultQUANTIFIER_EXISTS
Usage example:
>>> pcnf = PCNF(from_clauses=[[1, 2, 3], [-1, 2, 3], [-1, -2, -3]]) >>> pcnf.forall(1) >>> print(pcnf.prefix) [-1] >>> print(pcnf.nv) 3 >>> pcnf.quantify_free_variables() >>> print(pcnf.prefix) [2, 3, -1] >>> # free variables are added at the front >>> print(pcnf.nv) 3 >>> pcnf = PCNF(from_clauses=[[1, 2, 3], [-1, 2, 3], [-1, -2, -3]]) >>> pcnf.forall(1) >>> pcnf.quantify_free_variables(QUANTIFIER_FORALL) >>> print(pcnf.prefix) [-2, -3, -1]
- set_quantifier(var, quantifier=1)#
Changes the quantifier of the specified variable to the value specified. If the target is not in the prefix yet, it is appended.
- Parameters:
var (
int
) β identifier of the variablequantifier (
int
) β type of the quantifier.QUANTIFIER_NONE
is not supported
Usage example:
>>> pcnf = PCNF(...) >>> pcnf.set_quantifier(1, QUANTIFIER_EXISTS) >>> pcnf.set_quantifier(2, QUANTIFIER_FORALL) >>> pcnf.set_quantifier(3) >>> print(pcnf.prefix) [1, -2, 3, ...] >>> pcnf.set_quantifier(2, QUANTIFIER_EXISTS) [1, 2, 3, ...]
- to_alien(file_pointer, format='opb', comments=None)#
Warning
This method is currently not supported for PCNF-formulas
- Raises:
NotImplementedError
- to_file(fname, comments=None, compress_with='use_ext')#
A method for saving a PCNF formula into a file in the QDIMACS format. A file name is expected as an argument. Additionally, supplementary comment lines can be specified in the
comments
parameter. Also, a file can be compressed using either gzip, bzip2, or lzma (xz).- Parameters:
fname (
str
) β a file name where to store the formula.comments (
list[str]
) β additional comments to put in the file.compress_with (
str
) β file compression algorithm
Note that the
compress_with
parameter can beNone
(i.e. the file is uncompressed),'gzip'
,'bzip2'
,'lzma'
, or'use_ext'
. The latter value indicates that compression type should be automatically determined based on the file extension. Using'lzma'
in Python 2 requires thebackports.lzma
package to be additionally installed.Example:
>>> from pysat.formula import PCNF >>> pcnf = PCNF() ... >>> # the formula is filled with a bunch of clauses and a prefix >>> pcnf.to_file('some-file-name.qcnf') # writing to a file
- to_fp(file_pointer, comments=None)#
The method can be used to save a PCNF formula into a file pointer using QDIMACS encoding. Additional comment lines can be specified with the
comments
parameter.- Parameters:
file_pointer (
SupportsWrite[str]
(file pointer)) β a file pointer where to store the formula.comments (
list[str]
) β additional comments to put in the file.
Usage example:
>>> pcnf = PCNF() >>> with open("file.qdimacs", "w") as fp: ... pcnf.to_fp(fp)
- to_qdimacs()#
Return the current state of the object in QDIMACS encoding.
- Returns:
a QDIMACS representation of the formula
- Return type:
str
Usage example:
>>> pcnf = PCNF(from_file="some-file.qdimacs") >>> print(pcnf.comments) ["c First Comment", "c Another Comment"] >>> print(pcnf.to_qdimacs()) c First Comment c Another Comment p cnf 3 3 a 1 0 e 2 3 0 -1 2 0 -2 3 0 -3 0
- var_type(var)#
Provides quantifier information for the specified variable.
- Parameters:
var (
int
) β identifier of the variable- Returns:
- Return type:
int
Usage example:
>>> pcnf = PCNF() >>> pcnf.exists(1).forall(2) >>> print(pcnf.var_type(1)) 1 >>> print(pcnf.var_type(1) == QUANTIFIER_EXISTS) True >>> print(pcnf.var_type(2)) -1 >>> print(pcnf.var_type(2) == QUANTIFIER_FORALL) True >>> print(pcnf.var_type(3)) 0 >>> print(pcnf.var_type(3) == QUANTIFIER_NONE) True
- pyqbf.formula.QUANTIFIER_EXISTS = 1#
Indicator for an existential quantifier
- pyqbf.formula.QUANTIFIER_FORALL = -1#
Indicator for an universial quantifier
- pyqbf.formula.QUANTIFIER_NONE = 0#
Indicator for a free variable
- pyqbf.formula.to_pcnf(cnf)#
Safely transforms the input to a
PCNF
class. The following inputs are currently supported for conversion:PCNF
: (no copy is made)pysat.formula.CNF
: (variables are existentially quantified)list[list[int]]
: (variables are existentially quantified)
- Parameters:
cnf (e.g.
pysat.formula.CNF
) β target of the domain expansion
Usage example:
>>> cnf = pysat.formula.CNF(from_clauses = [[-1, 2], [-2, 3], [-3, 1]]) >>> pcnf = to_pcnf(cnf) >>> print(pcnf.prefix) [1, 2, 3] >>> print(pcnf.clauses) [[-1, 2], [-2, 3], [-3, 1]] >>> clauses = [[-1, 2], [-2, 3], [-3, 1]] >>> pcnf2 = to_pcnf(clauses) >>> print(pcnf2.prefix) [1, 2, 3] >>> print(pcnf2.clauses) [[-1, 2], [-2, 3], [-3, 1]]