├── src ├── utils │ ├── mod.rs │ └── statistics.rs ├── dimacs.rs ├── bin │ ├── caqe.rs │ ├── dcaqe.rs │ └── experiment.rs ├── solve │ └── mod.rs ├── literal.rs ├── preprocessor.rs ├── matrix │ ├── schemes.rs │ ├── mod.rs │ └── dependency.rs ├── parse │ ├── mod.rs │ ├── dqdimacs.rs │ ├── dimacs.rs │ └── qdimacs.rs ├── clause.rs ├── experiment.rs └── lib.rs ├── docs ├── qbfeval-2020 │ ├── main.pdf │ ├── main.tex │ └── main.bib ├── qbfeval-2018 │ ├── main.tex │ └── main.bib └── qbfeval-2019 │ ├── main.tex │ └── main.bib ├── changelog.md ├── .github └── workflows │ └── rust.yml ├── benches └── endtoend.rs ├── LICENSE ├── .gitlab-ci.yml ├── README.md ├── Cargo.toml ├── .gitignore └── Cargo.lock /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "statistics")] 2 | pub mod statistics; 3 | -------------------------------------------------------------------------------- /docs/qbfeval-2020/main.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ltentrup/caqe/HEAD/docs/qbfeval-2020/main.pdf -------------------------------------------------------------------------------- /src/dimacs.rs: -------------------------------------------------------------------------------- 1 | /// This trait describes objects that can be repesented as 2 | /// (a possible extension) of the DIMACS file format. 3 | pub trait Dimacs { 4 | fn dimacs(&self) -> String; 5 | } 6 | -------------------------------------------------------------------------------- /src/bin/caqe.rs: -------------------------------------------------------------------------------- 1 | use std::{env, process}; 2 | 3 | fn main() { 4 | let args: Vec = env::args().collect(); 5 | 6 | let config = caqe::CaqeConfig::new(&args); 7 | 8 | println!("c {:?}", config); 9 | 10 | let result = config.run().unwrap_or_else(|err| { 11 | eprintln!("Problem while solving: {}", err); 12 | process::exit(1); 13 | }); 14 | 15 | println!("c {:?}", result); 16 | process::exit(result as i32); 17 | } 18 | -------------------------------------------------------------------------------- /src/bin/dcaqe.rs: -------------------------------------------------------------------------------- 1 | use std::{env, process}; 2 | 3 | fn main() { 4 | let args: Vec = env::args().collect(); 5 | 6 | let config = caqe::DCaqeConfig::new(&args); 7 | 8 | println!("c {:?}", config); 9 | 10 | let result = config.run().unwrap_or_else(|err| { 11 | eprintln!("Problem while solving: {}", err); 12 | process::exit(1); 13 | }); 14 | 15 | println!("c {:?}", result); 16 | process::exit(result as i32); 17 | } 18 | -------------------------------------------------------------------------------- /src/bin/experiment.rs: -------------------------------------------------------------------------------- 1 | use std::{env, process}; 2 | 3 | fn main() { 4 | let args: Vec = env::args().collect(); 5 | 6 | let config = caqe::experiment::ExperimentConfig::new(&args).unwrap_or_else(|err| { 7 | eprintln!("Error during execution: {}", err); 8 | process::exit(1); 9 | }); 10 | 11 | config.run().unwrap_or_else(|err| { 12 | eprintln!("Error during execution: {}", err); 13 | process::exit(1); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [4.0.1] - 2020-01-21 11 | ### Fixed 12 | - Crash during parsing caused by an empty matrix and innnermost universal quantification (thanks to Andreas Niskanen) 13 | - Crash when requesting partial assignments (`--qdo`) in combination with miniscoping (thanks to Valentin Mayer-Eichberger) 14 | - Fixed rust deprecation warnings and clippy suggestions 15 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Install Dependencies 13 | run: | 14 | sudo apt-get update -yqq 15 | sudo apt-get install -yqq --no-install-recommends build-essential cmake xxd 16 | rustup update 17 | rustup component add clippy rustfmt 18 | - name: Check format 19 | run: cargo fmt --verbose -- --check 20 | - name: Run clippy 21 | run: cargo clippy --verbose -- #-D warnings 22 | - name: Build 23 | run: cargo build --verbose 24 | - name: Run tests 25 | run: cargo test --verbose 26 | -------------------------------------------------------------------------------- /src/solve/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod caqe; 2 | #[cfg(dcaqe)] 3 | pub mod dcaqe; 4 | 5 | use crate::dimacs::Dimacs; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] 9 | pub enum SolverResult { 10 | Satisfiable = 10, 11 | Unsatisfiable = 20, 12 | Unknown = 30, 13 | } 14 | 15 | impl Dimacs for SolverResult { 16 | #[must_use] 17 | fn dimacs(&self) -> String { 18 | match *self { 19 | Self::Satisfiable => String::from("1"), 20 | Self::Unsatisfiable => String::from("0"), 21 | Self::Unknown => String::from("-1"), 22 | } 23 | } 24 | } 25 | 26 | pub trait Solver { 27 | fn solve(&mut self) -> SolverResult; 28 | } 29 | -------------------------------------------------------------------------------- /benches/endtoend.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | mod instances; 6 | 7 | use caqe::{dimacs::Dimacs, parse::qdimacs, CaqeSolver, Solver, SolverResult}; 8 | use test::Bencher; 9 | 10 | #[bench] 11 | fn endtoend_bench_arbiter_05_comp_error01_qbf_hardness_depth_8_qdimacs(b: &mut Bencher) { 12 | b.iter(|| { 13 | let instance = &instances::ARBITER_05_COMP_ERROR01_QBF_HARDNESS_DEPTH_8_QDIMACS; 14 | let mut matrix = qdimacs::parse(instance).unwrap(); 15 | matrix.unprenex_by_miniscoping(); 16 | let mut solver = CaqeSolver::new(&mut matrix); 17 | assert_eq!(solver.solve(), SolverResult::Unsatisfiable); 18 | assert_eq!(solver.qdimacs_output().dimacs(), "s cnf 0 1056 3040\nV -26 0\nV -31 0\nV -36 0\nV -41 0\nV -46 0\nV -51 0\nV -57 0\nV -63 0\nV -69 0\nV -75 0\n"); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Leander Tentrup 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "rust:latest" 2 | 3 | # Install additional packages, like a C compiler and cmake 4 | # 5 | # Notes: 6 | # * xxd is needed to build cryptominisat 7 | before_script: 8 | - apt-get update -yqq 9 | - apt-get install -yqq --no-install-recommends build-essential cmake xxd 10 | - rustup component add clippy-preview rustfmt-preview 11 | 12 | # Use cargo to test the project 13 | cargo:test: 14 | script: 15 | - rustc --version && cargo --version # Print version info for debugging 16 | - cargo test --all --all-features --verbose 17 | 18 | # Use cargo to check the source code formatting 19 | cargo:check-format: 20 | script: 21 | - rustc --version && cargo --version # Print version info for debugging 22 | - cargo fmt --all --verbose -- --check 23 | 24 | # Use cargo to run clippy (Rust linter) 25 | cargo:clippy: 26 | script: 27 | - rustc --version && cargo --version # Print version info for debugging 28 | - cargo clippy --all-features -- -D warnings 29 | 30 | # Use cargo to test the project on nightly toolchain 31 | # cargo:test-nightly: 32 | # image: rustlang/rust:nightly 33 | # script: 34 | # - rustc --version && cargo --version # Print version info for debugging 35 | # - cargo test --all --all-features --verbose 36 | # allow_failure: true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CAQE 2 | 3 | CAQE is a solver for quantified Boolean formulas (QBF) in prenex conjunctive normal (prenex CNF) form supporting the [QDIMACS](http://www.qbflib.org/qdimacs.html) file format. 4 | dCAQE is a solver for dependency quantified Boolean formulas (DQBF) in prenex conjunctive normal form supporting the DQDIMACS file format. 5 | 6 | ## Installation 7 | 8 | CAQE is written in [Rust](https://www.rust-lang.org/), the latest version of the Rust compiler is available at [rustup.rs](http://rustup.rs). 9 | To build (d)CAQE, execute `cargo build --release` from the checkout which builds the static binaries `caqe` and `dcaqe` located at `./target/release/`. 10 | 11 | ## Usage 12 | 13 | caqe [FLAGS] [OPTIONS] 14 | 15 | `INPUT` should be the path to an instance in [QDIMACS](http://www.qbflib.org/qdimacs.html) file format. 16 | See `--help` for more details on `FLAGS` and `OPTIONS`. 17 | CAQE exits with result code `10` and `20` for satisfiable and unsatisfiable instances, respectively. 18 | 19 | CAQE also supports the QDIMACS output format, which contains partial assignments to the variables, using the flag `--qdo`: 20 | 21 | ``` 22 | $ ./caqe --qdo satisfiable.qdimacs 23 | s cnf 1 3 4 24 | V -1 0 25 | ``` 26 | 27 | where the instance is 28 | 29 | ``` 30 | c satisfiable.qdimacs // comment 31 | p cnf 3 4 // 3 variables, 4 clauses 32 | e 1 0 // ∃ variable 1 33 | a 2 0 // ∀ variable 2 34 | e 3 0 // ∃ variable 3 35 | -1 2 -3 0 // (-1 or 2 or -3) 36 | 2 3 0 // (2 or 3) 37 | -2 3 0 // (-2 or 3) 38 | 1 3 0 // (1 or 3) 39 | ``` -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "caqe" 3 | version = "4.0.1" 4 | authors = ["Leander Tentrup "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | cryptominisat = { git = "https://github.com/ltentrup/cryptominisat-rs.git", rev = "79380e6117fb83dfce7224fffcee992446577686" } 9 | log = { version = "0.4.11", features = ["release_max_level_warn"] } 10 | env_logger = "0.7.1" 11 | bit-vec = "0.6.2" 12 | tempfile = "3.1.0" # for first running preprocessor and piping result to temporary 13 | clap = "2.33.1" # parsing command line arguments 14 | rustc-hash = "1.1.0" # fast, non-randomized HashMap/HashSet 15 | uncover = "0.1.1" # test maintainence 16 | ena = "0.14.0" # union-find implementation 17 | dot = "0.1.4" # graphviz dot representation of structures 18 | jemallocator = { version = "0.3.2", optional = true } 19 | serde = { version = "1.0", features = ["derive"] } 20 | serde_json = "1.0" 21 | indicatif = "0.15.0" # progress bars 22 | atomicwrites = "0.2.5" # atomic file writes 23 | colored-diff = "0.2.2" # visualize differences in configurations 24 | 25 | 26 | [patch.crates-io] 27 | #cryptominisat = { git = "https://github.com/ltentrup/cryptominisat-rs.git" } 28 | 29 | [features] 30 | default = ["jemalloc"] 31 | statistics = [] # enable statistics 32 | jemalloc = ["jemallocator"] # use jemalloc allocator instead of system one 33 | dcaqe = [] # build dcaqe 34 | largemem = ["cryptominisat/largemem"] 35 | 36 | [profile.release] 37 | opt-level = 3 38 | lto = true 39 | codegen-units = 1 40 | incremental = false 41 | 42 | [[bin]] 43 | name = "caqe" 44 | path = "src/bin/caqe.rs" 45 | 46 | [[bin]] 47 | name = "dcaqe" 48 | path = "src/bin/dcaqe.rs" 49 | required-features = ["dcaqe"] 50 | 51 | [[bin]] 52 | name = "experiment" 53 | path = "src/bin/experiment.rs" 54 | required-features = ["statistics"] 55 | -------------------------------------------------------------------------------- /docs/qbfeval-2018/main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[11pt,twocolumn]{article} 2 | 3 | \title{CAQE at QBFEval 2018} 4 | \author{Leander Tentrup, Saarland University \and Markus N. Rabe, University of California, Berkeley} 5 | \date{} 6 | 7 | \usepackage{amsmath} 8 | \usepackage{mathpazo} 9 | \usepackage{Alegreya} 10 | \usepackage{url} 11 | 12 | \newcommand{\caqe}{\text{CAQE}} 13 | 14 | \begin{document} 15 | 16 | \maketitle 17 | 18 | \section{Introduction} 19 | 20 | This paper presents the QBF solver $\caqe$~\cite{conf/fmcad/RabeT15} as submitted to QBFEval 2018. 21 | $\caqe$ aims to be a high performance QBF solver by carefully mixing orthogonal solving techniques and using optimized data structures. 22 | The source code of $\caqe$ is available at \url{https://github.com/ltentrup/caqe}. 23 | 24 | \section{Major Improvements} 25 | 26 | \paragraph{New Implementation.} 27 | 28 | The new version of $\caqe$ is implemented in the programming language Rust. 29 | The previous C implementation accumulated much cruft originating back to the initial implementation from 2014. 30 | The new implementation optimizes data structures and avoided bottlenecks that we discovered over the years. 31 | As the underlying SAT solver, we use CryptoMiniSat~\cite{conf/sat/SoosNC09} in version 5.5. 32 | 33 | \paragraph{Preprocessing.} 34 | 35 | Preprocessing is an important factor in the performance of $\caqe$. 36 | We submitted two configurations of $\caqe$, one using Bloqqer~\cite{conf/cade/BiereLS11} and one using HQSPre~\cite{conf/tacas/WimmerRM017}. 37 | 38 | \paragraph{Expansion and Strong Unsat Refinement.} 39 | 40 | As last years version, $\caqe$ implements two additional refinement methods~\cite{conf/cav/Tentrup17}. 41 | \emph{Expansion refinement} does partial expansion for the innermost quantifier alternation similar to RAReQS. 42 | \emph{Strong unsat refinement} improves on the standard disjunctive refinement by excluding a conjunction subsumed clauses. 43 | 44 | \paragraph{Tree-shaped Quantifier Prefix.} 45 | $\caqe$ does recursion on the quantifier prefix of the QBF. 46 | From the linear quantifier prefix, we try to reconstruct a tree-shaped prefix by applying the mini-scoping rules. 47 | 48 | \paragraph{Abstraction Optimization.} 49 | 50 | We implemented an extension of the abstraction minimization techniques~\cite{conf/sat/BalabanovJSMB16}. 51 | For universal abstractions, we use the universal variable as $t$-literal if it is the only universal variable in the clause (and the outermost quantified variable in clause). 52 | For both polarities, we do only generate one (abstraction) clause if two clauses are equal with respect to variables bound $\leq$ current level. 53 | We also implemented refinement literal subsumption, but in our experiments this did not improve solving time. 54 | 55 | \paragraph{QDIMACS Output.} 56 | 57 | $\caqe$ now supports the QDIMACS output format. 58 | Using the partial assignments from Bloqqer~\cite{conf/date/SeidlK14}, $\caqe$ can provide complete assignments even in case of preprocessing. 59 | 60 | 61 | \section{Acknowledgments} 62 | 63 | This work was supported by the European Research Council (ERC) Grant OSARES (No. 683300). 64 | 65 | 66 | \bibliographystyle{plain} 67 | \bibliography{main} 68 | 69 | \end{document} -------------------------------------------------------------------------------- /docs/qbfeval-2019/main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[11pt,twocolumn]{article} 2 | 3 | \title{CAQE, dCAQE, and QuAbS at QBFEval 2019} 4 | \author{Leander Tentrup, Saarland University} 5 | \date{} 6 | 7 | \usepackage{amsmath} 8 | \usepackage{mathpazo} 9 | \usepackage{Alegreya} 10 | \usepackage{url} 11 | \usepackage{cite} 12 | 13 | \newcommand{\caqe}{\text{CAQE}} 14 | \newcommand{\dcaqe}{\text{dCAQE}} 15 | \newcommand{\quabs}{\text{QuAbS}} 16 | 17 | \begin{document} 18 | 19 | \maketitle 20 | 21 | \section{Introduction} 22 | 23 | This paper gives an overview of the submissions to QBFEval 2019. 24 | The prenex CNF solver $\caqe$~\cite{conf/fmcad/RabeT15}, the prenex non-CNF solver $\quabs$~\cite{journals/corr/Tentrup16}, and the DQBF solver $\dcaqe$~\cite{conf/sat/RabeT19}. 25 | 26 | \subsection{$\caqe$ and $\dcaqe$} 27 | 28 | The solver $\caqe$ (version $4.0$) is based on the \emph{clausal abstraction} algorithm~\cite{conf/fmcad/RabeT15,conf/ijcai/JanotaM15}\footnote{called \emph{clause selection} in~\cite{conf/ijcai/JanotaM15}} for solving QBF. 29 | $\dcaqe$ implements a variant of the algorithm for dependency quantified Boolean formulas~\cite{conf/sat/RabeT19}. 30 | The implementation is written in Rust and uses CryptoMiniSat~\cite{conf/sat/SoosNC09} in version 5.0.1 as the underlying SAT solver. 31 | The source code of $\caqe$ and $\dcaqe$ is available at \url{https://github.com/ltentrup/caqe}. 32 | We submitted three configurations of $\caqe$, one using only HQSPre~\cite{conf/tacas/WimmerRM017} as preprocessor, one configuration using both, Bloqqer~\cite{conf/cade/BiereLS11} and HQSPre, and one configuration that produces QDIMACS outputs using the partial assignments from Bloqqer~\cite{conf/date/SeidlK14}. 33 | For $\dcaqe$, we submitted one configuration using HQSPre as preprocessor. 34 | 35 | \subsection{$\quabs$} 36 | 37 | $\quabs$ is based on the extension of clausal abstraction to formulas in negation normal form~\cite{journals/corr/Tentrup16,conf/sat/Tentrup16}. 38 | The implementation is written in C++ and uses CryptoMiniSat~\cite{conf/sat/SoosNC09} in version 5.5 as the underlying SAT solver. 39 | The source code of $\quabs$ is available at \url{https://github.com/ltentrup/quabs}. 40 | In addition to $\quabs$, we submitted a configuration of $\caqe$ that transforms the circuit representation and its negation to CNF. 41 | 42 | \section{Major Improvements} 43 | 44 | \paragraph{Improved Expansions.} 45 | 46 | Previous versions of $\caqe$ only implemented expansion refinement for the innermost quantifier alternation. 47 | $\caqe$ version $4.0$ produces partial expansion trees during solving and builds expansion refinements at every existential quantifier as described in~\cite{conf/cav/Tentrup17}. 48 | 49 | \paragraph{Expanding Conflict Clauses.} 50 | 51 | In~\cite{conf/cav/Tentrup17} we proposed to apply $\forall\text{Exp+Res}$~\cite{journals/tcs/JanotaM15} as an axiom rule to the clausal abstraction proof system. 52 | During solving additional clauses, called \emph{conflict clauses} as they are learned as a reason why an existential assignment leads to unsatisfiability, are added to the formula. 53 | $\caqe$ version $4.0$ expands those clauses as well when applying expansion refinement. 54 | 55 | 56 | \section{Acknowledgments} 57 | 58 | This work was partially supported by the German Research Foundation (DFG) as part of the Collaborative Research Center ``Foundations of Perspicuous Software Systems’' (TRR 248, 389792660) and by the European Research Council (ERC) Grant OSARES (No. 683300). 59 | 60 | 61 | \bibliographystyle{plain} 62 | \bibliography{main} 63 | 64 | \end{document} -------------------------------------------------------------------------------- /docs/qbfeval-2020/main.tex: -------------------------------------------------------------------------------- 1 | \documentclass[11pt,twocolumn]{article} 2 | 3 | \title{CAQE and QuAbS at QBFEval 2020} 4 | \author{Leander Tentrup, Saarland University} 5 | \date{} 6 | 7 | \usepackage{amsmath} 8 | \usepackage{mathpazo} 9 | \usepackage{Alegreya} 10 | \usepackage{url} 11 | \usepackage{cite} 12 | 13 | \newcommand{\caqe}{\text{CAQE}} 14 | \newcommand{\dcaqe}{\text{dCAQE}} 15 | \newcommand{\quabs}{\text{QuAbS}} 16 | 17 | \begin{document} 18 | 19 | \maketitle 20 | 21 | \section{Introduction} 22 | 23 | This paper gives an overview of the submissions to QBFEval 2019. 24 | The prenex CNF solver $\caqe$~\cite{conf/fmcad/RabeT15} and the prenex non-CNF solver $\quabs$~\cite{journals/corr/Tentrup16}. 25 | We refer to~\cite{journals/jsat/Tentrup19} for a detailed system description. 26 | 27 | 28 | \subsection{$\caqe$} 29 | 30 | The solver $\caqe$ (version $4.0$) is based on the \emph{clausal abstraction} algorithm~\cite{conf/fmcad/RabeT15,conf/ijcai/JanotaM15}\footnote{called \emph{clause selection} in~\cite{conf/ijcai/JanotaM15}} for solving QBF. 31 | The underlying idea of clausal abstraction is to assign variables, where the assignment order is determined by the quantifier prefix until either all clauses are satisfied or there is a set of clauses that cannot be satisfied at the same time. 32 | The effect of assignments, i.e., whether they satisfy a clause, is abstracted into one bit of information per clause, and this information is communicated through the quantifier prefix. 33 | The fundamental data structure of the algorithm is an abstraction, a propositional formula for each maximal block of quantifiers, that, given the valuation of outer variables, generates candidate assignments for the variables bound at this quantifier block. 34 | In case this candidate is refuted by inner quantifiers, the returned counterexample is excluded in the abstraction. 35 | Thus, the clausal abstraction algorithm uses ideas of search-based solving~\cite{series/faia/GiunchigliaMN09}, and counterexample guided abstraction refinement (CEGAR) algorithms~\cite{conf/cav/ClarkeGJLV00}. 36 | A proof-theoretic analysis of the clausal abstraction approach~\cite{conf/cav/Tentrup17} has shown that the refutation proofs correspond to the (level-ordered) $Q$-resolution calculus~\cite{journals/iandc/BuningKF95}. 37 | 38 | The implementation is written in Rust and uses CryptoMiniSat~\cite{conf/sat/SoosNC09} in version 5.0.1 as the underlying SAT solver. 39 | The source code of $\caqe$ is available at \url{https://github.com/ltentrup/caqe}. 40 | We submitted three configurations of $\caqe$, one using only HQSPre~\cite{conf/tacas/WimmerRM017} as preprocessor, one configuration using both, Bloqqer~\cite{conf/cade/BiereLS11} and HQSPre, and one configuration that produces QDIMACS outputs using the partial assignments from Bloqqer~\cite{conf/date/SeidlK14}. 41 | 42 | 43 | \subsection{$\quabs$} 44 | 45 | $\quabs$ is based on the extension of clausal abstraction to formulas in negation normal form~\cite{journals/corr/Tentrup16,conf/sat/Tentrup16}. 46 | The implementation is written in C++ and uses CryptoMiniSat~\cite{conf/sat/SoosNC09} in version 5.5 as the underlying SAT solver. 47 | The source code of $\quabs$ is available at \url{https://github.com/ltentrup/quabs}. 48 | In addition to $\quabs$, we submitted a configuration of $\caqe$ that transforms the circuit representation and its negation to CNF. 49 | 50 | 51 | \section{Acknowledgments} 52 | 53 | This work was partially supported by the German Research Foundation (DFG) as part of the Collaborative Research Center ``Foundations of Perspicuous Software Systems’' (TRR 248, 389792660) and by the European Research Council (ERC) Grant OSARES (No. 683300). 54 | 55 | 56 | \bibliographystyle{plain} 57 | \bibliography{main} 58 | 59 | \end{document} -------------------------------------------------------------------------------- /src/literal.rs: -------------------------------------------------------------------------------- 1 | use rustc_hash::FxHashMap; 2 | use std::ops; 3 | 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] 5 | pub struct Variable(u32); 6 | 7 | #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)] 8 | pub struct Literal { 9 | x: u32, 10 | } 11 | 12 | #[derive(PartialEq, Eq, Clone)] 13 | pub struct Assignment(FxHashMap); 14 | 15 | impl Into for Variable { 16 | fn into(self) -> u32 { 17 | self.0 18 | } 19 | } 20 | impl Into for &Variable { 21 | #[must_use] 22 | fn into(self) -> u32 { 23 | self.0 24 | } 25 | } 26 | 27 | impl Into for Variable { 28 | fn into(self) -> usize { 29 | self.0 as usize 30 | } 31 | } 32 | 33 | impl From for Variable { 34 | fn from(val: u32) -> Self { 35 | Self(val) 36 | } 37 | } 38 | impl From for Variable { 39 | fn from(val: usize) -> Self { 40 | #[allow(clippy::cast_possible_truncation)] 41 | Self(val as u32) 42 | } 43 | } 44 | 45 | impl Literal { 46 | pub fn new>(variable: V, signed: bool) -> Self { 47 | let variable = variable.into(); 48 | Self { 49 | x: variable.0 << 1 | (signed as u32), 50 | } 51 | } 52 | 53 | /// Returns true if `Literal` is signed 54 | /// 55 | /// # Examples 56 | /// 57 | /// ``` 58 | /// assert!(caqe::Literal::new(0_u32, true).signed()); 59 | /// assert!(!caqe::Literal::new(0_u32, false).signed()); 60 | /// ``` 61 | #[must_use] 62 | pub fn signed(self) -> bool { 63 | (self.x & 1) != 0 64 | } 65 | 66 | #[must_use] 67 | pub fn unsigned(self) -> Self { 68 | Self { x: self.x & !1 } 69 | } 70 | 71 | #[must_use] 72 | pub fn variable(self) -> Variable { 73 | Variable(self.x >> 1) 74 | } 75 | 76 | #[must_use] 77 | pub fn dimacs(self) -> i32 { 78 | #[allow(clippy::cast_possible_wrap)] 79 | let base = self.variable().0 as i32; 80 | if self.signed() { 81 | -base 82 | } else { 83 | base 84 | } 85 | } 86 | } 87 | 88 | impl ops::Neg for Literal { 89 | type Output = Self; 90 | 91 | #[must_use] 92 | fn neg(self) -> Self { 93 | Self { x: self.x ^ 1 } 94 | } 95 | } 96 | 97 | impl From for Literal { 98 | #[must_use] 99 | fn from(literal: i32) -> Self { 100 | let signed = literal < 0; 101 | #[allow(clippy::cast_sign_loss)] 102 | let abs = Variable(literal.abs() as u32); 103 | Self::new(abs, signed) 104 | } 105 | } 106 | 107 | impl std::fmt::Debug for Literal { 108 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 109 | write!(f, "Literal({})", self.dimacs()) 110 | } 111 | } 112 | 113 | impl std::fmt::Display for Variable { 114 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 115 | write!(f, "{}", self.0) 116 | } 117 | } 118 | 119 | impl Assignment { 120 | #[allow(dead_code)] 121 | pub(crate) fn hamming(&self, other: &Self) -> u32 { 122 | let mut count = 0; 123 | for (var, &val) in &self.0 { 124 | if other.0[var] != val { 125 | count += 1; 126 | } 127 | } 128 | count 129 | } 130 | } 131 | 132 | impl From> for Assignment { 133 | fn from(map: FxHashMap) -> Self { 134 | Self(map) 135 | } 136 | } 137 | 138 | impl std::fmt::Debug for Assignment { 139 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 140 | for (var, val) in &self.0 { 141 | let x = Literal::new(*var, !val); 142 | write!(f, "{} ", x.dimacs())?; 143 | } 144 | Ok(()) 145 | } 146 | } 147 | 148 | #[cfg(test)] 149 | mod tests { 150 | 151 | use std::mem; 152 | 153 | use super::*; 154 | 155 | #[test] 156 | fn size_of_literal() { 157 | let result = mem::size_of::(); 158 | assert!( 159 | result == 4, 160 | "Size of `Literal` should be 4 bytes, was `{}`", 161 | result 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/preprocessor.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | matrix::{hierarchical::HierarchicalPrefix, Matrix}, 3 | parse::qdimacs, 4 | CaqeSpecificSolverConfig, 5 | }; 6 | use std::{ 7 | error::Error, 8 | fs::File, 9 | io::{self, prelude::*, Read, SeekFrom}, 10 | process::{Command, Stdio}, 11 | str::FromStr, 12 | }; 13 | use tempfile::tempfile; 14 | 15 | #[allow(clippy::module_name_repetitions)] 16 | #[derive(Debug)] 17 | pub enum QBFPreprocessor { 18 | Bloqqer, 19 | HQSPre, 20 | } 21 | 22 | impl FromStr for QBFPreprocessor { 23 | type Err = Box; 24 | fn from_str(s: &str) -> Result { 25 | match s { 26 | "bloqqer" => Ok(Self::Bloqqer), 27 | "hqspre" => Ok(Self::HQSPre), 28 | _ => panic!("unknown value {} for QBFPreprocessor", s), 29 | } 30 | } 31 | } 32 | 33 | impl QBFPreprocessor { 34 | pub fn values() -> &'static [&'static str] { 35 | &["bloqqer", "hqspre"] 36 | } 37 | } 38 | 39 | pub fn preprocess( 40 | config: &super::CommonSolverConfig, 41 | ) -> Result< 42 | ( 43 | Matrix, 44 | Option, 45 | ), 46 | Box, 47 | > { 48 | let mut partial_qdo = None; 49 | let mut contents = String::new(); 50 | match config.specific.preprocessor { 51 | None => match &config.filename { 52 | None => { 53 | //reading from stdin 54 | io::stdin().read_to_string(&mut contents)?; 55 | } 56 | Some(filename) => { 57 | let mut f = File::open(&filename)?; 58 | f.read_to_string(&mut contents)?; 59 | } 60 | }, 61 | Some(QBFPreprocessor::Bloqqer) => { 62 | let filename = config 63 | .filename 64 | .as_ref() 65 | .expect("filename has to be present when using preprocessor"); 66 | let mut f = tempfile()?; 67 | let f_copy = f.try_clone()?; 68 | if config.specific.qdimacs_output { 69 | let mut cert = tempfile()?; 70 | let cert_copy = cert.try_clone()?; 71 | Command::new("./bloqqer-qdo") 72 | .arg("--partial-assignment=1") 73 | .arg(&filename) 74 | .stdout(f_copy) 75 | .stderr(cert_copy) 76 | .spawn() 77 | .expect("bloqqer failed to start") 78 | .wait() 79 | .expect("failed to wait on bloqqer"); 80 | cert.seek(SeekFrom::Start(0))?; 81 | let mut qdo = String::new(); 82 | cert.read_to_string(&mut qdo)?; 83 | partial_qdo = Some(qdo.parse()?); 84 | } else { 85 | Command::new("./bloqqer") 86 | .arg("--keep=0") 87 | .arg(&filename) 88 | .stdout(f_copy) 89 | .stderr(Stdio::null()) 90 | .spawn() 91 | .expect("bloqqer failed to start") 92 | .wait() 93 | .expect("failed to wait on bloqqer"); 94 | }; 95 | 96 | f.seek(SeekFrom::Start(0))?; 97 | f.read_to_string(&mut contents)?; 98 | } 99 | Some(QBFPreprocessor::HQSPre) => { 100 | let filename = config 101 | .filename 102 | .as_ref() 103 | .expect("filename has to be present when using preprocessor"); 104 | let mut f = tempfile()?; 105 | let f_copy = f.try_clone()?; 106 | let mut child = Command::new("./hqspre") 107 | .arg("--pipe") 108 | .arg(&filename) 109 | .stdout(f_copy) 110 | .stderr(Stdio::null()) 111 | .spawn() 112 | .expect("hqspre failed to start"); 113 | child.wait().expect("failed to wait on hqspre"); 114 | f.seek(SeekFrom::Start(0))?; 115 | f.read_to_string(&mut contents)?; 116 | } 117 | } 118 | 119 | Ok((qdimacs::parse(&contents)?, partial_qdo)) 120 | } 121 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/rust,macos,latex 3 | 4 | ### LaTeX ### 5 | ## Core latex/pdflatex auxiliary files: 6 | *.aux 7 | *.lof 8 | *.log 9 | *.lot 10 | *.fls 11 | *.out 12 | *.toc 13 | *.fmt 14 | *.fot 15 | *.cb 16 | *.cb2 17 | 18 | ## Intermediate documents: 19 | *.dvi 20 | *.xdv 21 | *-converted-to.* 22 | # these rules might exclude image files for figures etc. 23 | # *.ps 24 | # *.eps 25 | # *.pdf 26 | 27 | ## Generated if empty string is given at "Please type another file name for output:" 28 | .pdf 29 | 30 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 31 | *.bbl 32 | *.bcf 33 | *.blg 34 | *-blx.aux 35 | *-blx.bib 36 | *.run.xml 37 | 38 | ## Build tool auxiliary files: 39 | *.fdb_latexmk 40 | *.synctex 41 | *.synctex(busy) 42 | *.synctex.gz 43 | *.synctex.gz(busy) 44 | *.pdfsync 45 | *Notes.bib 46 | 47 | ## Auxiliary and intermediate files from other packages: 48 | # algorithms 49 | *.alg 50 | *.loa 51 | 52 | # achemso 53 | acs-*.bib 54 | 55 | # amsthm 56 | *.thm 57 | 58 | # beamer 59 | *.nav 60 | *.pre 61 | *.snm 62 | *.vrb 63 | 64 | # changes 65 | *.soc 66 | 67 | # cprotect 68 | *.cpt 69 | 70 | # elsarticle (documentclass of Elsevier journals) 71 | *.spl 72 | 73 | # endnotes 74 | *.ent 75 | 76 | # fixme 77 | *.lox 78 | 79 | # feynmf/feynmp 80 | *.mf 81 | *.mp 82 | *.t[1-9] 83 | *.t[1-9][0-9] 84 | *.tfm 85 | 86 | #(r)(e)ledmac/(r)(e)ledpar 87 | *.end 88 | *.?end 89 | *.[1-9] 90 | *.[1-9][0-9] 91 | *.[1-9][0-9][0-9] 92 | *.[1-9]R 93 | *.[1-9][0-9]R 94 | *.[1-9][0-9][0-9]R 95 | *.eledsec[1-9] 96 | *.eledsec[1-9]R 97 | *.eledsec[1-9][0-9] 98 | *.eledsec[1-9][0-9]R 99 | *.eledsec[1-9][0-9][0-9] 100 | *.eledsec[1-9][0-9][0-9]R 101 | 102 | # glossaries 103 | *.acn 104 | *.acr 105 | *.glg 106 | *.glo 107 | *.gls 108 | *.glsdefs 109 | 110 | # gnuplottex 111 | *-gnuplottex-* 112 | 113 | # gregoriotex 114 | *.gaux 115 | *.gtex 116 | 117 | # hyperref 118 | *.brf 119 | 120 | # knitr 121 | *-concordance.tex 122 | # TODO Comment the next line if you want to keep your tikz graphics files 123 | *.tikz 124 | *-tikzDictionary 125 | 126 | # listings 127 | *.lol 128 | 129 | # makeidx 130 | *.idx 131 | *.ilg 132 | *.ind 133 | *.ist 134 | 135 | # minitoc 136 | *.maf 137 | *.mlf 138 | *.mlt 139 | *.mtc[0-9]* 140 | *.slf[0-9]* 141 | *.slt[0-9]* 142 | *.stc[0-9]* 143 | 144 | # minted 145 | _minted* 146 | *.pyg 147 | 148 | # morewrites 149 | *.mw 150 | 151 | # nomencl 152 | *.nlo 153 | 154 | # pax 155 | *.pax 156 | 157 | # pdfpcnotes 158 | *.pdfpc 159 | 160 | # sagetex 161 | *.sagetex.sage 162 | *.sagetex.py 163 | *.sagetex.scmd 164 | 165 | # scrwfile 166 | *.wrt 167 | 168 | # sympy 169 | *.sout 170 | *.sympy 171 | sympy-plots-for-*.tex/ 172 | 173 | # pdfcomment 174 | *.upa 175 | *.upb 176 | 177 | # pythontex 178 | *.pytxcode 179 | pythontex-files-*/ 180 | 181 | # thmtools 182 | *.loe 183 | 184 | # TikZ & PGF 185 | *.dpth 186 | *.md5 187 | *.auxlock 188 | 189 | # todonotes 190 | *.tdo 191 | 192 | # easy-todo 193 | *.lod 194 | 195 | # xindy 196 | *.xdy 197 | 198 | # xypic precompiled matrices 199 | *.xyc 200 | 201 | # endfloat 202 | *.ttt 203 | *.fff 204 | 205 | # Latexian 206 | TSWLatexianTemp* 207 | 208 | ## Editors: 209 | # WinEdt 210 | *.bak 211 | *.sav 212 | 213 | # Texpad 214 | .texpadtmp 215 | 216 | # Kile 217 | *.backup 218 | 219 | # KBibTeX 220 | *~[0-9]* 221 | 222 | # auto folder when using emacs and auctex 223 | /auto/* 224 | 225 | # expex forward references with \gathertags 226 | *-tags.tex 227 | 228 | ### LaTeX Patch ### 229 | # glossaries 230 | *.glstex 231 | 232 | ### macOS ### 233 | *.DS_Store 234 | .AppleDouble 235 | .LSOverride 236 | 237 | # Icon must end with two \r 238 | Icon 239 | 240 | # Thumbnails 241 | ._* 242 | 243 | # Files that might appear in the root of a volume 244 | .DocumentRevisions-V100 245 | .fseventsd 246 | .Spotlight-V100 247 | .TemporaryItems 248 | .Trashes 249 | .VolumeIcon.icns 250 | .com.apple.timemachine.donotpresent 251 | 252 | # Directories potentially created on remote AFP share 253 | .AppleDB 254 | .AppleDesktop 255 | Network Trash Folder 256 | Temporary Items 257 | .apdisk 258 | 259 | ### Rust ### 260 | # Generated by Cargo 261 | # will have compiled files and executables 262 | /target/ 263 | 264 | # These are backup files generated by rustfmt 265 | **/*.rs.bk 266 | 267 | 268 | # End of https://www.gitignore.io/api/rust,macos,latex -------------------------------------------------------------------------------- /src/matrix/schemes.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of dependency schemes 2 | //! 3 | //! A dependency scheme allows to detect pseudo-dependencies in a quantified formula. 4 | //! We currently implement the ``Reflexive resolution-path dependency scheme'' [1] which is sound for QBF and DQBF [2]. 5 | //! 6 | //! [1] ``Soundness of Q-resolution with dependency schemes'' by Slivovsky and Szeider 7 | //! [2] ``Dependency Schemes for DQBF'' by Wimmer et al. 8 | 9 | use crate::{ 10 | literal::{Literal, Variable}, 11 | matrix::{Matrix, Prefix, VariableInfo}, 12 | }; 13 | use log::{debug, trace}; 14 | use rustc_hash::FxHashSet; 15 | 16 | impl Matrix

