2018, Machine Intelligence and Data Science Laboratory - UFMG - Brazil

Machine Intelligence and Data Science Laboratory - UFMG - Brazil

SelecaoSimples_MenorRMSE() (in module pyFTS.benchmarks.benchmarks)

get_benchmark_interval_methods() (in module pyFTS.benchmarks.benchmarks)

get_benchmark_point_methods() (in module pyFTS.benchmarks.benchmarks)

get_benchmark_probabilistic_methods() (in module pyFTS.benchmarks.benchmarks)

get_probabilistic_methods() (in module pyFTS.benchmarks.benchmarks)

pftsExploreOrderAndPartitions() (in module pyFTS.benchmarks.benchmarks)

plot_compared_intervals_ahead() (in module pyFTS.benchmarks.benchmarks)

plot_probability_distributions() (in module pyFTS.benchmarks.benchmarks)

print_distribution_statistics() (in module pyFTS.benchmarks.benchmarks)

print_interval_statistics() (in module pyFTS.benchmarks.benchmarks)

print_point_statistics() (in module pyFTS.benchmarks.benchmarks)

process_probabilistic_jobs() (in module pyFTS.benchmarks.benchmarks)

sliding_window_benchmarks() (in module pyFTS.benchmarks.benchmarks)

get_distribution_statistics() (in module pyFTS.benchmarks.Measures)

compare_residuals() (in module pyFTS.benchmarks.ResidualAnalysis)

single_plot_residuals() (in module pyFTS.benchmarks.ResidualAnalysis)

cast_dataframe_to_synthetic_interval() (in module pyFTS.benchmarks.Util)

cast_dataframe_to_synthetic_point() (in module pyFTS.benchmarks.Util)

cast_dataframe_to_synthetic_probabilistic() (in module pyFTS.benchmarks.Util)

interval_dataframe_analytic_columns() (in module pyFTS.benchmarks.Util)

interval_dataframe_synthetic_columns() (in module pyFTS.benchmarks.Util)

plot_dataframe_interval_pinball() (in module pyFTS.benchmarks.Util)

plot_dataframe_probabilistic() (in module pyFTS.benchmarks.Util)

point_dataframe_analytic_columns() (in module pyFTS.benchmarks.Util)

point_dataframe_synthetic_columns() (in module pyFTS.benchmarks.Util)

probabilistic_dataframe_analytic_columns() (in module pyFTS.benchmarks.Util)

probabilistic_dataframe_synthetic_columns() (in module pyFTS.benchmarks.Util)

save_dataframe_probabilistic() (in module pyFTS.benchmarks.Util)

unified_scaled_interval_pinball() (in module pyFTS.benchmarks.Util)

unified_scaled_probabilistic() (in module pyFTS.benchmarks.Util)

forecast_ahead_distribution() (pyFTS.benchmarks.arima.ARIMA method)

forecast_distribution() (pyFTS.benchmarks.knn.KNearestNeighbors method)

forecast() (pyFTS.benchmarks.quantreg.QuantileRegression method)

forecast_ahead_interval() (pyFTS.benchmarks.quantreg.QuantileRegression method)

forecast_distribution() (pyFTS.benchmarks.quantreg.QuantileRegression method)

forecast_interval() (pyFTS.benchmarks.quantreg.QuantileRegression method)

interval_to_interval() (pyFTS.benchmarks.quantreg.QuantileRegression method)

linearmodel() (pyFTS.benchmarks.quantreg.QuantileRegression method)

point_to_interval() (pyFTS.benchmarks.quantreg.QuantileRegression method)

generate_high_order_recurrent_flr() (in module pyFTS.common.FLR)

get_maximum_membership_fuzzyset() (in module pyFTS.common.FuzzySet)

get_maximum_membership_fuzzyset_index() (in module pyFTS.common.FuzzySet)

around() (pyFTS.common.SortedCollection.SortedCollection method)

between() (pyFTS.common.SortedCollection.SortedCollection method)

find_ge() (pyFTS.common.SortedCollection.SortedCollection method)

find_gt() (pyFTS.common.SortedCollection.SortedCollection method)

find_le() (pyFTS.common.SortedCollection.SortedCollection method)

find_lt() (pyFTS.common.SortedCollection.SortedCollection method)

insert() (pyFTS.common.SortedCollection.SortedCollection method)

insert_right() (pyFTS.common.SortedCollection.SortedCollection method)

inside() (pyFTS.common.SortedCollection.SortedCollection method)

remove() (pyFTS.common.SortedCollection.SortedCollection method)

apply() (pyFTS.common.Transformations.AdaptiveExpectation method)

inverse() (pyFTS.common.Transformations.AdaptiveExpectation method)

parameters (pyFTS.common.Transformations.AdaptiveExpectation attribute)

parameters (pyFTS.common.Transformations.Differential attribute)

flr_membership_matrix() (pyFTS.models.song.ConventionalFTS method)

get_sequence_membership() (pyFTS.models.ifts.IntervalFTS method)

append_rhs() (pyFTS.models.ismailefendi.ImprovedWeightedFLRG method)

weights() (pyFTS.models.ismailefendi.ImprovedWeightedFLRG method)

forecast() (pyFTS.models.ismailefendi.ImprovedWeightedFTS method)

generate_flrg() (pyFTS.models.ismailefendi.ImprovedWeightedFTS method)

append_rhs() (pyFTS.models.pwfts.ProbabilisticWeightedFLRG method)

get_lower() (pyFTS.models.pwfts.ProbabilisticWeightedFLRG method)

get_membership() (pyFTS.models.pwfts.ProbabilisticWeightedFLRG method)

get_midpoint() (pyFTS.models.pwfts.ProbabilisticWeightedFLRG method)

get_upper() (pyFTS.models.pwfts.ProbabilisticWeightedFLRG method)

partition_function() (pyFTS.models.pwfts.ProbabilisticWeightedFLRG method)

add_new_PWFLGR() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

forecast_ahead() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

forecast_ahead_interval() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

forecast_distribution() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

forecast_interval() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

generate_flrg() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

generate_lhs_flrg() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

get_lower() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

get_midpoint() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

get_upper() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

interval_heuristic() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

interval_quantile() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

point_expected_value() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

point_heuristic() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

update_model() (pyFTS.models.pwfts.ProbabilisticWeightedFTS method)

append_rhs() (pyFTS.models.sadaei.ExponentialyWeightedFLRG method)

generate_flrg() (pyFTS.models.sadaei.ExponentialyWeightedFTS method)

train() (pyFTS.models.ensemble.ensemble.AllMethodEnsembleFTS method)

append_model() (pyFTS.models.ensemble.ensemble.EnsembleFTS method)

forecast_ahead_interval() (pyFTS.models.ensemble.ensemble.EnsembleFTS method)

forecast_distribution() (pyFTS.models.ensemble.ensemble.EnsembleFTS method)

forecast_interval() (pyFTS.models.ensemble.ensemble.EnsembleFTS method)

get_interval() (pyFTS.models.ensemble.ensemble.EnsembleFTS method)

get_models_forecasts() (pyFTS.models.ensemble.ensemble.EnsembleFTS method)

parameters (pyFTS.models.ensemble.ensemble.EnsembleFTS attribute)

SeasonalEnsembleFTS (class in pyFTS.models.ensemble.multiseasonal)

train() (pyFTS.models.ensemble.multiseasonal.SeasonalEnsembleFTS method)

update_uod() (pyFTS.models.ensemble.multiseasonal.SeasonalEnsembleFTS method)

train_individual_model() (in module pyFTS.models.ensemble.multiseasonal)

append_variable() (pyFTS.models.multivariate.mvfts.MVFTS method)

apply_transformations() (pyFTS.models.multivariate.mvfts.MVFTS method)

clone_parameters() (pyFTS.models.multivariate.mvfts.MVFTS method)

generate_lhs_flrs() (pyFTS.models.multivariate.mvfts.MVFTS method)

apply_transformations() (pyFTS.models.multivariate.variable.Variable method)

data_label (pyFTS.models.multivariate.variable.Variable attribute)

get_midpoint() (pyFTS.models.nonstationary.common.FuzzySet method)

location_params (pyFTS.models.nonstationary.common.FuzzySet attribute)

membership() (pyFTS.models.nonstationary.common.FuzzySet method)

noise_params (pyFTS.models.nonstationary.common.FuzzySet attribute)

perform_location() (pyFTS.models.nonstationary.common.FuzzySet method)

perform_width() (pyFTS.models.nonstationary.common.FuzzySet method)

perturbate_parameters() (pyFTS.models.nonstationary.common.FuzzySet method)

width_params (pyFTS.models.nonstationary.common.FuzzySet attribute)

check_bounds_index() (in module pyFTS.models.nonstationary.common)

ConditionalVarianceFTS (class in pyFTS.models.nonstationary.cvfts)

forecast() (pyFTS.models.nonstationary.cvfts.ConditionalVarianceFTS method)

generate_flrg() (pyFTS.models.nonstationary.cvfts.ConditionalVarianceFTS method)

train() (pyFTS.models.nonstationary.cvfts.ConditionalVarianceFTS method)

HighOrderNonstationaryFLRG (class in pyFTS.models.nonstationary.cvfts)

get_key() (pyFTS.models.nonstationary.flrg.NonStationaryFLRG method)

get_lower() (pyFTS.models.nonstationary.flrg.NonStationaryFLRG method)

get_membership() (pyFTS.models.nonstationary.flrg.NonStationaryFLRG method)

get_midpoint() (pyFTS.models.nonstationary.flrg.NonStationaryFLRG method)

get_upper() (pyFTS.models.nonstationary.flrg.NonStationaryFLRG method)

unpack_args() (pyFTS.models.nonstationary.flrg.NonStationaryFLRG method)

HighOrderNonStationaryFLRG (class in pyFTS.models.nonstationary.honsfts)

HighOrderNonStationaryFTS (class in pyFTS.models.nonstationary.honsfts)

forecast() (pyFTS.models.nonstationary.honsfts.HighOrderNonStationaryFTS method)

train() (pyFTS.models.nonstationary.honsfts.HighOrderNonStationaryFTS method)

ConventionalNonStationaryFLRG (class in pyFTS.models.nonstationary.nsfts)

forecast() (pyFTS.models.nonstationary.nsfts.NonStationaryFTS method)

forecast_interval() (pyFTS.models.nonstationary.nsfts.NonStationaryFTS method)

generate_flrg() (pyFTS.models.nonstationary.nsfts.NonStationaryFTS method)

train() (pyFTS.models.nonstationary.nsfts.NonStationaryFTS method)

PolynomialNonStationaryPartitioner (class in pyFTS.models.nonstationary.partitioners)

SimpleNonStationaryPartitioner (class in pyFTS.models.nonstationary.partitioners)

simplenonstationary_gridpartitioner_builder() (in module pyFTS.models.nonstationary.partitioners)

exponential() (in module pyFTS.models.nonstationary.perturbation)

polynomial() (in module pyFTS.models.nonstationary.perturbation)

plot_sets_conditional() (in module pyFTS.models.nonstationary.util)

DataFrameSeasonalIndexer (class in pyFTS.models.seasonal.SeasonalIndexer)

DateTimeSeasonalIndexer (class in pyFTS.models.seasonal.SeasonalIndexer)

LinearSeasonalIndexer (class in pyFTS.models.seasonal.SeasonalIndexer)

get_data() (pyFTS.models.seasonal.SeasonalIndexer.LinearSeasonalIndexer method)

SeasonalIndexer (class in pyFTS.models.seasonal.SeasonalIndexer)

get_data() (pyFTS.models.seasonal.SeasonalIndexer.SeasonalIndexer method)

get_index() (pyFTS.models.seasonal.SeasonalIndexer.SeasonalIndexer method)

ContextualMultiSeasonalFTS (class in pyFTS.models.seasonal.cmsfts)

forecast() (pyFTS.models.seasonal.cmsfts.ContextualMultiSeasonalFTS method)

generate_flrg() (pyFTS.models.seasonal.cmsfts.ContextualMultiSeasonalFTS method)

get_midpoints() (pyFTS.models.seasonal.cmsfts.ContextualMultiSeasonalFTS method)

train() (pyFTS.models.seasonal.cmsfts.ContextualMultiSeasonalFTS method)

append_rhs() (pyFTS.models.seasonal.cmsfts.ContextualSeasonalFLRG method)

minute_of_hour (pyFTS.models.seasonal.common.DateTime attribute)

minute_of_month (pyFTS.models.seasonal.common.DateTime attribute)

minute_of_week (pyFTS.models.seasonal.common.DateTime attribute)

minute_of_year (pyFTS.models.seasonal.common.DateTime attribute)

second_of_hour (pyFTS.models.seasonal.common.DateTime attribute)

second_of_minute (pyFTS.models.seasonal.common.DateTime attribute)

forecast() (pyFTS.models.seasonal.msfts.MultiSeasonalFTS method)

forecast_ahead() (pyFTS.models.seasonal.msfts.MultiSeasonalFTS method)

generate_flrg() (pyFTS.models.seasonal.msfts.MultiSeasonalFTS method)

TimeGridPartitioner (class in pyFTS.models.seasonal.partitioner)

build() (pyFTS.models.seasonal.partitioner.TimeGridPartitioner method)

plot() (pyFTS.models.seasonal.partitioner.TimeGridPartitioner method)

membership_function (pyFTS.partitioners.partitioner.Partitioner attribute)

partitions (pyFTS.partitioners.partitioner.Partitioner attribute)

transformation (pyFTS.partitioners.partitioner.Partitioner attribute)

explore_partitioners() (in module pyFTS.partitioners.parallel_util)

ProbabilityDistribution (class in pyFTS.probabilistic.ProbabilityDistribution)

kernel_function() (pyFTS.probabilistic.kde.KernelSmoothing method)

Using locks, conditions and semaphores in the async with statement

email.message.Message: Representing an email message using the compat32 API

What is the most efficient way to concatenate many strings together?

Why does a_tuple[i] += [‘item’] raise an exception when the addition works?

PEP 3118: New memoryview implementation and buffer protocol documentation

PEP 476: Enabling certificate verification by default for stdlib http clients

PEP 471 - os.scandir() function – a better and faster directory iterator

Why must ‘self’ be used explicitly in method definitions and calls?

argparse — Parser for command-line options, arguments and sub-commands

functools — Higher-order functions and operations on callable objects

sysconfig — Provide access to Python’s configuration information

#!/usr/bin/python
# -*- coding: utf8 -*-

"""Benchmarks methods for FTS methods"""

import datetime
import time
from copy import deepcopy
import traceback

import matplotlib as plt
import matplotlib.cm as cmx
import matplotlib.colors as pltcolors
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

from pyFTS.probabilistic import ProbabilityDistribution
from pyFTS.common import Transformations
from pyFTS.models import song, chen, yu, ismailefendi, sadaei, hofts, pwfts, ifts, cheng, hwang
from pyFTS.models.ensemble import ensemble
from pyFTS.benchmarks import Measures, naive, arima, ResidualAnalysis, quantreg, knn
from pyFTS.benchmarks import Util as bUtil
from pyFTS.common import Util as cUtil
# from sklearn.cross_validation import KFold
from pyFTS.partitioners import Grid
from matplotlib import rc

#rc('font',**{'family':'sans-serif','sans-serif':['Helvetica']})
## for Palatino and other serif fonts use:
#rc('font',**{'family':'serif','serif':['Palatino']})
#rc('text', usetex=True)

colors = ['grey', 'darkgrey', 'rosybrown', 'maroon', 'red','orange', 'gold', 'yellow', 'olive', 'green',
          'darkgreen', 'cyan', 'lightblue','blue', 'darkblue', 'purple', 'darkviolet' ]

ncol = len(colors)

styles = ['-','--','-.',':','.']

nsty = len(styles)

def __pop(key, default, kwargs):
    if key in kwargs:
        return kwargs.pop(key)
    else:
        return default

def get_benchmark_point_methods():
    """Return all non FTS methods for point forecasting"""
    return [naive.Naive, arima.ARIMA, quantreg.QuantileRegression]

def get_point_methods():
    """Return all FTS methods for point forecasting"""
    return [song.ConventionalFTS, chen.ConventionalFTS, yu.WeightedFTS, ismailefendi.ImprovedWeightedFTS,
            cheng.TrendWeightedFTS, sadaei.ExponentialyWeightedFTS, hofts.HighOrderFTS, hwang.HighOrderFTS,
            pwfts.ProbabilisticWeightedFTS]

def get_benchmark_interval_methods():
    """Return all non FTS methods for point_to_interval forecasting"""
    return [ arima.ARIMA, quantreg.QuantileRegression]

def get_interval_methods():
    """Return all FTS methods for point_to_interval forecasting"""
    return [ifts.IntervalFTS, pwfts.ProbabilisticWeightedFTS]

def get_probabilistic_methods():
    """Return all FTS methods for probabilistic forecasting"""
    return [ensemble.AllMethodEnsembleFTS, pwfts.ProbabilisticWeightedFTS]

def get_benchmark_probabilistic_methods():
    return [arima.ARIMA, quantreg.QuantileRegression, knn.KNearestNeighbors]

def sliding_window_benchmarks(data, windowsize, train=0.8, **kwargs):
    """
    Sliding window benchmarks for FTS forecasters.

    For each data window, a train and test datasets will be splitted. For each train split, number of
    partitions and partitioning method will be created a partitioner model. And for each partitioner, order,
    steps ahead and FTS method a foreasting model will be trained.

    Then all trained models are benchmarked on the test data and the metrics are stored on a sqlite3 database
    (identified by the 'file' parameter) for posterior analysis.

    All these process can be distributed on a dispy cluster, setting the atributed 'distributed' to true and
    informing the list of dispy nodes on 'nodes' parameter.

    The number of experiments is determined by 'windowsize' and 'inc' parameters.

    :param data: test data
    :param windowsize: size of sliding window
    :param train: percentual of sliding window data used to train the models
    :param kwargs: dict, optional arguments

    :keyword benchmark_methods:  a list with Non FTS models to benchmark. The default is None.
    :keyword benchmark_methods_parameters:  a list with Non FTS models parameters. The default is None.
    :keyword benchmark_models: A boolean value indicating if external FTS methods will be used on benchmark. The default is False.
    :keyword build_methods: A boolean value indicating if the default FTS methods will be used on benchmark. The default is True.
    :keyword dataset: the dataset name to identify the current set of benchmarks results on database.
    :keyword distributed: A boolean value indicating if the forecasting procedure will be distributed in a dispy cluster. . The default is False
    :keyword file: file path to save the results. The default is benchmarks.db.
    :keyword inc: a float on interval [0,1] indicating the percentage of the windowsize to move the window
    :keyword methods: a list with FTS class names. The default depends on the forecasting type and contains the list of all FTS methods.
    :keyword models: a list with prebuilt FTS objects. The default is None.
    :keyword nodes: a list with the dispy cluster nodes addresses. The default is [127.0.0.1].
    :keyword orders: a list with orders of the models (for high order models). The default is [1,2,3].
    :keyword partitions: a list with the numbers of partitions on the Universe of Discourse. The default is [10].
    :keyword partitioners_models: a list with prebuilt Universe of Discourse partitioners objects. The default is None.
    :keyword partitioners_methods: a list with Universe of Discourse partitioners class names. The default is [partitioners.Grid.GridPartitioner].
    :keyword progress: If true a progress bar will be displayed during the benchmarks. The default is False.
    :keyword start: in the multi step forecasting, the index of the data where to start forecasting. The default is 0.
    :keyword steps_ahead: a list with  the forecasting horizons, i. e., the number of steps ahead to forecast. The default is 1.
    :keyword tag: a name to identify the current set of benchmarks results on database.
    :keyword type: the forecasting type, one of these values: point(default), interval or distribution. The default is point.
    :keyword transformations: a list with data transformations do apply . The default is [None].
    """

    tag = __pop('tag', None, kwargs)
    dataset = __pop('dataset', None, kwargs)

    distributed = __pop('distributed', False, kwargs)

    transformations = kwargs.get('transformations', [None])
    progress = kwargs.get('progress', None)
    type = kwargs.get("type", 'point')

    orders = __pop("orders", [1,2,3], kwargs)

    partitioners_models = __pop("partitioners_models", None, kwargs)
    partitioners_methods = __pop("partitioners_methods", [Grid.GridPartitioner], kwargs)
    partitions = __pop("partitions", [10], kwargs)

    steps_ahead = __pop('steps_ahead', [1], kwargs)

    methods = __pop('methods', None, kwargs)

    models = __pop('models', None, kwargs)

    pool = [] if models is None else models

    if methods is None:
        if type  == 'point':
            methods = get_point_methods()
        elif type == 'interval':
            methods = get_interval_methods()
        elif type == 'distribution':
            methods = get_probabilistic_methods()

    build_methods = __pop("build_methods", True, kwargs)

    if build_methods:
        for method in methods:
            mfts = method()

            if mfts.is_high_order:
                for order in orders:
                    if order >= mfts.min_order:
                        mfts = method()
                        mfts.order = order
                        pool.append(mfts)
            else:
                mfts.order = 1
                pool.append(mfts)

    benchmark_models = __pop("benchmark_models", False, kwargs)

    if benchmark_models != False:

        benchmark_methods = __pop("benchmark_methods", None, kwargs)
        benchmark_methods_parameters = __pop("benchmark_methods_parameters", None, kwargs)

        benchmark_pool = [] if ( benchmark_models is None or not isinstance(benchmark_models, list)) \
            else benchmark_models

        if benchmark_models is None and benchmark_methods is None:
            if type == 'point'or type  == 'partition':
                benchmark_methods = get_benchmark_point_methods()
            elif type == 'interval':
                benchmark_methods = get_benchmark_interval_methods()
            elif type == 'distribution':
                benchmark_methods = get_benchmark_probabilistic_methods()

        if benchmark_methods is not None:
            for transformation in transformations:
                for count, model in enumerate(benchmark_methods, start=0):
                    par = benchmark_methods_parameters[count]
                    mfts = model(**par)
                    mfts.append_transformation(transformation)
                    benchmark_pool.append(mfts)

    if type == 'point':
        experiment_method = run_point
        synthesis_method = process_point_jobs
    elif type == 'interval':
        experiment_method = run_interval
        synthesis_method = process_interval_jobs
    elif type == 'distribution':
        experiment_method = run_probabilistic
        synthesis_method = process_probabilistic_jobs
    else:
        raise ValueError("Type parameter has a unkown value!")

    if distributed:
        import dispy, dispy.httpd

        nodes = kwargs.get("nodes", ['127.0.0.1'])
        cluster, http_server = cUtil.start_dispy_cluster(experiment_method, nodes)

    jobs = []

    inc = __pop("inc", 0.1, kwargs)

    if progress:
        from tqdm import tqdm
        _tdata = len(data) / (windowsize * inc)
        _tasks = (len(partitioners_models) * len(orders) * len(partitions) * len(transformations) * len(steps_ahead))
        _tbcmk = len(benchmark_pool)*len(steps_ahead)
        progressbar = tqdm(total=_tdata*_tasks + _tdata*_tbcmk, desc="Benchmarks:")

    file = kwargs.get('file', "benchmarks.db")

    conn = bUtil.open_benchmark_db(file)

    for ct, train, test in cUtil.sliding_window(data, windowsize, train, inc=inc, **kwargs):
        if benchmark_models != False:
            for model in benchmark_pool:
                for step in steps_ahead:

                    kwargs['steps_ahead'] = step

                    if not distributed:
                        if progress:
                            progressbar.update(1)
                        try:
                            job = experiment_method(deepcopy(model), None, train, test, **kwargs)
                            synthesis_method(dataset, tag, job, conn)
                        except Exception as ex:
                            print('EXCEPTION! ', model.shortname, model.order)
                            traceback.print_exc()
                    else:
                        job = cluster.submit(deepcopy(model), None, train, test, **kwargs)
                        jobs.append(job)

        partitioners_pool = []

        if partitioners_models is None:

                for partition in partitions:

                    for partitioner in partitioners_methods:

                        data_train_fs = partitioner(data=train, npart=partition, transformation=transformation)

                        partitioners_pool.append(data_train_fs)
        else:
            partitioners_pool = partitioners_models

        for step in steps_ahead:

            for partitioner in partitioners_pool:

                for _id, model in enumerate(pool,start=0):

                        try:
                            job = experiment_method(deepcopy(model), deepcopy(partitioner), train, test, **kwargs)
                            print('EXCEPTION! ',model.shortname, model.order, partitioner.name,
                                  partitioner.partitions, str(partitioner.transformation))
                    else:
                        job = cluster.submit(deepcopy(model), deepcopy(partitioner), train, test, **kwargs)
                        job.id = id  # associate an ID to identify jobs (if needed later)

        progressbar.close()

        for job in jobs:
            if progress:
                progressbar.update(1)
            job()
            if job.status == dispy.DispyJob.Finished and job is not None:
                tmp = job.result
                synthesis_method(dataset, tag, tmp, conn)
            else:
                print("status",job.status)
                print("result",job.result)
                print("stdout",job.stdout)
                print("stderr",job.exception)

        cluster.wait()  # wait for all jobs to finish

        cUtil.stop_dispy_cluster(cluster, http_server)

    conn.close()

def run_point(mfts, partitioner, train_data, test_data, window_key=None, **kwargs):
    """
    Run the point forecasting benchmarks

    :param mfts: FTS model
    :param partitioner: Universe of Discourse partitioner
    :param train_data: data used to train the model
    :param test_data: ata used to test the model
    :param window_key: id of the sliding window
    :param transformation: data transformation
    :param indexer: seasonal indexer
    :return: a dictionary with the benchmark results
    """
    import time
    from pyFTS.models import yu, chen, hofts, pwfts,ismailefendi,sadaei, song, cheng, hwang
    from pyFTS.partitioners import Grid, Entropy, FCM
    from pyFTS.benchmarks import Measures, naive, arima, quantreg
    from pyFTS.common import Transformations

    tmp = [song.ConventionalFTS, chen.ConventionalFTS, yu.WeightedFTS, ismailefendi.ImprovedWeightedFTS,
           cheng.TrendWeightedFTS, sadaei.ExponentialyWeightedFTS, hofts.HighOrderFTS, hwang.HighOrderFTS,
           pwfts.ProbabilisticWeightedFTS]

    tmp2 = [Grid.GridPartitioner, Entropy.EntropyPartitioner, FCM.FCMPartitioner]

    tmp4 = [naive.Naive, arima.ARIMA, quantreg.QuantileRegression]

    tmp3 = [Measures.get_point_statistics]

    tmp5 = [Transformations.Differential]

    indexer = kwargs.get('indexer', None)

    steps_ahead = kwargs.get('steps_ahead', 1)
    method = kwargs.get('method', None)

    if mfts.benchmark_only:
        _key = mfts.shortname + str(mfts.order if mfts.order is not None else "")
    else:
        pttr = str(partitioner.__module__).split('.')[-1]
        _key = mfts.shortname + " n = " + str(mfts.order) + " " + pttr + " q = " + str(partitioner.partitions)
        mfts.partitioner = partitioner
        mfts.append_transformation(partitioner.transformation)

    _key += str(steps_ahead)
    _key += str(method) if method is not None else ""

    _start = time.time()
    mfts.fit(train_data, **kwargs)
    _end = time.time()
    times = _end - _start

    _rmse, _smape, _u = Measures.get_point_statistics(test_data, mfts, **kwargs)
    times += _end - _start

    ret = {'key': _key, 'obj': mfts, 'rmse': _rmse, 'smape': _smape, 'u': _u, 'time': times, 'window': window_key,
           'steps': steps_ahead, 'method': method}

    return ret

def run_interval(mfts, partitioner, train_data, test_data, window_key=None, **kwargs):
    """
    Run the interval forecasting benchmarks

    """
    from pyFTS.models import hofts,ifts,pwfts
    from pyFTS.benchmarks import Measures, arima, quantreg

    tmp = [hofts.HighOrderFTS, ifts.IntervalFTS,  pwfts.ProbabilisticWeightedFTS]

    tmp4 = [arima.ARIMA, quantreg.QuantileRegression]

    tmp3 = [Measures.get_interval_statistics]

        _key = mfts.shortname + str(mfts.order if mfts.order is not None else "") + str(mfts.alpha)
    else:

    #_sharp, _res, _cov, _q05, _q25, _q75, _q95, _w05, _w25
    metrics = Measures.get_interval_statistics(test_data, mfts, **kwargs)

    ret = {'key': _key, 'obj': mfts, 'sharpness': metrics[0], 'resolution': metrics[1], 'coverage': metrics[2],
           'time': times,'Q05': metrics[3], 'Q25': metrics[4], 'Q75': metrics[5], 'Q95': metrics[6],
           'winkler05': metrics[7], 'winkler25': metrics[8],
           'window': window_key,'steps': steps_ahead, 'method': method}

    return ret

def run_probabilistic(mfts, partitioner, train_data, test_data, window_key=None, **kwargs):
    """
    Run the probabilistic forecasting benchmarks

    :param steps:
    :param resolution:
    """
    import numpy as np
    from pyFTS.models import hofts, ifts, pwfts
    from pyFTS.models.ensemble import ensemble
    from pyFTS.benchmarks import Measures, arima, quantreg, knn
    from pyFTS.models.seasonal import SeasonalIndexer

    tmp = [hofts.HighOrderFTS, ifts.IntervalFTS, pwfts.ProbabilisticWeightedFTS, arima.ARIMA,
           ensemble.AllMethodEnsembleFTS, knn.KNearestNeighbors]

    tmp3 = [Measures.get_distribution_statistics, SeasonalIndexer.SeasonalIndexer, SeasonalIndexer.LinearSeasonalIndexer]

    else:

    if mfts.has_seasonality:
        mfts.indexer = indexer

    _crps1, _t1, _brier = Measures.get_distribution_statistics(test_data, mfts, **kwargs)
    _t1 += times

    ret = {'key': _key, 'obj': mfts, 'CRPS': _crps1, 'time': _t1, 'brier': _brier, 'window': window_key,

    return ret

def process_point_jobs(dataset, tag,  job, conn):
    """
    Extract information from a dictionary with point benchmark results and save it on a database

    :param dataset: the benchmark dataset name
    :param tag: alias for the benchmark group being executed
    :param job: a dictionary with the benchmark results
    :param conn: a connection to a Sqlite database
    :return:
    """

    data = bUtil.process_common_data(dataset, tag, 'point',job)

    rmse = deepcopy(data)
    rmse.extend(["rmse", job["rmse"]])
    bUtil.insert_benchmark(rmse, conn)
    smape = deepcopy(data)
    smape.extend(["smape", job["smape"]])
    bUtil.insert_benchmark(smape, conn)
    u = deepcopy(data)
    u.extend(["u", job["u"]])
    bUtil.insert_benchmark(u, conn)
    time = deepcopy(data)
    time.extend(["time", job["time"]])
    bUtil.insert_benchmark(time, conn)

def process_interval_jobs(dataset, tag, job, conn):
    """
    Extract information from an dictionary with interval benchmark results and save it on a database

    :return:
    """

    data = bUtil.process_common_data(dataset, tag, 'interval', job)

    sharpness = deepcopy(data)
    sharpness.extend(["sharpness", job["sharpness"]])
    bUtil.insert_benchmark(sharpness, conn)
    resolution = deepcopy(data)
    resolution.extend(["resolution", job["resolution"]])
    bUtil.insert_benchmark(resolution, conn)
    coverage = deepcopy(data)
    coverage.extend(["coverage", job["coverage"]])
    bUtil.insert_benchmark(coverage, conn)
    Q05 = deepcopy(data)
    Q05.extend(["Q05", job["Q05"]])
    bUtil.insert_benchmark(Q05, conn)
    Q25 = deepcopy(data)
    Q25.extend(["Q25", job["Q25"]])
    bUtil.insert_benchmark(Q25, conn)
    Q75 = deepcopy(data)
    Q75.extend(["Q75", job["Q75"]])
    bUtil.insert_benchmark(Q75, conn)
    Q95 = deepcopy(data)
    Q95.extend(["Q95", job["Q95"]])
    bUtil.insert_benchmark(Q95, conn)
    W05 = deepcopy(data)
    W05.extend(["winkler05", job["winkler05"]])
    bUtil.insert_benchmark(W05, conn)
    W25 = deepcopy(data)
    W25.extend(["winkler25", job["winkler25"]])
    bUtil.insert_benchmark(W25, conn)

def process_probabilistic_jobs(dataset, tag,  job, conn):
    """
    Extract information from an dictionary with probabilistic benchmark results and save it on a database

    :return:
    """

    data = bUtil.process_common_data(dataset, tag,  'density', job)

    crps = deepcopy(data)
    crps.extend(["crps",job["CRPS"]])
    bUtil.insert_benchmark(crps, conn)
    brier = deepcopy(data)
    brier.extend(["brier", job["brier"]])
    bUtil.insert_benchmark(brier, conn)

def print_point_statistics(data, models, externalmodels = None, externalforecasts = None, indexers=None):
    """
    Run point benchmarks on given models and data and print the results

    :param models: a list of FTS models to benchmark
    :param externalmodels: a list with benchmark models (façades for other methods)
    :param externalforecasts:
    :param indexers:
    :return:
    """
    ret = "Model		& Order     & RMSE		& SMAPE      & Theil's U		\\\\ \n"
    for count,model in enumerate(models,start=0):
        _rmse, _smape, _u = Measures.get_point_statistics(data, model, indexers)
        ret += model.shortname + "		& "
        ret += str(model.order) + "		& "
        ret += str(_rmse) + "		& "
        ret += str(_smape)+ "		& "
        ret += str(_u)
        #ret += str(round(Measures.TheilsInequality(np.array(data[fts.order:]), np.array(forecasts[:-1])), 4))
        ret += "	\\\\ \n"
    if externalmodels is not None:
        l = len(externalmodels)
        for k in np.arange(0,l):
            ret += externalmodels[k] + "		& "
            ret += " 1		& "
            ret += str(round(Measures.rmse(data, externalforecasts[k][:-1]), 2)) + "		& "
            ret += str(round(Measures.smape(data, externalforecasts[k][:-1]), 2))+ "		& "
            ret += str(round(Measures.UStatistic(data, externalforecasts[k][:-1]), 2))
            ret += "	\\\\ \n"
    print(ret)

def print_interval_statistics(original, models):
    """
    Run interval benchmarks on given models and data and print the results

    :return:
    """
    ret = "Model	& Order     & Sharpness		& Resolution		& Coverage & .05  & .25 & .75 & .95	\\\\ \n"
    for fts in models:
        _sharp, _res, _cov, _q5, _q25, _q75, _q95  = Measures.get_interval_statistics(original, fts)
        ret += fts.shortname + "		& "
        ret += str(fts.order) + "		& "
        ret += str(_sharp) + "		& "
        ret += str(_res) + "		& "
        ret += str(_cov) + "        &"
        ret += str(_q5) + "        &"
        ret += str(_q25) + "        &"
        ret += str(_q75) + "        &"
        ret += str(_q95) + "\\\\ \n"

def print_distribution_statistics(original, models, steps, resolution):
    """
    Run probabilistic benchmarks on given models and data and print the results

    :return:
    """
    ret = "Model	& Order     &  Interval & Distribution	\\\\ \n"
        _crps1, _crps2, _t1, _t2 = Measures.get_distribution_statistics(original, fts, steps, resolution)
        ret += str(_crps1) + "		& "
        ret += str(_crps2) + "	\\\\ \n"

def plot_compared_intervals_ahead(original, models, colors, distributions, time_from, time_to, intervals = True,
                               save=False, file=None, tam=[20, 5], resolution=None,
                               cmap='Blues', linewidth=1.5):
    """
    Plot the forecasts of several one step ahead models, by point or by interval

    :param original: Original time series data (list)
    :param models: List of models to compare
    :param colors: List of models colors
    :param distributions: True to plot a distribution
    :param time_from: index of data poit to start the ahead forecasting
    :param time_to: number of steps ahead to forecast
    :param interpol: Fill space between distribution plots
    :param save: Save the picture on file
    :param file: Filename to save the picture
    :param tam: Size of the picture
    :param resolution: 
    :param cmap: Color map to be used on distribution plot 
    :param option: Distribution type to be passed for models
    :return: 
    """
    fig = plt.figure(figsize=tam)
    ax = fig.add_subplot(111)

    cm = plt.get_cmap(cmap)
    cNorm = pltcolors.Normalize(vmin=0, vmax=1)
    scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)

    if resolution is None: resolution = (max(original) - min(original)) / 100

    mi = []
    ma = []

    for count, fts in enumerate(models, start=0):
        if fts.has_probability_forecasting and distributions[count]:
            density = fts.forecast_ahead_distribution(original[time_from - fts.order:time_from], time_to,
                                                      resolution=resolution)

            #plot_density_scatter(ax, cmap, density, fig, resolution, time_from, time_to)
            plot_density_rectange(ax, cm, density, fig, resolution, time_from, time_to)

        if fts.has_interval_forecasting and intervals:
            forecasts = fts.forecast_ahead_interval(original[time_from - fts.order:time_from], time_to)
            lower = [kk[0] for kk in forecasts]
            upper = [kk[1] for kk in forecasts]
            mi.append(min(lower))
            ma.append(max(upper))
            for k in np.arange(0, time_from - fts.order):
                lower.insert(0, None)
                upper.insert(0, None)
            ax.plot(lower, color=colors[count], label=fts.shortname, linewidth=linewidth)
            ax.plot(upper, color=colors[count], linewidth=linewidth*1.5)

    ax.plot(original, color='black', label="Original", linewidth=linewidth*1.5)
    handles0, labels0 = ax.get_legend_handles_labels()
    if True in distributions:
        lgd = ax.legend(handles0, labels0, loc=2)
    else:
        lgd = ax.legend(handles0, labels0, loc=2, bbox_to_anchor=(1, 1))
    _mi = min(mi)
    if _mi < 0:
        _mi *= 1.1
    else:
        _mi *= 0.9
    _ma = max(ma)
    if _ma < 0:
        _ma *= 0.9
    else:
        _ma *= 1.1

    ax.set_ylim([_mi, _ma])
    ax.set_ylabel('F(T)')
    ax.set_xlabel('T')
    ax.set_xlim([0, len(original)])

    cUtil.show_and_save_image(fig, file, save, lgd=lgd)

