Skip to content

Commit

Permalink
Merge pull request #122 from plaans/feat/metric-optimization
Browse files Browse the repository at this point in the history
feat: metric optimization
  • Loading branch information
arbimo authored Jan 25, 2024
2 parents f1dec6f + c1010ff commit a3bbd70
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 65 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 28 additions & 6 deletions planning/grpc/server/src/bin/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub struct SolverConfiguration {

/// If provided, the solver will only run the specified strategy instead of default set of strategies.
/// When repeated, several strategies will be run in parallel.
/// Allowed values: forward | activity | activity-no-time | causal
/// Allowed values: forward | activity | activity-bool | activity-bool-light | causal
#[clap(long = "strategy", short = 's')]
strategies: Vec<Strat>,
}
Expand Down Expand Up @@ -144,6 +144,10 @@ fn solve_blocking(

let htn_mode = problem.hierarchy.is_some();

let base_problem = problem_to_chronicles(&problem)
.with_context(|| format!("In problem {}/{}", &problem.domain_name, &problem.problem_name))?;
let bounded = htn_mode && hierarchical_is_non_recursive(&base_problem) || base_problem.templates.is_empty();

ensure!(problem.metrics.len() <= 1, "Unsupported: multiple metrics provided.");
let metric = if !conf.optimal {
None
Expand All @@ -152,16 +156,24 @@ fn solve_blocking(
Some(MetricKind::MinimizeActionCosts) => Some(Metric::ActionCosts),
Some(MetricKind::MinimizeSequentialPlanLength) => Some(Metric::PlanLength),
Some(MetricKind::MinimizeMakespan) => Some(Metric::Makespan),
Some(MetricKind::MinimizeExpressionOnFinalState) => Some(Metric::MinimizeVar(
base_problem
.context
.metric_final_value()
.context("Trying to minimize an empty expression metric.")?,
)),
Some(MetricKind::MaximizeExpressionOnFinalState) => Some(Metric::MaximizeVar(
base_problem
.context
.metric_final_value()
.context("Trying to maximize an empty expression metric.")?,
)),
_ => bail!("Unsupported metric kind with ID: {}", metric.kind),
}
} else {
None
};

let base_problem = problem_to_chronicles(&problem)
.with_context(|| format!("In problem {}/{}", &problem.domain_name, &problem.problem_name))?;
let bounded = htn_mode && hierarchical_is_non_recursive(&base_problem) || base_problem.templates.is_empty();

let max_depth = conf.max_depth;
let min_depth = if bounded {
max_depth // non recursive htn: bounded size, go directly to max
Expand Down Expand Up @@ -453,7 +465,17 @@ async fn main() -> Result<(), Error> {

let answer = solve(problem, |_| {}, conf).await;

println!("{answer:?}");
match answer {
Ok(res) => {
let plan = if res.plan.is_some() { "PLAN FOUND" } else { "NO PLAN..." };
let status = match plan_generation_result::Status::from_i32(res.status) {
Some(s) => s.as_str_name(),
None => "???",
};
println!("{plan} ({status})")
}
Err(e) => bail!(e),
}
}
}

Expand Down
22 changes: 21 additions & 1 deletion planning/grpc/server/src/chronicles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use up::atom::Content;
use up::effect_expression::EffectKind;
use up::metric::MetricKind;
use up::timepoint::TimepointKind;
use up::{Expression, ExpressionKind, Problem};
use up::{Expression, ExpressionKind, Metric, Problem};

/// If true, Aries will assume that all real state variables are ints.
/// This is typically useful when the input problem was parsed from PDDL that does not
Expand Down Expand Up @@ -223,6 +223,7 @@ pub fn problem_to_chronicles(problem: &Problem) -> Result<aries_planning::chroni
factory.add_initial_state(&problem.initial_state, &problem.fluents)?;
factory.add_timed_effects(&problem.timed_effects)?;
factory.add_goals(&problem.goals)?;
factory.add_final_value_metric(&problem.metrics)?;

if let Some(hierarchy) = &problem.hierarchy {
let tn = hierarchy
Expand Down Expand Up @@ -745,6 +746,25 @@ impl<'a> ChronicleFactory<'a> {
Ok(())
}

/// Final value to minimize is converted to a condition at the chronicle end time
fn add_final_value_metric(&mut self, metrics: &Vec<Metric>) -> Result<(), Error> {
ensure!(metrics.len() <= 1, "Unsupported: multiple metrics provided.");
let Some(metric) = metrics.first() else {
return Ok(()); // no metric to handle
};
if let Some(MetricKind::MinimizeExpressionOnFinalState | MetricKind::MaximizeExpressionOnFinalState) =
MetricKind::from_i32(metric.kind)
{
let expr = metric
.expression
.as_ref()
.context("Trying to optimize an empty expression metric.")?;
let value = self.reify(expr, Some(Span::instant(self.context.horizon())))?;
self.context.set_metric_final_value(value.try_into()?);
};
Ok(())
}

fn create_variable(&mut self, tpe: Type, var_type: VarType) -> Variable {
let var: Variable = match tpe {
Type::Sym(tpe) => self
Expand Down
39 changes: 27 additions & 12 deletions planning/planners/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,16 @@ pub fn add_metric(pb: &FiniteProblem, model: &mut Model, metric: Metric) -> IAto
// plan cost is the metric that should be minimized.
plan_cost.into()
}
Metric::MinimizeVar(value) => value,
Metric::MaximizeVar(to_maximize) => {
// we must return a variable to minimize.
// return a new variable constrained to be the negation of the one to maximize
let to_minimize = model.new_ivar(INT_CST_MIN, INT_CST_MAX, VarLabel(Container::Base, VarType::Cost));
let sum = LinearSum::zero() + to_maximize - to_minimize;
model.enforce(sum.clone().leq(0), []);
model.enforce(sum.geq(0), []);
to_minimize.into()
}
}
}

Expand Down Expand Up @@ -560,7 +570,7 @@ pub fn encode(pb: &FiniteProblem, metric: Option<Metric>) -> std::result::Result
.map(|(eff_id, prez, _)| {
let var = solver.model.new_optional_fvar(
ORIGIN * TIME_SCALE.get(),
HORIZON * TIME_SCALE.get(),
HORIZON.get() * TIME_SCALE.get(),
TIME_SCALE.get(),
*prez,
Container::Instance(eff_id.instance_id) / VarType::EffectEnd,
Expand Down Expand Up @@ -1112,14 +1122,14 @@ fn encode_resource_constraints(
.collect::<Vec<_>>();

// Vector to store the `la_j` literals, `ca_j` values and the end of the transition time point of the effect `e_j`.
let la_ca_ta = if RESOURCE_CONSTRAINT_TIMEPOINTS.get() {
let la_ca_ta_pa = if RESOURCE_CONSTRAINT_TIMEPOINTS.get() {
create_la_vector_with_timepoints(compatible_assignments, &cond, prez_cond, eff_mutex_ends, solver)
} else {
create_la_vector_without_timepoints(compatible_assignments, &cond, prez_cond, eff_mutex_ends, solver)
};

// Force to have at least one assignment.
let la_disjuncts = la_ca_ta.iter().map(|(la, _, _)| *la).collect::<Vec<_>>();
let la_disjuncts = la_ca_ta_pa.iter().map(|(la, _, _, _)| *la).collect::<Vec<_>>();
solver.enforce(or(la_disjuncts), [prez_cond]);

/*
Expand All @@ -1131,17 +1141,20 @@ fn encode_resource_constraints(
* - is after the assignment effect `e_j`
* - has the same state variable as the condition
* - `la_j` is true
* - the assignment effect associated to `la_j` is present
* - the condition is present
*/
let m_li_ci = la_ca_ta
let m_li_ci = la_ca_ta_pa
.iter()
.map(|(la, _, ta)| {
.map(|(la, _, ta, pa)| {
compatible_increases
.iter()
.map(|(_, prez_eff, eff)| {
let mut li_conjunction: Vec<Lit> = Vec::with_capacity(12);
// `la_j` is true
li_conjunction.push(*la);
// the assignment effect assiciated to `la_j` is present
li_conjunction.push(*pa);
// the condition is present
li_conjunction.push(prez_cond);
// is present
Expand Down Expand Up @@ -1172,7 +1185,7 @@ fn encode_resource_constraints(
.collect::<Vec<_>>();

// Create the linear sum constraints.
for (&(la, ca, _), li_ci) in la_ca_ta.iter().zip(m_li_ci) {
for (&(la, ca, _, _), li_ci) in la_ca_ta_pa.iter().zip(m_li_ci) {
// Create the sum.
let mut sum = LinearSum::zero();
sum += LinearSum::with_lit(
Expand Down Expand Up @@ -1213,7 +1226,8 @@ fn encode_resource_constraints(
}

/**
Vector to store the `la_j` literals, `ca_j` values and the end of the transition time point of the effect `e_j`.
Vector to store the `la_j` literals, `ca_j` values, the end of the transition time point of the effect `e_j`
and the presence of the assignment effect.
`la_j` is true if and only if the associated assignment effect `e_j`:
- is present
Expand All @@ -1228,7 +1242,7 @@ fn create_la_vector_without_timepoints(
prez_cond: Lit,
eff_mutex_ends: &HashMap<EffID, FVar>,
solver: &mut Solver<VarLabel>,
) -> Vec<(Lit, IAtom, FAtom)> {
) -> Vec<(Lit, IAtom, FAtom, Lit)> {
assignments
.iter()
.map(|(eff_id, prez_eff, eff)| {
Expand Down Expand Up @@ -1284,13 +1298,14 @@ fn create_la_vector_without_timepoints(

// Get the end of the transition time point of the effect `e_j`.
let ta = eff.transition_end;
(la_lit, ca, ta)
(la_lit, ca, ta, *prez_eff)
})
.collect::<Vec<_>>()
}

/**
Vector to store the `la_j` literals, `ca_j` values and the end of the transition time point of the effect `e_j`.
Vector to store the `la_j` literals, `ca_j` values and the end of the transition time point of the effect `e_j`
and the presence of the assignment effect.
`la_j` is true if and only if the associated assignment effect `e_j`:
- is present
Expand All @@ -1304,7 +1319,7 @@ fn create_la_vector_with_timepoints(
prez_cond: Lit,
eff_mutex_ends: &HashMap<EffID, FVar>,
solver: &mut Solver<VarLabel>,
) -> Vec<(Lit, IAtom, FAtom)> {
) -> Vec<(Lit, IAtom, FAtom, Lit)> {
assignments
.iter()
.map(|(eff_id, prez_eff, eff)| {
Expand Down Expand Up @@ -1336,7 +1351,7 @@ fn create_la_vector_with_timepoints(

// Get the end of the transition time point of the effect `e_j`.
let ta = eff.transition_end;
(la_lit, ca, ta)
(la_lit, ca, ta, *prez_eff)
})
.collect::<Vec<_>>()
}
10 changes: 7 additions & 3 deletions planning/planners/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
use aries::core::Lit;
use aries::model::lang::FAtom;
use aries_planning::chronicles::*;
use env_param::EnvParam;
use std::collections::{BTreeSet, HashSet};

/// Temporal origin
pub const ORIGIN: i32 = 0;

/// The maximum duration of the plan.
pub static HORIZON: EnvParam<i32> = EnvParam::new("ARIES_PLANNING_HORIZON", "10000");

/// Identifier of a condition
#[derive(Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)]
pub struct CondID {
Expand Down Expand Up @@ -78,9 +85,6 @@ pub fn conditions(pb: &FiniteProblem) -> impl Iterator<Item = (CondID, Lit, &Con
})
}

pub const ORIGIN: i32 = 0;
pub const HORIZON: i32 = 999999;

pub struct TaskRef<'a> {
pub presence: Lit,
pub start: FAtom,
Expand Down
1 change: 1 addition & 0 deletions planning/planners/src/search/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ fn branching_variables<'a>(ch: &'a ChronicleInstance, model: &'a Model) -> impl
// This can happen when a single variable is used to represent several things such as the start and the end of the chronicle
v == start_ref
}
Some(VarLabel(_, Reification)) => false,
_ => true,
})
.filter(move |&v| {
Expand Down
Loading

0 comments on commit a3bbd70

Please sign in to comment.