blob: ec997430e31b86f8a9c22418c5c30c6b4fd32db1 [file] [log] [blame] [edit]
//! 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));
}
}
}