pub(crate) mod cost;
pub(crate) mod meter;
mod mixture;
pub(crate) mod plan;
pub(crate) mod record;
use std::{
collections::HashMap,
error::Error,
fmt::{self, Display, Formatter},
mem
};
use cost::Cost;
use meter::{
Budget, Dimension, Exhausted, Halt, Meter, Progress, Usage, charge_of
};
use mixture::Mixture;
use plan::{Location, Plan, Step};
use record::{Roll, clamp, convolve, distinct_faces};
use crate::{
Add, AddressingMode, CanAllocate as _, CanVisitInstructions as _,
Distribution, Div, DropHighest, DropLowest, EvaluationError, Evaluator,
Exp, Instruction, InstructionVisitor, Max, Mod, Mul, Neg, ProgramCounter,
RegisterIndex, Return, RollCustomDice, RollRange, RollStandardDice,
RollingRecordIndex, Sub, SumRollingRecord, Weight, add, div, exp, max,
r#mod, mul, neg, sub
};
impl Evaluator
{
pub fn plan_distribution(
&self,
args: impl IntoIterator<Item = i32>
) -> Result<DistributionPlan<'_>, EvaluationError<'static>>
{
let args = args.into_iter().collect::<Vec<_>>();
let cost =
cost::estimate(self, args.iter().copied()).map_err(|e| match e
{
PropagationError::BadArity { expected, given } =>
{
EvaluationError::BadArity { expected, given }
},
PropagationError::Exhausted(_)
| PropagationError::Cancelled =>
{
unreachable!("estimation neither meters nor reports")
}
})?;
Ok(DistributionPlan {
evaluator: self,
args,
cost
})
}
}
#[cfg_attr(doc, aquamarine::aquamarine)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DistributionPlan<'eval>
{
evaluator: &'eval Evaluator,
args: Vec<i32>,
cost: Cost
}
impl DistributionPlan<'_>
{
#[inline]
pub fn estimate(&self) -> Cost { self.cost }
pub fn build(
&self,
budget: Budget,
progress: &dyn Progress
) -> Result<Distribution, BuildError>
{
propagate_worlds(
self.evaluator,
self.args.iter().copied(),
budget,
&self.cost,
progress
)
.map(|(distribution, _)| distribution)
.map_err(|e| match e
{
PropagationError::BadArity { .. } =>
{
unreachable!("the plan checked the arity")
},
PropagationError::Exhausted(Exhausted {
dimension,
requested,
remaining,
consumed
}) => BuildError::BudgetExhausted {
estimate: self.cost,
dimension,
requested,
remaining,
consumed
},
PropagationError::Cancelled => BuildError::Cancelled
})
}
}
#[cfg(test)]
pub(crate) fn propagate(
evaluator: &Evaluator,
args: impl IntoIterator<Item = i32>
) -> Result<Distribution, PropagationError>
{
let args = args.into_iter().collect::<Vec<_>>();
let estimate = cost::estimate(evaluator, args.iter().copied())?;
propagate_worlds(
evaluator,
args,
Budget::UNLIMITED,
&estimate,
&meter::Unobserved
)
.map(|(distribution, _)| distribution)
}
#[cfg_attr(doc, aquamarine::aquamarine)]
pub(crate) fn propagate_worlds(
evaluator: &Evaluator,
args: impl IntoIterator<Item = i32>,
budget: Budget,
estimate: &Cost,
progress: &dyn Progress
) -> Result<(Distribution, Usage), PropagationError>
{
let function = &evaluator.function;
debug_assert_eq!(function.validate(), Ok(()), "{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 world = World {
registers: vec![Register::Point(0); function.register_count],
records: vec![Record::default(); function.rolling_record_count],
scale: Weight::ONE,
result: None,
fixed: Vec::new()
};
for (i, arg) in args.into_iter().enumerate()
{
world.registers[i] = Register::Point(arg);
}
for (index, value) in &evaluator.environment
{
world.registers[arity + *index] = Register::Point(*value);
}
let mut meter = Meter::new(budget, world.cells(), estimate, progress)?;
let plan = Plan::new(&function.instructions);
let mut worlds = vec![world];
let mut pc = ProgramCounter::default();
for inst in &function.instructions
{
let touched = touched(inst);
for world in &mut worlds
{
let before = world.cells_at(&touched);
inst.visit(&mut Execution {
world,
meter: &mut meter
})?;
meter.settle(before, world.cells_at(&touched));
}
for step in plan.steps(pc)
{
let before = cells(&worlds);
worlds = match step
{
Step::Split(location) => split(worlds, *location, &mut meter)?,
Step::Merge {
split,
survivor,
dead
} => merge(worlds, *split, *survivor, dead, &mut meter)?
};
meter.settle(before, cells(&worlds));
meter.count_worlds(worlds.len());
}
debug_assert_eq!(
meter.held(),
cells(&worlds),
"the meter holds the cells of the worlds"
);
pc.allocate();
}
let world = worlds.pop().expect("one world remains");
debug_assert!(worlds.is_empty(), "every split merges by the end");
let distribution = world.finish(&mut meter)?;
Ok((distribution, meter.usage()))
}
fn touched(inst: &Instruction) -> Vec<Location>
{
let mut touched = inst
.sources()
.into_iter()
.filter_map(Location::read_by)
.collect::<Vec<_>>();
touched.push(Location::written_by(inst));
touched.sort_by_key(|location| match location
{
Location::Register(reg) => (0, reg.0),
Location::RollingRecord(rec) => (1, rec.0),
Location::Answer => (2, 0)
});
touched.dedup();
touched
}
fn cells(worlds: &[World]) -> u64 { worlds.iter().map(World::cells).sum() }
#[derive(Debug, Clone, PartialEq, Eq)]
struct World
{
registers: Vec<Register>,
records: Vec<Record>,
scale: Weight,
result: Option<Distribution>,
fixed: Vec<(Outcome, Weight)>
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Outcome
{
Value(i32),
Record(Mixture)
}
impl Outcome
{
fn cells(&self) -> u64
{
match self
{
Outcome::Value(_) => 1,
Outcome::Record(mixture) => mixture.cells()
}
}
}
impl World
{
fn cells(&self) -> u64
{
self.registers.len() as u64
+ self.registers.iter().map(Register::cells).sum::<u64>()
+ self
.records
.iter()
.map(|record| record.mixture.cells())
.sum::<u64>()
+ self.result.as_ref().map_or(0, |d| d.len() as u64)
+ self
.fixed
.iter()
.map(|(outcome, _)| outcome.cells())
.sum::<u64>()
}
fn cells_at(&self, locations: &[Location]) -> u64
{
locations
.iter()
.map(|location| match location
{
Location::Register(reg) => self.registers[reg.0].cells(),
Location::RollingRecord(rec) =>
{
self.records[rec.0].mixture.cells()
},
Location::Answer =>
{
self.result.as_ref().map_or(0, |d| d.len() as u64)
},
})
.sum()
}
fn finish(mut self, meter: &mut Meter) -> Result<Distribution, Halt>
{
for register in mem::take(&mut self.registers)
{
self.discard(register, meter)?;
}
for record in mem::take(&mut self.records)
{
self.retire(record, meter)?;
}
let result = self.result.expect("every function returns an answer");
if self.scale == Weight::ONE
{
return Ok(result)
}
let len = result.len() as u64;
meter.charge(len, len)?;
Ok(Distribution::from_weights(
result
.into_iter()
.map(|(outcome, weight)| (outcome, weight * &self.scale))
)
.expect("scaling preserves outcomes"))
}
}
fn split(
worlds: Vec<World>,
location: Location,
meter: &mut Meter
) -> Result<Vec<World>, Halt>
{
let mut split = Vec::new();
for world in worlds
{
let outcomes = world.outcomes(location, meter)?;
let cells = world.cells() as u128;
let cells = charge_of(outcomes.iter().fold(0, |sum, (outcome, _)| {
sum + cells + 2 * outcome.cells() as u128
}));
meter.charge(cells, cells)?;
for (outcome, weight) in outcomes
{
let mut world = world.clone();
world.fix(location, &outcome);
world.fixed.push((outcome, weight));
split.push(world);
}
}
Ok(split)
}
impl World
{
fn outcomes(
&self,
location: Location,
meter: &mut Meter
) -> Result<Vec<(Outcome, Weight)>, Halt>
{
match location
{
Location::Register(reg) => match &self.registers[reg.0]
{
Register::Point(value) =>
{
meter.charge(1, 1)?;
Ok(vec![(Outcome::Value(*value), Weight::ONE)])
},
Register::Random(distribution) =>
{
let len = distribution.len() as u64;
meter.charge(len, len)?;
Ok(distribution
.iter()
.map(|(value, weight)| {
(Outcome::Value(value), weight.clone())
})
.collect())
},
Register::Consumed =>
{
unreachable!("a split follows the write of its value")
}
},
Location::RollingRecord(rec) => Ok(self.records[rec.0]
.mixture
.outcomes(meter)?
.into_iter()
.map(|(mixture, weight)| (Outcome::Record(mixture), weight))
.collect()),
Location::Answer => unreachable!("no instruction reads the answer")
}
}
fn fix(&mut self, location: Location, outcome: &Outcome)
{
match (location, outcome)
{
(Location::Register(reg), Outcome::Value(value)) =>
{
self.registers[reg.0] = Register::Point(*value);
},
(Location::RollingRecord(rec), Outcome::Record(mixture)) =>
{
self.records[rec.0].mixture = mixture.clone();
},
_ => unreachable!("an outcome fits the location of its value")
}
}
}
fn merge(
worlds: Vec<World>,
split: usize,
survivor: Option<Location>,
dead: &[Location],
meter: &mut Meter
) -> Result<Vec<World>, Halt>
{
let mut groups = Vec::<Vec<(Weight, World)>>::new();
let mut positions = HashMap::<Vec<Outcome>, usize>::new();
for mut world in worlds
{
for &location in dead
{
world.bury(location, meter)?;
}
let key = world
.fixed
.iter()
.map(|(outcome, _)| outcome.cells())
.sum::<u64>();
meter.charge(world.fixed.len() as u64, key)?;
let (_, weight) = world.fixed.remove(split);
let key = world
.fixed
.iter()
.map(|(outcome, _)| outcome.clone())
.collect();
let position = *positions.entry(key).or_insert_with(|| {
groups.push(Vec::new());
groups.len() - 1
});
groups[position].push((weight, world));
}
groups
.into_iter()
.map(|group| mix(group, survivor, meter))
.collect()
}
fn mix(
group: Vec<(Weight, World)>,
survivor: Option<Location>,
meter: &mut Meter
) -> Result<World, Halt>
{
let survivors = group.len() as u64;
meter.charge(survivors, survivors)?;
let mut parts = Vec::with_capacity(group.len());
let mut worlds = Vec::with_capacity(group.len());
for (weight, mut world) in group
{
let distribution = world.take(survivor);
let scale = mem::replace(&mut world.scale, Weight::ONE);
parts.push((weight, distribution, scale));
worlds.push(world);
}
let outcomes = parts
.iter()
.map(|(_, distribution, _)| distribution.len() as u64)
.sum::<u64>();
meter.charge(outcomes, outcomes)?;
let denominator = parts.iter().fold(Weight::ONE, |lcm, (_, d, scale)| {
lcm.lcm(&(d.total() * scale))
});
let mixture = Distribution::from_weights(parts.iter().flat_map(
|(weight, distribution, _)| {
let factor = weight * denominator.exact_div(distribution.total());
distribution
.iter()
.map(move |(value, w)| (value, w * &factor))
}
))
.expect("a mixture has outcomes");
let mut world = worlds.swap_remove(0);
debug_assert!(
worlds.iter().all(|other| *other == world),
"the worlds of a merge differ only in the survivor and the dead"
);
world.put(survivor, mixture, meter)?;
Ok(world)
}
impl World
{
fn bury(
&mut self,
location: Location,
meter: &mut Meter
) -> Result<(), Halt>
{
match location
{
Location::Register(reg) =>
{
let old = mem::replace(
&mut self.registers[reg.0],
Register::Consumed
);
self.discard(old, meter)
},
Location::RollingRecord(rec) =>
{
let old = mem::replace(
&mut self.records[rec.0],
Record {
mixture: Mixture::default(),
summed: true
}
);
self.retire(old, meter)
},
Location::Answer => unreachable!("the answer lives to the end")
}
}
fn take(&mut self, survivor: Option<Location>) -> Distribution
{
match survivor
{
Some(Location::Register(reg)) =>
{
match mem::replace(
&mut self.registers[reg.0],
Register::Consumed
)
{
Register::Point(value) => Distribution::point(value),
Register::Random(distribution) => distribution,
Register::Consumed =>
{
unreachable!("no instruction has read a survivor")
}
}
},
Some(Location::Answer) =>
{
self.result.take().expect("a Return wrote the survivor")
},
Some(Location::RollingRecord(_)) =>
{
unreachable!("a survivor is never a rolling record")
},
None => Distribution::point(0)
}
}
fn put(
&mut self,
survivor: Option<Location>,
mixture: Distribution,
meter: &mut Meter
) -> Result<(), Halt>
{
match survivor
{
Some(Location::Register(reg)) => self.write(reg, mixture, meter)?,
Some(Location::Answer) => self.result = Some(mixture),
Some(Location::RollingRecord(_)) =>
{
unreachable!("a survivor is never a rolling record")
},
None =>
{
meter.charge(1, 0)?;
self.scale *= mixture.total();
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Register
{
Point(i32),
Random(Distribution),
Consumed
}
impl Register
{
fn cells(&self) -> u64
{
match self
{
Register::Random(distribution) => distribution.len() as u64,
Register::Point(_) | Register::Consumed => 0
}
}
}
impl World
{
fn read(
&mut self,
op: AddressingMode,
meter: &mut Meter
) -> Result<Distribution, Halt>
{
match op
{
AddressingMode::Immediate(value) =>
{
meter.charge(1, 1)?;
Ok(Distribution::point(value.0))
},
AddressingMode::Register(reg) => match &self.registers[reg.0]
{
&Register::Point(value) =>
{
meter.charge(1, 1)?;
Ok(Distribution::point(value))
},
Register::Random(_) => match mem::replace(
&mut self.registers[reg.0],
Register::Consumed
)
{
Register::Random(distribution) => Ok(distribution),
_ => unreachable!("the register is random")
},
Register::Consumed =>
{
unreachable!("the plan splits every value read twice")
}
},
AddressingMode::RollingRecord(_) => unreachable!()
}
}
fn read_pair(
&mut self,
op1: AddressingMode,
op2: AddressingMode,
meter: &mut Meter
) -> Result<Pairs, Halt>
{
match op1
{
AddressingMode::Register(_) if op1 == op2 =>
{
let xs = self.read(op1, meter)?;
let len = xs.len() as u64;
meter.charge(len, len)?;
Ok(xs.into_iter().map(|(x, w)| ((x, x), w)).collect())
},
_ =>
{
let xs = self.read(op1, meter)?;
let ys = self.read(op2, meter)?;
let pairs = charge_of(xs.len() as u128 * ys.len() as u128);
meter.charge(pairs, pairs)?;
Ok(xs
.iter()
.flat_map(|(x, wx)| {
ys.iter().map(move |(y, wy)| ((x, y), wx * wy))
})
.collect())
}
}
}
fn write(
&mut self,
reg: RegisterIndex,
distribution: Distribution,
meter: &mut Meter
) -> Result<(), Halt>
{
let register = match distribution.len()
{
1 =>
{
meter.charge(1, 0)?;
self.scale *= distribution.total();
Register::Point(distribution.min())
},
_ => Register::Random(distribution)
};
let old = mem::replace(&mut self.registers[reg.0], register);
self.discard(old, meter)
}
fn discard(
&mut self,
register: Register,
meter: &mut Meter
) -> Result<(), Halt>
{
if let Register::Random(distribution) = register
{
meter.charge(1, 0)?;
self.scale *= distribution.total();
}
Ok(())
}
}
type Pairs = Vec<((i32, i32), Weight)>;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Record
{
mixture: Mixture,
summed: bool
}
impl World
{
fn roll(
&mut self,
dest: RollingRecordIndex,
rolls: impl IntoIterator<Item = (Roll, Weight)>,
meter: &mut Meter
) -> Result<(), Halt>
{
let old = mem::replace(
&mut self.records[dest.0],
Record {
mixture: Mixture::rolls(rolls),
summed: false
}
);
self.retire(old, meter)
}
fn retire(&mut self, record: Record, meter: &mut Meter)
-> Result<(), Halt>
{
if !record.summed
{
let total = record.mixture.total(meter)?;
meter.charge(1, 0)?;
self.scale *= total;
}
Ok(())
}
}
fn map(
xs: Distribution,
op: impl Fn(i32) -> i32,
meter: &mut Meter
) -> Result<Distribution, Halt>
{
let len = xs.len() as u64;
meter.charge(len, len)?;
Ok(
Distribution::from_weights(xs.into_iter().map(|(x, w)| (op(x), w)))
.expect("a mapping of a distribution has outcomes")
)
}
fn pairwise(
xs: &Distribution,
ys: &Distribution,
op: fn(i32, i32) -> i32,
meter: &mut Meter
) -> Result<Distribution, Halt>
{
let pairs = charge_of(xs.len() as u128 * ys.len() as u128);
meter.charge(pairs, pairs)?;
Ok(Distribution::from_weights(
xs.iter().flat_map(|(x, wx)| {
ys.iter().map(move |(y, wy)| (op(x, y), wx * wy))
})
)
.expect("a product of distributions has outcomes"))
}
fn saturating_sum(
xs: Distribution,
ys: Distribution,
negate: bool,
meter: &mut Meter
) -> Result<Distribution, Halt>
{
let wide = (xs.len() + ys.len()) as u64;
meter.charge(wide, wide)?;
let xs = xs
.into_iter()
.map(|(x, w)| (x as i64, w))
.collect::<Vec<_>>();
let ys = match negate
{
false => ys
.into_iter()
.map(|(y, w)| (y as i64, w))
.collect::<Vec<_>>(),
true => ys
.into_iter()
.rev()
.map(|(y, w)| (-(y as i64), w))
.collect()
};
let sum = convolve(&xs, &ys, meter)?;
meter.free(wide);
clamp(sum, meter)
}
struct Execution<'a, 'b>
{
world: &'a mut World,
meter: &'a mut Meter<'b>
}
impl Execution<'_, '_>
{
fn binary(
&mut self,
dest: RegisterIndex,
op1: AddressingMode,
op2: AddressingMode,
op: fn(i32, i32) -> i32,
combine: impl FnOnce(
Distribution,
Distribution,
&mut Meter
) -> Result<Distribution, Halt>
) -> Result<(), Halt>
{
let Self { world, meter } = self;
let result = match op1
{
AddressingMode::Register(_) if op1 == op2 =>
{
map(world.read(op1, meter)?, |x| op(x, x), meter)?
},
_ =>
{
let xs = world.read(op1, meter)?;
let ys = world.read(op2, meter)?;
combine(xs, ys, meter)?
}
};
world.write(dest, result, meter)
}
}
impl InstructionVisitor<Halt> for Execution<'_, '_>
{
fn visit_roll_range(&mut self, inst: &RollRange) -> Result<(), Halt>
{
let Self { world, meter } = self;
let ends = world.read_pair(inst.start, inst.end, meter)?;
let setups = ends.len() as u64;
meter.charge(setups, setups)?;
world.roll(
inst.dest,
ends.into_iter()
.map(|((start, end), w)| (Roll::Range { start, end }, w)),
meter
)
}
fn visit_roll_standard_dice(
&mut self,
inst: &RollStandardDice
) -> Result<(), Halt>
{
let Self { world, meter } = self;
let dice = world.read_pair(inst.count, inst.faces, meter)?;
let steps = dice.iter().fold(0u128, |steps, ((count, _), _)| {
steps + 1 + (*count).max(0) as u128
});
meter.charge(charge_of(steps), dice.len() as u64)?;
world.roll(
inst.dest,
dice.into_iter().map(|((count, faces), w)| {
(Roll::Standard { count, faces }, w)
}),
meter
)
}
fn visit_roll_custom_dice(
&mut self,
inst: &RollCustomDice
) -> Result<(), Halt>
{
let Self { world, meter } = self;
let counts = world.read(inst.count, meter)?;
let faces = inst.faces.len() as u128;
let (steps, cells) =
counts
.iter()
.fold((faces, faces), |(steps, cells), (count, _)| {
(steps + 1 + count.max(0) as u128, cells + 1 + faces)
});
meter.charge(charge_of(steps), charge_of(cells))?;
let faces = distinct_faces(&inst.faces);
world.roll(
inst.dest,
counts.into_iter().map(|(count, w)| {
let faces = faces.clone();
(Roll::Custom { count, faces }, w)
}),
meter
)
}
fn visit_drop_lowest(&mut self, inst: &DropLowest) -> Result<(), Halt>
{
let Self { world, meter } = self;
let counts = world.read(inst.count, meter)?;
let record = &mut world.records[inst.dest.0];
record.mixture.drop_lowest(&counts, meter)?;
record.summed = false;
Ok(())
}
fn visit_drop_highest(&mut self, inst: &DropHighest) -> Result<(), Halt>
{
let Self { world, meter } = self;
let counts = world.read(inst.count, meter)?;
let record = &mut world.records[inst.dest.0];
record.mixture.drop_highest(&counts, meter)?;
record.summed = false;
Ok(())
}
fn visit_sum_rolling_record(
&mut self,
inst: &SumRollingRecord
) -> Result<(), Halt>
{
let Self { world, meter } = self;
let record = &mut world.records[inst.src.0];
record.summed = true;
let sum = record.mixture.sum(meter)?;
world.write(inst.dest, sum, meter)
}
fn visit_add(&mut self, inst: &Add) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, add, |xs, ys, meter| {
saturating_sum(xs, ys, false, meter)
})
}
fn visit_sub(&mut self, inst: &Sub) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, sub, |xs, ys, meter| {
saturating_sum(xs, ys, true, meter)
})
}
fn visit_mul(&mut self, inst: &Mul) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, mul, |xs, ys, meter| {
pairwise(&xs, &ys, mul, meter)
})
}
fn visit_div(&mut self, inst: &Div) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, div, |xs, ys, meter| {
pairwise(&xs, &ys, div, meter)
})
}
fn visit_mod(&mut self, inst: &Mod) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, r#mod, |xs, ys, meter| {
pairwise(&xs, &ys, r#mod, meter)
})
}
fn visit_exp(&mut self, inst: &Exp) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, exp, |xs, ys, meter| {
pairwise(&xs, &ys, exp, meter)
})
}
fn visit_max(&mut self, inst: &Max) -> Result<(), Halt>
{
self.binary(inst.dest, inst.op1, inst.op2, max, |xs, ys, meter| {
pairwise(&xs, &ys, max, meter)
})
}
fn visit_neg(&mut self, inst: &Neg) -> Result<(), Halt>
{
let Self { world, meter } = self;
let xs = world.read(inst.op, meter)?;
let result = map(xs, neg, meter)?;
world.write(inst.dest, result, meter)
}
fn visit_return(&mut self, inst: &Return) -> Result<(), Halt>
{
let Self { world, meter } = self;
world.result = Some(world.read(inst.src, meter)?);
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PropagationError
{
BadArity
{
expected: usize,
given: usize
},
Exhausted(Exhausted),
Cancelled
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BuildError
{
BudgetExhausted
{
estimate: Cost,
dimension: Dimension,
requested: u64,
remaining: u64,
consumed: u64
},
Cancelled
}
impl Display for BuildError
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result
{
match self
{
BuildError::BudgetExhausted {
estimate,
dimension,
requested,
remaining,
consumed
} => write!(
f,
"distribution budget of {dimension} exhausted: {requested} \
requested, {remaining} remaining, {consumed} consumed, of an \
estimated {} steps and {} cells",
estimate.steps, estimate.cells
),
BuildError::Cancelled => write!(f, "distribution build cancelled")
}
}
}
impl Error for BuildError {}
impl From<Halt> for PropagationError
{
fn from(halt: Halt) -> Self
{
match halt
{
Halt::Exhausted(exhausted) => Self::Exhausted(exhausted),
Halt::Cancelled => Self::Cancelled
}
}
}