diff --git a/doc/x:y.png b/doc/x:y.png deleted file mode 100644 index fb34baf..0000000 Binary files a/doc/x:y.png and /dev/null differ diff --git a/docs/operations/x:y.png b/docs/operations/x:y.png deleted file mode 100644 index fb34baf..0000000 Binary files a/docs/operations/x:y.png and /dev/null differ diff --git a/ipynb/fuzzy_plots.ipynb b/ipynb/fuzzy_plots.ipynb index d5fa720..7a2af67 100644 --- a/ipynb/fuzzy_plots.ipynb +++ b/ipynb/fuzzy_plots.ipynb @@ -141,7 +141,9 @@ { "cell_type": "code", "execution_count": 13, - "metadata": {}, + "metadata": { + "collapsed": false + }, "outputs": [ { "data": { diff --git a/ipynb/sample_points.ipynb b/ipynb/sample_points.ipynb index 08df9b4..478ed3f 100644 --- a/ipynb/sample_points.ipynb +++ b/ipynb/sample_points.ipynb @@ -6,11 +6,7 @@ "metadata": {}, "outputs": [ { - "data": { - "application/javascript": [ - "IPython.notebook.set_autosave_interval(0)" - ] - }, + "data": {}, "metadata": {}, "output_type": "display_data" }, diff --git a/phuzzy/__init__.py b/phuzzy/__init__.py index f2761e1..89a750a 100644 --- a/phuzzy/__init__.py +++ b/phuzzy/__init__.py @@ -20,6 +20,7 @@ from phuzzy.shapes import FuzzyNumber, Trapezoid, Triangle, Uniform from phuzzy.shapes.superellipse import Superellipse from phuzzy.shapes.truncnorm import TruncGenNorm, TruncNorm +from phuzzy.shapes.skewnorm import Skewnorm class Analysis(object): def __init__(self, **kwargs): diff --git a/phuzzy/fuzzification/__init__.py b/phuzzy/fuzzification/__init__.py new file mode 100644 index 0000000..cdb843b --- /dev/null +++ b/phuzzy/fuzzification/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from phuzzy.fuzzification import fuzzy_fitting + + + + +if __name__ == "__main__": + pass diff --git a/phuzzy/fuzzification/fuzzy_fitting.py b/phuzzy/fuzzification/fuzzy_fitting.py new file mode 100644 index 0000000..e7da6e0 --- /dev/null +++ b/phuzzy/fuzzification/fuzzy_fitting.py @@ -0,0 +1,188 @@ +import numpy as np +import pandas as pd +import scipy.stats as st + +import phuzzy.mpl as phm +import phuzzy.mpl.plots + +import warnings +import matplotlib.pyplot as plt +plt.style.use('seaborn') + + + + +class Data_Fitting(object): + + def __init__(self): + pass + + + def best_fit_distribution(self, data, number_of_alpha_levels=6, bins=False, bootstrap=False, ax=True, + filepath=None): + """ + Routine to determin automatically a suiting membership function to a given data set + + :param data: input array + :param number_of_alpha_levels: level of discritization of the membership function + :param bins: number of histogram bins + :param bootstrap: applying / not applying bootstrap algorithm + :param ax: plot command + :param filepath: safe plot + + :return: fuzzy parameter + """ + + """Model data by finding best fit distribution to data""" + + if bootstrap: + data = self._bootstrap(data) + + # Get histogram of original data + if bins: + y, x = np.histogram(data, bins=bins, density=True) + else: + y, x = np.histogram(data, bins='fd', density=True) + + y, x = np.histogram(data, density=True) + x = (x + np.roll(x, -1))[:-1] / 2.0 + DISTRIBUTIONS = [ + st.triang, + st.norm, + st.uniform + ] + # Best holders + best_distribution = st.triang + best_params = (0.0, 1.0) + best_sse = np.inf + dists = [] + # Estimate distribution parameters from data + for distribution in DISTRIBUTIONS: + # Try to fit the distribution + try: + # Ignore warnings from data that can't be fit + with warnings.catch_warnings(): + warnings.filterwarnings('ignore') + # fit dist to data + params = distribution.fit(data) + # Separate parts of parameters + arg = params[:-2] + loc = params[-2] + scale = params[-1] + # Calculate fitted PDF and error with fit in distribution + pdf = distribution.pdf(x, loc=loc, scale=scale, *arg) + sse = np.sum(np.power(y - pdf, 2.0)) + dist = distribution(loc=loc, scale=scale, *arg) + dists.append([dist, loc, scale, arg]) + # if axis pass in add to plot + #try: + # if ax: + # pd.Series(pdf, x).plot(ax=ax) + #except Exception: + # pass + # identify if this distribution is better + if best_sse > sse > 0: + best_distribution = distribution.name + best_params = params + best_sse = sse + except Exception: + pass + + x = np.linspace(data.min(),data.max(), 500) + + if best_distribution == 'norm': + dist, loc, scale, args = dists[0] + a = max(abs(loc-data.min()), abs(loc-data.max())) + fuzzy_var = phm.TruncNorm(alpha0=[loc-a, loc+a], alpha1=[data.mean()], + number_of_alpha_levels=number_of_alpha_levels) + #return (tgn, tgn.get_shape()) + + elif best_distribution == 'triang': + dist, loc, scale, args = dists[0] + y = dist.pdf(x) + y /= y.max() + df = pd.DataFrame({"x":x, "y":y}) + loc = df.loc[df.y.idxmax()].x + fuzzy_var = phm.Triangle(alpha0=[data.min(), data.max()], alpha1=[loc], + number_of_alpha_levels=number_of_alpha_levels) + #return (tria, tria.get_shape()) + + elif best_distribution == 'uniform': + dist, loc, scale, args = dists[1] + fuzzy_var = phm.Uniform(alpha0=[data.min(),data.max()], alpha1=[1.00,1.00], + number_of_alpha_levels=number_of_alpha_levels) + #return (uni, uni.get_shape()) + else: + raise ValueError + + + if ax: + fig, ax = plt.subplots(1,1, figsize=(8,5)) + x = np.linspace(data.min(),data.max(), 500) + y = dist.pdf(x) + y /= y.max() + df = pd.DataFrame({"x":x, "y":y}) + ax.plot(x,y, label="hist fit", color="g", lw=2, alpha=.8, ls="--") + ax.axvline(data.min(), dashes=[5,2,1,2], c="k", alpha=.5) + ax.axvline(data.max(), dashes=[5,2,1,2], c="k", alpha=.5) + ax.axvline(loc, dashes=[5,2,1,2], c="k", alpha=.5) + ax.scatter(data, np.ones_like(data)*(-.1), color="g", alpha=.4, label="data") + ax.set_ylabel(r"$\alpha$ [$-$]") + fuzzy_var_shape = fuzzy_var.get_shape() + ax.plot(fuzzy_var_shape.x, fuzzy_var_shape.alpha, label="Phuzzy Variable", alpha=.4, color="r") + + if bins: + phuzzy.mpl.plots.plot_hist(data, bins=bins, ax=ax, normed=True, color="b", + filled=True, alpha=.3, label='histo data') + else: + phuzzy.mpl.plots.plot_hist(data, ax=ax, bins='fd', normed=True, color="b", + filled=True, alpha=.3, label='histo data') + + if best_distribution == 'norm': ax.set_title('TruncNorm') + elif best_distribution == 'triang': ax.set_title('Triangle') + elif best_distribution == 'uniform': ax.set_title('Uniform') + + ax.legend(fancybox=True, framealpha=0.5, loc=1) + plt.show() + + if filepath: + fig.savefig(filepath, dpi=360) + + + return fuzzy_var + + + def _bootstrap(self,data): + """ + Bootstrapping Algorithm for stretching data + + :param data: Input Data + :return: Boostrapped Data + """ + xbar = np.zeros(shape=1000) + for i in range(1000): + sample = data[np.random.randint(0,len(data),size=len(data))] + xbar[i] = np.mean(sample) + np.append(xbar,data.min()) + np.append(xbar,data.max()) + return xbar + + + def fit_plot(self,data,fuzzy_var,dist): + + fig, ax = plt.subplots(1,1, figsize=(10,5)) + + x = np.linspace(data.min(),data.max(), 500) + y = dist.pdf(x) + y /= y.max() + df = pd.DataFrame({"x":x, "y":y}) + # pdf = make_pdf(st.norm, [loc, scale]+args) + ax.plot(x,y, label="hist fit", color="g", lw=2, alpha=.8, ls="--") + dist, loc, scale, args = dist[0] + + ax.axvline(data.min(), dashes=[5,2,1,2], c="k", alpha=.5) + ax.axvline(data.max(), dashes=[5,2,1,2], c="k", alpha=.5) + ax.axvline(loc, dashes=[5,2,1,2], c="k", alpha=.5) + ax.scatter(data, np.ones_like(data)*(-.1), color="g", alpha=.4, label="data") + + diff --git a/phuzzy/mpl/__init__.py b/phuzzy/mpl/__init__.py index ac8c621..9623554 100644 --- a/phuzzy/mpl/__init__.py +++ b/phuzzy/mpl/__init__.py @@ -10,6 +10,7 @@ import numpy as np import phuzzy + def extend_instance(obj, cls): """Apply mixins to a class instance after creation""" base_cls = obj.__class__ @@ -25,7 +26,7 @@ def mix_mpl(obj): class MPL_Mixin(): - def plot(self, ax=None, filepath=None, show=False, xlim=None, labels=True, title=False, ppf=None): + def plot(self, ax=None, filepath=None, show=False, xlim=None, labels=True, title=False, ppf=None, defuzzy=None): """plots fuzzy number with mpl""" logging.debug("plots fuzzy number with mpl") df = self.df @@ -43,6 +44,15 @@ def plot(self, ax=None, filepath=None, show=False, xlim=None, labels=True, title ax.grid(c="gray", alpha=.5, lw=.5, dashes=[1, 3]) + if defuzzy is not None: + ax.plot([defuzzy[0], defuzzy[0]], [0, defuzzy[1]], linestyle= ':',color='#3188cb', label=defuzzy[0]) + + if defuzzy is not None and labels is True: + ax.annotate('%.3g' % defuzzy[0], xy=(defuzzy[0], (defuzzy[1]+0.018)), xycoords='data', + xytext=(-2, -9), textcoords='offset points', + horizontalalignment='right', verticalalignment='bottom', alpha=.4) + + xs = np.hstack([df["l"].values, df["r"].values[::-1]]) ys = np.hstack([df["alpha"].values, df["alpha"].values[::-1]]) ax.plot(xs, ys, lw=1, alpha=.7) @@ -60,17 +70,17 @@ def plot(self, ax=None, filepath=None, show=False, xlim=None, labels=True, title ax.set_xlabel('%s' % self.name) ax.set_ylabel(r'$\alpha$') ax.grid(c="gray", alpha=.5, lw=.5, dashes=[1, 3]) - ax.annotate('%.3g' % a0["l"], xy=(a0["l"], a0["alpha"]), xycoords='data', + ax.annotate('%.5g' % a0["l"], xy=(a0["l"], a0["alpha"]), xycoords='data', xytext=(-2, 2), textcoords='offset points', horizontalalignment='right', verticalalignment='bottom', alpha=.4) - ax.annotate('%.3g' % a0["r"], xy=(a0["r"], a0["alpha"]), xycoords='data', + ax.annotate('%.5g' % a0["r"], xy=(a0["r"], a0["alpha"]), xycoords='data', xytext=(2, 2), textcoords='offset points', horizontalalignment='left', verticalalignment='bottom', alpha=.4) a1 = self.alpha1 - ax.annotate('%.3g' % a1["l"], xy=(a1["l"], a1["alpha"]), xycoords='data', + ax.annotate('%.5g' % a1["l"], xy=(a1["l"], a1["alpha"]), xycoords='data', xytext=(-2, 2), textcoords='offset points', horizontalalignment='right', verticalalignment='bottom', alpha=.4) - ax.annotate('%.3g' % a1["r"], xy=(a1["r"], a1["alpha"]), xycoords='data', + ax.annotate('%.5g' % a1["r"], xy=(a1["r"], a1["alpha"]), xycoords='data', xytext=(2, 2), textcoords='offset points', horizontalalignment='left', verticalalignment='bottom', alpha=.4) dx = abs(self.alpha0["r"] - self.alpha0["l"]) @@ -89,7 +99,7 @@ def plot(self, ax=None, filepath=None, show=False, xlim=None, labels=True, title try: fig.tight_layout() if filepath: - fig.savefig(filepath, dpi=90) + fig.savefig(filepath, dpi=360) except UnboundLocalError: pass @@ -206,3 +216,8 @@ class Superellipse(phuzzy.Superellipse, MPL_Mixin): """Superellipse fuzzy number with matplotlib mixin""" def __init__(self, **kwargs): phuzzy.Superellipse.__init__(self, **kwargs) + +class Skewnorm(phuzzy.Skewnorm, MPL_Mixin): + """Superellipse fuzzy number with matplotlib mixin""" + def __init__(self, **kwargs): + phuzzy.Skewnorm.__init__(self, **kwargs) diff --git a/phuzzy/mpl/plots.py b/phuzzy/mpl/plots.py index db0a376..c2cb89c 100644 --- a/phuzzy/mpl/plots.py +++ b/phuzzy/mpl/plots.py @@ -32,6 +32,7 @@ def plot_xy(x, y, height=100, width=200): return fig, axs + def plot_xyz(x, y, z, height=70, width=200): """plot two fuzzy numbers @@ -61,6 +62,7 @@ def plot_xyz(x, y, z, height=70, width=200): return fig, axs + def plot_xy_3d(x, y, height=200, width=200): """plot two fuzzy numbers @@ -156,6 +158,7 @@ def plot_3d(x, y, ax=None, show=False, height=200, width=200): return fig, ax + def plot_hist(x, ax=None, bins=None, normed=1, **kwargs): if bins is None: @@ -179,6 +182,19 @@ def plot_hist(x, ax=None, bins=None, normed=1, **kwargs): return fig, ax +""" +def plot_bar(x,y,ax=None, **kwargs): + + if ax is None: + fig, ax = plt.subplots(1, 1, figsize=(10,5)) + else: + fig = plt.gcf() + + ax.bar(x,y, label=kwargs.get("label"), color=kwargs.get("color", "r")) + + return fig, ax +""" + def plot_cdf(x, method="rossow", ax=None, bins=None, color=None, **kwargs): df=pd.DataFrame({"x":x}) diff --git a/phuzzy/optimization/__init__.py b/phuzzy/optimization/__init__.py new file mode 100644 index 0000000..2863181 --- /dev/null +++ b/phuzzy/optimization/__init__.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from shgo._shgo import SHGO +from scipy.optimize import minimize + +import phuzzy +from phuzzy.mpl import MPL_Mixin +from phuzzy.shapes import FuzzyNumber + +from asteval import Interpreter +from pathlib import Path +#from tqdm import tqdm + +import numpy as np +import pandas as pd +import xarray as xr +from phuzzy.optimization import alphaOpt, sensitivity_analysis + +from SALib.sample import saltelli +from SALib.analyze import sobol + +#from math import * + + + + + +if __name__ == "__main__": + + + v1 = phuzzy.Triangle(alpha0=[1,4], alpha1=[2], name='v1', number_of_alpha_levels=3) + v2 = phuzzy.Triangle(alpha0=[0, 4], alpha1=[1], name='v2', number_of_alpha_levels=5) + v3 = phuzzy.Triangle(alpha0=[1, 4], alpha1=[2], name='v3', number_of_alpha_levels=3) + v4 = phuzzy.Triangle(alpha0=[0,4], alpha1=[2.5], name='v4', number_of_alpha_levels=3) + v5 = phuzzy.Triangle(alpha0=[1, 4], alpha1=[3], name='v5', number_of_alpha_levels=3) + v6 = phuzzy.Triangle(alpha0=[1,5], alpha1=[3], name='v6', number_of_alpha_levels=3) + + input = [v1,v2,v3,v4,v5,v6] + obj_function = '-1*((x[0] - 1) ** 2 + (x[1] + .1) ** 2 + .1 - (x[2] + 2) ** 2 - (x[3] - 0.1) ** 2 - (x[4] * x[5]) ** 2)' + + kwargs = {'fuzzy_variables': input,'obj_function': obj_function} + + + #z = alphaOpt.Alpha_Level_Optimization(**kwargs) + #z.calculation() + + p = phuzzy.Triangle(alpha0=[1,5], alpha1=[3], + number_of_alpha_levels=11, name='x') + + rr = sensitivity_analysis.Fuzzy_Sensitivity_Analysis(**kwargs) + rr.calculation(**kwargs) + rr.barchart_plot(plot=True) + diff --git a/phuzzy/optimization/alphaOpt.py b/phuzzy/optimization/alphaOpt.py new file mode 100644 index 0000000..3929229 --- /dev/null +++ b/phuzzy/optimization/alphaOpt.py @@ -0,0 +1,746 @@ +from shgo._shgo import SHGO +from scipy.optimize import minimize + +from phuzzy.mpl import MPL_Mixin +from phuzzy.shapes import FuzzyNumber + +from asteval import Interpreter +from pathlib import Path +from tqdm import tqdm + +import numpy as np +import pandas as pd +import xarray as xr + + + +class Function(): + def __init__(self, function): + if isinstance(function, str): + self.aeval = Interpreter() + self.exprc = self.aeval.parse(function) + + def func(x): + self.aeval.symtable['x'] = x + return self.aeval.run(self.exprc) + self.func = func # here + + elif callable(function): + self.func = function + + def __call__(self, x): + return self.func(x) + + +class Alpha_Level_Optimization(FuzzyNumber, MPL_Mixin, Function): + + def __init__(self, **kwargs): + """ + :param input_fuzzy_var_list: List of all Fuzzy Variables + :param var_names_list: List of Names of all Fuzzy Variables + :param kwargs: Inputvariables / Fuzzyvariables / Name (name) / Objective Function (obj_function) / Objective Link (obj_link) + """ + + # READ FUZZY INPUT VARIABLES + if kwargs.get('fuzzy_variables') is not None: input_fuzzy_var_list = kwargs.get('fuzzy_variables') + else: raise ValueError('PLEASE IMPORT FUZZY VARIABLES as -- fuzzy_variables --') + + # Check for identical number_of_alpha_levels and adapt + fuzzy_var_list = self._alpha_level_check(input_fuzzy_var_list) + + # Define Bounds of Each Alpha Level + self.global_bounds_DataArray = self._boundary_constraints(fuzzy_var_list) + + + # Filter Objective Function / Link and Safe it + if kwargs.get('obj_function') is not None: self.objective = Function(kwargs.get('obj_function')) + else: raise ValueError('PLEASE IMPORT OBJECTIVE FUNCTION as -- obj_function --') + + # Setup Parameters + if kwargs.get('name') is None: self.name = 'Fuzzy Objective Value' + else: self.name = kwargs.get('name') + + self.x_glob = [] + self.dim = self.global_bounds_DataArray['fuzzy_variable'].size + self.number_of_alpha_lvls = self.global_bounds_DataArray['number_of_alpha_levels'].size + self.best_indi_list_min = [] + self.best_indi_list_max = [] + self.nfev_list_min = [] + self.nfev_list_max = [] + self.nit_list_min = [] + self.nit_list_max = [] + + + def __repr__(self): + return "{}(x:[[{:.3g}, {:.3g}], [{:.3g}, {:.3g}]])".format(self.name, self._df.iloc[0].l, self._df.iloc[0].r, + self._df.iloc[-1].l, self._df.iloc[-1].r) + + + def main(self, n=50, iters=3, optimizer='sobol', progressbar_disable=False, backup=False, start_at=None): + """ + Main Routine calculating the Minimum and Maximum of the Objective on each Alpha Level to generate + the Fuzzy Objective Membershipfunction. + :param n: Number of Sampling Points / Individuals for the Optimization Algorithm + :param iters: Number of max. Iterations per Optimization Loop + :param optimizer: Selected Optimizer Strategy: "sobol" / "simplicial" + :param backup: Creates a Backup Folder saving the result of each Alpha Level Result + :param start_at: Start at certain Alpha Level (Counts starts from Alpha Level 1) + """ + + # Input Variables + self.n = n + self.iters = iters + self.optimizer = optimizer # simplicial / sobol + self.backup = backup + self.start_at = start_at + + zmin_value_list = [] + zmax_value_list = [] + boundlist = [] + + if self.start_at is not None: self._cut_global_blounds() + + for i in range(1, self.global_bounds_DataArray['number_of_alpha_levels'].size + 1): + boundlist.append(np.delete(self.global_bounds_DataArray.values[:, -i, :], 0, 1)) + + with tqdm(total=len(boundlist), desc='1st Loop', leave=True, disable=progressbar_disable) as pbar: + for lvl, bounds in enumerate(boundlist): + comp_bounds = [] + + for item_i, item_j in zip(bounds[:, 0], bounds[:, 1]): comp_bounds.extend([item_i == item_j]) + + if 'shc_fuzzy_min' not in locals(): + + if all(comp_bounds) == True: # if all bounds are constants + ## calculate objective value + zmin = self._objective_function(bounds[:, 0]) + + ## safe values in list + zmin_value_list.append(np.array(zmin)) + zmax_value_list.append(np.array(zmin)) + + best_indi_min = np.array(bounds[:, 0]) + best_indi_max = np.array(bounds[:, 0]) + nfev_min = np.array(0) + nfev_max = np.array(0) + nit_min = np.array(0) + nit_max = np.array(0) + + else: + if all(comp_bounds) == False and any(comp_bounds) == True: + bounds = self._pop_constants(comp_bounds, bounds) + + ## optimization routine + shc_const_min = self._call_minimizer_shgo(bounds) + shc_const_max = self._call_maximizer_shgo(bounds) + + if any(comp_bounds) == False: + shc_fuzzy_min = self._find_result(shc_const_min) + shc_fuzzy_max = self._find_result(shc_const_max) + + shc_const_min = self._find_result(shc_const_min) + shc_const_max = self._find_result(shc_const_max) + + ## safe results in lists + best_indi_min = self._safe_best_array(comp_bounds, shc_const_min.res) + best_indi_max = self._safe_best_array(comp_bounds, shc_const_max.res) + nfev_min = shc_const_min.res.nfev + nfev_max = shc_const_max.res.nfev + nit_min = shc_const_min.res.nit + nit_max = shc_const_max.res.nit + + zmin_value_list.append(shc_const_min.res.fun * (1)) + zmax_value_list.append(shc_const_max.res.fun * (-1)) + + self.x_glob = [] + + else: + + shc_fuzzy_min.bounds = bounds + shc_fuzzy_min.iterate() + shc_fuzzy_min.find_minima() + + shc_fuzzy_max.bounds = bounds + shc_fuzzy_max.iterate() + shc_fuzzy_max.find_minima() + + ## safe results in lists + best_indi_min = shc_fuzzy_min.res.x + best_indi_max = shc_fuzzy_max.res.x + if lvl <= 1: + nfev_min = shc_const_min.res.nfev + nfev_max = shc_const_max.res.nfev + else: + nfev_min = np.absolute((shc_const_min.res.nfev - np.sum(self.nfev_list_min[0:lvl-1]))) + nfev_max = np.absolute((shc_const_max.res.nfev - np.sum(self.nfev_list_max[0:lvl-1]))) + nit_min = shc_const_min.res.nit + nit_max = shc_const_max.res.nit + + zmin_value_list.append(shc_fuzzy_min.res.fun * (1)) + zmax_value_list.append(shc_fuzzy_max.res.fun * (-1)) + + + self.best_indi_list_min.append(best_indi_min) + self.best_indi_list_max.append(best_indi_max) + self.nfev_list_min.append(nfev_min) + self.nfev_list_max.append(nfev_max) + self.nit_list_min.append(nit_min) + self.nit_list_max.append(nit_max) + + + if self.backup == True: + self.zmin_values = self._safe_z_values(min_max='min', z_value_list=zmin_value_list) + self.zmax_values = self._safe_z_values(min_max='max', z_value_list=zmax_value_list) + self._call_backup(iteration=lvl) + + pbar.update() + + self.zmin_values = self._safe_z_values(min_max='min', z_value_list=zmin_value_list) + self.zmax_values = self._safe_z_values(min_max='max', z_value_list=zmax_value_list) + + self.total_nfev = sum(self.nfev_list_min) + sum(self.nfev_list_max) + self.compact_output() + + + def _main_fast(self, n=250, iters=3, optimizer='sobol', backup=False, start_at=None): + """ + Main Routine calculating the Minimum and Maximum of the Objective on each Alpha Level to generate + the Fuzzy Objective Membershipfunction. + :param n: Number of Sampling Points / Individuals for the Optimization Algorithm + :param iters: Number of max. Iterations per Optimization Loop + :param optimizer: Selected Optimizer Strategy: "sobol" / "simplicial" + :param backup: Creates a Backup Folder saving the result of each Alpha Level Result + :param start_at: Start at certain Alpha Level (Counts starts from Alpha Level 1) + """ + + ###################### STILL IN WORK ################################################## + + """ + # Input Variables + self.n = n + self.iters = iters + self.optimizer = optimizer # simplicial / sobol + self.backup = backup + self.start_at = start_at + + zmin_value_list = [] + zmax_value_list = [] + boundlist = [] + + for i in range(1, self.global_bounds_DataArray['number_of_alpha_levels'].size + 1): + boundlist.append(np.delete(self.global_bounds_DataArray.values[:, -i, :], 0, 1)) + + + bounds = boundlist[-1] + + shc_base_min = self._call_minimizer_shgo(bounds) + shc_base_max = self._call_maximizer_shgo(bounds) + + r = 1 + + + if self.start_at is not None: self._cut_global_blounds() + + for i in range(1, self.global_bounds_DataArray['number_of_alpha_levels'].size + 1): + boundlist.append(np.delete(self.global_bounds_DataArray.values[:, -i, :], 0, 1)) + + with tqdm(total=len(boundlist)) as pbar: + for lvl, bounds in enumerate(boundlist): + comp_bounds = [] + + for item_i, item_j in zip(bounds[:, 0], bounds[:, 1]): comp_bounds.extend([item_i == item_j]) + + if lvl == 0: # Alpha-Level = 1 + if self.start_at is None: + if all(comp_bounds) == True: # if all bounds are constants + ## calculate objective value + zmin = self._objective_function(bounds[:, 0]) + + ## safe values in list + zmin_value_list.append(np.array(zmin)) + zmax_value_list.append(np.array(zmin)) + + best_indi_min = np.array(bounds[:, 0]) + best_indi_max = np.array(bounds[:, 0]) + nfev_min = np.array(0) + nfev_max = np.array(0) + nit_min = np.array(0) + nit_max = np.array(0) + + #elif all(comp_bounds) == False and any(comp_bounds) == True: # if one or more bounds are constants + ## prepare bounds / pop constants + else: # if one or more bounds are constants or all intervals + if all(comp_bounds) == False and any(comp_bounds) == True: + bounds = self._pop_constants(comp_bounds, bounds) + + ## optimization routine + shc_const_min = self._call_minimizer_shgo(bounds) + shc_const_max = self._call_maximizer_shgo(bounds) + + shc_const_min = self._find_result(shc_const_min) + shc_const_max = self._find_result(shc_const_max) + + ## safe results in lists + best_indi_min = self._safe_best_array(comp_bounds, shc_const_min.res) + best_indi_max = self._safe_best_array(comp_bounds, shc_const_max.res) + nfev_min = shc_const_min.res.nfev + nfev_max = shc_const_max.res.nfev + nit_min = shc_const_min.res.nit + nit_max = shc_const_max.res.nit + + zmin_value_list.append(shc_const_min.res.fun * (1)) + zmax_value_list.append(shc_const_max.res.fun * (-1)) + self.x_glob = [] + + + else: + shc_const_min = self._call_minimizer_shgo(bounds) + shc_const_max = self._call_maximizer_shgo(bounds) + + shc_const_min = self._find_result(shc_const_min) + shc_const_max = self._find_result(shc_const_max) + + ## safe results in lists + best_indi_min = self._safe_best_array(comp_bounds, shc_const_min.res) + best_indi_max = self._safe_best_array(comp_bounds, shc_const_max.res) + nfev_min = shc_const_min.res.nfev + nfev_max = shc_const_max.res.nfev + nit_min = shc_const_min.res.nit + nit_max = shc_const_max.res.nit + + zmin_value_list.append(shc_const_min.res.fun * (1)) + zmax_value_list.append(shc_const_max.res.fun * (-1)) + + else: # Alpha-Level < 1 + # if all bounds are Fuzzy-Intervalls + if 'shc_fuzzy_min' not in locals(): + shc_const_min = self._call_minimizer_shgo(bounds) + shc_const_max = self._call_maximizer_shgo(bounds) + + shc_fuzzy_min = self._find_result(shc_const_min) + shc_fuzzy_max = self._find_result(shc_const_max) + else: + shc_fuzzy_min.bounds = bounds + shc_fuzzy_min.iterate() + shc_fuzzy_min.find_minima() + + shc_fuzzy_max.bounds = bounds + shc_fuzzy_max.iterate() + shc_fuzzy_max.find_minima() + + ## safe results in lists + best_indi_min = shc_fuzzy_min.res.x + best_indi_max = shc_fuzzy_max.res.x + if lvl <= 1: + nfev_min = shc_const_min.res.nfev + nfev_max = shc_const_max.res.nfev + else: + nfev_min = np.absolute((shc_const_min.res.nfev - np.sum(self.nfev_list_min[0:lvl-1]))) + nfev_max = np.absolute((shc_const_max.res.nfev - np.sum(self.nfev_list_max[0:lvl-1]))) + nit_min = shc_const_min.res.nit + nit_max = shc_const_max.res.nit + + zmin_value_list.append(shc_fuzzy_min.res.fun * (1)) + zmax_value_list.append(shc_fuzzy_max.res.fun * (-1)) + + self.best_indi_list_min.append(best_indi_min) + self.best_indi_list_max.append(best_indi_max) + self.nfev_list_min.append(nfev_min) + self.nfev_list_max.append(nfev_max) + self.nit_list_min.append(nit_min) + self.nit_list_max.append(nit_max) + + if self.backup == True: + self.zmin_values = self._safe_z_values(min_max='min', z_value_list=zmin_value_list) + self.zmax_values = self._safe_z_values(min_max='max', z_value_list=zmax_value_list) + self._call_backup(iteration=lvl) + + pbar.update() + + self.zmin_values = self._safe_z_values(min_max='min', z_value_list=zmin_value_list) + self.zmax_values = self._safe_z_values(min_max='max', z_value_list=zmax_value_list) + + self.total_nfev = sum(self.nfev_list_min) + sum(self.nfev_list_max) + self.compact_output() + """ + + + def compact_output(self, round=None): + """ + Returns Dataframe of Objective Memebership Function + :param round: Round Values of Dataframe + :return: Dataframe + """ + if self.start_at is None: + self._df = pd.DataFrame(data={'alpha': np.linspace(0, 1.0, self.number_of_alpha_lvls), + 'l': self.zmin_values, 'r': self.zmax_values}) + else: + self._df = pd.DataFrame(data={'alpha': np.linspace(0, + np.linspace(0, 1.0, self.orig_number_of_alpha_lvls) + [self.orig_number_of_alpha_lvls-self.start_at], self.number_of_alpha_lvls), + 'l': self.zmin_values, + 'r': self.zmax_values}) + + self.df = self._df + if round is not None: + self.df = self.df.round(round) + self._df = self._df.round(round) + + + def extanded_output(self,round=None): + """ + Create extanded Solution Dataframe of Objective + :param round: Round Values of Dataframe + :return: Dataframe + """ + self.best_indi_list_min.reverse() + self.best_indi_list_max.reverse() + self.nfev_list_min.reverse() + self.nfev_list_max.reverse() + self.nit_list_min.reverse() + self.nit_list_max.reverse() + if round is not None: + min_arr = np.round(self.best_indi_list_min,round) + max_arr = np.round(self.best_indi_list_max,round) + else: + min_arr = np.array(self.best_indi_list_min) + max_arr = np.array(self.best_indi_list_max) + + if self.start_at is None: + self.df_extanded = pd.DataFrame(data={'alpha': np.linspace(0, 1.0, self.number_of_alpha_lvls), + 'l': self.zmin_values, 'best_indi_l': min_arr.tolist(), + 'nfev_l': self.nfev_list_min, 'nit_l': self.nit_list_min, + 'r': self.zmax_values, 'best_indi_r': max_arr.tolist(), + 'nfev_r':self.nfev_list_max, 'nit_r': self.nit_list_max}) + else: + self.df_extanded = pd.DataFrame(data={'alpha': + np.linspace(0, np.linspace(0, 1.0, self.orig_number_of_alpha_lvls) + [self.orig_number_of_alpha_lvls-self.start_at], self.number_of_alpha_lvls), + 'l': self.zmin_values, 'best_indi_l': min_arr.tolist(), + 'nfev_l': self.nfev_list_min, 'nit_l': self.nit_list_min, + 'r': self.zmax_values, 'best_indi_r': max_arr.tolist(), + 'nfev_r':self.nfev_list_max, 'nit_r': self.nit_list_max}) + + if round is not None: self.df_extanded = self.df_extanded.round(round) + + + def export_to_csv(self, df='simple', filepath= None): + """ + Export Dataframe as CSV + :param df Define Dataframe-Type which is to be exported simple / extended + :param filepath: Filepath where to save the DF + :return: CSV of Dataframe + """ + if df == 'simple': + if filepath is None: + datatype_str = '_results.csv' + self._df.to_csv(self.name+datatype_str, sep=';', encoding='utf8', index=None, header=True) + else: + if filepath[-2:] != '\\': filepath = filepath + '\\' + datatype_str = '_results.csv' + self._df.to_csv(filepath+self.name+datatype_str, sep=';', encoding='utf8', index=None, header=True) + elif df == 'extended': + if filepath is None: + datatype_str = '_extended_results.csv' + self.df_extanded.to_csv(self.name+datatype_str, sep=';', encoding='utf8', index=None, header=True) + else: + if filepath[-2:] != '\\': filepath = filepath + '\\' + datatype_str = '_extended_results.csv' + self.df_extanded.to_csv(filepath+self.name+datatype_str, sep=';', encoding='utf8', index=None, header=True) + + + def defuzzification(self, method='mean'): + """ + Defuzzyfication of Uncertain Objective + :param method: Select Method of Defuzzyfication - alpha_one / mean / centroid + """ + self.zmax_values = np.flip(self.zmax_values) + if method == 'alpha_one': + self.determin_objective = ((self.alpha1['l']+self.alpha1['r']) / 2) + + elif method == 'mean': + if self.zmin_values[-1]==self.zmax_values[0]: + self.determin_objective = np.mean(np.concatenate((self.zmin_values[:-1],self.zmax_values), axis=0)) + else: + self.determin_objective = np.mean(np.concatenate((self.zmin_values,self.zmax_values), axis=0)) + #self.determin_objective = np.mean((self.df['l'].values+self.df['r'].values)/2) + elif method == 'centroid': + A = 0 + B = 0 + if self.zmin_values[-1]==self.zmax_values[0]: + X = np.hstack((np.hstack((self.zmin_values[:-1],self.zmax_values)),self.zmin_values[0])) + Y = np.hstack((np.hstack((np.linspace(0,1,self.number_of_alpha_lvls)[:-1],np.linspace(1,0,self.number_of_alpha_lvls))),np.array([0]))) + else: + X = np.hstack((np.hstack((self.zmin_values,self.zmax_values)),self.zmin_values[0])) + Y = np.hstack((np.hstack((np.linspace(0,1,self.number_of_alpha_lvls),np.linspace(1,0,self.number_of_alpha_lvls))),np.array([0]))) + + for i in range(0,len(X)-1): + a = (X[i]*Y[i+1]-X[i+1]*Y[i]) + b = (X[i]+X[i+1])*(X[i]*Y[i+1]-X[i+1]*Y[i]) + A = A + a + B = B + b + self.determin_objective = (1/(3*A))*B + + if self.determin_objective < self.zmin_values[-1]: + index_i = np.where(self.zmin_values < self.determin_objective)[0][-1] + index_ii = np.where(self.zmin_values > self.determin_objective)[0][0] + y_interpol = np.interp(self.determin_objective, [self.zmin_values[index_i],self.zmin_values[index_ii]], + [np.linspace(0,1,self.number_of_alpha_lvls)[index_i], + np.linspace(0,1,self.number_of_alpha_lvls)[index_ii]]) + elif self.determin_objective > self.zmax_values[0]: + index_i = np.where(self.determin_objective > self.zmax_values)[0][-1] + index_ii = np.where(self.determin_objective < self.zmax_values)[0][0] + y_interpol = np.interp(self.determin_objective, [self.zmax_values[index_i],self.zmax_values[index_ii]], + [np.linspace(1,0,self.number_of_alpha_lvls)[index_i], + np.linspace(1,0,self.number_of_alpha_lvls)[index_ii]]) + else: + y_interpol = 1.0 + + self.determin_point = np.array([self.determin_objective,y_interpol]) + + + def _call_minimizer_shgo(self,bounds): + """ + Minimizes Objective Function with the SHGO Optimizer + :param bounds: Boundary of the current Alpha Level + :return: Minimum + """ + if self.optimizer == 'sobol': + return SHGO(self._min_function_value, bounds = bounds, n=self.n, iters=self.iters, + sampling_method= 'sobol' , options={'ftol': 1e-4}, constraints = None) + else: + return SHGO(self._min_function_value, bounds = bounds, n=self.n, iters=self.iters, + options={'ftol': 1e-4}, constraints = None) + + + def _call_maximizer_shgo(self,bounds): + """ + Maximizes Objective Function with the SHGO Optimizer + :param bounds: Boundary of the current Alpha Level + :return: Maximum + """ + if self.optimizer == 'sobol': + return SHGO(self._max_function_value, bounds = bounds, n=self.n, iters=self.iters, + sampling_method=self.optimizer , options={'ftol': 1e-4}, constraints = None) + else: + return SHGO(self._max_function_value, bounds = bounds, n=self.n, iters=self.iters, + options={'ftol': 1e-4}, constraints = None) + + + def _objective_function(self, x): + """ + Abstract Objective Function Call + :param x: Input Variable + :return: Function Value of Objective + """ + #aeval = Interpreter() + #exprc = aeval.parse(self.objective) + #aeval.symtable['x'] = x + #return aeval.run(exprc) + return self.objective(x) + + + def _cut_global_blounds(self): + """ + Cut global Bounds for the Option to -start at- a selected Alpha Level + """ + if self.start_at is not None: + self.orig_number_of_alpha_lvls = self.number_of_alpha_lvls + self.global_bounds_DataArray = self.global_bounds_DataArray[:,0:self.orig_number_of_alpha_lvls-self.start_at+1,:] + self.dim = self.global_bounds_DataArray['fuzzy_variable'].size + self.number_of_alpha_lvls = self.global_bounds_DataArray['number_of_alpha_levels'].size + else: + raise ValueError('Please select starting Alpha Level or keep variable "start_at=None"') + + + def _min_function_value(self, x): + """ + Call Minimization Opt Routine + :param x: Input Variable + :return: Objective Value + """ + if isinstance(self.x_glob, np.ndarray): + for row in self.x_glob: + x = np.insert(x, (row[0].astype(int)), row[1]) + return self._objective_function(x) + + + def _max_function_value(self, x): + """ + Call Maximization Opt Routine + :param x: Input Variable + :return: Objective Value + """ + if isinstance(self.x_glob, np.ndarray): + for row in self.x_glob: + x = np.insert(x, (row[0].astype(int)), row[1]) + return -1 * (self._objective_function(x)) + + + def _safe_best_array(self, comp_bounds, z_res): + """ + Post Preparation to extrapolate sampling Point with min/max Objective Value from Optimization Result + :param comp_bounds: Boolean Array of all Fuzzy Input Variables which are constants for a certain Alpha Level + :param z_res: Result of Optimization Loop + :return: + best_indi Best Individual + """ + best_indi = np.zeros((self.dim), dtype=float) + j = 0 + for i, comp in enumerate(comp_bounds): + if comp: + best_indi[i] = self.global_bounds_DataArray[i, -1, 1].values + else: + best_indi[i] = z_res.x[j] + j += 1 + return best_indi + + + def _pop_constants(self, comp_bounds, bounds): + """ + Pop Constant Fuzzy Variables of Input Variable + :param comp_bounds: Boolean Array of all Fuzzy Input Variables which are constants for a certain Alpha Level + :param bounds: All Optimization Bound of cetrain Alpha Level + :return: + bounds + """ + pop = np.array([i for i, x in enumerate(comp_bounds) if x]) + self.x_glob = np.concatenate((np.atleast_2d(pop).T, np.atleast_2d(bounds[pop, 0]).T), axis=1) + bounds = np.delete(bounds, pop, 0) + return bounds + + + def _call_backup(self,iteration): + """ + Create Backup Dataframe for each Alpha Level and export it as CSV + :param iteration: Current Iterationstep / Alpha Level + """ + if Path('back_up').exists(): pass + else: Path('back_up').mkdir() + + cache_str = 'backup_'+ str(iteration) + '_iteration_step.csv' + filepath = Path.cwd() / 'back_up' / cache_str + if self.start_at is None: + df_cache = pd.DataFrame(data={'alpha': np.linspace(np.linspace(0, 1.0, self.number_of_alpha_lvls) + [self.number_of_alpha_lvls-1-iteration], 1.0, iteration+1), + 'l': self.zmin_values, + 'r': self.zmax_values}) + else: + df_cache = pd.DataFrame(data={'alpha': np.linspace(np.linspace(0, 1.0, self.number_of_alpha_lvls) + [self.number_of_alpha_lvls-1-iteration], + np.linspace(0, 1.0, self.orig_number_of_alpha_lvls) + [self.orig_number_of_alpha_lvls-self.start_at],iteration+1), + 'l': self.zmin_values, + 'r': self.zmax_values}) + + df_cache.to_csv(filepath, sep=';', encoding='utf8', index=None, header=True) + + + @staticmethod + def _alpha_level_check(input_fuzzy_var_list): + """ + Checks if all Fuzzy Input Variables have the same alpha Level Discretization. + If not, adapt all Variables to the highes alpha Level. + + :param input_fuzzy_var_list: List of all Fuzzy Input Variables + :return: List of all Fuzzy Input Variables + """ + alpha_array = np.zeros(shape=(len(input_fuzzy_var_list))) + + for i, fuzzy_var in enumerate(input_fuzzy_var_list): + alpha_array[i] = fuzzy_var.number_of_alpha_levels + + if np.all(alpha_array == alpha_array[0]): + pass + else: + alvl_max = np.max(alpha_array) + index = np.argmax(alpha_array) + for i, fuzzy_var in enumerate(input_fuzzy_var_list): + if i != index: + fuzzy_var.convert_df(alpha_levels=alvl_max) + input_fuzzy_var_list[i] = fuzzy_var + return input_fuzzy_var_list + + + @staticmethod + def _boundary_constraints(input_fuzzy_var_list): + """ + Calculating the Optimization Boundaries based on the Fuzzy Variables Membership Funciton + :param kwargs: Input Fuzzy Variables + :return: 3D DataArray representing each Fuzzy Inputvariables Boundaries for each Alpha Level + prepared for the Optimization Routine + """ + + df_list = [] + var_names_list = [] + for i, fuzzy_var in enumerate(input_fuzzy_var_list): + df_list.append(fuzzy_var.df) + var_names_list.append(fuzzy_var.name) + fuzzy_array = np.array([df.values for df in df_list]) + alpha_levels = np.linspace(1,len(fuzzy_array[0,:]),len(fuzzy_array[0,:])) + head = ['alpha','l','r'] + dims=['fuzzy_variable','number_of_alpha_levels','alpha_level_bounds'] + + return xr.DataArray(fuzzy_array,coords=[var_names_list, alpha_levels, head],dims=dims) + + + @staticmethod + def _safe_z_values(min_max, z_value_list): + """ + Post Preparation for Results of Optimization Routines for creation of Fuzzy Dataframe + :param min_max: Define if Result is from a Minimization or Maximization + :param z_value_list: List of all Objective Value Results (for each Alpha Level) + :return: + z_value_list Adapted List of all Objective Value Results + """ + if min_max == 'min': + for (i, current_item), next_item in zip(enumerate(z_value_list), z_value_list[1:]): + if current_item < next_item: + z_value_list[i + 1] = current_item + elif min_max == 'max': + for (i, current_item), next_item in zip(enumerate(z_value_list), z_value_list[1:]): + if current_item > next_item: + z_value_list[i + 1] = current_item + else: + raise ValueError('Please define -min- or -max- in min_max') + return np.flip(z_value_list, axis=0) + + + @staticmethod + def _find_result(shc): + """ + Post Calculation for Shgo-Optimization Algorithm + """ + shc.construct_complex() + if len(shc.LMC.xl_maps) > 0: + return shc + else: + lres = minimize(shc.func, shc.x_lowest, + **shc.minimizer_kwargs) + shc.res.nlfev += lres.nfev + try: + lres.fun = lres.fun[0] + except (IndexError, TypeError): + lres.fun + + shc.LMC[shc.x_lowest] + shc.LMC.add_res(shc.x_lowest, lres) + shc.sort_result() + # Lowest values used to report in case of failures + shc.f_lowest = shc.res.fun + shc.x_lowest = shc.res.x + return shc + + + @classmethod + def from_str(cls, s): + pass + + + def to_str(self): + pass + + + +if __name__ == "__main__": + pass diff --git a/phuzzy/optimization/ipynb/Example_Alpha_Opt.ipynb b/phuzzy/optimization/ipynb/Example_Alpha_Opt.ipynb new file mode 100644 index 0000000..c7fa3a8 --- /dev/null +++ b/phuzzy/optimization/ipynb/Example_Alpha_Opt.ipynb @@ -0,0 +1,879 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "no display found. Using non-interactive Agg backend\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\boos\\PycharmProjects\\phuzzy\\phuzzy\\mpl\\__init__.py:7: UserWarning: \nThis call to matplotlib.use() has no effect because the backend has already\nbeen chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,\nor matplotlib.backends is imported for the first time.\n\nThe backend was *originally* set to 'module://ipykernel.pylab.backend_inline' by the following code:\n File \"C:\\ProgramData\\Anaconda3\\lib\\runpy.py\", line 193, in _run_module_as_main\n \"__main__\", mod_spec)\n File \"C:\\ProgramData\\Anaconda3\\lib\\runpy.py\", line 85, in _run_code\n exec(code, run_globals)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel_launcher.py\", line 16, in \n app.launch_new_instance()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\traitlets\\config\\application.py\", line 658, in launch_instance\n app.start()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\kernelapp.py\", line 505, in start\n self.io_loop.start()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\platform\\asyncio.py\", line 132, in start\n self.asyncio_loop.run_forever()\n File \"C:\\ProgramData\\Anaconda3\\lib\\asyncio\\base_events.py\", line 421, in run_forever\n self._run_once()\n File \"C:\\ProgramData\\Anaconda3\\lib\\asyncio\\base_events.py\", line 1431, in _run_once\n handle._run()\n File \"C:\\ProgramData\\Anaconda3\\lib\\asyncio\\events.py\", line 145, in _run\n self._callback(*self._args)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\ioloop.py\", line 758, in _run_callback\n ret = callback()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\stack_context.py\", line 300, in null_wrapper\n return fn(*args, **kwargs)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 1233, in inner\n self.run()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 1147, in run\n yielded = self.gen.send(value)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 370, in dispatch_queue\n yield self.process_one()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 346, in wrapper\n runner = Runner(result, future, yielded)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 1080, in __init__\n self.run()\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 1147, in run\n yielded = self.gen.send(value)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 357, in process_one\n yield gen.maybe_future(dispatch(*args))\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 326, in wrapper\n yielded = next(result)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 267, in dispatch_shell\n yield gen.maybe_future(handler(stream, idents, msg))\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 326, in wrapper\n yielded = next(result)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 534, in execute_request\n user_expressions, allow_stdin,\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\tornado\\gen.py\", line 326, in wrapper\n yielded = next(result)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\ipkernel.py\", line 294, in do_execute\n res = shell.run_cell(code, store_history=store_history, silent=silent)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\ipykernel\\zmqshell.py\", line 536, in run_cell\n return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2819, in run_cell\n raw_cell, store_history, silent, shell_futures)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2845, in _run_cell\n return runner(coro)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\IPython\\core\\async_helpers.py\", line 67, in _pseudo_sync_runner\n coro.send(None)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 3020, in run_cell_async\n interactivity=interactivity, compiler=compiler, result=result)\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 3185, in run_ast_nodes\n if (yield from self.run_code(code, result)):\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 3267, in run_code\n exec(code_obj, self.user_global_ns, self.user_ns)\n File \"\", line 1, in \n import phuzzy\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\phuzzy\\__init__.py\", line 23, in \n from phuzzy.shapes.skewnorm import Skewnorm\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\phuzzy\\shapes\\skewnorm.py\", line 21, in \n import matplotlib.pyplot as plt\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\matplotlib\\pyplot.py\", line 71, in \n from matplotlib.backends import pylab_setup\n File \"C:\\Users\\boos\\PycharmProjects\\phuzzy\\venv\\lib\\site-packages\\matplotlib\\backends\\__init__.py\", line 16, in \n line for line in traceback.format_stack()\n\n\n matplotlib.use('Agg')\n" + ] + } + ], + "source": [ + "import phuzzy\n", + "from phuzzy.optimization import alphaOpt\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "plt.style.use('seaborn')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "v1 = phuzzy.Triangle(alpha0=[1,4], alpha1=[2], name='v1', number_of_alpha_levels=3)\n", + "v2 = phuzzy.Triangle(alpha0=[0, 4], alpha1=[1], name='v2', number_of_alpha_levels=5)\n", + "v3 = phuzzy.Triangle(alpha0=[1, 4], alpha1=[2], name='v3', number_of_alpha_levels=3)\n", + "v4 = phuzzy.Triangle(alpha0=[0,4], alpha1=[2.5], name='v4', number_of_alpha_levels=3)\n", + "v5 = phuzzy.Triangle(alpha0=[1, 4], alpha1=[3], name='v5', number_of_alpha_levels=3)\n", + "v6 = phuzzy.Triangle(alpha0=[1,5], alpha1=[3], name='v6', number_of_alpha_levels=3)\n", + "fuzzy_input_variables = [v1,v2,v3,v4,v5,v6]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r1st Loop: 0%| | 0/5 [00:00\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alphalr
00.00-15.909987451.10000
10.25-1.671875327.15625
20.5018.712500230.21250
30.7550.503125155.95625
41.00100.450000100.45000
\n", + "" + ], + "text/plain": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alphalr
00.00-15.909987451.10000
10.25-1.671875327.15625
20.5018.712500230.21250
30.7550.503125155.95625
41.00100.450000100.45000
\n", + "
" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.df" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alphalr
00.00-15.91451.10
10.25-1.67327.16
20.5018.71230.21
30.7550.50155.96
41.00100.45100.45
\n", + "
" + ], + "text/plain": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alphalr
00.00-15.91451.10
10.25-1.67327.16
20.5018.71230.21
30.7550.50155.96
41.00100.45100.45
\n", + "
" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.compact_output(round=2) # Default: round=None\n", + "z_string.df" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alphalbest_indi_lnfev_lnit_lrbest_indi_rnfev_rnit_r
00.00-15.9100[4.0, 4.0, 1.0, 0.1036, 1.0, 1.0]263451.1000[1.0, 0.0, 4.0, 4.0, 4.0, 5.0]183
10.25-1.6719[3.5, 3.25, 1.25, 0.625, 1.5, 1.5]1323327.1562[1.25, 0.25, 3.5, 3.625, 3.75, 4.5]1323
20.5018.7125[3.0, 2.5, 1.5, 1.25, 2.0, 2.0]2323230.2125[1.5, 0.5, 3.0, 3.25, 3.5, 4.0]2323
30.7550.5031[2.5, 1.75, 1.75, 1.875, 2.5, 2.5]1663155.9562[1.75, 0.75, 2.5, 2.875, 3.25, 3.5]1663
41.00100.4500[2.0, 1.0, 2.0, 2.5, 3.0, 3.0]00100.4500[2.0, 1.0, 2.0, 2.5, 3.0, 3.0]00
\n", + "
" + ], + "text/plain": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
alphalbest_indi_lnfev_lnit_lrbest_indi_rnfev_rnit_r
00.00-15.9100[4.0, 4.0, 1.0, 0.1036, 1.0, 1.0]263451.1000[1.0, 0.0, 4.0, 4.0, 4.0, 5.0]183
10.25-1.6719[3.5, 3.25, 1.25, 0.625, 1.5, 1.5]1323327.1562[1.25, 0.25, 3.5, 3.625, 3.75, 4.5]1323
20.5018.7125[3.0, 2.5, 1.5, 1.25, 2.0, 2.0]2323230.2125[1.5, 0.5, 3.0, 3.25, 3.5, 4.0]2323
30.7550.5031[2.5, 1.75, 1.75, 1.875, 2.5, 2.5]1663155.9562[1.75, 0.75, 2.5, 2.875, 3.25, 3.5]1663
41.00100.4500[2.0, 1.0, 2.0, 2.5, 3.0, 3.0]00100.4500[2.0, 1.0, 2.0, 2.5, 3.0, 3.0]00
\n", + "
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.extanded_output(round=4) # Default: round=None\n", + "z_string.df_extanded\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1104" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.total_nfev" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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wACef/55/P73v8epU6ewceNGAMDGjRuxd+9ebNiwAdu3b0d1dTWWLVsGAKiursYtt9yC3/zmN7j++utRVVWF3bt3w2azBd/z6aefxvXXX4/169fjJz/5Ce666y689NJLEakbiRxqsiRsdXV1MBqN0/5ZrVAocNttt+H1118HABw/fhwVFRU4deoU9u7di7179+LkyZPYtWsXtm7diq1bt+KFF16I6Ph8Ph927twJpVKJt956Cy+++CIaGxvxi1/8AsDougYWiwXvvvsu3nnnHWi1Wjz55JMoKirCU089BYlEgrq6OixZsmTO99qyZQsef/xxfP/738fhw4fR0tICuVyOhx56CAsXLsSdd96JHTt2YO3atXjvvfeC33fx4kW8+eabeOqpp6Yc02AwYGhoCMePH8fzzz+PU6dO4fnnnwcAvPjii/jjH/+IZ555BidOnEBKSgrq6+sjVDkSSdRkSdj6+/sBABkZGdO+rtfrg/ssW7YMX/nKVyCTybBx40Z85jOfwdGjRwUdX319PS5duoS9e/dCo9EgIyMDu3fvxj/+8Q94vV7I5XI0Nzfj2LFjGBgYwCOPPIKHHnoorPf61re+hSeeeAJutxu//OUvceutt2Ljxo149tlnZ/2+W265BUlJSUhJSZn29XvvvRcKhQKFhYWorKxEa2srAODw4cP40pe+hLKyMsjlcnz7299Genp6WGMnwpLGegCEXXq9HgDQ3d2NBQsWTHm9q6sruM/ChQsnvJabm4umpiZBx9fV1QWv14sbbrhhwnaRSISenh7s3LkTCoUCf/3rX7F3714UFRVh9+7d2LRpU1jvd9NNN+Gmm24CMDqL4I033sAjjzyClJQUbNu2bdrvmekfKGD0ES86nS74tVQqhc/nAwD09vYiOzt7wr7jvybxg85kSdjWrFkDvV4fvL46nt1ux6uvvorNmzcDGG0K43V2dgavXwolKysLGo0Gp06dCq6d+t577+HIkSPIy8vDpUuXsHnzZhw+fBgnT57Erbfeivvvv5/zgxQvXryIFStWoKenJ7itoKAAX//613HDDTfg/PnzM36vSCQK62fLyclBd3d38Gu/34++vr6wjkWERU2WhE0mk+HnP/85nn32Wezbtw8DAwPw+XxobGzEN77xDSiVStxzzz0ARoOhl156CT6fDydOnMDx48eDQZdcLsfIyEjEx7d69WpkZmbif//3f+FwOGCz2bB3797gUxOee+457NmzB4ODg9BqtUhJSYFGo4FcLodcLoff7w+p4ZaWlqKsrAy7d+9GQ0MDAoEAbDYb3n77bXz00UfBM+NI/px33nknnnvuOTQ1NcHj8WD//v0YGBiIyLFJZFGTJbxs3LgRf/3rX9HS0oLbb78da9aswXe+8x2sWbMGzz//PJKTkwEAZWVlePvtt1FVVYVf/OIX+NWvfoWVK1cCAD772c+itrYWt9xyS0THJpfL8fTTT8NgMODmm2/Gpz71KVitVjz99NMQi8V44IEHkJeXh1tvvRUVFRV4+eWXsX//fsjlcixduhSrVq3C9ddfP+dMB5FIhN/97ndYtWoVdu/ejYqKCtxwww3405/+hF//+teoqqoCANx8880wGo249tprgzMcwrV161Zs374dO3bswA033IDBwUFkZWVBJpPxOi6JPFq0mwhu3759OHXqFA4dOhTroSSMpqYmpKamIicnBwAQCASwdu1a7Nu3D+vWrYvx6Mh4dCZLCIPef/993HfffRgaGoLP58Mf//hHSCQSrFq1KtZDI5PQ7AISl4aGhvDpT396xtd1Oh3++c9/RmUsu3btQnV19Yyv//a3v8WGDRuiMpYxX/nKV9DR0YEtW7bA6XSirKwMv/vd76Y844zEHl0uIIQQAdHlAkIIERA1WUIIERA1WUIIERA1WUIIERA1WUIIERA1WUIIERA1WUIIERA1WUIIEdD/B0AzHbb8QVguAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.plot(show=True, filepath=\"z_plot.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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9uxZ7N27F01NTWhsbMQXvvAFPPbYY7Db7RH9/0ceeQS//vWvg68bGxuDK5HmYtu2bWhsbERjYyNWrFiBFStWBF9v27Yt5s8D+D9TNFvRO51OPPLII7jxxhvR0NCAjRs34jvf+c60KlUHDx7E/fffP+s5jh07FlzmSmQOJLIEL95991187Wtfw7Jly3D48GGcPHkSjz32GE6dOoV/+Id/iKg2q9FonPa6tbU1IrF59dVX0draitbWVuzcuRM7duwIvn711Vdj/kwAYDab4Xa7Iz7+wQcfxOnTp/H73/8ep06dwptvvgmdToevfe1rwRiMjY1hrrU/TU1NEf1xIYQFiSwRM263G/fddx/27NmDu+66CzqdDgzDYMmSJfjNb34Dl8uFp59+Gk888QTuvPNOfPe730VjYyO2bt2KF154AYC/ItQrr7yCV155BbfeeisA/zLPY8eOxaWPIyMj+Nd//Vds3rwZ1113HR566CG4XC4AgNVqxd13342mpiZs2bIF//zP/4y+vj50d3fjW9/6FliWRWNjIy5evDjvdU6ePInPfvazqKioAABoNBrce++9uP766zE2Noa//OUv+O1vf4vjx4/juuuuAwBcd911uP/++7F582bs2rULR48exfLlywEAR48exU033YQnn3wS1157LZqamnDPPffAZrMFr/nMM8/g2muvxaZNm/DjH/8Yt99+O1566aW4xI2IHySyRMy0trbCYDCE/Votl8uxfft2vPHGGwCAI0eOYO3atTh+/Djuv/9+3H///fjkk0+wd+9e7NixAzt27MDzzz8f1/6xLIs9e/ZAoVDg7bffxgsvvID29nb8/Oc/B+Cva2A2m/Hee+/h3XffhUajwVNPPYWamhr85je/gVgsRmtrK5YsWTLvtbZt24bHHnsM9957L1588UV0dnZCJpPhZz/7GRYuXIjbbrsNu3f//+3dS0hUXxzA8e+kcw0mp0QnNSlrE06gI06phDOYZtJE0qaFbVwYQT7Kxo2rWRZJFjSIYS7MQLCB7CFCoYESCZMbIR1UcmH5HpyFj6Ic/S/kf9F/+X9Mc7P+/D67c8/j/u4sfnM493BPKZmZmfT29qr9hoeHefnyJffu3ftmzPHxcQKBAN3d3Xg8HrxeLx6PB4DHjx/T3NzM/fv36enpwWg0MjAwEKZfToSTJFkRstnZWQDi4uK+W28ymdQ2R44coaSkBL1ej91u59SpUzx79kzT+AYGBhgZGcHlcmEwGIiLi8PpdPLo0SNWVlZQFIXR0VE6Ojrw+/3U1tZy48aNkO515coV7t69y5cvX6irq8PhcGC322lpafnbfgUFBezatQuj0fjd+oqKCqKiojhw4ABHjx5lbGwMgPb2doqLizGbzSiKwtWrV4mNjQ0pdqGtyO0OQPy+TCYTAJOTkyQnJ39TPzExobY5ePDgprp9+/bh8/k0jW9iYoKVlRVsNtum6zqdjqmpKS5fvkxUVBStra24XC4OHTqE0+nk5MmTId0vNzeX3NxcYH0XwYsXL6itrcVoNHLu3Lnv9tnqDwrWj3iJiYlRy5GRkQSDQQCmp6dJSEjY1HZjWfw6ZCYrQma1WjGZTOr66kbLy8t0dnZSWFgIrCeFjT5+/KiuX2olPj4eg8GA1+tVv53a29vLkydPSEpKYmRkhMLCQtrb2+nr68PhcFBVVfWfD1IcHh4mNTWVqakp9dr+/fu5ePEiNpuNwcHBLfvqdLqQni0xMZHJyUm1vLq6yszMTEhjCW1JkhUh0+v1XL9+nZaWFtxuN36/n2AwyNDQEJcuXWLnzp2UlZUB6y+Gnj59SjAYpKenh+7ubvVFl6IoLCwshD2+9PR09u7dy61bt/j06RNLS0u4XC711IS2tjZqamqYn59n9+7dGI1GDAYDiqKgKAqrq6v/KuEePnwYs9mM0+nk3bt3rK2tsbS0RFdXF2/fvlVnxuF8zvPnz9PW1obP5+Pr1680NDTg9/vDMrYIL0my4ofY7XZaW1t5//49RUVFWK1Wrl27htVqxePxEB0dDYDZbKarq4usrCxu3rzJ7du3SUtLA+D06dP09/dTUFAQ1tgURaGxsZHx8XHy8/M5ceIEi4uLNDY2smPHDqqrq0lKSsLhcJCRkcHz589paGhAURRSUlKwWCzk5OT8404HnU5HU1MTFosFp9NJRkYGNpuNBw8ecOfOHbKysgDIz89nbm6OY8eOqTscQnX27FkuXLhAaWkpNpuN+fl54uPj0ev1PzSuCD/5aLfQnNvtxuv18vDhw+0O5X/D5/OxZ88eEhMTAVhbWyMzMxO32012dvY2Ryc2kpmsEL+h169fU1lZSSAQIBgM0tzcTEREBBaLZbtDE38huwvELykQCJCXl7dlfUxMDK9evfopsZSXl/PmzZst6+vr6zl+/PhPieVPJSUlfPjwgTNnzvD582fMZjNNTU3fnHEmtp8sFwghhIZkuUAIITQkSVYIITQkSVYIITQkSVYIITQkSVYIITQkSVYIITQkSVYIITQkSVYIITT0B1vsnIQxfD14AAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.defuzzification(method = 'centroid') # mean / alpha_one / centroid\n", + "z_string.plot(show=True, defuzzy=z_string.determin_point,labels=True, filepath=\"z_centroid_plot.png\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [], + "source": [ + "# Export Simple Dataframe as CSV\n", + "z_string.export_to_csv() # Default df='simple', filepath=None\n", + "\n", + "# Export Extanded Dataframe as CSV\n", + "z_string.export_to_csv(df='extended') # Default df='simple', filepath=None" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.defuzzification(method = 'mean') # mean / alpha_one / centroid\n", + "z_string.plot(show=True, defuzzy=z_string.determin_point,labels=True, filepath=\"z_mean_plot.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\r1st Loop: 0%| | 0/2 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "z_string.calculation(start_at=4)\n", + "z_string.plot(show=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.4" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/phuzzy/optimization/ipynb/Example_Fuzzy_Sensi_Analysis.ipynb b/phuzzy/optimization/ipynb/Example_Fuzzy_Sensi_Analysis.ipynb new file mode 100644 index 0000000..183ab0f --- /dev/null +++ b/phuzzy/optimization/ipynb/Example_Fuzzy_Sensi_Analysis.ipynb @@ -0,0 +1,567 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import phuzzy\n", + "from phuzzy.optimization import alphaOpt\n", + "from phuzzy.optimization.sensitivity_analysis import Fuzzy_Sensitivity_Analysis\n", + "import matplotlib.pyplot as plt\n", + "\n", + "%matplotlib inline\n", + "plt.style.use('seaborn')" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "v1 = phuzzy.Triangle(alpha0=[1,4], alpha1=[2], name='v1', number_of_alpha_levels=3)\n", + "v2 = phuzzy.Triangle(alpha0=[0, 4], alpha1=[1], name='v2', number_of_alpha_levels=5)\n", + "v3 = phuzzy.Triangle(alpha0=[1, 4], alpha1=[2], name='v3', number_of_alpha_levels=3)\n", + "v4 = phuzzy.Triangle(alpha0=[0,4], alpha1=[2.5], name='v4', number_of_alpha_levels=6)\n", + "v5 = phuzzy.Triangle(alpha0=[1, 4], alpha1=[3], name='v5', number_of_alpha_levels=3)\n", + "v6 = phuzzy.Triangle(alpha0=[1,5], alpha1=[3], name='v6', number_of_alpha_levels=3)\n", + "\n", + "fuzzy_input_variables = [v1,v2,v3,v4,v5,v6]\n", + "obj_function = '-1*((x[0] - 1) ** 2 + (x[1] + .1) ** 2 + .1 - (x[2] + 2) ** 2 - ' \\\n", + " '(x[3] - 0.1) ** 2 - (x[4] * x[5]) ** 2)'\n", + "\n", + "name = 'Sensi_Test'\n", + "\n", + "kwargs = {'fuzzy_variables': fuzzy_input_variables, \n", + " 'obj_function': obj_function, 'name': name}" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\rSensitivity Analysis: 0%| | 0/6 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fuzzySensi = Fuzzy_Sensitivity_Analysis(**kwargs)\n", + "fuzzySensi.main(**kwargs)\n", + "fig, ax = fuzzySensi.barchart_plot()\n", + "plt.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\rSensitivity Analysis: 0%| | 0/6 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fuzzySensi = Fuzzy_Sensitivity_Analysis(**kwargs)\n", + "fuzzySensi.main(error=True, **kwargs)\n", + "fig, ax = fuzzySensi.barchart_plot()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/phuzzy/optimization/sensitivity_analysis.py b/phuzzy/optimization/sensitivity_analysis.py new file mode 100644 index 0000000..4e5bd9c --- /dev/null +++ b/phuzzy/optimization/sensitivity_analysis.py @@ -0,0 +1,271 @@ +import numpy as np +import pandas as pd + +import phuzzy +from phuzzy.optimization.alphaOpt import Alpha_Level_Optimization + +from tqdm import tqdm +import matplotlib.pyplot as plt + + + + +class Fuzzy_Sensitivity_Analysis(object): + + def __init__(self, **kwargs): + """ + :param input_fuzzy_var_list: List of all Fuzzy Variables + :param var_names_list: List of Names of all Fuzzy Variables + :param kwargs: Inputvariables / Fuzzyvariables / Name (name) / Objective Function (obj_function) / Objective Link (obj_link) + """ + + # READ FUZZY INPUT VARIABLES + if kwargs.get('fuzzy_variables') is not None: input_fuzzy_var_list = kwargs.get('fuzzy_variables') + else: raise ValueError('PLEASE IMPORT FUZZY VARIABLES as -- fuzzy_variables --') + + # Check for identical number_of_alpha_levels and adapt + self.fuzzy_var_list = Alpha_Level_Optimization._alpha_level_check(input_fuzzy_var_list) + + # Define Bounds of Each Alpha Level + self.global_bounds_DataArray = Alpha_Level_Optimization._boundary_constraints(self.fuzzy_var_list) + + # Setup Parameters + self.number_of_alpha_lvl = self.global_bounds_DataArray['number_of_alpha_levels'].size + self.x_glob = [] + + if kwargs.get('name') is None: self.name = 'Fuzzy Objective Value' + else: self.name = kwargs.get('name') + + + def main(self, search='quick', error=False, progressbar_disable = False, lsa=None, **kwargs): + """ + Calculate Fuzzy Sensitivity of Fuzzy Variables + + :param search: Search Precision of Optimization Routine (quick / default / deep) + :param error: include calculation of possible error margin + :param progressbar_disable: disable progressbar + :param lsa: local search algorithm (instead of uniform fuzzy variables it uses + deterministic variables to calculate the sensitivity at a certain Point) + :param kwargs: kwargs + :return: + """ + + if search == "quick": + self.n = 15 + self.iters = 2 + elif search == "default": + self.n = 30 + self.iters = 3 + elif search == 'deep': + self.n = 60 + self.iters = 4 + + + if not error: + self.sensis_total, self.sensis_l, self.sensis_r = \ + self._sensitivity_rotuine(progressbar_disable = progressbar_disable, **kwargs) + else: + sensi_total_dict = {} + sensi_l_dict = {} + sensi_r_dict = {} + #with tqdm(total=7,desc='Average Sensitivity Analysis',leave=True, disable=True) as pbar_glob: + for alpha_Level in range(3,9): + sensis_total, sensis_l, sensis_r =\ + self._sensitivity_rotuine(alpha_Level=alpha_Level, progressbar_disable = progressbar_disable, lsa=lsa, + leave=False,**kwargs) + sensi_total_dict[str(alpha_Level)] = sensis_total + sensi_l_dict[str(alpha_Level)] = sensis_l + sensi_r_dict[str(alpha_Level)] = sensis_r + #pbar_glob.update() + + + self.sensi_total_df = pd.DataFrame.from_dict(sensi_total_dict) + self.sensi_l_df = pd.DataFrame.from_dict(sensi_l_dict) + self.sensi_r_df = pd.DataFrame.from_dict(sensi_r_dict) + + self.sensis_total = self.sensi_total_df.mean(axis=1).values + self.sensis_total_var = self.sensi_total_df.var(axis=1).values + self.sensis_l = self.sensi_l_df.mean(axis=1).values + self.sensis_l_var = self.sensi_l_df.var(axis=1).values + self.sensis_r = self.sensi_r_df.mean(axis=1).values + self.sensis_r_var = self.sensi_r_df.var(axis=1).values + + + def barchart_plot(self, plot=False): + + y_pos = np.arange(self.global_bounds_DataArray['fuzzy_variable'].size) + fig, ax = plt.subplots(nrows=1, ncols=2, sharex=False,figsize=(10,len(y_pos))) + width = 0.6 + width2 = 0.3 + + if hasattr(self,'sensis_total_var') == True: + fig.suptitle('Average Fuzzy Sensitivity Analysis') + ax[0].set_title('Average Total Relative Sensitivity') + ax[0].set_yticks(y_pos) + ax[0].set_yticklabels(self.global_bounds_DataArray['fuzzy_variable'].values) + ax[0].invert_yaxis() # labels read top-to-bottom + ax[0].set_ylabel('Variables') + ax[0].set_xlabel('Total Sensitivity') + ax[0].set_axisbelow(True) + ax[0].set_xlim(0,max(self.sensis_total)+0.1) + ax[0].grid(b=True, which='major') + ax[0].grid(b=True, which='minor') + formatted_sensis = [round(elem, 3) for elem in self.sensis_total] + for i, v in enumerate(formatted_sensis): + ax[0].text(v+0.001, i-0.055, " "+str(v), color='black', va='center',fontweight='bold') + ax[0].barh(y_pos, self.sensis_total, width, xerr=self.sensis_total_var, align='center', color='lightslategrey', ecolor='black') + ax[1].set_title('Average Absolute Left / Right Sensitivity') + ax[1].set_yticks(y_pos) + ax[1].set_yticklabels(self.global_bounds_DataArray['fuzzy_variable'].values) + ax[1].invert_yaxis() # labels read top-to-bottom + ax[1].set_ylabel('Variables') + ax[1].set_xlabel('Relative Partial Sensitivity') + ax[1].set_axisbelow(True) + ax[1].set_xlim(0,max(max(self.sensis_l),max(self.sensis_r))+0.1) + ax[1].grid(b=True, which='major') + ax[1].grid(b=True, which='minor') + formatted_sensis_l = [round(elem, 3) for elem in self.sensis_l] + formatted_sensis_r = [round(elem, 3) for elem in self.sensis_r] + for (i_l, v_l),(i_r, v_r) in zip(enumerate(formatted_sensis_l),enumerate(formatted_sensis_r)): + ax[1].text(v_l+0.001, i_l-0.5*width2-0.055, " "+str(v_l), color='black', va='center',fontweight='bold') + ax[1].text(v_r+0.001, i_r+0.5*width2+0.055, " "+str(v_r), color='black', va='center',fontweight='bold') + ax[1].barh(y_pos-0.5*width2, self.sensis_l, width2, xerr=self.sensis_l_var, color='SkyBlue', label='Left', ecolor='black') + ax[1].barh(y_pos+0.5*width2, self.sensis_r, width2, xerr=self.sensis_r_var, color='IndianRed', label='Right', ecolor='black') + ax[1].legend(fancybox=True, framealpha=0.5) + + else: + + fig.suptitle('Fuzzy Sensitivity Analysis') + ax[0].set_title('Total Relative Sensitivity') + ax[0].set_yticks(y_pos) + ax[0].set_yticklabels(self.global_bounds_DataArray['fuzzy_variable'].values) + ax[0].invert_yaxis() # labels read top-to-bottom + ax[0].set_ylabel('Variables') + ax[0].set_xlabel('Total Sensitivity') + ax[0].set_axisbelow(True) + ax[0].set_xlim(left=0,right=max(self.sensis_total)+0.1) + ax[0].grid(b=True, which='major') + ax[0].grid(b=True, which='minor') + formatted_sensis = [round(elem, 3) for elem in self.sensis_total] + for i, v in enumerate(formatted_sensis): + ax[0].text(v+0.001, i-0.055, " "+str(v), color='black', va='center',fontweight='bold') + ax[0].barh(y_pos, self.sensis_total, width, align='center', color='lightslategrey') + ax[1].set_title('Absolute Left / Right Sensitivity') + ax[1].set_yticks(y_pos) + ax[1].set_yticklabels(self.global_bounds_DataArray['fuzzy_variable'].values) + ax[1].invert_yaxis() # labels read top-to-bottom + ax[1].set_ylabel('Variables') + ax[1].set_xlabel('Relative Partial Sensitivity') + ax[1].set_axisbelow(True) + ax[1].set_xlim(0,max(max(self.sensis_l),max(self.sensis_r))+0.1) + ax[1].grid(b=True, which='major') + ax[1].grid(b=True, which='minor') + formatted_sensis_l = [round(elem, 3) for elem in self.sensis_l] + formatted_sensis_r = [round(elem, 3) for elem in self.sensis_r] + for (i_l, v_l),(i_r, v_r) in zip(enumerate(formatted_sensis_l),enumerate(formatted_sensis_r)): + ax[1].text(v_l+0.001, i_l-0.5*width2-0.055, " "+str(v_l), color='black', va='center',fontweight='bold') + ax[1].text(v_r+0.001, i_r+0.5*width2+0.055, " "+str(v_r), color='black', va='center',fontweight='bold') + ax[1].barh(y_pos-0.5*width2, self.sensis_l, width2, color='SkyBlue', label='Left') + ax[1].barh(y_pos+0.5*width2, self.sensis_r, width2, color='IndianRed', label='Right') + ax[1].legend(fancybox=True, framealpha=0.5) + + + if plot: plt.show() + return fig, ax + + + def _sensitivity_rotuine(self, alpha_Level=None, progressbar_disable = False, lsa=None, leave=False, **kwargs): + res_total = [] + res_l = [] + res_r = [] + + s_sum = 0 + l_sum = 0 + r_sum = 0 + + if alpha_Level is not None: + self.fuzzy_variables_dict= self._change_alpha_level(alpha_Level=alpha_Level) + self.global_bounds_DataArray = Alpha_Level_Optimization._boundary_constraints(self.fuzzy_var_list) + self.number_of_alpha_lvl = self.global_bounds_DataArray['number_of_alpha_levels'].size + + index = np.arange(len(self.global_bounds_DataArray)) + + with tqdm(total=len(index), desc='Sensitivity Analysis', disable = progressbar_disable, leave=leave) as pbar_loc: + + for i in range(len(index)): + name_i = self.global_bounds_DataArray['fuzzy_variable'].values[i] + df_i = self.global_bounds_DataArray.data[i] + fuzzy_var_list_new = [] + names_new = [] + + for j in range(len(index)): + name_j = self.global_bounds_DataArray['fuzzy_variable'].values[j] + df_j = self.global_bounds_DataArray.data[j] + + if name_i == name_j: + fuzzy_var_list_new.append(self.fuzzy_var_list[i]) + names_new.append(name_i) + else: + if lsa is None: + y = phuzzy.Uniform(alpha0=[df_j[0][1], df_j[0][2]], + number_of_alpha_levels=self.number_of_alpha_lvl) + else: + y = phuzzy.Uniform(alpha0=[lsa[j],lsa[j]], number_of_alpha_levels=self.number_of_alpha_lvl) + + fuzzy_var_list_new.append(y) + + #names_new.append(name_j) + #kwargs = {**self.input_dict , **fvars} + #kwargs = {self.input_dict ,fvars} + + kwargs['fuzzy_variables'] = fuzzy_var_list_new + + z = Alpha_Level_Optimization(**kwargs) + z.main(n=self.n,iters=self.iters, progressbar_disable=True, backup=False) + + E = 1. - (z.df['r']-z.df['l'])/(z.df['r'].iloc[0]-z.df['l'].iloc[0]) + full_length = abs(z.df['r']-z.df['l']) + E_l = abs(((z.df['l'].iloc[-1]-z.df['l'])/full_length)/(len(z.df)-1)) + E_r = abs(((z.df['r']-z.df['r'].iloc[-1])/full_length)/(len(z.df)-1)) + + sensi_i = sum(E) + sensi_l = sum(E_l) + sensi_r = sum(E_r) + + s_sum += sensi_i + l_sum += sensi_l + r_sum += sensi_r + + res_total.append(sensi_i) + res_l.append(sensi_l) + res_r.append(sensi_r) + + pbar_loc.update() + + sensis_total = [] + sensis_l = [] + sensis_r = [] + + for i in range(len(res_total)): + sensis_total_j = res_total[i] / s_sum + sensis_l_j = res_l[i] + sensis_r_j = res_r[i] + + sensis_total.append(sensis_total_j) + sensis_l.append(sensis_l_j) + sensis_r.append(sensis_r_j) + + + return sensis_total, sensis_l, sensis_r + + + def _change_alpha_level(self, alpha_Level): + input_fuzzy_var_list = self.fuzzy_var_list + for i, fuzzy_var in enumerate(self.fuzzy_var_list): + fuzzy_var.convert_df(alpha_levels=alpha_Level) + input_fuzzy_var_list[i] = fuzzy_var + return input_fuzzy_var_list + + +if __name__ == "__main__": + pass diff --git a/phuzzy/shapes/__init__.py b/phuzzy/shapes/__init__.py index 98160a3..4d82a9c 100644 --- a/phuzzy/shapes/__init__.py +++ b/phuzzy/shapes/__init__.py @@ -43,7 +43,8 @@ def _get_number_of_alpha_levels(self): return self._number_of_alpha_levels def _set_number_of_alpha_levels(self, value): - self._number_of_alpha_levels = int(value) + self.convert_df(alpha_levels=int(value)) + # self._number_of_alpha_levels = int(value) number_of_alpha_levels = property(fget=_get_number_of_alpha_levels, fset=_set_number_of_alpha_levels, doc="number of alpha levels") @@ -74,7 +75,7 @@ def discretize(self, alpha0, alpha1, alpha_levels): def convert_df(self, alpha_levels=None, zero=0): df = self.df.copy() if alpha_levels is not None: - self.number_of_alpha_levels = alpha_levels + self._number_of_alpha_levels = int(alpha_levels) df.sort_values(['alpha'], ascending=[True], inplace=True) # print("!",df) xs_l = df.l.values @@ -815,6 +816,7 @@ def get_alpha_from_value(self, x): shape = self.get_shape() return np.interp(x, shape.x, shape.alpha) + class Triangle(FuzzyNumber): """triange fuzzy number""" @@ -906,7 +908,7 @@ def cdf(self, x, **kwargs): def to_str(self): if len(self.df) > 0: - return "tri[{:.3g}, {:.3g}, {:.3g}]".format(self.df.iloc[0].l, self.df.iloc[0].r, self.df.iloc[-1].l) + return "tri[{:.5g}, {:.5g}, {:.5g}]".format(self.df.iloc[0].l, self.df.iloc[0].r, self.df.iloc[-1].l) else: return "tri[nan, nan, nan]" @@ -984,7 +986,7 @@ def cdf(self, x, **kwargs): def to_str(self): if len(self.df) > 0: - return "trap[{:.3g}, {:.3g}, {:.3g}, {:.3g}]".format(self.df.iloc[0].l, self.df.iloc[0].r, + return "trap[{:.5g}, {:.5g}, {:.5g}, {:.5g}]".format(self.df.iloc[0].l, self.df.iloc[0].r, self.df.iloc[-1].l, self.df.iloc[-1].r) else: return "trap[nan, nan, nan, nan]" @@ -1044,7 +1046,7 @@ def cdf(self, x, **kwargs): return np.select(condlist, choicelist) def to_str(self): - return "Uniform[{:.4g},{:.4g}]".format(self.alpha0.l, self.alpha0.r) + return "Uniform[{:.5g},{:.5g}]".format(self.alpha0.l, self.alpha0.r) # @classmethod # def from_str(cls, s): diff --git a/phuzzy/shapes/superellipse.py b/phuzzy/shapes/superellipse.py index 8f902b5..b709948 100644 --- a/phuzzy/shapes/superellipse.py +++ b/phuzzy/shapes/superellipse.py @@ -14,7 +14,6 @@ """ - from phuzzy.shapes import FuzzyNumber import numpy as np import pandas as pd diff --git a/tests/test_data.py b/tests/test_data.py index d4a59b5..70c8666 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -15,7 +15,7 @@ def test_bootstrapping(): df_boot = data.bootstrap(n=1000) print(df_boot.head()) - phuzzy.data.plots.bootstrapping(data, df_boot, show=True) + # phuzzy.data.plots.bootstrapping(data, df_boot, show=True) def test_shuffling(): @@ -27,7 +27,7 @@ def test_shuffling(): df_boot = data.shuffling(n=1000, train_fraction=.7) print(df_boot.head()) - phuzzy.data.plots.bootstrapping(data, df_boot, show=True) + # phuzzy.data.plots.bootstrapping(data, df_boot, show=True) def test_estimate_probability(): @@ -37,7 +37,7 @@ def test_estimate_probability(): data = phuzzy.data.Data(raw_data) df = data.estimate_probability() print(df) - phuzzy.data.plots.p_estimates(df, show=True) + # phuzzy.data.plots.p_estimates(df, show=True) def test_histogram(): @@ -63,4 +63,4 @@ def test_histogram(): ax.set_ylabel("frequency") axcdf.set_xlabel("x") axcdf.set_ylabel("p") - plt.show() + # plt.show() diff --git a/tests/test_fuzzy analysis.ipynb b/tests/test_fuzzy analysis.ipynb new file mode 100644 index 0000000..aa25b4b --- /dev/null +++ b/tests/test_fuzzy analysis.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import phuzzy\n", + "import phuzzy.analysis\n", + "import numpy as np\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(FuzzyAnalysis:'FuzzyAnalysis N.N.', dv=[TruncNorm(x:[[-6,2], [-2,-2]]), Triangle(y:[[-6,6], [6,6]]), Triangle(z:[[-6,6], [6,6]])]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x.sk=0.014\ny.sk=0.99\nz.sk=0\n" + ] + }, + { + "data": { + "text/plain": [ + "[0.01382664604097675, 0.9861733539590233, 0.0]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "a = 1.23\n", + "def f(x):\n", + " x1 = x[0]\n", + " x2 = x[1]\n", + " return (x1+1.15)**2+(x2+.4)**4 + a\n", + "\n", + "x = phuzzy.TruncNorm(alpha0=[-6, 2], name=\"x\", number_of_alpha_levels=2)\n", + "y = phuzzy.Triangle(alpha0=[-6, 6], alpha1=[6], name=\"y\", number_of_alpha_levels=2)\n", + "z = phuzzy.Triangle(alpha0=[-6, 6], alpha1=[6], name=\"z\", number_of_alpha_levels=2)\n", + "\n", + "pa = phuzzy.analysis.FuzzyAnalysis(designvars=[x, y, z], function=f)\n", + "print(pa)\n", + "\n", + "pa.lcefa()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(FuzzyAnalysis:'FuzzyAnalysis N.N.', dv=[TruncNorm(x:[[0,6.28], [3.14,3.14]]), Triangle(y:[[0,6.28], [6,6]])]\n" + ] + }, + { + "ename": "AttributeError", + "evalue": "'TruncNorm' object has no attribute 'sin'", + "traceback": [ + "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m\u001b[0m\n\u001b[0;32m 10\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpa\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 11\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 12\u001b[1;33m \u001b[0msensibilities\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mpa\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlcefa\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 13\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0msensibilities\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 14\u001b[0m \u001b[1;32massert\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0misclose\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msk\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m.5\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m~\\PycharmProjects\\phuzzy\\phuzzy\\analysis\\__init__.py\u001b[0m in \u001b[0;36mlcefa\u001b[1;34m(self)\u001b[0m\n\u001b[0;32m 94\u001b[0m \u001b[0mfvars\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0my\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 95\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 96\u001b[1;33m \u001b[0mz\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mfunction\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfvars\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 97\u001b[0m \u001b[0mz\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdesignvars\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 98\u001b[0m \u001b[0mdflr\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mz\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdf\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"l\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;34m\"r\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;32m\u001b[0m in \u001b[0;36mf\u001b[1;34m(x)\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[0mx1\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[0mx2\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mx\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mx1\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m4\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mx2\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[0mx\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mphuzzy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mTruncNorm\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0malpha0\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0mnp\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpi\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mname\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;34m\"x\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnumber_of_alpha_levels\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", + "\u001b[1;31mAttributeError\u001b[0m: 'TruncNorm' object has no attribute 'sin'" + ], + "output_type": "error" + } + ], + "source": [ + "def f(x):\n", + " x1 = x[0]\n", + " x2 = x[1]\n", + " return np.sin(x1) + np.sin(4*x2)\n", + "\n", + "x = phuzzy.TruncNorm(alpha0=[0, 2*np.pi], name=\"x\", number_of_alpha_levels=2)\n", + "y = phuzzy.Triangle(alpha0=[0, 2*np.pi], alpha1=[6], name=\"y\", number_of_alpha_levels=2)\n", + "\n", + "pa = phuzzy.analysis.FuzzyAnalysis(designvars=[x, y], function=f)\n", + "print(pa)\n", + "\n", + "sensibilities = pa.lcefa()\n", + "print(sensibilities)\n", + "assert np.isclose(x.sk, .5)\n", + "assert np.isclose(y.sk, .5)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/tests/test_fuzzy_analysis.py b/tests/test_fuzzy_analysis.py index 6c814f7..b674684 100644 --- a/tests/test_fuzzy_analysis.py +++ b/tests/test_fuzzy_analysis.py @@ -91,3 +91,10 @@ def f(x): print(sensibilities) assert np.isclose(x.sk, .5) assert np.isclose(y.sk, .5) + + + + + + +test_lcefa() diff --git a/tests/test_fuzzy_number.py b/tests/test_fuzzy_number.py index ff82a0d..b0020d4 100644 --- a/tests/test_fuzzy_number.py +++ b/tests/test_fuzzy_number.py @@ -7,6 +7,17 @@ import numpy as np from io import StringIO +def test_number_of_alpha_levels(): + t = phuzzy.Triangle(alpha0=[1, 3], alpha1=[2], number_of_alpha_levels=4) + print(t.number_of_alpha_levels) + print(t.df) + assert t.number_of_alpha_levels==4 + t.number_of_alpha_levels = 5 + # t.convert_df(alpha_levels=5) + print(t.number_of_alpha_levels) + print(t.df) + + def test_fuzzy(): n = phuzzy.FuzzyNumber() print(n)