





'''simple docstring'''


from typing import Any

import numpy as np


class 		UpperCamelCase__     :
      '''simple docstring'''






      def __init__(     self     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )				->  None:
        # Check if RHS is negative
        if np.any(tableau[:, -1]     , where=tableau[:, -1] < 0 ):
          raise ValueError('RHS must be > 0' )

        __lowerCAmelCase		:    Any			     =  tableau
        __lowerCAmelCase     ,					__lowerCAmelCase		:    Optional[int]			     =  tableau.shape

        # Number of decision variables x1, x2, x3...
        __lowerCAmelCase		:    Any			     =  n_vars

        # Number of artificial variables to be minimised
        __lowerCAmelCase		:    str			     =  len(np.where(tableau[self.n_vars : -1] == -1 )[0] )

        # 2 if there are >= or == constraints (nonstandard), 1 otherwise (std)
        __lowerCAmelCase		:    Union[str, Any]			     =  (self.n_art_vars > 0) + 1

        # Number of slack variables added to make inequalities into equalities
        __lowerCAmelCase		:    Any			     =  self.n_rows - self.n_stages

        # Objectives for each stage
        __lowerCAmelCase		:    List[Any]			     =  ['max']

        # In two stage simplex, first minimise then maximise
        if self.n_art_vars:
          self.objectives.append('min' )

        __lowerCAmelCase		:    Optional[Any]			     =  ['']

        # Index of current pivot row and column
        __lowerCAmelCase		:    List[Any]			     =  None
        __lowerCAmelCase		:    Dict			     =  None

        # Does objective row only contain (non)-negative values?
        __lowerCAmelCase		:    Any			     =  False




      @staticmethod
      def 	snake_case  (     *SCREAMING_SNAKE_CASE )				->  list[str]:
        if len(SCREAMING_SNAKE_CASE ) != 3:
          raise ValueError('Must provide n_vars, n_slack, and n_art_vars' )

        if not all(x >= 0 and isinstance(SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE ) for x in args ):
          raise ValueError('All arguments must be non-negative integers' )

        # decision | slack | artificial
        __lowerCAmelCase		:    int			     =  ['x', 's', 'a']
        __lowerCAmelCase		:    Tuple			     =  []
        for i in range(3 ):
          for j in range(args[i] ):
            titles.append(string_starts[i] + str(j + 1 ) )
        titles.append('RHS' )
        return titles




      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE )				->  tuple[Any, Any]:
        __lowerCAmelCase		:    List[Any]			     =  self.objectives[-1]

        # Find entries of highest magnitude in objective rows
        __lowerCAmelCase		:    List[Any]			     =  (objective == 'min') - (objective == 'max')
        __lowerCAmelCase		:    Dict			     =  np.argmax(sign * tableau[0, : self.n_vars] )

        # Choice is only valid if below 0 for maximise, and above for minimise
        if sign * self.tableau[0, col_idx] <= 0:
          __lowerCAmelCase		:    Dict			     =  True
          return 0, 0

        # Pivot row is chosen as having the lowest quotient when elements of
        # the pivot column divide the right-hand side

        # Slice excluding the objective rows
        __lowerCAmelCase		:    str			     =  slice(self.n_stages     , self.n_rows )

        # RHS
        __lowerCAmelCase		:    Union[str, Any]			     =  tableau[s, -1]

        # Elements of pivot column within slice
        __lowerCAmelCase		:    Optional[Any]			     =  tableau[s, col_idx]

        # Array filled with nans
        __lowerCAmelCase		:    Optional[Any]			     =  np.full(self.n_rows - self.n_stages     , np.nan )

        # If element in pivot column is greater than zeron_stages, return
        # quotient or nan otherwise
        __lowerCAmelCase		:    Tuple			     =  np.divide(SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE     , out=SCREAMING_SNAKE_CASE     , where=divisor > 0 )

        # Arg of minimum quotient excluding the nan values. n_stages is added
        # to compensate for earlier exclusion of objective columns
        __lowerCAmelCase		:    Any			     =  np.nanargmin(SCREAMING_SNAKE_CASE ) + self.n_stages
        return row_idx, col_idx




      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )				->  np.ndarray:
        __lowerCAmelCase		:    Tuple			     =  tableau[row_idx].copy()

        __lowerCAmelCase		:    Optional[int]			     =  piv_row[col_idx]

        # Entry becomes 1
        piv_row *= 1 / piv_val

        # Variable in pivot column becomes basic, ie the only non-zero entry
        for idx, coeff in enumerate(tableau[:, col_idx] ):
          tableau[idx] += -coeff * piv_row
        __lowerCAmelCase		:    str			     =  piv_row
        return tableau




      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE )				->  np.ndarray:
        self.objectives.pop()

        if not self.objectives:
          return tableau

        # Slice containing ids for artificial columns
        __lowerCAmelCase		:    Optional[Any]			     =  slice(-self.n_art_vars - 1     , -1 )

        # Delete the artificial variable columns
        __lowerCAmelCase		:    str			     =  np.delete(SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE     , axis=1 )

        # Delete the objective row of the first stage
        __lowerCAmelCase		:    Optional[int]			     =  np.delete(SCREAMING_SNAKE_CASE     , 0     , axis=0 )

        __lowerCAmelCase		:    Tuple			     =  1
        self.n_rows -= 1
        __lowerCAmelCase		:    str			     =  0
        __lowerCAmelCase		:    Any			     =  False
        return tableau




      def 	snake_case  (     self )				->  dict[Any, Any]:
        # Stop simplex algorithm from cycling.
        for _ in range(1_00 ):
          # Completion of each stage removes an objective. If both stages
          # are complete, then no objectives are left
          if not self.objectives:
            __lowerCAmelCase		:    Any			     =  self.generate_col_titles(
                self.n_vars     , self.n_slack     , self.n_art_vars )

            # Find the values of each variable at optimal solution
            return self.interpret_tableau(self.tableau     , self.col_titles )

          __lowerCAmelCase     ,					__lowerCAmelCase		:    Tuple			     =  self.find_pivot(self.tableau )

          # If there are no more negative values in objective row
          if self.stop_iter:
            # Delete artificial variable columns and rows. Update attributes
            __lowerCAmelCase		:    Dict			     =  self.change_stage(self.tableau )
          else:
            __lowerCAmelCase		:    Optional[Any]			     =  self.pivot(self.tableau     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )
        return {}







      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )				->  dict[str, float]:
        __lowerCAmelCase		:    List[Any]			     =  {'P': abs(tableau[0, -1] )}

        for i in range(self.n_vars ):
          # Gives ids of nonzero entries in the ith column
          __lowerCAmelCase		:    str			     =  np.nonzero(tableau[:, i] )
          __lowerCAmelCase		:    Union[str, Any]			     =  len(nonzero[0] )

          # First entry in the nonzero ids
          __lowerCAmelCase		:    Optional[Any]			     =  nonzero[0][0]
          __lowerCAmelCase		:    str			     =  tableau[nonzero_rowidx, i]

          # If there is only one nonzero value in column, which is one
          if n_nonzero == nonzero_val == 1:
            __lowerCAmelCase		:    Dict			     =  tableau[nonzero_rowidx, -1]
            __lowerCAmelCase		:    List[Any]			     =  rhs_val

        # Check for basic variables
        for title in col_titles:
          # Don't add RHS or slack variables to output dict
          if title[0] not in "R-s-a":
            output_dict.setdefault(SCREAMING_SNAKE_CASE     , 0 )
        return output_dict


if __name__ == "__main__":
    import doctest

    doctest.testmod()












'''simple docstring'''


from typing import Any

import numpy as np


class 		UpperCamelCase__     :
      '''simple docstring'''






      def __init__(     self     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )				->  None:
        # Check if RHS is negative
        if np.any(tableau[:, -1]     , where=tableau[:, -1] < 0 ):
          raise ValueError('RHS must be > 0' )

        __lowerCAmelCase		:    Any			     =  tableau
        __lowerCAmelCase     ,					__lowerCAmelCase		:    Optional[int]			     =  tableau.shape

        # Number of decision variables x1, x2, x3...
        __lowerCAmelCase		:    Any			     =  n_vars

        # Number of artificial variables to be minimised
        __lowerCAmelCase		:    str			     =  len(np.where(tableau[self.n_vars : -1] == -1 )[0] )

        # 2 if there are >= or == constraints (nonstandard), 1 otherwise (std)
        __lowerCAmelCase		:    Union[str, Any]			     =  (self.n_art_vars > 0) + 1

        # Number of slack variables added to make inequalities into equalities
        __lowerCAmelCase		:    Any			     =  self.n_rows - self.n_stages

        # Objectives for each stage
        __lowerCAmelCase		:    List[Any]			     =  ['max']

        # In two stage simplex, first minimise then maximise
        if self.n_art_vars:
          self.objectives.append('min' )

        __lowerCAmelCase		:    Optional[Any]			     =  ['']

        # Index of current pivot row and column
        __lowerCAmelCase		:    List[Any]			     =  None
        __lowerCAmelCase		:    Dict			     =  None

        # Does objective row only contain (non)-negative values?
        __lowerCAmelCase		:    Any			     =  False




      @staticmethod
      def 	snake_case  (     *SCREAMING_SNAKE_CASE )				->  list[str]:
        if len(SCREAMING_SNAKE_CASE ) != 3:
          raise ValueError('Must provide n_vars, n_slack, and n_art_vars' )

        if not all(x >= 0 and isinstance(SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE ) for x in args ):
          raise ValueError('All arguments must be non-negative integers' )

        # decision | slack | artificial
        __lowerCAmelCase		:    int			     =  ['x', 's', 'a']
        __lowerCAmelCase		:    Tuple			     =  []
        for i in range(3 ):
          for j in range(args[i] ):
            titles.append(string_starts[i] + str(j + 1 ) )
        titles.append('RHS' )
        return titles




      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE )				->  tuple[Any, Any]:
        __lowerCAmelCase		:    List[Any]			     =  self.objectives[-1]

        # Find entries of highest magnitude in objective rows
        __lowerCAmelCase		:    List[Any]			     =  (objective == 'min') - (objective == 'max')
        __lowerCAmelCase		:    Dict			     =  np.argmax(sign * tableau[0, : self.n_vars] )

        # Choice is only valid if below 0 for maximise, and above for minimise
        if sign * self.tableau[0, col_idx] <= 0:
          __lowerCAmelCase		:    Dict			     =  True
          return 0, 0

        # Pivot row is chosen as having the lowest quotient when elements of
        # the pivot column divide the right-hand side

        # Slice excluding the objective rows
        __lowerCAmelCase		:    str			     =  slice(self.n_stages     , self.n_rows )

        # RHS
        __lowerCAmelCase		:    Union[str, Any]			     =  tableau[s, -1]

        # Elements of pivot column within slice
        __lowerCAmelCase		:    Optional[Any]			     =  tableau[s, col_idx]

        # Array filled with nans
        __lowerCAmelCase		:    Optional[Any]			     =  np.full(self.n_rows - self.n_stages     , np.nan )

        # If element in pivot column is greater than zeron_stages, return
        # quotient or nan otherwise
        __lowerCAmelCase		:    Tuple			     =  np.divide(SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE     , out=SCREAMING_SNAKE_CASE     , where=divisor > 0 )

        # Arg of minimum quotient excluding the nan values. n_stages is added
        # to compensate for earlier exclusion of objective columns
        __lowerCAmelCase		:    Any			     =  np.nanargmin(SCREAMING_SNAKE_CASE ) + self.n_stages
        return row_idx, col_idx




      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )				->  np.ndarray:
        __lowerCAmelCase		:    Tuple			     =  tableau[row_idx].copy()

        __lowerCAmelCase		:    Optional[int]			     =  piv_row[col_idx]

        # Entry becomes 1
        piv_row *= 1 / piv_val

        # Variable in pivot column becomes basic, ie the only non-zero entry
        for idx, coeff in enumerate(tableau[:, col_idx] ):
          tableau[idx] += -coeff * piv_row
        __lowerCAmelCase		:    str			     =  piv_row
        return tableau




      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE )				->  np.ndarray:
        self.objectives.pop()

        if not self.objectives:
          return tableau

        # Slice containing ids for artificial columns
        __lowerCAmelCase		:    Optional[Any]			     =  slice(-self.n_art_vars - 1     , -1 )

        # Delete the artificial variable columns
        __lowerCAmelCase		:    str			     =  np.delete(SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE     , axis=1 )

        # Delete the objective row of the first stage
        __lowerCAmelCase		:    Optional[int]			     =  np.delete(SCREAMING_SNAKE_CASE     , 0     , axis=0 )

        __lowerCAmelCase		:    Tuple			     =  1
        self.n_rows -= 1
        __lowerCAmelCase		:    str			     =  0
        __lowerCAmelCase		:    Any			     =  False
        return tableau




      def 	snake_case  (     self )				->  dict[Any, Any]:
        # Stop simplex algorithm from cycling.
        for _ in range(1_00 ):
          # Completion of each stage removes an objective. If both stages
          # are complete, then no objectives are left
          if not self.objectives:
            __lowerCAmelCase		:    Any			     =  self.generate_col_titles(
                self.n_vars     , self.n_slack     , self.n_art_vars )

            # Find the values of each variable at optimal solution
            return self.interpret_tableau(self.tableau     , self.col_titles )

          __lowerCAmelCase     ,					__lowerCAmelCase		:    Tuple			     =  self.find_pivot(self.tableau )

          # If there are no more negative values in objective row
          if self.stop_iter:
            # Delete artificial variable columns and rows. Update attributes
            __lowerCAmelCase		:    Dict			     =  self.change_stage(self.tableau )
          else:
            __lowerCAmelCase		:    Optional[Any]			     =  self.pivot(self.tableau     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )
        return {}







      def 	snake_case  (     self     , SCREAMING_SNAKE_CASE     , SCREAMING_SNAKE_CASE )				->  dict[str, float]:
        __lowerCAmelCase		:    List[Any]			     =  {'P': abs(tableau[0, -1] )}

        for i in range(self.n_vars ):
          # Gives ids of nonzero entries in the ith column
          __lowerCAmelCase		:    str			     =  np.nonzero(tableau[:, i] )
          __lowerCAmelCase		:    Union[str, Any]			     =  len(nonzero[0] )

          # First entry in the nonzero ids
          __lowerCAmelCase		:    Optional[Any]			     =  nonzero[0][0]
          __lowerCAmelCase		:    str			     =  tableau[nonzero_rowidx, i]

          # If there is only one nonzero value in column, which is one
          if n_nonzero == nonzero_val == 1:
            __lowerCAmelCase		:    Dict			     =  tableau[nonzero_rowidx, -1]
            __lowerCAmelCase		:    List[Any]			     =  rhs_val

        # Check for basic variables
        for title in col_titles:
          # Don't add RHS or slack variables to output dict
          if title[0] not in "R-s-a":
            output_dict.setdefault(SCREAMING_SNAKE_CASE     , 0 )
        return output_dict


if __name__ == "__main__":
    import doctest

    doctest.testmod()





