sim_lib_pitch_ratio/
rank.rs1use 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
8pub const MAX_RANK_EXPONENT: i16 = 31;
10
11pub 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
18pub 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}