def calculateMetric(metric_name, param_vals):
    '''
    Calculates a metric.

    Input:
        metric_name     metric name
        param_vals      metric parameters
    Returns:
                        result of the metric
    '''
    if metric_name == 'count':
        if len(param_vals)!= 1:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be count(data)'
            raise Exception()
        return red.count(*param_vals)
    elif metric_name == 'pdf':
        if len(param_vals)!= 3:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be pdf(data, bin_values, continuous_bins)'
            raise Exception()
        return PDF.single(*param_vals)
    elif metric_name == 'deft':
        if len(param_vals) < 4 or len(param_vals) > 5:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be deft(data, g, minLimit, maxLimit, alpha=2)'
            raise Exception()
        return deft.deft(*param_vals)
    elif metric_name == 'deft_joint':
        if len(param_vals) < 7 or len(param_vals) > 8:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be deft_joint(dataA, dataB, g, minLimitA, maxLimitA, minLimitB, maxLimitB, alpha=2)'
            raise Exception()
        return deft.deft(*param_vals)
    elif metric_name == 'pdf_joint':
        if len(param_vals)!= 6:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be pdf_joint(dataA, bin_valuesA, continuous_binsA, dataB, bin_valuesB, continuous_binsB)'
            raise Exception()
        return PDF.joint(*param_vals)
    elif metric_name == 'mutual_information':
        if len(param_vals) < 3 or len(param_vals) > 4:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be mutual_information(pdfA, pdfB, joint_pdf, logbase="log2")'
            raise Exception()
        return MI.calculate(*param_vals)
    elif metric_name == 'shannon':
        if len(param_vals) < 1 or len(param_vals) > 2:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be shannon(pdf, logbase="log2")'
            raise Exception()
        return shannon.calculate(*param_vals)
    elif metric_name == 'kullback-leibler':
        if len(param_vals) < 2 or len(param_vals) > 3:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be kullback-leibler(pdf_p, pdf_q, logbase="log2")'
            raise Exception()
        return kullback.calculate(*param_vals)
    elif metric_name == 'fisher':
        if len(param_vals) < 2 or len(param_vals) > 3:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be fisher(pdf, eps, logbase="log2")'
            raise Exception()
        return fis.calculate(*param_vals)
    elif metric_name == 'hellinger-distance':
        if len(param_vals) != 2:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be hellinger-distance(pdf_p, pdf_q)'
            raise Exception()
        return hellinger.calculate(*param_vals)
    elif metric_name == 'surprise':
        if len(param_vals)!= 1:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be surprise(prob)'
            raise Exception()
        return surprise.calculate(*param_vals)
    elif metric_name == 'idt':
        if len(param_vals) < 6 or len(param_vals) > 7:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be idt(initial, time_series, epsilon, dt, bin_values, continuous_bins, logbase="log2")'
            raise Exception()
        return IDT.system(*param_vals)
    elif metric_name == 'idt_individual':
        if len(param_vals) < 8 or len(param_vals) > 9:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be idt_individual(initial, time_series, dt, bin_values, continuous_bins, sample_state_0, sample_state_t, sample_time, logbase="log2")'
            raise Exception()
        return IDT.individual(*param_vals)
    elif metric_name == 'information_integration':
        if len(param_vals) < 9 or len(param_vals) > 10:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be information_integration(initial, group, dt, bin_values, continuous_bins, sample_N1, sample_N2, sample_G, sample_t, logbase="log2")'
            raise Exception()
        return II.calculate(*param_vals)
    elif metric_name == 'multi_information':
        if len(param_vals) < 6 or len(param_vals) > 7:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be multi_information(data, bin_values, continuous_bins, sample_var, sample_elems, sample_pop, logbase="log2")'
            raise Exception()
        return multi.calculate(*param_vals)
    elif metric_name == 'early_warning_difference':
        if len(param_vals) < 4 or len(param_vals) > 5:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be early_warning_difference(time_series_ref, time_series_comp, change_values, warning_values, histogram_limit=50)'
            raise Exception()
        return ew.early_warning_difference(*param_vals)
    elif metric_name == 'early_warning_flips':
        if len(param_vals) != 2:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be early_warning_flips(time_series, change_values)'
            raise Exception()
        return ew.early_warning_flips(*param_vals)
    elif metric_name == 'add_dimension':
        if len(param_vals)!= 2:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be add_dimension(data, dimNumber)'
            raise Exception()
        return  np.expand_dims(*param_vals)
    elif metric_name == 'join_dimensions':
        if len(param_vals)!= 3:
            print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be join_dimensions(data, dimNumberA, dimNumberB)'
            raise Exception()
        return  red.join(*param_vals)
    else :
        # Try to get a numpy function
        try :
            func = getattr(np, metric_name)
            return func(*param_vals)
        except:
            print 'ERROR:Metric ', metric_name, ' does not exist'
            raise Exception()
