miniplan/plan.rs
1//! Plan representation.
2//!
3//! A plan is a totally-ordered sequence of grounded operator applications
4//! whose execution transforms the initial state into a goal-satisfying state.
5//! Plans here are **sequential** — there is no partial ordering or parallelism.
6//! They are produced by the planners in [`crate::search`].
7//!
8//! The [`Plan`] struct carries two invariants:
9//!
10//! - `steps` records *which* grounded operators to apply and in what order.
11//! - `cost` is the numeric sum of the executed operators' costs, so empty
12//! plans always have `cost == 0.0`.
13//!
14//! The [`Display`](std::fmt::Display) impl on [`Plan`] emits a PDDL-style
15//! human-readable format.
16
17use std::fmt;
18
19use crate::task::OpId;
20
21/// A single step in a [`Plan`].
22///
23/// A `PlanStep` names one grounded-operator application inside a plan.
24/// The `op_id` is the authoritative identifier (an index into the
25/// [`Task`](crate::task::Task)'s operator list), while `op_name` is a
26/// redundant, pretty-printed copy carried along so that plans can be
27/// displayed or serialised without needing the originating `Task`.
28#[derive(Debug, Clone, PartialEq)]
29pub struct PlanStep {
30 /// The operator ID that identifies this step.
31 ///
32 /// Indexes into `crate::task::Task::operators` of the task that
33 /// produced this plan. See [`OpId`] for details.
34 pub op_id: OpId,
35 /// A human-readable operator name (typically [`Operator::name`](crate::task::Operator::name),
36 /// e.g. `"move-A-B"`).
37 ///
38 /// Not guaranteed unique; intended for display and serialisation only —
39 /// do not parse it to recover an operator.
40 pub op_name: String,
41}
42
43/// The output produced by a planner when search succeeds.
44///
45/// This is the structure wrapped by [`SearchOutcome::Plan`](crate::search::SearchOutcome::Plan).
46///
47/// # Invariants
48///
49/// - `steps` is ordered: `steps[0]` is applied first.
50/// - `cost` equals the sum of the applied operators' cost values; it is not
51/// necessarily `steps.len()` because operator costs need not be unit.
52/// - An empty plan (`steps` is empty, `cost == 0.0`) is a valid result when
53/// the initial state already satisfies the goal.
54///
55/// # Examples
56///
57/// ```
58/// use miniplan::plan::{Plan, PlanStep};
59/// use miniplan::task::OpId;
60///
61/// let plan = Plan::new();
62/// assert!(plan.is_empty());
63/// assert_eq!(plan.len(), 0);
64/// assert_eq!(plan.cost, 0.0);
65///
66/// let mut plan = Plan::new();
67/// plan.steps.push(PlanStep { op_id: OpId(0), op_name: "pick".into() });
68/// plan.cost = 1.0;
69/// assert!(!plan.is_empty());
70/// assert_eq!(plan.len(), 1);
71///
72/// assert_eq!(
73/// plan.to_string(),
74/// "; cost = 1\n; length = 1\n(pick)\n"
75/// );
76/// ```
77#[derive(Debug, Clone, PartialEq)]
78pub struct Plan {
79 /// Ordered list of [`PlanStep`]s; execute left-to-right.
80 pub steps: Vec<PlanStep>,
81 /// Accumulated cost of the steps.
82 ///
83 /// Must stay in sync with `steps` — callers mutate both together.
84 pub cost: f64,
85}
86
87impl Default for Plan {
88 /// Creates an empty plan with `steps` empty and `cost` set to `0.0`.
89 ///
90 /// Equivalent to [`Plan::new`].
91 fn default() -> Self {
92 Self {
93 steps: Vec::new(),
94 cost: 0.0,
95 }
96 }
97}
98
99impl Plan {
100 /// Create an empty plan.
101 ///
102 /// The returned value satisfies `steps.is_empty()` and `cost == 0.0`.
103 /// This is equivalent to `Plan::default()`.
104 ///
105 /// # Examples
106 ///
107 /// ```
108 /// use miniplan::plan::Plan;
109 ///
110 /// let plan = Plan::new();
111 /// assert!(plan.is_empty());
112 /// assert_eq!(plan.cost, 0.0);
113 /// ```
114 #[must_use]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 /// Returns `true` if the plan has no steps.
120 ///
121 /// # Examples
122 ///
123 /// ```
124 /// use miniplan::plan::Plan;
125 ///
126 /// assert!(Plan::new().is_empty());
127 /// ```
128 #[must_use]
129 pub fn is_empty(&self) -> bool {
130 self.steps.is_empty()
131 }
132
133 /// Returns the number of steps in the plan.
134 ///
135 /// This counts steps, not cost.
136 ///
137 /// # Examples
138 ///
139 /// ```
140 /// use miniplan::plan::Plan;
141 ///
142 /// assert_eq!(Plan::new().len(), 0);
143 /// ```
144 #[must_use]
145 pub fn len(&self) -> usize {
146 self.steps.len()
147 }
148}
149
150/// Human-readable display format for a [`Plan`].
151///
152/// Emits a PDDL-style plan file:
153///
154/// - Line 1: `; cost = <cost>` (semicolons are PDDL plan comments).
155/// - Line 2: `; length = <len>`.
156/// - Lines 3..: one `(<op_name>)` per step, each terminated by `\n`.
157///
158/// The trailing newline after the last step matches the `writeln!` macro.
159///
160/// # Examples
161///
162/// ```
163/// use miniplan::plan::{Plan, PlanStep};
164/// use miniplan::task::OpId;
165///
166/// let mut plan = Plan::new();
167/// plan.steps.push(PlanStep { op_id: OpId(0), op_name: "pick".into() });
168/// plan.steps.push(PlanStep { op_id: OpId(1), op_name: "place".into() });
169/// plan.cost = 2.0;
170///
171/// assert_eq!(
172/// plan.to_string(),
173/// "; cost = 2\n; length = 2\n(pick)\n(place)\n"
174/// );
175/// ```
176impl fmt::Display for Plan {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 writeln!(f, "; cost = {}", self.cost)?;
179 writeln!(f, "; length = {}", self.len())?;
180 for step in &self.steps {
181 writeln!(f, "({})", step.op_name)?;
182 }
183 Ok(())
184 }
185}