Skip to content
This repository has been archived by the owner on Jul 30, 2024. It is now read-only.

Update edition & add example #4

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ name = "fuzzy_logic"
version = "0.3.0"
authors = ["Lutfullin Bulat <lb6557@gmail.com>"]

edition = "2021"

[dependencies]
ordered-float = {version = "0.2.0", git = "https://github.com/AerialX/rust-ordered-float.git", rev="3aa8aa506b3231712958093ee513b37206a474da"}

[dev-dependencies]
approx = "0.5"
106 changes: 106 additions & 0 deletions examples/use.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::collections::HashMap;

use fuzzy_logic::{
functions::{DefuzzFactory, MembershipFactory},
inference::{InferenceMachine, InferenceOptions},
ops::{MinMaxOps, ZadehOps},
rules::{And, Is, Rule, RuleSet},
set::UniversalSet,
};

fn main() {
// first column of x dest
let nb_nb = And::new(Is::new("x_dest", "NB"), Is::new("x", "NB"));
let nb_ns = And::new(Is::new("x_dest", "NB"), Is::new("x", "NS"));
let nb_z = And::new(Is::new("x_dest", "NB"), Is::new("x", "Z"));
let nb_ps = And::new(Is::new("x_dest", "NB"), Is::new("x", "PS"));
let nb_pb = And::new(Is::new("x_dest", "NB"), Is::new("x", "PB"));
// second column of x dest
let ns_nb = And::new(Is::new("x_dest", "NS"), Is::new("x", "NB"));
let ns_ns = And::new(Is::new("x_dest", "NS"), Is::new("x", "NS"));
let ns_z = And::new(Is::new("x_dest", "NS"), Is::new("x", "Z"));
let ns_ps = And::new(Is::new("x_dest", "NS"), Is::new("x", "PS"));
let ns_pb = And::new(Is::new("x_dest", "NS"), Is::new("x", "PB"));

let rules = vec![
// first column of x dest
Rule::new(nb_nb.boxed(), "pitch_output".into(), "Z".into()),
Rule::new(nb_ns.boxed(), "pitch_output".into(), "NS".into()),
Rule::new(nb_z.boxed(), "pitch_output".into(), "NB".into()),
Rule::new(nb_ps.boxed(), "pitch_output".into(), "NB".into()),
Rule::new(nb_pb.boxed(), "pitch_output".into(), "NB".into()),
// second column of x dest
Rule::new(ns_nb.boxed(), "pitch_output".into(), "PS".into()),
Rule::new(ns_ns.boxed(), "pitch_output".into(), "Z".into()),
Rule::new(ns_z.boxed(), "pitch_output".into(), "NS".into()),
Rule::new(ns_ps.boxed(), "pitch_output".into(), "NB".into()),
Rule::new(ns_pb.boxed(), "pitch_output".into(), "NB".into()),
];

let (x_dest, x, pitch_output) = {
let x_dest = UniversalSet::new("x_dest")
.with_domain(vec![-2.0, -1.0, 0.0, 1.0, 2.0])
.add_set("NB", MembershipFactory::triangular(-2.0, -2.0, -1.0))
.add_set("NS", MembershipFactory::triangular(-2.0, -1.0, 0.0))
.add_set("Z", MembershipFactory::triangular(-1.0, 0.0, 1.0))
.add_set("PS", MembershipFactory::triangular(0.0, 1.0, 2.0))
.add_set("PB", MembershipFactory::triangular(1.0, 2.0, 2.0));

let x = UniversalSet::new("x")
.with_domain(vec![-2.0, -1.0, 0.0, 1.0, 2.0])
.add_set("NB", MembershipFactory::triangular(-2.0, -2.0, -1.0))
.add_set("NS", MembershipFactory::triangular(-2.0, -1.0, 0.0))
.add_set("Z", MembershipFactory::triangular(-1.0, 0.0, 1.0))
.add_set("PS", MembershipFactory::triangular(0.0, 1.0, 2.0))
.add_set("PB", MembershipFactory::triangular(1.0, 2.0, 2.0));

let pitch_output = UniversalSet::new("pitch_output")
.with_domain(vec![-0.5, -0.25, 0.0, 0.25, 0.5])
.add_set("NB", MembershipFactory::singleton(-0.5))
.add_set("NS", MembershipFactory::singleton(-0.25))
.add_set("Z", MembershipFactory::singleton(0.0))
.add_set("PS", MembershipFactory::singleton(0.25))
.add_set("PB", MembershipFactory::singleton(0.5));

(x_dest, x, pitch_output)
};

let mut universes = HashMap::default();
universes.insert("x_dest".into(), x_dest);
universes.insert("x".into(), x);
universes.insert("pitch_output".into(), pitch_output);

let rule_set = RuleSet::new(rules).expect("Valid RuleSet");

let options = InferenceOptions {
logic_ops: Box::new(ZadehOps {}),
set_ops: Box::new(MinMaxOps {}),
defuzz_func: DefuzzFactory::center_of_mass(),
};

let mut inference = InferenceMachine::new(rule_set, universes, options);

let input = vec![
// NB
("x_dest".into(), -1.9_f32),
// NS
("x".into(), -1.0_f32),
]
.into_iter()
.collect();


inference.update(input);

dbg!(&inference);

// NB of x_dest
// NS of x
// Expected: NS
let output = inference.compute();

// TODO: Check what's going wrong here.
// value: NaN
// Actual: Set: pitch_output: Z UNION pitch_output: NS UNION pitch_output: NB UNION pitch_output: NB UNION pitch_output: NB UNION pitch_output: PS UNION pitch_output: Z UNION pitch_output: NS UNION pitch_output: NB UNION pitch_output: NB value: NaN
println!("Set: {} value: {}", output.0, output.1)
}
Binary file added examples/use_example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 24 additions & 10 deletions src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
//! Module contains implementation of membership functions and defuzzification functions.
//! Also contains factory methods to create most used functions.

