def get_discordance(comparables_a, comparables_perf_a, comparables_b,
                    comparables_perf_b, criteria, thresholds, pref_directions,
                    use_pre_veto):

    # 'partial' in this function's name has nothing to do w/ functools.partial
    def _get_partial_discordance(x, y, criterion, use_pre_veto):
        _omega = partial(omega, pref_directions, criterion)
        _get_linear = partial(get_linear, pref_directions, criterion, x, y)
        p = _get_linear(thresholds[criterion].get('preference', 0))
        v = _get_linear(thresholds[criterion].get('veto'))
        cv = _get_linear(thresholds[criterion].get('counter_veto'))
        crossed = False  # crossed 'counter_veto' threshold
        if not v:
            return (0.0, crossed)
        if cv is not None and _omega(x, y) > cv:
            crossed = True
        if use_pre_veto:
            # originally (i.e. w/o pre_veto) pv == p
            pv = _get_linear(thresholds[criterion].get('pre_veto', p))
            if _omega(x, y) > -pv:
                pd = 0.0
            elif _omega(x, y) <= -v:
                pd = 1.0
            else:
                pd = (_omega(y, x) - pv) / (v - pv)
        else:
            if _omega(x, y) >= -p:
                pd = 0.0
            elif _omega(x, y) < -v:
                pd = 1.0
            else:
                pd = (_omega(x, y) + p) / (p - v)
        return (pd, crossed)

    two_way_comparison = True if comparables_a != comparables_b else False
    partial_discordance = Vividict()
    cv_crossed = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            for criterion in criteria:
                x = comparables_perf_a[a][criterion]
                y = comparables_perf_b[b][criterion]
                pd, crossed = _get_partial_discordance(x, y, criterion,
                                                       use_pre_veto)
                partial_discordance[a][b][criterion] = pd
                cv_crossed[a][b][criterion] = crossed
                if two_way_comparison:
                    x = comparables_perf_b[b][criterion]
                    y = comparables_perf_a[a][criterion]
                    pd, crossed = _get_partial_discordance(
                        x, y, criterion, use_pre_veto)
                    partial_discordance[b][a][criterion] = pd
                    cv_crossed[b][a][criterion] = crossed
    return partial_discordance, cv_crossed
Esempio n. 2
0
def get_concordance(comparables_a, comparables_perf_a, comparables_b,
                    comparables_perf_b, criteria, thresholds, pref_directions,
                    weights):

    # 'partial' in this function's name has nothing to do w/ functools.partial
    def _get_partial_concordance(x, y, criterion):
        _omega = partial(omega, pref_directions, criterion)
        _get_linear = partial(get_linear, pref_directions, criterion, x, y)
        p = _get_linear(thresholds[criterion].get('preference', 0))
        q = _get_linear(thresholds[criterion].get('indifference', 0))
        if _omega(x, y) < -p:
            return 0.0
        elif _omega(x, y) >= -q:
            return 1.0
        else:
            return (_omega(x, y) + p) / (p - q)

    def _get_aggregated_concordance(x, y):
        sum_of_weights = sum([weights[criterion] for criterion in criteria])
        s = sum([
            weights[criterion] * partial_concordances[x][y][criterion]
            for criterion in criteria
        ])
        concordance = s / sum_of_weights
        return concordance

    two_way_comparison = True if comparables_a != comparables_b else False
    # compute partial concordances
    partial_concordances = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            for criterion in criteria:
                pc = _get_partial_concordance(comparables_perf_a[a][criterion],
                                              comparables_perf_b[b][criterion],
                                              criterion)
                partial_concordances[a][b][criterion] = pc
                if two_way_comparison:
                    pc = _get_partial_concordance(
                        comparables_perf_b[b][criterion],
                        comparables_perf_a[a][criterion],
                        criterion,
                    )
                    partial_concordances[b][a][criterion] = pc
    # aggregate partial concordances
    aggregated_concordances = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            aggregated_concordances[a][b] = _get_aggregated_concordance(a, b)
            if two_way_comparison:
                aggregated_concordances[b][a] = _get_aggregated_concordance(
                    b, a)
    return aggregated_concordances
