use crate::largest_remainder;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LayerAlloc {
pub pinned: Vec<usize>,
pub unpinned_budget: usize,
pub unpinned: Vec<usize>,
}
impl LayerAlloc {
pub fn total(&self) -> usize {
self.pinned.iter().sum::<usize>() + self.unpinned_budget
}
pub fn node_target(&self, n: usize) -> usize {
self.pinned[n] + self.unpinned[n]
}
}
#[derive(Clone, Debug)]
pub struct WaterFillEntry {
pub weight: usize,
pub demand: usize,
}
pub fn water_fill(pool: usize, entries: &[WaterFillEntry]) -> Vec<usize> {
let n = entries.len();
if n == 0 {
return vec![];
}
let mut result = vec![0usize; n];
let mut locked = vec![false; n];
let mut remaining_pool = pool;
loop {
let total_weight: usize = entries
.iter()
.enumerate()
.filter(|(i, _)| !locked[*i])
.map(|(_, e)| e.weight)
.sum();
if total_weight == 0 {
break;
}
let quotas: Vec<f64> = entries
.iter()
.enumerate()
.filter(|(i, _)| !locked[*i])
.map(|(_, e)| e.weight as f64)
.collect();
let competing_indices: Vec<usize> = (0..n).filter(|i| !locked[*i]).collect();
let shares = largest_remainder(remaining_pool, "as);
let mut newly_capped = false;
for (pos, &idx) in competing_indices.iter().enumerate() {
if shares[pos] > entries[idx].demand {
result[idx] = entries[idx].demand;
locked[idx] = true;
remaining_pool -= entries[idx].demand;
newly_capped = true;
}
}
if !newly_capped {
for (pos, &idx) in competing_indices.iter().enumerate() {
result[idx] = shares[pos];
}
break;
}
}
result
}
#[derive(Clone, Debug)]
pub struct LayerDemand {
pub raw_pinned: Vec<usize>,
pub raw_unpinned: usize,
pub weight: usize,
pub spread: bool,
}
impl LayerDemand {
pub fn raw_total(&self) -> usize {
self.raw_pinned.iter().sum::<usize>() + self.raw_unpinned
}
}
pub fn unified_alloc(
total_units: usize,
node_caps: &[usize],
demands: &[LayerDemand],
cur_node_cpus: &[Vec<usize>],
norders: &[Vec<usize>],
) -> Vec<LayerAlloc> {
let mut allocs = allocate_budgets(total_units, node_caps, demands);
resolve_spread(&mut allocs, demands, node_caps);
place_unpinned(&mut allocs, demands, node_caps, cur_node_cpus, norders);
allocs
}
fn allocate_budgets(
total_units: usize,
node_caps: &[usize],
demands: &[LayerDemand],
) -> Vec<LayerAlloc> {
let nr_layers = demands.len();
let nr_nodes = node_caps.len();
if nr_layers == 0 {
return vec![];
}
let mut allocs: Vec<LayerAlloc> = demands
.iter()
.map(|_| LayerAlloc {
pinned: vec![0; nr_nodes],
unpinned_budget: 0,
unpinned: vec![0; nr_nodes],
})
.collect();
let mut locked = vec![false; nr_layers];
let mut pool = total_units;
let mut node_used: Vec<usize> = vec![0; nr_nodes];
for _iteration in 0..nr_layers + 1 {
let total_weight: usize = demands
.iter()
.enumerate()
.filter(|(i, _)| !locked[*i])
.map(|(_, d)| d.weight)
.sum();
if total_weight == 0 {
break;
}
let mut global_targets = vec![0usize; nr_layers];
let competing: Vec<usize> = (0..nr_layers).filter(|i| !locked[*i]).collect();
{
let quotas: Vec<f64> = demands
.iter()
.enumerate()
.filter(|(i, _)| !locked[*i])
.map(|(_, d)| d.weight as f64)
.collect();
let shares = largest_remainder(pool, "as);
for (pos, &idx) in competing.iter().enumerate() {
global_targets[idx] = shares[pos];
}
}
for &idx in &competing {
if demands[idx].raw_total() > 0 && global_targets[idx] == 0 {
if let Some(&donor) = competing
.iter()
.filter(|&&j| global_targets[j] > 1)
.max_by_key(|&&j| global_targets[j])
{
global_targets[donor] -= 1;
global_targets[idx] = 1;
}
}
}
let mut pinned_targets: Vec<Vec<f64>> = vec![vec![0.0; nr_nodes]; nr_layers];
for i in 0..nr_layers {
if locked[i] {
continue;
}
let raw_total = demands[i].raw_total();
let scale = if raw_total > 0 {
(global_targets[i] as f64 / raw_total as f64).min(1.0)
} else {
0.0
};
for n in 0..nr_nodes {
pinned_targets[i][n] = demands[i].raw_pinned[n] as f64 * scale;
}
}
let mut actual_pinned: Vec<Vec<usize>> = vec![vec![0; nr_nodes]; nr_layers];
for n in 0..nr_nodes {
let node_avail = node_caps[n].saturating_sub(node_used[n]);
let mut wf_entries: Vec<WaterFillEntry> = Vec::new();
let mut wf_indices: Vec<usize> = Vec::new();
for i in 0..nr_layers {
if locked[i] || pinned_targets[i][n] == 0.0 {
continue;
}
wf_entries.push(WaterFillEntry {
weight: demands[i].weight,
demand: pinned_targets[i][n].ceil() as usize,
});
wf_indices.push(i);
}
let wf_allocs = water_fill(node_avail, &wf_entries);
for (pos, &idx) in wf_indices.iter().enumerate() {
actual_pinned[idx][n] = wf_allocs[pos];
}
}
let mut unpinned_alloc = vec![0usize; nr_layers];
for i in 0..nr_layers {
if locked[i] {
continue;
}
let pinned_total: usize = actual_pinned[i].iter().sum();
let budget_left = global_targets[i].saturating_sub(pinned_total);
unpinned_alloc[i] = demands[i].raw_unpinned.min(budget_left);
}
let mut newly_locked = false;
for i in 0..nr_layers {
if locked[i] {
continue;
}
let pinned_total: usize = actual_pinned[i].iter().sum();
let total_alloc = pinned_total + unpinned_alloc[i];
if total_alloc < global_targets[i] {
allocs[i].pinned = actual_pinned[i].clone();
allocs[i].unpinned_budget = unpinned_alloc[i];
locked[i] = true;
pool -= total_alloc;
for n in 0..nr_nodes {
node_used[n] += actual_pinned[i][n];
}
newly_locked = true;
}
}
if !newly_locked {
for i in 0..nr_layers {
if locked[i] {
continue;
}
allocs[i].pinned = actual_pinned[i].clone();
allocs[i].unpinned_budget = unpinned_alloc[i];
}
break;
}
}
allocs
}
fn resolve_spread(allocs: &mut [LayerAlloc], demands: &[LayerDemand], node_caps: &[usize]) {
if allocs.is_empty() {
return;
}
let nr_nodes = node_caps.len();
let nr_layers = allocs.len();
let mut spread_avail = node_caps.to_vec();
for (idx, alloc) in allocs.iter().enumerate() {
if demands[idx].spread {
continue; }
for n in 0..nr_nodes {
spread_avail[n] = spread_avail[n].saturating_sub(alloc.pinned[n]);
}
}
let spread_indices: Vec<usize> = (0..nr_layers).filter(|&i| demands[i].spread).collect();
if spread_indices.is_empty() {
return;
}
let totals: Vec<usize> = spread_indices
.iter()
.map(|&idx| allocs[idx].pinned.iter().sum::<usize>() + allocs[idx].unpinned_budget)
.collect();
for &idx in &spread_indices {
allocs[idx].pinned = vec![0; nr_nodes];
}
let spread_pool = spread_avail.iter().copied().min().unwrap_or(0);
let entries: Vec<WaterFillEntry> = spread_indices
.iter()
.enumerate()
.map(|(pos, &idx)| WaterFillEntry {
weight: demands[idx].weight,
demand: totals[pos] / nr_nodes,
})
.collect();
let per_node_allocs = water_fill(spread_pool, &entries);
let mut freed = 0usize;
for (pos, &idx) in spread_indices.iter().enumerate() {
let per_node = per_node_allocs[pos];
let total = totals[pos];
let want_per_node = total / nr_nodes;
if per_node >= want_per_node {
let remainder = total % nr_nodes;
let mut rem_left = remainder;
for n in 0..nr_nodes {
allocs[idx].unpinned[n] = per_node;
if rem_left > 0 && spread_avail[n] > per_node {
allocs[idx].unpinned[n] += 1;
rem_left -= 1;
}
}
allocs[idx].unpinned_budget = total - rem_left;
freed += rem_left;
} else {
for n in 0..nr_nodes {
allocs[idx].unpinned[n] = per_node;
}
let new_total = per_node * nr_nodes;
allocs[idx].unpinned_budget = new_total;
freed += total - new_total;
}
}
if freed > 0 {
let mut entries = Vec::new();
let mut indices = Vec::new();
for (idx, d) in demands.iter().enumerate() {
if d.spread {
continue;
}
let remaining_demand = d.raw_total().saturating_sub(allocs[idx].total());
if remaining_demand > 0 {
entries.push(WaterFillEntry {
weight: d.weight,
demand: remaining_demand,
});
indices.push(idx);
}
}
if !entries.is_empty() {
let shares = water_fill(freed, &entries);
for (pos, &idx) in indices.iter().enumerate() {
allocs[idx].unpinned_budget += shares[pos];
}
}
}
}
fn place_unpinned(
allocs: &mut [LayerAlloc],
demands: &[LayerDemand],
node_caps: &[usize],
cur_node_cpus: &[Vec<usize>],
norders: &[Vec<usize>],
) {
if cur_node_cpus.is_empty() {
return;
}
let nr_layers = allocs.len();
let nr_nodes = node_caps.len();
for (idx, alloc) in allocs.iter_mut().enumerate() {
if demands[idx].spread {
continue;
}
for n in 0..nr_nodes {
alloc.unpinned[n] = cur_node_cpus[idx][n].saturating_sub(alloc.pinned[n]);
}
}
for (idx, alloc) in allocs.iter_mut().enumerate() {
if demands[idx].spread {
continue;
}
let cur_total: usize = alloc.unpinned.iter().sum();
if cur_total > alloc.unpinned_budget {
let mut excess = cur_total - alloc.unpinned_budget;
for &n in norders[idx].iter().rev() {
let trim = excess.min(alloc.unpinned[n]);
alloc.unpinned[n] -= trim;
excess -= trim;
if excess == 0 {
break;
}
}
}
}
let mut node_remaining = node_caps.to_vec();
for alloc in allocs.iter() {
for n in 0..nr_nodes {
node_remaining[n] =
node_remaining[n].saturating_sub(alloc.pinned[n] + alloc.unpinned[n]);
}
}
let mut growth: Vec<usize> = allocs
.iter()
.map(|a| {
a.unpinned_budget
.saturating_sub(a.unpinned.iter().sum::<usize>())
})
.collect();
for rank in 0..nr_nodes {
let mut node_to_layers: Vec<Vec<usize>> = vec![vec![]; nr_nodes];
for idx in 0..nr_layers {
if growth[idx] == 0 || rank >= norders[idx].len() {
continue;
}
node_to_layers[norders[idx][rank]].push(idx);
}
for n in 0..nr_nodes {
let contestants = &node_to_layers[n];
if contestants.is_empty() || node_remaining[n] == 0 {
continue;
}
let entries: Vec<WaterFillEntry> = contestants
.iter()
.map(|&idx| WaterFillEntry {
weight: demands[idx].weight,
demand: growth[idx],
})
.collect();
let shares = water_fill(node_remaining[n], &entries);
for (pos, &idx) in contestants.iter().enumerate() {
allocs[idx].unpinned[n] += shares[pos];
growth[idx] -= shares[pos];
node_remaining[n] -= shares[pos];
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wf_no_contention() {
let entries = vec![
WaterFillEntry {
weight: 1,
demand: 10,
},
WaterFillEntry {
weight: 1,
demand: 10,
},
];
let result = water_fill(20, &entries);
assert_eq!(result, vec![10, 10]);
}
#[test]
fn test_wf_equal_split() {
let entries = vec![
WaterFillEntry {
weight: 1,
demand: 100,
},
WaterFillEntry {
weight: 1,
demand: 100,
},
];
let result = water_fill(10, &entries);
assert_eq!(result, vec![5, 5]);
}
#[test]
fn test_wf_one_capped() {
let entries = vec![
WaterFillEntry {
weight: 1,
demand: 3,
},
WaterFillEntry {
weight: 1,
demand: 100,
},
];
let result = water_fill(10, &entries);
assert_eq!(result[0], 3);
assert_eq!(result[1], 7);
}
#[test]
fn test_wf_all_capped() {
let entries = vec![
WaterFillEntry {
weight: 1,
demand: 3,
},
WaterFillEntry {
weight: 1,
demand: 4,
},
];
let result = water_fill(20, &entries);
assert_eq!(result, vec![3, 4]);
}
#[test]
fn test_wf_unequal_weights() {
let entries = vec![
WaterFillEntry {
weight: 3,
demand: 100,
},
WaterFillEntry {
weight: 1,
demand: 100,
},
];
let result = water_fill(20, &entries);
assert_eq!(result[0], 15);
assert_eq!(result[1], 5);
}
#[test]
fn test_wf_cascading_caps() {
let entries = vec![
WaterFillEntry {
weight: 1,
demand: 2,
},
WaterFillEntry {
weight: 1,
demand: 3,
},
WaterFillEntry {
weight: 1,
demand: 100,
},
];
let result = water_fill(12, &entries);
assert_eq!(result, vec![2, 3, 7]);
}
#[test]
fn test_wf_zero_demand() {
let entries = vec![
WaterFillEntry {
weight: 1,
demand: 0,
},
WaterFillEntry {
weight: 1,
demand: 10,
},
];
let result = water_fill(10, &entries);
assert_eq!(result[0], 0);
assert_eq!(result[1], 10);
}
#[test]
fn test_wf_single_entry() {
let entries = vec![WaterFillEntry {
weight: 1,
demand: 5,
}];
let result = water_fill(10, &entries);
assert_eq!(result, vec![5]);
}
#[test]
fn test_wf_conservation() {
let entries = vec![
WaterFillEntry {
weight: 2,
demand: 100,
},
WaterFillEntry {
weight: 3,
demand: 100,
},
WaterFillEntry {
weight: 5,
demand: 100,
},
];
let result = water_fill(50, &entries);
assert_eq!(result.iter().sum::<usize>(), 50);
}
#[test]
fn test_wf_empty() {
let result = water_fill(10, &[]);
assert!(result.is_empty());
}
fn caps_2n() -> Vec<usize> {
vec![48, 48]
}
fn demand(w: usize, p0: usize, p1: usize, u: usize) -> LayerDemand {
LayerDemand {
raw_pinned: vec![p0, p1],
raw_unpinned: u,
weight: w,
spread: false,
}
}
fn total_alloc(allocs: &[LayerAlloc]) -> usize {
allocs.iter().map(|a| a.total()).sum()
}
#[test]
fn test_ua_s1_no_contention() {
let demands = vec![
demand(1, 18, 0, 18),
demand(1, 0, 18, 18),
demand(1, 12, 0, 24),
demand(1, 0, 12, 24),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
for a in &allocs {
assert_eq!(a.total(), 24);
}
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_s2_demand_cap_one_restart() {
let demands = vec![
demand(1, 18, 0, 18),
demand(1, 0, 18, 18),
demand(1, 4, 0, 2),
demand(1, 0, 4, 2),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[2].total(), 6);
assert_eq!(allocs[3].total(), 6);
assert_eq!(allocs[0].total(), 36);
assert_eq!(allocs[1].total(), 36);
assert_eq!(total_alloc(&allocs), 84);
}
#[test]
fn test_ua_s3_cascading_demand_caps() {
let demands = vec![
demand(1, 18, 0, 18),
demand(1, 12, 0, 12),
demand(1, 6, 0, 2),
demand(1, 0, 2, 2),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 36);
assert_eq!(allocs[1].total(), 24);
assert_eq!(allocs[2].total(), 8);
assert_eq!(allocs[3].total(), 4);
assert_eq!(total_alloc(&allocs), 72);
}
#[test]
fn test_ua_s4_pinned_contention() {
let demands = vec![
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
for a in &allocs {
assert_eq!(a.total(), 24);
assert_eq!(a.pinned[0], 12);
assert_eq!(a.unpinned_budget, 12);
}
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_s5_pinned_contention_demand_cap() {
let demands = vec![
demand(1, 40, 0, 8),
demand(1, 40, 0, 8),
demand(1, 40, 0, 8),
demand(1, 3, 0, 3),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[3].total(), 6);
for i in 0..3 {
assert_eq!(allocs[i].unpinned_budget, 8);
assert!(allocs[i].total() >= 22 && allocs[i].total() <= 24);
}
}
#[test]
fn test_ua_s6_per_node_cascading() {
let demands = vec![
demand(1, 6, 0, 6),
demand(1, 6, 0, 6),
demand(1, 40, 0, 8),
demand(1, 40, 0, 8),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 12);
assert_eq!(allocs[1].total(), 12);
assert_eq!(allocs[2].unpinned_budget, 8);
assert_eq!(allocs[3].unpinned_budget, 8);
assert_eq!(allocs[2].total(), 26);
assert_eq!(allocs[3].total(), 26);
assert_eq!(total_alloc(&allocs), 76);
}
#[test]
fn test_ua_s7_weight_disparity() {
let demands = vec![
demand(5, 18, 0, 18),
demand(1, 18, 0, 18),
demand(1, 18, 0, 18),
demand(1, 6, 0, 6),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 36);
assert_eq!(allocs[3].total(), 12);
assert_eq!(allocs[1].total(), 24);
assert_eq!(allocs[2].total(), 24);
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_s8_mixed_pinned_unpinned() {
let demands = vec![
demand(1, 24, 0, 0),
demand(1, 0, 24, 0),
demand(1, 0, 0, 24),
demand(1, 0, 0, 24),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
for a in &allocs {
assert_eq!(a.total(), 24);
}
assert_eq!(allocs[0].pinned[0], 24);
assert_eq!(allocs[1].pinned[1], 24);
assert_eq!(allocs[2].unpinned_budget, 24);
assert_eq!(allocs[3].unpinned_budget, 24);
}
#[test]
fn test_ua_s8b_mixed_with_demand_cap() {
let demands = vec![
demand(1, 24, 0, 0),
demand(1, 0, 24, 0),
demand(1, 0, 0, 6),
demand(1, 0, 0, 6),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 24);
assert_eq!(allocs[1].total(), 24);
assert_eq!(allocs[2].total(), 6);
assert_eq!(allocs[3].total(), 6);
assert_eq!(total_alloc(&allocs), 60);
}
#[test]
fn test_ua_s9_all_pinned_same_node() {
let demands = vec![
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
for a in &allocs {
assert_eq!(a.total(), 24);
assert_eq!(a.pinned[0], 12);
assert_eq!(a.unpinned_budget, 12);
}
}
#[test]
fn test_ua_s9b_unequal_weights() {
let demands = vec![
demand(3, 36, 0, 12),
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
demand(1, 36, 0, 12),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 36);
assert_eq!(allocs[0].unpinned_budget, 12);
for i in 1..4 {
assert_eq!(allocs[i].unpinned_budget, 12);
assert_eq!(allocs[i].total(), 20);
}
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_s10_pinned_spread() {
let demands = vec![
demand(2, 18, 0, 18),
demand(2, 0, 18, 18),
demand(1, 3, 0, 3),
demand(1, 0, 3, 3),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 36);
assert_eq!(allocs[1].total(), 36);
assert_eq!(allocs[2].total(), 6);
assert_eq!(allocs[3].total(), 6);
assert_eq!(total_alloc(&allocs), 84);
}
#[test]
fn test_ua_s11_supply_constrained() {
let demands = vec![
demand(1, 48, 0, 0),
demand(1, 48, 0, 0),
demand(1, 48, 0, 0),
demand(1, 0, 0, 24),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[3].total(), 24);
for i in 0..3 {
assert_eq!(allocs[i].pinned[0], 16);
assert_eq!(allocs[i].total(), 16);
}
assert_eq!(total_alloc(&allocs), 72);
}
#[test]
fn test_ua_s12_pinned_contention_cascading() {
let demands = vec![
demand(1, 40, 0, 8),
demand(1, 40, 0, 8),
demand(1, 40, 0, 8),
demand(1, 2, 0, 2),
];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[3].total(), 4);
for i in 0..3 {
assert_eq!(allocs[i].unpinned_budget, 8);
}
let n0_pinned: usize = allocs.iter().map(|a| a.pinned[0]).sum();
assert!(n0_pinned <= 48);
}
#[test]
fn test_ua_single_node_degenerate() {
let demands = vec![
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 20,
weight: 1,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 20,
weight: 1,
spread: false,
},
];
let allocs = unified_alloc(16, &[16], &demands, &[], &[]);
assert_eq!(allocs[0].total(), 8);
assert_eq!(allocs[1].total(), 8);
assert_eq!(allocs[0].unpinned_budget, 8);
assert_eq!(allocs[1].unpinned_budget, 8);
}
#[test]
fn test_ua_single_layer() {
let demands = vec![demand(1, 10, 5, 20)];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 35);
assert_eq!(allocs[0].pinned[0], 10);
assert_eq!(allocs[0].pinned[1], 5);
assert_eq!(allocs[0].unpinned_budget, 20);
}
#[test]
fn test_ua_conservation_no_stranded() {
let demands = vec![demand(1, 40, 0, 40), demand(1, 0, 40, 40)];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_empty() {
let allocs = unified_alloc(96, &caps_2n(), &[], &[], &[]);
assert!(allocs.is_empty());
}
#[test]
fn test_ua_all_zero_demand() {
let demands = vec![demand(1, 0, 0, 0), demand(1, 0, 0, 0)];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
for a in &allocs {
assert_eq!(a.total(), 0);
}
}
#[test]
fn test_ua_single_node_weight_proportional() {
let demands = vec![
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 100,
weight: 2,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 100,
weight: 1,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 100,
weight: 1,
spread: false,
},
];
let allocs = unified_alloc(32, &[32], &demands, &[], &[]);
assert_eq!(allocs[0].total(), 16);
assert_eq!(allocs[1].total(), 8);
assert_eq!(allocs[2].total(), 8);
assert_eq!(total_alloc(&allocs), 32);
}
#[test]
fn test_ua_single_node_demand_capped() {
let demands = vec![
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 5,
weight: 1,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 100,
weight: 1,
spread: false,
},
];
let allocs = unified_alloc(32, &[32], &demands, &[], &[]);
assert_eq!(allocs[0].total(), 5);
assert_eq!(allocs[1].total(), 27);
}
#[test]
fn test_ua_floor_low_weight_gets_one() {
let demands = vec![
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 50,
weight: 100,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 10,
weight: 1,
spread: false,
},
];
let allocs = unified_alloc(4, &[4], &demands, &[], &[]);
assert!(
allocs[1].total() >= 1,
"low-weight layer must get >= 1, got {}",
allocs[1].total()
);
assert_eq!(total_alloc(&allocs), 4);
}
#[test]
fn test_ua_floor_zero_demand_stays_zero() {
let demands = vec![
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 50,
weight: 100,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 0,
weight: 1,
spread: false,
},
];
let allocs = unified_alloc(4, &[4], &demands, &[], &[]);
assert_eq!(allocs[1].total(), 0);
}
#[test]
fn test_ua_floor_multiple_low_weight() {
let demands = vec![
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 50,
weight: 100,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 5,
weight: 1,
spread: false,
},
LayerDemand {
raw_pinned: vec![0],
raw_unpinned: 5,
weight: 1,
spread: false,
},
];
let allocs = unified_alloc(6, &[6], &demands, &[], &[]);
assert!(allocs[1].total() >= 1);
assert!(allocs[2].total() >= 1);
assert_eq!(total_alloc(&allocs), 6);
}
fn demand_spread(w: usize, p0: usize, p1: usize, u: usize) -> LayerDemand {
LayerDemand {
raw_pinned: vec![p0, p1],
raw_unpinned: u,
weight: w,
spread: true,
}
}
#[test]
fn test_ua_spread_even_split() {
let demands = vec![demand_spread(1, 10, 10, 20)];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 40);
assert_eq!(allocs[0].pinned, vec![0, 0]);
assert_eq!(allocs[0].unpinned[0], 20);
assert_eq!(allocs[0].unpinned[1], 20);
}
#[test]
fn test_ua_spread_odd_budget() {
let demands = vec![demand_spread(1, 10, 10, 21)];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 41);
assert_eq!(allocs[0].unpinned[0], 21);
assert_eq!(allocs[0].unpinned[1], 20);
}
#[test]
fn test_ua_spread_vs_nonspread() {
let demands = vec![
demand_spread(1, 20, 20, 50), demand(1, 20, 0, 70), ];
let cur = vec![vec![20, 20], vec![20, 0]];
let norders = vec![vec![0, 1], vec![0, 1]];
let allocs = unified_alloc(96, &caps_2n(), &demands, &cur, &norders);
assert_eq!(allocs[0].total(), 48);
assert_eq!(allocs[0].unpinned[0], 24);
assert_eq!(allocs[0].unpinned[1], 24);
assert_eq!(allocs[1].total(), 48);
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_spread_demand_capped() {
let demands = vec![
demand_spread(1, 5, 5, 10), demand(1, 30, 10, 40), ];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 20);
assert_eq!(allocs[0].unpinned[0], 10);
assert_eq!(allocs[0].unpinned[1], 10);
assert_eq!(allocs[1].total(), 76);
assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_spread_node_capacity() {
let demands = vec![
demand_spread(1, 20, 20, 20), demand(1, 30, 0, 30), ];
let cur = vec![vec![24, 24], vec![24, 0]];
let norders = vec![vec![0, 1], vec![0, 1]];
let allocs = unified_alloc(96, &caps_2n(), &demands, &cur, &norders);
assert_eq!(total_alloc(&allocs), 96);
for n in 0..2 {
let node_total: usize = allocs.iter().map(|a| a.pinned[n] + a.unpinned[n]).sum();
assert!(node_total <= 48, "node {} has {} > 48", n, node_total);
}
}
#[test]
fn test_ua_spread_node_cap() {
let demands = vec![
demand(2, 40, 0, 0), demand_spread(1, 0, 0, 40), demand(1, 0, 0, 60), ];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 40); assert_eq!(allocs[1].unpinned[0], 8); assert_eq!(allocs[1].unpinned[1], 8);
assert_eq!(allocs[1].total(), 16); assert_eq!(allocs[2].total(), 40); assert_eq!(total_alloc(&allocs), 96);
}
#[test]
fn test_ua_spread_multi_cap() {
let demands = vec![
demand(1, 40, 0, 0), demand_spread(1, 0, 0, 20), demand_spread(1, 0, 0, 20), ];
let allocs = unified_alloc(96, &caps_2n(), &demands, &[], &[]);
assert_eq!(allocs[0].total(), 40); assert_eq!(allocs[1].unpinned[0], 4);
assert_eq!(allocs[1].unpinned[1], 4);
assert_eq!(allocs[1].total(), 8);
assert_eq!(allocs[2].unpinned[0], 4);
assert_eq!(allocs[2].unpinned[1], 4);
assert_eq!(allocs[2].total(), 8);
}
}