2 years ago

#23678

test-img

Jaka Orehek

optimization problem scipy.optimize.minimize

I'm having a hard time to optimize my system... I'm working on an antisolvent continuous crystallization in a tubular system and my goal is to find antisolvent flow rates at each antisolvent injection locations throughout the system length to achieve desired median crystal size. So initial concentration, solvent flow rate, temperature, injection locations are constant, only flow rates vary.

I've written a code which is posted below - I know, it is a bit messy... The idea behind it is that initial guesses of flow rates are given initial_guesses = [25, 0, 45, 0, 0] which are optimized based on least-squares optimization procedure:

desired_size = 50 # um
objective = (desired_size - d50[-1])**2 

However, I applied some restriction to the optimization proces:

  • If the solute concentration at the end of the sector (sectors are tube section between antisolvent injection locations) is equal or higher than metastable concentration * 0.333 for given antisolvent composition, the antisolvent flow rate at the following injection point is 0 - to prevent further nucleation.
  • If the added antisolvent dissolved the solution the the degree which causes the crystals to dissolve c_raz[l,z] < c_s[l], than the initial guess is being reduced for 1 mL/min initial_guesses[l] -= 1 until the antisolvent flow rate at the concerned injection point is not small enough to prevent dissolving (this is stopped if the flow rate reach value <= 0).

However, it was found out that these restrictions are not working properly. Does anyone have better idea how should it be written to achieve better optimization?

The code:

from time import time
startTime = time()

#%% parameters

# length and diameter of the system
L_reaktor = (41.7+41.5+42.5+41.8+60)*0.01 # m
D_reaktor = 3e-3 # m

# solvent flow rate
fi_vtok_mL_min = 50 # mL/min
fi_v_top = fi_vtok_mL_min * 1e-6 / 60 # m^3/s
del_top_vtok = 0.55 # ethanol fraction in solvent, vol% 
cistost_top = 0.96 # vol%

# antisolvent flow rates and injection points
lokacija = [0, 0.417, 0.832, 1.257, 1.675] # m
del_prot_pritok = 1 # water fraction in antisolvent, vol%

# solute concentration in the solvent at the inlet
y = 110 # g/L = kg/m^3 # 118.3

# solute mol mass
M_m = 122.123e-3 # kg/mol

# solute density
rho_krist = 1265.9 # kg/m^3

# antisolvent and solvent density at 20°C
rho_voda = 1000 # kg/m3
rho_etoh = 789 # kg/m3

# antisolvent and solvent viskozity at 20°C
mi_voda = 0.0010005
mi_etoh = 0.0011841

#%% liberies

from numpy import zeros, interp, linspace, log10, pi, concatenate
from matplotlib.pyplot import figure, plot, close, xlabel, ylabel, rc, xscale, legend, xlim
from scipy.optimize import minimize
close('all')          
           
#%% other parameters

# crystal size range
L_min = 1 # um
L_max = 180 # um

# diskretization
Nx = 20
Nz_0 = 1e4
Nz = int(Nz_0)
L = zeros(len(lokacija))
for i in range(len(lokacija)-1):
    L[i] = lokacija[i+1]-lokacija[i]
L[-1]  = L_reaktor - lokacija[-1]  
Nz = int(Nz_0)
dz = L/Nz_0

# log size distribution
min_L = log10(L_min) # log um
max_L = log10(L_max) # log um
velikosti = 10**(linspace(min_L,max_L,Nx)) * 1e-6 # m
Vi = 4 * pi * (velikosti/2)**3 / 3 # m^3
Ai = 4 * pi * (velikosti/2)**2 # m^2
dx = velikosti[1:] - velikosti[0:-1] # m
velikosti_um = velikosti * 1e6 # um

# system volume
A_reaktor = (pi * D_reaktor**2 / 4)
V_reaktor = L_reaktor * A_reaktor

# bilance part volume
V_bil = A_reaktor * dz

#%% kinetics

# nucleation
k_b1 = 1.1637e+11
Nb1 = 2.8994

# crystal growth
k_G = 1.46961e-05
Ng = 0.125455

# breakage
# C_b = 2.36235e-008 * 1.4 # breakage
# K4 = 4.144022492245547e-08 #C_b * 2**(3/2) * (3.14)**0.5/4 # breakage

#%% experiments

