varisat-checker 0.2.2

Proof checker for proofs generate by the Varisat SAT solver
Documentation
//! Variable metadata.
use partial_ref::{partial, PartialRef};
use rustc_hash::FxHashSet as HashSet;

use varisat_formula::Var;

use crate::{
    context::{parts::*, Context},
    processing::{process_step, CheckedProofStep, CheckedSamplingMode, CheckedUserVar},
    CheckerError,
};

/// Data for each literal.
#[derive(Clone, Default)]
pub struct LitData {
    pub clause_count: usize,
}

#[derive(Copy, Clone, PartialEq, Eq)]
pub enum SamplingMode {
    Sample,
    Witness,
    Hide,
}

/// Data for each variable.
#[derive(Clone)]
pub struct VarData {
    pub user_var: Option<Var>,
    pub sampling_mode: SamplingMode,
}

impl Default for VarData {
    fn default() -> VarData {
        VarData {
            user_var: None,
            sampling_mode: SamplingMode::Sample,
        }
    }
}

#[derive(Default)]
pub struct Variables {
    /// Information about literals in the current formula.
    pub lit_data: Vec<LitData>,
    /// Information about variables in the current formula.
    pub var_data: Vec<VarData>,
    /// User var names in use.
    ///
    /// This is used to check for colliding mappings which are not allowed.
    used_user_vars: HashSet<Var>,
}

/// Check that var is a sampling user var and create new variables as necessary.
pub fn ensure_sampling_var(
    mut ctx: partial!(Context, mut ClausesP, mut VariablesP, CheckerStateP),
    var: Var,
) -> Result<(), CheckerError> {
    ensure_var(ctx.borrow(), var);

    let variables = ctx.part_mut(VariablesP);

    if variables.var_data[var.index()].sampling_mode != SamplingMode::Sample {
        return Err(CheckerError::check_failed(
            ctx.part(CheckerStateP).step,
            format!("variable {:?} is not a sampling variable", var),
        ));
    }
    Ok(())
}

/// Ensure that a variable is present.
pub fn ensure_var(mut ctx: partial!(Context, mut ClausesP, mut VariablesP), var: Var) {
    let (variables, mut ctx) = ctx.split_part_mut(VariablesP);

    if variables.var_data.len() <= var.index() {
        variables
            .var_data
            .resize(var.index() + 1, VarData::default());
        variables
            .lit_data
            .resize((var.index() + 1) * 2, LitData::default());
        ctx.part_mut(ClausesP)
            .unit_clauses
            .resize(var.index() + 1, None);
    }
}

/// Add a user/global var mapping.
pub fn add_user_mapping<'a>(
    mut ctx: partial!(Context<'a>, mut ClausesP, mut ProcessingP<'a>, mut VariablesP, CheckerStateP),
    global_var: Var,
    user_var: Var,
) -> Result<(), CheckerError> {
    ensure_var(ctx.borrow(), global_var);

    let mut ctx_in = ctx;
    let (variables, ctx) = ctx_in.split_part_mut(VariablesP);

    // TODO will the first check cause problems when observing solver added variables?
    // That check is required for the workaround in CheckerData's user from proof method
    if user_var.index() >= variables.var_data.len() || variables.used_user_vars.contains(&user_var)
    {
        return Err(CheckerError::check_failed(
            ctx.part(CheckerStateP).step,
            format!("user name {:?} used for two different variables", user_var),
        ));
    }

    let var_data = &mut variables.var_data[global_var.index()];

    // sampling_mode will be Witness for a newly observed internal variable and Sample for a a
    // fresh variable
    if var_data.sampling_mode == SamplingMode::Hide {
        return Err(CheckerError::check_failed(
            ctx.part(CheckerStateP).step,
            format!(
                "user name added to variable {:?} which is still hidden",
                global_var
            ),
        ));
    }

    if var_data.user_var.is_some() {
        return Err(CheckerError::check_failed(
            ctx.part(CheckerStateP).step,
            format!("change of user name for in use varible {:?}", global_var),
        ));
    }

    var_data.user_var = Some(user_var);

    variables.used_user_vars.insert(user_var);

    let sampling_mode = if var_data.sampling_mode == SamplingMode::Witness {
        CheckedSamplingMode::Witness
    } else {
        CheckedSamplingMode::Sample
    };

    process_step(
        ctx_in.borrow(),
        &CheckedProofStep::UserVar {
            var: global_var,
            user_var: Some(CheckedUserVar {
                user_var,
                sampling_mode,
                new_var: true,
            }),
        },
    )?;

    Ok(())
}

/// Remove a user/global var mapping.
pub fn remove_user_mapping<'a>(
    mut ctx: partial!(Context<'a>, mut ClausesP, mut ProcessingP<'a>, mut VariablesP, CheckerStateP),
    global_var: Var,
) -> Result<(), CheckerError> {
    ensure_var(ctx.borrow(), global_var);

    let variables = ctx.part_mut(VariablesP);

    let var_data = &variables.var_data[global_var.index()];

    // We process this step before removing the mapping, so the processor can still look up the
    // mapping.

    if var_data.user_var.is_some() {
        process_step(
            ctx.borrow(),
            &CheckedProofStep::UserVar {
                var: global_var,
                user_var: None,
            },
        )?;
    } else {
        return Err(CheckerError::check_failed(
            ctx.part(CheckerStateP).step,
            format!("no user name to remove for variable {:?}", global_var),
        ));
    }

    let variables = ctx.part_mut(VariablesP);

    let var_data = &mut variables.var_data[global_var.index()];
    if let Some(user_var) = var_data.user_var {
        variables.used_user_vars.remove(&user_var);
        var_data.user_var = None;
        var_data.sampling_mode = SamplingMode::Hide;
    }

    Ok(())
}