use crate::ground::{ground, Outcome};
use crate::resolve::Solved;
use crate::{pddl3, report, resolve};
pub fn run_planner(
domain_src: &str,
problem_src: &str,
opts: &crate::Options,
ipc: bool,
) -> (String, i32) {
let threads = if opts.threads == 0 {
crate::par::num_threads()
} else {
opts.threads
}
.max(1);
let cfg =
crate::search::SearchCfg::from_weights(opts.weight_g, opts.weight_h, opts.max_evaluated);
let mut out = String::new();
out.push_str("\nff: parsing domain file\n");
let domain = match crate::parser::parse_domain(domain_src) {
Ok(d) => d,
Err(e) => {
out.push_str(&format!("\nff: parse error in domain file: {}\n", e));
return (out, 1);
}
};
out.push_str(&format!("domain '{}' defined\n ... done.\n", domain.name));
out.push_str("ff: parsing problem file\n");
let problem = match crate::parser::parse_problem(problem_src) {
Ok(p) => p,
Err(e) => {
out.push_str(&format!("\nff: parse error in problem file: {}\n", e));
return (out, 1);
}
};
out.push_str(&format!("problem '{}' defined\n ... done.\n", problem.name));
let (domain, problem) = match crate::derived::compile(&domain, &problem) {
Ok(dp) => dp,
Err(e) => {
out.push_str(&format!("\nff: {}\n", e));
return (out, 1);
}
};
let (domain, problem, constrained) = match crate::constraints::gate(&domain, &problem) {
Ok(Some((d, p))) => (d, p, true),
Ok(None) => (domain, problem, false),
Err(reason) => {
out.push_str(&format!("\nff: {}\n", reason));
return (out, 1);
}
};
if crate::temporal::is_temporal(&domain) {
let solved = if crate::features::tdecomp() {
crate::tresolve::solve(&domain, &problem, threads)
} else {
crate::temporal::solve(&domain, &problem, threads)
};
match solved {
Some(tp) => {
out.push_str("\nff: found legal plan as follows\n");
out.push_str(&tp.to_ipc());
out.push_str(&format!("\nplan makespan: {:.3}\n", tp.makespan));
return (out, 0);
}
None => {
out.push_str("\n\nno temporal plan found.\n\n");
return (out, 1);
}
}
}
if pddl3::is_pddl3(&problem)
&& (pddl3::has_preferences(&problem) || crate::costs::metric_fluent(&problem).is_none())
{
let code = plan_pddl3(
&mut out,
&domain,
&problem,
opts.optimize,
threads,
cfg,
ipc,
constrained,
);
return (out, code);
}
let task = match ground(&domain, &problem, threads) {
Outcome::EmptyType { kind, pred, ty } => {
out.push_str(&format!(
"\n\n{} {} is declared to use unknown or empty type {}\n",
kind, pred, ty
));
return (out, 1);
}
Outcome::GoalTrue => {
out.push_str("\n\nff: goal can be simplified to TRUE. The empty plan solves it\n\n");
return (out, 1);
}
Outcome::GoalFalse => {
out.push_str("\n\nff: goal can be simplified to FALSE. No plan will solve it\n\n");
return (out, 1);
}
Outcome::GoalUndefinedFluent => {
out.push_str(
"\n\nff: goal accesses a fluent that will never have a defined value. Problem unsolvable.\n\n",
);
return (out, 1);
}
Outcome::Task(t) => t,
};
out.push_str(&report::preamble(threads));
let groups = crate::invariants::synthesize(&domain, &task);
match resolve::solve(&task, threads, cfg, &groups) {
Solved::Plan(mut ops, stats) => {
let cost =
crate::costs::optimize_text(&problem, &task, opts.optimize, threads, cfg, &mut ops);
if constrained {
crate::constraints::strip_end(&task, &mut ops);
}
if ipc {
out.push_str(&report::ipc_plan(&task, &ops, cost.map(|(c, _)| c)));
} else {
out.push('\n');
out.push_str(&report::ff_plan(&task, &ops));
out.push('\n');
}
out.push_str(&report::timing(&stats, threads));
if let Some((c, note)) = cost {
out.push_str(&format!("plan cost: {:.6}{}\n", c, note));
}
(out, 0)
}
Solved::Unsolvable => {
out.push_str("\n\nbest first search space empty! problem proven unsolvable.\n\n");
(out, 0)
}
}
}
#[allow(clippy::too_many_arguments)]
fn plan_pddl3(
out: &mut String,
domain: &crate::types::Domain,
problem: &crate::types::Problem,
optimize: bool,
threads: usize,
cfg: crate::search::SearchCfg,
ipc: bool,
constrained: bool,
) -> i32 {
if !optimize {
return satisficing_fallback(
out,
domain,
problem,
false,
threads,
cfg,
ipc,
"optimize disabled (--satisfice)",
constrained,
);
}
let mut c = pddl3::compile(domain, problem);
if constrained {
c.synthetic
.insert(crate::constraints::END_ACTION.to_string());
}
if let Some(reason) = c.unsupported.clone() {
return satisficing_fallback(
out,
domain,
problem,
true,
threads,
cfg,
ipc,
&reason,
constrained,
);
}
let task = match ground(&c.domain, &c.problem, threads) {
Outcome::EmptyType { kind, pred, ty } => {
out.push_str(&format!(
"\n\n{} {} is declared to use unknown or empty type {}\n",
kind, pred, ty
));
return 1;
}
Outcome::GoalTrue => {
out.push_str("\n\nff: goal can be simplified to TRUE. The empty plan solves it\n\n");
return 1;
}
Outcome::GoalFalse => {
out.push_str("\n\nff: goal can be simplified to FALSE. No plan will solve it\n\n");
return 1;
}
Outcome::GoalUndefinedFluent => {
out.push_str(
"\n\nff: goal accesses a fluent that will never have a defined value. Problem unsolvable.\n\n",
);
return 1;
}
Outcome::Task(t) => t,
};
out.push_str(&report::preamble(threads));
let cf = task
.fluent_id(pddl3::COST_DISP)
.expect("compile injects total-cost");
let forgos: Vec<(usize, f64)> = c
.forgos
.iter()
.filter_map(|(name, w)| {
task.op_display
.iter()
.position(|d| d == name)
.map(|oi| (oi, *w))
})
.collect();
let groups = crate::invariants::synthesize(&c.domain, &task);
match pddl3::metric_optimize(&task, cf, &forgos, &groups, c.folded_metric, threads) {
Some(r) => {
let mut note = String::new();
if c.warn_other {
note.push_str(" metric has terms beyond is-violated/total-cost; optimized the supported part.");
}
if !r.proven {
note.push_str(" search bound hit; value is best-found, not proven optimal.");
}
render_plan(
out,
&task,
&r.ops,
Some(c.display_metric(r.cost)),
threads,
&c,
r.iterations,
ipc,
¬e,
);
0
}
None => {
out.push_str("\n\nbest first search space empty! problem proven unsolvable.\n\n");
0
}
}
}
#[allow(clippy::too_many_arguments)]
fn satisficing_fallback(
out: &mut String,
domain: &crate::types::Domain,
problem: &crate::types::Problem,
optimize: bool,
threads: usize,
cfg: crate::search::SearchCfg,
ipc: bool,
reason: &str,
constrained: bool,
) -> i32 {
let task = match ground(domain, problem, threads) {
Outcome::Task(t) => t,
Outcome::GoalTrue => {
out.push_str("\n\nff: goal can be simplified to TRUE. The empty plan solves it\n\n");
return 1;
}
Outcome::GoalFalse => {
out.push_str("\n\nff: goal can be simplified to FALSE. No plan will solve it\n\n");
return 1;
}
_ => {
out.push_str("\n\nbest first search space empty! problem proven unsolvable.\n\n");
return 0;
}
};
out.push_str(&report::preamble(threads));
let groups = crate::invariants::synthesize(domain, &task);
match resolve::solve(&task, threads, cfg, &groups) {
Solved::Plan(mut ops, stats) => {
let cost =
crate::costs::optimize_text(problem, &task, optimize, threads, cfg, &mut ops);
let note = if cost.is_some() && optimize {
String::new()
} else {
format!(
" PDDL3 metric NOT optimized ({}); returning a satisficing plan.",
reason
)
};
if constrained {
crate::constraints::strip_end(&task, &mut ops);
}
if ipc {
out.push_str(&report::ipc_plan(&task, &ops, cost.map(|(c, _)| c)));
if !note.is_empty() {
out.push_str(&format!(";{}\n", note));
}
} else {
out.push('\n');
out.push_str(&report::ff_plan(&task, &ops));
out.push('\n');
out.push_str(&report::timing(&stats, threads));
if !note.is_empty() {
out.push_str(&format!("note:{}\n", note));
}
}
if let Some((c, n)) = cost {
out.push_str(&format!("plan cost: {:.6}{}\n", c, n));
}
0
}
Solved::Unsolvable => {
out.push_str("\n\nbest first search space empty! problem proven unsolvable.\n\n");
0
}
}
}
#[allow(clippy::too_many_arguments)]
fn render_plan(
out: &mut String,
task: &crate::packed::PackedTask,
ops: &[usize],
cost: Option<f64>,
threads: usize,
c: &pddl3::Compiled,
iterations: usize,
ipc: bool,
note: &str,
) {
let display: Vec<usize> = ops
.iter()
.copied()
.filter(|&oi| {
let name = task.op_display[oi].split_whitespace().next().unwrap_or("");
!c.synthetic.contains(name)
})
.collect();
let ops = &display[..];
if ipc {
out.push_str(&report::ipc_plan(task, ops, cost));
if !note.is_empty() {
out.push_str(&format!(";{}\n", note));
}
} else {
out.push('\n');
out.push_str(&report::ff_plan(task, ops));
out.push('\n');
out.push_str(&report::metric_footer(
cost.unwrap_or(0.0),
iterations,
c.n_prefs,
threads,
c.warn_other,
));
if !note.is_empty() {
out.push_str(&format!("note:{}\n", note));
}
}
}
pub fn run_ff(domain_src: &str, problem_src: &str, opts: &crate::Options) -> (String, i32) {
let threads = if opts.threads == 0 {
crate::par::num_threads()
} else {
opts.threads
}
.max(1);
let cfg =
crate::search::SearchCfg::from_weights(opts.weight_g, opts.weight_h, opts.max_evaluated);
let mut out = String::new();
out.push_str("\nff: parsing domain file\n");
let domain = match crate::parser::parse_domain(domain_src) {
Ok(d) => d,
Err(e) => {
out.push_str(&format!("\nff: parse error in domain file: {}\n", e));
return (out, 1);
}
};
out.push_str(&format!("domain '{}' defined\n ... done.\n", domain.name));
out.push_str("ff: parsing problem file\n");
let problem = match crate::parser::parse_problem(problem_src) {
Ok(p) => p,
Err(e) => {
out.push_str(&format!("\nff: parse error in problem file: {}\n", e));
return (out, 1);
}
};
out.push_str(&format!("problem '{}' defined\n ... done.\n", problem.name));
let (domain, problem) = if !domain.constraints.is_empty() || !problem.constraints.is_empty() {
match crate::derived::compile(&domain, &problem) {
Ok(pair) => pair,
Err(e) => {
out.push_str(&format!("\nff: {}\n", e));
return (out, 1);
}
}
} else {
(domain, problem)
};
let (domain, problem, constrained) = match crate::constraints::gate(&domain, &problem) {
Ok(Some((d, p))) => (d, p, true),
Ok(None) => (domain, problem, false),
Err(reason) => {
out.push_str(&format!("\nff: {}\n", reason));
return (out, 1);
}
};
match ground(&domain, &problem, threads) {
Outcome::EmptyType { kind, pred, ty } => {
out.push_str(&format!(
"\n\n{} {} is declared to use unknown or empty type {}\n",
kind, pred, ty
));
(out, 1)
}
Outcome::GoalTrue => {
out.push_str("\n\nff: goal can be simplified to TRUE. The empty plan solves it\n\n");
(out, 1)
}
Outcome::GoalFalse => {
out.push_str("\n\nff: goal can be simplified to FALSE. No plan will solve it\n\n");
(out, 1)
}
Outcome::GoalUndefinedFluent => {
out.push_str("\n\nff: goal accesses a fluent that will never have a defined value. Problem unsolvable.\n\n");
(out, 1)
}
Outcome::Task(task) => {
let o =
crate::search::plan(&task, threads, cfg, opts.search != crate::Search::BestFirst);
let mut cost = None;
let result = match o.ops {
Some(mut ops) => {
cost = crate::costs::optimize_text(
&problem,
&task,
opts.optimize,
threads,
cfg,
&mut ops,
);
if constrained {
crate::constraints::strip_end(&task, &mut ops);
}
crate::search::PlanResult::Plan {
ops,
advance: Vec::new(),
evaluated: o.evaluated,
max_g: 0,
}
}
None => crate::search::PlanResult::Unsolvable {
evaluated: o.evaluated,
capped: false,
},
};
let (body, code) = crate::output::render(&task, &result, threads);
out.push_str(&body);
if let Some((c, note)) = cost {
out.push_str(&format!("plan cost: {:.6}{}\n", c, note));
}
(out, code)
}
}
}