def assign_class(alternatives, categories_rank, categories_profiles,
                 outranking):
    # sort categories by their rank, but we want the worst one on the 'left'
    # - hence 'reverse=True'
    categories = [i[0] for i in sorted(categories_rank.items(),
                                       key=lambda x: x[1], reverse=True)]
    exploitation = Vividict()
    for alternative in alternatives:
        # conjuctive ('pessimistic' - from 'best' to 'worst')
        conjuctive_idx = 0
        for profile_idx, profile in list(enumerate(categories_profiles))[::-1]:
            relation = get_relation_type(alternative, profile, outranking)
            if relation in ('indifference', 'preference'):
                conjuctive_idx = profile_idx + 1
                break
            else:
                continue
        # disjunctive ('optimistic' - from 'worst' to 'best')
        disjunctive_idx = len(categories_profiles)
        for profile_idx, profile in enumerate(categories_profiles):
            relation = get_relation_type(profile, alternative, outranking)
            if relation == 'preference':
                disjunctive_idx = profile_idx
                break
            else:
                continue
        exploitation[alternative] = (categories[conjuctive_idx],
                                     categories[disjunctive_idx])
    return exploitation
Esempio n. 4
0
def get_outranking(comparables_a, comparables_b, credibility,
                            cut_threshold):
    outranking = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            if credibility[a][b] >= cut_threshold:
                outranking[a][b] = True
            if credibility[b][a] >= cut_threshold:
                outranking[b][a] = True
    return outranking
Esempio n. 5
0
def get_discordance(comparables_a, comparables_perf_a, comparables_b,
                    comparables_perf_b, criteria, thresholds, pref_directions):
    def _get_partial_discordances(x, y, criterion):
        v = get_linear(pref_directions, criterion, x, y,
                       thresholds[criterion].get('veto'))
        if v is None:
            d = 0
        elif pref_directions[criterion] == 'max':
            d = 0 if (y < x + v) else 1
        else:
            d = 0 if (y > x - v) else 1
        return d

    def _get_aggregated_discordance(x, y):
        d_aggregated = 0
        for d_partial in partial_discordances[x][y].itervalues():
            if d_partial == 1:
                d_aggregated = 1
                break
        return d_aggregated

    two_way_comparison = True if comparables_a != comparables_b else False
    partial_discordances = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            for criterion in criteria:
                pc = _get_partial_discordances(
                    comparables_perf_a[a][criterion],
                    comparables_perf_b[b][criterion], criterion)
                partial_discordances[a][b][criterion] = pc
                if two_way_comparison:
                    pc = _get_partial_discordances(
                        comparables_perf_b[b][criterion],
                        comparables_perf_a[a][criterion], criterion)
                    partial_discordances[b][a][criterion] = pc
    discordance = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            discordance[a][b] = _get_aggregated_discordance(a, b)
            if two_way_comparison:
                discordance[b][a] = _get_aggregated_discordance(b, a)
    return discordance, partial_discordances
