Skip to content

Instantly share code, notes, and snippets.

@jbdamask
Created February 23, 2026 00:24
Show Gist options
  • Select an option

  • Save jbdamask/936b06ed1714238d2dd4f1e2b3a2be64 to your computer and use it in GitHub Desktop.

Select an option

Save jbdamask/936b06ed1714238d2dd4f1e2b3a2be64 to your computer and use it in GitHub Desktop.
NowIGetIt: multi.pdf
<!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