use crate::packed::{PackedTask, State};
use crate::par;
use crate::search::{solve_subgoal, solve_subgoal_avoiding};
use crate::partition::{interaction_partition, merge_at, merge_with_neighbor, Subgoal};
#[derive(Clone, Copy)]
pub struct Stats {
pub init_groups: usize,
pub final_groups: usize,
pub merges: usize,
pub fallback: bool, }
pub enum Solved {
Plan(Vec<usize>, Stats),
Unsolvable,
}
fn lama_rung(
task: &PackedTask,
start: &State,
g: &Subgoal,
threads: usize,
cfg: crate::search::SearchCfg,
) -> Option<Vec<usize>> {
if std::env::var("FF_NO_LAMA").is_ok() {
return None;
}
const LAMA_CAP: usize = 400_000;
crate::lama::search_subgoal(
task,
start,
&g.pos,
&g.num,
threads,
LAMA_CAP.min(cfg.max_eval),
&[],
)
.map(|(ops, _)| ops)
}
fn replay_ok(task: &PackedTask, state: &State, ops: &[usize], g: &Subgoal) -> bool {
let mut s = state.clone();
for &oi in ops {
if !task.op_applicable(oi, &s) {
return false;
}
s = task.apply(oi, &s);
}
task.goal_met_with(&s, &g.pos, &g.num)
}
pub fn solve(
task: &PackedTask,
threads: usize,
cfg: crate::search::SearchCfg,
mutex_groups: &[Vec<u32>],
) -> Solved {
let init = task.initial();
let mut groups = interaction_partition(task, mutex_groups);
let init_groups = groups.len();
let mut merges = 0usize;
const SUB_CAP: usize = 100_000;
let sub_cfg = crate::search::SearchCfg {
max_eval: SUB_CAP.min(cfg.max_eval),
..cfg
};
loop {
let monolithic = groups.len() == 1;
let subplans: Vec<Option<Vec<usize>>> = if monolithic {
vec![crate::search::plan(task, threads, cfg, true).ops]
} else {
par::par_map(&groups, threads, |g| {
if g.is_empty() {
Some(Vec::new())
} else {
solve_subgoal(task, &init, &g.pos, &g.num, 1, sub_cfg)
.or_else(|| lama_rung(task, &init, g, 1, sub_cfg))
}
})
};
if let Some(i) = subplans.iter().position(|s| s.is_none()) {
if monolithic {
return Solved::Unsolvable;
}
merge_with_neighbor(&mut groups, i);
merges += 1;
continue;
}
let mut state = init.clone();
let mut plan: Vec<usize> = Vec::new();
let mut done = vec![false; groups.len()];
let mut conflict: Option<(usize, Option<usize>)> = None;
for i in 0..groups.len() {
if task.goal_met_with(&state, &groups[i].pos, &groups[i].num) {
done[i] = true;
continue; }
let protected: crate::hash::FxHashSet<u32> = (0..i)
.filter(|&j| done[j])
.flat_map(|j| groups[j].pos.iter().copied())
.collect();
let forbidden: Vec<bool> = if protected.is_empty() {
Vec::new()
} else {
(0..task.n_ops)
.map(|oi| task.del.slice(oi).iter().any(|f| protected.contains(f)))
.collect()
};
let pre = subplans[i].as_ref().unwrap();
let ops = if protected.is_empty() && replay_ok(task, &state, pre, &groups[i]) {
pre.clone() } else {
let protected_solve = solve_subgoal_avoiding(
task,
&state,
&groups[i].pos,
&groups[i].num,
&forbidden,
threads,
sub_cfg,
);
match protected_solve
.or_else(|| {
solve_subgoal(
task,
&state,
&groups[i].pos,
&groups[i].num,
threads,
sub_cfg,
)
})
.or_else(|| lama_rung(task, &state, &groups[i], threads, sub_cfg))
{
Some(o) => o,
None => {
conflict = Some((i, None));
break;
}
}
};
let mut ns = state.clone();
for &oi in &ops {
ns = task.apply(oi, &ns);
}
let breaker = (0..i)
.find(|&j| done[j] && !task.goal_met_with(&ns, &groups[j].pos, &groups[j].num));
if let Some(j) = breaker {
conflict = Some((i, Some(j)));
break;
}
state = ns;
plan.extend(ops);
done[i] = true;
}
if conflict.is_none() && task.goal_met_with(&state, &task.goal_pos, &task.goal_num) {
return Solved::Plan(
plan,
Stats {
init_groups,
final_groups: groups.len(),
merges,
fallback: monolithic,
},
);
}
if monolithic {
return Solved::Unsolvable;
}
let last = groups.len() - 1;
match conflict {
Some((i, Some(j))) => merge_at(&mut groups, i, j),
Some((i, None)) => merge_with_neighbor(&mut groups, i),
None => merge_with_neighbor(&mut groups, last),
};
merges += 1;
}
}