{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":118765,"databundleVersionId":15231210,"sourceType":"competition"},{"sourceId":14723884,"sourceType":"datasetVersion","datasetId":9408086}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"fa65a3cc5ad40e79","cell_type":"code","source":"\"\"\"\n# RNA Folding Submission Comparison Tool\nCompare different model versions with statistics, visualizations, and analysis.\n\n## Usage:\n1. Place your submission files in the same directory or specify paths\n2. Update the SUBMISSIONS dict below with your file paths\n3. Run all cells to generate comparison report\n\"\"\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:23.207763Z","iopub.execute_input":"2026-02-03T23:03:23.208344Z","iopub.status.idle":"2026-02-03T23:03:23.219085Z","shell.execute_reply.started":"2026-02-03T23:03:23.208307Z","shell.execute_reply":"2026-02-03T23:03:23.21794Z"}},"outputs":[],"execution_count":null},{"id":"3b05078dcb6a61dd","cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom pathlib import Path\nfrom scipy import stats\nfrom scipy.spatial.distance import cdist\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Set style for better visualizations\nplt.style.use('seaborn-v0_8-whitegrid')\nsns.set_palette(\"husl\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:23.220472Z","iopub.execute_input":"2026-02-03T23:03:23.220881Z","iopub.status.idle":"2026-02-03T23:03:27.180708Z","shell.execute_reply.started":"2026-02-03T23:03:23.220832Z","shell.execute_reply":"2026-02-03T23:03:27.179603Z"}},"outputs":[],"execution_count":null},{"id":"1b70efa57fa624f","cell_type":"code","source":"# =============================================================================\n# CONFIGURATION - Update these paths to your submission files\n# =============================================================================\n\nSUBMISSIONS = {\n    'v1': '/kaggle/input/rnafold-submissions/V1.csv',\n    'v2': '/kaggle/input/rnafold-submissions/V2.csv',\n    'v3': '/kaggle/input/rnafold-submissions/V3.csv',\n    'v4': '/kaggle/input/rnafold-submissions/V4(V1).csv',\n}\n\n# Optional: Path to test sequences for additional analysis\nTEST_SEQUENCES_PATH = '/kaggle/input/stanford-rna-3d-folding-2/test_sequences.csv'\n\n# Filter to only existing files\ndef load_submissions(submission_dict):\n    \"\"\"Load all available submission files.\"\"\"\n    loaded = {}\n    for name, path in submission_dict.items():\n        if Path(path).exists():\n            try:\n                df = pd.read_csv(path)\n                loaded[name] = df\n                print(f\"✓ Loaded {name}: {path} ({len(df)} rows)\")\n            except Exception as e:\n                print(f\"✗ Failed to load {name}: {e}\")\n        else:\n            print(f\"✗ File not found: {path}\")\n    return loaded\n\nsubmissions = load_submissions(SUBMISSIONS)\n\nif len(submissions) < 2:\n    print(\"\\n⚠️ Warning: Need at least 2 submissions to compare!\")\n    print(\"Please update the SUBMISSIONS dict with valid file paths.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:27.183287Z","iopub.execute_input":"2026-02-03T23:03:27.183687Z","iopub.status.idle":"2026-02-03T23:03:27.504347Z","shell.execute_reply.started":"2026-02-03T23:03:27.18366Z","shell.execute_reply":"2026-02-03T23:03:27.503399Z"}},"outputs":[],"execution_count":null},{"id":"c3d071a2f123f6b0","cell_type":"code","source":"# =============================================================================\n# HELPER FUNCTIONS\n# =============================================================================\n\ndef extract_target_id(id_col):\n    \"\"\"Extract target_id from ID column (format: target_id_resid).\"\"\"\n    return id_col.str.rsplit('_', n=1).str[0]\n\ndef get_coordinates(df, model_num=1):\n    \"\"\"Extract x, y, z coordinates for a specific model.\"\"\"\n    return df[[f'x_{model_num}', f'y_{model_num}', f'z_{model_num}']].values\n\ndef compute_per_residue_rmsd(coords1, coords2):\n    \"\"\"Compute per-residue distance between two coordinate sets.\"\"\"\n    return np.sqrt(np.sum((coords1 - coords2) ** 2, axis=1))\n\ndef compute_structure_stats(coords):\n    \"\"\"Compute structural statistics for a coordinate set.\"\"\"\n    # Bond lengths (consecutive residue distances)\n    bonds = np.linalg.norm(coords[1:] - coords[:-1], axis=1)\n\n    # Radius of gyration\n    center = coords.mean(axis=0)\n    rg = np.sqrt(np.mean(np.sum((coords - center) ** 2, axis=1)))\n\n    # Span (max distance)\n    span = np.max(cdist(coords, coords))\n\n    return {\n        'bond_mean': np.mean(bonds),\n        'bond_std': np.std(bonds),\n        'bond_min': np.min(bonds),\n        'bond_max': np.max(bonds),\n        'radius_of_gyration': rg,\n        'span': span,\n        'coord_range_x': coords[:, 0].max() - coords[:, 0].min(),\n        'coord_range_y': coords[:, 1].max() - coords[:, 1].min(),\n        'coord_range_z': coords[:, 2].max() - coords[:, 2].min(),\n    }\n\ndef compute_model_diversity(df, target_id):\n    \"\"\"Compute diversity among the 5 models for a single target.\"\"\"\n    mask = extract_target_id(df['ID']) == target_id\n    target_df = df[mask].sort_values('resid')\n\n    if len(target_df) == 0:\n        return None\n\n    # Get coordinates for all 5 models\n    models = [get_coordinates(target_df, i) for i in range(1, 6)]\n\n    # Compute pairwise RMSD between models\n    n_models = len(models)\n    pairwise_rmsd = []\n    for i in range(n_models):\n        for j in range(i + 1, n_models):\n            rmsd = np.sqrt(np.mean(np.sum((models[i] - models[j]) ** 2, axis=1)))\n            pairwise_rmsd.append(rmsd)\n\n    return {\n        'mean_pairwise_rmsd': np.mean(pairwise_rmsd),\n        'max_pairwise_rmsd': np.max(pairwise_rmsd),\n        'min_pairwise_rmsd': np.min(pairwise_rmsd),\n        'std_pairwise_rmsd': np.std(pairwise_rmsd),\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:27.505811Z","iopub.execute_input":"2026-02-03T23:03:27.506202Z","iopub.status.idle":"2026-02-03T23:03:27.519331Z","shell.execute_reply.started":"2026-02-03T23:03:27.506167Z","shell.execute_reply":"2026-02-03T23:03:27.518262Z"}},"outputs":[],"execution_count":null},{"id":"5bd66fe2d3b39e17","cell_type":"code","source":"# =============================================================================\n# 1. BASIC STATISTICS COMPARISON\n# =============================================================================\n\nif len(submissions) >= 1:\n    print(\"=\" * 80)\n    print(\"1. BASIC STATISTICS\")\n    print(\"=\" * 80)\n\n    basic_stats = []\n    for name, df in submissions.items():\n        # Count unique targets\n        target_ids = extract_target_id(df['ID']).unique()\n\n        # Coordinate statistics for model 1\n        coords = get_coordinates(df, 1)\n\n        stats_row = {\n            'Version': name,\n            'Total Rows': len(df),\n            'Unique Targets': len(target_ids),\n            'X Mean': coords[:, 0].mean(),\n            'Y Mean': coords[:, 1].mean(),\n            'Z Mean': coords[:, 2].mean(),\n            'X Std': coords[:, 0].std(),\n            'Y Std': coords[:, 1].std(),\n            'Z Std': coords[:, 2].std(),\n            'Coord Min': coords.min(),\n            'Coord Max': coords.max(),\n        }\n        basic_stats.append(stats_row)\n\n    basic_stats_df = pd.DataFrame(basic_stats)\n    print(basic_stats_df.to_string(index=False))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:27.52059Z","iopub.execute_input":"2026-02-03T23:03:27.52094Z","iopub.status.idle":"2026-02-03T23:03:27.727568Z","shell.execute_reply.started":"2026-02-03T23:03:27.520903Z","shell.execute_reply":"2026-02-03T23:03:27.726095Z"}},"outputs":[],"execution_count":null},{"id":"2513462b65b5cc76","cell_type":"code","source":"# =============================================================================\n# 2. BOND LENGTH ANALYSIS\n# =============================================================================\n\nif len(submissions) >= 1:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"2. BOND LENGTH ANALYSIS (Model 1)\")\n    print(\"=\" * 80)\n\n    fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\n    bond_stats = []\n    all_bonds = {}\n\n    for name, df in submissions.items():\n        target_ids = extract_target_id(df['ID']).unique()\n\n        all_target_bonds = []\n        for tid in target_ids[:100]:  # Sample 100 targets for efficiency\n            mask = extract_target_id(df['ID']) == tid\n            target_df = df[mask].sort_values('resid')\n            coords = get_coordinates(target_df, 1)\n            bonds = np.linalg.norm(coords[1:] - coords[:-1], axis=1)\n            all_target_bonds.extend(bonds)\n\n        all_bonds[name] = np.array(all_target_bonds)\n\n        bond_stats.append({\n            'Version': name,\n            'Mean Bond': np.mean(all_target_bonds),\n            'Std Bond': np.std(all_target_bonds),\n            'Min Bond': np.min(all_target_bonds),\n            'Max Bond': np.max(all_target_bonds),\n            '% in [5.0, 7.0]Å': 100 * np.mean((np.array(all_target_bonds) >= 5.0) &\n                                               (np.array(all_target_bonds) <= 7.0)),\n        })\n\n    bond_stats_df = pd.DataFrame(bond_stats)\n    print(bond_stats_df.to_string(index=False))\n\n    # Plot bond length distributions\n    ax1 = axes[0]\n    for name, bonds in all_bonds.items():\n        ax1.hist(bonds, bins=50, alpha=0.5, label=name, density=True)\n    ax1.axvline(x=5.95, color='red', linestyle='--', label='Target (5.95Å)')\n    ax1.set_xlabel('Bond Length (Å)')\n    ax1.set_ylabel('Density')\n    ax1.set_title('Bond Length Distribution')\n    ax1.legend()\n    ax1.set_xlim(0, 15)\n\n    # Box plot comparison\n    ax2 = axes[1]\n    bond_data = pd.DataFrame({name: pd.Series(bonds) for name, bonds in all_bonds.items()})\n    bond_data_melted = bond_data.melt(var_name='Version', value_name='Bond Length')\n    sns.boxplot(data=bond_data_melted, x='Version', y='Bond Length', ax=ax2)\n    ax2.axhline(y=5.95, color='red', linestyle='--', label='Target (5.95Å)')\n    ax2.set_title('Bond Length Box Plot')\n    ax2.set_ylim(0, 15)\n\n    plt.tight_layout()\n    plt.savefig('bond_length_comparison.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    print(\"Saved: bond_length_comparison.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:27.728482Z","iopub.execute_input":"2026-02-03T23:03:27.728766Z","iopub.status.idle":"2026-02-03T23:03:31.271678Z","shell.execute_reply.started":"2026-02-03T23:03:27.728738Z","shell.execute_reply":"2026-02-03T23:03:31.270459Z"}},"outputs":[],"execution_count":null},{"id":"9e44e56b2e4f119e","cell_type":"code","source":"# =============================================================================\n# 3. PAIRWISE VERSION COMPARISON\n# =============================================================================\n\nif len(submissions) >= 2:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"3. PAIRWISE VERSION COMPARISON (Model 1)\")\n    print(\"=\" * 80)\n\n    version_names = list(submissions.keys())\n    n_versions = len(version_names)\n\n    # Create comparison matrix\n    comparison_matrix = np.zeros((n_versions, n_versions))\n\n    # Get common targets across all versions\n    common_targets = None\n    for name, df in submissions.items():\n        targets = set(extract_target_id(df['ID']).unique())\n        if common_targets is None:\n            common_targets = targets\n        else:\n            common_targets = common_targets.intersection(targets)\n\n    print(f\"Common targets across all versions: {len(common_targets)}\")\n\n    # Sample targets for comparison\n    sample_targets = list(common_targets)[:50]\n\n    pairwise_results = []\n\n    for i, name1 in enumerate(version_names):\n        for j, name2 in enumerate(version_names):\n            if i >= j:\n                continue\n\n            df1, df2 = submissions[name1], submissions[name2]\n\n            target_rmsds = []\n            for tid in sample_targets:\n                mask1 = extract_target_id(df1['ID']) == tid\n                mask2 = extract_target_id(df2['ID']) == tid\n\n                coords1 = get_coordinates(df1[mask1].sort_values('resid'), 1)\n                coords2 = get_coordinates(df2[mask2].sort_values('resid'), 1)\n\n                if len(coords1) == len(coords2) and len(coords1) > 0:\n                    rmsd = np.sqrt(np.mean(np.sum((coords1 - coords2) ** 2, axis=1)))\n                    target_rmsds.append(rmsd)\n\n            mean_rmsd = np.mean(target_rmsds) if target_rmsds else np.nan\n            comparison_matrix[i, j] = mean_rmsd\n            comparison_matrix[j, i] = mean_rmsd\n\n            pairwise_results.append({\n                'Version 1': name1,\n                'Version 2': name2,\n                'Mean RMSD': mean_rmsd,\n                'Std RMSD': np.std(target_rmsds) if target_rmsds else np.nan,\n                'Max RMSD': np.max(target_rmsds) if target_rmsds else np.nan,\n                'Min RMSD': np.min(target_rmsds) if target_rmsds else np.nan,\n            })\n\n    pairwise_df = pd.DataFrame(pairwise_results)\n    print(\"\\nPairwise RMSD Statistics:\")\n    print(pairwise_df.to_string(index=False))\n\n    # Heatmap\n    if n_versions > 2:\n        fig, ax = plt.subplots(figsize=(8, 6))\n        mask = np.eye(n_versions, dtype=bool)\n        sns.heatmap(comparison_matrix, annot=True, fmt='.2f',\n                    xticklabels=version_names, yticklabels=version_names,\n                    cmap='RdYlGn_r', mask=mask, ax=ax)\n        ax.set_title('Pairwise RMSD Between Versions (Model 1)')\n        plt.tight_layout()\n        plt.savefig('pairwise_rmsd_heatmap.png', dpi=150, bbox_inches='tight')\n        plt.show()\n        print(\"Saved: pairwise_rmsd_heatmap.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:31.273107Z","iopub.execute_input":"2026-02-03T23:03:31.273761Z","iopub.status.idle":"2026-02-03T23:03:37.653972Z","shell.execute_reply.started":"2026-02-03T23:03:31.273732Z","shell.execute_reply":"2026-02-03T23:03:37.653003Z"}},"outputs":[],"execution_count":null},{"id":"273860411640d002","cell_type":"code","source":"# =============================================================================\n# 4. MODEL DIVERSITY ANALYSIS\n# =============================================================================\n\nif len(submissions) >= 1:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"4. MODEL DIVERSITY ANALYSIS (5 predictions per target)\")\n    print(\"=\" * 80)\n\n    diversity_stats = []\n    diversity_data = {}\n\n    for name, df in submissions.items():\n        target_ids = extract_target_id(df['ID']).unique()\n\n        target_diversities = []\n        for tid in target_ids[:100]:  # Sample 100 targets\n            div = compute_model_diversity(df, tid)\n            if div:\n                target_diversities.append(div['mean_pairwise_rmsd'])\n\n        diversity_data[name] = target_diversities\n\n        diversity_stats.append({\n            'Version': name,\n            'Mean Diversity (RMSD)': np.mean(target_diversities),\n            'Std Diversity': np.std(target_diversities),\n            'Min Diversity': np.min(target_diversities),\n            'Max Diversity': np.max(target_diversities),\n        })\n\n    diversity_stats_df = pd.DataFrame(diversity_stats)\n    print(diversity_stats_df.to_string(index=False))\n\n    # Plot diversity distributions\n    fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\n    ax1 = axes[0]\n    for name, divs in diversity_data.items():\n        ax1.hist(divs, bins=30, alpha=0.5, label=name, density=True)\n    ax1.set_xlabel('Mean Pairwise RMSD Between Models (Å)')\n    ax1.set_ylabel('Density')\n    ax1.set_title('Model Diversity Distribution')\n    ax1.legend()\n\n    ax2 = axes[1]\n    div_df = pd.DataFrame({name: pd.Series(divs) for name, divs in diversity_data.items()})\n    div_melted = div_df.melt(var_name='Version', value_name='Diversity (RMSD)')\n    sns.boxplot(data=div_melted, x='Version', y='Diversity (RMSD)', ax=ax2)\n    ax2.set_title('Model Diversity Box Plot')\n\n    plt.tight_layout()\n    plt.savefig('model_diversity_comparison.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    print(\"Saved: model_diversity_comparison.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:37.655178Z","iopub.execute_input":"2026-02-03T23:03:37.655455Z","iopub.status.idle":"2026-02-03T23:03:40.970207Z","shell.execute_reply.started":"2026-02-03T23:03:37.655428Z","shell.execute_reply":"2026-02-03T23:03:40.968954Z"}},"outputs":[],"execution_count":null},{"id":"1b2a46c7f9cf5f36","cell_type":"code","source":"# =============================================================================\n# 5. STRUCTURAL QUALITY METRICS\n# =============================================================================\n\nif len(submissions) >= 1:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"5. STRUCTURAL QUALITY METRICS\")\n    print(\"=\" * 80)\n\n    quality_stats = []\n\n    for name, df in submissions.items():\n        target_ids = extract_target_id(df['ID']).unique()\n\n        all_rg = []\n        all_span = []\n\n        for tid in target_ids[:100]:\n            mask = extract_target_id(df['ID']) == tid\n            target_df = df[mask].sort_values('resid')\n            coords = get_coordinates(target_df, 1)\n\n            if len(coords) > 1:\n                struct_stats = compute_structure_stats(coords)\n                all_rg.append(struct_stats['radius_of_gyration'])\n                all_span.append(struct_stats['span'])\n\n        quality_stats.append({\n            'Version': name,\n            'Mean Rg': np.mean(all_rg),\n            'Std Rg': np.std(all_rg),\n            'Mean Span': np.mean(all_span),\n            'Std Span': np.std(all_span),\n        })\n\n    quality_stats_df = pd.DataFrame(quality_stats)\n    print(quality_stats_df.to_string(index=False))\n\n    # Plot Rg vs Span\n    fig, ax = plt.subplots(figsize=(10, 6))\n\n    for name, df in submissions.items():\n        target_ids = extract_target_id(df['ID']).unique()\n\n        rg_list = []\n        span_list = []\n        size_list = []\n\n        for tid in target_ids[:100]:\n            mask = extract_target_id(df['ID']) == tid\n            target_df = df[mask].sort_values('resid')\n            coords = get_coordinates(target_df, 1)\n\n            if len(coords) > 1:\n                struct_stats = compute_structure_stats(coords)\n                rg_list.append(struct_stats['radius_of_gyration'])\n                span_list.append(struct_stats['span'])\n                size_list.append(len(coords))\n\n        ax.scatter(rg_list, span_list, alpha=0.5, label=name, s=30)\n\n    ax.set_xlabel('Radius of Gyration (Å)')\n    ax.set_ylabel('Span (Å)')\n    ax.set_title('Structural Compactness: Rg vs Span')\n    ax.legend()\n\n    plt.tight_layout()\n    plt.savefig('structural_quality.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    print(\"Saved: structural_quality.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:40.973803Z","iopub.execute_input":"2026-02-03T23:03:40.974101Z","iopub.status.idle":"2026-02-03T23:03:47.294866Z","shell.execute_reply.started":"2026-02-03T23:03:40.974077Z","shell.execute_reply":"2026-02-03T23:03:47.293621Z"}},"outputs":[],"execution_count":null},{"id":"cf688d3d4eee963b","cell_type":"code","source":"# =============================================================================\n# 6. PER-TARGET DIFFERENCE ANALYSIS\n# =============================================================================\n\nif len(submissions) >= 2:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"6. PER-TARGET DIFFERENCE ANALYSIS\")\n    print(\"=\" * 80)\n\n    # Compare first two versions in detail\n    v1_name, v2_name = list(submissions.keys())[:2]\n    df1, df2 = submissions[v1_name], submissions[v2_name]\n\n    target_ids = extract_target_id(df1['ID']).unique()\n\n    target_analysis = []\n\n    for tid in target_ids[:200]:  # Analyze 200 targets\n        mask1 = extract_target_id(df1['ID']) == tid\n        mask2 = extract_target_id(df2['ID']) == tid\n\n        if mask2.sum() == 0:\n            continue\n\n        coords1 = get_coordinates(df1[mask1].sort_values('resid'), 1)\n        coords2 = get_coordinates(df2[mask2].sort_values('resid'), 1)\n\n        if len(coords1) != len(coords2):\n            continue\n\n        # Compute per-residue differences\n        per_res_diff = compute_per_residue_rmsd(coords1, coords2)\n\n        # Bond lengths\n        bonds1 = np.linalg.norm(coords1[1:] - coords1[:-1], axis=1)\n        bonds2 = np.linalg.norm(coords2[1:] - coords2[:-1], axis=1)\n\n        target_analysis.append({\n            'target_id': tid,\n            'length': len(coords1),\n            'rmsd': np.sqrt(np.mean(per_res_diff ** 2)),\n            'max_diff': np.max(per_res_diff),\n            f'{v1_name}_bond_mean': np.mean(bonds1),\n            f'{v2_name}_bond_mean': np.mean(bonds2),\n            'bond_diff': np.mean(np.abs(bonds1 - bonds2)),\n        })\n\n    target_df = pd.DataFrame(target_analysis)\n\n    print(f\"\\nComparison: {v1_name} vs {v2_name}\")\n    print(f\"Targets analyzed: {len(target_df)}\")\n    print(f\"\\nTop 10 targets with LARGEST differences:\")\n    print(target_df.nlargest(10, 'rmsd')[['target_id', 'length', 'rmsd', 'max_diff']].to_string(index=False))\n\n    print(f\"\\nTop 10 targets with SMALLEST differences:\")\n    print(target_df.nsmallest(10, 'rmsd')[['target_id', 'length', 'rmsd', 'max_diff']].to_string(index=False))\n\n    # Plot RMSD vs sequence length\n    fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\n    ax1 = axes[0]\n    ax1.scatter(target_df['length'], target_df['rmsd'], alpha=0.5, s=30)\n    ax1.set_xlabel('Sequence Length')\n    ax1.set_ylabel('RMSD (Å)')\n    ax1.set_title(f'RMSD vs Sequence Length ({v1_name} vs {v2_name})')\n\n    ax2 = axes[1]\n    ax2.hist(target_df['rmsd'], bins=50, edgecolor='black', alpha=0.7)\n    ax2.set_xlabel('RMSD (Å)')\n    ax2.set_ylabel('Count')\n    ax2.set_title('Distribution of Per-Target RMSD')\n    ax2.axvline(x=target_df['rmsd'].mean(), color='red', linestyle='--',\n                label=f'Mean: {target_df[\"rmsd\"].mean():.2f}Å')\n    ax2.legend()\n\n    plt.tight_layout()\n    plt.savefig('per_target_analysis.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    print(\"Saved: per_target_analysis.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:47.296067Z","iopub.execute_input":"2026-02-03T23:03:47.296379Z","iopub.status.idle":"2026-02-03T23:03:49.215895Z","shell.execute_reply.started":"2026-02-03T23:03:47.296353Z","shell.execute_reply":"2026-02-03T23:03:49.214854Z"}},"outputs":[],"execution_count":null},{"id":"d68ed1bcf480dfcf","cell_type":"code","source":"# =============================================================================\n# 7. COORDINATE DISTRIBUTION ANALYSIS\n# =============================================================================\n\nif len(submissions) >= 1:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"7. COORDINATE DISTRIBUTION ANALYSIS\")\n    print(\"=\" * 80)\n\n    fig, axes = plt.subplots(2, 3, figsize=(15, 10))\n\n    for idx, (name, df) in enumerate(submissions.items()):\n        if idx >= 6:  # Max 6 versions in plot\n            break\n\n        coords = get_coordinates(df, 1)\n\n        row, col = idx // 3, idx % 3\n        ax = axes[row, col] if len(submissions) > 3 else axes[col]\n\n        # 2D projection (X vs Y)\n        sample_idx = np.random.choice(len(coords), min(5000, len(coords)), replace=False)\n        ax.scatter(coords[sample_idx, 0], coords[sample_idx, 1], alpha=0.1, s=1)\n        ax.set_xlabel('X (Å)')\n        ax.set_ylabel('Y (Å)')\n        ax.set_title(f'{name} - XY Projection')\n        ax.set_aspect('equal')\n\n    # Hide unused subplots\n    for idx in range(len(submissions), 6):\n        row, col = idx // 3, idx % 3\n        if len(submissions) > 3:\n            axes[row, col].set_visible(False)\n\n    plt.tight_layout()\n    plt.savefig('coordinate_distributions.png', dpi=150, bbox_inches='tight')\n    plt.show()\n    print(\"Saved: coordinate_distributions.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:49.217066Z","iopub.execute_input":"2026-02-03T23:03:49.217345Z","iopub.status.idle":"2026-02-03T23:03:50.687542Z","shell.execute_reply.started":"2026-02-03T23:03:49.217322Z","shell.execute_reply":"2026-02-03T23:03:50.686297Z"}},"outputs":[],"execution_count":null},{"id":"528641bd4173c5b","cell_type":"code","source":"# =============================================================================\n# 8. SUMMARY REPORT\n# =============================================================================\n\nif len(submissions) >= 1:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"8. SUMMARY REPORT\")\n    print(\"=\" * 80)\n\n    summary_data = []\n\n    for name, df in submissions.items():\n        target_ids = extract_target_id(df['ID']).unique()\n        coords = get_coordinates(df, 1)\n\n        # Bond lengths\n        all_bonds = []\n        for tid in target_ids[:100]:\n            mask = extract_target_id(df['ID']) == tid\n            target_df = df[mask].sort_values('resid')\n            target_coords = get_coordinates(target_df, 1)\n            bonds = np.linalg.norm(target_coords[1:] - target_coords[:-1], axis=1)\n            all_bonds.extend(bonds)\n\n        all_bonds = np.array(all_bonds)\n\n        # Model diversity\n        diversities = []\n        for tid in target_ids[:50]:\n            div = compute_model_diversity(df, tid)\n            if div:\n                diversities.append(div['mean_pairwise_rmsd'])\n\n        summary_data.append({\n            'Version': name,\n            'Targets': len(target_ids),\n            'Total Residues': len(df),\n            'Bond Mean (Å)': f\"{np.mean(all_bonds):.2f}\",\n            'Bond Std (Å)': f\"{np.std(all_bonds):.2f}\",\n            '% Good Bonds': f\"{100 * np.mean((all_bonds >= 5.0) & (all_bonds <= 7.0)):.1f}%\",\n            'Model Diversity': f\"{np.mean(diversities):.2f}\" if diversities else \"N/A\",\n        })\n\n    summary_df = pd.DataFrame(summary_data)\n\n    print(\"\\n\" + \"─\" * 100)\n    print(\"FINAL SUMMARY TABLE\")\n    print(\"─\" * 100)\n    print(summary_df.to_string(index=False))\n    print(\"─\" * 100)\n\n    # Save summary to CSV\n    summary_df.to_csv('comparison_summary.csv', index=False)\n    print(\"\\nSaved: comparison_summary.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:50.68877Z","iopub.execute_input":"2026-02-03T23:03:50.68908Z","iopub.status.idle":"2026-02-03T23:03:55.224428Z","shell.execute_reply.started":"2026-02-03T23:03:50.689054Z","shell.execute_reply":"2026-02-03T23:03:55.223385Z"}},"outputs":[],"execution_count":null},{"id":"b89a853c479dcbe4","cell_type":"code","source":"# =============================================================================\n# 9. STATISTICAL SIGNIFICANCE TESTS\n# =============================================================================\n\nif len(submissions) >= 2:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"9. STATISTICAL SIGNIFICANCE TESTS\")\n    print(\"=\" * 80)\n\n    version_names = list(submissions.keys())\n\n    print(\"\\nMann-Whitney U Test for Bond Length Distributions:\")\n    print(\"(Tests if bond lengths come from different distributions)\")\n    print(\"-\" * 60)\n\n    for i, name1 in enumerate(version_names):\n        for j, name2 in enumerate(version_names):\n            if i >= j:\n                continue\n\n            # Get bond lengths for each version\n            bonds1 = []\n            bonds2 = []\n\n            df1, df2 = submissions[name1], submissions[name2]\n            targets1 = extract_target_id(df1['ID']).unique()[:50]\n            targets2 = extract_target_id(df2['ID']).unique()[:50]\n\n            for tid in targets1:\n                mask = extract_target_id(df1['ID']) == tid\n                coords = get_coordinates(df1[mask].sort_values('resid'), 1)\n                bonds1.extend(np.linalg.norm(coords[1:] - coords[:-1], axis=1))\n\n            for tid in targets2:\n                mask = extract_target_id(df2['ID']) == tid\n                coords = get_coordinates(df2[mask].sort_values('resid'), 1)\n                bonds2.extend(np.linalg.norm(coords[1:] - coords[:-1], axis=1))\n\n            stat, p_value = stats.mannwhitneyu(bonds1, bonds2, alternative='two-sided')\n\n            significance = \"***\" if p_value < 0.001 else \"**\" if p_value < 0.01 else \"*\" if p_value < 0.05 else \"\"\n            print(f\"{name1} vs {name2}: U={stat:.0f}, p={p_value:.2e} {significance}\")\n\n    print(\"\\nSignificance levels: * p<0.05, ** p<0.01, *** p<0.001\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:03:55.225718Z","iopub.execute_input":"2026-02-03T23:03:55.226005Z","iopub.status.idle":"2026-02-03T23:04:01.844091Z","shell.execute_reply.started":"2026-02-03T23:03:55.22598Z","shell.execute_reply":"2026-02-03T23:04:01.843093Z"}},"outputs":[],"execution_count":null},{"id":"af8715b03fe22049","cell_type":"code","source":"# =============================================================================\n# 10. EXPORT DETAILED COMPARISON DATA\n# =============================================================================\n\nif len(submissions) >= 2:\n    print(\"\\n\" + \"=\" * 80)\n    print(\"10. EXPORT DETAILED COMPARISON DATA\")\n    print(\"=\" * 80)\n\n    # Export per-target comparison for first two versions\n    v1_name, v2_name = list(submissions.keys())[:2]\n\n    if len(target_analysis) > 0:\n        target_df.to_csv(f'comparison_{v1_name}_vs_{v2_name}.csv', index=False)\n        print(f\"Saved: comparison_{v1_name}_vs_{v2_name}.csv\")\n\n    print(\"\\nAll output files:\")\n    print(\"  - bond_length_comparison.png\")\n    print(\"  - pairwise_rmsd_heatmap.png (if >2 versions)\")\n    print(\"  - model_diversity_comparison.png\")\n    print(\"  - structural_quality.png\")\n    print(\"  - per_target_analysis.png\")\n    print(\"  - coordinate_distributions.png\")\n    print(\"  - comparison_summary.csv\")\n    print(f\"  - comparison_{v1_name}_vs_{v2_name}.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:04:01.845223Z","iopub.execute_input":"2026-02-03T23:04:01.845484Z","iopub.status.idle":"2026-02-03T23:04:01.893416Z","shell.execute_reply.started":"2026-02-03T23:04:01.845461Z","shell.execute_reply":"2026-02-03T23:04:01.892557Z"}},"outputs":[],"execution_count":null},{"id":"3aec9d78173c6141","cell_type":"code","source":"print(\"\\n\" + \"=\" * 80)\nprint(\"COMPARISON COMPLETE!\")\nprint(\"=\" * 80)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-03T23:04:01.894734Z","iopub.execute_input":"2026-02-03T23:04:01.895074Z","iopub.status.idle":"2026-02-03T23:04:01.900816Z","shell.execute_reply.started":"2026-02-03T23:04:01.895043Z","shell.execute_reply":"2026-02-03T23:04:01.899853Z"}},"outputs":[],"execution_count":null}]}