# bioprocess_model.py import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy.integrate import odeint from scipy.optimize import curve_fit from sklearn.metrics import mean_squared_error import seaborn as sns class BioprocessModel: def __init__(self): self.params = {} self.r2 = {} self.rmse = {} self.datax = [] self.datas = [] self.datap = [] self.dataxp = [] self.datasp = [] self.datapp = [] self.datax_std = [] self.datas_std = [] self.datap_std = [] @staticmethod def logistic(time, xo, xm, um): return (xo * np.exp(um * time)) / (1 - (xo / xm) * (1 - np.exp(um * time))) @staticmethod def substrate(time, so, p, q, xo, xm, um): return so - (p * xo * ((np.exp(um * time)) / (1 - (xo / xm) * (1 - np.exp(um * time))) - 1)) - \ (q * (xm / um) * np.log(1 - (xo / xm) * (1 - np.exp(um * time)))) @staticmethod def product(time, po, alpha, beta, xo, xm, um): return po + (alpha * xo * ((np.exp(um * time) / (1 - (xo / xm) * (1 - np.exp(um * time)))) - 1)) + \ (beta * (xm / um) * np.log(1 - (xo / xm) * (1 - np.exp(um * time)))) @staticmethod def logistic_diff(X, t, params): xo, xm, um = params dXdt = um * X * (1 - X / xm) return dXdt def substrate_diff(self, S, t, params, biomass_params, X_func): so, p, q = params xo, xm, um = biomass_params X_t = X_func(t) dSdt = -p * (um * X_t * (1 - X_t / xm)) - q * X_t return dSdt def product_diff(self, P, t, params, biomass_params, X_func): po, alpha, beta = params xo, xm, um = biomass_params X_t = X_func(t) dPdt = alpha * (um * X_t * (1 - X_t / xm)) + beta * X_t return dPdt def process_data(self, df): biomass_cols = [col for col in df.columns if 'Biomasa' in col] substrate_cols = [col for col in df.columns if 'Sustrato' in col] product_cols = [col for col in df.columns if 'Producto' in col] time_col = [col for col in df.columns if 'Tiempo' in col][0] time = df[time_col].values data_biomass = np.array([df[col].values for col in biomass_cols]) self.datax.append(data_biomass) self.dataxp.append(np.mean(data_biomass, axis=0)) self.datax_std.append(np.std(data_biomass, axis=0, ddof=1)) data_substrate = np.array([df[col].values for col in substrate_cols]) self.datas.append(data_substrate) self.datasp.append(np.mean(data_substrate, axis=0)) self.datas_std.append(np.std(data_substrate, axis=0, ddof=1)) data_product = np.array([df[col].values for col in product_cols]) self.datap.append(data_product) self.datapp.append(np.mean(data_product, axis=0)) self.datap_std.append(np.std(data_product, axis=0, ddof=1)) self.time = time def fit_model(self, model_type='logistic'): if model_type == 'logistic': self.fit_biomass = self.fit_biomass_logistic self.fit_substrate = self.fit_substrate_logistic self.fit_product = self.fit_product_logistic def fit_biomass_logistic(self, time, biomass, bounds): popt, _ = curve_fit(self.logistic, time, biomass, bounds=bounds, maxfev=10000) self.params['biomass'] = {'xo': popt[0], 'xm': popt[1], 'um': popt[2]} y_pred = self.logistic(time, *popt) self.r2['biomass'] = 1 - (np.sum((biomass - y_pred) ** 2) / np.sum((biomass - np.mean(biomass)) ** 2)) self.rmse['biomass'] = np.sqrt(mean_squared_error(biomass, y_pred)) return y_pred def fit_substrate_logistic(self, time, substrate, biomass_params, bounds): popt, _ = curve_fit(lambda t, so, p, q: self.substrate(t, so, p, q, *biomass_params.values()), time, substrate, bounds=bounds) self.params['substrate'] = {'so': popt[0], 'p': popt[1], 'q': popt[2]} y_pred = self.substrate(time, *popt, *biomass_params.values()) self.r2['substrate'] = 1 - (np.sum((substrate - y_pred) ** 2) / np.sum((substrate - np.mean(substrate)) ** 2)) self.rmse['substrate'] = np.sqrt(mean_squared_error(substrate, y_pred)) return y_pred def fit_product_logistic(self, time, product, biomass_params, bounds): popt, _ = curve_fit(lambda t, po, alpha, beta: self.product(t, po, alpha, beta, *biomass_params.values()), time, product, bounds=bounds) self.params['product'] = {'po': popt[0], 'alpha': popt[1], 'beta': popt[2]} y_pred = self.product(time, *popt, *biomass_params.values()) self.r2['product'] = 1 - (np.sum((product - y_pred) ** 2) / np.sum((product - np.mean(product)) ** 2)) self.rmse['product'] = np.sqrt(mean_squared_error(product, y_pred)) return y_pred def plot_combined_results(self, time, biomass, substrate, product, y_pred_biomass, y_pred_substrate, y_pred_product, biomass_std=None, substrate_std=None, product_std=None, experiment_name='', legend_position='best', params_position='upper right', show_legend=True, show_params=True, style='whitegrid', line_color='#0000FF', point_color='#000000', line_style='-', marker_style='o'): sns.set_style(style) fig, ax1 = plt.subplots(figsize=(10, 7)) ax1.set_xlabel('Tiempo') ax1.set_ylabel('Biomasa', color=line_color) ax1.plot(time, biomass, marker=marker_style, linestyle='', color=point_color, label='Biomasa (Datos)') ax1.plot(time, y_pred_biomass, linestyle=line_style, color=line_color, label='Biomasa (Modelo)') ax1.tick_params(axis='y', labelcolor=line_color) ax2 = ax1.twinx() ax2.set_ylabel('Sustrato', color='green') ax2.plot(time, substrate, marker=marker_style, linestyle='', color='green', label='Sustrato (Datos)') ax2.plot(time, y_pred_substrate, linestyle=line_style, color='green', label='Sustrato (Modelo)') ax2.tick_params(axis='y', labelcolor='green') ax3 = ax1.twinx() ax3.spines["right"].set_position(("axes", 1.1)) ax3.set_ylabel('Producto', color='red') ax3.plot(time, product, marker=marker_style, linestyle='', color='red', label='Producto (Datos)') ax3.plot(time, y_pred_product, linestyle=line_style, color='red', label='Producto (Modelo)') ax3.tick_params(axis='y', labelcolor='red') fig.tight_layout() return fig