Skip to content

Commit

Permalink
sweet nothings + investigating failed Iso::From(Obs) on paired Obs
Browse files Browse the repository at this point in the history
  • Loading branch information
krukah committed Oct 12, 2024
1 parent ff94031 commit a8ff6fd
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 38 deletions.
2 changes: 1 addition & 1 deletion benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ criterion::criterion_group! {

fn enumerating_flops(c: &mut criterion::Criterion) {
c.bench_function("enumerate all Flops", |b| {
b.iter(|| Observation::enumerate(Street::Flop))
b.iter(|| Observation::exhaust(Street::Flop))
});
}

Expand Down
33 changes: 17 additions & 16 deletions src/cards/hand.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::{card::Card, suit::Suit};
use super::card::Card;
use super::suit::Suit;

/// Hand represents an unordered set of Cards. only in the limit, it is more memory efficient than Vec<Card>, ... but also, an advantage even for small N is that we avoid heap allocation. nice to use a single word for the full Hand independent of size stored as a u64, but only needs LSB bitstring of 52 bits. Each bit represents a unique card in the (unordered) set.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -116,24 +117,24 @@ impl From<Vec<Card>> for Hand {
impl From<Hand> for u16 {
fn from(h: Hand) -> Self {
let mut x = u64::from(h);
let mut y = u16::default();
x |= x >> 1;
x |= x >> 2;
x &= 0x1111111111111;
y |= ((x >> 00) & 0001) as u16;
y |= ((x >> 03) & 0002) as u16;
y |= ((x >> 06) & 0004) as u16;
y |= ((x >> 09) & 0008) as u16;
y |= ((x >> 12) & 0016) as u16;
y |= ((x >> 15) & 0032) as u16;
y |= ((x >> 18) & 0064) as u16;
y |= ((x >> 21) & 0128) as u16;
y |= ((x >> 24) & 0256) as u16;
y |= ((x >> 27) & 0512) as u16;
y |= ((x >> 30) & 1024) as u16;
y |= ((x >> 33) & 2048) as u16;
y |= ((x >> 36) & 4096) as u16;
y
let mut y = u64::default();
y |= (x >> 00) & 0x0001;
y |= (x >> 03) & 0x0002;
y |= (x >> 06) & 0x0004;
y |= (x >> 09) & 0x0008;
y |= (x >> 12) & 0x0010;
y |= (x >> 15) & 0x0020;
y |= (x >> 18) & 0x0040;
y |= (x >> 21) & 0x0080;
y |= (x >> 24) & 0x0100;
y |= (x >> 27) & 0x0200;
y |= (x >> 30) & 0x0400;
y |= (x >> 33) & 0x0800;
y |= (x >> 36) & 0x1000;
y as u16
}
}

Expand Down
51 changes: 47 additions & 4 deletions src/cards/isomorphism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ impl Isomorphism {
pub fn is_canonical(observation: &Observation) -> bool {
Permutation::from(*observation) == Permutation::identity()
}
pub fn enumerate(street: Street) -> Vec<Observation> {
Observation::enumerate(street)
pub fn exhaust(street: Street) -> Vec<Self> {
Observation::exhaust(street)
.into_iter()
.filter(|o| Self::is_canonical(o))
.map(|o| Self(o))
.collect()
}
}

impl From<Observation> for Isomorphism {
fn from(observation: Observation) -> Self {
Self(Permutation::from(observation).transform(observation))
let permutation = Permutation::from(observation);
let observation = permutation.transform(observation);
print!("{permutation}");
Self(observation)
}
}

Expand All @@ -42,14 +46,53 @@ mod tests {
fn isomorphic_exhaustion() {
let observation = Observation::from(Street::Rive);
let isomorphism = Isomorphism::from(observation);
Permutation::enumerate()
println!("{observation}");
println!("{isomorphism}");
Permutation::exhaust()
.iter()
.map(|p| p.transform(observation))
.map(|o| Isomorphism::from(o))
.inspect(|&i| assert!(isomorphism == i))
.count();

// the following cases fail this test
// something about Observation as Iterator<Suit> not properly handling pairs.

// 2c2d + 3s4c5sJsKs
// 2c2d + 3h4d5hJhKh

// 6dTc + 4c6c7h8h8s
// 6dTc + 4c6c7s8h8s

// 7c7h + 5h6c9hJhJs
// 7c7d + 5c6d9cJcJd

// 3d7d + 4c7h9dAcAh
// 3c7c + 4h7d9cAdAh

// 2h2s + 6c6dJhQhKh
// 2c2d + 6h6sJdQdKd
}

// #[test]
// fn tricky_1() {
// let observation = Observation::from((Hand::from("2c 2d"), Hand::from("3c 4s 5s")));
// let isomorphism = Isomorphism::from(observation);
// println!("OBS {observation}");
// println!("ISO {isomorphism}");
// println!();
// Permutation::exhaust()
// .iter()
// .inspect(|_| println!("{observation} ORIGINAL"))
// .inspect(|p| print!("{p}"))
// .map(|p| p.transform(observation))
// .inspect(|o| println!("{o} TRANSFORMED"))
// .map(|o| Isomorphism::from(o))
// .inspect(|&i| println!("{i} CANONICAL\n"))
// .inspect(|&i| assert!(isomorphism == i))
// .count();
// }

#[test]
fn isomorphic_monochrome() {
let a = Isomorphism::from(Observation::from((
Expand Down
16 changes: 8 additions & 8 deletions src/cards/observation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::card::Card;
use super::deck::Deck;
use super::hand::Hand;
use super::hands::HandIterator;
use super::isomorphism::Isomorphism;
// use super::isomorphism::Isomorphism;
use super::rank::Rank;
use super::street::Street;
use super::strength::Strength;
Expand All @@ -18,13 +18,13 @@ use std::cmp::Ordering;
/// then impl From<[Card; 2]> for Hand. But the convenience of having the same Hand type is worth it.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, PartialOrd, Ord)]
pub struct Observation {
secret: Hand,
public: Hand,
secret: Hand, // if memory-bound: could be Hole/u16
public: Hand, // if memory-bound: could be Board/[Option<Card>; 5]
}

impl Observation {
/// Generate all possible observations for a given street
pub fn enumerate(street: Street) -> Vec<Self> {
pub fn exhaust(street: Street) -> Vec<Self> {
let n = Self::observable(street);
let inner = HandIterator::from((n, Hand::from(0b11))).combinations();
let outer = HandIterator::from((2, Hand::from(0b00))).combinations();
Expand All @@ -33,9 +33,9 @@ impl Observation {
for hole in HandIterator::from((2, Hand::from(0b00))) {
for board in HandIterator::from((n, hole)) {
let obs = Self::from((hole, board));
if Isomorphism::is_canonical(&obs) {
observations.push(obs);
}
// if Isomorphism::is_canonical(&obs) {
observations.push(obs);
// }
}
}
observations
Expand All @@ -51,7 +51,7 @@ impl Observation {
HandIterator::from((n, excluded))
.map(|reveal| Hand::add(self.public, reveal))
.map(|public| Observation::from((self.secret, public)))
.filter(|obs| Isomorphism::is_canonical(obs))
// .filter(|obs| Isomorphism::is_canonical(obs))
.collect::<Vec<Self>>()
}

Expand Down
23 changes: 17 additions & 6 deletions src/cards/permutation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use itertools::Itertools;
use rand::seq::SliceRandom;

/// an array of 4 unique Suits represents
/// any of the 4! = 24 possible permutations.
/// any of the 4! = 24 elements in the Suit permutation group.
/// by assuming a "canonical" order of suits (C < D < H < S),
/// we map C -> P[0], D -> P[1], H -> P[2], S -> P[3].
/// we use [Suit; 4] to map C -> P[0], D -> P[1], H -> P[2], S -> P[3].
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Permutation([Suit; 4]);

Expand All @@ -27,7 +27,7 @@ impl Permutation {
.map(|suit| self.suited(hand, suit))
.fold(Hand::empty(), |acc, x| Hand::add(acc, x))
}
pub fn enumerate() -> [Self; 24] {
pub fn exhaust() -> [Self; 24] {
Suit::all()
.into_iter()
.permutations(4)
Expand Down Expand Up @@ -78,6 +78,16 @@ impl From<Observation> for Permutation {
}
}

impl std::fmt::Display for Permutation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Suit::all()
.into_iter()
.inspect(|s| write!(f, "{} -> {}\n", s, self.get(s)).unwrap())
.count();
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -112,12 +122,13 @@ mod tests {
#[test]
fn permute_unique() {
let ref hand = Hand::from("Ac Kd Qh Js");
Permutation::enumerate()
let mut unique = std::collections::HashSet::new();
let n = Permutation::exhaust()
.into_iter()
.filter(|p| p != &Permutation::identity())
.map(|p| p.permute(hand))
.inspect(|p| assert!(p != hand))
.inspect(|h| assert!(unique.insert(*h)))
.count();
assert!(n == 24);
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/clustering/learning.rs → src/clustering/layer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl Layer {
fn inner_points(&self) -> ObservationSpace {
log::info!("computing projections {}", self.street);
ObservationSpace(
Observation::enumerate(self.street.prev())
Observation::exhaust(self.street.prev())
.into_par_iter()
.map(|inner| (inner, self.lookup.projection(&inner)))
.collect::<BTreeMap<Observation, Histogram>>(),
Expand Down
2 changes: 1 addition & 1 deletion src/clustering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pub mod abstractor;
pub mod centroid;
pub mod datasets;
pub mod histogram;
pub mod learning;
pub mod layer;
pub mod metric;
pub mod potential;
pub mod progress;
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ fn main() {
// Boring stuff
logging();
// The k-means earth mover's distance hand-clustering algorithm.
clustering::learning::Layer::learn();
clustering::layer::Layer::learn();
// Monet Carlo counter-factual regret minimization. External sampling, alternating regret updates, linear weighting schedules.
mccfr::trainer::Explorer::train();
// After 100s of CPU-days of training in the arena, the CPU is ready to see you.
Expand Down

0 comments on commit a8ff6fd

Please sign in to comment.