From 495f9c893f00c659c8315c5661f8282ac6c19575 Mon Sep 17 00:00:00 2001 From: Stefan Ellmauthaler <71695780+ellmau@users.noreply.github.com> Date: Tue, 16 Aug 2022 23:13:27 +0200 Subject: [PATCH] Add a random heuristic, based on rand-crate (#96) * Add a random heuristic, based on rand-crate * Add cryptographically strong seed option --- Cargo.lock | 15 ++++++++++++++- lib/Cargo.toml | 1 + lib/src/adf.rs | 36 ++++++++++++++++++++++++++++++++++++ lib/src/adf/heuristics.rs | 23 +++++++++++++++++++++++ 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 30f02b8..9020db4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,7 @@ dependencies = [ "nom", "quickcheck", "quickcheck_macros", + "rand 0.8.5", "roaring", "serde", "serde_json", @@ -622,7 +623,7 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha", + "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc", ] @@ -633,6 +634,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.3", ] @@ -646,6 +649,16 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + [[package]] name = "rand_core" version = "0.5.1" diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 2a9599b..5147496 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -33,6 +33,7 @@ derivative = "2.2.0" roaring = "0.9.0" strum = { version = "0.24", features = ["derive"] } crossbeam-channel = "0.5" +rand = {version = "0.8.5", features = ["std_rng"]} [dev-dependencies] test-log = "0.2" diff --git a/lib/src/adf.rs b/lib/src/adf.rs index f8822dd..3b007a1 100644 --- a/lib/src/adf.rs +++ b/lib/src/adf.rs @@ -6,6 +6,8 @@ This module describes the abstract dialectical framework. */ pub mod heuristics; +use std::cell::RefCell; + use crate::{ datatypes::{ adf::{ @@ -18,6 +20,7 @@ use crate::{ obdd::Bdd, parser::{AdfParser, Formula}, }; +use rand::{rngs::StdRng, SeedableRng}; use serde::{Deserialize, Serialize}; use self::heuristics::Heuristic; @@ -30,6 +33,8 @@ pub struct Adf { ordering: VarContainer, bdd: Bdd, ac: Vec, + #[serde(skip, default = "Adf::default_rng")] + rng: RefCell, } impl Default for Adf { @@ -38,6 +43,7 @@ impl Default for Adf { ordering: VarContainer::default(), bdd: Bdd::new(), ac: Vec::new(), + rng: Adf::default_rng(), } } } @@ -50,6 +56,7 @@ impl Adf { ordering: parser.var_container(), bdd: Bdd::new(), ac: vec![Term(0); parser.dict_size()], + rng: Adf::default_rng(), }; (0..parser.dict_size()).into_iter().for_each(|value| { log::trace!("adding variable {}", Var(value)); @@ -85,6 +92,7 @@ impl Adf { ordering: ordering.clone(), bdd: Bdd::new(), ac: vec![Term(0); bio_ac.len()], + rng: Adf::default_rng(), }; result .ac @@ -134,6 +142,15 @@ impl Adf { result } + fn default_rng() -> RefCell { + RefCell::new(StdRng::from_entropy()) + } + + /// Sets a cryptographiclly strong seed + pub fn seed(&mut self, seed: [u8; 32]) { + self.rng = RefCell::new(StdRng::from_seed(seed)) + } + /// Instantiates a new ADF, based on a [biodivine adf][crate::adfbiodivine::Adf]. pub fn from_biodivine(bio_adf: &super::adfbiodivine::Adf) -> Self { Self::from_biodivine_vector(bio_adf.var_container(), bio_adf.ac()) @@ -1232,6 +1249,25 @@ mod test { solving.join().unwrap(); } + #[test] + fn rand_stable_heu() { + let parser = AdfParser::default(); + parser.parse()("s(a).s(b).ac(a,neg(b)).ac(b,neg(a)).").unwrap(); + let mut adf = Adf::from_parser(&parser); + let result = adf.stable_nogood(Heuristic::Rand).collect::>(); + assert!(result.contains(&vec![Term(0), Term(1)])); + assert!(result.contains(&vec![Term(1), Term(0)])); + assert_eq!(result.len(), 2); + + let mut adf = Adf::from_parser(&parser); + adf.seed([ + 122, 186, 240, 42, 235, 102, 89, 81, 187, 203, 127, 188, 167, 198, 126, 156, 25, 205, + 204, 132, 112, 93, 23, 193, 21, 108, 166, 231, 158, 250, 128, 135, + ]); + let result = adf.stable_nogood(Heuristic::Rand).collect::>(); + assert_eq!(result, vec![vec![Term(1), Term(0)], vec![Term(0), Term(1)]]); + } + #[test] fn complete() { let parser = AdfParser::default(); diff --git a/lib/src/adf/heuristics.rs b/lib/src/adf/heuristics.rs index 2cbb9f6..5b43ba5 100644 --- a/lib/src/adf/heuristics.rs +++ b/lib/src/adf/heuristics.rs @@ -5,6 +5,7 @@ In addition there is the public enum [Heuristic], which allows to set a heuristi use super::Adf; use crate::datatypes::{Term, Var}; +use rand::{Rng, RngCore}; use strum::{EnumString, EnumVariantNames}; /// Return value for heuristics. @@ -76,6 +77,23 @@ pub(crate) fn heu_mc_maxvarimp_minpaths(adf: &Adf, interpr: &[Term]) -> Option<( }) } +pub(crate) fn heu_rand(adf: &Adf, interpr: &[Term]) -> Option<(Var, Term)> { + let possible = interpr + .iter() + .enumerate() + .filter(|(_var, term)| !term.is_truth_value()) + .collect::>(); + if possible.is_empty() { + return None; + } + let mut rng = adf.rng.borrow_mut(); + if let Ok(position) = usize::try_from(rng.next_u64() % (possible.len() as u64)) { + Some((Var::from(position), rng.gen_bool(0.5).into())) + } else { + None + } +} + /// Enumeration of all currently implemented heuristics. /// It represents a public view on the crate-view implementations of heuristics. #[derive(EnumString, EnumVariantNames, Copy, Clone)] @@ -89,6 +107,8 @@ pub enum Heuristic<'a> { /// Implementation of a heuristic, which which uses maximal [variable-impact][crate::obdd::Bdd::passive_var_impact] and minimal number of [paths][crate::obdd::Bdd::paths] to identify the variable to be set. /// As the value of the variable value with the maximal model-path is chosen. MinModMaxVarImpMinPaths, + /// Implementation of a heuristic, which chooses random values. + Rand, /// Allow passing in an externally-defined custom heuristic. #[strum(disabled)] Custom(&'a HeuristicFn), @@ -106,6 +126,7 @@ impl std::fmt::Debug for Heuristic<'_> { Self::Simple => write!(f, "Simple"), Self::MinModMinPathsMaxVarImp => write!(f, "Maximal model-path count as value and minimum paths with maximal variable impact as variable choice"), Self::MinModMaxVarImpMinPaths => write!(f, "Maximal model-path count as value and maximal variable impact with minimum paths as variable choice"), + Self::Rand => write!(f, "Random heuristics"), Self::Custom(_) => f.debug_tuple("Custom function").finish(), } } @@ -117,6 +138,7 @@ impl Heuristic<'_> { Heuristic::Simple => &heu_simple, Heuristic::MinModMinPathsMaxVarImp => &heu_mc_minpaths_maxvarimp, Heuristic::MinModMaxVarImpMinPaths => &heu_mc_maxvarimp_minpaths, + Heuristic::Rand => &heu_rand, Self::Custom(f) => f, } } @@ -132,6 +154,7 @@ mod test { dbg!(Heuristic::Simple); dbg!(Heuristic::MinModMaxVarImpMinPaths); dbg!(Heuristic::MinModMinPathsMaxVarImp); + dbg!(Heuristic::Rand); dbg!(Heuristic::Custom(&|_adf: &Adf, _int: &[Term]| -> Option<(Var, Term)> { None }));