Skip to main content

sim_lib_pitch_ratio/
rank.rs

1//! Finite prime-vector rank/unrank over discrete mixed-radix ordinals.
2
3use num_bigint::BigUint;
4use sim_lib_discrete_comb::{CombError, mixed_radix_rank, mixed_radix_unrank};
5
6use crate::{FactorVector, PitchRatio, PitchRatioError, RatioPolicy, model::primes_up_to};
7
8/// Largest absolute exponent represented by finite rank/unrank.
9pub const MAX_RANK_EXPONENT: i16 = 31;
10
11/// Rank a ratio's bounded signed prime vector with discrete mixed-radix ranking.
12pub fn rank_ratio(ratio: PitchRatio, policy: RatioPolicy) -> Result<BigUint, PitchRatioError> {
13    let vector = ratio.factor_vector(policy)?;
14    let (digits, radices) = vector_digits(&vector)?;
15    Ok(mixed_radix_rank(&digits, &radices)?)
16}
17
18/// Unrank a ratio from a finite signed prime-exponent vector domain.
19pub fn unrank_ratio(rank: &BigUint, policy: RatioPolicy) -> Result<PitchRatio, PitchRatioError> {
20    let Some(prime_limit) = policy.prime_limit else {
21        return Err(PitchRatioError::UnboundedFactorization);
22    };
23    let primes = primes_up_to(prime_limit)?;
24    let radix = u64::from((MAX_RANK_EXPONENT * 2 + 1) as u16);
25    let radices = vec![radix; primes.len()];
26    let digits = mixed_radix_unrank(rank, &radices)?;
27    let exponents = digits
28        .into_iter()
29        .map(|digit| {
30            let shifted = i64::try_from(digit).map_err(|_| PitchRatioError::Overflow)?
31                - i64::from(MAX_RANK_EXPONENT);
32            i16::try_from(shifted).map_err(|_| PitchRatioError::Overflow)
33        })
34        .collect::<Result<Vec<_>, _>>()?;
35    FactorVector { primes, exponents }
36        .to_ratio()?
37        .canonical(policy)
38}
39
40fn vector_digits(vector: &FactorVector) -> Result<(Vec<u64>, Vec<u64>), PitchRatioError> {
41    let radix = u64::from((MAX_RANK_EXPONENT * 2 + 1) as u16);
42    let mut digits = Vec::with_capacity(vector.exponents.len());
43    for &exponent in &vector.exponents {
44        if !(-MAX_RANK_EXPONENT..=MAX_RANK_EXPONENT).contains(&exponent) {
45            return Err(PitchRatioError::RankExponentOutOfRange {
46                exponent,
47                min: -MAX_RANK_EXPONENT,
48                max: MAX_RANK_EXPONENT,
49            });
50        }
51        let shifted = i32::from(exponent) + i32::from(MAX_RANK_EXPONENT);
52        digits.push(u64::try_from(shifted).map_err(|_| PitchRatioError::Overflow)?);
53    }
54    Ok((digits, vec![radix; vector.exponents.len()]))
55}
56
57impl From<CombError> for PitchRatioError {
58    fn from(error: CombError) -> Self {
59        Self::DiscreteRank(error.to_string())
60    }
61}