def get_credibility(comparables_a, comparables_b, concordance, discordance,
                    with_denominator, only_max_discordance, cv_crossed):
    def _get_credibility_idx(x, y, num_crossed, only_max_discordance):
        discordance_values = discordance[x][y].values()
        if set(discordance_values) == set([0]):  # only zeros
            c_idx = concordance[x][y]
        elif 1 in discordance_values:  # at least one '1'
            if not concordance[x][y] < 1:
                raise RuntimeError("When discordance == 1, "
                                   "concordance must be < 1.")
            c_idx = 0.0
        elif only_max_discordance and not with_denominator:
            c_idx = concordance[x][y] * (1 - max(discordance_values))
        else:
            factors = []
            for d in discordance_values:
                if with_denominator:
                    if d > concordance[x][y]:
                        factor = (1 - d) / (1 - concordance[x][y])
                    else:
                        factor = None
                else:
                    factor = (1 - d)
                if factor:
                    factors.append(factor)
            if factors == []:
                c_idx = concordance[x][y]
            else:
                discordance_aggr = reduce(lambda f1, f2: f1 * f2, factors)
                c_idx = (concordance[x][y] *
                         discordance_aggr**(1 - num_crossed / num_total))
        return c_idx

    two_way_comparison = True if comparables_a != comparables_b else False
    # 'num_total' == total number of criteria.
    # Instead of this monstrosity below, maybe it would be better to provide
    # 'criteria.xml' as another input..?
    num_total = len(discordance.values()[0].values()[0].keys())
    credibility = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            num_crossed = len(cv_crossed[a][b])
            credibility[a][b] = _get_credibility_idx(a, b, num_crossed,
                                                     only_max_discordance)
            if two_way_comparison:
                credibility[b][a] = _get_credibility_idx(
                    b, a, num_crossed, only_max_discordance)
    return credibility
Esempio n. 7
0
def get_credibility(comparables_a, comparables_perf_a, comparables_b,
                    comparables_perf_b, criteria, pref_directions, thresholds):
    """np, nq, ni, no - number of criteria where:
    n_p(a, b) - 'a' is strictly preferred over 'b'
    n_q(a, b) - 'a' is weakly preferred over 'b'
    n_i(a, b) - 'a' is indifferent than 'b', but 'a' has better performance
    n_o(a, b) - 'a' is indifferent than 'b' and both have the same performances
    """
    def _check_diff(diff, criterion):
        if ((pref_directions[criterion] == 'max' and diff > 0)
                or (pref_directions[criterion] == 'min' and diff < 0)):
            diff = abs(diff)
        elif diff == 0:
            pass
        else:
            diff = None
        return diff

    def _check_for_veto(performances_x, performances_y):
        veto = False
        for c in criteria:
            if thresholds[c].get('veto') is None:
                continue
            diff = _check_diff(performances_x[c] - performances_y[c], c)
            if diff > thresholds[c]['veto']:
                veto = True
                break
        return veto

    def _get_criteria_counts(x, y, performances_x, performances_y):
        np = nq = ni = no = 0
        for c in criteria:
            diff = _check_diff(performances_x[c] - performances_y[c], c)
            if diff:
                if diff >= thresholds[c].get('preference', 0):  # diff >= p
                    np += 1
                elif diff > thresholds[c].get('indifference',
                                              0):  # q > diff < p
                    nq += 1
                else:  # diff <= q
                    ni += 1
            elif diff == 0:
                no += 1
        return {'np': np, 'nq': nq, 'ni': ni, 'no': no}

    def _get_cred_values(comparables_x, comparables_y, comparables_perf_x,
                         comparables_perf_y, credibility):
        for x in comparables_x:
            for y in comparables_y:
                if x == y:
                    credibility[x][y] = 1.0
                    continue
                # let's abbreviate these two for convenience
                cc_xy = criteria_counts[x][y]
                cc_yx = criteria_counts[y][x]
                if (cc_yx['np'] + cc_yx['nq'] == 0 and
                        cc_yx['ni'] < cc_xy['np'] + cc_xy['nq'] + cc_xy['ni']):
                    credibility[x][y] = 1.0
                    continue
                elif cc_yx['np'] == 0:
                    sum_cc_yx = cc_yx['nq'] + cc_yx['ni']
                    sum_cc_xy = cc_xy['np'] + cc_xy['nq'] + cc_xy['ni']
                    if cc_yx['nq'] <= cc_xy['np'] and sum_cc_yx < sum_cc_xy:
                        credibility[x][y] = 0.8
                        continue
                    elif cc_yx['nq'] <= cc_xy['np'] + cc_xy['nq']:
                        credibility[x][y] = 0.6
                        continue
                    else:
                        credibility[x][y] = 0.4
                elif cc_yx['np'] <= 1 and cc_xy['np'] >= len(criteria) // 2:
                    veto = _check_for_veto(comparables_perf_y[y],
                                           comparables_perf_x[x])
                    if not veto:
                        credibility[x][y] = 0.2
                        continue
                    else:
                        credibility[x][y] = 0.0
                        continue
                else:
                    credibility[x][y] = 0.0
                    continue
        return credibility

    two_way_comparison = True if comparables_a != comparables_b else False
    criteria_counts = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            cc = _get_criteria_counts(a, b, comparables_perf_a[a],
                                      comparables_perf_b[b])
            criteria_counts[a][b] = cc
            if two_way_comparison:
                cc = _get_criteria_counts(b, a, comparables_perf_b[b],
                                          comparables_perf_a[a])
                criteria_counts[b][a] = cc
    credibility = Vividict()
    credibility = _get_cred_values(comparables_a, comparables_b,
                                   comparables_perf_a, comparables_perf_b,
                                   credibility)
    if two_way_comparison:
        credibility = _get_cred_values(comparables_b, comparables_a,
                                       comparables_perf_b, comparables_perf_a,
                                       credibility)
    return credibility
