C2MV commited on
Commit
326c90a
·
verified ·
1 Parent(s): 3ce9550

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -169
app.py CHANGED
@@ -1,6 +1,5 @@
1
  #import os
2
  #!pip install gradio seaborn scipy scikit-learn openpyxl pydantic==1.10.0 -q
3
-
4
  from pydantic import BaseModel, ConfigDict
5
  import numpy as np
6
  import pandas as pd
@@ -80,28 +79,23 @@ class BioprocessModel:
80
  biomass_cols = [col for col in df.columns if col[1] == 'Biomasa']
81
  substrate_cols = [col for col in df.columns if col[1] == 'Sustrato']
82
  product_cols = [col for col in df.columns if col[1] == 'Producto']
83
-
84
  time_col = [col for col in df.columns if col[1] == 'Tiempo'][0]
85
  time = df[time_col].values
86
-
87
  data_biomass = [df[col].values for col in biomass_cols]
88
  data_biomass = np.array(data_biomass)
89
  self.datax.append(data_biomass)
90
  self.dataxp.append(np.mean(data_biomass, axis=0))
91
  self.datax_std.append(np.std(data_biomass, axis=0, ddof=1))
92
-
93
  data_substrate = [df[col].values for col in substrate_cols]
94
  data_substrate = np.array(data_substrate)
95
  self.datas.append(data_substrate)
96
  self.datasp.append(np.mean(data_substrate, axis=0))
97
  self.datas_std.append(np.std(data_substrate, axis=0, ddof=1))
98
-
99
  data_product = [df[col].values for col in product_cols]
100
  data_product = np.array(data_product)
101
  self.datap.append(data_product)
102
  self.datapp.append(np.mean(data_product, axis=0))
103
  self.datap_std.append(np.std(data_product, axis=0, ddof=1))
104
-
105
  self.time = time
106
 
107
  def fit_model(self):
@@ -132,7 +126,6 @@ class BioprocessModel:
132
  popt, _ = curve_fit(self.moser, time, biomass, p0=p0, maxfev=self.maxfev)
133
  self.params['biomass'] = {'Xm': popt[0], 'um': popt[1], 'Ks': popt[2]}
134
  y_pred = self.moser(time, *popt)
135
-
136
  self.r2['biomass'] = 1 - (np.sum((biomass - y_pred) ** 2) / np.sum((biomass - np.mean(biomass)) ** 2))
137
  self.rmse['biomass'] = np.sqrt(mean_squared_error(biomass, y_pred))
138
  return y_pred
@@ -212,7 +205,6 @@ class BioprocessModel:
212
 
213
  def system(self, y, t, biomass_params, substrate_params, product_params, model_type):
214
  X, S, P = y
215
-
216
  if model_type == 'logistic':
217
  dXdt = self.logistic_diff(X, t, biomass_params)
218
  elif model_type == 'gompertz':
@@ -221,10 +213,8 @@ class BioprocessModel:
221
  dXdt = self.moser_diff(X, t, biomass_params)
222
  else:
223
  dXdt = 0.0
224
-
225
  so, p, q = substrate_params
226
  po, alpha, beta = product_params
227
-
228
  dSdt = -p * dXdt - q * X
229
  dPdt = alpha * dXdt + beta * X
230
  return [dXdt, dSdt, dPdt]
@@ -246,26 +236,22 @@ class BioprocessModel:
246
  X0 = Xm*(1 - np.exp(-um*(0 - Ks)))
247
  else:
248
  X0 = biomass[0]
249
-
250
  if 'substrate' in self.params:
251
  so = self.params['substrate']['so']
252
  S0 = so
253
  else:
254
  S0 = substrate[0]
255
-
256
  if 'product' in self.params:
257
  po = self.params['product']['po']
258
  P0 = po
259
  else:
260
  P0 = product[0]
261
-
262
  return [X0, S0, P0]
263
 
264
  def solve_differential_equations(self, time, biomass, substrate, product):
265
  if 'biomass' not in self.params or not self.params['biomass']:
266
  print("No hay parámetros de biomasa, no se pueden resolver las EDO.")
267
  return None, None, None, time
268
-
269
  if self.model_type == 'logistic':
270
  biomass_params = [self.params['biomass']['xo'], self.params['biomass']['xm'], self.params['biomass']['um']]
271
  elif self.model_type == 'gompertz':
@@ -274,26 +260,21 @@ class BioprocessModel:
274
  biomass_params = [self.params['biomass']['Xm'], self.params['biomass']['um'], self.params['biomass']['Ks']]
275
  else:
276
  biomass_params = [0,0,0]
277
-
278
  if 'substrate' in self.params:
279
  substrate_params = [self.params['substrate']['so'], self.params['substrate']['p'], self.params['substrate']['q']]
280
  else:
281
  substrate_params = [0,0,0]
282
-
283
  if 'product' in self.params:
284
  product_params = [self.params['product']['po'], self.params['product']['alpha'], self.params['product']['beta']]
285
  else:
286
  product_params = [0,0,0]
287
-
288
  initial_conditions = self.get_initial_conditions(time, biomass, substrate, product)
289
  time_fine = self.generate_fine_time_grid(time)
290
  sol = odeint(self.system, initial_conditions, time_fine,
291
  args=(biomass_params, substrate_params, product_params, self.model_type))
292
-
293
  X = sol[:, 0]
294
  S = sol[:, 1]
295
  P = sol[:, 2]
296
-
297
  return X, S, P, time_fine
298
 
299
  def plot_results(self, time, biomass, substrate, product,
@@ -303,15 +284,11 @@ class BioprocessModel:
303
  show_legend=True, show_params=True,
304
  style='whitegrid',
305
  line_color='#0000FF', point_color='#000000', line_style='-', marker_style='o',
306
- use_differential=False,
307
- x_label='Tiempo', y_label_biomass='Biomasa', y_label_substrate='Sustrato', y_label_product='Producto'):
308
-
309
  if y_pred_biomass is None:
310
  print(f"No se pudo ajustar biomasa para {experiment_name} con {self.model_type}. Omitiendo figura.")
311
  return None
312
-
313
  sns.set_style(style)
314
-
315
  if use_differential and 'biomass' in self.params and self.params['biomass']:
316
  X, S, P, time_to_plot = self.solve_differential_equations(time, biomass, substrate, product)
317
  if X is not None:
@@ -320,36 +297,30 @@ class BioprocessModel:
320
  time_to_plot = time
321
  else:
322
  time_to_plot = time
323
-
324
  fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 15))
325
  fig.suptitle(f'{experiment_name}', fontsize=16)
326
-
327
  plots = [
328
- (ax1, biomass, y_pred_biomass, biomass_std, y_label_biomass, 'Modelo', self.params.get('biomass', {}),
329
  self.r2.get('biomass', np.nan), self.rmse.get('biomass', np.nan)),
330
- (ax2, substrate, y_pred_substrate, substrate_std, y_label_substrate, 'Modelo', self.params.get('substrate', {}),
331
  self.r2.get('substrate', np.nan), self.rmse.get('substrate', np.nan)),
332
- (ax3, product, y_pred_product, product_std, y_label_product, 'Modelo', self.params.get('product', {}),
333
  self.r2.get('product', np.nan), self.rmse.get('product', np.nan))
334
  ]
