use crate::hash::{FxHashMap, FxHashSet};
use crate::packed::PackedTask;
pub struct ResourceVar {
pub members: Vec<(u32, u32)>,
}
impl ResourceVar {
#[inline]
pub fn occupancy(&self, bits: &[u64]) -> u32 {
for &(f, occ) in &self.members {
if crate::bitset::test(bits, f as usize) {
return occ;
}
}
0
}
}
pub fn detect_resources(task: &PackedTask, groups: &[Vec<u32>], init: &[u64]) -> Vec<ResourceVar> {
let mut out = Vec::new();
for g in groups {
if g.len() < 3 {
continue;
}
let gset: FxHashSet<u32> = g.iter().copied().collect();
let mut adj: FxHashMap<u32, FxHashSet<u32>> = FxHashMap::default();
for &f in g {
adj.entry(f).or_default();
}
for oi in 0..task.n_ops {
let mut dels = task
.del
.slice(oi)
.iter()
.copied()
.filter(|f| gset.contains(f));
let mut adds = task
.add
.slice(oi)
.iter()
.copied()
.filter(|f| gset.contains(f));
let (d0, a0) = (dels.next(), adds.next());
if let (Some(a), None, Some(b), None) = (d0, dels.next(), a0, adds.next()) {
if a != b {
adj.get_mut(&a).unwrap().insert(b);
adj.get_mut(&b).unwrap().insert(a);
}
}
}
let mut endpoints = Vec::new();
let mut shape_ok = true;
for &f in g {
match adj[&f].len() {
1 => endpoints.push(f),
2 => {}
_ => {
shape_ok = false;
break;
}
}
}
if !shape_ok || endpoints.len() != 2 {
continue;
}
let start = match endpoints
.iter()
.copied()
.find(|&f| crate::bitset::test(init, f as usize))
{
Some(f) => f,
None => continue,
};
let mut members = Vec::with_capacity(g.len());
let mut prev: Option<u32> = None;
let mut cur = start;
let mut occ = 0u32;
loop {
members.push((cur, occ));
let next = adj[&cur].iter().copied().find(|&n| Some(n) != prev);
match next {
Some(n) => {
prev = Some(cur);
cur = n;
occ += 1;
}
None => break,
}
}
if members.len() == g.len() {
out.push(ResourceVar { members });
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ground::{ground, Outcome};
use crate::parser::{parse_domain, parse_problem};
const DOM: &str = "(define (domain ctr) (:requirements :typing)
(:types count)
(:predicates (avail ?s - count) (nxt ?lo ?hi - count))
(:action consume :parameters (?a ?b - count)
:precondition (and (avail ?a) (nxt ?b ?a))
:effect (and (not (avail ?a)) (avail ?b)))
(:action restore :parameters (?a ?b - count)
:precondition (and (avail ?a) (nxt ?a ?b))
:effect (and (not (avail ?a)) (avail ?b))))";
const PROB: &str = "(define (problem ctr1) (:domain ctr)
(:objects c0 c1 c2 c3 - count)
(:init (avail c3) (nxt c0 c1) (nxt c1 c2) (nxt c2 c3))
(:goal (avail c0)))";
#[test]
fn detects_counter_and_orders_occupancy_from_full() {
let d = parse_domain(DOM).expect("domain");
let p = parse_problem(PROB).expect("problem");
let task = match ground(&d, &p, 1) {
Outcome::Task(t) => t,
_ => panic!("expected a task"),
};
let groups = crate::invariants::synthesize(&d, &task);
let res = detect_resources(&task, &groups, &task.init_bits);
assert_eq!(res.len(), 1, "exactly one counter resource detected");
let r = &res[0];
assert_eq!(r.members.len(), 4, "4 levels (capacity 3)");
assert_eq!(r.occupancy(&task.init_bits), 0, "full level => 0 in use");
let mut occs: Vec<u32> = r.members.iter().map(|&(_, o)| o).collect();
occs.sort_unstable();
assert_eq!(occs, vec![0, 1, 2, 3], "monotone occupancy along the chain");
}
}