pedrobritto-123 commited on
Commit
9666d22
·
verified ·
1 Parent(s): 03837b5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +2 -339
app.py CHANGED
@@ -1,340 +1,3 @@
1
- import gradio as gr
2
- import numpy as np
3
- import re
4
- import math
5
- import pandas as pd
6
- from typing import List, Dict, Any, Tuple
7
-
8
- EPS = 1e-9
9
- BIG_M = 1e6
10
-
11
- # ---------------- Parsing utilities ----------------
12
-
13
- def parse_coeffs(text: str) -> List[float]:
14
- if not text or not text.strip():
15
- return []
16
- s = text.replace(',', ' ')
17
- parts = [p for p in s.split() if p.strip()]
18
- coeffs = []
19
- for p in parts:
20
- try:
21
- coeffs.append(float(eval(p)))
22
- except Exception:
23
- raise ValueError(f"Coeficiente inválido: '{p}'")
24
- return coeffs
25
-
26
- def parse_constraints(text: str, nvars: int) -> List[Dict[str,Any]]:
27
- lines = [ln.strip() for ln in text.strip().splitlines() if ln.strip()]
28
- cons = []
29
- pattern = r'([+-]?[0-9./]*)x([0-9]+)'
30
-
31
- for ln in lines:
32
- s = ln.replace(' ', '')
33
- if '<=' in s or '=< ' in s:
34
- s = s.replace('=<', '<=')
35
- left, right = s.split('<=')
36
- sense = '<='
37
- elif '>=' in s or '=>' in s:
38
- s = s.replace('=>', '>=')
39
- left, right = s.split('>=')
40
- sense = '>='
41
- elif '=' in s:
42
- left, right = s.split('=')
43
- sense = '='
44
- else:
45
- raise ValueError(f"Restrição inválida, faltando <=, >= ou = : '{ln}'")
46
- try:
47
- rhs = float(eval(right))
48
- except Exception:
49
- raise ValueError(f"RHS inválido na restrição: '{right}' na linha '{ln}'")
50
-
51
- terms = re.findall(pattern, left)
52
- coeffs = [0.0] * nvars
53
- for coef_str, var_str in terms:
54
- idx = int(var_str) - 1
55
- if idx < 0 or idx >= nvars:
56
- raise ValueError(f"Variável fora do intervalo: x{idx+1} na linha '{ln}'")
57
- if coef_str in ['', '+']:
58
- v = 1.0
59
- elif coef_str == '-':
60
- v = -1.0
61
- else:
62
- try:
63
- v = float(eval(coef_str))
64
- except Exception:
65
- raise ValueError(f"Coeficiente inválido: '{coef_str}' na linha '{ln}'")
66
- coeffs[idx] += v
67
- cons.append({'coeffs': coeffs, 'sense': sense, 'rhs': rhs})
68
- return cons
69
-
70
- # ---------------- Tableau builder (Big-M) ----------------
71
-
72
- def build_tableau_bigM(c: List[float], constraints: List[Dict[str,Any]], sense: str = 'max') -> Tuple[np.ndarray, List[int], Tuple[int,int,int]]:
73
- obj_mult = 1.0
74
- if sense == 'min':
75
- obj_mult = -1.0
76
- c_adj = [ci * obj_mult for ci in c]
77
-
78
- n = len(c_adj)
79
- m = len(constraints)
80
-
81
- slacks = 0
82
- artificials = 0
83
- for row in constraints:
84
- if row['sense'] == '<=':
85
- slacks += 1
86
- elif row['sense'] == '>=':
87
- slacks += 1
88
- artificials += 1
89
- else:
90
- artificials += 1
91
-
92
- total_cols = n + slacks + artificials + 1
93
- T = np.zeros((m + 1, total_cols))
94
-
95
- slack_idx = n
96
- artificial_idx = n + slacks
97
-
98
- basis = []
99
- art_positions = []
100
-
101
- s_counter = 0
102
- a_counter = 0
103
-
104
- for i, row in enumerate(constraints):
105
- coeffs = row['coeffs']
106
- T[i, :n] = coeffs
107
- if row['sense'] == '<=':
108
- T[i, slack_idx + s_counter] = 1.0
109
- basis.append(slack_idx + s_counter)
110
- s_counter += 1
111
- elif row['sense'] == '>=':
112
- T[i, slack_idx + s_counter] = -1.0
113
- T[i, artificial_idx + a_counter] = 1.0
114
- basis.append(artificial_idx + a_counter)
115
- art_positions.append(artificial_idx + a_counter)
116
- s_counter += 1
117
- a_counter += 1
118
- else: # equality
119
- T[i, artificial_idx + a_counter] = 1.0
120
- basis.append(artificial_idx + a_counter)
121
- art_positions.append(artificial_idx + a_counter)
122
- a_counter += 1
123
- T[i, -1] = row['rhs']
124
-
125
- T[-1, :n] = -np.array(c_adj)
126
-
127
- for a_pos in art_positions:
128
- T[-1, a_pos] = T[-1, a_pos] - BIG_M
129
-
130
- return T, basis, (n, slacks, artificials)
131
-
132
- # ---------------- Simplex tableau operations ----------------
133
-
134
- def snapshot_html(tableau: np.ndarray, basis: List[int]) -> str:
135
- cols = tableau.shape[1]
136
- html = '<table border="1" style="border-collapse:collapse;font-family:Arial; font-size:12px;">'
137
- for i in range(tableau.shape[0]):
138
- html += '<tr>'
139
- for j in range(cols):
140
- val = tableau[i, j]
141
- html += f'<td style="padding:4px;">{val:.6g}</td>'
142
- html += '</tr>'
143
- html += '</table>'
144
- return html
145
-
146
- def primal_simplex_tableau(T: np.ndarray, basis: List[int], max_iters=500) -> Tuple[np.ndarray, List[int], List[Dict[str,Any]]]:
147
- m = T.shape[0] - 1
148
- path = []
149
- path.append({'tableau': T.copy(), 'basis': basis.copy(), 'html': snapshot_html(T, basis)})
150
- it = 0
151
- while it < max_iters:
152
- it += 1
153
- obj_row = T[-1, :-1]
154
- entering_candidates = np.where(obj_row < -EPS)[0]
155
- if entering_candidates.size == 0:
156
- break
157
- entering = int(entering_candidates[0])
158
- ratios = np.full(m, np.inf)
159
- for i in range(m):
160
- a = T[i, entering]
161
- if a > EPS:
162
- ratios[i] = T[i, -1] / a
163
- if np.all(np.isinf(ratios)):
164
- raise ValueError('Unbounded LP')
165
- leaving = int(np.argmin(ratios))
166
- piv = T[leaving, entering]
167
- T[leaving, :] = T[leaving, :] / piv
168
- for i in range(m+1):
169
- if i == leaving: continue
170
- T[i, :] = T[i, :] - T[i, entering] * T[leaving, :]
171
- basis[leaving] = entering
172
- path.append({'tableau': T.copy(), 'basis': basis.copy(), 'html': snapshot_html(T, basis)})
173
- return T, basis, path
174
-
175
- def dual_simplex_tableau(T: np.ndarray, basis: List[int], max_iters=500) -> Tuple[np.ndarray, List[int], List[Dict[str,Any]]]:
176
- m = T.shape[0] - 1
177
- ncols = T.shape[1]
178
- path = []
179
- path.append({'tableau': T.copy(), 'basis': basis.copy(), 'html': snapshot_html(T, basis)})
180
- it = 0
181
- while it < max_iters:
182
- it += 1
183
- rhs = T[:-1, -1]
184
- leaving_candidates = np.where(rhs < -EPS)[0]
185
- if leaving_candidates.size == 0:
186
- break
187
- leaving = int(leaving_candidates[0])
188
- entering = -1
189
- best = math.inf
190
- for j in range(ncols-1):
191
- a = T[leaving, j]
192
- cj = T[-1, j]
193
- if a < -EPS:
194
- val = cj / a
195
- if val < best:
196
- best = val
197
- entering = j
198
- if entering == -1:
199
- raise ValueError('Dual infeasible')
200
- piv = T[leaving, entering]
201
- T[leaving, :] = T[leaving, :] / piv
202
- for i in range(m+1):
203
- if i == leaving: continue
204
- T[i, :] = T[i, :] - T[i, entering] * T[leaving, :]
205
- basis[leaving] = entering
206
- path.append({'tableau': T.copy(), 'basis': basis.copy(), 'html': snapshot_html(T, basis)})
207
- return T, basis, path
208
-
209
- # ---------------- Extraction & reporting ----------------
210
-
211
- def extract_solution_from_bigM_tableau(T: np.ndarray, basis: List[int], n_orig: int) -> Tuple[List[float], float]:
212
- m = T.shape[0] - 1
213
- x = [0.0] * n_orig
214
- for i, bi in enumerate(basis):
215
- if bi < n_orig:
216
- x[bi] = float(T[i, -1])
217
- z = float(T[-1, -1])
218
- return x, z
219
-
220
- def clean_vector(vec):
221
- try:
222
- return [float(v) for v in vec]
223
- except:
224
- return vec
225
-
226
- # ---------------- Gradio handler ----------------
227
-
228
- def run_algorithms(nvars_str, objective_str, cons_str, sense, mode):
229
- try:
230
- nvars = int(nvars_str)
231
- if nvars <= 0:
232
- return 'Erro: nvars deve ser inteiro positivo', '', '', '', ''
233
- c = parse_coeffs(objective_str)
234
- if len(c) != nvars:
235
- return 'Erro: coeficientes do objetivo não correspondem a nvars', '', '', '', ''
236
- constraints = parse_constraints(cons_str, nvars)
237
- except Exception as e:
238
- return f'Erro ao ler entrada: {e}', '', '', '', ''
239
-
240
- try:
241
- T0, basis0, (n_orig, n_slack, n_art) = build_tableau_bigM(c, constraints, sense)
242
- except Exception as e:
243
- return f'Erro ao construir tableau: {e}', '', '', '', ''
244
-
245
- # run primal simplex
246
- try:
247
- T_primal, basis_primal, path_primal = primal_simplex_tableau(T0.copy(), basis0.copy())
248
- except Exception as e:
249
- return f'Erro durante Simplex primal: {e}', '', '', '', ''
250
-
251
- # run dual simplex starting from same initial tableau (demonstration)
252
- try:
253
- T_dual, basis_dual, path_dual = dual_simplex_tableau(T0.copy(), basis0.copy())
254
- except Exception as e:
255
- # dual pode falhar, mas não quebra a app — colocamos mensagem
256
- T_dual, basis_dual, path_dual = None, None, []
257
- dual_error = str(e)
258
-
259
- x_primal, z_primal = extract_solution_from_bigM_tableau(T_primal, basis_primal, n_orig)
260
-
261
- # final HTML steps (primal)
262
- steps_html_primal = ''
263
- for idx, step in enumerate(path_primal):
264
- steps_html_primal += f"<h4>Primal — Passo {idx+1} — Base: {step['basis']}</h4>"
265
- steps_html_primal += step['html'] + '<br/>'
266
-
267
- # final HTML steps (dual)
268
- steps_html_dual = ''
269
- if path_dual:
270
- for idx, step in enumerate(path_dual):
271
- steps_html_dual += f"<h4>Dual — Passo {idx+1} — Base: {step['basis']}</h4>"
272
- steps_html_dual += step['html'] + '<br/>'
273
- else:
274
- steps_html_dual = f"<p>Dual não executado: {locals().get('dual_error','(sem erro especificado)')}</p>"
275
-
276
- # solution table (primal)
277
- df = pd.DataFrame({'Variável': [f'x{i+1}' for i in range(len(x_primal))], 'Valor': x_primal})
278
- solution_html = df.to_html(index=False)
279
- solution_html += f"<p><b>Valor ótimo (estimado) = {z_primal:.6g}</b></p>"
280
-
281
- # reduced costs and shadow prices
282
- reduced = []
283
- for j in range(n_orig):
284
- z_j = -T_primal[-1, j]
285
- reduced.append(round(c[j] - z_j, 8))
286
- shadow = []
287
- for i in range(len(constraints)):
288
- idx = n_orig + i
289
- if idx < T_primal.shape[1]-1:
290
- shadow.append(round(-T_primal[-1, idx], 8))
291
- else:
292
- shadow.append(0.0)
293
-
294
- # clean numeric types
295
- x_primal = clean_vector(x_primal)
296
- reduced = clean_vector(reduced)
297
- shadow = clean_vector(shadow)
298
- z_primal = float(z_primal)
299
-
300
- model_txt = f"Objective ({'min' if sense=='min' else 'max'}): {c} \n Constraints: \n"
301
- for r in constraints:
302
- model_txt += f" {r['coeffs']} {r['sense']} {r['rhs']} \n"
303
-
304
- summary = ''
305
- summary += f"Solução primal x* = {x_primal} \n"
306
- summary += f"Z_primal (estimado) = {z_primal:.6g} \n"
307
- summary += f"Preços-sombra (dual estimado) = {shadow} \n"
308
- summary += f"Custos reduzidos (orig vars) = {reduced} \n"
309
-
310
- # outputs: model, solution_html, steps_primal_html, steps_dual_html, summary
311
- return model_txt, solution_html, steps_html_primal, steps_html_dual, summary
312
-
313
- # ---------------- Gradio UI ----------------
314
-
315
- with gr.Blocks() as demo:
316
- gr.Markdown("# Simplex (Big-M) — Primal & Dual (educational)")
317
- with gr.Row():
318
- with gr.Column(scale=1):
319
- nvars = gr.Textbox(label='Número de variáveis (n)', value='2')
320
- objective = gr.Textbox(label='Coeficientes da função objetivo (ex: \"60 30\" ou \"60,30\")', value='60 30')
321
- cons = gr.Textbox(label='Restrições (uma por linha). Ex.: 2x1+3x2<=300', lines=6,
322
- value='6x1 + 8x2 <= 48 \n1x1 <= 5 \n1x2 <= 4')
323
- sense = gr.Radio(['max','min'], value='max', label='Tipo de objetivo')
324
- run = gr.Button('Executar Simplex (Big-M)')
325
- with gr.Column(scale=2):
326
- model_out = gr.Textbox(label='Função objetivo e restrições (modelo)', lines=6)
327
- solution_out = gr.HTML(label='Solução ótima (tabela)')
328
- steps_primal_out = gr.HTML(label='Passos do Simplex (primal tableaus)')
329
- steps_dual_out = gr.HTML(label='Passos do Simplex (dual tableaus)')
330
- summary_out = gr.Textbox(label='Resumo', lines=8)
331
-
332
- run.click(run_algorithms, inputs=[nvars, objective, cons, sense, gr.State(value='primal_and_dual')], outputs=[model_out, solution_out, steps_primal_out, steps_dual_out, summary_out])
333
-
334
- gr.Examples(examples=[["2","60 30","6x1 + 8x2 <= 48 \n 1x1 <= 5 \n 1x2 <= 4","max"]], inputs=[nvars, objective, cons, sense])
335
-
336
- if __name__ == '__main__':
337
- demo.launch()
338
 
339
  """
340
  # app.py — Simplex Duas Fases + Simplex Dual + PDF + Gradio (corrigido)
@@ -1033,7 +696,7 @@ if __name__ == '__main__':
1033
 
1034
 
1035
 
1036
- """import gradio as gr
1037
  from typing import List, Tuple, Dict, Any
1038
  import math
1039
  import re
@@ -1248,6 +911,6 @@ with gr.Blocks() as demo:
1248
  inputs=[nvars, objective, cons, sense],
1249
  outputs=[model_out, primal_out, dual_out])
1250
  if __name__ == "__main__":
1251
- demo.launch()"""
1252
 
1253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
  """
3
  # app.py — Simplex Duas Fases + Simplex Dual + PDF + Gradio (corrigido)
 
696
 
697
 
698
 
699
+ import gradio as gr
700
  from typing import List, Tuple, Dict, Any
701
  import math
702
  import re
 
911
  inputs=[nvars, objective, cons, sense],
912
  outputs=[model_out, primal_out, dual_out])
913
  if __name__ == "__main__":
914
+ demo.launch()
915
 
916