Source code for simplex.solver
# Author: Aru Bhoop
# Copyright: This module has been placed in the public domain.
import logging
import sys
from typing import List
import numpy as np
from .exceptions import LinearlyDependentError
from .exceptions import SizeMismatchError
from .exceptions import UnsolvableError
from .tableau import Tableau
[docs]class SimplexSolver:
"""
Solves a Linear Programming problem using Dantzig's Simplex Method
by manipulating the `Tableau` class.
Methods
-------
solve(max_iterations=100, use_blands_rule=False) : solves problem, yielding
a`Solution` object
"""
def __init__(self, obj_func: List[float], coeffs: List[List[float]], constraints: List[float]):
"""
Assigns internal variables after performing basic checks.
Parameters
----------
obj_func: values af the objective function, in order. Must be of
size *n* (n = number of variables).
coeffs: values of technological coefficients (params), row-major.
Must be size *m x n* (m = number of constraints)
constraints: values of the constraint column-vector (right-hand
side). Must be size *m*.
"""
# validate dimensions
m = len(constraints) # rows
n = len(obj_func) # columns
if len(coeffs) != m:
raise SizeMismatchError
for coeff in coeffs:
if len(coeff) != n:
raise SizeMismatchError
# full rank assumption
if m > n:
raise LinearlyDependentError
# create corresponding tableau
self.tableau = Tableau(
obj_func=obj_func,
coeffs=coeffs,
constraints=constraints
)
[docs] def solve(self, max_iterations=100, use_blands_rule=False, print_tableau=True):
"""
Solves Linear Programming Problem. Returns `Solution` instance`.
Parameters
----------
max_iterations : int
number of times to pivot before resorting to Bland's rule
use_blands_rule : bool
whether to use Bland's Rule for anti-cycling
print_tableau : bool
whether to print the tableau at every iteration
"""
# configure logging
logging.basicConfig(stream=sys.stdout,
format='%(message)s',
level=logging.DEBUG if print_tableau else logging.INFO)
with self.tableau as t:
# if incomplete basis, use two-phase method
if -1 in t.basis:
logging.info("No identifiable basis. Using two-phase method.")
# solve phase 1 problem
t.add_artificial_variables()
logging.debug(f"\nPhase I Tableau:")
sol = self.solve(max_iterations=max_iterations)
# if phase I is infeasible
if not sol.solution:
return sol
# begin solving phase II
t.drop_artificial_variables()
logging.debug(f'\nPhase II Tableau:')
# print starting/phase 2 tableau
logging.debug(f'{t}\n')
iterations = 0
# keep pivoting until exception is raised or max iterations
while iterations < max_iterations:
t.pivot(use_blands_rule=use_blands_rule)
logging.info(f"[{iterations + 1}] Pivoted around "
f"{t.pivot_idx[0] - 1, t.pivot_idx[1]}") # log pivots
logging.debug(f'{t}\n') # log tableau
iterations += 1
# resort to Bland's rule if necessary
if not use_blands_rule:
logging.info("Possible Cycling detected. Resorting to Bland's Rule.")
return self.solve(use_blands_rule=True)
# if no solution if found
raise UnsolvableError(max_iterations)
return Solution(state=t.state, basis=t.basis,
solution=t.solution, obj_value=t.obj_value)
[docs]class Solution:
"""
Converts Tableau parameters into a human-readable solution.
"""
def __init__(self, state: str, obj_value: float,
basis: List[int], solution: List[float]):
"""
Calculates objective value and basic solution.
Parameters
----------
state : final state of the tableau
obj_value : final objective value of tableau
basis : indices of the basis variables
solution : raw solution from tableau
"""
self.state = state
# objective value
self.obj_value = {
"Optimal": obj_value,
"Unbounded": np.inf,
"Infeasible": np.NaN
}[self.state]
# calculate solution if optimal
if self.state == "Optimal":
self.solution = [0.0] * (max(basis) + 1) # start from zero vector
for i, j in zip(basis, solution):
self.solution[i] = j
else:
self.solution = None
def __repr__(self):
"""
Returns string representation of solution.
"""
return 'Solution: ' + {
"Optimal": f"z*={self.obj_value}, x*={self.solution}",
"Unbounded": "The problem is unbounded.",
"Infeasible": "The problem is infeasible."
}[self.state]