Spaces:
Running
Running
File size: 7,432 Bytes
6dc9d46 696f787 6dc9d46 696f787 9659593 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 696f787 6dc9d46 696f787 6dc9d46 9659593 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 9659593 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 9659593 6dc9d46 696f787 6dc9d46 696f787 9659593 6dc9d46 9659593 696f787 6dc9d46 9659593 696f787 9659593 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 9659593 6dc9d46 696f787 9659593 6dc9d46 9659593 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 696f787 9659593 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 9659593 6dc9d46 696f787 6dc9d46 696f787 6dc9d46 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | """
Pareto Frontier Analysis
Identifies optimal trade-offs in multi-objective optimization
"""
from typing import Any
import matplotlib
import numpy as np
matplotlib.use("Agg") # Use non-interactive backend
import matplotlib.pyplot as plt
def identify_pareto_front(gene_pool_entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""
Identifies non-dominated solutions (Pareto Frontier).
A solution is dominated if another solution is:
- Better or equal on ALL metrics
- Strictly better on AT LEAST ONE metric
"""
pareto_front = []
for i, candidate in enumerate(gene_pool_entries):
is_dominated = False
# Get candidate's 5D score vector
cand_scores = np.array(candidate["evaluation"].to_vector())
for j, other in enumerate(gene_pool_entries):
if i == j:
continue
# Get other solution's 5D vector
other_scores = np.array(other["evaluation"].to_vector())
# Check domination: other >= candidate on ALL, other > candidate on SOME
if np.all(other_scores >= cand_scores) and np.any(other_scores > cand_scores):
is_dominated = True
break
if not is_dominated:
pareto_front.append(candidate)
return pareto_front
def visualize_pareto_frontier(pareto_front: list[dict[str, Any]]):
"""
Creates two visualizations:
1. Parallel coordinates plot (5D)
2. Radar chart (5D profile)
"""
if not pareto_front:
print("No solutions on Pareto front to visualize")
return
fig = plt.figure(figsize=(18, 7))
# --- Plot 1: Bar Chart (since pandas might not be available) ---
ax1 = plt.subplot(1, 2, 1)
metrics = ["Clinical\nAccuracy", "Evidence\nGrounding", "Actionability", "Clarity", "Safety"]
x = np.arange(len(metrics))
width = 0.8 / len(pareto_front)
for idx, entry in enumerate(pareto_front):
e = entry["evaluation"]
scores = [
e.clinical_accuracy.score,
e.evidence_grounding.score,
e.actionability.score,
e.clarity.score,
e.safety_completeness.score,
]
offset = (idx - len(pareto_front) / 2) * width + width / 2
label = f"SOP v{entry['version']}"
ax1.bar(x + offset, scores, width, label=label, alpha=0.8)
ax1.set_xlabel("Metrics", fontsize=12)
ax1.set_ylabel("Score", fontsize=12)
ax1.set_title("5D Performance Comparison (Bar Chart)", fontsize=14)
ax1.set_xticks(x)
ax1.set_xticklabels(metrics, fontsize=10)
ax1.set_ylim(0, 1.0)
ax1.legend(loc="upper left")
ax1.grid(True, alpha=0.3, axis="y")
# --- Plot 2: Radar Chart ---
ax2 = plt.subplot(1, 2, 2, projection="polar")
categories = ["Clinical\nAccuracy", "Evidence\nGrounding", "Actionability", "Clarity", "Safety"]
num_vars = len(categories)
angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
angles += angles[:1]
for entry in pareto_front:
e = entry["evaluation"]
values = [
e.clinical_accuracy.score,
e.evidence_grounding.score,
e.actionability.score,
e.clarity.score,
e.safety_completeness.score,
]
values += values[:1]
desc = entry.get("description", "")[:30]
label = f"SOP v{entry['version']}: {desc}"
ax2.plot(angles, values, "o-", linewidth=2, label=label)
ax2.fill(angles, values, alpha=0.15)
ax2.set_xticks(angles[:-1])
ax2.set_xticklabels(categories, size=10)
ax2.set_ylim(0, 1)
ax2.set_title("5D Performance Profiles (Radar Chart)", size=14, y=1.08)
ax2.legend(loc="upper left", bbox_to_anchor=(1.2, 1.0), fontsize=9)
ax2.grid(True)
plt.tight_layout()
# Create data directory if it doesn't exist
from pathlib import Path
data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
output_path = data_dir / "pareto_frontier_analysis.png"
plt.savefig(output_path, dpi=300, bbox_inches="tight")
plt.close()
print(f"\n✓ Visualization saved to: {output_path}")
def print_pareto_summary(pareto_front: list[dict[str, Any]]):
"""Print human-readable summary of Pareto frontier"""
print("\n" + "=" * 80)
print("PARETO FRONTIER ANALYSIS")
print("=" * 80)
print(f"\nFound {len(pareto_front)} optimal (non-dominated) solutions:\n")
for entry in pareto_front:
v = entry["version"]
p = entry.get("parent")
desc = entry.get("description", "Baseline")
e = entry["evaluation"]
print(f"SOP v{v} {f'(Child of v{p})' if p else '(Baseline)'}")
print(f" Description: {desc}")
print(f" Clinical Accuracy: {e.clinical_accuracy.score:.3f}")
print(f" Evidence Grounding: {e.evidence_grounding.score:.3f}")
print(f" Actionability: {e.actionability.score:.3f}")
print(f" Clarity: {e.clarity.score:.3f}")
print(f" Safety & Completeness: {e.safety_completeness.score:.3f}")
# Calculate average
avg_score = np.mean(e.to_vector())
print(f" Average Score: {avg_score:.3f}")
print()
print("=" * 80)
print("\nRECOMMENDATION:")
print("Review the visualizations and choose the SOP that best matches")
print("your strategic priorities (e.g., maximum accuracy vs. clarity).")
print("=" * 80)
def analyze_improvements(gene_pool_entries: list[dict[str, Any]]):
"""Analyze improvements over baseline"""
if len(gene_pool_entries) < 2:
print("\n⚠️ Not enough SOPs to analyze improvements")
return
baseline = gene_pool_entries[0]
baseline_scores = np.array(baseline["evaluation"].to_vector())
print("\n" + "=" * 80)
print("IMPROVEMENT ANALYSIS")
print("=" * 80)
print(f"\nBaseline (v{baseline['version']}): {baseline.get('description', 'Initial')}")
print(f" Average Score: {np.mean(baseline_scores):.3f}")
improvements_found = False
for entry in gene_pool_entries[1:]:
scores = np.array(entry["evaluation"].to_vector())
avg_score = np.mean(scores)
baseline_avg = np.mean(baseline_scores)
if avg_score > baseline_avg:
improvements_found = True
improvement_pct = ((avg_score - baseline_avg) / baseline_avg) * 100
print(f"\n✓ SOP v{entry['version']}: {entry.get('description', '')}")
print(f" Average Score: {avg_score:.3f} (+{improvement_pct:.1f}% vs baseline)")
# Show per-metric improvements
metric_names = [
"Clinical Accuracy",
"Evidence Grounding",
"Actionability",
"Clarity",
"Safety & Completeness",
]
for i, (name, score, baseline_score) in enumerate(zip(metric_names, scores, baseline_scores)):
diff = score - baseline_score
if abs(diff) > 0.01: # Show significant changes
symbol = "↑" if diff > 0 else "↓"
print(f" {name}: {score:.3f} {symbol} ({diff:+.3f})")
if not improvements_found:
print("\n⚠️ No improvements found over baseline yet")
print(" Consider running more evolution cycles or adjusting mutation strategies")
print("\n" + "=" * 80)
|