335
-
336
  for idx, (ax, data, y_pred, data_std, ylabel, model_name, params, r2, rmse) in enumerate(plots):
337
  if data_std is not None:
338
  ax.errorbar(time, data, yerr=data_std, fmt=marker_style, color=point_color,
339
- label='Datos experimentales', capsize=5)
340
  else:
341
  ax.plot(time, data, marker=marker_style, linestyle='', color=point_color,
342
- label='Datos experimentales')
343
-
344
  if y_pred is not None:
345
  ax.plot(time_to_plot, y_pred, linestyle=line_style, color=line_color, label=model_name)
346
-
347
- ax.set_xlabel(x_label)
348
  ax.set_ylabel(ylabel)
349
  if show_legend:
350
  ax.legend(loc=legend_position)
351
  ax.set_title(f'{ylabel}')
352
-
353
  if show_params and params and all(np.isfinite(list(map(float, params.values())))):
354
  param_text = '\n'.join([f"{k} = {v:.3f}" for k, v in params.items()])
355
  text = f"{param_text}\nR² = {r2:.3f}\nRMSE = {rmse:.3f}"
@@ -364,26 +335,21 @@ class BioprocessModel:
364
  else:
365
  text_x = 0.05
366
  ha = 'left'
367
-
368
  if params_position in ['upper right', 'upper left']:
369
  text_y = 0.95
370
  va = 'top'
371
  else:
372
  text_y = 0.05
373
  va = 'bottom'
374
-
375
  ax.text(text_x, text_y, text, transform=ax.transAxes,
376
  verticalalignment=va, horizontalalignment=ha,
377
  bbox={'boxstyle': 'round', 'facecolor':'white', 'alpha':0.5})
378
-
379
  plt.tight_layout(rect=[0, 0.03, 1, 0.95])
380
-
381
  buf = io.BytesIO()
382
  fig.savefig(buf, format='png')
383
  buf.seek(0)
384
  image = Image.open(buf).convert("RGB")
385
  plt.close(fig)
386
-
387
  return image
388
 
389
  def plot_combined_results(self, time, biomass, substrate, product,
@@ -393,15 +359,11 @@ class BioprocessModel:
393
  show_legend=True, show_params=True,
394
  style='whitegrid',
395
  line_color='#0000FF', point_color='#000000', line_style='-', marker_style='o',
396
- use_differential=False,
397
- x_label='Tiempo', y_label_biomass='Biomasa', y_label_substrate='Sustrato', y_label_product='Producto'):
398
-
399
  if y_pred_biomass is None:
400
  print(f"No se pudo ajustar biomasa para {experiment_name} con {self.model_type}. Omitiendo figura.")
401
  return None
402
-
403
  sns.set_style(style)
404
-
405
  if use_differential and 'biomass' in self.params and self.params['biomass']:
406
  X, S, P, time_to_plot = self.solve_differential_equations(time, biomass, substrate, product)
407
  if X is not None:
@@ -410,79 +372,67 @@ class BioprocessModel:
410
  time_to_plot = time
411
  else:
412
  time_to_plot = time
413
-
414
  fig, ax1 = plt.subplots(figsize=(10, 7))
415
  fig.suptitle(f'{experiment_name}', fontsize=16)
416
-
417
- colors = {'Biomasa': 'blue', 'Sustrato': 'green', 'Producto': 'red'}
418
-
419
- ax1.set_xlabel(x_label)
420
- ax1.set_ylabel(y_label_biomass, color=colors['Biomasa'])
421
  if biomass_std is not None:
422
- ax1.errorbar(time, biomass, yerr=biomass_std, fmt=marker_style, color=colors['Biomasa'],
423
- label='Biomasa (Datos)', capsize=5)
424
  else:
425
- ax1.plot(time, biomass, marker=marker_style, linestyle='', color=colors['Biomasa'],
426
- label='Biomasa (Datos)')
427
- ax1.plot(time_to_plot, y_pred_biomass, linestyle=line_style, color=colors['Biomasa'],
428
- label='Biomasa (Modelo)')
429
- ax1.tick_params(axis='y', labelcolor=colors['Biomasa'])
430
-
431
  ax2 = ax1.twinx()
432
- ax2.set_ylabel(y_label_substrate, color=colors['Sustrato'])
433
  if substrate_std is not None:
434
- ax2.errorbar(time, substrate, yerr=substrate_std, fmt=marker_style, color=colors['Sustrato'],
435
- label='Sustrato (Datos)', capsize=5)
436
  else:
437
- ax2.plot(time, substrate, marker=marker_style, linestyle='', color=colors['Sustrato'],
438
- label='Sustrato (Datos)')
439
  if y_pred_substrate is not None:
440
- ax2.plot(time_to_plot, y_pred_substrate, linestyle=line_style, color=colors['Sustrato'],
441
- label='Sustrato (Modelo)')
442
- ax2.tick_params(axis='y', labelcolor=colors['Sustrato'])
443
-
444
  ax3 = ax1.twinx()
445
  ax3.spines["right"].set_position(("axes", 1.2))
446
  ax3.set_frame_on(True)
447
  ax3.patch.set_visible(False)
448
  for sp in ax3.spines.values():
449
  sp.set_visible(True)
450
-
451
- ax3.set_ylabel(y_label_product, color=colors['Producto'])
452
  if product_std is not None:
453
- ax3.errorbar(time, product, yerr=product_std, fmt=marker_style, color=colors['Producto'],
454
- label='Producto (Datos)', capsize=5)
455
  else:
456
- ax3.plot(time, product, marker=marker_style, linestyle='', color=colors['Producto'],
457
- label='Producto (Datos)')
458
  if y_pred_product is not None:
459
- ax3.plot(time_to_plot, y_pred_product, linestyle=line_style, color=colors['Producto'],
460
- label='Producto (Modelo)')
461
- ax3.tick_params(axis='y', labelcolor=colors['Producto'])
462
-
463
  lines_labels = [ax.get_legend_handles_labels() for ax in [ax1, ax2, ax3]]
464
  lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
465
  if show_legend:
466
  ax1.legend(lines, labels, loc=legend_position)
467
-
468
  if show_params:
469
  param_text_biomass = ''
470
  if 'biomass' in self.params:
471
  param_text_biomass = '\n'.join([f"{k} = {v:.3f}" for k, v in self.params['biomass'].items()])
472
- text_biomass = f"Biomasa:\n{param_text_biomass}\nR² = {self.r2.get('biomass', np.nan):.3f}\nRMSE = {self.rmse.get('biomass', np.nan):.3f}"
473
-
474
  param_text_substrate = ''
475
  if 'substrate' in self.params:
476
  param_text_substrate = '\n'.join([f"{k} = {v:.3f}" for k, v in self.params['substrate'].items()])
