I recently came across some old C++ code I wrote about 10 years ago to assist fuzzy logic reasoning. This program is now posted on GitHub at https://github.com/badassdatascience/fuzzy-logic-toolkit and is described below. An example of the tool in action (with code) follows the description.

# Fuzzy Logic

Suppose we have a numerical value for distance to a place of interest, and we want to fit it into one of the following categories: “very near”, “near”, “neither near nor far”, “far”, or “very far”. To complicate matters, suppose that experts can’t agree on what values of the distance define membership in each category, so they define functions that estimate the degree of membership in each category for each possible value of the distance. For example, such functions might place a given distance value at 5% “very close”, 20% “close”, and 75% “neither close nor far”. Therefore if one had to choose a category for a given distance they could only specify “fuzzy” levels of membership in each category. This is the crux of fuzzy set theory.

Now suppose that some reasoning based on the selection of the category is required. Perhaps another fuzzy group of categories is involved, such as cost (“cheap”, “modest”, “expensive”). The reasoning could look like boolean logic: e.g, IF “cheap” AND “near” THEN “eat lunch there”. However, the boundaries for “cheap” and “near” are fuzzy and therefore the computation is imprecise. This is fuzzy logic.

The code described below defines C++ classes for describing functions specifying membership in a category, a class for specifying the categories themselves, and a class for compiling the possible categories into a domain of related categories. There is also a class for working with the numerical values of interest (e.g., distance in the case described above), enabling fuzzy boolean operations on those numerical values and estimating “crisp” (non-fuzzy) numerical conclusions from the fuzzy operations.

# UML Model

A UML model of the program’s classes is shown below. Each of the classes is described following the image.

# Membership Functions

Membership functions are used to define the ranges and computation method with which numerical values are evaluated for degree of membership in a fuzzy category. Each fuzzy category (called a LinguisticSet in the code and UML model) will have a membership function associated with it.

We start with an abstract class called MembershipFunction. From this abstract class we generate four child classes corresponding to different ways of evaluating membership. They are StandardMBF_S, StandardMBF_Z, StandardMBF_Pi, and StandardMBF_Lambda. Objects instantiated from these child classes are associated with a LinguisticSet object during instantiation of the LinguisticSet object.

We now examine one of the membership functions, StandardMBF_Z, in more detail: This is a “Z” function that returns one if a given numerical value is below a minimum value, zero if the test value is above a maximum value, and a linear interpolation between one and zero if the test value falls between the minimum and maximum. An example of what this function looks like is:

Examples of the other three membership functions are:

# Categories (LinguisticSet)

Categories, called “linguistic sets” in the code, encapsulate a membership function and a name. The membership function is used to compute degree of membership of a value in the category. Linguistic sets are combined into LinguistDomain objects according to a common variable, such as distance in the description above.

# LinguisticDomain

“Linguistic domains” are collections of categories that are related by a variable, such as distance in the case described above. They might define for instance the set (“very close”, “close”, “neither close nor far”, “far”, “very far”). These are used to relate linguistic set objects by a common variable.

# FuzzyValue

Once a linguistic domain is established, a FuzzyValue object can be defined on it that enables fuzzy reasoning using the categories in the domain. This class plays the role of the variable under consideration by the elements of the linguistic domain, and can be set to an exact number if that number is known at the beginning of the computations. For example, one might be executing a fuzzy reasoning procedure involving domains A, B, and C, where the FuzzyValue result for C is computed using known measurements and membership functions for A and B. The example program shown below will illustrate how to use FuzzyValue.

# Example Program

The following example comes from Constantin von Altrock’s book “Fuzzy Logic & Neurofuzzy Applications Explained“, which is a very accessible introduction to the subject. It consists of a crane operation where a program must decide through fuzzy reasoning how much power to apply to the crane, given known measurements of distance and angle. This code is also contained in the file “main.cc” that comes with the program when you check it out from GitHub.

The program starts by defining membership functions and linguistic sets for the various options for distance, e.g., “too far” or “medium”. These are then compiled into a linguistic domain for distance. Similarly, membership functions, linguistic sets, and linguistic domains are defined for angle measurements and power values (power values will be computed).

The program then declares values for the known quantities distance (12 yards) and angle (4 degrees). These values are associated with their respective domain upon declaration. It then declares a fuzzy value for power, but does not specify a known measurement, since we are computing it using fuzzy reasoning.

We then apply the following logic: If distance is medium and angle is small positive, apply positive medium power. If distance is medium and angle is zero, apply zero power. Finally, if distance is far and angle is zero, apply positive medium power. Note that “small positive”, “positive medium”, “medium”, “zero”, and “far” are all categories not exact values.

To reason through these categories to choose an outcome, the measured values of distance and angle are each assigned degree of membership to each category in their respective domains. Then the logic is applied through fuzzy reasoning to assign probabilities of power membership in the power categories. Finally, an exact value for power is inferred from the computed probabilities of group membership and reported.