def calculateMetric(metric_name, param_vals):
	if metric_name == 'count':
		if len(param_vals)!= 1:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be count(data)'
			raise Exception()
		return red.count(*param_vals)
	elif metric_name == 'pdf':
		if len(param_vals)!= 3:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be pdf(data, bin_values, continuous_bins)'
			raise Exception()
		return PDF.single(*param_vals)
	elif metric_name == 'deft':
		if len(param_vals) < 2 or len(param_vals) > 3:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be deft(data, g, alpha)'
			raise Exception()
		return deft.deft(*param_vals)
	elif metric_name == 'pdf_joint':
		if len(param_vals)!= 6:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be pdf_joint(dataA, bin_valuesA, continuous_binsA, dataB, bin_valuesB, continuous_binsB)'
			raise Exception()
		return PDF.joint(*param_vals)
	elif metric_name == 'mutual_information':
		if len(param_vals) < 3 or len(param_vals) > 4:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be mutual_information(pdfA, pdfB, joint_pdf, logbase="log2")'
			raise Exception()
		return MI.calculate(*param_vals)
	elif metric_name == 'shannon':
		if len(param_vals) < 1 or len(param_vals) > 2:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be shannon(pdf, logbase="log2")'
			raise Exception()
		return shannon.calculate(*param_vals)
	elif metric_name == 'kullback-leibler':
		if len(param_vals) < 2 or len(param_vals) > 3:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be kullback-leibler(pdf_p, pdf_q, logbase="log2")'
			raise Exception()
		return kullback.calculate(*param_vals)
	elif metric_name == 'fisher':
		if len(param_vals) < 2 or len(param_vals) > 3:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be fisher(pdf, eps, logbase="log2")'
			raise Exception()
		return fis.calculate(*param_vals)
	elif metric_name == 'hellinger-distance':
		if len(param_vals) != 2:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be hellinger-distance(pdf_p, pdf_q)'
			raise Exception()
		return hellinger.calculate(*param_vals)
	elif metric_name == 'surprise':
		if len(param_vals)!= 1:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be surprise(prob)'
			raise Exception()
		return surprise.calculate(*param_vals)
	elif metric_name == 'idt':
		if len(param_vals) < 6 or len(param_vals) > 7:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be idt(initial, time_series, epsilon, dt, bin_values, continuous_bins, logbase="log2")'
			raise Exception()
		return IDT.system(*param_vals)
	elif metric_name == 'idt_individual':
		if len(param_vals) < 8 or len(param_vals) > 9:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be idt_individual(initial, time_series, dt, bin_values, continuous_bins, sample_state_0, sample_state_t, sample_time, logbase="log2")'
			raise Exception()
		return IDT.individual(*param_vals)
	elif metric_name == 'information_integration':
		if len(param_vals) < 9 or len(param_vals) > 10:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be information_integration(initial, group, dt, bin_values, continuous_bins, sample_N1, sample_N2, sample_G, sample_t, logbase="log2")'
			raise Exception()
		return II.calculate(*param_vals)
	elif metric_name == 'multi_information':
		if len(param_vals) < 6 or len(param_vals) > 7:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be multi_information(data, bin_values, continuous_bins, sample_var, sample_elems, sample_pop, logbase="log2")'
			raise Exception()
		return multi.calculate(*param_vals)
	elif metric_name == 'swap_axes':
		if len(param_vals)!= 3:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be swap_axes(data, axis0, axis1)'
			raise Exception()
		return np.swapaxes(*param_vals)
	elif metric_name == 'add_dimension':
		if len(param_vals)!= 2:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be add_dimension(data, dimNumber)'
			raise Exception()
		return  np.expand_dims(*param_vals)
	elif metric_name == 'join_dimensions':
		if len(param_vals)!= 3:
			print 'ERROR:Error in ', metric_name, ', number of parameters incorrect. It must be join_dimensions(data, dimNumberA, dimNumberB)'
			raise Exception()
		return  red.join(*param_vals)
	else :
		# Try to get a numpy function
		try :
			func = getattr(np, metric_name)
			return func(*param_vals)
		except:
			print 'ERROR:Metric ', metric_name, ' does not exist'
			raise Exception()
