From 0f7fa66200224eeaeeb62cf710237cca1f787133 Mon Sep 17 00:00:00 2001 From: Roland Godet Date: Thu, 28 Nov 2024 11:40:22 +0100 Subject: [PATCH 1/3] refactor(planning): split extract_plan into smaller functions --- planning/planners/src/fmt.rs | 81 ++++++++++++++++++++---------------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/planning/planners/src/fmt.rs b/planning/planners/src/fmt.rs index edc03a12..b472e2f8 100644 --- a/planning/planners/src/fmt.rs +++ b/planning/planners/src/fmt.rs @@ -206,46 +206,55 @@ pub fn format_partial_plan(problem: &FiniteProblem, ass: &Model) -> Result Result> { - let mut plan = Vec::new(); - for ch in &problem.chronicles { - if ass.value(ch.chronicle.presence) != Some(true) { - continue; - } - match ch.chronicle.kind { - ChronicleKind::Problem | ChronicleKind::Method => continue, - _ => {} - } - let start = ass.f_domain(ch.chronicle.start).lb(); - let end = ass.f_domain(ch.chronicle.end).lb(); - let duration = end - start; - let name = format_atom(&ch.chronicle.name[0], &problem.model, ass); - let params = ch.chronicle.name[1..] - .iter() - .map(|atom| ass.evaluate(*atom).unwrap()) - .collect_vec(); - - let instance = ActionInstance { - name, - params, - start, - duration, - }; - - // if the action corresponds to a rolled-up action, unroll it in the solution - let roll_compil = match ch.origin { - ChronicleOrigin::FreeAction { template_id, .. } => problem.meta.action_rolling.get(&template_id), - _ => None, - }; - if let Some(roll_compil) = roll_compil { - plan.extend(roll_compil.unroll(&instance)) - } else { - plan.push(instance); - } - } + let mut plan = problem.chronicles.iter().try_fold(vec![], |p, c| { + let mut r = p.clone(); + r.extend(extract_plan_actions(c, problem, ass)?); + Ok(r) + })?; plan.sort_by_key(|a| a.start); Ok(plan) } +pub fn extract_plan_actions( + ch: &ChronicleInstance, + problem: &FiniteProblem, + ass: &SavedAssignment, +) -> Result> { + if ass.value(ch.chronicle.presence) != Some(true) { + return Ok(vec![]); + } + match ch.chronicle.kind { + ChronicleKind::Problem | ChronicleKind::Method => return Ok(vec![]), + _ => {} + } + let start = ass.f_domain(ch.chronicle.start).lb(); + let end = ass.f_domain(ch.chronicle.end).lb(); + let duration = end - start; + let name = format_atom(&ch.chronicle.name[0], &problem.model, ass); + let params = ch.chronicle.name[1..] + .iter() + .map(|atom| ass.evaluate(*atom).unwrap()) + .collect_vec(); + + let instance = ActionInstance { + name, + params, + start, + duration, + }; + + // if the action corresponds to a rolled-up action, unroll it in the solution + let roll_compil = match ch.origin { + ChronicleOrigin::FreeAction { template_id, .. } => problem.meta.action_rolling.get(&template_id), + _ => None, + }; + Ok(if let Some(roll_compil) = roll_compil { + roll_compil.unroll(&instance) + } else { + vec![instance] + }) +} + fn str(r: Rational32) -> String { let scale = TIME_SCALE.get(); if scale % r.denom() != 0 { From a7ff3426c111cfdeef2114c5215c13b25f381754 Mon Sep 17 00:00:00 2001 From: Roland Godet Date: Thu, 28 Nov 2024 11:41:55 +0100 Subject: [PATCH 2/3] fix(grpc): use planning extract_plan_actions to build the actions This will take into account the potential action unrolling. Also refactoring the conversion of a chronicle to group logic by chronicle kind. --- planning/grpc/server/src/serialize.rs | 115 +++++++++++++++----------- 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/planning/grpc/server/src/serialize.rs b/planning/grpc/server/src/serialize.rs index 09a91a69..7f7ea9f1 100644 --- a/planning/grpc/server/src/serialize.rs +++ b/planning/grpc/server/src/serialize.rs @@ -1,15 +1,16 @@ use anyhow::{ensure, Context, Result}; use aries::core::state::Domains; use aries::model::extensions::AssignmentExt; -use aries::model::lang::{Atom, FAtom, SAtom}; +use aries::model::lang::{Atom, FAtom}; use aries_planners::encoding::ChronicleId; +use aries_planners::fmt::{extract_plan_actions, format_atom}; use aries_planning::chronicles::{ChronicleKind, ChronicleOrigin, FiniteProblem, TaskId}; use std::collections::HashMap; use unified_planning as up; use unified_planning::{Real, Schedule}; pub fn serialize_plan( - _problem_request: &up::Problem, + problem_request: &up::Problem, problem: &FiniteProblem, assignment: &Domains, ) -> Result { @@ -46,39 +47,21 @@ pub fn serialize_plan( if assignment.value(ch.chronicle.presence) != Some(true) { continue; // chronicle is absent, skip } - let start = serialize_time(ch.chronicle.start, assignment)?; - let end = serialize_time(ch.chronicle.end, assignment)?; - - // extract name and parameters (possibly empty if not an action or method chronicle) - let name = match ch.chronicle.kind { - ChronicleKind::Problem => "problem".to_string(), - ChronicleKind::Method | ChronicleKind::Action | ChronicleKind::DurativeAction => { - let name = ch.chronicle.name.first().context("No name for action")?; - let name = SAtom::try_from(*name).context("Action name is not a symbol")?; - let name = assignment.sym_value_of(name).context("Unbound sym var")?; - problem.model.shape.symbols.symbol(name).to_string() - } - }; - let parameters = if ch.chronicle.name.len() > 1 { - ch.chronicle.name[1..] - .iter() - .map(|¶m| serialize_atom(param, problem, assignment)) - .collect::>>()? - } else { - Vec::new() - }; - - // map identifying subtasks of the chronicle - let mut subtasks: HashMap = Default::default(); - for (tid, t) in ch.chronicle.subtasks.iter().enumerate() { - let subtask_up_id = t.id.as_ref().cloned().unwrap_or_default(); - let subtask_id = TaskId { - instance_id: id, - task_id: tid, - }; - subtasks.insert(subtask_up_id, refining_chronicle(subtask_id)?.to_string()); - } + let subtasks: HashMap = ch + .chronicle + .subtasks + .iter() + .enumerate() + .map(|(tid, t)| { + let subtask_up_id = t.id.as_ref().cloned().unwrap_or_default(); + let subtask_id = TaskId { + instance_id: id, + task_id: tid, + }; + (subtask_up_id, refining_chronicle(subtask_id).unwrap().to_string()) + }) + .collect(); match ch.chronicle.kind { ChronicleKind::Problem => { @@ -86,18 +69,17 @@ pub fn serialize_plan( ensure!(hier.root_tasks.is_empty(), "More than one set of root tasks."); hier.root_tasks = subtasks; } - ChronicleKind::Action | ChronicleKind::DurativeAction => { - ensure!(subtasks.is_empty(), "Action with subtasks."); - actions.push(up::ActionInstance { - id: id.to_string(), - action_name: name.to_string(), - parameters, - start_time: Some(start), - end_time: Some(end), - }); - } - ChronicleKind::Method => { + let name = match ch.chronicle.kind { + ChronicleKind::Problem => "problem".to_string(), + ChronicleKind::Method | ChronicleKind::Action | ChronicleKind::DurativeAction => { + format_atom(&ch.chronicle.name[0], &problem.model, assignment) + } + }; + let parameters = ch.chronicle.name[1..] + .iter() + .map(|¶m| serialize_atom(param, problem, assignment)) + .collect::>>()?; hier.methods.push(up::MethodInstance { id: id.to_string(), method_name: name.to_string(), @@ -105,7 +87,40 @@ pub fn serialize_plan( subtasks, }); } - } + ChronicleKind::Action | ChronicleKind::DurativeAction => { + ensure!(subtasks.is_empty(), "Action with subtasks."); + let instances = extract_plan_actions(ch, problem, assignment)?; + actions.extend( + instances + .iter() + .map(|a| { + // The id is used in HTNs plans where there are no rolling + let id = if problem_request.hierarchy.is_some() { + ensure!(instances.len() == 1, "Rolling in HTN plan"); + id.to_string() + } else { + (actions.len() + id).to_string() + }; + let parameters = a + .params + .iter() + .map(|&p| serialize_atom(p.into(), problem, assignment)) + .collect::>>()?; + let start_time = Some(serialize_time(a.start.into(), assignment)?); + let end_time = Some(serialize_time((a.start + a.duration).into(), assignment)?); + + Ok(up::ActionInstance { + id, + action_name: a.name.to_string(), + parameters, + start_time, + end_time, + }) + }) + .collect::>>()?, + ); + } + }; } // sort actions by increasing start time. actions.sort_by_key(|a| real_to_rational(a.start_time.as_ref().unwrap())); @@ -113,7 +128,7 @@ pub fn serialize_plan( fn is_temporal(feature: i32) -> bool { feature == (up::Feature::ContinuousTime as i32) || feature == (up::Feature::DiscreteTime as i32) } - if !_problem_request.features.iter().any(|feature| is_temporal(*feature)) { + if !problem_request.features.iter().any(|feature| is_temporal(*feature)) { // the problem is not temporal, remove time annotations // Note that the sorting done earlier ensures the plan is a valid sequence for action in &mut actions { @@ -122,7 +137,7 @@ pub fn serialize_plan( } } - let hierarchy = if _problem_request.hierarchy.is_some() { + let hierarchy = if problem_request.hierarchy.is_some() { Some(hier) } else { None @@ -130,7 +145,7 @@ pub fn serialize_plan( // If this is a scheduling problem, interpret all actions as activities // TODO: currently, variables are not supported. - let schedule = if _problem_request.scheduling_extension.is_some() { + let schedule = if problem_request.scheduling_extension.is_some() { let mut schedule = Schedule { activities: vec![], variable_assignments: Default::default(), @@ -148,7 +163,7 @@ pub fn serialize_plan( ); if !a.parameters.is_empty() { // Search for the corresponding activity definition - let act = _problem_request + let act = problem_request .scheduling_extension .as_ref() .expect("Missing scheduling extension") From 2eac1611f62140e4eba9e791d5f83708482aa333 Mon Sep 17 00:00:00 2001 From: Arthur Bit-Monnot Date: Thu, 28 Nov 2024 22:23:45 +0100 Subject: [PATCH 3/3] feat(planning): re-enable action rolling by default --- planning/planning/src/chronicles/preprocessing/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/planning/planning/src/chronicles/preprocessing/mod.rs b/planning/planning/src/chronicles/preprocessing/mod.rs index 42ed61ec..6fdd98f9 100644 --- a/planning/planning/src/chronicles/preprocessing/mod.rs +++ b/planning/planning/src/chronicles/preprocessing/mod.rs @@ -12,7 +12,7 @@ static PREPRO_STATE_VARS: EnvParam = EnvParam::new("ARIES_PLANNING_PREPRO_ static PREPRO_MUTEX_PREDICATES: EnvParam = EnvParam::new("ARIES_PLANNING_PREPRO_MUTEX", "true"); static PREPRO_UNUSABLE_EFFECTS: EnvParam = EnvParam::new("ARIES_PLANNING_PREPRO_UNUSABLE_EFFECTS", "true"); static PREPRO_MERGE_STATEMENTS: EnvParam = EnvParam::new("ARIES_PLANNING_PREPRO_MERGE_STATEMENTS", "true"); -static PREPRO_ROLL_ACTIONS: EnvParam = EnvParam::new("ARIES_ROLL", "false"); +static PREPRO_ROLL_ACTIONS: EnvParam = EnvParam::new("ARIES_ROLL", "true"); use crate::chronicles::Problem; pub use merge_conditions_effects::merge_conditions_effects;