[来自Mathematica Stack Exchange的部分交叉文章。在这里打开它,这样我就可以在原则上征求numpy/scipy生态系统中理想的非南极洲解决方案。
我正试着沿着图3的线沿着确定一个相图。或Deviri and Safran (2021)的图4:
绘制的曲线是双节点的,并且由所有相中的每个组分的化学势相等和每个相中的渗透压相等的条件确定(SI的第4节是一个很好的引物)。
作为一个例子,我使用的自由能函数类似于论文中定义的自由能函数(尽管我实际上感兴趣的自由能函数更复杂一点。理想情况下,我可以提出一个鲁棒且普遍适用的解决方案)。这仍然是一个很好的起点,因为我的系统确实在某些限制下减少到这个。
我们将注意力集中到两种溶质(1种溶剂)和两相的情况。然后,我们希望找到($\phi_a,\phi_b$
)中满足以下条件的点的轨迹
基本上,我们有4个未知数$\phi^{(1)}_a,\phi^{(1)}_b, \phi^{(0)}_a,\phi^{(0)}_b$
和3个方程。解决方案应该看起来像链接出版物的图3或图4中的曲线(取决于参数值)。在我的符号中,下标标记组件,上标标记阶段。上述条件应等同于该SI中的等式(39)-(41
上述方程中的量可以使用自由能的导数获得,Wolfram Mathematica中的一个例子。
F[a_, b_, uaa_, ubb_, uab_, na_, nb_] :=
a/na*Log[a] + b/nb*Log[b] + (1 - a - b)*Log[1 - a - b + 10^-14] -
1/2*uaa*a^2 - 1/2*ubb*b^2 - uab*b*a
det[a_, b_, uaa_, ubb_, uab_, na_, nb_] :=
Det[D[F[a, b, uaa, ubb, uab, na, nb], {{a, b}, 2}]] // Evaluate
\[Mu]a[a_, b_, uaa_, ubb_, uab_, na_, nb_] :=
D[F[a, b, uaa, ubb, uab, na, nb], a] // Evaluate
\[Mu]b[a_, b_, uaa_, ubb_, uab_, na_, nb_] :=
D[F[a, b, uaa, ubb, uab, na, nb], b] // Evaluate
\[CapitalPi]F[a_, b_, uaa_, ubb_, uab_, na_,
nb_] := \[Mu]a[a, b, uaa, ubb, uab, na, nb]*
a + \[Mu]b[a, b, uaa, ubb, uab, na, nb]*b -
F[a, b, uaa, ubb, uab, na, nb] // Evaluate
我在numpy/python
中尝试了一个简单的蛮力解决方案,因为我的聪明想法已经不多了。
import numpy as np
from numpy import log as log
import matplotlib.pyplot as plt
一些辅助功能。
def approx_first_derivative(f, x0, y0, h=1e-7):
'''
chemical potentials are first derivs of F.
Quick and dirty finite-diff based first derivative.
'''
fx = (f(x0 + h, y0) - f(x0 - h, y0)) / (2 * h)
fy = (f(x0, y0 + h) - f(x0, y0 - h)) / (2 * h)
return fx, fy
def approx_second_derivative(f, x0, y0, h=1e-7):
'''
spinodal line is the boundary of stab. for F.
can be obtained from det(Hessian(F)) = 0. Finite diff.
Quick and dirty finite-diff based second derivative.
'''
# Approximate second order partial derivatives using finite differences
fxx = (f(x0 + h, y0) - 2 * f(x0, y0) + f(x0 - h, y0)) / h**2
fyy = (f(x0, y0 + h) - 2 * f(x0, y0) + f(x0, y0 - h)) / h**2
fxy = (f(x0 + h, y0 + h) - f(x0 + h, y0 - h) - f(x0 - h, y0 + h) + f(x0 - h, y0 - h)) / (4 * h**2)
return np.array([[fxx, fxy], [fxy, fyy]])
定义物理量
def F(a,b,uaa,ubb,uab,na,nb):
entropy = (a/na)*log(a+1e-14)+(b/nb)*log(b+1e-14)+(1-a-b)*log(1-a-b+1e-14)
energy = -0.5*uaa*a**2 - 0.5*ubb*b**2 -uab*a*b
return entropy+energy
def mu_a(a, b, uaa, ubb, uab, na, nb):
fx, _ = approx_first_derivative(lambda x, y: F(x, y, uaa, ubb, uab, na, nb), a, b)
return fx
def mu_b(a, b, uaa, ubb, uab, na, nb):
_, fy = approx_first_derivative(lambda x, y: F(x, y, uaa, ubb, uab, na, nb), a, b)
return fy
def Pi(a, b, uaa, ubb, uab, na=10, nb=6):
return mu_a(a, b, uaa, ubb, uab, na, nb) * a + mu_b(a, b, uaa, ubb, uab, na, nb) * b - F(a, b, uaa, ubb, uab, na, nb)
def det_approx(a, b, uaa, ubb, uab, na, nb):
H = approx_second_derivative(lambda x, y: F(x, y, uaa, ubb, uab, na, nb), a, b)
return np.linalg.det(H)
现在我们在x,y平面上寻找一对点,使得上面定义的mu_a
,mu_b
和Pi
都相等。在这条曲线上有两个点。
我只是尝试蛮力和搜索这个。我在下面分享了我的尝试。
"""
Very janky brute force grid search.
Most likely not the right way to do this.
"""
epsilon = 1e-1
uaa,ubb,uab=0,0,4.36
satisfying_points= []
mypoints = set()
# Check each pair of points
for i in np.arange(0,1,epsilon):
for j in np.arange(0,1,epsilon):
for k in np.arange(i+epsilon,1,epsilon):
for l in np.arange(j+epsilon,1,epsilon):
#print((i,j),(k,l))
if abs(mu_a(i,j,uaa,ubb,uab,10,6) - mu_a(k,l,uaa,ubb,uab,10,6)) <= epsilon and \
abs(mu_b(i,j,uaa,ubb,uab,10,6) - mu_b(k,l,uaa,ubb,uab,10,6)) <= epsilon and \
abs(Pi(i,j,uaa,ubb,uab,10,6) - Pi(k,l,uaa,ubb,uab,10,6)) <= epsilon:
mypoints.add((i, j))
mypoints.add((k, l))
#break
satisfying_points = np.array([point for point in mypoints])
plt.scatter(satisfying_points[:, 0], satisfying_points[:, 1], s=10, color='blue')
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(0,1)
plt.ylim(0,1)
plt.title('Points satisfying the conditions')
plt.grid(True)
plt.show()
和
"""
Attempting to vectorise the janky brute force solution.
Most likely not the right way to do this.
"""
eps = 1e-2
thresh = 1e-2
x_vals = np.arange(0, 0.4, eps)
y_vals = np.arange(0, 0.4, eps)
satisfying_points_x = []
satisfying_points_y = []
uaa,ubb,uab=0,0,4.36
# Compute the mu_a, mu_b, and Pi values for all points in the grid
mu_a_values = mu_a(x_vals[:, np.newaxis], y_vals, uaa, ubb, uab, 10, 6)
mu_b_values = mu_b(x_vals[:, np.newaxis], y_vals, uaa, ubb, uab, 10, 6)
Pi_values = Pi(x_vals[:, np.newaxis], y_vals, uaa, ubb, uab, 10, 6)
# Loop over the grid
for idx_i, i in enumerate(x_vals):
for idx_j, j in enumerate(y_vals):
# Compare current point's values with all subsequent points using vectorization
diff_mu_a = np.abs(mu_a_values[idx_i, idx_j] - mu_a_values[idx_i + 1:, idx_j + 1:])
diff_mu_b = np.abs(mu_b_values[idx_i, idx_j] - mu_b_values[idx_i + 1:, idx_j + 1:])
diff_Pi = np.abs(Pi_values[idx_i, idx_j] - Pi_values[idx_i + 1:, idx_j + 1:])
# Find indices where all conditions are satisfied
match_indices = np.where((diff_mu_a <= thresh) & (diff_mu_b <= thresh) & (diff_Pi <= thresh))
if match_indices[0].size > 0:
# If we found matches, add the points to our list
satisfying_points_x.extend([i, x_vals[match_indices[0][0] + idx_i + 1]])
satisfying_points_y.extend([j, y_vals[match_indices[1][0] + idx_j + 1]])
#break
# Convert the lists to arrays for plotting
satisfying_points_x = np.array(satisfying_points_x)
satisfying_points_y = np.array(satisfying_points_y)
x = np.linspace(0, 1, 100)
y = np.linspace(0, 1, 100)
X, Y = np.meshgrid(x, y)
Z_approx = np.array([[det_approx(x_ij, y_ij, uaa,ubb,uab, 10, 6) for x_ij, y_ij in zip(x_row, y_row)] for x_row, y_row in zip(X, Y)])
plt.contour(X, Y, Z_approx, levels=[0], colors='red')
plt.scatter(satisfying_points_x, satisfying_points_y, s=10, color='blue')
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(0,1)
plt.ylim(0,1)
plt.grid(True)
plt.show()
对于某些参数值,例如这让我在附近得到了很多分数。uaa,ubb,uab=0,0,4.36
的正确曲线(应类似于图4篇论文)。对于uaa,ubb,uab=1.8,0,0
根本无法工作(应该与图相似)。3篇论文)。
我附上一个快速可视化我从我的代码。我用红色标出了旋节线。我感兴趣的双节点曲线应该在临界点的接触点附近。蓝色是我使用网格搜索方法识别的点。
第二种方法
我被@Reinderien的巨大努力所鼓舞。我认为他的算法和方法似乎很好,然而,在我对这个问题的理解中,在旋节线(det(Hessian(F)=0
)内部承认临界点的解曲线是绝对不符合物理的。这也是接近,但不完全相同的结果公布。
我的第二次尝试将问题描述为优化问题。
import numpy as np
from scipy.optimize import minimize
from scipy.optimize import NonlinearConstraint
uaa, ubb, uab = 0, 0, 4.36
na, nb = 10, 6
def objective_fun(x):
x1, y1, x2, y2 = x
term1 = mu_a(x1, y1, uaa, ubb, uab, na, nb) - mu_a(x2, y2, uaa, ubb, uab, na, nb)
term2 = mu_b(x1, y1, uaa, ubb, uab, na, nb) - mu_b(x2, y2, uaa, ubb, uab, na, nb)
term3 = Pi(x1, y1, uaa, ubb, uab, na, nb) - Pi(x2, y2, uaa, ubb, uab, na, nb)
return term1**2 + term2**2 + term3**2
# Define a nonlinear constraint to ensure x1 != x2 and y1 != y2
#def constraint(x):
# return (x[0] - x[2])**2 + (x[1] - x[3])**2 - 1e-4 # Should be greater than 0
#nonlinear_constraint = NonlinearConstraint(constraint, 1e-4, np.inf)
# Create a grid of initial guesses
x1_values = np.linspace(0, 0.4, 20) # Adjust the range and number of points as needed
y1_values = np.linspace(0, 0.4, 20)
x2_values = np.linspace(0, 0.4, 20)
y2_values = np.linspace(0, 0.4, 20)
solutions = []
for x1 in x1_values:
for y1 in y1_values:
for x2 in x2_values:
for y2 in y2_values:
initial_guess = [x1, y1, x2, y2]
# Perform the optimization
#sol = minimize(objective_fun, initial_guess, constraints=[nonlinear_constraint], bounds=[(0, 1), (0, 1), (0, 1), (0, 1)])
sol = minimize(objective_fun, initial_guess, bounds=[(0, 1), (0, 1), (0, 1), (0, 1)])
# Extract the solution
x1_sol, y1_sol, x2_sol, y2_sol = sol.x
# Check if the solver was successful
if sol.success:
# Check if x1 != x2 and y1 != y2
if np.abs(x1_sol - x2_sol) > 1e-4 and np.abs(y1_sol - y2_sol) > 1e-4:
# Check if the chemical potentials are close to each other
if np.abs(mu_a(x1_sol, y1_sol, uaa, ubb, uab, na, nb) - mu_a(x2_sol, y2_sol, uaa, ubb, uab, na, nb)) <= 1e-4 and \
np.abs(mu_b(x1_sol, y1_sol, uaa, ubb, uab, na, nb) - mu_b(x2_sol, y2_sol, uaa, ubb, uab, na, nb)) <= 1e-4 and \
np.abs(Pi(x1_sol, y1_sol, uaa, ubb, uab, na, nb) - Pi(x2_sol, y2_sol, uaa, ubb, uab, na, nb)) <= 1e-4:
print(f"Found a solution at {sol.x} with initial guess {initial_guess}")
solutions.append(sol)
else:
continue
#print(f"No with initial guess {initial_guess}")
并制作一个情节
import matplotlib.pyplot as plt
# Initialize lists to store x1, y1, x2, y2 values
x1_vals = []
y1_vals = []
x2_vals = []
y2_vals = []
# Extract the values from the solutions
for sol in solutions:
x1, y1, x2, y2 = sol.x
x1_vals.append(x1)
y1_vals.append(y1)
x2_vals.append(x2)
y2_vals.append(y2)
Z_approx = np.array([[det_approx(x_ij, y_ij, 0,0, 4.36, 10, 6) for x_ij, y_ij in zip(x_row, y_row)] for x_row, y_row in zip(X, Y)])
# Create the scatter plot
plt.figure(figsize=(10, 10))
plt.scatter(x1_vals, y1_vals, c='blue', label='(x1, y1)', alpha=0.6)
plt.scatter(x2_vals, y2_vals, c='red', label='(x2, y2)', alpha=0.6)
plt.contour(X, Y, Z_approx, levels=[0], colors='blue')
# Add labels and title
plt.xlabel('x')
plt.ylabel('y')
plt.title('Scatter Plot of Solutions')
plt.legend()
# Show the plot
plt.show()
这再次接近,但不等同于图4。链接的出版物。
在蓝色实线中绘制了旋节线,2个临界点(在出版物的图4中标记为X)发生在接近双节线应该接触旋节线的地方,因此这是令人鼓舞的。但是,对曲线沿着方向(x=y)的解并不是很好。
Q1:从本质上解决这个问题的正确方法是找到一条满足欠定非线性方程组的曲线。理想情况下,数值方法是有效和鲁棒的,适用于F
类似,但不完全相同的玩具版本在这里描述。理想的解决方案可以使用任何标准的包/算法/封装方法。我只是不知道他们。
1条答案
按热度按时间cigdeys31#
这是困难的,我不相信我的解决方案是最有效的;但它基本上是有效的。大致来说
1.在径向表面pi的中心内,找到其最小值的位置。
1.开始一个广泛的搜索,引用了最小的位置,任何对低错误,使用一个调用,以最小化。
1.从该对的原点开始,使用有界跟随例程向左和向右进行分支。如果以下任何一项变为真,则终止:
所有三个组件的简单图来观察它们是如何工作的:
这个比较复杂:
在最近的编辑中,修复了剩余的bug: