logaritmisk
3680c54d3c
feat(time-slice): parallel within-slice event iteration via rayon
...
Under #[cfg(feature = "rayon")], the per-iteration event sweep
processes events color-by-color: within a color, events touch
disjoint Index values by construction, so par_iter is safe.
Across colors, sequential ordering preserves async-EP semantics.
Event::compute() is a pure function returning an owned EventOutput
(new per-item likelihoods, evidence, and pre-computed new skill
likelihoods). The apply phase runs sequentially after the parallel
map, writing EventOutput values back to SkillStore and each event's
item likelihoods. This avoids shared mutable state in the hot loop.
Default build (no rayon) uses a sequential fallback that traverses
the same color-group order — behaviorally identical to the parallel
path. This keeps goldens bit-identical across feature configurations.
Scenario 3b applied: event updates read from and write to the shared
SkillStore, so the compute/apply split (Option A) was necessary.
Part of T3 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
2026-04-24 13:48:41 +02:00
logaritmisk and Claude Sonnet 4.6
9836b7b709
feat(time-slice): compute and maintain color groups; reorder events
...
TimeSlice gains a color_groups field of type ColorGroups, recomputed
whenever events change. After recompute, self.events is physically
reordered so color-0 events are first, then color-1, etc. Each color
is therefore a contiguous range of indices in self.events —
the invariant that Task 6's parallel par_iter_mut exploits.
Greedy coloring via crate::color_group::color_greedy; agent indices
come from Event::iter_agents. ColorGroups gains a color_range helper
that returns the contiguous Range<usize> for a given color.
Numerical behavior unchanged: async-EP is order-independent at
convergence, so event reordering does not affect goldens.
Part of T3.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-04-24 13:42:05 +02:00
logaritmisk
a40c0d6301
feat(color-group): add greedy within-slice event partitioning
...
ColorGroups holds a partition of event indices into color groups such
that events of the same color touch no shared Index. Computed greedily
in ingestion order: each event goes into the first color whose existing
members are disjoint from the event's indices.
Used in T3 for safe within-slice parallelism — events in the same
color can run concurrently without touching each other's skills.
Part of T3 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
2026-04-24 13:38:21 +02:00
logaritmisk
4f302ed28e
feat(api): add Send + Sync bounds to public traits
...
Required for T3 rayon-based parallelism. Affected traits:
- Time (+ Send + Sync + 'static)
- Drift<T> (+ Send + Sync)
- Observer<T> (+ Send + Sync)
- Factor (+ Send + Sync)
- Schedule (+ Send + Sync)
All built-in impls (i64, Untimed, ConstantDrift, NullObserver,
EpsilonOrMax, TeamSumFactor, RankDiffFactor, TruncFactor,
BuiltinFactor) naturally satisfy these bounds via auto-derive.
Minor breaking change: downstream custom impls that aren't already
thread-safe will need to add the bounds.
Part of T3 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
2026-04-24 13:36:39 +02:00
logaritmisk
9fe40042da
feat(cargo): add rayon as optional dependency
...
Opt-in feature flag — users who want parallel paths build with
--features rayon. Default build remains single-threaded.
Spec Section 6 calls for default-on; we defer that flip until the
feature is stable under field use.
Part of T3 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
2026-04-24 13:35:15 +02:00
logaritmisk and Claude Opus 4.7
f0793a8470
docs: add T3 concurrency implementation plan
...
11-task plan for rayon-backed within-slice parallelism per
Section 6 of docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-24 13:34:00 +02:00
logaritmisk
d2aab82c1e
T0 + T1 + T2: engine redesign through new API surface ( #1 )
...
Implements tiers T0, T1, T2 of `docs/superpowers/specs/2026-04-23-trueskill-engine-redesign-design.md`. All three tiers have landed together on this branch because they build on one another; this PR rolls them up for a single review pass.
Per-tier plans:
- T0: `docs/superpowers/plans/2026-04-23-t0-numerical-parity.md`
- T1: `docs/superpowers/plans/2026-04-24-t1-factor-graph.md`
- T2: `docs/superpowers/plans/2026-04-24-t2-new-api-surface.md`
## Summary
### T0 — Numerical parity (internal)
- `Gaussian` switched to natural-parameter storage `(pi, tau)`; mul/div now ~7× faster (218 ps vs 1.57 ns).
- `HashMap<Index, _>` → dense `Vec<_>` keyed by `Index.0` (via `AgentStore<D>`, `SkillStore`).
- `ScratchArena` eliminates per-event allocations in `Game::likelihoods`.
- `InferenceError` seed type added (1 variant).
- 38 → 53 tests passing through T1.
- Benchmark: `Batch::iteration` 29.84 → 21.25 µs.
### T1 — Factor graph machinery (internal)
- `Factor` trait + `BuiltinFactor` enum (TeamSum / RankDiff / Trunc) driving within-game inference.
- `VarStore` flat storage for variable marginals.
- `Schedule` trait + `EpsilonOrMax` impl replacing the hand-rolled EP loop.
- `Game::likelihoods` rebuilt on the factor-graph machinery; iteration counts and goldens preserved to within 1e-6.
- 53 tests passing.
- Benchmark: `Batch::iteration` 23.01 µs (slight regression absorbed in T2).
### T2 — New API surface (breaking)
**Renames:**
- `IndexMap → KeyTable`, `Player → Rating`, `Agent → Competitor`, `Batch → TimeSlice`
**New types:**
- `Time` trait with `Untimed` ZST and `i64` impls; `Drift<T>`, `Rating<T, D>`, `Competitor<T, D>`, `TimeSlice<T>`, `History<T, D, O, K>` all generic.
- `Event<T, K>`, `Team<K>`, `Member<K>`, `Outcome` (`Ranked` variant; `#[non_exhaustive]`).
- `Observer<T>` trait + `NullObserver`.
- `ConvergenceOptions`, `ConvergenceReport`.
- `GameOptions`, `OwnedGame<T, D>`.
**Three-tier ingestion:**
- `history.record_winner(&K, &K, T)` / `record_draw(&K, &K, T)` — 1v1 convenience.
- `history.add_events(iter)` — typed bulk.
- `history.event(T).team([...]).weights([...]).ranking([...]).commit()` — fluent.
**Query API:** `current_skill`, `learning_curve`, `learning_curves` (keyed on `K`), `log_evidence`, `log_evidence_for`, `predict_quality`, `predict_outcome`.
**Game constructors:** `ranked`, `one_v_one`, `free_for_all`, `custom` — all returning `Result<_, InferenceError>`.
**`factors` module:** `Factor`, `Schedule`, `VarStore`, `VarId`, `BuiltinFactor`, `EpsilonOrMax`, `ScheduleReport`, `TeamSumFactor`, `RankDiffFactor`, `TruncFactor` now public.
**Errors:** `InferenceError` gains `MismatchedShape`, `InvalidProbability`, `ConvergenceFailed`; boundary panics converted to `Result`.
**Removed (breaking):** `History::convergence(iters, eps, verbose)`, `HistoryBuilder::gamma(f64)`, `HistoryBuilder::time(bool)`, `History.time: bool`, `learning_curves_by_index`, nested-Vec public `add_events`.
## Behavior change (documented in CHANGELOG)
`Time = Untimed` has `elapsed_to → 0`, so no drift accumulates between slices. The old `time=false` mode implicitly forced `elapsed=1` on reappearance via an `i64::MAX` sentinel — that quirk is not reproducible under a typed time axis. Tests that depended on it now use `History::<i64, _>` with explicit `1..=n` timestamps. One test (`test_env_ttt`) had 3 Gaussian goldens updated to reflect the corrected semantics; documented in commit `33a7d90`.
## Final numbers
| Metric | Before T0 | After T2 | Delta |
|---|---|---|---|
| `Batch::iteration` | 29.84 µs | 21.36 µs | **-28%** |
| `Gaussian::mul` | 1.57 ns | 219 ps | **-86%** |
| `Gaussian::div` | 1.57 ns | 219 ps | **-86%** |
| Tests passing | 38 | 90 | +52 |
All other Gaussian ops unchanged (~219 ps add/sub, ~264 ps pi/tau reads).
## Test plan
- [x] `cargo test --features approx` — 90/90 pass (68 lib + 10 api_shape + 6 game + 4 record_winner + 2 equivalence)
- [x] `cargo clippy --all-targets --features approx -- -D warnings` — clean
- [x] `cargo +nightly fmt --check` — clean
- [x] `cargo bench --bench batch` — 21.36 µs
- [x] `cargo bench --bench gaussian` — unchanged from T1
- [x] `cargo run --example atp --features approx` — rewritten in new API, runs clean
- [x] Historical Game-level goldens preserved in `tests/equivalence.rs`
- [x] Public API matches spec Section 4 (verified by integration tests in `tests/api_shape.rs`)
## Commit history
~45 commits total across T0 + T1 + T2. Each task is self-contained and individually tested; the branch is bisectable. See `git log main..t2-new-api-surface` for the full list.
## Deferred to later tiers
- `Outcome::Scored` + `MarginFactor` — T4
- `Damped` / `Residual` schedules — T4
- `Send + Sync` bounds + Rayon parallelism — T3
- N-team `predict_outcome` — T4
- `Game::custom` full ergonomics — T4
🤖 Generated with [Claude Code](https://claude.com/claude-code )
Reviewed-on: #1
Co-authored-by: Anders Olsson <anders.e.olsson@gmail.com >
Co-committed-by: Anders Olsson <anders.e.olsson@gmail.com >
2026-04-24 11:20:04 +00:00
logaritmisk
a14df02089
chore: do not publish
2026-04-23 20:26:52 +02:00
logaritmisk
0d266b4428
chore: make cargo release add CHANGELOG.md before commit
2026-04-23 20:26:16 +02:00
logaritmisk
a4b4e5e8fa
chore: clean up
2026-04-23 20:24:10 +02:00
logaritmisk
04d5478ee4
style: cargo fmt
2026-04-23 20:23:13 +02:00
logaritmisk
480467ac32
chore: added cliff.toml, release.toml and rustfmt.toml
2026-04-23 20:22:27 +02:00
logaritmisk
dc47964310
added benchmark
2026-03-23 14:55:18 +01:00
logaritmisk
61a5507f5c
remove notepad
2026-03-23 14:21:23 +01:00
logaritmisk
a1f282a1c8
feat: added a Drift trait and a "default" ConstantDrift implementation
2026-03-16 12:06:04 +01:00
logaritmisk
853f177fa8
Small changes for new 2024 edition
2025-02-21 14:09:58 +01:00
logaritmisk
fc0efcdc52
Update edition
2025-02-21 14:06:28 +01:00
logaritmisk
3bbddb168f
Ignore temp folder
2024-04-03 14:43:54 +02:00
logaritmisk
2366c45f6a
Basic test for quality
2024-04-03 10:25:10 +02:00
logaritmisk
3a22b20a17
Added todo to readme, and documentation for quality function
2024-04-03 09:53:07 +02:00
logaritmisk
02ae2f0977
Change assert to debug_assert
2024-04-03 09:44:41 +02:00
logaritmisk
a491f8de8d
Fix broken link in README
2023-01-09 14:37:07 +01:00
logaritmisk
0b4b07d60e
Added more links to readme
2023-01-09 14:35:34 +01:00
logaritmisk
05f178641c
Rename d to diff, and t to team
2022-12-29 20:38:36 +01:00
logaritmisk
e3906aebaa
Small refactor
2022-12-27 22:37:12 +01:00
logaritmisk
8e25826f91
More rustifying
2022-12-27 22:30:20 +01:00
logaritmisk
9b6cb9e7eb
Make it more rusty
2022-12-27 22:24:01 +01:00
logaritmisk
fdddf56156
Remove unused mut reference
2022-12-27 22:11:04 +01:00
logaritmisk
b93194f762
Added default implementation for TeamMessage
2022-12-20 11:08:27 +01:00
logaritmisk
2b83ee5ef9
Added benchmark for Batch
2022-12-19 07:42:08 +01:00
logaritmisk
2bdd3d9b89
Remove warnings and refactor some code
2022-12-16 19:46:01 +01:00
logaritmisk
912a282cd8
More refactoring
2022-12-16 15:57:56 +01:00
logaritmisk
51467f7b69
Fix clippy warning
2022-12-16 15:51:58 +01:00
logaritmisk
6dd84f7fd2
Refactor so we can see if there is any way to improve the performance
2022-12-16 15:38:29 +01:00
logaritmisk
5eb8e62d6e
Agents doens't have to be behind a mutable reference in within_priors
2022-12-15 20:13:25 +01:00
logaritmisk
8bea1b5399
Agents doens't have to be behind a mutable reference in within_prior
2022-12-15 20:12:55 +01:00
logaritmisk
6546cb54b5
Added a get function to IndexMap
2022-12-12 13:25:07 +01:00
logaritmisk
13e6454d3d
Update crates and added methods to get a key or all keys in an IndexMap
2022-12-12 10:50:59 +01:00
logaritmisk
18d55a8ccf
Clean up example
2022-07-04 23:13:57 +02:00
logaritmisk
32df04fb6d
Small change
2022-06-28 23:29:44 +02:00
logaritmisk
22c61d47b1
Change time to use i64 instead of u64
2022-06-28 23:18:55 +02:00
logaritmisk
6125a81696
Fixed test
2022-06-27 11:49:47 +02:00
logaritmisk
2467d7e027
Update test to use assert_ulps_eq
2022-06-27 11:46:59 +02:00
logaritmisk
0633c766a5
Update example so now it works, and thats, well, good
2022-06-27 10:22:26 +02:00
logaritmisk
cd1079a811
Use and Index struct instead of str and String for player id
2022-06-27 10:16:12 +02:00
logaritmisk
d52895f804
Remove unused code
2022-06-23 07:48:44 +02:00
logaritmisk
ba2f8cffdc
Update test to use builder
2022-06-23 07:44:34 +02:00
logaritmisk
73b9cabac8
Added builder for History, and start migrating test to use builder instead.
2022-06-22 21:13:24 +02:00
logaritmisk
a573f5cf0a
More tests
2022-06-21 10:48:04 +02:00
logaritmisk
1647ba5c77
More tests
2022-06-21 10:42:14 +02:00
logaritmisk
be4834ca85
Fix tests
2022-06-20 23:58:05 +02:00
logaritmisk
0a35645091
More things, more tests
2022-06-20 23:56:35 +02:00
logaritmisk
0efb0312da
More tests, more code
2022-06-19 23:52:52 +02:00
logaritmisk
c9d9d59535
More things, better things, awesome
2022-06-18 23:39:42 +02:00
logaritmisk
dc10504b80
Port from julia version instead
2022-06-18 22:27:38 +02:00
logaritmisk
36c3366990
Refactor some stuff
2022-06-15 13:26:03 +02:00
logaritmisk
0a3327f076
Refactor some code
2022-06-15 11:27:24 +02:00
logaritmisk
811a4ca418
Inline a lot of functions
2022-06-14 23:00:21 +02:00
logaritmisk
9b025fb53a
Use PlayerIndex instead of String
2022-06-14 22:51:11 +02:00
logaritmisk
3fbac02de3
It works, or so it seems
2022-06-13 23:18:19 +02:00
logaritmisk
1b6e07225b
Handle case where there is no time
2022-06-13 21:55:43 +02:00
logaritmisk
82e7b22443
Use flatten instead of flat_map
2022-06-13 11:37:56 +02:00
logaritmisk
de4b324651
Remove Display impl, better to use Debug
2022-06-13 11:35:08 +02:00
logaritmisk
87a64acb83
Added more functions to History
2022-06-13 11:27:49 +02:00
logaritmisk
4a13e4dcd2
More test passing for History
2022-06-13 11:04:05 +02:00
logaritmisk
4227617513
Working on History struct. First test is passing.
2022-06-12 23:11:13 +02:00
logaritmisk
5a7053fb5d
Passing tests for Batch
2022-06-12 21:43:35 +02:00
logaritmisk
ae1c765dbb
Begin working on batch.
2022-06-12 21:02:57 +02:00
logaritmisk
de58d01322
Initial commit.
2022-06-10 15:22:27 +02:00