# Source code for pyFTS.models.multivariate.cmvfts

    import numpy as np
    import pandas as pd
    from pyFTS.common import FuzzySet, FLR, fts, flrg
    from pyFTS.models import hofts
    from pyFTS.models.multivariate import mvfts, grid, common

    class ClusteredMVFTS(mvfts.MVFTS):
        """
        Meta model for multivariate, high order, clustered multivariate FTS
        """
        def __init__(self, **kwargs):
            super(ClusteredMVFTS, self).__init__(**kwargs)

            self.cluster_method = kwargs.get('cluster_method', grid.GridCluster)
            """The cluster method to be called when a new model is build"""
            self.cluster_params = kwargs.get('cluster_params', {})
            """The cluster method parameters"""
            self.cluster = kwargs.get('cluster', None)
            """The trained clusterer"""

            self.fts_method = kwargs.get('fts_method', hofts.WeightedHighOrderFTS)
            """The FTS method to be called when a new model is build"""
            self.fts_params = kwargs.get('fts_params', {})
            """The FTS method specific parameters"""
            self.model = None
            """The most recent trained model"""
            self.knn = kwargs.get('knn', 2)

            self.is_high_order = True

            self.order = kwargs.get("order", 2)
            self.lags = kwargs.get("lags", None)
            self.alpha_cut = kwargs.get('alpha_cut', 0.25)

            self.shortname = "ClusteredMVFTS"
            self.name = "Clustered Multivariate FTS"

            self.pre_fuzzyfy = kwargs.get('pre_fuzzyfy', True)

        def fuzzyfy(self,data):
            ndata = []
            for index, row in data.iterrows():
                data_point = self.format_data(row)
                ndata.append(common.fuzzyfy_instance_clustered(data_point, self.cluster, alpha_cut=self.alpha_cut))

            return ndata

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

            if self.cluster is None:
                self.cluster = self.cluster_method(data=data, mvfts=self, neighbors=self.knn, **self.cluster_params)

            self.model = self.fts_method(partitioner=self.cluster, **self.fts_params)
            if self.model.is_high_order:
                self.model.order = self.order

            ndata = self.check_data(data)

            self.model.train(ndata, fuzzyfied=self.pre_fuzzyfy)

            self.cluster.prune()

        def check_data(self, data):
            if self.pre_fuzzyfy:
                ndata = self.fuzzyfy(data)
            else:
                ndata = [self.format_data(k) for k in data.to_dict('records')]

            return ndata

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

            ndata = self.check_data(ndata)

            return self.model.forecast(ndata, fuzzyfied=self.pre_fuzzyfy, **kwargs)

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

            ndata = self.check_data(data)

            ret = {}
            for var in self.explanatory_variables:
                if self.target_variable.name != var.name:
                    self.target_variable = var
                    self.cluster.change_target_variable(var)
                    self.model.partitioner = self.cluster
                    self.model.reset_calculated_values()

                ret[var.name] = self.model.forecast(ndata, fuzzyfied=self.pre_fuzzyfy, **kwargs)

            return pd.DataFrame(ret, columns=ret.keys())

        def __str__(self):
            """String representation of the model"""
            return str(self.model)

        def __len__(self):
            """
            The length (number of rules) of the model

            :return: number of rules
            """
            return len(self.model)