# bioprocess_model.py import numpy as np from scipy.optimize import curve_fit from sklearn.metrics import mean_squared_error class BioprocessModel: def __init__(self): self.params = {} self.r2 = {} self.rmse = {} self.models = {} # Initialize the models dictionary @staticmethod def logistic(time, xo, xm, um): return (xo * np.exp(um * time)) / (1 - (xo / xm) * (1 - np.exp(um * time))) def set_model_biomass(self, equation, params_str): """ Configura el modelo de Biomasa. :param equation: La ecuación de Biomasa como cadena de texto :param params_str: Cadena de parámetros separados por comas """ try: # Define la función de Biomasa directamente def biomass_func(t, xo, xm, um): return (xo * np.exp(um * t)) / (1 - (xo / xm) * (1 - np.exp(um * t))) self.models['biomass'] = { 'function': biomass_func, 'params': [param.strip() for param in params_str.split(',')] } except Exception as e: raise ValueError(f"Error al configurar el modelo de biomasa: {e}") def set_model_substrate(self, equation, params_str): """ Configura el modelo de Sustrato. :param equation: La ecuación de Sustrato como cadena de texto (no usada en este enfoque) :param params_str: Cadena de parámetros separados por comas """ try: # Define la función de Sustrato que depende de Biomasa def substrate_func(t, so, p, q, xo, xm, um): X_t = self.models['biomass']['function'](t, xo, xm, um) return so - p * X_t - q * np.log(1 - (xo / xm) * (1 - np.exp(um * t))) self.models['substrate'] = { 'function': substrate_func, 'params': [param.strip() for param in params_str.split(',')] } except Exception as e: raise ValueError(f"Error al configurar el modelo de sustrato: {e}") def set_model_product(self, equation, params_str): """ Configura el modelo de Producto. :param equation: La ecuación de Producto como cadena de texto (no usada en este enfoque) :param params_str: Cadena de parámetros separados por comas """ try: # Define la función de Producto que depende de Biomasa def product_func(t, po, alpha, beta, xo, xm, um): X_t = self.models['biomass']['function'](t, xo, xm, um) return po + alpha * X_t + beta * np.log(1 - (xo / xm) * (1 - np.exp(um * t))) self.models['product'] = { 'function': product_func, 'params': [param.strip() for param in params_str.split(',')] } except Exception as e: raise ValueError(f"Error al configurar el modelo de producto: {e}") def fit_model(self, model_type, time, data, bounds=([-np.inf], [np.inf])): """ Ajusta el modelo a los datos. :param model_type: Tipo de modelo ('biomass', 'substrate', 'product') :param time: Datos de tiempo :param data: Datos observados para ajustar :param bounds: Límites para los parámetros :return: Datos predichos por el modelo """ if model_type not in self.models: raise ValueError(f"Tipo de modelo '{model_type}' no está configurado. Usa set_model primero.") func = self.models[model_type]['function'] params = self.models[model_type]['params'] # Definir la función de ajuste para curve_fit def fit_func(t, *args): try: # Para Sustrato y Producto, se necesitan los parámetros de Biomasa if model_type in ['substrate', 'product']: # Extraer los parámetros de Biomasa xo, xm, um = self.params['biomass']['xo'], self.params['biomass']['xm'], self.params['biomass']['um'] return func(t, *args, xo, xm, um) else: return func(t, *args) except Exception as e: raise RuntimeError(f"Error en fit_func: {e}") # Estimación inicial de los parámetros p0 = [1.0] * len(params) # Puedes ajustar estos valores según sea necesario try: popt, _ = curve_fit(fit_func, time, data, p0=p0, bounds=bounds, maxfev=10000) # Guardar los parámetros ajustados self.params[model_type] = {param: val for param, val in zip(params, popt)} y_pred = fit_func(time, *popt) self.r2[model_type] = 1 - (np.sum((data - y_pred) ** 2) / np.sum((data - np.mean(data)) ** 2)) self.rmse[model_type] = np.sqrt(mean_squared_error(data, y_pred)) return y_pred except Exception as e: raise RuntimeError(f"Error al ajustar el modelo '{model_type}': {e}")