import numpy as np
import skfuzzy as fuzz
from matplotlib import pyplot as plt

# Problem: from service quality and food quality to tip amount
x_service = np.arange(0, 10.01, 0.5)#Generate the array [0.0, 0.5, 1.0, 1.5, ..., 9.5, 10.0]
x_food = np.arange(0, 10.01, 0.5)
x_tip = np.arange(0, 25.01, 1.0)#Generate the array [0.0, 1.0, 2.0, 3.0, ..., 24.0, 25.0]

# Membership functions
service_low = fuzz.trimf(x_service, [0, 0, 5])
service_middle = fuzz.trimf(x_service, [0, 5, 10])
service_high = fuzz.trimf(x_service, [5, 10, 10])

food_low = fuzz.gaussmf(x_food, 0, 2) #mean(center) & standard deviation(width) of gaussian mf
food_middle = fuzz.gaussmf(x_food, 5, 2)
food_high = fuzz.gaussmf(x_food, 10, 2)

tip_low = fuzz.trimf(x_tip, [0, 0, 13])
tip_middle = fuzz.trimf(x_tip, [0, 13, 25])
tip_high = fuzz.trimf(x_tip, [13, 25, 25])

# Input: service score and food score
service_score = 9.8
food_score = 6.5

# Fuzzification||Compute the degree of membership of "service_score" in functions "service_low/middle/high"
service_low_degree = fuzz.interp_membership(x_service, service_low, service_score)#9.8
service_middle_degree = fuzz.interp_membership(x_service, service_middle, service_score)#9.8
service_high_degree = fuzz.interp_membership(x_service, service_high, service_score)#9.8

food_low_degree = fuzz.interp_membership(x_food, food_low, food_score)#6.5
food_middle_degree = fuzz.interp_membership(x_food, food_middle, food_score)#6.5
food_high_degree = fuzz.interp_membership(x_food, food_high, food_score)#6.5

# Create a plot that includes all subplots
fig_scale_x = 2.0 #Figure 2 times wider than default in Matplotlib (6.4 inches) 
fig_scale_y = 1.5 #Figure 1.5 times taller than default in Matplotlib (4.8 inches)
fig = plt.figure(figsize=(6.4 * fig_scale_x, 4.8 * fig_scale_y))
row = 2
col = 3

plt.subplot(row, col, 1)
plt.title("Service Quality")
plt.plot(x_service, service_low, label="low", marker=".")
plt.plot(x_service, service_middle, label="middle", marker=".")
plt.plot(x_service, service_high, label="high", marker=".")
plt.legend(loc="upper left")

plt.subplot(row, col, 2)
plt.title("Food Quality")
plt.plot(x_food, food_low, label="low", marker=".")
plt.plot(x_food, food_middle, label="middle", marker=".")
plt.plot(x_food, food_high, label="high", marker=".")
plt.legend(loc="upper left")

plt.subplot(row, col, 3)
plt.title("Tip")
plt.plot(x_tip, tip_low, label="low", marker=".")
plt.plot(x_tip, tip_middle, label="middle", marker=".")
plt.plot(x_tip, tip_high, label="high", marker=".")
plt.legend(loc="upper left")

# =======================================
# Mamdani (max-min) inference method:
# 1) low_degree <-> tip_low
# 2) middle_degree <-> tip_middle
# 3) high_degree <-> tip_high

# =======================================

#Fuzzy Rules: Mamdani Inference

# bad food OR bad service
low_degree = np.fmax(service_low_degree, food_low_degree)#np.fmax() represents the logical OR for fuzzy sets

# medium service
middle_degree = service_middle_degree

# good food OR good service
high_degree = np.fmax(service_high_degree, food_high_degree)

plt.subplot(row, col, 4)
plt.title("")
t = ("FUZZY RULES\n"
     "\n"
     "Bad food OR bad service <-> low tip\n"
     "Medium service <-> middle tip\n"
     "Good food OR good service <-> high tip")
plt.text(0.04, 0.5, t)

#Activation levels for "low", "middle", "high" of the output variable "tip"
#Find how much each rule is applicable based on input (and how much it contributes to output)
activation_low = np.fmin(low_degree, tip_low)#Calculate the degree where both "low_degree" and "tip_low" are TRUE 
activation_middle = np.fmin(middle_degree, tip_middle)
activation_high = np.fmin(high_degree, tip_high)#np.fmin() represents the logical AND for fuzzy sets

plt.subplot(row, col, 5)
plt.title("Tip Activation: Mamdani Inference System")
plt.plot(x_tip, activation_low, label="low tip", marker=".")
plt.plot(x_tip, activation_middle, label="middle tip", marker=".")
plt.plot(x_tip, activation_high, label="high tip", marker=".")
plt.legend(loc="upper left")

# Apply the rules:
# Determine the overall strength of the rules in determining the final output
aggregated = np.fmax(activation_low,np.fmax(activation_middle, activation_high))

# Defuzzification
# Calculate the centroid
tip_centroid = fuzz.defuzz(x_tip, aggregated, 'centroid') #from skfuzzy library

print(tip_centroid) #19.86

plt.subplot(row, col, 6)
plt.title("Aggregation and Defuzzification")
plt.plot(x_tip, aggregated, label="fuzzy result", marker=".")
plt.plot(tip_centroid, 0.0, label="centroid", marker="o")
plt.legend(loc="upper left")

plt.show()