ʻOhana
Population structure, admixture history, and selection using learning methods.
jade.lemke.hpp
1 /* -------------------------------------------------------------------------
2  Ohana
3  Copyright (c) 2015-2020 Jade Cheng (\___/)
4  Jade Cheng <info@jade-cheng.com> (='.'=)
5  ------------------------------------------------------------------------- */
6 
7 #ifndef JADE_LEMKE_HPP__
8 #define JADE_LEMKE_HPP__
9 
10 #include "jade.matrix.hpp"
11 
12 namespace jade
13 {
14  ///
15  /// A template for a class that implements Lemke's algorithm.
16  ///
17  template <typename TValue>
19  {
20  public:
21  ///
22  /// The value associated with an invalid or unassigned index.
23  ///
24  static constexpr auto invalid_index =
25  std::numeric_limits<size_t>::max();
26 
27  ///
28  /// Possible states of the algorithm.
29  ///
30  struct state
31  {
32  ///
33  /// The state type.
34  ///
35  enum type
36  {
37  executing, ///< The algorithm is executing.
38  completed, ///< The algorithm completed.
39  aborted_initialization, ///< Aborted during initialization.
40  aborted_elimination, ///< Aborted with an invalid pivot.
41  aborted_pivot ///< Aborted finding the pivot.
42  };
43 
44  ///
45  /// \return The string representation of the type.
46  ///
47  static const char * str(
48  const type value) ///< The type.
49  {
50  switch (value)
51  {
52  #define CASE(E) case E: return #E; break
53  CASE(executing);
54  CASE(completed);
56  CASE(aborted_elimination);
57  CASE(aborted_pivot);
58  #undef CASE
59  }
60 
61  return nullptr;
62  }
63  };
64 
65  /// The value type.
66  typedef TValue value_type;
67 
68  /// The matrix type.
70 
71  /// The labels type.
72  typedef std::vector<size_t> labels_type;
73 
74  /// The state type.
75  typedef typename state::type state_type;
76 
77  ///
78  /// Initializes a new instance of the class based on the specified
79  /// tableau.
80  ///
81  explicit basic_lemke(
82  const matrix_type & tableau) ///< The tableau.
83  : _labels ()
84  , _pivot_col (invalid_index)
85  , _pivot_row (invalid_index)
86  , _state (state::executing)
87  , _tableau (tableau)
88  {
89  assert(tableau.get_height() > 0);
90  assert(tableau.get_width() == tableau.get_height() * 2 + 2);
91 
92  const auto & t = _tableau;
93  const auto n = t.get_height();
94  const auto z0 = n + n;
95 
96  //
97  // Initially add the labels.
98  //
99  _labels.reserve(n);
100  for (size_t i = 0; i < n; i++)
101  _labels.push_back(i);
102 
103  //
104  // The first pivot column is z_0; attempt to find the first pivot
105  // row, but abort if necessary.
106  //
107  _pivot_col = z0;
108  if (!_find_initial_pivot_row())
109  _terminate(state::aborted_initialization);
110  }
111 
112  ///
113  /// Initializes a new instance of the class based on the specified
114  /// M and Q matrices.
115  ///
116  inline basic_lemke(
117  const matrix_type & m, ///< The M matrix.
118  const matrix_type & q) ///< The Q matrix.
119  : basic_lemke(_create_tableau(m, q))
120  {
121  }
122 
123  ///
124  /// Initializes a new instance of the class based on the specified
125  /// Q and A matrices and c and b vectors.
126  ///
127  inline basic_lemke(
128  const matrix_type & q, ///< The Q matrix.
129  const matrix_type & a, ///< The A matrix.
130  const matrix_type & c, ///< The c vector.
131  const matrix_type & b) ///< The b vector.
132  : basic_lemke(_create_tableau(q, a, c, b))
133  {
134  }
135 
136  ///
137  /// \return The specified label index as a string; labels are
138  /// formatted as w_1, w_2, ..., w_n; z_0, z_1, ..., z_n; or q.
139  ///
140  std::string format_label(
141  const size_t label) ///< The label to format.
142  const
143  {
144  const auto & t = _tableau;
145  const auto n = t.get_height();
146  const auto z1 = n;
147  const auto z0 = n + n;
148  const auto q = n + n + 1;
149 
150  std::ostringstream out;
151  if (label < z1) out << "w_" << label + 1;
152  else if (label < z0) out << "z_" << label + 1 - n;
153  else if (label == z0) out << "z_0";
154  else if (label == q) out << "q";
155  else out << label;
156  return out.str();
157  }
158 
159  ///
160  /// \return The vector of labels.
161  ///
162  inline const labels_type & get_labels() const
163  {
164  return _labels;
165  }
166 
167  ///
168  /// \return The output.
169  ///
171  {
172  const auto & t = _tableau;
173  const auto n = t.get_height();
174  const auto z1 = n;
175  const auto q = n + n + 1;
176 
177  matrix_type out (n, 1);
178 
179  for (size_t i = 0; i < n; i++)
180  {
181  const auto label = _labels[i];
182  if (label >= z1)
183  out[label - z1] = t(i, q);
184  }
185 
186  return out;
187  }
188 
189  ///
190  /// \return The pivot column, or invalid_index if the algorithm has
191  /// terminated.
192  ///
193  inline size_t get_pivot_col() const
194  {
195  return _pivot_col;
196  }
197 
198  ///
199  /// \return The pivot row, or invalid_index if the algorithm has
200  /// terminated.
201  ///
202  inline size_t get_pivot_row() const
203  {
204  return _pivot_row;
205  }
206 
207  ///
208  /// \return The state.
209  ///
210  inline state_type get_state() const
211  {
212  return _state;
213  }
214 
215  ///
216  /// \return The tableau.
217  ///
218  inline const matrix_type & get_tableau() const
219  {
220  return _tableau;
221  }
222 
223  ///
224  /// \return True if the algorithm is still executing; otherwise, false.
225  ///
226  inline bool is_executing() const
227  {
228  return _state == state::executing;
229  }
230 
231  ///
232  /// Performs one step of the algorithm.
233  /// \return True if the still executing; otherwise, false.
234  ///
235  bool iterate()
236  {
237  if (_state != state::executing)
238  return false;
239 
240  if (!_eliminate())
241  return _terminate(state::aborted_elimination);
242 
243  if (!_relabel())
244  return _terminate(state::completed);
245 
246  if (!_find_pivot_row())
247  return _terminate(state::aborted_pivot);
248 
249  return true;
250  }
251 
252  ///
253  /// Executes the algorithm until it has completed or has aborted.
254  /// \return True if completed; otherwise, false.
255  ///
256  bool solve()
257  {
258  while (_state == state::executing)
259  iterate();
260 
261  return _state == state::completed;
262  }
263 
264  ///
265  /// Attempts to solve the linear complementarity problem using Lemke's
266  /// Algorithm. If successful, this method stores the z vector into the
267  /// output parameter.
268  ///
269  /// \return True if successful; otherwise, false.
270  ///
271  static bool solve(
272  matrix_type & out, ///< The output vector.
273  const matrix_type & tableau) ///< The tableau.
274  {
275  basic_lemke lemke (tableau);
276  if (!lemke.solve())
277  return false;
278 
279  out = lemke.get_output();
280  return true;
281  }
282 
283  ///
284  /// Attempts to solve the linear complementarity problem using Lemke's
285  /// Algorithm. If successful, this method stores the z vector into the
286  /// output parameter.
287  ///
288  /// \return True if successful; otherwise, false.
289  ///
290  inline static bool solve(
291  matrix_type & out, ///< The output vector.
292  const matrix_type & m, ///< The M matrix.
293  const matrix_type & q) ///< The q vector.
294  {
295  return solve(out, _create_tableau(m, q));
296  }
297 
298  ///
299  /// Attempts to solve the linear complementarity problem using Lemke's
300  /// Algorithm. If successful, this method stores the z vector into the
301  /// output parameter.
302  ///
303  /// \return True if successful; otherwise, false.
304  ///
305  inline static bool solve(
306  matrix_type & out, ///< The output vector.
307  const matrix_type & q, ///< The Q matrix.
308  const matrix_type & a, ///< The A matrix.
309  const matrix_type & c, ///< The c vector.
310  const matrix_type & b) ///< The b vector.
311  {
312  return solve(out, _create_tableau(q, a, c, b));
313  }
314 
315  ///
316  /// \return A string representation of the class.
317  ///
318  std::string str() const
319  {
320  const auto & t = _tableau;
321  const auto n = t.get_height();
322  const auto q = n + n + 1;
323 
324  static const size_t cx = 8;
325  std::ostringstream out;
326  out << std::setprecision(3)
327  << std::setw(cx) << "BV";
328 
329  for (size_t j = 0; j <= q; j++)
330  out << std::setw(cx) << format_label(j);
331  out << std::endl;
332 
333  for (size_t i = 0; i < n; i++)
334  {
335  out << std::setw(cx) << format_label(_labels[i]);
336  for (size_t j = 0; j <= q; j++)
337  out << std::setw(cx) << t(i, j);
338  out << std::endl;
339  }
340 
341  out << std::endl
342  << "state: " << state::str(_state) << std::endl
343  << "pivot: ";
344 
345  if (_pivot_row == invalid_index)
346  out << "<none>";
347  else
348  out << "{ " << format_label(_pivot_row)
349  << ", " << format_label(_pivot_col) << " }";
350 
351  out << std::endl << std::endl;
352  return out.str();
353  }
354 
355  private:
356  // --------------------------------------------------------------------
357  static matrix_type _create_m(
358  const matrix_type & q,
359  const matrix_type & a)
360  {
361  assert(q.get_width() == a.get_width() || a.is_empty());
362  assert(q.is_square());
363  assert(q.get_height() >= 1);
364 
365  const auto qn = q.get_height();
366  const auto ah = a.get_height();
367  const auto mn = qn + ah;
368 
369  matrix_type m (mn, mn);
370 
371  //
372  // Assign Q to the top-left quadrant.
373  //
374  for (size_t i = 0; i < qn; i++)
375  for (size_t j = 0; j < qn; j++)
376  m(i, j) = q(i, j);
377 
378  //
379  // Assign A to the bottom-left quadrant.
380  //
381  for (size_t i = 0; i < ah; i++)
382  for (size_t j = 0; j < qn; j++)
383  m(qn + i, j) = a(i, j);
384 
385  //
386  // Assign -A^T to the top-right quadrant.
387  //
388  for (size_t i = 0; i < ah; i++)
389  for (size_t j = 0; j < qn; j++)
390  m(j, qn + i) = -a(i, j);
391 
392  return m;
393  }
394 
395  // --------------------------------------------------------------------
396  static matrix_type _create_q(
397  const matrix_type & c,
398  const matrix_type & b)
399  {
400  assert(c.is_column_vector());
401  assert(b.is_column_vector() || b.is_empty());
402  assert(c.get_height() >= 1);
403 
404  const auto ch = c.get_height();
405  const auto bh = b.get_height();
406 
407  matrix_type q (ch + bh, 1);
408 
409  //
410  // Assign C to the top.
411  //
412  for (size_t i = 0; i < ch; i++)
413  q[i] = c[i];
414 
415  //
416  // Assign -B to the bottom.
417  //
418  for (size_t i = 0; i < bh; i++)
419  q[ch + i] = -b[i];
420 
421  return q;
422  }
423 
424  // --------------------------------------------------------------------
425  static matrix_type _create_tableau(
426  const matrix_type & m,
427  const matrix_type & q)
428  {
429  assert(m.is_square());
430  assert(q.is_column_vector());
431  assert(m.get_height() == q.get_height());
432 
433  const auto n = q.get_length();
434  matrix_type t (n, 2 * n + 2);
435 
436  //
437  // Assign the identity matrix to w_1, w_2, ..., w_n.
438  //
439  for (size_t i = 0; i < n; i++)
440  t(i, i) = 1;
441 
442  //
443  // Assign -M_ij to z_ik where k = n + j.
444  //
445  for (size_t i = 0; i < n; i++)
446  for (size_t j = 0; j < n; j++)
447  t(i, n + j) = -m(i, j);
448 
449  //
450  // Assign -1 to z_0.
451  //
452  for (size_t i = 0; i < n; i++)
453  t(i, 2 * n) = -1;
454 
455  //
456  // Assign q.
457  //
458  for (size_t i = 0; i < n; i++)
459  t(i, 2 * n + 1) = q[i];
460 
461  return t;
462  }
463 
464  // --------------------------------------------------------------------
465  inline static matrix_type _create_tableau(
466  const matrix_type & q,
467  const matrix_type & a,
468  const matrix_type & c,
469  const matrix_type & b)
470  {
471  return _create_tableau(_create_m(q, a), _create_q(c, b));
472  }
473 
474  // --------------------------------------------------------------------
475  bool _eliminate()
476  {
477  auto & t = _tableau;
478  const auto w = t.get_width();
479  const auto n = t.get_height();
480 
481  //
482  // If the pivot is zero, then abort.
483  //
484  auto & t_pivot = t(_pivot_row, _pivot_col);
485  if (std::fabs(t_pivot) < _epsilon)
486  return false;
487 
488  //
489  // Normalize the pivot row.
490  //
491  for (size_t j = 0; j < w; j++)
492  if (j != _pivot_col)
493  t(_pivot_row, j) /= t_pivot;
494  t_pivot = value_type(1);
495 
496  //
497  // Eliminate the non-pivot rows.
498  //
499  for (size_t i = 0; i < n; i++)
500  {
501  if (i == _pivot_row)
502  continue;
503 
504  auto & t_ip = t(i, _pivot_col);
505  for (size_t j = 0; j < w; j++)
506  if (j != _pivot_col)
507  t(i, j) -= t_ip * t(_pivot_row, j);
508  t_ip = value_type(0);
509  }
510 
511  return true;
512  }
513 
514  // --------------------------------------------------------------------
515  bool _find_initial_pivot_row()
516  {
517  const auto & t = _tableau;
518  const auto n = t.get_height();
519  const auto q = n + n + 1;
520 
521  _pivot_row = invalid_index;
522 
523  auto pivot_value = value_type(0);
524 
525  //
526  // Loop over each row.
527  //
528  for (size_t i = 0; i < n; i++)
529  {
530  //
531  // Skip rows with a non-negative q value.
532  //
533  const auto t_iq = t(i, q);
534  if (t_iq >= 0)
535  continue;
536 
537  //
538  // Select the row with the lowest q value.
539  //
540  if (_pivot_row == invalid_index || t_iq < pivot_value)
541  {
542  _pivot_row = i;
543  pivot_value = t_iq;
544  }
545  }
546 
547  //
548  // Return true if the pivot row was found.
549  //
550  return _pivot_row != invalid_index;
551  }
552 
553  // --------------------------------------------------------------------
554  bool _find_pivot_row()
555  {
556  const auto & t = _tableau;
557  const auto n = t.get_height();
558  const auto q = n + n + 1;
559 
560  _pivot_row = invalid_index;
561 
562  auto ratio = value_type(0);
563 
564  //
565  // Loop over each row.
566  //
567  for (size_t i = 0; i < n; i++)
568  {
569  //
570  // Skip rows that are non-positive in the pivot column.
571  //
572  const auto t_ip = t(i, _pivot_col);
573  if (t_ip <= 0)
574  continue;
575 
576  //
577  // Select the row with the lowest ratio.
578  //
579  const auto r_i = t(i, q) / t_ip;
580  if (_pivot_row == invalid_index || r_i < ratio)
581  {
582  _pivot_row = i;
583  ratio = r_i;
584  }
585  }
586 
587  //
588  // Return true if the pivot row was found.
589  //
590  return _pivot_row != invalid_index;
591  }
592 
593  // --------------------------------------------------------------------
594  bool _relabel()
595  {
596  const auto & t = _tableau;
597  const auto n = t.get_height();
598  const auto z1 = n;
599  const auto z0 = n + n;
600 
601  //
602  // Keep track of the label being replaced.
603  //
604  const auto old_label = _labels[_pivot_row];
605 
606  //
607  // Replace it with the label associated with the pivot column.
608  //
609  _labels[_pivot_row] = _pivot_col;
610 
611  //
612  // If the z_0 column was replaced, then the algorithm has completed
613  // successfully.
614  //
615  if (old_label == z0)
616  return false;
617 
618  //
619  // Use the complementary label as the next pivot column.
620  //
621  _pivot_col = old_label >= z1
622  ? old_label - n
623  : old_label + n;
624 
625  return true;
626  }
627 
628  // --------------------------------------------------------------------
629  bool _terminate(const state_type new_state)
630  {
631  _pivot_row = invalid_index;
632  _pivot_col = invalid_index;
633  _state = new_state;
634  return false;
635  }
636 
637  static constexpr auto _epsilon = value_type(0.000001);
638 
639  labels_type _labels;
640  size_t _pivot_col;
641  size_t _pivot_row;
642  state_type _state;
643  matrix_type _tableau;
644  };
645 }
646 
647 #endif // JADE_LEMKE_HPP__
jade::basic_lemke::solve
bool solve()
Executes the algorithm until it has completed or has aborted.
Definition: jade.lemke.hpp:256
jade::basic_lemke::format_label
std::string format_label(const size_t label) const
Definition: jade.lemke.hpp:140
jade::basic_lemke::solve
static bool solve(matrix_type &out, const matrix_type &m, const matrix_type &q)
Attempts to solve the linear complementarity problem using Lemke's Algorithm. If successful,...
Definition: jade.lemke.hpp:290
jade::basic_lemke::get_output
matrix_type get_output() const
Definition: jade.lemke.hpp:170
jade::basic_matrix::get_width
size_t get_width() const
Definition: jade.matrix.hpp:757
jade::basic_lemke
A template for a class that implements Lemke's algorithm.
Definition: jade.lemke.hpp:19
jade::basic_lemke::is_executing
bool is_executing() const
Definition: jade.lemke.hpp:226
jade::basic_lemke::state::aborted_pivot
@ aborted_pivot
Aborted finding the pivot.
Definition: jade.lemke.hpp:41
jade::basic_lemke::labels_type
std::vector< size_t > labels_type
The labels type.
Definition: jade.lemke.hpp:72
jade::basic_lemke::state::type
type
The state type.
Definition: jade.lemke.hpp:36
jade::basic_lemke::basic_lemke
basic_lemke(const matrix_type &tableau)
Initializes a new instance of the class based on the specified tableau.
Definition: jade.lemke.hpp:81
jade::basic_lemke::invalid_index
static constexpr auto invalid_index
The value associated with an invalid or unassigned index.
Definition: jade.lemke.hpp:24
jade::basic_lemke::matrix_type
basic_matrix< value_type > matrix_type
The matrix type.
Definition: jade.lemke.hpp:69
jade::basic_lemke::get_state
state_type get_state() const
Definition: jade.lemke.hpp:210
jade::basic_lemke::state::aborted_initialization
@ aborted_initialization
Aborted during initialization.
Definition: jade.lemke.hpp:39
jade::basic_lemke::str
std::string str() const
Definition: jade.lemke.hpp:318
jade::basic_lemke::state::str
static const char * str(const type value)
Definition: jade.lemke.hpp:47
jade::basic_lemke::state::completed
@ completed
The algorithm completed.
Definition: jade.lemke.hpp:38
jade::basic_lemke::state
Possible states of the algorithm.
Definition: jade.lemke.hpp:31
jade::basic_matrix::get_height
size_t get_height() const
Definition: jade.matrix.hpp:603
jade::basic_lemke::get_labels
const labels_type & get_labels() const
Definition: jade.lemke.hpp:162
jade::basic_lemke::get_pivot_row
size_t get_pivot_row() const
Definition: jade.lemke.hpp:202
jade::basic_lemke::basic_lemke
basic_lemke(const matrix_type &q, const matrix_type &a, const matrix_type &c, const matrix_type &b)
Initializes a new instance of the class based on the specified Q and A matrices and c and b vectors.
Definition: jade.lemke.hpp:127
jade::basic_lemke::solve
static bool solve(matrix_type &out, const matrix_type &q, const matrix_type &a, const matrix_type &c, const matrix_type &b)
Attempts to solve the linear complementarity problem using Lemke's Algorithm. If successful,...
Definition: jade.lemke.hpp:305
jade::basic_lemke::value_type
TValue value_type
The value type.
Definition: jade.lemke.hpp:66
jade::basic_lemke::get_tableau
const matrix_type & get_tableau() const
Definition: jade.lemke.hpp:218
jade::basic_lemke::get_pivot_col
size_t get_pivot_col() const
Definition: jade.lemke.hpp:193
jade::basic_lemke::solve
static bool solve(matrix_type &out, const matrix_type &tableau)
Attempts to solve the linear complementarity problem using Lemke's Algorithm. If successful,...
Definition: jade.lemke.hpp:271
jade::basic_lemke::basic_lemke
basic_lemke(const matrix_type &m, const matrix_type &q)
Initializes a new instance of the class based on the specified M and Q matrices.
Definition: jade.lemke.hpp:116
jade::basic_lemke::state_type
state::type state_type
The state type.
Definition: jade.lemke.hpp:75
jade::basic_lemke::iterate
bool iterate()
Performs one step of the algorithm.
Definition: jade.lemke.hpp:235
jade::basic_lemke::state::executing
@ executing
The algorithm is executing.
Definition: jade.lemke.hpp:37
jade::basic_matrix< value_type >
jade::basic_lemke::state::aborted_elimination
@ aborted_elimination
Aborted with an invalid pivot.
Definition: jade.lemke.hpp:40