Yamamoto-tool results in error depending on ionic capacity

Hey there,

I am currently using the yamamoto-tool within CADET-Process to estimate characteristic_charge and k_eq of my target molecule. See below the code I use to do so.

exp_16cv = pd.read_csv("231115_LGE16CV_transformed.csv",delimiter=",", header=0)
exp_24cv = pd.read_csv("231115_LGE24CV_transformed.csv",delimiter=",", header=0)
exp_48cv = pd.read_csv("231115_LGE48CV_transformed.csv",delimiter=",", header=0)

exp_16cv_t_protein = exp_16cv.iloc[:,0].dropna();   exp_16cv_t_protein_np = exp_16cv_t_protein.to_numpy()*60
exp_16cv_c_protein = exp_16cv.iloc[:,10].dropna();  exp_16cv_c_protein_np = exp_16cv_c_protein.to_numpy()
exp_16cv_t_salt =    exp_16cv.iloc[:,4].dropna();   exp_16cv_t_salt_np =    exp_16cv_t_salt.to_numpy()*60
exp_16cv_c_salt =    exp_16cv.iloc[:,9].dropna();   exp_16cv_c_salt_np =    exp_16cv_c_salt.to_numpy()
# => c_protein needs to be in a second dimension => expand dimension [[c_protein]]
exp_16cv_c_protein_np = np.expand_dims(exp_16cv_c_protein_np, axis=1)

exp_24cv_t_protein = exp_24cv.iloc[:,0].dropna();   exp_24cv_t_protein_np = exp_24cv_t_protein.to_numpy()*60
exp_24cv_c_protein = exp_24cv.iloc[:,10].dropna();  exp_24cv_c_protein_np = exp_24cv_c_protein.to_numpy()
exp_24cv_t_salt =    exp_24cv.iloc[:,4].dropna();   exp_24cv_t_salt_np =    exp_24cv_t_salt.to_numpy()*60
exp_24cv_c_salt =    exp_24cv.iloc[:,9].dropna();   exp_24cv_c_salt_np =    exp_24cv_c_salt.to_numpy()
# => c_protein needs to be in a second dimension => expand dimension [[c_protein]]
exp_24cv_c_protein_np = np.expand_dims(exp_24cv_c_protein_np, axis=1)

exp_48cv_t_protein = exp_48cv.iloc[:,0].dropna();   exp_48cv_t_protein_np = exp_48cv_t_protein.to_numpy()*60
exp_48cv_c_protein = exp_48cv.iloc[:,10].dropna();  exp_48cv_c_protein_np = exp_48cv_c_protein.to_numpy()
exp_48cv_t_salt =    exp_48cv.iloc[:,4].dropna();   exp_48cv_t_salt_np =    exp_48cv_t_salt.to_numpy()*60
exp_48cv_c_salt =    exp_48cv.iloc[:,9].dropna();   exp_48cv_c_salt_np =    exp_48cv_c_salt.to_numpy()
# => c_protein needs to be in a second dimension => expand dimension [[c_protein]]
exp_48cv_c_protein_np = np.expand_dims(exp_48cv_c_protein_np, axis=1)

# create interpolation function to interpolate shorter
# array (salt) to same x-locations as the longer array (protein)
def salt_interpolation(salt_time, salt_conc, prot_time):
    interp_func = interp1d(salt_time,salt_conc, axis=0, fill_value="extrapolate")
    c_salt_interp = interp_func(prot_time)
    return c_salt_interp

exp_16cv_c_salt_np_ip = salt_interpolation(exp_16cv_t_salt_np, exp_16cv_c_salt_np, exp_16cv_t_protein_np)
exp_24cv_c_salt_np_ip = salt_interpolation(exp_24cv_t_salt_np, exp_24cv_c_salt_np, exp_24cv_t_protein_np)
exp_48cv_c_salt_np_ip = salt_interpolation(exp_48cv_t_salt_np, exp_48cv_c_salt_np, exp_48cv_t_protein_np)

CV = ((0.005*0.5)**2*pi)

experiment_1 = GradientExperiment(exp_16cv_t_protein_np, exp_16cv_c_salt_np_ip, exp_16cv_c_protein_np, 16*CV)
experiment_2 = GradientExperiment(exp_24cv_t_protein_np, exp_24cv_c_salt_np_ip, exp_24cv_c_protein_np, 24*CV)
experiment_3 = GradientExperiment(exp_48cv_t_protein_np, exp_48cv_c_salt_np_ip, exp_48cv_c_protein_np, 48*CV)

experiments = [experiment_1, experiment_2, experiment_3]

for experiment in experiments:
    experiment = experiment.plot()

component_system = ComponentSystem(['Salt', 'Protein'])
binding_model = StericMassAction(component_system)
binding_model.is_kinetic = False
binding_model.adsorption_rate = [0, 0]
binding_model.desorption_rate = [1, 1]
binding_model.capacity = 300
binding_model.steric_factor = [1, 1]

