//! 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, /// Global variables that can be recycled without increasing the global_watermark. global_freelist: HashSet, /// Solver variables that are unused and can be recycled. solver_freelist: HashSet, /// Variable metadata. /// /// Indexed by global variable indices. var_data: Vec, } 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 + '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 + '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, 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 { 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 { 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)); } } }