Created
February 23, 2026 00:24
-
-
Save jbdamask/936b06ed1714238d2dd4f1e2b3a2be64 to your computer and use it in GitHub Desktop.
NowIGetIt: multi.pdf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>MA-CMAB: Multi-Agent Bandits for Welfare Maximization</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap'); | |
| :root { | |
| --bg-primary: #0a0a0f; | |
| --bg-secondary: #12121a; | |
| --bg-card: #1a1a2e; | |
| --bg-card-hover: #222240; | |
| --accent-blue: #4fc3f7; | |
| --accent-purple: #b388ff; | |
| --accent-pink: #ff80ab; | |
| --accent-green: #69f0ae; | |
| --accent-orange: #ffab40; | |
| --accent-cyan: #18ffff; | |
| --text-primary: #e8e8f0; | |
| --text-secondary: #9999bb; | |
| --text-dim: #666688; | |
| --border-color: #2a2a44; | |
| --glow-blue: rgba(79,195,247,0.3); | |
| --glow-purple: rgba(179,136,255,0.3); | |
| } | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| html { scroll-behavior: smooth; } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| overflow-x: hidden; | |
| line-height: 1.7; | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { width: 6px; } | |
| ::-webkit-scrollbar-track { background: var(--bg-primary); } | |
| ::-webkit-scrollbar-thumb { background: var(--accent-purple); border-radius: 3px; } | |
| /* Canvas background */ | |
| #bg-canvas { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 0; | |
| pointer-events: none; | |
| } | |
| .content-wrapper { | |
| position: relative; | |
| z-index: 1; | |
| } | |
| /* Navigation */ | |
| nav { | |
| position: fixed; | |
| top: 0; left: 0; right: 0; | |
| z-index: 100; | |
| background: rgba(10,10,15,0.85); | |
| backdrop-filter: blur(20px); | |
| border-bottom: 1px solid var(--border-color); | |
| padding: 0 2rem; | |
| transition: all 0.3s ease; | |
| } | |
| nav.scrolled { | |
| box-shadow: 0 4px 30px rgba(0,0,0,0.5); | |
| } | |
| .nav-inner { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| height: 64px; | |
| } | |
| .nav-logo { | |
| font-weight: 800; | |
| font-size: 1.1rem; | |
| background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| letter-spacing: -0.5px; | |
| } | |
| .nav-links { | |
| display: flex; | |
| gap: 0.25rem; | |
| } | |
| .nav-links a { | |
| color: var(--text-secondary); | |
| text-decoration: none; | |
| font-size: 0.82rem; | |
| font-weight: 500; | |
| padding: 0.4rem 0.8rem; | |
| border-radius: 8px; | |
| transition: all 0.2s; | |
| } | |
| .nav-links a:hover { | |
| color: var(--text-primary); | |
| background: rgba(179,136,255,0.1); | |
| } | |
| /* Hero */ | |
| .hero { | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| text-align: center; | |
| padding: 6rem 2rem 4rem; | |
| position: relative; | |
| } | |
| .hero-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| background: rgba(179,136,255,0.1); | |
| border: 1px solid rgba(179,136,255,0.2); | |
| padding: 0.4rem 1rem; | |
| border-radius: 50px; | |
| font-size: 0.8rem; | |
| color: var(--accent-purple); | |
| margin-bottom: 2rem; | |
| font-weight: 500; | |
| } | |
| .hero-badge .dot { | |
| width: 6px; height: 6px; | |
| background: var(--accent-green); | |
| border-radius: 50%; | |
| animation: pulse-dot 2s ease-in-out infinite; | |
| } | |
| @keyframes pulse-dot { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.5; transform: scale(1.5); } | |
| } | |
| .hero h1 { | |
| font-size: clamp(2.2rem, 5vw, 4rem); | |
| font-weight: 900; | |
| line-height: 1.1; | |
| margin-bottom: 1.5rem; | |
| letter-spacing: -2px; | |
| max-width: 900px; | |
| } | |
| .hero h1 .gradient-text { | |
| background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple), var(--accent-pink)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .hero-subtitle { | |
| font-size: 1.15rem; | |
| color: var(--text-secondary); | |
| max-width: 650px; | |
| margin-bottom: 2.5rem; | |
| line-height: 1.8; | |
| } | |
| .hero-cta { | |
| display: flex; | |
| gap: 1rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .btn { | |
| padding: 0.75rem 1.8rem; | |
| border-radius: 12px; | |
| font-size: 0.9rem; | |
| font-weight: 600; | |
| text-decoration: none; | |
| transition: all 0.3s; | |
| cursor: pointer; | |
| border: none; | |
| font-family: inherit; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); | |
| color: #fff; | |
| box-shadow: 0 4px 20px var(--glow-blue); | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 30px var(--glow-purple); | |
| } | |
| .btn-secondary { | |
| background: rgba(255,255,255,0.05); | |
| color: var(--text-primary); | |
| border: 1px solid var(--border-color); | |
| } | |
| .btn-secondary:hover { | |
| background: rgba(255,255,255,0.1); | |
| border-color: var(--accent-purple); | |
| } | |
| .hero-stats { | |
| display: flex; | |
| gap: 3rem; | |
| margin-top: 4rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .hero-stat { | |
| text-align: center; | |
| } | |
| .hero-stat .value { | |
| font-size: 1.8rem; | |
| font-weight: 800; | |
| font-family: 'JetBrains Mono', monospace; | |
| background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .hero-stat .label { | |
| font-size: 0.75rem; | |
| color: var(--text-dim); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| margin-top: 0.25rem; | |
| } | |
| /* Sections */ | |
| section { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| padding: 5rem 2rem; | |
| } | |
| .section-header { | |
| text-align: center; | |
| margin-bottom: 3.5rem; | |
| } | |
| .section-tag { | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| color: var(--accent-purple); | |
| margin-bottom: 0.75rem; | |
| } | |
| .section-title { | |
| font-size: clamp(1.8rem, 3vw, 2.5rem); | |
| font-weight: 800; | |
| letter-spacing: -1px; | |
| margin-bottom: 1rem; | |
| } | |
| .section-desc { | |
| color: var(--text-secondary); | |
| max-width: 600px; | |
| margin: 0 auto; | |
| font-size: 1.05rem; | |
| } | |
| /* Cards grid */ | |
| .card-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .card { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| transition: all 0.35s; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 0; right: 0; | |
| height: 3px; | |
| background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple)); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .card:hover { | |
| transform: translateY(-4px); | |
| border-color: rgba(179,136,255,0.3); | |
| box-shadow: 0 12px 40px rgba(0,0,0,0.4); | |
| } | |
| .card:hover::before { | |
| opacity: 1; | |
| } | |
| .card-icon { | |
| width: 48px; height: 48px; | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.4rem; | |
| margin-bottom: 1.25rem; | |
| } | |
| .card h3 { | |
| font-size: 1.15rem; | |
| font-weight: 700; | |
| margin-bottom: 0.75rem; | |
| letter-spacing: -0.3px; | |
| } | |
| .card p { | |
| color: var(--text-secondary); | |
| font-size: 0.92rem; | |
| line-height: 1.7; | |
| } | |
| /* Analogy section */ | |
| .analogy-container { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 20px; | |
| padding: 3rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .analogy-container::after { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; right: -20%; | |
| width: 300px; height: 300px; | |
| background: radial-gradient(circle, var(--glow-purple), transparent 70%); | |
| pointer-events: none; | |
| } | |
| .analogy-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 2rem; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .analogy-step { | |
| display: flex; | |
| gap: 1rem; | |
| align-items: flex-start; | |
| } | |
| .analogy-num { | |
| flex-shrink: 0; | |
| width: 36px; height: 36px; | |
| border-radius: 10px; | |
| background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 700; | |
| font-size: 0.85rem; | |
| } | |
| .analogy-step h4 { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| margin-bottom: 0.3rem; | |
| } | |
| .analogy-step p { | |
| color: var(--text-secondary); | |
| font-size: 0.88rem; | |
| } | |
| /* Interactive demo */ | |
| .demo-container { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 20px; | |
| padding: 2.5rem; | |
| overflow: hidden; | |
| } | |
| .demo-controls { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 2rem; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| } | |
| .demo-controls label { | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| font-weight: 500; | |
| } | |
| .demo-controls input[type="range"] { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| height: 6px; | |
| background: var(--border-color); | |
| border-radius: 3px; | |
| outline: none; | |
| width: 120px; | |
| } | |
| .demo-controls input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 18px; height: 18px; | |
| border-radius: 50%; | |
| background: var(--accent-purple); | |
| cursor: pointer; | |
| box-shadow: 0 0 10px var(--glow-purple); | |
| } | |
| .demo-display { | |
| display: flex; | |
| gap: 2rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .agent-column { | |
| flex: 1; | |
| min-width: 140px; | |
| max-width: 200px; | |
| } | |
| .agent-header { | |
| text-align: center; | |
| font-weight: 700; | |
| font-size: 0.9rem; | |
| padding: 0.5rem; | |
| border-radius: 10px; | |
| margin-bottom: 0.75rem; | |
| } | |
| .item-slot { | |
| background: rgba(255,255,255,0.03); | |
| border: 1px dashed var(--border-color); | |
| border-radius: 8px; | |
| padding: 0.5rem; | |
| margin-bottom: 0.4rem; | |
| text-align: center; | |
| font-size: 0.82rem; | |
| font-family: 'JetBrains Mono', monospace; | |
| transition: all 0.4s; | |
| min-height: 36px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .item-slot.filled { | |
| border-style: solid; | |
| background: rgba(79,195,247,0.08); | |
| border-color: rgba(79,195,247,0.3); | |
| } | |
| .welfare-bar { | |
| margin-top: 1.5rem; | |
| text-align: center; | |
| } | |
| .welfare-bar .bar-track { | |
| height: 10px; | |
| background: rgba(255,255,255,0.05); | |
| border-radius: 5px; | |
| overflow: hidden; | |
| margin: 0.5rem 0; | |
| } | |
| .welfare-bar .bar-fill { | |
| height: 100%; | |
| border-radius: 5px; | |
| transition: width 0.6s ease; | |
| background: linear-gradient(90deg, var(--accent-blue), var(--accent-green)); | |
| } | |
| .welfare-value { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 1.6rem; | |
| font-weight: 700; | |
| color: var(--accent-green); | |
| } | |
| /* Algorithm flow */ | |
| .flow-container { | |
| display: flex; | |
| align-items: stretch; | |
| gap: 0; | |
| position: relative; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| } | |
| .flow-step { | |
| flex: 1; | |
| min-width: 200px; | |
| max-width: 280px; | |
| padding: 2rem 1.5rem; | |
| text-align: center; | |
| position: relative; | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 16px; | |
| margin: 0 -1px; | |
| transition: all 0.3s; | |
| } | |
| .flow-step:hover { | |
| transform: scale(1.03); | |
| z-index: 2; | |
| border-color: var(--accent-purple); | |
| box-shadow: 0 8px 30px rgba(0,0,0,0.4); | |
| } | |
| .flow-arrow { | |
| display: flex; | |
| align-items: center; | |
| font-size: 1.5rem; | |
| color: var(--accent-purple); | |
| padding: 0 0.5rem; | |
| } | |
| .flow-step .step-num { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.7rem; | |
| color: var(--accent-blue); | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| margin-bottom: 0.5rem; | |
| } | |
| .flow-step h4 { | |
| font-size: 1.05rem; | |
| font-weight: 700; | |
| margin-bottom: 0.5rem; | |
| } | |
| .flow-step p { | |
| font-size: 0.82rem; | |
| color: var(--text-secondary); | |
| line-height: 1.6; | |
| } | |
| /* Regret visualization */ | |
| .regret-viz { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 20px; | |
| padding: 2.5rem; | |
| } | |
| #regret-chart { | |
| width: 100%; | |
| height: 350px; | |
| display: block; | |
| } | |
| .regret-legend { | |
| display: flex; | |
| gap: 2rem; | |
| justify-content: center; | |
| margin-top: 1.5rem; | |
| flex-wrap: wrap; | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| font-size: 0.82rem; | |
| color: var(--text-secondary); | |
| } | |
| .legend-dot { | |
| width: 10px; height: 10px; | |
| border-radius: 50%; | |
| } | |
| /* Math display */ | |
| .math-block { | |
| background: rgba(179,136,255,0.05); | |
| border: 1px solid rgba(179,136,255,0.15); | |
| border-radius: 12px; | |
| padding: 1.5rem 2rem; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.95rem; | |
| text-align: center; | |
| margin: 1.5rem 0; | |
| color: var(--accent-cyan); | |
| overflow-x: auto; | |
| } | |
| /* Comparison table */ | |
| .comparison-table { | |
| width: 100%; | |
| border-collapse: separate; | |
| border-spacing: 0; | |
| border-radius: 16px; | |
| overflow: hidden; | |
| border: 1px solid var(--border-color); | |
| } | |
| .comparison-table th { | |
| background: var(--bg-card); | |
| padding: 1rem 1.5rem; | |
| font-size: 0.8rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-secondary); | |
| font-weight: 600; | |
| text-align: left; | |
| } | |
| .comparison-table td { | |
| padding: 1rem 1.5rem; | |
| font-size: 0.9rem; | |
| border-top: 1px solid var(--border-color); | |
| background: var(--bg-secondary); | |
| } | |
| .comparison-table tr:last-child td { | |
| background: rgba(179,136,255,0.05); | |
| } | |
| .comparison-table tr:last-child td:first-child { | |
| font-weight: 700; | |
| color: var(--accent-purple); | |
| } | |
| .highlight-cell { | |
| color: var(--accent-green); | |
| font-weight: 600; | |
| font-family: 'JetBrains Mono', monospace; | |
| } | |
| /* Timeline */ | |
| .timeline { | |
| position: relative; | |
| padding-left: 3rem; | |
| } | |
| .timeline::before { | |
| content: ''; | |
| position: absolute; | |
| left: 14px; | |
| top: 0; | |
| bottom: 0; | |
| width: 2px; | |
| background: linear-gradient(to bottom, var(--accent-blue), var(--accent-purple), var(--accent-pink)); | |
| } | |
| .timeline-item { | |
| position: relative; | |
| margin-bottom: 2.5rem; | |
| padding-left: 1rem; | |
| } | |
| .timeline-item::before { | |
| content: ''; | |
| position: absolute; | |
| left: -2.25rem; | |
| top: 0.5rem; | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| background: var(--accent-purple); | |
| border: 3px solid var(--bg-primary); | |
| box-shadow: 0 0 10px var(--glow-purple); | |
| } | |
| .timeline-item h4 { | |
| font-size: 1.05rem; | |
| font-weight: 700; | |
| margin-bottom: 0.3rem; | |
| } | |
| .timeline-item p { | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| } | |
| /* Interactive allocation demo */ | |
| #allocation-demo { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 20px; | |
| padding: 2.5rem; | |
| } | |
| .items-pool { | |
| display: flex; | |
| gap: 0.5rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| margin-bottom: 2rem; | |
| } | |
| .item-chip { | |
| padding: 0.5rem 1rem; | |
| border-radius: 10px; | |
| font-size: 0.82rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| border: 2px solid var(--border-color); | |
| background: var(--bg-secondary); | |
| user-select: none; | |
| } | |
| .item-chip:hover { | |
| border-color: var(--accent-blue); | |
| background: rgba(79,195,247,0.1); | |
| } | |
| .item-chip.assigned { | |
| opacity: 0.4; | |
| pointer-events: none; | |
| } | |
| .agents-row { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .agent-box { | |
| border: 2px dashed var(--border-color); | |
| border-radius: 14px; | |
| padding: 1.25rem; | |
| min-height: 160px; | |
| text-align: center; | |
| transition: all 0.3s; | |
| cursor: pointer; | |
| } | |
| .agent-box.active { | |
| border-color: var(--accent-blue); | |
| border-style: solid; | |
| background: rgba(79,195,247,0.03); | |
| } | |
| .agent-box .agent-name { | |
| font-weight: 700; | |
| font-size: 0.95rem; | |
| margin-bottom: 0.75rem; | |
| } | |
| .agent-box .agent-items { | |
| display: flex; | |
| gap: 0.4rem; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .agent-box .mini-chip { | |
| padding: 0.3rem 0.7rem; | |
| border-radius: 6px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .agent-box .agent-value { | |
| margin-top: 0.75rem; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.9rem; | |
| color: var(--accent-green); | |
| } | |
| .total-welfare-display { | |
| text-align: center; | |
| padding: 1.5rem; | |
| background: rgba(105,240,174,0.05); | |
| border: 1px solid rgba(105,240,174,0.2); | |
| border-radius: 14px; | |
| } | |
| .total-welfare-display .tw-label { | |
| font-size: 0.8rem; | |
| text-transform: uppercase; | |
| letter-spacing: 1.5px; | |
| color: var(--text-dim); | |
| margin-bottom: 0.3rem; | |
| } | |
| .total-welfare-display .tw-value { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 2rem; | |
| font-weight: 800; | |
| color: var(--accent-green); | |
| } | |
| /* Key insight boxes */ | |
| .insight-box { | |
| background: linear-gradient(135deg, rgba(79,195,247,0.08), rgba(179,136,255,0.08)); | |
| border: 1px solid rgba(179,136,255,0.2); | |
| border-radius: 14px; | |
| padding: 1.5rem 2rem; | |
| margin: 1.5rem 0; | |
| display: flex; | |
| gap: 1rem; | |
| align-items: flex-start; | |
| } | |
| .insight-icon { | |
| font-size: 1.5rem; | |
| flex-shrink: 0; | |
| } | |
| .insight-box p { | |
| font-size: 0.92rem; | |
| color: var(--text-secondary); | |
| } | |
| .insight-box strong { | |
| color: var(--text-primary); | |
| } | |
| /* Submodularity demo */ | |
| .submod-demo { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: 20px; | |
| padding: 2.5rem; | |
| } | |
| .submod-bars { | |
| display: flex; | |
| gap: 1.5rem; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| margin: 2rem 0; | |
| } | |
| .submod-bar-group { | |
| text-align: center; | |
| } | |
| .submod-bar-group .bar-container { | |
| width: 70px; | |
| height: 200px; | |
| background: rgba(255,255,255,0.03); | |
| border-radius: 10px; | |
| position: relative; | |
| overflow: hidden; | |
| margin: 0.5rem auto; | |
| border: 1px solid var(--border-color); | |
| } | |
| .submod-bar-group .bar-inner { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| border-radius: 0 0 9px 9px; | |
| transition: height 0.8s ease; | |
| } | |
| .submod-bar-group .bar-label { | |
| font-size: 0.75rem; | |
| color: var(--text-dim); | |
| margin-top: 0.3rem; | |
| } | |
| .submod-bar-group .bar-value { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| color: var(--text-primary); | |
| margin-bottom: 0.25rem; | |
| } | |
| /* Footer */ | |
| footer { | |
| border-top: 1px solid var(--border-color); | |
| padding: 2.5rem 2rem; | |
| background: var(--bg-secondary); | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .footer-inner { | |
| max-width: 1100px; | |
| margin: 0 auto; | |
| } | |
| .footer-paper { | |
| text-align: center; | |
| margin-bottom: 1.25rem; | |
| font-size: 0.82rem; | |
| color: var(--text-dim); | |
| line-height: 1.8; | |
| } | |
| .footer-paper a { | |
| color: var(--accent-blue); | |
| text-decoration: none; | |
| } | |
| .footer-paper a:hover { | |
| text-decoration: underline; | |
| } | |
| .footer-bottom { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 0.8rem; | |
| color: var(--text-dim); | |
| border-top: 1px solid var(--border-color); | |
| padding-top: 1.25rem; | |
| flex-wrap: wrap; | |
| gap: 0.5rem; | |
| } | |
| .footer-bottom a { | |
| color: var(--accent-purple); | |
| text-decoration: none; | |
| } | |
| .footer-bottom a:hover { | |
| text-decoration: underline; | |
| } | |
| /* Fade-in animations */ | |
| .fade-in { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| transition: opacity 0.7s ease, transform 0.7s ease; | |
| } | |
| .fade-in.visible { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .nav-links { display: none; } | |
| .analogy-grid { grid-template-columns: 1fr; } | |
| .flow-container { flex-direction: column; align-items: center; } | |
| .flow-arrow { transform: rotate(90deg); padding: 0.5rem 0; } | |
| .hero-stats { gap: 1.5rem; } | |
| section { padding: 3rem 1.25rem; } | |
| .analogy-container { padding: 2rem 1.5rem; } | |
| .demo-container, .regret-viz, #allocation-demo, .submod-demo { padding: 1.5rem; } | |
| .comparison-table { font-size: 0.8rem; } | |
| .comparison-table th, .comparison-table td { padding: 0.7rem 0.8rem; } | |
| } | |
| /* Colors for agent-specific items */ | |
| .color-agent1 { background: rgba(79,195,247,0.15); border-color: var(--accent-blue); color: var(--accent-blue); } | |
| .color-agent2 { background: rgba(179,136,255,0.15); border-color: var(--accent-purple); color: var(--accent-purple); } | |
| .color-agent3 { background: rgba(255,128,171,0.15); border-color: var(--accent-pink); color: var(--accent-pink); } | |
| .color-agent4 { background: rgba(105,240,174,0.15); border-color: var(--accent-green); color: var(--accent-green); } | |
| /* Glowing orbs for hero */ | |
| .orb { | |
| position: absolute; | |
| border-radius: 50%; | |
| filter: blur(80px); | |
| opacity: 0.15; | |
| pointer-events: none; | |
| } | |
| .orb-1 { width: 400px; height: 400px; background: var(--accent-blue); top: 10%; left: -10%; } | |
| .orb-2 { width: 350px; height: 350px; background: var(--accent-purple); bottom: 10%; right: -5%; } | |
| .orb-3 { width: 250px; height: 250px; background: var(--accent-pink); top: 40%; right: 20%; } | |
| </style> | |
| </head> | |
| <body> | |
| <canvas id="bg-canvas"></canvas> | |
| <div class="content-wrapper"> | |
| <!-- Navigation --> | |
| <nav id="main-nav"> | |
| <div class="nav-inner"> | |
| <div class="nav-logo">MA-CMAB</div> | |
| <div class="nav-links"> | |
| <a href="#problem">Problem</a> | |
| <a href="#submodularity">Submodularity</a> | |
| <a href="#challenge">Challenge</a> | |
| <a href="#algorithm">Algorithm</a> | |
| <a href="#results">Results</a> | |
| <a href="#demo">Demo</a> | |
| <a href="#comparison">Comparison</a> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Hero --> | |
| <section class="hero"> | |
| <div class="orb orb-1"></div> | |
| <div class="orb orb-2"></div> | |
| <div class="orb orb-3"></div> | |
| <div class="hero-badge"> | |
| <span class="dot"></span> | |
| AAMAS 2026 · Extended Abstract | |
| </div> | |
| <h1> | |
| Learning to Divide:<br> | |
| <span class="gradient-text">Multi-Agent Bandits</span><br> | |
| for Welfare Maximization | |
| </h1> | |
| <p class="hero-subtitle"> | |
| How do you fairly allocate items among agents when you can't see individual preferences—only the total group happiness? This paper solves that puzzle with a novel bandit algorithm. | |
| </p> | |
| <div class="hero-cta"> | |
| <a href="#problem" class="btn btn-primary">Explore the Idea ↓</a> | |
| <a href="https://arxiv.org/abs/2602.16183" target="_blank" class="btn btn-secondary">Read the Paper →</a> | |
| </div> | |
| <div class="hero-stats"> | |
| <div class="hero-stat"> | |
| <div class="value">T<sup style="font-size:0.6em">2/3</sup></div> | |
| <div class="label">Regret Growth Rate</div> | |
| </div> | |
| <div class="hero-stat"> | |
| <div class="value">1−1/e</div> | |
| <div class="label">Approximation Ratio</div> | |
| </div> | |
| <div class="hero-stat"> | |
| <div class="value">M×N</div> | |
| <div class="label">Agent-Item Pairs</div> | |
| </div> | |
| <div class="hero-stat"> | |
| <div class="value">0</div> | |
| <div class="label">Communication Needed</div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- The Problem --> | |
| <section id="problem"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">The Problem</div> | |
| <h2 class="section-title">Dividing Items, Maximizing Happiness</h2> | |
| <p class="section-desc">Imagine splitting a set of resources among agents so everyone's total satisfaction is as high as possible—but you can only observe the group's total reward.</p> | |
| </div> | |
| <div class="analogy-container fade-in"> | |
| <h3 style="margin-bottom: 1.5rem; font-size: 1.15rem;">🍰 Think of it like splitting a party buffet</h3> | |
| <div class="analogy-grid"> | |
| <div class="analogy-step"> | |
| <div class="analogy-num">1</div> | |
| <div> | |
| <h4>Multiple Agents</h4> | |
| <p>Several guests at a buffet, each with different food preferences. Some love sushi, others prefer pasta.</p> | |
| </div> | |
| </div> | |
| <div class="analogy-step"> | |
| <div class="analogy-num">2</div> | |
| <div> | |
| <h4>Indivisible Items</h4> | |
| <p>Each dish can go to exactly one guest—no sharing plates. Items must be partitioned into disjoint sets.</p> | |
| </div> | |
| </div> | |
| <div class="analogy-step"> | |
| <div class="analogy-num">3</div> | |
| <div> | |
| <h4>Bandit Feedback</h4> | |
| <p>After each round, you only hear the table's total satisfaction score—not how happy each individual guest is.</p> | |
| </div> | |
| </div> | |
| <div class="analogy-step"> | |
| <div class="analogy-num">4</div> | |
| <div> | |
| <h4>Submodular Utilities</h4> | |
| <p>Getting a 2nd sushi is nice, but not as exciting as the 1st. There are diminishing returns—this is submodularity.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="insight-box fade-in" style="margin-top: 2rem;"> | |
| <div class="insight-icon">🎯</div> | |
| <p><strong>The Submodular Welfare Problem (SWP):</strong> Given M agents and N items, partition items among agents to maximize total welfare f(S) = Σᵢ wᵢ(sᵢ), where each wᵢ is monotone submodular. Offline, the best achievable guarantee is (1−1/e) ≈ 63.2% of optimal. This paper achieves the same guarantee online, learning from noisy feedback!</p> | |
| </div> | |
| </section> | |
| <!-- Submodularity --> | |
| <section id="submodularity"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Key Concept</div> | |
| <h2 class="section-title">What is Submodularity?</h2> | |
| <p class="section-desc">The "diminishing returns" property: adding an item to a smaller set gives more value than adding it to a larger set.</p> | |
| </div> | |
| <div class="submod-demo fade-in"> | |
| <p style="text-align:center; color: var(--text-secondary); margin-bottom: 0.5rem; font-size: 0.9rem;">Click to add items and watch the marginal gain decrease:</p> | |
| <div style="text-align:center; margin-bottom: 1rem;"> | |
| <button id="submod-add-btn" class="btn btn-primary" style="padding: 0.6rem 1.5rem; font-size: 0.85rem;">+ Add Next Item</button> | |
| <button id="submod-reset-btn" class="btn btn-secondary" style="padding: 0.6rem 1.5rem; font-size: 0.85rem; margin-left: 0.5rem;">Reset</button> | |
| </div> | |
| <div class="submod-bars" id="submod-bars"></div> | |
| <div class="math-block"> | |
| w({A,B}) − w({A}) ≤ w({B}) − w(∅)<br> | |
| <span style="font-size: 0.8rem; color: var(--text-dim);">Adding B when you already have A gives less than adding B to nothing.</span> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- The Challenge --> | |
| <section id="challenge"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Why It's Hard</div> | |
| <h2 class="section-title">Three Layers of Difficulty</h2> | |
| <p class="section-desc">This isn't just an optimization problem—it's an optimization-under-uncertainty problem with unique structural challenges.</p> | |
| </div> | |
| <div class="card-grid"> | |
| <div class="card fade-in"> | |
| <div class="card-icon" style="background: rgba(79,195,247,0.12); color: var(--accent-blue);">🎰</div> | |
| <h3>Bandit Feedback</h3> | |
| <p>You only see total reward—never individual agent utilities. Like grading a group project by only seeing the final score, not each student's contribution.</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <div class="card-icon" style="background: rgba(179,136,255,0.12); color: var(--accent-purple);">🧩</div> | |
| <h3>Combinatorial Actions</h3> | |
| <p>The action space is exponentially large: the number of ways to partition N items among M agents grows astronomically. You can't try them all!</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <div class="card-icon" style="background: rgba(255,128,171,0.12); color: var(--accent-pink);">🔗</div> | |
| <h3>Coupled Constraints</h3> | |
| <p>Agents compete for items—giving item A to Alice means Bob can't have it. Prior multi-agent bandit models don't handle this kind of coupling.</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <div class="card-icon" style="background: rgba(105,240,174,0.12); color: var(--accent-green);">🔇</div> | |
| <h3>No Communication</h3> | |
| <p>Agents don't talk to each other—there's no gossip or coordination. A central learner must figure out the optimal allocation from aggregate signals alone.</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <div class="card-icon" style="background: rgba(255,171,64,0.12); color: var(--accent-orange);">📉</div> | |
| <h3>Diminishing Returns</h3> | |
| <p>Submodular utilities mean marginal gains decrease as bundles grow. Estimating these marginals is critical but hard under noise.</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <div class="card-icon" style="background: rgba(24,255,255,0.12); color: var(--accent-cyan);">⚖️</div> | |
| <h3>NP-Hardness</h3> | |
| <p>Even offline (with perfect information), finding the optimal allocation is NP-hard. The best polynomial algorithm achieves only ≈63.2% of optimal.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Algorithm --> | |
| <section id="algorithm"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">The Solution</div> | |
| <h2 class="section-title">Explore, Then Commit</h2> | |
| <p class="section-desc">The MA-CMAB algorithm uses a two-phase strategy: gather information, then act on the best allocation found.</p> | |
| </div> | |
| <div class="flow-container fade-in"> | |
| <div class="flow-step"> | |
| <div class="step-num">Phase 1</div> | |
| <h4>🔍 Explore</h4> | |
| <p>Try different allocations, observe total rewards, and build empirical estimates of each allocation's welfare. The exploration length m is carefully tuned.</p> | |
| </div> | |
| <div class="flow-arrow">→</div> | |
| <div class="flow-step"> | |
| <div class="step-num">Phase 2</div> | |
| <h4>🧠 Solve Offline</h4> | |
| <p>Feed the noisy estimates into a resilient offline algorithm (Continuous Greedy + Pipage Rounding) to find a high-quality allocation.</p> | |
| </div> | |
| <div class="flow-arrow">→</div> | |
| <div class="flow-step"> | |
| <div class="step-num">Phase 3</div> | |
| <h4>🎯 Commit</h4> | |
| <p>Play the chosen allocation for all remaining rounds. The offline guarantee transfers to the online setting thanks to the resilience property.</p> | |
| </div> | |
| </div> | |
| <div class="insight-box fade-in" style="margin-top: 2.5rem;"> | |
| <div class="insight-icon">⚡</div> | |
| <p><strong>The key insight—Resilience:</strong> Even when the offline algorithm receives noisy utility estimates (because we're learning!), it still returns a near-optimal solution. Specifically, if each estimate is off by at most ε, the output degrades by only δ·ε. This "noise tolerance" is what makes the offline-to-online reduction work.</p> | |
| </div> | |
| <div class="math-block fade-in"> | |
| Optimal exploration length: m* = ( T·δ·C / 2η · √(log T / 2) )<sup>2/3</sup> | |
| </div> | |
| <p style="text-align:center; color: var(--text-dim); font-size: 0.82rem;">This balances the cost of exploration (wasted rounds) against the accuracy gained (better estimates for the offline solver).</p> | |
| </section> | |
| <!-- Results --> | |
| <section id="results"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Main Result</div> | |
| <h2 class="section-title">Sublinear Regret: Õ(T<sup style="font-size:0.7em">2/3</sup>)</h2> | |
| <p class="section-desc">The algorithm's regret grows much slower than the number of rounds T—meaning it learns to allocate nearly as well as the best offline algorithm.</p> | |
| </div> | |
| <div class="regret-viz fade-in"> | |
| <canvas id="regret-chart"></canvas> | |
| <div class="regret-legend"> | |
| <div class="legend-item"><div class="legend-dot" style="background: var(--accent-pink);"></div> Linear regret (no learning)</div> | |
| <div class="legend-item"><div class="legend-dot" style="background: var(--accent-orange);"></div> T<sup>2/3</sup> regret (this paper)</div> | |
| <div class="legend-item"><div class="legend-dot" style="background: var(--accent-green);"></div> √T regret (theoretical ideal)</div> | |
| </div> | |
| </div> | |
| <div class="math-block fade-in"> | |
| E[R(T)] = O( δ<sup>2/3</sup> · η<sup>1/3</sup> · C<sup>2/3</sup> · T<sup>2/3</sup> · log(T)<sup>1/3</sup> ) | |
| </div> | |
| <div class="card-grid" style="margin-top: 2rem;"> | |
| <div class="card fade-in"> | |
| <h3 style="color: var(--accent-blue);">α = 1 − 1/e ≈ 0.632</h3> | |
| <p>The benchmark: the best guarantee achievable by any polynomial-time offline algorithm for submodular welfare maximization.</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <h3 style="color: var(--accent-purple);">δ = O(MN)</h3> | |
| <p>Noise sensitivity: measures how much a perturbation ε in estimates degrades the solution quality. Scales linearly with problem size.</p> | |
| </div> | |
| <div class="card fade-in"> | |
| <h3 style="color: var(--accent-green);">η = (MN)⁸</h3> | |
| <p>Oracle complexity: total value queries the offline algorithm needs. Large but polynomial, and standard for continuous greedy methods.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Interactive Demo --> | |
| <section id="demo"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Interactive</div> | |
| <h2 class="section-title">Try It Yourself</h2> | |
| <p class="section-desc">Allocate items to agents and see how total welfare changes. Each agent has different submodular preferences.</p> | |
| </div> | |
| <div id="allocation-demo" class="fade-in"> | |
| <p style="color: var(--text-secondary); font-size: 0.88rem; margin-bottom: 1rem; text-align:center;">Click an item, then click an agent to assign it. Welfare is computed with diminishing returns.</p> | |
| <div class="items-pool" id="items-pool"></div> | |
| <div class="agents-row" id="agents-row"></div> | |
| <div style="text-align:center; margin-bottom: 1rem;"> | |
| <button id="demo-reset" class="btn btn-secondary" style="font-size: 0.82rem; padding: 0.5rem 1.2rem;">Reset Allocation</button> | |
| <button id="demo-random" class="btn btn-primary" style="font-size: 0.82rem; padding: 0.5rem 1.2rem; margin-left: 0.5rem;">Random Allocation</button> | |
| <button id="demo-greedy" class="btn btn-primary" style="font-size: 0.82rem; padding: 0.5rem 1.2rem; margin-left: 0.5rem; background: linear-gradient(135deg, var(--accent-green), var(--accent-cyan));">Greedy Allocation</button> | |
| </div> | |
| <div class="total-welfare-display"> | |
| <div class="tw-label">Total Welfare (Σ wᵢ(sᵢ))</div> | |
| <div class="tw-value" id="total-welfare-value">0.00</div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Regret Live Simulation --> | |
| <section id="simulation"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Simulation</div> | |
| <h2 class="section-title">Watch the Algorithm Learn</h2> | |
| <p class="section-desc">See how regret accumulates during exploration and then slows down during exploitation.</p> | |
| </div> | |
| <div class="demo-container fade-in"> | |
| <div class="demo-controls"> | |
| <label>Rounds (T):</label> | |
| <input type="range" id="sim-T" min="100" max="2000" value="500" step="100"> | |
| <span id="sim-T-val" style="font-family:'JetBrains Mono';font-size:0.85rem;color:var(--accent-cyan);min-width:45px;">500</span> | |
| <label style="margin-left:1rem;">Agents (M):</label> | |
| <input type="range" id="sim-M" min="2" max="5" value="3"> | |
| <span id="sim-M-val" style="font-family:'JetBrains Mono';font-size:0.85rem;color:var(--accent-cyan);min-width:20px;">3</span> | |
| <button id="sim-run" class="btn btn-primary" style="font-size:0.82rem;padding:0.5rem 1.2rem;margin-left:auto;">▶ Run Simulation</button> | |
| </div> | |
| <canvas id="sim-chart" style="width:100%;height:280px;display:block;"></canvas> | |
| <div style="display:flex;justify-content:space-between;margin-top:1rem;flex-wrap:wrap;gap:0.5rem;"> | |
| <div style="font-size:0.82rem;color:var(--text-dim);"> | |
| <span style="display:inline-block;width:12px;height:12px;background:var(--accent-orange);border-radius:3px;vertical-align:middle;margin-right:4px;"></span> Cumulative Regret | |
| </div> | |
| <div style="font-size:0.82rem;color:var(--text-dim);"> | |
| <span style="display:inline-block;width:40px;height:2px;border-top:2px dashed var(--accent-pink);vertical-align:middle;margin-right:4px;"></span> Exploration ends here | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Comparison --> | |
| <section id="comparison"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Context</div> | |
| <h2 class="section-title">How This Fits In</h2> | |
| <p class="section-desc">Comparing the MA-CMAB framework to prior approaches in the bandit literature.</p> | |
| </div> | |
| <div class="fade-in" style="overflow-x:auto;"> | |
| <table class="comparison-table"> | |
| <thead> | |
| <tr> | |
| <th>Framework</th> | |
| <th>Agents</th> | |
| <th>Action Space</th> | |
| <th>Feedback</th> | |
| <th>Communication</th> | |
| <th>Regret</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td>C-ETC (Nie et al., 2023)</td> | |
| <td>Single</td> | |
| <td>Subset selection</td> | |
| <td>Full-bandit</td> | |
| <td>N/A</td> | |
| <td class="highlight-cell">Õ(T<sup>2/3</sup>)</td> | |
| </tr> | |
| <tr> | |
| <td>Fourati et al. (2024)</td> | |
| <td>Multi (M≥1)</td> | |
| <td>Shared subset</td> | |
| <td>Semi-bandit</td> | |
| <td>Required</td> | |
| <td class="highlight-cell">Õ(T<sup>1/2</sup>)</td> | |
| </tr> | |
| <tr> | |
| <td>Collision-based MA-MAB</td> | |
| <td>Multi</td> | |
| <td>Single arm each</td> | |
| <td>Per-agent</td> | |
| <td>Varies</td> | |
| <td class="highlight-cell">Õ(log T)</td> | |
| </tr> | |
| <tr> | |
| <td>MA-CMAB (This Paper)</td> | |
| <td>Multi</td> | |
| <td>Partition (disjoint)</td> | |
| <td>Full-bandit</td> | |
| <td>None</td> | |
| <td class="highlight-cell">Õ(T<sup>2/3</sup>)</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="insight-box fade-in" style="margin-top: 2rem;"> | |
| <div class="insight-icon">🏆</div> | |
| <p><strong>First of its kind:</strong> This is the first framework to handle discrete partitioning for submodular welfare as a multi-agent CMAB problem under full-bandit feedback with no inter-agent communication. The Õ(T<sup>2/3</sup>) rate matches the best known for single-agent monotone submodular CMAB with bandit feedback.</p> | |
| </div> | |
| </section> | |
| <!-- Key Takeaways --> | |
| <section id="takeaways"> | |
| <div class="section-header fade-in"> | |
| <div class="section-tag">Summary</div> | |
| <h2 class="section-title">Key Takeaways</h2> | |
| </div> | |
| <div class="timeline fade-in"> | |
| <div class="timeline-item"> | |
| <h4>New Framework</h4> | |
| <p>MA-CMAB extends combinatorial bandits to multi-agent partition-based allocation—a setting not previously addressed.</p> | |
| </div> | |
| <div class="timeline-item"> | |
| <h4>Resilience Under Noise</h4> | |
| <p>The Continuous Greedy algorithm is proven resilient: noisy value oracles degrade the solution by at most δ·ε, keeping the (1−1/e) guarantee intact.</p> | |
| </div> | |
| <div class="timeline-item"> | |
| <h4>Offline-to-Online Reduction</h4> | |
| <p>A principled explore-then-commit strategy lifts offline guarantees to the online bandit setting, achieving Õ(T<sup>2/3</sup>) regret.</p> | |
| </div> | |
| <div class="timeline-item"> | |
| <h4>Practical Applications</h4> | |
| <p>The framework applies to combinatorial auctions, recommendation systems, distributed sensing, influence maximization, and fair division of indivisible goods.</p> | |
| </div> | |
| <div class="timeline-item"> | |
| <h4>Open Questions</h4> | |
| <p>Can we get tighter lower bounds? Can regret be improved to Õ(√T)? Can this extend to non-stationary or adversarial settings?</p> | |
| </div> | |
| </div> | |
| </section> | |
| </div><!-- end content-wrapper --> | |
| <!-- Footer --> | |
| <footer> | |
| <div class="footer-inner"> | |
| <div class="footer-paper"> | |
| <strong>Paper:</strong> "Multi-Agent Combinatorial-Multi-Armed-Bandit framework for the Submodular Welfare Problem under Bandit Feedback"<br> | |
| Subham Pokhriyal, Shweta Jain, Vaneet Aggarwal · 2026 · <a href="https://arxiv.org/abs/2602.16183" target="_blank">arXiv:2602.16183</a><br> | |
| <em>Built for educational purposes. Not affiliated with the authors.</em> | |
| </div> | |
| <div class="footer-bottom"> | |
| <span>© 2026 Amroja, LLC</span> | |
| <a href="https://johndamask.com" target="_blank">johndamask.com</a> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| // ========== Background Particles ========== | |
| (function() { | |
| const canvas = document.getElementById('bg-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| let particles = []; | |
| const PARTICLE_COUNT = 60; | |
| function resize() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| resize(); | |
| window.addEventListener('resize', resize); | |
| class Particle { | |
| constructor() { | |
| this.reset(); | |
| } | |
| reset() { | |
| this.x = Math.random() * canvas.width; | |
| this.y = Math.random() * canvas.height; | |
| this.vx = (Math.random() - 0.5) * 0.3; | |
| this.vy = (Math.random() - 0.5) * 0.3; | |
| this.r = Math.random() * 2 + 0.5; | |
| this.alpha = Math.random() * 0.3 + 0.05; | |
| const colors = ['79,195,247', '179,136,255', '255,128,171', '105,240,174']; | |
| this.color = colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| update() { | |
| this.x += this.vx; | |
| this.y += this.vy; | |
| if (this.x < 0 || this.x > canvas.width) this.vx *= -1; | |
| if (this.y < 0 || this.y > canvas.height) this.vy *= -1; | |
| } | |
| draw() { | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2); | |
| ctx.fillStyle = `rgba(${this.color},${this.alpha})`; | |
| ctx.fill(); | |
| } | |
| } | |
| for (let i = 0; i < PARTICLE_COUNT; i++) particles.push(new Particle()); | |
| function animate() { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| particles.forEach(p => { p.update(); p.draw(); }); | |
| // Draw connections | |
| for (let i = 0; i < particles.length; i++) { | |
| for (let j = i + 1; j < particles.length; j++) { | |
| const dx = particles[i].x - particles[j].x; | |
| const dy = particles[i].y - particles[j].y; | |
| const dist = Math.sqrt(dx * dx + dy * dy); | |
| if (dist < 150) { | |
| ctx.beginPath(); | |
| ctx.moveTo(particles[i].x, particles[i].y); | |
| ctx.lineTo(particles[j].x, particles[j].y); | |
| ctx.strokeStyle = `rgba(179,136,255,${0.06 * (1 - dist / 150)})`; | |
| ctx.lineWidth = 0.5; | |
| ctx.stroke(); | |
| } | |
| } | |
| } | |
| requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| })(); | |
| // ========== Nav scroll effect ========== | |
| window.addEventListener('scroll', () => { | |
| document.getElementById('main-nav').classList.toggle('scrolled', window.scrollY > 50); | |
| }); | |
| // ========== Fade-in on scroll ========== | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('visible'); | |
| } | |
| }); | |
| }, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }); | |
| document.querySelectorAll('.fade-in').forEach(el => observer.observe(el)); | |
| // ========== Submodularity Demo ========== | |
| (function() { | |
| const container = document.getElementById('submod-bars'); | |
| const addBtn = document.getElementById('submod-add-btn'); | |
| const resetBtn = document.getElementById('submod-reset-btn'); | |
| const items = ['📱', '💻', '📷', '🎧', '⌚', '🖨️']; | |
| const labels = ['Phone', 'Laptop', 'Camera', 'Headset', 'Watch', 'Printer']; | |
| // Submodular values: total value of first k items | |
| const totalValues = [0, 0.45, 0.72, 0.88, 0.95, 0.98, 1.0]; | |
| let currentCount = 0; | |
| function render() { | |
| container.innerHTML = ''; | |
| const colors = ['var(--accent-blue)', 'var(--accent-purple)', 'var(--accent-pink)', 'var(--accent-green)', 'var(--accent-orange)', 'var(--accent-cyan)']; | |
| for (let i = 0; i < items.length; i++) { | |
| const marginal = i < currentCount ? (totalValues[i+1] - totalValues[i]) : 0; | |
| const height = i < currentCount ? (marginal / 0.5) * 100 : 0; | |
| const group = document.createElement('div'); | |
| group.className = 'submod-bar-group'; | |
| group.innerHTML = ` | |
| <div class="bar-value">${i < currentCount ? '+' + (marginal * 100).toFixed(0) + '%' : '—'}</div> | |
| <div class="bar-container"> | |
| <div class="bar-inner" style="height:${height}%; background:${colors[i]};"></div> | |
| </div> | |
| <div class="bar-label">${items[i]} ${labels[i]}</div> | |
| `; | |
| container.appendChild(group); | |
| } | |
| } | |
| addBtn.addEventListener('click', () => { | |
| if (currentCount < items.length) { | |
| currentCount++; | |
| render(); | |
| } | |
| }); | |
| resetBtn.addEventListener('click', () => { | |
| currentCount = 0; | |
| render(); | |
| }); | |
| render(); | |
| })(); | |
| // ========== Interactive Allocation Demo ========== | |
| (function() { | |
| const itemNames = ['📱 Phone', '💻 Laptop', '📷 Camera', '🎧 Headset', '⌚ Watch', '🎮 Console']; | |
| const agentNames = ['Alice', 'Bob', 'Carol']; | |
| const agentColors = ['color-agent1', 'color-agent2', 'color-agent3']; | |
| // Preference weights for submodular computation (higher = agent likes that item more) | |
| const prefs = [ | |
| [0.5, 0.3, 0.4, 0.2, 0.1, 0.15], // Alice | |
| [0.2, 0.5, 0.15, 0.4, 0.3, 0.45], // Bob | |
| [0.3, 0.2, 0.5, 0.3, 0.4, 0.1], // Carol | |
| ]; | |
| let allocation = new Array(itemNames.length).fill(-1); // -1 = unassigned | |
| let selectedItem = -1; | |
| function submodValue(agentIdx, itemIndices) { | |
| // Submodular: sum of sqrt of pref values (concave => diminishing returns) | |
| let val = 0; | |
| itemIndices.forEach(idx => { val += prefs[agentIdx][idx]; }); | |
| return Math.sqrt(val) * 0.7; // scaled | |
| } | |
| function renderPool() { | |
| const pool = document.getElementById('items-pool'); | |
| pool.innerHTML = ''; | |
| itemNames.forEach((name, i) => { | |
| const chip = document.createElement('div'); | |
| chip.className = 'item-chip' + (allocation[i] >= 0 ? ' assigned' : '') + (selectedItem === i ? ' color-agent1' : ''); | |
| if (selectedItem === i) { | |
| chip.style.borderColor = 'var(--accent-cyan)'; | |
| chip.style.background = 'rgba(24,255,255,0.1)'; | |
| chip.style.color = 'var(--accent-cyan)'; | |
| } | |
| chip.textContent = name; | |
| chip.addEventListener('click', () => { | |
| if (allocation[i] < 0) { | |
| selectedItem = i; | |
| renderPool(); | |
| } | |
| }); | |
| pool.appendChild(chip); | |
| }); | |
| } | |
| function renderAgents() { | |
| const row = document.getElementById('agents-row'); | |
| row.innerHTML = ''; | |
| agentNames.forEach((name, a) => { | |
| const box = document.createElement('div'); | |
| box.className = 'agent-box'; | |
| const myItems = []; | |
| allocation.forEach((ag, idx) => { if (ag === a) myItems.push(idx); }); | |
| const val = submodValue(a, myItems); | |
| let itemsHTML = ''; | |
| myItems.forEach(idx => { | |
| itemsHTML += `<span class="mini-chip ${agentColors[a]}">${itemNames[idx]}</span>`; | |
| }); | |
| box.innerHTML = ` | |
| <div class="agent-name" style="color: var(${a===0?'--accent-blue':a===1?'--accent-purple':'--accent-pink'});">${name}</div> | |
| <div class="agent-items">${itemsHTML || '<span style="color:var(--text-dim);font-size:0.8rem;">Drop items here</span>'}</div> | |
| <div class="agent-value">w = ${val.toFixed(3)}</div> | |
| `; | |
| box.addEventListener('click', () => { | |
| if (selectedItem >= 0) { | |
| allocation[selectedItem] = a; | |
| selectedItem = -1; | |
| render(); | |
| } | |
| }); | |
| row.appendChild(box); | |
| }); | |
| } | |
| function renderWelfare() { | |
| let total = 0; | |
| agentNames.forEach((_, a) => { | |
| const myItems = []; | |
| allocation.forEach((ag, idx) => { if (ag === a) myItems.push(idx); }); | |
| total += submodValue(a, myItems); | |
| }); | |
| document.getElementById('total-welfare-value').textContent = total.toFixed(3); | |
| } | |
| function render() { | |
| renderPool(); | |
| renderAgents(); | |
| renderWelfare(); | |
| } | |
| document.getElementById('demo-reset').addEventListener('click', () => { | |
| allocation.fill(-1); | |
| selectedItem = -1; | |
| render(); | |
| }); | |
| document.getElementById('demo-random').addEventListener('click', () => { | |
| allocation = allocation.map(() => Math.floor(Math.random() * agentNames.length)); | |
| selectedItem = -1; | |
| render(); | |
| }); | |
| document.getElementById('demo-greedy').addEventListener('click', () => { | |
| // Greedy: assign each item to the agent that gains most marginal value | |
| allocation.fill(-1); | |
| for (let round = 0; round < itemNames.length; round++) { | |
| let bestItem = -1, bestAgent = -1, bestGain = -Infinity; | |
| for (let i = 0; i < itemNames.length; i++) { | |
| if (allocation[i] >= 0) continue; | |
| for (let a = 0; a < agentNames.length; a++) { | |
| const current = []; | |
| allocation.forEach((ag, idx) => { if (ag === a) current.push(idx); }); | |
| const curVal = submodValue(a, current); | |
| current.push(i); | |
| const newVal = submodValue(a, current); | |
| const gain = newVal - curVal; | |
| if (gain > bestGain) { | |
| bestGain = gain; | |
| bestItem = i; | |
| bestAgent = a; | |
| } | |
| } | |
| } | |
| if (bestItem >= 0) allocation[bestItem] = bestAgent; | |
| } | |
| selectedItem = -1; | |
| render(); | |
| }); | |
| render(); | |
| })(); | |
| // ========== Regret Chart ========== | |
| (function() { | |
| const canvas = document.getElementById('regret-chart'); | |
| const ctx = canvas.getContext('2d'); | |
| function draw() { | |
| const rect = canvas.parentElement.getBoundingClientRect(); | |
| const dpr = window.devicePixelRatio || 1; | |
| canvas.width = rect.width * dpr; | |
| canvas.height = 350 * dpr; | |
| canvas.style.width = rect.width + 'px'; | |
| canvas.style.height = '350px'; | |
| ctx.scale(dpr, dpr); | |
| const W = rect.width; | |
| const H = 350; | |
| const pad = { top: 30, right: 30, bottom: 50, left: 60 }; | |
| const plotW = W - pad.left - pad.right; | |
| const plotH = H - pad.top - pad.bottom; | |
| ctx.clearRect(0, 0, W, H); | |
| // Axes | |
| ctx.strokeStyle = 'rgba(255,255,255,0.1)'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(pad.left, pad.top); | |
| ctx.lineTo(pad.left, H - pad.bottom); | |
| ctx.lineTo(W - pad.right, H - pad.bottom); | |
| ctx.stroke(); | |
| // Grid lines | |
| for (let i = 1; i <= 4; i++) { | |
| const y = pad.top + (plotH / 4) * i; | |
| ctx.beginPath(); | |
| ctx.moveTo(pad.left, y); | |
| ctx.lineTo(W - pad.right, y); | |
| ctx.strokeStyle = 'rgba(255,255,255,0.04)'; | |
| ctx.stroke(); | |
| } | |
| // Labels | |
| ctx.fillStyle = 'rgba(255,255,255,0.3)'; | |
| ctx.font = '11px Inter'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Rounds (T)', W / 2, H - 10); | |
| ctx.save(); | |
| ctx.translate(15, H / 2); | |
| ctx.rotate(-Math.PI / 2); | |
| ctx.fillText('Cumulative Regret', 0, 0); | |
| ctx.restore(); | |
| const N = 200; | |
| function drawCurve(fn, color, lineWidth) { | |
| ctx.beginPath(); | |
| ctx.strokeStyle = color; | |
| ctx.lineWidth = lineWidth; | |
| for (let i = 0; i <= N; i++) { | |
| const t = i / N; | |
| const x = pad.left + t * plotW; | |
| const y = pad.top + plotH * (1 - fn(t)); | |
| if (i === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| } | |
| ctx.stroke(); | |
| } | |
| // Linear: regret = T | |
| drawCurve(t => t, 'rgba(255,128,171,0.6)', 2); | |
| // T^(2/3) | |
| drawCurve(t => Math.pow(t, 2/3), 'rgba(255,171,64,0.9)', 3); | |
| // sqrt(T) | |
| drawCurve(t => Math.pow(t, 0.5), 'rgba(105,240,174,0.6)', 2); | |
| // Labels on curves | |
| ctx.font = '600 11px JetBrains Mono'; | |
| ctx.fillStyle = 'rgba(255,128,171,0.7)'; | |
| ctx.fillText('O(T)', pad.left + plotW * 0.85, pad.top + plotH * 0.12); | |
| ctx.fillStyle = 'rgba(255,171,64,0.9)'; | |
| ctx.fillText('O(T²ʹ³)', pad.left + plotW * 0.85, pad.top + plotH * 0.32); | |
| ctx.fillStyle = 'rgba(105,240,174,0.7)'; | |
| ctx.fillText('O(√T)', pad.left + plotW * 0.85, pad.top + plotH * 0.48); | |
| } | |
| draw(); | |
| window.addEventListener('resize', draw); | |
| })(); | |
| // ========== Simulation Chart ========== | |
| (function() { | |
| const canvas = document.getElementById('sim-chart'); | |
| const ctx = canvas.getContext('2d'); | |
| const sliderT = document.getElementById('sim-T'); | |
| const sliderM = document.getElementById('sim-M'); | |
| const valT = document.getElementById('sim-T-val'); | |
| const valM = document.getElementById('sim-M-val'); | |
| const runBtn = document.getElementById('sim-run'); | |
| sliderT.addEventListener('input', () => { valT.textContent = sliderT.value; }); | |
| sliderM.addEventListener('input', () => { valM.textContent = sliderM.value; }); | |
| let animId = null; | |
| function runSim() { | |
| if (animId) cancelAnimationFrame(animId); | |
| const T = parseInt(sliderT.value); | |
| const M = parseInt(sliderM.value); | |
| const exploreLen = Math.floor(Math.pow(T, 2/3)); | |
| const totalRounds = T; | |
| const regretData = []; | |
| let cumRegret = 0; | |
| // Simulated: during exploration, per-round regret is ~0.5; during exploitation, ~0.05 | |
| for (let t = 0; t < totalRounds; t++) { | |
| const inExplore = t < exploreLen; | |
| const perRoundRegret = inExplore | |
| ? 0.3 + Math.random() * 0.4 | |
| : 0.02 + Math.random() * 0.06 * M * 0.3; | |
| cumRegret += perRoundRegret; | |
| regretData.push(cumRegret); | |
| } | |
| let frame = 0; | |
| const speed = Math.max(1, Math.floor(totalRounds / 300)); | |
| function drawFrame() { | |
| const rect = canvas.parentElement.getBoundingClientRect(); | |
| const dpr = window.devicePixelRatio || 1; | |
| canvas.width = (rect.width - 0) * dpr; | |
| canvas.height = 280 * dpr; | |
| canvas.style.width = (rect.width) + 'px'; | |
| canvas.style.height = '280px'; | |
| ctx.scale(dpr, dpr); | |
| const W = rect.width; | |
| const H = 280; | |
| const pad = { top: 20, right: 20, bottom: 40, left: 55 }; | |
| const plotW = W - pad.left - pad.right; | |
| const plotH = H - pad.top - pad.bottom; | |
| ctx.clearRect(0, 0, W, H); | |
| // Axes | |
| ctx.strokeStyle = 'rgba(255,255,255,0.1)'; | |
| ctx.lineWidth = 1; | |
| ctx.beginPath(); | |
| ctx.moveTo(pad.left, pad.top); | |
| ctx.lineTo(pad.left, H - pad.bottom); | |
| ctx.lineTo(W - pad.right, H - pad.bottom); | |
| ctx.stroke(); | |
| const maxRegret = regretData[regretData.length - 1] || 1; | |
| const drawUpto = Math.min(frame, totalRounds); | |
| // Exploration zone | |
| const exploreX = pad.left + (exploreLen / totalRounds) * plotW; | |
| ctx.fillStyle = 'rgba(179,136,255,0.05)'; | |
| ctx.fillRect(pad.left, pad.top, exploreX - pad.left, plotH); | |
| // Dashed line at exploration boundary | |
| ctx.setLineDash([4, 4]); | |
| ctx.strokeStyle = 'rgba(255,128,171,0.4)'; | |
| ctx.beginPath(); | |
| ctx.moveTo(exploreX, pad.top); | |
| ctx.lineTo(exploreX, H - pad.bottom); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| ctx.font = '10px Inter'; | |
| ctx.fillStyle = 'rgba(255,128,171,0.5)'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Explore', (pad.left + exploreX) / 2, pad.top + 15); | |
| if (frame > exploreLen) | |
| ctx.fillText('Exploit', (exploreX + W - pad.right) / 2, pad.top + 15); | |
| // Draw regret curve | |
| ctx.beginPath(); | |
| ctx.strokeStyle = 'var(--accent-orange)'; | |
| ctx.lineWidth = 2.5; | |
| for (let i = 0; i < drawUpto; i++) { | |
| const x = pad.left + (i / totalRounds) * plotW; | |
| const y = H - pad.bottom - (regretData[i] / maxRegret) * plotH; | |
| if (i === 0) ctx.moveTo(x, y); | |
| else ctx.lineTo(x, y); | |
| } | |
| ctx.stroke(); | |
| // Labels | |
| ctx.fillStyle = 'rgba(255,255,255,0.3)'; | |
| ctx.font = '11px Inter'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText(`Round (T=${T})`, W / 2, H - 8); | |
| frame += speed; | |
| if (frame <= totalRounds + speed) { | |
| animId = requestAnimationFrame(drawFrame); | |
| } | |
| } | |
| frame = 0; | |
| drawFrame(); | |
| } | |
| runBtn.addEventListener('click', runSim); | |
| // Initial draw placeholder | |
| (function() { | |
| const rect = canvas.parentElement.getBoundingClientRect(); | |
| const dpr = window.devicePixelRatio || 1; | |
| canvas.width = rect.width * dpr; | |
| canvas.height = 280 * dpr; | |
| canvas.style.width = rect.width + 'px'; | |
| canvas.style.height = '280px'; | |
| ctx.scale(dpr, dpr); | |
| ctx.fillStyle = 'rgba(255,255,255,0.15)'; | |
| ctx.font = '14px Inter'; | |
| ctx.textAlign = 'center'; | |
| ctx.fillText('Press ▶ Run Simulation to start', rect.width / 2, 140); | |
| })(); | |
| })(); | |
| // ========== Smooth scroll for anchor links ========== | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| target.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment