A lightweight fuzzy logic library inspired by Mamdani
The primary goal of this project is to to provide a tested library of C# classes and functions that support the development of fuzzy logic controllers within C# projects.
I have intentionally avoided using interpreted strings to name fuzzy variables, define fuzzy rules, etc. Instead I have chosen to embrace C#'s built-in features for symbol naming and anonymous functions. This approach reduces complexity and improves performance at the cost of being less purist which I feel is a good tradeoff for a lightweight library.
Here is an overview of the API. Refer to the source code for details. There is also an example in the next section, or feel free to look at the unit tests if that is helpful.
static class Imagibee.Fuzzy- contains the whole libraryclass Imagibee.Fuzzy.Input- for defining trapezoidal, triangular, or box membership functions and fuzzifying physical valuesclass Imagibee.Fuzzy.InputGroup- a convenient way to fuzzify a group of inputs that derive their fuzzy values from the same physical valueclass Imagibee.Fuzzy.Rule- for combining fuzzy inputs into IF/THEN rulesfunction Imagibee.Fuzzy.DefuzzifyByCentroid- defuzzify rules to a physical valuefunction Imagibee.Fuzzy.DefineInputsByPeaks- An even more convenient way to define inputs that should work for most casesclass Imagibee.Fuzzy.PeakDefinition- used by DefineInputsByPeaks
Here is an example that demonstrates how you might want to use this library. It shows how you could implement a fuzzy-logic tip calculator. The kind of thing you would use to calculate a tip when you eat at a restaraunt. For the sake of this exampe, the physical value of the tip ranges between 7.5% to 25%. The tip is the ultimate value we want to get from our calculator so we can pay our waiter fairly. The service rating ranges from 1-5 stars, and it is a physical input value based on how good you thought the service was. The food rating also ranges from 1-5 stars, and it is another physical input value based on how good you thought the food was.
Here are the rules ...
IFthe service was excellentTHENthe tip should be generousIFthe service was okTHENthe tip should be averageIFthe service was poorORthe food was terribleTHENthe tip should be low
And here is how I would code the rules ...
using Imagibee;
public class MyTipCalculator
{
// Construct a MyTipCalculator
public MyTipCalculator(
double lowTip,
double averageTip,
double generousTip)
{
// Define membership function trapezoids based on physical star values
serviceWasExcellent = new(3, 5, 5, double.MaxValue);
serviceWasOk = new(1, 3, 3, 5);
serviceWasPoor = new(double.MinValue, 1, 1, 3);
foodWasTerrible = new(double.MinValue, 1, 1, 3);
#if NET8_0_OR_GREATER
// If you are using .net8 or later you can use params instead of explicit arrays
service = new Fuzzy.InputGroup(
serviceWasPoor, serviceWasOk, serviceWasExcellent);
#else
service = new Fuzzy.InputGroup(
new Fuzzy.Input[]
{
serviceWasPoor,
serviceWasOk,
serviceWasExcellent
});
#endif
// Define the fuzzy IF/THEN rules
rules = new Fuzzy.Rule[]
{
new(generousTip, () => serviceWasExcellent.FX),
new(averageTip, () => serviceWasOk.FX),
new(lowTip, () => Fuzzy.OR(serviceWasPoor.FX, foodWasTerrible.FX)),
};
}
public double Calculate(double serviceStars, double foodStars)
{
// Convert physical star values to fuzzy values
service.Fuzzify(serviceStars);
foodWasTerrible.Fuzzify(foodStars);
// Apply rules to convert fuzzy inputs to a physical tip value
return Fuzzy.DefuzzifyByCentroid(rules);
}
// private data
readonly Fuzzy.Input serviceWasExcellent;
readonly Fuzzy.Input serviceWasOk;
readonly Fuzzy.Input serviceWasPoor;
readonly Fuzzy.Input foodWasTerrible;
readonly Fuzzy.InputGroup service;
readonly Fuzzy.Rule[] rules;
}And here are the tests that were used to validate this example ...
MyTipCalculator tip = new(7.5, 15, 25);
Assert.AreEqual(25, tip.Calculate(5, 3), ALLOWEDERROR);
Assert.AreEqual(20, tip.Calculate(4, 3), ALLOWEDERROR);
Assert.AreEqual(17.5, tip.Calculate(3.5, 3), ALLOWEDERROR);
Assert.AreEqual(15, tip.Calculate(3, 3), ALLOWEDERROR);
Assert.AreEqual(14.1666666, tip.Calculate(3.5, 2), ALLOWEDERROR);
Assert.AreEqual(12.5, tip.Calculate(3, 2), ALLOWEDERROR);
Assert.AreEqual(11.25, tip.Calculate(3, 1), ALLOWEDERROR);
Assert.AreEqual(10, tip.Calculate(2, 1), ALLOWEDERROR);
Assert.AreEqual(7.5, tip.Calculate(1, 1), ALLOWEDERROR);Run Scripts/test.
Report and track issues here.
To make minor changes (such as bug fixes) simply make a pull request. Please open an issue to discuss other changes.