# metastable limit
V_del_izpad = [0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65,0.7,0.75,0.8, 1]
c_izpad = [433.3,437.7550769,425.83833,401.5159019,368.36608,329.5792969,287.95813,245.9173019,205.48368,168.2962769,135.60625,108.2769019,86.78368,71.21417688,61.26813,56.25742188,55.10608,54.000006]

# solubility
V_del_nasic = [0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65,0.7,0.75,0.8, 1]
c_nasic = [383.3,387.7550769,375.83833,351.5159019,318.36608,279.5792969,237.95813,195.9173019,155.48368,118.2962769,85.60625,58.27690188,36.78368,21.21417688,11.26813,6.257421875,5.10608,2.9]

#%% REGRESsION ######################################################################################

# prealocation
Nfi = len(lokacija)
V_krist_na_s = zeros([Nfi,Nz+1]) # m^3/s
n_krist_na_s = zeros([Nfi,Nz+1]) # mol/s
c_raz = zeros([Nfi,Nz+1]) # mol/m^3
y_raz = zeros([Nfi,Nz+1])
n = zeros([Nfi,Nz+1,Nx])
n_kon = zeros([Nfi,Nx])
n_kom = zeros([Nfi,Nx+1])
n_kom_del = zeros([Nfi,Nx+1])
d10 = zeros(Nfi)
d50 = zeros(Nfi)
d90 = zeros(Nfi)
pretok_sum = zeros(Nfi)
fi_v_prot_sum = zeros(Nfi)
V_del_prot = zeros(Nfi)
V_del_top = zeros(Nfi)
m_mix = zeros(Nfi)
m_prot = zeros(Nfi)
m_top = zeros(Nfi)
rho_top = zeros(Nfi)
m_top_i = zeros(Nfi)
m_prot_i = zeros(Nfi)
m_krist_na_s = zeros([Nfi,Nz])
V_krist_na_s = zeros([Nfi,Nz]) # m^3/s
n_krist_na_s = zeros([Nfi,Nz]) # mol/s
fi_v_prot = zeros(Nfi)
c_s_0 = zeros(Nfi)
c_s = zeros(Nfi)
c_nuk_0 = zeros(Nfi)
c_nuk = zeros(Nfi)
rho_mix = zeros(Nfi)
mi_mix = zeros(Nfi)
Re = zeros(Nfi)
De = zeros(Nfi) 
v = zeros(Nfi)
fi_v_prot_mlmin = zeros(Nfi)

# initial guesses
initial_guesses = [25, 0, 45, 0, 0]  

# inlet solubility
c_s_inlet = y / M_m # mol/m^3

# inlet molarity
n_0 = c_s_inlet * fi_v_top # mol/s vstopa 
  
