"""Demo 3 - Multi-criteria candidate selection. You have 100 candidates evaluated on 4 independent criteria (quality, cost-efficiency, robustness, compatibility - or whatever your domain calls them). You want to pick the ones worth a deeper look. Naive ranking by total score finds the high-mean candidates - which are often single-criterion peaks that compensate with weakness on the rest. SEM's two-stage filter 1) best-tradeoff filter ('Pareto core') 2) cross-criterion filter ('non-redundant witnesses') finds the genuine all-rounders: candidates that are not strictly worse than another on every axis AND that contribute meaningfully on multiple axes (not just one). Run: python 03_multicriteria_selection.py """ from __future__ import annotations import numpy as np from sem_cython12 import wrapper as cy def main() -> int: if not cy.available(): print("ERROR: sem_cython12 compiled extension did not load.") return 1 rng = np.random.default_rng(7) N, K = 100, 4 criteria_names = ["Quality", "Cost-efficiency", "Robustness", "Compatibility"] # Most candidates: noisy uniform draws across the criteria S = rng.uniform(0.30, 0.95, size=(N, K)) # Inject 5 hidden 'all-rounders' that score moderately well on EVERY # criterion - none top any single axis, but they're well-balanced. S[0:5] = rng.uniform(0.65, 0.85, size=(5, K)) # ---- Naive ranking by sum of scores --------------------------------- naive_order = np.argsort(S.sum(axis=1))[::-1] naive_top10 = naive_order[:10] # ---- SEM ranking ---------------------------------------------------- pareto_mask = cy.pareto_core_mask(S) pareto_idx = np.where(pareto_mask == 1)[0] nrw = cy.non_redundant_witnesses(S) # ---- Reporting ------------------------------------------------------ print(f"Candidates : {N}") print(f"Criteria : {K} ({', '.join(criteria_names)})") print() print(f"Best-tradeoff frontier size : {len(pareto_idx)}") print(f"Cross-criterion winners (NRW) : {len(nrw)}") print(f"Hidden all-rounders we injected : 5 (indices 0-4)") print() overlap_with_hidden = set(nrw.tolist()) & set(range(5)) naive_overlap_with_hidden = set(naive_top10.tolist()) & set(range(5)) print(f"NRW recovered hidden all-rounders : " f"{len(overlap_with_hidden)}/5 {sorted(overlap_with_hidden)}") print(f"Naive top-10 found hidden all-rounders: " f"{len(naive_overlap_with_hidden)}/5 {sorted(naive_overlap_with_hidden)}") print() # Profile of NRW candidates print("Cross-criterion winners (NRW) - score profiles:") print(f" {'idx':>4} " + " ".join(f"{n[:8]:>9}" for n in criteria_names) + f" {'min':>5} {'mean':>5}") for i in nrw: scores = S[i] print(f" {int(i):>4} " + " ".join(f"{v:9.3f}" for v in scores) + f" {scores.min():5.2f} {scores.mean():5.2f}") print() print("Naive top-3 (by total score) - score profiles for comparison:") print(f" {'idx':>4} " + " ".join(f"{n[:8]:>9}" for n in criteria_names) + f" {'min':>5} {'mean':>5}") for i in naive_top10[:3]: scores = S[i] print(f" {int(i):>4} " + " ".join(f"{v:9.3f}" for v in scores) + f" {scores.min():5.2f} {scores.mean():5.2f}") print() # Wow line - honest comparison n_nrw_hits = len(overlap_with_hidden) n_naive_hits = len(naive_overlap_with_hidden) print(f"*** SEM's NRW filter recovered {n_nrw_hits}/5 hidden all-rounders. ***") print(f"*** Naive sum-of-scores top-10 found only {n_naive_hits}/5. ***") if n_nrw_hits > n_naive_hits: print(f"*** SEM surfaces {n_nrw_hits - n_naive_hits} candidates the naive ranking misses ***") print(f"*** because they don't peak on any single criterion. ***") return 0 if __name__ == "__main__": raise SystemExit(main())