def compare(self, dist_1, dist_2):
        dist_1_interval = TimeInterval(*self.bounds_of(dist_1))
        dist_2_interval = TimeInterval(*self.bounds_of(dist_2))
        dictionary_input_output = {}
        for time_step in dist_1_interval + dist_2_interval:
            dictionary_input_output[time_step] = sqrt(dist_1.pdf(time_step) * dist_2.pdf(time_step))

        geometric_mean = FunctionPiecewiseLinear(dictionary_input_output, function_undefined=FUNCTION_ZERO)
        same = integral(geometric_mean, NEGATIVE_INFINITY, POSITIVE_INFINITY)

        dist_1_mean, dist_1_skewness, dist_1_kurtosis = dist_1.stats(moments='msk')
        dist_1_standard_deviation = dist_1.std()
        dist_2_mean, dist_2_skewness, dist_2_kurtosis = dist_2.stats(moments='msk')
        dist_2_standard_deviation = dist_2.std()

        distance = fabs(dist_1_standard_deviation - dist_2_standard_deviation) + fabs(dist_1_skewness - dist_2_skewness)
        distance += fabs(dist_1_kurtosis - dist_2_kurtosis)
        delta = dist_1_mean - dist_2_mean
        non_same_portion = 1.0 - same

        portion_after, portion_before = 1.0, 0.0
        if almost_equals(distance, 0):
            if delta < 0:
                portion_after, portion_before = 0.0, 1.0
        else:
            dist_1_standardized_pdf = lambda x: dist_1.pdf(dist_1_standard_deviation * x + dist_1_mean)
            dist_2_standardized_pdf = lambda x: dist_2.pdf(dist_2_standard_deviation * x + dist_2_mean)

            geometric_mean = lambda t: sqrt(dist_1_standardized_pdf(t) * dist_2_standardized_pdf(t))
            geometric_mean_scaled = lambda p: geometric_mean(p / distance)
            geometric_mean_scaled_length = max(self.duration_of(dist_1), self.duration_of(dist_2))

            dictionary_input_output = {}
            for time_step in TimeInterval(-geometric_mean_scaled_length / 2.0, geometric_mean_scaled_length / 2.0):
                dictionary_input_output[time_step] = geometric_mean_scaled(time_step)

            geometric_mean_scaled = FunctionPiecewiseLinear(dictionary_input_output, function_undefined=FUNCTION_ZERO)
            portion_after = integral(geometric_mean_scaled, NEGATIVE_INFINITY, delta)
            portion_before = integral(geometric_mean_scaled, delta, POSITIVE_INFINITY)

        if (portion_after + portion_before) == 0:
            pass

        after = portion_after / (portion_after + portion_before) * non_same_portion
        return 1.0 - same - after, same, after
    def unpack(self, relation_a_b_beginning, relation_a_b_ending):
        before_a_b_beginning, same_a_b_beginning, after_a_b_beginning = relation_a_b_beginning
        before_a_b_ending, same_a_b_ending, after_a_b_ending = relation_a_b_ending

        if almost_equals(before_a_b_beginning + same_a_b_beginning + same_a_b_ending + after_a_b_ending, 2.0, epsilon):
            return [], uniform_reference   # Inconsistent

        if almost_equals(before_a_b_beginning + before_a_b_ending, 2.0, epsilon):
            return [], uniform_reference

        if almost_equals(after_a_b_beginning + after_a_b_ending, 2.0, epsilon):
            return [], uniform_reference

        if almost_equals(before_a_b_ending, 1.0, epsilon):
            return self.unpack_partial(relation_a_b_beginning), uniform_reference

        if almost_equals(after_a_b_beginning, 1.0, epsilon):
            return self.unpack_partial(relation_a_b_ending), uniform_reference

        a_possibilities = self.unpack_partial(relation_a_b_beginning)
        if len(a_possibilities) == 1:
            a_possibility = a_possibilities[0]

            return
        else:
            a_possibility_1, a_possibility_2 = a_possibilities
            a_possibility = a_possibility_1
            if a_possibility.args[1] < a_possibility_2.args[1]:
                a_possibility = a_possibility_2

        a_start_point, length_a = a_possibility.args

        if before_a_b_ending * same_a_b_ending * after_a_b_ending > 0:
            if almost_equals(before_a_b_beginning, 0, epsilon):
                length_b_ending = length_a * same_a_b_ending ** 2
            else:
                length_b_ending = same_a_b_ending ** 2 / same_a_b_beginning ** 2
            b_ending = uniform(a_start_point + before_a_b_ending * (length_a - length_b_ending) /
                               (before_a_b_ending + after_a_b_ending), length_b_ending)
            return [a_possibility], b_ending
        else:
            denominator = length_a * same_a_b_ending ** 2
            length_b_ending_lower_bound = (length_a * same_a_b_ending ** 2) ** 2 / denominator
            length_b_ending_upper_bound = (a_start_point + length_a - 1) ** 2 / denominator
            length_b_ending = (length_b_ending_lower_bound + length_b_ending_upper_bound) / 2.0
            b_start_point = a_start_point + length_a - same_a_b_ending * sqrt(length_b_ending * length_a)
            b_ending = uniform(b_start_point, length_b_ending)
            return [a_possibility], b_ending
    def compare(self, dist_1, dist_2):
        dist_1_interval = TimeInterval(*self.bounds_of(dist_1))
        dist_2_interval = TimeInterval(*self.bounds_of(dist_2))
        dictionary_input_output = {}
        for time_step in dist_1_interval + dist_2_interval:
            dictionary_input_output[time_step] = sqrt(
                dist_1.pdf(time_step) * dist_2.pdf(time_step))

        geometric_mean = FunctionPiecewiseLinear(
            dictionary_input_output, function_undefined=FUNCTION_ZERO)
        same = integral(geometric_mean, NEGATIVE_INFINITY, POSITIVE_INFINITY)

        dist_1_mean, dist_1_skewness, dist_1_kurtosis = dist_1.stats(
            moments='msk')
        dist_1_standard_deviation = dist_1.std()
        dist_2_mean, dist_2_skewness, dist_2_kurtosis = dist_2.stats(
            moments='msk')
        dist_2_standard_deviation = dist_2.std()

        distance = fabs(dist_1_standard_deviation -
                        dist_2_standard_deviation) + fabs(dist_1_skewness -
                                                          dist_2_skewness)
        distance += fabs(dist_1_kurtosis - dist_2_kurtosis)
        delta = dist_1_mean - dist_2_mean
        non_same_portion = 1.0 - same

        portion_after, portion_before = 1.0, 0.0
        if almost_equals(distance, 0):
            if delta < 0:
                portion_after, portion_before = 0.0, 1.0
        else:
            dist_1_standardized_pdf = lambda x: dist_1.pdf(
                dist_1_standard_deviation * x + dist_1_mean)
            dist_2_standardized_pdf = lambda x: dist_2.pdf(
                dist_2_standard_deviation * x + dist_2_mean)

            geometric_mean = lambda t: sqrt(
                dist_1_standardized_pdf(t) * dist_2_standardized_pdf(t))
            geometric_mean_scaled = lambda p: geometric_mean(p / distance)
            geometric_mean_scaled_length = max(self.duration_of(dist_1),
                                               self.duration_of(dist_2))

            dictionary_input_output = {}
            for time_step in TimeInterval(-geometric_mean_scaled_length / 2.0,
                                          geometric_mean_scaled_length / 2.0):
                dictionary_input_output[time_step] = geometric_mean_scaled(
                    time_step)

            geometric_mean_scaled = FunctionPiecewiseLinear(
                dictionary_input_output, function_undefined=FUNCTION_ZERO)
            portion_after = integral(geometric_mean_scaled, NEGATIVE_INFINITY,
                                     delta)
            portion_before = integral(geometric_mean_scaled, delta,
                                      POSITIVE_INFINITY)

        if (portion_after + portion_before) == 0:
            pass

        after = portion_after / (portion_after +
                                 portion_before) * non_same_portion
        return 1.0 - same - after, same, after
    def combine(self, relation_a_b_beginning, relation_a_b_ending, relation_b_beginning_c, relation_b_ending_c):
        before_a_b, same_a_b, after_a_b = relation_a_b_beginning
        before_b_c, same_b_c, after_b_c = relation_b_beginning_c
        unknown = 1.0 / 3

        if almost_equals(before_a_b + before_b_c, 2.0, epsilon)\
                or almost_equals(before_a_b + same_b_c, 2.0, epsilon)\
                or almost_equals(same_a_b + before_b_c, 2.0, epsilon):
            return [(1.0, 0.0, 0.0)]

        if almost_equals(after_a_b + after_b_c, 2.0, epsilon)\
                or almost_equals(after_a_b + same_b_c, 2.0, epsilon)\
                or almost_equals(same_a_b + after_b_c, 2.0, epsilon):
            return [(1.0, 0.0, 0.0)]

        if almost_equals(same_a_b + same_b_c, 2.0, epsilon):
            return [(0.0, 1.0, 0.0)]

        if almost_equals(before_a_b + after_b_c, 2.0, epsilon) or almost_equals(after_a_b + before_b_c, 2.0, epsilon):
            return [(unknown, unknown, unknown)]

        relation_a_c_possibilities = []

        a_possibilities, b_ending_1 = self.unpack(relation_a_b_beginning, relation_a_b_ending)
        c_possibilities, b_ending_2 = self.unpack(tuple(reversed(relation_b_beginning_c)), tuple(reversed(relation_b_ending_c)))

        if len(a_possibilities) == 0:
            if before_a_b == 0:
                if len(c_possibilities) == 1:
                    if before_b_c == 0:
                        relation_a_c_possibilities.append((0.0, 0.0, 1.0))
                    else:
                        relation_a_c_possibilities.append((unknown, unknown, unknown))
                else:
                    relation_a_c_possibilities.append((unknown, unknown, unknown))
                    relation_a_c_possibilities.append((0.0, 0.0, 1.0))
            else:
                if len(c_possibilities) == 1:
                    if after_b_c == 0:
                        relation_a_c_possibilities.append((1.0, 0.0, 0.0))
                    else:
                        relation_a_c_possibilities.append((unknown, unknown, unknown))
                else:
                    relation_a_c_possibilities.append((unknown, unknown, unknown))
                    relation_a_c_possibilities.append((1.0, 0.0, 0.0))

        if len(c_possibilities) == 0:
            if before_b_c == 0:
                if len(a_possibilities) == 1:
                    if before_a_b == 0:
                        relation_a_c_possibilities.append((0.0, 0.0, 1.0))
                    else:
                        relation_a_c_possibilities.append((unknown, unknown, unknown))
                else:
                    relation_a_c_possibilities.append((unknown, unknown, unknown))
                    relation_a_c_possibilities.append((0.0, 0.0, 1.0))
            else:
                if len(a_possibilities) == 1:
                    if after_a_b == 0:
                        relation_a_c_possibilities.append((1.0, 0.0, 0.0))
                    else:
                        relation_a_c_possibilities.append((unknown, unknown, unknown))
                else:
                    relation_a_c_possibilities.append((unknown, unknown, unknown))
                    relation_a_c_possibilities.append((1.0, 0.0, 0.0))

        for possible_a in a_possibilities:
            for possible_c in c_possibilities:
                relation_a_c_possibilities.append(self.relation_formula.compare(possible_a, possible_c))

        return relation_a_c_possibilities