def plot_density_rectange(ax, cmap, density, fig, resolution, time_from, time_to):
    """
    Auxiliar function to plot_compared_intervals_ahead
    """
    from matplotlib.patches import Rectangle
    from matplotlib.collections import PatchCollection
    patches = []
    colors = []
    for x in density.index:
        for y in density.columns:
            s = Rectangle((time_from + x, y), 1, resolution, fill=True, lw = 0)
            patches.append(s)
            colors.append(density[y][x]*5)
    pc = PatchCollection(patches=patches, match_original=True)
    pc.set_clim([0, 1])
    pc.set_cmap(cmap)
    pc.set_array(np.array(colors))
    ax.add_collection(pc)
    cb = fig.colorbar(pc, ax=ax)
    cb.set_label('Density')

def plot_distribution(ax, cmap, probabilitydist, fig, time_from, reference_data=None):
    colors = []
    for ct, dt in enumerate(probabilitydist):
        disp = 0.0
        if reference_data is not None:
            disp = reference_data[time_from+ct]

        for y in dt.bins:
            s = Rectangle((time_from+ct, y+disp), 1, dt.resolution, fill=True, lw = 0)
            colors.append(dt.density(y))
    scale = Transformations.Scale()
    colors = scale.apply(colors)

def plot_interval(axis, intervals, order, label, color='red', typeonlegend=False, ls='-', linewidth=1):
    lower = [kk[0] for kk in intervals]
    upper = [kk[1] for kk in intervals]
    mi = min(lower) * 0.95
    ma = max(upper) * 1.05
    for k in np.arange(0, order):
        lower.insert(0, None)
        upper.insert(0, None)
    if typeonlegend: label += " (Interval)"
    axis.plot(lower, color=color, label=label, ls=ls,linewidth=linewidth)
    axis.plot(upper, color=color, ls=ls,linewidth=linewidth)
    return [mi, ma]

def plot_point(axis, points, order, label, color='red', ls='-', linewidth=1):
    mi = min(points) * 0.95
    ma = max(points) * 1.05
        points.insert(0, None)
    axis.plot(points, color=color, label=label, ls=ls,linewidth=linewidth)

def plot_compared_series(original, models, colors, typeonlegend=False, save=False, file=None, tam=[20, 5],
                         points=True, intervals=True, linewidth=1.5):
    """

    :param typeonlegend: Add the type of forecast (point / interval) on legend
    :param points: True to plot the point forecasts, False otherwise
    :param intervals: True to plot the interval forecasts, False otherwise
    :param linewidth:
    :return:
    """

    mi = []
    ma = []

    legends = []

        try:
            if fts.has_point_forecasting and points:
                forecasts = fts.forecast(original)
                if isinstance(forecasts, np.ndarray):
                    forecasts = forecasts.tolist()
                mi.append(min(forecasts) * 0.95)
                ma.append(max(forecasts) * 1.05)
                for k in np.arange(0, fts.order):
                    forecasts.insert(0, None)
                lbl = fts.shortname + str(fts.order if fts.is_high_order and not fts.benchmark_only else "")
                if typeonlegend: lbl += " (Point)"
                ax.plot(forecasts, color=colors[count], label=lbl, ls="-",linewidth=linewidth)

            if fts.has_interval_forecasting and intervals:
                forecasts = fts.forecast_interval(original)
                lbl = fts.shortname + " " + str(fts.order if fts.is_high_order and not fts.benchmark_only else "")
                if not points and intervals:
                    ls = "-"
                else:
                    ls = "--"
                tmpmi, tmpma = plot_interval(ax, forecasts, fts.order, label=lbl, typeonlegend=typeonlegend,
                                             color=colors[count], ls=ls, linewidth=linewidth)
                mi.append(tmpmi)
                ma.append(tmpma)
        except ValueError as ex:
            print(fts.shortname)

        handles0, labels0 = ax.get_legend_handles_labels()
        legends.append(lgd)

    # ax.set_title(fts.name)
    ax.set_ylim([min(mi), max(ma)])

    #Util.show_and_save_image(fig, file, save, lgd=legends)

def plot_probability_distributions(pmfs, lcolors, tam=[15, 7]):

    for k,m in enumerate(pmfs,start=0):
        m.plot(ax, color=lcolors[k])

    ax.legend(handles0, labels0)

def plotCompared(original, forecasts, labels, title):
    fig = plt.figure(figsize=[13, 6])
    ax.plot(original, color='k', label="Original")
    for c in range(0, len(forecasts)):
        ax.plot(forecasts[c], label=labels[c])
    ax.set_title(title)
    ax.set_ylim([min(original), max(original)])

def SelecaoSimples_MenorRMSE(original, parameters, modelo):
    ret = []
    errors = []
    forecasted_best = []
    print("Série Original")
    fig = plt.figure(figsize=[20, 12])
    fig.suptitle("Comparação de modelos ")
    ax0 = fig.add_axes([0, 0.5, 0.65, 0.45])  # left, bottom, width, height
    ax0.set_xlim([0, len(original)])
    ax0.set_ylim([min(original), max(original)])
    ax0.set_title('Série Temporal')
    ax0.set_ylabel('F(T)')
    ax0.set_xlabel('T')
    ax0.plot(original, label="Original")
    min_rmse = 100000.0
    best = None
    for p in parameters:
        sets = Grid.GridPartitioner(data=original, npart=p).sets
        fts = modelo(str(p) + " particoes")
        fts.train(original, sets=sets)
        # print(original)
        forecasted = fts.forecast(original)
        forecasted.insert(0, original[0])
        # print(forecasted)
        ax0.plot(forecasted, label=fts.name)
        error = Measures.rmse(np.array(forecasted), np.array(original))
        print(p, error)
        errors.append(error)
        if error < min_rmse:
            min_rmse = error
            best = fts
            forecasted_best = forecasted
    handles0, labels0 = ax0.get_legend_handles_labels()
    ax0.legend(handles0, labels0)
    ax1 = fig.add_axes([0.7, 0.5, 0.3, 0.45])  # left, bottom, width, height
    ax1.set_title('Comparação dos Erros Quadráticos Médios')
    ax1.set_ylabel('RMSE')
    ax1.set_xlabel('Quantidade de Partições')
    ax1.set_xlim([min(parameters), max(parameters)])
    ax1.plot(parameters, errors)
    ret.append(best)
    ret.append(forecasted_best)
    # Modelo diferencial
    print("\nSérie Diferencial")
    difffts = Transformations.differential(original)
    errors = []
    forecastedd_best = []
    ax2 = fig.add_axes([0, 0, 0.65, 0.45])  # left, bottom, width, height
    ax2.set_xlim([0, len(difffts)])
    ax2.set_ylim([min(difffts), max(difffts)])
    ax2.set_title('Série Temporal')
    ax2.set_ylabel('F(T)')
    ax2.set_xlabel('T')
    ax2.plot(difffts, label="Original")
    bestd = None
        sets = Grid.GridPartitioner(data=difffts, npart=p)
        fts.train(difffts, sets=sets)
        forecasted = fts.forecast(difffts)
        forecasted.insert(0, difffts[0])
        ax2.plot(forecasted, label=fts.name)
        error = Measures.rmse(np.array(forecasted), np.array(difffts))
            bestd = fts
            forecastedd_best = forecasted
    handles0, labels0 = ax2.get_legend_handles_labels()
    ax2.legend(handles0, labels0)
    ax3 = fig.add_axes([0.7, 0, 0.3, 0.45])  # left, bottom, width, height
    ax3.set_title('Comparação dos Erros Quadráticos Médios')
    ax3.set_ylabel('RMSE')
    ax3.set_xlabel('Quantidade de Partições')
    ax3.set_xlim([min(parameters), max(parameters)])
    ax3.plot(parameters, errors)
    ret.append(bestd)
    ret.append(forecastedd_best)
    return ret

def compareModelsPlot(original, models_fo, models_ho):
    ax0 = fig.add_axes([0, 0, 1, 1])  # left, bottom, width, height
    rows = []
    for model in models_fo:
        fts = model["model"]
        ax0.plot(model["forecasted"], label=model["name"])
    for model in models_ho:

def compareModelsTable(original, models_fo, models_ho):
    fig = plt.figure(figsize=[12, 4])
    columns = ['Modelo', 'Ordem', 'Partições', 'RMSE', 'MAPE (%)']
    rows = []
        error_r = Measures.rmse(model["forecasted"], original)
        error_m = round(Measures.mape(model["forecasted"], original) * 100, 2)
        rows.append([model["name"], fts.order, len(fts.sets), error_r, error_m])
        error_r = Measures.rmse(model["forecasted"][fts.order:], original[fts.order:])
        error_m = round(Measures.mape(model["forecasted"][fts.order:], original[fts.order:]) * 100, 2)
    ax1 = fig.add_axes([0, 0, 1, 1])  # left, bottom, width, height
    ax1.set_xticks([])
    ax1.set_yticks([])
    ax1.table(cellText=rows,
              colLabels=columns,
              cellLoc='center',
              bbox=[0, 0, 1, 1])
    sup = "\\begin{tabular}{"
    header = ""
    body = ""
    footer = ""

    for c in columns:
        sup = sup + "|c"
        if len(header) > 0:
            header = header + " & "
        header = header + "\\textbf{" + c + "} "
    sup = sup + "|} \\hline\n"
    header = header + "\\\\ \\hline \n"

    for r in rows:
        lin = ""
        for c in r:
            if len(lin) > 0:
                lin = lin + " & "
            lin = lin + str(c)

        body = body + lin + "\\\\ \\hline \n"

    return sup + header + body + "\\end{tabular}"

def simpleSearch_RMSE(train, test, model, partitions, orders, save=False, file=None, tam=[10, 15],
                      plotforecasts=False, elev=30, azim=144, intervals=False,parameters=None,
                      partitioner=Grid.GridPartitioner,transformation=None,indexer=None):
    _3d = len(orders) > 1
    ret = []
    if _3d:
        errors = np.array([[0 for k in range(len(partitions))] for kk in range(len(orders))])
    else:
        errors = []
    # fig.suptitle("Comparação de modelos ")
    if plotforecasts:
        ax0 = fig.add_axes([0, 0.4, 0.9, 0.5])  # left, bottom, width, height
        ax0.set_xlim([0, len(train)])
        ax0.set_ylim([min(train) * 0.9, max(train) * 1.1])
        ax0.set_title('Forecasts')
        ax0.set_ylabel('F(T)')
        ax0.set_xlabel('T')
    min_rmse = 1000000.0
    best = None

    for pc, p in enumerate(partitions, start=0):

        sets = partitioner(data=train, npart=p, transformation=transformation).sets
        for oc, o in enumerate(orders, start=0):
            fts = model("q = " + str(p) + " n = " + str(o))
            fts.append_transformation(transformation)
            fts.train(train, sets=sets, order=o, parameters=parameters)
            if not intervals:
                forecasted = fts.forecast(test)
                if not fts.has_seasonality:
                    error = Measures.rmse(np.array(test[o:]), np.array(forecasted[:-1]))
                else:
                    error = Measures.rmse(np.array(test[o:]), np.array(forecasted))
                for kk in range(o):
                    forecasted.insert(0, None)
                if plotforecasts: ax0.plot(forecasted, label=fts.name)
            else:
                forecasted = fts.forecast_interval(test)
                error = 1.0 - Measures.rmse_interval(np.array(test[o:]), np.array(forecasted[:-1]))
            if _3d:
                errors[oc, pc] = error
            else:
                errors.append( error )
            if error < min_rmse:
                min_rmse = error
                best = fts
                forecasted_best = forecasted

    # print(min_rmse)
        # handles0, labels0 = ax0.get_legend_handles_labels()
        # ax0.legend(handles0, labels0)
        ax0.plot(test, label="Original", linewidth=3.0, color="black")
        if _3d: ax1 = Axes3D(fig, rect=[0, 1, 0.9, 0.9], elev=elev, azim=azim)
    if _3d and not plotforecasts:
        ax1 = Axes3D(fig, rect=[0, 1, 0.9, 0.9], elev=elev, azim=azim)
        ax1.set_title('Error Surface')
        ax1.set_ylabel('Model order')
        ax1.set_xlabel('Number of partitions')
        ax1.set_zlabel('RMSE')
        X, Y = np.meshgrid(partitions, orders)
        surf = ax1.plot_surface(X, Y, errors, rstride=1, cstride=1, antialiased=True)
    else:
        ax1 = fig.add_axes([0, 1, 0.9, 0.9])
        ax1.set_title('Error Curve')
        ax1.set_ylabel('RMSE')
        ax1.plot(partitions, errors)
    ret.append(min_rmse)

    # plt.tight_layout()

    cUtil.show_and_save_image(fig, file, save)

    return ret

def pftsExploreOrderAndPartitions(data,save=False, file=None):
    fig, axes = plt.subplots(nrows=4, ncols=1, figsize=[6, 8])
    data_fs1 = Grid.GridPartitioner(data=data, npart=10).sets
    mi = []
    ma = []

    axes[0].set_title('Point Forecasts by Order')
    axes[2].set_title('Interval Forecasts by Order')

    for order in np.arange(1, 6):
        fts = pwfts.ProbabilisticWeightedFTS("")
        fts.shortname = "n = " + str(order)
        fts.train(data, sets=data_fs1.sets, order=order)
        point_forecasts = fts.forecast(data)
        interval_forecasts = fts.forecast_interval(data)
        lower = [kk[0] for kk in interval_forecasts]
        upper = [kk[1] for kk in interval_forecasts]
        mi.append(min(lower) * 0.95)
        ma.append(max(upper) * 1.05)
        for k in np.arange(0, order):
            point_forecasts.insert(0, None)
            lower.insert(0, None)
            upper.insert(0, None)
        axes[0].plot(point_forecasts, label=fts.shortname)
        axes[2].plot(lower, label=fts.shortname)
        axes[2].plot(upper)

    axes[1].set_title('Point Forecasts by Number of Partitions')
    axes[3].set_title('Interval Forecasts by Number of Partitions')

    for partitions in np.arange(5, 11):
        data_fs = Grid.GridPartitioner(data=data, npart=partitions).sets
        fts.shortname = "q = " + str(partitions)
        fts.train(data, sets=data_fs.sets, order=1)
        point_forecasts.insert(0, None)
        axes[1].plot(point_forecasts, label=fts.shortname)
        axes[3].plot(lower, label=fts.shortname)
        axes[3].plot(upper)

    for ax in axes:
        ax.set_ylabel('F(T)')
        ax.set_xlabel('T')
        ax.plot(data, label="Original", color="black", linewidth=1.5)
        handles, labels = ax.get_legend_handles_labels()
        ax.legend(handles, labels, loc=2, bbox_to_anchor=(1, 1))
        ax.set_ylim([min(mi), max(ma)])
        ax.set_xlim([0, len(data)])

    plt.tight_layout()

"""
pyFTS module for common benchmark metrics
"""

import pandas as pd
from pyFTS.common import FuzzySet,SortedCollection

def acf(data, k):
    """
    Autocorrelation function estimative

    :param data: 
    :param k: 
    :return: 
    """
    mu = np.mean(data)
    sigma = np.var(data)
    n = len(data)
    s = 0
    for t in np.arange(0,n-k):
        s += (data[t]-mu) * (data[t+k] - mu)

    return 1/((n-k)*sigma)*s

def rmse(targets, forecasts):
    """
    Root Mean Squared Error

    :param targets: 
    :param forecasts: 
    :return: 
    """
    if isinstance(targets, list):
        targets = np.array(targets)
    if isinstance(forecasts, list):
        forecasts = np.array(forecasts)
    return np.sqrt(np.nanmean((targets - forecasts) ** 2))

def rmse_interval(targets, forecasts):
    """

    :return: 
    """
    fmean = [np.mean(i) for i in forecasts]
    return np.sqrt(np.nanmean((fmean - targets) ** 2))

def mape(targets, forecasts):
    """
    Mean Average Percentual Error

    :return: 
    """
    return np.mean(np.abs((targets - forecasts) / targets)) * 100

def smape(targets, forecasts, type=2):
    """
    Symmetric Mean Average Percentual Error

    :param type: 
    :return: 
    """
    if type == 1:
        return np.mean(np.abs(forecasts - targets) / ((forecasts + targets)/2))
    elif type == 2:
        return np.mean(np.abs(forecasts - targets) / (abs(forecasts) + abs(targets)) )*100
    else:
        return sum(np.abs(forecasts - targets)) / sum(forecasts + targets)

def mape_interval(targets, forecasts):
    return np.mean(abs(fmean - targets) / fmean) * 100

def UStatistic(targets, forecasts):
    """
    Theil's U Statistic

    :return: 
    """
    l = len(targets)

    naive = []
    y = []
    for k in np.arange(0,l-1):
        y.append((forecasts[k ] - targets[k]) ** 2)
        naive.append((targets[k + 1] - targets[k]) ** 2)
    return np.sqrt(sum(y) / sum(naive))

def TheilsInequality(targets, forecasts):
    """
    Theil’s Inequality Coefficient

    :return: 
    """
    res = targets - forecasts
    t = len(res)
    us = np.sqrt(sum([u**2 for u in res]))
    ys = np.sqrt(sum([y**2 for y in targets]))
    fs = np.sqrt(sum([f**2 for f in forecasts]))
    return  us / (ys + fs)

def BoxPierceStatistic(data, h):
    """
    Q Statistic for Box-Pierce test

    :param h: 
    :return: 
    """
    s = 0
    for k in np.arange(1,h+1):
        r = acf(data, k)
        s += r**2
    return n*s

def BoxLjungStatistic(data, h):
    """
    Q Statistic for Ljung–Box test

    :param h: 
    :return: 
    """
    s = 0
        s += r**2 / (n -k)
    return n*(n-2)*s

def sharpness(forecasts):
    """Sharpness - Mean size of the intervals"""
    tmp = [i[1] - i[0] for i in forecasts]
    return np.mean(tmp)

def resolution(forecasts):
    """Resolution - Standard deviation of the intervals"""
    shp = sharpness(forecasts)
    tmp = [abs((i[1] - i[0]) - shp) for i in forecasts]

def coverage(targets, forecasts):
    """Percent of target values that fall inside forecasted interval"""
    preds = []
    for i in np.arange(0, len(forecasts)):
        if targets[i] >= forecasts[i][0] and targets[i] <= forecasts[i][1]:
            preds.append(1)
        else:
            preds.append(0)
    return np.mean(preds)

def pinball(tau, target, forecast):
    """
    Pinball loss function. Measure the distance of forecast to the tau-quantile of the target

    :param tau: quantile value in the range (0,1)
    :param target: 
    :param forecast: 
    :return: float, distance of forecast to the tau-quantile of the target
    """
    if target >= forecast:
        return (target - forecast) * tau
    else:
        return (forecast - target) * (1 - tau)

def pinball_mean(tau, targets, forecasts):
    """
    Mean pinball loss value of the forecast for a given tau-quantile of the targets

    :param targets: list of target values
    :param forecasts: list of prediction intervals
    :return: float, the pinball loss mean for tau quantile
    """
    if tau <= 0.5:
        preds = [pinball(tau, targets[i], forecasts[i][0]) for i in np.arange(0, len(forecasts))]
    else:
        preds = [pinball(tau, targets[i], forecasts[i][1]) for i in np.arange(0, len(forecasts))]
    return np.nanmean(preds)

def winkler_score(tau, target, forecast):
    '''R. L. Winkler, A Decision-Theoretic Approach to Interval Estimation, J. Am. Stat. Assoc. 67 (337) (1972) 187–191. doi:10.2307/2284720. '''
    delta = forecast[1] - forecast[0]
    if forecast[0] < target and target < forecast[1]:
        return delta
    elif forecast[0] > target:
        return delta + 2*(forecast[0] - target)/tau
    elif forecast[1] < target:
        return delta + 2*(target - forecast[1])/tau

def winkler_mean(tau, targets, forecasts):
    """
    Mean Winkler score value of the forecast for a given tau-quantile of the targets

    :return: float, the Winkler score mean for tau quantile
    """
    preds = [winkler_score(tau, targets[i], forecasts[i]) for i in np.arange(0, len(forecasts))]

def brier_score(targets, densities):
    '''Brier (1950). "Verification of Forecasts Expressed in Terms of Probability". Monthly Weather Review. 78: 1–3. '''
    ret = []
    for ct, d in enumerate(densities):
        try:
            v = d.bin_index.find_ge(targets[ct])

            score = sum([d.distribution[k] ** 2 for k in d.bins if k != v])
            score += (d.distribution[v] - 1) ** 2
            ret.append(score)
            ret.append(sum([d.distribution[k] ** 2 for k in d.bins]))
    return sum(ret)/len(ret)

def pmf_to_cdf(density):
    ret = []
    for row in density.index:
        tmp = []
        prev = 0
        for col in density.columns:
            prev += density[col][row] if not np.isnan(density[col][row]) else 0
            tmp.append( prev )
        ret.append(tmp)
    df = pd.DataFrame(ret, columns=density.columns)
    return df

def heavyside(bin, target):
    return 1 if bin >= target else 0

def heavyside_cdf(bins, targets):
    ret = []
    for t in targets:
        result = [1 if b >= t else 0 for b in bins]
        ret.append(result)
    df = pd.DataFrame(ret, columns=bins)
    return df

def crps(targets, densities):
    '''
    Continuous Ranked Probability Score

    :param targets: a list with the target values
    :param densities: a list with pyFTS.probabil objectsistic.ProbabilityDistribution
    :return: float
    '''
    _crps = float(0.0)
    if isinstance(densities, ProbabilityDistribution.ProbabilityDistribution):
        densities = [densities]

    l = len(densities[0].bins)
    n = len(densities)
    for ct, df in enumerate(densities):
        _crps += sum([(df.cummulative(bin) - (1 if bin >= targets[ct] else 0)) ** 2 for bin in df.bins])

    return _crps / float(l * n)

def get_point_statistics(data, model, **kwargs):
    '''
    Condensate all measures for point forecasters

    :param model: FTS model with point forecasting capability
    :param kwargs:
    :return: a list with the RMSE, SMAPE and U Statistic
    '''

    steps_ahead = kwargs.get('steps_ahead',1)
    kwargs['type'] = 'point'

    if indexer is not None:
        ndata = np.array(indexer.get_data(data))
    elif model.is_multivariate:
        if not isinstance(data, pd.DataFrame):
            raise ValueError("Multivariate data must be a Pandas DataFrame!")
        ndata = data
    else:
        ndata = np.array(data)

    ret = list()

    if steps_ahead == 1:
        forecasts = model.predict(ndata, **kwargs)
        
        if model.is_multivariate and model.has_seasonality:
            ndata = model.indexer.get_data(ndata)
        elif model.is_multivariate:
            ndata = ndata[model.target_variable.data_label].values

        if not isinstance(forecasts, (list, np.ndarray)):
            forecasts = [forecasts]

        forecasts = np.array(forecasts[:-1])

        ret.append(np.round(rmse(ndata[model.max_lag:], forecasts), 2))
        ret.append(np.round(mape(ndata[model.max_lag:], forecasts), 2))
        ret.append(np.round(UStatistic(ndata[model.max_lag:], forecasts), 2))
    else:
        steps_ahead_sampler = kwargs.get('steps_ahead_sampler', 1)
        nforecasts = []
        for k in np.arange(model.order, len(ndata)-steps_ahead,steps_ahead_sampler):
            sample = ndata[k - model.order: k]
            tmp = model.predict(sample, **kwargs)
            nforecasts.append(tmp[-1])

        start = model.max_lag + steps_ahead -1
        ret.append(np.round(rmse(ndata[start:-1:steps_ahead_sampler], nforecasts), 2))
        ret.append(np.round(mape(ndata[start:-1:steps_ahead_sampler], nforecasts), 2))
        ret.append(np.round(UStatistic(ndata[start:-1:steps_ahead_sampler], nforecasts), 2))

    return ret

def get_interval_statistics(data, model, **kwargs):
    '''
    Condensate all measures for point interval forecasters

    :param model: FTS model with interval forecasting capability
    :return: a list with the sharpness, resolution, coverage, .05 pinball mean,
    .25 pinball mean, .75 pinball mean and .95 pinball mean.
    '''

    kwargs['type'] = 'interval'

        forecasts = model.predict(data, **kwargs)
        ret.append(round(sharpness(forecasts), 2))
        ret.append(round(resolution(forecasts), 2))
        ret.append(round(coverage(data[model.order:], forecasts[:-1]), 2))
        ret.append(round(pinball_mean(0.05, data[model.max_lag:], forecasts[:-1]), 2))
        ret.append(round(pinball_mean(0.25, data[model.max_lag:], forecasts[:-1]), 2))
        ret.append(round(pinball_mean(0.75, data[model.max_lag:], forecasts[:-1]), 2))
        ret.append(round(pinball_mean(0.95, data[model.max_lag:], forecasts[:-1]), 2))
        ret.append(round(winkler_mean(0.05, data[model.max_lag:], forecasts[:-1]), 2))
        ret.append(round(winkler_mean(0.25, data[model.max_lag:], forecasts[:-1]), 2))
    else:
        forecasts = []
        for k in np.arange(model.order, len(data) - steps_ahead):
            sample = data[k - model.order: k]
            forecasts.append(tmp[-1])

        ret.append(round(coverage(data[model.max_lag:], forecasts), 2))
        ret.append(round(pinball_mean(0.05, data[start:], forecasts), 2))
        ret.append(round(pinball_mean(0.25, data[start:], forecasts), 2))
        ret.append(round(pinball_mean(0.75, data[start:], forecasts), 2))
        ret.append(round(pinball_mean(0.95, data[start:], forecasts), 2))
        ret.append(round(winkler_mean(0.05, data[start:], forecasts), 2))
        ret.append(round(winkler_mean(0.25, data[start:], forecasts), 2))
    return ret

def get_distribution_statistics(data, model, **kwargs):
    '''
    Get CRPS statistic and time for a forecasting model

    :param model: FTS model with probabilistic forecasting capability
    :return: a list with the CRPS and execution time
    '''
    kwargs['type'] = 'distribution'

        _s1 = time.time()
        _e1 = time.time()
        ret.append(round(crps(data[model.max_lag:], forecasts[:-1]), 3))
        ret.append(round(_e1 - _s1, 3))
        ret.append(round(brier_score(data[model.max_lag:], forecasts[:-1]), 3))
    else:
        skip = kwargs.get('steps_ahead_sampler', 1)
        for k in np.arange(model.max_lag, len(data) - steps_ahead, skip):
            sample = data[k - model.max_lag: k]

        start = model.max_lag + steps_ahead
        ret.append(round(crps(data[start:-1:skip], forecasts), 3))
        ret.append(round(brier_score(data[start:-1:skip], forecasts), 3))
    return ret

"""Residual Analysis methods"""

from pyFTS.common import Transformations,Util
from pyFTS.benchmarks import Measures
from scipy import stats

def residuals(targets, forecasts, order=1):
    """First order residuals"""
    return np.array(targets[order:]) - np.array(forecasts[:-1])

def chi_squared(q, h):
    """
    Chi-Squared value

    :param q: 
    :param h: 
    :return: 
    """
    p = stats.chi2.sf(q, h)
    return p

def compare_residuals(data, models):
    """
    Compare residual's statistics of several models

    :param models: 
    :return: a Pandas dataframe with the Box-Ljung statistic for each model
    """
    from statsmodels.stats.diagnostic import acorr_ljungbox
    rows = []
    columns = ["Model","Order","AVG","STD","Box-Ljung","p-value"]
    for mfts in models:
        forecasts = mfts.forecast(data)
        res = residuals(data, forecasts, mfts.order)
        mu = np.mean(res)
        sig = np.std(res)
        row = [mfts.shortname, mfts.order, mu, sig]
        stat, pval = acorr_ljungbox(res)
        row.extend([stat, pval])
        rows.append(row)
    return pd.DataFrame(rows, columns=columns)

def plotResiduals(targets, models, tam=[8, 8], save=False, file=None):
    """
    Plot residuals and statistics

    :param tam: 
    :param save: 
    :param file: 
    :return: 
    """
    fig, axes = plt.subplots(nrows=len(models), ncols=3, figsize=tam)
    for c, mfts in enumerate(models):
        if len(models) > 1:
            ax = axes[c]
        else:
            ax = axes
        forecasts = mfts.forecast(targets)
        res = residuals(targets,forecasts,mfts.order)

        ax[0].set_title("Residuals Mean=" + str(mu) + " STD = " + str(sig))
        ax[0].set_ylabel('E')
        ax[0].set_xlabel('T')
        ax[0].plot(res)

        ax[1].set_title("Residuals Autocorrelation")
        ax[1].set_ylabel('ACS')
        ax[1].set_xlabel('Lag')
        ax[1].acorr(res)

        ax[2].set_title("Residuals Histogram")
        ax[2].set_ylabel('Freq')
        ax[2].set_xlabel('Bins')
        ax[2].hist(res)

        c += 1

    Util.show_and_save_image(fig, file, save)

def plot_residuals(targets, models, tam=[8, 8], save=False, file=None):

    for c, mfts in enumerate(models, start=0):
        else:
            ax = axes
        res = residuals(targets, forecasts, mfts.order)

        if c == 0: ax[0].set_title("Residuals", size='large')
        ax[0].set_ylabel(mfts.shortname, size='large')
        ax[0].set_xlabel(' ')

        if c == 0: ax[1].set_title("Residuals Autocorrelation", size='large')

        if c == 0: ax[2].set_title("Residuals Histogram", size='large')

def single_plot_residuals(targets, forecasts, order, tam=[8, 8], save=False, file=None):
    fig, ax = plt.subplots(nrows=1, ncols=3, figsize=tam)

    res = residuals(targets, forecasts, order)

    ax[0].set_title("Residuals", size='large')
    ax[0].set_ylabel("Model", size='large')
    ax[0].set_xlabel(' ')
    ax[0].plot(res)

    ax[1].set_title("Residuals Autocorrelation", size='large')
    ax[1].set_ylabel('ACS')
    ax[1].set_xlabel('Lag')
    ax[1].acorr(res)

    ax[2].set_title("Residuals Histogram", size='large')
    ax[2].set_ylabel('Freq')
    ax[2].set_xlabel('Bins')
    ax[2].hist(res)

"""
Facilities for pyFTS Benchmark module
"""

import sqlite3
#from mpl_toolkits.mplot3d import Axes3D

from pyFTS.common import Util

def open_benchmark_db(name):
    """
    Open a connection with a Sqlite database designed to store benchmark results.

    :param name: database filenem
    :return: a sqlite3 database connection
    """
    conn = sqlite3.connect(name)

    #performance optimizations
    conn.execute("PRAGMA journal_mode = WAL")
    conn.execute("PRAGMA synchronous = NORMAL")

    create_benchmark_tables(conn)
    return conn

def create_benchmark_tables(conn):
    """
    Create a sqlite3 table designed to store benchmark results.

    :param conn: a sqlite3 database connection
    """
    c = conn.cursor()

    c.execute('''CREATE TABLE if not exists benchmarks(
                 ID integer primary key, Date int, Dataset text, Tag text, 
                 Type text, Model text, Transformation text, 'Order' int, 
                 Scheme text, Partitions int,
                 Size int, Steps int, Method text, Measure text, Value real)''')

    conn.commit()

def insert_benchmark(data, conn):
    """
    Insert benchmark data on database

    :param data: a tuple with the benchmark data with format:

    ID: integer incremental primary key
    Date: Date/hour of benchmark execution
    Dataset: Identify on which dataset the dataset was performed
    Tag: a user defined word that indentify a benchmark set
    Type: forecasting type (point, interval, distribution)
    Model: FTS model
    Transformation: The name of data transformation, if one was used
    Order: the order of the FTS method
    Scheme: UoD partitioning scheme
    Partitions: Number of partitions
    Size: Number of rules of the FTS model
    Steps: prediction horizon, i. e., the number of steps ahead
    Measure: accuracy measure
    Value: the measure value

    :return:
    """

    c.execute("INSERT INTO benchmarks(Date, Dataset, Tag, Type, Model, "
              + "Transformation, 'Order', Scheme, Partitions, "
              + "Size, Steps, Method, Measure, Value) "
              + "VALUES(datetime('now'),?,?,?,?,?,?,?,?,?,?,?,?,?)", data)

def process_common_data(dataset, tag, type, job):
    """
    Wraps benchmark information on a tuple for sqlite database

    :param dataset: benchmark dataset
    :param tag: benchmark set alias
    :param type: forecasting type
    :param job: a dictionary with benchmark data
    :return: tuple for sqlite database
    """
    model = job["obj"]
    if model.benchmark_only:
        data = [dataset, tag, type, model.shortname,
                str(model.transformations[0]) if len(model.transformations) > 0 else None,
                model.order, None, None,
                None, job['steps'], job['method']]
    else:
                str(model.partitioner.transformation) if model.partitioner.transformation is not None else None,
                model.order, model.partitioner.name, str(model.partitioner.partitions),
                len(model), job['steps'], job['method']]

    return data

def get_dataframe_from_bd(file, filter):
    """
    Query the sqlite benchmark database and return a pandas dataframe with the results

    :param file: the url of the benchmark database
    :param filter: sql conditions to filter
    :return: pandas dataframe with the query results
    """
    con = sqlite3.connect(file)
    sql = "SELECT * from benchmarks"
    if filter is not None:
        sql += " WHERE " + filter
    return pd.read_sql_query(sql, con)

def extract_measure(dataframe, measure, data_columns):
    if not dataframe.empty:
        df = dataframe[(dataframe.Measure == measure)][data_columns]
        tmp = df.to_dict(orient="records")[0]
        ret = [k for k in tmp.values() if not np.isnan(k)]
        return ret
    else:
        return None

def find_best(dataframe, criteria, ascending):
    models = dataframe.Model.unique()
    orders = dataframe.Order.unique()
    ret = {}
    for m in models:
        for o in orders:
            mod = {}
            df = dataframe[(dataframe.Model == m) & (dataframe.Order == o)].sort_values(by=criteria, ascending=ascending)
            if not df.empty:
                _key = str(m) + str(o)
                best = df.loc[df.index[0]]
                mod['Model'] = m
                mod['Order'] = o
                mod['Scheme'] = best["Scheme"]
                mod['Partitions'] = best["Partitions"]

                ret[_key] = mod

    return ret

def analytic_tabular_dataframe(dataframe):
    experiments = len(dataframe.columns) - len(base_dataframe_columns()) - 1
    schemes = dataframe.Scheme.unique()
    partitions = dataframe.Partitions.unique()
    steps = dataframe.Steps.unique()
    measures = dataframe.Measure.unique()
    data_columns = analytical_data_columns(experiments)

    ret = []

            for s in schemes:
                for p in partitions:
                    for st in steps:
                        for ms in measures:
                            df = dataframe[(dataframe.Model == m) & (dataframe.Order == o)
                                           & (dataframe.Scheme == s) & (dataframe.Partitions == p)
                                           & (dataframe.Steps == st) & (dataframe.Measure == ms) ]

                            if not df.empty:
                                for col in data_columns:
                                    mod = [m, o, s, p, st, ms, df[col].values[0]]
                                    ret.append(mod)

    dat = pd.DataFrame(ret, columns=tabular_dataframe_columns())
    return dat

