use crate::native::floats::sign_aware_lte;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IntegerChoice {
pub min_value: i128,
pub max_value: i128,
pub shrink_towards: i128,
}
impl IntegerChoice {
pub(crate) fn clamped_shrink_towards(&self) -> i128 {
self.shrink_towards.clamp(self.min_value, self.max_value)
}
pub fn simplest(&self) -> i128 {
self.clamped_shrink_towards()
}
pub fn unit(&self) -> i128 {
let s = self.simplest();
if self.validate(s + 1) {
s + 1
} else if self.validate(s - 1) {
s - 1
} else {
s
}
}
pub fn validate(&self, value: i128) -> bool {
self.min_value <= value && value <= self.max_value
}
pub fn sort_key(&self, value: i128) -> (u128, bool) {
let target = self.clamped_shrink_towards();
let distance = value.wrapping_sub(target).unsigned_abs();
(distance, value < target)
}
pub fn max_index(&self) -> crate::native::bignum::BigUint {
use crate::native::bignum::BigUint;
let diff = (self.max_value as u128).wrapping_sub(self.min_value as u128);
BigUint::from(diff)
}
pub fn to_index(&self, value: i128) -> crate::native::bignum::BigUint {
use crate::native::bignum::{BigUint, Zero};
let s = self.simplest();
if value == s {
return BigUint::zero();
}
let above = BigUint::from((self.max_value as u128).wrapping_sub(s as u128));
let below = BigUint::from((s as u128).wrapping_sub(self.min_value as u128));
let d_abs_u = if value > s {
(value as u128).wrapping_sub(s as u128)
} else {
(s as u128).wrapping_sub(value as u128)
};
let d_abs = BigUint::from(d_abs_u);
let d_minus_one = &d_abs - BigUint::from(1u32);
let mut count = std::cmp::min(&d_minus_one, &above).clone()
+ std::cmp::min(&d_minus_one, &below).clone();
if value > s {
return count + BigUint::from(1u32);
}
if d_abs <= above {
count += BigUint::from(1u32);
}
count + BigUint::from(1u32)
}
#[allow(clippy::wrong_self_convention)]
pub fn from_index(&self, index: crate::native::bignum::BigUint) -> Option<i128> {
use crate::native::bignum::{BigUint, Zero};
let s = self.simplest();
if index.is_zero() {
return Some(s);
}
let above_u = (self.max_value as u128).wrapping_sub(s as u128);
let below_u = (s as u128).wrapping_sub(self.min_value as u128);
let above = BigUint::from(above_u);
let below = BigUint::from(below_u);
let mut lo = BigUint::from(1u32);
let mut hi = &above + &below;
let two = BigUint::from(2u32);
while lo < hi {
let mid = (&lo + &hi) / &two;
let total = std::cmp::min(&mid, &above).clone() + std::cmp::min(&mid, &below).clone();
if total >= index {
hi = mid;
} else {
lo = mid + BigUint::from(1u32);
}
}
let d = lo;
let total_at_d = std::cmp::min(&d, &above).clone() + std::cmp::min(&d, &below).clone();
if total_at_d < index {
return None;
}
let d_minus_one = &d - BigUint::from(1u32);
let before = std::cmp::min(&d_minus_one, &above).clone()
+ std::cmp::min(&d_minus_one, &below).clone();
let pos_in_d = &index - before;
let d_u: u128 = (&d)
.try_into()
.expect("d fits in u128 (range is <= u128::MAX)");
if pos_in_d == BigUint::from(1u32) && d <= above {
return Some((s as u128).wrapping_add(d_u) as i128);
}
debug_assert!(d <= below);
Some((s as u128).wrapping_sub(d_u) as i128)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BooleanChoice;
impl BooleanChoice {
pub fn simplest(&self) -> bool {
false
}
pub fn unit(&self) -> bool {
true
}
pub fn max_index(&self) -> crate::native::bignum::BigUint {
crate::native::bignum::BigUint::from(1u32)
}
pub fn to_index(&self, value: bool) -> crate::native::bignum::BigUint {
crate::native::bignum::BigUint::from(u32::from(value))
}
#[allow(clippy::wrong_self_convention)]
pub fn from_index(&self, index: crate::native::bignum::BigUint) -> Option<bool> {
use crate::native::bignum::BigUint;
if index == BigUint::from(0u32) {
Some(false)
} else if index == BigUint::from(1u32) {
Some(true)
} else {
None
}
}
}
#[derive(Clone, Debug)]
pub struct FloatChoice {
pub min_value: f64,
pub max_value: f64,
pub allow_nan: bool,
pub allow_infinity: bool,
}
impl PartialEq for FloatChoice {
fn eq(&self, other: &Self) -> bool {
self.min_value.to_bits() == other.min_value.to_bits()
&& self.max_value.to_bits() == other.max_value.to_bits()
&& self.allow_nan == other.allow_nan
&& self.allow_infinity == other.allow_infinity
}
}
impl Eq for FloatChoice {}
impl FloatChoice {
pub fn simplest(&self) -> f64 {
use super::float_index::{float_to_index, index_to_float};
if self.validate(0.0) {
return 0.0;
}
let mut best: Option<f64> = None;
let mut best_key: (u64, bool) = (u64::MAX, true);
macro_rules! try_candidate {
($v:expr) => {{
let v: f64 = $v;
if !v.is_nan() && self.validate(v) {
let is_neg = v.is_sign_negative();
let mag = if is_neg { -v } else { v };
let key = (float_to_index(mag), is_neg);
if key < best_key {
best = Some(v);
best_key = key;
}
}
}};
}
if self.min_value.is_finite() {
try_candidate!(self.min_value);
}
if self.max_value.is_finite() {
try_candidate!(self.max_value);
}
if self.max_value >= 0.0 {
let lo_int = self.min_value.max(0.0).ceil() as i64;
try_candidate!(lo_int as f64);
}
if self.min_value <= 0.0 {
let hi_int = self.max_value.min(0.0).floor() as i64;
try_candidate!(hi_int as f64);
}
for exp_enc in 0u64..64 {
let base_idx = (1u64 << 63) | (exp_enc << 52);
if (base_idx, false) >= best_key {
break;
}
for mantissa_enc in 0u64..8 {
let idx = base_idx | mantissa_enc;
if (idx, false) >= best_key {
break;
}
let v = index_to_float(idx);
try_candidate!(v);
try_candidate!(-v);
}
}
if let Some(v) = best {
return v;
}
if self.allow_infinity && self.validate(f64::INFINITY) {
return f64::INFINITY;
}
if self.allow_infinity && self.validate(f64::NEG_INFINITY) {
return f64::NEG_INFINITY;
}
if self.allow_nan {
return f64::NAN;
}
panic!("FloatChoice::simplest: no valid float for this choice")
}
pub fn unit(&self) -> f64 {
use super::float_index::{float_to_index, index_to_float};
let s = self.simplest();
if s.is_nan() {
return s;
}
let base = float_to_index(s.abs());
let is_neg = s.is_sign_negative();
for offset in 1u64..4 {
let v_mag = index_to_float(base + offset);
let v = if is_neg { -v_mag } else { v_mag };
if !v.is_nan() && self.validate(v) {
return v;
}
}
s
}
pub fn validate(&self, v: f64) -> bool {
if v.is_nan() {
return self.allow_nan;
}
if v.is_infinite() {
if !self.allow_infinity {
return false;
}
if v == f64::NEG_INFINITY && self.min_value > f64::NEG_INFINITY {
return false;
}
if v == f64::INFINITY && self.max_value < f64::INFINITY {
return false;
}
return true;
}
sign_aware_lte(self.min_value, v) && sign_aware_lte(v, self.max_value)
}
pub fn sort_key(&self, v: f64) -> (u64, bool) {
use super::float_index::float_to_index;
if v.is_nan() {
return (u64::MAX, false);
}
let is_neg = v.is_sign_negative();
let mag = if is_neg { -v } else { v };
(float_to_index(mag), is_neg)
}
pub fn max_index(&self) -> crate::native::bignum::BigUint {
use crate::native::bignum::BigUint;
max_finite_global_rank() + BigUint::from(2u32) + BigUint::from(1u64 << 53)
}
pub fn to_index(&self, value: f64) -> crate::native::bignum::BigUint {
float_global_rank(value) - float_global_rank(self.simplest())
}
#[allow(clippy::wrong_self_convention)]
pub fn from_index(&self, index: crate::native::bignum::BigUint) -> Option<f64> {
let raw = float_global_rank(self.simplest()) + index;
let value = float_from_global_rank(raw)?;
if self.validate(value) {
Some(value)
} else {
None
}
}
}
fn float_global_rank(v: f64) -> crate::native::bignum::BigUint {
use super::float_index::float_to_index;
use crate::native::bignum::BigUint;
if v.is_nan() {
let bits = v.to_bits();
let nan_offset = bits & ((1u64 << 52) - 1);
let sign = bits >> 63;
return max_finite_global_rank()
+ BigUint::from(3u32)
+ BigUint::from(nan_offset) * BigUint::from(2u32)
+ BigUint::from(sign);
}
if v.is_infinite() {
return if v > 0.0 {
max_finite_global_rank() + BigUint::from(1u32)
} else {
max_finite_global_rank() + BigUint::from(2u32)
};
}
let is_neg = v.is_sign_negative();
let mag = if is_neg { -v } else { v };
let mag_idx = float_to_index(mag);
BigUint::from(mag_idx) * BigUint::from(2u32) + BigUint::from(u32::from(is_neg))
}
fn float_from_global_rank(rank: crate::native::bignum::BigUint) -> Option<f64> {
use super::float_index::index_to_float;
use crate::native::bignum::BigUint;
let max_finite = max_finite_global_rank();
if rank > max_finite {
let offset = &rank - &max_finite;
if offset == BigUint::from(1u32) {
return Some(f64::INFINITY);
}
if offset == BigUint::from(2u32) {
return Some(f64::NEG_INFINITY);
}
let nan_rel = offset - BigUint::from(3u32);
let sign: u64 = (&nan_rel % BigUint::from(2u32))
.try_into()
.expect("mod 2 fits in u64");
let mantissa_base: u64 = (nan_rel / BigUint::from(2u32)).try_into().ok()?;
let mantissa = mantissa_base | (1u64 << 51);
let bits = (sign << 63) | (0x7FFu64 << 52) | mantissa;
let v = f64::from_bits(bits);
return if v.is_nan() { Some(v) } else { None };
}
let is_neg_u: u64 = (&rank % BigUint::from(2u32))
.try_into()
.expect("mod 2 fits in u64");
let mag_big = rank / BigUint::from(2u32);
let mag_idx: u64 = (&mag_big).try_into().ok()?;
let mag = index_to_float(mag_idx);
Some(if is_neg_u == 1 { -mag } else { mag })
}
fn max_finite_global_rank() -> crate::native::bignum::BigUint {
use crate::native::bignum::BigUint;
let max_finite_lex = (1u64 << 63) | (2046u64 << 52) | ((1u64 << 52) - 1);
BigUint::from(max_finite_lex) * BigUint::from(2u32) + BigUint::from(1u32)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ChoiceKind {
Integer(IntegerChoice),
Boolean(BooleanChoice),
Float(FloatChoice),
}
#[derive(Clone, Debug)]
pub enum ChoiceValue {
Integer(i128),
Boolean(bool),
Float(f64),
}
impl PartialEq for ChoiceValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ChoiceValue::Integer(a), ChoiceValue::Integer(b)) => a == b,
(ChoiceValue::Boolean(a), ChoiceValue::Boolean(b)) => a == b,
(ChoiceValue::Float(a), ChoiceValue::Float(b)) => a.to_bits() == b.to_bits(),
_ => false,
}
}
}
impl Eq for ChoiceValue {}
impl std::hash::Hash for ChoiceValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
ChoiceValue::Integer(n) => n.hash(state),
ChoiceValue::Boolean(b) => b.hash(state),
ChoiceValue::Float(f) => f.to_bits().hash(state),
}
}
}
impl ChoiceKind {
pub fn simplest(&self) -> ChoiceValue {
match self {
ChoiceKind::Integer(ic) => ChoiceValue::Integer(ic.simplest()),
ChoiceKind::Boolean(bc) => ChoiceValue::Boolean(bc.simplest()),
ChoiceKind::Float(fc) => ChoiceValue::Float(fc.simplest()),
}
}
pub fn max_index(&self) -> crate::native::bignum::BigUint {
match self {
ChoiceKind::Integer(ic) => ic.max_index(),
ChoiceKind::Boolean(bc) => bc.max_index(),
ChoiceKind::Float(fc) => fc.max_index(),
}
}
pub fn to_index(&self, value: &ChoiceValue) -> crate::native::bignum::BigUint {
match (self, value) {
(ChoiceKind::Integer(ic), ChoiceValue::Integer(v)) => ic.to_index(*v),
(ChoiceKind::Boolean(bc), ChoiceValue::Boolean(v)) => bc.to_index(*v),
(ChoiceKind::Float(fc), ChoiceValue::Float(v)) => fc.to_index(*v),
_ => panic!("ChoiceKind::to_index: kind/value mismatch"),
}
}
#[allow(clippy::wrong_self_convention)]
pub fn from_index(&self, index: crate::native::bignum::BigUint) -> Option<ChoiceValue> {
match self {
ChoiceKind::Integer(ic) => ic.from_index(index).map(ChoiceValue::Integer),
ChoiceKind::Boolean(bc) => bc.from_index(index).map(ChoiceValue::Boolean),
ChoiceKind::Float(fc) => fc.from_index(index).map(ChoiceValue::Float),
}
}
pub fn validate(&self, value: &ChoiceValue) -> bool {
match (self, value) {
(ChoiceKind::Integer(ic), ChoiceValue::Integer(v)) => ic.validate(*v),
(ChoiceKind::Boolean(_), ChoiceValue::Boolean(_)) => true,
(ChoiceKind::Float(fc), ChoiceValue::Float(v)) => fc.validate(*v),
_ => false,
}
}
pub fn max_children(&self) -> crate::native::bignum::BigUint {
use crate::native::bignum::BigUint;
match self {
ChoiceKind::Integer(ic) => {
let diff = (ic.max_value as u128).wrapping_sub(ic.min_value as u128);
BigUint::from(diff) + BigUint::from(1u32)
}
ChoiceKind::Boolean(_) => BigUint::from(2u32),
ChoiceKind::Float(fc) => fc.max_index() + BigUint::from(1u32),
}
}
pub fn random_value(&self, rng: &mut rand::rngs::SmallRng) -> ChoiceValue {
use rand::RngExt;
match self {
ChoiceKind::Integer(ic) => {
ChoiceValue::Integer(crate::native::core::state::biased_integer_sample(ic, rng))
}
ChoiceKind::Boolean(_) => ChoiceValue::Boolean(rng.random::<bool>()),
ChoiceKind::Float(fc) => {
ChoiceValue::Float(crate::native::core::state::biased_float_sample(fc, rng))
}
}
}
pub fn enumerate(&self, cap: u64) -> Option<Vec<ChoiceValue>> {
use crate::native::bignum::BigUint;
let max_c = self.max_children();
if max_c > BigUint::from(cap) {
return None;
}
match self {
ChoiceKind::Integer(ic) => {
let mut v = Vec::new();
let mut n = ic.min_value;
loop {
v.push(ChoiceValue::Integer(n));
if n == ic.max_value {
break;
}
n += 1;
}
Some(v)
}
ChoiceKind::Boolean(_) => Some(vec![
ChoiceValue::Boolean(false),
ChoiceValue::Boolean(true),
]),
ChoiceKind::Float(_) => {
unreachable!("FloatChoice max_children always exceeds u64::MAX cap")
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChoiceNode {
pub kind: ChoiceKind,
pub value: ChoiceValue,
pub was_forced: bool,
}
impl ChoiceNode {
pub fn with_value(&self, value: ChoiceValue) -> ChoiceNode {
ChoiceNode {
kind: self.kind.clone(),
value,
was_forced: self.was_forced,
}
}
pub fn sort_key(&self) -> NodeSortKey {
match (&self.kind, &self.value) {
(ChoiceKind::Integer(ic), ChoiceValue::Integer(v)) => {
let (abs, neg) = ic.sort_key(*v);
NodeSortKey(abs, neg)
}
(ChoiceKind::Boolean(_), ChoiceValue::Boolean(v)) => NodeSortKey(u128::from(*v), false),
(ChoiceKind::Float(fc), ChoiceValue::Float(v)) => {
let (mag, neg) = fc.sort_key(*v);
NodeSortKey(u128::from(mag), neg)
}
_ => unreachable!("mismatched choice kind and value"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeSortKey(pub u128, pub bool);
pub fn sort_key(nodes: &[ChoiceNode]) -> (usize, Vec<NodeSortKey>) {
(nodes.len(), nodes.iter().map(|n| n.sort_key()).collect())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Status {
EarlyStop = 0,
Invalid = 1,
Valid = 2,
Interesting = 3,
}
pub struct StopTest;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct InterestingOrigin(pub String);
#[cfg(test)]
#[path = "../../../tests/embedded/native/choices_tests.rs"]
mod tests;