use anyhow::{Context, Result, anyhow};
use malachite::{
Natural, base::random::Seed, natural::random::random_naturals_less_than, rational::Rational,
};
use rand::{Rng, RngCore};
use crate::{
ebi_number::{ChooseRandomly, Zero},
exact::{MaybeExact, is_exact_globally},
fraction::{
fraction_enum::FractionEnum, fraction_exact::FractionExact, fraction_f64::FractionF64,
},
};
#[cfg(any(
all(
not(feature = "exactarithmetic"),
not(feature = "approximatearithmetic")
),
all(feature = "exactarithmetic", feature = "approximatearithmetic")
))]
pub type FractionRandomCache = FractionRandomCacheEnum;
#[cfg(all(not(feature = "exactarithmetic"), feature = "approximatearithmetic"))]
pub type FractionRandomCache = FractionRandomCacheF64;
#[cfg(all(feature = "exactarithmetic", not(feature = "approximatearithmetic")))]
pub type FractionRandomCache = FractionRandomCacheExact;
pub enum FractionRandomCacheEnum {
Exact(Vec<Rational>, Natural),
Approx(Vec<f64>),
}
impl ChooseRandomly for FractionEnum {
type Cache = FractionRandomCacheEnum;
fn choose_randomly(fractions: &Vec<FractionEnum>) -> Result<usize> {
if fractions.is_empty() {
return Err(anyhow!("cannot take an element of an empty list"));
}
let mut probabilities: Vec<FractionEnum> = fractions.iter().cloned().collect();
let sum = probabilities
.iter()
.fold(FractionEnum::zero(), |x, y| &x + y);
if sum.is_zero() {
return Err(anyhow!("sum of fractions is zero"));
}
if sum == FractionEnum::CannotCombineExactAndApprox {
return Err(anyhow!("cannot combine exact and approximate arithmetic"));
}
probabilities.retain_mut(|v| {
*v /= ∑
true
});
let mut rng = rand::rng();
let rand_val = if sum.is_exact() {
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
let seed = Seed::from_bytes(buf);
let max_denom = probabilities
.iter()
.map(|f| match f {
FractionEnum::Exact(e) => e.to_denominator(),
_ => unreachable!(),
})
.max()
.unwrap();
let rand_val = random_naturals_less_than(seed, max_denom.clone())
.next()
.unwrap();
FractionEnum::Exact(Rational::from(rand_val) / Rational::from(max_denom.clone()))
} else {
FractionEnum::Approx(rng.random_range(0.0..=1.0))
};
let mut cum_prob = FractionEnum::zero();
for (index, value) in probabilities.iter().enumerate() {
cum_prob += value;
if rand_val < cum_prob {
return Ok(index);
}
}
Ok(probabilities.len() - 1)
}
fn choose_randomly_create_cache<'a>(
mut fractions: impl Iterator<Item = &'a Self>,
) -> Result<FractionRandomCacheEnum>
where
Self: Sized,
Self: 'a,
{
if is_exact_globally() {
if let Some(first) = fractions.next() {
let mut cumulative_probabilities = vec![
first
.exact_ref()
.with_context(|| "cannot combine exact and approximate arithmetic")?
.clone(),
];
let mut highest_denom = first.exact_ref()?.to_denominator();
while let Some(fraction) = fractions.next() {
highest_denom = highest_denom.max(fraction.exact_ref()?.to_denominator());
let mut x = fraction
.exact_ref()
.with_context(|| "cannot combine exact and approximate arithmetic")?
.clone();
x += cumulative_probabilities.last().unwrap();
cumulative_probabilities.push(x);
}
let highest_denom = highest_denom.clone();
Ok(FractionRandomCacheEnum::Exact(
cumulative_probabilities,
highest_denom,
))
} else {
Err(anyhow!("cannot take an element of an empty list"))
}
} else {
if let Some(first) = fractions.next() {
let mut cumulative_probabilities = vec![
*first
.approx_ref()
.with_context(|| "cannot combine exact and approximate arithmetic")?,
];
while let Some(fraction) = fractions.next() {
cumulative_probabilities.push(
fraction
.approx_ref()
.with_context(|| "cannot combine exact and approximate arithmetic")?
+ cumulative_probabilities.last().unwrap(),
);
}
Ok(FractionRandomCacheEnum::Approx(cumulative_probabilities))
} else {
Err(anyhow!("cannot take an element of an empty list"))
}
}
}
fn choose_randomly_cached(cache: &FractionRandomCacheEnum) -> usize
where
Self: Sized,
{
match cache {
FractionRandomCacheEnum::Exact(cumulative_probabilities, highest_denom) => {
let mut rng = rand::rng();
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
let seed = Seed::from_bytes(buf);
let rand_val = {
let rand_val = random_naturals_less_than(seed, highest_denom.clone())
.next()
.unwrap();
Rational::from(rand_val) / Rational::from(highest_denom.clone())
};
match cumulative_probabilities.binary_search(&rand_val) {
Ok(index) | Err(index) => index,
}
}
FractionRandomCacheEnum::Approx(cumulative_probabilities) => {
let mut rng = rand::rng();
let rand_val = rng.random_range(0.0..=*cumulative_probabilities.last().unwrap());
match cumulative_probabilities.binary_search_by(|probe| probe.total_cmp(&rand_val))
{
Ok(index) | Err(index) => index,
}
}
}
}
}
pub struct FractionRandomCacheExact {
cumulative_probabilities: Vec<FractionExact>,
highest_denom: Natural,
}
impl ChooseRandomly for FractionExact {
type Cache = FractionRandomCacheExact;
fn choose_randomly(fractions: &Vec<FractionExact>) -> Result<usize> {
if fractions.is_empty() {
return Err(anyhow!("cannot take an element of an empty list"));
}
let mut probabilities: Vec<FractionExact> = fractions.iter().cloned().collect();
let sum = probabilities
.iter()
.fold(FractionExact::zero(), |x, y| &x + y);
if sum.is_zero() {
return Err(anyhow!("sum of fractions is zero"));
}
probabilities.retain_mut(|v| {
*v /= ∑
true
});
let mut rng = rand::rng();
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
let seed = Seed::from_bytes(buf);
let rand_val = {
let max_denom = probabilities
.iter()
.map(|f| match f {
FractionExact(e) => e.to_denominator(),
})
.max()
.unwrap();
let rand_val = random_naturals_less_than(seed, max_denom.clone())
.next()
.unwrap();
FractionExact(Rational::from(rand_val) / Rational::from(max_denom.clone()))
};
let mut cum_prob = FractionExact::zero();
for (index, value) in probabilities.iter().enumerate() {
cum_prob += value;
if rand_val < cum_prob {
return Ok(index);
}
}
Ok(probabilities.len() - 1)
}
fn choose_randomly_create_cache<'a>(
mut fractions: impl Iterator<Item = &'a Self>,
) -> Result<FractionRandomCacheExact>
where
Self: Sized,
Self: 'a,
{
if let Some(first) = fractions.next() {
let mut cumulative_probabilities = vec![first.clone()];
let mut highest_denom = first.0.to_denominator();
while let Some(fraction) = fractions.next() {
highest_denom = highest_denom.max(fraction.0.to_denominator());
cumulative_probabilities.push(fraction + cumulative_probabilities.last().unwrap());
}
let highest_denom = highest_denom.clone();
Ok(FractionRandomCacheExact {
cumulative_probabilities,
highest_denom,
})
} else {
Err(anyhow!("cannot take an element of an empty list"))
}
}
fn choose_randomly_cached(cache: &FractionRandomCacheExact) -> usize
where
Self: Sized,
{
let mut rng = rand::rng();
let mut buf = [0u8; 32];
rng.fill_bytes(&mut buf);
let seed = Seed::from_bytes(buf);
let rand_val = {
let rand_val = random_naturals_less_than(seed, cache.highest_denom.clone())
.next()
.unwrap();
FractionExact(Rational::from(rand_val) / Rational::from(cache.highest_denom.clone()))
};
match cache.cumulative_probabilities.binary_search(&rand_val) {
Ok(index) | Err(index) => index,
}
}
}
pub struct FractionRandomCacheF64 {
cumulative_probabilities: Vec<FractionF64>,
}
impl ChooseRandomly for FractionF64 {
type Cache = FractionRandomCacheF64;
fn choose_randomly(fractions: &Vec<FractionF64>) -> Result<usize> {
if fractions.is_empty() {
return Err(anyhow!("cannot take an element of an empty list"));
}
let mut probabilities: Vec<FractionF64> = fractions.iter().cloned().collect();
let sum = probabilities
.iter()
.fold(FractionF64::zero(), |x, y| &x + y);
probabilities.retain_mut(|v| {
*v /= ∑
true
});
let mut rng = rand::rng();
let rand_val = FractionF64(rng.random_range(0.0..=1.0));
let mut cum_prob = FractionF64::zero();
for (index, value) in probabilities.iter().enumerate() {
cum_prob += value;
if rand_val < cum_prob {
return Ok(index);
}
}
Ok(probabilities.len() - 1)
}
fn choose_randomly_create_cache<'a>(
mut fractions: impl Iterator<Item = &'a Self>,
) -> Result<FractionRandomCacheF64>
where
Self: Sized,
Self: 'a,
{
if let Some(first) = fractions.next() {
let mut cumulative_probabilities = vec![*first];
while let Some(fraction) = fractions.next() {
cumulative_probabilities.push(fraction + cumulative_probabilities.last().unwrap());
}
Ok(FractionRandomCacheF64 {
cumulative_probabilities,
})
} else {
Err(anyhow!("cannot take an element of an empty list"))
}
}
fn choose_randomly_cached(cache: &FractionRandomCacheF64) -> usize
where
Self: Sized,
{
let mut rng = rand::rng();
let rand_val = FractionF64::from(
rng.random_range(
0.0..=*cache
.cumulative_probabilities
.last()
.unwrap()
.approx_ref()
.unwrap(),
),
);
match cache.cumulative_probabilities.binary_search(&rand_val) {
Ok(index) | Err(index) => index,
}
}
}