Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(planning): Support non-symbolic parameters in tasks and actions #129

Merged
merged 3 commits into from
Feb 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 104 additions & 72 deletions planning/grpc/server/src/chronicles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ static METHOD_TYPE: &str = "★method★";
static FLUENT_TYPE: &str = "★fluent★";
static OBJECT_TYPE: &str = "★object★";

fn from_upf_type(name: &str, types: &TypeHierarchy) -> anyhow::Result<Type> {
let int_type_regex = Regex::new(r"^up:integer\[(-?\d+)\s*,\s*(-?\d+)\]$").unwrap();
if name == "up:bool" {
Ok(Type::Bool)
} else if name == "up:integer" {
// integer type with no bounds
Ok(Type::UNBOUNDED_INT)
} else if let Some(x) = int_type_regex.captures_iter(name).next() {
// integer type with bounds
let lb: IntCst = x[1].parse().unwrap();
let ub: IntCst = x[2].parse().unwrap();
ensure!(lb <= ub, "Invalid bounds [{lb}, {ub}]");
ensure!(lb >= INT_CST_MIN, "Int lower bound is too small: {lb}");
ensure!(ub <= INT_CST_MAX, "Int upper bound is too big: {ub}");
Ok(Type::Int { lb, ub })
} else if name == "up:real" && ASSUME_REALS_ARE_INTS.get() {
Ok(Type::UNBOUNDED_INT)
} else if name.starts_with("up:real") {
Err(anyhow!("Real types are not supported"))
} else if let Some(tpe) = types.id_of(name) {
Ok(Type::Sym(tpe))
} else {
Err(anyhow!("Unsupported type `{}`", name))
}
}

