Skip to content

Example 18: Interval-valued AHP and CODAS

IVIFN pairwise comparisons give criteria weights with ahp_fuzzy_numbers, IVIFN ratings are ranked with CODAS, and the same pipeline is repeated with interval-valued neutrosophic ratings.

Source

examples/18_interval_valued_ahp_codas.py
"""Interval-valued AHP and CODAS: IVIFN criteria weights, IVIFN ratings, plus an IVNN variant.

The criteria are compared pairwise with interval-valued intuitionistic numbers
(Saaty-style: a high membership means "row more important than column", the
reciprocal swaps membership and non-membership). `ahp_fuzzy_numbers` turns the
matrix into weights, and `rank_fuzzy(method="codas")` ranks the alternatives.
CODAS divides by entries, so the signed default score would be rejected; both
steps use `score="normalized"`, a score in [0, 1].
"""

from bulanik import IVIFN, IVNeutrosophicFN
from bulanik.mcdm import DecisionMatrix, ahp_fuzzy_numbers, rank_fuzzy

criteria = ["cost", "quality", "delivery"]
alternatives = ["Supplier A", "Supplier B", "Supplier C"]
types = ["cost", "benefit", "benefit"]

# pairwise comparison matrix of the criteria; the diagonal is "equally important"
equal = IVIFN(0.5, 0.5, 0.5, 0.5)
strong = IVIFN(0.7, 0.8, 0.1, 0.2)  # row clearly more important
mild = IVIFN(0.5, 0.6, 0.3, 0.4)  # row slightly more important
comparisons = [
    [equal, mild, strong],
    [mild.complement(), equal, mild],
    [strong.complement(), mild.complement(), equal],
]
weights = ahp_fuzzy_numbers(comparisons, criteria, score="normalized")
print("weights:", {k: round(v, 4) for k, v in weights.weights.items()})

# one expert's ratings: IVIFN [alternative][criterion]
ratings = [
    [IVIFN(0.3, 0.4, 0.4, 0.5), IVIFN(0.7, 0.8, 0.1, 0.2), IVIFN(0.5, 0.6, 0.3, 0.4)],
    [IVIFN(0.5, 0.6, 0.3, 0.4), IVIFN(0.5, 0.6, 0.3, 0.4), IVIFN(0.7, 0.8, 0.1, 0.2)],
    [IVIFN(0.6, 0.7, 0.2, 0.3), IVIFN(0.3, 0.4, 0.4, 0.5), IVIFN(0.3, 0.4, 0.4, 0.5)],
]
matrix = DecisionMatrix(alternatives, criteria, [ratings])
result = rank_fuzzy(matrix, weights, types, method="codas", score="normalized")
print("IVIFN ranking:", " > ".join(result.ranking))
for name in result.ranking:
    print(f"{name}: H {result.scores[name]:+.4f}")

# the same pipeline with interval-valued neutrosophic ratings (truth, indeterminacy,
# falsity); their only score, "average", is already non-negative
def nn(t, i, f):
    return IVNeutrosophicFN(t - 0.05, t + 0.05, i - 0.05, i + 0.05, f - 0.05, f + 0.05)

nn_ratings = [
    [nn(0.4, 0.2, 0.4), nn(0.8, 0.2, 0.2), nn(0.6, 0.3, 0.3)],
    [nn(0.6, 0.2, 0.3), nn(0.6, 0.3, 0.3), nn(0.8, 0.1, 0.2)],
    [nn(0.7, 0.2, 0.2), nn(0.4, 0.3, 0.5), nn(0.4, 0.3, 0.5)],
]
nn_matrix = DecisionMatrix(alternatives, criteria, [nn_ratings])
nn_result = rank_fuzzy(nn_matrix, weights, types, method="codas")
print("IVNN ranking:", " > ".join(nn_result.ranking))

assert result.operators["score"] == "normalized" and weights.operators["score"] == "normalized"
assert abs(sum(weights.weights.values()) - 1.0) < 1e-9
assert weights.ranking[0] == "cost"
expected_w = {"cost": 0.4279, "quality": 0.3391, "delivery": 0.2331}
assert all(abs(weights.weights[k] - v) < 5e-5 for k, v in expected_w.items())
assert result.ranking == ("Supplier A", "Supplier B", "Supplier C")
expected_h = {"Supplier A": 0.7845, "Supplier B": 0.1177, "Supplier C": -0.9023}
assert all(abs(result.scores[k] - v) < 5e-5 for k, v in expected_h.items())
assert nn_result.ranking == result.ranking

Output

Run when this page is built:

weights: {'cost': 0.4279, 'quality': 0.3391, 'delivery': 0.2331}
IVIFN ranking: Supplier A > Supplier B > Supplier C
Supplier A: H +0.7845
Supplier B: H +0.1177
Supplier C: H -0.9023
IVNN ranking: Supplier A > Supplier B > Supplier C

What to notice

  • Both steps use score="normalized": the default orthopair score is signed and CODAS, which divides by entries, rejects it.
  • IVNeutrosophicFN needs no such choice; its only score, "average", is already non-negative.
  • CODAS scores are assessment scores, higher is better, and can be negative.

Go further

Run it yourself: python examples/18_interval_valued_ahp_codas.py.