Skill Tracer
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.
3
4This script demonstrates how to select preferred solutions from
5an approximate Pareto front using various MCDM methods.
6"""
7
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()