Esempio n. 8
0
def get_concordance(comparables_a, comparables_perf_a, comparables_b,
                    comparables_perf_b, criteria, thresholds, pref_directions,
                    weights, reinforcement_factors):

    # 'partial' in this function's name has nothing to do w/ functools.partial
    def _get_partial_concordance(x, y, criterion):
        _omega = partial(omega, pref_directions, criterion)
        _get_linear = partial(get_linear, pref_directions, criterion, x, y)
        p = _get_linear(thresholds[criterion].get('preference', 0))
        q = _get_linear(thresholds[criterion].get('indifference', 0))
        rp = _get_linear(thresholds[criterion].get('reinforced_preference'))
        crossed = False  # crossed 'reinforced_preference' threshold
        if rp is not None and _omega(x, y) > rp:
            crossed = True
        if _omega(x, y) < -p:
            pc = 0.0
        elif _omega(x, y) >= -q:
            pc = 1.0
        else:
            pc = (_omega(x, y) + p) / (p - q)
        return (pc, crossed)

    def _get_aggregated_concordance(x, y, rp_crossed):
        sum_of_weights = sum([
            weights[criterion] * rp_crossed.get((x, y, criterion), 1)
            for criterion in criteria
        ])
        s = sum([
            weights[criterion] * rp_crossed.get(
                (x, y, criterion), 1) * partial_concordances[x][y][criterion]
            for criterion in criteria
        ])
        concordance = s / sum_of_weights
        return concordance

    two_way_comparison = True if comparables_a != comparables_b else False
    # compute partial concordances
    partial_concordances = Vividict()
    rp_crossed = {}
    for a in comparables_a:
        for b in comparables_b:
            for criterion in criteria:
                x = comparables_perf_a[a][criterion]
                y = comparables_perf_b[b][criterion]
                pc, crossed = _get_partial_concordance(x, y, criterion)
                if crossed:
                    # it may be better to just throw an error here if there's
                    # no reinforcement factor defined (although using '1' as a
                    # default value makes sense too)
                    rf = reinforcement_factors.get(criterion, 1)
                    rp_crossed.update({(a, b, criterion): rf})
                partial_concordances[a][b][criterion] = pc
                if two_way_comparison:
                    x = comparables_perf_b[b][criterion]
                    y = comparables_perf_a[a][criterion]
                    pc, crossed = _get_partial_concordance(x, y, criterion)
                    if crossed:
                        rf = reinforcement_factors.get(criterion, 1)
                        rp_crossed.update({(b, a, criterion): rf})
                    partial_concordances[b][a][criterion] = pc
    # aggregate partial concordances
    aggregated_concordances = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            C = _get_aggregated_concordance(a, b, rp_crossed)
            aggregated_concordances[a][b] = C
            if two_way_comparison:
                C = _get_aggregated_concordance(b, a, rp_crossed)
                aggregated_concordances[b][a] = C
    return aggregated_concordances
