summaryrefslogtreecommitdiffstats
path: root/vendor/varisat/src/variables.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/varisat/src/variables.rs')
-rw-r--r--vendor/varisat/src/variables.rs669
1 files changed, 669 insertions, 0 deletions
diff --git a/vendor/varisat/src/variables.rs b/vendor/varisat/src/variables.rs
new file mode 100644
index 000000000..ec997430e
--- /dev/null
+++ b/vendor/varisat/src/variables.rs
@@ -0,0 +1,669 @@
+//! Variable mapping and metadata.
+
+use rustc_hash::FxHashSet as HashSet;
+
+use partial_ref::{partial, PartialRef};
+
+use varisat_formula::{Lit, Var};
+use varisat_internal_proof::ProofStep;
+
+use crate::{
+ context::{parts::*, set_var_count, Context},
+ decision, proof,
+};
+
+pub mod data;
+pub mod var_map;
+
+use data::{SamplingMode, VarData};
+use var_map::{VarBiMap, VarBiMapMut, VarMap};
+
+/// Variable mapping and metadata.
+pub struct Variables {
+ /// Bidirectional mapping from user variables to global variables.
+ ///
+ /// Initially this is the identity mapping. This ensures that in the non-assumptions setting the
+ /// map from used user variables to global variables is the identity. This is a requirement for
+ /// generating proofs in non-native formats. Those proofs are not aware of variable renaming,
+ /// but are restricted to the non-incremental setting, so this works out.
+ ///
+ /// This is also requried for native proofs, as they assume that the mapping during the initial
+ /// load is the identity.
+ global_from_user: VarBiMap,
+ /// Bidirectional mapping from global variables to user variables.
+ ///
+ /// This starts with the empty mapping, so only used variables are allocated.
+ solver_from_global: VarBiMap,
+ /// User variables that were explicitly hidden by the user.
+ user_freelist: HashSet<Var>,
+ /// Global variables that can be recycled without increasing the global_watermark.
+ global_freelist: HashSet<Var>,
+ /// Solver variables that are unused and can be recycled.
+ solver_freelist: HashSet<Var>,
+
+ /// Variable metadata.
+ ///
+ /// Indexed by global variable indices.
+ var_data: Vec<VarData>,
+}
+
+impl Default for Variables {
+ fn default() -> Variables {
+ Variables {
+ global_from_user: VarBiMap::default(),
+ solver_from_global: VarBiMap::default(),
+ user_freelist: Default::default(),
+ global_freelist: Default::default(),
+ solver_freelist: Default::default(),
+
+ var_data: vec![],
+ }
+ }
+}
+
+impl Variables {
+ /// Number of allocated solver variables.
+ pub fn solver_watermark(&self) -> usize {
+ self.global_from_solver().watermark()
+ }
+
+ /// Number of allocated global variables.
+ pub fn global_watermark(&self) -> usize {
+ self.var_data.len()
+ }
+
+ /// Number of allocated global variables.
+ pub fn user_watermark(&self) -> usize {
+ self.global_from_user().watermark()
+ }
+
+ /// Iterator over all user variables that are in use.
+ pub fn user_var_iter<'a>(&'a self) -> impl Iterator<Item = Var> + 'a {
+ let global_from_user = self.global_from_user.fwd();
+ (0..self.global_from_user().watermark())
+ .map(Var::from_index)
+ .filter(move |&user_var| global_from_user.get(user_var).is_some())
+ }
+
+ /// Iterator over all global variables that are in use.
+ pub fn global_var_iter<'a>(&'a self) -> impl Iterator<Item = Var> + 'a {
+ (0..self.global_watermark())
+ .map(Var::from_index)
+ .filter(move |&global_var| !self.var_data[global_var.index()].deleted)
+ }
+
+ /// The user to global mapping.
+ pub fn global_from_user(&self) -> &VarMap {
+ &self.global_from_user.fwd()
+ }
+
+ /// Mutable user to global mapping.
+ pub fn global_from_user_mut(&mut self) -> VarBiMapMut {
+ self.global_from_user.fwd_mut()
+ }
+
+ /// The global to solver mapping.
+ pub fn solver_from_global(&self) -> &VarMap {
+ &self.solver_from_global.fwd()
+ }
+
+ /// Mutable global to solver mapping.
+ pub fn solver_from_global_mut(&mut self) -> VarBiMapMut {
+ self.solver_from_global.fwd_mut()
+ }
+
+ /// The global to user mapping.
+ pub fn user_from_global(&self) -> &VarMap {
+ &self.global_from_user.bwd()
+ }
+
+ /// Mutable global to user mapping.
+ pub fn user_from_global_mut(&mut self) -> VarBiMapMut {
+ self.global_from_user.bwd_mut()
+ }
+
+ /// The solver to global mapping.
+ pub fn global_from_solver(&self) -> &VarMap {
+ &self.solver_from_global.bwd()
+ }
+
+ /// Mutable solver to global mapping.
+ pub fn global_from_solver_mut(&mut self) -> VarBiMapMut {
+ self.solver_from_global.bwd_mut()
+ }
+
+ /// Get an existing solver var for a user var.
+ pub fn existing_solver_from_user(&self, user: Var) -> Var {
+ let global = self
+ .global_from_user()
+ .get(user)
+ .expect("no existing global var for user var");
+ let solver = self
+ .solver_from_global()
+ .get(global)
+ .expect("no existing solver var for global var");
+ solver
+ }
+
+ /// Get an existing user var from a solver var.
+ pub fn existing_user_from_solver(&self, solver: Var) -> Var {
+ let global = self
+ .global_from_solver()
+ .get(solver)
+ .expect("no existing global var for solver var");
+ let user = self
+ .user_from_global()
+ .get(global)
+ .expect("no existing user var for global var");
+ user
+ }
+
+ /// Mutable reference to the var data for a global variable.
+ pub fn var_data_global_mut(&mut self, global: Var) -> &mut VarData {
+ if self.var_data.len() <= global.index() {
+ self.var_data.resize(global.index() + 1, VarData::default());
+ }
+ &mut self.var_data[global.index()]
+ }
+
+ /// Mutable reference to the var data for a solver variable.
+ pub fn var_data_solver_mut(&mut self, solver: Var) -> &mut VarData {
+ let global = self
+ .global_from_solver()
+ .get(solver)
+ .expect("no existing global var for solver var");
+ &mut self.var_data[global.index()]
+ }
+
+ /// Var data for a global variable.
+ pub fn var_data_global(&self, global: Var) -> &VarData {
+ &self.var_data[global.index()]
+ }
+
+ /// Check if a solver var is mapped to a global var
+ pub fn solver_var_present(&self, solver: Var) -> bool {
+ self.global_from_solver().get(solver).is_some()
+ }
+
+ /// Get an unmapped global variable.
+ pub fn next_unmapped_global(&self) -> Var {
+ self.global_freelist
+ .iter()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| Var::from_index(self.global_watermark()))
+ }
+
+ /// Get an unmapped global variable.
+ pub fn next_unmapped_solver(&self) -> Var {
+ self.solver_freelist
+ .iter()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| Var::from_index(self.solver_watermark()))
+ }
+
+ /// Get an unmapped user variable.
+ pub fn next_unmapped_user(&self) -> Var {
+ self.user_freelist
+ .iter()
+ .next()
+ .cloned()
+ .unwrap_or_else(|| Var::from_index(self.user_watermark()))
+ }
+}
+
+/// Maps a user variable into a global variable.
+///
+/// If no matching global variable exists a new global variable is allocated.
+pub fn global_from_user<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+ user: Var,
+ require_sampling: bool,
+) -> Var {
+ let variables = ctx.part_mut(VariablesP);
+
+ if user.index() > variables.user_watermark() {
+ // TODO use a batch proof step for this?
+ for index in variables.user_watermark()..user.index() {
+ global_from_user(ctx.borrow(), Var::from_index(index), false);
+ }
+ }
+
+ let variables = ctx.part_mut(VariablesP);
+
+ match variables.global_from_user().get(user) {
+ Some(global) => {
+ if require_sampling
+ && variables.var_data[global.index()].sampling_mode != SamplingMode::Sample
+ {
+ panic!("witness variables cannot be constrained");
+ }
+ global
+ }
+ None => {
+ // Can we add an identity mapping?
+ let global = match variables.var_data.get(user.index()) {
+ Some(var_data) if var_data.deleted => user,
+ None => user,
+ _ => variables.next_unmapped_global(),
+ };
+
+ *variables.var_data_global_mut(global) = VarData::user_default();
+
+ variables.global_from_user_mut().insert(global, user);
+ variables.global_freelist.remove(&global);
+ variables.user_freelist.remove(&user);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::UserVarName {
+ global,
+ user: Some(user),
+ },
+ );
+
+ global
+ }
+ }
+}
+
+/// Maps an existing global variable to a solver variable.
+///
+/// If no matching solver variable exists a new one is allocated.
+pub fn solver_from_global<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ global: Var,
+) -> Var {
+ let variables = ctx.part_mut(VariablesP);
+
+ debug_assert!(!variables.var_data[global.index()].deleted);
+
+ match variables.solver_from_global().get(global) {
+ Some(solver) => solver,
+ None => {
+ let solver = variables.next_unmapped_solver();
+
+ let old_watermark = variables.global_from_solver().watermark();
+
+ variables.solver_from_global_mut().insert(solver, global);
+ variables.solver_freelist.remove(&solver);
+
+ let new_watermark = variables.global_from_solver().watermark();
+
+ if new_watermark > old_watermark {
+ set_var_count(ctx.borrow(), new_watermark);
+ }
+
+ initialize_solver_var(ctx.borrow(), solver, global);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::SolverVarName {
+ global,
+ solver: Some(solver),
+ },
+ );
+
+ solver
+ }
+ }
+}
+
+/// Maps a user variable to a solver variable.
+///
+/// Allocates global and solver variables as requried.
+pub fn solver_from_user<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ user: Var,
+ require_sampling: bool,
+) -> Var {
+ let global = global_from_user(ctx.borrow(), user, require_sampling);
+ solver_from_global(ctx.borrow(), global)
+}
+
+/// Allocates a currently unused user variable.
+///
+/// This is either a user variable above any user variable used so far, or a user variable that was
+/// previously hidden by the user.
+pub fn new_user_var<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+) -> Var {
+ let variables = ctx.part_mut(VariablesP);
+ let user_var = variables.next_unmapped_user();
+ global_from_user(ctx.borrow(), user_var, false);
+ user_var
+}
+
+/// Maps a slice of user lits to solver lits using [`solver_from_user`].
+pub fn solver_from_user_lits<'a>(
+ mut ctx: partial!(
+ Context<'a>,
+ mut AnalyzeConflictP,
+ mut AssignmentP,
+ mut BinaryClausesP,
+ mut ImplGraphP,
+ mut ProofP<'a>,
+ mut SolverStateP,
+ mut TmpFlagsP,
+ mut VariablesP,
+ mut VsidsP,
+ mut WatchlistsP,
+ ),
+ solver_lits: &mut Vec<Lit>,
+ user_lits: &[Lit],
+ require_sampling: bool,
+) {
+ solver_lits.clear();
+ solver_lits.extend(user_lits.iter().map(|user_lit| {
+ user_lit.map_var(|user_var| solver_from_user(ctx.borrow(), user_var, require_sampling))
+ }))
+}
+
+/// Changes the sampling mode of a global variable.
+///
+/// If the mode is changed to hidden, an existing user mapping is automatically removed.
+///
+/// If the mode is changed from hidden, a new user mapping is allocated and the user variable is
+/// returned.
+pub fn set_sampling_mode<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+ global: Var,
+ mode: SamplingMode,
+) -> Option<Var> {
+ let variables = ctx.part_mut(VariablesP);
+
+ let var_data = &mut variables.var_data[global.index()];
+
+ assert!(!var_data.deleted);
+
+ if var_data.assumed {
+ panic!("cannot change sampling mode of assumption variable")
+ }
+
+ let previous_mode = var_data.sampling_mode;
+
+ if previous_mode == mode {
+ return None;
+ }
+
+ var_data.sampling_mode = mode;
+
+ let mut result = None;
+
+ if mode != SamplingMode::Hide {
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::ChangeSamplingMode {
+ var: global,
+ sample: mode == SamplingMode::Sample,
+ },
+ );
+ }
+ let variables = ctx.part_mut(VariablesP);
+
+ if previous_mode == SamplingMode::Hide {
+ let user = variables.next_unmapped_user();
+ variables.user_from_global_mut().insert(user, global);
+ variables.user_freelist.remove(&user);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::UserVarName {
+ global,
+ user: Some(user),
+ },
+ );
+
+ result = Some(user);
+ } else if mode == SamplingMode::Hide {
+ if let Some(user) = variables.user_from_global_mut().remove(global) {
+ variables.user_freelist.insert(user);
+ }
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::UserVarName { global, user: None },
+ );
+
+ delete_global_if_unused(ctx.borrow(), global);
+ }
+
+ result
+}
+
+/// Turns all hidden vars into witness vars and returns them.
+pub fn observe_internal_vars<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+) -> Vec<Var> {
+ let mut result = vec![];
+ let mut variables = ctx.part_mut(VariablesP);
+ for global_index in 0..variables.global_watermark() {
+ let global = Var::from_index(global_index);
+ let var_data = &variables.var_data[global.index()];
+ if !var_data.deleted && var_data.sampling_mode == SamplingMode::Hide {
+ let user = set_sampling_mode(ctx.borrow(), global, SamplingMode::Witness).unwrap();
+ result.push(user);
+ variables = ctx.part_mut(VariablesP);
+ }
+ }
+ result
+}
+
+/// Initialize a newly allocated solver variable
+pub fn initialize_solver_var(
+ mut ctx: partial!(
+ Context,
+ mut AssignmentP,
+ mut ImplGraphP,
+ mut VsidsP,
+ VariablesP
+ ),
+ solver: Var,
+ global: Var,
+) {
+ let (variables, mut ctx) = ctx.split_part(VariablesP);
+ let data = &variables.var_data[global.index()];
+
+ // This recovers the state of a variable that has a known value and was already propagated. This
+ // is important so that when new clauses containing this variable are added, load_clause knows
+ // to reenqueue the assignment.
+ ctx.part_mut(AssignmentP).set_var(solver, data.unit);
+ if data.unit.is_some() {
+ ctx.part_mut(ImplGraphP).update_removed_unit(solver);
+ }
+ decision::initialize_var(ctx.borrow(), solver, data.unit.is_none());
+
+ // TODO unhiding beyond unit clauses
+}
+
+/// Remove a solver var.
+///
+/// If the variable is isolated and hidden, the global variable is also removed.
+pub fn remove_solver_var<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP, mut VsidsP),
+ solver: Var,
+) {
+ decision::remove_var(ctx.borrow(), solver);
+
+ let variables = ctx.part_mut(VariablesP);
+
+ let global = variables
+ .global_from_solver_mut()
+ .remove(solver)
+ .expect("no existing global var for solver var");
+
+ variables.solver_freelist.insert(solver);
+
+ proof::add_step(
+ ctx.borrow(),
+ false,
+ &ProofStep::SolverVarName {
+ global,
+ solver: None,
+ },
+ );
+
+ delete_global_if_unused(ctx.borrow(), global);
+}
+
+/// Delete a global variable if it is unused
+fn delete_global_if_unused<'a>(
+ mut ctx: partial!(Context<'a>, mut ProofP<'a>, mut SolverStateP, mut VariablesP),
+ global: Var,
+) {
+ let variables = ctx.part_mut(VariablesP);
+
+ if variables.user_from_global().get(global).is_some() {
+ return;
+ }
+
+ if variables.solver_from_global().get(global).is_some() {
+ return;
+ }
+
+ let data = &mut variables.var_data[global.index()];
+
+ assert!(data.sampling_mode == SamplingMode::Hide);
+
+ if !data.isolated {
+ return;
+ }
+
+ data.deleted = true;
+
+ proof::add_step(ctx.borrow(), false, &ProofStep::DeleteVar { var: global });
+
+ // TODO deletion of unit clauses isn't supported by most DRAT checkers, needs an extra
+ // variable mapping instead.
+
+ ctx.part_mut(VariablesP).global_freelist.insert(global);
+}
+
+#[cfg(test)]
+mod tests {
+ use proptest::{collection, prelude::*};
+
+ use varisat_formula::{
+ test::{sat_formula, sgen_unsat_formula},
+ ExtendFormula, Var,
+ };
+
+ use crate::solver::Solver;
+
+ #[test]
+ #[should_panic(expected = "cannot change sampling mode of assumption variable")]
+ fn cannot_hide_assumed_vars() {
+ let mut solver = Solver::new();
+
+ let (x, y, z) = solver.new_lits();
+
+ solver.assume(&[x, y, z]);
+ solver.hide_var(x.var());
+ }
+
+ #[test]
+ #[should_panic(expected = "cannot change sampling mode of assumption variable")]
+ fn cannot_witness_assumed_vars() {
+ let mut solver = Solver::new();
+
+ let (x, y, z) = solver.new_lits();
+
+ solver.assume(&[x, y, z]);
+ solver.witness_var(x.var());
+ }
+
+ proptest! {
+ #[test]
+ fn sgen_unsat_hidden_with_sat(
+ unsat_formula in sgen_unsat_formula(1..7usize),
+ sat_formula in sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0),
+ ) {
+ use std::cmp::max;
+
+ let mut solver = Solver::new();
+
+ solver.enable_self_checking();
+
+ let cond = Var::from_index(max(unsat_formula.var_count(), sat_formula.var_count()));
+
+ let mut tmp = vec![];
+
+ for clause in unsat_formula.iter() {
+ tmp.clear();
+ tmp.extend_from_slice(&clause);
+ tmp.push(cond.negative());
+ solver.add_clause(&tmp);
+ }
+
+ for i in 0..unsat_formula.var_count() {
+ solver.hide_var(Var::from_index(i));
+ }
+
+ solver.add_formula(&sat_formula);
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+
+ solver.add_clause(&[cond.positive()]);
+
+ prop_assert_eq!(solver.solve().ok(), Some(false));
+ }
+
+ #[test]
+ fn sgen_sat_many_hidden_observe_internal(
+ sat_formulas in collection::vec(
+ sat_formula(4..20usize, 10..100usize, 0.05..0.2, 0.9..1.0),
+ 1..10,
+ )
+ ) {
+ let mut solver = Solver::new();
+
+ solver.enable_self_checking();
+
+ for formula in sat_formulas {
+ solver.add_formula(&formula);
+
+ let new_vars = solver.observe_internal_vars();
+
+ for i in 0..formula.var_count() {
+ solver.hide_var(Var::from_index(i));
+ }
+
+ for var in new_vars {
+ solver.hide_var(var);
+ }
+ }
+
+ prop_assert_eq!(solver.solve().ok(), Some(true));
+ }
+ }
+}