codexpast-exam-of-the-quantum-condensed-matter-course-of-the-university-of-cambridge2007-polymer-phase-diagram.py
past-exam-of-the-quantum-condensed-matter-course-of-the-university-of-cambridge/2007-polymer-phase-diagram.py
"""Original Flory-Huggins phase diagram; coexistence from a common tangent.
The inner bisections find the two stable stationary concentrations for a
trial chemical potential. The outer one equates their grand potentials.
Only NumPy and Matplotlib are required; output goes to the current directory.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
N = 1000
critical_phi = 1 / (1 + np.sqrt(N))
critical_chi = 0.5 * (1 + 1 / np.sqrt(N))**2
def free_energy(phi, chi):
return phi / N * np.log(phi) + (1 - phi) * np.log1p(-phi) + chi * phi * (1 - phi)
def chemical_potential(phi, chi):
return (np.log(phi) + 1) / N - np.log1p(-phi) - 1 + chi * (1 - 2 * phi)
def bisect(function, lower, upper, iterations=65):
left_value = function(lower)
right_value = function(upper)
assert left_value * right_value <= 0, (lower, upper, left_value, right_value)
for _ in range(iterations):
middle = (lower + upper) / 2
value = function(middle)
if value * left_value > 0:
lower, left_value = middle, value
else:
upper = middle
return (lower + upper) / 2
def coexistence(chi):
roots = np.sort(np.roots([2 * chi * N, N - 1 - 2 * chi * N, 1]))
spin_low, spin_high = roots
def phases(mu):
low = np.exp(bisect(lambda u: chemical_potential(np.exp(u), chi) - mu,
-700, np.log(spin_low)))
high = bisect(lambda p: chemical_potential(p, chi) - mu,
spin_high, 1 - 1e-12)
return low, high
def difference(mu):
low, high = phases(mu)
return free_energy(high, chi) - mu * high - free_energy(low, chi) + mu * low
# Stay just inside the metastability limits to avoid roundoff at double roots.
minimum = chemical_potential(spin_high, chi)
maximum = chemical_potential(spin_low, chi)
pad = (maximum - minimum) * 1e-9
mu = bisect(difference, minimum + pad, maximum - pad)
low, high = phases(mu)
assert low < spin_low < spin_high < high
assert abs(chemical_potential(low, chi) - chemical_potential(high, chi)) < 1e-10
assert abs(difference(mu)) < 1e-10
return low, high, spin_low, spin_high
chis = np.linspace(critical_chi + 0.00015, 0.61, 180)
branches = np.array([coexistence(chi) for chi in chis])
fig, ax = plt.subplots(figsize=(8.5, 5.2), layout="constrained")
ax.fill_betweenx(chis, branches[:, 0], branches[:, 1], color="#e6edf8",
label="two-phase equilibrium")
ax.fill_betweenx(chis, branches[:, 2], branches[:, 3], color="#f8d2c5",
label="unstable homogeneous state")
for column in [0, 1]:
ax.plot(np.r_[critical_phi, branches[:, column]], np.r_[critical_chi, chis],
color="#245b9f", lw=2, label="binodal" if column == 0 else None)
for column in [2, 3]:
ax.plot(np.r_[critical_phi, branches[:, column]], np.r_[critical_chi, chis],
color="#b55032", lw=1.6, ls="--", label="spinodal" if column == 2 else None)
ax.axhline(0.5, color="#447747", lw=1.3, ls=":", label=r"theta condition $\chi=1/2$")
ax.plot(critical_phi, critical_chi, "o", color="black", ms=5)
ax.annotate("critical point", (critical_phi, critical_chi), xytext=(0.002, 0.518),
arrowprops={"arrowstyle": "->", "lw": 0.8}, fontsize=10)
ax.text(1.6e-10, 0.492, "good solvent", color="#447747", fontsize=10)
ax.text(1.6e-10, 0.505, "poorer solvent above the theta line", fontsize=10)
ax.text(0.012, 0.584, "unstable", color="#873e29", fontsize=10)
ax.text(5e-7, 0.574, "metastable", color="#245b9f", fontsize=10)
ax.set(xscale="log", xlim=(1e-10, 1), ylim=(0.48, 0.612),
xlabel=r"Polymer volume fraction $\phi$ (log scale)",
ylabel=r"Interaction parameter $\chi$ (larger = poorer solvent)",
title=r"Flory-Huggins polymer solution, $N=1000$")
ax.legend(loc="upper left", fontsize=9, framealpha=0.96)
ax.grid(alpha=0.12)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=120, facecolor="white")