Esempio n. 9
0
def get_concordance(comparables_a, comparables_perf_a, comparables_b,
                    comparables_perf_b, criteria, thresholds, pref_directions,
                    weights, interactions, z_function):
    def _check_net_balance(interactions, weights):
        int_weak = interactions.get('weakening', [])
        int_antag = interactions.get('antagonistic', [])
        int_chained = chain(int_weak, int_antag)
        criteria_affected = set([i[0] for i in int_chained])
        for criterion in criteria_affected:
            weak_sum = sum([abs(i[2]) for i in int_weak if i[0] == criterion])
            antag_sum = sum([i[2] for i in int_antag if i[0] == criterion])
            net_balance = weights[criterion] - weak_sum + antag_sum
            if net_balance <= 0:
                raise RuntimeError(
                    "Positive net balance condition is not "
                    "fulfilled for criterion '{}'.".format(criterion))

    # XXX this function is exactly the same as in ElectreConcordance
    # 'partial' in this function's name has nothing to do w/ functools.partial
    def _get_partial_concordance(x, y, criterion):
        _omega = partial(omega, pref_directions, criterion)
        _get_linear = partial(get_linear, pref_directions, criterion, x, y)
        p = _get_linear(thresholds[criterion].get('preference', 0))
        q = _get_linear(thresholds[criterion].get('indifference', 0))
        if _omega(x, y) < -p:
            return 0.0
        elif _omega(x, y) >= -q:
            return 1.0
        else:
            return (_omega(x, y) + p) / (p - q)

    # I don't like those cryptic variables' names here (ch, ci, cj, _cki,
    # _ckj, _kij, _kih) - they all come from math equations
    def _get_aggregated_concordance(x, y):
        if x == y:
            aggregated_concordance = 1.0
        else:
            sum_cki = sum(
                [partial_concordances[x][y][c] * weights[c] for c in criteria])
            sum_kij = float(0)
            for interaction_name in ('strengthening', 'weakening'):
                for interaction in interactions.get(interaction_name, []):
                    ci = partial_concordances[x][y][interaction[0]]
                    cj = partial_concordances[x][y][interaction[1]]
                    sum_kij += Z(ci, cj) * interaction[2]
            sum_kih = 0.0
            for interaction in interactions.get('antagonistic', []):
                ci = partial_concordances[x][y][interaction[0]]
                ch = partial_concordances[y][x][interaction[1]]
                sum_kih += Z(ci, ch) * interaction[2]
            sum_ki = sum(weights.values())
            K = sum_ki + sum_kij - sum_kih
            aggregated_concordance = (sum_cki + sum_kij - sum_kih) / K
        return aggregated_concordance

    # some initial checks
    _check_net_balance(interactions, weights)
    if z_function == 'multiplication':
        Z = lambda x, y: x * y
    elif z_function == 'minimum':
        Z = lambda x, y: min(x, y)
    else:
        raise RuntimeError("Invalid Z function: '{}'.".format(z_function))

    # XXX this block below is exactly the same as in ElectreConcordance
    two_way_comparison = True if comparables_a != comparables_b else False
    # compute partial concordances
    partial_concordances = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            for criterion in criteria:
                pc = _get_partial_concordance(comparables_perf_a[a][criterion],
                                              comparables_perf_b[b][criterion],
                                              criterion)
                partial_concordances[a][b][criterion] = pc
                if two_way_comparison:
                    pc = _get_partial_concordance(
                        comparables_perf_b[b][criterion],
                        comparables_perf_a[a][criterion], criterion)
                    partial_concordances[b][a][criterion] = pc
    # aggregate partial concordances
    aggregated_concordances = Vividict()
    for a in comparables_a:
        for b in comparables_b:
            aggregated_concordances[a][b] = _get_aggregated_concordance(a, b)
            if two_way_comparison:
                aggregated_concordances[b][a] = _get_aggregated_concordance(
                    b, a)
    return aggregated_concordances