#!/usr/bin/python

import sys
import os
import json
import pickle

import copy
import subprocess

"""
This script is written to grab the logs from our GLRM runs and saved them onto our local machines for
later analysis.  For me, this is how I will call this script and the input arguments I will use:

python grabGLRMrunLogs.py /Users/wendycwong/Documents/PUBDEV_3454_GLRM/experimentdata/glrm_memory_10_25_16
    http://mr-0xa1:8080/view/wendy/job/glrm_memory_performance/
    /Users/wendycwong/Documents/PUBDEV_3454_GLRM/experimentdata/glrm_memory_10_25_16
    java_0_0.out_airline.txt java_1_0.out_milsongs.txt pyunit_airlines_performance_profile.py.out.txt
    pyunit_milsongs_performance_profile.py.out.txt 8 26
"""

# --------------------------------------------------------------------
# Main program
# --------------------------------------------------------------------
    # directory where we are running out code from h2o-3/scripts
g_test_root_dir = os.path.dirname(os.path.realpath(__file__))

g_airline_py_tail = "/artifact/h2o-py/GLRM_performance_tests/results/pyunit_airlines_performance_profile.py.out.txt"
g_milsongs_py_tail = "/artifact/h2o-py/GLRM_performance_tests/results/pyunit_milsong_performance_profile.py.out.txt"
g_airline_java_tail = "/artifact/h2o-py/GLRM_performance_tests/results/java_0_0.out.txt"
g_milsongs_java_tail = "/artifact/h2o-py/GLRM_performance_tests/results/java_1_0.out.txt"

    # base directory where different logs will be stored under different build directory, e.g. glrm_memory_10_25_16
g_log_base_dir = ""
g_airline_java = ""     # name of java log file for airline runs you want to store as, e.g. java_0_0.out_airline.txt
g_milsongs_java = ""    # name of java log file for milsongs runs you want to store as, e.g. java_1_0.out_milsongs.txt
    # name of python run results for airline runs to store as, e.g. pyunit_airlines_performance_profile.py.out.txt
g_airline_python = ""
    # name of python run results for airline runs to store as, e.g. pyunit_milsong_performance_profile.py.out.txt
g_milsongs_python = ""
g_jenkins_url = ""  # url to your jenkins job, e.g.http://mr-0xa1:8080/view/wendy/job/glrm_original_performance/
g_start_build_number = 0    # starting build number to collect your data
g_end_build_number = 1      # ending build number to collect your data

def get_file_out(build_index, python_name, jenkin_name):
    """
    This function will grab one log file from Jenkins and save it to local user directory
    :param g_jenkins_url:
    :param build_index:
    :param airline_java:
    :param airline_java_tail:
    :return:
    """
    global g_log_base_dir
    global g_jenkins_url
    global g_log_base_dir

    directoryB = g_log_base_dir+'/Build'+str(build_index)

    if not(os.path.isdir(directoryB)): # make directory if it does not exist
        os.mkdir(directoryB)

    url_string_full = g_jenkins_url+'/'+str(build_index)+jenkin_name
    filename = os.path.join(directoryB, python_name)

    full_command = 'curl ' + url_string_full + ' > ' + filename
    subprocess.call(full_command,shell=True)

def main(argv):
    """
    Main program.

    @return: none
    """
    global g_log_base_dir
    global g_airline_java
    global g_milsongs_java
    global g_airline_python
    global g_milsongs_python
    global g_jenkins_url
    global g_airline_py_tail
    global g_milsongs_py_tail
    global g_airline_java_tail
    global g_milsongs_java_tail

    if len(argv) < 9:
        print "python grabGLRMrunLogs logsBaseDirectory airlineJavaFileNameWithPath milsongJavaFileNameWithPath " \
              "airlinePyunitWithPath airlinePyunitWithPath jenkinsJobURL startBuild# endBuild#.\n"
        sys.exit(1)
    else:   # we may be in business
#        g_script_name = os.path.basename(argv[0])   # get name of script being run.
            # base directory where all logs will be collected according to build #
        g_log_base_dir = argv[1]
        g_jenkins_url = argv[2]
        g_airline_java = argv[3]
        g_milsongs_java = argv[4]
        g_airline_python = argv[5]
        g_milsongs_python = argv[6]
        start_number = int(argv[7])
        end_number = int(argv[8])

        if (start_number > end_number):
            print "startBuild# must be <= end_number"
            sys.exit(1)
        else:
            for build_index in range(start_number, end_number+1):   # grab log info for all builds
                # copy the java jobs
                get_file_out(build_index, g_airline_java, g_airline_java_tail)
                get_file_out(build_index, g_milsongs_java, g_milsongs_java_tail)

                # copy the pyunit jobs
                get_file_out(build_index, g_airline_python, g_airline_py_tail)
                get_file_out(build_index, g_milsongs_python, g_milsongs_py_tail)

if __name__ == "__main__":
    main(sys.argv)


# -*- encoding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
from h2o.utils.compatibility import *  # NOQA
from h2o.utils.compatibility import viewitems
from h2o.utils.typechecks import assert_is_type
from .model_base import ModelBase

