CCCPaste

Мірошниченко_КМЗПМ_ЛР№4

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()