class TrestleTree(Cobweb3Tree):
    """
    The TrestleTree instantiates the Trestle algorithm, which can
    be used to learn from and categorize instances. Trestle adds the ability to
    handle component attributes as well as relations in addition to the
    numerical and nominal attributes of Cobweb and Cobweb/3.

    The scaling parameter determines whether online normalization of continuous
    attributes is used, and to what standard deviation the values are scaled
    to. Scaling divides the std of each attribute by the std of the attribute
    in the root divided by the scaling constant (i.e.,
    :math:`\\sigma_{root} / scaling` when making category utility calculations.
    Scaling is useful to balance the weight of different numerical attributes,
    without scaling the magnitude of numerical attributes can affect category
    utility calculation meaning numbers that are naturally larger will recieve
    preference in the category utility calculation.

    :param scaling: The number of standard deviations numeric attributes
        are scaled to. By default this value is 0.5 (half a standard
        deviation), which is the max std of nominal values. If disabiling
        scaling is desirable, then it can be set to False or None.
    :type scaling: a float greater than 0.0, None, or False
    :param inner_attr_scaling: Whether to use the inner most attribute name
        when scaling numeric attributes. For example, if `('attr', '?o1')` was
        an attribute, then the inner most attribute would be 'attr'. When using
        inner most attributes, some objects might have multiple attributes
        (i.e., 'attr' for different objects) that contribute to the scaling.
    :param inner_attr_scaling: boolean
    :param structure_map_internally: Determines whether structure mapping is
        used at each node during categorization (and when merging), this
        drastically reduces performance, but allows the category structure to
        influcence structure mapping.
    :type structure_map_internally: boolean
    """

    def __init__(self, scaling=0.5, inner_attr_scaling=True):
        """
        The tree constructor.
        """
        self.gensym_counter = 0
        self.root = Cobweb3Node()
        self.root.tree = self
        self.scaling = scaling
        self.inner_attr_scaling = inner_attr_scaling
        self.attr_scales = {}

    def clear(self):
        """
        Clear the tree but keep initialization parameters
        """
        self.gensym_counter = 0
        self.root = Cobweb3Node()
        self.root.tree = self
        self.attr_scales = {}

    def gensym(self):
        """
        Generates unique names for naming renaming apart objects.

        :return: a unique object name
        :rtype: '?o'+counter
        """
        self.gensym_counter += 1
        return '?o' + str(self.gensym_counter)

    def _sanity_check_instance(self, instance):
        """
        Checks the attributes of an instance to ensure they are properly
        subscriptable types and throws an excpetion if they are not.
        Lots of sub-processes in the structure mapper freak out if you have
        non-str non-tuple attributes so I decided it was best to do a one
        time check at the first call to transform.
        """
        for attr in instance:
            try:
                hash(attr)
                attr[0]
            except Exception:
                raise ValueError('Invalid attribute: '+str(attr) +
                                 ' of type: ' + str(type(attr)) +
                                 ' in instance: ' + str(instance) +
                                 ',\n' + type(self).__name__ +
                                 ' only works with hashable and' +
                                 ' subscriptable attributes (e.g., strings).')
            if isinstance(attr, tuple):
                self._sanity_check_relation(attr, instance)
            if isinstance(instance[attr], dict):
                self._sanity_check_instance(instance[attr])
            else:
                try:
                    hash(instance[attr])
                except Exception:
                    raise ValueError('Invalid value: ' + str(instance[attr]) +
                                     ' of type: ' + str(type(instance[attr])) +
                                     ' in instance: ' + str(instance) +
                                     ',\n' + type(self).__name__ +
                                     ' only works with hashable values.')

    def _sanity_check_relation(self, relation, instance):
        for v in relation:
            try:
                v[0]
            except Exception:
                raise(ValueError('Invalid relation value: ' + str(v) +
                                 ' of type: ' + str(type(v)) +
                                 ' in instance: ' + str(instance) +
                                 ',\n' + type(self).__name__ +
                                 'requires that values inside relation' +
                                 ' tuples be of type str or tuple.'))
            if isinstance(v, tuple):
                self._sanity_check_relation(v, instance)

    def ifit(self, instance):
        """
        Incrementally fit a new instance into the tree and return its resulting
        concept.

        The instance is passed down the tree and updates each node to
        incorporate the instance. **This modifies the tree's knowledge** for a
        non-modifying version see: :meth:`TrestleTree.categorize`.

        This version is modified from the normal :meth:`CobwebTree.ifit
        <concept_formation.cobweb.CobwebTree.ifit>` by first structure mapping
        the instance before fitting it into the knoweldge base.

        :param instance: an instance to be categorized into the tree.
        :type instance: :ref:`Instance<instance-rep>`
        :return: A concept describing the instance
        :rtype: Cobweb3Node

        .. seealso:: :meth:`TrestleTree.trestle`
        """
        return self.trestle(instance)

    def _trestle_categorize(self, instance):
        """
        The structure maps the instance, categorizes the matched instance, and
        returns the resulting concept.

        :param instance: an instance to be categorized into the tree.
        :type instance: {a1:v1, a2:v2, ...}
        :return: A concept describing the instance
        :rtype: concept
        """
        preprocessing = Pipeline(NameStandardizer(self.gensym),
                                 Flattener(), SubComponentProcessor(),
                                 StructureMapper(self.root))
        temp_instance = preprocessing.transform(instance)
        self._sanity_check_instance(temp_instance)
        return self._cobweb_categorize(temp_instance)

    def infer_missing(self, instance, choice_fn="most likely",
                      allow_none=True):
        """
        Given a tree and an instance, returns a new instance with attribute
        values picked using the specified choice function (either "most likely"
        or "sampled").

        .. todo:: write some kind of test for this.

        :param instance: an instance to be completed.
        :type instance: :ref:`Instance<instance-rep>`
        :param choice_fn: a string specifying the choice function to use,
            either "most likely" or "sampled".
        :type choice_fn: a string
        :param allow_none: whether attributes not in the instance can be
            inferred to be missing. If False, then all attributes will be
            inferred with some value.
        :type allow_none: Boolean
        :return: A completed instance
        :rtype: instance
        """
        preprocessing = Pipeline(NameStandardizer(self.gensym),
                                 Flattener(), SubComponentProcessor(),
                                 StructureMapper(self.root))

        temp_instance = preprocessing.transform(instance)
        concept = self._cobweb_categorize(temp_instance)

        for attr in concept.attrs('all'):
            if attr in temp_instance:
                continue
            val = concept.predict(attr, choice_fn, allow_none)
            if val is not None:
                temp_instance[attr] = val

        temp_instance = preprocessing.undo_transform(temp_instance)
        return temp_instance

    def categorize(self, instance):
        """
        Sort an instance in the categorization tree and return its resulting
        concept.

        The instance is passed down the the categorization tree according to
        the normal cobweb algorithm except using only the new and best
        opperators and without modifying nodes' probability tables. **This does
        not modify the tree's knowledge base** for a modifying version see
        :meth:`TrestleTree.ifit`

        This version differs fomr the normal :meth:`CobwebTree.categorize
        <concept_formation.cobweb.CobwebTree.categorize>` and
        :meth:`Cobweb3Tree.categorize
        <concept_formation.cobweb3.Cobweb3Tree.categorize>` by structure
        mapping instances before categorizing them.

        :param instance: an instance to be categorized into the tree.
        :type instance: :ref:`Instance<instance-rep>`
        :return: A concept describing the instance
        :rtype: CobwebNode

        .. seealso:: :meth:`TrestleTree.trestle`
        """
        return self._trestle_categorize(instance)

    def trestle(self, instance):
        """
        The core trestle algorithm used in fitting and categorization.

        This function is similar to :meth:`Cobweb.cobweb
        <concept_formation.cobweb.CobwebTree.cobweb>` The key difference
        between trestle and cobweb is that trestle performs structure mapping
        (see: :meth:`structure_map
        <concept_formation.structure_mapper.StructureMapper.transform>`) before
        proceeding through the normal cobweb algorithm.

        :param instance: an instance to be categorized into the tree.
        :type instance: :ref:`Instance<instance-rep>`
        :return: A concept describing the instance
        :rtype: CobwebNode
        """
        preprocessing = Pipeline(NameStandardizer(self.gensym),
                                 Flattener(), SubComponentProcessor(),
                                 StructureMapper(self.root))
        temp_instance = preprocessing.transform(instance)
        self._sanity_check_instance(temp_instance)
        return self.cobweb(temp_instance) def cluster_split_search(tree, instances, heuristic=CU, minsplit=1, maxsplit=1,
                         mod=True, labels=True, verbose=False):
    """
    Find a clustering of the instances given the tree that is based on
    successive splittings of the tree in order to minimize some heuristic
    function.

    :param tree: A category tree to be used to generate clusters.
    :param instances: A list of instances to cluster
    :param heuristic: A heuristic function to minimize in search
    :param minsplit: The minimum number of splits to perform on the tree
    :param maxsplit: the maximum number of splits to perform on the tree
    :param mod: A flag to determine if instances will be fit (i.e. modifying
        knoweldge) or categorized (i.e. not modifiying knowledge)
    :param labels: A flag to determine whether the process should return a
        list of cluster labels (true) or a list of concept nodes (false).
    :param verbose: If True, the process will print the heuristic at each
        split as it searches.
    :type tree: :class:`CobwebTree <concept_formation.cobweb.CobwebTree>`,
        :class:`Cobweb3Tree <concept_formation.cobweb3.Cobweb3Tree>`, or
        :class:`TrestleTree <concept_formation.trestle.TrestleTree>`
    :type instances: [:ref:`Instance<instance-rep>`,
        :ref:`Instance<instance-rep>`, ...]
    :type heuristic: a function.
    :type minsplit: int
    :type maxsplit: int
    :type mod: bool
    :type labels: bool
    :type verbose: bool
    :returns: a list of cluster labels based on the optimal number of splits
        according to the heuristic
    :rtype: list

    .. seealso:: :meth:`cluster_iter`
    """
    clus_it = cluster_iter(tree, instances, heuristic=heuristic,
                           minsplit=minsplit, maxsplit=maxsplit, mod=mod,
                           labels=False)
    min_h = (-1, float('inf'), ["Concept" + str(tree.root.concept_id)
                                for i in range(len(instances))])
    split = minsplit
    if verbose:
        print('S\tC\tH')
    for split_clus, h in clus_it:
        # clus = set(split_clus)
        # h = heuristic(clus,tree,instances)
        if verbose:
            print(split, len(set(split_clus)), '%.3f' % h, sep='\t')
        if h < min_h[1]:
            min_h = (split, h, ["Concept" + str(c.concept_id)
                                for c in split_clus] if labels else split_clus)
        split += 1
    return min_h[2] def AIC(clusters, leaves):
    """
    Calculates the Akaike Information Criterion of the a given clustering
    from a given tree and set of instances.

    This can be used as one of the heuristic functions in
    :meth:`cluster_split_search`.

    .. math ::
        AIC = 2k - 2\\ln (\\mathcal{L})

    * :math:`\\ln(\\mathcal{L})` is the total log-likelihood of the cluster
      concepts
    * :math:`k` is the total number of unique attribute value pairs in
      the tree root times the number of clusters

    :param clusters: A unique set of cluster concepts from the tree
    :type clusters: {:class:`CobwebNode<concept_formation.cobweb.CobwebNode>`,
        :class:`CobwebNode<concept_formation.cobweb.CobwebNode>`, ...}
    :param tree: The tree that the clusters come from (used to calculated
        number of parameters)
    :type tree: :class:`CobwebTree <concept_formation.cobweb.CobwebTree>`,
        :class:`Cobweb3Tree <concept_formation.cobweb3.Cobweb3Tree>`, or
        :class:`TrestleTree <concept_formation.trestle.TrestleTree>`
    :param instances: The set of clustered instances
    :type instances: [:ref:`Instance<instance-rep>`,
        :ref:`Instance<instance-rep>`, ...]
    :returns: The AIC of the clustering
    :rtype: float
    """
    ll = 0
    # r = 0
    # c = len(clusters)
    k = 0

    for i, conc in enumerate(clusters):
        ll += conc.log_likelihood(leaves[i])

    for conc in set(clusters):
        for attr in conc.attrs():
            for val in conc.av_counts[attr]:
                if val == cv_key:
                    k += 2
                else:
                    k += 1
    return 2 * k - 2 * ll def BIC(clusters, leaves):
    """
    Calculates the Bayesian Information Criterion of the a given clustering
    from a given tree and set of instances.

    This can be used as one of the heuristic functions in
    :meth:`cluster_split_search`.

    .. math ::
        BIC = k\\ln (n) - 2\\ln (\\mathcal{L})

    * :math:`\\ln(\\mathcal{L})` is the total log-likelihood of the cluster
      concepts
    * :math:`k` is the total number of unique attribute value pairs in the tree
      root times the number of clusters
    * :math:`n` is the number of instances.

    :param clusters: A unique set of cluster concepts from the tree
    :type clusters: {:class:`CobwebNode<concept_formation.cobweb.CobwebNode>`,
        :class:`CobwebNode<concept_formation.cobweb.CobwebNode>`, ...}
    :param tree: The tree that the clusters come from (used to calculated
        number of parameters)
    :type tree: :class:`CobwebTree <concept_formation.cobweb.CobwebTree>`,
        :class:`Cobweb3Tree <concept_formation.cobweb3.Cobweb3Tree>`, or
        :class:`TrestleTree <concept_formation.trestle.TrestleTree>`
    :param instances: The set of clustered instances
    :type instances: [:ref:`Instance<instance-rep>`,
        :ref:`Instance<instance-rep>`, ...]
    :returns: The BIC of the clustering
    :rtype: float
    """
    # c = len(clusters)
    n = len(leaves)
    ll = 0
    # r = 0
    k = 0

    for i, conc in enumerate(clusters):
        ll += conc.log_likelihood(leaves[i])

    for conc in set(clusters):
        for attr in conc.attrs():
            for val in conc.av_counts[attr]:
                if val == cv_key:
                    k += 2
                else:
                    k += 1
    return -2 * ll + k * log(n) def AICc(clusters, leaves):
    """
    Calculates the Akaike Information Criterion of the a given clustering
    from a given tree and set of instances with a correction for finite sample
    sizes.

    This can be used as one of the heuristic functions in
    :meth:`cluster_split_search`.

    .. math ::
        AICc = 2k - 2\\ln (\\mathcal{L}) + \\frac{2k(k+1)}{n-k-1}

    * :math:`\\ln(\\mathcal{L})` is the total log-likelihood of the cluster
      concepts
    * :math:`k` is the total number of unique attribute value pairs in
      the tree root times the number of clusters
    * :math:`n` is the number of instances.

    Given the particular math of AICc I decided that is n-k-1 = 0 then the
    function will return float('inf').

    :param clusters: A unique set of cluster concepts from the tree
    :type clusters: {:class:`CobwebNode<concept_formation.cobweb.CobwebNode>`,
        :class:`CobwebNode<concept_formation.cobweb.CobwebNode>`, ...}
    :param tree: The tree that the clusters come from (used to calculated
        number of parameters)
    :type tree: :class:`CobwebTree <concept_formation.cobweb.CobwebTree>`,
        :class:`Cobweb3Tree <concept_formation.cobweb3.Cobweb3Tree>`, or
        :class:`TrestleTree <concept_formation.trestle.TrestleTree>`
    :param instances: The set of clustered instances
    :type instances: [:ref:`Instance<instance-rep>`,
        :ref:`Instance<instance-rep>`, ...]
    :returns: The AIC of the clustering
    :rtype: float
    """
    ll = 0
    n = len(leaves)
    k = 0

    for i, conc in enumerate(clusters):
        ll += conc.log_likelihood(leaves[i])

    for conc in set(clusters):
        for attr in conc.attrs():
            for val in conc.av_counts[attr]:
                if val == cv_key:
                    k += 2
                else:
                    k += 1
    if n - k <= 1:
        return float('inf')
    else:
        return 2 * k - 2 * ll + 2 * k * (k + 1)/(n - k - 1) def CU(cluster, leaves):
    """
    Calculates the Category Utility of a tree state given clusters and leaves.

    This just uses the basic category utility function of a node created from
    the leave instances. This also negates the result so it can be minimized
    like the other heuristic functions.

    .. todo :: we might want to do this with infered missing instances rather
        than leaves

    :param clusters: A unique set of cluster concepts from the tree
    :type clusters: {:class:`CobwebNode<concept_formation.cobweb.CobwebNode>`,
        :class:`CobwebNode<concept_formation.cobweb.CobwebNode>`, ...}
    :param tree: The tree that the clusters come from (used to calculated
        number of parameters)
    :type tree: :class:`CobwebTree <concept_formation.cobweb.CobwebTree>`,
        :class:`Cobweb3Tree <concept_formation.cobweb3.Cobweb3Tree>`, or
        :class:`TrestleTree <concept_formation.trestle.TrestleTree>`
    :param instances: The set of clustered instances
    :type instances: [:ref:`Instance<instance-rep>`,
        :ref:`Instance<instance-rep>`, ...]
    :returns: The CU of the clustering
    :rtype: float
    """
    temp_root = cluster[0].__class__()
    temp_root.tree = cluster[0].tree
    for c in set(cluster):
        temp_child = cluster[0].__class__()
        temp_child.tree = c.tree
        for leaf in leaves:
            if c.is_parent(leaf):
                temp_child.update_counts_from_node(leaf)
        temp_root.update_counts_from_node(temp_child)
        temp_root.children.append(temp_child)
    return -temp_root.category_utility() def load_rb_wb_03(num_instances=None):
    """
    Load the RumbleBlocks, Wide Base Level 03, dataset.

    This is an example of instances with all the attribute and value types
    described in the :ref:`instance-rep`. This dataset contains 254 instances.

    >>> import pprint
    >>> data = load_rb_wb_03(num_instances=1)
    >>> pprint.pprint(data[0])
    {'_guid': 'aa5eff72-0572-4eff-a007-3def9a82ba5b',
     '_human_cluster_label': '0',
     'component0': {'b': 2.0, 'l': 2.0, 'r': 3.0, 't': 3.0, 'type': 'cube0'},
     'component1': {'b': 2.0, 'l': 3.0, 'r': 4.0, 't': 3.0, 'type': 'cube0'},
     'component11': {'b': 3.0, 'l': 1.0, 'r': 4.0, 't': 4.0, 'type': 'ufoo0'},
     'component2': {'b': 1.0, 'l': 2.0, 'r': 5.0, 't': 2.0, 'type': 'plat0'},
     'component3': {'b': 0.0, 'l': 0.0, 'r': 5.0, 't': 1.0, 'type': 'rect0'}}
    """
    return _load_json('rb_wb_03_continuous.json', num_instances) def load_rb_com_11(num_instances=None):
    """
    Load the RumbleBlocks, Center of Mass Level 11, dataset.

    This is an example of instances with all the attribute and value types
    described in the :ref:`instance-rep`. This dataset contains 251 instances.

    >>> import pprint
    >>> data = load_rb_com_11(num_instances=1)
    >>> pprint.pprint(data[0])
    {'_guid': 'ea022d3d-5c9e-46d7-be23-8ea718fe7816',
     '_human_cluster_label': '0',
     'component0': {'b': 1.0, 'l': 0.0, 'r': 1.0, 't': 2.0, 'type': 'cube0'},
     'component1': {'b': 3.0, 'l': 2.0, 'r': 3.0, 't': 4.0, 'type': 'cube0'},
     'component14': {'b': 4.0, 'l': 1.0, 'r': 4.0, 't': 5.0, 'type': 'ufoo0'},
     'component2': {'b': 1.0, 'l': 1.0, 'r': 4.0, 't': 2.0, 'type': 'plat0'},
     'component3': {'b': 2.0, 'l': 1.0, 'r': 4.0, 't': 3.0, 'type': 'plat0'},
     'component4': {'b': 0.0, 'l': 0.0, 'r': 5.0, 't': 1.0, 'type': 'rect0'}}
    """
    return _load_json('rb_com_11_continuous.json', num_instances) def load_rb_s_13(num_instances=None):
    """
    Load the RumbleBlocks, Symmetry Level 13, dataset.

    This is an example of instances with all the attribute and value types
    described in the :ref:`instance-rep`. This dataset contains 249 instances.

    >>> import pprint
    >>> data = load_rb_s_13(num_instances=1)
    >>> pprint.pprint(data[0])
    {'_guid': '684b4ce5-0f55-481c-ae9a-1474de8418ea',
     '_human_cluster_label': '0',
     'component0': {'b': 3.0, 'l': 2.0, 'r': 3.0, 't': 4.0, 'type': 'cube0'},
     'component1': {'b': 4.0, 'l': 2.0, 'r': 3.0, 't': 5.0, 'type': 'cube0'},
     'component14': {'b': 0.0, 'l': 0.0, 'r': 4.0, 't': 1.0, 'type': 'trap0'},
     'component15': {'b': 5.0, 'l': 1.0, 'r': 3.0, 't': 6.0, 'type': 'ufoo0'},
     'component2': {'b': 1.0, 'l': 0.0, 'r': 3.0, 't': 2.0, 'type': 'plat0'},
     'component3': {'b': 2.0, 'l': 0.0, 'r': 3.0, 't': 3.0, 'type': 'plat0'}}
    """
    return _load_json('rb_s_13_continuous.json', num_instances) class ObjectVariablizer(OneWayPreprocessor):
    """
    Converts all attributes with dictionary values into variables by adding a
    question mark.

    Attribute names beginning with `?` are treated as bindable variables while
    all other attributes names are considered constants. This process searches
    through an instances and variablizes attributes that might not have been
    defined this way in the original data.

    This is a helper function preprocessor and so is not part of
    :class:`StructureMapper
    <concept_formation.structure_mapper.StructureMapper>`'s standard pipeline.

    >>> from pprint import pprint
    >>> instance = {"ob1": {"myX": 12.4, "myY": 13.1, "myType": "square"},
    ... "ob2": {"myX": 9.5, "myY": 12.6, "myType": "rect"}}
    >>> ov = ObjectVariablizer()
    >>> instance = ov.transform(instance)
    >>> pprint(instance)
    {'?ob1': {'myType': 'square', 'myX': 12.4, 'myY': 13.1},
     '?ob2': {'myType': 'rect', 'myX': 9.5, 'myY': 12.6}}
    >>> instance = ov.undo_transform(instance)
    >>> pprint(instance)
    {'?ob1': {'myType': 'square', 'myX': 12.4, 'myY': 13.1},
     '?ob2': {'myType': 'rect', 'myX': 9.5, 'myY': 12.6}}
    >>> instance = {"p1": {"x": 12, "y": 3}, "p2": {"x": 5, "y": 14}, "p3": {
    ... "x": 4, "y": 18}, "setttings": {"x_lab": "height", "y_lab": "age"}}
    >>> ov = ObjectVariablizer("p1","p2","p3")
    >>> instance = ov.transform(instance)
    >>> pprint(instance)
    {'?p1': {'x': 12, 'y': 3},
     '?p2': {'x': 5, 'y': 14},
     '?p3': {'x': 4, 'y': 18},
     'setttings': {'x_lab': 'height', 'y_lab': 'age'}}

    :param attrs: A list of specific attribute names to variablize. If left
        empty then all variables will be converted.
    :type attrs: strings
    """

    def __init__(self, *attrs):
        if len(attrs) == 0:
            self.targets = None
        else:
            self.targets = attrs

    def transform(self, instance):
        """
        Variablize target attributes.
        """
        return self._variablize(instance)

    def _variablize(self, instance, mapping={}, prefix=None):
        new_instance = {}

        mapping = {}
        relations = []
        if self.targets is None:
            attrs = [k for k in instance.keys()]
        else:
            attrs = self.targets

        for attr in instance:
            if prefix is None:
                prefix = attr
            else:
                prefix = (attr, prefix)

            if isinstance(attr, tuple):
                relations.append(attr)

            elif attr in attrs and isinstance(instance[attr], dict):
                name = attr
                if attr[0] != '?':
                    name = '?' + attr
                new_instance[name] = self._variablize(instance[attr], mapping,
                                                      prefix)
            else:
                new_instance[attr] = instance[attr]

        for rel in relations:
            new_instance[rename_relation(rel, mapping)] = instance[rel]

        return new_instance
from random import shuffle
from random import seed
from sklearn.metrics import adjusted_rand_score
from concept_formation.trestle import TrestleTree
from concept_formation.cluster import cluster_split_search
from concept_formation.cluster import AIC, BIC, AICc, CU
from concept_formation.datasets import load_rb_wb_03
from concept_formation.datasets import load_rb_com_11
from concept_formation.datasets import load_rb_s_13
from concept_formation.preprocessor import ObjectVariablizer
import matplotlib.pyplot as plt
import numpy as np
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import
from __future__ import division



seed(5)

hueristics = [AIC, BIC, CU, AICc]


def calculate_aris(dataset):
    shuffle(dataset)
    dataset = dataset[:60]


    variablizer = ObjectVariablizer()