def tabular_dataframe_columns():
        return ["Model", "Order", "Scheme", "Partitions", "Steps", "Measure", "Value"]

def base_dataframe_columns():
    return ["Model", "Order", "Scheme", "Partitions", "Size", "Steps", "Method"]

def point_dataframe_synthetic_columns():
    return base_dataframe_columns().extend(["RMSEAVG", "RMSESTD",
            "SMAPEAVG", "SMAPESTD", "UAVG","USTD", "TIMEAVG", "TIMESTD"])

def point_dataframe_analytic_columns(experiments):
    columns = [str(k) for k in np.arange(0, experiments)]
    columns.insert(0, "Model")
    columns.insert(1, "Order")
    columns.insert(2, "Scheme")
    columns.insert(3, "Partitions")
    columns.insert(4, "Size")
    columns.insert(5, "Steps")
    columns.insert(6, "Method")
    columns.insert(7, "Measure")
    return columns

def save_dataframe_point(experiments, file, objs, rmse, save, synthetic, smape, times, u, steps, method):
    """
    Create a dataframe to store the benchmark results

    :param experiments: dictionary with the execution results
    :param objs: 
    :param rmse: 
    :param synthetic: 
    :param smape: 
    :param times: 
    :param u: 
    :return: 
    """
    ret = []

    if synthetic:

        for k in sorted(objs.keys()):
            try:
                mod = []
                mfts = objs[k]
                mod.append(mfts.shortname)
                mod.append(mfts.order)
                if not mfts.benchmark_only:
                    mod.append(mfts.partitioner.name)
                    mod.append(mfts.partitioner.partitions)
                    mod.append(len(mfts))
                else:
                    mod.append('-')
                mod.append(steps[k])
                mod.append(method[k])
                mod.append(np.round(np.nanmean(rmse[k]), 2))
                mod.append(np.round(np.nanstd(rmse[k]), 2))
                mod.append(np.round(np.nanmean(smape[k]), 2))
                mod.append(np.round(np.nanstd(smape[k]), 2))
                mod.append(np.round(np.nanmean(u[k]), 2))
                mod.append(np.round(np.nanstd(u[k]), 2))
                mod.append(np.round(np.nanmean(times[k]), 4))
                mod.append(np.round(np.nanstd(times[k]), 4))
                ret.append(mod)
            except Exception as ex:
                print("Erro ao salvar ", k)
                print("Exceção ", ex)

        columns = point_dataframe_synthetic_columns()
    else:
            try:
                n = mfts.shortname
                o = mfts.order
                    s = mfts.partitioner.name
                    p = mfts.partitioner.partitions
                    l = len(mfts)
                else:
                    s = '-'
                    p = '-'
                    l = '-'
                st = steps[k]
                mt = method[k]
                tmp = [n, o, s, p, l, st, mt, 'RMSE']
                tmp.extend(rmse[k])
                ret.append(deepcopy(tmp))
                tmp = [n, o, s, p, l, st, mt, 'SMAPE']
                tmp.extend(smape[k])
                tmp = [n, o, s, p, l, st, mt, 'U']
                tmp.extend(u[k])
                tmp = [n, o, s, p, l, st, mt, 'TIME']
                tmp.extend(times[k])
        columns = point_dataframe_analytic_columns(experiments)
    try:
        dat = pd.DataFrame(ret, columns=columns)
        if save: dat.to_csv(Util.uniquefilename(file), sep=";", index=False)
        return dat
    except Exception as ex:
        print(ex)
        print(experiments)
        print(columns)
        print(ret)

def cast_dataframe_to_synthetic(infile, outfile, experiments, type):
        analytic_columns = point_dataframe_analytic_columns
        synthetic_columns = point_dataframe_synthetic_columns
        synthetize_measures = cast_dataframe_to_synthetic_point
        analytic_columns = interval_dataframe_analytic_columns
        synthetic_columns = interval_dataframe_synthetic_columns
        synthetize_measures = cast_dataframe_to_synthetic_interval
        analytic_columns = probabilistic_dataframe_analytic_columns
        synthetic_columns = probabilistic_dataframe_synthetic_columns
        synthetize_measures = cast_dataframe_to_synthetic_probabilistic
    else:
        raise ValueError("Type parameter has an unknown value!")

    columns = analytic_columns(experiments)
    dat = pd.read_csv(infile, sep=";", usecols=columns)
    models = dat.Model.unique()
    orders = dat.Order.unique()
    schemes = dat.Scheme.unique()
    partitions = dat.Partitions.unique()
    steps = dat.Steps.unique()
    methods = dat.Method.unique()

    ret = []

                        for mt in methods:
                            df = dat[(dat.Model == m) & (dat.Order == o) & (dat.Scheme == s) &
                                     (dat.Partitions == p) & (dat.Steps == st) & (dat.Method == mt)]
                                mod = synthetize_measures(df, data_columns)
                                mod.insert(0, m)
                                mod.insert(1, o)
                                mod.insert(2, s)
                                mod.insert(3, p)
                                mod.insert(4, df.iat[0,5])
                                mod.insert(5, st)
                                mod.insert(6, mt)
                                ret.append(mod)

    dat = pd.DataFrame(ret, columns=synthetic_columns())
    dat.to_csv(outfile, sep=";", index=False)

def cast_dataframe_to_synthetic_point(df, data_columns):
    ret = []
    rmse = extract_measure(df, 'RMSE', data_columns)
    smape = extract_measure(df, 'SMAPE', data_columns)
    u = extract_measure(df, 'U', data_columns)
    times = extract_measure(df, 'TIME', data_columns)
    ret.append(np.round(np.nanmean(rmse), 2))
    ret.append(np.round(np.nanstd(rmse), 2))
    ret.append(np.round(np.nanmean(smape), 2))
    ret.append(np.round(np.nanstd(smape), 2))
    ret.append(np.round(np.nanmean(u), 2))
    ret.append(np.round(np.nanstd(u), 2))
    ret.append(np.round(np.nanmean(times), 4))
    ret.append(np.round(np.nanstd(times), 4))

    return ret

def analytical_data_columns(experiments):
    data_columns = [str(k) for k in np.arange(0, experiments)]
    return data_columns

def scale_params(data):
    vmin = np.nanmin(data)
    vlen = np.nanmax(data) - vmin
    return (vmin, vlen)

def scale(data, params):
    ndata = [(k-params[0])/params[1] for k in data]
    return ndata

def stats(measure, data):
    print(measure, np.nanmean(data), np.nanstd(data))

def unified_scaled_point(experiments, tam, save=False, file=None,
                         sort_columns=['UAVG', 'RMSEAVG', 'USTD', 'RMSESTD'],
                         sort_ascend=[1, 1, 1, 1],save_best=False,
                         ignore=None, replace=None):

    fig, axes = plt.subplots(nrows=3, ncols=1, figsize=tam)

    axes[0].set_title('RMSE')
    axes[1].set_title('SMAPE')
    axes[2].set_title('U Statistic')

    models = {}

    for experiment in experiments:

        mdl = {}

        dat_syn = pd.read_csv(experiment[0], sep=";", usecols=point_dataframe_synthetic_columns())

        bests = find_best(dat_syn, sort_columns, sort_ascend)

        dat_ana = pd.read_csv(experiment[1], sep=";", usecols=point_dataframe_analytic_columns(experiment[2]))

        rmse = []
        smape = []
        u = []
        times = []

        data_columns = analytical_data_columns(experiment[2])

        for b in sorted(bests.keys()):
            if check_ignore_list(b, ignore):
                continue

            if b not in models:
                models[b] = {}
                models[b]['rmse'] = []
                models[b]['smape'] = []
                models[b]['u'] = []
                models[b]['times'] = []

            if b not in mdl:
                mdl[b] = {}
                mdl[b]['rmse'] = []
                mdl[b]['smape'] = []
                mdl[b]['u'] = []
                mdl[b]['times'] = []

            best = bests[b]
            tmp = dat_ana[(dat_ana.Model == best["Model"]) & (dat_ana.Order == best["Order"])
                    & (dat_ana.Scheme == best["Scheme"]) & (dat_ana.Partitions == best["Partitions"])]
            tmpl = extract_measure(tmp,'RMSE',data_columns)
            mdl[b]['rmse'].extend( tmpl )
            rmse.extend( tmpl )
            tmpl = extract_measure(tmp, 'SMAPE', data_columns)
            mdl[b]['smape'].extend(tmpl)
            smape.extend(tmpl)
            tmpl = extract_measure(tmp, 'U', data_columns)
            mdl[b]['u'].extend(tmpl)
            u.extend(tmpl)
            tmpl = extract_measure(tmp, 'TIME', data_columns)
            mdl[b]['times'].extend(tmpl)
            times.extend(tmpl)

            models[b]['label'] = check_replace_list(best["Model"] + " " + str(best["Order"]), replace)

        print("GLOBAL")
        rmse_param = scale_params(rmse)
        stats("rmse", rmse)
        smape_param = scale_params(smape)
        stats("smape", smape)
        u_param = scale_params(u)
        stats("u", u)
        times_param = scale_params(times)

        for key in sorted(models.keys()):
            models[key]['rmse'].extend( scale(mdl[key]['rmse'], rmse_param) )
            models[key]['smape'].extend( scale(mdl[key]['smape'], smape_param) )
            models[key]['u'].extend( scale(mdl[key]['u'], u_param) )
            models[key]['times'].extend( scale(mdl[key]['times'], times_param) )

    rmse = []
    smape = []
    u = []
    times = []
    labels = []
    for key in sorted(models.keys()):
        print(key)
        rmse.append(models[key]['rmse'])
        stats("rmse", models[key]['rmse'])
        smape.append(models[key]['smape'])
        stats("smape", models[key]['smape'])
        u.append(models[key]['u'])
        stats("u", models[key]['u'])
        times.append(models[key]['times'])
        labels.append(models[key]['label'])

    axes[0].boxplot(rmse, labels=labels, autorange=True, showmeans=True)
    axes[0].set_title("RMSE")
    axes[1].boxplot(smape, labels=labels, autorange=True, showmeans=True)
    axes[1].set_title("SMAPE")
    axes[2].boxplot(u, labels=labels, autorange=True, showmeans=True)
    axes[2].set_title("U Statistic")

def plot_dataframe_point(file_synthetic, file_analytic, experiments, tam, save=False, file=None,
                         ignore=None,replace=None):

    dat_syn = pd.read_csv(file_synthetic, sep=";", usecols=point_dataframe_synthetic_columns())

    bests = find_best(dat_syn, sort_columns, sort_ascend)

    dat_ana = pd.read_csv(file_analytic, sep=";", usecols=point_dataframe_analytic_columns(experiments))

    if save_best:
        dat = pd.DataFrame.from_dict(bests, orient='index')
        dat.to_csv(Util.uniquefilename(file_synthetic.replace("synthetic","best")), sep=";", index=False)

    rmse = []
    smape = []
    u = []
    times = []
    labels = []

    for b in sorted(bests.keys()):
        if check_ignore_list(b, ignore):
            continue

        best = bests[b]
        tmp = dat_ana[(dat_ana.Model == best["Model"]) & (dat_ana.Order == best["Order"])
                & (dat_ana.Scheme == best["Scheme"]) & (dat_ana.Partitions == best["Partitions"])]
        rmse.append( extract_measure(tmp,'RMSE',data_columns) )
        smape.append(extract_measure(tmp, 'SMAPE', data_columns))
        u.append(extract_measure(tmp, 'U', data_columns))
        times.append(extract_measure(tmp, 'TIME', data_columns))

        labels.append(check_replace_list(best["Model"] + " " + str(best["Order"]),replace))

def check_replace_list(m, replace):
    if replace is not None:
        for r in replace:
            if r[0] in m:
                return r[1]
    return m

def check_ignore_list(b, ignore):
    flag = False
    if ignore is not None:
        for i in ignore:
            if i in b:
                flag = True
    return flag

def save_dataframe_interval(coverage, experiments, file, objs, resolution, save, sharpness, synthetic, times,
                            q05, q25, q75, q95, steps, method):
    ret = []
            mod = []
            mfts = objs[k]
            mod.append(mfts.shortname)
            mod.append(mfts.order)
            l = len(mfts)
            if not mfts.benchmark_only:
                mod.append(mfts.partitioner.name)
                mod.append(mfts.partitioner.partitions)
                mod.append(l)
            else:
                mod.append('-')
            mod.append(steps[k])
            mod.append(method[k])
            mod.append(round(np.nanmean(sharpness[k]), 2))
            mod.append(round(np.nanstd(sharpness[k]), 2))
            mod.append(round(np.nanmean(resolution[k]), 2))
            mod.append(round(np.nanstd(resolution[k]), 2))
            mod.append(round(np.nanmean(coverage[k]), 2))
            mod.append(round(np.nanstd(coverage[k]), 2))
            mod.append(round(np.nanmean(times[k]), 2))
            mod.append(round(np.nanstd(times[k]), 2))
            mod.append(round(np.nanmean(q05[k]), 2))
            mod.append(round(np.nanstd(q05[k]), 2))
            mod.append(round(np.nanmean(q25[k]), 2))
            mod.append(round(np.nanstd(q25[k]), 2))
            mod.append(round(np.nanmean(q75[k]), 2))
            mod.append(round(np.nanstd(q75[k]), 2))
            mod.append(round(np.nanmean(q95[k]), 2))
            mod.append(round(np.nanstd(q95[k]), 2))
            mod.append(l)
            ret.append(mod)

        columns = interval_dataframe_synthetic_columns()
    else:
            try:
                else:
                    s = '-'
                    p = '-'
                    l = '-'
                tmp = [n, o, s, p, l, st, mt, 'Sharpness']
                tmp.extend(sharpness[k])
                tmp = [n, o, s, p, l, st, mt, 'Resolution']
                tmp.extend(resolution[k])
                tmp = [n, o, s, p, l, st, mt, 'Coverage']
                tmp.extend(coverage[k])
                tmp = [n, o, s, p, l, st, mt, 'Q05']
                tmp.extend(q05[k])
                tmp = [n, o, s, p, l, st, mt, 'Q25']
                tmp.extend(q25[k])
                tmp = [n, o, s, p, l, st, mt, 'Q75']
                tmp.extend(q75[k])
                tmp = [n, o, s, p, l, st, mt, 'Q95']
                tmp.extend(q95[k])
        columns = interval_dataframe_analytic_columns(experiments)
    dat = pd.DataFrame(ret, columns=columns)
    if save: dat.to_csv(Util.uniquefilename(file), sep=";")
    return dat

def interval_dataframe_analytic_columns(experiments):

def interval_dataframe_synthetic_columns():
    columns = ["Model", "Order", "Scheme", "Partitions","SIZE", "Steps","Method" "SHARPAVG", "SHARPSTD", "RESAVG", "RESSTD", "COVAVG",
               "COVSTD", "TIMEAVG", "TIMESTD", "Q05AVG", "Q05STD", "Q25AVG", "Q25STD", "Q75AVG", "Q75STD", "Q95AVG", "Q95STD"]

def cast_dataframe_to_synthetic_interval(df, data_columns):
    sharpness = extract_measure(df, 'Sharpness', data_columns)
    resolution = extract_measure(df, 'Resolution', data_columns)
    coverage = extract_measure(df, 'Coverage', data_columns)
    q05 = extract_measure(df, 'Q05', data_columns)
    q25 = extract_measure(df, 'Q25', data_columns)
    q75 = extract_measure(df, 'Q75', data_columns)
    q95 = extract_measure(df, 'Q95', data_columns)
    ret = []
    ret.append(np.round(np.nanmean(sharpness), 2))
    ret.append(np.round(np.nanstd(sharpness), 2))
    ret.append(np.round(np.nanmean(resolution), 2))
    ret.append(np.round(np.nanstd(resolution), 2))
    ret.append(np.round(np.nanmean(coverage), 2))
    ret.append(np.round(np.nanstd(coverage), 2))
    ret.append(np.round(np.nanmean(q05), 4))
    ret.append(np.round(np.nanstd(q05), 4))
    ret.append(np.round(np.nanmean(q25), 4))
    ret.append(np.round(np.nanstd(q25), 4))
    ret.append(np.round(np.nanmean(q75), 4))
    ret.append(np.round(np.nanstd(q75), 4))
    ret.append(np.round(np.nanmean(q95), 4))
    ret.append(np.round(np.nanstd(q95), 4))
    return ret

def unified_scaled_interval(experiments, tam, save=False, file=None,
                            sort_columns=['COVAVG', 'SHARPAVG', 'COVSTD', 'SHARPSTD'],
                            sort_ascend=[True, False, True, True],save_best=False,
                            ignore=None, replace=None):

    axes[0].set_title('Sharpness')
    axes[1].set_title('Resolution')
    axes[2].set_title('Coverage')

    models = {}

        mdl = {}

        dat_syn = pd.read_csv(experiment[0], sep=";", usecols=interval_dataframe_synthetic_columns())

        dat_ana = pd.read_csv(experiment[1], sep=";", usecols=interval_dataframe_analytic_columns(experiment[2]))

        sharpness = []
        resolution = []
        coverage = []
        times = []

                continue

                models[b]['sharpness'] = []
                models[b]['resolution'] = []
                models[b]['coverage'] = []

                mdl[b] = {}
                mdl[b]['sharpness'] = []
                mdl[b]['resolution'] = []
                mdl[b]['coverage'] = []

            print(best)
                          & (dat_ana.Scheme == best["Scheme"]) & (dat_ana.Partitions == best["Partitions"])]
            tmpl = extract_measure(tmp, 'Sharpness', data_columns)
            mdl[b]['sharpness'].extend(tmpl)
            sharpness.extend(tmpl)
            tmpl = extract_measure(tmp, 'Resolution', data_columns)
            mdl[b]['resolution'].extend(tmpl)
            resolution.extend(tmpl)
            tmpl = extract_measure(tmp, 'Coverage', data_columns)
            mdl[b]['coverage'].extend(tmpl)
            coverage.extend(tmpl)

        sharpness_param = scale_params(sharpness)
        resolution_param = scale_params(resolution)
        coverage_param = scale_params(coverage)

            models[key]['sharpness'].extend(scale(mdl[key]['sharpness'], sharpness_param))
            models[key]['resolution'].extend(scale(mdl[key]['resolution'], resolution_param))
            models[key]['coverage'].extend(scale(mdl[key]['coverage'], coverage_param))
            models[key]['times'].extend(scale(mdl[key]['times'], times_param))

    sharpness = []
    resolution = []
    coverage = []
    times = []
    labels = []
        sharpness.append(models[key]['sharpness'])
        resolution.append(models[key]['resolution'])
        coverage.append(models[key]['coverage'])

    axes[0].boxplot(sharpness, labels=labels, autorange=True, showmeans=True)
    axes[1].boxplot(resolution, labels=labels, autorange=True, showmeans=True)
    axes[2].boxplot(coverage, labels=labels, autorange=True, showmeans=True)

