use std::{cmp::Reverse, collections::BTreeSet, f64::consts::LN_2};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::{
PropagationError,
plan::{Location, Plan, Step}
};
use crate::{
Add, AddressingMode, CanVisitInstructions as _, Div, DropHighest,
DropLowest, EvaluationBounds, Evaluator, Exp, Function, Instruction,
InstructionVisitor, Max, Mod, Mul, Neg, ProgramCounter, RegisterIndex,
Return, RollCustomDice, RollRange, RollStandardDice, Sub, SumRollingRecord,
evaluator::BoundsEvaluator
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Cost
{
pub steps: u64,
pub cells: u64,
pub worlds: u64,
pub bits: u64,
pub class: Class
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Class
{
pub drops: bool,
pub dynamic: bool,
pub correlated: bool,
pub pairwise: bool
}
pub(crate) fn estimate(
evaluator: &Evaluator,
args: impl IntoIterator<Item = i32>
) -> Result<Cost, PropagationError>
{
observe(evaluator, args, |_| {})
}
fn observe(
evaluator: &Evaluator,
args: impl IntoIterator<Item = i32>,
mut observer: impl FnMut(&Lane)
) -> Result<Cost, PropagationError>
{
let function = &evaluator.function;
let arity = function.arity();
let args = args.into_iter().collect::<Vec<_>>();
if args.len() != arity
{
return Err(PropagationError::BadArity {
expected: arity,
given: args.len()
})
}
let mut bounds = BoundsEvaluator::new(function);
bounds
.seed(
args.into_iter().map(|arg| Some(arg.into())),
evaluator
.environment
.iter()
.map(|(index, value)| (*index, (*value).into())),
EvaluationBounds::default()
)
.expect("the arity agrees");
let mut lane = Lane {
bounds,
shadow: Shadow::new(function),
fork: None
};
let mut consumed = Consumed::new(lane.shadow.cells());
let plan = Plan::new(&function.instructions);
let mut depths = depths(&plan, function.instructions.len()).into_iter();
for (pc, inst) in function.instructions.iter().enumerate()
{
consumed.charge(lane.instruction(inst));
for step in plan.steps(ProgramCounter(pc))
{
consumed.charge(lane.step(step));
if let Step::Split(location) = step
{
let depth = depths.next().expect("every split has a depth");
if let Location::Register(reg) = location
{
lane.fork(*reg, depth);
}
}
observer(&lane);
}
}
debug_assert!(lane.fork.is_none(), "every split merges by the end");
consumed.charge(lane.shadow.finish());
Ok(lane.shadow.cost(&consumed))
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Event
{
steps: u128,
settled: u128,
peak: u128,
worlds: u128
}
impl Event
{
fn min(self, other: Event) -> Event
{
Event {
steps: self.steps.min(other.steps),
settled: self.settled.min(other.settled),
peak: self.peak.min(other.peak),
worlds: self.worlds.min(other.worlds)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Consumed
{
steps: u128,
peak: u128,
most: u128
}
impl Consumed
{
fn new(cells: u128) -> Self
{
Self {
steps: 0,
peak: cells,
most: 1
}
}
fn charge(&mut self, event: Event)
{
self.steps = self.steps.saturating_add(event.steps);
self.peak = self.peak.max(event.peak);
self.most = self.most.max(event.worlds);
}
}
const LANES: usize = 256;
#[cfg_attr(doc, aquamarine::aquamarine)]
#[derive(Debug, Clone)]
struct Lane<'f>
{
bounds: BoundsEvaluator<'f>,
shadow: Shadow,
fork: Option<Fork<'f>>
}
#[derive(Debug, Clone)]
struct Fork<'f>
{
split: usize,
outcomes: u128,
children: Vec<(u128, Lane<'f>)>
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Settling
{
Each,
Once
}
impl<'f> Lane<'f>
{
fn instruction(&mut self, inst: &Instruction) -> Event
{
let before = self.bounds.registers().to_vec();
self.bounds.step(inst);
let held = self.shadow.cells();
let mut pricing = Pricing {
shadow: &mut self.shadow,
before: &before,
after: self.bounds.registers(),
tally: Tally::default()
};
inst.visit(&mut pricing).unwrap();
let tally = pricing.tally;
let worlds = self.shadow.worlds();
let most = held.max(self.shadow.cells());
let own = Event {
steps: tally.times(worlds).steps,
settled: worlds.saturating_mul(most),
peak: (worlds - 1)
.saturating_mul(most)
.saturating_add(held)
.saturating_add(tally.peak),
worlds
};
match &mut self.fork
{
None => own,
Some(fork) =>
{
let events = fork
.children
.iter_mut()
.map(|(values, child)| (*values, child.instruction(inst)))
.collect::<Vec<_>>();
own.min(fork.combine(&events, Settling::Each))
}
}
}
fn step(&mut self, step: &Step) -> Event
{
let own = match step
{
Step::Split(location) => self.shadow.split(*location),
Step::Merge {
split,
survivor,
dead
} => self.shadow.merge(*split, *survivor, dead)
};
let Some(mut fork) = self.fork.take()
else
{
return own
};
let events = fork
.children
.iter_mut()
.map(|(values, child)| (*values, child.step(step)))
.collect::<Vec<_>>();
let mut combined = fork.combine(&events, Settling::Once);
match *step
{
Step::Merge {
split, survivor, ..
} if split == fork.split =>
{
combined.worlds = own.worlds;
if let Some(survivor) = survivor
{
let support = fork.allot(fork.children.iter().map(
|(values, child)| {
(*values, child.shadow.support(survivor))
}
));
self.shadow.narrow(survivor, support);
}
return own.min(combined)
},
Step::Merge { split, .. } if split < fork.split => fork.split -= 1,
_ =>
{}
}
self.fork = Some(fork);
own.min(combined)
}
fn fork(&mut self, reg: RegisterIndex, depth: u32)
{
let spare = LANES.saturating_sub(self.lanes()) as u128;
let mut leaves = Vec::new();
self.leaves(&mut leaves);
let leaves = leaves
.into_iter()
.filter_map(|leaf| {
let values = width(leaf.bounds.registers()[reg.0]);
let outcomes =
leaf.shadow.splits.last().map_or(1, |s| s.outcomes);
(values >= 2 && outcomes >= 2).then_some((values, leaf))
})
.collect::<Vec<_>>();
let descendants = |level: u128| {
leaves.iter().fold(0u128, |sum, (values, _)| {
sum.saturating_add((*values).min(level).saturating_pow(depth))
})
};
let (mut fits, mut exceeds) = (0u128, spare + 1);
while exceeds - fits > 1
{
let level = fits + (exceeds - fits) / 2;
match descendants(level) <= spare
{
true => fits = level,
false => exceeds = level
}
}
for (values, leaf) in leaves
{
let buckets = values.min(fits);
if buckets >= 2
{
leaf.divide(reg, buckets);
}
}
}
fn divide(&mut self, reg: RegisterIndex, buckets: u128)
{
let interval = self.bounds.registers()[reg.0];
let least = interval.min as i128;
let values = width(interval) as i128;
let split = self.shadow.splits.len() - 1;
let outcomes = self.shadow.splits[split].outcomes;
let buckets = buckets as i128;
let children = (0..buckets)
.map(|i| {
let start = least + i * values / buckets;
let end = least + (i + 1) * values / buckets - 1;
let mut child = self.clone();
child.bounds.narrow(reg, (start as i32, end as i32).into());
child.shadow.splits[split].outcomes = 1;
((end - start + 1) as u128, child)
})
.collect();
self.fork = Some(Fork {
split,
outcomes,
children
});
}
fn lanes(&self) -> usize
{
1 + self.fork.as_ref().map_or(0, |fork| {
fork.children.iter().map(|(_, child)| child.lanes()).sum()
})
}
fn leaves<'a>(&'a mut self, leaves: &mut Vec<&'a mut Lane<'f>>)
{
if self.fork.is_none()
{
leaves.push(self);
return
}
for (_, child) in
self.fork.iter_mut().flat_map(|fork| &mut fork.children)
{
child.leaves(leaves);
}
}
}
impl Fork<'_>
{
fn allot(&self, buckets: impl Iterator<Item = (u128, u128)>) -> u128
{
let mut buckets = buckets.collect::<Vec<_>>();
buckets.sort_unstable_by_key(|&(_, bound)| Reverse(bound));
let mut left = self.outcomes;
let mut sum = 0u128;
for (values, bound) in buckets
{
let taken = values.min(left);
sum = sum.saturating_add(taken.saturating_mul(bound));
left -= taken;
if left == 0
{
break
}
}
sum
}
fn combine(&self, events: &[(u128, Event)], settling: Settling) -> Event
{
let allot = |quantity: fn(&Event) -> u128| {
self.allot(
events
.iter()
.map(|(values, event)| (*values, quantity(event)))
)
};
let settled = allot(|event| event.settled);
let peak = match settling
{
Settling::Each => settled.saturating_add(
events
.iter()
.map(|(_, event)| event.peak.saturating_sub(event.settled))
.max()
.unwrap_or(0)
),
Settling::Once => allot(|event| event.peak.max(event.settled))
};
Event {
steps: allot(|event| event.steps),
settled,
peak,
worlds: allot(|event| event.worlds)
}
}
}
fn depths(plan: &Plan, len: usize) -> Vec<u32>
{
let mut depths = Vec::new();
let mut open = Vec::<(usize, bool)>::new();
for pc in 0..len
{
for step in plan.steps(ProgramCounter(pc))
{
match step
{
Step::Split(location) =>
{
open.push((
depths.len(),
matches!(location, Location::Register(_))
));
depths.push(0);
let mut above = 0;
for &(split, register) in open.iter().rev()
{
above += register as u32;
depths[split] = depths[split].max(above);
}
},
Step::Merge { split, .. } =>
{
open.remove(*split);
}
}
}
}
depths
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct Tally
{
steps: u128,
working: u128,
peak: u128
}
impl Tally
{
fn charge(&mut self, steps: u128, cells: u128)
{
self.steps = self.steps.saturating_add(steps);
self.working = self.working.saturating_add(cells);
self.peak = self.peak.max(self.working);
}
fn free(&mut self, cells: u128)
{
self.working = self.working.saturating_sub(cells);
}
fn keep(&mut self, charged: u128, kept: u128)
{
self.working =
self.working.saturating_sub(charged).saturating_add(kept);
}
fn then(&mut self, next: Tally)
{
self.steps = self.steps.saturating_add(next.steps);
self.peak = self.peak.max(self.working.saturating_add(next.peak));
self.working = self.working.saturating_add(next.working);
}
fn beside(self, other: Tally) -> Tally
{
Tally {
steps: self.steps.saturating_add(other.steps),
working: self.working.saturating_add(other.working),
peak: self
.peak
.saturating_add(other.working)
.max(other.peak.saturating_add(self.working))
}
}
fn times(self, n: u128) -> Tally
{
match n
{
0 => Tally::default(),
n => Tally {
steps: self.steps.saturating_mul(n),
working: self.working.saturating_mul(n),
peak: self
.working
.saturating_mul(n - 1)
.saturating_add(self.peak)
}
}
}
fn max(self, other: Tally) -> Tally
{
Tally {
steps: self.steps.max(other.steps),
working: self.working.max(other.working),
peak: self.peak.max(other.peak)
}
}
}
#[derive(Debug, Clone)]
struct Shadow
{
registers: Vec<Slot>,
records: Vec<Record>,
answer: Option<Slot>,
splits: Vec<Split>,
scaled: bool,
log: f64,
class: Class
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Slot
{
support: u128,
unread: bool,
varied: bool,
random: bool,
dense: bool,
width: u128
}
impl Slot
{
const FIXED: Self = Self {
support: 1,
unread: false,
varied: false,
random: false,
dense: false,
width: 1
};
fn cells(&self) -> u128
{
match self.unread && self.support > 1
{
true => self.support,
false => 0
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Split
{
outcomes: u128,
cells: u128
}
impl Shadow
{
fn new(function: &Function) -> Self
{
Self {
registers: vec![Slot::FIXED; function.register_count],
records: vec![Record::default(); function.rolling_record_count],
answer: None,
splits: Vec::new(),
scaled: false,
log: 0.0,
class: Class::default()
}
}
fn worlds(&self) -> u128
{
self.splits
.iter()
.fold(1u128, |worlds, split| worlds.saturating_mul(split.outcomes))
}
fn cells(&self) -> u128
{
let registers = self
.registers
.iter()
.fold(self.registers.len() as u128, |cells, slot| {
cells.saturating_add(slot.cells())
});
let records = self
.records
.iter()
.fold(0u128, |cells, record| cells.saturating_add(record.cells));
registers
.saturating_add(records)
.saturating_add(self.answer.map_or(0, |answer| answer.support))
.saturating_add(self.fixed())
}
fn fixed(&self) -> u128
{
self.splits
.iter()
.fold(0u128, |cells, split| cells.saturating_add(split.cells))
}
fn event(&self, steps: u128, worlds: u128, cells: u128, peak: u128)
-> Event
{
let held = worlds.saturating_mul(cells);
let after = self.worlds();
Event {
steps,
settled: held.max(after.saturating_mul(self.cells())),
peak: held.saturating_add(peak),
worlds: after
}
}
fn cost(&self, consumed: &Consumed) -> Cost
{
let log = self.log * (1.0 + 1e-12) + 1e-9;
let bits = match log.is_finite() && log < u64::MAX as f64
{
true => log.floor() as u64 + 1,
false => u64::MAX
};
Cost {
steps: saturate(consumed.steps),
cells: saturate(consumed.peak),
worlds: saturate(consumed.most),
bits,
class: self.class
}
}
}
#[inline]
fn saturate(bound: u128) -> u64 { u64::try_from(bound).unwrap_or(u64::MAX) }
fn width(bounds: EvaluationBounds) -> u128
{
(bounds.max as i64 - bounds.min as i64 + 1).max(1) as u128
}
fn unsaturated(bounds: EvaluationBounds) -> bool
{
bounds.min > i32::MIN && bounds.max < i32::MAX
}
fn top_sum(bounds: EvaluationBounds, k: u128) -> u128
{
let greatest = bounds.max.max(0) as u128;
let least = bounds.min.max(0) as u128;
let k = k.min(greatest - least + 1);
k.saturating_mul(2 * greatest + 1 - k) / 2
}
const LISTED: u128 = 256;
fn tops(bounds: EvaluationBounds, floor: i32, k: u128) -> Vec<(u128, u128)>
{
if bounds.max < floor
{
return Vec::new()
}
let greatest = bounds.max as u128;
let k = k.min(greatest - bounds.min.max(floor) as u128 + 1);
match k <= LISTED
{
true => (0..k).map(|i| (greatest - i, 1)).collect(),
false => vec![(greatest, k)]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Operand
{
bounds: EvaluationBounds,
support: u128,
varied: bool,
random: bool,
dense: bool
}
impl Operand
{
fn width(&self) -> u128 { width(self.bounds) }
fn nonzero(&self) -> bool { !self.bounds.contains(0) }
}
struct Pricing<'a>
{
shadow: &'a mut Shadow,
before: &'a [EvaluationBounds],
after: &'a [EvaluationBounds],
tally: Tally
}
impl Pricing<'_>
{
fn operand(&self, op: AddressingMode) -> Operand
{
match op
{
AddressingMode::Immediate(value) => Operand {
bounds: value.0.into(),
support: 1,
varied: false,
random: false,
dense: true
},
AddressingMode::Register(reg) =>
{
let slot = &self.shadow.registers[reg.0];
let width = width(self.before[reg.0]);
Operand {
bounds: self.before[reg.0],
support: slot.support.min(width),
varied: slot.varied,
random: slot.random,
dense: width == 1 || (slot.dense && slot.width == width)
}
},
AddressingMode::RollingRecord(_) => unreachable!()
}
}
fn read(&mut self, op: AddressingMode) -> Operand
{
let operand = self.operand(op);
if !operand.varied
{
self.tally.charge(1, 1);
}
if let AddressingMode::Register(reg) = op
{
self.shadow.registers[reg.0].unread = false;
}
operand
}
fn read_pair(
&mut self,
op1: AddressingMode,
op2: AddressingMode
) -> (Operand, Operand, u128)
{
match op1
{
AddressingMode::Register(_) if op1 == op2 =>
{
let x = self.read(op1);
self.tally.charge(x.support, x.support);
(x, x, x.support)
},
_ =>
{
let x = self.read(op1);
let y = self.read(op2);
let pairs = x.support.saturating_mul(y.support);
self.tally.charge(pairs, pairs);
(x, y, pairs)
}
}
}
fn write(
&mut self,
dest: usize,
support: u128,
varied: bool,
random: bool,
dense: bool
)
{
let old = self.shadow.registers[dest];
let discard = old.cells() > 0;
self.tally.charge(!varied as u128 + discard as u128, 0);
self.shadow.scaled |= discard || (random && !varied);
let width = width(self.after[dest]);
self.shadow.registers[dest] = Slot {
support: support.min(width).max(1),
unread: true,
varied,
random,
dense: dense || width == 1,
width
};
}
fn densify(&mut self, dest: usize, dense: bool, least: i64, greatest: i64)
{
let bounds = self.after[dest];
if dense && bounds.min as i64 == least && bounds.max as i64 == greatest
{
self.shadow.registers[dest].dense = true;
}
}
fn binary(
&mut self,
dest: usize,
op1: AddressingMode,
op2: AddressingMode,
op: Op
) -> Option<(Operand, Operand)>
{
let exact = unsaturated(self.after[dest]);
let (result, varied, random, operands) = match op1
{
AddressingMode::Register(_) if op1 == op2 =>
{
let x = self.read(op1);
self.tally.charge(x.support, x.support);
let varied = exact && x.varied && op == Op::Add;
(x.support, varied, x.random, None)
},
_ =>
{
let x = self.read(op1);
let y = self.read(op2);
let (result, varied) = match op
{
Op::Add => (
self.saturating_sum(x, y, dest),
exact && (x.varied || y.varied)
),
Op::Mul | Op::Other =>
{
let pairs = x.support.saturating_mul(y.support);
self.tally.charge(pairs, pairs);
let varied = op == Op::Mul
&& exact
&& ((x.varied && y.nonzero())
|| (y.varied && x.nonzero())
|| (x.varied && y.varied));
(pairs, varied)
}
};
(result, varied, x.random || y.random, Some((x, y)))
}
};
self.write(dest, result, varied, random, false);
operands
}
fn saturating_sum(&mut self, x: Operand, y: Operand, dest: usize) -> u128
{
let wide = x.support.saturating_add(y.support);
self.tally.charge(wide, wide);
let span = x.width() + y.width() - 1;
let pairs = x.support.saturating_mul(y.support);
let sums = span.min(pairs);
self.tally.charge(pairs, sums.saturating_mul(2));
self.tally.keep(sums.saturating_mul(2), sums);
self.tally.free(wide);
clamp(&mut self.tally, sums, width(self.after[dest]))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Op
{
Add,
Mul,
Other
}
fn clamp(tally: &mut Tally, len: u128, limit: u128) -> u128
{
tally.charge(len, len);
let clamped = len.min(limit);
tally.keep(len.saturating_mul(2), clamped);
clamped
}
impl InstructionVisitor<()> for Pricing<'_>
{
fn visit_roll_range(&mut self, inst: &RollRange) -> Result<(), ()>
{
let (start, end, pairs) = self.read_pair(inst.start, inst.end);
self.tally.charge(pairs, pairs);
self.shadow.class.dynamic |= start.random || end.random;
let widest = width((start.bounds.min, end.bounds.max).into());
let narrowest = width((start.bounds.max, end.bounds.min).into());
self.shadow.log += lcm_log(widest, widest - narrowest + 1);
let record = Record {
kind: Kind::Range { start, end },
setups: pairs,
cells: pairs,
dice: 0,
lowest: 0.into(),
highest: 0.into(),
summed: false,
random: true
};
self.fill(inst.dest.0, record);
Ok(())
}
fn visit_roll_standard_dice(
&mut self,
inst: &RollStandardDice
) -> Result<(), ()>
{
let (count, faces, pairs) = self.read_pair(inst.count, inst.faces);
self.shadow.class.dynamic |= count.random || faces.random;
let (counts, dies, dice) = match inst.count == inst.faces
{
true => (1, pairs, top_sum(count.bounds, pairs)),
false => (
count.support,
faces.support,
faces
.support
.saturating_mul(top_sum(count.bounds, count.support))
)
};
self.tally.charge(pairs.saturating_add(dice), pairs);
let greatest = count.bounds.max.max(0) as f64;
let faces_in_any =
width((faces.bounds.min.max(1), faces.bounds.max).into());
self.shadow.log +=
greatest * lcm_log(faces.bounds.max.max(0) as u128, faces_in_any);
let record = Record {
kind: Kind::Standard {
count,
faces,
counts,
dies
},
setups: pairs,
cells: pairs,
dice,
lowest: 0.into(),
highest: 0.into(),
summed: false,
random: true
};
self.fill(inst.dest.0, record);
Ok(())
}
fn visit_roll_custom_dice(
&mut self,
inst: &RollCustomDice
) -> Result<(), ()>
{
let count = self.read(inst.count);
self.shadow.class.dynamic |= count.random;
let raw = inst.faces.len() as u128;
let distinct = inst.faces.iter().copied().collect::<BTreeSet<_>>();
let die = Die::custom(distinct.into_iter().collect());
let dice = top_sum(count.bounds, count.support);
self.tally.charge(
raw.saturating_add(count.support).saturating_add(dice),
raw.saturating_add(count.support.saturating_mul(1 + raw))
);
self.shadow.log +=
count.bounds.max.max(0) as f64 * (raw as f64).log2().max(0.0);
let cells = count.support.saturating_mul(1 + die.faces);
let record = Record {
kind: Kind::Custom { count, die },
setups: count.support,
cells,
dice,
lowest: 0.into(),
highest: 0.into(),
summed: false,
random: true
};
self.fill(inst.dest.0, record);
Ok(())
}
fn visit_drop_lowest(&mut self, inst: &DropLowest) -> Result<(), ()>
{
self.drop(inst.dest.0, inst.count, true);
Ok(())
}
fn visit_drop_highest(&mut self, inst: &DropHighest) -> Result<(), ()>
{
self.drop(inst.dest.0, inst.count, false);
Ok(())
}
fn visit_sum_rolling_record(
&mut self,
inst: &SumRollingRecord
) -> Result<(), ()>
{
let limit = width(self.after[inst.dest.0]);
let record = &mut self.shadow.records[inst.src.0];
record.summed = true;
let (tally, support) = record.sum(limit);
let (varied, random) = (record.varied(), record.random);
let dense = record.dense(self.after[inst.dest.0]);
self.tally.then(tally);
self.write(inst.dest.0, support, varied, random, dense);
Ok(())
}
fn visit_add(&mut self, inst: &Add) -> Result<(), ()>
{
if let Some((x, y)) =
self.binary(inst.dest.0, inst.op1, inst.op2, Op::Add)
{
self.densify(
inst.dest.0,
x.dense && y.dense,
x.bounds.min as i64 + y.bounds.min as i64,
x.bounds.max as i64 + y.bounds.max as i64
);
}
Ok(())
}
fn visit_sub(&mut self, inst: &Sub) -> Result<(), ()>
{
if let AddressingMode::Register(_) = inst.op1
&& inst.op1 == inst.op2
{
let x = self.read(inst.op1);
self.tally.charge(x.support, x.support);
self.write(inst.dest.0, x.support, false, x.random, false);
return Ok(())
}
if let Some((x, y)) =
self.binary(inst.dest.0, inst.op1, inst.op2, Op::Add)
{
self.densify(
inst.dest.0,
x.dense && y.dense,
x.bounds.min as i64 - y.bounds.max as i64,
x.bounds.max as i64 - y.bounds.min as i64
);
}
Ok(())
}
fn visit_mul(&mut self, inst: &Mul) -> Result<(), ()>
{
self.pairwise(inst.dest.0, inst.op1, inst.op2, Op::Mul);
Ok(())
}
fn visit_div(&mut self, inst: &Div) -> Result<(), ()>
{
self.pairwise(inst.dest.0, inst.op1, inst.op2, Op::Other);
Ok(())
}
fn visit_mod(&mut self, inst: &Mod) -> Result<(), ()>
{
self.pairwise(inst.dest.0, inst.op1, inst.op2, Op::Other);
Ok(())
}
fn visit_exp(&mut self, inst: &Exp) -> Result<(), ()>
{
self.pairwise(inst.dest.0, inst.op1, inst.op2, Op::Other);
Ok(())
}
fn visit_max(&mut self, inst: &Max) -> Result<(), ()>
{
self.pairwise(inst.dest.0, inst.op1, inst.op2, Op::Other);
Ok(())
}
fn visit_neg(&mut self, inst: &Neg) -> Result<(), ()>
{
let x = self.read(inst.op);
self.tally.charge(x.support, x.support);
let varied = x.varied && x.bounds.min > i32::MIN;
self.write(inst.dest.0, x.support, varied, x.random, false);
self.densify(
inst.dest.0,
x.dense,
-(x.bounds.max as i64),
-(x.bounds.min as i64)
);
Ok(())
}
fn visit_return(&mut self, inst: &Return) -> Result<(), ()>
{
let x = self.read(inst.src);
self.shadow.answer = Some(Slot {
support: x.support,
unread: true,
varied: x.varied,
random: x.random,
dense: x.dense,
width: x.width()
});
Ok(())
}
}
impl Pricing<'_>
{
fn pairwise(
&mut self,
dest: usize,
op1: AddressingMode,
op2: AddressingMode,
op: Op
)
{
if let Some((x, y)) = self.binary(dest, op1, op2, op)
{
self.shadow.class.pairwise |= x.random && y.random;
}
}
fn fill(&mut self, dest: usize, record: Record)
{
let old = std::mem::replace(&mut self.shadow.records[dest], record);
self.tally.charge(old.retirement(), 0);
self.shadow.scaled |= old.scales();
}
fn drop(&mut self, dest: usize, op: AddressingMode, lowest: bool)
{
let count = self.read(op);
self.shadow.class.dynamic |= count.random;
self.shadow.class.drops |= count.bounds.max > 0;
let record = &mut self.shadow.records[dest];
let outcomes = count.support;
self.tally.charge(
outcomes.saturating_mul(record.setups.saturating_add(record.dice)),
outcomes.saturating_mul(record.cells)
);
record.setups = record.setups.saturating_mul(outcomes);
record.cells = record.cells.saturating_mul(outcomes);
record.dice = record.dice.saturating_mul(outcomes);
record.random |= count.random;
record.summed = false;
let count = EvaluationBounds::from((
count.bounds.min.max(0),
count.bounds.max.max(0)
));
match lowest
{
true => record.lowest += count,
false => record.highest += count
}
}
}
impl Shadow
{
fn split(&mut self, location: Location) -> Event
{
self.class.correlated = true;
let worlds = self.worlds();
let cells = self.cells();
let mut tally = Tally::default();
let (outcomes, outcome_cells) = match location
{
Location::Register(reg) =>
{
let slot = &mut self.registers[reg.0];
let outcomes = slot.support;
tally.charge(outcomes, outcomes);
slot.support = 1;
slot.unread = false;
slot.varied = false;
slot.dense = slot.width == 1;
(outcomes, 1)
},
Location::RollingRecord(rec) =>
{
let record = &mut self.records[rec.0];
let (outcomes, outcome_cells) = record.outcomes(&mut tally);
record.fix(outcome_cells);
(outcomes, outcome_cells)
},
Location::Answer => unreachable!("no instruction reads the answer")
};
let copies = outcomes.saturating_mul(
cells.saturating_add(outcome_cells.saturating_mul(2))
);
tally.charge(copies, copies);
let tally = tally.times(worlds);
self.splits.push(Split {
outcomes,
cells: outcome_cells
});
self.event(tally.steps, worlds, cells, tally.peak)
}
fn merge(
&mut self,
split: usize,
survivor: Option<Location>,
dead: &[Location]
) -> Event
{
let worlds = self.worlds();
let cells = self.cells();
let mut tally = Tally::default();
for &location in dead
{
match location
{
Location::Register(reg) =>
{
let slot = &mut self.registers[reg.0];
tally.charge((slot.cells() > 0) as u128, 0);
slot.unread = false;
},
Location::RollingRecord(rec) =>
{
let record = &mut self.records[rec.0];
tally.charge(record.retirement(), 0);
*record = Record {
summed: true,
..Record::default()
};
},
Location::Answer => unreachable!("the answer lives to the end")
}
}
tally.charge(self.splits.len() as u128, self.fixed());
let mut tally = tally.times(worlds);
let merged = self.splits.remove(split);
let groups = self.worlds();
let slot = match survivor
{
Some(Location::Register(reg)) => self.registers[reg.0],
Some(Location::Answer) => self.answer.unwrap_or(Slot::FIXED),
Some(Location::RollingRecord(_)) =>
{
unreachable!("a survivor is never a rolling record")
},
None => Slot::FIXED
};
let outcomes = merged.outcomes.saturating_mul(slot.support);
let mut mix = Tally::default();
mix.charge(merged.outcomes, merged.outcomes);
mix.charge(outcomes, outcomes);
mix.charge(!slot.varied as u128, 0);
tally.then(mix.times(groups));
let merged = Slot {
support: outcomes.min(slot.width).max(1),
unread: true,
..slot
};
match survivor
{
Some(Location::Register(reg)) =>
{
self.registers[reg.0] = merged;
self.scaled = !merged.varied;
},
Some(Location::Answer) =>
{
self.answer = Some(merged);
self.scaled = false;
},
_ => self.scaled = true
}
self.event(tally.steps, worlds, cells, tally.peak)
}
fn support(&self, survivor: Location) -> u128
{
match survivor
{
Location::Register(reg) => self.registers[reg.0].support,
Location::Answer => self.answer.map_or(1, |answer| answer.support),
Location::RollingRecord(_) =>
{
unreachable!("a survivor is never a rolling record")
}
}
}
fn narrow(&mut self, survivor: Location, support: u128)
{
let slot = match survivor
{
Location::Register(reg) => &mut self.registers[reg.0],
Location::Answer => match &mut self.answer
{
Some(answer) => answer,
None => return
},
Location::RollingRecord(_) =>
{
unreachable!("a survivor is never a rolling record")
}
};
slot.support = slot.support.min(support).max(1);
}
fn finish(&mut self) -> Event
{
let cells = self.cells();
let mut tally = Tally::default();
for slot in &self.registers
{
let discard = slot.cells() > 0;
tally.charge(discard as u128, 0);
self.scaled |= discard;
}
for record in &self.records
{
tally.charge(record.retirement(), 0);
self.scaled |= record.scales();
}
if self.scaled
{
let answer = self.answer.map_or(1, |answer| answer.support);
tally.charge(answer, answer);
}
self.event(tally.steps, 1, cells, tally.peak)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Record
{
kind: Kind,
setups: u128,
cells: u128,
dice: u128,
lowest: EvaluationBounds,
highest: EvaluationBounds,
summed: bool,
random: bool
}
impl Default for Record
{
fn default() -> Self
{
Self {
kind: Kind::Empty,
setups: 1,
cells: 1,
dice: 0,
lowest: 0.into(),
highest: 0.into(),
summed: false,
random: false
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Kind
{
Empty,
Range
{
start: Operand,
end: Operand
},
Standard
{
count: Operand,
faces: Operand,
counts: u128,
dies: u128
},
Custom
{
count: Operand,
die: Die
},
Results(u128)
}
impl Record
{
fn retirement(&self) -> u128
{
match self.summed
{
true => 0,
false => self.setups.saturating_mul(2).saturating_add(1)
}
}
fn scales(&self) -> bool
{
!self.summed && (self.kind != Kind::Empty || self.setups > 1)
}
fn least_drops(&self) -> (u128, u128)
{
(
self.lowest.min.max(0) as u128,
self.highest.min.max(0) as u128
)
}
fn varied(&self) -> bool
{
let dropped = self.lowest.max as i64 + self.highest.max as i64;
let kept = |count: &Operand| count.bounds.min as i64 - dropped >= 1;
match &self.kind
{
Kind::Range { start, end } =>
{
end.bounds.min as i64 > start.bounds.max as i64 && dropped == 0
},
Kind::Standard { count, faces, .. } =>
{
kept(count)
&& faces.bounds.min >= 2
&& count.bounds.max as i64 * faces.bounds.max as i64
<= i32::MAX as i64
},
Kind::Custom { count, die } =>
{
let largest =
die.least.unsigned_abs().max(die.greatest.unsigned_abs());
kept(count)
&& die.faces >= 2
&& count.bounds.max as i64 * largest as i64
<= i32::MAX as i64
},
Kind::Empty | Kind::Results(_) => false
}
}
fn dense(&self, bounds: EvaluationBounds) -> bool
{
let fixed = |op: &Operand| op.bounds.min == op.bounds.max;
if self.lowest.min != self.lowest.max
|| self.highest.min != self.highest.max
{
return false
}
let dropped = self.lowest.max as i64 + self.highest.max as i64;
let reached = |count: &Operand, least: i32, greatest: i32| {
let kept = count.bounds.min as i64 - dropped;
(fixed(count) && kept >= 1)
.then(|| (kept * least as i64, kept * greatest as i64))
};
let reached = match &self.kind
{
Kind::Range { start, end }
if fixed(start)
&& fixed(end)
&& dropped == 0
&& start.bounds.min <= end.bounds.min =>
{
Some((start.bounds.min as i64, end.bounds.min as i64))
},
Kind::Standard { count, faces, .. }
if fixed(faces) && faces.bounds.min >= 1 =>
{
match fixed(count)
{
true => reached(count, 1, faces.bounds.min),
false =>
{
(count.dense && dropped == 0 && count.bounds.max >= 1)
.then(|| {
(
count.bounds.min.max(0) as i64,
count.bounds.max as i64
* faces.bounds.min as i64
)
})
},
}
},
Kind::Custom { count, die } if die.contiguous() =>
{
reached(count, die.least, die.greatest)
},
_ => None
};
reached.is_some_and(|(least, greatest)| {
least == bounds.min as i64 && greatest == bounds.max as i64
})
}
fn fix(&mut self, cells: u128)
{
*self = Record {
kind: Kind::Results(cells - 1),
setups: 1,
cells,
dice: 0,
lowest: self.lowest,
highest: self.highest,
summed: false,
random: self.random
};
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Die
{
faces: u128,
least: i32,
greatest: i32,
step: u128,
values: Option<Vec<i32>>
}
impl Die
{
fn standard(faces: u128) -> Self
{
Self {
faces,
least: 1,
greatest: faces as i32,
step: 1,
values: None
}
}
fn custom(values: Vec<i32>) -> Self
{
match (values.first(), values.last())
{
(Some(&least), Some(&greatest)) =>
{
let step = values
.iter()
.map(|&value| (value as i64 - least as i64) as u128)
.fold(0, gcd)
.max(1);
Self {
faces: values.len() as u128,
least,
greatest,
step,
values: Some(values)
}
},
_ => Self::custom(vec![0])
}
}
fn spread(&self) -> u128
{
(self.greatest as i64 - self.least as i64) as u128
}
fn contiguous(&self) -> bool { self.faces == self.spread() + 1 }
fn alpha(&self) -> u128 { self.spread() / self.step }
fn span(&self, k: u128) -> u128
{
self.spread().saturating_mul(k).saturating_add(1)
}
fn linear(&self, k: u128) -> u128
{
self.alpha().saturating_mul(k).saturating_add(1)
}
fn linear_sum(&self, a: u128, b: u128) -> u128
{
match a > b
{
true => 0,
false =>
{
let n = b - a + 1;
let dice = (a + b).saturating_mul(n) / 2;
self.alpha().saturating_mul(dice).saturating_add(n)
}
}
}
fn support(&self, k: u128) -> u128
{
self.linear(k)
.min(binomial(k + self.faces - 1, self.faces - 1))
}
fn folds(&self, count: u128) -> bool
{
self.least >= 0
|| self.greatest <= 0
|| count as i128 * self.least as i128 >= i32::MIN as i128
}
}
fn gcd(a: u128, b: u128) -> u128
{
match b
{
0 => a,
b => gcd(b, a % b)
}
}
fn binomial(n: u128, k: u128) -> u128
{
if k > n
{
return 0
}
let k = k.min(n - k);
let mut c = 1u128;
for i in 0..k
{
match c.checked_mul(n - i)
{
Some(product) => c = product / (i + 1),
None => return u128::MAX
}
}
c
}
fn lcm_log(greatest: u128, count: u128) -> f64
{
if greatest <= 1
{
return 0.0
}
let greatest = greatest as f64;
(count as f64 * greatest.log2()).min(1.03883 * greatest / LN_2)
}
impl Record
{
fn sum(&self, limit: u128) -> (Tally, u128)
{
if self.setups == 1
{
return self.setup_sum(limit)
}
let (setups, outcomes) = match &self.kind
{
Kind::Empty | Kind::Results(_) =>
{
let (mut each, support) = self.setup_sum(limit);
each.charge(support, support);
each.keep(support.saturating_mul(2), 0);
(each.times(self.setups), support.saturating_mul(self.setups))
},
Kind::Range { start, end } => (self.range_sums(start, end), limit),
Kind::Standard { .. } | Kind::Custom { .. } => self.dice_sums(limit)
};
let outcomes = outcomes.min(limit);
let mut tally = Tally::default();
tally.charge(self.setups.saturating_mul(2), outcomes);
tally.then(setups);
tally.charge(outcomes, outcomes);
tally.keep(outcomes.saturating_mul(2), outcomes);
(tally, outcomes)
}
fn setup_sum(&self, limit: u128) -> (Tally, u128)
{
let mut tally = Tally::default();
match &self.kind
{
Kind::Empty =>
{
tally.charge(1, 1);
(tally, 1)
},
Kind::Results(distinct) =>
{
tally.charge(*distinct, 1);
(tally, 1)
},
Kind::Range { start, end } =>
{
let widest = width((start.bounds.min, end.bounds.max).into());
tally.charge(widest, widest);
(tally, widest.min(limit))
},
Kind::Standard { count, faces, .. } => match faces.bounds.max
{
..=0 =>
{
tally.charge(1, 1);
(tally, 1)
},
greatest => self.dice_sum(
count,
&Die::standard(greatest as u128),
limit
)
},
Kind::Custom { count, die } => self.dice_sum(count, die, limit)
}
}
fn dice_sum(&self, count: &Operand, die: &Die, limit: u128)
-> (Tally, u128)
{
let mut tally = Tally::default();
tally.charge(1, 1);
let (lowest, highest) = self.least_drops();
let greatest = count.bounds.max.max(0) as u128;
if greatest == 0 || lowest + highest >= greatest
{
return (tally, 1)
}
let least = count.bounds.min.max(1) as u128;
let mut support = 1;
if lowest == 0 && highest == 0 && die.folds(least)
{
let (power, len) = power_sum(die, least, greatest, limit);
tally = tally.max(power);
support = support.max(len);
}
if self.lowest.max > 0 || self.highest.max > 0 || !die.folds(greatest)
{
let (order, len) = order_sum(die, greatest, lowest, highest, limit);
tally = tally.max(order);
support = support.max(len);
}
(tally, support)
}
fn range_sums(&self, start: &Operand, end: &Operand) -> Tally
{
let widest = width((start.bounds.min, end.bounds.max).into());
let mut each = Tally::default();
each.charge(2, 2);
let mut point = Tally::default();
point.charge(2, 2);
point.keep(2, 0);
let mut tally = each.max(point).times(self.setups);
let edges = self
.setups
.saturating_mul(2)
.min(start.support.saturating_add(end.support));
let sweep = widest.saturating_add(edges.saturating_mul(2));
let mut swept = Tally::default();
swept.charge(widest.saturating_add(edges), sweep);
swept.keep(sweep, 0);
tally.then(swept);
tally
}
fn dice_sums(&self, limit: u128) -> (Tally, u128)
{
let (count, dies, counts) = match &self.kind
{
Kind::Standard {
count,
faces,
counts,
dies
} =>
{
let dies = tops(faces.bounds, 1, (*dies).min(self.setups))
.into_iter()
.map(|(faces, times)| (Die::standard(faces), times))
.collect::<Vec<_>>();
(count, dies, *counts)
},
Kind::Custom { count, die } =>
{
(count, vec![(die.clone(), 1)], count.support)
},
_ => unreachable!("the record holds dice")
};
let (lowest, highest) = self.least_drops();
let (most_lowest, most_highest) = (
self.lowest.max.max(0) as u128,
self.highest.max.max(0) as u128
);
let counts = counts.min(self.setups);
let variants = self.setups.div_ceil(
counts
.saturating_mul(dies.iter().map(|(_, times)| times).sum())
.max(1)
);
let mut point = Tally::default();
point.charge(2, 2);
point.keep(2, 0);
let mut tally = point.times(self.setups);
let mut outcomes = self.setups;
let least = count.bounds.min.max(1) as u128;
let greatest = count.bounds.max.max(0) as u128;
if greatest == 0
{
return (tally, outcomes)
}
let listed = tops(count.bounds, 1, counts);
for (die, times) in &dies
{
if lowest == 0 && highest == 0 && die.folds(least)
{
let family = power_family(
die,
least,
greatest,
counts,
count.dense,
limit
);
tally = tally.beside(family.times(*times));
let m = counts.clamp(1, greatest - least + 1);
let supports = die
.linear_sum(greatest - m + 1, greatest)
.min(m.saturating_mul(die.support(greatest)));
outcomes =
outcomes.saturating_add(supports.saturating_mul(*times));
}
for &(n, per) in &listed
{
let power = lowest == 0 && highest == 0 && die.folds(n);
let pairs = kept_pairs(
n,
(lowest, most_lowest),
(highest, most_highest)
) - power as u128;
if pairs == 0
{
continue
}
let dearest = match power
{
true => vec![(1, 0), (0, 1)],
false => vec![(lowest, highest)]
};
let (mut order, len) = dearest
.into_iter()
.filter(|&(l, h)| {
l <= most_lowest && h <= most_highest && l + h < n
})
.map(|(l, h)| order_sum(die, n, l, h, limit))
.reduce(|(a, la), (b, lb)| (a.max(b), la.max(lb)))
.expect("a pair keeps a die");
order.charge(len, len);
order.keep(len.saturating_mul(2), 0);
let setups = per
.saturating_mul(variants.min(pairs))
.saturating_mul(*times);
tally = tally.beside(order.times(setups));
outcomes = outcomes.saturating_add(len.saturating_mul(setups));
}
}
(tally, outcomes)
}
}
fn convolve(
tally: &mut Tally,
die: &Die,
(a, la): (u128, u128),
(b, lb): (u128, u128),
cap: u128
) -> u128
{
let dice = (a + b).min(cap);
let pairs = la.saturating_mul(lb);
let sums = die.span(dice).min(pairs);
tally.charge(pairs, sums.saturating_mul(2));
let len = sums.min(die.support(dice));
tally.keep(sums.saturating_mul(2), len);
len
}
fn convolution_power(
tally: &mut Tally,
die: &Die,
mut n: u128,
cap: u128
) -> u128
{
let mut power = None::<(u128, u128)>;
let mut square = (1u128, die.faces);
loop
{
if n & 1 == 1
{
power = Some(match power
{
None =>
{
tally.charge(square.1, square.1);
square
},
Some(power) =>
{
let len = convolve(tally, die, power, square, cap);
tally.free(power.1);
((power.0 + square.0).min(cap), len)
}
});
}
n >>= 1;
if n == 0
{
tally.free(square.1);
return power.expect("at least one die").1
}
let next = convolve(tally, die, square, square, cap);
tally.free(square.1);
square = ((square.0 * 2).min(cap), next);
}
}
fn any_power(tally: &mut Tally, die: &Die, least: u128, greatest: u128)
-> u128
{
let exact = |n: u128| {
let mut power = Tally::default();
let len = convolution_power(&mut power, die, n, n);
(power, len)
};
let (mut dearest, mut len) = exact(greatest);
for j in 0..u128::BITS - greatest.leading_zeros()
{
let bit = 1u128 << j;
let count = (greatest ^ bit) | (bit - 1);
if greatest & bit != 0 && count >= least
{
let (power, power_len) = exact(count);
dearest = dearest.max(power);
len = len.max(power_len);
}
}
tally.then(dearest);
len
}
fn power_sum(
die: &Die,
least: u128,
greatest: u128,
limit: u128
) -> (Tally, u128)
{
let mut tally = Tally::default();
tally.charge(die.faces, die.faces);
tally.charge(die.faces, die.faces);
let len = any_power(&mut tally, die, least, greatest);
tally.charge(len, len);
let support = clamp(&mut tally, len, limit);
(tally, support)
}
fn power_family(
die: &Die,
least: u128,
greatest: u128,
m: u128,
consecutive: bool,
limit: u128
) -> Tally
{
let m = m.clamp(1, greatest - least + 1);
let later = m - 1;
let consecutive = consecutive && m == greatest - least + 1;
let first = match consecutive
{
true => greatest - later,
false => greatest
};
let (mut family, support) = power_sum(die, least, first, limit);
family.charge(support, support);
family.keep(support.saturating_mul(2), 0);
if later == 0
{
return family
}
let top = die.support(greatest);
let one = die.support(1);
let fewest = match consecutive
{
true => later,
false => 1
};
let excess = greatest - least - fewest;
let delta = die
.alpha()
.saturating_mul(greatest - least)
.saturating_sub(fewest.saturating_mul(one - 1));
let previous = die
.linear_sum(greatest - later, greatest - 1)
.min(later.saturating_mul(die.support(greatest - 1)));
let current = die
.linear_sum(greatest - later + 1, greatest)
.min(later.saturating_mul(top));
let powers = match die.faces
{
1 => excess.saturating_mul(2),
_ =>
{
let widest = die.support(excess + 1);
widest.saturating_mul(widest) - one * one
}
};
let convolutions = one
.saturating_mul(previous)
.saturating_add(top.saturating_mul(delta));
let squares = later.saturating_mul(one).saturating_add(delta);
let each = later.saturating_mul(die.faces.saturating_mul(2));
family.charge(
each.saturating_add(current.saturating_mul(3))
.saturating_add(squares)
.saturating_add(powers)
.saturating_add(convolutions),
0
);
let kept = die.faces.saturating_add(top);
let mut step = Tally {
working: kept,
peak: kept,
..Tally::default()
};
step.charge(0, die.faces.saturating_mul(2));
let mut difference = Tally::default();
let len = any_power(&mut difference, die, 1, excess + 1);
step.then(difference);
let sums = die.span(greatest).min(top.saturating_mul(len));
step.charge(0, sums.saturating_mul(2));
step.keep(sums.saturating_mul(2), top);
step.free(len);
step.charge(0, top);
step.free(top.saturating_add(die.faces));
step.charge(0, top);
step.keep(top.saturating_mul(2), top);
step.charge(0, top);
step.keep(top.saturating_mul(2), 0);
family.working = family.working.max(kept);
family.peak = family.peak.max(step.peak);
family
}
fn order_sum(die: &Die, n: u128, l: u128, h: u128, limit: u128)
-> (Tally, u128)
{
let shape = Shape::new(n, l, h);
let mut tally = Tally::default();
tally.charge(die.faces, die.faces);
let base = tally.working;
let slots = n + 1;
tally.charge(slots, slots + 1);
let placements = shape.placements();
let d = die.faces;
let spread = |i: u128| -> u128 {
match &die.values
{
None => i - 1,
Some(values) =>
{
(values[i as usize - 1] as i64 - values[0] as i64) as u128
/ die.step
},
}
};
let sums = |i: u128| -> (u128, u128) {
match i
{
0 => (n + 1, 1),
i =>
{
let linear = shape.linear(spread(i));
match &die.values
{
None => linear,
Some(_) =>
{
let multisets = shape.multisets(i);
(linear.0.min(multisets.0), linear.1.min(multisets.1))
}
}
}
}
};
let held = |sums: u128| slots.saturating_add(sums);
let mut placed = (0u128, 0u128);
let place = |placed: &mut (u128, u128), moves: u128, held: u128| {
placed.0 = placed
.0
.saturating_add(slots)
.saturating_add(placements)
.saturating_add(moves);
let cells = moves.saturating_add(slots);
placed.1 = placed.1.max(held.saturating_add(cells));
};
match (&die.values, d)
{
(_, 1) => place(&mut placed, 1, slots + 1),
(None, d) =>
{
place(&mut placed, n + 1, slots + 1);
let (a, a1) = (shape.weighed(), shape.unweighed());
let b = placements;
if d > 2
{
let between = d - 2;
let triangle = between.saturating_mul(between - 1) / 2;
let moves = a
.saturating_mul(triangle)
.saturating_add(b.saturating_mul(between));
placed.0 = placed
.0
.saturating_add(
slots.saturating_add(b).saturating_mul(between)
)
.saturating_add(moves);
let last = a.saturating_mul(d - 3).saturating_add(b);
let before = held(sums(d - 2).1);
placed.1 = placed
.1
.max(before.saturating_add(last).saturating_add(slots));
}
let before = held(a1.saturating_mul(d - 2).saturating_add(n + 1));
place(&mut placed, sums(d - 1).1, before);
},
(Some(_), d) =>
{
let mut before = slots + 1;
for i in 0..d
{
let (weighed, unweighed) = sums(i);
let moves = match i == d - 1
{
true => unweighed,
false => weighed
};
place(&mut placed, moves, before);
before = held(sums(i + 1).1);
}
}
}
tally.charge(placed.0, 0);
tally.peak = tally.peak.max(base.saturating_add(placed.1));
let len = die.support(shape.kept).min(1u128 << 32);
let states = held(sums(d).1);
tally.keep(slots + 1, states);
tally.charge(len, len);
let support = len.min(limit);
tally.keep(states.saturating_add(len), support);
tally.free(d);
(tally, support)
}
fn kept_pairs(n: u128, (a, b): (u128, u128), (c, d): (u128, u128)) -> u128
{
let (b, d) = (b.min(n), d.min(n));
if a + c >= n
{
return 0
}
let top = b.min(n - 1 - c);
let split = n - d;
let full = (top + 1).min(split).saturating_sub(a) * (d - c + 1);
let start = a.max(split);
let partial = match start <= top
{
true => ((n - start - c) + (n - top - c)) * (top - start + 1) / 2,
false => 0
};
full + partial
}
#[derive(Debug, Clone, Copy)]
struct Shape
{
n: u128,
l: u128,
h: u128,
kept: u128
}
impl Shape
{
fn new(n: u128, l: u128, h: u128) -> Self
{
Self {
n,
l,
h,
kept: n - l - h
}
}
fn placements(&self) -> u128 { (self.n + 1) * (self.n + 2) / 2 }
fn weighed(&self) -> u128
{
let k = self.kept;
let big_n = self.n - self.l + 1;
let rising = big_n * (k * (k + 1) / 2) - k * (k + 1) * (2 * k + 1) / 6;
rising + k * (self.h * (self.h + 1) / 2)
}
fn unweighed(&self) -> u128
{
let k = self.kept;
k * (k + 1) / 2 + k * self.h
}
fn linear(&self, w: u128) -> (u128, u128)
{
(
self.weighed()
.saturating_mul(w)
.saturating_add(self.placements()),
self.unweighed()
.saturating_mul(w)
.saturating_add(self.n + 1)
)
}
fn multisets(&self, i: u128) -> (u128, u128)
{
let (n, l, h, k) = (self.n, self.l, self.h, self.kept);
let checked = || -> Option<(u128, u128)> {
let low = (l + 1) * (n + 1) - l * (l + 1) / 2;
let all = binomial(k + i, i);
let big_n = n - l + 1;
let rising = big_n
.checked_mul(all.checked_sub(1)?)?
.checked_sub(i.checked_mul(binomial(k + i, i + 1))?)?;
let top = binomial(k + i - 1, i - 1);
let high = top.checked_mul(h * (h + 1) / 2)?;
let weighed = low.checked_add(rising)?.checked_add(high)?;
let unweighed = (l + 1)
.checked_add(all - 1)?
.checked_add(top.checked_mul(h)?)?;
(all != u128::MAX && top != u128::MAX)
.then_some((weighed, unweighed))
};
checked().unwrap_or((u128::MAX, u128::MAX))
}
}
impl Record
{
fn outcomes(&self, tally: &mut Tally) -> (u128, u128)
{
tally.charge(self.setups.saturating_mul(2), 0);
let mut each = Tally::default();
let (outcomes, cells) = match &self.kind
{
Kind::Empty =>
{
each.charge(1, 1);
(1, 1)
},
Kind::Results(distinct) =>
{
each.charge(1, distinct + 1);
(1, distinct + 1)
},
Kind::Range { start, end } =>
{
let widest = width((start.bounds.min, end.bounds.max).into());
each.charge(widest, widest.saturating_mul(2));
(widest, 2)
},
Kind::Standard { count, faces, .. } => match faces.bounds.max
{
..=0 =>
{
each.charge(1, 2);
(1, 2)
},
greatest => multisets(
&mut each,
count,
&Die::standard(greatest as u128)
)
},
Kind::Custom { count, die } => multisets(&mut each, count, die)
};
each.charge(outcomes, 0);
tally.then(each.times(self.setups));
(outcomes.saturating_mul(self.setups), cells)
}
}
fn multisets(tally: &mut Tally, count: &Operand, die: &Die) -> (u128, u128)
{
let n = count.bounds.max.max(0) as u128;
if n == 0
{
tally.charge(1, 1);
return (1, 1)
}
let d = die.faces;
tally.charge(d, d);
let base = tally.working;
tally.charge(1, 1);
let cells = |i: u128| i.min(n) + 1;
let partials = |i: u128| binomial(n + i, i);
let between = binomial(n + d, d - 1).saturating_sub(1);
let complete = (d - 1)
.saturating_mul(binomial(n + d - 1, d))
.saturating_add(partials(d - 1));
tally.charge(between.saturating_add(complete), 0);
let mut peak = 0u128;
for i in [0, d.saturating_sub(2), d - 1]
{
let held = match i
{
0 => 1,
i => partials(i).saturating_mul(cells(i))
};
let grown = match i == d - 1
{
true => partials(d - 1),
false => binomial(n + i + 1, i + 1)
};
peak = peak.max(held.saturating_add(grown.saturating_mul(i + 2)));
}
tally.peak = tally.peak.max(base.saturating_add(peak));
let outcomes = partials(d - 1);
let outcome_cells = cells(d);
tally.keep(1, outcomes.saturating_mul(outcome_cells));
tally.free(d);
(outcomes, outcome_cells)
}
#[cfg(test)]
mod tests
{
use pretty_assertions::assert_eq;
use super::{
Die, LANES, Lane, Tally, any_power, convolution_power, kept_pairs,
observe, order_sum
};
use crate::{
Evaluator, Passes,
support::{compile_valid, optimize}
};
type Shape = (usize, Vec<Option<(usize, u128)>>);
fn lanes(source: &str) -> (usize, Vec<Shape>)
{
let function = optimize(compile_valid(source), Passes::all());
let evaluator = Evaluator::new(function);
let mut most = 0;
let mut shapes = Vec::new();
observe(&evaluator, [], |lane: &Lane| {
most = most.max(lane.lanes());
if let Some(fork) = &lane.fork
{
let children = fork
.children
.iter()
.map(|(_, child)| {
child.fork.as_ref().map(|fork| {
let values = fork
.children
.iter()
.map(|(values, _)| values)
.sum();
(fork.children.len(), values)
})
})
.collect();
shapes.push((fork.children.len(), children));
}
})
.unwrap();
(most, shapes)
}
#[test]
fn test_wide_and_narrow_forks()
{
let (most, shapes) = lanes("{x}@(1D1000) + [1:{x}]");
assert_eq!(most, LANES);
assert_eq!(shapes.first().map(|shape| shape.0), Some(LANES - 1));
let (most, shapes) = lanes("{x}@(1D20) + {x}D6");
assert_eq!(most, 21);
assert_eq!(shapes.first().map(|shape| shape.0), Some(20));
}
#[test]
fn test_fair_forks()
{
let (most, shapes) =
lanes("{x}@(1D300) + {y}@(1D300) + [{x}:{x} + {y}]");
assert_eq!(most, LANES);
let nested = shapes
.iter()
.find(|(_, children)| children.iter().all(Option::is_some))
.expect("the children fork");
assert_eq!(nested.0, 15);
assert!(
nested.1.iter().all(|child| child.unwrap().0 == 16),
"{nested:?}"
);
let (most, shapes) =
lanes("{x}@(1D40) + {y}@(1D{x}) + [{x}:{x} + {y}]");
assert!(most <= LANES, "{most}");
let (_, children) = shapes
.iter()
.find(|(_, children)| children.iter().any(Option::is_some))
.expect("the children fork");
let level = children
.iter()
.flatten()
.map(|(buckets, _)| *buckets)
.max()
.unwrap();
for (buckets, values) in children.iter().flatten()
{
assert!(
*buckets == level || *buckets as u128 == *values,
"{children:?}"
);
}
assert!(
children
.iter()
.flatten()
.any(|(buckets, _)| *buckets < level),
"{children:?}"
);
let (most, _) = lanes(
"{x}@(1D30) + {y}@(1D30) + {z}@(1D30) + [{x}:{y}] * [{y}:{z}] * \
[{z}:{x}]"
);
assert_eq!(most, 1 + 6 + 36 + 36 * 5);
}
#[test]
fn test_lanes_within_limit()
{
for source in [
"{x}@(1D300) + {y}@(1D300) + [{x}:{x} + {y}]",
"{x}@(1D300) + {y}@(1D2) + {z}@(1D300) + [{x}:{x} + {y} + {z}]",
"{x}@(1D30) + {y}@(1D30) + {z}@(1D30) + {x}D{y} + {y}D{z} + {z}D{x}",
"{a}@(1D300) + {b}@(1D300) + {c}@(1D300) + {d}@(1D300) + [{a}:{b}] \
* [{b}:{c}] * [{c}:{d}] * [{d}:{a}]"
]
{
let (most, _) = lanes(source);
assert!(most <= LANES, "{source}: {most}");
}
}
#[test]
fn test_any_power()
{
for die in [
Die::standard(1),
Die::standard(2),
Die::standard(6),
Die::custom(vec![1, 3, 7]),
Die::custom(vec![-5, 0, 5])
]
{
let power = |n: u128, cap: u128| {
let mut tally = Tally::default();
let len = convolution_power(&mut tally, &die, n, cap);
(tally, len)
};
for greatest in 1..=64u128
{
let all = u128::MAX >> greatest.leading_zeros();
let (old, old_len) = power(all, greatest);
for least in 1..=greatest
{
let (dearest, len) = (least..=greatest)
.map(|n| power(n, n))
.reduce(|(a, la), (b, lb)| (a.max(b), la.max(lb)))
.unwrap();
let mut tally = Tally::default();
let any = any_power(&mut tally, &die, least, greatest);
assert_eq!(
(tally, any),
(dearest, len),
"{die:?}: {least} to {greatest}"
);
assert!(
tally.max(old) == old && any <= old_len,
"{die:?}: {least} to {greatest}: {tally:?} > {old:?}"
);
}
}
}
}
#[test]
fn test_kept_pairs()
{
for n in 1..=8u128
{
for a in 0..=10
{
for b in a..=10
{
for c in 0..=10
{
for d in c..=10
{
let mut pairs = (a..=b)
.flat_map(|l| (c..=d).map(move |h| (l, h)))
.map(|(l, h)| (l.min(n), h.min(n)))
.filter(|&(l, h)| l + h < n)
.collect::<Vec<_>>();
pairs.sort();
pairs.dedup();
assert_eq!(
kept_pairs(n, (a, b), (c, d)),
pairs.len() as u128,
"{n}: [{a}, {b}] × [{c}, {d}]"
);
}
}
}
}
}
}
#[test]
fn test_order_sum_by_drops()
{
for die in [
Die::standard(1),
Die::standard(2),
Die::standard(6),
Die::custom(vec![1, 3, 7]),
Die::custom(vec![-5, 0, 5])
]
{
for n in 1..=16u128
{
for l in 0..n
{
for h in 0..n - l
{
let (fewer, fewer_len) =
order_sum(&die, n, l, h, u128::MAX);
for (l2, h2) in [(l + 1, h), (l, h + 1)]
{
if l2 + h2 >= n
{
continue
}
let (more, len) =
order_sum(&die, n, l2, h2, u128::MAX);
assert!(
fewer.max(more) == fewer && len <= fewer_len,
"{die:?}: {n} dice, ({l}, {h}) to ({l2}, \
{h2}): {more:?} > {fewer:?}"
);
}
}
}
}
}
}
}