Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions example/incremental_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env python
"""
Example demonstrating incremental updates using History.add_events()

This shows how to:
1. Create an initial History with some games
2. Add new games incrementally without recomputing from scratch
3. Handle both with and without timestamps
"""

import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import trueskillthroughtime as ttt

def main():
print("=" * 60)
print("TrueSkillThroughTime: Incremental Update Example")
print("=" * 60)

# Example 1: Basic incremental update without timestamps
print("\n1. Basic Incremental Update (no timestamps)")
print("-" * 40)

# Initial games
composition_initial = [
[["Alice"], ["Bob"]], # Alice vs Bob
[["Alice"], ["Charlie"]], # Alice vs Charlie
]
results_initial = [
[1, 0], # Alice beats Bob
[0, 1], # Charlie beats Alice
]

# Create initial history
h = ttt.History(composition_initial, results_initial,
mu=25.0, sigma=8.333, beta=4.166, gamma=0.083)

# Run convergence
h.convergence(epsilon=1e-4, iterations=10, verbose=False)

# Print initial skills
print("Initial skills after 2 games:")
lc = h.learning_curves()
for player in ["Alice", "Bob", "Charlie"]:
if player in lc:
final_skill = lc[player][-1][1]
print(f" {player}: μ={final_skill.mu:.2f}, σ={final_skill.sigma:.2f}")

# Add new games incrementally
print("\nAdding new games...")
new_composition = [
[["Bob"], ["Charlie"]], # Bob vs Charlie
[["Alice"], ["Bob"]], # Alice vs Bob (rematch)
]
new_results = [
[1, 0], # Bob beats Charlie
[0, 1], # Bob beats Alice
]

h.add_events(new_composition, new_results)

# Run convergence again
h.convergence(epsilon=1e-4, iterations=10, verbose=False)

# Print updated skills
print("\nUpdated skills after 4 games total:")
lc = h.learning_curves()
for player in ["Alice", "Bob", "Charlie"]:
if player in lc:
final_skill = lc[player][-1][1]
print(f" {player}: μ={final_skill.mu:.2f}, σ={final_skill.sigma:.2f}")

# Example 2: Incremental update with timestamps
print("\n2. Incremental Update with Timestamps")
print("-" * 40)

# Initial games with timestamps
composition_ts = [
[["Team1"], ["Team2"]],
[["Team1"], ["Team3"]],
]
results_ts = [[1, 0], [1, 0]]
times_ts = [100, 200] # Day 100 and 200

# Create history with timestamps
h_ts = ttt.History(composition_ts, results_ts, times_ts,
mu=25.0, sigma=8.333, beta=4.166, gamma=0.25)

h_ts.convergence(epsilon=1e-4, iterations=10, verbose=False)

print("Initial skills (with time decay):")
lc_ts = h_ts.learning_curves()
for team in ["Team1", "Team2", "Team3"]:
if team in lc_ts:
final_skill = lc_ts[team][-1][1]
print(f" {team}: μ={final_skill.mu:.2f}, σ={final_skill.sigma:.2f}")

# Add games at later times
print("\nAdding games at days 300 and 400...")
new_comp_ts = [
[["Team2"], ["Team3"]],
[["Team1"], ["Team2"]],
]
new_res_ts = [[0, 1], [1, 0]]
new_times = [300, 400]

h_ts.add_events(new_comp_ts, new_res_ts, new_times)
h_ts.convergence(epsilon=1e-4, iterations=10, verbose=False)

print("\nFinal skills (showing time decay effect):")
lc_ts = h_ts.learning_curves()
for team in ["Team1", "Team2", "Team3"]:
if team in lc_ts:
final_skill = lc_ts[team][-1][1]
print(f" {team}: μ={final_skill.mu:.2f}, σ={final_skill.sigma:.2f}")

# Example 3: Adding new players
print("\n3. Adding New Players Incrementally")
print("-" * 40)

# Add games with new players
new_players_comp = [
[["David"], ["Eve"]], # Two new players
[["Alice"], ["David"]], # Existing vs new player
]
new_players_res = [[1, 0], [1, 0]]

print("Adding games with new players David and Eve...")
h.add_events(new_players_comp, new_players_res)
h.convergence(epsilon=1e-4, iterations=10, verbose=False)

print("\nAll player skills including new players:")
lc = h.learning_curves()
for player in ["Alice", "Bob", "Charlie", "David", "Eve"]:
if player in lc:
final_skill = lc[player][-1][1]
print(f" {player}: μ={final_skill.mu:.2f}, σ={final_skill.sigma:.2f}")

print("\n" + "=" * 60)
print("Incremental updates complete!")
print("This allows you to maintain a running skill estimate")
print("without recomputing everything from scratch.")
print("=" * 60)

