use miden_core::field::{BasedVectorSpace, QuadFelt};
use miden_crypto::stark::{log_quotient_degree, pcs::PcsParams};
use crate::{AIRS, BaseAir, Felt, LiftedAir, MIDEN_AIR_COUNT, MidenAir};
const FELT_BYTES: u64 = size_of::<Felt>() as u64;
const EXT_DIMENSION: u64 = <QuadFelt as BasedVectorSpace<Felt>>::DIMENSION as u64;
const DIGEST_BYTES: u64 = 32;
const LMCS_TREES_AT_PEAK: u64 = 3;
pub const SAFETY_NUMERATOR: u64 = 5;
pub const SAFETY_DENOMINATOR: u64 = 4;
pub fn prover_peak_bytes(heights: &[usize; MIDEN_AIR_COUNT], params: &PcsParams) -> Option<u64> {
let blowup = 1u64.checked_shl(u32::from(params.log_blowup()))?;
let one_plus_blowup = blowup.checked_add(1)?;
let mut per_air_total: u64 = 0;
let mut max_height: u64 = 0;
let mut max_quotient_degree: u64 = 0;
for (air, &height) in AIRS.iter().zip(heights.iter()) {
let height = u64::try_from(height).ok()?;
let width = u64::try_from(air.width()).ok()?;
let aux_width =
u64::try_from(<MidenAir as LiftedAir<Felt, QuadFelt>>::aux_width(air)).ok()?;
let log_d = log_quotient_degree::<Felt, QuadFelt, _>(air);
let quotient_degree = 1u64.checked_shl(u32::from(log_d))?;
let aux_base_columns = aux_width.checked_mul(EXT_DIMENSION)?;
let columns = width.checked_add(aux_base_columns)?;
let bytes_per_row = FELT_BYTES.checked_mul(one_plus_blowup)?.checked_mul(columns)?;
let per_air = height.checked_mul(bytes_per_row)?;
per_air_total = per_air_total.checked_add(per_air)?;
max_height = max_height.max(height);
max_quotient_degree = max_quotient_degree.max(quotient_degree);
}
let quotient_bytes = EXT_DIMENSION
.checked_mul(FELT_BYTES)?
.checked_mul(max_quotient_degree)?
.checked_mul(blowup)?;
let tree_bytes = LMCS_TREES_AT_PEAK
.checked_mul(2)?
.checked_mul(blowup)?
.checked_mul(DIGEST_BYTES)?;
let shared_total = max_height.checked_mul(quotient_bytes.checked_add(tree_bytes)?)?;
let modelled = per_air_total.checked_add(shared_total)?;
modelled
.checked_mul(SAFETY_NUMERATOR)
.map(|scaled| scaled.div_ceil(SAFETY_DENOMINATOR))
}
pub fn max_any_height_for_budget(budget_bytes: u64, params: &PcsParams) -> usize {
let mut best = 0usize;
for i in 0..MIDEN_AIR_COUNT {
let height = max_height_for_budget(budget_bytes, params, |n| {
let mut heights = [0usize; MIDEN_AIR_COUNT];
heights[i] = n;
heights
});
best = best.max(height);
}
best
}
fn max_height_for_budget(
budget_bytes: u64,
params: &PcsParams,
heights_for: impl Fn(usize) -> [usize; MIDEN_AIR_COUNT],
) -> usize {
let fits =
|n: usize| prover_peak_bytes(&heights_for(n), params).is_some_and(|b| b <= budget_bytes);
let mut lo = 0usize;
let mut hi = usize::MAX;
if fits(hi) {
return hi;
}
while hi - lo > 1 {
let mid = lo + (hi - lo) / 2;
if fits(mid) {
lo = mid;
} else {
hi = mid;
}
}
lo
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::pcs_params;
#[test]
fn pinned_bytes_for_current_air_shape() {
let params = pcs_params();
assert_eq!(prover_peak_bytes(&[1, 0, 0], ¶ms), Some(8510), "Core alone");
assert_eq!(prover_peak_bytes(&[0, 1, 0], ¶ms), Some(5720), "Chiplets alone");
assert_eq!(
prover_peak_bytes(&[0, 0, 1], ¶ms),
Some(4820),
"Poseidon2Permutation alone"
);
assert_eq!(prover_peak_bytes(&[1, 1, 1], ¶ms), Some(12650), "all three at height 1");
}
#[test]
fn zero_heights_cost_nothing() {
let params = pcs_params();
assert_eq!(prover_peak_bytes(&[0, 0, 0], ¶ms), Some(0));
}
#[test]
fn increasing_any_height_never_decreases_the_result() {
let params = pcs_params();
let base = [1_000usize, 2_000, 500];
let base_bytes = prover_peak_bytes(&base, ¶ms).expect("fits in u64");
for i in 0..MIDEN_AIR_COUNT {
let mut bumped = base;
bumped[i] += 1;
let bumped_bytes = prover_peak_bytes(&bumped, ¶ms).expect("fits in u64");
assert!(bumped_bytes >= base_bytes, "bumping height {i} decreased the modelled peak");
}
}
#[test]
fn max_any_height_round_trips_through_the_cheapest_air() {
let params = pcs_params();
let cheapest = (0..MIDEN_AIR_COUNT)
.min_by_key(|&i| {
let mut heights = [0usize; MIDEN_AIR_COUNT];
heights[i] = 1;
prover_peak_bytes(&heights, ¶ms).expect("fits in u64")
})
.expect("MIDEN_AIR_COUNT is non-zero");
for n in [0usize, 1, 7, 100, 1 << 10, 1 << 20] {
let mut heights = [0usize; MIDEN_AIR_COUNT];
heights[cheapest] = n;
let budget = prover_peak_bytes(&heights, ¶ms).expect("fits in u64");
assert_eq!(
max_any_height_for_budget(budget, ¶ms),
n,
"round trip failed for n = {n}"
);
}
}
#[test]
fn max_any_height_for_budget_never_rejects_a_uniform_shape_that_fits() {
let params = pcs_params();
for n in [0usize, 1, 7, 100, 1 << 10, 1 << 20] {
let budget = prover_peak_bytes(&[n; MIDEN_AIR_COUNT], ¶ms).expect("fits in u64");
let any = max_any_height_for_budget(budget, ¶ms);
assert!(
any >= n,
"max_any_height_for_budget({budget}) = {any} rejects uniform height {n}"
);
}
}
#[test]
fn overflow_returns_none_instead_of_panicking() {
let params = pcs_params();
assert_eq!(prover_peak_bytes(&[usize::MAX; MIDEN_AIR_COUNT], ¶ms), None);
}
}