diff --git a/resources/simplex/run_simplex.py b/resources/simplex/run_simplex.py index 04bd42ca141e4570d5cdae259a27b869cde546e8..a8329ebf11f9a2aee9f3394cff0b98c1f2308b95 100644 --- a/resources/simplex/run_simplex.py +++ b/resources/simplex/run_simplex.py @@ -34,20 +34,70 @@ class RunSimplex(): self._basic_var_indexes = None self._nonbasic_var_indexes = None - def __call__(self, *args, **kwargs): + def __call__(self, *args, debug=False, **kwargs): + # Optionally override parent debug setting on call. + orig_debug = self._debug + if debug is True: + self._debug = debug + # Run simplex logic loop until optimized. - return self._optimization_loop() + results = self._start_simplex() - def _optimization_loop(self): + # Reset original debug settings. + self._debug = orig_debug + + return results + + def _start_simplex(self): # Run "Initialize-Simplex" function before we do any work. self._parent.initialize() + # Our initialize calls part of this logic. So put it in a separate method to avoid unintentional recursion. + return self._run_simplex() + + def _run_simplex(self): + # Run optimization loop. + self._optimization_loop() + + # If we got this far, then it should be solved. Get parent values once more. + matrix_a = self._parent._matrix_a + vector_b = self._parent._vector_b + vector_c = self._parent._vector_c + obj_constant_index = self._parent._obj_constant_index + basic_var_indexes = self._parent._b_array + + # Get solution. + solution = [] + for col_index in range(len(vector_c) - 1): + # Check if col is basic or not. + if col_index in basic_var_indexes: + # Col is basic. Find associated row where basic value exists. + for row_index in range(len(matrix_a)): + if matrix_a[row_index][col_index] == 1: + # Found row where basic value exists. Return row's constant value. + solution.append(vector_b[row_index]) + else: + # Col is not basic. Return 0. + solution.append(0) + + return (-vector_c[obj_constant_index], solution) + + def _optimization_loop(self): + """ + A method containing only the while loop logic. + Separate to avoid unintentional recursion errors in our "Initialize-Simplex" function. + """ # Create loop to continually improve simplex until optimized. still_optimizable = True while still_optimizable: still_optimizable = False highest_obj_index = -1 + logger.info('Running simplex pivot loop...') + if self._debug: + logger.info('') + self._parent.display_tableau() + # Update parent data, after each loop. matrix_a = self._parent._matrix_a vector_b = self._parent._vector_b @@ -58,98 +108,52 @@ class RunSimplex(): # Optional debugging printout. if self._debug: - logger.info('') logger.info('Starting RunSimplex._optimization_loop()') logger.info(' n_array: {0}'.format(nonbasic_var_indexes)) logger.info(' b_array: {0}'.format(basic_var_indexes)) logger.info(' matrix_a: {0}'.format(matrix_a)) logger.info(' vector_b: {0}'.format(vector_b)) logger.info(' obj_constant_index: {0}'.format(obj_constant_index)) + logger.info('') # Check that we actually can still optimize values. for col_index in range(len(vector_b)): # Check if value is greater than 0. - logger.info('vector_c: {0}'.format(vector_c)) - logger.info('vector_c[{0}]: {1}'.format(col_index, vector_c[col_index])) if vector_c[col_index] > 0: still_optimizable = True # Check if current index is larger than previously found one. if highest_obj_index == -1 or vector_c[col_index] > vector_c[highest_obj_index]: - logger.info('Updating index to: {0}'.format(col_index)) highest_obj_index = col_index # Check that we found something to optimize. if still_optimizable: self._optimize_on_index(highest_obj_index) - # If we got this far, then it should be solved. Get parent values once more. - matrix_a = self._parent._matrix_a - vector_b = self._parent._vector_b - vector_c = self._parent._vector_c - obj_constant_index = self._parent._obj_constant_index - basic_var_indexes = self._parent._b_array - - # Get solution. - solution = [] - for col_index in range(len(vector_c) - 1): - # Check if col is basic or not. - if col_index in basic_var_indexes: - # Col is basic. Find associated row where basic value exists. - for row_index in range(len(matrix_a)): - if matrix_a[row_index][col_index] == 1: - # Found row where basic value exists. Return row's constant value. - solution.append(vector_b[row_index]) - else: - # Col is not basic. Return 0. - solution.append(0) - logger.info('solution: {0}'.format(solution)) - - return (-vector_c[obj_constant_index], solution) - def _optimize_on_index(self, col_index): # Get parent data, after initialized. matrix_a = self._parent._matrix_a vector_b = self._parent._vector_b - vector_c = self._parent._vector_c basic_var_indexes = self._parent._b_array # Optional debugging printout. if self._debug: - logger.info('') logger.info('Starting RunSimplex._optimization_on_index()') logger.info(' col_index: {0}'.format(col_index)) logger.info(' b_array: {0}'.format(basic_var_indexes)) logger.info(' matrix_a: {0}'.format(matrix_a)) logger.info(' vector_b: {0}'.format(vector_b)) - - logger.info('Attempting to pivot on index {0}: {1}'.format(col_index, vector_c[col_index])) + logger.info('') # Loop through all rows, finding best one to pivot on. delta = [] - logger.info('') - logger.info('matrix_a: {0}'.format(matrix_a)) - logger.info('vector_b: {0}'.format(vector_b)) for row_index in range(len(matrix_a)): # Check that we can divide. - logger.info('vector_b[{0}]: {1} matrix_a[{0}][{2}]: {3}'.format( - row_index, - vector_b[row_index], - col_index, - matrix_a[row_index][col_index], - )) - try: - logger.info('b/a: {0}'.format(vector_b[row_index] / matrix_a[row_index][col_index])) - except: - logger.info('b/a: -1') if matrix_a[row_index][col_index] > 0: delta.append(vector_b[row_index] / matrix_a[row_index][col_index]) else: # Less than 1. Can't divide. delta.append(-1) - logger.info('delta: {0}'.format(delta)) - - logger.info('delta: {0}'.format(delta)) # Now check to see which row we actually use. smallest_valid_index = -1 @@ -160,6 +164,4 @@ class RunSimplex(): smallest_valid_index = row_index # Now pivot on our found values. - self._parent.pivot(basic_var_indexes[smallest_valid_index], col_index) - - self._parent.display_tableau() + self._parent.pivot(basic_var_indexes[smallest_valid_index], col_index, debug=self._debug) diff --git a/tests/resources/simplex/run_simplex.py b/tests/resources/simplex/run_simplex.py index d54b67b3e6ef7c0b8457900203fc1a2d0be89551..4f034bdb6d8d5531058b93d97c9674e3edb0b3ff 100644 --- a/tests/resources/simplex/run_simplex.py +++ b/tests/resources/simplex/run_simplex.py @@ -15,7 +15,7 @@ import unittest from resources.simplex.base import SimplexBase -class TestPivot(unittest.TestCase): +class TestSimplex(unittest.TestCase): def setUp(self): self.simplex = SimplexBase() @@ -69,7 +69,6 @@ class TestPivot(unittest.TestCase): self.assertEqual(self.simplex._n_array, [0, 1, 2]) # Attempt to fully solve simplex. - self.simplex.display_tableau() results = self.simplex.solve() # Test values after solving. @@ -87,3 +86,65 @@ class TestPivot(unittest.TestCase): # Test solution results. self.assertEqual(results[0], 17) self.assertEqual(results[1], [0, 4, 1, 0, 5, 0]) + + def test__run_simplex__ex4(self): + # Setup initial simplex. + self.simplex.read_data_from_json('./resources/json_files/ex_4.json') + + # Test initial values after setup. + self.assertEqual(self.simplex._matrix_a, [ + [-1, 1, 1, 0, 0], + [1, 3, 0, 1, 0], + [1, -1, 0, 0, 1], + ]) + self.assertEqual(self.simplex._vector_b, [3, 13, 1]) + self.assertEqual(self.simplex._vector_c, [1, 2, 0, 0, 0, 0]) + self.assertEqual(self.simplex._obj_constant_index, 5) + self.assertEqual(self.simplex._b_array, [2, 3, 4]) + self.assertEqual(self.simplex._n_array, [0, 1]) + + # Attempt to fully solve simplex. + self.simplex.solve() + + # Test initial values "initialize" method. + self.assertEqual(self.simplex._matrix_a, [ + [0, 1, 0, 0.25, -0.25], + [1, 0, 0, 0.25, 0.75], + [0, 0, 1, 0, 1], + ]) + self.assertEqual(self.simplex._vector_b, [3, 4, 4]) + self.assertEqual(self.simplex._vector_c, [0, 0, 0, -0.75, -0.25, -10]) + self.assertEqual(self.simplex._obj_constant_index, 5) + self.assertEqual(self.simplex._b_array, [1, 0, 2]) + self.assertEqual(self.simplex._n_array, [3, 4]) + + def test__run_simplex__ex6(self): + # Setup initial simplex. + self.simplex.read_data_from_json('./resources/json_files/ex_6.json') + + # Test initial values after setup. + self.assertEqual(self.simplex._matrix_a, [ + [1, 1, 3, 1, 0, 0], + [2, 2, 5, 0, 1, 0], + [4, 1, 2, 0, 0, 1], + ]) + self.assertEqual(self.simplex._vector_b, [30, 24, 36]) + self.assertEqual(self.simplex._vector_c, [3, 1, 2, 0, 0, 0, 0]) + self.assertEqual(self.simplex._obj_constant_index, 6) + self.assertEqual(self.simplex._b_array, [3, 4, 5]) + self.assertEqual(self.simplex._n_array, [0, 1, 2]) + + # Attempt to fully solve simplex. + self.simplex.solve() + + # Test initial values "initialize" method. + self.assertEqual(self.simplex._matrix_a, [ + [0, 0, 1 / 2, 1, -1 / 2, 0], + [0, 1, 8 / 3, 0, 2 / 3, -1 / 3], + [1, 0, -1/6, 0, -1/6, 1/3], + ]) + self.assertEqual(self.simplex._vector_b, [18, 4, 8]) + self.assertEqual(self.simplex._vector_c, [0, 0, -1/6, 0, -1/6, -2/3, -28]) + self.assertEqual(self.simplex._obj_constant_index, 6) + self.assertEqual(self.simplex._b_array, [3, 1, 0]) + self.assertEqual(self.simplex._n_array, [2, 4, 5])