1use std::fmt;
2
3use crate::num::{Coef, Rational};
4use crate::problem::Sense;
5
6#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum Phase {
14 Single,
16 One,
18 Two,
20}
21
22impl fmt::Display for Phase {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 match self {
25 Phase::Single => write!(f, "simplex"),
26 Phase::One => write!(f, "phase 1"),
27 Phase::Two => write!(f, "phase 2"),
28 }
29 }
30}
31
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[derive(Clone, Debug, PartialEq, Eq)]
41#[non_exhaustive]
42pub struct Snapshot {
43 pub sense: Sense,
46 pub objective_label: String,
48 pub columns: Vec<String>,
50 pub basis: Vec<usize>,
52 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::vec_vec"))]
54 pub rows: Vec<Vec<Rational>>,
55 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::vec"))]
57 pub rhs: Vec<Rational>,
58 pub z: Vec<Coef>,
60 pub z_rhs: Coef,
62}
63
64impl Snapshot {
65 pub fn is_optimal(&self) -> bool {
68 match self.sense {
69 Sense::Maximize => !self.z.iter().any(Coef::is_negative),
70 Sense::Minimize => !self.z.iter().any(Coef::is_positive),
71 }
72 }
73}
74
75#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77#[derive(Clone, Debug, PartialEq, Eq)]
78#[non_exhaustive]
79pub struct RatioTest {
80 pub row: usize,
81 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::option"))]
83 pub ratio: Option<Rational>,
84}
85
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
88#[derive(Clone, Debug, PartialEq, Eq)]
89#[non_exhaustive]
90pub struct NamedValue {
91 pub name: String,
92 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::one"))]
93 pub value: Rational,
94}
95
96impl NamedValue {
97 pub(crate) fn new(name: String, value: Rational) -> Self {
98 NamedValue { name, value }
99 }
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum Termination {
107 Optimal,
108 Infeasible,
109 Unbounded,
110}
111
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
118#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
119#[derive(Clone, Debug, PartialEq, Eq)]
120#[non_exhaustive]
121pub enum Step {
122 #[non_exhaustive]
124 Tableau {
125 phase: Phase,
126 iteration: usize,
127 snapshot: Snapshot,
128 },
129 #[non_exhaustive]
131 Pivot {
132 phase: Phase,
133 iteration: usize,
134 entering: usize,
135 entering_name: String,
136 leaving_row: usize,
137 leaving: usize,
138 leaving_name: String,
139 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::one"))]
140 pivot_value: Rational,
141 ratios: Vec<RatioTest>,
142 },
143 #[non_exhaustive]
145 PhaseOneFinished {
146 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::one"))]
147 artificial_sum: Rational,
148 feasible: bool,
149 },
150 #[non_exhaustive]
152 ArtificialPivotedOut {
153 row: usize,
154 entering: usize,
155 entering_name: String,
156 },
157 #[non_exhaustive]
159 RedundantRowRemoved { row: usize },
160 #[non_exhaustive]
164 ArtificialRemainsPositive {
165 artificials: Vec<NamedValue>,
167 unbounded_direction: bool,
168 },
169 #[non_exhaustive]
171 Finished { termination: Termination },
172}
173
174#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
176#[derive(Clone, Debug, PartialEq, Eq)]
177#[non_exhaustive]
178pub struct Trace {
179 pub sense: Sense,
180 pub steps: Vec<Step>,
181}
182
183impl Trace {
184 pub(crate) fn new(sense: Sense) -> Self {
185 Trace {
186 sense,
187 steps: Vec::new(),
188 }
189 }
190
191 pub(crate) fn push(&mut self, step: Step) {
192 self.steps.push(step);
193 }
194
195 pub fn pivot_count(&self) -> usize {
197 self.steps
198 .iter()
199 .filter(|s| matches!(s, Step::Pivot { .. } | Step::ArtificialPivotedOut { .. }))
200 .count()
201 }
202
203 pub fn tableaus(&self) -> impl Iterator<Item = &Snapshot> {
205 self.steps.iter().filter_map(|s| match s {
206 Step::Tableau { snapshot, .. } => Some(snapshot),
207 _ => None,
208 })
209 }
210
211 pub fn termination(&self) -> Option<Termination> {
213 match self.steps.last() {
214 Some(Step::Finished { termination }) => Some(*termination),
215 _ => None,
216 }
217 }
218}
219
220impl fmt::Display for Snapshot {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 let mut table: Vec<Vec<String>> = Vec::new();
223
224 let mut header = vec!["basis".to_string()];
225 header.extend(self.columns.iter().cloned());
226 header.push("rhs".to_string());
227 table.push(header);
228
229 for (i, row) in self.rows.iter().enumerate() {
230 let mut line = vec![self.columns[self.basis[i]].clone()];
231 line.extend(row.iter().map(|v| v.to_string()));
232 line.push(self.rhs[i].to_string());
233 table.push(line);
234 }
235
236 let mut z_line = vec![self.objective_label.clone()];
237 z_line.extend(self.z.iter().map(|v| v.to_string()));
238 z_line.push(self.z_rhs.to_string());
239 table.push(z_line);
240 write_table(f, &table)
241 }
242}
243
244pub(crate) fn write_table(f: &mut fmt::Formatter<'_>, table: &[Vec<String>]) -> fmt::Result {
247 let widths: Vec<usize> = (0..table[0].len())
248 .map(|c| {
249 table
250 .iter()
251 .map(|r| r[c].chars().count())
252 .max()
253 .unwrap_or(0)
254 })
255 .collect();
256
257 for (r, line) in table.iter().enumerate() {
258 let cells: Vec<String> = line
259 .iter()
260 .zip(&widths)
261 .enumerate()
262 .map(|(c, (cell, w))| {
263 if c == 0 {
264 format!("{cell:<w$}")
265 } else {
266 format!("{cell:>w$}")
267 }
268 })
269 .collect();
270 writeln!(f, "{}", cells.join(" | "))?;
271 if r == 0 {
272 let rule: Vec<String> = widths.iter().map(|w| "-".repeat(*w)).collect();
273 writeln!(f, "{}", rule.join("-+-"))?;
274 }
275 }
276 Ok(())
277}
278
279impl fmt::Display for Trace {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 for step in &self.steps {
282 match step {
283 Step::Tableau {
284 phase,
285 iteration,
286 snapshot,
287 } => {
288 writeln!(f, "== {phase}, iteration {iteration} ==")?;
289 write!(f, "{snapshot}")?;
290 }
291 Step::Pivot {
292 entering_name,
293 leaving_name,
294 leaving_row,
295 pivot_value,
296 ratios,
297 ..
298 } => {
299 let tests: Vec<String> = ratios
300 .iter()
301 .map(|r| match &r.ratio {
302 Some(v) => format!("row {}: {v}", r.row + 1),
303 None => format!("row {}: -", r.row + 1),
304 })
305 .collect();
306 writeln!(
307 f,
308 "-> {entering_name} enters, {leaving_name} leaves (row {}, pivot {pivot_value}); ratios [{}]",
309 leaving_row + 1,
310 tests.join(", ")
311 )?;
312 }
313 Step::PhaseOneFinished {
314 artificial_sum,
315 feasible,
316 } => {
317 writeln!(
318 f,
319 "== phase 1 finished: sum of artificials = {artificial_sum} ({}) ==",
320 if *feasible { "feasible" } else { "infeasible" }
321 )?;
322 }
323 Step::ArtificialPivotedOut {
324 row, entering_name, ..
325 } => {
326 writeln!(
327 f,
328 "-> artificial variable in row {} replaced by {entering_name}",
329 row + 1
330 )?;
331 }
332 Step::RedundantRowRemoved { row } => {
333 writeln!(f, "-> row {} is redundant and was removed", row + 1)?;
334 }
335 Step::ArtificialRemainsPositive {
336 artificials,
337 unbounded_direction,
338 } => {
339 let list: Vec<String> = artificials
340 .iter()
341 .map(|a| format!("{} = {}", a.name, a.value))
342 .collect();
343 let stop = if *unbounded_direction {
344 "an unbounded direction was found"
345 } else {
346 "the tableau is optimal"
347 };
348 writeln!(
349 f,
350 "-> {stop}, but the M row cannot improve and {} still positive: no feasible solution",
351 if list.len() == 1 {
352 format!("{} is", list[0])
353 } else {
354 format!("{} are", list.join(", "))
355 }
356 )?;
357 }
358 Step::Finished { termination } => writeln!(f, "== {termination:?} ==")?,
359 }
360 writeln!(f)?;
361 }
362 Ok(())
363 }
364}