if __name__ == "__main__":
main()
147 changes: 128 additions & 19 deletions test/runtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,25 +257,30 @@ def test_batch_same_strength(self):
self.assertAlmostEqual(post["c"].mu,25.000,3)
self.assertAlmostEqual(post["c"].sigma,5.419,3)
def test_add_events_batch(self):
pass
#agents= dict()
#for k in ["a", "b", "c", "d", "e", "f"]:
#agents[k] = ttt.Agent(ttt.Player(ttt.Gaussian(25., 25.0/3), 25.0/6, 25.0/300 ) , ttt.Ninf, -ttt.inf)
#composition = [ [["a"],["b"]], [["a"],["c"]] , [["b"],["c"]] ]
#results = [[1,0],[0,1],[1,0]]
#b = ttt.Batch(composition = composition, results = results, time=0, agents = agents)
#b.convergence()
#b.add_events(composition,results)
#self.assertEqual(len(b),6)
#post = b.posteriors()
#b.iteration(trace=True)
#b.iteration(trace=True)
#self.assertAlmostEqual(post["a"].mu,25.000,3)
#self.assertAlmostEqual(post["a"].sigma,3.88,3)
#self.assertAlmostEqual(post["b"].mu,25.000,3)
#self.assertAlmostEqual(post["b"].sigma,3.88,3)
#self.assertAlmostEqual(post["c"].mu,25.000,3)
#self.assertAlmostEqual(post["c"].sigma,3.88,3)
"""Test adding events to an existing Batch."""
agents = dict()
for k in ["a", "b", "c", "d", "e", "f"]:
agents[k] = ttt.Agent(ttt.Player(ttt.Gaussian(25., 25.0/3), 25.0/6, 25.0/300), ttt.Ninf, -ttt.inf)
composition = [[["a"],["b"]], [["a"],["c"]], [["b"],["c"]]]
results = [[1,0],[0,1],[1,0]]
b = ttt.Batch(composition=composition, results=results, time=0, agents=agents)
b.convergence()

# Add the same events again
b.add_events(composition, results)
self.assertEqual(len(b), 6)

# Run convergence
b.convergence()
post = b.posteriors()

# With doubled events, skills should converge to equal values
self.assertAlmostEqual(post["a"].mu, 25.000, 2)
self.assertAlmostEqual(post["a"].sigma, 3.88, 2)
self.assertAlmostEqual(post["b"].mu, 25.000, 2)
self.assertAlmostEqual(post["b"].sigma, 3.88, 2)
self.assertAlmostEqual(post["c"].mu, 25.000, 2)
self.assertAlmostEqual(post["c"].sigma, 3.88, 2)
def test_history_init(self):
composition = [ [["aa"],["b"]], [["aa"],["c"]] , [["b"],["c"]] ]
results = [[1,0],[0,1],[1,0]]
Expand Down Expand Up @@ -586,6 +591,110 @@ def gamma(self):
mu100, sigma100 = h.batches[0].posterior("a")
self.assertAlmostEqual(mu100, 6.555467)
self.assertAlmostEqual(sigma100, 9.6449906)
def test_history_add_events(self):
"""Test adding events to an existing History."""
# Initial history with 2 games
composition = [[["a"],["b"]], [["a"],["c"]]]
results = [[1,0],[0,1]]
h = ttt.History(composition, results, mu=0.0, sigma=6.0, beta=1.0, gamma=0.0)
h.convergence(verbose=False)

# Check initial state
self.assertEqual(len(h), 2)
self.assertEqual(len(h.batches), 2)

# Add a new game
new_composition = [[["b"],["c"]]]
new_results = [[1,0]]
h.add_events(new_composition, new_results)

# Check updated state
self.assertEqual(len(h), 3)
self.assertEqual(len(h.batches), 3)

# Run convergence
h.convergence(verbose=False)

# Check that all players have reasonable skills
lc = h.learning_curves()
self.assertIn("a", lc)
self.assertIn("b", lc)
self.assertIn("c", lc)

def test_history_add_events_with_times(self):
"""Test adding events with timestamps."""
# Initial history with times
composition = [[["a"],["b"]], [["a"],["c"]]]
results = [[1,0],[0,1]]
times = [100, 200]
h = ttt.History(composition, results, times, mu=0.0, sigma=6.0, beta=1.0, gamma=0.03)
h.convergence(verbose=False)

# Add new events with later times
new_composition = [[["b"],["c"]], [["a"],["b"]]]
new_results = [[1,0], [0,1]]
new_times = [300, 400]
h.add_events(new_composition, new_results, new_times)

# Check state
self.assertEqual(len(h), 4)
self.assertEqual(len(h.batches), 4)

# Verify temporal ordering
for i in range(len(h.batches)-1):
self.assertLessEqual(h.batches[i].time, h.batches[i+1].time)

def test_history_add_events_same_time(self):
"""Test adding events at the same time as existing batch."""
composition = [[["a"],["b"]]]
results = [[1,0]]
times = [100]
h = ttt.History(composition, results, times, mu=0.0, sigma=6.0)

# Add event at same time
new_composition = [[["c"],["d"]]]
new_results = [[1,0]]
new_times = [100]
h.add_events(new_composition, new_results, new_times)

# Should be in same batch
self.assertEqual(len(h.batches), 1)
self.assertEqual(len(h.batches[0]), 2)

def test_history_add_events_new_players(self):
"""Test adding events with new players."""
composition = [[["a"],["b"]]]
results = [[1,0]]
h = ttt.History(composition, results, mu=25.0, sigma=8.333)

# Add events with new players
new_composition = [[["c"],["d"]], [["a"],["c"]]]
new_results = [[1,0], [0,1]]
h.add_events(new_composition, new_results)

# Check all players exist
self.assertIn("a", h.agents)
self.assertIn("b", h.agents)
self.assertIn("c", h.agents)
self.assertIn("d", h.agents)

# New players should have default priors
self.assertEqual(h.agents["c"].player.prior.mu, 25.0)
self.assertEqual(h.agents["d"].player.prior.mu, 25.0)

def test_history_add_events_validation(self):
"""Test input validation for add_events."""
h = ttt.History([[["a"],["b"]]], [[1,0]])

# Test mismatched results
with self.assertRaises(ValueError):
h.add_events([[["c"],["d"]]], [[1,0], [0,1]])

# Test time consistency
h2 = ttt.History([[["a"],["b"]]], [[1,0]], [100])
with self.assertRaises(ValueError):
h2.add_events([[["c"],["d"]]], [[1,0]]) # No times when history uses times

def ToDo(self):
print("Ningun toDo")

Expand Down
Loading