use crate::packed::PackedTask;
use crate::search::{search_from, PlanResult, SearchCfg};
use crate::types::{Expr, MetricDir, Problem, Term};
const SWEEP_FLOOR: usize = 30_000;
const SWEEP_MULT: usize = 2;
pub fn metric_fluent(problem: &Problem) -> Option<String> {
let (dir, e) = problem.metric.as_ref()?;
if !matches!(dir, MetricDir::Minimize) {
return None;
}
if let Expr::Fluent(name, args) = e {
let mut parts = vec![name.to_uppercase()];
for t in args {
match t {
Term::Const(c) => parts.push(c.to_uppercase()),
Term::Var(_) => return None,
}
}
Some(if parts.len() == 1 {
format!("({})", parts[0])
} else {
format!("({} {})", parts[0], parts[1..].join(" "))
})
} else {
None
}
}
pub fn plan_cost(task: &PackedTask, cf: usize, ops: &[usize]) -> Option<f64> {
let mut s = task.initial();
for &oi in ops {
s = task.apply(oi, &s);
}
if s.fdef[cf] {
Some(s.fv[cf])
} else {
None
}
}
pub struct CostOutcome {
pub ops: Vec<usize>,
pub cost: f64,
pub improved: bool,
pub proven: bool,
pub evaluated: usize,
}
fn sweep_budget(spent: usize, cfg_max: usize) -> usize {
if let Ok(v) = std::env::var("FF_COST_SWEEP_EVALS") {
if let Ok(n) = v.trim().parse::<usize>() {
return n;
}
}
cfg_max
.saturating_sub(spent)
.min((SWEEP_MULT * spent).max(SWEEP_FLOOR))
}
pub fn improve(
task: &PackedTask,
cf: usize,
ops: Vec<usize>,
first_cost: f64,
threads: usize,
base: SearchCfg,
spent: usize,
) -> CostOutcome {
if first_cost <= 0.0 {
return CostOutcome {
ops,
cost: first_cost,
improved: false,
proven: true,
evaluated: 0,
};
}
let budget = sweep_budget(spent, base.max_eval);
if budget == 0 {
return CostOutcome {
ops,
cost: first_cost,
improved: false,
proven: false,
evaluated: 0,
};
}
let cfg = SearchCfg {
max_eval: budget,
anytime: true,
..base.with_cost_weight(1.0).with_cost_h(cf)
};
match search_from(
task,
&task.initial(),
&task.goal_pos,
&task.goal_num,
Some(cf),
first_cost,
threads,
cfg,
&[],
None,
None,
) {
PlanResult::Plan {
ops: better,
evaluated,
..
} => {
match plan_cost(task, cf, &better) {
Some(c) if c < first_cost => CostOutcome {
ops: better,
cost: c,
improved: true,
proven: false,
evaluated,
},
_ => CostOutcome {
ops,
cost: first_cost,
improved: false,
proven: false,
evaluated,
},
}
}
PlanResult::Unsolvable { evaluated, capped } => CostOutcome {
ops,
cost: first_cost,
proven: !capped,
improved: false,
evaluated,
},
}
}
pub fn optimize_text(
problem: &Problem,
task: &PackedTask,
optimize: bool,
threads: usize,
cfg: SearchCfg,
ops: &mut Vec<usize>,
) -> Option<(f64, &'static str)> {
let disp = metric_fluent(problem)?;
let cf = task.fluent_id(&disp)?;
let c0 = plan_cost(task, cf, ops)?;
if !optimize {
return Some((c0, " (not optimized: --satisfice)"));
}
let r = improve(task, cf, std::mem::take(ops), c0, threads, cfg, 0);
*ops = r.ops;
let note = if r.proven {
" (proven optimal)"
} else if r.improved {
" (anytime-improved)"
} else {
""
};
Some((r.cost, note))
}
pub fn improve_length(
task: &PackedTask,
ops: Vec<usize>,
threads: usize,
base: SearchCfg,
spent: usize,
) -> (Vec<usize>, usize, bool) {
let _ = spent; let budget = std::env::var("FF_LEN_SWEEP_EVALS")
.ok()
.and_then(|v| v.trim().parse::<usize>().ok())
.unwrap_or(0)
.min(base.max_eval);
const RUNGS: [f64; 3] = [3.0, 2.0, 1.0];
let per = budget / RUNGS.len();
if per == 0 || ops.is_empty() {
return (ops, 0, false);
}
let mut best = ops;
let mut evals = 0usize;
let mut improved = false;
for wh in RUNGS {
let cfg = SearchCfg {
g_bound: best.len(),
..SearchCfg::from_weights(1.0, wh, Some(per))
};
match search_from(
task,
&task.initial(),
&task.goal_pos,
&task.goal_num,
None,
f64::INFINITY,
threads,
cfg,
&[],
None,
None,
) {
PlanResult::Plan {
ops: cand,
evaluated,
..
} => {
evals += evaluated;
if cand.len() < best.len() {
best = cand;
improved = true;
}
}
PlanResult::Unsolvable { evaluated, .. } => evals += evaluated,
}
}
(best, evals, improved)
}