class H2OBinomialModel(ModelBase):

    def F1(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the F1 value for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where
        the keys are "train", "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the F1 value for the training data.
        :param bool valid: If True, return the F1 value for the validation data.
        :param bool xval: If True, return the F1 value for each of the cross-validated splits.

        :returns: The F1 values for the specified key(s).

        :examples:
            >>> import h2o as ml
            >>> from h2o.estimators.gbm import H2OGradientBoostingEstimator
            >>> ml.init()
            >>> rows=[[1,2,3,4,0],[2,1,2,4,1],[2,1,4,2,1],[0,1,2,34,1],[2,3,4,1,0]]*50
            >>> fr = ml.H2OFrame(rows)
            >>> fr[4] = fr[4].asfactor()
            >>> model = H2OGradientBoostingEstimator(ntrees=10, max_depth=10, nfolds=4)
            >>> model.train(x=range(4), y=4, training_frame=fr)
            >>> model.F1(train=True)
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("f1", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def F2(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the F2 for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the F2 value for the training data.
        :param bool valid: If True, return the F2 value for the validation data.
        :param bool xval: If True, return the F2 value for each of the cross-validated splits.

        :returns: The F2 values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("f2", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def F0point5(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the F0.5 for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the F0.5 value for the training data.
        :param bool valid: If True, return the F0.5 value for the validation data.
        :param bool xval: If True, return the F0.5 value for each of the cross-validated splits.

        :returns: The F0.5 values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("f0point5", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def accuracy(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the accuracy for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the accuracy value for the training data.
        :param bool valid: If True, return the accuracy value for the validation data.
        :param bool xval: If True, return the accuracy value for each of the cross-validated splits.

        :returns: The accuracy values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("accuracy", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def error(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the error for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the error value for the training data.
        :param bool valid: If True, return the error value for the validation data.
        :param bool xval: If True, return the error value for each of the cross-validated splits.

        :returns: The error values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else [[acc[0], 1 - acc[1]] for acc in v.metric("accuracy", thresholds=thresholds)]
        return list(m.values())[0] if len(m) == 1 else m

    def precision(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the precision for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the precision value for the training data.
        :param bool valid: If True, return the precision value for the validation data.
        :param bool xval: If True, return the precision value for each of the cross-validated splits.

        :returns: The precision values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("precision", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def tpr(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the True Positive Rate for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the TPR value for the training data.
        :param bool valid: If True, return the TPR value for the validation data.
        :param bool xval: If True, return the TPR value for each of the cross-validated splits.

        :returns: The TPR values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("tpr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def tnr(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the True Negative Rate for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the TNR value for the training data.
        :param bool valid: If True, return the TNR value for the validation data.
        :param bool xval: If True, return the TNR value for each of the cross-validated splits.

        :returns: The TNR values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("tnr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def fnr(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the False Negative Rates for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the FNR value for the training data.
        :param bool valid: If True, return the FNR value for the validation data.
        :param bool xval: If True, return the FNR value for each of the cross-validated splits.

        :returns: The FNR values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("fnr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def fpr(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the False Positive Rates for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the FPR value for the training data.
        :param bool valid: If True, return the FPR value for the validation data.
        :param bool xval: If True, return the FPR value for each of the cross-validated splits.

        :returns: The FPR values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("fpr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def recall(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the recall for a set of thresholds (aka True Positive Rate).

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the recall value for the training data.
        :param bool valid: If True, return the recall value for the validation data.
        :param bool xval: If True, return the recall value for each of the cross-validated splits.

        :returns: The recall values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("tpr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def sensitivity(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the sensitivity for a set of thresholds (aka True Positive Rate or Recall).

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the sensitivity value for the training data.
        :param bool valid: If True, return the sensitivity value for the validation data.
        :param bool xval: If True, return the sensitivity value for each of the cross-validated splits.

        :returns: The sensitivity values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("tpr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def fallout(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the fallout for a set of thresholds (aka False Positive Rate).

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the fallout value for the training data.
        :param bool valid: If True, return the fallout value for the validation data.
        :param bool xval: If True, return the fallout value for each of the cross-validated splits.

        :returns: The fallout values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("fpr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def missrate(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the miss rate for a set of thresholds (aka False Negative Rate).

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the miss rate value for the training data.
        :param bool valid: If True, return the miss rate value for the validation data.
        :param bool xval: If True, return the miss rate value for each of the cross-validated splits.

        :returns: The miss rate values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("fnr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def specificity(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the specificity for a set of thresholds (aka True Negative Rate).

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the specificity value for the training data.
        :param bool valid: If True, return the specificity value for the validation data.
        :param bool xval: If True, return the specificity value for each of the cross-validated splits.

        :returns: The specificity values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("tnr", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def mcc(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the MCC for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the MCC value for the training data.
        :param bool valid: If True, return the MCC value for the validation data.
        :param bool xval: If True, return the MCC value for each of the cross-validated splits.

        :returns: The MCC values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric("absolute_mcc", thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def max_per_class_error(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the max per class error for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the max per class error value for the training data.
        :param bool valid: If True, return the max per class error value for the validation data.
        :param bool xval: If True, return the max per class error value for each of the cross-validated splits.

        :returns: The max per class error values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else [[mpca[0], 1 - mpca[1]] for mpca in v.metric(
                "min_per_class_accuracy", thresholds=thresholds)]
        return list(m.values())[0] if len(m) == 1 else m

    def mean_per_class_error(self, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the mean per class error for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the mean per class error value for the training data.
        :param bool valid: If True, return the mean per class error value for the validation data.
        :param bool xval: If True, return the mean per class error value for each of the cross-validated splits.

        :returns: The mean per class error values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            if v is None:
                m[k] = None
            else:
                m[k] = [[mpca[0], 1 - mpca[1]] for mpca in v.metric("mean_per_class_accuracy", thresholds=thresholds)]
        return list(m.values())[0] if len(m) == 1 else m

    def metric(self, metric, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the metric value for a set of thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param str metric: name of the metric to retrieve.
        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the metric value for the training data.
        :param bool valid: If True, return the metric value for the validation data.
        :param bool xval: If True, return the metric value for each of the cross-validated splits.

        :returns: The metric values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.metric(metric, thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def plot(self, timestep="AUTO", metric="AUTO", server=False, **kwargs):
        """
        Plot training set (and validation set if available) scoring history for an H2OBinomialModel.

        The timestep and metric arguments are restricted to what is available in its scoring history.

        :param str timestep: A unit of measurement for the x-axis.
        :param str metric: A unit of measurement for the y-axis.
        :param bool server: if True, then generate the image inline (using matplotlib's "Agg" backend)
        """
        assert_is_type(metric, "AUTO", "logloss", "auc", "classification_error", "rmse")
        if self._model_json["algo"] in ("deeplearning", "deepwater", "xgboost", "drf", "gbm"):
            if metric == "AUTO":
                metric = "logloss"
        self._plot(timestep=timestep, metric=metric, server=server)

    def roc(self, train=False, valid=False, xval=False):
        """
        Return the coordinates of the ROC curve for a given set of data.

        The coordinates are two-tuples containing the false positive rates as a list and true positive rates as a list.
        If all are False (default), then return is the training data. If more than one ROC
        curve is requested, the data is returned as a dictionary of two-tuples.

        :param bool train: If True, return the ROC value for the training data.
        :param bool valid: If True, return the ROC value for the validation data.
        :param bool xval: If True, return the ROC value for each of the cross-validated splits.

        :returns: The ROC values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):

            if v is not None:
                m[k] = (v.fprs, v.tprs)
        return list(m.values())[0] if len(m) == 1 else m

    def gains_lift(self, train=False, valid=False, xval=False):
        """
        Get the Gains/Lift table for the specified metrics.

        If all are False (default), then return the training metric Gains/Lift table.
        If more than one options is set to True, then return a dictionary of metrics where t
        he keys are "train", "valid", and "xval".

        :param bool train: If True, return the gains lift value for the training data.
        :param bool valid: If True, return the gains lift value for the validation data.
        :param bool xval: If True, return the gains lift value for each of the cross-validated splits.

        :returns: The gains lift values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.gains_lift()
        return list(m.values())[0] if len(m) == 1 else m

    def confusion_matrix(self, metrics=None, thresholds=None, train=False, valid=False, xval=False):
        """
        Get the confusion matrix for the specified metrics/thresholds.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the
        keys are "train", "valid", and "xval"

        :param metrics: One or more of ``"min_per_class_accuracy"``, ``"absolute_mcc"``, ``"tnr"``, ``"fnr"``,
            ``"fpr"``, ``"tpr"``, ``"precision"``, ``"accuracy"``, ``"f0point5"``, ``"f2"``, ``"f1"``.
        :param thresholds: If None, then the thresholds in this set of metrics will be used.
        :param bool train: If True, return the confusion matrix value for the training data.
        :param bool valid: If True, return the confusion matrix value for the validation data.
        :param bool xval: If True, return the confusion matrix value for each of the cross-validated splits.

        :returns: The confusion matrix values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.confusion_matrix(metrics=metrics, thresholds=thresholds)
        return list(m.values())[0] if len(m) == 1 else m

    def find_threshold_by_max_metric(self, metric, train=False, valid=False, xval=False):
        """
        If all are False (default), then return the training metric value.

        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param str metric: The metric to search for.
        :param bool train: If True, return the find threshold by max metric value for the training data.
        :param bool valid: If True, return the find threshold by max metric value for the validation data.
        :param bool xval: If True, return the find threshold by max metric value for each of the cross-validated splits.

        :returns: The find threshold by max metric values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.find_threshold_by_max_metric(metric)
        return list(m.values())[0] if len(m) == 1 else m

    def find_idx_by_threshold(self, threshold, train=False, valid=False, xval=False):
        """
        Retrieve the index in this metric's threshold list at which the given threshold is located.

        If all are False (default), then return the training metric value.
        If more than one options is set to True, then return a dictionary of metrics where the keys are "train",
        "valid", and "xval".

        :param float threshold: Threshold value to search for in the threshold list.
        :param bool train: If True, return the find idx by threshold value for the training data.
        :param bool valid: If True, return the find idx by threshold value for the validation data.
        :param bool xval: If True, return the find idx by threshold value for each of the cross-validated splits.

        :returns: The find idx by threshold values for the specified key(s).
        """
        tm = ModelBase._get_metrics(self, train, valid, xval)
        m = {}
        for k, v in viewitems(tm):
            m[k] = None if v is None else v.find_idx_by_threshold(threshold)
        return list(m.values())[0] if len(m) == 1 else m


#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# This file is auto-generated by h2o-3/h2o-bindings/bin/gen_python.py
# Copyright 2016 H2O.ai;  Apache License Version 2.0 (see LICENSE for details)
#
from __future__ import absolute_import, division, print_function, unicode_literals

from h2o.estimators.estimator_base import H2OEstimator
from h2o.exceptions import H2OValueError
from h2o.frame import H2OFrame
from h2o.utils.typechecks import assert_is_type, Enum, numeric

class H2OWord2vecEstimator(H2OEstimator):
    """
    Word2Vec

    """

    algo = "word2vec"

    def __init__(self, **kwargs):
        super(H2OWord2vecEstimator, self).__init__()
        self._parms = {}
        names_list = {"model_id", "training_frame", "min_word_freq", "word_model", "norm_model", "vec_size",
                      "window_size", "sent_sample_rate", "init_learning_rate", "epochs", "pre_trained",
                      "max_runtime_secs", "export_checkpoints_dir"}
        if "Lambda" in kwargs: kwargs["lambda_"] = kwargs.pop("Lambda")
        for pname, pvalue in kwargs.items():
            if pname == 'model_id':
                self._id = pvalue
                self._parms["model_id"] = pvalue
            elif pname == 'pre_trained':
                setattr(self, pname, pvalue)
                self._determine_vec_size();
                setattr(self, 'vec_size', self.vec_size)
            elif pname in names_list:
                # Using setattr(...) will invoke type-checking of the arguments
                setattr(self, pname, pvalue)
            else:
                raise H2OValueError("Unknown parameter %s = %r" % (pname, pvalue))

    @property
    def training_frame(self):
        """
        Id of the training data frame.

        Type: ``H2OFrame``.
        """
        return self._parms.get("training_frame")

    @training_frame.setter
    def training_frame(self, training_frame):
        assert_is_type(training_frame, None, H2OFrame)
        self._parms["training_frame"] = training_frame

    @property
    def min_word_freq(self):
        """
        This will discard words that appear less than <int> times

        Type: ``int``  (default: ``5``).
        """
        return self._parms.get("min_word_freq")

    @min_word_freq.setter
    def min_word_freq(self, min_word_freq):
        assert_is_type(min_word_freq, None, int)
        self._parms["min_word_freq"] = min_word_freq

    @property
    def word_model(self):
        """
        Use the Skip-Gram model

        One of: ``"skip_gram"``  (default: ``"skip_gram"``).
        """
        return self._parms.get("word_model")

    @word_model.setter
    def word_model(self, word_model):
        assert_is_type(word_model, None, Enum("skip_gram"))
        self._parms["word_model"] = word_model

    @property
    def norm_model(self):
        """
        Use Hierarchical Softmax

        One of: ``"hsm"``  (default: ``"hsm"``).
        """
        return self._parms.get("norm_model")

    @norm_model.setter
    def norm_model(self, norm_model):
        assert_is_type(norm_model, None, Enum("hsm"))
        self._parms["norm_model"] = norm_model

    @property
    def vec_size(self):
        """
        Set size of word vectors

        Type: ``int``  (default: ``100``).
        """
        return self._parms.get("vec_size")

    @vec_size.setter
    def vec_size(self, vec_size):
        assert_is_type(vec_size, None, int)
        self._parms["vec_size"] = vec_size

    @property
    def window_size(self):
        """
        Set max skip length between words

        Type: ``int``  (default: ``5``).
        """
        return self._parms.get("window_size")

    @window_size.setter
    def window_size(self, window_size):
        assert_is_type(window_size, None, int)
        self._parms["window_size"] = window_size

    @property
    def sent_sample_rate(self):
        """
        Set threshold for occurrence of words. Those that appear with higher frequency in the training data
        will be randomly down-sampled; useful range is (0, 1e-5)

        Type: ``float``  (default: ``0.001``).
        """
        return self._parms.get("sent_sample_rate")

    @sent_sample_rate.setter
    def sent_sample_rate(self, sent_sample_rate):
        assert_is_type(sent_sample_rate, None, float)
        self._parms["sent_sample_rate"] = sent_sample_rate

    @property
    def init_learning_rate(self):
        """
        Set the starting learning rate

        Type: ``float``  (default: ``0.025``).
        """
        return self._parms.get("init_learning_rate")

    @init_learning_rate.setter
    def init_learning_rate(self, init_learning_rate):
        assert_is_type(init_learning_rate, None, float)
        self._parms["init_learning_rate"] = init_learning_rate

    @property
    def epochs(self):
        """
        Number of training iterations to run

        Type: ``int``  (default: ``5``).
        """
        return self._parms.get("epochs")

    @epochs.setter
    def epochs(self, epochs):
        assert_is_type(epochs, None, int)
        self._parms["epochs"] = epochs

    @property
    def pre_trained(self):
        """
        Id of a data frame that contains a pre-trained (external) word2vec model

        Type: ``H2OFrame``.
        """
        return self._parms.get("pre_trained")

    @pre_trained.setter
    def pre_trained(self, pre_trained):
        assert_is_type(pre_trained, None, H2OFrame)
        self._parms["pre_trained"] = pre_trained

    @property
    def max_runtime_secs(self):
        """
        Maximum allowed runtime in seconds for model training. Use 0 to disable.

        Type: ``float``  (default: ``0``).
        """
        return self._parms.get("max_runtime_secs")

    @max_runtime_secs.setter
    def max_runtime_secs(self, max_runtime_secs):
        assert_is_type(max_runtime_secs, None, numeric)
        self._parms["max_runtime_secs"] = max_runtime_secs

    @property
    def export_checkpoints_dir(self):
        """
        Automatically export generated models to this directory.

        Type: ``str``.
        """
        return self._parms.get("export_checkpoints_dir")

    @export_checkpoints_dir.setter
    def export_checkpoints_dir(self, export_checkpoints_dir):
        assert_is_type(export_checkpoints_dir, None, str)
        self._parms["export_checkpoints_dir"] = export_checkpoints_dir

    def _requires_training_frame(self):
        """
        Determines if Word2Vec algorithm requires a training frame.
        :return: False.
        """
        return False

    @staticmethod
    def from_external(external=H2OFrame):
        """
        Creates new H2OWord2vecEstimator based on an external model.
        :param external: H2OFrame with an external model
        :return: H2OWord2vecEstimator instance representing the external model
        """
        w2v_model = H2OWord2vecEstimator(pre_trained=external)
        w2v_model.train()
        return w2v_model

    def _determine_vec_size(self):
        """
        Determines vec_size for a pre-trained model after basic model verification.
        """
        first_column = self.pre_trained.types[self.pre_trained.columns[0]]

        if first_column != 'string':
            raise H2OValueError("First column of given pre_trained model %s is required to be a String",
                                self.pre_trained.frame_id)

        if list(self.pre_trained.types.values()).count('string') > 1:
            raise H2OValueError("There are multiple columns in given pre_trained model %s with a String type.",
                                self.pre_trained.frame_id)

        self.vec_size = self.pre_trained.dim[1] - 1;


# -*- encoding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals

from h2o.model.model_base import ModelBase
from h2o.utils.compatibility import *  # NOQA
from h2o.utils.shared_utils import _colmean

class H2ORegressionModel(ModelBase):
    def _make_model(self):
        return H2ORegressionModel()

    def plot(self, timestep="AUTO", metric="AUTO", **kwargs):
        """
        Plots training set (and validation set if available) scoring history for an H2ORegressionModel. The timestep
        and metric arguments are restricted to what is available in its scoring history.

        :param timestep: A unit of measurement for the x-axis.
        :param metric: A unit of measurement for the y-axis.
        :returns: A scoring history plot.
        """

        if self._model_json["algo"] in ("deeplearning", "deepwater", "xgboost", "drf", "gbm"):
            if metric == "AUTO":
                metric = "rmse"
            elif metric not in ("rmse", "deviance", "mae"):
                raise ValueError("metric for H2ORegressionModel must be one of: AUTO, rmse, deviance, or mae")

        self._plot(timestep=timestep, metric=metric, **kwargs)

def _mean_var(frame, weights=None):
    """
    Compute the (weighted) mean and variance.

    :param frame: Single column H2OFrame
    :param weights: optional weights column
    :returns: The (weighted) mean and variance
    """
    return _colmean(frame), frame.var()

def h2o_mean_absolute_error(y_actual, y_predicted, weights=None):
    """
    Mean absolute error regression loss.

    :param y_actual: H2OFrame of actual response.
    :param y_predicted: H2OFrame of predicted response.
    :param weights: (Optional) sample weights
    :returns: mean absolute error loss (best is 0.0).
    """
    ModelBase._check_targets(y_actual, y_predicted)
    return _colmean((y_predicted - y_actual).abs())

def h2o_mean_squared_error(y_actual, y_predicted, weights=None):
    """
    Mean squared error regression loss

    :param y_actual: H2OFrame of actual response.
    :param y_predicted: H2OFrame of predicted response.
    :param weights: (Optional) sample weights
    :returns: mean squared error loss (best is 0.0).
    """
    ModelBase._check_targets(y_actual, y_predicted)
    return _colmean((y_predicted - y_actual) ** 2)

def h2o_median_absolute_error(y_actual, y_predicted):
    """
    Median absolute error regression loss

    :param y_actual: H2OFrame of actual response.
    :param y_predicted: H2OFrame of predicted response.
    :returns: median absolute error loss (best is 0.0)
    """
    ModelBase._check_targets(y_actual, y_predicted)
    return (y_predicted - y_actual).abs().median()

def h2o_explained_variance_score(y_actual, y_predicted, weights=None):
    """
    Explained variance regression score function.

    :param y_actual: H2OFrame of actual response.
    :param y_predicted: H2OFrame of predicted response.
    :param weights: (Optional) sample weights
    :returns: the explained variance score.
    """
    ModelBase._check_targets(y_actual, y_predicted)

    _, numerator = _mean_var(y_actual - y_predicted, weights)
    _, denominator = _mean_var(y_actual, weights)
    if denominator == 0.0:
        return 1. if numerator == 0 else 0.  # 0/0 => 1, otherwise, 0
    return 1 - numerator / denominator

def h2o_r2_score(y_actual, y_predicted, weights=1.):
    """
    R-squared (coefficient of determination) regression score function

    :param y_actual: H2OFrame of actual response.
    :param y_predicted: H2OFrame of predicted response.
    :param weights: (Optional) sample weights
    :returns: R-squared (best is 1.0, lower is worse).
    """
    ModelBase._check_targets(y_actual, y_predicted)
    numerator = (weights * (y_actual - y_predicted) ** 2).sum()
    denominator = (weights * (y_actual - _colmean(y_actual)) ** 2).sum()

    if denominator == 0.0:
        return 1. if numerator == 0. else 0.  # 0/0 => 1, else 0
    return 1 - numerator / denominator


# -*- encoding: utf-8 -*-
"""
Utilities for checking types and validity of variables.

The primary purpose of this module is to facilitate testing whether some variable has the desired type. Such testing
comes in two flavors: as the ``is_type()`` function, and the ``assert_is_type()`` assert. The latter should be used
for checking types of input variables in public methods / functions. Its advantage over simple ``assert is_type()``
construct is that: firstly it composes the error message in case of failure, and secondly it throws an
:class:`H2OTypeError` exception instead of an ``AssertionError``, which is both more precise, and more user-friendly
(in the sense that it produces much nicer error message).

General interface of this module is:

    assert_is_type(var, type1, ..., typeN)
    assert_satisfies(var, condition)
    assert_matches(var, regular_expression)
    if is_type(var, type1, ..., typeN): ...

The ``typeI`` items here deserve a more thorough explanation. They could be:

    # Plain types
    assert_is_type(flag, bool) # note that in Python ``bool`` is a subclass of ``int``
    assert_is_type(port, int)  # ``int`` and ``str`` will work on Py2 as if you were on Py3
    assert_is_type(text, str)  # (i.e. they'll also match ``long`` and ``unicode`` respectively)
    assert_is_type(hls, H2OLocalServer)
    assert_is_type(arr, list, tuple, set)
    assert_is_type(json, dict)
    assert_is_type(asdffkj, object)  # in Python ``object`` is equivalent to ``any``

    # "numeric" is a special type, meaning ``U(int, float)``
    assert_is_type(x, numeric)

    # Literals are matched by value
    assert_is_type(v, None)
    assert_is_type(scheme, "http", "https", "ftp")
    assert_is_type(dir, -1, 0, 1)

    # Testing lists
    assert_is_type(arr, [numeric])   # List of numbers
    assert_is_type(arr2, [[float]])  # List of lists of floats (i.e. a 2-dimensional array)
    assert_is_type(arr, list)        # Generic list, same as ``[object]``
    assert_is_type(arr, [int, str])  # List of either ints or strings, same as ``[U(int, str)]``

    # Sets follow the same semantic as lists, only use curly braces ``{}`` instead of square ones
    assert_is_type(s, {str})  # Set of string values
    assert_is_type(s, set)    # Generic set, same as ``{object}``

    # Tuples
    assert_is_type(t, tuple)  # any tuple
    assert_is_type(t, (int, int, int, [str]))  # Test for a 4-tuple having first 3 ints and last an array of strings
    assert_is_type(t, Tuple(int))  # tuple of ints of arbitrary length

    # Dictionaries
    assert_is_type(t, dict)  # any dictionary
    assert_is_type(cols, {str: H2OFrame})  # Same as Map<str, H2OFrame> in Java
    assert_is_type(vals, {str: U(numeric, str)})  # Dictionary with string keys and ``U(numeric, str)`` values
    # Dictionary whose key-value pairs match either ``(ktype1, vtype1)``, or ..., or ``(ktypeN, vtypeN)``
    assert_is_type(foo, {ktype1: vtype1, ..., ktypeN: vtypeN})
    # Here we test whether ``xyz`` has keys of the specified types. For example ``xyz = {"foo": 1, "bar": 2}`` will
    # pass the test, whereas ``xyz = {"foo": 0, "kaboom": None}`` will not.
    assert_is_type(xyz, {"foo": int, "bar": U(int, float, None), "baz": bool})

    # Functions and lambda-expressions
    assert_is_type(progress, I(numeric, lambda x: 0 <= x <= 1))
    assert_is_type(x, None, "N/A", I(float, math.isnan))
    assert_is_type(matrix, I([[numeric]], lambda v: all(len(vi) == len(v[0]) for vi in v)))
    assert_is_type(a, lambda t: issubclass(t, object))

As you have noticed, we define a number of special classes to facilitate type construction::

    # Union / intersection / negation
    U(str, int, float)     # denotes a type which can be either a string, or an integer, or a float
    I(Widget, Renderable)  # denotes a class which is both a Widget and a Renderable (it uses multiple inheritance)
    NOT(None)              # denotes any type except None
    # Intersection and negation are best used together:
    I(int, NOT(0))         # integer which is not zero

    # ``Tuple`` may be used to denote tuples with variable number of arguments (same as lists)
    Tuple(int)             # tuple with any number of integer elements

    # ``Dict`` is a dictionary type which should match exactly (i.e. each key must be present in tested variable)
    Dict(error=str)        # dictionary with only one key "error" with string value

    # ``BoundInt``, ``BoundNumeric`` are numbers that are bound from below below and/or above
    BoundInt(1, 100)
    BoundNumeric(0, 1)

    # Lazy class references: these types can be used anywhere without having to load the corresponding modules. Their
    # resolution is deferred until the run time, and if the module cannot be loaded no exception will be raised (but
    # of course the type check will fail).
    h2oframe          # Same as H2OFrame
    pandas_dataframe  # Same as pandas.DataFrame
    numpy_ndarray     # Same as numpy.ndarray

    # An enum. This is similar to a mere union of strings, except that we match case-insensitively
    Enum("case1", "case2", ...)

:copyright: (c) 2016 H2O.ai
:license:   Apache License Version 2.0 (see LICENSE for details)
"""
from __future__ import absolute_import, division, print_function, unicode_literals

import importlib
import io
import re
import sys
import tokenize
from types import BuiltinFunctionType, FunctionType

from h2o.exceptions import H2OTypeError, H2OValueError
from h2o.utils.compatibility import *  # NOQA
from h2o.utils.compatibility import PY2, viewitems

__all__ = ("U", "I", "NOT", "Tuple", "Dict", "MagicType", "BoundInt", "BoundNumeric", "Enum",
           "numeric", "h2oframe", "pandas_dataframe", "numpy_ndarray", "scipy_sparse",
           "assert_is_type", "assert_matches", "assert_satisfies", "is_type")

if PY2:
    # noinspection PyProtectedMember
    from h2o.utils.compatibility import _native_unicode, _native_long
    _str_type = (str, _native_unicode)
    _int_type = (int, _native_long)
    _num_type = (int, _native_long, float)
    _primitive_type = (str, int, float, bool, _native_unicode, _native_long)
else:
    _str_type = str
    _int_type = int
    _num_type = (int, float)
    _primitive_type = (str, int, float, bool, bytes)

def is_type(var, *args):
    """
    Return True if the variable is of the specified type(s) and False otherwise.

    This function is similar to :func:`assert_is_type`, however instead of raising an error when the variable does not
    match the provided type, it merely returns False.
    """
    return _check_type(var, U(*args))

#-----------------------------------------------------------------------------------------------------------------------
# Special types
#-----------------------------------------------------------------------------------------------------------------------

class MagicType(object):
    """Abstract "special" type."""

    def check(self, var):
        """Return True if the variable matches this type, and False otherwise."""

    def name(self, src=None):
        """Return string representing the name of this type."""
        return "<%s>" % self.__class__.__name__

class U(MagicType):
    """
    Union of types.

    We say that ``type(x) is U(type1, ..., typeN)`` if type of ``x`` is one of ``type1``, ..., ``typeN``.

    This is a helper class that can be used in situations where unions are otherwise hard or impossible to declare.
    For example, one doesn't need a union type to in the test ``assert_is_type(x, int, str, None)``, but if we want to
    have unions as dictionary keys we can say: ``assert_is_type(d, {str: U(int, bool, None)})``.
    """

    def __init__(self, *types):
        """Create an object representing the union of ``*types``."""
        assert len(types) >= 1
        self._types = types

    def check(self, var):
        """Return True if the variable matches this type, and False otherwise."""
        return any(_check_type(var, tt) for tt in self._types)

    def name(self, src=None):
        """Return string representing the name of this type."""
        res = [_get_type_name(tt, src) for tt in self._types]
        if len(res) == 2 and "None" in res:
            res.remove("None")
            return "?" + res[0]
        else:
            return " | ".join(res)

class I(MagicType):
    """
    Intersection of types.

    We say that ``type(x) is I(type1, ..., typeN)`` if type of ``x`` is all of ``type1``, ..., ``typeN``. Arguably,
    this is much less useful concept than the union, however it may occasionally be used for classes with
    multiple inheritance.
    """

    def __init__(self, *types):
        """Create an intersection of types."""
        assert len(types) >= 1
        self._types = types

    def check(self, var):
        """Return True if the variable matches this type, and False otherwise."""
        return all(_check_type(var, tt) for tt in self._types)

    def name(self, src=None):
        """Return string representing the name of this type."""
        return " & ".join(_get_type_name(tt, src) for tt in self._types)

class NOT(MagicType):
    """
    Negation of a type.

    This type matches if and only if the variable is *not* of any of the provided types.
    """

    def __init__(self, *types):
        """Create a negation of types."""
        assert len(types) >= 1
        self._types = types

    def check(self, var):
        """Return True if the variable does not match any of the types, and False otherwise."""
        return not any(_check_type(var, tt) for tt in self._types)

    def name(self, src=None):
        """Return string representing the name of this type."""
        if len(self._types) > 1:
            return "!(%s)" % str("|".join(_get_type_name(tt, src) for tt in self._types))
        else:
            return "!" + _get_type_name(self._types[0], src)

class Tuple(MagicType):
    """Tuple of arbitrary length and having elements of same type(s)."""

    def __init__(self, *types):
        """Create a tuple of types."""
        assert len(types) >= 1
        self._element_type = types[0] if len(types) == 1 else U(*types)

    def check(self, var):
        """Return True if the variable matches this type, and False otherwise."""
        return isinstance(var, tuple) and all(_check_type(t, self._element_type) for t in var)

    def name(self, src=None):
        """Return string representing the name of this type."""
        return "(*%s)" % _get_type_name(self._element_type, src)

class Dict(MagicType):
    """
    Dictionary with strict shape signature.

    Simple dict literals can be used to specify dictionary types where keys may be optionally present, but when they
    are they should match the specified types. For example, ``{"foo": int, "bar": str}`` is a valid type for ``{}`` or
    ``{"foo": 3}`` or ``{"bar": "^_^"}`` or ``{"foo": 0, "bar": ""}``. On the other hand, ``Dict(foo=int, bar=str)``
    specifies a dictionary type where both keys "foo" and "bar" must be present and their values must be of integer
    and string types respectively.

    As a convenience, we assume that any key which is missing in the variable being tested is equivalent to
    ``value = None``, therefore if the Dict type allows for some key to be None, then it can also be missing.
    """

    def __init__(self, **kwargs):
        """Create a Dictionary object."""
        self._types = kwargs

    def check(self, var):
        """Return True if the variable matches this type, and False otherwise."""
        if not isinstance(var, dict): return False
        if any(key not in self._types for key in var): return False
        for key, ktype in viewitems(self._types):
            val = var.get(key, None)
            if not _check_type(val, ktype):
                return False
        return True

    def name(self, src=None):
        """Return string representing the name of this type."""
        return "{%s}" % ", ".join("%s: %s" % (key, _get_type_name(ktype, src))
                                  for key, ktype in viewitems(self._types))

class BoundInt(MagicType):
    """Integer type bounded from below/above."""

    def __init__(self, lb=None, ub=None):
        """
        Create a BoundInt object.

        The type will match any integer that is within the specified bounds (inclusively). Thus, ``BoundInt(0, 100)``
        matches any integer in the range from 0 to 100 (including 100). Also ``BoundInt(1)`` is a positive integer,
        and ``BoundInt(None, -1)`` is a negative integer.

        :param lb: lower bound (can be None or int)
        :param ub: upper bound (can be None or int)
        """
        self._lower_bound = lb
        self._upper_bound = ub

    def check(self, var):
        """Return True if the variable matches the specified type."""
        return (isinstance(var, _int_type) and
                (self._lower_bound is None or var >= self._lower_bound) and
                (self._upper_bound is None or var <= self._upper_bound))

    def name(self, src=None):
        """Return string representing the name of this type."""
        if self._upper_bound is None and self._lower_bound is None: return "int"
        if self._upper_bound is None:
            if self._lower_bound == 1: return "int>0"
            return "int≥%d" % self._lower_bound
        if self._lower_bound is None:
            return "int≤%d" % self._upper_bound
        return "int[%d…%d]" % (self._lower_bound, self._upper_bound)

class BoundNumeric(MagicType):
    """Numeric type bounded from below/above."""

    def __init__(self, lb=None, ub=None):
        """
        Create a BoundNumeric object.

        :param lb: lower bound (can be None or numeric)
        :param ub: upper bound (can be None or numeric)
        """
        self._lower_bound = lb
        self._upper_bound = ub

    def check(self, var):
        """Return True if the variable matches the specified type."""
        return (isinstance(var, _num_type) and
                (self._lower_bound is None or var >= self._lower_bound) and
                (self._upper_bound is None or var <= self._upper_bound))

    def name(self, src=None):
        """Return string representing the name of this type."""
        if self._upper_bound is None and self._lower_bound is None: return "numeric"
        if self._upper_bound is None: return "numeric≥%d" % self._lower_bound
        if self._lower_bound is None: return "numeric≤%d" % self._upper_bound
        return "numeric[%d…%d]" % (self._lower_bound, self._upper_bound)

class _LazyClass(MagicType):
    """
    Helper class for lazy (on-demand) loading of some external classes.

    The purpose of this class is to provide a way of testing against classes that either cannot be loaded immediately,
    or may not even be present on user's system. This class must be taught how to load each class of interest, and
    therefore should not be exposed to the user code. Instead use "singletons" ``h2oframe``, ``pandas_dataframe``,
    ``numpy_ndarray`` (and perhaps others in future). Usage::

        from h2o.utils.typechecks import assert_is_type, h2oframe, pandas_dataframe, numpy_ndarray
        assert_is_type(fr, h2oframe, pandas_dataframe, numpy_ndarray)
    """

    def __init__(self, module, symbol, checker=None):
        """Lazily load ``symbol`` from ``module``."""
        self._module = module
        self._symbol = symbol
        self._checker = checker or (lambda value, t: isinstance(value, t))
        self._name = symbol if module.startswith("h2o") else module + "." + symbol
        # Initially this is None, but will contain the class object once the class is loaded. If the class cannot be
        # loaded, this will be set to False.
        self._class = None

    def check(self, var):
        """Return True if the variable matches this type, and False otherwise."""
        if self._class is None: self._init()
        return self._class and self._checker(var, self._class)

    def _init(self):
        try:
            mod = importlib.import_module(self._module)
            self._class = getattr(mod, self._symbol, False)
        except ImportError:
            self._class = False

    def name(self, src=None):
        """Return string representing the name of this type."""
        return self._name

_enum_mangle_pattern = re.compile(r"[^a-z]+")
def _enum_mangle(var):
    return _enum_mangle_pattern.sub("", var.lower())

class Enum(MagicType):
    """
    Enum-like type, however values are matched case-insensitively.
    """

    def __init__(self, *consts):
        """Initialize the Enum."""
        self._consts = set(_enum_mangle(c) for c in consts)

    def check(self, var):
        """Check whether the provided value is a valid enum constant."""
        if not isinstance(var, _str_type): return False
        return _enum_mangle(var) in self._consts

    def name(self, src=None):
        """Return string representing the name of this type."""
        return "Enum[%s]" % ", ".join('"%s"' % c for c in self._consts)

numeric = U(int, float)
"""Number, either integer or real."""

h2oframe = _LazyClass("h2o", "H2OFrame")
pandas_dataframe = _LazyClass("pandas", "DataFrame")
pandas_timestamp = _LazyClass("pandas", "Timestamp")
numpy_ndarray = _LazyClass("numpy", "ndarray")
numpy_datetime = _LazyClass("numpy", "datetime64")
scipy_sparse = _LazyClass("scipy.sparse", "issparse", lambda value, t: t(value))

#-----------------------------------------------------------------------------------------------------------------------
# Asserts
#-----------------------------------------------------------------------------------------------------------------------

def assert_is_type(var, *types, **kwargs):
    """
    Assert that the argument has the specified type.

    This function is used to check that the type of the argument is correct, otherwises it raises an H2OTypeError.
    See more details in the module's help.

    :param var: variable to check
    :param types: the expected types
    :param kwargs:
        message: override the error message
        skip_frames: how many local frames to skip when printing out the error.

    :raises H2OTypeError: if the argument is not of the desired type.
    """
    assert types, "The list of expected types was not provided"
    expected_type = types[0] if len(types) == 1 else U(*types)
    if _check_type(var, expected_type): return

    # Type check failed => Create a nice error message
    assert set(kwargs).issubset({"message", "skip_frames"}), "Unexpected keyword arguments: %r" % kwargs
    message = kwargs.get("message", None)
    skip_frames = kwargs.get("skip_frames", 1)
    args = _retrieve_assert_arguments()
    vname = args[0]
    etn = _get_type_name(expected_type, dump=", ".join(args[1:]))
    vtn = _get_type_name(type(var))
    raise H2OTypeError(var_name=vname, var_value=var, var_type_name=vtn, exp_type_name=etn, message=message,
                       skip_frames=skip_frames)

def assert_matches(v, regex):
    """
    Assert that string variable matches the provided regular expression.

    :param v: variable to check.
    :param regex: regular expression to check against (can be either a string, or compiled regexp).
    """
    m = re.match(regex, v)
    if m is None:
        vn = _retrieve_assert_arguments()[0]
        message = "Argument `{var}` (= {val!r}) did not match /{regex}/".format(var=vn, regex=regex, val=v)
        raise H2OValueError(message, var_name=vn, skip_frames=1)
    return m

def assert_satisfies(v, cond, message=None):
    """
    Assert that variable satisfies the provided condition.

    :param v: variable to check. Its value is only used for error reporting.
    :param bool cond: condition that must be satisfied. Should be somehow related to the variable ``v``.
    :param message: message string to use instead of the default.
    """
    if not cond:
        vname, vexpr = _retrieve_assert_arguments()
        if not message:
            message = "Argument `{var}` (= {val!r}) does not satisfy the condition {expr}" \
                      .format(var=vname, val=v, expr=vexpr)
        raise H2OValueError(message=message, var_name=vname, skip_frames=1)

#-----------------------------------------------------------------------------------------------------------------------
# Implementation details
#-----------------------------------------------------------------------------------------------------------------------

def _retrieve_assert_arguments():
    """
    Magic variable name retrieval.

    This function is designed as a helper for assert_is_type() function. Typically such assertion is used like this::

        assert_is_type(num_threads, int)

    If the variable `num_threads` turns out to be non-integer, we would like to raise an exception such as

        H2OTypeError("`num_threads` is expected to be integer, but got <str>")

    and in order to compose an error message like that, we need to know that the variables that was passed to
    assert_is_type() carries a name "num_threads". Naturally, the variable itself knows nothing about that.

    This is where this function comes in: we walk up the stack trace until the first frame outside of this
    file, find the original line that called the assert_is_type() function, and extract the variable name from
    that line. This is slightly fragile, in particular we assume that only one assert_is_type statement can be per line,
    or that this statement does not spill over multiple lines, etc.
    """
    try:
        raise RuntimeError("Catch me!")
    except RuntimeError:
        # Walk up the stacktrace until we are outside of this file
        tb = sys.exc_info()[2]
        assert tb.tb_frame.f_code.co_name == "_retrieve_assert_arguments"
        this_filename = tb.tb_frame.f_code.co_filename
        fr = tb.tb_frame
        while fr is not None and fr.f_code.co_filename == this_filename:
            fr = fr.f_back

        # Read the source file and tokenize it, extracting the expressions.
        try:
            with io.open(fr.f_code.co_filename, "r", encoding="utf-8") as f:
                # Skip initial lines that are irrelevant
                for i in range(fr.f_lineno - 1): next(f)
                # Create tokenizer
                g = tokenize.generate_tokens(f.readline)
                step = 0
                args_tokens = []
                level = 0
                for ttt in g:
                    if step == 0:
                        if ttt[0] != tokenize.NAME: continue
                        if not ttt[1].startswith("assert_"): continue
                        step = 1
                    elif step == 1:
                        assert ttt[0] == tokenize.OP and ttt[1] == "("
                        args_tokens.append([])
                        step = 2
                    elif step == 2:
                        if level == 0 and ttt[0] == tokenize.OP and ttt[1] == ",":
                            args_tokens.append([])
                        elif level == 0 and ttt[0] == tokenize.OP and ttt[1] == ")":
                            break
                        else:
                            if ttt[0] == tokenize.OP and ttt[1] in "([{": level += 1
                            if ttt[0] == tokenize.OP and ttt[1] in ")]}": level -= 1
                            assert level >= 0, "Parse error: parentheses level became negative"
                            args_tokens[-1].append(ttt)
                args = [tokenize.untokenize(at).strip().replace("\n", " ") for at in args_tokens]
                return args
        except IOError:
            return "arg",

def _check_type(var, vtype):
    """
    Return True if the variable is of the specified type, and False otherwise.

    :param var: variable to check
    :param vtype: expected variable's type
    """
    if vtype is None:
        return var is None
    if isinstance(vtype, _primitive_type):
        return var == vtype
    if vtype is str:
        return isinstance(var, _str_type)
    if vtype is int:
        return isinstance(var, _int_type)
    if vtype is numeric:
        return isinstance(var, _num_type)
    if isinstance(vtype, MagicType):
        return vtype.check(var)
    if isinstance(vtype, type):
        # ``vtype`` is a name of the class, or a built-in type such as "list", "tuple", etc
        return isinstance(var, vtype)
    if isinstance(vtype, list):
        # ``vtype`` is a list literal
        elem_type = U(*vtype)
        return isinstance(var, list) and all(_check_type(item, elem_type) for item in var)
    if isinstance(vtype, set):
        # ``vtype`` is a set literal
        elem_type = U(*vtype)
        return isinstance(var, set) and all(_check_type(item, elem_type) for item in var)
    if isinstance(vtype, tuple):
        # ``vtype`` is a tuple literal
        return (isinstance(var, tuple) and len(vtype) == len(var) and
                all(_check_type(var[i], vtype[i]) for i in range(len(vtype))))
    if isinstance(vtype, dict):
        # ``vtype`` is a dict literal
        ttkv = U(*viewitems(vtype))
        return isinstance(var, dict) and all(_check_type(kv, ttkv) for kv in viewitems(var))
    if isinstance(vtype, (FunctionType, BuiltinFunctionType)):
        return vtype(var)
    raise RuntimeError("Ivalid type %r in _check_type()" % vtype)

def _get_type_name(vtype, dump=None):
    """
    Return the name of the provided type.

        _get_type_name(int) == "integer"
        _get_type_name(str) == "string"
        _get_type_name(tuple) == "tuple"
        _get_type_name(Exception) == "Exception"
        _get_type_name(U(int, float, bool)) == "integer|float|bool"
        _get_type_name(U(H2OFrame, None)) == "?H2OFrame"
    """
    if vtype is None:
        return "None"
    if vtype is str:
        return "string"
    if vtype is int:
        return "integer"
    if vtype is numeric:
        return "numeric"
    if is_type(vtype, str):
        return '"%s"' % repr(vtype)[1:-1]
    if is_type(vtype, int):
        return str(vtype)
    if isinstance(vtype, MagicType):
        return vtype.name(dump)
    if isinstance(vtype, type):
        return vtype.__name__
    if isinstance(vtype, list):
        return "list(%s)" % _get_type_name(U(*vtype), dump)
    if isinstance(vtype, set):
        return "set(%s)" % _get_type_name(U(*vtype), dump)
    if isinstance(vtype, tuple):
        return "(%s)" % ", ".join(_get_type_name(item, dump) for item in vtype)
    if isinstance(vtype, dict):
        return "dict(%s)" % ", ".join("%s: %s" % (_get_type_name(tk, dump), _get_type_name(tv, dump))
                                      for tk, tv in viewitems(vtype))
    if isinstance(vtype, (FunctionType, BuiltinFunctionType)):
        if vtype.__name__ == "<lambda>":
            return _get_lambda_source_code(vtype, dump)
        else:
            return vtype.__name__
    raise RuntimeError("Unexpected `vtype`: %r" % vtype)

def _get_lambda_source_code(lambda_fn, src):
    """Attempt to find the source code of the ``lambda_fn`` within the string ``src``."""
    def gen_lambdas():
        def gen():
            yield src + "\n"

        g = gen()
        step = 0
        tokens = []
        for tok in tokenize.generate_tokens(getattr(g, "next", getattr(g, "__next__", None))):
            if step == 0:
                if tok[0] == tokenize.NAME and tok[1] == "lambda":
                    step = 1
                    tokens = [tok]
                    level = 0
            elif step == 1:
                if tok[0] == tokenize.NAME:
                    tokens.append(tok)
                    step = 2
                else:
                    step = 0
            elif step == 2:
                if tok[0] == tokenize.OP and tok[1] == ":":
                    tokens.append(tok)
                    step = 3
                else:
                    step = 0
            elif step == 3:
                if level == 0 and (tok[0] == tokenize.OP and tok[1] in ",)" or tok[0] == tokenize.ENDMARKER):
                    yield tokenize.untokenize(tokens).strip()
                    step = 0
                else:
                    tokens.append(tok)
                    if tok[0] == tokenize.OP:
                        if tok[1] in "[({": level += 1
                        if tok[1] in "])}": level -= 1
        assert not tokens

    actual_code = lambda_fn.__code__.co_code
    for lambda_src in gen_lambdas():
        try:
            fn = eval(lambda_src, globals(), locals())
            if fn.__code__.co_code == actual_code:
                return lambda_src.split(":", 1)[1].strip()
        except Exception:
            pass
    return "<lambda>"


#!/usr/bin/env python3
# Copyright 2016 H2O.ai; Apache License Version 2.0;  -*- encoding: utf-8 -*-
"""Read h2opy configuration file(s)."""

import io
import logging
import os
import re

__all__ = ("H2OConfigReader", "get_config_value")

class H2OConfigReader(object):
    """
    Helper class responsible for reading h2o config files.

    This module will look for file(s) named ".h2oconfig" in the current folder, in all parent folders, and finally in
    the user's home directory. The first such file found will be used for configuration purposes. The format for such
    file is a simple "key = value" store, with possible section names in square brackets. Single-line comments starting
    with '#' are also allowed.
    """

    @staticmethod
    def get_config():
        """Retrieve the config as a dictionary of key-value pairs."""
        self = H2OConfigReader._get_instance()
        if not self._config_loaded:
            self._read_config()
        return self._config

    #-------------------------------------------------------------------------------------------------------------------
    # Private
    #-------------------------------------------------------------------------------------------------------------------

    _allowed_config_keys = {
        "init.check_version", "init.proxy", "init.url", "init.verify_ssl_certificates",
        "init.cookies", "init.username", "init.password",
        "general.allow_breaking_changes"
    }

    def __init__(self):
        """Initialize the singleton instance of H2OConfigReader."""
        assert not hasattr(H2OConfigReader, "_instance"), "H2OConfigReader is intended to be used as a singleton"
        self._logger = logging.getLogger("h2o")
        self._config = {}
        self._config_loaded = False

    @staticmethod
    def _get_instance():
        """Return the singleton instance of H2OConfigReader."""
        if not hasattr(H2OConfigReader, "_instance"):
            H2OConfigReader._instance = H2OConfigReader()
        return H2OConfigReader._instance

    def _read_config(self):
        """Find and parse config file, storing all variables in ``self._config``."""
        self._config_loaded = True
        conf = []
        for f in self._candidate_log_files():
            if os.path.isfile(f):
                self._logger.info("Reading config file %s" % f)
                section_rx = re.compile(r"^\[(\w+)\]$")
                keyvalue_rx = re.compile(r"^(\w+:)?([\w.]+)\s*=(.*)$")
                with io.open(f, "rt", encoding="utf-8") as config_file:
                    section_name = None
                    for lineno, line in enumerate(config_file):
                        line = line.strip()
                        if line == "" or line.startswith("#"): continue
                        m1 = section_rx.match(line)
                        if m1:
                            section_name = m1.group(1)
                            continue
                        m2 = keyvalue_rx.match(line)
                        if m2:
                            lng = m2.group(1)
                            key = m2.group(2)
                            val = m2.group(3).strip()
                            if lng and lng.lower() != "py:": continue
                            if section_name:
                                key = section_name + "." + key
                            if key in H2OConfigReader._allowed_config_keys:
                                conf.append((key, val))
                            else:
                                self._logger.error("Key %s is not a valid config key" % key)
                            continue
                        self._logger.error("Syntax error in config file line %d: %s" % (lineno, line))
                self._config = dict(conf)
                return

    @staticmethod
    def _candidate_log_files():
        """Return possible locations for the .h2oconfig file, one at a time."""
        # Search for .h2oconfig in the current directory and all parent directories
        relpath = ".h2oconfig"
        prevpath = None
        while True:
            abspath = os.path.abspath(relpath)
            if abspath == prevpath: break
            prevpath = abspath
            relpath = "../" + relpath
            yield abspath
        # Also check if .h2oconfig exists in the user's directory
        yield os.path.expanduser("~/.h2oconfig")

def get_config_value(key, default=None):
    """Return config value corresponding to the provided `key`."""
    return H2OConfigReader.get_config().get(key, default)