use set::Set;
use crate::set::Set;

/// Used to calculate the membership of the given item.
/// All membership functions must be this type.
pub type MembershipFunction = Fn(f32) -> f32;
pub type MembershipFunction = dyn Fn(f32) -> f32;

/// Used to defuzzificate the fuzzy logic inference result.
/// All defuzzification functions must be this type.
pub type DefuzzFunc = Fn(&Set) -> f32;
pub type DefuzzFunc = dyn Fn(&Set) -> f32;

/// Defines methods to create most used membership functions.
///
/// #Usage
/// # Examples
/// Create triangular function:
///
/// ```rust
/// use fuzzy_logic::functions::MembershipFactory;
///
/// let mem = MembershipFactory::triangular(-15.0, -15.0, 22.0);
/// mem(-15.0); // -> 1.0
/// ```
Expand Down Expand Up @@ -65,16 +66,25 @@ impl MembershipFactory {
pub fn gaussian(a: f32, b: f32, c: f32) -> Box<MembershipFunction> {
Box::new(move |x: f32| a * (-1.0 * ((x - b).powi(2) / (2.0 * c.powi(2)))).exp())
}

/// Creates a singleton function
// If value == x returns 1.0 (part of the set)
// If value != x returns 0.0 (not part of the set)
pub fn singleton(value: f32) -> Box<MembershipFunction> {
Box::new(move |x: f32| if value == x { 1.0 } else { 0.0 })
}
}

/// Defines methods to create most used defuzzification functions.
///
/// #Usage
/// # Examples
/// Create function which calculates center of mass:
///
/// ```rust
/// use fuzzy_logic::functions::{DefuzzFactory, MembershipFactory};
/// use fuzzy_logic::set::Set;
/// use fuzzy_logic::{
/// functions::{DefuzzFactory, MembershipFactory},
/// set::Set
/// };
///
/// let mem = MembershipFactory::triangular(-15.0, -15.0, 22.0);
/// let df = DefuzzFactory::center_of_mass();
Expand All @@ -88,24 +98,28 @@ impl DefuzzFactory {
pub fn center_of_mass() -> Box<DefuzzFunc> {
Box::new(|s: &Set| {
let sum = s.cache.borrow().iter().fold(0.0, |acc, (_, &v)| acc + v);
let prod_sum = s.cache.borrow().iter().fold(0.0, |acc, (&k, &v)| acc + k.into_inner() * v);
let prod_sum = s
.cache
.borrow()
.iter()
.fold(0.0, |acc, (&k, &v)| acc + k.into_inner() * v);
prod_sum / sum
})
}
}

