diff --git a/planning/grpc/server/src/chronicles.rs b/planning/grpc/server/src/chronicles.rs index 604c4925..991329bb 100644 --- a/planning/grpc/server/src/chronicles.rs +++ b/planning/grpc/server/src/chronicles.rs @@ -895,12 +895,47 @@ impl<'a> ChronicleFactory<'a> { } fn set_cost(&mut self, cost: &Expression) -> Result<(), Error> { - ensure!(kind(cost)? == ExpressionKind::Constant); - ensure!(cost.r#type == "up:integer"); - let cost = match cost.atom.as_ref().unwrap().content.as_ref().unwrap() { - Content::Int(i) => *i as IntCst, - _ => bail!("Unexpected cost type."), + let tpe = from_upf_type(cost.r#type.as_ref(), &self.context.model.get_symbol_table().types) + .with_context(|| format!("Unknown argument type: {0}", cost.r#type))?; + ensure!(tpe.is_numeric()); + + let cost = match kind(cost)? { + ExpressionKind::Constant => { + ensure!(cost.r#type == "up:integer"); + Cost::Fixed(match cost.atom.as_ref().unwrap().content.as_ref().unwrap() { + Content::Int(i) => *i as IntCst, + _ => bail!("Unexpected cost type."), + }) + } + ExpressionKind::Parameter => { + let name = match cost.atom.as_ref().unwrap().content.as_ref().unwrap() { + Content::Symbol(s) => s.clone(), + _ => bail!("Unexpected cost parameter name type."), + }; + let var = self + .env + .parameters + .get(&name) + .with_context(|| format!("Unknown parameter: {name}"))?; + match var { + Variable::Int(var) => Cost::Variable(*var), + _ => bail!("Cost parameter must be an integer variable."), + } + } + _ => bail!("Unsupported cost expression: {cost:?}"), }; + + match cost { + Cost::Fixed(cost) => ensure!(cost >= 0, "Cost must be a non-negative integer ({cost})"), + Cost::Variable(..) => match tpe { + Type::Int { lb, .. } => ensure!( + lb >= 0, + "Cost variable must be a non-negative integer (lower bound = {lb})" + ), + _ => bail!("Cost variable must be an integer variable."), + }, + }; + self.chronicle.cost = Some(cost); Ok(()) } diff --git a/planning/planners/src/encode.rs b/planning/planners/src/encode.rs index f3a4f581..e4d5ce4d 100644 --- a/planning/planners/src/encode.rs +++ b/planning/planners/src/encode.rs @@ -428,8 +428,13 @@ pub fn add_metric(pb: &FiniteProblem, model: &mut Model, metric: Metric) -> IAto // retrieve the presence and cost of each chronicle let mut costs = Vec::with_capacity(8); for (ch_id, ch) in pb.chronicles.iter().enumerate() { - if let Some(cost) = ch.chronicle.cost { - assert!(cost >= 0, "A chronicle has a negative cost"); + if let Some(cost) = &ch.chronicle.cost { + match cost { + Cost::Fixed(c) => assert!(*c >= 0, "A chronicle has a negative cost"), + Cost::Variable(v) => { + assert!(model.domain_of(*v).0 >= 0, "A chronicle could have a negative cost") + } + }; costs.push((ch_id, ch.chronicle.presence, cost)); } } @@ -438,8 +443,12 @@ pub fn add_metric(pb: &FiniteProblem, model: &mut Model, metric: Metric) -> IAto let action_costs: Vec = costs .iter() .map(|&(ch_id, p, cost)| { + let bounds = match cost { + Cost::Fixed(c) => (*c, *c), + Cost::Variable(v) => model.domain_of(*v), + }; model - .new_optional_ivar(cost, cost, p, Container::Instance(ch_id).var(VarType::Cost)) + .new_optional_ivar(bounds.0, bounds.1, p, Container::Instance(ch_id).var(VarType::Cost)) .or_zero(p) }) .collect(); diff --git a/planning/planning/src/chronicles/concrete.rs b/planning/planning/src/chronicles/concrete.rs index a23b79b3..857c51c6 100644 --- a/planning/planning/src/chronicles/concrete.rs +++ b/planning/planning/src/chronicles/concrete.rs @@ -280,6 +280,24 @@ impl Substitute for StateVar { } } +/// The cost of a chronicle +#[derive(Clone, Debug)] +pub enum Cost { + /// The cost is a fixed integer value + Fixed(IntCst), + /// The cost is a chronicle's variable + Variable(IVar), +} + +impl Substitute for Cost { + fn substitute(&self, s: &impl Substitution) -> Self { + match self { + Cost::Fixed(x) => Cost::Fixed(*x), + Cost::Variable(x) => Cost::Variable(s.sub_ivar(*x)), + } + } +} + /// Represents an effect on a state variable. /// The effect has a first transition phase `]transition_start, transition_end[` during which the /// value of the state variable is unknown. @@ -493,7 +511,7 @@ pub struct Chronicle { /// expression on the start/end timepoint of these subtasks. pub subtasks: Vec, /// Cost of this chronicle. If left empty, it is interpreted as 0. - pub cost: Option, + pub cost: Option, } struct VarSet(HashSet); @@ -625,7 +643,7 @@ impl Substitute for Chronicle { effects: self.effects.iter().map(|e| e.substitute(s)).collect(), constraints: self.constraints.iter().map(|c| c.substitute(s)).collect(), subtasks: self.subtasks.iter().map(|c| c.substitute(s)).collect(), - cost: self.cost, + cost: self.cost.as_ref().map(|c| c.substitute(s)), } } } diff --git a/planning/planning/src/chronicles/printer.rs b/planning/planning/src/chronicles/printer.rs index df7e5ec5..c5c25e17 100644 --- a/planning/planning/src/chronicles/printer.rs +++ b/planning/planning/src/chronicles/printer.rs @@ -99,8 +99,8 @@ impl<'a> Printer<'a> { println!() } - if let Some(cost) = ch.cost { - println!(" cost: {cost}") + if let Some(cost) = &ch.cost { + println!(" cost: {cost:?}") } println!() diff --git a/planning/planning/src/parsing/mod.rs b/planning/planning/src/parsing/mod.rs index 84f2290d..e5cf8b8b 100644 --- a/planning/planning/src/parsing/mod.rs +++ b/planning/planning/src/parsing/mod.rs @@ -393,7 +393,7 @@ fn read_chronicle_template( // TODO: here the cost is simply 1 for any primitive action let cost = match pddl.kind() { ChronicleKind::Problem | ChronicleKind::Method => None, - ChronicleKind::Action | ChronicleKind::DurativeAction => Some(1), + ChronicleKind::Action | ChronicleKind::DurativeAction => Some(Cost::Fixed(1)), }; let mut ch = Chronicle { diff --git a/planning/unified/plugin/up_aries_tests/__init__.py b/planning/unified/plugin/up_aries_tests/__init__.py index 99c940cd..bb6f2e13 100644 --- a/planning/unified/plugin/up_aries_tests/__init__.py +++ b/planning/unified/plugin/up_aries_tests/__init__.py @@ -42,7 +42,7 @@ def test_action_costs(): a = InstantaneousAction("a") a.add_precondition(Not(pa)) a.add_effect(pa, True) - b = InstantaneousAction("b", k=IntType()) + b = InstantaneousAction("b", k=IntType(lower_bound=0)) b.add_precondition(Not(pb)) b.add_effect(pb, True) costs = {a: 10, b: b.k}