# calculation
def ode_enacbe(initial_guesses): 

    for l in range(Nfi):    
        
        # initial conditions:
        if l == 0:
            n[0,0,:] = 0
            
        else:
            n[l,0,:] = n[l-1,-1,:]
        
        #from mL/min to m3/s
        fi_v_prot_mlmin[l] = initial_guesses[l]
        fi_v_prot[l] = fi_v_prot_mlmin[l] * 1e-6 / 60 # m^3/s
        if l > 0:
            if c_raz[l-1,-2] >= c_nuk[l-1] * 0.333:
                initial_guesses[l] = 0
                fi_v_prot_mlmin[l] = 0
                fi_v_prot[l] = 0  
                
        # total flow rate and velocity after each injection point
        fi_v_prot_sum[l] = sum(fi_v_prot[0:l+1])
        pretok_sum[l] = fi_v_top + fi_v_prot_sum[l]
        v[l] = pretok_sum[l] / A_reaktor # m/s
 
        # solvent/antisolvnt ratios
        V_del_prot[l] = (fi_v_top * (1-del_top_vtok) + (fi_v_prot_sum[l] * (del_prot_pritok))) / pretok_sum[l] #1 - V_del_top # V_del_prot = sum(fi_v_prot[0:k+1] * del_prot_pritok) / pretok_sum # kontrola
        V_del_top[l] = 1 - V_del_prot[l]
        
        # solubility kg/m^3, mol/m^3
        c_s_0[l] = interp(V_del_prot[l],V_del_nasic,c_nasic) # kg/m^3
        c_s[l] = c_s_0[l] / M_m # mol/m^3
        
        # metastable limit
        c_nuk_0[l] = interp(V_del_prot[l],V_del_izpad,c_izpad) # kg/m^3
        c_nuk[l] = c_nuk_0[l] / M_m # mol/m^3
                
        # density and viscosity of solution
        rho_mix[l] = V_del_prot[l] * rho_voda + V_del_top[l] * rho_etoh
        mi_mix[l] = V_del_prot[l] * mi_voda + V_del_top[l] * mi_etoh
        
        # Reynold' s and Dean's numbers
        Re[l] = rho_mix[l] * v[l] * D_reaktor / mi_mix[l]
        De[l] = Re[l] * 0.1**0.5
          
        # composition before and after of injection points
        if l == 0:
            m_top_i[l] = rho_etoh * del_top_vtok * fi_v_top
            m_prot_i[l] = rho_voda * (1-del_top_vtok) * fi_v_top  
        else:
            m_top_i[l] = rho_etoh * V_del_top[l-1] * pretok_sum[l-1]
            m_prot_i[l] = rho_voda * V_del_prot[l-1] * pretok_sum[l-1]  
            
        m_prot[l] = V_del_prot[l] * pretok_sum[l] * rho_voda
        m_top[l] = V_del_top[l] * pretok_sum[l] * rho_etoh        
        
        # Euler's method
        for z in range(0,Nz):  
        
            # mass balance
            m_krist_na_s[l,z] = sum(n[l,z,:] * Vi) * fi_v_top * rho_krist # kg/s
            
            # concentration balance
            c_raz[l,z] = (n_0 - (m_krist_na_s[l,z] / M_m)) / pretok_sum[l] # mol/m^3
            y_raz[l,z] = c_raz[l,z]*M_m
    
            # supersaturation
            S = c_raz[l,z] / c_s[l]
            A = (m_prot[l]/m_top[l]) - (m_prot_i[l]/m_top_i[l]) # for nucleation
                
            if c_raz[l,z] > c_s[l] and c_raz[l,z] < c_nuk[l]*0.333:                    
    
                for i in range(Nx):
                    if i == 0:
                        n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (- (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                    elif i < Nx-1:
                        n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * ((k_G * (S-1)**Ng) * n[l,z,i-1]/dx[i-1] - (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                    else:
                        n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (k_G * (S-1)**Ng) * (n[l,z,i-1]/dx[i-1]) # št./m^3                   
            
            elif c_raz[l,z] >= c_nuk[l]*0.333:
                B1 = k_b1 * (A)**Nb1 # nuclei/m^3 s
                
                for i in range(Nx):
                    if i == 0:
                        n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (- (k_G * (S-1)**Ng) * n[l,z,i]/dx[i] + B1) # št./m^3
                    elif i < Nx-1:
                        n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * ((k_G * (S-1)**Ng) * n[l,z,i-1]/dx[i-1] - (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                    else:
                        n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (k_G * (S-1)**Ng) * (n[l,z,i-1]/dx[i-1]) # št./m^3                                 

            elif c_raz[l,z] <= c_s[l]:
                n[l,z+1,:] = n[l,z,:]
                
            else:
                n[l,z+1,:] = n[l,z,:]

        if l > 0 and c_raz[l,0] < c_s[l]:
            while c_raz[l,0] < c_s[l]:
                initial_guesses[l] -= 1
                
                if initial_guesses[l] <= 0:
                    initial_guesses[l] = 0
                    
                    #from mL/min to m3/s
                    fi_v_prot_mlmin[l] = initial_guesses[l]
                    fi_v_prot[l] = fi_v_prot_mlmin[l] * 1e-6 / 60 # m^3/s

                    # total flow rate and velocity after each injection point
                    fi_v_prot_sum[l] = sum(fi_v_prot[0:l+1])
                    pretok_sum[l] = fi_v_top + fi_v_prot_sum[l]
                    v[l] = pretok_sum[l] / A_reaktor # m/s
             
                    # solvent/antisolvnt ratios
                    V_del_prot[l] = (fi_v_top * (1-del_top_vtok) + (fi_v_prot_sum[l] * (del_prot_pritok))) / pretok_sum[l] #1 - V_del_top # V_del_prot = sum(fi_v_prot[0:k+1] * del_prot_pritok) / pretok_sum # kontrola
                    V_del_top[l] = 1 - V_del_prot[l]
                    
                    # solubility kg/m^3, mol/m^3
                    c_s_0[l] = interp(V_del_prot[l],V_del_nasic,c_nasic) # kg/m^3
                    c_s[l] = c_s_0[l] / M_m # mol/m^3
                    
                    # metastable limit
                    c_nuk_0[l] = interp(V_del_prot[l],V_del_izpad,c_izpad) # kg/m^3
                    c_nuk[l] = c_nuk_0[l] / M_m # mol/m^3
                            
                    # density and viscosity of solution
                    rho_mix[l] = V_del_prot[l] * rho_voda + V_del_top[l] * rho_etoh
                    mi_mix[l] = V_del_prot[l] * mi_voda + V_del_top[l] * mi_etoh
                    
                    # Reynold' s and Dean's numbers
                    Re[l] = rho_mix[l] * v[l] * D_reaktor / mi_mix[l]
                    De[l] = Re[l] * 0.1**0.5
                      
                    # composition before and after of injection points
                    m_top_i[l] = rho_etoh * V_del_top[l-1] * pretok_sum[l-1]
                    m_prot_i[l] = rho_voda * V_del_prot[l-1] * pretok_sum[l-1]  
                        
                    m_prot[l] = V_del_prot[l] * pretok_sum[l] * rho_voda
                    m_top[l] = V_del_top[l] * pretok_sum[l] * rho_etoh 

                    # Euler's method
                    for z in range(0,Nz):  
                    
                        # mass balance 
                        m_krist_na_s[l,z] = sum(n[l,z,:] * Vi) * fi_v_top * rho_krist # kg/s
                        
                        # concentration balance
                        c_raz[l,z] = (n_0 - (m_krist_na_s[l,z] / M_m)) / pretok_sum[l] # mol/m^3
                        y_raz[l,z] = c_raz[l,z]*M_m
                
                        # supersaturation
                        S = c_raz[l,z] / c_s[l]
                        A = (m_prot[l]/m_top[l]) - (m_prot_i[l]/m_top_i[l])
                            
                        if c_raz[l,z] > c_s[l] and c_raz[l,z] < c_nuk[l]*0.333:                    
                
                            for i in range(Nx):
                                if i == 0:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (- (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                                elif i < Nx-1:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * ((k_G * (S-1)**Ng) * n[l,z,i-1]/dx[i-1] - (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                                else:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (k_G * (S-1)**Ng) * (n[l,z,i-1]/dx[i-1]) # št./m^3                   
                        
                        elif c_raz[l,z] >= c_nuk[l]*0.333:
                            B1 = k_b1 * (A)**Nb1 # nuclei/m^3 s
                            
                            for i in range(Nx):
                                if i == 0:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (- (k_G * (S-1)**Ng) * n[l,z,i]/dx[i] + B1) # št./m^3
                                elif i < Nx-1:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * ((k_G * (S-1)**Ng) * n[l,z,i-1]/dx[i-1] - (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                                else:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (k_G * (S-1)**Ng) * (n[l,z,i-1]/dx[i-1]) # št./m^3                                 
                
                        else:
                            n[l,z+1,:] = n[l,z,:]
                            
                    break  
                else: 
                    # from mL/min to m3/s
                    fi_v_prot_mlmin[l] = initial_guesses[l]
                    fi_v_prot[l] = fi_v_prot_mlmin[l] * 1e-6 / 60 # m^3/s
                    if l > 0:
                        if c_raz[l-1,-2] >= c_nuk[l-1] * 0.333:
                            initial_guesses[l] = 0
                            fi_v_prot_mlmin[l] = 0
                            fi_v_prot[l] = 0  
    
                    # total flow rate and velocity after each injection point
                    fi_v_prot_sum[l] = sum(fi_v_prot[0:l+1])
                    pretok_sum[l] = fi_v_top + fi_v_prot_sum[l]
                    v[l] = pretok_sum[l] / A_reaktor # m/s
             
                    # solvent/antisolvnt ratios
                    V_del_prot[l] = (fi_v_top * (1-del_top_vtok) + (fi_v_prot_sum[l] * (del_prot_pritok))) / pretok_sum[l] #1 - V_del_top # V_del_prot = sum(fi_v_prot[0:k+1] * del_prot_pritok) / pretok_sum # kontrola
                    V_del_top[l] = 1 - V_del_prot[l]
                    
                    # solubility kg/m^3, mol/m^3
                    c_s_0[l] = interp(V_del_prot[l],V_del_nasic,c_nasic) # kg/m^3
                    c_s[l] = c_s_0[l] / M_m # mol/m^3
                    
                    # metastable limit
                    c_nuk_0[l] = interp(V_del_prot[l],V_del_izpad,c_izpad) # kg/m^3
                    c_nuk[l] = c_nuk_0[l] / M_m # mol/m^3
                            
                    # density and viscosity of solution
                    rho_mix[l] = V_del_prot[l] * rho_voda + V_del_top[l] * rho_etoh
                    mi_mix[l] = V_del_prot[l] * mi_voda + V_del_top[l] * mi_etoh
                    
                    # Reynold' s and Dean's numbers
                    Re[l] = rho_mix[l] * v[l] * D_reaktor / mi_mix[l]
                    De[l] = Re[l] * 0.1**0.5
                      
                    # composition before and after of injection points
                    m_top_i[l] = rho_etoh * V_del_top[l-1] * pretok_sum[l-1]
                    m_prot_i[l] = rho_voda * V_del_prot[l-1] * pretok_sum[l-1]  
                        
                    m_prot[l] = V_del_prot[l] * pretok_sum[l] * rho_voda
                    m_top[l] = V_del_top[l] * pretok_sum[l] * rho_etoh 
    
                    # izračun Euler
                    for z in range(0,Nz):  
                    
                        # mass balance
                        m_krist_na_s[l,z] = sum(n[l,z,:] * Vi) * fi_v_top * rho_krist # kg/s
                        
                        # concentration balance
                        c_raz[l,z] = (n_0 - (m_krist_na_s[l,z] / M_m)) / pretok_sum[l] # mol/m^3
                        y_raz[l,z] = c_raz[l,z]*M_m
                
                        # supersaturation
                        S = c_raz[l,z] / c_s[l]
                        A = (m_prot[l]/m_top[l]) - (m_prot_i[l]/m_top_i[l])
                            
                        if c_raz[l,z] > c_s[l] and c_raz[l,z] < c_nuk[l]*0.333:                    
                
                            for i in range(Nx):
                                if i == 0:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (- (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                                elif i < Nx-1:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * ((k_G * (S-1)**Ng) * n[l,z,i-1]/dx[i-1] - (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                                else:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (k_G * (S-1)**Ng) * (n[l,z,i-1]/dx[i-1]) # št./m^3                   
                        
                        elif c_raz[l,z] >= c_nuk[l]*0.333:
                            B1 = k_b1 * (A)**Nb1 # nuclei/m^3 s
                            
                            for i in range(Nx):
                                if i == 0:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (- (k_G * (S-1)**Ng) * n[l,z,i]/dx[i] + B1) # št./m^3
                                elif i < Nx-1:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * ((k_G * (S-1)**Ng) * n[l,z,i-1]/dx[i-1] - (k_G * (S-1)**Ng) * n[l,z,i]/dx[i]) # št./m^3
                                else:
                                    n[l,z+1,i] = n[l,z,i] + 1/v[l] * dz[l] * (k_G * (S-1)**Ng) * (n[l,z,i-1]/dx[i-1]) # št./m^3                                 
                
                        else:
                            n[l,z+1,:] = n[l,z,:]    
                                                                        
        # normalized comulative crystal number
        n_kon[l] = n[l,-1,:]
        n_kom = zeros([Nfi,Nx+1])
        for i in range(Nx):
            n_kom[l,i+1] = (n_kom[l,i]+n_kon[l,i])
            
        n_kom_del[l] = n_kom[l,0:Nx+1]/sum(n_kon[l])
        
        d10[l] = interp(0.1,n_kom_del[l,0:Nx],velikosti_um)
        d50[l] = interp(0.5,n_kom_del[l,0:Nx],velikosti_um)
        d90[l] = interp(0.9,n_kom_del[l,0:Nx],velikosti_um)       
        
    # target function
    desired_size = 50 # um
    objective = (desired_size - d50[-1])**2 
    return objective
        
# Solution
parametri = minimize(ode_enacbe, initial_guesses, method='Nelder-Mead', bounds =((25,200), (0,40), 
                                                                                 (0,45), (0,40), (0,40)))

executionTime = (time() - startTime)/60
print('Execution time in minutes: ' +str(executionTime))

python

optimization

minimize

scipy-optimize-minimize

0 Answers

Your Answer

Accepted video resources