fn build_context(problem: &Problem) -> Result<Ctx, Error> {
// Construct the type hierarchy
let types = {
Expand Down Expand Up @@ -133,33 +159,6 @@ fn build_context(problem: &Problem) -> Result<Ctx, Error> {
.collect();
let symbol_table = SymbolTable::new(types.clone(), symbols)?;

let int_type_regex = Regex::new(r"^up:integer\[(-?\d+)\s*,\s*(-?\d+)\]$").unwrap();

let from_upf_type = |name: &str| {
if name == "up:bool" {
Ok(Type::Bool)
} else if name == "up:integer" {
// integer type with no bounds
Ok(Type::UNBOUNDED_INT)
} else if let Some(x) = int_type_regex.captures_iter(name).next() {
// integer type with bounds
let lb: IntCst = x[1].parse().unwrap();
let ub: IntCst = x[2].parse().unwrap();
ensure!(lb <= ub, "Invalid bounds [{lb}, {ub}]");
ensure!(lb >= INT_CST_MIN, "Int lower bound is too small: {lb}");
ensure!(ub <= INT_CST_MAX, "Int upper bound is too big: {ub}");
Ok(Type::Int { lb, ub })
} else if name == "up:real" && ASSUME_REALS_ARE_INTS.get() {
Ok(Type::UNBOUNDED_INT)
} else if name.starts_with("up:real") {
Err(anyhow!("Real types are not supported"))
} else if let Some(tpe) = types.id_of(name) {
Ok(Type::Sym(tpe))
} else {
Err(anyhow!("Unsupported type `{}`", name))
}
};

let mut state_variables = vec![];
{
for fluent in &problem.fluents {
Expand All @@ -169,15 +168,15 @@ fn build_context(problem: &Problem) -> Result<Ctx, Error> {
let mut signature = Vec::with_capacity(1 + fluent.parameters.len());

for arg in &fluent.parameters {
signature.push(from_upf_type(arg.r#type.as_str()).with_context(|| {
signature.push(from_upf_type(arg.r#type.as_str(), &types).with_context(|| {
format!(
"Invalid parameter type `{}` for fluent parameter `{}`",
arg.r#type, arg.name
)
})?);
}

signature.push(from_upf_type(&fluent.value_type)?);
signature.push(from_upf_type(&fluent.value_type, &types)?);

state_variables.push(Fluent { sym, signature });
}
Expand Down Expand Up @@ -235,7 +234,9 @@ pub fn problem_to_chronicles(problem: &Problem) -> Result<aries_planning::chroni
}

for subtask in &tn.subtasks {
factory.add_subtask(subtask)?;
factory
.add_subtask(subtask)
.with_context(|| format!("Adding initial task {} ({})", subtask.task_name, subtask.id))?;
}

for constraint in &tn.constraints {
Expand Down Expand Up @@ -268,14 +269,16 @@ pub fn problem_to_chronicles(problem: &Problem) -> Result<aries_planning::chroni

for a in &problem.actions {
let cont = Container::Template(templates.len());
let template = read_action(cont, a, &action_costs, &mut context)?;
let template =
read_action(cont, a, &action_costs, &mut context).with_context(|| format!("Adding action: {}", a.name))?;
templates.push(template);
}

if let Some(hierarchy) = &problem.hierarchy {
for method in &hierarchy.methods {
let cont = Container::Template(templates.len());
let template = read_method(cont, method, &mut context)?;
let template =
read_method(cont, method, &mut context).with_context(|| format!("Adding method: {}", method.name))?;
templates.push(template);
}
}
Expand Down Expand Up @@ -504,31 +507,45 @@ impl<'a> ChronicleFactory<'a> {
}

/// Adds a parameter to the chronicle name. This creates a new variable to with teh corresponding type.
fn add_parameter(&mut self, name: impl Into<Sym>, tpe: impl Into<Sym>) -> Result<SVar, Error> {
fn add_parameter(&mut self, name: impl Into<Sym>, tpe: impl Into<Sym>) -> Result<Variable, Error> {
let name = name.into();

let tpe = tpe.into();
let tpe = self
.context
.model
.get_symbol_table()
.types
.id_of(&tpe)
.ok_or_else(|| name.invalid("Unknown argument"))?;
let arg = self.context.model.new_optional_sym_var(
tpe,
self.chronicle.presence,
self.container / VarType::Parameter(name.to_string()),
);
let tpe = from_upf_type(tpe.as_ref(), &self.context.model.get_symbol_table().types)
.with_context(|| format!("Unknown argument type: {tpe}"))?;
let label = self.container / VarType::Parameter(name.to_string());
let arg: Variable = match tpe {
Type::Sym(tpe) => self
.context
.model
.new_optional_sym_var(tpe, self.chronicle.presence, label)
.into(),
Type::Int { lb, ub } => self
.context
.model
.new_optional_ivar(lb, ub, self.chronicle.presence, label)
.into(),
Type::Fixed(denom) => self
.context
.model
.new_optional_fvar(INT_CST_MIN, INT_CST_MAX, denom, self.chronicle.presence, label)
.into(),
Type::Bool => self
.context
.model
.new_optional_bvar(self.chronicle.presence, label)
.into(),
};

// append parameters to the name of the chronicle
self.chronicle.name.push(arg.into());

self.variables.push(arg.into());
self.variables.push(arg);

// add parameters to the mapping
// add parameter to the mapping
let name_string = name.to_string();
assert!(!self.env.parameters.contains_key(&name_string));
self.env.parameters.insert(name_string, arg.into());
self.env.parameters.insert(name_string, arg);

Ok(arg)
}
Expand Down Expand Up @@ -891,14 +908,13 @@ impl<'a> ChronicleFactory<'a> {
let task_index = self.chronicle.subtasks.len() as u32;
let start = self.create_timepoint(VarType::TaskStart(task_index));
let end = self.create_timepoint(VarType::TaskEnd(task_index));
let mut task_name = Vec::with_capacity(subtask.parameters.len() + 1);
task_name.push(str_to_symbol(&subtask.task_name, &self.context.model.shape.symbols)?);
let mut task_name: Vec<Atom> = Vec::with_capacity(subtask.parameters.len() + 1);
let head = str_to_symbol(&subtask.task_name, &self.context.model.shape.symbols)?;
task_name.push(Atom::Sym(head));
for param in &subtask.parameters {
let param = self.reify(param, None)?;
let param: SAtom = param.try_into()?;
task_name.push(param);
}
let task_name = task_name.iter().map(|satom| Atom::Sym(*satom)).collect();
self.chronicle.subtasks.push(SubTask {
id: Some(subtask.id.clone()),
start,
Expand Down Expand Up @@ -1364,24 +1380,31 @@ fn read_action(

// process the arguments of the action, adding them to the parameters of the chronicle and to the name of the action
for param in &action.parameters {
factory.add_parameter(&param.name, &param.r#type)?;
factory
.add_parameter(&param.name, &param.r#type)
.with_context(|| format!("Adding parameter: {}", param.name))?;
}

// set the action's achieved task to be the same as the its name
// note that we wait until all parameters have been add to the name before doing this
factory.chronicle.task = Some(factory.chronicle.name.clone());

// Process the effects of the action
for eff in &action.effects {
factory.add_up_effect(eff)?;
factory
.add_up_effect(eff)
.with_context(|| format!("Adding effect: {eff:?}"))?;
}

for condition in &action.conditions {
factory.add_condition(condition)?;
factory
.add_condition(condition)
.with_context(|| format!("Adding condition: {condition:?}"))?;
}

if let Some(duration) = action.duration.as_ref() {
factory.set_duration(duration)?;
factory
.set_duration(duration)
.with_context(|| format!("Setting duration: {duration:?}"))?;
}

let cost_expr = costs.costs.get(&action.name).or(costs.default.as_ref());
Expand Down Expand Up @@ -1439,20 +1462,27 @@ fn read_activity(
// note that we wait until all parameters have been add to the name before doing this
factory.chronicle.task = None;

// // Process the effects of the action
for eff in &activity.effects {
factory.add_up_effect(eff)?;
factory
.add_up_effect(eff)
.with_context(|| format!("Adding effect: {eff:?}"))?;
}
//
for condition in &activity.conditions {
factory.add_condition(condition)?;
factory
.add_condition(condition)
.with_context(|| format!("Adding condition: {condition:?}"))?;
}
for constraint in &activity.constraints {
factory.enforce(constraint, Some(Span::interval(start, end)))?;
factory
.enforce(constraint, Some(Span::interval(start, end)))
.with_context(|| format!("Adding constraint: {constraint:?}"))?;
}

if let Some(duration) = activity.duration.as_ref() {
factory.set_duration(duration)?;
factory
.set_duration(duration)
.with_context(|| format!("Setting duration: {duration:?}"))?;
}

factory.build_instance(ChronicleOrigin::Original)
Expand Down Expand Up @@ -1527,29 +1557,31 @@ fn read_method(container: Container, method: &up::Method, context: &mut Ctx) ->
.achieved_task
.as_ref()
.with_context(|| format!("Missing achieved task in method: {}", &method.name))?;
let mut task_name = Vec::with_capacity(achieved_task.parameters.len() + 1);
task_name.push(str_to_symbol(
&achieved_task.task_name,
&factory.context.model.shape.symbols,
)?);
let mut task_name: Vec<Atom> = Vec::with_capacity(achieved_task.parameters.len() + 1);
let head = str_to_symbol(&achieved_task.task_name, &factory.context.model.shape.symbols)?;
task_name.push(head.into());
for param in &achieved_task.parameters {
let param = factory.reify(param, None)?;
let param: SAtom = param.try_into()?;
task_name.push(param);
}
let task_name = task_name.iter().map(|satom| Atom::from(*satom)).collect();
factory.chronicle.task = Some(task_name);

for subtask in &method.subtasks {
factory.add_subtask(subtask)?;
factory
.add_subtask(subtask)
.with_context(|| format!("Adding subtask: {} ({})", subtask.task_name, subtask.id))?;
}

for condition in &method.conditions {
factory.add_condition(condition)?;
factory
.add_condition(condition)
.with_context(|| format!("Adding condition {condition:?}"))?;
}

for constraint in &method.constraints {
factory.enforce(constraint, None)?;
factory
.enforce(constraint, None)
.with_context(|| format!("Adding constraint {constraint:?}"))?;
}

factory.build_template(method.name.clone())
Expand Down
Loading