#[cfg(test)]
mod test {
use std::f32;
use super::*;
use std::f32;

#[test]
fn sigmoidal() {
let steepness = 2.0;
for i in -100..100 {
let midpoint = i as f32;
let f = MembershipFactory::sigmoidal(steepness, midpoint);
let mut diff = (0.5 - f(midpoint)).abs();
let diff = (0.5 - f(midpoint)).abs();
assert!(diff <= f32::EPSILON);
}
}
Expand Down
59 changes: 40 additions & 19 deletions src/inference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,41 @@
//! Fuzzy logic mechanism is implemented in `InferenceMachine`.
//! User can modify input variables with `update` method and get inference result with `compute` method.

use set::UniversalSet;
use ops::{LogicOps, SetOps};
use rules::RuleSet;
use functions::DefuzzFunc;
use crate::functions::DefuzzFunc;
use crate::ops::{LogicOps, SetOps};
use crate::rules::RuleSet;
use crate::set::UniversalSet;
use std::any::{type_name, TypeId};
use std::collections::HashMap;
use std::fmt;

/// Structure which contains the implementation of fuzzy logic operations.
pub struct InferenceOptions {
/// Contains fuzzy logical operations.
pub logic_ops: Box<LogicOps>,
pub logic_ops: Box<dyn LogicOps>,
/// Contains fuzzy set operations.
pub set_ops: Box<SetOps>,
pub set_ops: Box<dyn SetOps>,
/// Contains defuzzification function.
pub defuzz_func: Box<DefuzzFunc>,
}

impl fmt::Debug for InferenceOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("InferenceOptions")
.field("logic_ops", &self.logic_ops)
.field("set_ops", &self.set_ops)
.field(
"defuzz_func",
&format!(
"{} & {:?}",
type_name::<DefuzzFunc>(),
TypeId::of::<DefuzzFunc>()
),
)
.finish()
}
}

/// Structure which contains the evaluation context. Passed to `RuleSet`.
pub struct InferenceContext<'a> {
/// Reference to the Key-Value container, which contains input variables' values.
Expand All @@ -32,6 +51,7 @@ pub struct InferenceContext<'a> {
}

/// Structure which contains the implementation of the fuzzy logic inference mechanism.
#[derive(Debug)]
pub struct InferenceMachine {
/// List of rules to be evaluated.
pub rules: RuleSet,
Expand All @@ -44,26 +64,25 @@ pub struct InferenceMachine {
}

impl InferenceMachine {
/// Constructs the new `InferenceMachine`.
/// Constructs a new `InferenceMachine`.
///
/// This function moves all arguments to the structure.
pub fn new(rules: RuleSet,
universes: HashMap<String, UniversalSet>,
options: InferenceOptions)
-> InferenceMachine {
InferenceMachine {
rules: rules,
universes: universes,
pub fn new(
rules: RuleSet,
universes: HashMap<String, UniversalSet>,
options: InferenceOptions,
) -> Self {
Self {
rules,
universes,
values: HashMap::new(),
options: options,
options,
}
}

/// Updates values in `values`.
///
/// Basically, this method just clones the argument.
pub fn update(&mut self, values: &HashMap<String, f32>) {
self.values = values.clone();
pub fn update(&mut self, values: HashMap<String, f32>) {
self.values = values;
}

/// Computes the result of the fuzzy logic inference.
Expand All @@ -76,6 +95,8 @@ impl InferenceMachine {
options: &self.options,
};
let result = self.rules.compute_all(&mut context);
dbg!(&result);

(result.name.clone(), (*self.options.defuzz_func)(&result))
}
}
10 changes: 7 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
//!
//! User is available to implement his own functions and operations.
pub mod functions;
pub mod set;
pub mod inference;
pub mod ops;
pub mod rules;
pub mod inference;
pub mod set;

#[cfg(test)]
mod test {
use crate::functions::MembershipFactory;
#[test]
fn it_works() {}
fn fuzzy_logic() {
let mem = MembershipFactory::triangular(-15.0, -15.0, 22.0);
mem(-15.0); // -> 1.0
}
}
Loading