Skills › Scientific Agent Skills › pymoo › decision_making_example.py
decision_making_example.py
184 lines · skills/pymoo/scripts/decision_making_example.py at 5f2bc03
1"""
2Multi-criteria decision making example using pymoo.
4This script demonstrates how to select preferred solutions from
5an approximate Pareto front using various MCDM methods.
6"""
8from pymoo.algorithms.moo.nsga2 import NSGA2
9from pymoo.problems import get_problem
10from pymoo.optimize import minimize
11from pymoo.mcdm.pseudo_weights import PseudoWeights
12from pymoo.visualization.scatter import Scatter
13from pymoo.visualization.petal import Petal
14import numpy as np
17def run_optimization_for_decision_making():
18 """Run optimization to obtain approximate Pareto front."""
20 print("Running optimization to obtain approximate Pareto front...")
22 # Solve ZDT1 problem
23 problem = get_problem("zdt1")
24 algorithm = NSGA2(pop_size=40)
26 result = minimize(
27 problem,
28 algorithm,
29 ('n_gen', 50),
30 seed=1,
31 verbose=False
32 )
34 print(f"Obtained {len(result.F)} solutions in approximate Pareto front\n")
36 return problem, result
39def apply_pseudo_weights(result, weights):
40 """Apply pseudo-weights MCDM method."""
42 print(f"Applying Pseudo-Weights with weights: {weights}")
44 if result.F is None or result.X is None:
45 raise ValueError("No feasible candidates are available for selection")
46 F = np.asarray(result.F, dtype=float)
47 weights = np.asarray(weights, dtype=float)
48 if F.ndim != 2 or len(F) == 0 or not np.isfinite(F).all():
49 raise ValueError("Expected a nonempty, finite multi-objective matrix")
50 if len(result.X) != len(F):
51 raise ValueError("Candidate X and F rows must remain aligned")
52 if weights.shape != (F.shape[1],) or not np.isfinite(weights).all():
53 raise ValueError("Supply one finite weight per objective")
54 if np.any(weights < 0) or not np.isclose(weights.sum(), 1.0):
55 raise ValueError("Weights must be nonnegative and sum to one")
56 if result.CV is not None and np.asarray(result.CV).shape != (len(F), 1):
57 raise ValueError("Expected one constraint-violation value per candidate")
58 if result.CV is not None and (
59 not np.isfinite(result.CV).all() or np.any(np.asarray(result.CV) > 0)
60 ):
61 raise ValueError("Recheck and filter infeasible candidates before selection")
62 from pymoo.util.nds.non_dominated_sorting import NonDominatedSorting
63 if len(NonDominatedSorting().do(F, only_non_dominated_front=True)) != len(F):
64 raise ValueError("Filter dominated candidates before preference selection")
65 if len(F) == 1:
66 selected_idx = 0
67 else:
68 if np.any(np.ptp(F, axis=0) <= 0):
69 raise ValueError("Remove constant objectives and revise weights explicitly")
70 # PseudoWeights normalizes distances from the nadir internally.
71 # It matches pseudo-weight vectors, not a weighted sum of objectives.
72 selected_idx = int(PseudoWeights(weights).do(F))
74 selected_x = result.X[selected_idx]
75 selected_f = result.F[selected_idx]
77 print(f"Selected solution (decision variables): {selected_x}")
78 print(f"Selected solution (objectives): {selected_f}")
79 print()
81 return selected_idx, selected_x, selected_f
84def compare_different_preferences(result):
85 """Compare selections with different preference weights."""
87 print("="*60)
88 print("COMPARING DIFFERENT PREFERENCE WEIGHTS")
89 print("="*60 + "\n")
91 # Define different preference scenarios
92 scenarios = [
93 ("Equal preference", np.array([0.5, 0.5])),
94 ("Prefer f1", np.array([0.8, 0.2])),
95 ("Prefer f2", np.array([0.2, 0.8])),
96 ]
98 selections = {}
100 for name, weights in scenarios:
101 print(f"Scenario: {name}")
102 idx, x, f = apply_pseudo_weights(result, weights)
103 selections[name] = (idx, f)
105 # Visualize all selections
106 plot = Scatter(title="Decision Making - Different Preferences")
107 plot.add(result.F, color="lightgray", alpha=0.5, s=20, label="Approximate Front")
109 colors = ["red", "blue", "green"]
110 for (name, (idx, f)), color in zip(selections.items(), colors):
111 plot.add(f, color=color, s=100, marker="*", label=name)
113 plot.show()
115 return selections
118def visualize_selected_solutions(result, selections):
119 """Visualize selected solutions using petal diagram."""
121 # Get objective bounds for normalization
122 f_min = result.F.min(axis=0)
123 f_max = result.F.max(axis=0)
125 plot = Petal(
126 title="Selected Solutions Comparison",
127 bounds=[f_min, f_max],
128 labels=["f1", "f2"]
129 )
131 colors = ["red", "blue", "green"]
132 for (name, (idx, f)), color in zip(selections.items(), colors):
133 plot.add(f, color=color, label=name)
135 plot.show()
138def find_extreme_solutions(result):
139 """Find extreme solutions (best in each objective)."""
141 print("\n" + "="*60)
142 print("EXTREME SOLUTIONS")
143 print("="*60 + "\n")
145 # Best f1 (minimize f1)
146 best_f1_idx = np.argmin(result.F[:, 0])
147 print(f"Best f1 solution: {result.F[best_f1_idx]}")
148 print(f" Decision variables: {result.X[best_f1_idx]}\n")
150 # Best f2 (minimize f2)
151 best_f2_idx = np.argmin(result.F[:, 1])
152 print(f"Best f2 solution: {result.F[best_f2_idx]}")
153 print(f" Decision variables: {result.X[best_f2_idx]}\n")
155 return best_f1_idx, best_f2_idx
158def main():
159 """Main execution function."""
161 # Step 1: Run optimization
162 problem, result = run_optimization_for_decision_making()
164 # Step 2: Find extreme solutions
165 best_f1_idx, best_f2_idx = find_extreme_solutions(result)
167 # Step 3: Compare different preference weights
168 selections = compare_different_preferences(result)
170 # Step 4: Visualize selections with petal diagram
171 visualize_selected_solutions(result, selections)
173 print("="*60)
174 print("DECISION MAKING EXAMPLE COMPLETED")
175 print("="*60)
176 print("\nKey Takeaways:")
177 print("1. Different weights lead to different selected solutions")
178 print("2. Weight sensitivity must be checked on the actual candidate set")
179 print("3. Visualization helps understand trade-offs")
180 print("4. MCDM methods help formalize decision maker preferences")
183if __name__ == "__main__":
184 main()