Skip to content

Commit b1b7af3

Browse files
authored
Merge pull request #155 from plaans/fix/rolling
Fix and re-enable rolling: the plan was not unrolled in the unified-planning library.
2 parents f4c80b5 + 2eac161 commit b1b7af3

File tree

3 files changed

+111
-87
lines changed

3 files changed

+111
-87
lines changed

planning/grpc/server/src/serialize.rs

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use anyhow::{ensure, Context, Result};
22
use aries::core::state::Domains;
33
use aries::model::extensions::AssignmentExt;
4-
use aries::model::lang::{Atom, FAtom, SAtom};
4+
use aries::model::lang::{Atom, FAtom};
55
use aries_planners::encoding::ChronicleId;
6+
use aries_planners::fmt::{extract_plan_actions, format_atom};
67
use aries_planning::chronicles::{ChronicleKind, ChronicleOrigin, FiniteProblem, TaskId};
78
use std::collections::HashMap;
89
use unified_planning as up;
910
use unified_planning::{Real, Schedule};
1011

1112
pub fn serialize_plan(
12-
_problem_request: &up::Problem,
13+
problem_request: &up::Problem,
1314
problem: &FiniteProblem,
1415
assignment: &Domains,
1516
) -> Result<unified_planning::Plan> {
@@ -46,74 +47,88 @@ pub fn serialize_plan(
4647
if assignment.value(ch.chronicle.presence) != Some(true) {
4748
continue; // chronicle is absent, skip
4849
}
49-
let start = serialize_time(ch.chronicle.start, assignment)?;
50-
let end = serialize_time(ch.chronicle.end, assignment)?;
51-
52-
// extract name and parameters (possibly empty if not an action or method chronicle)
53-
let name = match ch.chronicle.kind {
54-
ChronicleKind::Problem => "problem".to_string(),
55-
ChronicleKind::Method | ChronicleKind::Action | ChronicleKind::DurativeAction => {
56-
let name = ch.chronicle.name.first().context("No name for action")?;
57-
let name = SAtom::try_from(*name).context("Action name is not a symbol")?;
58-
let name = assignment.sym_value_of(name).context("Unbound sym var")?;
59-
problem.model.shape.symbols.symbol(name).to_string()
60-
}
61-
};
6250

63-
let parameters = if ch.chronicle.name.len() > 1 {
64-
ch.chronicle.name[1..]
65-
.iter()
66-
.map(|&param| serialize_atom(param, problem, assignment))
67-
.collect::<Result<Vec<_>>>()?
68-
} else {
69-
Vec::new()
70-
};
71-
72-
// map identifying subtasks of the chronicle
73-
let mut subtasks: HashMap<String, String> = Default::default();
74-
for (tid, t) in ch.chronicle.subtasks.iter().enumerate() {
75-
let subtask_up_id = t.id.as_ref().cloned().unwrap_or_default();
76-
let subtask_id = TaskId {
77-
instance_id: id,
78-
task_id: tid,
79-
};
80-
subtasks.insert(subtask_up_id, refining_chronicle(subtask_id)?.to_string());
81-
}
51+
let subtasks: HashMap<String, String> = ch
52+
.chronicle
53+
.subtasks
54+
.iter()
55+
.enumerate()
56+
.map(|(tid, t)| {
57+
let subtask_up_id = t.id.as_ref().cloned().unwrap_or_default();
58+
let subtask_id = TaskId {
59+
instance_id: id,
60+
task_id: tid,
61+
};
62+
(subtask_up_id, refining_chronicle(subtask_id).unwrap().to_string())
63+
})
64+
.collect();
8265

8366
match ch.chronicle.kind {
8467
ChronicleKind::Problem => {
8568
// base chronicles, its subtasks are the problem's subtasks
8669
ensure!(hier.root_tasks.is_empty(), "More than one set of root tasks.");
8770
hier.root_tasks = subtasks;
8871
}
89-
ChronicleKind::Action | ChronicleKind::DurativeAction => {
90-
ensure!(subtasks.is_empty(), "Action with subtasks.");
91-
actions.push(up::ActionInstance {
92-
id: id.to_string(),
93-
action_name: name.to_string(),
94-
parameters,
95-
start_time: Some(start),
96-
end_time: Some(end),
97-
});
98-
}
99-
10072
ChronicleKind::Method => {
73+
let name = match ch.chronicle.kind {
74+
ChronicleKind::Problem => "problem".to_string(),
75+
ChronicleKind::Method | ChronicleKind::Action | ChronicleKind::DurativeAction => {
76+
format_atom(&ch.chronicle.name[0], &problem.model, assignment)
77+
}
78+
};
79+
let parameters = ch.chronicle.name[1..]
80+
.iter()
81+
.map(|&param| serialize_atom(param, problem, assignment))
82+
.collect::<Result<Vec<_>>>()?;
10183
hier.methods.push(up::MethodInstance {
10284
id: id.to_string(),
10385
method_name: name.to_string(),
10486
parameters,
10587
subtasks,
10688
});
10789
}
108-
}
90+
ChronicleKind::Action | ChronicleKind::DurativeAction => {
91+
ensure!(subtasks.is_empty(), "Action with subtasks.");
92+
let instances = extract_plan_actions(ch, problem, assignment)?;
93+
actions.extend(
94+
instances
95+
.iter()
96+
.map(|a| {
97+
// The id is used in HTNs plans where there are no rolling
98+
let id = if problem_request.hierarchy.is_some() {
99+
ensure!(instances.len() == 1, "Rolling in HTN plan");
100+
id.to_string()
101+
} else {
102+
(actions.len() + id).to_string()
103+
};
104+
let parameters = a
105+
.params
106+
.iter()
107+
.map(|&p| serialize_atom(p.into(), problem, assignment))
108+
.collect::<Result<Vec<_>>>()?;
109+
let start_time = Some(serialize_time(a.start.into(), assignment)?);
110+
let end_time = Some(serialize_time((a.start + a.duration).into(), assignment)?);
111+
112+
Ok(up::ActionInstance {
113+
id,
114+
action_name: a.name.to_string(),
115+
parameters,
116+
start_time,
117+
end_time,
118+
})
119+
})
120+
.collect::<Result<Vec<_>>>()?,
121+
);
122+
}
123+
};
109124
}
110125
// sort actions by increasing start time.
111126
actions.sort_by_key(|a| real_to_rational(a.start_time.as_ref().unwrap()));
112127

