#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum EpsilonFilterType {
None,
#[default]
Sequencing,
Matching,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum FilterState {
#[default]
None,
Eps1,
Eps2,
}
#[derive(Clone, Debug)]
pub struct EpsilonFilter {
filter_type: EpsilonFilterType,
}
impl Default for EpsilonFilter {
fn default() -> Self {
Self {
filter_type: EpsilonFilterType::Sequencing,
}
}
}
impl EpsilonFilter {
pub fn new(filter_type: EpsilonFilterType) -> Self {
Self { filter_type }
}
pub fn filter_type(&self) -> EpsilonFilterType {
self.filter_type
}
pub fn allowed_moves(&self, state: FilterState) -> (bool, bool, bool) {
match self.filter_type {
EpsilonFilterType::None => {
(true, true, true)
}
EpsilonFilterType::Sequencing => {
match state {
FilterState::None => (true, true, true),
FilterState::Eps1 => (true, false, true), FilterState::Eps2 => (false, true, true), }
}
EpsilonFilterType::Matching => {
match state {
FilterState::None => (true, true, true),
FilterState::Eps1 => (true, true, false), FilterState::Eps2 => (true, true, false), }
}
}
}
pub fn next_state(
&self,
_current: FilterState,
eps1_output: bool,
eps2_input: bool,
) -> FilterState {
match self.filter_type {
EpsilonFilterType::None => FilterState::None,
EpsilonFilterType::Sequencing => {
if eps1_output && !eps2_input {
FilterState::Eps1
} else if eps2_input && !eps1_output {
FilterState::Eps2
} else {
FilterState::None
}
}
EpsilonFilterType::Matching => {
if eps1_output == eps2_input {
FilterState::None
} else if eps1_output {
FilterState::Eps1
} else {
FilterState::Eps2
}
}
}
}
pub fn is_transition_allowed(
&self,
state: FilterState,
eps1_output: bool,
eps2_input: bool,
is_match: bool,
) -> bool {
let (can_eps1, can_eps2, can_match) = self.allowed_moves(state);
if is_match {
can_match
} else if eps1_output && !eps2_input {
can_eps1
} else if eps2_input && !eps1_output {
can_eps2
} else if eps1_output && eps2_input {
can_eps1 || can_eps2
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_filter_type_default() {
let filter = EpsilonFilter::default();
assert_eq!(filter.filter_type(), EpsilonFilterType::Sequencing);
}
#[test]
fn test_filter_state_default() {
let state = FilterState::default();
assert_eq!(state, FilterState::None);
}
#[test]
fn test_no_filter_allows_all() {
let filter = EpsilonFilter::new(EpsilonFilterType::None);
for state in [FilterState::None, FilterState::Eps1, FilterState::Eps2] {
let (eps1, eps2, match_) = filter.allowed_moves(state);
assert!(eps1);
assert!(eps2);
assert!(match_);
}
}
#[test]
fn test_sequencing_filter_none_state() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
let (eps1, eps2, match_) = filter.allowed_moves(FilterState::None);
assert!(eps1);
assert!(eps2);
assert!(match_);
}
#[test]
fn test_sequencing_filter_eps1_state() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
let (eps1, eps2, match_) = filter.allowed_moves(FilterState::Eps1);
assert!(eps1); assert!(!eps2); assert!(match_); }
#[test]
fn test_sequencing_filter_eps2_state() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
let (eps1, eps2, match_) = filter.allowed_moves(FilterState::Eps2);
assert!(!eps1); assert!(eps2); assert!(match_); }
#[test]
fn test_next_state_sequencing() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
assert_eq!(
filter.next_state(FilterState::None, true, false),
FilterState::Eps1
);
assert_eq!(
filter.next_state(FilterState::None, false, true),
FilterState::Eps2
);
assert_eq!(
filter.next_state(FilterState::Eps1, false, false),
FilterState::None
);
assert_eq!(
filter.next_state(FilterState::None, true, true),
FilterState::None
);
}
#[test]
fn test_is_transition_allowed_match() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
assert!(filter.is_transition_allowed(FilterState::None, false, false, true));
assert!(filter.is_transition_allowed(FilterState::Eps1, false, false, true));
assert!(filter.is_transition_allowed(FilterState::Eps2, false, false, true));
}
#[test]
fn test_is_transition_allowed_eps1() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
assert!(filter.is_transition_allowed(FilterState::None, true, false, false));
assert!(filter.is_transition_allowed(FilterState::Eps1, true, false, false));
assert!(!filter.is_transition_allowed(FilterState::Eps2, true, false, false));
}
#[test]
fn test_is_transition_allowed_eps2() {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
assert!(filter.is_transition_allowed(FilterState::None, false, true, false));
assert!(!filter.is_transition_allowed(FilterState::Eps1, false, true, false));
assert!(filter.is_transition_allowed(FilterState::Eps2, false, true, false));
}
#[test]
fn test_matching_filter() {
let filter = EpsilonFilter::new(EpsilonFilterType::Matching);
let (_, _, match_) = filter.allowed_moves(FilterState::Eps1);
assert!(!match_);
let (_, _, match_) = filter.allowed_moves(FilterState::Eps2);
assert!(!match_);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
fn arb_filter_type() -> impl Strategy<Value = EpsilonFilterType> {
prop_oneof![
Just(EpsilonFilterType::None),
Just(EpsilonFilterType::Sequencing),
Just(EpsilonFilterType::Matching),
]
}
fn arb_filter_state() -> impl Strategy<Value = FilterState> {
prop_oneof![
Just(FilterState::None),
Just(FilterState::Eps1),
Just(FilterState::Eps2),
]
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(100))]
#[test]
fn next_state_deterministic(
filter_type in arb_filter_type(),
state in arb_filter_state(),
eps1 in any::<bool>(),
eps2 in any::<bool>()
) {
let filter = EpsilonFilter::new(filter_type);
let result1 = filter.next_state(state, eps1, eps2);
let result2 = filter.next_state(state, eps1, eps2);
prop_assert_eq!(result1, result2);
}
#[test]
fn no_filter_always_none(
state in arb_filter_state(),
eps1 in any::<bool>(),
eps2 in any::<bool>()
) {
let filter = EpsilonFilter::new(EpsilonFilterType::None);
let result = filter.next_state(state, eps1, eps2);
prop_assert_eq!(result, FilterState::None);
}
#[test]
fn no_filter_allows_all(state in arb_filter_state()) {
let filter = EpsilonFilter::new(EpsilonFilterType::None);
let (eps1, eps2, match_) = filter.allowed_moves(state);
prop_assert!(eps1);
prop_assert!(eps2);
prop_assert!(match_);
}
#[test]
fn sequencing_allows_match(state in arb_filter_state()) {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
let (_, _, match_) = filter.allowed_moves(state);
prop_assert!(match_);
}
#[test]
fn matching_disallows_match_in_eps_states(state in arb_filter_state()) {
let filter = EpsilonFilter::new(EpsilonFilterType::Matching);
let (_, _, match_) = filter.allowed_moves(state);
match state {
FilterState::None => prop_assert!(match_),
FilterState::Eps1 | FilterState::Eps2 => prop_assert!(!match_),
}
}
#[test]
fn none_state_symmetric(filter_type in arb_filter_type()) {
let filter = EpsilonFilter::new(filter_type);
let (eps1, eps2, match_) = filter.allowed_moves(FilterState::None);
prop_assert!(eps1);
prop_assert!(eps2);
prop_assert!(match_);
}
#[test]
fn sequencing_symmetric(eps1_only in any::<bool>()) {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
if eps1_only {
let next = filter.next_state(FilterState::None, true, false);
prop_assert_eq!(next, FilterState::Eps1);
} else {
let next = filter.next_state(FilterState::None, false, true);
prop_assert_eq!(next, FilterState::Eps2);
}
}
#[test]
fn sequencing_match_resets(state in arb_filter_state()) {
let filter = EpsilonFilter::new(EpsilonFilterType::Sequencing);
let next = filter.next_state(state, false, false);
prop_assert_eq!(next, FilterState::None);
}
#[test]
fn is_transition_allowed_consistent(
filter_type in arb_filter_type(),
state in arb_filter_state()
) {
let filter = EpsilonFilter::new(filter_type);
let (can_eps1, can_eps2, can_match) = filter.allowed_moves(state);
prop_assert_eq!(
filter.is_transition_allowed(state, false, false, true),
can_match
);
prop_assert_eq!(
filter.is_transition_allowed(state, true, false, false),
can_eps1
);
prop_assert_eq!(
filter.is_transition_allowed(state, false, true, false),
can_eps2
);
}
#[test]
fn both_eps_needs_at_least_one(
filter_type in arb_filter_type(),
state in arb_filter_state()
) {
let filter = EpsilonFilter::new(filter_type);
let (can_eps1, can_eps2, _) = filter.allowed_moves(state);
let both_eps_allowed = filter.is_transition_allowed(state, true, true, false);
prop_assert_eq!(both_eps_allowed, can_eps1 || can_eps2);
}
}
}