column = LumpedRateModelWithPores(component_system, 'column')
column.binding_model = binding_model
column.length = 0.2
column.diameter = 0.005
column.bed_porosity = 0.4136
column.particle_radius = 50*1e-6
column.particle_porosity = 0.3 ##
column.axial_dispersion = 5.101e-07
column.film_diffusion = [1e-04, 1e-04] ##

column.c = [experiment_1.c_salt_start, 0]
column.q = [binding_model.capacity, 0]

yamamoto_results = fit_parameters(experiments, column)

print('yamamoto_results.characteristic_charge =',yamamoto_results.characteristic_charge)
print('yamamoto_results.k_eq =',yamamoto_results.k_eq)

yamamoto_results.plot()

This approach works perfectly fine when the ionic capacity is 300. However, the resin used in the experiment has an ionic capacity of 102.54. Once this smaller ionic capacity is defined, the code runs into an error. See error message below:

ValueError                                Traceback (most recent call last)
Cell In[64], line 1
----> 1 yamamoto_results = fit_parameters(experiments, column)
      3 print('yamamoto_results.characteristic_charge =',yamamoto_results.characteristic_charge)
      4 print('yamamoto_results.k_eq =',yamamoto_results.k_eq)

File C:\ProgramData\miniconda3\envs\cadet_env_backup\lib\site-packages\CADETProcess\tools\yamamoto.py:217, in fit_parameters(experiments, column)
    214     c_salt_at_max = [exp.c_salt_at_max[i_p] for exp in experiments]
    215     log_c_salt_at_max_M[:, i_p] = np.log10(np.array(c_salt_at_max)/1000)
--> 217     nu[i_p], k_eq[i_p] = _fit_yamamoto(
    218         log_c_salt_at_max_M[:, i_p], log_gradient_slope, column.binding_model.capacity
    219     )
    221 column.binding_model.characteristic_charge = [0, *nu.tolist()]
    222 column.binding_model.adsorption_rate = [0, *k_eq.tolist()]

File C:\ProgramData\miniconda3\envs\cadet_env_backup\lib\site-packages\CADETProcess\tools\yamamoto.py:257, in _fit_yamamoto(log_c_salt_at_max_M, log_gradient_slope, lambda_)
    254 def yamamoto_wrapper(c_s, nu, k_eq):
    255     return yamamoto_equation(c_s, lambda_, nu, k_eq)
--> 257 results, pcov = curve_fit(
    258     yamamoto_wrapper, log_c_salt_at_max_M, log_gradient_slope, bounds=bounds
    259 )
    261 return results

File C:\ProgramData\miniconda3\envs\cadet_env_backup\lib\site-packages\scipy\optimize\_minpack_py.py:974, in curve_fit(f, xdata, ydata, p0, sigma, absolute_sigma, check_finite, bounds, method, jac, full_output, nan_policy, **kwargs)
    971 if 'max_nfev' not in kwargs:
    972     kwargs['max_nfev'] = kwargs.pop('maxfev', None)
--> 974 res = least_squares(func, p0, jac=jac, bounds=bounds, method=method,
    975                     **kwargs)
    977 if not res.success:
    978     raise RuntimeError("Optimal parameters not found: " + res.message)

File C:\ProgramData\miniconda3\envs\cadet_env_backup\lib\site-packages\scipy\optimize\_lsq\least_squares.py:837, in least_squares(fun, x0, jac, bounds, method, ftol, xtol, gtol, x_scale, loss, f_scale, diff_step, tr_solver, tr_options, jac_sparsity, max_nfev, verbose, args, kwargs)
    833     raise ValueError("`fun` must return at most 1-d array_like. "
    834                      "f0.shape: {}".format(f0.shape))
    836 if not np.all(np.isfinite(f0)):
--> 837     raise ValueError("Residuals are not finite in the initial point.")
    839 n = x0.size
    840 m = f0.size

ValueError: Residuals are not finite in the initial point.

Any help is greatly appreciated.

Cheers, Samuel

Hey Samuel,

thank you for raising that report. I’m currently struggling to reproduce the error. Would it be possible for you to share the .csv files?

Best wishes,
Ron

combined_array_16cv.csv (874.7 KB)
combined_array_24cv.csv (1.1 MB)
combined_array_48cv.csv (975.7 KB)

Hey Ron,

See attached the csv-files for the 3 different linear gradient elution experiments with the target molecule. The csv-file contains the following info:

1st column = time-points for protein concentration
2nd column = interpolated salt concentration
3rd column = protein concentration

I hope this will allow to reproduce my problem.
Thanks for your help!

Hey Samuel,

I replicated the bug and found it was a bad initial value during the fitting of the Yamamoto equation. I’ve pushed a change here that you can install with

pip install git+https://github.com/fau-advanced-separations/CADET-Process.git@fix/yamamoto_bad_initial_value
3 Likes

That change is now also on the master branch.

1 Like