def individual(initial, times, dt, bin_values, continuous_bins, sample_N1, sample_N2, sample_t, logbase="log2"):
    """
    IDT individual metric

    Input:
        initial         Initial data NxP
                            N = elements
                            P = population
        times           Time state data TxNxP
                            T = time series 
                            N = elements
                            P = population
        dt              Number of timesteps between time series
        bin_values      values of the bins
        continuous_bins true if the values of the bins are continuous
        sample_N1       percentage of elements to choose as a sample for state 0
        sample_N2       percentage of elements to choose as a sample for state t
        sample_time     percentage of elements to choose as a sample for time series
        logbase         Base for the logarithm ("log2", "log", "log10")
    Returns:
                        IDT N
                            N = elements
    """
    assert logbase in ["log2", "log", "log10"], 'Logbase parameter must be one of ("log2", "log", "log10")'
    assert 0 < sample_N1 <= 1, "Sample for N1 must be within (0, 1]"
    assert 0 < sample_N2 <= 1, "Sample for N2 must be within (0, 1]"
    assert 0 < sample_time <= 1, "Sample for time must be within (0, 1]"

    number_of_bins = len(bin_values)
    if continuous_bins:
        number_of_bins = number_of_bins - 1
    # Sampling input data
    sample_elements_1 = np.arange(len(initial))
    sample_elements_2 = np.arange(len(initial))
    sample_time = np.arange(len(times))
    np.random.shuffle(sample_elements_1)
    np.random.shuffle(sample_elements_2)
    np.random.shuffle(sample_time)
    sample_elements_1 = sample_elements_1[: len(initial) * sample_N1]
    sample_elements_2 = sample_elements_2[: len(initial) * sample_N2]
    sample_time = sample_time[: len(times) * sample_t]
    sample_time = np.sort(sample_time)
    times_sampled = times[sample_time]
    initial_sampled = initial[sample_elements_1]
    initial_sampled_2 = initial[sample_elements_2]
    initial_sampled_len = len(initial_sampled)
    initial_sampled_len_2 = len(initial_sampled_2)
    times_sampled_len = len(times_sampled)

    # Maximum value for IDT when there is no enough decay
    IDT_max = len(times) * dt

    # Initial marginals pdf
    pdf_initial = PDF.single(initial_sampled, bin_values, continuous_bins)
    pdf_initial_2 = PDF.single(initial_sampled_2, bin_values, continuous_bins)
    # Initial entropy
    h_initial = shannon.calculate(pdf_initial, logbase)
    # Temporal marginals pdf
    pdf_t = np.ndarray((len(sample_time), len(sample_elements_2), number_of_bins), dtype="float")
    for t in xrange(len(sample_time)):
        pdf_t[t] = PDF.single(times_sampled[t][sample_elements_2, ...], bin_values, continuous_bins)

    # Calculate IDT for each element (sample)
    IDT_var = np.ndarray(initial_sampled_len, dtype="float")
    init = time.clock()
    for i in xrange(initial_sampled_len):
        # Target decay limit
        h_target = h_initial[i] / 2

        # Maximum mutual information
        max_I = np.ndarray(times_sampled_len + 1, dtype="float")
        initial_sampled_i_len = len(initial_sampled[i])

        found = False
        # Initial mutual information
        mi_init = np.ndarray((len(times_sampled[t][sample_elements_2])), dtype="float")
        for j in xrange(len(times_sampled[t][sample_elements_2])):
            initial_sampled_i_len_2 = len(initial_sampled_2[j])
            # Calculate joint pdf from initial state
            pdf_joint = PDF.joint(
                initial_sampled[i].reshape(1, initial_sampled_i_len),
                bin_values,
                continuous_bins,
                initial_sampled_2[j].reshape(1, initial_sampled_i_len_2),
                bin_values,
                continuous_bins,
            )
            # Mutual information
            mi_init[j] = MI.calculate(
                pdf_initial[i].reshape(1, number_of_bins),
                pdf_initial_2[j].reshape(1, number_of_bins),
                pdf_joint,
                logbase,
            )
        max_I[0] = np.amax(mi_init)

        # Time series mutual information
        for t in xrange(times_sampled_len):
            mi = np.ndarray((len(times_sampled[t][sample_elements_2])), dtype="float")
            # Mutual information in time t
            for j in xrange(len(times_sampled[t][sample_elements_2])):
                # Calculate joint pdf from initial state and time t
                pdf_joint = PDF.joint(
                    initial_sampled[i].reshape(1, initial_sampled_i_len),
                    bin_values,
                    continuous_bins,
                    times_sampled[t][sample_elements_2][j].reshape(1, len(times_sampled[t][sample_elements_2][j])),
                    bin_values,
                    continuous_bins,
                )
                # Mutual information
                mi[j] = MI.calculate(
                    pdf_initial[i].reshape(1, number_of_bins),
                    pdf_t[t, j, :].reshape(1, number_of_bins),
                    pdf_joint,
                    logbase,
                )
            max_I[t + 1] = np.amax(mi)

        # Find t crossing target decay
        for t in xrange(times_sampled_len):
            # Interpolate when found
            if max_I[t + 1] - h_target < 0:
                t1 = t
                t2 = t + 1
                found = True
                h1 = max_I[t1]
                h2 = max_I[t2]

                if h2 - h1 == 0:
                    IDT_var[i] = 0
                else:
                    IDT_var[i] = (t1 + (t2 - t1) * (h_target - h1) / (h2 - h1)) * dt
                break
        # Setting maximum IDT value when not found
        if not found:
            IDT_var[i] = IDT_max

    return IDT_var