Commit c9260906 authored by Bharat Garhewal's avatar Bharat Garhewal
Browse files

Updated SystemUnderLearning trait to also provide output maps

parent 3a33fcdf
Pipeline #60736 passed with stages
in 6 minutes and 37 seconds
......@@ -12,6 +12,7 @@ documentation = "https://sws.pages.science.ru.nl/lsharp/lsharp_ru/index.html"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bimap = "0.6.2"
derive-getters = "0.2.0"
derive_more = "0.99.17"
derive_builder = "0.10.2"
......@@ -39,6 +40,5 @@ assert_fs = "1.0"
rstest = "0.12.0"
[profile.release]
debug = true
lto = "fat"
codegen-units = 1
use crate::automatadefs::mealy::shortest_separating_sequence;
use crate::learner::l_sharp::{Rule2, Rule3};
use crate::oracles::equivalence::{
EquivalenceOracle, ExternalEquivalenceOracle, InternalEquivalenceOracle,
use crate::{
automatadefs::mealy::shortest_separating_sequence,
learner::l_sharp::{Rule2, Rule3},
oracles::equivalence::{
EquivalenceOracle, ExternalEquivalenceOracle, InternalEquivalenceOracle,
},
};
use crate::{
automatadefs::{
......@@ -26,8 +28,7 @@ use crate::{
};
use fnv::{FnvHashMap, FnvHashSet};
use rand::{prelude::StdRng, Rng, SeedableRng};
use std::collections::HashMap;
use std::{cell::RefCell, hash::BuildHasherDefault, rc::Rc, sync::Arc};
use std::{cell::RefCell, collections::HashMap, hash::BuildHasherDefault, rc::Rc, sync::Arc};
#[derive(derive_builder::Builder)]
pub struct Options {
......@@ -44,23 +45,24 @@ pub struct Options {
#[allow(clippy::type_complexity)]
#[allow(clippy::too_many_lines)]
#[must_use]
pub fn learn_fsm<S: ::std::hash::BuildHasher>(
pub fn learn_fsm<S: ::std::hash::BuildHasher + Default>(
sul: &Mealy,
input_map: &HashMap<String, InputSymbol, S>,
output_map: &HashMap<String, OutputSymbol, S>,
options: &Options,
logs: Option<Vec<(Box<[InputSymbol]>, Box<[OutputSymbol]>)>>,
) -> LearnResult {
type DefaultFxHasher = BuildHasherDefault<rustc_hash::FxHasher>;
type WrappedOracle<'a> = Rc<RefCell<OQOracle<'a, ObsMapTree<DefaultFxHasher>>>>;
let mealy_machine = Arc::new(sul.clone());
log::info!("Qsize : {}", mealy_machine.as_ref().states().len());
log::info!("Isize : {}", mealy_machine.as_ref().input_alphabet().len());
let mut trial = Simulator::new(Arc::clone(&mealy_machine));
let oq_oracle: Rc<RefCell<OQOracle<'_, ObsMapTree<BuildHasherDefault<rustc_hash::FxHasher>>>>> =
Rc::new(RefCell::new(OQOracle::new(
&mut trial,
options.rule2_mode.clone(),
options.rule3_mode.clone(),
)));
let mut trial = Simulator::new(&mealy_machine, input_map, output_map);
let oq_oracle: WrappedOracle = Rc::new(RefCell::new(OQOracle::new(
&mut trial,
options.rule2_mode.clone(),
options.rule3_mode.clone(),
)));
let rev_input_map: FnvHashMap<_, _> = input_map.iter().map(|(x, y)| (*y, x.clone())).collect();
let rev_output_map: FnvHashMap<_, _> =
......@@ -93,7 +95,7 @@ pub fn learn_fsm<S: ::std::hash::BuildHasher>(
let mut iads_oracle = IadsEO::new(Rc::clone(&oq_oracle), options.extra_states, rng.gen());
let mut seq_oracle = SequenceOracle::new(Rc::clone(&oq_oracle), 10, rng.gen());
let mut learner = Lsharp::new(
oq_oracle,
Rc::clone(&oq_oracle),
&mealy_machine
.input_alphabet()
.into_iter()
......@@ -128,7 +130,20 @@ pub fn learn_fsm<S: ::std::hash::BuildHasher>(
.format(MealyWriter::MealyEncoding::Dot)
.build()
.expect("Could not write Hypothesis file.");
MealyWriter::write_machine(&writer_config, &hypothesis, &rev_input_map, &rev_output_map);
{
fn flip<A, B>((x, y): (A, B)) -> (B, A) {
(y, x)
}
let (input_vec, output_vec) = RefCell::borrow(&oq_oracle).pass_maps();
let rev_input_map = input_vec.into_iter().map(flip).collect();
let rev_output_map = output_vec.into_iter().map(flip).collect();
MealyWriter::write_machine::<S, _>(
&writer_config,
&hypothesis,
&rev_input_map,
&rev_output_map,
);
}
let ideal_ce = shortest_separating_sequence(&sul.clone(), &hypothesis, None, None);
if ideal_ce.is_none() {
success = true;
......
......@@ -22,7 +22,6 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
pub struct Oracle<'a, T> {
sul: &'a mut dyn SystemUnderLearning,
_input_alphabet: Vec<InputSymbol>,
obs_tree: T,
rule2: Rule2,
rule3: Rule3,
......@@ -34,8 +33,7 @@ where
{
pub fn new(sul: &'a mut dyn SystemUnderLearning, rule2: Rule2, rule3: Rule3) -> Self {
Self {
_input_alphabet: sul.get_alphabet(),
obs_tree: T::new(sul.get_alphabet().len()),
obs_tree: T::new(sul.input_map().len()),
sul,
rule2,
rule3,
......@@ -352,4 +350,8 @@ where
self.obs_tree
.insert_observation(None, input_seq, output_seq)
}
pub fn pass_maps(&self) -> (Vec<(String, InputSymbol)>, Vec<(String, OutputSymbol)>) {
(self.sul.input_map(), self.sul.output_map())
}
}
......@@ -3,53 +3,61 @@ use crate::automatadefs::{
mealy::{InputSymbol, Mealy, OutputSymbol, State},
FiniteStateMachine,
};
use std::sync::Arc;
use bimap::BiHashMap;
use itertools::Itertools;
use std::{collections::HashMap, sync::Arc};
/**
`Simulator` is the System Under Learning/Testing (SUL/SUT)
that we want to learn. This struct uses an FSM to "simulate"
the behaviour of a real SUL.
*/
/// `Simulator` is the System Under Learning/Testing (SUL/SUT)
/// that we want to learn. This struct uses an FSM to "simulate"
/// the behaviour of a real SUL.
#[derive(Debug)]
pub struct External {
pub struct External<S> {
fsm: Arc<Mealy>,
initial_state: State,
curr_state: State,
cnt_inputs: usize,
cnt_resets: usize,
input_map: BiHashMap<String, InputSymbol, S, S>,
output_map: BiHashMap<String, OutputSymbol, S, S>,
}
impl External {
impl<S: std::hash::BuildHasher + std::default::Default> External<S> {
#[must_use]
pub fn new(fsm: Arc<Mealy>) -> Self {
pub fn new(
fsm: Arc<Mealy>,
input_map: &HashMap<String, InputSymbol, S>,
output_map: &HashMap<String, OutputSymbol, S>,
) -> Self {
let i_m = input_map.iter().map(|(s, i)| (s.clone(), *i)).collect();
let o_m = output_map.iter().map(|(s, o)| (s.clone(), *o)).collect();
Self {
initial_state: fsm.initial_state(),
curr_state: fsm.initial_state(),
fsm,
cnt_inputs: 0,
cnt_resets: 0,
input_map: i_m,
output_map: o_m,
}
}
}
impl SystemUnderLearning for External {
impl<S: std::hash::BuildHasher> SystemUnderLearning for External<S> {
fn step(&mut self, input_seq: &[InputSymbol]) -> Box<[OutputSymbol]> {
let (dest, out_seq) = self.fsm.trace_from(self.curr_state, input_seq);
let i_s = input_seq
.iter()
.map(|i| self.input_map.get_by_right(i).expect("Safe"))
.collect_vec();
let (_dest, out_seq) = self.fsm.trace_from(State::new(0), input_seq);
self.cnt_inputs += input_seq.len();
self.curr_state = dest;
log::trace!(" {:?} / {:?}", input_seq, out_seq);
out_seq
}
fn reset(&mut self) {
self.cnt_resets += 1;
self.curr_state = self.initial_state;
log::trace!("\tRESET");
}
fn trace(&mut self, input_seq: &[InputSymbol]) -> Box<[OutputSymbol]> {
self.reset();
let (_dest, out_seq) = self.fsm.trace_from(self.curr_state, input_seq);
let (_dest, out_seq) = self.fsm.trace_from(State::new(0), input_seq);
self.cnt_inputs += input_seq.len();
log::trace!(" {:?} / {:?}", input_seq, out_seq);
out_seq
......@@ -62,7 +70,17 @@ impl SystemUnderLearning for External {
ret
}
fn get_alphabet(&self) -> Vec<InputSymbol> {
self.fsm.input_alphabet()
fn input_map(&self) -> Vec<(String, InputSymbol)> {
self.input_map
.iter()
.map(|(s, i)| (s.clone(), *i))
.collect()
}
fn output_map(&self) -> Vec<(String, OutputSymbol)> {
self.output_map
.iter()
.map(|(s, o)| (s.clone(), *o))
.collect()
}
}
......@@ -6,7 +6,9 @@
use crate::automatadefs::mealy::{InputSymbol, OutputSymbol};
/// External SUL, i.e., we interface with something else to connect to the SUL.
pub mod external;
/// Simulate an SUL.
pub mod simulator;
/// System Under Learning, as a trait.
......@@ -25,12 +27,15 @@ pub trait SystemUnderLearning {
#[must_use]
fn trace(&mut self, input_seq: &[InputSymbol]) -> Box<[OutputSymbol]>;
/// Get the number of inputs and resets sent to the SUL thus far,
/// **and reset the counters**.
/// Get the number of inputs and resets sent to the SUL thus far, **and reset the counters**.
#[must_use]
fn get_counts(&mut self) -> (usize, usize);
/// Get a copy of the input alphabet of the SUL.
/// Get a ref to the input bimap of the SUL.
#[must_use]
fn get_alphabet(&self) -> Vec<InputSymbol>;
fn input_map(&self) -> Vec<(String, InputSymbol)>;
/// Get a ref to the input bimap of the SUL.
#[must_use]
fn output_map(&self) -> Vec<(String, OutputSymbol)>;
}
use std::collections::HashMap;
use bimap::BiHashMap;
use super::SystemUnderLearning;
use crate::automatadefs::{
mealy::{InputSymbol, Mealy, OutputSymbol, State},
FiniteStateMachine,
};
use std::sync::Arc;
/**
`Simulator` is the System Under Learning/Testing (SUL/SUT)
......@@ -11,32 +14,39 @@ that we want to learn. This struct uses an FSM to "simulate"
the behaviour of a real SUL.
*/
#[derive(Debug)]
pub struct Simulator {
fsm: Arc<Mealy>,
pub struct Simulator<'a, S> {
fsm: &'a Mealy,
initial_state: State,
curr_state: State,
input_map: BiHashMap<String, InputSymbol, S, S>,
output_map: BiHashMap<String, OutputSymbol, S, S>,
cnt_inputs: usize,
cnt_resets: usize,
}
impl Simulator {
impl<'a, S: std::hash::BuildHasher + std::default::Default> Simulator<'a, S> {
/// Constructor.
///
/// As the FSM can never be mutated while learning,
/// it is required to be wrapped in an [`Arc`].
#[must_use]
pub fn new(fsm: Arc<Mealy>) -> Self {
pub fn new(
fsm: &'a Mealy,
input_map: &HashMap<String, InputSymbol, S>,
output_map: &HashMap<String, OutputSymbol, S>,
) -> Self {
let i_m = input_map.iter().map(|(s, i)| (s.clone(), *i)).collect();
let o_m = output_map.iter().map(|(s, o)| (s.clone(), *o)).collect();
Self {
initial_state: fsm.initial_state(),
curr_state: fsm.initial_state(),
fsm,
input_map: i_m,
output_map: o_m,
cnt_inputs: 0,
cnt_resets: 0,
}
}
}
impl SystemUnderLearning for Simulator {
impl<'a, S: std::hash::BuildHasher> SystemUnderLearning for Simulator<'a, S> {
fn step(&mut self, input_seq: &[InputSymbol]) -> Box<[OutputSymbol]> {
let (dest, out_seq) = self.fsm.trace_from(self.curr_state, input_seq);
self.cnt_inputs += input_seq.len();
......@@ -66,7 +76,17 @@ impl SystemUnderLearning for Simulator {
ret
}
fn get_alphabet(&self) -> Vec<InputSymbol> {
self.fsm.input_alphabet()
fn input_map(&self) -> Vec<(String, InputSymbol)> {
self.input_map
.iter()
.map(|(s, i)| (s.clone(), *i))
.collect()
}
fn output_map(&self) -> Vec<(String, OutputSymbol)> {
self.output_map
.iter()
.map(|(s, o)| (s.clone(), *o))
.collect()
}
}
......@@ -17,13 +17,13 @@ pub enum EqOracle {
Internal,
/// Generates tests using Observation tree, see [`SequenceOracle`](crate::oracles::equivalence::sep_seq::SequenceOracle).
SepSeq,
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::SouchaOracle).
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::Oracle).
SouchaH,
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::SouchaOracle).
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::Oracle).
SouchaHSI,
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::SouchaOracle).
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::Oracle).
SouchaSPY,
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::SouchaOracle).
/// FSMLib HSI implementation, see [`SouchaOracle`](crate::oracles::equivalence::soucha::Oracle).
SouchaSPYH,
/// IADS implementation.
Iads,
......
......@@ -2,15 +2,17 @@ use crate::automatadefs::{
mealy::{InputSymbol, OutputSymbol},
FiniteStateMachine,
};
use itertools::Itertools;
use std::{collections::HashMap, hash::BuildHasher, io::Write};
/// Accepts an fsm and conversion maps and returns a vector of bytes.
#[must_use]
pub fn write_to_dot<S: BuildHasher + Default, Machine: FiniteStateMachine>(
fsm: &Machine,
///
/// If the input and output maps are empty, we print the bare natural numbers of the alphabets.
pub fn write_to_dot<S: BuildHasher + Default, M: FiniteStateMachine>(
fsm: &M,
input_rev_map: &HashMap<InputSymbol, String, S>,
output_rev_map: &HashMap<OutputSymbol, String, S>,
) -> Vec<u8> {
) -> Result<Vec<u8>, std::io::Error> {
let mut vec = Vec::new();
writeln!(&mut vec, "digraph g {{\n").expect("Safe");
let mut state_info = HashMap::<_, _, S>::default();
......@@ -20,45 +22,56 @@ pub fn write_to_dot<S: BuildHasher + Default, Machine: FiniteStateMachine>(
&mut vec,
"\t s{0} [shape = \"circle\" label=\"s{0}\"];",
s.raw()
)
.expect("Safe");
)?;
}
for src in fsm.states() {
for input in fsm.input_alphabet() {
let (dest, output) = fsm.step_from(src, input);
state_info
.entry(src)
.and_modify(|trans| trans.push((input, output, dest)));
}
for (src, input) in
Itertools::cartesian_product(fsm.states().into_iter(), fsm.input_alphabet().into_iter())
{
let (dest, output) = fsm.step_from(src, input);
state_info
.entry(src)
.or_default()
.push((input, output, dest));
}
let mut v: Vec<_> = state_info.into_iter().collect();
v.sort_by(|x, y| x.0.raw().cmp(&y.0.raw()));
for (src, transitions) in v {
for (i_s, o_s, dest) in transitions {
writeln!(
&mut vec,
"\t s{} -> s{} [label=\"{} / {}\"];",
src.raw(),
dest.raw(),
input_rev_map.get(&i_s).expect("Safe"),
output_rev_map.get(&o_s).expect("Safe")
)
.expect("Safe");
if input_rev_map.is_empty() && output_rev_map.is_empty() {
for (src, transitions) in state_info.into_iter().sorted() {
for (i_s, o_s, dest) in transitions {
let i: usize = i_s.into();
let o: u16 = o_s.raw();
writeln!(
&mut vec,
"\t s{} -> s{} [label=\"{} / {}\"];",
src.raw(),
dest.raw(),
i,
o
)?;
}
}
} else {
for (src, transitions) in state_info.into_iter().sorted() {
for (i_s, o_s, dest) in transitions {
writeln!(
&mut vec,
"\t s{} -> s{} [label=\"{} / {}\"];",
src.raw(),
dest.raw(),
input_rev_map.get(&i_s).expect("Safe"),
output_rev_map.get(&o_s).expect("Safe")
)?;
}
}
}
writeln!(
&mut vec,
r#"__start0 [label="" shape="none" width="0" height="0"];"#
)
.expect("Safe");
)?;
writeln!(
&mut vec,
"\t\t__start0 -> s{};\n\n}}\n",
fsm.initial_state().raw()
)
.expect("Safe");
vec
)?;
Ok(vec)
}
......@@ -45,7 +45,8 @@ pub fn write_machine<S: BuildHasher + Default, Machine: FiniteStateMachine>(
super::soucha_fmt::write_to_soucha(fsm, input_rev_map, output_rev_map)
}
MealyEncoding::Dot => super::dot_fmt::write_to_dot(fsm, input_rev_map, output_rev_map),
};
}
.ok()?;
if config.to_file() {
write_to_file(&config.file().expect("Safe"), &byte_mealy);
return None;
......
......@@ -6,12 +6,11 @@ use itertools::Itertools;
use std::{collections::HashMap, hash::BuildHasher, io::Write};
/// Convert a Mealy Machine to Soucha's textual format.
#[must_use]
pub fn write_to_soucha<S: BuildHasher + Default, Machine: FiniteStateMachine>(
fsm: &Machine,
_input_rev_map: &HashMap<InputSymbol, String, S>,
_output_rev_map: &HashMap<OutputSymbol, String, S>,
) -> Vec<u8> {
) -> Result<Vec<u8>, std::io::Error> {
let t = 2; // Always a mealy machine.
let r = 1; // Always reduced.
let num_states = fsm.states().len(); // Num states
......@@ -21,13 +20,13 @@ pub fn write_to_soucha<S: BuildHasher + Default, Machine: FiniteStateMachine>(
let mut vec = Vec::new();
// First line: type reduced_or_not
writeln!(&mut vec, "{} {}", t, r).expect("Safe");
writeln!(&mut vec, "{} {}", t, r)?;
// Second line: num_states num_inputs num_outputs
writeln!(&mut vec, "{} {} {}", num_states, num_inputs, num_outputs).expect("Safe");
writeln!(&mut vec, "{} {} {}", num_states, num_inputs, num_outputs)?;
// Third line: max_state_id
writeln!(&mut vec, "{}", max_state_id).expect("Safe");
writeln!(&mut vec, "{}", max_state_id)?;
let state_mapping = normalise_states(fsm);
let output_mapping = normalise_outputs(fsm);
......@@ -35,7 +34,7 @@ pub fn write_to_soucha<S: BuildHasher + Default, Machine: FiniteStateMachine>(
// Follows the format of
// state_id (input1's output) (input2's output) ... (inputN's output)
for state_id in 0..num_states {
write!(&mut vec, "{}", state_id).expect("Safe");
write!(&mut vec, "{}", state_id)?;
for input in 0..num_inputs {
let output_symbol = fsm
.step_from(
......@@ -45,16 +44,16 @@ pub fn write_to_soucha<S: BuildHasher + Default, Machine: FiniteStateMachine>(
.1;
// let output_raw = output_symbol.raw();
let output_raw = output_mapping.binary_search(&output_symbol).expect("Safe");
write!(&mut vec, " {}", output_raw).expect("Safe");
write!(&mut vec, " {}", output_raw)?;
}
writeln!(&mut vec).expect("Safe");
writeln!(&mut vec)?;
}
// n lines of transition function.
// Follows the format of
// state_id (input1's dest) ... (inputN's dest)
for state_id in 0..num_states {
write!(&mut vec, "{}", state_id).expect("Safe");
write!(&mut vec, "{}", state_id)?;
for input in 0..num_inputs {
let dest_state = fsm
.step_from(
......@@ -64,11 +63,11 @@ pub fn write_to_soucha<S: BuildHasher + Default, Machine: FiniteStateMachine>(
.0;
let state_raw = state_mapping.binary_search(&dest_state).expect("Safe");
// let state_raw = dest_state.raw();
write!(&mut vec, " {}", state_raw).expect("Safe");
write!(&mut vec, " {}", state_raw)?;
}
writeln!(&mut vec).expect("Safe");
writeln!(&mut vec)?;
}
vec
Ok(vec)
}
fn normalise_outputs<M: FiniteStateMachine>(fsm: &M) -> Vec<OutputSymbol> {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment