55class Tableau :
66 """Operate on simplex tableaus"""
77
8- # Max iteration number to prevent cycling
98 maxiter = 100
109
1110 def __init__ (
@@ -29,7 +28,6 @@ def __init__(
2928 self .n_artificial_vars = n_artificial_vars
3029
3130 self .n_stages = (self .n_artificial_vars > 0 ) + 1
32-
3331 self .n_slack = n_cols - self .n_vars - self .n_artificial_vars - 1
3432
3533 self .objectives = ["max" ]
@@ -38,19 +36,16 @@ def __init__(
3836
3937 self .col_titles = self .generate_col_titles ()
4038
41- self .row_idx = None
42- self .col_idx = None
43-
4439 self .stop_iter = False
4540
4641 def generate_col_titles (self ) -> list [str ]:
47- args = (self .n_vars , self .n_slack )
48-
4942 string_starts = ["x" , "s" ]
5043 titles = []
44+
5145 for i in range (2 ):
52- for j in range (args [i ]):
46+ for j in range (( self . n_vars , self . n_slack ) [i ]):
5347 titles .append (string_starts [i ] + str (j + 1 ))
48+
5449 titles .append ("RHS" )
5550 return titles
5651
@@ -75,16 +70,23 @@ def find_pivot(self) -> tuple[Any, Any]:
7570 row_idx = np .nanargmin (quotients ) + self .n_stages
7671 return row_idx , col_idx
7772
73+ # 🔥 OPTIMIZED PIVOT (major speed improvement)
7874 def pivot (self , row_idx : int , col_idx : int ) -> np .ndarray :
79- piv_row = self .tableau [row_idx ].copy ()
75+ tableau = self .tableau
76+
77+ piv_row = tableau [row_idx ].copy ()
8078 piv_val = piv_row [col_idx ]
8179
82- piv_row *= 1 / piv_val
80+ # normalize pivot row
81+ piv_row /= piv_val
8382
84- for idx , coeff in enumerate (self .tableau [:, col_idx ]):
85- self .tableau [idx ] += - coeff * piv_row
86- self .tableau [row_idx ] = piv_row
87- return self .tableau
83+ # vectorized elimination (FAST)
84+ tableau -= tableau [:, col_idx ][:, None ] * piv_row
85+
86+ tableau [row_idx ] = piv_row
87+ self .tableau = tableau
88+
89+ return tableau
8890
8991 def change_stage (self ) -> np .ndarray :
9092 self .objectives .pop ()
@@ -101,12 +103,15 @@ def change_stage(self) -> np.ndarray:
101103 self .n_rows -= 1
102104 self .n_artificial_vars = 0
103105 self .stop_iter = False
106+
104107 return self .tableau
105108
106109 def run_simplex (self ) -> dict [Any , Any ]:
107110 """Run simplex algorithm until optimal solution is found."""
108111
109- for iteration in range (Tableau .maxiter ):
112+ maxiter = Tableau .maxiter
113+
114+ for iteration in range (maxiter ):
110115
111116 if not self .objectives :
112117 return self .interpret_tableau ()
@@ -118,34 +123,30 @@ def run_simplex(self) -> dict[Any, Any]:
118123 else :
119124 self .tableau = self .pivot (row_idx , col_idx )
120125
121- # FIX: raise error instead of returning {}
126+ # FIXED: no silent failure anymore
122127 raise ValueError (
123128 "Simplex algorithm failed to converge.\n "
124- f"- Iterations performed: { iteration + 1 } \n "
125- f"- Max iterations allowed: { Tableau .maxiter } \n "
129+ f"- Iterations performed: { maxiter } \n "
126130 f"- Remaining objectives: { self .objectives } \n "
127131 "Possible causes:\n "
128- "- Cycling due to degeneracy\n "
129- "- Unbounded feasible region \n "
130- "- Ill-formed or poorly scaled input\n "
132+ "- Cycling ( degeneracy) \n "
133+ "- Unbounded solution \n "
134+ "- Ill-conditioned input"
131135 )
132136
133137 def interpret_tableau (self ) -> dict [str , float ]:
134138 output_dict = {"P" : abs (self .tableau [0 , - 1 ])}
135139
136140 for i in range (self .n_vars ):
137141 nonzero = np .nonzero (self .tableau [:, i ])
138- n_nonzero = len (nonzero [0 ])
139-
140- if n_nonzero == 0 :
142+ if len (nonzero [0 ]) == 0 :
141143 continue
142144
143- nonzero_rowidx = nonzero [0 ][0 ]
144- nonzero_val = self .tableau [nonzero_rowidx , i ]
145+ r = nonzero [0 ][0 ]
146+ val = self .tableau [r , i ]
145147
146- if n_nonzero == 1 and nonzero_val == 1 :
147- rhs_val = self .tableau [nonzero_rowidx , - 1 ]
148- output_dict [self .col_titles [i ]] = rhs_val
148+ if len (nonzero [0 ]) == 1 and val == 1 :
149+ output_dict [self .col_titles [i ]] = self .tableau [r , - 1 ]
149150
150151 return output_dict
151152
0 commit comments