477
- text_substrate = f"Sustrato:\n{param_text_substrate}\nR² = {self.r2.get('substrate', np.nan):.3f}\nRMSE = {self.rmse.get('substrate', np.nan):.3f}"
478
-
479
  param_text_product = ''
480
  if 'product' in self.params:
481
  param_text_product = '\n'.join([f"{k} = {v:.3f}" for k, v in self.params['product'].items()])
482
- text_product = f"Producto:\n{param_text_product}\nR² = {self.r2.get('product', np.nan):.3f}\nRMSE = {self.rmse.get('product', np.nan):.3f}"
483
-
484
- total_text = f"{text_biomass}\n\n{text_substrate}\n\n{text_product}"
485
-
486
  if params_position == 'outside right':
487
  bbox_props = dict(boxstyle='round', facecolor='white', alpha=0.5)
488
  ax3.annotate(total_text, xy=(1.2, 0.5), xycoords='axes fraction',
@@ -494,55 +444,44 @@ class BioprocessModel:
494
  else:
495
  text_x = 0.05
496
  ha = 'left'
497
-
498
  if params_position in ['upper right', 'upper left']:
499
  text_y = 0.95
500
  va = 'top'
501
  else:
502
  text_y = 0.05
503
  va = 'bottom'
504
-
505
  ax1.text(text_x, text_y, total_text, transform=ax1.transAxes,
506
  verticalalignment=va, horizontalalignment=ha,
507
  bbox={'boxstyle':'round', 'facecolor':'white', 'alpha':0.5})
508
-
509
  plt.tight_layout(rect=[0, 0.03, 1, 0.95])
510
-
511
  buf = io.BytesIO()
512
  fig.savefig(buf, format='png')
513
  buf.seek(0)
514
  image = Image.open(buf).convert("RGB")
515
  plt.close(fig)
516
-
517
  return image
518
 
519
  def process_all_data(file, legend_position, params_position, model_types, experiment_names, lower_bounds, upper_bounds,
520
  mode='independent', style='whitegrid', line_color='#0000FF', point_color='#000000',
521
- line_style='-', marker_style='o', show_legend=True, show_params=True, use_differential=False, maxfev_val=50000,
522
- x_label='Tiempo', y_label_biomass='Biomasa', y_label_substrate='Sustrato', y_label_product='Producto'):
523
-
524
  try:
525
  xls = pd.ExcelFile(file.name)
526
  except Exception as e:
527
  print(f"Error al leer el archivo Excel: {e}")
528
  return [], pd.DataFrame()
529
-
530
  sheet_names = xls.sheet_names
531
  figures = []
532
  comparison_data = []
533
  experiment_counter = 0
534
-
535
  for sheet_name in sheet_names:
536
  try:
537
  df = pd.read_excel(file.name, sheet_name=sheet_name, header=[0, 1])
538
  except Exception as e:
539
  print(f"Error al leer la hoja '{sheet_name}': {e}")
540
  continue
541
-
542
  model_dummy = BioprocessModel()
543
  model_dummy.process_data(df)
544
  time = model_dummy.time
545
-
546
  if mode == 'independent':
547
  num_experiments = len(df.columns.levels[0])
548
  for idx in range(num_experiments):
@@ -555,7 +494,6 @@ def process_all_data(file, legend_position, params_position, model_types, experi
555
  except KeyError as e:
556
  print(f"Error al procesar el experimento '{col}': {e}")
557
  continue
558
-
559
  biomass_std = None
560
  substrate_std = None
561
  product_std = None
@@ -568,14 +506,11 @@ def process_all_data(file, legend_position, params_position, model_types, experi
568
  if product.ndim > 1:
569
  product_std = np.std(product, axis=0, ddof=1)
570
  product = np.mean(product, axis=0)
571
-
572
  experiment_name = (experiment_names[experiment_counter] if experiment_counter < len(experiment_names)
573
  else f"Tratamiento {experiment_counter + 1}")
574
-
575
  for model_type in model_types:
576
  model = BioprocessModel(model_type=model_type, maxfev=maxfev_val)
577
  model.fit_model()
578
-
579
  y_pred_biomass = model.fit_biomass(time_exp, biomass)
580
  if y_pred_biomass is None:
581
  comparison_data.append({
@@ -596,7 +531,6 @@ def process_all_data(file, legend_position, params_position, model_types, experi
596
  else:
597
  y_pred_substrate = None
598
  y_pred_product = None
599
-
600
  comparison_data.append({
601
  'Experimento': experiment_name,
602
  'Modelo': model_type.capitalize(),
@@ -607,7 +541,6 @@ def process_all_data(file, legend_position, params_position, model_types, experi
607
  'R² Producto': model.r2.get('product', np.nan),
608
  'RMSE Producto': model.rmse.get('product', np.nan)
609
  })
610
-
611
  if mode == 'combinado':
612
  fig = model.plot_combined_results(time_exp, biomass, substrate, product,
613
  y_pred_biomass, y_pred_substrate, y_pred_product,
@@ -617,8 +550,7 @@ def process_all_data(file, legend_position, params_position, model_types, experi
617
  show_legend, show_params,
618
  style,
619
  line_color, point_color, line_style, marker_style,
620
- use_differential,
621
- x_label, y_label_biomass, y_label_substrate, y_label_product)
622
  else:
623
  fig = model.plot_results(time_exp, biomass, substrate, product,
624
  y_pred_biomass, y_pred_substrate, y_pred_product,
@@ -628,13 +560,10 @@ def process_all_data(file, legend_position, params_position, model_types, experi
628
  show_legend, show_params,
629
  style,
630
  line_color, point_color, line_style, marker_style,
631
- use_differential,
632
- x_label, y_label_biomass, y_label_substrate, y_label_product)
633
  if fig is not None:
634
  figures.append(fig)
635
-
636
  experiment_counter += 1
637
-
638
  elif mode in ['average', 'combinado']:
639
  try:
640
  time_exp = df[(df.columns.levels[0][0], 'Tiempo')].dropna().values
@@ -644,18 +573,14 @@ def process_all_data(file, legend_position, params_position, model_types, experi
644
  except IndexError as e:
645
  print(f"Error al obtener los datos promedio de la hoja '{sheet_name}': {e}")
646
  continue
647
-
648
  biomass_std = model_dummy.datax_std[-1]
649
  substrate_std = model_dummy.datas_std[-1]
650
  product_std = model_dummy.datap_std[-1]
651
-
652
  experiment_name = (experiment_names[experiment_counter] if experiment_counter < len(experiment_names)
653
  else f"Tratamiento {experiment_counter + 1}")
654
-
655
  for model_type in model_types:
656
  model = BioprocessModel(model_type=model_type, maxfev=maxfev_val)
657
  model.fit_model()
658
-
659
  y_pred_biomass = model.fit_biomass(time_exp, biomass)
660
  if y_pred_biomass is None:
661
  comparison_data.append({
@@ -676,7 +601,6 @@ def process_all_data(file, legend_position, params_position, model_types, experi
676
  else:
677
  y_pred_substrate = None
678
  y_pred_product = None
679
-
680
  comparison_data.append({
681
  'Experimento': experiment_name,
682
  'Modelo': model_type.capitalize(),
@@ -687,7 +611,6 @@ def process_all_data(file, legend_position, params_position, model_types, experi
687
  'R² Producto': model.r2.get('product', np.nan),
688
  'RMSE Producto': model.rmse.get('product', np.nan)
689
  })
690
-
691
  if mode == 'combinado':
692
  fig = model.plot_combined_results(time_exp, biomass, substrate, product,
693
  y_pred_biomass, y_pred_substrate, y_pred_product,
@@ -697,8 +620,7 @@ def process_all_data(file, legend_position, params_position, model_types, experi
697
  show_legend, show_params,
698
  style,
699
  line_color, point_color, line_style, marker_style,
700
- use_differential,
701
- x_label, y_label_biomass, y_label_substrate, y_label_product)
702
  else:
703
  fig = model.plot_results(time_exp, biomass, substrate, product,
704
  y_pred_biomass, y_pred_substrate, y_pred_product,
@@ -708,15 +630,11 @@ def process_all_data(file, legend_position, params_position, model_types, experi
708
  show_legend, show_params,
709
  style,
710
  line_color, point_color, line_style, marker_style,
711
- use_differential,
712
- x_label, y_label_biomass, y_label_substrate, y_label_product)
713
  if fig is not None:
714
  figures.append(fig)
715
-
716
  experiment_counter += 1
717
-
718
  comparison_df = pd.DataFrame(comparison_data)
719
-
720
  if not comparison_df.empty:
721
  comparison_df_sorted = comparison_df.sort_values(
722
  by=['R² Biomasa', 'R² Sustrato', 'R² Producto', 'RMSE Biomasa', 'RMSE Sustrato', 'RMSE Producto'],
@@ -724,54 +642,42 @@ def process_all_data(file, legend_position, params_position, model_types, experi
724
  ).reset_index(drop=True)
725
  else:
726
  comparison_df_sorted = comparison_df
727
-
728
  return figures, comparison_df_sorted
729
 
730
  def create_interface():
731
  with gr.Blocks() as demo:
732
  gr.Markdown("# Modelos de Bioproceso: Logístico, Gompertz, Moser y Luedeking-Piret")
733
-
734
  gr.Markdown(r"""
735
  ## Ecuaciones Diferenciales Utilizadas
736
-
737
  **Biomasa:**
738
-
739
  - Logístico:
740
  $$
741
  \frac{dX}{dt} = \mu_m X\left(1 - \frac{X}{X_m}\right)
742
  $$
743
-
744
  - Gompertz:
745
  $$
746
  X(t) = X_m \exp\left(-\exp\left(\left(\frac{\mu_m e}{X_m}\right)(\text{lag}-t)+1\right)\right)
747
  $$
748
-
749
  Ecuación diferencial:
750
  $$
751
  \frac{dX}{dt} = X(t)\left(\frac{\mu_m e}{X_m}\right)\exp\left(\left(\frac{\mu_m e}{X_m}\right)(\text{lag}-t)+1\right)
752
  $$
753
-
754
  - Moser (simplificado):
755
  $$
756
  X(t)=X_m(1-e^{-\mu_m(t-K_s)})
757
  $$
758
-
759
  $$
760
  \frac{dX}{dt}=\mu_m(X_m - X)
761
  $$
762
-
763
  **Sustrato y Producto (Luedeking-Piret):**
764
  $$
765
  \frac{dS}{dt} = -p \frac{dX}{dt} - q X
766
  $$
767
-
768
  $$
769
  \frac{dP}{dt} = \alpha \frac{dX}{dt} + \beta X
770
  $$
771
  """)
772
-
773
  file_input = gr.File(label="Subir archivo Excel")
774
-
775
  with gr.Row():
776
  with gr.Column():
777
  legend_position = gr.Radio(
@@ -780,7 +686,6 @@ $$
780
  value="best"
781
  )
782
  show_legend = gr.Checkbox(label="Mostrar Leyenda", value=True)
783
-
784
  with gr.Column():
785
  params_positions = ["upper left", "upper right", "lower left", "lower right", "outside right"]
786
  params_position = gr.Radio(
@@ -789,7 +694,6 @@ $$
789
  value="upper right"
790
  )
791
  show_params = gr.Checkbox(label="Mostrar Parámetros", value=True)
792
-
793
  model_types = gr.CheckboxGroup(
794
  choices=["logistic", "gompertz", "moser"],
795
  label="Tipo(s) de Modelo",
@@ -797,19 +701,11 @@ $$
797
  )
798
  mode = gr.Radio(["independent", "average", "combinado"], label="Modo de Análisis", value="independent")
799
  use_differential = gr.Checkbox(label="Usar ecuaciones diferenciales para graficar", value=False)
800
-
801
  experiment_names = gr.Textbox(
802
  label="Nombres de los experimentos (uno por línea)",
803
  placeholder="Experimento 1\nExperimento 2\n...",
804
  lines=5
805
  )
806
-
807
- with gr.Row():
808
- x_label = gr.Textbox(label="Etiqueta del eje X", value="Tiempo")
809
- y_label_biomass = gr.Textbox(label="Etiqueta del eje Y (Biomasa)", value="Biomasa")
810
- y_label_substrate = gr.Textbox(label="Etiqueta del eje Y (Sustrato)", value="Sustrato")
811
- y_label_product = gr.Textbox(label="Etiqueta del eje Y (Producto)", value="Producto")
812
-
813
  with gr.Row():
814
  with gr.Column():
815
  lower_bounds = gr.Textbox(
@@ -817,30 +713,22 @@ $$
817
  placeholder="0,0,0\n0,0,0\n...",
818
  lines=5
819
  )
820
-
821
  with gr.Column():
822
  upper_bounds = gr.Textbox(
823
  label="Upper Bounds (uno por línea, formato: param1,param2,param3)",
824
  placeholder="inf,inf,inf\ninf,inf,inf\n...",
825
  lines=5
826
  )
827
-
828
  styles = ['white', 'dark', 'whitegrid', 'darkgrid', 'ticks']
829
  style_dropdown = gr.Dropdown(choices=styles, label="Selecciona el estilo de gráfico", value='whitegrid')
830
-
831
  line_color_picker = gr.ColorPicker(label="Color de la línea", value='#0000FF')
832
  point_color_picker = gr.ColorPicker(label="Color de los puntos", value='#000000')
833
-
834
  line_style_options = ['-', '--', '-.', ':']
835
  line_style_dropdown = gr.Dropdown(choices=line_style_options, label="Estilo de línea", value='-')
836
-
837
  marker_style_options = ['o', 's', '^', 'v', 'D', 'x', '+', '*']
838
  marker_style_dropdown = gr.Dropdown(choices=marker_style_options, label="Estilo de punto", value='o')
839
-
840
  maxfev_input = gr.Number(label="maxfev (Máx. evaluaciones para el ajuste)", value=50000)
841
-
842
  simulate_btn = gr.Button("Simular")
843
-
844
  output_gallery = gr.Gallery(label="Resultados", columns=2, height='auto')
845
  output_table = gr.Dataframe(
846
  label="Tabla Comparativa de Modelos",
@@ -848,15 +736,12 @@ $$
848
  "R² Sustrato", "RMSE Sustrato", "R² Producto", "RMSE Producto"],
849
  interactive=False
850
  )
851
-
852
  state_df = gr.State()
853
 
854
  def process_and_plot(file, legend_position, params_position, model_types, mode, experiment_names,
855
  lower_bounds, upper_bounds, style,
856
  line_color, point_color, line_style, marker_style,
857
- show_legend, show_params, use_differential, maxfev_input,
858
- x_label, y_label_biomass, y_label_substrate, y_label_product):
859
-
860
  experiment_names_list = experiment_names.strip().split('\n') if experiment_names.strip() else []
861
  lower_bounds_list = []
862
  if lower_bounds.strip():
@@ -886,13 +771,10 @@ $$
886
  except ValueError:
887
  ub_values.append(np.inf)
888
  upper_bounds_list.append(tuple(ub_values))
889
-
890
  figures, comparison_df = process_all_data(file, legend_position, params_position, model_types, experiment_names_list,
891
  lower_bounds_list, upper_bounds_list, mode, style,
892
  line_color, point_color, line_style, marker_style,
893
- show_legend, show_params, use_differential, maxfev_val=int(maxfev_input),
894
- x_label="Tiempo", y_label_biomass="Biomasa", y_label_substrate="Sustrato", y_label_product="Producto")
895
-
896
  return figures, comparison_df, comparison_df
897
 
898
  simulate_output = simulate_btn.click(
@@ -913,11 +795,7 @@ $$
913
  show_legend,
914
  show_params,
915
  use_differential,
916
- maxfev_input,
917
- x_label,
918
- y_label_biomass,
919
- y_label_substrate,
920
- y_label_product],
921
  outputs=[output_gallery, output_table, state_df]
922
  )
923
 
@@ -930,7 +808,6 @@ $$
930
 
931
  export_btn = gr.Button("Exportar Tabla a Excel")
932
  file_output = gr.File()
933
-
934
  export_btn.click(
935
  fn=export_excel,
936
  inputs=state_df,
 
1
  #import os
2
  #!pip install gradio seaborn scipy scikit-learn openpyxl pydantic==1.10.0 -q
 
3
  from pydantic import BaseModel, ConfigDict
4
  import numpy as np
5
  import pandas as pd
 
79
  biomass_cols = [col for col in df.columns if col[1] == 'Biomasa']
80
  substrate_cols = [col for col in df.columns if col[1] == 'Sustrato']
81
  product_cols = [col for col in df.columns if col[1] == 'Producto']
 
82
  time_col = [col for col in df.columns if col[1] == 'Tiempo'][0]
83
  time = df[time_col].values
 
84
  data_biomass = [df[col].values for col in biomass_cols]
85
  data_biomass = np.array(data_biomass)
86
  self.datax.append(data_biomass)
87
  self.dataxp.append(np.mean(data_biomass, axis=0))
88
  self.datax_std.append(np.std(data_biomass, axis=0, ddof=1))
 
89
  data_substrate = [df[col].values for col in substrate_cols]
90
  data_substrate = np.array(data_substrate)
91
  self.datas.append(data_substrate)
92
  self.datasp.append(np.mean(data_substrate, axis=0))
93
  self.datas_std.append(np.std(data_substrate, axis=0, ddof=1))
 
94
  data_product = [df[col].values for col in product_cols]
95
  data_product = np.array(data_product)
96
  self.datap.append(data_product)
97
  self.datapp.append(np.mean(data_product, axis=0))
98
  self.datap_std.append(np.std(data_product, axis=0, ddof=1))
 
99
  self.time = time
100
 
101
  def fit_model(self):
 
126
  popt, _ = curve_fit(self.moser, time, biomass, p0=p0, maxfev=self.maxfev)
127
  self.params['biomass'] = {'Xm': popt[0], 'um': popt[1], 'Ks': popt[2]}
128
  y_pred = self.moser(time, *popt)
 
129
  self.r2['biomass'] = 1 - (np.sum((biomass - y_pred) ** 2) / np.sum((biomass - np.mean(biomass)) ** 2))
130
  self.rmse['biomass'] = np.sqrt(mean_squared_error(biomass, y_pred))
131
  return y_pred
 
205
 
206
  def system(self, y, t, biomass_params, substrate_params, product_params, model_type):
207
  X, S, P = y
 
208
  if model_type == 'logistic':
209
  dXdt = self.logistic_diff(X, t, biomass_params)
210
  elif model_type == 'gompertz':
 
213
  dXdt = self.moser_diff(X, t, biomass_params)
214
  else:
215
  dXdt = 0.0
 
216
  so, p, q = substrate_params
217
  po, alpha, beta = product_params
 
218
  dSdt = -p * dXdt - q * X
219
  dPdt = alpha * dXdt + beta * X
220
  return [dXdt, dSdt, dPdt]
 
236
  X0 = Xm*(1 - np.exp(-um*(0 - Ks)))
237
  else:
238
  X0 = biomass[0]
 
239
  if 'substrate' in self.params:
240
  so = self.params['substrate']['so']
241
  S0 = so
242
  else:
243
  S0 = substrate[0]
 
244
  if 'product' in self.params:
245
  po = self.params['product']['po']
246
  P0 = po
247
  else:
248
  P0 = product[0]
 
249
  return [X0, S0, P0]
250
 
251
  def solve_differential_equations(self, time, biomass, substrate, product):
252
  if 'biomass' not in self.params or not self.params['biomass']:
253
  print("No hay parámetros de biomasa, no se pueden resolver las EDO.")
254
  return None, None, None, time
 
255
  if self.model_type == 'logistic':
256
  biomass_params = [self.params['biomass']['xo'], self.params['biomass']['xm'], self.params['biomass']['um']]
257
  elif self.model_type == 'gompertz':
 
260
  biomass_params = [self.params['biomass']['Xm'], self.params['biomass']['um'], self.params['biomass']['Ks']]
261
  else:
262
  biomass_params = [0,0,0]
 
263
  if 'substrate' in self.params:
264
  substrate_params = [self.params['substrate']['so'], self.params['substrate']['p'], self.params['substrate']['q']]
265
  else:
266
  substrate_params = [0,0,0]
 
267
  if 'product' in self.params:
268
  product_params = [self.params['product']['po'], self.params['product']['alpha'], self.params['product']['beta']]
269
  else:
270
  product_params = [0,0,0]
 
271
  initial_conditions = self.get_initial_conditions(time, biomass, substrate, product)
272
  time_fine = self.generate_fine_time_grid(time)
273
  sol = odeint(self.system, initial_conditions, time_fine,
274
  args=(biomass_params, substrate_params, product_params, self.model_type))
 
275
  X = sol[:, 0]
276
  S = sol[:, 1]
277
  P = sol[:, 2]
 
278
  return X, S, P, time_fine
279
 
280
  def plot_results(self, time, biomass, substrate, product,
 
284
  show_legend=True, show_params=True,
285
  style='whitegrid',
286
  line_color='#0000FF', point_color='#000000', line_style='-', marker_style='o',
287
+ use_differential=False):
 
 
288
  if y_pred_biomass is None:
289
  print(f"No se pudo ajustar biomasa para {experiment_name} con {self.model_type}. Omitiendo figura.")
290
  return None
 
291
  sns.set_style(style)
 
292
  if use_differential and 'biomass' in self.params and self.params['biomass']:
293
  X, S, P, time_to_plot = self.solve_differential_equations(time, biomass, substrate, product)
294
  if X is not None:
 
297
  time_to_plot = time
298
  else:
299
  time_to_plot = time
 
300
  fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 15))
301
  fig.suptitle(f'{experiment_name}', fontsize=16)
 
302
  plots = [
303
+ (ax1, biomass, y_pred_biomass, biomass_std, 'Biomass', 'Model', self.params.get('biomass', {}),
304
  self.r2.get('biomass', np.nan), self.rmse.get('biomass', np.nan)),
305
+ (ax2, substrate, y_pred_substrate, substrate_std, 'Substrate', 'Model', self.params.get('substrate', {}),
306
  self.r2.get('substrate', np.nan), self.rmse.get('substrate', np.nan)),
307
+ (ax3, product, y_pred_product, product_std, 'Product', 'Model', self.params.get('product', {}),
308
  self.r2.get('product', np.nan), self.rmse.get('product', np.nan))
309
  ]
 
310
  for idx, (ax, data, y_pred, data_std, ylabel, model_name, params, r2, rmse) in enumerate(plots):
311
  if data_std is not None:
312
  ax.errorbar(time, data, yerr=data_std, fmt=marker_style, color=point_color,
313
+ label='Experimental Data', capsize=5)
314
  else:
315
  ax.plot(time, data, marker=marker_style, linestyle='', color=point_color,
316
+ label='Experimental Data')
 
317
  if y_pred is not None:
318
  ax.plot(time_to_plot, y_pred, linestyle=line_style, color=line_color, label=model_name)
319
+ ax.set_xlabel('Time')
 
320
  ax.set_ylabel(ylabel)
321
  if show_legend:
322
  ax.legend(loc=legend_position)
323
  ax.set_title(f'{ylabel}')
 
324
  if show_params and params and all(np.isfinite(list(map(float, params.values())))):
325
  param_text = '\n'.join([f"{k} = {v:.3f}" for k, v in params.items()])
326
  text = f"{param_text}\nR² = {r2:.3f}\nRMSE = {rmse:.3f}"
 
335
  else:
336
  text_x = 0.05
337
  ha = 'left'
 
338
  if params_position in ['upper right', 'upper left']:
339
  text_y = 0.95
340
  va = 'top'
341
  else:
342
  text_y = 0.05
343
  va = 'bottom'
 
344
  ax.text(text_x, text_y, text, transform=ax.transAxes,
345
  verticalalignment=va, horizontalalignment=ha,
346
  bbox={'boxstyle': 'round', 'facecolor':'white', 'alpha':0.5})
 
347
  plt.tight_layout(rect=[0, 0.03, 1, 0.95])
 
348
  buf = io.BytesIO()
349
  fig.savefig(buf, format='png')
350
  buf.seek(0)
351
  image = Image.open(buf).convert("RGB")
352
  plt.close(fig)
 
353
  return image
354
 
355
  def plot_combined_results(self, time, biomass, substrate, product,
 
359
  show_legend=True, show_params=True,
360
  style='whitegrid',
361
  line_color='#0000FF', point_color='#000000', line_style='-', marker_style='o',
362
+ use_differential=False):
 
 
363
  if y_pred_biomass is None:
364
  print(f"No se pudo ajustar biomasa para {experiment_name} con {self.model_type}. Omitiendo figura.")
365
  return None
 
366
  sns.set_style(style)
 
367
  if use_differential and 'biomass' in self.params and self.params['biomass']:
368
  X, S, P, time_to_plot = self.solve_differential_equations(time, biomass, substrate, product)
369
  if X is not None:
 
372
  time_to_plot = time
373
  else:
374
  time_to_plot = time
 
375
  fig, ax1 = plt.subplots(figsize=(10, 7))
376
  fig.suptitle(f'{experiment_name}', fontsize=16)
377
+ colors = {'Biomass': 'blue', 'Substrate': 'green', 'Product': 'red'}
378
+ ax1.set_xlabel('Time')
379
+ ax1.set_ylabel('Biomass', color=colors['Biomass'])
 
 
380
  if biomass_std is not None:
381
+ ax1.errorbar(time, biomass, yerr=biomass_std, fmt=marker_style, color=colors['Biomass'],
382
+ label='Biomass (Data)', capsize=5)
383
  else:
384
+ ax1.plot(time, biomass, marker=marker_style, linestyle='', color=colors['Biomass'],
385
+ label='Biomass (Data)')
386
+ ax1.plot(time_to_plot, y_pred_biomass, linestyle=line_style, color=colors['Biomass'],
387
+ label='Biomass (Model)')
388
+ ax1.tick_params(axis='y', labelcolor=colors['Biomass'])
 
389
  ax2 = ax1.twinx()
390
+ ax2.set_ylabel('Substrate', color=colors['Substrate'])
391
  if substrate_std is not None:
392
+ ax2.errorbar(time, substrate, yerr=substrate_std, fmt=marker_style, color=colors['Substrate'],
393
+ label='Substrate (Data)', capsize=5)
394
  else:
395
+ ax2.plot(time, substrate, marker=marker_style, linestyle='', color=colors['Substrate'],
396
+ label='Substrate (Data)')
397
  if y_pred_substrate is not None:
398
+ ax2.plot(time_to_plot, y_pred_substrate, linestyle=line_style, color=colors['Substrate'],
399
+ label='Substrate (Model)')
400
+ ax2.tick_params(axis='y', labelcolor=colors['Substrate'])
 
401
  ax3 = ax1.twinx()
402
  ax3.spines["right"].set_position(("axes", 1.2))
403
  ax3.set_frame_on(True)
404
  ax3.patch.set_visible(False)
405
  for sp in ax3.spines.values():
406
  sp.set_visible(True)
407
+ ax3.set_ylabel('Product', color=colors['Product'])
 
408
  if product_std is not None:
409
+ ax3.errorbar(time, product, yerr=product_std, fmt=marker_style, color=colors['Product'],
410
+ label='Product (Data)', capsize=5)
411
  else:
412
+ ax3.plot(time, product, marker=marker_style, linestyle='', color=colors['Product'],
413
+ label='Product (Data)')
414
  if y_pred_product is not None:
415
+ ax3.plot(time_to_plot, y_pred_product, linestyle=line_style, color=colors['Product'],
416
+ label='Product (Model)')
417
+ ax3.tick_params(axis='y', labelcolor=colors['Product'])
 
418
  lines_labels = [ax.get_legend_handles_labels() for ax in [ax1, ax2, ax3]]
419
  lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
420
  if show_legend:
421
  ax1.legend(lines, labels, loc=legend_position)
 
422
  if show_params:
423
  param_text_biomass = ''
424
  if 'biomass' in self.params:
425
  param_text_biomass = '\n'.join([f"{k} = {v:.3f}" for k, v in self.params['biomass'].items()])
426
+ text_biomass = f"Biomass:\n{param_text_biomass}\nR² = {self.r2.get('biomass', np.nan):.3f}\nRMSE = {self.rmse.get('biomass', np.nan):.3f}"
 
427
  param_text_substrate = ''
428
  if 'substrate' in self.params:
429
  param_text_substrate = '\n'.join([f"{k} = {v:.3f}" for k, v in self.params['substrate'].items()])
430
+ text_substrate = f"Substrate:\n{param_text_substrate}\nR² = {self.r2.get('substrate', np.nan):.3f}\nRMSE = {self.rmse.get('substrate', np.nan):.3f}"
 
431
  param_text_product = ''
432
  if 'product' in self.params:
433
  param_text_product = '\n'.join([f"{k} = {v:.3f}" for k, v in self.params['product'].items()])
434
+ text_product = f"Product:\n{param_text_product}\nR² = {self.r2.get('product', np.nan):.3f}\nRMSE = {self.rmse.get('product', np.nan):.3f}"
435
+ total_text = f"{text_biomass}\n{text_substrate}\n{text_product}"
 
 
436
  if params_position == 'outside right':
437
  bbox_props = dict(boxstyle='round', facecolor='white', alpha=0.5)
438
  ax3.annotate(total_text, xy=(1.2, 0.5), xycoords='axes fraction',
 
444
  else:
445
  text_x = 0.05
446
  ha = 'left'
 
447
  if params_position in ['upper right', 'upper left']:
448
  text_y = 0.95
449
  va = 'top'
450
  else:
451
  text_y = 0.05
452
  va = 'bottom'
 
453
  ax1.text(text_x, text_y, total_text, transform=ax1.transAxes,
454
  verticalalignment=va, horizontalalignment=ha,
455
  bbox={'boxstyle':'round', 'facecolor':'white', 'alpha':0.5})
 
456
  plt.tight_layout(rect=[0, 0.03, 1, 0.95])
 
457
  buf = io.BytesIO()
458
  fig.savefig(buf, format='png')
459
  buf.seek(0)
460
  image = Image.open(buf).convert("RGB")
461
  plt.close(fig)
 
462
  return image
463
 
464
  def process_all_data(file, legend_position, params_position, model_types, experiment_names, lower_bounds, upper_bounds,
465
  mode='independent', style='whitegrid', line_color='#0000FF', point_color='#000000',
466
+ line_style='-', marker_style='o', show_legend=True, show_params=True, use_differential=False, maxfev_val=50000):
 
 
467
  try:
468
  xls = pd.ExcelFile(file.name)
469
  except Exception as e:
470
  print(f"Error al leer el archivo Excel: {e}")
471
  return [], pd.DataFrame()
 
472
  sheet_names = xls.sheet_names
473
  figures = []
474
  comparison_data = []
475
  experiment_counter = 0
 
476
  for sheet_name in sheet_names:
477
  try:
478
  df = pd.read_excel(file.name, sheet_name=sheet_name, header=[0, 1])
479
  except Exception as e:
480
  print(f"Error al leer la hoja '{sheet_name}': {e}")
481
  continue
 
482
  model_dummy = BioprocessModel()
483
  model_dummy.process_data(df)
484
  time = model_dummy.time
 
485
  if mode == 'independent':
486
  num_experiments = len(df.columns.levels[0])
487
  for idx in range(num_experiments):
 
494
  except KeyError as e:
495
  print(f"Error al procesar el experimento '{col}': {e}")
496
  continue
 
497
  biomass_std = None
498
  substrate_std = None
499
  product_std = None
 
506
  if product.ndim > 1:
507
  product_std = np.std(product, axis=0, ddof=1)
508
  product = np.mean(product, axis=0)
 
509
  experiment_name = (experiment_names[experiment_counter] if experiment_counter < len(experiment_names)
510
  else f"Tratamiento {experiment_counter + 1}")
 
511
  for model_type in model_types:
512
  model = BioprocessModel(model_type=model_type, maxfev=maxfev_val)
513
  model.fit_model()
 
514
  y_pred_biomass = model.fit_biomass(time_exp, biomass)
515
  if y_pred_biomass is None:
516
  comparison_data.append({
 
531
  else:
532
  y_pred_substrate = None
533
  y_pred_product = None
 
534
  comparison_data.append({
535
  'Experimento': experiment_name,
536
  'Modelo': model_type.capitalize(),
 
541
  'R² Producto': model.r2.get('product', np.nan),
542
  'RMSE Producto': model.rmse.get('product', np.nan)
543
  })
 
544
  if mode == 'combinado':
545
  fig = model.plot_combined_results(time_exp, biomass, substrate, product,
546
  y_pred_biomass, y_pred_substrate, y_pred_product,
 
550
  show_legend, show_params,
551
  style,
552
  line_color, point_color, line_style, marker_style,
553
+ use_differential)
 
554
  else:
555
  fig = model.plot_results(time_exp, biomass, substrate, product,
556
  y_pred_biomass, y_pred_substrate, y_pred_product,
 
560
  show_legend, show_params,
561
  style,
562
  line_color, point_color, line_style, marker_style,
563
+ use_differential)
 
564
  if fig is not None:
565
  figures.append(fig)
 
566
  experiment_counter += 1
 
567
  elif mode in ['average', 'combinado']:
568
  try:
569
  time_exp = df[(df.columns.levels[0][0], 'Tiempo')].dropna().values
 
573
  except IndexError as e:
574
  print(f"Error al obtener los datos promedio de la hoja '{sheet_name}': {e}")
575
  continue
 
576
  biomass_std = model_dummy.datax_std[-1]
577
  substrate_std = model_dummy.datas_std[-1]
578
  product_std = model_dummy.datap_std[-1]
 
579
  experiment_name = (experiment_names[experiment_counter] if experiment_counter < len(experiment_names)
580
  else f"Tratamiento {experiment_counter + 1}")
 
581
  for model_type in model_types:
582
  model = BioprocessModel(model_type=model_type, maxfev=maxfev_val)
583
  model.fit_model()
 
584
  y_pred_biomass = model.fit_biomass(time_exp, biomass)
585
  if y_pred_biomass is None:
586
  comparison_data.append({
 
601
  else:
602
  y_pred_substrate = None
603
  y_pred_product = None
 
604
  comparison_data.append({
605
  'Experimento': experiment_name,
606
  'Modelo': model_type.capitalize(),
 
611
  'R² Producto': model.r2.get('product', np.nan),
612
  'RMSE Producto': model.rmse.get('product', np.nan)
613
  })
 
614
  if mode == 'combinado':
615
  fig = model.plot_combined_results(time_exp, biomass, substrate, product,
616
  y_pred_biomass, y_pred_substrate, y_pred_product,
 
620
  show_legend, show_params,
621
  style,
622
  line_color, point_color, line_style, marker_style,
623
+ use_differential)
 
624
  else:
625
  fig = model.plot_results(time_exp, biomass, substrate, product,
626
  y_pred_biomass, y_pred_substrate, y_pred_product,
 
630
  show_legend, show_params,
631
  style,
632
  line_color, point_color, line_style, marker_style,
633
+ use_differential)
 
634
  if fig is not None:
635
  figures.append(fig)
 
636
  experiment_counter += 1
 
637
  comparison_df = pd.DataFrame(comparison_data)
 
638
  if not comparison_df.empty:
639
  comparison_df_sorted = comparison_df.sort_values(
640
  by=['R² Biomasa', 'R² Sustrato', 'R² Producto', 'RMSE Biomasa', 'RMSE Sustrato', 'RMSE Producto'],
 
642
  ).reset_index(drop=True)
643
  else:
644
  comparison_df_sorted = comparison_df
 
645
  return figures, comparison_df_sorted
646
 
647
  def create_interface():
648
  with gr.Blocks() as demo:
649
  gr.Markdown("# Modelos de Bioproceso: Logístico, Gompertz, Moser y Luedeking-Piret")
 
650
  gr.Markdown(r"""
651
  ## Ecuaciones Diferenciales Utilizadas
 
652
  **Biomasa:**
 
653
  - Logístico:
654
  $$
655
  \frac{dX}{dt} = \mu_m X\left(1 - \frac{X}{X_m}\right)
656
  $$
 
657
  - Gompertz:
658
  $$
659
  X(t) = X_m \exp\left(-\exp\left(\left(\frac{\mu_m e}{X_m}\right)(\text{lag}-t)+1\right)\right)
660
  $$
 
661
  Ecuación diferencial:
662
  $$
663
  \frac{dX}{dt} = X(t)\left(\frac{\mu_m e}{X_m}\right)\exp\left(\left(\frac{\mu_m e}{X_m}\right)(\text{lag}-t)+1\right)
664
  $$
 
665
  - Moser (simplificado):
666
  $$
667
  X(t)=X_m(1-e^{-\mu_m(t-K_s)})
668
  $$
 
669
  $$
670
  \frac{dX}{dt}=\mu_m(X_m - X)
671
  $$
 
672
  **Sustrato y Producto (Luedeking-Piret):**
673
  $$
674
  \frac{dS}{dt} = -p \frac{dX}{dt} - q X
675
  $$
 
676
  $$
677
  \frac{dP}{dt} = \alpha \frac{dX}{dt} + \beta X
678
  $$
679
  """)
 
680
  file_input = gr.File(label="Subir archivo Excel")
 
681
  with gr.Row():
682
  with gr.Column():
683
  legend_position = gr.Radio(
 
686
  value="best"
687
  )
688
  show_legend = gr.Checkbox(label="Mostrar Leyenda", value=True)
 
689
  with gr.Column():
690
  params_positions = ["upper left", "upper right", "lower left", "lower right", "outside right"]
691
  params_position = gr.Radio(
 
694
  value="upper right"
695
  )
696
  show_params = gr.Checkbox(label="Mostrar Parámetros", value=True)
 
697
  model_types = gr.CheckboxGroup(
698
  choices=["logistic", "gompertz", "moser"],
699
  label="Tipo(s) de Modelo",
 
701
  )
702
  mode = gr.Radio(["independent", "average", "combinado"], label="Modo de Análisis", value="independent")
703
  use_differential = gr.Checkbox(label="Usar ecuaciones diferenciales para graficar", value=False)
 
704
  experiment_names = gr.Textbox(
705
  label="Nombres de los experimentos (uno por línea)",
706
  placeholder="Experimento 1\nExperimento 2\n...",
707
  lines=5
708
  )
 
 
 
 
 
 
 
709
  with gr.Row():
710
  with gr.Column():
711
  lower_bounds = gr.Textbox(
 
713
  placeholder="0,0,0\n0,0,0\n...",
714
  lines=5
715
  )
 
716
  with gr.Column():
717
  upper_bounds = gr.Textbox(
718
  label="Upper Bounds (uno por línea, formato: param1,param2,param3)",
719
  placeholder="inf,inf,inf\ninf,inf,inf\n...",
720
  lines=5
721
  )
 
722
  styles = ['white', 'dark', 'whitegrid', 'darkgrid', 'ticks']
723
  style_dropdown = gr.Dropdown(choices=styles, label="Selecciona el estilo de gráfico", value='whitegrid')
 
724
  line_color_picker = gr.ColorPicker(label="Color de la línea", value='#0000FF')
725
  point_color_picker = gr.ColorPicker(label="Color de los puntos", value='#000000')
 
726
  line_style_options = ['-', '--', '-.', ':']
727
  line_style_dropdown = gr.Dropdown(choices=line_style_options, label="Estilo de línea", value='-')
 
728
  marker_style_options = ['o', 's', '^', 'v', 'D', 'x', '+', '*']
729
  marker_style_dropdown = gr.Dropdown(choices=marker_style_options, label="Estilo de punto", value='o')
 
730
  maxfev_input = gr.Number(label="maxfev (Máx. evaluaciones para el ajuste)", value=50000)
 
731
  simulate_btn = gr.Button("Simular")
 
732
  output_gallery = gr.Gallery(label="Resultados", columns=2, height='auto')
733
  output_table = gr.Dataframe(
734
  label="Tabla Comparativa de Modelos",
 
736
  "R² Sustrato", "RMSE Sustrato", "R² Producto", "RMSE Producto"],
737
  interactive=False
738
  )
 
739
  state_df = gr.State()
740
 
741
  def process_and_plot(file, legend_position, params_position, model_types, mode, experiment_names,
742
  lower_bounds, upper_bounds, style,
743
  line_color, point_color, line_style, marker_style,
744
+ show_legend, show_params, use_differential, maxfev_input):
 
 
745
  experiment_names_list = experiment_names.strip().split('\n') if experiment_names.strip() else []
746
  lower_bounds_list = []
747
  if lower_bounds.strip():
 
771
  except ValueError:
772
  ub_values.append(np.inf)
773
  upper_bounds_list.append(tuple(ub_values))
 
774
  figures, comparison_df = process_all_data(file, legend_position, params_position, model_types, experiment_names_list,
775
  lower_bounds_list, upper_bounds_list, mode, style,
776
  line_color, point_color, line_style, marker_style,
777
+ show_legend, show_params, use_differential, maxfev_val=int(maxfev_input))
 
 
778
  return figures, comparison_df, comparison_df
779
 
780
  simulate_output = simulate_btn.click(
 
795
  show_legend,
796
  show_params,
797
  use_differential,
798
+ maxfev_input],
 
 
 
 
799
  outputs=[output_gallery, output_table, state_df]
800
  )
801
 
 
808
 
809
  export_btn = gr.Button("Exportar Tabla a Excel")
810
  file_output = gr.File()
 
811
  export_btn.click(
812
  fn=export_excel,
813
  inputs=state_df,