#include "FuzzyValue.hh" #include <iostream> int main() { std::cout << std::endl; std::cout << "This program demonstrates Dan Williams' yet untitled" << std::endl; std::cout << "fuzzy logic toolkit." << std::endl; std::cout << std::endl; std::cout << "It implements the crane example in Constantin von Altrock's book " << std::endl; std::cout << "'Fuzzy Logic & Neurofuzzy Applications Explained'" << std::endl; std::cout << std::endl; std::cout << "Distance sensor reads 12 yards." << std::endl; std::cout << "Angle sensor reads 4 degrees." << std::endl; /* create and name a linguistic domain for distance */ LinguisticDomain* distance_domain = new LinguisticDomain("distance_domain"); /* define some linguistic values and membership functions for the distance domain */ // too_far StandardMBF_Z* distance_domain_too_far_membership = new StandardMBF_Z(-5, 0); LinguisticSet* distance_domain_too_far = new LinguisticSet(distance_domain_too_far_membership, "too_far"); // zero StandardMBF_Lambda* distance_domain_zero_membership = new StandardMBF_Lambda(-5, 0, 5); LinguisticSet* distance_domain_zero = new LinguisticSet(distance_domain_zero_membership, "zero"); // close StandardMBF_Lambda* distance_domain_close_membership = new StandardMBF_Lambda(0, 5, 10); LinguisticSet* distance_domain_close = new LinguisticSet(distance_domain_close_membership, "close"); // medium StandardMBF_Lambda* distance_domain_medium_membership = new StandardMBF_Lambda(5, 10, 30); LinguisticSet* distance_domain_medium = new LinguisticSet(distance_domain_medium_membership, "medium"); // far StandardMBF_S* distance_domain_far_membership = new StandardMBF_S(10, 30); LinguisticSet* distance_domain_far = new LinguisticSet(distance_domain_far_membership, "far"); /* Add the linguistic values to the distance domain */ distance_domain->addLinguisticSet(distance_domain_too_far); distance_domain->addLinguisticSet(distance_domain_zero); distance_domain->addLinguisticSet(distance_domain_close); distance_domain->addLinguisticSet(distance_domain_medium); distance_domain->addLinguisticSet(distance_domain_far); /* create and name a linguistic domain for angle */ LinguisticDomain* angle_domain = new LinguisticDomain("angle_domain"); /* define some linguistic values and membership functions for the angle domain */ // neg_big StandardMBF_Z* angle_domain_neg_big_membership = new StandardMBF_Z(-45, -5); LinguisticSet* angle_domain_neg_big = new LinguisticSet(angle_domain_neg_big_membership, "neg_big"); // neg_small StandardMBF_Lambda* angle_domain_neg_small_membership = new StandardMBF_Lambda(-45, -5, 0); LinguisticSet* angle_domain_neg_small = new LinguisticSet(angle_domain_neg_small_membership, "neg_small"); // zero StandardMBF_Lambda* angle_domain_zero_membership = new StandardMBF_Lambda(-5, 0, 5); LinguisticSet* angle_domain_zero = new LinguisticSet(angle_domain_zero_membership, "zero"); // pos_small StandardMBF_Lambda* angle_domain_pos_small_membership = new StandardMBF_Lambda(0, 5, 45); LinguisticSet* angle_domain_pos_small = new LinguisticSet(angle_domain_pos_small_membership, "pos_small"); // pos_big StandardMBF_S* angle_domain_pos_big_membership = new StandardMBF_S(5, 45); LinguisticSet* angle_domain_pos_big = new LinguisticSet(angle_domain_pos_big_membership, "pos_big"); /* Add the linguistic values to the angle domain */ angle_domain->addLinguisticSet(angle_domain_neg_big); angle_domain->addLinguisticSet(angle_domain_neg_small); angle_domain->addLinguisticSet(angle_domain_zero); angle_domain->addLinguisticSet(angle_domain_pos_small); angle_domain->addLinguisticSet(angle_domain_pos_big); /* create and name a linguistic domain for power */ LinguisticDomain* power_domain = new LinguisticDomain("power_domain"); /* define some linguistic values and membership functions for the power domain */ // neg_high StandardMBF_Lambda* power_domain_neg_high_membership = new StandardMBF_Lambda(-30, -25, -8); LinguisticSet* power_domain_neg_high = new LinguisticSet(power_domain_neg_high_membership, "neg_high"); // neg_medium StandardMBF_Lambda* power_domain_neg_medium_membership = new StandardMBF_Lambda(-25, -8, 0); LinguisticSet* power_domain_neg_medium = new LinguisticSet(power_domain_neg_medium_membership, "neg_medium"); // zero StandardMBF_Lambda* power_domain_zero_membership = new StandardMBF_Lambda(-8, 0, 8); LinguisticSet* power_domain_zero = new LinguisticSet(power_domain_zero_membership, "zero"); // pos_medium StandardMBF_Lambda* power_domain_pos_medium_membership = new StandardMBF_Lambda(0, 8, 25); LinguisticSet* power_domain_pos_medium = new LinguisticSet(power_domain_pos_medium_membership, "pos_medium"); // pos_high StandardMBF_Lambda* power_domain_pos_high_membership = new StandardMBF_Lambda(8, 25, 20); LinguisticSet* power_domain_pos_high = new LinguisticSet(power_domain_pos_high_membership, "pos"); /* add the linguistic values to the power domain */ power_domain->addLinguisticSet(power_domain_neg_high); power_domain->addLinguisticSet(power_domain_neg_medium); power_domain->addLinguisticSet(power_domain_zero); power_domain->addLinguisticSet(power_domain_pos_medium); power_domain->addLinguisticSet(power_domain_pos_high); /* "Fuzzify" sensor readings */ FuzzyValue* distance = new FuzzyValue(distance_domain); distance->setCrispValue(12); FuzzyValue* angle = new FuzzyValue(angle_domain); angle->setCrispValue(4); /* Create a fuzzy variable to store power inference calculations */ FuzzyValue* power = new FuzzyValue(power_domain); /* Fuzzy inference of power value */ power->OR_setSetMembership( distance->AND("medium", angle, "pos_small"), "pos_medium" ); power->OR_setSetMembership( distance->AND("medium", angle, "zero"), "zero" ); power->OR_setSetMembership( distance->AND("far", angle, "zero"), "pos_medium" ); /* "Defuzzify" infered power value */ long double power_setting; power_setting = power->getCrispValue(); std::cout << "Set power to " << power_setting << " kW." << std::endl; std::cout << std::endl; return 0; }