def plot_dataframe_interval(file_synthetic, file_analytic, experiments, tam, save=False, file=None,

    dat_syn = pd.read_csv(file_synthetic, sep=";", usecols=interval_dataframe_synthetic_columns())

    dat_ana = pd.read_csv(file_analytic, sep=";", usecols=interval_dataframe_analytic_columns(experiments))

    times = []
    labels = []
    bounds_shp = []

            continue
        df = dat_ana[(dat_ana.Model == best["Model"]) & (dat_ana.Order == best["Order"])
        sharpness.append( extract_measure(df,'Sharpness',data_columns) )
        resolution.append(extract_measure(df, 'Resolution', data_columns))
        coverage.append(extract_measure(df, 'Coverage', data_columns))
        times.append(extract_measure(df, 'TIME', data_columns))
        labels.append(check_replace_list(best["Model"] + " " + str(best["Order"]), replace))

    axes[0].set_title("Sharpness")
    axes[1].set_title("Resolution")
    axes[2].set_title("Coverage")
    axes[2].set_ylim([0, 1.1])

def unified_scaled_interval_pinball(experiments, tam, save=False, file=None,
                                    sort_columns=['COVAVG','SHARPAVG','COVSTD','SHARPSTD'],
                                    sort_ascend=[True, False, True, True], save_best=False,
                                    ignore=None, replace=None):
    fig, axes = plt.subplots(nrows=1, ncols=4, figsize=tam)
    axes[0].set_title(r'$\tau=0.05$')
    axes[1].set_title(r'$\tau=0.25$')
    axes[2].set_title(r'$\tau=0.75$')
    axes[3].set_title(r'$\tau=0.95$')
    models = {}

        mdl = {}

        q05	= []
        q25 = []
        q75 = []
        q95 = []

                continue

                models[b]['q05'] = []
                models[b]['q25'] = []
                models[b]['q75'] = []
                models[b]['q95'] = []

                mdl[b] = {}
                mdl[b]['q05'] = []
                mdl[b]['q25'] = []
                mdl[b]['q75'] = []
                mdl[b]['q95'] = []

            tmpl = extract_measure(tmp, 'Q05', data_columns)
            mdl[b]['q05'].extend(tmpl)
            q05.extend(tmpl)
            tmpl = extract_measure(tmp, 'Q25', data_columns)
            mdl[b]['q25'].extend(tmpl)
            q25.extend(tmpl)
            tmpl = extract_measure(tmp, 'Q75', data_columns)
            mdl[b]['q75'].extend(tmpl)
            q75.extend(tmpl)
            tmpl = extract_measure(tmp, 'Q95', data_columns)
            mdl[b]['q95'].extend(tmpl)
            q95.extend(tmpl)

        q05_param = scale_params(q05)
        q25_param = scale_params(q25)
        q75_param = scale_params(q75)
        q95_param = scale_params(q95)

            models[key]['q05'].extend(scale(mdl[key]['q05'], q05_param))
            models[key]['q25'].extend(scale(mdl[key]['q25'], q25_param))
            models[key]['q75'].extend(scale(mdl[key]['q75'], q75_param))
            models[key]['q95'].extend(scale(mdl[key]['q95'], q95_param))

    q05 = []
    q25 = []
    q75 = []
    q95 = []
    labels = []
        q05.append(models[key]['q05'])
        q25.append(models[key]['q25'])
        q75.append(models[key]['q75'])
        q95.append(models[key]['q95'])

    axes[0].boxplot(q05, labels=labels, vert=False, autorange=True, showmeans=True)
    axes[1].boxplot(q25, labels=labels, vert=False, autorange=True, showmeans=True)
    axes[2].boxplot(q75, labels=labels, vert=False, autorange=True, showmeans=True)
    axes[3].boxplot(q95, labels=labels, vert=False, autorange=True, showmeans=True)

def plot_dataframe_interval_pinball(file_synthetic, file_analytic, experiments, tam, save=False, file=None,

    q05 = []
    q25 = []
    q75 = []
    q95 = []
    labels = []

            continue
        q05.append(extract_measure(df, 'Q05', data_columns))
        q25.append(extract_measure(df, 'Q25', data_columns))
        q75.append(extract_measure(df, 'Q75', data_columns))
        q95.append(extract_measure(df, 'Q95', data_columns))

def save_dataframe_probabilistic(experiments, file, objs, crps, times, save, synthetic, steps, method):
    """
    Save benchmark results for m-step ahead probabilistic forecasters 
    :param experiments: 
    :param crps_interval: 
    :param crps_distr: 
    :param times2: 
    :return: 
    """
    ret = []

            try:
                ret = []
                for k in sorted(objs.keys()):
                    try:
                        mod = []
                        mfts = objs[k]
                        mod.append(mfts.shortname)
                        mod.append(mfts.order)
                        if not mfts.benchmark_only:
                            mod.append(mfts.partitioner.name)
                            mod.append(mfts.partitioner.partitions)
                            mod.append(len(mfts))
                        else:
                            mod.append('-')
                        mod.append(steps[k])
                        mod.append(method[k])
                        mod.append(np.round(np.nanmean(crps[k]), 2))
                        mod.append(np.round(np.nanstd(crps[k]), 2))
                        mod.append(np.round(np.nanmean(times[k]), 4))
                        mod.append(np.round(np.nanstd(times[k]), 4))
                        ret.append(mod)
                    except Exception as e:
                        print('Erro: %s' % e)

        columns = probabilistic_dataframe_synthetic_columns()
    else:
            try:
                else:
                    s = '-'
                    p = '-'
                    l = '-'
                tmp = [n, o, s, p, l, st, mt, 'CRPS']
                tmp.extend(crps[k])
        columns = probabilistic_dataframe_analytic_columns(experiments)
    return dat

def probabilistic_dataframe_analytic_columns(experiments):

def probabilistic_dataframe_synthetic_columns():
    columns = ["Model", "Order", "Scheme", "Partitions","Size", "Steps", "Method", "CRPSAVG", "CRPSSTD",
               "TIMEAVG", "TIMESTD"]

def cast_dataframe_to_synthetic_probabilistic(df, data_columns):
    crps1 = extract_measure(df, 'CRPS', data_columns)
    times1 = extract_measure(df, 'TIME', data_columns)
    ret = []
    ret.append(np.round(np.nanmean(crps1), 2))
    ret.append(np.round(np.nanstd(crps1), 2))
    ret.append(np.round(np.nanmean(times1), 2))
    ret.append(np.round(np.nanstd(times1), 2))
    return ret

def unified_scaled_probabilistic(experiments, tam, save=False, file=None,
                                 sort_columns=['CRPSAVG', 'CRPSSTD'],
                                 sort_ascend=[True, True], save_best=False,
                                 ignore=None, replace=None):
    fig, axes = plt.subplots(nrows=1, ncols=1, figsize=tam)

    axes.set_title('CRPS')
    #axes[1].set_title('CRPS Distribution Ahead')

    models = {}

        print(experiment)

        mdl = {}

        dat_syn = pd.read_csv(experiment[0], sep=";", usecols=probabilistic_dataframe_synthetic_columns())

        dat_ana = pd.read_csv(experiment[1], sep=";", usecols=probabilistic_dataframe_analytic_columns(experiment[2]))

        crps1 = []
        crps2 = []

                continue

                models[b]['crps1'] = []
                models[b]['crps2'] = []

                mdl[b] = {}
                mdl[b]['crps1'] = []
                mdl[b]['crps2'] = []

            tmpl = extract_measure(tmp, 'CRPS_Interval', data_columns)
            mdl[b]['crps1'].extend(tmpl)
            crps1.extend(tmpl)
            tmpl = extract_measure(tmp, 'CRPS_Distribution', data_columns)
            mdl[b]['crps2'].extend(tmpl)
            crps2.extend(tmpl)

        crps1_param = scale_params(crps1)
        crps2_param = scale_params(crps2)

        for key in sorted(mdl.keys()):
            print(key)
            models[key]['crps1'].extend(scale(mdl[key]['crps1'], crps1_param))
            models[key]['crps2'].extend(scale(mdl[key]['crps2'], crps2_param))

    crps1 = []
    crps2 = []
    labels = []
        crps1.append(models[key]['crps1'])
        crps2.append(models[key]['crps2'])

    axes[0].boxplot(crps1, labels=labels, autorange=True, showmeans=True)
    axes[1].boxplot(crps2, labels=labels, autorange=True, showmeans=True)

def plot_dataframe_probabilistic(file_synthetic, file_analytic, experiments, tam, save=False, file=None,
                                 sort_columns=['CRPS1AVG', 'CRPS2AVG', 'CRPS1STD', 'CRPS2STD'],
                                 sort_ascend=[True, True, True, True], save_best=False,

    fig, axes = plt.subplots(nrows=2, ncols=1, figsize=tam)

    axes[0].set_title('CRPS')
    axes[1].set_title('CRPS')

    dat_syn = pd.read_csv(file_synthetic, sep=";", usecols=probabilistic_dataframe_synthetic_columns())

    dat_ana = pd.read_csv(file_analytic, sep=";", usecols=probabilistic_dataframe_analytic_columns(experiments))

    crps1 = []
    crps2 = []
    labels = []

            continue
        crps1.append( extract_measure(df,'CRPS_Interval',data_columns) )
        crps2.append(extract_measure(df, 'CRPS_Distribution', data_columns))

from statsmodels.tsa.arima_model import ARIMA as stats_arima
import scipy.stats as st
from pyFTS.common import SortedCollection, fts

class ARIMA(fts.FTS):
    """
    Façade for statsmodels.tsa.arima_model
    """
    def __init__(self, **kwargs):
        super(ARIMA, self).__init__(**kwargs)
        self.name = "ARIMA"
        self.detail = "Auto Regressive Integrated Moving Average"
        self.is_high_order = True
        self.has_point_forecasting = True
        self.has_interval_forecasting = True
        self.has_probability_forecasting = True
        self.model = None
        self.model_fit = None
        self.trained_data = None
        self.p = 1
        self.d = 0
        self.q = 0
        self.benchmark_only = True
        self.min_order = 1
        self.alpha = kwargs.get("alpha", 0.05)
        self.order = kwargs.get("order", (1,0,0))
        self._decompose_order(self.order)

    def _decompose_order(self, order):
        if isinstance(order, (tuple, set, list)):
            self.p = order[0]
            self.d = order[1]
            self.q = order[2]
            self.order = self.p + self.q + (self.q - 1 if self.q > 0 else 0)
            self.max_lag = self.order
            self.d = len(self.transformations)
            self.shortname = "ARIMA(" + str(self.p) + "," + str(self.d) + "," + str(self.q) + ") - " + str(self.alpha)

    def train(self, data, **kwargs):

        if 'order' in kwargs:
            order = kwargs.pop('order')
            self._decompose_order(order)

        if self.indexer is not None:
            data = self.indexer.get_data(data)

        try:
            self.model =  stats_arima(data, order=(self.p, self.d, self.q))
            self.model_fit = self.model.fit(disp=0)
        except Exception as ex:
            print(ex)
            self.model_fit = None

    def ar(self, data):
        return data.dot(self.model_fit.arparams)

    def ma(self, data):
        return data.dot(self.model_fit.maparams)

    def forecast(self, ndata, **kwargs):
        if self.model_fit is None:
            return np.nan

        ndata = np.array(ndata)

        l = len(ndata)

        ret = []

        ar = np.array([self.ar(ndata[k - self.p: k]) for k in np.arange(self.p, l+1)]) #+1 to forecast one step ahead given all available lags

        if self.q > 0:
            residuals = ndata[self.p-1:] - ar

            ma = np.array([self.ma(residuals[k - self.q: k]) for k in np.arange(self.q, len(residuals) + 1)])

            ret = ar[self.q - 1:] + ma
            ret = ret[self.q:]
        else:
            ret = ar

        #ret = self.apply_inverse_transformations(ret, params=[data[self.order - 1:]])        nforecasts = np.array(forecasts)

        return ret

    def forecast_interval(self, data, **kwargs):

        sigma = np.sqrt(self.model_fit.sigma2)

        l = len(data)

        ret = []

        for k in np.arange(self.order, l+1):
            tmp = []

            sample = [data[i] for i in np.arange(k - self.order, k)]

            mean = self.forecast(sample)

            if isinstance(mean,(list, np.ndarray)):
                mean = mean[0]

            tmp.append(mean + st.norm.ppf(self.alpha) * sigma)
            tmp.append(mean + st.norm.ppf(1 - self.alpha) * sigma)

            ret.append(tmp)

        return ret

    def forecast_ahead_interval(self, ndata, steps, **kwargs):

        smoothing = kwargs.get("smoothing",0.5)

        nmeans = self.forecast_ahead(ndata, steps, **kwargs)

        ret = []

        for k in np.arange(0, steps):
            tmp = []

            hsigma = (1 + k*smoothing)*sigma

            tmp.append(nmeans[k] + st.norm.ppf(self.alpha) * hsigma)
            tmp.append(nmeans[k] + st.norm.ppf(1 - self.alpha) * hsigma)

        return ret

    def forecast_distribution(self, data, **kwargs):

        ret = []

        for k in np.arange(self.order, l + 1):

            if isinstance(mean, (list, np.ndarray)):

            dist = ProbabilityDistribution.ProbabilityDistribution(type="histogram", uod=[self.original_min, self.original_max])
            intervals = []
            for alpha in np.arange(0.05, 0.5, 0.05):

                qt1 = mean + st.norm.ppf(alpha) * sigma
                qt2 = mean + st.norm.ppf(1 - alpha) * sigma

                intervals.append([qt1, qt2])

            dist.append_interval(intervals)

            ret.append(dist)

        return ret

    def forecast_ahead_distribution(self, data, steps, **kwargs):
        smoothing = kwargs.get("smoothing", 0.5)

        ret = []

        nmeans = self.forecast_ahead(data, steps, **kwargs)

            dist = ProbabilityDistribution.ProbabilityDistribution(type="histogram",
                                                                   uod=[self.original_min, self.original_max])
                tmp = []

                hsigma = (1 + k * smoothing) * sigma

                tmp.append(nmeans[k] + st.norm.ppf(alpha) * hsigma)
                tmp.append(nmeans[k] + st.norm.ppf(1 - alpha) * hsigma)

                intervals.append(tmp)

        return ret

from statsmodels.tsa.tsatools import lagmat
from pyFTS.common import fts

class KNearestNeighbors(fts.FTS):
    """
    K-Nearest Neighbors
    """
        super(KNearestNeighbors, self).__init__(**kwargs)
        self.name = "kNN"
        self.shortname = "kNN"
        self.detail = "K-Nearest Neighbors"
        self.lag = None
        self.k = kwargs.get("k", 30)
        self.uod = None

        self.data = np.array(data)

    def knn(self, sample):

        if self.order == 1:
            dist = np.apply_along_axis(lambda x: (x - sample) ** 2, 0, self.data)
            ix = np.argsort(dist) + 1
        else:
            dist = []
            for k in np.arange(self.order, len(self.data)):
                dist.append(sum([ (self.data[k - kk] - sample[kk])**2 for kk in range(self.order)]))
            ix = np.argsort(np.array(dist)) + self.order + 1

        ix2 = np.clip(ix[:self.k], 0, len(self.data)-1)
        return self.data[ix2]

        ret = []

        smooth = kwargs.get("smooth", "KDE")
        alpha = kwargs.get("alpha", None)

        uod = self.get_UoD()

        for k in np.arange(self.order, len(data)):

            sample = data[k-self.order : k]

            forecasts = self.knn(sample)

            dist = ProbabilityDistribution.ProbabilityDistribution(smooth, uod=uod, data=forecasts,
                                                                   name="", **kwargs)

        return ret

class Naive(fts.FTS):
    """Naïve Forecasting method"""
        super(Naive, self).__init__(order=1, name="Naive",**kwargs)
        self.name = "Naïve Model"
        self.detail = "Naïve Model"
        self.is_high_order = False

    def forecast(self, data, **kwargs):
        return data

from statsmodels.regression.quantile_regression import QuantReg

class QuantileRegression(fts.FTS):
    """Façade for statsmodels.regression.quantile_regression"""
        super(QuantileRegression, self).__init__(**kwargs)
        self.name = "QR"
        self.detail = "Quantile Regression"
        self.dist = kwargs.get("dist", False)
        self.upper_qt = None
        self.mean_qt = None
        self.lower_qt = None
        self.dist_qt = None

        if self.indexer is not None and isinstance(data, pd.DataFrame):

        lagdata, ndata = lagmat(data, maxlag=self.order, trim="both", original='sep')

        mqt = QuantReg(ndata, lagdata).fit(0.5)
        if self.alpha is not None:
            uqt = QuantReg(ndata, lagdata).fit(1 - self.alpha)
            lqt = QuantReg(ndata, lagdata).fit(self.alpha)

        self.mean_qt = [k for k in mqt.params]
            self.upper_qt = [k for k in uqt.params]
            self.lower_qt = [k for k in lqt.params]

        if self.dist:
            self.dist_qt = []
            for alpha in np.arange(0.05,0.5,0.05):
                lqt = QuantReg(ndata, lagdata).fit(alpha)
                uqt = QuantReg(ndata, lagdata).fit(1 - alpha)
                lo_qt = [k for k in lqt.params]
                up_qt = [k for k in uqt.params]
                self.dist_qt.append([lo_qt, up_qt])

        self.shortname = "QAR(" + str(self.order) + ") - " + str(self.alpha)

    def linearmodel(self,data,params):
        #return params[0] + sum([ data[k] * params[k+1] for k in np.arange(0, self.order) ])
        return sum([data[k] * params[k] for k in np.arange(0, self.order)])

    def point_to_interval(self, data, lo_params, up_params):
        lo = self.linearmodel(data, lo_params)
        up = self.linearmodel(data, up_params)
        return [lo, up]

    def interval_to_interval(self, data, lo_params, up_params):
        lo = self.linearmodel([k[0] for k in data], lo_params)
        up = self.linearmodel([k[1] for k in data], up_params)

        ret = []

        for k in np.arange(self.order, l+1):   #+1 to forecast one step ahead given all available lags
            sample = ndata[k - self.order : k]

            ret.append(self.linearmodel(sample, self.mean_qt))

        return ret

    def forecast_interval(self, ndata, **kwargs):

        ret = []

        for k in np.arange(self.order , l):
            sample = ndata[k - self.order: k]
            ret.append(self.point_to_interval(sample, self.lower_qt, self.upper_qt))

        return ret

        smoothing = kwargs.get("smoothing", 0.9)

        ret = []

        for k in np.arange(0, self.order):
            nmeans.insert(k,ndata[-(k+1)])

        for k in np.arange(self.order, steps+self.order):
            intl = self.point_to_interval(nmeans[k - self.order: k], self.lower_qt, self.upper_qt)

            ret.append([intl[0]*(1 + k*smoothing), intl[1]*(1 + k*smoothing)])

        return ret[-steps:]

    def forecast_distribution(self, ndata, **kwargs):

        ret = []

            for qt in self.dist_qt:
                sample = ndata[k - self.order: k]
                intl = self.point_to_interval(sample, qt[0], qt[1])
                intervals.append(intl)

        return ret

    def forecast_ahead_distribution(self, ndata, steps, **kwargs):

        ret = []

        for k in np.arange(self.order, steps + self.order):
            intervals = [[k, k] for k in ndata[-self.order:]]
                intl = self.interval_to_interval([intervals[x] for x in np.arange(k - self.order, k)], qt[0], qt[1])

        return ret

"""
Composite Fuzzy Sets
"""

from pyFTS import *
from pyFTS.common import Membership, FuzzySet

class FuzzySet(FuzzySet.FuzzySet):
    """
    Composite Fuzzy Set
    """
    def __init__(self, name, superset=False):
        """
        Create an empty composite fuzzy set
        :param name: fuzzy set name
        """
        super(FuzzySet, self).__init__(name, None, None, None, type='composite')
        self.superset = superset
        if self.superset:
            self.sets = []
        else:
            self.mf = []
            self.parameters = []

    def membership(self, x):
        """
        Calculate the membership value of a given input

        :param x: input value
        :return: membership value of x at this fuzzy set
        """
            return max([s.membership(x) for s in self.sets])
        else:
            return min([self.mf[ct](x, self.parameters[ct]) for ct in np.arange(0, len(self.mf))])

    def append(self, mf, parameters):
        """
        Adds a new function to composition

        :param mf:
        :param parameters:
        :return:
        """
        self.mf.append(mf)
        self.parameters.append(parameters)

    def append_set(self, set):
        """

        :param mf:
        :return:
        """
        self.sets.append(set)

"""
This module implements functions for Fuzzy Logical Relationship generation
"""

from pyFTS.common import FuzzySet

class FLR(object):
    """
    Fuzzy Logical Relationship

    Represents a temporal transition of the fuzzy set LHS on time t for the fuzzy set RHS on time t+1.
    """
    def __init__(self, LHS, RHS):
        """
        Creates a Fuzzy Logical Relationship
        """
        self.LHS = LHS
        """Left Hand Side fuzzy set"""
        self.RHS = RHS
        """Right Hand Side fuzzy set"""

    def __str__(self):
        return str(self.LHS) + " -> " + str(self.RHS)

class IndexedFLR(FLR):
    """Season Indexed Fuzzy Logical Relationship"""
    def __init__(self, index, LHS, RHS):
        """
        Create a Season Indexed Fuzzy Logical Relationship
        """
        super(IndexedFLR, self).__init__(LHS, RHS)
        self.index = index
        """seasonal index"""

        return str(self.index) + ": "+ str(self.LHS) + " -> " + str(self.RHS)

def generate_high_order_recurrent_flr(fuzzyData):
    """
    Create a ordered FLR set from a list of fuzzy sets with recurrence

    :param fuzzyData: ordered list of fuzzy sets
    :return: ordered list of FLR
    """
    flrs = []
    for i in np.arange(1,len(fuzzyData)):
        lhs = fuzzyData[i - 1]
        rhs = fuzzyData[i]
        if isinstance(lhs, list) and isinstance(rhs, list):
            for l in lhs:
                for r in rhs:
                    tmp = FLR(l, r)
                    flrs.append(tmp)
        else:
            tmp = FLR(lhs,rhs)
            flrs.append(tmp)
    return flrs

def generate_recurrent_flrs(fuzzyData):
    """

    """
    flrs = []
        lhs = [fuzzyData[i - 1]]
        rhs = [fuzzyData[i]]
        for l in np.array(lhs).flatten():
            for r in np.array(rhs).flatten():
                tmp = FLR(l, r)
                flrs.append(tmp)

def generate_non_recurrent_flrs(fuzzyData):
    """
    Create a ordered FLR set from a list of fuzzy sets without recurrence

    """
    flrs = generate_recurrent_flrs(fuzzyData)
    tmp = {}
    for flr in flrs: tmp[str(flr)] = flr
    ret = [value for key, value in tmp.items()]
    return ret

def generate_indexed_flrs(sets, indexer, data, transformation=None, alpha_cut=0.0):
    """
    Create a season-indexed ordered FLR set from a list of fuzzy sets with recurrence

    :param sets: fuzzy sets
    :param indexer: seasonality indexer 
    :param data: original data
    :return: ordered list of FLR 
    """
    flrs = []
    index = indexer.get_season_of_data(data)
    ndata = indexer.get_data(data)
    if transformation is not None:
        ndata = transformation.apply(ndata)
    for k in np.arange(1,len(ndata)):
        lhs = FuzzySet.fuzzyfy_series([ndata[k - 1]], sets, method='fuzzy',alpha_cut=alpha_cut)
        rhs = FuzzySet.fuzzyfy_series([ndata[k]], sets, method='fuzzy',alpha_cut=alpha_cut)
        season = index[k]
        for _l in np.array(lhs).flatten():
            for _r in np.array(rhs).flatten():
                flr = IndexedFLR(season,_l,_r)
                flrs.append(flr)

from pyFTS.common import Membership

class FuzzySet(object):
    """
    Fuzzy Set
    """
    def __init__(self, name, mf, parameters, centroid, alpha=1.0, **kwargs):
        """
        Create a Fuzzy Set
        """
        self.name = name
        """The fuzzy set name"""
        self.mf = mf
        """The membership function"""
        self.parameters = parameters
        """The parameters of the membership function"""
        self.centroid = centroid
        """The fuzzy set center of mass (or midpoint)"""
        self.alpha = alpha
        """The alpha cut value"""
        self.type = kwargs.get('type', 'common')
        """The fuzzy set type (common, composite, nonstationary, etc)"""
        self.variable = kwargs.get('variable',None)
        """In multivariate time series, indicate for which variable this fuzzy set belogs"""
        self.Z = None
        """Partition function in respect to the membership function"""
        if self.mf == Membership.trimf:
            self.lower = min(parameters)
            self.upper = max(parameters)
        elif self.mf == Membership.gaussmf:
            self.lower = parameters[0] - parameters[1]*3
            self.upper = parameters[0] + parameters[1]*3
        self.metadata = {}

        """

        :param x: input value 
        """
        return self.mf(x, self.parameters) * self.alpha

    def partition_function(self,uod=None, nbins=100):
        """
        Calculate the partition function over the membership function.

        :param uod:
        :param nbins:
        :return:
        """
        if self.Z is None and uod is not None:
            self.Z = 0.0
            for k in np.linspace(uod[0], uod[1], nbins):
                self.Z += self.membership(k)

        return self.Z

        return self.name + ": " + str(self.mf.__name__) + "(" + str(self.parameters) + ")"

def set_ordered(fuzzySets):
    """Order a fuzzy set list by their centroids"""
    if len(fuzzySets) > 0:
        tmp1 = [fuzzySets[k] for k in fuzzySets.keys()]
        return [k.name for k in sorted(tmp1, key=lambda x: x.centroid)]

def fuzzyfy_instance(inst, fuzzySets, ordered_sets=None):
    """
    Calculate the membership values for a data point given fuzzy sets

    :param inst: data point
    :param fuzzySets: dict of fuzzy sets
    :return: array of membership values
    """

    if ordered_sets is None:
        ordered_sets = set_ordered(fuzzySets)

    mv = []
    for key in ordered_sets:
        mv.append( fuzzySets[key].membership(inst))
    return np.array(mv)

def fuzzyfy_instances(data, fuzzySets, ordered_sets=None):
    """

    """
    ret = []
    for inst in data:
        mv = np.array([fuzzySets[key].membership(inst) for key in ordered_sets])
        ret.append(mv)
    return ret

def get_fuzzysets(inst, fuzzySets, ordered_sets=None, alpha_cut=0.0):
    """
    Return the fuzzy sets which membership value for a inst is greater than the alpha_cut

    :param alpha_cut: Minimal membership to be considered on fuzzyfication process
    """

    fs = [key for key in ordered_sets if fuzzySets[key].membership(inst) > alpha_cut]
    return fs

def get_maximum_membership_fuzzyset(inst, fuzzySets, ordered_sets=None):
    """
    Fuzzify a data point, returning the fuzzy set with maximum membership value

    :return: fuzzy set with maximum membership
    """
    mv = np.array([fuzzySets[key].membership(inst) for key in ordered_sets])
    key = ordered_sets[np.argwhere(mv == max(mv))[0, 0]]
    return fuzzySets[key]

def get_maximum_membership_fuzzyset_index(inst, fuzzySets):
    """

    """
    mv = fuzzyfy_instance(inst, fuzzySets)
    return np.argwhere(mv == max(mv))[0, 0]

def fuzzyfy_series_old(data, fuzzySets, method='maximum'):
    fts = []
    for item in data:
        fts.append(get_maximum_membership_fuzzyset(item, fuzzySets).name)
    return fts

def fuzzyfy_series(data, fuzzySets, method='maximum', alpha_cut=0.0):
    fts = []
    ordered_sets = set_ordered(fuzzySets)
    for t, i in enumerate(data):
        mv = np.array([fuzzySets[key].membership(i) for key in ordered_sets])
        if len(mv) == 0:
            sets = check_bounds(i, fuzzySets.items(), ordered_sets)
        else:
            if method == 'fuzzy':
                ix = np.ravel(np.argwhere(mv > alpha_cut))
                sets = [fuzzySets[ordered_sets[i]].name for i in ix]
            elif method == 'maximum':
                mx = max(mv)
                ix = np.ravel(np.argwhere(mv == mx))
                sets = fuzzySets[ordered_sets[ix[0]]].name
        fts.append(sets)
    return fts

def grant_bounds(data, sets, ordered_sets):
    if data < sets[ordered_sets[0]].lower:
        return sets[ordered_sets[0]].lower
    elif data > sets[ordered_sets[-1]].upper:
        return sets[ordered_sets[-1]].upper
    else:

def check_bounds(data, sets, ordered_sets):
        return sets[ordered_sets[0]]
        return sets[ordered_sets[-1]]

def check_bounds_index(data, sets, ordered_sets):
    if data < sets[ordered_sets[0]].get_lower():
        return 0
    elif data > sets[ordered_sets[-1]].get_upper():
        return len(sets) -1

"""
Membership functions for Fuzzy Sets
"""

import math

def trimf(x, parameters):
    """
    Triangular fuzzy membership function

    :param x: data point
    :param parameters: a list with 3 real values
    :return: the membership value of x given the parameters
    """
    xx = np.round(x, 3)
    if xx < parameters[0]:
        return 0
    elif parameters[0] <= xx < parameters[1]:
        return (x - parameters[0]) / (parameters[1] - parameters[0])
    elif parameters[1] <= xx <= parameters[2]:
        return (parameters[2] - xx) / (parameters[2] - parameters[1])
    else:
        return 0

def trapmf(x, parameters):
    """
    Trapezoidal fuzzy membership function

    :param parameters: a list with 4 real values
    """
    if x < parameters[0]:
        return 0
    elif parameters[0] <= x < parameters[1]:
    elif parameters[1] <= x <= parameters[2]:
        return 1
    elif parameters[2] <= x <= parameters[3]:
        return (parameters[3] - x) / (parameters[3] - parameters[2])
    else:
        return 0

def gaussmf(x, parameters):
    """
    Gaussian fuzzy membership function

    :param parameters: a list with 2 real values (mean and variance)
    """
    return math.exp((-(x - parameters[0])**2)/(2 * parameters[1]**2))

def bellmf(x, parameters):
    """
    Bell shaped membership function

    :param x:
    :param parameters:
    :return:
    """
    return 1 / (1 + abs((x - parameters[2]) / parameters[0]) ** (2 * parameters[1]))

def sigmf(x, parameters):
    """
    Sigmoid / Logistic membership function

    :param x:
    :param parameters: an list with 2 real values (smoothness and midpoint)
    :return:
    """
    return 1 / (1 + math.exp(-parameters[0] * (x - parameters[1])))

from bisect import bisect_left, bisect_right

#
# Original Source Code: https://code.activestate.com/recipes/577197-sortedcollection/
# Author: RAYMOND HETTINGER

class SortedCollection(object):
    '''Sequence sorted by a key function.

    SortedCollection() is much easier to work with than using bisect() directly.
    It supports key functions like those use in sorted(), min(), and max().
    The result of the key function call is saved so that keys can be searched
    efficiently.

    Instead of returning an insertion-point which can be hard to interpret, the
    five find-methods return a specific item in the sequence. They can scan for
    exact matches, the last item less-than-or-equal to a key, or the first item
    greater-than-or-equal to a key.

    Once found, an item's ordinal position can be located with the index() method.
    New items can be added with the insert() and insert_right() methods.
    Old items can be deleted with the remove() method.

    The usual sequence methods are provided to support indexing, slicing,
    length lookup, clearing, copying, forward and reverse iteration, contains
    checking, item counts, item removal, and a nice looking repr.

    Finding and indexing are O(log n) operations while iteration and insertion
    are O(n).  The initial sort is O(n log n).

    The key function is stored in the 'key' attibute for easy introspection or
    so that you can assign a new key function (triggering an automatic re-sort).

    In short, the class was designed to handle all of the common use cases for
    bisect but with a simpler API and support for key functions.

    >>> from pprint import pprint
    >>> from operator import itemgetter

    >>> s = SortedCollection(key=itemgetter(2))
    >>> for record in [
    ...         ('roger', 'young', 30),
    ...         ('angela', 'jones', 28),
    ...         ('bill', 'smith', 22),
    ...         ('david', 'thomas', 32)]:
    ...     s.insert(record)

    >>> pprint(list(s))         # show records sorted by age
    [('bill', 'smith', 22),
     ('angela', 'jones', 28),
     ('roger', 'young', 30),
     ('david', 'thomas', 32)]

    >>> s.find_le(29)           # find oldest person aged 29 or younger
    ('angela', 'jones', 28)
    >>> s.find_lt(28)           # find oldest person under 28
    ('bill', 'smith', 22)
    >>> s.find_gt(28)           # find youngest person over 28
    ('roger', 'young', 30)

    >>> r = s.find_ge(32)       # find youngest person aged 32 or older
    >>> s.index(r)              # get the index of their record
    3
    >>> s[3]                    # fetch the record at that index
    ('david', 'thomas', 32)

    >>> s.key = itemgetter(0)   # now sort by first name
    >>> pprint(list(s))
    [('angela', 'jones', 28),
     ('bill', 'smith', 22),
     ('david', 'thomas', 32),
     ('roger', 'young', 30)]

    '''

    def __init__(self, iterable=(), key=None):
        self._given_key = key
        key = (lambda x: x) if key is None else key
        decorated = sorted((key(item), item) for item in iterable)
        self._keys = [k for k, item in decorated]
        self._items = [item for k, item in decorated]
        self._key = key

    def _getkey(self):
        return self._key

    def _setkey(self, key):
        if key is not self._key:
            self.__init__(self._items, key=key)

    def _delkey(self):
        self._setkey(None)

    key = property(_getkey, _setkey, _delkey, 'key function')

    def clear(self):
        self.__init__([], self._key)

    def copy(self):
        return self.__class__(self, self._key)

    def __len__(self):
        return len(self._items)

    def __getitem__(self, i):
        return self._items[i]

    def __iter__(self):
        return iter(self._items)

    def __reversed__(self):
        return reversed(self._items)

    def __repr__(self):
        return '%s(%r, key=%s)' % (
            self.__class__.__name__,
            self._items,
            getattr(self._given_key, '__name__', repr(self._given_key))
        )

    def __reduce__(self):
        return self.__class__, (self._items, self._given_key)

    def __contains__(self, item):
        k = self._key(item)
        i = bisect_left(self._keys, k)
        j = bisect_right(self._keys, k)
        return item in self._items[i:j]

    def index(self, item):
        'Find the position of an item.  Raise ValueError if not found.'
        return self._items[i:j].index(item) + i

    def count(self, item):
        'Return number of occurrences of item'
        return self._items[i:j].count(item)

    def insert(self, item):
        'Insert a new item.  If equal keys are found, add to the left'
        self._keys.insert(i, k)
        self._items.insert(i, item)

    def insert_right(self, item):
        'Insert a new item.  If equal keys are found, add to the right'
        i = bisect_right(self._keys, k)

    def remove(self, item):
        'Remove first occurence of item.  Raise ValueError if not found'
        i = self.index(item)
        del self._keys[i]
        del self._items[i]

    def find(self, k):
        'Return first item with a key == k.  Raise ValueError if not found.'
        if i != len(self) and self._keys[i] == k:
            return self._items[i]
        raise ValueError('No item found with key equal to: %r' % (k,))

    def find_le(self, k):
        'Return last item with a key <= k.  Raise ValueError if not found.'
        if i:
            return self._items[i-1]
        raise ValueError('No item found with key at or below: %r' % (k,))

    def find_lt(self, k):
        'Return last item with a key < k.  Raise ValueError if not found.'
        if i:
        raise ValueError('No item found with key below: %r' % (k,))

    def find_ge(self, k):
        'Return first item with a key >= equal to k.  Raise ValueError if not found'
        if i != len(self):
        raise ValueError('No item found with key at or above: %r' % (k,))

    def find_gt(self, k):
        'Return first item with a key > k.  Raise ValueError if not found'
        raise ValueError('No item found with key above: %r' % (k,))

    def between(self, ge, le):
        g = bisect_left(self._keys, ge)
        l = bisect_right(self._keys, le)
        if g != len(self) and l != len(self):
            return self._items[g : l]
        raise ValueError('No item found between keys : %r,%r' % (ge,le))

    def inside(self, ge, le):
        if g != len(self) and l != len(self) and g != l:
        elif g != len(self) and l != len(self) and g == l:
            return [ self._items[g] ]
        elif g != len(self):
            return self._items[g-1: l]
        elif l != len(self):
            return self._items[g: l-1]
        else:
            return self._items[g - 1: l - 1]
        raise ValueError('No item found inside keys: %r,%r' % (ge,le))

    def around(self, k):
        g = bisect_right(self._keys, k)
        l = bisect_left(self._keys, k)
        raise ValueError('No item found around key : %r' % (k,))

"""
Common data transformation used on pre and post processing of the FTS
"""

class Transformation(object):
    """
    Data transformation used on pre and post processing of the FTS
    """

        self.is_invertible = True
        self.minimal_length = 1

    def apply(self, data, param, **kwargs):
        """
        Apply the transformation on input data

        :param data: input data
        :param param:
        :param kwargs:
        :return: numpy array with transformed data
        """
        pass

    def inverse(self,data, param, **kwargs):
        """

        :param data: transformed data
        :return: numpy array with inverse transformed data
        """
        pass

        return self.__class__.__name__ + '(' + str(self.parameters) + ')'

class Differential(Transformation):
    """
    Differentiation data transform
    """
    def __init__(self, lag):
        super(Differential, self).__init__()
        self.lag = lag
        self.minimal_length = 2

    @property
    def parameters(self):
        return self.lag

    def apply(self, data, param=None, **kwargs):
        if param is not None:
            self.lag = param

        if not isinstance(data, (list, np.ndarray, np.generic)):
            data = [data]

        if isinstance(data, (np.ndarray, np.generic)):
            data = data.tolist()

        n = len(data)
        diff = [data[t] - data[t - self.lag] for t in np.arange(self.lag, n)]
        for t in np.arange(0, self.lag): diff.insert(0, 0)
        return diff

    def inverse(self, data, param, **kwargs):

        type = kwargs.get("type","point")
        steps_ahead = kwargs.get("steps_ahead", 1)

        if not isinstance(data, list):

#        print(n)
#        print(len(param))

        if steps_ahead == 1:
            if type == "point":
                inc = [data[t] + param[t] for t in np.arange(0, n)]
            elif type == "interval":
                inc = [[data[t][0] + param[t], data[t][1] + param[t]] for t in np.arange(0, n)]
            elif type == "distribution":
                for t in np.arange(0, n):
                    data[t].differential_offset(param[t])
                inc = data
        else:
                inc = [data[0] + param[0]]
                for t in np.arange(1, steps_ahead):
                    inc.append(data[t] + inc[t-1])
                inc = [[data[0][0] + param[0], data[0][1] + param[0]]]
                    inc.append([data[t][0] + np.nanmean(inc[t-1]), data[t][1] + np.nanmean(inc[t-1])])
                data[0].differential_offset(param[0])
                    ex = data[t-1].expected_value()
                    data[t].differential_offset(ex)
                inc = data

        if n == 1:
            return inc[0]
        else:
            return inc

class Scale(Transformation):
    """
    Scale data inside a interval [min, max]

    """
    def __init__(self, min=0, max=1):
        super(Scale, self).__init__()
        self.data_max = None
        self.data_min = None
        self.transf_max = max
        self.transf_min = min

    @property
        return [self.transf_max, self.transf_min]

    def apply(self, data, param=None,**kwargs):
        if self.data_max is None:
            self.data_max = np.nanmax(data)
            self.data_min = np.nanmin(data)
        data_range = self.data_max - self.data_min
        transf_range = self.transf_max - self.transf_min
        if isinstance(data, list):
            tmp = [(k + (-1 * self.data_min)) / data_range for k in data]
            tmp2 = [ (k * transf_range) + self.transf_min for k in tmp]
        else:
            tmp = (data + (-1 * self.data_min)) / data_range
            tmp2 = (tmp * transf_range) + self.transf_min

        return  tmp2

            tmp2 = [(k - self.transf_min) / transf_range   for k in data]
            tmp = [(k * data_range) + self.data_min for k in tmp2]
        else:
            tmp2 = (data - self.transf_min) / transf_range
            tmp = (tmp2 * data_range) + self.data_min
        return tmp

class AdaptiveExpectation(Transformation):
    """
    Adaptive Expectation post processing
    """
    def __init__(self, parameters):
        super(AdaptiveExpectation, self).__init__(parameters)
        self.h = parameters

    @property
        return self.parameters

    def inverse(self, data, param,**kwargs):

        inc = [param[t] + self.h*(data[t] - param[t]) for t in np.arange(0, n)]

        if n == 1:
        else:
            return inc

class BoxCox(Transformation):
    """
    Box-Cox power transformation
    """
    def __init__(self, plambda):
        super(BoxCox, self).__init__()
        self.plambda = plambda

    @property
        return self.plambda

        if self.plambda != 0:
            modified = [(dat ** self.plambda - 1) / self.plambda for dat in data]
        else:
            modified = [np.log(dat) for dat in data]
        return np.array(modified)

    def inverse(self, data, param=None, **kwargs):
            modified = [np.exp(np.log(dat * self.plambda + 1) ) / self.plambda for dat in data]
        else:
            modified = [np.exp(dat) for dat in data]

def Z(original):
    mu = np.mean(original)
    sigma = np.std(original)
    z = [(k - mu)/sigma for k in original]
    return z

# retrieved from Sadaei and Lee (2014) - Multilayer Stock ForecastingModel Using Fuzzy Time Series
def roi(original):
    n = len(original)
    roi = []
    for t in np.arange(0, n-1):
        roi.append( (original[t+1] - original[t])/original[t]  )
    return roi

def smoothing(original, lags):
    pass

def aggregate(original, operation):
    pass

"""
Common facilities for pyFTS
"""

import dill

def plot_rules(model, size=[5, 5], axis=None, rules_by_axis=None, columns=1):
    if axis is None and rules_by_axis is None:
        rows = 1
    elif axis is None and rules_by_axis is not None:
        rows = (((len(model.flrgs.keys())//rules_by_axis)) // columns)+1

    fig, axis = plt.subplots(nrows=rows, ncols=columns, figsize=size)

    if rules_by_axis is None:
        draw_sets_on_axis(axis, model, size)

    _lhs = model.partitioner.ordered_sets if not model.is_high_order else model.flrgs.keys()

    for ct, key in enumerate(_lhs):

        xticks = []
        xtickslabels = []

        if rules_by_axis is None:
            ax = axis
        else:
            colcount = (ct // rules_by_axis) % columns
            rowcount = (ct // rules_by_axis) // columns

            if rows > 1 and columns > 1:
                ax = axis[rowcount, colcount]
            elif columns > 1:
                ax = axis[rowcount]
            else:
                ax = axis

            if ct % rules_by_axis == 0:
                draw_sets_on_axis(ax, model, size)

        if not model.is_high_order:
            if key in model.flrgs:
                x = (ct % rules_by_axis) + 1
                flrg = model.flrgs[key]
                y = model.sets[key].centroid
                ax.plot([x],[y],'o')
                xticks.append(x)
                xtickslabels.append(key)
                for rhs in flrg.RHS:
                    dest = model.sets[rhs].centroid
                    ax.arrow(x+.1, y, 0.8, dest - y, #length_includes_head=True,
                               head_width=0.1, head_length=0.1, shape='full', overhang=0,
                               fc='k', ec='k')
        else:
            flrg = model.flrgs[key]
            x = (ct%rules_by_axis)*model.order + 1
            for ct2, lhs in enumerate(flrg.LHS):
                y = model.sets[lhs].centroid
                ax.plot([x+ct2], [y], 'o')
                xticks.append(x+ct2)
                xtickslabels.append(lhs)
            for ct2 in range(1, model.order):
                fs1 = flrg.LHS[ct2-1]
                fs2 = flrg.LHS[ct2]
                y = model.sets[fs1].centroid
                dest = model.sets[fs2].centroid
                ax.plot([x+ct2-1,x+ct2], [y,dest],'-')

            y = model.sets[flrg.LHS[-1]].centroid
            for rhs in flrg.RHS:
                dest = model.sets[rhs].centroid
                ax.arrow(x + model.order -1 + .1, y, 0.8, dest - y,  # length_includes_head=True,
                           head_width=0.1, head_length=0.1, shape='full', overhang=0,
                           fc='k', ec='k')

        ax.set_xticks(xticks)
        ax.set_xticklabels(xtickslabels)
        ax.set_xlim([0,rules_by_axis*model.order+1])

    plt.show()

def draw_sets_on_axis(axis, model, size):
    if axis is None:
        fig, axis = plt.subplots(nrows=1, ncols=1, figsize=size)
    for ct, key in enumerate(model.partitioner.ordered_sets):
        fs = model.sets[key]
        axis.plot([0, 1, 0], fs.parameters, label=fs.name)
        axis.axhline(fs.centroid, c="lightgray", alpha=0.5)
    axis.set_xlim([0, len(model.partitioner.ordered_sets)])
    axis.set_xticks(range(0, len(model.partitioner.ordered_sets)))
    tmp = ['']
    tmp.extend(model.partitioner.ordered_sets)
    axis.set_xticklabels(tmp)
    axis.set_ylim([model.partitioner.min, model.partitioner.max])
    axis.set_yticks([model.sets[k].centroid for k in model.partitioner.ordered_sets])
    axis.set_yticklabels([str(round(model.sets[k].centroid, 1)) + " - " + k
                          for k in model.partitioner.ordered_sets])

current_milli_time = lambda: int(round(time.time() * 1000))

def uniquefilename(name):
    if '.' in name:
        tmp = name.split('.')
        return  tmp[0] + str(current_milli_time()) + '.' + tmp[1]
    else:
        return name + str(current_milli_time())

def show_and_save_image(fig, file, flag, lgd=None):
    """
    Show and image and save on file

    :param fig: Matplotlib Figure object
    :param file: filename to save the picture
    :param flag: if True the image will be saved
    :param lgd: legend
    """
    if flag:
        plt.show()
        if lgd is not None:
            fig.savefig(file, additional_artists=lgd,bbox_inches='tight')  #bbox_extra_artists=(lgd,), )
        else:
            fig.savefig(file)
        plt.close(fig)

def enumerate2(xs, start=0, step=1):
    for x in xs:
        yield (start, x)
        start += step

def sliding_window(data, windowsize, train=0.8, inc=0.1, **kwargs):
    """
    Sliding window method of cross validation for time series

    :param data: the entire dataset
    :param windowsize: window size
    :param train: percentual of the window size will be used for training the models
    :param inc: percentual of data used for slide the window
    :return: window count, training set, test set
    """
    l = len(data)
    ttrain = int(round(windowsize * train, 0))
    ic = int(round(windowsize * inc, 0))

    progressbar = kwargs.get('progress', None)

    rng = np.arange(0,l-windowsize+ic,ic)

    if progressbar:
        rng = tqdm(rng)

    for count in rng:
        if count + windowsize > l:
            _end = l
        else:
            _end = count + windowsize
        yield (count,  data[count : count + ttrain], data[count + ttrain : _end]  )

def persist_obj(obj, file):
    """
    Persist an object on filesystem. This function depends on Dill package

    :param obj: object on memory
    :param file: file name to store the object
    """
    with open(file, 'wb') as _file:
        dill.dump(obj, _file)

def load_obj(file):
    """
    Load to memory an object stored filesystem. This function depends on Dill package

    :param file: file name where the object is stored
    :return: object
    """
    with open(file, 'rb') as _file:
        obj = dill.load(_file)
    return obj

def persist_env(file):
    """
    Persist an entire environment on file. This function depends on Dill package

    :param file: file name to store the environment
    """
    dill.dump_session(file)

def load_env(file):
    dill.load_session(file)

def start_dispy_cluster(method, nodes):
    import dispy, dispy.httpd, logging

    cluster = dispy.JobCluster(method, nodes=nodes, loglevel=logging.DEBUG, ping_interval=1000)

    http_server = dispy.httpd.DispyHTTPServer(cluster)

    return cluster, http_server

def stop_dispy_cluster(cluster, http_server):
    cluster.wait()  # wait for all jobs to finish

    cluster.print_status()

    http_server.shutdown()  # this waits until browser gets all updates
    cluster.close()

def simple_model_train(model, data, parameters):
    model.train(data, **parameters)
    return model

def distributed_train(model, train_method, nodes, fts_method, data, num_batches=10,
                      train_parameters={}, **kwargs):
    import dispy, dispy.httpd, datetime

    batch_save = kwargs.get('batch_save', False)  # save model between batches

    batch_save_interval = kwargs.get('batch_save_interval', 1)

    file_path = kwargs.get('file_path', None)

    cluster, http_server = start_dispy_cluster(train_method, nodes)

    print("[{0: %H:%M:%S}] Distrituted Train Started".format(datetime.datetime.now()))

    jobs = []
    batch_size = int(n / num_batches)
    bcount = 1
    for ct in range(model.order, n, batch_size):
        if model.is_multivariate:
            ndata = data.iloc[ct - model.order:ct + batch_size]
        else:
            ndata = data[ct - model.order: ct + batch_size]

        tmp_model = fts_method(str(bcount))

        tmp_model.clone_parameters(model)

        job = cluster.submit(tmp_model, ndata, train_parameters)
        job.id = bcount  # associate an ID to identify jobs (if needed later)
        jobs.append(job)

        bcount += 1

    for job in jobs:
        print("[{0: %H:%M:%S}] Processing batch ".format(datetime.datetime.now()) + str(job.id))
        tmp = job()
        if job.status == dispy.DispyJob.Finished and tmp is not None:
            model.merge(tmp)

            if batch_save and (job.id % batch_save_interval) == 0:
                persist_obj(model, file_path)

        else:
            print(job.exception)
            print(job.stdout)

        print("[{0: %H:%M:%S}] Finished batch ".format(datetime.datetime.now()) + str(job.id))

    print("[{0: %H:%M:%S}] Distrituted Train Finished".format(datetime.datetime.now()))

    stop_dispy_cluster(cluster, http_server)

def simple_model_predict(model, data, parameters):
    return model.predict(data, **parameters)

def distributed_predict(model, parameters, nodes, data, num_batches):
    import dispy, dispy.httpd

    cluster, http_server = start_dispy_cluster(simple_model_predict, nodes)

    jobs = []
    bcount = 1
        else:

        job = cluster.submit(model, ndata, parameters)

        bcount += 1

    ret = []

        tmp = job()
            if job.id < batch_size:
                ret.extend(tmp[:-1])
            else:
                ret.extend(tmp)
        else:

    return ret

class FLRG(object):
    """
    Fuzzy Logical Relationship Group

    Group a set of FLR's with the same LHS. Represents the temporal patterns for time t+1 (the RHS fuzzy sets)
    when the LHS pattern is identified on time t.
    """

    def __init__(self, order, **kwargs):
        self.LHS = None
        """Left Hand Side of the rule"""
        self.RHS = None
        """Right Hand Side of the rule"""
        self.order = order
        """Number of lags on LHS"""
        self.midpoint = None
        self.lower = None
        self.upper = None
        self.key = None

    def append_rhs(self, set, **kwargs):
        pass

    def get_key(self):
        """Returns a unique identifier for this FLRG"""
        if self.key is None:
            if isinstance(self.LHS, (list, set)):
                names = [c for c in self.LHS]
            elif isinstance(self.LHS, dict):
                names = [self.LHS[k] for k in self.LHS.keys()]
            else:
                names = [self.LHS]

            self.key = ""

            for n in names:
                if len(self.key) > 0:
                    self.key += ","
                self.key = self.key + n
        return self.key

    def get_membership(self, data, sets):
        """
        Returns the membership value of the FLRG for the input data

        :param sets: fuzzy sets
        :return: the membership value
        """
        ret = 0.0
        if isinstance(self.LHS, (list, set)):
            if len(self.LHS) == len(data):
                ret = np.nanmin([sets[self.LHS[ct]].membership(dat) for ct, dat in enumerate(data)])
        else:
            ret = sets[self.LHS].membership(data)
        return ret

    def get_midpoint(self, sets):
        """
        Returns the midpoint value for the RHS fuzzy sets

        :return: the midpoint value
        """
        if self.midpoint is None:
            self.midpoint = np.nanmean(self.get_midpoints(sets))
        return self.midpoint

    def get_midpoints(self, sets):
        if isinstance(self.RHS, (list, set)):
            return np.array([sets[s].centroid for s in self.RHS])
        elif isinstance(self.RHS, dict):
            return np.array([sets[self.RHS[s]].centroid for s in self.RHS.keys()])

    def get_lower(self, sets):
        """
        Returns the lower bound value for the RHS fuzzy sets

        :return: lower bound value
        """
        if self.lower is None:
            if isinstance(self.RHS, list):
                self.lower = min([sets[rhs].lower for rhs in self.RHS])
            elif isinstance(self.RHS, dict):
                self.lower = min([sets[self.RHS[s]].lower for s in self.RHS.keys()])
        return self.lower

    def get_upper(self, sets):
        """
        Returns the upper bound value for the RHS fuzzy sets

        :return: upper bound value
        """
        if self.upper is None:
                self.upper = max([sets[rhs].upper for rhs in self.RHS])
                self.upper = max([sets[self.RHS[s]].upper for s in self.RHS.keys()])
        return self.upper

        return len(self.RHS)

from pyFTS.common import FuzzySet, SortedCollection, tree, Util

class FTS(object):
    """
    Fuzzy Time Series object model
    """
        """
        Create a Fuzzy Time Series model
        """

        self.sets = {}
        """The list of fuzzy sets used on this model"""
        self.flrgs = {}
        """The list of Fuzzy Logical Relationship Groups - FLRG"""
        self.order = kwargs.get('order',1)
        """A integer with the model order (number of past lags are used on forecasting)"""
        self.shortname = kwargs.get('name',"")
        """A string with a short name or alias for the model"""
        self.name = kwargs.get('name',"")
        """A string with the model name"""
        self.detail = kwargs.get('name',"")
        """A string with the model detailed information"""
        """A boolean value indicating if the model support orders greater than 1, default: False"""
        """In high order models, this integer value indicates the minimal order supported for the model, default: 1"""
        self.has_seasonality = False
        """A boolean value indicating if the model supports seasonal indexers, default: False"""
        """A boolean value indicating if the model supports point forecasting, default: True"""
        self.has_interval_forecasting = False
        """A boolean value indicating if the model supports interval forecasting, default: False"""
        self.has_probability_forecasting = False
        """A boolean value indicating if the model support probabilistic forecasting, default: False"""
        self.is_multivariate = False
        """A boolean value indicating if the model support multivariate time series (Pandas DataFrame), default: False"""
        self.dump = False
        self.transformations = []
        """A list with the data transformations (common.Transformations) applied on model pre and post processing, default: []"""
        self.transformations_param = []
        """A list with the specific parameters for each data transformation"""
        self.original_max = 0
        """A float with the upper limit of the Universe of Discourse, the maximal value found on training data"""
        self.original_min = 0
        """A float with the lower limit of the Universe of Discourse, the minimal value found on training data"""
        self.partitioner = kwargs.get("partitioner", None)
        """A pyFTS.partitioners.Partitioner object with the Universe of Discourse partitioner used on the model. This is a mandatory dependecy. """
        if self.partitioner != None:
            self.sets = self.partitioner.sets
        self.auto_update = False
        """A boolean value indicating that model is incremental"""
        self.benchmark_only = False
        """A boolean value indicating a façade for external (non-FTS) model used on benchmarks or ensembles."""
        self.indexer = kwargs.get("indexer", None)
        """An pyFTS.models.seasonal.Indexer object for indexing the time series data"""
        self.uod_clip = kwargs.get("uod_clip", True)
        """Flag indicating if the test data will be clipped inside the training Universe of Discourse"""
        self.alpha_cut = kwargs.get("alpha_cut", 0.0)
        """A float with the minimal membership to be considered on fuzzyfication process"""
        self.max_lag = self.order
        """A integer indicating the largest lag used by the model. This value also indicates the minimum number of past lags 
        needed to forecast a single step ahead"""

    def fuzzy(self, data):
        """
        Fuzzify a data point

        :param data: data point
        :return: maximum membership fuzzy set
        """
        best = {"fuzzyset": "", "membership": 0.0}

        for f in self.sets:
            fset = self.sets[f]
            if best["membership"] <= fset.membership(data):
                best["fuzzyset"] = fset.name
                best["membership"] = fset.membership(data)

        return best

    def predict(self, data, **kwargs):
        """
        Forecast using trained model

        :param data: time series with minimal length to the order of the model

        :keyword type: the forecasting type, one of these values: point(default), interval or distribution.
        :keyword steps_ahead: The forecasting horizon, i. e., the number of steps ahead to forecast
        :keyword start: in the multi step forecasting, the index of the data where to start forecasting
        :keyword distributed: boolean, indicate if the forecasting procedure will be distributed in a dispy cluster
        :keyword nodes: a list with the dispy cluster nodes addresses

        :return: a numpy array with the forecasted data
        """

        if self.is_multivariate:
            ndata = data
        else:
            ndata = self.apply_transformations(data)

            if self.uod_clip:
                ndata = np.clip(ndata, self.original_min, self.original_max)

        if 'distributed' in kwargs:
            distributed = kwargs.pop('distributed')
        else:
            distributed = False

        if distributed is None or distributed == False:

            if 'type' in kwargs:
                type = kwargs.pop("type")
            else:
                type = 'point'

            steps_ahead = kwargs.get("steps_ahead", None)

            if steps_ahead == None or steps_ahead == 1:
                if type == 'point':
                    ret = self.forecast(ndata, **kwargs)
                elif type == 'interval':
                    ret = self.forecast_interval(ndata, **kwargs)
                elif type == 'distribution':
                    ret = self.forecast_distribution(ndata, **kwargs)
            elif steps_ahead > 1:
                    ret = self.forecast_ahead(ndata, steps_ahead, **kwargs)
                    ret = self.forecast_ahead_interval(ndata, steps_ahead, **kwargs)
                    ret = self.forecast_ahead_distribution(ndata, steps_ahead, **kwargs)

            if not ['point', 'interval', 'distribution'].__contains__(type):
                raise ValueError('The argument \'type\' has an unknown value.')

        else:

            nodes = kwargs.get("nodes", ['127.0.0.1'])
            num_batches = kwargs.get('num_batches', 10)

            ret = Util.distributed_predict(self, kwargs, nodes, ndata, num_batches)

        if not self.is_multivariate:
            kwargs['type'] = type
            ret = self.apply_inverse_transformations(ret, params=[data[self.max_lag - 1:]], **kwargs)

        return ret

        """
        Point forecast one step ahead

        :param data: time series data with the minimal length equal to the max_lag of the model
        :param kwargs: model specific parameters
        :return: a list with the forecasted values
        """
        raise NotImplementedError('This model do not perform one step ahead point forecasts!')

        """
        Interval forecast one step ahead

        :return: a list with the forecasted intervals
        """
        raise NotImplementedError('This model do not perform one step ahead interval forecasts!')

        """
        Probabilistic forecast one step ahead

        :return: a list with the forecasted Probability Distributions
        """
        raise NotImplementedError('This model do not perform one step ahead distribution forecasts!')

    def forecast_ahead(self, data, steps, **kwargs):
        """
        Point forecast n steps ahead

        :param steps: the number of steps ahead to forecast
        """
        ret = []
        for k in np.arange(0,steps):
            tmp = self.forecast(data[-self.order:], **kwargs)

            if isinstance(tmp,(list, np.ndarray)):
                tmp = tmp[0]

            data.append_rhs(tmp)

        return ret

    def forecast_ahead_interval(self, data, steps, **kwargs):
        """
        Interval forecast n steps ahead

        """
        raise NotImplementedError('This model do not perform multi step ahead interval forecasts!')

        """
        Probabilistic forecast n steps ahead

        """
        raise NotImplementedError('This model do not perform multi step ahead distribution forecasts!')

        """
        Method specific parameter fitting

        :param data: training time series data
        :param kwargs: Method specific parameters

        """
        pass

    def fit(self, ndata, **kwargs):
        """
        Fit the model's parameters based on the training data.

        :param ndata: training time series data

        :keyword num_batches: split the training data in num_batches to save memory during the training process
        :keyword save_model: save final model on disk
        :keyword batch_save: save the model between each batch
        :keyword file_path: path to save the model
        :keyword distributed: boolean, indicate if the training procedure will be distributed in a dispy cluster

        """

        import datetime

            data = ndata
        else:
            data = self.apply_transformations(ndata)

            self.original_min = np.nanmin(data)
            self.original_max = np.nanmax(data)

        if 'sets' in kwargs:
            self.sets = kwargs.pop('sets')

        if 'partitioner' in kwargs:
            self.partitioner = kwargs.pop('partitioner')

        if (self.sets is None or len(self.sets) == 0) and not self.benchmark_only and not self.is_multivariate:
            if self.partitioner is not None:
                self.sets = self.partitioner.sets
            else:
                raise Exception("Fuzzy sets were not provided for the model. Use 'sets' parameter or 'partitioner'. ")

            self.order = kwargs.pop('order')

        dump = kwargs.get('dump', None)

        num_batches = kwargs.get('num_batches', None)

        save = kwargs.get('save_model', False)  # save model on disk

        batch_save = kwargs.get('batch_save', False) #save model between batches

        file_path = kwargs.get('file_path', None)

        distributed = kwargs.get('distributed', False)

        batch_save_interval = kwargs.get('batch_save_interval', 10)

        if distributed:
            nodes = kwargs.get('nodes', False)
            train_method = kwargs.get('train_method', Util.simple_model_train)
            Util.distributed_train(self, train_method, nodes, type(self), data, num_batches, {},
                                   batch_save=batch_save, file_path=file_path,
                                   batch_save_interval=batch_save_interval)
        else:

            if dump == 'time':
                print("[{0: %H:%M:%S}] Start training".format(datetime.datetime.now()))

            if num_batches is not None:
                n = len(data)
                batch_size = int(n / num_batches)
                bcount = 1

                rng = range(self.order, n, batch_size)

                if dump == 'tqdm':
                    from tqdm import tqdm

                    rng = tqdm(rng)

                for ct in rng:
                    if dump == 'time':
                        print("[{0: %H:%M:%S}] Starting batch ".format(datetime.datetime.now()) + str(bcount))
                    if self.is_multivariate:
                        mdata = data.iloc[ct - self.order:ct + batch_size]
                    else:
                        mdata = data[ct - self.order : ct + batch_size]

                    self.train(mdata, **kwargs)

                    if batch_save:
                        Util.persist_obj(self,file_path)

                        print("[{0: %H:%M:%S}] Finish batch ".format(datetime.datetime.now()) + str(bcount))

                    bcount += 1

            else:
                self.train(data, **kwargs)

                print("[{0: %H:%M:%S}] Finish training".format(datetime.datetime.now()))

        if save:
            Util.persist_obj(self, file_path)

    def clone_parameters(self, model):
        """
        Import the parameters values from other model

        :param model:
        """

        self.order = model.order
        self.shortname = model.shortname
        self.name = model.name
        self.detail = model.detail
        self.is_high_order = model.is_high_order
        self.min_order = model.min_order
        self.has_seasonality = model.has_seasonality
        self.has_point_forecasting = model.has_point_forecasting
        self.has_interval_forecasting = model.has_interval_forecasting
        self.has_probability_forecasting = model.has_probability_forecasting
        self.is_multivariate = model.is_multivariate
        self.dump = model.dump
        self.transformations = model.transformations
        self.transformations_param = model.transformations_param
        self.original_max = model.original_max
        self.original_min = model.original_min
        self.partitioner = model.partitioner
        self.sets = model.sets
        self.auto_update = model.auto_update
        self.benchmark_only = model.benchmark_only
        self.indexer = model.indexer

    def merge(self, model):
        """
        Merge the FLRG rules from other model

        :param model: source model
        :return:
        """

        for key in model.flrgs.keys():
            if flrg.get_key() not in self.flrgs:
                self.flrgs[flrg.get_key()] = flrg
            else:
                if isinstance(flrg.RHS, (list, set)):
                    for k in flrg.RHS:
                        self.flrgs[flrg.get_key()].append_rhs(k)
                elif isinstance(flrg.RHS, dict):
                    for k in flrg.RHS.keys():
                        self.flrgs[flrg.get_key()].append_rhs(flrg.RHS[k])
                else:
                    self.flrgs[flrg.get_key()].append_rhs(flrg.RHS)

    def append_transformation(self, transformation):
        if transformation is not None:
            self.transformations.append(transformation)

    def apply_transformations(self, data, params=None, updateUoD=False, **kwargs):
        """
        Apply the data transformations for data preprocessing

        :param params: transformation parameters
        :param updateUoD:
        :return: preprocessed data
        """

        if updateUoD:
            if min(data) < 0:
                self.original_min = min(data) * 1.1
            else:
                self.original_min = min(data) * 0.9

            if max(data) > 0:
                self.original_max = max(data) * 1.1
            else:
                self.original_max = max(data) * 0.9

        if len(self.transformations) > 0:
            if params is None:
                params = [ None for k in self.transformations]

            for c, t in enumerate(self.transformations, start=0):
                ndata = t.apply(ndata,params[c])

        return ndata

    def apply_inverse_transformations(self, data, params=None, **kwargs):
        """
        Apply the data transformations for data postprocessing

        :return: postprocessed data
        """
                params = [None for k in self.transformations]

            for c, t in enumerate(reversed(self.transformations), start=0):
                ndata = t.inverse(data, params[c], **kwargs)

            return ndata
        else:
            return data

    def get_UoD(self):
        return [self.original_min, self.original_max]

        """String representation of the model"""

        tmp = self.name + ":\n"
        for r in sorted(self.flrgs, key=lambda key: self.flrgs[key].get_midpoint(self.sets)):
            tmp = tmp + str(self.flrgs[r]) + "\n"
        return tmp

        """
        The length (number of rules) of the model

        :return: number of rules
        """
        return len(self.flrgs)

    def len_total(self):
        return sum([len(k) for k in self.flrgs])

"""
Tree data structure
"""

from functools import reduce

class FLRGTreeNode:
    """
    Tree node for
    """

    def __init__(self, value):
        self.isRoot = False
        self.children = []
        self.value = value

    def appendChild(self, child):
        self.children.append(child)

    def getChildren(self):
        for child in self.children:
            yield child

    def paths(self, acc=[]):
        if len(self.children) == 0:
            yield [self.value] + acc

            for leaf_path in child.paths([self.value] + acc):  # these two
                yield leaf_path

    def getStr(self, k):
        if self.isRoot:
            tmp = str(self.value)
        else:
            tmp = "\\" + ("-" * k) + str(self.value)
        for child in self.getChildren():
            tmp = tmp + "\n" + child.getStr(k + 1)
        return tmp

        return self.getStr(0)

class FLRGTree:
    """Represents a FLRG set with a tree structure"""
    def __init__(self):
        self.root = FLRGTreeNode(None)

def flat(dados):
    for inst in dados:
        if isinstance(inst, (list, tuple)):
            x = flat(inst)
            for k in x:
                yield k
        else:
            yield inst

def build_tree_without_order(node, lags, level):

    if level not in lags:
        return

    for s in lags[level]:
        node.appendChild(FLRGTreeNode(s))

    for child in node.getChildren():
        build_tree_without_order(child, lags, level + 1)

"""
Monthly totals of a airline passengers from USA, from January 1949 through December 1960.

Source: Hyndman, R.J., Time Series Data Library, http://www-personal.buseco.monash.edu.au/~hyndman/TSDL/.
"""

from pyFTS.data import common

def get_data():
    """
    Get a simple univariate time series data.

    :return: numpy array
    """
    dat = get_dataframe()
    dat = np.array(dat["Passengers"])
    return dat

def get_dataframe():
    """
    Get the complete multivariate time series data.

    :return: Pandas DataFrame
    """
    dat = common.get_dataframe('AirPassengers.csv',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/AirPassengers.csv',
                               sep=",")
    return dat

"""
Yearly University of Alabama enrollments from 1971 to 1992.
"""

    """

    """
    dat = np.array(dat["Enrollments"])
    return dat

    dat = common.get_dataframe('Enrollments.csv',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/Enrollments.csv',
                               sep=";")
    return dat

"""
INMET - Instituto Nacional Meteorologia / Brasil

Belo Horizonte station, from 2000-01-01 to  31/12/2012

Source: http://www.inmet.gov.br
"""

    """

    """
    dat = common.get_dataframe('INMET.csv.bz2',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/INMET.csv.bz2',
                               sep=";", compression='bz2')
    dat["DataHora"] = pd.to_datetime(dat["DataHora"], format='%d/%m/%Y %H:%M')
    return dat

"""
National Association of Securities Dealers Automated Quotations - Composite Index (NASDAQ IXIC)

Daily averaged index by business day, from 2000 to 2016.

Source: http://www.nasdaq.com/aspx/flashquotes.aspx?symbol=IXIC&selected=IXIC
"""

def get_data(field="avg"):
    """

    :param field: the dataset field name to extract
    """
    dat = np.array(dat[field])
    return dat

    """

    """
    dat = common.get_dataframe('NASDAQ.csv.bz2',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/NASDAQ.csv.bz2',
                               sep=",", compression='bz2')
    return dat

"""
SONDA - Sistema de Organização Nacional de Dados Ambientais, from INPE - Instituto Nacional de Pesquisas Espaciais, Brasil.

Brasilia station

Source: http://sonda.ccst.inpe.br/

"""

def get_data(field):
    """

    """
    return dat

    """

    """
    dat = common.get_dataframe('SONDA_BSB.csv.bz2',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/SONDA_BSB.csv.bz2',
    dat["datahora"] = pd.to_datetime(dat["datahora"], format='%Y-%m-%d %H:%M:%S')
    return dat

"""
S&P500 - Standard & Poor's 500

Daily averaged index, by business day, from 1950 to 2017.

Source: https://finance.yahoo.com/quote/%5EGSPC/history?p=%5EGSPC
"""

    """
    Get the univariate time series data.

    """
    return np.array(dat["Avg"])

    """

    """
    dat = common.get_dataframe('SP500.csv.bz2',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/SP500.csv.bz2',
    return dat

"""
The Taiwan Stock Exchange Capitalization Weighted Stock Index (TAIEX)

Daily averaged index by business day, from 1995 to 2014.

Source: http://www.twse.com.tw/en/products/indices/Index_Series.php
"""

    """

    """
    dat = np.array(dat["avg"])
    return dat

    """

    """
    dat = common.get_dataframe('TAIEX.csv.bz2',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/TAIEX.csv.bz2',
    dat["Date"] = pd.to_datetime(dat["Date"])
    return dat

"""
Facilities to generate synthetic stochastic processes
"""

def generate_gaussian_linear(mu_ini, sigma_ini, mu_inc, sigma_inc, it=100, num=10, vmin=None, vmax=None):
    """
    Generate data sampled from Gaussian distribution, with constant or linear changing parameters

    :param mu_ini: Initial mean
    :param sigma_ini: Initial variance
    :param mu_inc:  Mean increment after 'num' samples
    :param sigma_inc: Variance increment after 'num' samples
    :param it: Number of iterations
    :param num: Number of samples generated on each iteration
    :param vmin: Lower bound value of generated data
    :param vmax: Upper bound value of generated data
    :return: A list of it*num float values
    """
    mu = mu_ini
    sigma = sigma_ini
    ret = []
    for k in np.arange(0,it):
        tmp = np.random.normal(mu, sigma, num)
        if vmin is not None:
            tmp = np.maximum(np.full(num, vmin), tmp)
        if vmax is not None:
            tmp = np.minimum(np.full(num, vmax), tmp)
        ret.extend(tmp)
        mu += mu_inc
        sigma += sigma_inc
    return ret

def generate_uniform_linear(min_ini, max_ini, min_inc, max_inc, it=100, num=10, vmin=None, vmax=None):
    """
    Generate data sampled from Uniform distribution, with constant or  linear changing bounds

    """
    _min = min_ini
    _max = max_ini
    ret = []
        tmp = np.random.uniform(_min, _max, num)
        _min += min_inc
        _max += max_inc
    return ret

def white_noise(n=500):
    return np.random.normal(0, 1, n)

def random_walk(n=500, type='gaussian'):
    if type == 'gaussian':
        tmp = generate_gaussian_linear(0, 1, 0, 0, it=1, num=n)
    else:
        tmp = generate_uniform_linear(-1, 1, 0, 0, it=1, num=n)
    ret = [0]
    for i in range(n):
        ret.append(tmp[i] + ret[i])

    return ret

import os
import pkg_resources
from pathlib import Path
from urllib import request

def get_dataframe(filename, url, sep=";", compression='infer'):
    """
    This method check if filename already exists, read the file and return its data.
    If the file don't already exists, it will be downloaded and decompressed.

    :param filename: dataset local filename
    :param url: dataset internet URL
    :param sep: CSV field separator
    :param compression: type of compression
    :return:  Pandas dataset
    """

    tmp_file = Path(filename)

    if tmp_file.is_file():
        return pd.read_csv(filename, sep=sep, compression=compression)
    else:
        request.urlretrieve(url, filename)

"""
M. Hénon. "A two-dimensional mapping with a strange attractor". Commun. Math. Phys. 50, 69-77 (1976)

dx/dt = a + by(t-1) - x(t-1)^2
dy/dt = x
"""

def get_data(var, a=1.4, b=0.3, initial_values = [1, 1], iterations=1000):
    """

    :param var: the dataset field name to extract
    """
    return get_dataframe(a,b, initial_values, iterations)[var].values

def get_dataframe(a=1.4, b=0.3, initial_values = [1, 1], iterations=1000):
    '''
    Return a dataframe with the bivariate Henon Map time series (x, y).

    :param a: Equation coefficient
    :param b: Equation coefficient
    :param initial_values: numpy array with the initial values of x and y. Default: [1, 1]
    :param iterations: number of iterations. Default: 1000
    :return: Panda dataframe with the x and y values
    '''

    x = [initial_values[0]]
    y = [initial_values[1]]
    for t in np.arange(0, iterations):
        xx = a + b * y[t] - x[t] ** 2
        y.append(x[t])
        x.append(xx)

    return pd.DataFrame({'x': x, 'y':y})

"""
May, Robert M. (1976). "Simple mathematical models with very complicated dynamics".
Nature. 261 (5560): 459–467. doi:10.1038/261459a0.

x(t) = r * x(t-1) * (1 - x(t -1) )
"""

def get_data(r = 4, initial_value = 0.3, iterations=100):
    '''
    Return a list with the logistic map chaotic time series.

    :param r: Equation coefficient
    :param initial_value: Initial value of x. Default: 0.3
    :param iterations: number of iterations. Default: 100
    :return:
    '''
    x = [initial_value]
    for t in np.arange(0,iterations):
      x.append(r * x[t]*(1 - x[t]))

    return x

"""
Lorenz, Edward Norton (1963). "Deterministic nonperiodic flow". Journal of the Atmospheric Sciences. 20 (2): 130–141.
https://doi.org/10.1175/1520-0469(1963)020<0130:DNF>2.0.CO;2

dx/dt = a(y -x)
dy/dt = x(b - z) - y
dz/dt = xy - cz
"""

def get_data(var, a = 10.0, b = 28.0, c = 8.0 / 3.0, dt = 0.01,
                  initial_values = [0.1, 0, 0], iterations=1000):
    """
        Get a simple univariate time series data.

        :param var: the dataset field name to extract
        :return: numpy array
        """
    return get_dataframe(a, b, c, dt, initial_values, iterations)[var].values

def get_dataframe(a = 10.0, b = 28.0, c = 8.0 / 3.0, dt = 0.01,
    '''
    Return a dataframe with the multivariate Lorenz Map time series (x, y, z).

    :param a: Equation coefficient. Default value: 10
    :param b: Equation coefficient. Default value: 28
    :param c: Equation coefficient. Default value: 8.0/3.0
    :param dt: Time differential for continuous time integration. Default value: 0.01
    :param initial_values: numpy array with the initial values of x,y and z. Default: [0.1, 0, 0]
    :return: Panda dataframe with the x, y and z values
    '''

    z = [initial_values[2]]

        dxdt = a * (y[t] - x[t])
        dydt = x[t] * (b - z[t]) - y[t]
        dzdt = x[t] * y[t] - c * z[t]
        x.append(x[t] + dt * dxdt)
        y.append(y[t] + dt * dydt)
        z.append(z[t] + dt * dzdt)

    return pd.DataFrame({'x': x, 'y':y, 'z': z})

"""
Mackey, M. C. and Glass, L. (1977). Oscillation and chaos in physiological control systems.
Science, 197(4300):287-289.

dy/dt = -by(t)+ cy(t - tau) / 1+y(t-tau)^10
"""

def get_data(b=0.1, c=0.2, tau=17, initial_values = np.linspace(0.5,1.5, 18), iterations=1000):
    '''
    Return a list with the Mackey-Glass chaotic time series.

    :param c: Equation coefficient
    :param tau: Lag parameter, default: 17
    :param initial_values: numpy array with the initial values of y. Default: np.linspace(0.5,1.5,18)
    :return:
    '''
    y = initial_values.tolist()

    for n in np.arange(len(y)-1, iterations+100):
        y.append(y[n] - b * y[n] + c * y[n - tau] / (1 + y[n - tau] ** 10))

    return y[100:]

"""
O. E. Rössler, Phys. Lett. 57A, 397 (1976).

dx/dt = -z - y
dy/dt = x + ay
dz/dt = b + z( x - c )

"""

def get_data(var, a = 0.2, b = 0.2, c = 5.7, dt = 0.01,
                  initial_values = [0.001, 0.001, 0.001], iterations=5000):
    """

    """

def get_dataframe(a = 0.2, b = 0.2, c = 5.7, dt = 0.01,
    '''
    Return a dataframe with the multivariate Rössler Map time series (x, y, z).

    :param a: Equation coefficient. Default value: 0.2
    :param b: Equation coefficient. Default value: 0.2
    :param c: Equation coefficient. Default value: 5.7
    :param initial_values: numpy array with the initial values of x,y and z. Default: [0.001, 0.001, 0.001]
    :param iterations: number of iterations. Default: 5000
    '''

        dxdt = - (y[t] + z[t])
        dydt = x[t] + a * y[t]
        dzdt = b + z[t] * x[t] - z[t] * c

"""
Monthly sunspot numbers from 1749 to May 2016

Source: https://www.esrl.noaa.gov/psd/gcos_wgsp/Timeseries/SUNSPOT/
"""

    """

    """
    dat = np.array(dat["SUNACTIVITY"])
    return dat

    """

    """
    dat = common.get_dataframe('sunspots.csv',
                               'https://github.com/petroniocandido/pyFTS/raw/8f20f3634aa6a8f58083bdcd1bbf93795e6ed767/pyFTS/data/sunspots.csv',
                               sep=",")
    return dat

"""
First Order Traditional Fuzzy Time Series method by Song & Chissom (1993)

Q. Song and B. S. Chissom, “Fuzzy time series and its models,” Fuzzy Sets Syst., vol. 54, no. 3, pp. 269–277, 1993.
"""

from pyFTS.common import FuzzySet, FLR, fts

class ConventionalFTS(fts.FTS):
    """Traditional Fuzzy Time Series"""
        super(ConventionalFTS, self).__init__(order=1, name="FTS", **kwargs)
        self.name = "Traditional FTS"
        self.detail = "Song & Chissom"
        if self.sets is not None and self.partitioner is not None:

        self.R = None

        if self.sets is not None:
            l = len(self.sets)
            self.R = np.zeros((l,l))

    def flr_membership_matrix(self, flr):
        ordered_set = FuzzySet.set_ordered(self.sets)
        centroids = [self.sets[k].centroid for k in ordered_set]
        lm = [self.sets[flr.LHS].membership(k) for k in centroids]
        rm = [self.sets[flr.RHS].membership(k) for k in centroids]

        l = len(ordered_set)
        r = np.zeros((l, l))
        for k in range(0,l):
            for l in range(0, l):
                r[k][l] = min(lm[k], rm[l])

        return r

    def operation_matrix(self, flrs):
        l = len(self.sets)
        if self.R is None or len(self.R) == 0 :
            self.R = np.zeros((l, l))
        for k in flrs:
            mm = self.flr_membership_matrix(k)
            for k in range(0, l):
                for l in range(0, l):
                    self.R[k][l] = max(self.R[k][l], mm[k][l])

        tmpdata = FuzzySet.fuzzyfy_series(data, self.sets, method='maximum')
        flrs = FLR.generate_non_recurrent_flrs(tmpdata)
        self.operation_matrix(flrs)

        if self.partitioner is not None:
            ordered_sets = self.partitioner.ordered_sets
        else:
            ordered_sets = FuzzySet.set_ordered(self.sets)

        npart = len(self.sets)

        ret = []

        for k in np.arange(0, l):
            mv = FuzzySet.fuzzyfy_instance(ndata[k], self.sets)

            r = [max([ min(self.R[i][j], mv[j]) for j in np.arange(0,npart) ]) for i in np.arange(0,npart)]

            fs = np.ravel(np.argwhere(r == max(r)))

            if len(fs) == 1:
                ret.append(self.sets[ordered_sets[fs[0]]].centroid)
            else:
                mp = [self.sets[ordered_sets[s]].centroid for s in fs]

                ret.append( sum(mp)/len(mp))

        return ret

        return tmp + str(self.R)

"""
First Order Conventional Fuzzy Time Series by Chen (1996)

S.-M. Chen, “Forecasting enrollments based on fuzzy time series,” Fuzzy Sets Syst., vol. 81, no. 3, pp. 311–319, 1996.
"""

from pyFTS.common import FuzzySet, FLR, fts, flrg

class ConventionalFLRG(flrg.FLRG):
    """First Order Conventional Fuzzy Logical Relationship Group"""
    def __init__(self, LHS, **kwargs):
        super(ConventionalFLRG, self).__init__(1, **kwargs)
        self.RHS = set()

    def get_key(self, sets):
        return sets[self.LHS].name

    def append_rhs(self, c, **kwargs):
        self.RHS.add(c)

        tmp = str(self.LHS) + " -> "
        tmp2 = ""
        for c in sorted(self.RHS, key=lambda s: s):
            if len(tmp2) > 0:
                tmp2 = tmp2 + ","
            tmp2 = tmp2 + str(c)
        return tmp + tmp2

    """Conventional Fuzzy Time Series"""
        super(ConventionalFTS, self).__init__(order=1, **kwargs)
        self.name = "Conventional FTS"
        self.detail = "Chen"
        self.shortname = "CFTS"

    def generate_flrg(self, flrs):
        for flr in flrs:
            if flr.LHS in self.flrgs:
                self.flrgs[flr.LHS].append_rhs(flr.RHS)
            else:
                self.flrgs[flr.LHS] = ConventionalFLRG(flr.LHS)

        self.generate_flrg(flrs)

        ret = []

            actual = FuzzySet.get_maximum_membership_fuzzyset(ndata[k], self.sets) #self.sets[np.argwhere(mv == max(mv))[0, 0]]

            if actual.name not in self.flrgs:
                ret.append(actual.centroid)
            else:
                _flrg = self.flrgs[actual.name]

                ret.append(_flrg.get_midpoint(self.sets))

        return ret

"""
First Order Weighted Fuzzy Time Series by Yu(2005)

H.-K. Yu, “Weighted fuzzy time series models for TAIEX forecasting,” 
Phys. A Stat. Mech. its Appl., vol. 349, no. 3, pp. 609–624, 2005.
"""

from pyFTS.models import chen

class WeightedFLRG(flrg.FLRG):
    """First Order Weighted Fuzzy Logical Relationship Group"""
        super(WeightedFLRG, self).__init__(1, **kwargs)
        self.RHS = []
        self.count = 1.0
        self.w = None

        self.RHS.append(c)
        self.count = self.count + 1.0

    def weights(self, sets):
        if self.w is None:
            tot = sum(np.arange(1.0, self.count, 1.0))
            self.w = np.array([k / tot for k in np.arange(1.0, self.count, 1.0)])
        return self.w

        tmp = self.LHS + " -> "
        tmp2 = ""
        cc = 1.0
        tot = sum(np.arange(1.0, self.count, 1.0))
        for c in sorted(self.RHS):
            tmp2 = tmp2 + c + "(" + str(round(cc / tot, 3)) + ")"
            cc = cc + 1.0

class WeightedFTS(fts.FTS):
    """First Order Weighted Fuzzy Time Series"""
        super(WeightedFTS, self).__init__(order=1, name="WFTS", **kwargs)
        self.name = "Weighted FTS"
        self.detail = "Yu"

    def generate_FLRG(self, flrs):
            else:
                self.flrgs[flr.LHS] = WeightedFLRG(flr.LHS);

    def train(self, ndata, **kwargs):
        tmpdata = FuzzySet.fuzzyfy_series(ndata, self.sets, method='maximum')
        flrs = FLR.generate_recurrent_flrs(tmpdata)
        self.generate_FLRG(flrs)

        else:

        ret = []

            actual = FuzzySet.get_maximum_membership_fuzzyset(ndata[k], self.sets, ordered_sets)

            else:
                flrg = self.flrgs[actual.name]
                mp = flrg.get_midpoints(self.sets)

                ret.append(mp.dot(flrg.weights(self.sets)))

        return ret

"""
Trend Weighted Fuzzy Time Series by Cheng, Chen and Wu (2009)

C.-H. Cheng, Y.-S. Chen, and Y.-L. Wu, “Forecasting innovation diffusion of products using trend-weighted fuzzy time-series model,” 
Expert Syst. Appl., vol. 36, no. 2, pp. 1826–1832, 2009.
"""

from pyFTS.models import yu

class TrendWeightedFLRG(yu.WeightedFLRG):
    """
    First Order Trend Weighted Fuzzy Logical Relationship Group
    """
        super(TrendWeightedFLRG, self).__init__(LHS, **kwargs)

            count_nochange = 0.0
            count_up = 0.0
            count_down = 0.0
            weights = []

            for c in self.RHS:
                tmp = 0
                if sets[self.LHS].centroid == sets[c].centroid:
                    count_nochange += 1.0
                    tmp = count_nochange
                elif sets[self.LHS].centroid > sets[c].centroid:
                    count_down += 1.0
                    tmp = count_down
                else:
                    count_up += 1.0
                    tmp = count_up
                weights.append(tmp)

            tot = sum(weights)
            self.w = np.array([k / tot for k in weights])

class TrendWeightedFTS(yu.WeightedFTS):
    """First Order Trend Weighted Fuzzy Time Series"""
        super(TrendWeightedFTS, self).__init__(**kwargs)
        self.shortname = "TWFTS"
        self.name = "Trend Weighted FTS"
        self.detail = "Cheng"

            else:
                self.flrgs[flr.LHS] = TrendWeightedFLRG(flr.LHS)

"""
High Order FTS

Severiano, S. A. Jr; Silva, P. C. L.; Sadaei, H. J.; Guimarães, F. G. Very Short-term Solar Forecasting
using Fuzzy Time Series. 2017 IEEE International Conference on Fuzzy Systems. DOI10.1109/FUZZ-IEEE.2017.8015732
"""

from pyFTS.common import FuzzySet, FLR, fts, flrg, tree

class HighOrderFLRG(flrg.FLRG):
    """Conventional High Order Fuzzy Logical Relationship Group"""
        super(HighOrderFLRG, self).__init__(order, **kwargs)
        self.LHS = []
        self.RHS = {}
        self.strlhs = ""

        if c not in self.RHS:
            self.RHS[c] = c

    def append_lhs(self, c):
        self.LHS.append(c)

        tmp = ""
            if len(tmp) > 0:
                tmp = tmp + ","
            tmp = tmp + c
        return self.get_key() + " -> " + tmp

class HighOrderFTS(fts.FTS):
    """Conventional High Order Fuzzy Time Series"""
        super(HighOrderFTS, self).__init__(**kwargs)
        self.name = "High Order FTS"
        self.shortname = "HOFTS"
        self.detail = "Severiano, Silva, Sadaei and Guimarães"
        self.order= kwargs.get("order", 2)
        self.lags = kwargs.get("lags", None)
        self.configure_lags(**kwargs)

    def configure_lags(self, **kwargs):
        if "order" in kwargs:
            self.order = kwargs.get("order", 2)

        if "lags" in kwargs:
            self.lags = kwargs.get("lags", None)

        if self.lags is not None:
            self.max_lag = max(self.lags)
        else:
            self.lags = np.arange(1, self.order+1)

    def generate_lhs_flrg(self, sample):
        lags = {}

        flrgs = []

        for ct, o in enumerate(self.lags):
            lhs = [key for key in self.partitioner.ordered_sets
                   if self.sets[key].membership(sample[o-1]) > self.alpha_cut]
            lags[ct] = lhs

        root = tree.FLRGTreeNode(None)

        tree.build_tree_without_order(root, lags, 0)

        # Trace the possible paths
        for p in root.paths():
            flrg = HighOrderFLRG(self.order)
            path = list(reversed(list(filter(None.__ne__, p))))

            for lhs in path:
                flrg.append_lhs(lhs)

            flrgs.append(flrg)

        return flrgs

    def generate_flrg(self, data):
        for k in np.arange(self.max_lag, l):
            if self.dump: print("FLR: " + str(k))

            sample = data[k - self.max_lag: k]

            rhs = [key for key in self.partitioner.ordered_sets
                   if self.sets[key].membership(data[k]) > self.alpha_cut]

            flrgs = self.generate_lhs_flrg(sample)

            for flrg in flrgs:
                if flrg.get_key() not in self.flrgs:
                    self.flrgs[flrg.get_key()] = flrg;

                for st in rhs:
                    self.flrgs[flrg.get_key()].append_rhs(st)

        self.generate_flrg(data)

        ret = []

        if l <= self.max_lag:

        for k in np.arange(self.max_lag, l+1):
            flrgs = self.generate_lhs_flrg(ndata[k - self.max_lag: k])

            tmp = []

                    if len(flrg.LHS) > 0:
                        tmp.append(self.sets[flrg.LHS[-1]].centroid)
                else:
                    flrg = self.flrgs[flrg.get_key()]
                    tmp.append(flrg.get_midpoint(self.sets))

            ret.append(np.nanmean(tmp))

        return ret

"""
High Order Fuzzy Time Series by Hwang, Chen and Lee (1998)

Jeng-Ren Hwang, Shyi-Ming Chen, and Chia-Hoang Lee, “Handling forecasting problems using fuzzy time series,” 
Fuzzy Sets Syst., no. 100, pp. 217–228, 1998.
"""

from pyFTS.common import FuzzySet, FLR, Transformations, fts

        self.min_order = 2
        self.name = "Hwang High Order FTS"
        self.shortname = "Hwang"
        self.detail = "Hwang"

        cn = np.array([0.0 for k in range(l)])
        ow = np.array([[0.0 for k in range(l)] for z in range(self.order - 1)])
        rn = np.array([[0.0 for k in range(l)] for z in range(self.order - 1)])
        ft = np.array([0.0 for k in range(l)])

        ret = []

        for t in np.arange(self.order-1, len(ndata)):

            for ix in range(l):
                s = self.partitioner.ordered_sets[ix]
                cn[ix] = self.sets[s].membership( FuzzySet.grant_bounds(ndata[t], self.sets, self.partitioner.ordered_sets))
                for w in np.arange(self.order-1):
                    ow[w, ix] = self.sets[s].membership(FuzzySet.grant_bounds(ndata[t - w], self.sets, self.partitioner.ordered_sets))
                    rn[w, ix] = ow[w, ix] * cn[ix]
                    ft[ix] = max(ft[ix], rn[w, ix])
            mft = max(ft)
            out = 0.0
            count = 0.0
                if ft[ix] == mft:
                    out = out + self.sets[s].centroid
                    count += 1.0
            ret.append(out / count)

        return ret

        if self.sets == None:

"""
High Order Interval Fuzzy Time Series

SILVA, Petrônio CL; SADAEI, Hossein Javedani; GUIMARÃES, Frederico Gadelha. Interval Forecasting with Fuzzy Time Series.
In: Computational Intelligence (SSCI), 2016 IEEE Symposium Series on. IEEE, 2016. p. 1-8.
"""

from pyFTS.common import FuzzySet, FLR, fts, tree
from pyFTS.models import hofts

class IntervalFTS(hofts.HighOrderFTS):
    """
    High Order Interval Fuzzy Time Series
    """
        super(IntervalFTS, self).__init__(**kwargs)
        self.shortname = "IFTS"
        self.name = "Interval FTS"
        self.detail = "Silva, P.; Guimarães, F.; Sadaei, H. (2016)"
        self.has_point_forecasting = False

    def get_upper(self, flrg):
        ret = np.nan
        if len(flrg.LHS) > 0:
            if flrg.get_key() in self.flrgs:
                tmp = self.flrgs[flrg.get_key()]
                ret = tmp.get_upper(self.sets)
            else:
                ret = self.sets[flrg.LHS[-1]].upper
        return ret

    def get_lower(self, flrg):
                ret = tmp.get_lower(self.sets)
            else:
                ret = self.sets[flrg.LHS[-1]].lower
        return ret

    def get_sequence_membership(self, data, fuzzySets):
        mb = [fuzzySets[k].membership(data[k]) for k in np.arange(0, len(data))]
        return mb

        ret = []

        if l <= self.order:

            sample = ndata[k - self.max_lag: k]

            up = []
            lo = []
            affected_flrgs_memberships = []

                if len(flrg.LHS) > 0:
                    # achar o os bounds de cada FLRG, ponderados pela pertinência
                    mv = flrg.get_membership(sample, self.sets)
                    up.append(mv * self.get_upper(flrg))
                    lo.append(mv * self.get_lower(flrg))
                    affected_flrgs_memberships.append(mv)

            # gerar o intervalo
            norm = sum(affected_flrgs_memberships)
            lo_ = sum(lo) / norm
            up_ = sum(up) / norm
            ret.append([lo_, up_])

        return ret

"""
First Order Improved Weighted Fuzzy Time Series by Efendi, Ismail and Deris (2013)

R. Efendi, Z. Ismail, and M. M. Deris, “Improved weight Fuzzy Time Series as used in the exchange rates forecasting of 
US Dollar to Ringgit Malaysia,” Int. J. Comput. Intell. Appl., vol. 12, no. 1, p. 1350005, 2013.
"""

class ImprovedWeightedFLRG(flrg.FLRG):
    """First Order Improved Weighted Fuzzy Logical Relationship Group"""
        super(ImprovedWeightedFLRG, self).__init__(1, **kwargs)
        self.rhs_counts = {}
        self.count = 0.0

            self.rhs_counts[c] = 1.0
        else:
            self.rhs_counts[c] += 1.0
        self.count += 1.0

    def weights(self):
            self.w = np.array([self.rhs_counts[c] / self.count for c in self.RHS.keys()])

        tmp2 = ""
        for c in sorted(self.RHS.keys()):
            tmp2 = tmp2 + c + "(" + str(round(self.rhs_counts[c] / self.count, 3)) + ")"

class ImprovedWeightedFTS(fts.FTS):
    """First Order Improved Weighted Fuzzy Time Series"""
        super(ImprovedWeightedFTS, self).__init__(order=1, name="IWFTS", **kwargs)
        self.name = "Improved Weighted FTS"
        self.detail = "Ismail & Efendi"

            else:
                self.flrgs[flr.LHS] = ImprovedWeightedFLRG(flr.LHS);

        l = 1

        else:

        ret = []

            else:

                ret.append(mp.dot(flrg.weights()))

        return ret

from operator import itemgetter
from pyFTS.common import FLR, FuzzySet, tree
from pyFTS.models import hofts, ifts

class ProbabilisticWeightedFLRG(hofts.HighOrderFLRG):
    """High Order Probabilistic Weighted Fuzzy Logical Relationship Group"""
    def __init__(self, order):
        super(ProbabilisticWeightedFLRG, self).__init__(order)
        self.rhs_count = {}
        self.frequency_count = 0.0

        if isinstance(data, (np.ndarray, list)):
            return np.nanprod([sets[key].membership(data[count])
                               for count, key in enumerate(self.LHS, start=0)])
        else:
            return sets[self.LHS[0]].membership(data)

        mv = kwargs.get('mv', 1.0)
        self.frequency_count += mv
        if c in self.RHS:
            self.rhs_count[c] += mv
        else:
            self.rhs_count[c] = mv

    def lhs_conditional_probability(self, x, sets, norm, uod, nbins):
        pk = self.frequency_count / norm

        tmp = pk * (self.get_membership(x, sets) / self.partition_function(sets, uod, nbins=nbins))

        return tmp

    def rhs_unconditional_probability(self, c):
        return self.rhs_count[c] / self.frequency_count

    def rhs_conditional_probability(self, x, sets, uod, nbins):
        total = 0.0
        for rhs in self.RHS:
            set = sets[rhs]
            wi = self.rhs_unconditional_probability(rhs)
            mv = set.membership(x) / set.partition_function(uod, nbins=nbins)
            total += wi * mv

        return total

    def partition_function(self, sets, uod, nbins=100):
        if self.Z is None:
                for key in self.LHS:
                    self.Z += sets[key].membership(k)

        '''Return the expectation of the PWFLRG, the weighted sum'''
            self.midpoint = np.sum(np.array([self.rhs_unconditional_probability(s) * sets[s].centroid
                                             for s in self.RHS]))

            self.upper = np.sum(np.array([self.rhs_unconditional_probability(s) * sets[s].upper for s in self.RHS]))

            self.lower = np.sum(np.array([self.rhs_unconditional_probability(s) * sets[s].lower for s in self.RHS]))

        tmp2 = ""
                tmp2 = tmp2 + ", "
            tmp2 = tmp2 + "(" + str(round(self.rhs_count[c] / self.frequency_count, 3)) + ")" + c
        return self.get_key() + " -> " + tmp2

class ProbabilisticWeightedFTS(ifts.IntervalFTS):
    """High Order Probabilistic Weighted Fuzzy Time Series"""
        super(ProbabilisticWeightedFTS, self).__init__(**kwargs)
        self.shortname = "PWFTS"
        self.name = "Probabilistic FTS"
        self.detail = "Silva, P.; Guimarães, F.; Sadaei, H."
        self.global_frequency_count = 0
        self.auto_update = kwargs.get('update',False)

        parameters = kwargs.get('parameters','fuzzy')

        if parameters == 'monotonic':
            tmpdata = FuzzySet.fuzzyfy_series_old(data, self.sets)
            flrs = FLR.generate_recurrent_flrs(tmpdata)
            self.generateFLRG(flrs)
        else:
            self.generate_flrg(data)

        lags = {}

        flrgs = []

            flrg = ProbabilisticWeightedFLRG(self.order)

                lhs_mv = flrg.get_membership(sample, self.sets)

                fuzzyfied = [(s, self.sets[s].membership(data[k]))
                             for s in self.sets.keys()
                             if self.sets[s].membership(data[k]) >  self.alpha_cut]

                mvs = []
                for set, mv in fuzzyfied:
                    self.flrgs[flrg.get_key()].append_rhs(set, mv=lhs_mv * mv)
                    mvs.append(mv)

                tmp_fq = sum([lhs_mv*kk for kk in mvs if kk > 0])

                self.global_frequency_count += tmp_fq

    def update_model(self,data):
        pass

    def add_new_PWFLGR(self, flrg):
        if flrg.get_key() not in self.flrgs:
            tmp = ProbabilisticWeightedFLRG(self.order)
            for fs in flrg.LHS: tmp.append_lhs(fs)
            tmp.append_rhs(flrg.LHS[-1])
            self.flrgs[tmp.get_key()] = tmp;
            self.global_frequency_count += 1

    def flrg_lhs_unconditional_probability(self, flrg):
        if flrg.get_key() in self.flrgs:
            return self.flrgs[flrg.get_key()].frequency_count / self.global_frequency_count
        else:
            return 0.0
            #self.add_new_PWFLGR(flrg)
            #return self.flrg_lhs_unconditional_probability(flrg)

    def flrg_lhs_conditional_probability(self, x, flrg):
        mv = flrg.get_membership(x, self.sets)
        pb = self.flrg_lhs_unconditional_probability(flrg)
        return mv * pb

    def get_midpoint(self, flrg):
            tmp = self.flrgs[flrg.get_key()]
            ret = tmp.get_midpoint(self.sets) #sum(np.array([tmp.rhs_unconditional_probability(s) * self.setsDict[s].centroid for s in tmp.RHS]))
        else:
            if len(flrg.LHS) > 0:
                pi = 1 / len(flrg.LHS)
                ret = sum(np.array([pi * self.sets[s].centroid for s in flrg.LHS]))
            else:
                ret = np.nan
        return ret

    def flrg_rhs_conditional_probability(self, x, flrg):

            _flrg = self.flrgs[flrg.get_key()]
            cond = []
            for s in _flrg.RHS.keys():
                _set = self.sets[s]
                tmp = _flrg.rhs_unconditional_probability(s) * (_set.membership(x) / _set.partition_function(uod=self.get_UoD()))
                cond.append(tmp)
            ret = sum(np.array(cond))
        else:
            ##########################################
            # this may be the problem! TEST IT!!!
            pi = 1 / len(flrg.LHS)
            ret = sum(np.array([pi * self.sets[s].membership(x) for s in flrg.LHS]))
        return ret

            ret = tmp.get_upper(self.sets)
        else:
            ret = 0
        return ret

            ret = tmp.get_lower(self.sets)
        else:
            ret = 0
        return ret

        method = kwargs.get('method','heuristic')

        ret = []

        for k in np.arange(self.max_lag - 1, l):
            sample = data[k - (self.max_lag - 1): k + 1]

            if method == 'heuristic':
                ret.append(self.point_heuristic(sample, **kwargs))
            elif method == 'expected_value':
                ret.append(self.point_expected_value(sample, **kwargs))
            else:
                raise ValueError("Unknown point forecasting method!")

            if self.auto_update and k > self.order+1: self.update_model(data[k - self.order - 1 : k])

        return ret

    def point_heuristic(self, sample, **kwargs):

        flrgs = self.generate_lhs_flrg(sample)

        mp = []
        norms = []
        for flrg in flrgs:
            norm = self.flrg_lhs_conditional_probability(sample, flrg)
            if norm == 0:
                norm = self.flrg_lhs_unconditional_probability(flrg)
            mp.append(norm * self.get_midpoint(flrg))
            norms.append(norm)

        norm = sum(norms)
        if norm == 0:
            return 0
        else:
            return sum(mp) / norm

    def point_expected_value(self, sample, **kwargs):
       return self.forecast_distribution(sample)[0].expected_value()

        alpha = kwargs.get('alpha', 0.05)

        ret = []

            sample = ndata[k - (self.max_lag - 1): k + 1]

                ret.append(self.interval_heuristic(sample))
            elif method == 'quantile':
                ret.append(self.interval_quantile(sample, alpha))
            else:
                raise ValueError("Unknown interval forecasting method!")

        return ret

    def interval_quantile(self, ndata, alpha):
        dist = self.forecast_distribution(ndata)
        itvl = dist[0].quantile([alpha, 1.0 - alpha])
        return itvl

    def interval_heuristic(self, sample):

        up = []
        lo = []
        norms = []
            up.append(norm * self.get_upper(flrg))
            lo.append(norm * self.get_lower(flrg))

            return [0, 0]
        else:
            return [lo_, up_]

        smooth = kwargs.get("smooth", "none")

        if 'bins' in kwargs:
            _bins = kwargs.pop('bins')
            nbins = len(_bins)
        else:
            nbins = kwargs.get("num_bins", 100)
            _bins = np.linspace(uod[0], uod[1], nbins)

        ret = []

                kwargs.pop('type')

            dist = ProbabilityDistribution.ProbabilityDistribution(smooth, uod=uod, bins=_bins, **kwargs)

            for bin in _bins:
                num = []
                den = []
                for s in flrgs:
                    if s.get_key() in self.flrgs:
                        flrg = self.flrgs[s.get_key()]
                        pk = flrg.lhs_conditional_probability(sample, self.sets, self.global_frequency_count, uod, nbins)
                        wi = flrg.rhs_conditional_probability(bin, self.sets, uod, nbins)
                        num.append(wi * pk)
                        den.append(pk)
                    else:
                        num.append(0.0)
                        den.append(0.000000001)
                pf = sum(num) / sum(den)

                dist.set(bin, pf)

        return ret

    def __check_point_bounds(self, point):
        lower_set = self.partitioner.lower_set()
        upper_set = self.partitioner.upper_set()
        return point <= lower_set.lower or point >= upper_set.upper

        start = kwargs.get('start', self.max_lag)

        ret = data[start - self.max_lag: start].tolist()

        for k in np.arange(self.max_lag, steps+self.max_lag):

            if self.__check_point_bounds(ret[-1]) :
                ret.append(ret[-1])
            else:
                mp = self.forecast(ret[k - self.max_lag: k], **kwargs)
                ret.append(mp[0])

        return ret[self.max_lag:]

    def __check_interval_bounds(self, interval):
            lower_set = self.partitioner.lower_set()
            upper_set = self.partitioner.upper_set()
            return interval[0] <= lower_set.lower and interval[1] >= upper_set.upper
        elif len(self.transformations) == 0:
            return interval[0] <= self.original_min and interval[1] >= self.original_max

        sample = data[start - self.max_lag: start]

        ret = [[k, k] for k in sample]
        
        ret.append(self.forecast_interval(sample)[0])

        for k in np.arange(self.max_lag+1, steps+self.max_lag):

            if len(ret) > 0 and self.__check_interval_bounds(ret[-1]):
            else:
                lower = self.forecast_interval([ret[x][0] for x in np.arange(k - self.max_lag, k)], **kwargs)
                upper = self.forecast_interval([ret[x][1] for x in np.arange(k - self.max_lag, k)], **kwargs)

                ret.append([np.min(lower), np.max(upper)])

        return ret[self.order:]

        ret = []

        else:

        sample = ndata[start - self.max_lag: start]

        for dat in sample:
            tmp = ProbabilityDistribution.ProbabilityDistribution(smooth, uod=uod, bins=_bins, **kwargs)
            tmp.set(dat, 1.0)

        dist = self.forecast_distribution(sample, bins=_bins)[0]

        ret.append(dist)

        for k in np.arange(self.max_lag+1, steps+self.max_lag+1):

            lags = {}

            # Find all bins of past distributions with probability greater than zero

            for ct, d in enumerate(self.lags):
                dd = ret[k - d]
                vals = [float(v) for v in dd.bins if round(dd.density(v), 4) > 0]
                lags[ct] = sorted(vals)

            root = tree.FLRGTreeNode(None)

            tree.build_tree_without_order(root, lags, 0)

            # Trace all possible combinations between the bins of past distributions

            for p in root.paths():
                path = list(reversed(list(filter(None.__ne__, p))))

                # get the combined probabilities for this path

                pk = np.prod([ret[k - self.max_lag + o].density(path[ct])
                              for ct, o in enumerate(self.lags)])

                d = self.forecast_distribution(path)[0]

                for bin in _bins:
                    dist.set(bin, dist.density(bin) + pk * d.density(bin))

        for r in sorted(self.flrgs):
            p = round(self.flrgs[r].frequency_count / self.global_frequency_count, 3)
            tmp = tmp + "(" + str(p) + ") " + str(self.flrgs[r]) + "\n"
        return tmp

def visualize_distributions(model, **kwargs):
    import matplotlib.pyplot as plt
    from matplotlib import gridspec
    import seaborn as sns

    ordered_sets = model.partitioner.ordered_sets
    ftpg_keys = sorted(model.flrgs.keys(), key=lambda x: model.flrgs[x].get_midpoint(model.sets))

    lhs_probs = [model.flrg_lhs_unconditional_probability(model.flrgs[k])
                 for k in ftpg_keys]

    mat = np.zeros((len(ftpg_keys), len(ordered_sets)))
    for row, w in enumerate(ftpg_keys):
        for col, k in enumerate(ordered_sets):
            if k in model.flrgs[w].RHS:
                mat[row, col] = model.flrgs[w].rhs_unconditional_probability(k)

    size = kwargs.get('size', (5,10))

    fig = plt.figure(figsize=size)

    gs = gridspec.GridSpec(1, 2, width_ratios=[1, 4])
    ax1 = plt.subplot(gs[0])
    sns.barplot(x='y', y='x', color='darkblue', data={'x': ftpg_keys, 'y': lhs_probs}, ax=ax1)
    ax1.set_ylabel("LHS Probabilities")

    ind_sets = range(len(ordered_sets))
    ax = plt.subplot(gs[1])
    sns.heatmap(mat, cmap='Blues', ax=ax, yticklabels=False)
    ax.set_title("RHS probabilities")
    ax.set_xticks(ind_sets)
    ax.set_xticklabels(ordered_sets)
    ax.grid(True)
    ax.xaxis.set_tick_params(rotation=90)

"""
First Order Exponentialy Weighted Fuzzy Time Series by Sadaei et al. (2013)

H. J. Sadaei, R. Enayatifar, A. H. Abdullah, and A. Gani, “Short-term load forecasting using a hybrid model with a 
refined exponentially weighted fuzzy time series and an improved harmony search,” Int. J. Electr. Power Energy Syst., vol. 62, no. from 2005, pp. 118–129, 2014.
"""

from pyFTS.common import FuzzySet,FLR,fts, flrg

default_c = 1.1

class ExponentialyWeightedFLRG(flrg.FLRG):
    """First Order Exponentialy Weighted Fuzzy Logical Relationship Group"""
        super(ExponentialyWeightedFLRG, self).__init__(1, **kwargs)
        self.c = kwargs.get("c",default_c)

            wei = [self.c ** k for k in np.arange(0.0, self.count, 1.0)]
            tot = sum(wei)
            self.w = np.array([k / tot for k in wei])

        tmp2 = ""
        cc = 0
        wei = [self.c ** k for k in np.arange(0.0, self.count, 1.0)]
        tot = sum(wei)
            tmp2 = tmp2 + c + "(" + str(wei[cc] / tot) + ")"
            cc = cc + 1

class ExponentialyWeightedFTS(fts.FTS):
    """First Order Exponentialy Weighted Fuzzy Time Series"""
        super(ExponentialyWeightedFTS, self).__init__(order=1, name="EWFTS", **kwargs)
        self.name = "Exponentialy Weighted FTS"
        self.detail = "Sadaei"
        self.c = kwargs.get('c', default_c)

    def generate_flrg(self, flrs, c):
            else:
                self.flrgs[flr.LHS] = ExponentialyWeightedFLRG(flr.LHS, c=c);

        self.generate_flrg(flrs, self.c)

        l = 1

        else:

        data = np.array(ndata)

        ret = []

            else:

        return ret

from pyFTS.common import SortedCollection, fts, tree
from pyFTS.models import chen, cheng, hofts, hwang, ismailefendi, sadaei, song, yu

def sampler(data, quantiles):
    ret = []
    for qt in quantiles:
        ret.append(np.nanpercentile(data, q=qt * 100))
    return ret

class EnsembleFTS(fts.FTS):
    """
    Ensemble FTS
    """
        super(EnsembleFTS, self).__init__(**kwargs)
        self.shortname = "Ensemble FTS"
        self.name = "Ensemble FTS"
        self.models = []
        """A list of FTS models, the ensemble components"""
        self.parameters = []
        """A list with the parameters for each component model"""
        self.point_method = kwargs.get('point_method', 'mean')
        self.interval_method = kwargs.get('interval_method', 'quantile')
        self.order = 1

    def append_model(self, model):
        """
        Append a new model to the ensemble

        :param model: FTS model

        """
        self.models.append(model)
        if model.order > self.order:
            self.order = model.order

            self.is_multivariate = True

        if model.has_seasonality:
            self.has_seasonality = True

        pass

    def get_models_forecasts(self,data):
        tmp = []
        for model in self.models:
            if model.is_multivariate or model.has_seasonality:
                forecast = model.forecast(data)
            else:

                if isinstance(data, pd.DataFrame) and self.indexer is not None:
                    data = self.indexer.get_data(data)

                sample = data[-model.order:]
                forecast = model.forecast(sample)
                if isinstance(forecast, (list,np.ndarray)) and len(forecast) > 0:
                    forecast = int(forecast[-1])
                elif isinstance(forecast, (list,np.ndarray)) and len(forecast) == 0:
                    forecast = np.nan
            if isinstance(forecast, list):
                tmp.extend(forecast)
            else:
                tmp.append(forecast)
        return tmp

    def get_point(self,forecasts, **kwargs):
        if self.point_method == 'mean':
            ret = np.nanmean(forecasts)
        elif self.point_method == 'median':
            ret = np.nanpercentile(forecasts, 50)
        elif self.point_method == 'quantile':
            alpha = kwargs.get("alpha",0.05)
            ret = np.percentile(forecasts, alpha*100)

        return ret

    def get_interval(self, forecasts):
        ret = []
        if self.interval_method == 'extremum':
            ret.append([min(forecasts), max(forecasts)])
        elif self.interval_method == 'quantile':
            qt_lo = np.nanpercentile(forecasts, q=self.alpha * 100)
            qt_up = np.nanpercentile(forecasts, q=(1-self.alpha) * 100)
            ret.append([qt_lo, qt_up])
        elif self.interval_method == 'normal':
            mu = np.nanmean(forecasts)
            sigma = np.sqrt(np.nanvar(forecasts))
            ret.append(mu + st.norm.ppf(self.alpha) * sigma)
            ret.append(mu + st.norm.ppf(1 - self.alpha) * sigma)

        return ret

    def get_distribution_interquantile(self,forecasts, alpha):
        size = len(forecasts)
        qt_lower = int(np.ceil(size * alpha)) - 1
        qt_upper = int(np.ceil(size * (1- alpha))) - 1

        ret = sorted(forecasts)[qt_lower : qt_upper]

        return ret

        if "method" in kwargs:
            self.point_method = kwargs.get('method','mean')

        ret = []

            sample = data[k - self.order : k]
            tmp = self.get_models_forecasts(sample)
            point = self.get_point(tmp)
            ret.append(point)

        return ret

            self.interval_method = kwargs.get('method','quantile')

        if 'alpha' in kwargs:
            self.alpha = kwargs.get('alpha',0.05)

        ret = []

            interval = self.get_interval(tmp)
            if len(interval) == 1:
                interval = interval[-1]
            ret.append(interval)

        return ret

        if 'method' in kwargs:

            self.alpha = kwargs.get('alpha', self.alpha)

        ret = []

        samples = [[k] for k in data[-self.order:]]

            forecasts = []
            lags = {}
            for i in np.arange(0, self.order): lags[i] = samples[k - self.order + i]

            # Build the tree with all possible paths

                forecasts.extend(self.get_models_forecasts(path))

            samples.append(sampler(forecasts, np.arange(0.1, 1, 0.2)))
            interval = self.get_interval(forecasts)

                interval = interval[0]

        return ret

        ret = []

            forecasts = self.get_models_forecasts(sample)

            if alpha is None:
                forecasts = np.ravel(forecasts).tolist()
            else:
                forecasts = self.get_distribution_interquantile(np.ravel(forecasts).tolist(), alpha)

        return ret

        ret = []

        start = kwargs.get('start', self.order)

        sample = data[start - self.order: start]

            lags = {}
            for i in np.arange(0, self.order): lags[i] = sample[k-self.order]

            sample.append(sampler(forecasts, np.arange(0.1, 1, 0.1)))

            else:

        return ret

class AllMethodEnsembleFTS(EnsembleFTS):
        super(AllMethodEnsembleFTS, self).__init__(**kwargs)
        self.min_order = 3
        self.shortname ="Ensemble FTS"

    def set_transformations(self, model):
        for t in self.transformations:
            model.append_transformation(t)

        fo_methods = [song.ConventionalFTS, chen.ConventionalFTS, yu.WeightedFTS, cheng.TrendWeightedFTS,
                      sadaei.ExponentialyWeightedFTS, ismailefendi.ImprovedWeightedFTS]

        ho_methods = [hofts.HighOrderFTS, hwang.HighOrderFTS]

        for method in fo_methods:
            model = method(partitioner=self.partitioner)
            self.set_transformations(model)
            model.fit(data, **kwargs)
            self.append_model(model)

        for method in ho_methods:
            for o in np.arange(1, self.order+1):
                model = method(partitioner=self.partitioner)
                if model.min_order >= o:
                    model.order = o
                    self.set_transformations(model)
                    model.fit(data, **kwargs)
                    self.append_model(model)

from pyFTS.models.seasonal import cmsfts
from joblib import Parallel, delayed
import multiprocessing

def train_individual_model(partitioner, train_data, indexer):
    pttr = str(partitioner.__module__).split('.')[-1]
    diff = "_diff" if partitioner.transformation is not None else ""
    _key = "msfts_" + pttr + str(partitioner.partitions) + diff + "_" + indexer.name

    print(_key)

    model = cmsfts.ContextualMultiSeasonalFTS(_key, indexer=indexer)
    model.append_transformation(partitioner.transformation)
    model.train(train_data, partitioner.sets, order=1)

    cUtil.persist_obj(model, "models/"+_key+".pkl")

class SeasonalEnsembleFTS(ensemble.EnsembleFTS):
    def __init__(self, name, **kwargs):
        super(SeasonalEnsembleFTS, self).__init__(name="Seasonal Ensemble FTS", **kwargs)
        self.indexers = []
        self.partitioners = []
        self.is_multivariate = True
        self.has_seasonality = True

    def update_uod(self, data):
        self.original_max = max(self.indexer.get_data(data))
        self.original_min = min(self.indexer.get_data(data))

        num_cores = multiprocessing.cpu_count()

        pool = {}
        count = 0
        for ix in self.indexers:
            for pt in self.partitioners:
                pool[count] = {'ix': ix, 'pt': pt}
                count += 1

        results = Parallel(n_jobs=num_cores)(
            delayed(train_individual_model)(deepcopy(pool[m]['pt']), data, deepcopy(pool[m]['ix']))
            for m in pool.keys())

        for tmp in results:
            self.append_model(tmp)

        cUtil.persist_obj(self, "models/"+self.name+".pkl")

        ret = []

        for k in data.index:

            tmp = self.get_models_forecasts(data.ix[k])

                tmp = np.ravel(tmp).tolist()
            else:
                tmp = self.get_distribution_interquantile( np.ravel(tmp).tolist(), alpha)

            name = str(self.indexer.get_index(data.ix[k]))

            dist = ProbabilityDistribution.ProbabilityDistribution(smooth, uod=uod, data=tmp,
                                                                   name=name, **kwargs)

        return ret

    """Multivariate Fuzzy Logical Relationship"""

        """
        :param LHS: Left Hand Side fuzzy set
        :param RHS: Right Hand Side fuzzy set
        """
        self.LHS = {}

    def set_lhs(self, var, set):
        self.LHS[var] = set

    def set_rhs(self, set):
        self.RHS = set

        return str([self.LHS[k] for k in self.LHS.keys()]) + " -> " + self.RHS

def fuzzyfy_instance(data_point, var):
    mv = np.array([var.partitioner.sets[key].membership(data_point) for key in var.partitioner.ordered_sets])
    ix = np.ravel(np.argwhere(mv > var.alpha_cut))
    sets = [(var.name, var.partitioner.ordered_sets[i]) for i in ix]
    return sets

from pyFTS.common import flrg as flg

class FLRG(flg.FLRG):
    """
    Multivariate Fuzzy Logical Rule Group
    """

        super(FLRG,self).__init__(0,**kwargs)
        self.order = kwargs.get('order', 1)
        self.LHS = kwargs.get('lhs', {})

    def set_lhs(self, var, fset):
            self.LHS[var] = fset
        else:
            if var not in self.LHS:
                self.LHS[var] = []
            self.LHS[var].append(fset)

    def append_rhs(self, fset, **kwargs):
        self.RHS.add(fset)

    def get_membership(self, data, variables):
        mvs = []
        for var in variables:
            s = self.LHS[var.name]
            mvs.append(var.partitioner.sets[s].membership(data[var.name]))

        return np.nanmin(mvs)

        _str = ""
        for k in self.RHS:
            _str += "," if len(_str) > 0 else ""
            _str += k

        return self.get_key() + " -> " + _str

from pyFTS.common import fts, FuzzySet, FLR, Membership, tree
from pyFTS.models.multivariate import FLR as MVFLR, common, flrg as mvflrg

class MVFTS(fts.FTS):
    """
    Multivariate extension of Chen's ConventionalFTS method
    """
        super(MVFTS, self).__init__(order=1, **kwargs)
        self.explanatory_variables = []
        self.target_variable = None
        self.shortname = "MVFTS"
        self.name = "Multivariate FTS"

    def append_variable(self, var):
        """
        Append a new endogenous variable to the model

        :param var: variable object
        :return:
        """
        self.explanatory_variables.append(var)

    def format_data(self, data):
        ndata = {}
        for var in self.explanatory_variables:
            ndata[var.name] = data[var.data_label]

        ndata = data.copy(deep=True)
            ndata[var.data_label] = var.apply_transformations(data[var.data_label].values)

    def generate_lhs_flrs(self, data):
        flrs = []
        lags = {}
        for vc, var in enumerate(self.explanatory_variables):
            data_point = data[var.data_label]
            lags[vc] = common.fuzzyfy_instance(data_point, var)

            flr = MVFLR.FLR()

            for v, s in path:
                flr.set_lhs(v, s)

            if len(flr.LHS.keys()) == len(self.explanatory_variables):

        return flrs

    def generate_flrs(self, data):
        flrs = []
        for ct in range(1, len(data.index)):
            ix = data.index[ct-1]
            data_point = data.loc[ix]

            tmp_flrs = self.generate_lhs_flrs(data_point)

            target_ix = data.index[ct]
            target_point = data[self.target_variable.data_label][target_ix]
            target = common.fuzzyfy_instance(target_point, self.target_variable)

            for flr in tmp_flrs:
                for v, s in target:
                    flr.set_rhs(s)
                    flrs.append(flr)

            flrg = mvflrg.FLRG(lhs=flr.LHS)

            self.flrgs[flrg.get_key()].append_rhs(flr.RHS)

        ndata = self.apply_transformations(data)

        flrs = self.generate_flrs(ndata)

        ret = []
        for ix in ndata.index:
            data_point = ndata.loc[ix]
            flrs = self.generate_lhs_flrs(data_point)
            mvs = []
            mps = []
            for flr in flrs:
                flrg = mvflrg.FLRG(lhs=flr.LHS)
                    #print('hit')
                    mvs.append(0.)
                    mps.append(0.)
                else:
                    mvs.append(self.flrgs[flrg.get_key()].get_membership(self.format_data(data_point), self.explanatory_variables))
                    mps.append(self.flrgs[flrg.get_key()].get_midpoint(self.target_variable.partitioner.sets))

            #print('mv', mvs)
            #print('mp', mps)
            mv = np.array(mvs)
            mp = np.array(mps)

            ret.append(np.dot(mv,mp.T)/np.sum(mv))

        ret = self.target_variable.apply_inverse_transformations(ret,
                                                           params=data[self.target_variable.data_label].values)
        return ret

        super(MVFTS, self).clone_parameters(model)

        self.explanatory_variables = model.explanatory_variables
        self.target_variable = model.target_variable

        _str = self.name + ":\n"
        for k in self.flrgs.keys():
            _str += str(self.flrgs[k]) + "\n"

        return _str

from pyFTS.models.multivariate import FLR as MVFLR

class Variable:
    """
    A variable of a fuzzy time series multivariate model. Each variable contains its own
    transformations and partitioners.
    """
        """

        :param name:
        :param \**kwargs: See below

        :Keyword Arguments:
            * *alias* -- Alternative name for the variable
        """
        """A string with the name of the variable"""
        self.alias = kwargs.get('alias', self.name)
        """A string with the alias of the variable"""
        self.data_label = kwargs.get('data_label', self.name)
        """A string with the column name on DataFrame"""
        self.transformation = kwargs.get('transformation', None)
        self.transformation_params = kwargs.get('transformation_params', None)
        self.partitioner = None
        self.alpha_cut = kwargs.get('alpha_cut', 0.0)

        if kwargs.get('data', None) is not None:
            self.build(**kwargs)

    def build(self, **kwargs):
        """

        :return:
        """
        fs = kwargs.get('partitioner', Grid.GridPartitioner)
        mf = kwargs.get('func', Membership.trimf)
        np = kwargs.get('npart', 10)
        data = kwargs.get('data', None)
        kw = kwargs.get('partitioner_specific', {})
        self.partitioner = fs(data=data[self.data_label].values, npart=np, func=mf,
                              transformation=self.transformation, prefix=self.alias,
                              variable=self.name, **kw)

        self.partitioner.name = self.name + " " + self.partitioner.name

    def apply_transformations(self, data, **kwargs):

        if kwargs.get('params', None) is not None:
            self.transformation_params = kwargs.get('params', None)

        if self.transformation is not None:
            return self.transformation.apply(data, self.transformation_params)

    def apply_inverse_transformations(self, data, **kwargs):

            return self.transformation.inverse(data, self.transformation_params)

        return self.name

"""
Non Stationary Fuzzy Sets

GARIBALDI, Jonathan M.; JAROSZEWSKI, Marcin; MUSIKASUWAN, Salang. Nonstationary fuzzy sets.
IEEE Transactions on Fuzzy Systems, v. 16, n. 4, p. 1072-1086, 2008.
"""

from pyFTS.common import FuzzySet as FS, Membership, FLR
from pyFTS.partitioners import partitioner
from pyFTS.models.nonstationary import perturbation

class FuzzySet(FS.FuzzySet):
    """
    Non Stationary Fuzzy Sets
    """

    def __init__(self, name, mf, parameters, **kwargs):
        """
        Constructor
        """
        super(FuzzySet, self).__init__(name=name, mf=mf, parameters=parameters, centroid=None, alpha=1.0, **kwargs)
    
        self.location = kwargs.get("location", None)
        """Pertubation function that affects the location of the membership function"""
        self.location_params = kwargs.get("location_params", None)
        """Parameters for location pertubation function"""
        self.location_roots = kwargs.get("location_roots", 0)
        self.width = kwargs.get("width", None)
        """Pertubation function that affects the width of the membership function"""
        self.width_params = kwargs.get("width_params", None)
        """Parameters for width pertubation function"""
        self.width_roots = kwargs.get("width_roots", 0)
        self.noise = kwargs.get("noise", None)
        """Pertubation function that adds noise on the membership function"""
        self.noise_params = kwargs.get("noise_params", None)
        """Parameters for noise pertubation function"""
        self.perturbated_parameters = {}
        self.type = 'nonstationary'
    
        if self.location is not None and not isinstance(self.location, (list, set)):
            self.location = [self.location]
            self.location_params = [self.location_params]
            self.location_roots = [self.location_roots]
    
        if self.width is not None and not isinstance(self.width, (list, set)):
            self.width = [self.width]
            self.width_params = [self.width_params]
            self.width_roots = [self.width_roots]
    
    def perform_location(self, t, param):
        if self.location is None:
            inc = t
        else:
            l = len(self.location)

            inc = sum([self.location[k](t + self.location_roots[k], self.location_params[k]) for k in np.arange(0, l)])

        if self.mf == Membership.gaussmf:
            # changes only the mean parameter
            return [param[0] + inc, param[1]]
        elif self.mf == Membership.sigmf:
            # changes only the midpoint parameter
            return [param[0], param[1] + inc]
        elif self.mf == Membership.bellmf:
            return [param[0], param[1], param[2] + inc]
        else:
            # translate all parameters
            return [k + inc for k in param]
    
    def perform_width(self, t, param):
        if self.width is None:
            inc = t
        else:

            l = len(self.width)

            inc = sum([self.width[k](t + self.width_roots[k], self.width_params[k]) for k in np.arange(0, l)])

            # changes only the variance parameter
            # changes only the smooth parameter
        elif self.mf == Membership.trimf:
            tmp = inc / 2
            return [param[0] - tmp, param[1], param[2] + tmp]
        elif self.mf == Membership.trapmf:
            l = (param[3] - param[0])
            rab = (param[1] - param[0]) / l
            rcd = (param[3] - param[2]) / l
            return [param[0] - inc, param[1] - inc * rab, param[2] + inc * rcd, param[3] + inc]
        else:
            return param
    
    def membership(self, x, t):
        """

        :param t: time displacement or perturbation parameters
        """
    
        self.perturbate_parameters(t)
    
        tmp = self.mf(x, self.perturbated_parameters[str(t)])
    
        if self.noise is not None:
            tmp += self.noise(t, self.noise_params)
    
        return tmp
    
    def perturbate_parameters(self, t):
        if str(t) not in self.perturbated_parameters:
            param = self.parameters
            if isinstance(t, (list, set)):
                param = self.perform_location(t[0], param)
                param = self.perform_width(t[1], param)
            else:
                param = self.perform_location(t, param)
                param = self.perform_width(t, param)
            self.perturbated_parameters[str(t)] = param
            
    def get_midpoint(self, t):

        param = self.perturbated_parameters[str(t)]

            return param[0]
            return param[1]
            return (param[2] - param[1]) / 2
        else:

    def get_lower(self, t):

            return param[0] - 3*param[1]
            return param[0] - param[1]
        else:

    def get_upper(self, t):

            return param[0] + 3*param[1]
            return param[0] + param[1]
            return param[2]
            return param[3]
        else:

        tmp = ""
        if self.location is not None:
            tmp += "Location: "
            for ct, f in enumerate(self.location):
                tmp += str(f.__name__) + "(" + str(["{0:.2f}".format(p) for p in self.location_params[ct]]) + ") "
        if self.width is not None:
            tmp += "Width: "
            for ct, f in enumerate(self.width):
                tmp += str(f.__name__) + "(" + str(["{0:.2f}".format(p) for p in self.width_params[ct]]) + ") "
        tmp = "(" + str(["{0:.2f}".format(p) for p in self.parameters]) + ") " + tmp
        return self.name + ": " + str(self.mf.__name__) + tmp

def fuzzify(inst, t, fuzzySets):
    """
    Calculate the membership values for a data point given nonstationary fuzzy sets

    :param inst: data points
    :param t: time displacement of the instance
    :param fuzzySets: list of fuzzy sets
    """
    ret = []
    if not isinstance(inst, list):
        inst = [inst]
    for t, i in enumerate(inst):
        mv = np.array([fs.membership(i, t) for fs in fuzzySets])
    return ret

def fuzzySeries(data, fuzzySets, ordered_sets, window_size=1, method='fuzzy', const_t= None):
    fts = []
        tdisp = window_index(t, window_size) if const_t is None else const_t
        mv = np.array([fuzzySets[fs].membership(i, tdisp) for fs in ordered_sets])
            sets = [check_bounds(i, fuzzySets, tdisp)]
        else:
                ix = np.ravel(np.argwhere(mv > 0.0))
            sets = [fuzzySets[ordered_sets[i]] for i in ix]
    return fts

def window_index(t, window_size):
    if isinstance(t, (list, set)):
        return t
    return t - (t % window_size)

def check_bounds(data, partitioner, t):
    if data < partitioner.lower_set().get_lower(t):
        return partitioner.lower_set()
    elif data > partitioner.upper_set().get_upper(t):
        return partitioner.upper_set()

def check_bounds_index(data, partitioner, t):
        return 0
        return len(partitioner.sets) -1

from pyFTS.models.nonstationary import common,nsfts
from pyFTS.common import FLR, flrg, tree

class HighOrderNonstationaryFLRG(hofts.HighOrderFTS):
        super(HighOrderNonstationaryFLRG, self).__init__(order, **kwargs)

        tmp = ""
            tmp = tmp + c

class ConditionalVarianceFTS(hofts.HighOrderFTS):
        super(ConditionalVarianceFTS, self).__init__(**kwargs)
        self.name = "Conditional Variance FTS"
        self.shortname = "CVFTS "
        self.detail = ""
            self.append_transformation(self.partitioner.transformation)

        self.min_stack = [0,0,0]
        self.max_stack = [0,0,0]
        self.uod_clip = False
        self.max_lag = 1
        self.inputs = []
        self.forecasts = []
        self.residuals = []
        self.variance_residual = 0.
        self.mean_residual = 0.
        self.memory_window = kwargs.get("memory_window",5)

        tmpdata = common.fuzzySeries(ndata, self.sets,
                                     self.partitioner.ordered_sets,
                                     method='fuzzy', const_t=0)

        self.forecasts = self.forecast(ndata, no_update=True)
        self.residuals = np.array(ndata[1:]) - np.array(self.forecasts[:-1])

        self.variance_residual = np.var(self.residuals) # np.max(self.residuals
        self.mean_residual = np.mean(self.residuals)

        self.residuals = self.residuals[-self.memory_window:].tolist()
        self.forecasts = self.forecasts[-self.memory_window:]
        self.inputs = np.array(ndata[-self.memory_window:]).tolist()

    def generate_flrg(self, flrs, **kwargs):
            if flr.LHS.name in self.flrgs:
                self.flrgs[flr.LHS.name].append_rhs(flr.RHS.name)
            else:
                self.flrgs[flr.LHS.name] = nsfts.ConventionalNonStationaryFLRG(flr.LHS.name)

    def _smooth(self, a):
        return .1 * a[0] + .3 * a[1] + .6 * a[2]

    def perturbation_factors(self, data, **kwargs):
        npart = len(self.partitioner.sets)
        _max = 0
        _min = 0
        if data < self.original_min:
            _min = data - self.original_min if data < 0 else self.original_min - data
        elif data > self.original_max:
            _max = data - self.original_max if data > 0 else self.original_max - data
        self.min_stack.pop(2)
        self.min_stack.insert(0, _min)
        _min = min(self.min_stack)
        self.max_stack.pop(2)
        self.max_stack.insert(0, _max)
        _max = max(self.max_stack)

        _range = (_max - _min)/2

        translate = np.linspace(_min, _max, npart)

        var = np.std(self.residuals)

        var = 0 if var < 1 else var

        loc = (self.mean_residual + np.mean(self.residuals))

        location = [_range + w + loc + k for k in np.linspace(-var,var, npart) for w in translate]

        scale = [abs(location[0] - location[2])]
        scale.extend([abs(location[k - 1] - location[k + 1]) for k in np.arange(1, npart)])
        scale.append(abs(location[-1] - location[-3]))

        perturb = [[location[k], scale[k]] for k in np.arange(npart)]

        return perturb

    def perturbation_factors__old(self, data):
        _max = 0
        _min = 0
        self.min_stack.insert(0,_min)

        location = np.linspace(_min, _max, npart)
        scale.extend([abs(location[k-1] - location[k+1]) for k in np.arange(1, npart)])

        perturb = [[location[k], scale[k]] for k in np.arange(0, npart)]

    def _fsset_key(self, ix):
        return self.partitioner.ordered_sets[ix]

    def _affected_sets(self, sample, perturb):

        affected_sets = [[ct, self.sets[self._fsset_key(ct)].membership(sample, perturb[ct])]
                         for ct in np.arange(len(self.partitioner.sets))
                         if self.sets[self._fsset_key(ct)].membership(sample, perturb[ct]) > 0.0]

        if len(affected_sets) == 0:

            if sample < self.partitioner.lower_set().get_lower(perturb[0]):
                affected_sets.append([0, 1])
            elif sample > self.partitioner.upper_set().get_upper(perturb[-1]):
                affected_sets.append([len(self.sets) - 1, 1])

        return affected_sets

        ret = []

        no_update = kwargs.get("no_update",False)

            sample = ndata[k]

            if not no_update:
                perturb = self.perturbation_factors(sample)
            else:
                perturb = [[0, 1] for k in np.arange(len(self.partitioner.sets))]

            affected_sets = self._affected_sets(sample, perturb)

            numerator = []
            denominator = []

            if len(affected_sets) == 1:
                ix = affected_sets[0][0]
                aset = self.partitioner.ordered_sets[ix]
                if aset in self.flrgs:
                    numerator.append(self.flrgs[aset].get_midpoint(self.sets, perturb[ix]))
                else:
                    fuzzy_set = self.sets[aset]
                    numerator.append(fuzzy_set.get_midpoint(perturb[ix]))
                denominator.append(1)
            else:
                for aset in affected_sets:
                    ix = aset[0]
                    fs = self.partitioner.ordered_sets[ix]
                    tdisp = perturb[ix]
                    if fs in self.flrgs:
                        numerator.append(self.flrgs[fs].get_midpoint(self.sets, tdisp) * aset[1])
                    else:
                        fuzzy_set = self.sets[fs]
                        numerator.append(fuzzy_set.get_midpoint(tdisp) * aset[1])
                    denominator.append(aset[1])

            if sum(denominator) > 0:
                pto = sum(numerator) /sum(denominator)
            else:
                pto = sum(numerator)

            ret.append(pto)

                self.forecasts.append(pto)
                self.residuals.append(self.inputs[-1] - self.forecasts[-1])
                self.inputs.append(sample)

                self.inputs.pop(0)
                self.forecasts.pop(0)
                self.residuals.pop(0)

        return ret

        ret = []

            perturb = self.perturbation_factors(sample)

            upper = []
            lower = []

                    lower.append(self.flrgs[aset].get_lower(perturb[ix]))
                    upper.append(self.flrgs[aset].get_upper(perturb[ix]))
                else:
                    lower.append(fuzzy_set.get_lower(perturb[ix]))
                    upper.append(fuzzy_set.get_upper(perturb[ix]))
            else:
                        lower.append(self.flrgs[fs].get_lower(tdisp) * aset[1])
                        upper.append(self.flrgs[fs].get_upper(tdisp) * aset[1])
                    else:
                        lower.append(fuzzy_set.get_lower(tdisp) * aset[1])
                        upper.append(fuzzy_set.get_upper(tdisp) * aset[1])

            itvl = [sum(lower), sum(upper)]

            ret.append(itvl)

        return ret

from pyFTS.common import flrg
from pyFTS.models.nonstationary import common

class NonStationaryFLRG(flrg.FLRG):
    def unpack_args(self, *args):
        l = len(args)
        tmp = args
        sets, t, w = None, 0, 1
        if l > 0 and isinstance(tmp[0], dict):
            sets = tmp[0]
        if l > 1 and isinstance(tmp[1], (int, list, tuple)):
            t = tmp[1]
        if l > 2 and isinstance(tmp[2], int):
            w = tmp[2]

        return (sets, t, w)

        super(NonStationaryFLRG, self).__init__(1, **kwargs)

        if isinstance(self.LHS, list):
            return str([k for k in self.LHS])
        elif isinstance(self.LHS, dict):
            return str(self.LHS.keys())
        else:
            return self.LHS

    def get_membership(self, data, *args):
        sets, t, window_size = self.unpack_args(*args)
        ret = 0.0
            ret = min([sets[self.LHS[ct]].membership(dat, common.window_index(t - (self.order - ct), window_size))
                       for ct, dat in enumerate(data)])
        else:
            ret = self.LHS.membership(data, common.window_index(t, window_size))
        return ret

    def get_midpoint(self, *args):
        if len(self.RHS) > 0:
            if isinstance(self.RHS, (list, set)):
                tmp = [sets[r].get_midpoint(common.window_index(t, window_size)) for r in self.RHS]
                tmp = [sets[r].get_midpoint(common.window_index(t, window_size)) for r in self.RHS.keys()]
            return sum(tmp) / len(tmp)
        else:
            return sets[self.LHS[-1]].get_midpoint(common.window_index(t, window_size))

    def get_lower(self, *args):
                return min([sets[r].get_lower(common.window_index(t, window_size)) for r in self.RHS])
                return min([sets[r].get_lower(common.window_index(t, window_size)) for r in self.RHS.keys()])
        else:
            return sets[self.LHS[-1]].get_lower(common.window_index(t, window_size))

    def get_upper(self, *args):
                return max([sets[r].get_upper(common.window_index(t, window_size)) for r in self.RHS])
                return max([sets[r].get_upper(common.window_index(t, window_size)) for r in self.RHS.keys()])
        else:
            return sets[self.LHS[-1]].get_upper(common.window_index(t, window_size))

from pyFTS.models.nonstationary import common, flrg

class HighOrderNonStationaryFLRG(flrg.NonStationaryFLRG):
    """First Order NonStationary Fuzzy Logical Relationship Group"""
        super(HighOrderNonStationaryFLRG, self).__init__(order, **kwargs)

        if c.name not in self.RHS:
            self.RHS[c.name] = c

        tmp = ""
            tmp = tmp + c

class HighOrderNonStationaryFTS(hofts.HighOrderFTS):
    """NonStationaryFTS Fuzzy Time Series"""
        super(HighOrderNonStationaryFTS, self).__init__("HONSFTS " + name, **kwargs)
        self.name = "High Order Non Stationary FTS"

    def generate_flrg(self, data, **kwargs):
        window_size = kwargs.get("window_size", 1)
        for k in np.arange(self.order, l):

            sample = data[k - self.order: k]

            disp = common.window_index(k, window_size)

            rhs = [self.sets[key] for key in self.partitioner.ordered_sets
                   if self.sets[key].membership(data[k], disp) > 0.0]

            if len(rhs) == 0:
                rhs = [common.check_bounds(data[k], self.partitioner, disp)]

            lags = {}

            for o in np.arange(0, self.order):
                tdisp = common.window_index(k - (self.order - o), window_size)
                lhs = [self.sets[key] for key in self.partitioner.ordered_sets
                   if self.sets[key].membership(sample[o], tdisp) > 0.0]

                if len(lhs) == 0:
                    lhs = [common.check_bounds(sample[o], self.partitioner, tdisp)]

                lags[o] = lhs

            # Trace the possible paths
                flrg = HighOrderNonStationaryFLRG(self.order)

                for c, e in enumerate(path, start=0):
                    flrg.append_lhs(e)

        # flrgs = sorted(flrgs, key=lambda flrg: flrg.get_midpoint(0, window_size=1))

        if kwargs.get('order', None) is not None:
            self.order = kwargs.get('order', 1)

        if kwargs.get('sets', None) is not None:
            self.sets = kwargs.get('sets', None)

        window_size = kwargs.get('parameters', 1)
        self.generate_flrg(data, window_size=window_size)

    def _affected_flrgs(self, sample, k, time_displacement, window_size):
        # print("input: " + str(ndata[k]))

        affected_flrgs = []
        affected_flrgs_memberships = []

        lags = {}

        for ct, dat in enumerate(sample):
            tdisp = common.window_index((k + time_displacement) - (self.order - ct), window_size)

            sel = [ct for ct, key in enumerate(self.partitioner.ordered_sets)
                   if self.sets[key].membership(dat, tdisp) > 0.0]

            if len(sel) == 0:
                sel.append(common.check_bounds_index(dat, self.partitioner, tdisp))

            lags[ct] = sel

        # Build the tree with all possible paths

        # Trace the possible paths and build the PFLRG's

            flrg = HighOrderNonStationaryFLRG(self.order)

            for kk in path:
                flrg.append_lhs(self.sets[self.partitioner.ordered_sets[kk]])

            affected_flrgs.append(flrg)
            # affected_flrgs_memberships.append_rhs(flrg.get_membership(sample, disp))

            #                print(flrg.get_key())

            # the FLRG is here because of the bounds verification
            mv = []
            for ct, dat in enumerate(sample):
                td = common.window_index((k + time_displacement) - (self.order - ct), window_size)
                tmp = flrg.LHS[ct].membership(dat, td)

                mv.append(tmp)
            # print(mv)

            affected_flrgs_memberships.append(np.prod(mv))

        return [affected_flrgs, affected_flrgs_memberships]

        time_displacement = kwargs.get("time_displacement",0)

        ret = []

            affected_flrgs, affected_flrgs_memberships = self._affected_flrgs(sample, k,
                                                                              time_displacement, window_size)

            #print([str(k) for k in affected_flrgs])
            #print(affected_flrgs_memberships)

            tmp = []
            tdisp = common.window_index(k + time_displacement, window_size)
            if len(affected_flrgs) == 0:
                tmp.append(common.check_bounds(sample[-1], self.sets, tdisp))
            elif len(affected_flrgs) == 1:
                flrg = affected_flrgs[0]
                if flrg.get_key() in self.flrgs:
                    tmp.append(self.flrgs[flrg.get_key()].get_midpoint(tdisp))
                else:
                    tmp.append(flrg.LHS[-1].get_midpoint(tdisp))
            else:
                for ct, aset in enumerate(affected_flrgs):
                    if aset.get_key() in self.flrgs:
                        tmp.append(self.flrgs[aset.get_key()].get_midpoint(tdisp) *
                                   affected_flrgs_memberships[ct])
                    else:
                        tmp.append(aset.LHS[-1].get_midpoint(tdisp)*
            pto = sum(tmp)

            #print(pto)

        return ret

        time_displacement = kwargs.get("time_displacement", 0)

        ret = []

            # print([str(k) for k in affected_flrgs])
            # print(affected_flrgs_memberships)

            upper = []
            lower = []

                aset = common.check_bounds(sample[-1], self.sets, tdisp)
                lower.append(aset.get_lower(tdisp))
                upper.append(aset.get_upper(tdisp))
                _flrg = affected_flrgs[0]
                if _flrg.get_key() in self.flrgs:
                    lower.append(self.flrgs[_flrg.get_key()].get_lower(tdisp))
                    upper.append(self.flrgs[_flrg.get_key()].get_upper(tdisp))
                else:
                    lower.append(_flrg.LHS[-1].get_lower(tdisp))
                    upper.append(_flrg.LHS[-1].get_upper(tdisp))
            else:
                        lower.append(self.flrgs[aset.get_key()].get_lower(tdisp) *
                                     affected_flrgs_memberships[ct])
                        upper.append(self.flrgs[aset.get_key()].get_upper(tdisp) *
                    else:
                        lower.append(aset.LHS[-1].get_lower(tdisp) *
                        upper.append(aset.LHS[-1].get_upper(tdisp) *

            ret.append([sum(lower), sum(upper)])

        return ret

from pyFTS.common import FLR, fts

class ConventionalNonStationaryFLRG(flrg.NonStationaryFLRG):

        super(ConventionalNonStationaryFLRG, self).__init__(1, **kwargs)

        return self.LHS

        tmp2 = ""
            tmp2 = tmp2 + c

class NonStationaryFTS(fts.FTS):
        super(NonStationaryFTS, self).__init__(**kwargs)
        self.name = "Non Stationary FTS"
        self.shortname = "NSFTS"
        self.method = kwargs.get('method','conditional')

        if self.method == 'conditional':
            self.min_stack = [0, 0, 0]
            self.max_stack = [0, 0, 0]
            self.uod_clip = False
            self.order = 1
            self.min_order = 1
            self.max_lag = 1
            self.inputs = []
            self.forecasts = []
            self.residuals = []
            self.variance_residual = 0.
            self.mean_residual = 0.
            self.memory_window = kwargs.get("memory_window", 5)

            else:
                self.flrgs[flr.LHS.name] = ConventionalNonStationaryFLRG(flr.LHS.name)

        if self.method == 'unconditional':
            window_size = kwargs.get('parameters', 1)
            tmpdata = common.fuzzySeries(data, self.sets,
                                         self.partitioner.ordered_sets,
                                         window_size, method='fuzzy')
        else:
                                 self.partitioner.ordered_sets,
                                 method='fuzzy', const_t=0)

            self.forecasts = self.forecast(data, no_update=True)
            self.residuals = np.array(data[1:]) - np.array(self.forecasts[:-1])

            self.variance_residual = np.var(self.residuals)  # np.max(self.residuals
            self.mean_residual = np.mean(self.residuals)

            self.residuals = self.residuals[-self.memory_window:].tolist()
            self.forecasts = self.forecasts[-self.memory_window:]
            self.inputs = np.array(data[-self.memory_window:]).tolist()

    def conditional_perturbation_factors(self, data, **kwargs):
        _max = 0
        _min = 0

            affected_sets = [[ct, self.sets[self._fsset_key(ct)].membership(sample, perturb[ct])]
                             for ct in np.arange(len(self.partitioner.sets))
                             if self.sets[self._fsset_key(ct)].membership(sample, perturb[ct]) > 0.0]
            if len(affected_sets) == 0:

                if sample < self.partitioner.lower_set().get_lower(perturb[0]):
                    affected_sets.append([0, 1])
                elif sample > self.partitioner.upper_set().get_upper(perturb[-1]):
                    affected_sets.append([len(self.sets) - 1, 1])

        else:
            affected_sets = [[ct, self.sets[self._fsset_key(ct)].membership(sample, perturb)]
                             if self.sets[self._fsset_key(ct)].membership(sample, perturb) > 0.0]

                if sample < self.partitioner.lower_set().get_lower(perturb):
                elif sample > self.partitioner.upper_set().get_upper(perturb):

        no_update = kwargs.get("no_update", False)

        ret = []

            if self.method == 'unconditional':
                perturb = common.window_index(k + time_displacement, window_size)
            elif self.method == 'conditional':
                if not no_update:
                    perturb = self.conditional_perturbation_factors(sample)
                else:
                    perturb = [[0, 1] for k in np.arange(len(self.partitioner.sets))]

                else:
            else:
                    else:

                pto = sum(numerator) / sum(denominator)
            else:

            if self.method == 'conditional' and not no_update:

        return ret

        ret = []

            # print("input: " + str(ndata[k]))

            affected_sets = [[self.sets[key], self.sets[key].membership(ndata[k], tdisp)]
                             for key in self.partitioner.ordered_sets
                             if self.sets[key].membership(ndata[k], tdisp) > 0.0]

                affected_sets.append([common.check_bounds(ndata[k], self.partitioner, tdisp), 1.0])

            upper = []
            lower = []

                aset = affected_sets[0][0]
                if aset.name in self.flrgs:
                    lower.append(self.flrgs[aset.name].get_lower(tdisp))
                    upper.append(self.flrgs[aset.name].get_upper(tdisp))
                else:
                    lower.append(aset.get_lower(tdisp))
                    upper.append(aset.get_upper(tdisp))
            else:
                    if aset[0].name in self.flrgs:
                        lower.append(self.flrgs[aset[0].name].get_lower(tdisp) * aset[1])
                        upper.append(self.flrgs[aset[0].name].get_upper(tdisp) * aset[1])
                    else:
                        lower.append(aset[0].get_lower(tdisp) * aset[1])
                        upper.append(aset[0].get_upper(tdisp) * aset[1])

        return ret

from pyFTS.models.nonstationary import common, perturbation
from pyFTS.common import FuzzySet as stationary_fs

class PolynomialNonStationaryPartitioner(partitioner.Partitioner):
    """
    Non Stationary Universe of Discourse Partitioner
    """

    def __init__(self, data, part, **kwargs):
        """"""
        super(PolynomialNonStationaryPartitioner, self).__init__(name=part.name, data=data, npart=part.partitions,
                                                                 func=part.membership_function, names=part.setnames,
                                                                 prefix=part.prefix, transformation=part.transformation,
                                                                 indexer=part.indexer, preprocess=False)

        loc_params, wid_params = self.get_polynomial_perturbations(data, **kwargs)

        if self.ordered_sets is None and self.setnames is not None:
            self.ordered_sets = part.setnames
        else:
            self.ordered_sets = stationary_fs.set_ordered(part.sets)

        for ct, key in enumerate(self.ordered_sets):
            set = part.sets[key]
            loc_roots = np.roots(loc_params[ct])[0]
            wid_roots = np.roots(wid_params[ct])[0]
            tmp = common.FuzzySet(set.name, set.mf, set.parameters,
                           location=perturbation.polynomial,
                           location_params=loc_params[ct],
                           location_roots=loc_roots, #**kwargs)
                           width=perturbation.polynomial,
                           width_params=wid_params[ct],
                           width_roots=wid_roots, **kwargs)

            self.sets[set.name] = tmp

    def poly_width(self, par1, par2, rng, deg):
        a = np.polyval(par1, rng)
        b = np.polyval(par2, rng)
        diff = [b[k] - a[k] for k in rng]
        tmp = np.polyfit(rng, diff, deg=deg)
        return tmp

    def scale_up(self,x,pct):
        if x > 0: return x*(1+pct)
        else: return x*pct

    def scale_down(self,x,pct):
        if x > 0: return x*pct
        else: return x*(1+pct)

    def get_polynomial_perturbations(self, data, **kwargs):
        w = kwargs.get("window_size", int(len(data) / 5))
        degree = kwargs.get("degree", 2)
        xmax = [data[0]]
        tmax = [0]
        xmin = [data[0]]
        tmin = [0]

        for i in np.arange(0, l, w):
            sample = data[i:i + w]
            tx = max(sample)
            xmax.append(tx)
            tmax.append(np.ravel(np.argwhere(data == tx)).tolist()[0])
            tn = min(sample)
            xmin.append(tn)
            tmin.append(np.ravel(np.argwhere(data == tn)).tolist()[0])

        cmax = np.polyfit(tmax, xmax, deg=degree)
        cmin = np.polyfit(tmin, xmin, deg=degree)

        cmed = []

        for d in np.arange(0, degree + 1):
            cmed.append(np.linspace(cmin[d], cmax[d], self.partitions)[1:self.partitions - 1])

        loc_params = [cmin.tolist()]
        for i in np.arange(0, self.partitions - 2):
            tmp = [cmed[k][i] for k in np.arange(0, degree + 1)]
            loc_params.append(tmp)
        loc_params.append(cmax.tolist())

        rng = np.arange(0, l)

        clen = []

        for i in np.arange(1, self.partitions-1):
            tmp = self.poly_width(loc_params[i - 1], loc_params[i + 1], rng, degree)
            clen.append(tmp)

        tmp = self.poly_width(loc_params[0], loc_params[1], rng, degree)
        clen.insert(0, tmp)

        tmp = self.poly_width(loc_params[self.partitions-2], loc_params[self.partitions-1], rng, degree)
        clen.append(tmp)

        tmp = (loc_params, clen)

        return tmp

    def build(self, data):
        pass

class SimpleNonStationaryPartitioner(partitioner.Partitioner):
    """
    """

        """"""
        super(SimpleNonStationaryPartitioner, self).__init__(name=part.name, data=data, npart=part.partitions,
                                                             func=part.membership_function, names=part.setnames,
                                                             prefix=part.prefix, transformation=part.transformation,
                                                             indexer=part.indexer)#, preprocess=False)

        for key in part.sets.keys():
            tmp = common.FuzzySet(set.name, set.mf, set.parameters, **kwargs)
            tmp.centroid = set.centroid

            self.sets[key] =tmp

        self.ordered_sets = stationary_fs.set_ordered(self.sets)

        return {}

def simplenonstationary_gridpartitioner_builder(data, npart, transformation):
    from pyFTS.partitioners import Grid
    from pyFTS.models.nonstationary import perturbation, partitioners

    tmp_fs = Grid.GridPartitioner(data=data, npart=npart, transformation=transformation)
    fs = partitioners.SimpleNonStationaryPartitioner(data, tmp_fs,
                                                     location=perturbation.polynomial,
                                                     location_params=[1, 0],
                                                     location_roots=0,
                                                     width=perturbation.polynomial,
                                                     width_params=[1, 0],
                                                     width_roots=0)
    return fs

"""
Pertubation functions for Non Stationary Fuzzy Sets
"""

from pyFTS.common import FuzzySet, Membership

def linear(x, parameters):
    return np.polyval(parameters, x)

def polynomial(x, parameters):
        return np.polyval(parameters, x)

def exponential(x, parameters):
    return np.exp(x*parameters[0])

def periodic(x, parameters):
    return parameters[0] * np.sin(x * parameters[1])

from pyFTS.common import Membership, Util

def plot_sets(partitioner, start=0, end=10, step=1, tam=[5, 5], colors=None,
              save=False, file=None, axes=None, data=None, window_size = 1, only_lines=False):

    range = np.arange(start,end,step)
    ticks = []
    if axes is None:
        fig, axes = plt.subplots(nrows=1, ncols=1, figsize=tam)

    for ct, key in enumerate(partitioner.ordered_sets):
        fset = partitioner.sets[key]
        if not only_lines:
            for t in range:
                tdisp = t - (t % window_size)
                fset.membership(0, tdisp)
                param = fset.perturbated_parameters[str(tdisp)]

                if fset.mf == Membership.trimf:
                    if t == start:
                        line = axes.plot([t, t+1, t], param, label=fset.name)
                        fset.metadata['color'] = line[0].get_color()
                    else:
                        axes.plot([t, t + 1, t], param,c=fset.metadata['color'])

                ticks.extend(["t+"+str(t),""])
        else:
            tmp = []
                tmp.append(np.polyval(param, tdisp))
            axes.plot(range, tmp, ls="--", c="blue")

    axes.set_ylabel("Universe of Discourse")
    axes.set_xlabel("Time")
    plt.xticks([k for k in range], ticks, rotation='vertical')

    handles0, labels0 = axes.get_legend_handles_labels()
    lgd = axes.legend(handles0, labels0, loc=2, bbox_to_anchor=(1, 1))

    if data is not None:
        axes.plot(np.arange(start, start + len(data), 1), data,c="black")

def plot_sets_conditional(model, data, step=1, size=[5, 5], colors=None,
                          save=False, file=None, axes=None):
    range = np.arange(0, len(data), step)
    ticks = []
        fig, axes = plt.subplots(nrows=1, ncols=1, figsize=size)

    for t in range:
        model.forecast([data[t]])
        perturb = model.conditional_perturbation_factors(data[t])

        for ct, key in enumerate(model.partitioner.ordered_sets):
            set = model.partitioner.sets[key]
            set.perturbate_parameters(perturb[ct])
            param = set.perturbated_parameters[str(perturb[ct])]

            if set.mf == Membership.trimf:
                if t == 0:
                    line = axes.plot([t, t+1, t], param, label=set.name)
                    set.metadata['color'] = line[0].get_color()
                else:
                    axes.plot([t, t + 1, t], param,c=set.metadata['color'])

            ticks.extend(["t+"+str(t),""])

        axes.plot(np.arange(0, len(data), 1), data,c="black")

from pyFTS.models.seasonal import common

class SeasonalIndexer(object):
    """
    Seasonal Indexer. Responsible to find the seasonal index of a data point inside its data set
    """
    def __init__(self,num_seasons, **kwargs):
        self.num_seasons = num_seasons
        self.name = kwargs.get("name","")

    def get_season_of_data(self,data):
        pass

    def get_season_by_index(self,inde):
        pass

    def get_data_by_season(self, data, indexes):
        pass

    def get_index_by_season(self, indexes):
        pass

    def get_data(self, data):
        pass

    def get_index(self, data):
        pass

class LinearSeasonalIndexer(SeasonalIndexer):
    """Use the data array/list position to index the seasonality """
    def __init__(self, seasons, units, ignore=None, **kwargs):
        """
        Indexer for array/list position
        :param seasons: A list with the season group (i.e: 7 for week, 30 for month, etc)
        :param units: A list with the units used for each season group, the default is 1 for each
        :param ignore:
        """
        super(LinearSeasonalIndexer, self).__init__(len(seasons), **kwargs)
        self.seasons = seasons
        self.units = units
        self.ignore = ignore

        return self.get_season_by_index(np.arange(0, len(data)).tolist())

    def get_season_by_index(self, index):
        ret = []
        if not isinstance(index, (list, np.ndarray)):
            if self.num_seasons == 1:
                season = (index // self.units[0]) % self.seasons[0]
            else:
                season = []
                for ct, seasonality in enumerate(self.seasons, start=0):
                    tmp = (index // self.units[ct]) % self.seasons[ct]
                    if not self.ignore[ct]:
                        season.append(tmp)
            ret.append(season)
        else:
            for ix in index:
                if self.num_seasons == 1:
                    season = (ix  // self.units[0]) % self.seasons[0]
                else:
                    season = []
                    for ct, seasonality in enumerate(self.seasons, start=0):
                        tmp = (ix  // self.units[ct]) % self.seasons[ct]
                        if not self.ignore[ct]:
                            season.append(tmp)
                ret.append(season)

        return ret

        ix = 0;

        for count,season in enumerate(self.seasons):
            ix += season*(indexes[count])

        #ix += indexes[-1]

        return ix

class DataFrameSeasonalIndexer(SeasonalIndexer):
    """Use the Pandas.DataFrame index position to index the seasonality """
    def __init__(self,index_fields,index_seasons, data_field,**kwargs):
        """

        :param index_fields: DataFrame field to use as index
        :param index_seasons: A list with the season group, i. e., multiples of positions that are considered a season (i.e: 7 for week, 30 for month, etc)
        :param data_fields: DataFrame field to use as data
        """
        super(DataFrameSeasonalIndexer, self).__init__(len(index_seasons), **kwargs)
        self.fields = index_fields
        self.seasons = index_seasons
        self.data_field = data_field

        #data = data.copy()
        ret = []
        for ix in data.index:
            season = []
            for c, f in enumerate(self.fields, start=0):
                if self.seasons[c] is None:
                    season.append(data[f][ix])
                else:
                    a = data[f][ix]
                    season.append(a // self.seasons[c])
        return ret

    def get_season_by_index(self,index):
        raise Exception("Operation not available!")

        for season in indexes:
                    data = data[data[f]== season[c]]
                else:
                    data = data[(data[f] // self.seasons[c]) == season[c]]
        return data[self.data_field]

        return data[self.data_field].tolist()

    def set_data(self, data, value):
        data.loc[:,self.data_field] = value

class DateTimeSeasonalIndexer(SeasonalIndexer):
    """Use a Pandas.DataFrame date field to index the seasonality """
    def __init__(self,date_field, index_fields, index_seasons, data_field,**kwargs):
        """

        :param date_field: DataFrame field that contains the datetime field used on index
        :param index_fields: List with commom.DataTime fields
        :param index_seasons: Multiples of index_fields, the default is 1
        :param data_field: DataFrame field with the time series data
        """
        super(DateTimeSeasonalIndexer, self).__init__(len(index_seasons), **kwargs)
        self.date_field = date_field

    def get_season_of_data(self, data):

        ret = []

        if isinstance(data, pd.DataFrame):
            for ix in data.index:
                date = data[self.date_field][ix]
                season = []
                for c, f in enumerate(self.fields, start=0):
                    tmp = common.strip_datepart(date, f)
                    if self.seasons[c] is not None:
                        tmp = tmp // self.seasons[c]
                    season.append(tmp)

        elif isinstance(data, pd.Series):
            date = data[self.date_field]
            season = []
                season.append(common.strip_datepart(date, f, self.seasons[c]))

        return ret

        return data[self.date_field].tolist() if isinstance(data, pd.DataFrame) else data[self.date_field]

from pyFTS.common import FuzzySet, FLR
from pyFTS.models.seasonal import sfts

class ContextualSeasonalFLRG(sfts.SeasonalFLRG):
    """
    Contextual Seasonal Fuzzy Logical Relationship Group
    """
    def __init__(self, seasonality):
        super(ContextualSeasonalFLRG, self).__init__(seasonality)

    def append_rhs(self, flr, **kwargs):
        if flr.LHS in self.RHS:
            self.RHS[flr.LHS].append_rhs(flr.RHS)
        else:
            self.RHS[flr.LHS] = chen.ConventionalFLRG(flr.LHS)

        tmp = str(self.LHS) + ": \n "
        tmp2 = "\t"
        for r in sorted(self.RHS):
            tmp2 += str(self.RHS[r]) + "\n\t"
        return tmp + tmp2 + "\n"

class ContextualMultiSeasonalFTS(sfts.SeasonalFTS):
    """
    Contextual Multi-Seasonal Fuzzy Time Series
    """
        super(ContextualMultiSeasonalFTS, self).__init__(**kwargs)
        self.name = "Contextual Multi Seasonal FTS"
        self.shortname = "CMSFTS "
        self.seasonality = 1

            if str(flr.index) not in self.flrgs:
                self.flrgs[str(flr.index)] = ContextualSeasonalFLRG(flr.index)

            self.flrgs[str(flr.index)].append_rhs(flr)

    def train(self, data,  **kwargs):
        if kwargs.get('parameters', None) is not None:
            self.seasonality = kwargs.get('parameters', None)
        flrs = FLR.generate_indexed_flrs(self.sets, self.indexer, data,
                                         transformation=self.partitioner.transformation,
                                         alpha_cut=self.alpha_cut)

    def get_midpoints(self, flrg, data):
        ret = []
        for d in data:
            if d in flrg.RHS:
                ret.extend([self.sets[s].centroid for s in flrg.RHS[d].RHS])
            else:
                ret.extend([self.sets[d].centroid])

        return np.array(ret)

        ordered_sets = FuzzySet.set_ordered(self.sets)

        ret = []

        index = self.indexer.get_season_of_data(data)
        ndata = self.indexer.get_data(data)

        for k in np.arange(0, len(data)):
            
            if str(index[k]) in self.flrgs:

                flrg = self.flrgs[str(index[k])]

                d = FuzzySet.get_fuzzysets(ndata[k], self.sets, ordered_sets, alpha_cut=self.alpha_cut)

                mp = self.get_midpoints(flrg, d)

                ret.append(sum(mp) / len(mp))
            else:
                ret.append(np.nan)

        return ret

        ret = []
        for i in steps:
            flrg = self.flrgs[str(i)]

            mp = self.get_midpoints(flrg)

            ret.append(sum(mp) / len(mp))

        return ret

from enum import Enum
from pyFTS.partitioners import partitioner, Grid

class DateTime(Enum):
    year = 1
    month = 12
    day_of_month = 30
    day_of_year = 364
    day_of_week = 7
    hour = 6
    minute = 7
    second = 8
    hour_of_day = 24
    hour_of_week = 168
    hour_of_month = 744
    hour_of_year = 8736
    minute_of_hour = 60
    minute_of_day = 1440
    minute_of_week = 10080
    minute_of_month = 44640
    minute_of_year = 524160
    second_of_minute = 60.00001
    second_of_hour = 3600
    second_of_day = 86400

def strip_datepart(date, date_part):
    if date_part == DateTime.year:
        tmp = date.year
    elif date_part == DateTime.month:
        tmp = date.month
    elif date_part == DateTime.day_of_year:
        tmp = date.timetuple().tm_yday
    elif date_part == DateTime.day_of_month:
        tmp = date.day
    elif date_part == DateTime.day_of_week:
        tmp = date.weekday()
    elif date_part == DateTime.hour or date_part == DateTime.hour_of_day:
        tmp = date.hour
    elif date_part == DateTime.hour_of_week:
        wk = (date.weekday()-1) * 24
        tmp = date.hour + wk
    elif date_part == DateTime.hour_of_month:
        wk = (date.day-1) * 24
    elif date_part == DateTime.hour_of_year:
        wk = (date.timetuple().tm_yday-1) * 24
    elif date_part == DateTime.minute or date_part == DateTime.minute_of_hour:
        tmp = date.minute
    elif date_part == DateTime.minute_of_day:
        wk = date.hour * 60
        tmp = date.minute + wk
    elif date_part == DateTime.minute_of_week:
        wk1 = (date.weekday()-1) * 1440 #24 * 60
        wk2 = date.hour * 60
        tmp = date.minute + wk1 + wk2
    elif date_part == DateTime.minute_of_month:
        wk1 = (date.day - 1) * 1440 #24 * 60
    elif date_part == DateTime.minute_of_year:
        wk1 = (date.timetuple().tm_yday - 1) * 1440 #24 * 60
    elif date_part == DateTime.second or date_part == DateTime.second_of_minute:
        tmp = date.second
    elif date_part == DateTime.second_of_hour:
        wk1 = date.minute * 60
        tmp = date.second + wk1
    elif date_part == DateTime.second_of_day:
        wk1 = date.hour * 3600 #60 * 60
        wk2 = date.minute * 60
        tmp = date.second + wk1 + wk2
    else:
        raise Exception("Unknown DateTime value!")

    return tmp

    """
    Temporal/Seasonal Fuzzy Set
    """

    def __init__(self, datepart, name, mf, parameters, centroid, alpha=1.0, **kwargs):
        super(FuzzySet, self).__init__(name, mf, parameters, centroid, alpha, type = 'datetime', **kwargs)
        self.datepart = datepart
        self.type = 'seasonal'

        dp = strip_datepart(x, self.datepart)
        return self.mf(dp, self.parameters) * self.alpha

from pyFTS.common import FLR

class MultiSeasonalFTS(sfts.SeasonalFTS):
    """
    Multi-Seasonal Fuzzy Time Series
    """
    def __init__(self, name, indexer, **kwargs):
        super(MultiSeasonalFTS, self).__init__("MSFTS")
        self.name = "Multi Seasonal FTS"
        self.shortname = "MSFTS " + name
        self.indexer = indexer

                self.flrgs[str(flr.index)] = sfts.SeasonalFLRG(flr.index)

            self.flrgs[str(flr.index)].append_rhs(flr.RHS)

        #ndata = self.indexer.set_data(data,self.doTransformations(self.indexer.get_data(data)))
        flrs = FLR.generate_indexed_flrs(self.sets, self.indexer, data)

        ret = []

        for k in np.arange(0, len(index)):

            flrg = self.flrgs[str(index[k])]

            mp = self.getMidpoints(flrg)

        return ret

        ret = []

        return ret

from pyFTS.common import Membership, FuzzySet as FS
from pyFTS.common.Composite import FuzzySet as Composite
from pyFTS.models.seasonal.common import DateTime, FuzzySet, strip_datepart
import matplotlib.pylab as plt

class TimeGridPartitioner(partitioner.Partitioner):
    """Even Length DateTime Grid Partitioner"""

        """
        Even Length Grid Partitioner
        :param seasonality: Time granularity, from pyFTS.models.seasonal.common.DateTime
        :param data: Training data of which the universe of discourse will be extracted. The universe of discourse is the open interval between the minimum and maximum values of the training data.
        :param npart: The number of universe of discourse partitions, i.e., the number of fuzzy sets that will be created
        :param func: Fuzzy membership function (pyFTS.common.Membership)
        """
        super(TimeGridPartitioner, self).__init__(name="TimeGrid", preprocess=False, **kwargs)

        self.season = kwargs.get('seasonality', DateTime.day_of_year)
        if self.season == DateTime.year:
            ndata = [strip_datepart(k, self.season) for k in data]
            self.min = min(ndata)
            self.max = max(ndata)
        else:
            tmp = (self.season.value / self.partitions) / 2
            self.min = tmp
            self.max = self.season.value + tmp

        self.sets = self.build(None)

            self.ordered_sets = self.setnames
        else:
            self.ordered_sets = FS.set_ordered(self.sets)

        sets = {}

        kwargs = {'variable': self.variable}

            dlen = (self.max - self.min)
            partlen = dlen / self.partitions
        else:
            partlen = self.season.value / self.partitions
            pl2 = partlen / 2

        count = 0
        for c in np.arange(self.min, self.max, partlen):
            set_name = self.get_name(count)
            if self.membership_function == Membership.trimf:
                if c == self.min:
                    tmp = Composite(set_name, superset=True)
                    tmp.append_set(FuzzySet(self.season, set_name, Membership.trimf,
                                            [self.season.value - pl2, self.season.value,
                                             self.season.value + 0.0000001], self.season.value, alpha=.5,
                                            **kwargs))
                                            [c - partlen, c, c + partlen], c,
                    tmp.centroid = c
                    sets[set_name] = tmp
                else:
                    sets[set_name] = FuzzySet(self.season, set_name, Membership.trimf,
                                         [c - partlen, c, c + partlen], c,
                                         **kwargs)
            elif self.membership_function == Membership.gaussmf:
                sets[set_name] = FuzzySet(self.season, set_name, Membership.gaussmf, [c, partlen / 3], c,
                                     **kwargs)
            elif self.membership_function == Membership.trapmf:
                q = partlen / 4
                                             self.season.value + 0.0000001], 0,
                    tmp.append_set(FuzzySet(self.season, set_name, Membership.trapmf,
                                            [c - partlen, c - q, c + q, c + partlen], c,
                else:
                    sets[set_name] = FuzzySet(self.season, set_name, Membership.trapmf,
                                         [c - partlen, c - q, c + q, c + partlen], c,
                                         **kwargs)
            count += 1

        self.min = 0

        return sets

    def plot(self, ax):
        """
        Plot the
        :param ax:
        :return:
        """
        ax.set_title(self.name)
        ax.set_ylim([0, 1])
        ax.set_xlim([0, self.season.value])
        ticks = []
        x = []
        for key in self.sets.keys():
            s = self.sets[key]
            if s.type == 'composite':
                for ss in s.sets:
                    self.plot_set(ax, ss)
            else:
                self.plot_set(ax, s)
            ticks.append(str(round(s.centroid, 0)) + '\n' + s.name)
            x.append(s.centroid)
        ax.xaxis.set_ticklabels(ticks)
        ax.xaxis.set_ticks(x)

"""
Simple First Order Seasonal Fuzzy Time Series implementation of Song (1999) based of Conventional FTS by Chen (1996)

Q. Song, “Seasonal forecasting in fuzzy time series,” Fuzzy sets Syst., vol. 107, pp. 235–236, 1999.

"""

from pyFTS.common import FuzzySet, FLR, flrg, fts

class SeasonalFLRG(flrg.FLRG):
    """First Order Seasonal Fuzzy Logical Relationship Group"""
        super(SeasonalFLRG, self).__init__(1)
        self.LHS = seasonality

        tmp2 = ""
        for c in sorted(self.RHS, key=lambda s: str(s)):

class SeasonalFTS(fts.FTS):
    """First Order Seasonal Fuzzy Time Series"""
        super(SeasonalFTS, self).__init__(**kwargs)
        self.name = "Seasonal FTS"
        self.shortname = "SFTS"

        for ct, flr in enumerate(flrs, start=1):

            season = self.indexer.get_season_by_index(ct)[0]

            ss = str(season)

            if ss not in self.flrgs:
                self.flrgs[ss] = SeasonalFLRG(season)

            #print(season)
            self.flrgs[ss].append_rhs(flr.RHS)

    def get_midpoints(self, flrg):
        ret = np.array([self.sets[s].centroid for s in flrg.RHS])
        return ret

        tmpdata = FuzzySet.fuzzyfy_series_old(data, self.sets)

        ret = []

            season = self.indexer.get_season_by_index(k)[0]

            flrg = self.flrgs[str(season)]

            ret.append(np.percentile(mp, 50))

        return ret

class Partitioner(object):
    """
    Universe of Discourse partitioner. Split data on several fuzzy sets
    """

        """
        Universe of Discourse partitioner scheme. Split data on several fuzzy sets
        """
        """partitioner name"""
        self.partitions = kwargs.get('npart', 10)
        """The number of universe of discourse partitions, i.e., the number of fuzzy sets that will be created"""
        self.membership_function = kwargs.get('func', Membership.trimf)
        """Fuzzy membership function (pyFTS.common.Membership)"""
        self.setnames = kwargs.get('names', None)
        """list of partitions names. If None is given the partitions will be auto named with prefix"""
        self.prefix = kwargs.get('prefix', 'A')
        """prefix of auto generated partition names"""
        """data transformation to be applied on data"""
        self.indexer = kwargs.get('indexer', None)
        self.variable = kwargs.get('variable', None)
        self.ordered_sets = None

        if kwargs.get('preprocess',True):

            data = kwargs.get('data',[None])

            if self.indexer is not None:
                ndata = self.indexer.get_data(data)
            else:
                ndata = data

            if self.transformation is not None:
                ndata = self.transformation.apply(ndata)
            else:

                ndata = self.indexer.get_data(ndata)

            _min = np.nanmin(ndata)
            if _min == -np.inf:
                ndata[ndata == -np.inf] = 0
                _min = np.nanmin(ndata)

            self.min = float(_min * 1.1 if _min < 0 else _min * 0.9)

            _max = max(ndata)
            self.max = float(_max * 1.1 if _max > 0 else _max * 0.9)

            self.sets = self.build(ndata)

            if self.ordered_sets is None and self.setnames is not None:
                self.ordered_sets = self.setnames
            else:
                self.ordered_sets = FuzzySet.set_ordered(self.sets)

            del(ndata)

        """
        Perform the partitioning of the Universe of Discourse

        :param data:  training data
        :return: 
        """
        pass

    def get_name(self, counter):
        return self.prefix + str(counter) if self.setnames is None else self.setnames[counter]

    def lower_set(self):
        return self.sets[self.ordered_sets[0]]

    def upper_set(self):
        return self.sets[self.ordered_sets[-1]]

        """
        Plot the 
        :param ax: 
        :return: 
        """
        ax.set_xlim([self.min, self.max])
        ticks = []
        x = []
            if s.type == 'common':
            elif s.type == 'composite':
            ticks.append(str(round(s.centroid,0))+'\n'+s.name)

    def plot_set(self, ax, s):
        if s.mf == Membership.trimf:
            ax.plot([s.parameters[0], s.parameters[1], s.parameters[2]], [0, s.alpha, 0])
        elif s.mf == Membership.gaussmf:
            tmpx = [kk for kk in np.arange(s.lower, s.upper)]
            tmpy = [s.membership(kk) for kk in np.arange(s.lower, s.upper)]
            ax.plot(tmpx, tmpy)
        elif s.mf == Membership.trapmf:
            ax.plot(s.parameters, [0, s.alpha, s.alpha, 0])

            tmp += str(self.sets[key])+ "\n"
        return tmp

"""Even Length Grid Partitioner"""

import random as rnd
import functools, operator

class GridPartitioner(partitioner.Partitioner):
    """Even Length Grid Partitioner"""

        """
        """
        super(GridPartitioner, self).__init__(name="Grid", **kwargs)

        sets = {}

        kwargs = {'type': self.type, 'variable': self.variable}

        dlen = self.max - self.min
        partlen = dlen / self.partitions

        count = 0
            _name = self.get_name(count)
                sets[_name] = FuzzySet.FuzzySet(_name, Membership.trimf, [c - partlen, c, c + partlen],c,**kwargs)
                sets[_name] = FuzzySet.FuzzySet(_name, Membership.gaussmf, [c, partlen / 3], c,**kwargs)
                q = partlen / 2
                sets[_name] = FuzzySet.FuzzySet(_name, Membership.trapmf, [c - partlen, c - q, c + q, c + partlen], c,**kwargs)
            count += 1

        self.min = self.min - partlen

"""
K. H. Huarng, “Effective lengths of intervals to improve forecasting in fuzzy time series,”
Fuzzy Sets Syst., vol. 123, no. 3, pp. 387–394, Nov. 2001.
"""

from pyFTS.common import FuzzySet, Membership, Transformations

class HuarngPartitioner(partitioner.Partitioner):
    """Huarng Empirical Partitioner"""
        super(HuarngPartitioner, self).__init__(name="Huarng", **kwargs)

        diff = Transformations.Differential(1)
        data2 = diff.apply(data)
        davg = np.abs( np.mean(data2) / 2 )

        if davg <= 1.0:
            base = 0.1
        elif 1 < davg <= 10:
            base = 1.0
        elif 10 < davg <= 100:
            base = 10
        else:
            base = 100

        sets = {}

        npart = math.ceil(dlen / base)
        partition = math.ceil(self.min)
        for c in range(npart):
            _name = self.get_name(c)
                sets[_name] = FuzzySet.FuzzySet(_name, Membership.trimf,
                                               [partition - base, partition, partition + base], partition)
                sets[_name] = FuzzySet.FuzzySet(_name, Membership.gaussmf,
                                              [partition, base/2], partition)
                sets[_name] = FuzzySet.FuzzySet(_name, Membership.trapmf,
                                              [partition - base, partition - (base/2),
                                               partition + (base / 2), partition + base], partition)

            partition += base

"""
C. H. Cheng, R. J. Chang, and C. A. Yeh, “Entropy-based and trapezoidal fuzzification-based fuzzy time series approach for forecasting IT project cost,”
Technol. Forecast. Social Change, vol. 73, no. 5, pp. 524–542, Jun. 2006.
"""

def splitBelow(data,threshold):
    return [k for k in data if k <= threshold]

def splitAbove(data,threshold):
    return [k for k in data if k > threshold]

def PMF(data, threshold):
    a = sum([1.0 for k in splitBelow(data,threshold)])
    b = sum([1.0 for k in splitAbove(data, threshold)])
    return [a / l, b / l]

def entropy(data, threshold):
    pmf = PMF(data, threshold)
    if pmf[0] == 0 or pmf[1] == 0:
        return 1
    else:
        return - sum([pmf[0] * math.log(pmf[0]), pmf[1] * math.log(pmf[1])])

def informationGain(data, thres1, thres2):
    return entropy(data, thres1) - entropy(data, thres2)

def bestSplit(data, npart):
    if len(data) < 2:
    count = 1
    ndata = list(set(data))
    ndata.sort()
    l = len(ndata)
    threshold = 0
    try:
        while count < l and informationGain(data, ndata[count - 1], ndata[count]) <= 0:
            threshold = ndata[count]
            count += 1
    except IndexError:
        print(threshold)
        print (ndata)
        print (count)

    rem = npart % 2

    if (npart - rem)/2 > 1:
        p1 = splitBelow(data,threshold)
        p2 = splitAbove(data,threshold)

        if len(p1) > len(p2):
            np1 = (npart - rem)/2 + rem
            np2 = (npart - rem)/2
        else:
            np1 = (npart - rem) / 2
            np2 = (npart - rem) / 2 + rem

        tmp = [threshold]

        for k in bestSplit(p1, np1 ): tmp.append(k)
        for k in bestSplit(p2, np2 ): tmp.append(k)

        return tmp

    else:
        return [threshold]

class EntropyPartitioner(partitioner.Partitioner):
    """Huarng Entropy Partitioner"""
        super(EntropyPartitioner, self).__init__(name="Entropy", **kwargs)

        sets = {}

        partitions = bestSplit(data, self.partitions)
        partitions.append(self.min)
        partitions.append(self.max)
        partitions = list(set(partitions))
        partitions.sort()
        for c in np.arange(1, len(partitions) - 1):
                                              [partitions[c - 1], partitions[c], partitions[c + 1]],partitions[c])
                b1 = (partitions[c] - partitions[c - 1])/2
                b2 = (partitions[c + 1] - partitions[c]) / 2
                                              [partitions[c - 1], partitions[c] - b1,
                                               partitions[c] + b2, partitions[c + 1]],
                                              partitions[c])

def distance(x, y):
    if isinstance(x, list):
        tmp = functools.reduce(operator.add, [(x[k] - y[k]) ** 2 for k in range(0, len(x))])
    else:
        tmp = (x - y) ** 2
    return math.sqrt(tmp)

def c_means(k, dados, tam):
    # Inicializa as centróides escolhendo elementos aleatórios dos conjuntos
    centroides = [dados[rnd.randint(0, len(dados)-1)] for kk in range(0, k)]

    grupos = [-1 for x in range(0, len(dados))]

    it_semmodificacao = 0

    # para cada instância
    iteracoes = 0
    while iteracoes < 1000 and it_semmodificacao < 10:
        inst_count = 0

        modificacao = False

        for instancia in dados:

            # verifica a distância para cada centroide
            grupo_count = 0
            dist = 10000

            grupotmp = grupos[inst_count]

            for grupo in centroides:
                tmp = distance(instancia, grupo)
                if tmp < dist:
                    dist = tmp
                    # associa a a centroide de menor distância à instância
                    grupos[inst_count] = grupo_count
                grupo_count = grupo_count + 1

            if grupotmp != grupos[inst_count]:
                modificacao = True

            inst_count = inst_count + 1

        if not modificacao:
            it_semmodificacao = it_semmodificacao + 1
        else:
            it_semmodificacao = 0

        # atualiza cada centroide com base nos valores médios de todas as instâncias à ela associadas
        grupo_count = 0
        for grupo in centroides:
            total_inst = functools.reduce(operator.add, [1 for xx in grupos if xx == grupo_count], 0)
            if total_inst > 0:
                if tam > 1:
                    for count in range(0, tam):
                        soma = functools.reduce(operator.add,
                                                [dados[kk][count] for kk in range(0, len(dados)) if
                                                 grupos[kk] == grupo_count])
                        centroides[grupo_count][count] = soma / total_inst
                else:
                    soma = functools.reduce(operator.add,
                                            [dados[kk] for kk in range(0, len(dados)) if grupos[kk] == grupo_count])
                    centroides[grupo_count] = soma / total_inst
            grupo_count = grupo_count + 1

        iteracoes = iteracoes + 1

    return centroides

class CMeansPartitioner(partitioner.Partitioner):
        super(CMeansPartitioner, self).__init__(name="CMeans", **kwargs)

        sets = {}
        centroides = c_means(self.partitions, data, 1)
        centroides.append(self.max)
        centroides.append(self.min)
        centroides = list(set(centroides))
        centroides.sort()
        for c in np.arange(1, len(centroides) - 1):
            sets[_name] = FuzzySet.FuzzySet(_name, Membership.trimf,
                                 [round(centroides[c - 1], 3), round(centroides[c], 3), round(centroides[c + 1], 3)],
                                 round(centroides[c], 3))

"""
S. T. Li, Y. C. Cheng, and S. Y. Lin, “A FCM-based deterministic forecasting model for fuzzy time series,”
Comput. Math. Appl., vol. 56, no. 12, pp. 3052–3063, Dec. 2008. DOI: 10.1016/j.camwa.2008.07.033.
"""

def fuzzy_distance(x, y):
    else:

def membership(val, vals):
    soma = 0
    for k in vals:
        if k == 0:
            k = 1
        soma = soma + (val / k) ** 2

    return soma

def fuzzy_cmeans(k, dados, tam, m, deltadist=0.001):
    tam_dados = len(dados)

    centroides = [dados[rnd.randint(0, tam_dados - 1)] for kk in range(0, k)]

    # Tabela de pertinência das instâncias aos grupos
    grupos = [[0 for kk in range(0, k)] for xx in range(0, tam_dados)]

    alteracaomedia = 1000

    m_exp = 1 / (m - 1)

    while iteracoes < 1000 and alteracaomedia > deltadist:

        alteracaomedia = 0

        # verifica a distância para cada centroide
        # Atualiza a pertinencia daquela instância para cada um dos grupos

            dist_grupos = [0 for xx in range(0, k)]

                dist_grupos[grupo_count] = fuzzy_distance(grupo, instancia)

            dist_grupos_total = functools.reduce(operator.add, [xk for xk in dist_grupos])

            for grp in range(0, k):
                if dist_grupos[grp] == 0:
                    grupos[inst_count][grp] = 1
                else:
                    grupos[inst_count][grp] = 1 / membership(dist_grupos[grp], dist_grupos)
                    # grupos[inst_count][grp] = 1/(dist_grupos[grp] / dist_grupos_total)
                    # grupos[inst_count][grp] = (1/(dist_grupos[grp]**2))**m_exp / (1/(dist_grupos_total**2))**m_exp

        # return centroides

        # atualiza cada centroide com base na Média de todos os padrões ponderados pelo grau de pertinência

            if tam > 1:
                oldgrp = [xx for xx in grupo]
                for atr in range(0, tam):
                                            [grupos[xk][grupo_count] * dados[xk][atr] for xk in range(0, tam_dados)])
                    norm = functools.reduce(operator.add, [grupos[xk][grupo_count] for xk in range(0, tam_dados)])
                    centroides[grupo_count][atr] = soma / norm
            else:
                oldgrp = grupo
                soma = functools.reduce(operator.add,
                                        [grupos[xk][grupo_count] * dados[xk] for xk in range(0, tam_dados)])
                norm = functools.reduce(operator.add, [grupos[xk][grupo_count] for xk in range(0, tam_dados)])
                centroides[grupo_count] = soma / norm

            alteracaomedia = alteracaomedia + fuzzy_distance(oldgrp, grupo)

        alteracaomedia = alteracaomedia / k

class FCMPartitioner(partitioner.Partitioner):
    """
    
    """

        super(FCMPartitioner, self).__init__(name="FCM", **kwargs)

        sets = {}

        centroids = fuzzy_cmeans(self.partitions, data, 1, 2)
        centroids.append(self.max)
        centroids.append(self.min)
        centroids = list(set(centroids))
        centroids.sort()
        for c in np.arange(1, len(centroids) - 1):
                                                [round(centroids[c - 1], 3), round(centroids[c], 3),
                                                 round(centroids[c + 1], 3)],
                                                round(centroids[c], 3))
                q1 = (round(centroids[c], 3) - round(centroids[c - 1], 3)) / 2
                q2 = (round(centroids[c + 1], 3) - round(centroids[c], 3)) / 2
                                                [round(centroids[c - 1], 3), round(centroids[c], 3) - q1,
                                                 round(centroids[c], 3) + q2, round(centroids[c + 1], 3)],

"""
Facility methods for pyFTS partitioners module
"""

from pyFTS.partitioners import Grid,Huarng,FCM,Entropy

all_methods = [Grid.GridPartitioner, Entropy.EntropyPartitioner, FCM.FCMPartitioner, Huarng.HuarngPartitioner]

mfs = [Membership.trimf, Membership.gaussmf, Membership.trapmf]

def plot_sets(data, sets, titles, size=[12, 10], save=False, file=None, axis=None):
    num = len(sets)

        fig, axes = plt.subplots(nrows=num, ncols=1,figsize=size)
    for k in np.arange(0,num):
        ticks = []
        x = []
        ax = axes[k] if axis is None else axis
        ax.set_title(titles[k])
        ax.set_ylim([0, 1.1])
        for key in sets[k].keys():
            s = sets[k][key]
            if s.mf == Membership.trimf:
                ax.plot(s.parameters,[0,1,0])
            elif s.mf == Membership.gaussmf:
                tmpx = [ kk for kk in np.arange(s.lower, s.upper)]
                tmpy = [s.membership(kk) for kk in np.arange(s.lower, s.upper)]
                ax.plot(tmpx, tmpy)
            elif s.mf == Membership.trapmf:
                ax.plot(s.parameters, [0, 1, 1, 0])

        plt.tight_layout()

        Util.show_and_save_image(fig, file, save)

def plot_partitioners(data, objs, tam=[12, 10], save=False, file=None, axis=None):
    sets = [k.sets for k in objs]
    titles = [k.name for k in objs]
    plot_sets(data, sets, titles, tam, save, file, axis)

def explore_partitioners(data, npart, methods=None, mf=None, transformation=None,
                         size=[12, 10], save=False, file=None):
    """
    Create partitioners for the mf membership functions and npart partitions and show the partitioning images.
    :data: Time series data
    :npart: Maximum number of partitions of the universe of discourse
    :methods: A list with the partitioning methods to be used
    :mf: A list with the membership functions to be used
    :transformation: a transformation to be used in partitioner
    :size: list, the size of the output image [width, height]
    :save: boolean, if the image will be saved on disk
    :file: string, the file path to save the image
    :return: the list of the built partitioners
    """
        methods = all_methods

    if mf is None:
        mf = mfs

    objs = []

    for p in methods:
        for m in mf:
            obj = p(data=data, npart=npart, func=m, transformation=transformation)
            obj.name = obj.name  + " - " + obj.membership_function.__name__
            objs.append(obj)

    plot_partitioners(data, objs, size, save, file)

    return objs

import  numpy as np

from pyFTS.partitioners import Util

def explore_partitioners(data, npart, methods=None, mf=None, tam=[12, 10], save=False, file=None):
    all_methods = [Grid.GridPartitioner, Entropy.EntropyPartitioner, FCM.FCMPartitioner]
    mfs = [Membership.trimf, Membership.gaussmf, Membership.trapmf]

        mf = mfs

    num_cores = multiprocessing.cpu_count()

    objs = []
    for method in methods:
        print(str(method))
        tmp = Parallel(n_jobs=num_cores)(delayed(method)(deepcopy(data), npart, m) for m in mf)
        objs.append(tmp)

    objs = np.ravel(objs).tolist()

    Util.plot_partitioners(data, objs, tam, save, file)

from pyFTS.common import FuzzySet,SortedCollection,tree
from pyFTS.probabilistic import kde

class ProbabilityDistribution(object):
    """
    Represents a discrete or continous probability distribution
    If type is histogram, the PDF is discrete
    If type is KDE the PDF is continuous
    """
    def __init__(self, type = "KDE", **kwargs):
        self.uod = kwargs.get("uod", None)
        """Universe of discourse"""

        self.data = []

        self.type = type
        """
        If type is histogram, the PDF is discrete
        If type is KDE the PDF is continuous
        """

        self.bins = kwargs.get("bins", None)
        """Number of bins on a discrete PDF"""
        self.labels = kwargs.get("bins_labels", None)
        """Bins labels on a discrete PDF"""

        data = kwargs.get("data", None)

        if self.type == "KDE":
            self.kde = kde.KernelSmoothing(kwargs.get("h", 0.5), kwargs.get("kernel", "epanechnikov"))
            _min = np.nanmin(data)
            _min = _min * .7 if _min > 0 else _min * 1.3
            _max = np.nanmax(data)
            _max = _max * 1.3 if _max > 0 else _max * .7
            self.uod = [_min, _max]

        self.nbins = kwargs.get("num_bins", 100)

        if self.bins is None:
            self.bins = np.linspace(int(self.uod[0]), int(self.uod[1]), int(self.nbins)).tolist()
            self.labels = [str(k) for k in self.bins]

        if self.uod is not None:
            self.resolution = (self.uod[1] - self.uod[0]) / self.nbins

        self.bin_index = SortedCollection.SortedCollection(iterable=sorted(self.bins))
        self.quantile_index = None
        self.distribution = {}
        self.cdf = None
        self.qtl = None
        self.count = 0
        for k in self.bins: self.distribution[k] = 0

        if data is not None:
            self.append(data)

        self.name = kwargs.get("name", "")

    def set(self, value, density):
        k = self.bin_index.find_ge(value)
        self.distribution[k] = density

    def append(self, values):
        if self.type == "histogram":
            for k in values:
                v = self.bin_index.find_ge(k)
                self.distribution[v] += 1
                self.count += 1
        else:
            self.data.extend(values)
            self.distribution = {}
            dens = self.density(self.bins)
            for v,d in enumerate(dens):
                self.distribution[self.bins[v]] = d

    def append_interval(self, intervals):
            for interval in intervals:
                for k in self.bin_index.inside(interval[0], interval[1]):
                    self.distribution[k] += 1
                    self.count += 1

    def density(self, values):
        ret = []
        scalar = False

        if not isinstance(values, list):
            values = [values]
            scalar = True

        for k in values:
            if self.type == "histogram":
                ret.append(self.distribution[v] / (self.count + 1e-5))
            elif self.type == "KDE":
                v = self.kde.probability(k, self.data)
                ret.append(v)
            else:
                ret.append(self.distribution[v])

        if scalar:
            return ret[0]

        return ret

    def differential_offset(self, value):
        nbins = []
        dist = {}

        for k in self.bins:
            nk = k+value
            nbins.append(nk)
            dist[nk] = self.distribution[k]

        self.bins = nbins
        self.distribution = dist
        self.labels = [str(k) for k in self.bins]

    def expected_value(self):
        return np.nansum([v * self.distribution[v] for v in self.bins])

    def build_cdf_qtl(self):
        ret = 0.0
        self.cdf = {}
        self.qtl = {}
        for k in sorted(self.bins):
            ret += self.density(k)
            if k not in self.cdf:
                self.cdf[k] = ret

            if str(ret) not in self.qtl:
                self.qtl[str(ret)] = []

            self.qtl[str(ret)].append(k)

        _keys = [float(k) for k in sorted(self.qtl.keys())]

        self.quantile_index = SortedCollection.SortedCollection(iterable=_keys)

    def cummulative(self, values):
        if self.cdf is None:
            self.build_cdf_qtl()

        if isinstance(values, list):
            ret = []
            for val in values:
                k = self.bin_index.find_ge(val)
                ret.append(self.cdf[k])
        else:
            k = self.bin_index.find_ge(values)
            return self.cdf[values]

    def quantile(self, values):
        if self.qtl is None:

            ret = []
                k = self.quantile_index.find_ge(val)
                ret.append(self.qtl[str(k)][0])
        else:
            k = self.quantile_index.find_ge(values)
            ret = self.qtl[str(k)]

        return ret

    def entropy(self):
        h = -sum([self.distribution[k] * np.log(self.distribution[k]) if self.distribution[k] > 0 else 0
                  for k in self.bins])
        return h

    def crossentropy(self,q):
        h = -sum([self.distribution[k] * np.log(q.distribution[k]) if self.distribution[k] > 0 else 0
        return h

    def kullbackleiblerdivergence(self,q):
        h = sum([self.distribution[k] * np.log(self.distribution[k]/q.distribution[k]) if self.distribution[k] > 0 else 0
        return h

    def empiricalloglikelihood(self):
        _s = 0
            if self.distribution[k] > 0:
                _s += np.log(self.distribution[k])
        return _s

    def pseudologlikelihood(self, data):

        densities = self.density(data)

        _s = 0
        for k in densities:
            if k > 0:
                _s += np.log(k)
        return _s

    def averageloglikelihood(self, data):

        _s = 0
            if k > 0:
        return _s / len(data)

    def plot(self,axis=None,color="black",tam=[10, 6], title = None):

        if axis is None:
            fig = plt.figure(figsize=tam)
            axis = fig.add_subplot(111)

            ys = [self.distribution[k]/self.count for k in self.bins]
        else:
            ys = [self.distribution[k] for k in self.bins]
            yp = [0 for k in self.data]
            axis.plot(self.data, yp, c="red")

        if title is None:
            title = self.name
        axis.plot(self.bins, ys, c=color)
        axis.set_title(title)

        axis.set_xlabel('Universe of Discourse')
        axis.set_ylabel('Probability')

        ret = ""
            ret += str(round(k,2)) + ':\t'
                ret +=  str(round(self.distribution[k]  / self.count,3))
                ret +=  str(round(self.density(k),3))
            else:
                ret += str(round(self.distribution[k], 6))
            ret += '\n'
        return ret

"""
Kernel Density Estimation 
"""

class KernelSmoothing(object):
    """Kernel Density Estimation"""
    def __init__(self,h, kernel="epanechnikov"):
        self.h = h
        """Width parameter"""
        self.kernel = kernel
        """Kernel function"""
        self.transf = Transformations.Scale(min=0,max=1)

    def kernel_function(self, u):
        if self.kernel == "epanechnikov":
            tmp = (3/4)*(1.0 - u**2)
            return tmp if tmp > 0 else 0
        elif self.kernel == "gaussian":
            return (1.0/np.sqrt(2*np.pi))*np.exp(-0.5*u**2)
        elif self.kernel == "uniform":
            return 0.5
        elif self.kernel == "triangular":
            tmp = 1.0 - np.abs(u)
        elif self.kernel == "logistic":
            return 1.0/(np.exp(u)+2+np.exp(-u))
        elif self.kernel == "cosine":
            return (np.pi/4.0)*np.cos((np.pi/2.0)*u)
        elif self.kernel == "sigmoid":
            return (2.0/np.pi)*(1.0/(np.exp(u)+np.exp(-u)))
        elif self.kernel == "tophat":
            return 1 if np.abs(u) < 0.5 else 0
        elif self.kernel == "exponential":
            return 0.5 * np.exp(-np.abs(u))

    def probability(self, x, data):
        """
        Probability of the point x on data

        :param x:
        :param data:
        :return:
        """

        ndata = self.transf.apply(data)
        nx = self.transf.apply(x)
        p = sum([self.kernel_function((nx - k)/self.h) for k in ndata]) / l*self.h

        return p