using System; using System.Collections.Generic; using System.Linq; namespace FuzzyLogicPCL.FuzzySets { /// /// Fuzzy set class : general methods /// public class FuzzySet { /// /// List of points describing the fuzzy set /// protected List Points; /// /// Minimal value of the fuzzy set /// protected double Min {get;set;} /// /// Maximal value of the fuzzy set /// protected double Max { get; set; } /// /// Simple constructor /// /// Min. value /// Max. value public FuzzySet(double min, double max) { this.Points = new List(); this.Min = min; this.Max = max; } /// /// Add a point to the fuzzy set. It will be sorted to be at the good place /// /// New point to add public void Add(Point2D pt) { Points.Add(pt); Points.Sort(); } /// /// Add a point to the fuzzy set. It will be sorted to be at the good place /// /// New point x coordinate /// New point y coordinate (degree) public void Add(double x, double y) { Point2D pt = new Point2D(x, y); Add(pt); } /// /// Truth degree at a special x value. It is a simple line interpolation. /// /// The x coordinate /// The truth degree at this point public double DegreeAtValue(double XValue) { // Check value in range (else, degree is null) if (XValue < Min || XValue > Max) { return 0; } // Compute value from interpolation : we locate the before and after point Point2D before = Points.LastOrDefault(pt => pt.X <= XValue); Point2D after = Points.FirstOrDefault(pt => pt.X >= XValue); if (before.Equals(after)) { // x is the coordinate of a defined point return before.Y; } else { // x is between two points, we compute the interpolated value return (((before.Y - after.Y) * (after.X - XValue) / (after.X - before.X)) + after.Y); } } /// /// Compute the centroid (or x coordinate of the center of gravity) of a fuzzy set. Used for defuzzification /// /// The centroid x value public double Centroid() { // Less than two points : no area, so no centroid if (Points.Count < 2) { return 0; } else { // We compute the total area, and the ponderated one double ponderatedArea = 0; double totalArea = 0; double localArea; Point2D oldPt = null; foreach (Point2D newPt in Points) { if (oldPt != null) { // Centroids computation if (oldPt.Y == newPt.Y) { // For a rectangle : at the center value localArea = oldPt.Y * (newPt.X - oldPt.X); totalArea += localArea; ponderatedArea += ((newPt.X - oldPt.X) / 2 + oldPt.X) * localArea; } else { // We have two geometric shapes : a rectangle (at half) and a triangle (at 1/3 or 2/3 depending on the slope) // For the rectangle localArea = Math.Min(oldPt.Y, newPt.Y) * (newPt.X - oldPt.X); totalArea += localArea; ponderatedArea += ((newPt.X - oldPt.X) / 2 + oldPt.X) * localArea; // For the triangle localArea = (newPt.X - oldPt.X) * (Math.Abs(newPt.Y - oldPt.Y)) / 2; totalArea += localArea; if (newPt.Y > oldPt.Y) { ponderatedArea += (2.0 / 3.0 * (newPt.X - oldPt.X) + oldPt.X) * localArea; } else { ponderatedArea += (1.0 / 3.0 * (newPt.X - oldPt.X) + oldPt.X) * localArea; } } } oldPt = newPt; } // Return the centroid, that is the division of the two areas return ponderatedArea / totalArea; } } /// /// ToString method, returns the fuzzy set with the format : [min-max] : list of points /// /// The fuzzy set returns as a string public override String ToString() { String result = "[" + Min + "-" + Max + "]:"; foreach (Point2D pt in Points) { result += pt.ToString(); //"(" + pt.X + ";" + pt.Y + ")"; } return result; } /// /// ! Operator (NOT) : inverse the fuzzy set (1- original value) /// /// The original fuzzy set /// A new fuzzy set public static FuzzySet operator !(FuzzySet fs) { FuzzySet result = new FuzzySet(fs.Min, fs.Max); foreach (Point2D pt in fs.Points) { result.Add(new Point2D(pt.X, 1 - pt.Y)); } return result; } /// /// Compute the multiplication of a fuzzy set : all y values are "value" times /// /// Original fuzzy set /// Number /// New fuzzy set (fuzzy set * value) public static FuzzySet operator *(FuzzySet fs, double value) { FuzzySet result = new FuzzySet(fs.Min, fs.Max); foreach (Point2D pt in fs.Points) { result.Add(new Point2D(pt.X, pt.Y * value)); } return result; } /// /// Equality operator /// /// First fuzzy set /// Second fuzzy set /// True if the two fuzzy sets have the same range and the same points, false otherwise public static Boolean operator == (FuzzySet fs1, FuzzySet fs2) { return fs1.ToString().Equals(fs2.ToString()); } /// /// Difference operator /// /// First fuzzy set /// Second fuzzy set /// True if the two fuzzy sets have not the same range or not the same points public static Boolean operator != (FuzzySet fs1, FuzzySet fs2) { return !(fs1 == fs2); } /// /// Intersection operator : min(fs1, fs2) /// /// First fuzzy set /// Second fuzzy set /// New fuzzy set representing the intersection public static FuzzySet operator &(FuzzySet fs1, FuzzySet fs2) { return Merge(fs1, fs2, Math.Min); } /// /// Union operator : max(fs1, fs2) /// /// First fuzzy set /// Second fuzzy set /// New fuzzy set representing the union public static FuzzySet operator |(FuzzySet fs1, FuzzySet fs2) { return Merge(fs1, fs2, Math.Max); } /// /// Private method computing the merging of two fuzzy sets /// /// First fuzzy set /// Second fuzzy set /// Function for merging (eg min, or max), with two parameters (two double values) and the one to keep (double) /// The resulting fuzzy set private static FuzzySet Merge(FuzzySet fs1, FuzzySet fs2, Func MergeFt) { // New Fuzzy set FuzzySet result = new FuzzySet(Math.Min(fs1.Min, fs2.Min), Math.Max(fs1.Max, fs2.Max)); // Creation of iterators on lists + initialization List.Enumerator enum1 = fs1.Points.GetEnumerator(); List.Enumerator enum2 = fs2.Points.GetEnumerator(); enum1.MoveNext(); enum2.MoveNext(); Point2D oldPt1 = enum1.Current; // Relative positions of fuzzy sets (to know when they intersect) int relativePosition = 0; int newRelativePosition = Math.Sign(enum1.Current.Y - enum2.Current.Y); // Loop while there are points in the two collections Boolean endOfList1 = false; Boolean endOfList2 = false; while (!endOfList1 && !endOfList2) { // New x values double x1 = enum1.Current.X; double x2 = enum2.Current.X; // New current position relativePosition = newRelativePosition; newRelativePosition = Math.Sign(enum1.Current.Y - enum2.Current.Y); if (relativePosition != newRelativePosition && relativePosition != 0 && newRelativePosition != 0) { // Positions have changed, so we have to compute the intersection and add it // Compute the points coordinates double x = (x1 == x2 ? oldPt1.X : Math.Min(x1, x2)); double xPrime = Math.Max(x1, x2); // Intersection double slope1 = (fs1.DegreeAtValue(xPrime) - fs1.DegreeAtValue(x)) / (xPrime - x); double slope2 = (fs2.DegreeAtValue(xPrime) - fs2.DegreeAtValue(x)) / (xPrime - x); double delta = (fs2.DegreeAtValue(x) - fs1.DegreeAtValue(x)) / (slope1 - slope2); // Add point result.Add(x + delta, fs1.DegreeAtValue(x + delta)); // Go on if (x1 < x2) { oldPt1 = enum1.Current; endOfList1 = !(enum1.MoveNext()); } else if (x1 > x2) { endOfList2 = !(enum2.MoveNext()); } } else if (x1 == x2) { // The two points are on the same X, so we take the good value (eg min or max) result.Add(x1, MergeFt(enum1.Current.Y, enum2.Current.Y)); oldPt1 = enum1.Current; endOfList1 = !(enum1.MoveNext()); endOfList2 = !(enum2.MoveNext()); } else if (x1 < x2) { // Fs1 point is first, we add the value (eg min or max) between the enum1 point and the degree for fs2 result.Add(x1, MergeFt(enum1.Current.Y, fs2.DegreeAtValue(x1))); oldPt1 = enum1.Current; endOfList1 = !(enum1.MoveNext()); } else { // This time, it's fs2 first result.Add(x2, MergeFt(fs1.DegreeAtValue(x2), enum2.Current.Y)); endOfList2 = !(enum2.MoveNext()); } } // Add end points if (!endOfList1) { while (!endOfList1) { result.Add(enum1.Current.X, MergeFt(0, enum1.Current.Y)); endOfList1 = !enum1.MoveNext(); } } else if (!endOfList2) { while (!endOfList2) { result.Add(enum2.Current.X, MergeFt(0, enum2.Current.Y)); endOfList2 = !enum2.MoveNext(); } } return result; } } }