113128
fn is_temporal(feature: i32) -> bool {
114129
feature == (up::Feature::ContinuousTime as i32) || feature == (up::Feature::DiscreteTime as i32)
115130
}
116-
if !_problem_request.features.iter().any(|feature| is_temporal(*feature)) {
131+
if !problem_request.features.iter().any(|feature| is_temporal(*feature)) {
117132
// the problem is not temporal, remove time annotations
118133
// Note that the sorting done earlier ensures the plan is a valid sequence
119134
for action in &mut actions {
@@ -122,15 +137,15 @@ pub fn serialize_plan(
122137
}
123138
}
124139

125-
let hierarchy = if _problem_request.hierarchy.is_some() {
140+
let hierarchy = if problem_request.hierarchy.is_some() {
126141
Some(hier)
127142
} else {
128143
None
129144
};
130145

131146
// If this is a scheduling problem, interpret all actions as activities
132147
// TODO: currently, variables are not supported.
133-
let schedule = if _problem_request.scheduling_extension.is_some() {
148+
let schedule = if problem_request.scheduling_extension.is_some() {
134149
let mut schedule = Schedule {
135150
activities: vec![],
136151
variable_assignments: Default::default(),
@@ -148,7 +163,7 @@ pub fn serialize_plan(
148163
);
149164
if !a.parameters.is_empty() {
150165
// Search for the corresponding activity definition
151-
let act = _problem_request
166+
let act = problem_request
152167
.scheduling_extension
153168
.as_ref()
154169
.expect("Missing scheduling extension")

planning/planners/src/fmt.rs

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -206,46 +206,55 @@ pub fn format_partial_plan(problem: &FiniteProblem, ass: &Model) -> Result<Strin
206206
}
207207

208208
pub fn extract_plan(problem: &FiniteProblem, ass: &SavedAssignment) -> Result<Vec<ActionInstance>> {
209-
let mut plan = Vec::new();
210-
for ch in &problem.chronicles {
211-
if ass.value(ch.chronicle.presence) != Some(true) {
212-
continue;
213-
}
214-
match ch.chronicle.kind {
215-
ChronicleKind::Problem | ChronicleKind::Method => continue,
216-
_ => {}
217-
}
218-
let start = ass.f_domain(ch.chronicle.start).lb();
219-
let end = ass.f_domain(ch.chronicle.end).lb();
220-
let duration = end - start;
221-
let name = format_atom(&ch.chronicle.name[0], &problem.model, ass);
222-
let params = ch.chronicle.name[1..]
223-
.iter()
224-
.map(|atom| ass.evaluate(*atom).unwrap())
225-
.collect_vec();
226-
227-
let instance = ActionInstance {
228-
name,
229-
params,
230-
start,
231-
duration,
232-
};
233-
234-
// if the action corresponds to a rolled-up action, unroll it in the solution
235-
let roll_compil = match ch.origin {
236-
ChronicleOrigin::FreeAction { template_id, .. } => problem.meta.action_rolling.get(&template_id),
237-
_ => None,
238-
};
239-
if let Some(roll_compil) = roll_compil {
240-
plan.extend(roll_compil.unroll(&instance))
241-
} else {
242-
plan.push(instance);
243-
}
244-
}
209+
let mut plan = problem.chronicles.iter().try_fold(vec![], |p, c| {
210+
let mut r = p.clone();
211+
r.extend(extract_plan_actions(c, problem, ass)?);
212+
Ok(r)
213+
})?;
245214
plan.sort_by_key(|a| a.start);
246215
Ok(plan)
247216
}
248217

218+
pub fn extract_plan_actions(
219+
ch: &ChronicleInstance,
220+
problem: &FiniteProblem,
221+
ass: &SavedAssignment,
222+
) -> Result<Vec<ActionInstance>> {
223+
if ass.value(ch.chronicle.presence) != Some(true) {
224+
return Ok(vec![]);
225+
}
226+
match ch.chronicle.kind {
227+
ChronicleKind::Problem | ChronicleKind::Method => return Ok(vec![]),
228+
_ => {}
229+
}
230+
let start = ass.f_domain(ch.chronicle.start).lb();
231+
let end = ass.f_domain(ch.chronicle.end).lb();
232+
let duration = end - start;
233+
let name = format_atom(&ch.chronicle.name[0], &problem.model, ass);
234+
let params = ch.chronicle.name[1..]
235+
.iter()
236+
.map(|atom| ass.evaluate(*atom).unwrap())
237+
.collect_vec();
238+
239+
let instance = ActionInstance {
240+
name,
241+
params,
242+
start,
243+
duration,
244+
};
245+
246+
// if the action corresponds to a rolled-up action, unroll it in the solution
247+
let roll_compil = match ch.origin {
248+
ChronicleOrigin::FreeAction { template_id, .. } => problem.meta.action_rolling.get(&template_id),
249+
_ => None,
250+
};
251+
Ok(if let Some(roll_compil) = roll_compil {
252+
roll_compil.unroll(&instance)
253+
} else {
254+
vec![instance]
255+
})
256+
}
257+
249258
fn str(r: Rational32) -> String {
250259
let scale = TIME_SCALE.get();
251260
if scale % r.denom() != 0 {

planning/planning/src/chronicles/preprocessing/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static PREPRO_STATE_VARS: EnvParam<bool> = EnvParam::new("ARIES_PLANNING_PREPRO_
1212
static PREPRO_MUTEX_PREDICATES: EnvParam<bool> = EnvParam::new("ARIES_PLANNING_PREPRO_MUTEX", "true");
1313
static PREPRO_UNUSABLE_EFFECTS: EnvParam<bool> = EnvParam::new("ARIES_PLANNING_PREPRO_UNUSABLE_EFFECTS", "true");
1414
static PREPRO_MERGE_STATEMENTS: EnvParam<bool> = EnvParam::new("ARIES_PLANNING_PREPRO_MERGE_STATEMENTS", "true");
15-
static PREPRO_ROLL_ACTIONS: EnvParam<bool> = EnvParam::new("ARIES_ROLL", "false");
15+
static PREPRO_ROLL_ACTIONS: EnvParam<bool> = EnvParam::new("ARIES_ROLL", "true");
1616

1717
use crate::chronicles::Problem;
1818
pub use merge_conditions_effects::merge_conditions_effects;

0 commit comments

Comments
 (0)