Skip to main content

PsroTrainer

Struct PsroTrainer 

Source
pub struct PsroTrainer<B, P, O, E, FP, FO, FE>
where B: AutodiffBackend, P: JointPolicy<B>, O: Optimizer<P, B>, E: JointEnv, FP: Fn(&B::Device, u64) -> P, FO: Fn() -> BurnOptimizer<B, P, O>, FE: Fn() -> E,
{ /* private fields */ }
Expand description

PSRO outer-loop trainer for symmetric N-agent games (N ≥ 2).

Generic over the Burn backend B, policy module P, and Burn optimizer type O. The trainer owns:

  • N populations of policies (one per agent role) under populations: Vec<Vec<P>>.
  • A MetaSolver for the empirical meta-game. For N=2 the 2-player MetaSolver::solve path is used (any in-tree solver works); for N≥3 the trainer calls MetaSolver::solve_n_player and only AlphaRankMetaSolver provides a non-panicking override.
  • A cached empirical-payoff N-tensor PayoffCache keyed by joint pure strategy.
  • User-supplied factories for fresh policies + optimizers + envs.

§Policy/optimizer factories

The trainer doesn’t know how to construct a Burn module of the caller’s chosen architecture, so we take closures:

  • policy_factory: Fn(&B::Device, u64) -> P — fresh policy. The u64 is a per-construction seed the trainer derives from PsroConfig::seed via a monotonic init-counter. A reproducibility-aware factory threads it into MlpBurnPolicy::new_seeded / MlpBurnConfig::with_seed so that every agent’s initial policy and every per-iteration best-response gets distinct but deterministic weights (issue #135). Factories that don’t care about reproducibility may ignore the argument.
  • optimizer_factory: Fn() -> BurnOptimizer<B, P, O> — fresh optimizer.
  • env_factory: Fn() -> E — fresh env instance.

This keeps PSRO architecture-agnostic at the cost of slightly awkward generics at the call site (see the matching-pennies test).

§Single-policy-class assumption

All agents in both populations share the same policy class P. For 2-agent symmetric games (matching pennies, homogeneous bucket brigade) this is exactly what we want — the symmetry lets us transpose the payoff matrix for the column player’s solve. For fully asymmetric games (different obs/action spaces per role), the trainer needs to be re-parameterized over (P_row, P_col); that’s out of scope for the first PR.

Implementations§

Source§

impl<B, P, O, E, FP, FO, FE> PsroTrainer<B, P, O, E, FP, FO, FE>
where B: AutodiffBackend, P: JointPolicy<B>, O: Optimizer<P, B>, E: JointEnv, FP: Fn(&B::Device, u64) -> P, FO: Fn() -> BurnOptimizer<B, P, O>, FE: Fn() -> E,

Source

pub fn new( config: PsroConfig, joint_config: JointTrainerConfig, meta_solver: Box<dyn MetaSolver>, device: B::Device, policy_factory: FP, optimizer_factory: FO, env_factory: FE, ) -> Result<Self>

Construct a PSRO trainer with one initial random policy per agent.

joint_config.num_agents must be ≥ 2. For num_agents == 2 the trainer accepts any MetaSolver implementation; for num_agents > 2 the meta-solver’s MetaSolver::solve_n_player is called — at the time of this PR only AlphaRankMetaSolver provides a non-panicking override for N>2.

Source

pub fn populations(&self, agent: usize) -> &[P]

Borrow agent agent’s policy population.

Source

pub fn population_row(&self) -> &[P]

Borrow the row-player (agent 0) population.

Backward-compat shim retained for callers that pre-date the N-tensor refactor (notably tests/test_psro_matching_pennies.rs). New N≥2 code should use PsroTrainer::populations.

Source

pub fn population_col(&self) -> &[P]

Borrow the column-player (agent 1) population.

Backward-compat shim retained for callers that pre-date the N-tensor refactor. Panics for N=1 (which is rejected by new anyway). New N≥2 code should use PsroTrainer::populations.

Source

pub fn payoff_cache(&self) -> &PayoffCache

Borrow the cached empirical N-tensor payoff cache.

Source

pub fn run<F>(&mut self, on_iteration: F) -> Result<PsroStats>
where F: FnMut(&PsroIterationStats, &[&P]), P: Send + Sync, E: Send, FP: Sync, FO: Sync, FE: Sync, B::Device: Sync,

Run the PSRO outer loop and return the per-iteration history.

on_iteration is invoked once per outer iteration, immediately after that iteration’s PsroIterationStats is constructed and before it is pushed onto the returned history. This mirrors NfspTrainer::run and lets callers observe per-iteration progress during the run (live tracing logging, mid-run checkpoint triggers, etc.) rather than only inspecting the aggregate stats after run returns.

The callback receives two arguments:

  1. &PsroIterationStats — this iteration’s stats, whose iteration field increases monotonically from 1 to config.max_iterations.
  2. &[&P] — the newest best-response policy for each agent (brs[a] is agent a’s freshly-trained BR appended this iteration, i.e. populations(a).last()). This lets the callback persist per-agent BR policies to disk during the run (mid-run checkpointing, issue #204) without a borrow conflict against the &mut self held by run: the trainer cannot itself write files (it is backend/format-agnostic, the Recorder lives in the example), so it hands the closure the policy references it needs to checkpoint. Checkpointing is a pure side-effect read; it does not alter the trainer state or the deterministic training trajectory.

For the common case of “run with no per-iteration hook”, use Self::run_silent.

Source

pub fn run_silent(&mut self) -> Result<PsroStats>
where P: Send + Sync, E: Send, FP: Sync, FO: Sync, FE: Sync, B::Device: Sync,

Convenience entry point: drives Self::run with a no-op iteration callback. Use this when per-iteration observation is not needed (mirrors NfspTrainer::run_silent).

Source

pub fn current_meta_nash_per_agent(&self) -> Vec<Vec<f32>>

Most-recent per-agent meta-Nash distributions (one row per agent), or uniform over the initial population if run has not been called.

Source

pub fn current_meta_nash(&self) -> Vec<f32>

Backward-compat shim returning agent 0’s meta-Nash marginal.

Auto Trait Implementations§

§

impl<B, P, O, E, FP, FO, FE> !RefUnwindSafe for PsroTrainer<B, P, O, E, FP, FO, FE>

§

impl<B, P, O, E, FP, FO, FE> !Send for PsroTrainer<B, P, O, E, FP, FO, FE>

§

impl<B, P, O, E, FP, FO, FE> !Sync for PsroTrainer<B, P, O, E, FP, FO, FE>

§

impl<B, P, O, E, FP, FO, FE> !UnwindSafe for PsroTrainer<B, P, O, E, FP, FO, FE>

§

impl<B, P, O, E, FP, FO, FE> Freeze for PsroTrainer<B, P, O, E, FP, FO, FE>
where <B as BackendTypes>::Device: Freeze, FP: Freeze, FO: Freeze, FE: Freeze,

§

impl<B, P, O, E, FP, FO, FE> Unpin for PsroTrainer<B, P, O, E, FP, FO, FE>
where <B as BackendTypes>::Device: Unpin, FP: Unpin, FO: Unpin, FE: Unpin, P: Unpin,

§

impl<B, P, O, E, FP, FO, FE> UnsafeUnpin for PsroTrainer<B, P, O, E, FP, FO, FE>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more