#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AirChoice {
pub airgroup_id: usize,
pub air_id: usize,
pub rows: u64,
pub memory: u64,
}
impl AirChoice {
pub fn new(airgroup_id: usize, air_id: usize, rows: usize, cost: usize) -> Self {
Self { airgroup_id, air_id, rows: rows as u64, memory: cost as u64 }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct Cost {
pub instances: u64,
pub memory: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Selection {
pub instances: Vec<u64>,
pub assignment: Vec<usize>,
pub cost: Cost,
}
pub fn select_airs(kinds: &[Vec<(usize, u64)>], airs: &[AirChoice]) -> Selection {
if kinds.is_empty() || airs.is_empty() {
return Selection { instances: vec![0; airs.len()], ..Default::default() };
}
let mut best: Option<(Cost, Vec<u64>, Vec<usize>)> = None;
let mut combo = vec![0usize; kinds.len()];
loop {
let mut rows = vec![0u64; airs.len()];
for (kind, &choice) in combo.iter().enumerate() {
if let Some(&(air, kind_rows)) = kinds[kind].get(choice) {
assert!(air < airs.len(), "kind {kind} names air {air}, out of {}", airs.len());
rows[air] += kind_rows;
}
}
let instances: Vec<u64> =
rows.iter().zip(airs).map(|(&r, air)| r.div_ceil(air.rows)).collect();
let cost = Cost {
instances: instances.iter().sum(),
memory: instances.iter().zip(airs).map(|(&n, air)| n * air.memory).sum(),
};
if best.as_ref().map_or(true, |(b, _, _)| cost < *b) {
let assignment = combo
.iter()
.enumerate()
.map(|(k, &c)| kinds[k].get(c).map_or(0, |o| o.0))
.collect();
best = Some((cost, instances, assignment));
}
let mut pos = kinds.len();
loop {
if pos == 0 {
let (cost, instances, assignment) = best.expect("one combination is always seen");
return Selection { instances, assignment, cost };
}
pos -= 1;
combo[pos] += 1;
if combo[pos] < kinds[pos].len().max(1) {
break;
}
combo[pos] = 0;
}
}
}
pub fn select_sizes(rows: u64, airs: &[AirChoice]) -> Vec<u64> {
assert!(!airs.is_empty(), "a family must offer at least one air");
let mut ladder: Vec<usize> = (0..airs.len()).collect();
ladder.sort_by_key(|&i| (airs[i].rows, airs[i].memory));
for pair in ladder.windows(2) {
let (shorter, taller) = (&airs[pair[0]], &airs[pair[1]]);
assert!(
shorter.rows == taller.rows || shorter.memory < taller.memory,
"air {} is shorter than air {} but not cheaper ({} vs {} memory): the family is not a \
size ladder, so select_airs is the tool for it",
shorter.air_id,
taller.air_id,
shorter.memory,
taller.memory,
);
}
let mut instances = vec![0u64; airs.len()];
if rows == 0 {
return instances;
}
let tallest = *ladder.last().expect("ladder is non-empty");
let count = rows.div_ceil(airs[tallest].rows);
let mut pending = rows;
for filled in 0..count {
let after = (count - filled - 1) * airs[tallest].rows;
let pick =
ladder.iter().copied().find(|&i| airs[i].rows + after >= pending).unwrap_or(tallest);
instances[pick] += 1;
pending = pending.saturating_sub(airs[pick].rows);
}
instances
}
#[cfg(test)]
mod tests {
use super::*;
fn ladder() -> [AirChoice; 2] {
[
AirChoice { airgroup_id: 0, air_id: 0, rows: 100, memory: 100 },
AirChoice { airgroup_id: 0, air_id: 1, rows: 200, memory: 200 },
]
}
#[test]
fn fewer_instances_wins_over_less_area() {
assert_eq!(
select_sizes(200, &ladder()),
vec![0, 1],
"two smalls would be one instance more"
);
assert_eq!(select_sizes(101, &ladder()), vec![0, 1], "a half-empty big beats two smalls");
}
#[test]
fn area_breaks_the_tie() {
assert_eq!(select_sizes(100, &ladder()), vec![1, 0]);
assert_eq!(select_sizes(1, &ladder()), vec![1, 0]);
}
#[test]
fn only_the_tail_is_demoted() {
assert_eq!(select_sizes(700, &ladder()), vec![1, 3]);
assert_eq!(select_sizes(800, &ladder()), vec![0, 4]);
}
#[test]
fn nothing_to_prove_needs_no_instance() {
assert_eq!(select_sizes(0, &ladder()), vec![0, 0]);
assert_eq!(
select_airs(&[], &ladder()),
Selection { instances: vec![0, 0], ..Default::default() }
);
}
#[test]
fn kinds_share_an_air_rather_than_open_two() {
let airs = [
AirChoice { airgroup_id: 0, air_id: 0, rows: 100, memory: 50 },
AirChoice { airgroup_id: 0, air_id: 1, rows: 100, memory: 100 },
];
let kinds = vec![vec![(0, 40), (1, 40)], vec![(1, 40)]];
let selection = select_airs(&kinds, &airs);
assert_eq!(selection.instances, vec![0, 1], "both kinds ride in one general instance");
assert_eq!(selection.cost, Cost { instances: 1, memory: 100 });
}
#[test]
fn the_cheaper_air_takes_what_it_can_on_a_tie() {
let airs = [
AirChoice { airgroup_id: 0, air_id: 0, rows: 100, memory: 50 },
AirChoice { airgroup_id: 0, air_id: 1, rows: 100, memory: 100 },
];
let kinds = vec![vec![(0, 80), (1, 80)], vec![(1, 80)]];
let selection = select_airs(&kinds, &airs);
assert_eq!(selection.instances, vec![1, 1]);
assert_eq!(selection.assignment[0], 0, "the specialised air is the cheaper home");
assert_eq!(selection.cost, Cost { instances: 2, memory: 150 });
}
#[test]
fn a_kind_with_no_air_is_ignored() {
let airs = ladder();
let selection = select_airs(&[vec![], vec![(0, 50)]], &airs);
assert_eq!(selection.instances, vec![1, 0]);
}
}