{ 17 | pub(crate) fn refl_res_path_dep_scheme(&mut self) -> usize { 18 | trace!("refl_res_path_dep_scheme"); 19 | let mut removed = 0; 20 | let mut seen_pos: FxHashSet = FxHashSet::default(); 21 | let mut seen_neg: FxHashSet = FxHashSet::default(); 22 | for var in 1..=self.prefix.variables().max_variable_id() { 23 | let var: Variable = var.into(); 24 | let info = self.prefix.variables().get(var); 25 | if !info.is_universal() { 26 | continue; 27 | } 28 | debug_assert!(info.is_bound()); 29 | 30 | seen_neg.clear(); 31 | seen_pos.clear(); 32 | 33 | self.search_resolution_path(Literal::new(var, false), &mut seen_pos); 34 | self.search_resolution_path(Literal::new(var, true), &mut seen_neg); 35 | 36 | for e_var in 1..=self.prefix.variables().max_variable_id() { 37 | let e_var: Variable = e_var.into(); 38 | let info = self.prefix.variables().get(e_var); 39 | if info.is_universal() { 40 | continue; 41 | } 42 | if !self.prefix.depends_on(e_var, var) { 43 | continue; 44 | } 45 | 46 | let pos = Literal::new(e_var, false); 47 | let neg = Literal::new(e_var, true); 48 | 49 | if (!seen_pos.contains(&pos) || !seen_neg.contains(&pos)) 50 | || (!seen_neg.contains(&pos) && !seen_pos.contains(&neg)) 51 | { 52 | self.prefix.mut_vars().get_mut(e_var).remove_dependency(var); 53 | debug!("detected spurious dependency {} of {}", var, e_var); 54 | removed += 1; 55 | } 56 | } 57 | } 58 | removed 59 | } 60 | 61 | fn search_resolution_path(&self, root: Literal, seen: &mut FxHashSet) { 62 | let mut stack = Vec::new(); 63 | for &clause_id in self.occurrences(root) { 64 | let clause = &self.clauses[clause_id as usize]; 65 | for literal in clause.iter() { 66 | if seen.contains(literal) { 67 | continue; 68 | } 69 | seen.insert(*literal); 70 | let info = self.prefix.variables().get(literal.variable()); 71 | if info.is_universal() { 72 | continue; 73 | } 74 | if !self.prefix.depends_on(literal.variable(), root.variable()) { 75 | continue; 76 | } 77 | stack.push(literal); 78 | } 79 | } 80 | while let Some(&lit) = stack.pop() { 81 | let lit = -lit; 82 | for &clause_id in self.occurrences(lit) { 83 | let clause = &self.clauses[clause_id as usize]; 84 | for literal in clause.iter() { 85 | if seen.contains(literal) { 86 | continue; 87 | } 88 | seen.insert(*literal); 89 | let info = self.prefix.variables().get(literal.variable()); 90 | if info.is_universal() { 91 | continue; 92 | } 93 | if !self.prefix.depends_on(literal.variable(), root.variable()) { 94 | continue; 95 | } 96 | stack.push(literal); 97 | } 98 | } 99 | } 100 | //dbg!(seen); 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | 107 | use super::*; 108 | use crate::parse::qdimacs; 109 | 110 | #[test] 111 | fn test_dep_scheme() { 112 | let instance = "c 113 | c reflexive resolution path dependency should be able to eliminate 4 from 2 and 3 accoding to Example 2 of ``Soundness of Q-resolution with dependency schemes'' 114 | p cnf 4 4 115 | e 1 0 116 | a 4 0 117 | e 2 3 0 118 | 4 2 0 119 | -4 3 0 120 | 1 -3 0 121 | -1 -2 0 122 | "; 123 | let mut matrix = qdimacs::parse(&instance).unwrap(); 124 | matrix.refl_res_path_dep_scheme(); 125 | 126 | assert!(!matrix.prefix.depends_on(2_u32, 4_u32)); 127 | assert!(!matrix.prefix.depends_on(3_u32, 4_u32)); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/parse/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dimacs; 2 | pub mod dqdimacs; 3 | pub mod qdimacs; 4 | 5 | use crate::literal::Literal; 6 | use std::{error::Error, str::Chars}; 7 | 8 | #[allow(clippy::module_name_repetitions)] 9 | #[derive(Debug, Eq, PartialEq)] 10 | pub struct ParseError { 11 | pub msg: String, 12 | pub pos: SourcePos, 13 | } 14 | 15 | impl std::fmt::Display for ParseError { 16 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 17 | write!(f, "parse error: {} at {}", self.msg, self.pos) 18 | } 19 | } 20 | 21 | impl Error for ParseError { 22 | #[must_use] 23 | fn description(&self) -> &str { 24 | self.msg.as_str() 25 | } 26 | 27 | #[must_use] 28 | fn cause(&self) -> Option<&dyn Error> { 29 | Some(self) 30 | } 31 | } 32 | 33 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 34 | pub struct SourcePos { 35 | line: usize, 36 | column: usize, 37 | } 38 | 39 | impl SourcePos { 40 | fn new() -> Self { 41 | Self { line: 0, column: 0 } 42 | } 43 | 44 | fn advance(&mut self, len: usize) { 45 | self.column += len; 46 | } 47 | 48 | fn newline(&mut self) { 49 | self.line += 1; 50 | self.column = 0; 51 | } 52 | } 53 | 54 | impl std::fmt::Display for SourcePos { 55 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 56 | write!(f, "{}:{}", self.line, self.column) 57 | } 58 | } 59 | 60 | struct CharIterator<'a> { 61 | chars: Chars<'a>, 62 | pos: SourcePos, 63 | next_char: Option, 64 | } 65 | 66 | impl<'a> CharIterator<'a> { 67 | fn new(content: &'a str) -> CharIterator<'a> { 68 | let mut chars = content.chars(); 69 | CharIterator { 70 | next_char: chars.next(), 71 | chars, 72 | pos: SourcePos::new(), 73 | } 74 | } 75 | 76 | fn next(&mut self) -> Option { 77 | match self.next_char { 78 | None => None, 79 | Some(c) => { 80 | if c == '\n' { 81 | self.pos.newline() 82 | } else { 83 | self.pos.advance(1) 84 | } 85 | self.next_char = self.chars.next(); 86 | Some(c) 87 | } 88 | } 89 | } 90 | 91 | fn read_literal(&mut self, first: char) -> Result { 92 | let signed; 93 | let mut value; 94 | if first == '-' { 95 | signed = true; 96 | value = None; 97 | } else if let Some(digit) = first.to_digit(10) { 98 | signed = false; 99 | value = Some(digit); 100 | } else { 101 | panic!( 102 | "Expect first character of literal to be a digit or `-`, were given `{}`", 103 | first 104 | ); 105 | } 106 | while let Some(c) = self.next() { 107 | if c.is_ascii_whitespace() { 108 | break; 109 | } 110 | if let Some(digit) = c.to_digit(10) { 111 | value = match value { 112 | None => Some(digit), 113 | Some(prev) => Some(prev * 10 + digit), 114 | } 115 | } else { 116 | return Err(ParseError { 117 | msg: format!( 118 | "Encountered non-digit character `{}` while parsing literal", 119 | c 120 | ), 121 | pos: self.pos, 122 | }); 123 | } 124 | } 125 | if let Some(value) = value { 126 | Ok(Literal::new(value, signed)) 127 | } else { 128 | assert!(first == '-'); 129 | Err(ParseError { 130 | msg: "Expect digits following `-` character".to_string(), 131 | pos: self.pos, 132 | }) 133 | } 134 | } 135 | 136 | fn expect_char(&mut self, expected: char) -> Result<(), ParseError> { 137 | match self.next() { 138 | None => Err(ParseError { 139 | msg: "Unexpected end of input".to_string(), 140 | pos: self.pos, 141 | }), 142 | Some(c) => { 143 | if c == expected { 144 | Ok(()) 145 | } else { 146 | Err(ParseError { 147 | msg: format!("Expected character `{}`, but found `{}`", expected, c), 148 | pos: self.pos, 149 | }) 150 | } 151 | } 152 | } 153 | } 154 | 155 | fn expect_str(&mut self, expected: &str) -> Result<(), ParseError> { 156 | for c in expected.chars() { 157 | self.expect_char(c)?; 158 | } 159 | Ok(()) 160 | } 161 | 162 | fn skip_while

(&mut self, predicate: P) 163 | where 164 | P: Fn(&char) -> bool, 165 | { 166 | while let Some(c) = self.next() { 167 | if !predicate(&c) { 168 | break; 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /docs/qbfeval-2018/main.bib: -------------------------------------------------------------------------------- 1 | %% This BibTeX bibliography file was created using BibDesk. 2 | %% http://bibdesk.sourceforge.net/ 3 | 4 | %% Created for Leander Tentrup at 2018-04-20 22:23:12 +0200 5 | 6 | 7 | %% Saved with string encoding Unicode (UTF-8) 8 | 9 | 10 | 11 | @inproceedings{conf/date/SeidlK14, 12 | Author = {Martina Seidl and Robert K{\"{o}}nighofer}, 13 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 14 | Biburl = {https://dblp.org/rec/bib/conf/date/SeidlK14}, 15 | Booktitle = {Proceedings of {DATE}}, 16 | Date-Added = {2018-04-20 20:23:12 +0000}, 17 | Date-Modified = {2018-04-20 20:23:12 +0000}, 18 | Doi = {10.7873/DATE.2014.162}, 19 | Pages = {1--6}, 20 | Publisher = {European Design and Automation Association}, 21 | Timestamp = {Wed, 15 Nov 2017 16:53:38 +0100}, 22 | Title = {Partial witnesses from preprocessed quantified Boolean formulas}, 23 | Year = {2014}, 24 | Bdsk-Url-1 = {https://doi.org/10.7873/DATE.2014.162}} 25 | 26 | @inproceedings{conf/sat/SoosNC09, 27 | Author = {Mate Soos and Karsten Nohl and Claude Castelluccia}, 28 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 29 | Biburl = {https://dblp.org/rec/bib/conf/sat/SoosNC09}, 30 | Booktitle = {Proceedings of {SAT}}, 31 | Date-Added = {2018-04-18 14:21:23 +0000}, 32 | Date-Modified = {2018-04-18 14:21:23 +0000}, 33 | Doi = {10.1007/978-3-642-02777-2_24}, 34 | Pages = {244--257}, 35 | Publisher = {Springer}, 36 | Series = {LNCS}, 37 | Timestamp = {Tue, 23 May 2017 01:08:19 +0200}, 38 | Title = {Extending {SAT} Solvers to Cryptographic Problems}, 39 | Volume = {5584}, 40 | Year = {2009}, 41 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-642-02777-2_24}} 42 | 43 | @inproceedings{conf/sat/BalabanovJSMB16, 44 | Author = {Valeriy Balabanov and Jie{-}Hong Roland Jiang and Christoph Scholl and Alan Mishchenko and Robert K. Brayton}, 45 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 46 | Biburl = {https://dblp.org/rec/bib/conf/sat/BalabanovJSMB16}, 47 | Booktitle = {Proceedings of {SAT}}, 48 | Date-Added = {2018-04-18 14:14:53 +0000}, 49 | Date-Modified = {2018-04-18 14:14:53 +0000}, 50 | Doi = {10.1007/978-3-319-40970-2_28}, 51 | Pages = {453--469}, 52 | Publisher = {Springer}, 53 | Series = {LNCS}, 54 | Timestamp = {Tue, 23 May 2017 01:08:19 +0200}, 55 | Title = {2QBF: Challenges and Solutions}, 56 | Volume = {9710}, 57 | Year = {2016}, 58 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-319-40970-2_28}} 59 | 60 | @inproceedings{conf/tacas/WimmerRM017, 61 | Author = {Ralf Wimmer and Sven Reimer and Paolo Marin and Bernd Becker}, 62 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 63 | Biburl = {https://dblp.org/rec/bib/conf/tacas/WimmerRM017}, 64 | Booktitle = {Proceedings of {TACAS}}, 65 | Date-Added = {2018-04-18 13:54:38 +0000}, 66 | Date-Modified = {2018-04-18 13:54:38 +0000}, 67 | Doi = {10.1007/978-3-662-54577-5_21}, 68 | Pages = {373--390}, 69 | Series = {LNCS}, 70 | Timestamp = {Wed, 24 May 2017 08:28:32 +0200}, 71 | Title = {HQSpre - An Effective Preprocessor for {QBF} and {DQBF}}, 72 | Volume = {10205}, 73 | Year = {2017}, 74 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-662-54577-5_21}} 75 | 76 | @inproceedings{conf/cade/BiereLS11, 77 | Author = {Armin Biere and Florian Lonsing and Martina Seidl}, 78 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 79 | Biburl = {https://dblp.org/rec/bib/conf/cade/BiereLS11}, 80 | Booktitle = {Proceedings of {CADE-23}}, 81 | Date-Added = {2018-04-18 13:53:42 +0000}, 82 | Date-Modified = {2018-04-18 14:18:24 +0000}, 83 | Doi = {10.1007/978-3-642-22438-6_10}, 84 | Pages = {101--115}, 85 | Publisher = {Springer}, 86 | Series = {LNCS}, 87 | Timestamp = {Sun, 21 May 2017 00:17:18 +0200}, 88 | Title = {Blocked Clause Elimination for {QBF}}, 89 | Volume = {6803}, 90 | Year = {2011}, 91 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-642-22438-6_10}} 92 | 93 | @inproceedings{conf/cav/Tentrup17, 94 | Author = {Leander Tentrup}, 95 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 96 | Biburl = {https://dblp.org/rec/bib/conf/cav/Tentrup17}, 97 | Booktitle = {Proceedings of {CAV}}, 98 | Date-Added = {2018-04-18 13:05:52 +0000}, 99 | Date-Modified = {2018-04-18 13:05:52 +0000}, 100 | Doi = {10.1007/978-3-319-63390-9_25}, 101 | Pages = {475--494}, 102 | Publisher = {Springer}, 103 | Series = {LNCS}, 104 | Timestamp = {Fri, 14 Jul 2017 13:10:55 +0200}, 105 | Title = {On Expansion and Resolution in {CEGAR} Based {QBF} Solving}, 106 | Volume = {10427}, 107 | Year = {2017}, 108 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-319-63390-9_25}} 109 | 110 | @inproceedings{conf/fmcad/RabeT15, 111 | Author = {Markus N. Rabe and Leander Tentrup}, 112 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 113 | Biburl = {https://dblp.org/rec/bib/conf/fmcad/RabeT15}, 114 | Booktitle = {Proceedings of {FMCAD}}, 115 | Date-Added = {2018-04-18 13:05:11 +0000}, 116 | Date-Modified = {2018-04-18 13:05:11 +0000}, 117 | Pages = {136--143}, 118 | Publisher = {IEEE}, 119 | Timestamp = {Thu, 07 Jan 2016 15:57:52 +0100}, 120 | Title = {{CAQE:} {A} Certifying {QBF} Solver}, 121 | Year = {2015}} 122 | -------------------------------------------------------------------------------- /src/utils/statistics.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::{Eq, Ord}, 3 | collections::HashMap, 4 | fmt, 5 | hash::Hash, 6 | }; 7 | 8 | #[derive(Debug)] 9 | pub struct CountingStats 10 | where 11 | E: Eq + Hash + Ord, 12 | { 13 | values: HashMap, 14 | } 15 | 16 | impl CountingStats { 17 | pub fn new() -> Self { 18 | Self { 19 | values: HashMap::new(), 20 | } 21 | } 22 | 23 | pub fn get(&self, value: &E) -> usize { 24 | *self.values.get(value).unwrap_or(&0) 25 | } 26 | 27 | pub fn inc(&mut self, value: E) { 28 | let val = self.values.entry(value).or_insert(0); 29 | *val += 1; 30 | } 31 | 32 | pub fn inc_by(&mut self, value: E, val: usize) { 33 | let val_entry = self.values.entry(value).or_insert(0); 34 | *val_entry += val; 35 | } 36 | } 37 | 38 | impl fmt::Display for CountingStats { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | let mut vals: Vec<_> = self.values.keys().collect(); 41 | vals.sort(); 42 | for event in &vals { 43 | writeln!(f, "{}\t{}", event, self.get(*event))?; 44 | } 45 | Ok(()) 46 | } 47 | } 48 | 49 | use std::cell::RefCell; 50 | use std::rc::Rc; 51 | use std::time::{Duration, Instant}; 52 | 53 | type TimingStatsValues = HashMap>; 54 | 55 | pub struct Timer 56 | where 57 | E: Eq + Hash + Copy, 58 | { 59 | pointer: Rc>>, 60 | phase: E, 61 | begin: Instant, 62 | stopped: bool, 63 | } 64 | 65 | impl Timer { 66 | pub fn stop(&mut self) { 67 | let duration = self.begin.elapsed(); 68 | let mut values = self.pointer.borrow_mut(); 69 | let e = values.entry(self.phase).or_insert_with(Vec::new); 70 | e.push(duration); 71 | self.stopped = true; 72 | } 73 | } 74 | 75 | impl Drop for Timer { 76 | fn drop(&mut self) { 77 | if !self.stopped { 78 | self.stop(); 79 | } 80 | } 81 | } 82 | 83 | pub struct TimingStats 84 | where 85 | E: Eq + Hash + Copy, 86 | { 87 | pointer: Rc>>, 88 | } 89 | 90 | impl TimingStats { 91 | pub fn new() -> Self { 92 | Self { 93 | pointer: Rc::new(RefCell::new(HashMap::new())), 94 | } 95 | } 96 | 97 | pub fn start(&self, phase: E) -> Timer { 98 | Timer { 99 | pointer: self.pointer.clone(), 100 | phase, 101 | begin: Instant::now(), 102 | stopped: false, 103 | } 104 | } 105 | 106 | pub fn count(&self, phase: E) -> usize { 107 | let values = self.pointer.borrow(); 108 | values.get(&phase).map_or(0, Vec::len) 109 | } 110 | 111 | pub fn sum(&self, phase: E) -> Duration { 112 | let values = self.pointer.borrow(); 113 | values 114 | .get(&phase) 115 | .map_or_else(|| Duration::new(0, 0), |v| v.iter().sum()) 116 | } 117 | 118 | pub fn avg(&self, phase: E) -> Duration { 119 | #[allow(clippy::cast_possible_truncation)] 120 | let count = self.count(phase) as u32; 121 | 122 | self.sum(phase) / count 123 | } 124 | 125 | pub fn min(&self, phase: E) -> Duration { 126 | let values = self.pointer.borrow(); 127 | values.get(&phase).map_or_else( 128 | || Duration::new(0, 0), 129 | |v| { 130 | v.iter() 131 | .fold(Duration::new(u64::max_value(), 0), |acc, &val| { 132 | if acc < val { 133 | acc 134 | } else { 135 | val 136 | } 137 | }) 138 | }, 139 | ) 140 | } 141 | 142 | pub fn max(&self, phase: E) -> Duration { 143 | let values = self.pointer.borrow(); 144 | values.get(&phase).map_or_else( 145 | || Duration::new(0, 0), 146 | |v| { 147 | v.iter().fold( 148 | Duration::new(0, 0), 149 | |acc, &val| { 150 | if acc > val { 151 | acc 152 | } else { 153 | val 154 | } 155 | }, 156 | ) 157 | }, 158 | ) 159 | } 160 | } 161 | 162 | impl TimingStats { 163 | pub fn print(&self) { 164 | let values = self.pointer.borrow(); 165 | for &stat in values.keys() { 166 | let sum = self.sum(stat); 167 | let avg = self.avg(stat); 168 | let min = self.min(stat); 169 | let max = self.max(stat); 170 | println!( 171 | " {} count {} sum {}.{} avg: {}.{} min {}.{} max {}.{}", 172 | stat, 173 | self.count(stat), 174 | sum.as_secs(), 175 | sum.subsec_millis(), 176 | avg.as_secs(), 177 | avg.subsec_millis(), 178 | min.as_secs(), 179 | min.subsec_millis(), 180 | max.as_secs(), 181 | max.subsec_millis() 182 | ); 183 | } 184 | } 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | 190 | use super::*; 191 | 192 | #[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] 193 | enum TestEnum { 194 | Case1, 195 | Case2, 196 | } 197 | 198 | #[test] 199 | fn counter() { 200 | let mut counter = CountingStats::new(); 201 | counter.inc(TestEnum::Case1); 202 | assert_eq!(counter.get(&TestEnum::Case1), 1); 203 | assert_eq!(counter.get(&TestEnum::Case2), 0); 204 | counter.inc(TestEnum::Case2); 205 | counter.inc(TestEnum::Case2); 206 | assert_eq!(counter.get(&TestEnum::Case1), 1); 207 | assert_eq!(counter.get(&TestEnum::Case2), 2); 208 | } 209 | 210 | #[test] 211 | fn timer() { 212 | let timing = TimingStats::new(); 213 | let mut timer1 = timing.start(TestEnum::Case1); 214 | let mut timer2 = timing.start(TestEnum::Case2); 215 | timer1.stop(); 216 | { 217 | let _timer = timing.start(TestEnum::Case1); 218 | // atomatically stopped when dropped 219 | } 220 | timer2.stop(); 221 | assert_eq!(timing.count(TestEnum::Case1), 2); 222 | assert_eq!(timing.count(TestEnum::Case2), 1); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /docs/qbfeval-2019/main.bib: -------------------------------------------------------------------------------- 1 | %% This BibTeX bibliography file was created using BibDesk. 2 | %% http://bibdesk.sourceforge.net/ 3 | 4 | %% Created for Leander Tentrup at 2019-04-30 12:25:50 +0200 5 | 6 | 7 | %% Saved with string encoding Unicode (UTF-8) 8 | 9 | 10 | 11 | @article{journals/tcs/JanotaM15, 12 | Author = {Mikol{\'{a}}s Janota and Jo{\~{a}}o Marques{-}Silva}, 13 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 14 | Biburl = {https://dblp.org/rec/bib/journals/tcs/JanotaM15}, 15 | Date-Added = {2019-04-30 08:36:00 +0000}, 16 | Date-Modified = {2019-04-30 08:36:00 +0000}, 17 | Doi = {10.1016/j.tcs.2015.01.048}, 18 | Journal = {Theor. Comput. Sci.}, 19 | Pages = {25--42}, 20 | Timestamp = {Fri, 28 Sep 2018 12:48:40 +0200}, 21 | Title = {Expansion-based {QBF} solving versus Q-resolution}, 22 | Volume = {577}, 23 | Year = {2015}, 24 | Bdsk-Url-1 = {https://doi.org/10.1016/j.tcs.2015.01.048}} 25 | 26 | @inproceedings{conf/sat/Tentrup16, 27 | Author = {Leander Tentrup}, 28 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 29 | Biburl = {https://dblp.org/rec/bib/conf/sat/Tentrup16}, 30 | Booktitle = {Proceedings of {SAT}}, 31 | Date-Added = {2019-04-30 08:08:42 +0000}, 32 | Date-Modified = {2019-04-30 08:08:42 +0000}, 33 | Doi = {10.1007/978-3-319-40970-2\_24}, 34 | Pages = {393--401}, 35 | Publisher = {Springer}, 36 | Series = {LNCS}, 37 | Timestamp = {Tue, 23 May 2017 01:08:19 +0200}, 38 | Title = {Non-prenex {QBF} Solving Using Abstraction}, 39 | Volume = {9710}, 40 | Year = {2016}, 41 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-319-40970-2%5C_24}} 42 | 43 | @inproceedings{conf/ijcai/JanotaM15, 44 | Author = {Mikol{\'{a}}s Janota and Jo{\~{a}}o Marques{-}Silva}, 45 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 46 | Biburl = {https://dblp.org/rec/bib/conf/ijcai/JanotaM15}, 47 | Booktitle = {Proceedings of {IJCAI}}, 48 | Date-Added = {2019-04-30 08:04:23 +0000}, 49 | Date-Modified = {2019-04-30 08:04:23 +0000}, 50 | Pages = {325--331}, 51 | Publisher = {{AAAI} Press}, 52 | Timestamp = {Fri, 26 Apr 2019 14:26:43 +0200}, 53 | Title = {Solving {QBF} by Clause Selection}, 54 | Url = {http://ijcai.org/Abstract/15/052}, 55 | Year = {2015}, 56 | Bdsk-Url-1 = {http://ijcai.org/Abstract/15/052}} 57 | 58 | @inproceedings{conf/sat/RabeT19, 59 | Author = {Markus N. Rabe and Leander Tentrup}, 60 | Booktitle = {To appear at {SAT}}, 61 | Date-Added = {2019-04-30 07:59:40 +0000}, 62 | Date-Modified = {2019-04-30 07:59:58 +0000}, 63 | Title = {Clausal Abstraction for DQBF}, 64 | Year = {2019}} 65 | 66 | @inproceedings{journals/corr/Tentrup16, 67 | Author = {Jesko Hecking{-}Harbusch and Leander Tentrup}, 68 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 69 | Biburl = {https://dblp.org/rec/bib/journals/corr/Tentrup16}, 70 | Booktitle = {Proceedings Ninth International Symposium on Games, Automata, Logics, and Formal Verification, GandALF 2018, Saarbr{\"{u}}cken, Germany, 26-28th September 2018.}, 71 | Date-Added = {2019-04-30 07:58:52 +0000}, 72 | Date-Modified = {2019-04-30 07:58:52 +0000}, 73 | Doi = {10.4204/EPTCS.277.7}, 74 | Pages = {88--102}, 75 | Series = {{EPTCS}}, 76 | Timestamp = {Wed, 24 Oct 2018 11:28:26 +0200}, 77 | Title = {Solving {QBF} by Abstraction}, 78 | Volume = {277}, 79 | Year = {2018}, 80 | Bdsk-Url-1 = {https://doi.org/10.4204/EPTCS.277.7}} 81 | 82 | @inproceedings{conf/date/SeidlK14, 83 | Author = {Martina Seidl and Robert K{\"{o}}nighofer}, 84 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 85 | Biburl = {https://dblp.org/rec/bib/conf/date/SeidlK14}, 86 | Booktitle = {Proceedings of {DATE}}, 87 | Date-Added = {2018-04-20 20:23:12 +0000}, 88 | Date-Modified = {2018-04-20 20:23:12 +0000}, 89 | Doi = {10.7873/DATE.2014.162}, 90 | Pages = {1--6}, 91 | Publisher = {European Design and Automation Association}, 92 | Timestamp = {Wed, 15 Nov 2017 16:53:38 +0100}, 93 | Title = {Partial witnesses from preprocessed quantified Boolean formulas}, 94 | Year = {2014}, 95 | Bdsk-Url-1 = {https://doi.org/10.7873/DATE.2014.162}} 96 | 97 | @inproceedings{conf/sat/SoosNC09, 98 | Author = {Mate Soos and Karsten Nohl and Claude Castelluccia}, 99 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 100 | Biburl = {https://dblp.org/rec/bib/conf/sat/SoosNC09}, 101 | Booktitle = {Proceedings of {SAT}}, 102 | Date-Added = {2018-04-18 14:21:23 +0000}, 103 | Date-Modified = {2018-04-18 14:21:23 +0000}, 104 | Doi = {10.1007/978-3-642-02777-2_24}, 105 | Pages = {244--257}, 106 | Publisher = {Springer}, 107 | Series = {LNCS}, 108 | Timestamp = {Tue, 23 May 2017 01:08:19 +0200}, 109 | Title = {Extending {SAT} Solvers to Cryptographic Problems}, 110 | Volume = {5584}, 111 | Year = {2009}, 112 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-642-02777-2_24}} 113 | 114 | @inproceedings{conf/tacas/WimmerRM017, 115 | Author = {Ralf Wimmer and Sven Reimer and Paolo Marin and Bernd Becker}, 116 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 117 | Biburl = {https://dblp.org/rec/bib/conf/tacas/WimmerRM017}, 118 | Booktitle = {Proceedings of {TACAS}}, 119 | Date-Added = {2018-04-18 13:54:38 +0000}, 120 | Date-Modified = {2018-04-18 13:54:38 +0000}, 121 | Doi = {10.1007/978-3-662-54577-5_21}, 122 | Pages = {373--390}, 123 | Series = {LNCS}, 124 | Timestamp = {Wed, 24 May 2017 08:28:32 +0200}, 125 | Title = {HQSpre - An Effective Preprocessor for {QBF} and {DQBF}}, 126 | Volume = {10205}, 127 | Year = {2017}, 128 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-662-54577-5_21}} 129 | 130 | @inproceedings{conf/cade/BiereLS11, 131 | Author = {Armin Biere and Florian Lonsing and Martina Seidl}, 132 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 133 | Biburl = {https://dblp.org/rec/bib/conf/cade/BiereLS11}, 134 | Booktitle = {Proceedings of {CADE-23}}, 135 | Date-Added = {2018-04-18 13:53:42 +0000}, 136 | Date-Modified = {2018-04-18 14:18:24 +0000}, 137 | Doi = {10.1007/978-3-642-22438-6_10}, 138 | Pages = {101--115}, 139 | Publisher = {Springer}, 140 | Series = {LNCS}, 141 | Timestamp = {Sun, 21 May 2017 00:17:18 +0200}, 142 | Title = {Blocked Clause Elimination for {QBF}}, 143 | Volume = {6803}, 144 | Year = {2011}, 145 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-642-22438-6_10}} 146 | 147 | @inproceedings{conf/cav/Tentrup17, 148 | Author = {Leander Tentrup}, 149 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 150 | Biburl = {https://dblp.org/rec/bib/conf/cav/Tentrup17}, 151 | Booktitle = {Proceedings of {CAV}}, 152 | Date-Added = {2018-04-18 13:05:52 +0000}, 153 | Date-Modified = {2018-04-18 13:05:52 +0000}, 154 | Doi = {10.1007/978-3-319-63390-9_25}, 155 | Pages = {475--494}, 156 | Publisher = {Springer}, 157 | Series = {LNCS}, 158 | Timestamp = {Fri, 14 Jul 2017 13:10:55 +0200}, 159 | Title = {On Expansion and Resolution in {CEGAR} Based {QBF} Solving}, 160 | Volume = {10427}, 161 | Year = {2017}, 162 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-319-63390-9_25}} 163 | 164 | @inproceedings{conf/fmcad/RabeT15, 165 | Author = {Markus N. Rabe and Leander Tentrup}, 166 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 167 | Biburl = {https://dblp.org/rec/bib/conf/fmcad/RabeT15}, 168 | Booktitle = {Proceedings of {FMCAD}}, 169 | Date-Added = {2018-04-18 13:05:11 +0000}, 170 | Date-Modified = {2018-04-18 13:05:11 +0000}, 171 | Pages = {136--143}, 172 | Publisher = {IEEE}, 173 | Timestamp = {Thu, 07 Jan 2016 15:57:52 +0100}, 174 | Title = {{CAQE:} {A} Certifying {QBF} Solver}, 175 | Year = {2015}} 176 | -------------------------------------------------------------------------------- /src/matrix/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dependency; 2 | pub mod hierarchical; 3 | mod schemes; 4 | 5 | use crate::{ 6 | clause::Clause, 7 | dimacs::Dimacs, 8 | literal::{Literal, Variable}, 9 | }; 10 | use bit_vec::BitVec; 11 | use rustc_hash::{FxHashMap, FxHashSet}; 12 | 13 | pub type ClauseId = u32; 14 | 15 | pub trait Prefix { 16 | type V: VariableInfo; 17 | 18 | fn new(num_variables: usize) -> Self; 19 | 20 | fn variables(&self) -> &VariableStore; 21 | fn mut_vars(&mut self) -> &mut VariableStore; 22 | 23 | /// This function is called in `Matrix::add` for every literal in clause 24 | fn import(&mut self, variable: Variable); 25 | 26 | /// Reduces a clause universally 27 | fn reduce_universal(&self, clause: &mut Clause); 28 | 29 | /// Checks if an existential variable `var` depends on `other`. 30 | fn depends_on, U: Into>(&self, var: V, other: U) -> bool; 31 | } 32 | 33 | #[derive(Debug)] 34 | pub struct Matrix { 35 | pub prefix: P, 36 | pub clauses: Vec, 37 | occurrences: FxHashMap>, 38 | conflict: bool, 39 | pub orig_clause_num: usize, 40 | } 41 | 42 | impl Matrix

{ 43 | #[must_use] 44 | pub fn new(num_variables: usize, num_clauses: usize) -> Self { 45 | Self { 46 | prefix: P::new(num_variables), 47 | clauses: Vec::with_capacity(num_clauses), 48 | occurrences: FxHashMap::default(), 49 | conflict: false, 50 | orig_clause_num: num_clauses, 51 | } 52 | } 53 | 54 | /// Adds a clause to the matrix and returns its `ClauseId` 55 | #[allow(clippy::cast_possible_truncation)] 56 | pub fn add(&mut self, mut clause: Clause) -> ClauseId { 57 | self.prefix.reduce_universal(&mut clause); 58 | for &literal in clause.iter() { 59 | let occurrences = self.occurrences.entry(literal).or_insert_with(Vec::new); 60 | occurrences.push(self.clauses.len() as ClauseId); 61 | self.prefix.import(literal.variable()); 62 | } 63 | if clause.len() == 0 { 64 | self.conflict = true; 65 | } 66 | self.clauses.push(clause); 67 | (self.clauses.len() - 1) as ClauseId 68 | } 69 | 70 | pub fn occurrences(&self, literal: Literal) -> std::slice::Iter { 71 | match self.occurrences.get(&literal) { 72 | None => [].iter(), 73 | Some(vec) => vec.iter(), 74 | } 75 | } 76 | 77 | pub fn conflict(&self) -> bool { 78 | self.conflict 79 | } 80 | 81 | pub fn enumerate(&self) -> impl Iterator + '_ { 82 | #[allow(clippy::cast_possible_truncation)] 83 | self.clauses 84 | .iter() 85 | .enumerate() 86 | .map(|(i, clause)| (i as ClauseId, clause)) 87 | } 88 | 89 | pub fn enumerate_mut(&mut self) -> impl Iterator + '_ { 90 | #[allow(clippy::cast_possible_truncation)] 91 | self.clauses 92 | .iter_mut() 93 | .enumerate() 94 | .map(|(i, clause)| (i as ClauseId, clause)) 95 | } 96 | } 97 | 98 | impl Dimacs for Matrix

99 | where 100 | P: Dimacs, 101 | { 102 | fn dimacs(&self) -> String { 103 | let mut dimacs = String::new(); 104 | dimacs.push_str(&format!( 105 | "p cnf {} {}\n", 106 | self.prefix.variables().max_variable_id(), 107 | self.clauses.len() 108 | )); 109 | dimacs.push_str(&self.prefix.dimacs()); 110 | for clause in &self.clauses { 111 | dimacs.push_str(&format!("{}\n", clause.dimacs())); 112 | } 113 | 114 | dimacs 115 | } 116 | } 117 | 118 | pub trait VariableInfo: Clone + std::fmt::Debug { 119 | fn new() -> Self; 120 | fn is_universal(&self) -> bool; 121 | fn is_bound(&self) -> bool; 122 | 123 | fn is_existential(&self) -> bool { 124 | !self.is_universal() 125 | } 126 | 127 | fn remove_dependency(&mut self, spurious: Variable); 128 | fn dependencies(&self) -> &FxHashSet; 129 | } 130 | 131 | #[derive(Debug)] 132 | pub struct VariableStore { 133 | variables: Vec, 134 | used: BitVec, 135 | orig_num_variables: usize, 136 | unbounded: V, 137 | } 138 | 139 | impl VariableStore { 140 | pub fn new(num_variables: usize) -> Self { 141 | let mut variables = Vec::with_capacity(num_variables + 1); 142 | variables.push(V::new()); 143 | 144 | let mut used = BitVec::with_capacity(num_variables + 1); 145 | used.grow(num_variables + 1, false); 146 | used.set(0, true); 147 | 148 | Self { 149 | variables, 150 | used, 151 | orig_num_variables: num_variables, 152 | unbounded: V::new(), 153 | } 154 | } 155 | 156 | pub fn get(&self, variable: Variable) -> &V { 157 | let index: usize = variable.into(); 158 | if index >= self.variables.len() { 159 | // variable was not bound prior 160 | return &self.unbounded; 161 | } 162 | &self.variables[index] 163 | } 164 | 165 | pub fn max_variable_id(&self) -> usize { 166 | assert!(!self.variables.is_empty()); 167 | self.variables.len() - 1 168 | } 169 | 170 | pub fn orig_max_variable_id(&self) -> usize { 171 | self.orig_num_variables 172 | } 173 | 174 | /// Makes sure variable vector is large enough 175 | /// Also, marks the variable as `used` 176 | fn import(&mut self, variable: Variable) { 177 | let index: usize = variable.into(); 178 | if self.variables.len() <= index { 179 | self.variables.resize(index + 1, V::new()) 180 | } 181 | if self.used.len() <= index { 182 | self.used.grow(index + 1, false); 183 | } 184 | self.used.set(index, true); 185 | } 186 | 187 | fn get_mut(&mut self, variable: Variable) -> &mut V { 188 | let index: usize = variable.into(); 189 | assert!(index < self.variables.len()); 190 | &mut self.variables[index] 191 | } 192 | 193 | /// Returns the next unused variable 194 | pub fn next_unused(&self) -> Variable { 195 | if let Some((index, _)) = self.used.iter().enumerate().find(|(_, val)| !val) { 196 | index.into() 197 | } else { 198 | self.used.len().into() 199 | } 200 | } 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use super::*; 206 | use crate::parse::qdimacs; 207 | 208 | #[test] 209 | fn test_matrix_occurrences() { 210 | let instance = "c 211 | p cnf 4 4 212 | a 1 2 0 213 | e 3 4 0 214 | 1 3 0 215 | -1 4 0 216 | -3 -4 0 217 | 1 2 4 0 218 | "; 219 | let lit1 = Literal::new(1_u32, false); 220 | let lit2 = Literal::new(2_u32, false); 221 | let lit3 = Literal::new(3_u32, false); 222 | let lit4 = Literal::new(4_u32, false); 223 | let matrix = qdimacs::parse(&instance).unwrap(); 224 | assert_eq!(matrix.occurrences(lit1).len(), 2); 225 | assert_eq!(matrix.occurrences(-lit1).len(), 1); 226 | assert_eq!(matrix.occurrences(lit2).len(), 1); 227 | assert_eq!(matrix.occurrences(-lit2).len(), 0); 228 | assert_eq!(matrix.occurrences(lit3).len(), 1); 229 | assert_eq!(matrix.occurrences(-lit3).len(), 1); 230 | assert_eq!(matrix.occurrences(lit4).len(), 2); 231 | assert_eq!(matrix.occurrences(-lit4).len(), 1); 232 | assert!(matrix.prefix.depends_on(lit3.variable(), lit1.variable())); 233 | } 234 | 235 | #[test] 236 | fn test_matrix_dimacs() { 237 | let instance = "p cnf 4 4 238 | a 1 2 0 239 | e 3 4 0 240 | 1 3 0 241 | -1 4 0 242 | -3 -4 0 243 | 1 2 4 0 244 | "; 245 | let matrix = qdimacs::parse(&instance).unwrap(); 246 | let dimacs = matrix.dimacs(); 247 | assert_eq!(instance, dimacs); 248 | } 249 | 250 | #[test] 251 | fn test_partitioning() { 252 | let instance = "c 253 | p cnf 10 8 254 | a 1 2 0 255 | e 3 4 0 256 | a 5 6 0 257 | e 7 8 9 10 0 258 | -1 3 9 0 259 | 1 -3 9 0 260 | -9 -5 7 0 261 | -9 5 -7 0 262 | -2 4 10 0 263 | 2 -4 10 0 264 | -10 -6 8 0 265 | -10 6 -8 0 266 | "; 267 | let mut matrix = qdimacs::parse(&instance).unwrap(); 268 | matrix.unprenex_by_miniscoping(); 269 | assert!(matrix.prefix.roots.len() == 2); 270 | } 271 | 272 | #[test] 273 | fn test_matrix_dimacs_tree() { 274 | let instance = "p cnf 4 4 275 | a 1 2 0 276 | e 3 4 0 277 | 1 3 0 278 | -1 4 0 279 | -3 -4 0 280 | 1 2 4 0 281 | "; 282 | let mut matrix = qdimacs::parse(&instance).unwrap(); 283 | matrix.unprenex_by_miniscoping(); 284 | let dimacs = matrix.dimacs(); 285 | assert_eq!(instance, dimacs); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/parse/dqdimacs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | literal::Variable, 3 | matrix::{dependency::DependencyPrefix, Matrix}, 4 | parse::{ 5 | dimacs::{parse_header, parse_matrix, DimacsToken, DimacsTokenStream, QuantKind}, 6 | ParseError, 7 | }, 8 | }; 9 | use rustc_hash::FxHashSet; 10 | 11 | /// Parses the QDIMACS string into its matrix representation 12 | pub fn parse(content: &str) -> Result, ParseError> { 13 | let mut lexer = DimacsTokenStream::new(content); 14 | let (num_variables, num_clauses) = parse_header(&mut lexer)?; 15 | let mut matrix = Matrix::new(num_variables, num_clauses); 16 | let token = parse_prefix(&mut lexer, &mut matrix)?; 17 | parse_matrix(&mut lexer, &mut matrix, token, num_clauses)?; 18 | Ok(matrix) 19 | } 20 | 21 | /// Parses the quantifier prefix of a DQDIMACS file, e.g., `a 1 2\ne 3 0\nd 4 1\n`. 22 | /// Returns the first token *after* the matrix. 23 | #[allow(clippy::too_many_lines)] 24 | pub(crate) fn parse_prefix( 25 | lexer: &mut DimacsTokenStream, 26 | matrix: &mut Matrix, 27 | ) -> Result { 28 | let mut bound_universals: FxHashSet = FxHashSet::default(); 29 | 30 | loop { 31 | // first character after newline, either `e`, `a`, `d`, or literal (in which case we return) 32 | match lexer.next_token()? { 33 | DimacsToken::Quant(q) => match q { 34 | QuantKind::Exists => { 35 | // dependencies are all prior bound universal variables 36 | loop { 37 | match lexer.next_token()? { 38 | DimacsToken::Lit(l) => { 39 | if l.signed() { 40 | return Err(ParseError { 41 | msg: format!( 42 | "Encountered signed literal `{:?}` in quantifier prefix", 43 | l 44 | ), 45 | pos: lexer.pos(), 46 | }); 47 | } 48 | matrix 49 | .prefix 50 | .add_existential(l.variable(), &bound_universals); 51 | } 52 | DimacsToken::Zero => { 53 | // end of quantifier block 54 | lexer.expect_next(&DimacsToken::EOL)?; 55 | break; 56 | } 57 | token => { 58 | return Err(ParseError { 59 | msg: format!("Expect literal, but found `{:?}`", token), 60 | pos: lexer.pos(), 61 | }); 62 | } 63 | } 64 | } 65 | } 66 | QuantKind::Forall => { 67 | loop { 68 | match lexer.next_token()? { 69 | DimacsToken::Lit(l) => { 70 | if l.signed() { 71 | return Err(ParseError { 72 | msg: format!( 73 | "Encountered signed literal `{:?}` in quantifier prefix", 74 | l 75 | ), 76 | pos: lexer.pos(), 77 | }); 78 | } 79 | matrix.prefix.add_universal(l.variable()); 80 | bound_universals.insert(l.variable()); 81 | } 82 | DimacsToken::Zero => { 83 | // end of quantifier block 84 | lexer.expect_next(&DimacsToken::EOL)?; 85 | break; 86 | } 87 | token => { 88 | return Err(ParseError { 89 | msg: format!("Expect literal, but found `{:?}`", token), 90 | pos: lexer.pos(), 91 | }); 92 | } 93 | } 94 | } 95 | } 96 | QuantKind::Henkin => { 97 | // the first literal is the existential variable, followed by the dependency set 98 | let existential = match lexer.next_token()? { 99 | DimacsToken::Lit(l) => { 100 | if l.signed() { 101 | return Err(ParseError { 102 | msg: format!( 103 | "Encountered signed literal `{:?}` in quantifier prefix", 104 | l 105 | ), 106 | pos: lexer.pos(), 107 | }); 108 | } 109 | l.variable() 110 | } 111 | token => { 112 | return Err(ParseError { 113 | msg: format!("Expect literal, but found `{:?}`", token), 114 | pos: lexer.pos(), 115 | }); 116 | } 117 | }; 118 | let mut dependencies = FxHashSet::default(); 119 | loop { 120 | match lexer.next_token()? { 121 | DimacsToken::Lit(l) => { 122 | if l.signed() { 123 | return Err(ParseError { 124 | msg: format!( 125 | "Encountered signed literal `{:?}` in quantifier prefix", 126 | l 127 | ), 128 | pos: lexer.pos(), 129 | }); 130 | } 131 | dependencies.insert(l.variable()); 132 | } 133 | DimacsToken::Zero => { 134 | // end of quantifier block 135 | lexer.expect_next(&DimacsToken::EOL)?; 136 | matrix.prefix.add_existential(existential, &dependencies); 137 | break; 138 | } 139 | token => { 140 | return Err(ParseError { 141 | msg: format!("Expect literal, but found `{:?}`", token), 142 | pos: lexer.pos(), 143 | }); 144 | } 145 | } 146 | } 147 | } 148 | }, 149 | DimacsToken::Lit(l) => return Ok(DimacsToken::Lit(l)), 150 | DimacsToken::Zero => return Ok(DimacsToken::Zero), 151 | DimacsToken::EOL => continue, 152 | DimacsToken::EOF => { 153 | // matrix contains no clauses 154 | return Ok(DimacsToken::EOF); 155 | } 156 | token => { 157 | return Err(ParseError { 158 | msg: format!("Expect `e`, `a`, `d`, or literal, but found `{:?}`", token), 159 | pos: lexer.pos(), 160 | }); 161 | } 162 | } 163 | } 164 | } 165 | 166 | #[cfg(test)] 167 | mod tests { 168 | 169 | use super::*; 170 | use crate::{ 171 | clause::Clause, 172 | matrix::{Prefix, VariableInfo}, 173 | }; 174 | 175 | #[test] 176 | fn test_simple() { 177 | let result = parse("p cnf 4 2\na 1 2 0\nd 3 1 0\ne 4 0\n-1 3 0\n2 -3 -4 0\n"); 178 | assert!(result.is_ok()); 179 | let matrix = result.unwrap(); 180 | 181 | let v1 = Variable::from(1_u32); 182 | let v2 = Variable::from(2_u32); 183 | let v3 = Variable::from(3_u32); 184 | let v4 = Variable::from(4_u32); 185 | 186 | // prefix 187 | let variables = matrix.prefix.variables(); 188 | assert!(variables.get(v1).is_universal()); 189 | assert!(variables.get(v2).is_universal()); 190 | assert!(variables.get(v3).is_existential()); 191 | assert!(variables.get(v4).is_existential()); 192 | 193 | // clauses 194 | let mut clause_iter = matrix.clauses.iter(); 195 | assert_eq!( 196 | clause_iter.next(), 197 | Some(&Clause::new(vec![(-1).into(), 3.into()])) 198 | ); 199 | assert_eq!( 200 | clause_iter.next(), 201 | Some(&Clause::new(vec![(2).into(), (-3).into(), (-4).into()])) 202 | ); 203 | assert_eq!(clause_iter.next(), None); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /docs/qbfeval-2020/main.bib: -------------------------------------------------------------------------------- 1 | %% This BibTeX bibliography file was created using BibDesk. 2 | %% http://bibdesk.sourceforge.net/ 3 | 4 | %% Created for Leander Tentrup at 2020-05-13 14:13:46 +0200 5 | 6 | 7 | %% Saved with string encoding Unicode (UTF-8) 8 | 9 | 10 | 11 | @article{journals/iandc/BuningKF95, 12 | Author = {Hans Kleine{ }B{\"{u}}ning and Marek Karpinski and Andreas Fl{\"{o}}gel}, 13 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 14 | Biburl = {https://dblp.org/rec/journals/iandc/BuningKF95.bib}, 15 | Date-Added = {2020-05-13 14:12:32 +0200}, 16 | Date-Modified = {2020-05-13 14:12:32 +0200}, 17 | Doi = {10.1006/inco.1995.1025}, 18 | Journal = {Inf. Comput.}, 19 | Number = {1}, 20 | Pages = {12--18}, 21 | Timestamp = {Wed, 14 Nov 2018 10:35:07 +0100}, 22 | Title = {Resolution for Quantified Boolean Formulas}, 23 | Volume = {117}, 24 | Year = {1995}, 25 | Bdsk-Url-1 = {https://doi.org/10.1006/inco.1995.1025}} 26 | 27 | @inproceedings{conf/cav/ClarkeGJLV00, 28 | Author = {Edmund M. Clarke and Orna Grumberg and Somesh Jha and Yuan Lu and Helmut Veith}, 29 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 30 | Biburl = {https://dblp.org/rec/conf/cav/ClarkeGJLV00.bib}, 31 | Booktitle = {Proceedings of {CAV}}, 32 | Date-Added = {2020-05-13 14:12:09 +0200}, 33 | Date-Modified = {2020-05-13 14:12:09 +0200}, 34 | Doi = {10.1007/10722167\_15}, 35 | Pages = {154--169}, 36 | Publisher = {Springer}, 37 | Series = {LNCS}, 38 | Timestamp = {Tue, 14 May 2019 10:00:43 +0200}, 39 | Title = {Counterexample-Guided Abstraction Refinement}, 40 | Volume = {1855}, 41 | Year = {2000}, 42 | Bdsk-Url-1 = {https://doi.org/10.1007/10722167%5C_15}} 43 | 44 | @incollection{series/faia/GiunchigliaMN09, 45 | Author = {Enrico Giunchiglia and Paolo Marin and Massimo Narizzano}, 46 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 47 | Biburl = {https://dblp.org/rec/series/faia/GiunchigliaMN09.bib}, 48 | Booktitle = {Handbook of Satisfiability}, 49 | Date-Added = {2020-05-13 14:11:46 +0200}, 50 | Date-Modified = {2020-05-13 14:11:46 +0200}, 51 | Doi = {10.3233/978-1-58603-929-5-761}, 52 | Pages = {761--780}, 53 | Publisher = {{IOS} Press}, 54 | Series = {Frontiers in Artificial Intelligence and Applications}, 55 | Timestamp = {Wed, 14 Jun 2017 20:39:07 +0200}, 56 | Title = {Reasoning with Quantified Boolean Formulas}, 57 | Volume = {185}, 58 | Year = {2009}, 59 | Bdsk-Url-1 = {https://doi.org/10.3233/978-1-58603-929-5-761}} 60 | 61 | @article{journals/jsat/Tentrup19, 62 | Author = {Leander Tentrup}, 63 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 64 | Biburl = {https://dblp.org/rec/journals/jsat/Tentrup19.bib}, 65 | Date-Added = {2020-05-13 14:02:48 +0200}, 66 | Date-Modified = {2020-05-13 14:02:48 +0200}, 67 | Doi = {10.3233/SAT190121}, 68 | Journal = {J. Satisf. Boolean Model. Comput.}, 69 | Number = {1}, 70 | Pages = {155--210}, 71 | Timestamp = {Wed, 26 Feb 2020 10:37:04 +0100}, 72 | Title = {{CAQE} and QuAbS: Abstraction Based {QBF} Solvers}, 73 | Volume = {11}, 74 | Year = {2019}, 75 | Bdsk-Url-1 = {https://doi.org/10.3233/SAT190121}} 76 | 77 | @article{journals/tcs/JanotaM15, 78 | Author = {Mikol{\'{a}}s Janota and Jo{\~{a}}o Marques{-}Silva}, 79 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 80 | Biburl = {https://dblp.org/rec/bib/journals/tcs/JanotaM15}, 81 | Date-Added = {2019-04-30 08:36:00 +0000}, 82 | Date-Modified = {2019-04-30 08:36:00 +0000}, 83 | Doi = {10.1016/j.tcs.2015.01.048}, 84 | Journal = {Theor. Comput. Sci.}, 85 | Pages = {25--42}, 86 | Timestamp = {Fri, 28 Sep 2018 12:48:40 +0200}, 87 | Title = {Expansion-based {QBF} solving versus Q-resolution}, 88 | Volume = {577}, 89 | Year = {2015}, 90 | Bdsk-Url-1 = {https://doi.org/10.1016/j.tcs.2015.01.048}} 91 | 92 | @inproceedings{conf/sat/Tentrup16, 93 | Author = {Leander Tentrup}, 94 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 95 | Biburl = {https://dblp.org/rec/bib/conf/sat/Tentrup16}, 96 | Booktitle = {Proceedings of {SAT}}, 97 | Date-Added = {2019-04-30 08:08:42 +0000}, 98 | Date-Modified = {2019-04-30 08:08:42 +0000}, 99 | Doi = {10.1007/978-3-319-40970-2\_24}, 100 | Pages = {393--401}, 101 | Publisher = {Springer}, 102 | Series = {LNCS}, 103 | Timestamp = {Tue, 23 May 2017 01:08:19 +0200}, 104 | Title = {Non-prenex {QBF} Solving Using Abstraction}, 105 | Volume = {9710}, 106 | Year = {2016}, 107 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-319-40970-2%5C_24}} 108 | 109 | @inproceedings{conf/ijcai/JanotaM15, 110 | Author = {Mikol{\'{a}}s Janota and Jo{\~{a}}o Marques{-}Silva}, 111 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 112 | Biburl = {https://dblp.org/rec/bib/conf/ijcai/JanotaM15}, 113 | Booktitle = {Proceedings of {IJCAI}}, 114 | Date-Added = {2019-04-30 08:04:23 +0000}, 115 | Date-Modified = {2019-04-30 08:04:23 +0000}, 116 | Pages = {325--331}, 117 | Publisher = {{AAAI} Press}, 118 | Timestamp = {Fri, 26 Apr 2019 14:26:43 +0200}, 119 | Title = {Solving {QBF} by Clause Selection}, 120 | Url = {http://ijcai.org/Abstract/15/052}, 121 | Year = {2015}, 122 | Bdsk-Url-1 = {http://ijcai.org/Abstract/15/052}} 123 | 124 | @inproceedings{conf/sat/RabeT19, 125 | Author = {Markus N. Rabe and Leander Tentrup}, 126 | Booktitle = {To appear at {SAT}}, 127 | Date-Added = {2019-04-30 07:59:40 +0000}, 128 | Date-Modified = {2019-04-30 07:59:58 +0000}, 129 | Title = {Clausal Abstraction for DQBF}, 130 | Year = {2019}} 131 | 132 | @inproceedings{journals/corr/Tentrup16, 133 | Author = {Jesko Hecking{-}Harbusch and Leander Tentrup}, 134 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 135 | Biburl = {https://dblp.org/rec/bib/journals/corr/Tentrup16}, 136 | Booktitle = {Proceedings of {GandALF}}, 137 | Date-Added = {2019-04-30 07:58:52 +0000}, 138 | Date-Modified = {2020-05-13 14:13:46 +0200}, 139 | Doi = {10.4204/EPTCS.277.7}, 140 | Pages = {88--102}, 141 | Series = {{EPTCS}}, 142 | Timestamp = {Wed, 24 Oct 2018 11:28:26 +0200}, 143 | Title = {Solving {QBF} by Abstraction}, 144 | Volume = {277}, 145 | Year = {2018}, 146 | Bdsk-Url-1 = {https://doi.org/10.4204/EPTCS.277.7}} 147 | 148 | @inproceedings{conf/date/SeidlK14, 149 | Author = {Martina Seidl and Robert K{\"{o}}nighofer}, 150 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 151 | Biburl = {https://dblp.org/rec/bib/conf/date/SeidlK14}, 152 | Booktitle = {Proceedings of {DATE}}, 153 | Date-Added = {2018-04-20 20:23:12 +0000}, 154 | Date-Modified = {2018-04-20 20:23:12 +0000}, 155 | Doi = {10.7873/DATE.2014.162}, 156 | Pages = {1--6}, 157 | Publisher = {European Design and Automation Association}, 158 | Timestamp = {Wed, 15 Nov 2017 16:53:38 +0100}, 159 | Title = {Partial witnesses from preprocessed quantified Boolean formulas}, 160 | Year = {2014}, 161 | Bdsk-Url-1 = {https://doi.org/10.7873/DATE.2014.162}} 162 | 163 | @inproceedings{conf/sat/SoosNC09, 164 | Author = {Mate Soos and Karsten Nohl and Claude Castelluccia}, 165 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 166 | Biburl = {https://dblp.org/rec/bib/conf/sat/SoosNC09}, 167 | Booktitle = {Proceedings of {SAT}}, 168 | Date-Added = {2018-04-18 14:21:23 +0000}, 169 | Date-Modified = {2018-04-18 14:21:23 +0000}, 170 | Doi = {10.1007/978-3-642-02777-2_24}, 171 | Pages = {244--257}, 172 | Publisher = {Springer}, 173 | Series = {LNCS}, 174 | Timestamp = {Tue, 23 May 2017 01:08:19 +0200}, 175 | Title = {Extending {SAT} Solvers to Cryptographic Problems}, 176 | Volume = {5584}, 177 | Year = {2009}, 178 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-642-02777-2_24}} 179 | 180 | @inproceedings{conf/tacas/WimmerRM017, 181 | Author = {Ralf Wimmer and Sven Reimer and Paolo Marin and Bernd Becker}, 182 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 183 | Biburl = {https://dblp.org/rec/bib/conf/tacas/WimmerRM017}, 184 | Booktitle = {Proceedings of {TACAS}}, 185 | Date-Added = {2018-04-18 13:54:38 +0000}, 186 | Date-Modified = {2018-04-18 13:54:38 +0000}, 187 | Doi = {10.1007/978-3-662-54577-5_21}, 188 | Pages = {373--390}, 189 | Series = {LNCS}, 190 | Timestamp = {Wed, 24 May 2017 08:28:32 +0200}, 191 | Title = {HQSpre - An Effective Preprocessor for {QBF} and {DQBF}}, 192 | Volume = {10205}, 193 | Year = {2017}, 194 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-662-54577-5_21}} 195 | 196 | @inproceedings{conf/cade/BiereLS11, 197 | Author = {Armin Biere and Florian Lonsing and Martina Seidl}, 198 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 199 | Biburl = {https://dblp.org/rec/bib/conf/cade/BiereLS11}, 200 | Booktitle = {Proceedings of {CADE-23}}, 201 | Date-Added = {2018-04-18 13:53:42 +0000}, 202 | Date-Modified = {2018-04-18 14:18:24 +0000}, 203 | Doi = {10.1007/978-3-642-22438-6_10}, 204 | Pages = {101--115}, 205 | Publisher = {Springer}, 206 | Series = {LNCS}, 207 | Timestamp = {Sun, 21 May 2017 00:17:18 +0200}, 208 | Title = {Blocked Clause Elimination for {QBF}}, 209 | Volume = {6803}, 210 | Year = {2011}, 211 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-642-22438-6_10}} 212 | 213 | @inproceedings{conf/cav/Tentrup17, 214 | Author = {Leander Tentrup}, 215 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 216 | Biburl = {https://dblp.org/rec/bib/conf/cav/Tentrup17}, 217 | Booktitle = {Proceedings of {CAV}}, 218 | Date-Added = {2018-04-18 13:05:52 +0000}, 219 | Date-Modified = {2018-04-18 13:05:52 +0000}, 220 | Doi = {10.1007/978-3-319-63390-9_25}, 221 | Pages = {475--494}, 222 | Publisher = {Springer}, 223 | Series = {LNCS}, 224 | Timestamp = {Fri, 14 Jul 2017 13:10:55 +0200}, 225 | Title = {On Expansion and Resolution in {CEGAR} Based {QBF} Solving}, 226 | Volume = {10427}, 227 | Year = {2017}, 228 | Bdsk-Url-1 = {https://doi.org/10.1007/978-3-319-63390-9_25}} 229 | 230 | @inproceedings{conf/fmcad/RabeT15, 231 | Author = {Markus N. Rabe and Leander Tentrup}, 232 | Bibsource = {dblp computer science bibliography, https://dblp.org}, 233 | Biburl = {https://dblp.org/rec/bib/conf/fmcad/RabeT15}, 234 | Booktitle = {Proceedings of {FMCAD}}, 235 | Date-Added = {2018-04-18 13:05:11 +0000}, 236 | Date-Modified = {2018-04-18 13:05:11 +0000}, 237 | Pages = {136--143}, 238 | Publisher = {IEEE}, 239 | Timestamp = {Thu, 07 Jan 2016 15:57:52 +0100}, 240 | Title = {{CAQE:} {A} Certifying {QBF} Solver}, 241 | Year = {2015}} 242 | -------------------------------------------------------------------------------- /src/clause.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dimacs::Dimacs, 3 | literal::{Literal, Variable}, 4 | matrix::{hierarchical::*, Prefix, VariableInfo}, 5 | }; 6 | use rustc_hash::FxHashMap; 7 | 8 | #[derive(PartialEq, Eq, Debug, Clone)] 9 | pub struct Clause { 10 | literals: Vec, 11 | } 12 | 13 | impl Clause { 14 | /// Creates a new clause from given literals 15 | /// 16 | /// The vector containing literals is sorted and deduplicated. 17 | pub fn new(literals: Vec) -> Self { 18 | let mut l = literals; 19 | l.sort(); 20 | l.dedup(); 21 | Self::new_normalized(l) 22 | } 23 | 24 | /// Creates an empty clause 25 | pub fn new_empty() -> Self { 26 | Self { 27 | literals: Vec::new(), 28 | } 29 | } 30 | 31 | /// Creates a new clause from given literals 32 | /// 33 | /// Assumes literals to be already normalized, i.e., 34 | /// sorted and without duplication. 35 | pub fn new_normalized(literals: Vec) -> Self { 36 | Self { literals } 37 | } 38 | 39 | pub fn len(&self) -> usize { 40 | self.literals.len() 41 | } 42 | 43 | pub fn is_tautology(&self) -> bool { 44 | for (i, &el1) in self.literals.iter().enumerate() { 45 | if i + 1 >= self.literals.len() { 46 | break; 47 | } 48 | let el2 = self.literals[i + 1]; 49 | if el1 == -el2 { 50 | return true; 51 | } 52 | } 53 | true 54 | } 55 | 56 | pub fn reduce_universal_qbf(&mut self, prefix: &HierarchicalPrefix) { 57 | let max = self 58 | .literals 59 | .iter() 60 | .filter(|l| !prefix.variables().get(l.variable()).is_universal()) 61 | .fold(0, |max, l| { 62 | let level = prefix.variables().get(l.variable()).level; 63 | if level > max { 64 | level 65 | } else { 66 | max 67 | } 68 | }); 69 | self.literals.retain(|l| { 70 | let info = prefix.variables().get(l.variable()); 71 | !info.is_universal() || info.level < max 72 | }); 73 | } 74 | 75 | pub fn iter(&self) -> std::slice::Iter { 76 | self.literals.iter() 77 | } 78 | 79 | pub fn iter_mut(&mut self) -> std::slice::IterMut { 80 | self.literals.iter_mut() 81 | } 82 | 83 | pub fn retain

(&mut self, predicate: P) 84 | where 85 | P: FnMut(&Literal) -> bool, 86 | { 87 | self.literals.retain(predicate); 88 | } 89 | 90 | /// Returns true, if the literals contained in `self` are a subset of the literals in `other`. 91 | /// Only literals satisfying the predicate are considered. 92 | /// Note that literals in clauses are sorted. 93 | pub fn is_subset_wrt_predicate

(&self, other: &Self, predicate: P) -> bool 94 | where 95 | P: Fn(&Literal) -> bool, 96 | { 97 | // iterate over all literals in lhs and try to match it to the literals in rhs 98 | let mut lhs = self.iter().filter(|l| predicate(l)); 99 | let mut rhs = other.iter().filter(|l| predicate(l)); 100 | let mut rhs_literal = match rhs.next() { 101 | None => { 102 | // check that there are no remaining literals in lhs 103 | return lhs.next().is_none(); 104 | } 105 | Some(l) => l, 106 | }; 107 | while let Some(lhs_literal) = lhs.next() { 108 | if lhs_literal < rhs_literal { 109 | // have not found a matching literal in rhs 110 | return false; 111 | } 112 | debug_assert!(lhs_literal >= rhs_literal); 113 | while lhs_literal > rhs_literal { 114 | rhs_literal = match rhs.next() { 115 | None => return false, 116 | Some(l) => l, 117 | } 118 | } 119 | if lhs_literal == rhs_literal { 120 | // same literal, proceed with both 121 | rhs_literal = match rhs.next() { 122 | None => { 123 | // check that there are no remaining literals in lhs 124 | return lhs.next().is_none(); 125 | } 126 | Some(l) => l, 127 | }; 128 | continue; 129 | } else { 130 | return false; 131 | } 132 | } 133 | true 134 | } 135 | 136 | pub fn is_subset(&self, other: &Self) -> bool { 137 | self.is_subset_wrt_predicate(other, |_| true) 138 | } 139 | 140 | /// Returns true, if the literals contained in `self` are equal to the literals in `other`. 141 | /// Only literals satisfying the predicate are considered. 142 | /// Note that literals in clauses are sorted. 143 | pub fn is_equal_wrt_predicate

(&self, other: &Self, predicate: P) -> bool 144 | where 145 | P: Fn(&Literal) -> bool, 146 | { 147 | // iterate over all literals in lhs and try to match it to the literals in rhs 148 | let mut lhs = self.iter().filter(|l| predicate(l)); 149 | let mut rhs = other.iter().filter(|l| predicate(l)); 150 | loop { 151 | match (lhs.next(), rhs.next()) { 152 | (None, None) => return true, 153 | (Some(l), Some(r)) => { 154 | if l != r { 155 | return false; 156 | } 157 | } 158 | (_, _) => return false, 159 | } 160 | } 161 | } 162 | 163 | pub fn is_equal(&self, other: &Self) -> bool { 164 | self.is_equal_wrt_predicate(other, |_| true) 165 | } 166 | 167 | pub fn is_satisfied_by_assignment(&self, assignment: &FxHashMap) -> bool { 168 | self.literals 169 | .iter() 170 | .any(|&l| match assignment.get(&l.variable()) { 171 | None => false, 172 | Some(&value) => l.signed() && !value || !l.signed() && value, 173 | }) 174 | } 175 | } 176 | 177 | impl Dimacs for Clause { 178 | fn dimacs(&self) -> String { 179 | let mut dimacs = String::new(); 180 | for &literal in self.iter() { 181 | dimacs.push_str(&format!("{} ", literal.dimacs())); 182 | } 183 | dimacs.push_str("0"); 184 | dimacs 185 | } 186 | } 187 | 188 | #[cfg(test)] 189 | mod tests { 190 | use std::mem; 191 | 192 | use super::*; 193 | 194 | #[test] 195 | fn size_of_clause() { 196 | let result = mem::size_of::(); 197 | assert!( 198 | result == 24, 199 | "Size of `Clause` should be 24 bytes, was `{}`", 200 | result 201 | ); 202 | } 203 | 204 | #[test] 205 | fn clause_normalization() { 206 | let lit1 = Literal::new(0_u32, false); 207 | let lit2 = Literal::new(1_u32, false); 208 | let literals = vec![lit2, lit1, lit2]; 209 | let clause1 = Clause::new(literals); 210 | let clause2 = Clause::new_normalized(vec![lit1, lit2]); 211 | assert_eq!(clause1, clause2); 212 | } 213 | 214 | #[test] 215 | fn clause_reduce_universal_qbf() { 216 | // cretate a qbf prefix 217 | let mut prefix = HierarchicalPrefix::new(3); 218 | let exists1 = prefix.new_scope(ScopeId::OUTERMOST, Quantifier::Existential); 219 | let forall = prefix.new_scope(exists1, Quantifier::Universal); 220 | let exists2 = prefix.new_scope(forall, Quantifier::Existential); 221 | 222 | prefix.add_variable(1_u32, exists1); 223 | prefix.add_variable(2_u32, forall); 224 | prefix.add_variable(3_u32, exists2); 225 | 226 | let lit1 = Literal::new(1_u32, false); 227 | let lit2 = Literal::new(2_u32, false); 228 | let lit3 = Literal::new(3_u32, false); 229 | 230 | // no universal reduction 231 | let mut clause1 = Clause::new(vec![lit1, lit2, lit3]); 232 | clause1.reduce_universal_qbf(&prefix); 233 | let clause2 = Clause::new(vec![lit1, lit2, lit3]); 234 | assert_eq!(clause1, clause2); 235 | 236 | // lit2 can be removed 237 | let mut clause1 = Clause::new(vec![lit1, lit2]); 238 | clause1.reduce_universal_qbf(&prefix); 239 | let clause2 = Clause::new(vec![lit1]); 240 | assert_eq!(clause1, clause2); 241 | } 242 | 243 | #[test] 244 | fn clause_subset_wrt_predicate() { 245 | let lit1 = Literal::new(0_u32, false); 246 | let lit2 = Literal::new(1_u32, false); 247 | let lit3 = Literal::new(2_u32, false); 248 | let clause1 = Clause::new(vec![lit1, -lit2, lit3]); 249 | let clause2 = Clause::new(vec![lit1, lit2, lit3]); 250 | assert!(!clause1.is_subset(&clause2)); 251 | assert!(clause1.is_subset_wrt_predicate(&clause2, |l| !l.signed())); 252 | } 253 | 254 | #[test] 255 | fn clause_equal_wrt_predicate() { 256 | let lit1 = Literal::new(0_u32, false); 257 | let lit2 = Literal::new(1_u32, false); 258 | let lit3 = Literal::new(2_u32, false); 259 | let clause1 = Clause::new(vec![lit1, -lit2, lit3]); 260 | let clause2 = Clause::new(vec![lit1, lit3]); 261 | assert!(!clause1.is_equal(&clause2)); 262 | assert!(clause1.is_equal_wrt_predicate(&clause2, |l| !l.signed())); 263 | } 264 | 265 | #[test] 266 | fn clause_is_satisifed() { 267 | let lit1 = Literal::new(0_u32, false); 268 | let lit2 = Literal::new(1_u32, false); 269 | let lit3 = Literal::new(2_u32, false); 270 | let clause = Clause::new(vec![lit1, -lit2, lit3]); 271 | let mut assignment: FxHashMap = FxHashMap::default(); 272 | assignment.insert(1_u32.into(), true); 273 | assignment.insert(2_u32.into(), false); 274 | assert!(!clause.is_satisfied_by_assignment(&assignment)); 275 | assignment.insert(0_u32.into(), true); 276 | assert!(clause.is_satisfied_by_assignment(&assignment)); 277 | assignment.insert(0_u32.into(), false); 278 | assert!(!clause.is_satisfied_by_assignment(&assignment)); 279 | } 280 | 281 | #[test] 282 | fn clause_dimacs() { 283 | let lit1 = Literal::new(1_u32, false); 284 | let lit2 = Literal::new(2_u32, false); 285 | let lit3 = Literal::new(3_u32, false); 286 | let clause = Clause::new(vec![lit1, -lit2, lit3]); 287 | assert_eq!(clause.dimacs(), "1 -2 3 0"); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/parse/dimacs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | clause::Clause, 3 | literal::Literal, 4 | matrix::{Matrix, Prefix}, 5 | parse::{CharIterator, ParseError, SourcePos}, 6 | }; 7 | 8 | #[allow(clippy::module_name_repetitions)] 9 | #[derive(Debug, Eq, PartialEq)] 10 | pub(crate) enum DimacsToken { 11 | /// p cnf header 12 | Header, 13 | 14 | /// A Literal, i.e., a signed or unsigned integer 15 | Lit(Literal), 16 | 17 | /// A zero integer, used as an ending sign 18 | Zero, 19 | 20 | /// Quantification, i.e., `e`, `a`, and `d` 21 | Quant(QuantKind), 22 | 23 | /// s cnf header 24 | SolutionHeader, 25 | 26 | /// First character of certificate line 27 | V, 28 | 29 | /// End-of-line 30 | EOL, 31 | 32 | /// End-of-file 33 | EOF, 34 | } 35 | 36 | impl Into for DimacsToken { 37 | fn into(self) -> Literal { 38 | match self { 39 | Self::Zero => Literal::new(0_u32, false), 40 | Self::Lit(l) => l, 41 | _ => panic!("cannot convert {:?} into Literal", self), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Debug, Eq, PartialEq)] 47 | pub enum QuantKind { 48 | Exists, 49 | Forall, 50 | Henkin, 51 | } 52 | 53 | #[allow(clippy::module_name_repetitions)] 54 | pub struct DimacsTokenStream<'a> { 55 | chars: CharIterator<'a>, 56 | } 57 | 58 | impl<'a> DimacsTokenStream<'a> { 59 | #[must_use] 60 | pub fn new(content: &'a str) -> Self { 61 | DimacsTokenStream { 62 | chars: CharIterator::new(content), 63 | } 64 | } 65 | 66 | pub(crate) fn next_token(&mut self) -> Result { 67 | while let Some(c) = self.chars.next() { 68 | match c { 69 | 'c' => { 70 | // comment line, ignore until next newline 71 | self.chars.skip_while(|c| *c != '\n'); 72 | } 73 | 'p' => { 74 | // DIMACS header 75 | self.chars.expect_str(" cnf ")?; 76 | return Ok(DimacsToken::Header); 77 | } 78 | 's' => { 79 | // DIMACS solution header 80 | self.chars.expect_str(" cnf ")?; 81 | return Ok(DimacsToken::SolutionHeader); 82 | } 83 | 'e' => return Ok(DimacsToken::Quant(QuantKind::Exists)), 84 | 'a' => return Ok(DimacsToken::Quant(QuantKind::Forall)), 85 | 'd' => return Ok(DimacsToken::Quant(QuantKind::Henkin)), 86 | '0' => return Ok(DimacsToken::Zero), 87 | '-' => { 88 | // negated literal 89 | return Ok(DimacsToken::Lit(self.chars.read_literal('-')?)); 90 | } 91 | c if c.is_ascii_digit() => { 92 | // digit 93 | return Ok(DimacsToken::Lit(self.chars.read_literal(c)?)); 94 | } 95 | 'V' => return Ok(DimacsToken::V), 96 | '\n' => return Ok(DimacsToken::EOL), 97 | ' ' => continue, 98 | _ => { 99 | return Err(ParseError { 100 | msg: format!("Encountered unknown token `{}` during lexing", c), 101 | pos: self.chars.pos, 102 | }); 103 | } 104 | } 105 | } 106 | // end of file 107 | Ok(DimacsToken::EOF) 108 | } 109 | 110 | pub(crate) fn expect_next(&mut self, token: &DimacsToken) -> Result<(), ParseError> { 111 | self.next_token().and_then(|next| { 112 | if next == *token { 113 | Ok(()) 114 | } else { 115 | Err(ParseError { 116 | msg: format!("Expected token `{:?}` but found `{:?}`", token, next), 117 | pos: self.chars.pos, 118 | }) 119 | } 120 | }) 121 | } 122 | 123 | pub(crate) fn pos(&self) -> SourcePos { 124 | self.chars.pos 125 | } 126 | } 127 | 128 | /// Parses the `p cnf NUM NUM` header and returns number of variables and number of clauses 129 | pub fn parse_header(lexer: &mut DimacsTokenStream) -> Result<(usize, usize), ParseError> { 130 | // first non-EOL token has to be `p cnf ` header 131 | loop { 132 | match lexer.next_token()? { 133 | DimacsToken::EOL => continue, 134 | DimacsToken::Header => break, 135 | token => { 136 | return Err(ParseError { 137 | msg: format!("Expect `p cnf`, but found `{:?}`", token), 138 | pos: lexer.pos(), 139 | }); 140 | } 141 | } 142 | } 143 | //lexer.expect_next(&DimacsToken::EOL); 144 | let num_variables = match lexer.next_token()? { 145 | DimacsToken::Zero => 0_u32.into(), 146 | DimacsToken::Lit(l) => { 147 | if l.signed() { 148 | return Err(ParseError { 149 | msg: "Malformed `p cnf` header, found negative value for number of variables" 150 | .to_string(), 151 | pos: lexer.pos(), 152 | }); 153 | } 154 | l.variable() 155 | } 156 | token => { 157 | return Err(ParseError { 158 | msg: format!( 159 | "Malformed `p cnf` header, expected number of variables, found `{:?}`", 160 | token 161 | ), 162 | pos: lexer.pos(), 163 | }); 164 | } 165 | }; 166 | let num_clauses = match lexer.next_token()? { 167 | DimacsToken::Zero => 0_u32.into(), 168 | DimacsToken::Lit(l) => { 169 | if l.signed() { 170 | return Err(ParseError { 171 | msg: "Malformed `p cnf` header, found negative value for number of clauses" 172 | .to_string(), 173 | pos: lexer.pos(), 174 | }); 175 | } 176 | l.variable() 177 | } 178 | token => { 179 | return Err(ParseError { 180 | msg: format!( 181 | "Malformed `p cnf` header, expected number of clauses, found `{:?}`", 182 | token 183 | ), 184 | pos: lexer.pos(), 185 | }); 186 | } 187 | }; 188 | Ok((num_variables.into(), num_clauses.into())) 189 | } 190 | 191 | pub(crate) fn parse_matrix( 192 | lexer: &mut DimacsTokenStream, 193 | matrix: &mut Matrix

, 194 | mut current: DimacsToken, 195 | num_clauses_expected: usize, 196 | ) -> Result<(), ParseError> { 197 | let mut literals: Vec = Vec::new(); 198 | let mut num_clauses_read = 0; 199 | 200 | loop { 201 | match current { 202 | DimacsToken::Zero => { 203 | // end of clause 204 | lexer.expect_next(&DimacsToken::EOL)?; 205 | matrix.add(Clause::new(literals)); 206 | literals = Vec::new(); 207 | num_clauses_read += 1; 208 | } 209 | DimacsToken::Lit(l) => { 210 | literals.push(l); 211 | } 212 | DimacsToken::EOL => { 213 | // End-of-line without prior `0` marker 214 | // either wrong format, or `EOF` follows 215 | if !literals.is_empty() { 216 | // End-of-line during clause read 217 | return Err(ParseError { 218 | msg: "Unexpected end of line while reading clause".to_string(), 219 | pos: lexer.pos(), 220 | }); 221 | } 222 | } 223 | DimacsToken::EOF => { 224 | if !literals.is_empty() { 225 | // End-of-file during clause read 226 | return Err(ParseError { 227 | msg: "Unexpected end of input while reading clause".to_string(), 228 | pos: lexer.pos(), 229 | }); 230 | } 231 | if num_clauses_expected != num_clauses_read { 232 | return Err(ParseError { 233 | msg: format!( 234 | "Expected {} clauses, but found {}", 235 | num_clauses_expected, num_clauses_read 236 | ), 237 | pos: lexer.pos(), 238 | }); 239 | } 240 | return Ok(()); 241 | } 242 | _ => { 243 | return Err(ParseError { 244 | msg: format!("Unexpected token `{:?}` while reading clause", current), 245 | pos: lexer.pos(), 246 | }); 247 | } 248 | } 249 | current = lexer.next_token()?; 250 | } 251 | } 252 | 253 | #[cfg(test)] 254 | mod tests { 255 | 256 | use super::*; 257 | use crate::matrix::hierarchical::HierarchicalPrefix; 258 | 259 | #[test] 260 | fn test_lexer_simple() { 261 | let mut stream = DimacsTokenStream::new("p cnf 0 0\n"); 262 | assert_eq!(stream.next_token(), Ok(DimacsToken::Header)); 263 | assert_eq!(stream.next_token(), Ok(DimacsToken::Zero)); 264 | assert_eq!(stream.next_token(), Ok(DimacsToken::Zero)); 265 | assert_eq!(stream.next_token(), Ok(DimacsToken::EOL)); 266 | assert_eq!(stream.next_token(), Ok(DimacsToken::EOF)); 267 | } 268 | 269 | #[test] 270 | fn test_lexer_all() { 271 | let mut stream = DimacsTokenStream::new("c comment\np cnf 0 0\ne a d\n-1 1 0\n"); 272 | assert_eq!(stream.next_token(), Ok(DimacsToken::Header)); 273 | assert_eq!(stream.next_token(), Ok(DimacsToken::Zero)); 274 | assert_eq!(stream.next_token(), Ok(DimacsToken::Zero)); 275 | assert_eq!(stream.next_token(), Ok(DimacsToken::EOL)); 276 | 277 | assert_eq!( 278 | stream.next_token(), 279 | Ok(DimacsToken::Quant(QuantKind::Exists)) 280 | ); 281 | assert_eq!( 282 | stream.next_token(), 283 | Ok(DimacsToken::Quant(QuantKind::Forall)) 284 | ); 285 | assert_eq!( 286 | stream.next_token(), 287 | Ok(DimacsToken::Quant(QuantKind::Henkin)) 288 | ); 289 | assert_eq!(stream.next_token(), Ok(DimacsToken::EOL)); 290 | 291 | assert_eq!(stream.next_token(), Ok(DimacsToken::Lit((-1).into()))); 292 | assert_eq!(stream.next_token(), Ok(DimacsToken::Lit(1.into()))); 293 | assert_eq!(stream.next_token(), Ok(DimacsToken::Zero)); 294 | assert_eq!(stream.next_token(), Ok(DimacsToken::EOL)); 295 | assert_eq!(stream.next_token(), Ok(DimacsToken::EOF)); 296 | } 297 | 298 | #[test] 299 | fn test_lexer_error() { 300 | let mut stream = DimacsTokenStream::new("x"); 301 | assert!(stream.next_token().is_err()); 302 | 303 | let mut stream = DimacsTokenStream::new("-a"); 304 | assert!(stream.next_token().is_err()); 305 | 306 | let mut stream = DimacsTokenStream::new("- "); 307 | assert!(stream.next_token().is_err()); 308 | 309 | let mut stream = DimacsTokenStream::new("--1"); 310 | assert!(stream.next_token().is_err()); 311 | } 312 | 313 | #[test] 314 | fn test_parse_matrix() { 315 | let mut lexer = DimacsTokenStream::new("-1 2 0\n2 -3 -4 0\n"); 316 | let mut matrix = Matrix::::new(4, 2); 317 | let current = lexer.next_token().unwrap(); 318 | assert!(parse_matrix(&mut lexer, &mut matrix, current, 2).is_ok()); 319 | let mut clause_iter = matrix.clauses.iter(); 320 | assert_eq!( 321 | clause_iter.next(), 322 | Some(&Clause::new(vec![(-1).into(), 2.into()])) 323 | ); 324 | assert_eq!( 325 | clause_iter.next(), 326 | Some(&Clause::new(vec![(2).into(), (-3).into(), (-4).into()])) 327 | ); 328 | assert_eq!(clause_iter.next(), None); 329 | } 330 | 331 | #[test] 332 | fn test_parse_header() { 333 | let mut lexer = DimacsTokenStream::new("p cnf 2 4\n"); 334 | assert_eq!(parse_header(&mut lexer), Ok((2, 4))); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/parse/qdimacs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dimacs::Dimacs, 3 | literal::Literal, 4 | matrix::{ 5 | hierarchical::{HierarchicalPrefix, Quantifier}, 6 | Matrix, 7 | }, 8 | parse::{ 9 | dimacs::{parse_header, parse_matrix, DimacsToken, DimacsTokenStream, QuantKind}, 10 | ParseError, 11 | }, 12 | solve::SolverResult, 13 | }; 14 | use std::str::FromStr; 15 | 16 | /// Parses the QDIMACS string into its matrix representation 17 | pub fn parse(content: &str) -> Result, ParseError> { 18 | let mut lexer = DimacsTokenStream::new(content); 19 | let (num_variables, num_clauses) = parse_header(&mut lexer)?; 20 | let mut matrix = Matrix::new(num_variables, num_clauses); 21 | let token = parse_prefix(&mut lexer, &mut matrix)?; 22 | parse_matrix(&mut lexer, &mut matrix, token, num_clauses)?; 23 | matrix.prefix.compute_dependencies(); 24 | 25 | // in the case the last scope is universal, we remove it 26 | let last_scope = matrix.prefix.last_scope(); 27 | if matrix.prefix.scopes[last_scope.to_usize()].quant == Quantifier::Universal { 28 | matrix.prefix.remove_scope(last_scope); 29 | } 30 | Ok(matrix) 31 | } 32 | 33 | /// Parses the quantifier prefix of a QDIMACS file, e.g., `e 1 2\na 3 4\n`. 34 | /// Returns the first token *after* the matrix. 35 | pub(crate) fn parse_prefix( 36 | lexer: &mut DimacsTokenStream, 37 | matrix: &mut Matrix, 38 | ) -> Result { 39 | let mut pref_scope_id = *matrix.prefix.roots.first().expect("root cannot be empty"); 40 | loop { 41 | // first character after newline, either `e`, `a`, or literal (in which case we return) 42 | match lexer.next_token()? { 43 | DimacsToken::Quant(q) => { 44 | let quantifier = match q { 45 | QuantKind::Exists => Ok(Quantifier::Existential), 46 | QuantKind::Forall => Ok(Quantifier::Universal), 47 | QuantKind::Henkin => Err(ParseError { 48 | msg: "Henkin quantifier (`d`) are not allowed in QDIMACS".to_string(), 49 | pos: lexer.pos(), 50 | }), 51 | }?; 52 | let scope_id = matrix.prefix.new_scope(pref_scope_id, quantifier); 53 | 54 | // add variables 55 | loop { 56 | match lexer.next_token()? { 57 | DimacsToken::Lit(l) => { 58 | if l.signed() { 59 | return Err(ParseError { 60 | msg: format!( 61 | "Encountered signed literal `{:?}` in quantifier prefix", 62 | l 63 | ), 64 | pos: lexer.pos(), 65 | }); 66 | } 67 | matrix.prefix.add_variable(l.variable(), scope_id); 68 | } 69 | DimacsToken::Zero => { 70 | // end of quantifier block 71 | lexer.expect_next(&DimacsToken::EOL)?; 72 | break; 73 | } 74 | token => { 75 | return Err(ParseError { 76 | msg: format!("Expect literal, but found `{:?}`", token), 77 | pos: lexer.pos(), 78 | }); 79 | } 80 | } 81 | } 82 | 83 | pref_scope_id = scope_id; 84 | } 85 | DimacsToken::Lit(l) => return Ok(DimacsToken::Lit(l)), 86 | DimacsToken::Zero => return Ok(DimacsToken::Zero), 87 | DimacsToken::EOL => continue, 88 | DimacsToken::EOF => { 89 | // matrix contains no clauses 90 | return Ok(DimacsToken::EOF); 91 | } 92 | token => { 93 | return Err(ParseError { 94 | msg: format!("Expect `e`, `a`, or literal, but found `{:?}`", token), 95 | pos: lexer.pos(), 96 | }); 97 | } 98 | } 99 | } 100 | } 101 | 102 | /// A partial QDIMACS certificate is an assignment to the outermost quantifiers in the QBF 103 | #[derive(Debug, PartialEq, Eq)] 104 | pub struct PartialQDIMACSCertificate { 105 | pub result: SolverResult, 106 | pub num_variables: usize, 107 | pub num_clauses: usize, 108 | assignments: Vec, 109 | } 110 | 111 | impl PartialQDIMACSCertificate { 112 | #[must_use] 113 | pub fn new(result: SolverResult, num_variables: usize, num_clauses: usize) -> Self { 114 | Self { 115 | result, 116 | num_variables, 117 | num_clauses, 118 | assignments: Vec::new(), 119 | } 120 | } 121 | 122 | pub fn add_assignment(&mut self, assignment: Literal) { 123 | if self.assignments.binary_search(&assignment).is_ok() { 124 | return; 125 | } 126 | self.assignments.push(assignment); 127 | self.assignments.sort(); 128 | } 129 | 130 | pub fn extend_assignments(&mut self, qdo: Self) { 131 | for assignment in qdo.assignments { 132 | self.add_assignment(assignment); 133 | } 134 | } 135 | } 136 | 137 | /// Parses the `s cnf RESULT NUM NUM` header and returns result, number of variables, and number of clauses 138 | pub fn parse_qdimacs_certificate_header( 139 | lexer: &mut DimacsTokenStream, 140 | ) -> Result<(SolverResult, usize, usize), ParseError> { 141 | // first non-EOL token has to be `s cnf ` header 142 | loop { 143 | match lexer.next_token()? { 144 | DimacsToken::EOL => continue, 145 | DimacsToken::SolutionHeader => break, 146 | token => { 147 | return Err(ParseError { 148 | msg: format!("Expect `s cnf`, but found `{:?}`", token), 149 | pos: lexer.pos(), 150 | }); 151 | } 152 | } 153 | } 154 | let result = match lexer.next_token()? { 155 | DimacsToken::Zero => SolverResult::Unsatisfiable, 156 | DimacsToken::Lit(l) => { 157 | if l.signed() { 158 | return Err(ParseError { 159 | msg: "Malformed `s cnf` header, found negative value for number of variables" 160 | .to_string(), 161 | pos: lexer.pos(), 162 | }); 163 | } 164 | if l.variable() == 1_u32.into() { 165 | if l.signed() { 166 | SolverResult::Unknown 167 | } else { 168 | SolverResult::Satisfiable 169 | } 170 | } else { 171 | return Err(ParseError { 172 | msg: format!( 173 | "Malformed `s cnf` header, expect `-1`, `0`, or `1` for result, found {}", 174 | l.dimacs() 175 | ), 176 | pos: lexer.pos(), 177 | }); 178 | } 179 | } 180 | token => { 181 | return Err(ParseError { 182 | msg: format!( 183 | "Malformed `s cnf` header, expected number of variables, found `{:?}`", 184 | token 185 | ), 186 | pos: lexer.pos(), 187 | }); 188 | } 189 | }; 190 | let num_variables = match lexer.next_token()? { 191 | DimacsToken::Zero => 0_u32.into(), 192 | DimacsToken::Lit(l) => { 193 | if l.signed() { 194 | return Err(ParseError { 195 | msg: "Malformed `p cnf` header, found negative value for number of variables" 196 | .to_string(), 197 | pos: lexer.pos(), 198 | }); 199 | } 200 | l.variable() 201 | } 202 | token => { 203 | return Err(ParseError { 204 | msg: format!( 205 | "Malformed `p cnf` header, expected number of variables, found `{:?}`", 206 | token 207 | ), 208 | pos: lexer.pos(), 209 | }); 210 | } 211 | }; 212 | let num_clauses = match lexer.next_token()? { 213 | DimacsToken::Zero => 0_u32.into(), 214 | DimacsToken::Lit(l) => { 215 | if l.signed() { 216 | return Err(ParseError { 217 | msg: "Malformed `p cnf` header, found negative value for number of clauses" 218 | .to_string(), 219 | pos: lexer.pos(), 220 | }); 221 | } 222 | l.variable() 223 | } 224 | token => { 225 | return Err(ParseError { 226 | msg: format!( 227 | "Malformed `p cnf` header, expected number of clauses, found `{:?}`", 228 | token 229 | ), 230 | pos: lexer.pos(), 231 | }); 232 | } 233 | }; 234 | Ok((result, num_variables.into(), num_clauses.into())) 235 | } 236 | 237 | impl FromStr for PartialQDIMACSCertificate { 238 | type Err = Box; 239 | fn from_str(s: &str) -> Result { 240 | let mut lexer = DimacsTokenStream::new(s); 241 | 242 | let (result, num_variables, num_clauses) = parse_qdimacs_certificate_header(&mut lexer)?; 243 | let mut certificate = Self::new(result, num_variables, num_clauses); 244 | 245 | loop { 246 | match lexer.next_token()? { 247 | DimacsToken::EOL => { 248 | // ignore empty lines 249 | continue; 250 | } 251 | DimacsToken::EOF => { 252 | break; 253 | } 254 | DimacsToken::V => { 255 | // V 0\n 256 | match lexer.next_token()? { 257 | DimacsToken::Lit(l) => { 258 | certificate.add_assignment(l); 259 | } 260 | token => { 261 | return Err(ParseError { 262 | msg: format!( 263 | "Encountered {:?} instead of literal in certificate line", 264 | token 265 | ), 266 | pos: lexer.pos(), 267 | } 268 | .into()); 269 | } 270 | } 271 | lexer.expect_next(&DimacsToken::Zero)?; 272 | lexer.expect_next(&DimacsToken::EOL)?; 273 | } 274 | token => { 275 | return Err(ParseError { 276 | msg: format!("Certificate line should start with `V`, found {:?}", token), 277 | pos: lexer.pos(), 278 | } 279 | .into()); 280 | } 281 | } 282 | } 283 | 284 | Ok(certificate) 285 | } 286 | } 287 | 288 | impl Dimacs for PartialQDIMACSCertificate { 289 | #[must_use] 290 | fn dimacs(&self) -> String { 291 | let mut dimacs = String::new(); 292 | dimacs.push_str(&format!( 293 | "s cnf {} {} {}\n", 294 | self.result.dimacs(), 295 | self.num_variables, 296 | self.num_clauses, 297 | )); 298 | for literal in &self.assignments { 299 | dimacs.push_str(&format!("V {} 0\n", literal.dimacs())); 300 | } 301 | dimacs 302 | } 303 | } 304 | 305 | #[cfg(test)] 306 | mod tests { 307 | 308 | use super::*; 309 | use crate::{ 310 | clause::Clause, 311 | literal::Variable, 312 | matrix::{Prefix, VariableInfo}, 313 | }; 314 | 315 | #[test] 316 | fn test_simple() { 317 | let result = parse("p cnf 4 2\na 1 2 0\ne 3 4 0\n-1 3 0\n2 -3 -4 0\n"); 318 | assert!(result.is_ok()); 319 | let matrix = result.unwrap(); 320 | 321 | let v1 = Variable::from(1_u32); 322 | let v2 = Variable::from(2_u32); 323 | let v3 = Variable::from(3_u32); 324 | let v4 = Variable::from(4_u32); 325 | 326 | // prefix 327 | let variables = matrix.prefix.variables(); 328 | assert!(variables.get(v1).is_universal()); 329 | assert!(variables.get(v2).is_universal()); 330 | assert!(variables.get(v3).is_existential()); 331 | assert!(variables.get(v4).is_existential()); 332 | 333 | // clauses 334 | let mut clause_iter = matrix.clauses.iter(); 335 | assert_eq!( 336 | clause_iter.next(), 337 | Some(&Clause::new(vec![(-1).into(), 3.into()])) 338 | ); 339 | assert_eq!( 340 | clause_iter.next(), 341 | Some(&Clause::new(vec![(2).into(), (-3).into(), (-4).into()])) 342 | ); 343 | assert_eq!(clause_iter.next(), None); 344 | } 345 | 346 | #[test] 347 | fn test_expect_header_or_comment() { 348 | let result = parse("c comment line\nsome wrong line\np cnf 0 0\n"); 349 | assert!(result.is_err()); 350 | } 351 | 352 | #[test] 353 | fn test_expect_header() { 354 | let result = parse("c comment line\n"); 355 | assert!(result.is_err()); 356 | } 357 | 358 | #[test] 359 | fn test_wrong_header() { 360 | let instances = vec![ 361 | "pcnf 0 0\n", 362 | "p\n", 363 | "pcnf\n", 364 | "p cnf\n", 365 | "p cnf 1\n", 366 | "p cnf -1 0\n", 367 | ]; 368 | for instance in instances { 369 | let result = parse(instance); 370 | assert!(result.is_err(), instance); 371 | } 372 | } 373 | 374 | #[test] 375 | fn test_prefix_after_clause() { 376 | let result = parse("p cnf 1 1\n1 2 0\ne 1 2 0\n"); 377 | assert!(result.is_err()); 378 | } 379 | 380 | #[test] 381 | fn test_negation_in_prefix() { 382 | let result = parse("p cnf 1 1\na 1 -2 0\n"); 383 | assert!(result.is_err()); 384 | } 385 | 386 | #[test] 387 | fn test_wrong_clauses() { 388 | let instances = vec![ 389 | "p cnf 2 1\n1 0 2\n", 390 | "p cnf 2 1\n1 2\n", 391 | "p cnf 2 1\n1 2 a 0\n", 392 | ]; 393 | for instance in instances { 394 | let result = parse(instance); 395 | assert!(result.is_err(), instance); 396 | } 397 | } 398 | 399 | #[test] 400 | fn test_wrong_number_of_clauses() { 401 | let instances = vec![ 402 | "p cnf 2 2\n1 2 0\n", // less 403 | "p cnf 2 1\n1 2 0\n-1 -2 0\n", // more 404 | ]; 405 | for instance in instances { 406 | let result = parse(instance); 407 | assert!(result.is_err(), instance); 408 | } 409 | } 410 | 411 | #[test] 412 | fn test_empty_lines() { 413 | let instances = vec![ 414 | "\np cnf 1 1\n1 2 0\n", // empty line in front 415 | "p cnf 1 1\n\n1 2 0\n", // empty line before clause 416 | "p cnf 1 1\n1 2 0\n\n", // empty line after clause 417 | ]; 418 | for instance in instances { 419 | let result = parse(instance); 420 | assert!(result.is_ok(), instance); 421 | } 422 | } 423 | 424 | #[test] 425 | fn test_whitespaces_in_clauses() { 426 | let instances = vec!["p cnf 2 1\n1 2 0\n", "p cnf 2 1\n1 2 0 \n"]; 427 | for instance in instances { 428 | let result = parse(instance); 429 | assert!(result.is_ok(), instance); 430 | } 431 | } 432 | 433 | #[test] 434 | fn test_correct_qdimacs() { 435 | let instances = vec!["p cnf 0 0\n", "p cnf 0 1\n0\n"]; 436 | for instance in instances { 437 | let result = parse(instance); 438 | assert!(result.is_ok(), instance); 439 | } 440 | } 441 | 442 | #[test] 443 | fn test_qdimacs_output() { 444 | let certificate = PartialQDIMACSCertificate { 445 | result: SolverResult::Satisfiable, 446 | num_variables: 4, 447 | num_clauses: 3, 448 | assignments: vec![Literal::new(2_u32, true), Literal::new(3_u32, false)], 449 | }; 450 | let dimacs_output = certificate.dimacs(); 451 | assert_eq!(dimacs_output.as_str(), "s cnf 1 4 3\nV -2 0\nV 3 0\n"); 452 | let parsed: PartialQDIMACSCertificate = dimacs_output.parse().unwrap(); 453 | assert_eq!(certificate, parsed); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /src/experiment.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | parse::qdimacs, 3 | solve::{ 4 | caqe::{CaqeSolver, SolverOptions}, 5 | Solver, SolverResult, 6 | }, 7 | }; 8 | use atomicwrites::{AllowOverwrite, AtomicFile}; 9 | use clap::{App, Arg, SubCommand}; 10 | use indicatif::{ProgressBar, ProgressStyle}; 11 | use serde::{Deserialize, Serialize}; 12 | use std::{ 13 | cmp::Ordering, 14 | collections::HashMap, 15 | error::Error, 16 | fs::File, 17 | io::Read as _, 18 | sync::{ 19 | atomic::{AtomicBool, Ordering as AtomicOrdering}, 20 | mpsc::{channel, RecvTimeoutError}, 21 | Arc, 22 | }, 23 | thread, 24 | time::{Duration, Instant}, 25 | }; 26 | 27 | #[allow(clippy::module_name_repetitions)] 28 | #[derive(Debug, Clone)] 29 | pub struct ExperimentConfig { 30 | config_file: String, 31 | mode: ExperimentMode, 32 | } 33 | 34 | #[allow(clippy::module_name_repetitions)] 35 | #[derive(Debug, Clone, Copy)] 36 | pub enum ExperimentMode { 37 | Run, 38 | Analyze, 39 | Compare(usize, usize), 40 | } 41 | 42 | #[allow(clippy::module_name_repetitions)] 43 | #[derive(Debug, Clone, Serialize, Deserialize)] 44 | pub struct ExperimentResult { 45 | result: SolverResult, 46 | iterations: usize, 47 | duration: Duration, 48 | } 49 | 50 | #[derive(Debug, Clone, Serialize, Deserialize)] 51 | struct Results { 52 | benchmarks: Vec, 53 | configs: Vec, 54 | /// indexed by `configs` 55 | results: Vec>, 56 | } 57 | 58 | impl ExperimentConfig { 59 | #[allow(unreachable_code)] 60 | #[allow(unused_variables)] 61 | pub fn new(args: &[String]) -> Result> { 62 | let flags = App::new("experiment") 63 | .version(env!("CARGO_PKG_VERSION")) 64 | .author(env!("CARGO_PKG_AUTHORS")) 65 | .about("This utility helps finding the best solver configuration for a given benchmark set.") 66 | .arg(Arg::with_name("config") 67 | .long("--config") 68 | .short("-c") 69 | .help("Sets the path of the config file") 70 | .required(true) 71 | .takes_value(true) 72 | ).subcommand( 73 | SubCommand::with_name("create") 74 | .about("Creates a new experiment") 75 | .arg( 76 | Arg::with_name("benchmarks") 77 | .long("--benchmarks") 78 | .help("Sets the benchmarks to use") 79 | .required(true) 80 | .takes_value(true) 81 | .multiple(true) 82 | ) 83 | ).subcommand(SubCommand::with_name("continue").about("Continues an experiment")) 84 | .subcommand(SubCommand::with_name("analyze").about("Analyzes an experiment")) 85 | .subcommand(SubCommand::with_name("compare").about("Compares two solver configurations") 86 | .arg( 87 | Arg::with_name("solver-configs") 88 | .help("Index of the solver configs that should be compared") 89 | .required(true) 90 | .takes_value(true) 91 | .multiple(true) 92 | ) 93 | ); 94 | 95 | #[cfg(not(feature = "statistics"))] 96 | panic!("Feature `statistics` needs to be enabled"); 97 | 98 | let matches = flags.get_matches_from(args); 99 | let config_file: &str = matches.value_of("config").unwrap(); 100 | 101 | Ok(match matches.subcommand() { 102 | ("create", Some(matches)) => { 103 | let benchmarks: Vec = matches 104 | .values_of("benchmarks") 105 | .unwrap() 106 | .map(std::string::ToString::to_string) 107 | .collect(); 108 | eprintln!("Selected {:?} benchmarks", benchmarks.len()); 109 | 110 | eprintln!("Write config to `{:?}`", config_file); 111 | 112 | let config = Results::new(benchmarks, SolverOptions::default()); 113 | let file = File::create(config_file)?; 114 | serde_json::to_writer_pretty(file, &config)?; 115 | 116 | Self { 117 | mode: ExperimentMode::Run, 118 | config_file: config_file.to_string(), 119 | } 120 | } 121 | ("continue", Some(_)) => Self { 122 | mode: ExperimentMode::Run, 123 | config_file: config_file.to_string(), 124 | }, 125 | ("analyze", Some(_)) => Self { 126 | mode: ExperimentMode::Analyze, 127 | config_file: config_file.to_string(), 128 | }, 129 | ("compare", Some(matches)) => { 130 | let cfgs: Vec = matches 131 | .values_of("solver-configs") 132 | .unwrap() 133 | .map(|s| { 134 | s.parse::().unwrap_or_else(|_| { 135 | panic!("Expected index of solver config, found {}", s); 136 | }) 137 | }) 138 | .collect(); 139 | assert_eq!(cfgs.len(), 2); 140 | 141 | Self { 142 | mode: ExperimentMode::Compare(cfgs[0], cfgs[1]), 143 | config_file: config_file.to_string(), 144 | } 145 | } 146 | (cmd, _) => unreachable!("unknown subcommand `{}`", cmd), 147 | }) 148 | } 149 | 150 | pub fn run(&self) -> Result<(), Box> { 151 | match self.mode { 152 | ExperimentMode::Run => { 153 | self.run_experiment()?; 154 | self.analyze_experiment() 155 | } 156 | ExperimentMode::Analyze => self.analyze_experiment(), 157 | ExperimentMode::Compare(a, b) => self.compare_solver_configs(a, b), 158 | } 159 | } 160 | 161 | fn run_experiment(&self) -> Result<(), Box> { 162 | let mut file = File::open(&self.config_file)?; 163 | let mut contents = String::new(); 164 | file.read_to_string(&mut contents)?; 165 | 166 | let mut results: Results = serde_json::from_str(&contents)?; 167 | 168 | assert_eq!(results.configs.len(), results.results.len()); 169 | 170 | let timeout = Duration::from_secs(60); 171 | 172 | let num_open = results.benchmarks.iter().fold(0, |val, bench| { 173 | val + results 174 | .configs 175 | .iter() 176 | .enumerate() 177 | .fold(0, |val, (config_idx, _)| { 178 | val + if let Some(result) = results.results[config_idx].get(&bench.clone()) { 179 | // re-run experiment only if timeout has increased 180 | match result.result { 181 | SolverResult::Satisfiable | SolverResult::Unsatisfiable => 0, 182 | SolverResult::Unknown if result.duration >= timeout => 0, 183 | SolverResult::Unknown => { 184 | assert!(result.duration < timeout); 185 | 1 186 | } 187 | } 188 | } else { 189 | 1 190 | } 191 | }) 192 | }); 193 | let progress_bar = ProgressBar::new(num_open); 194 | progress_bar.set_style( 195 | ProgressStyle::default_bar().template("{wide_bar} {pos}/{len} {eta} remaining"), 196 | ); 197 | progress_bar.enable_steady_tick(100); 198 | 199 | for benchmark in &results.benchmarks { 200 | for (config_idx, &config) in results.configs.iter().enumerate() { 201 | if let Some(result) = results.results[config_idx].get(&benchmark.clone()) { 202 | // re-run experiment only if timeout has increased 203 | match result.result { 204 | SolverResult::Satisfiable | SolverResult::Unsatisfiable => continue, 205 | SolverResult::Unknown if result.duration >= timeout => continue, 206 | SolverResult::Unknown => { 207 | assert!(result.duration < timeout); 208 | } 209 | } 210 | } 211 | //println!("run {:?}", benchmark); 212 | 213 | let mut benchmark_file = File::open(benchmark)?; 214 | let mut file_contents = String::new(); 215 | benchmark_file.read_to_string(&mut file_contents)?; 216 | 217 | let mut matrix = qdimacs::parse(&file_contents)?; 218 | 219 | let (tx, rx) = channel(); 220 | 221 | let interrupt_handler = Arc::new(AtomicBool::new(false)); 222 | 223 | let interrupted = interrupt_handler.clone(); 224 | 225 | let child = thread::spawn(move || { 226 | let start = Instant::now(); 227 | if config.miniscoping { 228 | matrix.unprenex_by_miniscoping(); 229 | } 230 | if config.expansion.dependency_schemes { 231 | matrix.refl_res_path_dep_scheme(); 232 | } 233 | let mut solver = CaqeSolver::new_with_options(&mut matrix, config); 234 | solver.set_interrupt(interrupted); 235 | let result = solver.solve(); 236 | #[allow(unused_assignments)] 237 | #[allow(unused_mut)] 238 | let mut iterations = 0; 239 | 240 | #[cfg(feature = "statistics")] 241 | { 242 | iterations = solver.get_iterations(); 243 | } 244 | 245 | tx.send(ExperimentResult::new(result, iterations, start.elapsed())) 246 | .expect("Unable to send on channel"); 247 | }); 248 | 249 | match rx.recv_timeout(timeout) { 250 | Ok(result) => { 251 | results.results[config_idx].insert(benchmark.clone(), result); 252 | } 253 | Err(RecvTimeoutError::Timeout) => { 254 | interrupt_handler.store(true, AtomicOrdering::Relaxed); 255 | 256 | results.results[config_idx].insert( 257 | benchmark.clone(), 258 | ExperimentResult::new(SolverResult::Unknown, 0, timeout), 259 | ); 260 | } 261 | _ => panic!("channel error"), 262 | } 263 | 264 | progress_bar.inc(1); 265 | 266 | let af = AtomicFile::new(&self.config_file, AllowOverwrite); 267 | af.write(|f| serde_json::to_writer_pretty(f, &results))?; 268 | 269 | //serde_json::to_writer_pretty(File::create(&self.config_file)?, &results)?; 270 | 271 | // some work here 272 | let _res = child.join(); 273 | } 274 | } 275 | 276 | progress_bar.finish(); 277 | 278 | Ok(()) 279 | } 280 | 281 | fn analyze_experiment(&self) -> Result<(), Box> { 282 | let mut file = File::open(&self.config_file)?; 283 | let mut contents = String::new(); 284 | file.read_to_string(&mut contents)?; 285 | 286 | let results: Results = serde_json::from_str(&contents)?; 287 | 288 | // check for inconsistent results 289 | for benchmark in &results.benchmarks { 290 | let mut sat = Vec::new(); 291 | let mut unsat = Vec::new(); 292 | for (config_idx, result) in results.results.iter().enumerate() { 293 | if let Some(res) = result.get(benchmark) { 294 | match res.result { 295 | SolverResult::Satisfiable => sat.push(config_idx), 296 | SolverResult::Unsatisfiable => unsat.push(config_idx), 297 | _ => {} 298 | } 299 | } 300 | } 301 | if !sat.is_empty() && !unsat.is_empty() { 302 | println!( 303 | "inconsistent result {}: sat {:?}, unsat {:?}", 304 | benchmark, sat, unsat 305 | ); 306 | } 307 | } 308 | 309 | for (config_idx, _) in results.configs.iter().enumerate() { 310 | let mut solved = 0; 311 | let mut sat = 0; 312 | let mut unsat = 0; 313 | let mut iterations = 0; 314 | 315 | for result in results.results[config_idx].values() { 316 | match result.result { 317 | SolverResult::Unknown => continue, 318 | SolverResult::Satisfiable => { 319 | sat += 1; 320 | } 321 | SolverResult::Unsatisfiable => { 322 | unsat += 1; 323 | } 324 | } 325 | solved += 1; 326 | iterations += result.iterations; 327 | } 328 | 329 | println!( 330 | "cfg {}, solved {}, sat {}, unsat {}, iter {}", 331 | config_idx, solved, sat, unsat, iterations 332 | ); 333 | } 334 | Ok(()) 335 | } 336 | 337 | #[allow(clippy::too_many_lines)] 338 | fn compare_solver_configs( 339 | &self, 340 | base_idx: usize, 341 | other_idx: usize, 342 | ) -> Result<(), Box> { 343 | let mut file = File::open(&self.config_file)?; 344 | let mut contents = String::new(); 345 | file.read_to_string(&mut contents)?; 346 | 347 | let results: Results = serde_json::from_str(&contents)?; 348 | 349 | //println!("cfg {}: {}", base_idx, serde_json::to_string_pretty(&results.configs[base_idx])?); 350 | //println!("cfg {}: {}", other_idx, serde_json::to_string_pretty(&results.configs[other_idx])?); 351 | 352 | println!( 353 | "{}", 354 | colored_diff::PrettyDifference { 355 | expected: &serde_json::to_string_pretty(&results.configs[base_idx])?, 356 | actual: &serde_json::to_string_pretty(&results.configs[other_idx])? 357 | } 358 | ); 359 | 360 | let mut equal_num_iterations = 0; 361 | let mut base_less_num_iterations = 0; 362 | let mut other_less_num_iterations = 0; 363 | let mut equal_solving_times = 0; 364 | let mut base_less_solving_time = 0; 365 | let mut other_less_solving_time = 0; 366 | let mut unique_base = Vec::new(); 367 | let mut unique_other = Vec::new(); 368 | for benchmark in &results.benchmarks { 369 | let base_res = if let Some(res) = results.results[base_idx].get(benchmark) { 370 | res 371 | } else { 372 | continue; 373 | }; 374 | let other_res = if let Some(res) = results.results[other_idx].get(benchmark) { 375 | res 376 | } else { 377 | continue; 378 | }; 379 | 380 | match (base_res.result, other_res.result) { 381 | (SolverResult::Unknown, SolverResult::Unknown) => { 382 | if base_res.duration != other_res.duration { 383 | eprintln!( 384 | "inconsistent timeouts on `{}`: {:?} vs. {:?}", 385 | benchmark, base_res.duration, other_res.duration 386 | ); 387 | } 388 | continue; 389 | } 390 | (SolverResult::Unknown, _) => { 391 | unique_other.push((benchmark.clone(), other_res.clone())); 392 | continue; 393 | } 394 | (_, SolverResult::Unknown) => { 395 | unique_base.push((benchmark.clone(), base_res.clone())); 396 | continue; 397 | } 398 | (SolverResult::Satisfiable, SolverResult::Satisfiable) 399 | | (SolverResult::Unsatisfiable, SolverResult::Unsatisfiable) => {} 400 | (a, b) => eprintln!( 401 | "inconsistent results on `{}`: {:?} vs. {:?}", 402 | benchmark, a, b 403 | ), 404 | } 405 | match base_res.iterations.cmp(&other_res.iterations) { 406 | Ordering::Equal => equal_num_iterations += 1, 407 | Ordering::Less => base_less_num_iterations += 1, 408 | Ordering::Greater => other_less_num_iterations += 1, 409 | } 410 | if let Some(diff) = base_res.duration.checked_sub(other_res.duration) { 411 | if diff > Duration::from_secs(1) { 412 | other_less_solving_time += 1; 413 | } else { 414 | equal_solving_times += 1; 415 | } 416 | } else if let Some(diff) = other_res.duration.checked_sub(base_res.duration) { 417 | if diff > Duration::from_secs(1) { 418 | base_less_solving_time += 1; 419 | } else { 420 | equal_solving_times += 1; 421 | } 422 | } else { 423 | unreachable!(); 424 | } 425 | } 426 | 427 | println!( 428 | "\niterations: equal {}, cfg {} less {}, cfg {} less {}", 429 | equal_num_iterations, 430 | base_idx, 431 | base_less_num_iterations, 432 | other_idx, 433 | other_less_num_iterations 434 | ); 435 | println!( 436 | "solving time: equal {}, cfg {} less {}, cfg {} less {}", 437 | equal_solving_times, 438 | base_idx, 439 | base_less_solving_time, 440 | other_idx, 441 | other_less_solving_time 442 | ); 443 | println!( 444 | "uniquely solved: cfg {}: {}, cfg {}: {}", 445 | base_idx, 446 | unique_base.len(), 447 | other_idx, 448 | unique_other.len() 449 | ); 450 | 451 | if !unique_base.is_empty() { 452 | println!("\nunique cfg {}", base_idx); 453 | for (bench, res) in &unique_base { 454 | println!("* {}: {:?}", bench, res); 455 | } 456 | } 457 | if !unique_other.is_empty() { 458 | println!("\nunique cfg {}", other_idx); 459 | for (bench, res) in &unique_other { 460 | println!("* {}: {:?}", bench, res); 461 | } 462 | } 463 | 464 | Ok(()) 465 | } 466 | } 467 | 468 | impl ExperimentResult { 469 | fn new(result: SolverResult, iterations: usize, duration: Duration) -> Self { 470 | Self { 471 | result, 472 | iterations, 473 | duration, 474 | } 475 | } 476 | } 477 | 478 | impl Results { 479 | fn new(benchmarks: Vec, options: SolverOptions) -> Self { 480 | Self { 481 | benchmarks, 482 | configs: vec![options], 483 | results: vec![HashMap::new()], 484 | } 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.13" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "ansi_term" 14 | version = "0.11.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 17 | dependencies = [ 18 | "winapi", 19 | ] 20 | 21 | [[package]] 22 | name = "atomicwrites" 23 | version = "0.2.5" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "6a2baf2feb820299c53c7ad1cc4f5914a220a1cb76d7ce321d2522a94b54651f" 26 | dependencies = [ 27 | "nix", 28 | "tempdir", 29 | "winapi", 30 | ] 31 | 32 | [[package]] 33 | name = "atty" 34 | version = "0.2.11" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" 37 | dependencies = [ 38 | "libc", 39 | "termion", 40 | "winapi", 41 | ] 42 | 43 | [[package]] 44 | name = "bit-vec" 45 | version = "0.6.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" 48 | 49 | [[package]] 50 | name = "bitflags" 51 | version = "1.0.4" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 54 | 55 | [[package]] 56 | name = "c2-chacha" 57 | version = "0.2.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" 60 | dependencies = [ 61 | "ppv-lite86", 62 | ] 63 | 64 | [[package]] 65 | name = "caqe" 66 | version = "4.0.1" 67 | dependencies = [ 68 | "atomicwrites", 69 | "bit-vec", 70 | "clap", 71 | "colored-diff", 72 | "cryptominisat", 73 | "dot", 74 | "ena", 75 | "env_logger", 76 | "indicatif", 77 | "jemallocator", 78 | "log", 79 | "rustc-hash", 80 | "serde", 81 | "serde_json", 82 | "tempfile", 83 | "uncover", 84 | ] 85 | 86 | [[package]] 87 | name = "cc" 88 | version = "1.0.29" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "4390a3b5f4f6bce9c1d0c00128379df433e53777fdd30e92f16a529332baec4e" 91 | 92 | [[package]] 93 | name = "cfg-if" 94 | version = "0.1.6" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" 97 | 98 | [[package]] 99 | name = "clap" 100 | version = "2.33.1" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 103 | dependencies = [ 104 | "ansi_term", 105 | "atty", 106 | "bitflags", 107 | "strsim", 108 | "textwrap", 109 | "unicode-width", 110 | "vec_map", 111 | ] 112 | 113 | [[package]] 114 | name = "clicolors-control" 115 | version = "1.0.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9" 118 | dependencies = [ 119 | "atty", 120 | "lazy_static", 121 | "libc", 122 | "winapi", 123 | ] 124 | 125 | [[package]] 126 | name = "cmake" 127 | version = "0.1.35" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "6ec65ee4f9c9d16f335091d23693457ed4928657ba4982289d7fafee03bc614a" 130 | dependencies = [ 131 | "cc", 132 | ] 133 | 134 | [[package]] 135 | name = "colored-diff" 136 | version = "0.2.2" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "516f260afc909bb0d056b76891ad91b3275b83682d851b566792077eee946efd" 139 | dependencies = [ 140 | "ansi_term", 141 | "difference", 142 | "itertools", 143 | ] 144 | 145 | [[package]] 146 | name = "console" 147 | version = "0.9.1" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "f5d540c2d34ac9dd0deb5f3b5f54c36c79efa78f6b3ad19106a554d07a7b5d9f" 150 | dependencies = [ 151 | "clicolors-control", 152 | "encode_unicode", 153 | "lazy_static", 154 | "libc", 155 | "regex", 156 | "termios", 157 | "unicode-width", 158 | "winapi", 159 | ] 160 | 161 | [[package]] 162 | name = "cryptominisat" 163 | version = "5.6.6" 164 | source = "git+https://github.com/ltentrup/cryptominisat-rs.git?rev=79380e6117fb83dfce7224fffcee992446577686#79380e6117fb83dfce7224fffcee992446577686" 165 | dependencies = [ 166 | "cmake", 167 | "libc", 168 | ] 169 | 170 | [[package]] 171 | name = "difference" 172 | version = "2.0.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" 175 | 176 | [[package]] 177 | name = "dot" 178 | version = "0.1.4" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "a74b6c4d4a1cff5f454164363c16b72fa12463ca6b31f4b5f2035a65fa3d5906" 181 | 182 | [[package]] 183 | name = "either" 184 | version = "1.5.2" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" 187 | 188 | [[package]] 189 | name = "ena" 190 | version = "0.14.0" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" 193 | dependencies = [ 194 | "log", 195 | ] 196 | 197 | [[package]] 198 | name = "encode_unicode" 199 | version = "0.3.5" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "90b2c9496c001e8cb61827acdefad780795c42264c137744cae6f7d9e3450abd" 202 | 203 | [[package]] 204 | name = "env_logger" 205 | version = "0.7.1" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 208 | dependencies = [ 209 | "atty", 210 | "humantime", 211 | "log", 212 | "regex", 213 | "termcolor", 214 | ] 215 | 216 | [[package]] 217 | name = "fs_extra" 218 | version = "1.1.0" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "5f2a4a2034423744d2cc7ca2068453168dcdb82c438419e639a26bd87839c674" 221 | 222 | [[package]] 223 | name = "fuchsia-cprng" 224 | version = "0.1.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 227 | 228 | [[package]] 229 | name = "getrandom" 230 | version = "0.1.14" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 233 | dependencies = [ 234 | "cfg-if", 235 | "libc", 236 | "wasi", 237 | ] 238 | 239 | [[package]] 240 | name = "humantime" 241 | version = "1.3.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 244 | dependencies = [ 245 | "quick-error", 246 | ] 247 | 248 | [[package]] 249 | name = "indicatif" 250 | version = "0.15.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" 253 | dependencies = [ 254 | "console", 255 | "lazy_static", 256 | "number_prefix", 257 | "regex", 258 | ] 259 | 260 | [[package]] 261 | name = "itertools" 262 | version = "0.7.11" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" 265 | dependencies = [ 266 | "either", 267 | ] 268 | 269 | [[package]] 270 | name = "itoa" 271 | version = "0.4.3" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" 274 | 275 | [[package]] 276 | name = "jemalloc-sys" 277 | version = "0.3.2" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" 280 | dependencies = [ 281 | "cc", 282 | "fs_extra", 283 | "libc", 284 | ] 285 | 286 | [[package]] 287 | name = "jemallocator" 288 | version = "0.3.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" 291 | dependencies = [ 292 | "jemalloc-sys", 293 | "libc", 294 | ] 295 | 296 | [[package]] 297 | name = "lazy_static" 298 | version = "1.2.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" 301 | 302 | [[package]] 303 | name = "libc" 304 | version = "0.2.66" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 307 | 308 | [[package]] 309 | name = "log" 310 | version = "0.4.11" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 313 | dependencies = [ 314 | "cfg-if", 315 | ] 316 | 317 | [[package]] 318 | name = "memchr" 319 | version = "2.3.3" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 322 | 323 | [[package]] 324 | name = "nix" 325 | version = "0.14.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" 328 | dependencies = [ 329 | "bitflags", 330 | "cc", 331 | "cfg-if", 332 | "libc", 333 | "void", 334 | ] 335 | 336 | [[package]] 337 | name = "number_prefix" 338 | version = "0.3.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" 341 | 342 | [[package]] 343 | name = "ppv-lite86" 344 | version = "0.2.6" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 347 | 348 | [[package]] 349 | name = "proc-macro2" 350 | version = "0.4.27" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" 353 | dependencies = [ 354 | "unicode-xid", 355 | ] 356 | 357 | [[package]] 358 | name = "quick-error" 359 | version = "1.2.3" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 362 | 363 | [[package]] 364 | name = "quote" 365 | version = "0.6.12" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" 368 | dependencies = [ 369 | "proc-macro2", 370 | ] 371 | 372 | [[package]] 373 | name = "rand" 374 | version = "0.4.6" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 377 | dependencies = [ 378 | "fuchsia-cprng", 379 | "libc", 380 | "rand_core 0.3.1", 381 | "rdrand", 382 | "winapi", 383 | ] 384 | 385 | [[package]] 386 | name = "rand" 387 | version = "0.7.3" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 390 | dependencies = [ 391 | "getrandom", 392 | "libc", 393 | "rand_chacha", 394 | "rand_core 0.5.1", 395 | "rand_hc", 396 | ] 397 | 398 | [[package]] 399 | name = "rand_chacha" 400 | version = "0.2.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 403 | dependencies = [ 404 | "c2-chacha", 405 | "rand_core 0.5.1", 406 | ] 407 | 408 | [[package]] 409 | name = "rand_core" 410 | version = "0.3.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 413 | dependencies = [ 414 | "rand_core 0.4.0", 415 | ] 416 | 417 | [[package]] 418 | name = "rand_core" 419 | version = "0.4.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" 422 | 423 | [[package]] 424 | name = "rand_core" 425 | version = "0.5.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 428 | dependencies = [ 429 | "getrandom", 430 | ] 431 | 432 | [[package]] 433 | name = "rand_hc" 434 | version = "0.2.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 437 | dependencies = [ 438 | "rand_core 0.5.1", 439 | ] 440 | 441 | [[package]] 442 | name = "rdrand" 443 | version = "0.4.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 446 | dependencies = [ 447 | "rand_core 0.3.1", 448 | ] 449 | 450 | [[package]] 451 | name = "redox_syscall" 452 | version = "0.1.51" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" 455 | 456 | [[package]] 457 | name = "redox_termios" 458 | version = "0.1.1" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" 461 | dependencies = [ 462 | "redox_syscall", 463 | ] 464 | 465 | [[package]] 466 | name = "regex" 467 | version = "1.3.3" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "b5508c1941e4e7cb19965abef075d35a9a8b5cdf0846f30b4050e9b55dc55e87" 470 | dependencies = [ 471 | "aho-corasick", 472 | "memchr", 473 | "regex-syntax", 474 | "thread_local", 475 | ] 476 | 477 | [[package]] 478 | name = "regex-syntax" 479 | version = "0.6.13" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "e734e891f5b408a29efbf8309e656876276f49ab6a6ac208600b4419bd893d90" 482 | 483 | [[package]] 484 | name = "remove_dir_all" 485 | version = "0.5.1" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" 488 | dependencies = [ 489 | "winapi", 490 | ] 491 | 492 | [[package]] 493 | name = "rustc-hash" 494 | version = "1.1.0" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 497 | 498 | [[package]] 499 | name = "ryu" 500 | version = "0.2.7" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" 503 | 504 | [[package]] 505 | name = "serde" 506 | version = "1.0.90" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "aa5f7c20820475babd2c077c3ab5f8c77a31c15e16ea38687b4c02d3e48680f4" 509 | dependencies = [ 510 | "serde_derive", 511 | ] 512 | 513 | [[package]] 514 | name = "serde_derive" 515 | version = "1.0.90" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "58fc82bec244f168b23d1963b45c8bf5726e9a15a9d146a067f9081aeed2de79" 518 | dependencies = [ 519 | "proc-macro2", 520 | "quote", 521 | "syn", 522 | ] 523 | 524 | [[package]] 525 | name = "serde_json" 526 | version = "1.0.39" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" 529 | dependencies = [ 530 | "itoa", 531 | "ryu", 532 | "serde", 533 | ] 534 | 535 | [[package]] 536 | name = "strsim" 537 | version = "0.8.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 540 | 541 | [[package]] 542 | name = "syn" 543 | version = "0.15.30" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "66c8865bf5a7cbb662d8b011950060b3c8743dca141b054bf7195b20d314d8e2" 546 | dependencies = [ 547 | "proc-macro2", 548 | "quote", 549 | "unicode-xid", 550 | ] 551 | 552 | [[package]] 553 | name = "tempdir" 554 | version = "0.3.7" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 557 | dependencies = [ 558 | "rand 0.4.6", 559 | "remove_dir_all", 560 | ] 561 | 562 | [[package]] 563 | name = "tempfile" 564 | version = "3.1.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 567 | dependencies = [ 568 | "cfg-if", 569 | "libc", 570 | "rand 0.7.3", 571 | "redox_syscall", 572 | "remove_dir_all", 573 | "winapi", 574 | ] 575 | 576 | [[package]] 577 | name = "termcolor" 578 | version = "1.1.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 581 | dependencies = [ 582 | "winapi-util", 583 | ] 584 | 585 | [[package]] 586 | name = "termion" 587 | version = "1.5.1" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" 590 | dependencies = [ 591 | "libc", 592 | "redox_syscall", 593 | "redox_termios", 594 | ] 595 | 596 | [[package]] 597 | name = "termios" 598 | version = "0.3.1" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "72b620c5ea021d75a735c943269bb07d30c9b77d6ac6b236bc8b5c496ef05625" 601 | dependencies = [ 602 | "libc", 603 | ] 604 | 605 | [[package]] 606 | name = "textwrap" 607 | version = "0.11.0" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 610 | dependencies = [ 611 | "unicode-width", 612 | ] 613 | 614 | [[package]] 615 | name = "thread_local" 616 | version = "1.0.1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 619 | dependencies = [ 620 | "lazy_static", 621 | ] 622 | 623 | [[package]] 624 | name = "uncover" 625 | version = "0.1.1" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "93b8c2f1aec170216136efd4020bac2d0fa736760e54c16da538b4eda4a0d665" 628 | dependencies = [ 629 | "lazy_static", 630 | ] 631 | 632 | [[package]] 633 | name = "unicode-width" 634 | version = "0.1.5" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" 637 | 638 | [[package]] 639 | name = "unicode-xid" 640 | version = "0.1.0" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" 643 | 644 | [[package]] 645 | name = "vec_map" 646 | version = "0.8.1" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 649 | 650 | [[package]] 651 | name = "void" 652 | version = "1.0.2" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 655 | 656 | [[package]] 657 | name = "wasi" 658 | version = "0.9.0+wasi-snapshot-preview1" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 661 | 662 | [[package]] 663 | name = "winapi" 664 | version = "0.3.6" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 667 | dependencies = [ 668 | "winapi-i686-pc-windows-gnu", 669 | "winapi-x86_64-pc-windows-gnu", 670 | ] 671 | 672 | [[package]] 673 | name = "winapi-i686-pc-windows-gnu" 674 | version = "0.4.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 677 | 678 | [[package]] 679 | name = "winapi-util" 680 | version = "0.1.5" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 683 | dependencies = [ 684 | "winapi", 685 | ] 686 | 687 | [[package]] 688 | name = "winapi-x86_64-pc-windows-gnu" 689 | version = "0.4.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 692 | -------------------------------------------------------------------------------- /src/matrix/dependency.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | clause::Clause, 3 | dimacs::Dimacs, 4 | literal::Variable, 5 | matrix::{Prefix, VariableInfo, VariableStore}, 6 | }; 7 | use rustc_hash::FxHashSet; 8 | use std::cmp::Ordering; 9 | 10 | pub type ScopeId = usize; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct DQVariableInfo { 14 | bound: bool, 15 | scope_id: Option, 16 | dependencies: FxHashSet, 17 | } 18 | 19 | impl VariableInfo for DQVariableInfo { 20 | fn new() -> Self { 21 | Self { 22 | scope_id: None, 23 | bound: false, 24 | dependencies: FxHashSet::default(), 25 | } 26 | } 27 | 28 | fn is_universal(&self) -> bool { 29 | debug_assert!(self.is_bound()); 30 | self.scope_id.is_none() 31 | } 32 | 33 | fn is_bound(&self) -> bool { 34 | self.bound 35 | } 36 | 37 | fn remove_dependency(&mut self, spurious: Variable) { 38 | self.dependencies.remove(&spurious); 39 | } 40 | 41 | fn dependencies(&self) -> &FxHashSet { 42 | &self.dependencies 43 | } 44 | } 45 | 46 | impl DQVariableInfo { 47 | pub fn get_scope_id(&self) -> &Option { 48 | &self.scope_id 49 | } 50 | } 51 | 52 | /// A scope represents a grouping of existential variables with the same dependency set 53 | #[derive(Debug, Clone)] 54 | pub struct Scope { 55 | pub dependencies: FxHashSet, 56 | pub existentials: Vec, 57 | } 58 | 59 | impl Scope { 60 | pub fn new(dependencies: &FxHashSet) -> Self { 61 | Self { 62 | dependencies: dependencies.clone(), 63 | existentials: Vec::new(), 64 | } 65 | } 66 | 67 | pub fn contains(&self, variable: Variable) -> bool { 68 | self.existentials.contains(&variable) 69 | } 70 | } 71 | 72 | impl Dimacs for Scope { 73 | fn dimacs(&self) -> String { 74 | let mut dimacs = String::new(); 75 | for &variable in &self.existentials { 76 | dimacs.push_str(&format!("d {} ", variable)); 77 | for &dependency in &self.dependencies { 78 | dimacs.push_str(&format!("{} ", dependency)); 79 | } 80 | dimacs.push_str("0\n"); 81 | } 82 | dimacs 83 | } 84 | } 85 | 86 | impl PartialEq for Scope { 87 | fn eq(&self, other: &Self) -> bool { 88 | self.dependencies == other.dependencies 89 | } 90 | } 91 | 92 | impl PartialOrd for Scope { 93 | fn partial_cmp(&self, other: &Self) -> Option { 94 | if self.dependencies.len() == other.dependencies.len() { 95 | // self can only be equal or incomparable 96 | if self.dependencies == other.dependencies { 97 | Some(Ordering::Equal) 98 | } else { 99 | None 100 | } 101 | } else if self.dependencies.len() < other.dependencies.len() { 102 | // can only be subset or incomparable 103 | if self.dependencies.is_subset(&other.dependencies) { 104 | Some(Ordering::Less) 105 | } else { 106 | None 107 | } 108 | } else { 109 | // can only be superset or incomparable 110 | if self.dependencies.is_superset(&other.dependencies) { 111 | Some(Ordering::Greater) 112 | } else { 113 | None 114 | } 115 | } 116 | } 117 | } 118 | 119 | #[allow(clippy::module_name_repetitions)] 120 | #[derive(Debug)] 121 | pub struct DependencyPrefix { 122 | variables: VariableStore, 123 | pub scopes: Vec, 124 | } 125 | 126 | impl DependencyPrefix { 127 | pub fn add_existential>( 128 | &mut self, 129 | variable: V, 130 | dependencies: &FxHashSet, 131 | ) { 132 | let variable = variable.into(); 133 | self.variables.import(variable); 134 | if self.variables.get(variable).is_bound() { 135 | panic!("variable cannot be bound twice"); 136 | } 137 | let scope_id = match self.scope_lookup(dependencies) { 138 | None => { 139 | let scope_id = self.scopes.len(); 140 | self.scopes.push(Scope::new(dependencies)); 141 | scope_id 142 | } 143 | Some(scope_id) => scope_id, 144 | }; 145 | let scope = self 146 | .scopes 147 | .get_mut(scope_id) 148 | .expect("scope is guaranteed to exists"); 149 | scope.existentials.push(variable); 150 | 151 | let variable_info = self.variables.get_mut(variable); 152 | variable_info.scope_id = Some(scope_id); 153 | variable_info.bound = true; 154 | variable_info.dependencies = dependencies.clone(); 155 | } 156 | 157 | pub fn add_universal>(&mut self, variable: V) { 158 | let variable = variable.into(); 159 | self.variables.import(variable); 160 | if self.variables.get(variable).is_bound() { 161 | panic!("variable cannot be bound twice"); 162 | } 163 | let variable_info = self.variables.get_mut(variable); 164 | variable_info.scope_id = None; 165 | variable_info.bound = true; 166 | } 167 | 168 | pub fn scope_lookup(&self, dependencies: &FxHashSet) -> Option { 169 | for (scope_id, scope) in self.scopes.iter().enumerate() { 170 | if scope.dependencies == *dependencies { 171 | return Some(scope_id); 172 | } 173 | } 174 | None 175 | } 176 | 177 | pub fn get_scope(&self, scope_id: ScopeId) -> &Scope { 178 | &self.scopes[scope_id as usize] 179 | } 180 | 181 | /// makes sure that for every pair of scopes, the intersection of dependencies 182 | /// is contained as well 183 | pub fn build_closure(&mut self) { 184 | let mut to_add = Vec::new(); 185 | let mut union = FxHashSet::default(); 186 | loop { 187 | // TODO: there is probably a more efficient way, but the number of scopes is usually small anyways 188 | let mut changed = false; 189 | for (i, scope) in self.scopes.iter().enumerate() { 190 | for other in &self.scopes[i + 1..] { 191 | let intersection: FxHashSet = scope 192 | .dependencies 193 | .intersection(&other.dependencies) 194 | .cloned() 195 | .collect(); 196 | if self.scope_lookup(&intersection).is_none() { 197 | // intersection is not contained 198 | to_add.push(intersection); 199 | changed = true; 200 | } 201 | } 202 | union = union.union(&scope.dependencies).cloned().collect(); 203 | } 204 | for dependencies in &to_add { 205 | if self.scope_lookup(dependencies).is_none() { 206 | // intersection is not contained 207 | self.scopes.push(Scope::new(dependencies)); 208 | } 209 | } 210 | to_add.clear(); 211 | if !changed { 212 | break; 213 | } 214 | } 215 | if self.scope_lookup(&union).is_none() { 216 | // union of all universal variables not contained 217 | self.scopes.push(Scope::new(&union)); 218 | } 219 | } 220 | 221 | /// Builds the antichain decomposition of the dependency lattice 222 | pub fn antichain_decomposition(&self) -> Vec> { 223 | // sort scopes by superset inclusion 224 | let mut scopes: Vec = (0..self.scopes.len()).collect(); 225 | scopes.sort_unstable_by(|&scope, &other| { 226 | let left = &self.scopes[scope]; 227 | let right = &self.scopes[other]; 228 | let res = match left.partial_cmp(right) { 229 | Some(ord) => ord, 230 | None => left.dependencies.len().cmp(&right.dependencies.len()), 231 | }; 232 | // reverse 233 | match res { 234 | Ordering::Equal => Ordering::Equal, 235 | Ordering::Greater => Ordering::Less, 236 | Ordering::Less => Ordering::Greater, 237 | } 238 | }); 239 | 240 | // extract antichains 241 | let mut antichains = Vec::new(); 242 | while let Some(characteristic) = scopes.pop() { 243 | let mut antichain = vec![characteristic]; 244 | for &other in scopes.iter().rev() { 245 | let rhs = &self.scopes[other].dependencies; 246 | let incomparable = antichain.iter().fold(true, |val, &ele| { 247 | let lhs = &self.scopes[ele as usize].dependencies; 248 | val && !lhs.is_subset(rhs) && !lhs.is_superset(rhs) 249 | }); 250 | if incomparable { 251 | antichain.push(other); 252 | } 253 | } 254 | scopes = scopes 255 | .iter() 256 | .filter(|ele| !antichain.contains(ele)) 257 | .cloned() 258 | .collect(); 259 | antichains.push(antichain); 260 | } 261 | #[cfg(debug_assertions)] 262 | { 263 | for antichain in &antichains { 264 | for &scope_id in antichain { 265 | for &other_id in antichain { 266 | if scope_id == other_id { 267 | continue; 268 | } 269 | // verify antichain property 270 | let scope = &self.scopes[scope_id as usize].dependencies; 271 | let other = &self.scopes[other_id as usize].dependencies; 272 | debug_assert!(!scope.is_subset(other)); 273 | debug_assert!(!other.is_subset(scope)); 274 | } 275 | } 276 | } 277 | } 278 | antichains 279 | } 280 | 281 | /// Checks if existential variables in `scope` may depend on `var`. 282 | /// If `var` is existential, the dependencies of `var` 283 | /// have to be a subset of the dependencies of `scope`. 284 | /// If `var` is universal, it has to be contained in the 285 | /// dependencies of `scope`. 286 | pub fn depends_on_scope(&self, scope: &Scope, var: Variable) -> bool { 287 | let info = self.variables().get(var); 288 | if info.is_universal() { 289 | scope.dependencies.contains(&var) 290 | } else { 291 | let scope_id = info.scope_id.expect("var should be existential"); 292 | let other_scope = &self.scopes[scope_id]; 293 | other_scope.dependencies.is_subset(&scope.dependencies) 294 | } 295 | } 296 | 297 | /// Returns `true` is scope is maximal 298 | pub fn is_maximal(&self, scope: &Scope) -> bool { 299 | self.scopes.iter().all(|other| { 300 | // checks that other is not a strict superset 301 | other.dependencies.len() <= scope.dependencies.len() 302 | || !other.dependencies.is_superset(&scope.dependencies) 303 | }) 304 | } 305 | 306 | #[allow(dead_code)] 307 | #[cfg(dcaqe)] 308 | pub(crate) fn contains_dependency_fork(&self, clause: &Clause) -> bool { 309 | use crate::solve::dcaqe::MaxElements; 310 | let mut maximal_scopes: MaxElements = MaxElements::new(); 311 | for &literal in clause.iter() { 312 | let info = self.variables().get(literal.variable()); 313 | if let Some(scope_id) = *info.get_scope_id() { 314 | // TODO: we need a clone here, since the prefix is modified below, but this is wasteful 315 | let scope = self.get_scope(scope_id).clone(); 316 | maximal_scopes.add(scope); 317 | } 318 | } 319 | maximal_scopes.len() > 1 320 | } 321 | } 322 | 323 | impl Prefix for DependencyPrefix { 324 | type V = DQVariableInfo; 325 | 326 | fn new(num_variables: usize) -> Self { 327 | Self { 328 | variables: VariableStore::new(num_variables), 329 | scopes: Vec::new(), 330 | } 331 | } 332 | 333 | fn variables(&self) -> &VariableStore { 334 | &self.variables 335 | } 336 | fn mut_vars(&mut self) -> &mut VariableStore { 337 | &mut self.variables 338 | } 339 | 340 | fn import(&mut self, variable: Variable) { 341 | if !self.variables().get(variable).is_bound() { 342 | // bound free variables at top level existential quantifier, i.e., without dependencies 343 | self.add_existential(variable, &FxHashSet::default()); 344 | } 345 | } 346 | 347 | fn reduce_universal(&self, clause: &mut Clause) { 348 | let dependencies = clause.iter().fold(FxHashSet::default(), |mut acc, v| { 349 | match self.variables().get(v.variable()).scope_id { 350 | None => acc, 351 | Some(scope_id) => { 352 | for var in &self.scopes[scope_id].dependencies { 353 | acc.insert(var); 354 | } 355 | acc 356 | } 357 | } 358 | }); 359 | clause.retain(|l| { 360 | if self.variables().get(l.variable()).is_universal() { 361 | dependencies.contains(&l.variable()) 362 | } else { 363 | // retain all existential literals 364 | true 365 | } 366 | }); 367 | } 368 | 369 | /// Checks if an existential variable `var` depends on `other`. 370 | /// If `other` is existential, the dependencies of `other` 371 | /// have to be a subset of the dependencies of `var`. 372 | /// If `other` is universal, it has to be contained in the 373 | /// dependencies of `var`. 374 | fn depends_on, U: Into>(&self, var: V, other: U) -> bool { 375 | let var = var.into(); 376 | let other = other.into(); 377 | let info = self.variables().get(var); 378 | let scope_id = info 379 | .scope_id 380 | .expect("depends_on called on universal variable"); 381 | let scope = &self.scopes[scope_id]; 382 | let other_info = self.variables().get(other); 383 | if other_info.is_universal() { 384 | scope.dependencies.contains(&other) 385 | } else { 386 | let other_scope_id = other_info 387 | .scope_id 388 | .expect("other var should be existential"); 389 | let other_scope = &self.scopes[other_scope_id]; 390 | other_scope.dependencies.is_subset(&scope.dependencies) 391 | } 392 | } 393 | } 394 | 395 | impl Dimacs for DependencyPrefix { 396 | fn dimacs(&self) -> String { 397 | let mut dimacs = String::new(); 398 | let mut universals = FxHashSet::default(); 399 | for scope in &self.scopes { 400 | universals = universals.union(&scope.dependencies).cloned().collect(); 401 | } 402 | if !universals.is_empty() { 403 | dimacs.push_str("a "); 404 | for universal in &universals { 405 | dimacs.push_str(&format!("{} ", universal)) 406 | } 407 | dimacs.push_str("0\n"); 408 | } 409 | for scope in &self.scopes { 410 | dimacs.push_str(&scope.dimacs()); 411 | } 412 | dimacs 413 | } 414 | } 415 | 416 | #[cfg(test)] 417 | mod tests { 418 | 419 | use super::*; 420 | 421 | #[test] 422 | fn test_closure() { 423 | let mut prefix = DependencyPrefix::new(4); 424 | let v1 = Variable::from(1_u32); 425 | let v2 = Variable::from(2_u32); 426 | let v3 = Variable::from(3_u32); 427 | let v4 = Variable::from(4_u32); 428 | prefix.add_universal(v1); 429 | prefix.add_universal(v2); 430 | let mut dep = FxHashSet::default(); 431 | dep.insert(v1); 432 | prefix.add_existential(v3, &dep); 433 | dep.clear(); 434 | dep.insert(v2); 435 | prefix.add_existential(v4, &dep); 436 | dep.clear(); 437 | 438 | // empty set and complete set not contained before... 439 | assert!(prefix.scope_lookup(&dep).is_none()); 440 | dep.insert(v1); 441 | dep.insert(v2); 442 | assert!(prefix.scope_lookup(&dep).is_none()); 443 | dep.clear(); 444 | 445 | // ... but after building closure 446 | prefix.build_closure(); 447 | assert_eq!(prefix.scope_lookup(&dep), Some(2)); 448 | dep.insert(v1); 449 | dep.insert(v2); 450 | assert_eq!(prefix.scope_lookup(&dep), Some(3)); 451 | 452 | // check `is_maximal` 453 | assert!(!prefix.is_maximal(&prefix.scopes[0])); 454 | assert!(!prefix.is_maximal(&prefix.scopes[1])); 455 | assert!(!prefix.is_maximal(&prefix.scopes[2])); 456 | assert!(prefix.is_maximal(&prefix.scopes[3])); 457 | 458 | // check antichain decomposition 459 | // there are 3 antichains: 460 | // * singletons for empty set and complete set 461 | // * the set containing both incomparable sets 462 | let antichains = prefix.antichain_decomposition(); 463 | assert_eq!(antichains.len(), 3); 464 | assert_eq!(antichains[0], vec![2]); 465 | assert_eq!(antichains[2], vec![3]); 466 | assert!(antichains[1].contains(&0) && antichains[1].contains(&1)); 467 | } 468 | 469 | #[test] 470 | fn test_closure_recursive() { 471 | let mut prefix = DependencyPrefix::new(6); 472 | let v1 = Variable::from(1_u32); 473 | let v2 = Variable::from(2_u32); 474 | let v3 = Variable::from(3_u32); 475 | let v4 = Variable::from(4_u32); 476 | let v5 = Variable::from(5_u32); 477 | let v6 = Variable::from(6_u32); 478 | 479 | prefix.add_universal(v1); 480 | prefix.add_universal(v2); 481 | prefix.add_universal(v3); 482 | let mut dep = FxHashSet::default(); 483 | dep.insert(v1); 484 | dep.insert(v2); 485 | prefix.add_existential(v4, &dep); 486 | dep.clear(); 487 | dep.insert(v2); 488 | dep.insert(v3); 489 | prefix.add_existential(v5, &dep); 490 | dep.clear(); 491 | dep.insert(v1); 492 | dep.insert(v3); 493 | prefix.add_existential(v6, &dep); 494 | dep.clear(); 495 | 496 | // empty set not contained before... 497 | assert!(prefix.scope_lookup(&dep).is_none()); 498 | 499 | // ... but after building closure 500 | prefix.build_closure(); 501 | assert!(prefix.scope_lookup(&dep).is_some()); 502 | 503 | let antichains = prefix.antichain_decomposition(); 504 | assert_eq!(antichains.len(), 4); 505 | } 506 | 507 | #[test] 508 | fn test_dep_on() { 509 | let mut prefix = DependencyPrefix::new(6); 510 | let v1 = Variable::from(1_u32); 511 | let v2 = Variable::from(2_u32); 512 | let v3 = Variable::from(3_u32); 513 | let v4 = Variable::from(4_u32); 514 | let v5 = Variable::from(5_u32); 515 | let v6 = Variable::from(6_u32); 516 | 517 | prefix.add_universal(v1); 518 | prefix.add_universal(v2); 519 | let mut dep = FxHashSet::default(); 520 | prefix.add_existential(v3, &dep); // d 3 0 521 | dep.insert(v1); 522 | prefix.add_existential(v4, &dep); // d 4 1 0 523 | dep.clear(); 524 | dep.insert(v2); 525 | prefix.add_existential(v5, &dep); // d 5 2 0 526 | dep.insert(v1); 527 | prefix.add_existential(v6, &dep); // d 6 1 2 0 528 | 529 | // 3 530 | assert!(!prefix.depends_on(v3, v1)); 531 | assert!(!prefix.depends_on(v3, v2)); 532 | assert!(prefix.depends_on(v3, v3)); 533 | assert!(!prefix.depends_on(v3, v4)); 534 | assert!(!prefix.depends_on(v3, v5)); 535 | assert!(!prefix.depends_on(v3, v6)); 536 | 537 | // 4 538 | assert!(prefix.depends_on(v4, v1)); 539 | assert!(!prefix.depends_on(v4, v2)); 540 | assert!(prefix.depends_on(v4, v3)); 541 | assert!(prefix.depends_on(v4, v4)); 542 | assert!(!prefix.depends_on(v4, v5)); 543 | assert!(!prefix.depends_on(v4, v6)); 544 | 545 | // 5 546 | assert!(!prefix.depends_on(v5, v1)); 547 | assert!(prefix.depends_on(v5, v2)); 548 | assert!(prefix.depends_on(v5, v3)); 549 | assert!(!prefix.depends_on(v5, v4)); 550 | assert!(prefix.depends_on(v5, v5)); 551 | assert!(!prefix.depends_on(v5, v6)); 552 | 553 | // 6 554 | assert!(prefix.depends_on(v6, v1)); 555 | assert!(prefix.depends_on(v6, v2)); 556 | assert!(prefix.depends_on(v6, v3)); 557 | assert!(prefix.depends_on(v6, v4)); 558 | assert!(prefix.depends_on(v6, v5)); 559 | assert!(prefix.depends_on(v6, v6)); 560 | } 561 | 562 | #[test] 563 | fn test_scope_comparison() { 564 | let v1 = Variable::from(1_u32); 565 | let v2 = Variable::from(2_u32); 566 | let scope1 = Scope::new(&vec![v1].iter().cloned().collect()); 567 | let scope2 = Scope::new(&vec![v2].iter().cloned().collect()); 568 | let empty = Scope::new(&FxHashSet::default()); 569 | let full = Scope::new(&vec![v1, v2].iter().cloned().collect()); 570 | 571 | assert_eq!(scope1.partial_cmp(&scope1), Some(Ordering::Equal)); 572 | assert_eq!(scope1.partial_cmp(&scope2), None); 573 | assert_eq!(scope1.partial_cmp(&empty), Some(Ordering::Greater)); 574 | assert_eq!(scope1.partial_cmp(&full), Some(Ordering::Less)); 575 | 576 | assert_eq!(scope2.partial_cmp(&scope1), None); 577 | assert_eq!(scope2.partial_cmp(&scope2), Some(Ordering::Equal)); 578 | assert_eq!(scope2.partial_cmp(&empty), Some(Ordering::Greater)); 579 | assert_eq!(scope2.partial_cmp(&full), Some(Ordering::Less)); 580 | 581 | assert_eq!(empty.partial_cmp(&scope1), Some(Ordering::Less)); 582 | assert_eq!(empty.partial_cmp(&scope2), Some(Ordering::Less)); 583 | assert_eq!(empty.partial_cmp(&empty), Some(Ordering::Equal)); 584 | assert_eq!(empty.partial_cmp(&full), Some(Ordering::Less)); 585 | 586 | assert_eq!(full.partial_cmp(&scope1), Some(Ordering::Greater)); 587 | assert_eq!(full.partial_cmp(&scope2), Some(Ordering::Greater)); 588 | assert_eq!(full.partial_cmp(&empty), Some(Ordering::Greater)); 589 | assert_eq!(full.partial_cmp(&full), Some(Ordering::Equal)); 590 | } 591 | } 592 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(unsafe_code)] 2 | #![deny(unused_must_use)] 3 | #![warn(clippy::all, clippy::pedantic)] 4 | //#![warn(clippy::cargo)] 5 | 6 | #[cfg(feature = "jemalloc")] 7 | #[global_allocator] 8 | static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc; 9 | 10 | mod clause; 11 | pub mod dimacs; 12 | pub mod experiment; 13 | mod literal; 14 | mod matrix; 15 | pub mod parse; 16 | mod preprocessor; 17 | pub mod solve; 18 | mod utils; 19 | 20 | use crate::{dimacs::Dimacs, preprocessor::preprocess, preprocessor::QBFPreprocessor}; 21 | use clap::{App, Arg}; 22 | use log::LevelFilter; 23 | use std::{default::Default, error::Error, fs::File, io::Read, str::FromStr}; 24 | use uncover::define_uncover_macros; 25 | 26 | // re-exports 27 | pub use crate::{ 28 | literal::Literal, 29 | matrix::Matrix, 30 | solve::{ 31 | caqe::{CaqeSolver, ExpansionMode, SolverOptions}, 32 | Solver, SolverResult, 33 | }, 34 | }; 35 | 36 | #[cfg(dcaqe)] 37 | pub use crate::solve::DCaqeSolver; 38 | 39 | #[cfg(feature = "statistics")] 40 | use utils::statistics::{CountingStats, TimingStats}; 41 | 42 | // This defines two macros, `covers!` and `covered_by!`. 43 | // They will be no-ops unless `cfg!(debug_assertions)` is true. 44 | define_uncover_macros!(enable_if(cfg!(debug_assertions))); 45 | 46 | // Command line parsing 47 | 48 | pub type CaqeConfig = CommonSolverConfig; 49 | #[cfg(dcaqe)] 50 | pub type DCaqeConfig = CommonSolverConfig; 51 | 52 | pub trait SolverSpecificConfig { 53 | fn add_arguments<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b>; 54 | fn parse_arguments(matches: &clap::ArgMatches) -> Self; 55 | const NAME: &'static str; 56 | const DESC: &'static str; 57 | } 58 | 59 | #[derive(Debug)] 60 | pub struct CommonSolverConfig { 61 | /// None for stdin 62 | filename: Option, 63 | verbosity: LevelFilter, 64 | statistics: Option, 65 | specific: T, 66 | } 67 | 68 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 69 | enum Statistics { 70 | Overview, 71 | Detailed, 72 | } 73 | 74 | impl CommonSolverConfig { 75 | pub fn new(args: &[String]) -> Self { 76 | let mut flags = App::new(T::NAME) 77 | .version(env!("CARGO_PKG_VERSION")) 78 | .author(env!("CARGO_PKG_AUTHORS")) 79 | .about(T::DESC) 80 | .arg( 81 | Arg::with_name("INPUT") 82 | .help("Sets the input file to use") 83 | .required(false) 84 | .index(1), 85 | ); 86 | #[cfg(debug_assertions)] 87 | { 88 | flags = flags.arg( 89 | Arg::with_name("v") 90 | .short("v") 91 | .multiple(true) 92 | .help("Sets the level of verbosity"), 93 | ); 94 | } 95 | #[cfg(feature = "statistics")] 96 | { 97 | flags = flags.arg( 98 | Arg::with_name("statistics") 99 | .long("--statistics") 100 | .takes_value(true) 101 | .default_value("overview") 102 | .possible_values(&["overview", "detailed"]) 103 | .help("Enables collection and printing of solving statistics"), 104 | ) 105 | } 106 | flags = T::add_arguments(flags); 107 | 108 | let matches = flags.get_matches_from(args); 109 | 110 | let filename = matches 111 | .value_of("INPUT") 112 | .map(std::string::ToString::to_string); 113 | 114 | let verbosity = match matches.occurrences_of("v") { 115 | 0 => LevelFilter::Warn, 116 | 1 => LevelFilter::Info, 117 | 2 => LevelFilter::Debug, 118 | 3 | _ => LevelFilter::Trace, 119 | }; 120 | 121 | let statistics = if matches.is_present("statistics") { 122 | match matches.value_of("statistics").unwrap() { 123 | "detailed" => Some(Statistics::Detailed), 124 | "overview" => Some(Statistics::Overview), 125 | _ => unreachable!(), 126 | } 127 | } else { 128 | None 129 | }; 130 | 131 | Self { 132 | filename, 133 | verbosity, 134 | statistics, 135 | specific: T::parse_arguments(&matches), 136 | } 137 | } 138 | } 139 | 140 | #[derive(Debug)] 141 | pub struct CaqeSpecificSolverConfig { 142 | pub options: SolverOptions, 143 | pub qdimacs_output: bool, 144 | pub preprocessor: Option, 145 | } 146 | 147 | impl SolverSpecificConfig for CaqeSpecificSolverConfig { 148 | const NAME: &'static str = "CAQE"; 149 | const DESC: &'static str = 150 | "CAQE is a solver for quantified Boolean formulas (QBF) in QDIMACS format."; 151 | 152 | fn add_arguments<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { 153 | let default_options = SolverOptions::default(); 154 | 155 | let default = |val| if val { "1" } else { "0" }; 156 | app.arg( 157 | Arg::with_name("config") 158 | .long("--config") 159 | .value_name("name_of_config.json") 160 | .takes_value(true) 161 | .help("Name of the solver configuration file to use (overwrites command line options)"), 162 | ) 163 | .arg( 164 | Arg::with_name("preprocessor") 165 | .help("Sets the preprocessor to use") 166 | .long("--preprocessor") 167 | .takes_value(true) 168 | .requires("INPUT") 169 | .possible_values(QBFPreprocessor::values()), 170 | ) 171 | .arg( 172 | Arg::with_name("qdimacs-output") 173 | .long("--qdo") 174 | .help("Prints QDIMACS output (partial assignment) after solving"), 175 | ) 176 | .arg( 177 | Arg::with_name("miniscoping") 178 | .long("--miniscoping") 179 | .default_value(default(default_options.miniscoping)) 180 | .value_name("bool") 181 | .takes_value(true) 182 | .possible_values(&["0", "1"]) 183 | .hide_possible_values(true) 184 | .help("Controls whether miniscoping should be used to build tree-shaped quantifier prefix"), 185 | ) 186 | .arg( 187 | Arg::with_name("dependency-schemes") 188 | .long("--dependency-schemes") 189 | .default_value(default(default_options.expansion.dependency_schemes)) 190 | .value_name("bool") 191 | .takes_value(true) 192 | .possible_values(&["0", "1"]) 193 | .hide_possible_values(true) 194 | .help("Controls whether dependency scheme should be computed"), 195 | ) 196 | .arg( 197 | Arg::with_name("strong-unsat-refinement") 198 | .long("--strong-unsat-refinement") 199 | .default_value(default(default_options.strong_unsat_refinement)) 200 | .value_name("bool") 201 | .takes_value(true) 202 | .possible_values(&["0", "1"]) 203 | .hide_possible_values(true) 204 | .help("Controls whether strong unsat refinement should be used"), 205 | ) 206 | .arg( 207 | Arg::with_name("expansion-refinement") 208 | .long("--expansion-refinement") 209 | .default_value("full") 210 | .takes_value(true) 211 | .possible_values(&["0", "1", "none", "light", "full"]) // 0 and 1 for backwards compatibility 212 | .help("Controls whether expansion refinement should be used"), 213 | ) 214 | .arg( 215 | Arg::with_name("refinement-literal-subsumption") 216 | .long("--refinement-literal-subsumption") 217 | .default_value(default(default_options.refinement_literal_subsumption)) 218 | .value_name("bool") 219 | .takes_value(true) 220 | .possible_values(&["0", "1"]) 221 | .hide_possible_values(true) 222 | .help("Controls whether refinements are minimized according to subsumption rules"), 223 | ) 224 | .arg( 225 | Arg::with_name("build-conflict-clauses") 226 | .long("--build-conflict-clauses") 227 | .default_value(default(default_options.build_conflict_clauses)) 228 | .value_name("bool") 229 | .takes_value(true) 230 | .possible_values(&["0", "1"]) 231 | .hide_possible_values(true) 232 | .help("Controls whether conflict clauses should be build during solving"), 233 | ) 234 | .arg( 235 | Arg::with_name("conflict-clause-expansion") 236 | .long("--conflict-clause-expansion") 237 | .default_value(default(default_options.expansion.conflict_clause_expansion)) 238 | .value_name("bool") 239 | .takes_value(true) 240 | .possible_values(&["0", "1"]) 241 | .hide_possible_values(true) 242 | .help("Controls whether conflict clauses should be used in expansion refinement"), 243 | ) 244 | .arg( 245 | Arg::with_name("abstraction-equivalence") 246 | .long("--abstraction-equivalence") 247 | .default_value(default(default_options.abstraction.equivalence_constraints)) 248 | .value_name("bool") 249 | .takes_value(true) 250 | .possible_values(&["0", "1"]) 251 | .hide_possible_values(true) 252 | .help("Controls whether equivalence constraint should be used in abstraction"), 253 | ) 254 | } 255 | 256 | #[must_use] 257 | fn parse_arguments(matches: &clap::ArgMatches) -> Self { 258 | let mut options = SolverOptions::default(); 259 | let qdimacs_output = matches.is_present("qdimacs-output"); 260 | let preprocessor = match matches.value_of("preprocessor") { 261 | None => None, 262 | Some(ref s) => Some(QBFPreprocessor::from_str(s).unwrap()), 263 | }; 264 | 265 | options.miniscoping = matches.value_of("miniscoping").unwrap() == "1"; 266 | 267 | options.expansion.dependency_schemes = 268 | matches.value_of("dependency-schemes").unwrap() == "1"; 269 | 270 | options.strong_unsat_refinement = 271 | matches.value_of("strong-unsat-refinement").unwrap() == "1"; 272 | 273 | options.expansion.conflict_clause_expansion = 274 | matches.value_of("conflict-clause-expansion").unwrap() == "1"; 275 | options.expansion.expansion_refinement = 276 | match matches.value_of("expansion-refinement").unwrap() { 277 | "0" | "none" => None, 278 | "1" | "full" => Some(ExpansionMode::Full), 279 | "light" => Some(ExpansionMode::Light), 280 | _ => unreachable!(), 281 | }; 282 | 283 | options.refinement_literal_subsumption = 284 | matches.value_of("refinement-literal-subsumption").unwrap() == "1"; 285 | 286 | options.build_conflict_clauses = matches.value_of("build-conflict-clauses").unwrap() == "1"; 287 | 288 | options.abstraction.equivalence_constraints = 289 | matches.value_of("abstraction-equivalence").unwrap() == "1"; 290 | 291 | if let Some(config_file) = matches.value_of("config") { 292 | let mut file = File::open(&config_file).unwrap_or_else(|e| { 293 | panic!("Cannot open given config file `{}`: {}", config_file, e) 294 | }); 295 | let mut contents = String::new(); 296 | file.read_to_string(&mut contents).unwrap_or_else(|e| { 297 | panic!( 298 | "Cannot read from given config file `{}`: {}", 299 | config_file, e 300 | ) 301 | }); 302 | 303 | options = serde_json::from_str(&contents).unwrap_or_else(|e| { 304 | panic!("Cannot parse given config file `{}`: {}", config_file, e) 305 | }); 306 | } 307 | 308 | Self { 309 | options, 310 | qdimacs_output, 311 | preprocessor, 312 | } 313 | } 314 | } 315 | 316 | #[cfg(feature = "statistics")] 317 | #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] 318 | enum SolverPhases { 319 | Preprocessing, 320 | Parsing, 321 | Miniscoping, 322 | ComputeDepScheme, 323 | Initializing, 324 | Solving, 325 | } 326 | 327 | #[cfg(feature = "statistics")] 328 | impl std::fmt::Display for SolverPhases { 329 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 330 | match self { 331 | Self::Preprocessing => write!(f, "Preprocessing"), 332 | Self::Parsing => write!(f, "Parsing"), 333 | Self::Initializing => write!(f, "Initialization"), 334 | Self::Solving => write!(f, "Solving"), 335 | Self::Miniscoping => write!(f, "Miniscoping"), 336 | Self::ComputeDepScheme => write!(f, "Compute Dep Scheme"), 337 | } 338 | } 339 | } 340 | 341 | #[cfg(feature = "statistics")] 342 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] 343 | enum MatrixStats { 344 | RemovedDependencies, 345 | } 346 | 347 | #[cfg(feature = "statistics")] 348 | impl std::fmt::Display for MatrixStats { 349 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 350 | match self { 351 | Self::RemovedDependencies => write!(f, "Dependencies removed: "), 352 | } 353 | } 354 | } 355 | 356 | impl CaqeConfig { 357 | pub fn run(&self) -> Result> { 358 | #[cfg(debug_assertions)] 359 | env_logger::init(); 360 | 361 | #[cfg(feature = "statistics")] 362 | let statistics = TimingStats::new(); 363 | 364 | #[cfg(feature = "statistics")] 365 | let mut counter = CountingStats::new(); 366 | 367 | #[cfg(feature = "statistics")] 368 | let mut timer = statistics.start(SolverPhases::Preprocessing); 369 | 370 | let (mut matrix, partial_qdo) = preprocess(&self)?; 371 | 372 | #[cfg(feature = "statistics")] 373 | timer.stop(); 374 | 375 | //println!("{}", matrix.dimacs()); 376 | 377 | if matrix.conflict() { 378 | if self.specific.qdimacs_output { 379 | if let Some(partial_qdo) = partial_qdo { 380 | if partial_qdo.result == SolverResult::Unsatisfiable { 381 | println!("{}", partial_qdo.dimacs()); 382 | } else { 383 | println!( 384 | "{}", 385 | parse::qdimacs::PartialQDIMACSCertificate::new( 386 | SolverResult::Unsatisfiable, 387 | partial_qdo.num_variables, 388 | partial_qdo.num_clauses 389 | ) 390 | .dimacs() 391 | ); 392 | } 393 | } 394 | } 395 | return Ok(SolverResult::Unsatisfiable); 396 | } 397 | 398 | if self.specific.options.miniscoping { 399 | #[cfg(feature = "statistics")] 400 | let mut timer = statistics.start(SolverPhases::Miniscoping); 401 | 402 | matrix.unprenex_by_miniscoping(); 403 | 404 | #[cfg(feature = "statistics")] 405 | timer.stop(); 406 | } 407 | 408 | if self.specific.options.expansion.dependency_schemes { 409 | #[cfg(feature = "statistics")] 410 | let mut timer = statistics.start(SolverPhases::ComputeDepScheme); 411 | 412 | let _removed = matrix.refl_res_path_dep_scheme(); 413 | 414 | #[allow(clippy::used_underscore_binding)] 415 | #[cfg(feature = "statistics")] 416 | counter.inc_by(MatrixStats::RemovedDependencies, _removed); 417 | 418 | #[cfg(feature = "statistics")] 419 | timer.stop(); 420 | } 421 | 422 | #[cfg(feature = "statistics")] 423 | let mut timer = statistics.start(SolverPhases::Initializing); 424 | 425 | let mut solver = CaqeSolver::new_with_options(&mut matrix, self.specific.options); 426 | 427 | #[cfg(feature = "statistics")] 428 | timer.stop(); 429 | 430 | #[cfg(feature = "statistics")] 431 | let mut timer = statistics.start(SolverPhases::Solving); 432 | 433 | let result = solver.solve(); 434 | 435 | #[cfg(feature = "statistics")] 436 | timer.stop(); 437 | 438 | #[cfg(feature = "statistics")] 439 | { 440 | if let Some(stats) = &self.statistics { 441 | println!( 442 | "Preprocessing took {:?}", 443 | statistics.sum(SolverPhases::Preprocessing) 444 | ); 445 | println!( 446 | "Miniscoping took {:?}", 447 | statistics.sum(SolverPhases::Miniscoping) 448 | ); 449 | println!( 450 | "Compute Dependency Scheme took {:?}", 451 | statistics.sum(SolverPhases::ComputeDepScheme) 452 | ); 453 | println!( 454 | "Initializing took {:?}", 455 | statistics.sum(SolverPhases::Initializing) 456 | ); 457 | println!("Solving took {:?}", statistics.sum(SolverPhases::Solving)); 458 | print!("{}", counter); 459 | solver.print_statistics(*stats); 460 | } 461 | } 462 | 463 | if self.specific.qdimacs_output { 464 | let mut solver_qdo = solver.qdimacs_output(); 465 | if let Some(partial_qdo) = partial_qdo { 466 | solver_qdo.num_clauses = partial_qdo.num_clauses; 467 | solver_qdo.num_variables = solver_qdo.num_variables; 468 | if partial_qdo.result == solver_qdo.result { 469 | solver_qdo.extend_assignments(partial_qdo); 470 | } 471 | } 472 | println!("{}", solver_qdo.dimacs()); 473 | } 474 | 475 | Ok(result) 476 | } 477 | } 478 | 479 | #[derive(Debug, Clone)] 480 | pub struct DCaqeSpecificSolverConfig { 481 | expansion_refinement: bool, 482 | dependency_schemes: bool, 483 | } 484 | 485 | impl Default for DCaqeSpecificSolverConfig { 486 | #[must_use] 487 | fn default() -> Self { 488 | Self { 489 | expansion_refinement: true, 490 | dependency_schemes: false, 491 | } 492 | } 493 | } 494 | 495 | impl SolverSpecificConfig for DCaqeSpecificSolverConfig { 496 | const NAME: &'static str = "dCAQE"; 497 | const DESC: &'static str = 498 | "dCAQE is a solver for dependency quantified Boolean formulas (DQBF) in DQDIMACS format."; 499 | 500 | fn add_arguments<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { 501 | let default_options = Self::default(); 502 | 503 | let default = |val| if val { "1" } else { "0" }; 504 | app.arg( 505 | Arg::with_name("expansion-refinement") 506 | .long("--expansion-refinement") 507 | .default_value(default(default_options.expansion_refinement)) 508 | .value_name("bool") 509 | .takes_value(true) 510 | .possible_values(&["0", "1"]) 511 | .hide_possible_values(true) 512 | .help("Controls whether expansion refinement should be used"), 513 | ) 514 | .arg( 515 | Arg::with_name("dependency-schemes") 516 | .long("--dependency-schemes") 517 | .default_value(default(default_options.dependency_schemes)) 518 | .value_name("bool") 519 | .takes_value(true) 520 | .possible_values(&["0", "1"]) 521 | .hide_possible_values(true) 522 | .help("Controls whether dependency scheme should be computed"), 523 | ) 524 | } 525 | 526 | #[must_use] 527 | fn parse_arguments(matches: &clap::ArgMatches) -> Self { 528 | let expansion_refinement = matches.value_of("expansion-refinement").unwrap() == "1"; 529 | let dependency_schemes = matches.value_of("dependency-schemes").unwrap() == "1"; 530 | 531 | Self { 532 | expansion_refinement, 533 | dependency_schemes, 534 | } 535 | } 536 | } 537 | 538 | #[cfg(dcaqe)] 539 | impl DCaqeConfig { 540 | pub fn run(&self) -> Result> { 541 | #[cfg(debug_assertions)] 542 | CombinedLogger::init(vec![ 543 | TermLogger::new( 544 | self.verbosity, 545 | simplelog::Config::default(), 546 | simplelog::TerminalMode::Mixed, 547 | ) 548 | .expect("Could not initialize TermLogger"), 549 | //WriteLogger::new(LevelFilter::Info, Config::default(), File::create("my_rust_binary.log").unwrap()), 550 | ]) 551 | .expect("Could not initialize logging"); 552 | 553 | #[cfg(feature = "statistics")] 554 | let statistics = TimingStats::new(); 555 | 556 | #[cfg(feature = "statistics")] 557 | let mut counter = CountingStats::new(); 558 | 559 | #[cfg(feature = "statistics")] 560 | let mut timer = statistics.start(SolverPhases::Parsing); 561 | 562 | let mut contents = String::new(); 563 | match &self.filename { 564 | None => { 565 | //reading from stdin 566 | std::io::stdin().read_to_string(&mut contents)?; 567 | } 568 | Some(filename) => { 569 | let mut f = File::open(&filename)?; 570 | f.read_to_string(&mut contents)?; 571 | } 572 | } 573 | 574 | let mut matrix = parse::dqdimacs::parse(&contents)?; 575 | 576 | #[cfg(feature = "statistics")] 577 | timer.stop(); 578 | 579 | //println!("{}", matrix.dimacs()); 580 | 581 | if matrix.conflict() { 582 | return Ok(SolverResult::Unsatisfiable); 583 | } 584 | 585 | if self.specific.dependency_schemes { 586 | #[cfg(feature = "statistics")] 587 | let mut timer = statistics.start(SolverPhases::ComputeDepScheme); 588 | 589 | let _removed = matrix.refl_res_path_dep_scheme(); 590 | 591 | #[allow(clippy::used_underscore_binding)] 592 | #[cfg(feature = "statistics")] 593 | counter.inc_by(MatrixStats::RemovedDependencies, _removed); 594 | 595 | #[cfg(feature = "statistics")] 596 | timer.stop(); 597 | } 598 | 599 | #[cfg(feature = "statistics")] 600 | let mut timer = statistics.start(SolverPhases::Initializing); 601 | 602 | let mut solver = DCaqeSolver::new(&mut matrix, &self.specific); 603 | 604 | #[cfg(feature = "statistics")] 605 | timer.stop(); 606 | 607 | #[cfg(feature = "statistics")] 608 | let mut timer = statistics.start(SolverPhases::Solving); 609 | 610 | let result = solver.solve(); 611 | 612 | #[cfg(feature = "statistics")] 613 | timer.stop(); 614 | 615 | #[cfg(feature = "statistics")] 616 | { 617 | if let Some(stats) = &self.statistics { 618 | println!("Parsing took {:?}", statistics.sum(SolverPhases::Parsing)); 619 | println!( 620 | "Compute Dependency Scheme took {:?}", 621 | statistics.sum(SolverPhases::ComputeDepScheme) 622 | ); 623 | println!( 624 | "Initializing took {:?}", 625 | statistics.sum(SolverPhases::Initializing) 626 | ); 627 | println!("Solving took {:?}", statistics.sum(SolverPhases::Solving)); 628 | println!("{}", counter); 629 | if stats == &Statistics::Detailed { 630 | solver.print_statistics(); 631 | } 632 | } 633 | } 634 | 635 | Ok(result) 636 | } 637 | } 638 | --------------------------------------------------------------------------------