from __future__ import annotations
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
BETA = 0.9
EPS = 4.0
RHO = 0.25
CROSSES = [
(0.1, 0.75), (0.1, 1.3), (0.1, 1.8),
(0.2, 0.75), (0.2, 1.3), (0.2, 1.8),
]
P_MIN, P_MAX, P_POINTS = 0.0, 0.5, 220
T_MIN, T_MAX, T_POINTS = 0.05, 2.00, 150
K_MAX, K_POINTS = 3.0, 400
def mu_of(c: np.ndarray | float) -> np.ndarray | float:
return c * (1.0 - c)
def f_c(c: np.ndarray | float, P: float, T: float, beta: float = BETA, eps: float = EPS, Dpar: float = 0.1) -> np.ndarray | float:
return (
P * (1.0 - c) * (1.0 - beta * c)
- c * (1.0 - beta * c) * np.exp(-2.0 * eps * c / T)
- Dpar * c * (1.0 - beta)
)
def df_dc(c: float, P: float, T: float, beta: float = BETA, eps: float = EPS, Dpar: float = 0.1) -> float:
term1 = P * (-(1.0 + beta) + 2.0 * beta * c)
g = c - beta * c * c
gp = 1.0 - 2.0 * beta * c
h = np.exp(-2.0 * eps * c / T)
hp = (-2.0 * eps / T) * h
term2 = -gp * h + (2.0 * eps / T) * g * h
term3 = -Dpar * (1.0 - beta)
return float(term1 + term2 + term3)
def find_c_st(P: float, T: float, beta: float = BETA, eps: float = EPS, Dpar: float = 0.1,
tol: float = 1e-10, maxit: int = 200) -> float:
a, b = 0.0, 1.0
fa = f_c(a, P, T, beta, eps, Dpar)
fb = f_c(b, P, T, beta, eps, Dpar)
if fa * fb > 0:
xs = np.linspace(0.0, 1.0, 2001)
vals = f_c(xs, P, T, beta, eps, Dpar)
s = np.sign(vals)
idx = np.where(s[:-1] * s[1:] <= 0)[0]
if len(idx) == 0:
return float(xs[np.argmin(np.abs(vals))])
a, b = float(xs[idx[0]]), float(xs[idx[0] + 1])
fa, fb = float(vals[idx[0]]), float(vals[idx[0] + 1])
for _ in range(maxit):
m = 0.5 * (a + b)
fm = f_c(m, P, T, beta, eps, Dpar)
if abs(fm) < tol or (b - a) < tol:
return float(m)
if fa * fm <= 0:
b, fb = m, fm
else:
a, fa = m, fm
return float(0.5 * (a + b))
def lambda_k(k: np.ndarray, P: float, T: float, Dpar: float = 0.1,
beta: float = BETA, eps: float = EPS, rho: float = RHO) -> np.ndarray:
T_eff = max(T, 1e-9)
cst = find_c_st(P, T_eff, beta=beta, eps=eps, Dpar=Dpar)
lam0 = df_dc(cst, P, T_eff, beta=beta, eps=eps, Dpar=Dpar)
mu = mu_of(cst)
return lam0 - k**2 * (1.0 - 2.0 * (eps / T_eff) * mu * (1.0 - (rho**2) * k**2))
def max_lambda_over_k(P: float, T: float, Dpar: float) -> float:
ks = np.linspace(0.0, K_MAX, K_POINTS)
lams = lambda_k(ks, P, T, Dpar=Dpar)
return float(np.max(lams))
def Tcrit_numeric_for_P(P: float, Dpar: float, t_min: float = T_MIN, t_max: float = T_MAX,
nT: int = T_POINTS) -> float | float('nan'):
Ts = np.linspace(t_min, t_max, nT)
phi = np.array([max_lambda_over_k(P, T, Dpar) for T in Ts])
sgn = np.sign(phi)
roots = []
for i in range(len(Ts) - 1):
if sgn[i] == 0:
roots.append(Ts[i]); continue
if sgn[i] * sgn[i + 1] < 0:
a, b = Ts[i], Ts[i + 1]
fa, fb = phi[i], phi[i + 1]
for _ in range(40):
m = 0.5 * (a + b)
fm = max_lambda_over_k(P, m, Dpar)
if abs(fm) < 1e-6 or (b - a) < 1e-3:
roots.append(m); break
if fa * fm <= 0:
b, fb = m, fm
else:
a, fa = m, fm
if len(roots) == 0:
return float('nan')
return float(np.max(roots))
def Tcrit_curve(P_grid: np.ndarray, Dpar: float) -> tuple[np.ndarray, np.ndarray]:
T_vals = np.array([Tcrit_numeric_for_P(P, Dpar) for P in P_grid])
mask = np.isfinite(T_vals)
return P_grid[mask], T_vals[mask]
def plot_PT_main():
P_grid = np.linspace(P_MIN, P_MAX, P_POINTS)
P_b, T_b = Tcrit_curve(P_grid, Dpar=0.1)
P_r, T_r = Tcrit_curve(P_grid, Dpar=0.5)
fig, ax = plt.subplots(figsize=(9.5, 5.5), dpi=130)
ax.plot(P_b, T_b, 'k-', lw=2.2, label=r'$D_\parallel=0.1$')
ax.plot(P_r, T_r, 'r--', lw=2.2, label=r'$D_\parallel=0.5$')
for (p, t) in CROSSES:
ax.plot(p, t, 'kx', ms=8, mew=1.8)
ax.set_xlim(P_MIN, P_MAX)
ax.set_ylim(0.0, T_MAX)
ax.set_xlabel('P'); ax.set_ylabel('T')
ax.text(0.04, 1.72, 'B', fontsize=12)
ax.text(0.44, 0.32, 'A', fontsize=12)
ax.legend(loc='upper right', frameon=False)
ax.set_title('Умови формування просторових структур')
plt.tight_layout(); plt.show()
def plot_lambda_small(P_val: float, T_val: float):
ks = np.linspace(0.0, K_MAX, K_POINTS)
lam_b = lambda_k(ks, P_val, T_val, Dpar=0.1)
lam_r = lambda_k(ks, P_val, T_val, Dpar=0.5)
fig, ax = plt.subplots(figsize=(5.3, 4.0), dpi=130)
ax.plot(ks, lam_b, 'k-', lw=2, label=r'$D_\parallel=0.1$')
ax.plot(ks, lam_r, 'r--', lw=2, label=r'$D_\parallel=0.5$')
ax.axhline(0.0, color='gray', lw=1)
ax.set_xlim(0.0, K_MAX)
ax.set_xlabel('k'); ax.set_ylabel(r'$\lambda(k)$')
ax.set_title(f'λ(k): P={P_val:.3f}, T={T_val:.3f}')
ax.legend(loc='upper right', frameon=False, fontsize=9)
plt.tight_layout(); plt.show()
def lambda_max_point(P_val: float, T_val: float, Dpar: float) -> tuple[float, float]:
ks = np.linspace(0.0, K_MAX, K_POINTS)
lam = lambda_k(ks, P_val, T_val, Dpar=Dpar)
i = int(np.argmax(lam))
return float(ks[i]), float(lam[i])
def plot_composite():
P_grid = np.linspace(P_MIN, P_MAX, P_POINTS)
P_b, T_b = Tcrit_curve(P_grid, Dpar=0.1)
P_r, T_r = Tcrit_curve(P_grid, Dpar=0.5)
fig = plt.figure(figsize=(13.5, 8.4), dpi=120)
gs = fig.add_gridspec(3, 3, width_ratios=[1.3, 2.2, 1.3], wspace=0.35, hspace=0.35)
axC = fig.add_subplot(gs[:, 1])
axC.plot(P_b, T_b, 'k-', lw=2.2, label=r'$D_\parallel=0.1$')
axC.plot(P_r, T_r, 'r--', lw=2.2, label=r'$D_\parallel=0.5$')
for (p, t) in CROSSES:
axC.plot(p, t, 'k*', ms=9)
axC.set_xlim(P_MIN, P_MAX); axC.set_ylim(0.0, T_MAX)
axC.set_xlabel('P'); axC.set_ylabel('T')
axC.legend(loc='upper right', frameon=False)
axC.set_title('Умови формування просторових структур')
left_points = CROSSES[2::-1] # (1.8, 1.3, 0.75) при P=0.1
right_points = CROSSES[5:2:-1] # (1.8, 1.3, 0.75) при P=0.2
def add_lambda_panel(ax, P_val, T_val):
ks = np.linspace(0.0, K_MAX, K_POINTS)
lam_b = lambda_k(ks, P_val, T_val, Dpar=0.1)
lam_r = lambda_k(ks, P_val, T_val, Dpar=0.5)
ax.plot(ks, lam_b, 'k-', lw=2, label=r'$D_\parallel=0.1$')
ax.plot(ks, lam_r, 'r--', lw=2, label=r'$D_\parallel=0.5$')
ax.axhline(0.0, color='gray', lw=1)
ax.set_xlim(0.0, K_MAX)
ax.set_title(f'P={P_val:.3f}, T={T_val:.3f}', fontsize=10)
ax.set_xlabel('k'); ax.set_ylabel(r'$\lambda(k)$')
km, lm = lambda_max_point(P_val, T_val, Dpar=0.5)
ax.plot(km, lm, 'o', color='#2b6fff', ms=5)
return km, lm
left_axes = [fig.add_subplot(gs[i, 0]) for i in range(3)]
for ax, (p, t) in zip(left_axes, left_points):
km, lm = add_lambda_panel(ax, p, t)
con = ConnectionPatch(xyA=(km, lm), coordsA=ax.transData,
xyB=(p, t), coordsB=axC.transData,
arrowstyle='->', lw=1.6, color='#2b6fff')
fig.add_artist(con)
right_axes = [fig.add_subplot(gs[i, 2]) for i in range(3)]
for ax, (p, t) in zip(right_axes, right_points):
km, lm = add_lambda_panel(ax, p, t)
con = ConnectionPatch(xyA=(km, lm), coordsA=ax.transData,
xyB=(p, t), coordsB=axC.transData,
arrowstyle='->', lw=1.6, color='#2b6fff')
fig.add_artist(con)
plt.show()
plt.close(fig)
if __name__ == '__main__':
plot_PT_main()
for i, (p, t) in enumerate(CROSSES, start=1):
plot_lambda_small(p, t)
plot_composite()