Modulus magic!

Suppose we have `total_information` choices, and we first use them to
encode the answer `x` to a question with `m` answers. That answer is encoded
by the choice we take modulo `m`.

How much "information" do we have left? That depends on the number of
numbers less than `total_information` that are equal to `x` modulo `m`.
Depending on the value of `x`, this is either
`floor(total_information/m)` or `floor(total_information/m) + 1`.

We now use all of this information as opposed to just
`floor(total_information/m)`, at the cost of making our math not a lot
more complicated but pretty confusing.
This commit is contained in:
Felix Bauckholt 2019-03-05 15:48:32 +01:00
parent 8ed01d47ca
commit 051ac7a097
2 changed files with 40 additions and 25 deletions

View file

@ -73,5 +73,5 @@ On the first 20000 seeds, we have these average scores and win rates:
|---------|---------|---------|---------|---------| |---------|---------|---------|---------|---------|
| cheat | 24.8594 | 24.9785 | 24.9720 | 24.9557 | | cheat | 24.8594 | 24.9785 | 24.9720 | 24.9557 |
| | 90.59 % | 98.17 % | 97.76 % | 96.42 % | | | 90.59 % | 98.17 % | 97.76 % | 96.42 % |
| info | 22.3427 | 24.7488 | 24.9055 | 24.9051 | | info | 22.3736 | 24.7840 | 24.9261 | 24.9160 |
| | 10.23 % | 81.67 % | 92.53 % | 92.62 % | | | 10.41 % | 84.14 % | 94.33 % | 93.49 % |

View file

@ -19,20 +19,37 @@ impl ModulusInformation {
Self::new(1, 0) Self::new(1, 0)
} }
pub fn combine(&mut self, other: Self) { pub fn combine(&mut self, other: Self, max_modulus: u32) {
assert!(other.modulus <= self.info_remaining(max_modulus));
self.value = self.value + self.modulus * other.value; self.value = self.value + self.modulus * other.value;
self.modulus = self.modulus * other.modulus; self.modulus = std::cmp::min(max_modulus, self.modulus * other.modulus);
assert!(self.value < self.modulus);
}
pub fn info_remaining(&self, max_modulus: u32) -> u32 {
// We want to find the largest number `result` such that
// `self.combine(other, max_modulus)` works whenever `other.modulus == result`.
// `other.value` can be up to `result - 1`, so calling combine could increase our value to
// up to `self.value + self.modulus * (result - 1)`, which must always be less than
// `max_modulus`.
// Therefore, we compute the largest number `result` such that
// `self.value + self.modulus * (result - 1) < max_modulus`.
let result = (max_modulus - self.value - 1) / self.modulus + 1;
assert!(self.value + self.modulus * (result - 1) < max_modulus);
assert!(self.value + self.modulus * ((result + 1) - 1) >= max_modulus);
result
} }
pub fn split(&mut self, modulus: u32) -> Self { pub fn split(&mut self, modulus: u32) -> Self {
assert!(self.modulus >= modulus); assert!(self.modulus >= modulus);
assert!(self.modulus % modulus == 0);
let original_modulus = self.modulus; let original_modulus = self.modulus;
let original_value = self.value; let original_value = self.value;
self.modulus = self.modulus / modulus;
let value = self.value % modulus; let value = self.value % modulus;
self.value = self.value / modulus; self.value = self.value / modulus;
assert!(original_modulus == modulus * self.modulus); // `self.modulus` is the largest number such that
// `value + (self.modulus - 1) * modulus < original_modulus`.
// TODO: find an explanation of why this makes everything work out
self.modulus = (original_modulus - value - 1) / modulus + 1;
assert!(original_value == value + modulus * self.value); assert!(original_value == value + modulus * self.value);
Self::new(modulus, value) Self::new(modulus, value)
} }
@ -42,11 +59,11 @@ impl ModulusInformation {
self.modulus = modulus; self.modulus = modulus;
} }
pub fn cast_down(&mut self, modulus: u32) { // pub fn cast_down(&mut self, modulus: u32) {
assert!(self.modulus >= modulus); // assert!(self.modulus >= modulus);
assert!(self.value < modulus); // assert!(self.value < modulus);
self.modulus = modulus; // self.modulus = modulus;
} // }
pub fn add(&mut self, other: &Self) { pub fn add(&mut self, other: &Self) {
assert!(self.modulus == other.modulus); assert!(self.modulus == other.modulus);
@ -127,10 +144,10 @@ pub trait PublicInformation: Clone {
let mut answer_info = ModulusInformation::none(); let mut answer_info = ModulusInformation::none();
{ {
let callback = |hand_info: &mut HandInfo<CardPossibilityTable>, info_remaining: &mut u32, question: Box<Question>| { let callback = |hand_info: &mut HandInfo<CardPossibilityTable>, info_remaining: &mut u32, question: Box<Question>| {
*info_remaining = *info_remaining / question.info_amount();
let new_answer_info = question.answer_info(view.get_hand(player), view.get_board()); let new_answer_info = question.answer_info(view.get_hand(player), view.get_board());
question.acknowledge_answer_info(new_answer_info.clone(), hand_info, view.get_board()); question.acknowledge_answer_info(new_answer_info.clone(), hand_info, view.get_board());
answer_info.combine(new_answer_info); answer_info.combine(new_answer_info, total_info);
*info_remaining = answer_info.info_remaining(total_info);
}; };
self.ask_questions(player, hand_info, callback, total_info); self.ask_questions(player, hand_info, callback, total_info);
} }
@ -146,18 +163,16 @@ pub trait PublicInformation: Clone {
mut info: ModulusInformation, mut info: ModulusInformation,
) { ) {
let total_info = info.modulus; let total_info = info.modulus;
{
let callback = |hand_info: &mut HandInfo<CardPossibilityTable>, info_remaining: &mut u32, question: Box<Question>| { let callback = |hand_info: &mut HandInfo<CardPossibilityTable>, info_remaining: &mut u32, question: Box<Question>| {
let q_info_amount = question.info_amount();
*info_remaining = *info_remaining / q_info_amount;
let info_modulus = info.modulus;
// Instead of casting down the ModulusInformation to the product of all the questions before
// answering any, we now have to cast down the ModulusInformation question-by-question.
info.cast_down((info_modulus / q_info_amount) * q_info_amount);
let answer_info = info.split(question.info_amount()); let answer_info = info.split(question.info_amount());
question.acknowledge_answer_info(answer_info, hand_info, board); question.acknowledge_answer_info(answer_info, hand_info, board);
*info_remaining = info.modulus;
}; };
self.ask_questions(player, hand_info, callback, total_info); self.ask_questions(player, hand_info, callback, total_info);
} }
assert!(info.value == 0);
}
/// When deciding on a move, if we can choose between `total_info` choices, /// When deciding on a move, if we can choose between `total_info` choices,
/// `self.get_hat_sum(total_info, view)` tells us which choice to take, and at the same time /// `self.get_hat_sum(total_info, view)` tells us which choice to take, and at the same time