Example 09: MCDM scores as LumiX data¶
TOPSIS scores the suppliers, and a LumiX binary model picks the best pair within a setup budget.
Needs an extra
This example uses the LumiX bridge (pip install "bulanik[lumix]"). The documentation build installs it, so the output below is real.
Source¶
examples/09_weights_to_lumix.py
"""MCDM scores as LumiX data: pick the best pair of suppliers within a budget.
TOPSIS scores the suppliers, scores_as_data turns them into records, and a LumiX
binary model chooses at most two suppliers that maximise the total score
without exceeding the setup budget.
Needs the lumix extra: pip install "bulanik[lumix]".
"""
from lumix import LXConstraint, LXModel, LXOptimizer, LXVariable
from lumix.core import LXLinearExpression
from bulanik.lumix import scores_as_data
from bulanik.mcdm import swara, topsis
# Same supplier data as example 04.
criteria = ["price", "quality", "delivery"]
types = ["cost", "benefit", "benefit"]
suppliers = ["Atlas", "Borealis", "Cedar", "Delta"]
data = [
[120, 8, 7], # Atlas
[100, 7, 5], # Borealis
[110, 9, 6], # Cedar
[130, 6, 9], # Delta
]
weights = swara([0.2, 0.5], criteria)
ranking = topsis(data, weights, types, alternatives=suppliers, criteria=criteria)
records = scores_as_data(ranking)
for r in records:
print(f"{r.key}: score {r.value:.4f}, rank {r.rank}")
setup_cost = {"Atlas": 50, "Borealis": 25, "Cedar": 45, "Delta": 30} # thousand EUR
budget = 75
pick = LXVariable[object, int]("pick").binary().indexed_by(lambda r: r.key).from_data(records)
model = (
LXModel("supplier_pair")
.add_variable(pick)
.add_constraint(
LXConstraint("budget")
.expression(LXLinearExpression().add_term(pick, lambda r: setup_cost[r.key]))
.le()
.rhs(budget)
)
.add_constraint(
LXConstraint("at_most_two").expression(LXLinearExpression().add_term(pick, 1.0)).le().rhs(2)
)
.maximize(LXLinearExpression().add_term(pick, lambda r: r.value))
)
solution = LXOptimizer().use_solver("ortools").solve(model)
assert solution.is_optimal(), solution.status
selected = sorted(k for k, v in solution.variables["pick"].items() if v > 0.5)
print("selected:", ", ".join(selected))
# Hand check: the TOPSIS scores are Cedar .5978, Atlas .5231, Delta .4338 and
# Borealis .4199 (example 04). The top two, Cedar and Atlas, cost 95 > 75. The
# pairs within budget are Cedar+Delta (75, 1.0316), Cedar+Borealis (70, 1.0177),
# Atlas+Borealis (75, .9429) and Borealis+Delta (55, .8537); a single supplier
# scores at most .5978. So the best feasible pair is Cedar and Delta.
assert selected == ["Cedar", "Delta"]
Output¶
Run when this page is built:
Atlas: score 0.5231, rank 2
Borealis: score 0.4199, rank 4
Cedar: score 0.5978, rank 1
Delta: score 0.4338, rank 3
selected: Cedar, Delta
What to notice¶
scores_as_datahands a ranking to LumiX as records, so the optimisation model can use the scores as coefficients.- The best pair is not the two highest ranked suppliers: Cedar and Atlas would cost 95, over the budget of 75, so the model picks Cedar and Delta.
- The same suppliers and scores appear in example 04.
Go further¶
- Guide: LumiX bridge
- Guide: Ranking
- API:
bulanik.lumix - API:
bulanik.mcdm
Run it yourself: python examples/09_weights_to_lumix.py.