|
| 1 | +use std::{collections::BTreeMap, time::Instant}; |
| 2 | + |
| 3 | +use aries::{core::state::Evaluable, prelude::*}; |
| 4 | +use aries_plan_engine::{ |
| 5 | + encode::{encoding::Encoding, tags::Tag}, |
| 6 | + plans::{ |
| 7 | + Operation, |
| 8 | + lifted_plan::{LiftedPlan, ObjectOrVariable}, |
| 9 | + }, |
| 10 | +}; |
| 11 | +use planx::{Model, Res, Sym}; |
| 12 | +use timelines::{Sched, explain::ExplainableSolver}; |
| 13 | + |
| 14 | +use crate::optimize_plan::{self, Objective}; |
| 15 | + |
| 16 | +pub type RelaxableConstraint = Tag; |
| 17 | + |
| 18 | +#[derive(clap::Args, Debug, Clone)] |
| 19 | +pub struct Options { |
| 20 | + /// Defines the maximum number of instances per action template. |
| 21 | + /// |
| 22 | + /// For instance, if set to 3, the resulting plan may have *at most* three instances |
| 23 | + /// of a `pick` action and at most 3 instances of a `drop` action. |
| 24 | + #[arg(short, long)] |
| 25 | + pub max_instances: usize, |
| 26 | + |
| 27 | + /// Defines the objective to be minimized |
| 28 | + #[arg(short, long, default_value("original"))] |
| 29 | + pub objective: Objective, |
| 30 | + |
| 31 | + /// If set, the planner will try tro find the optimal solution |
| 32 | + #[arg(long)] |
| 33 | + pub optimize: bool, |
| 34 | +} |
| 35 | + |
| 36 | +pub fn solve_finite_planning_problem(model: &Model, options: &Options) -> Res<()> { |
| 37 | + // create a dummy plan with the appropriate number of actions |
| 38 | + // this is temporary a workaround to reuse the existing `optimize_plan` facilities |
| 39 | + let plan = &new_empty_lifted_plan(model, BTreeMap::new(), options.max_instances)?; |
| 40 | + |
| 41 | + let start = Instant::now(); |
| 42 | + let (mut solver, encoding, _sched) = encode_finite_planning_problem(model, plan, options)?; |
| 43 | + |
| 44 | + let _encoding_time = start.elapsed().as_millis(); |
| 45 | + |
| 46 | + let objective = encoding.objective.unwrap(); //TODO: error message |
| 47 | + |
| 48 | + // set the objective to a constant if we are not optimizing |
| 49 | + let solver_objective = if options.optimize { objective } else { 0.into() }; |
| 50 | + |
| 51 | + let print = |sol: &Solution| { |
| 52 | + println!("\n==== Plan (objective: {}) =====", objective.evaluate(sol).unwrap()); |
| 53 | + println!("{}\n", encoding.plan(sol)); |
| 54 | + }; |
| 55 | + |
| 56 | + if let Some(solution) = solver.find_optimal(solver_objective, &print) { |
| 57 | + println!("\n> Found {}solution:", if options.optimize { "optimal " } else { "" }); |
| 58 | + print(&solution); |
| 59 | + } else { |
| 60 | + println!("No solution !!!!"); |
| 61 | + } |
| 62 | + Ok(()) |
| 63 | +} |
| 64 | + |
| 65 | +fn encode_finite_planning_problem( |
| 66 | + model: &Model, |
| 67 | + lifted_plan: &LiftedPlan, |
| 68 | + options: &Options, |
| 69 | +) -> Res<(ExplainableSolver<RelaxableConstraint>, Encoding, Sched)> { |
| 70 | + // TODO: make specific function. |
| 71 | + // - ability to specify explanations vocabulary via RelaxableConstraint (Tag), including removing (pre)conditions (like in domain repair). |
| 72 | + |
| 73 | + optimize_plan::encode_plan_optimization_problem( |
| 74 | + model, |
| 75 | + lifted_plan, |
| 76 | + &optimize_plan::Options { |
| 77 | + relaxation: vec![ |
| 78 | + optimize_plan::Relaxation::ActionPresence, |
| 79 | + optimize_plan::Relaxation::StartTime, |
| 80 | + ], |
| 81 | + objective: options.objective, |
| 82 | + }, |
| 83 | + ) |
| 84 | +} |
| 85 | + |
| 86 | +fn new_empty_lifted_plan( |
| 87 | + model: &Model, |
| 88 | + a_instances_per_template: BTreeMap<planx::ActionRef, usize>, |
| 89 | + a_instances_default: usize, |
| 90 | +) -> Res<LiftedPlan> { |
| 91 | + let top_type = model.env.types.top_user_type(); |
| 92 | + use planx::errors::*; |
| 93 | + |
| 94 | + let num_instances = |a_name| *a_instances_per_template.get(a_name).unwrap_or(&a_instances_default); |
| 95 | + |
| 96 | + // all actions in the plan |
| 97 | + let mut operations = Vec::with_capacity(model.actions.iter().map(|a| num_instances(&a.name)).sum()); |
| 98 | + |
| 99 | + // all variables appearing in the plan |
| 100 | + let mut variables = BTreeMap::new(); |
| 101 | + |
| 102 | + for a in model.actions.iter() { |
| 103 | + for aid in 0..num_instances(&a.name) { |
| 104 | + let mut arguments = Vec::with_capacity(a.parameters.len()); |
| 105 | + |
| 106 | + for param in a.parameters.iter() { |
| 107 | + let name = Sym::with_source( |
| 108 | + format!("{}.{}.{}", a.name.canonical_str(), aid, param.name().canonical_str()), |
| 109 | + param.name().span_or_default(), |
| 110 | + ); |
| 111 | + let tpe = if let planx::Type::User(tpe) = param.tpe() { |
| 112 | + tpe.to_single_type().unwrap_or_else(|| top_type.clone()) |
| 113 | + } else { |
| 114 | + top_type.clone() |
| 115 | + }; |
| 116 | + |
| 117 | + variables.insert(name.clone(), tpe); |
| 118 | + |
| 119 | + arguments.push(ObjectOrVariable::Variable { name }); |
| 120 | + } |
| 121 | + operations.push(Operation { |
| 122 | + start: 0, |
| 123 | + duration: 0, |
| 124 | + action_ref: a.name.clone(), |
| 125 | + arguments, |
| 126 | + span: None, |
| 127 | + }); |
| 128 | + } |
| 129 | + } |
| 130 | + Ok(LiftedPlan { operations, variables }) |
| 131 | +} |
0 commit comments