迁移到dhxdl
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the IGF illustration figure (fig_igf.pdf).
|
||||
|
||||
Implements the paper's equations verbatim on the synthetic site of sitegeom.py:
|
||||
|
||||
eq:compact_kernel K(d; rho, m) = (1 - d/rho)^m for d < rho, else 0
|
||||
eq:source_field U_S(z) = alpha_S * sum_{xi in S} K(|z-xi|; rho_S, m_S) ds_xi
|
||||
eq:field_park U_park = U_e - U_p + U_x
|
||||
eq:field_exit U_exit = U_e + U_p - U_x
|
||||
eq:field_bounds U_g^max = 2 alpha_g rho_g / (m_g + 1)
|
||||
U_e^max = alpha_e (2 rho_e / lbar_e + 1)
|
||||
eq:field_normalize Uhat = (U + U_g^max) / (U_e^max + 2 U_g^max), clipped [0,1]
|
||||
|
||||
For S_e the paper takes ds_xi == 1 (vertex counting); S_p and S_x are
|
||||
equal-arc-length samples carrying their true line element. Both are reproduced.
|
||||
|
||||
Construction illustration on a synthetic site -- not an experimental result.
|
||||
"""
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Polygon as MplPolygon
|
||||
from matplotlib.path import Path
|
||||
|
||||
import sitegeom as sg
|
||||
|
||||
# Field parameters; alpha_e is set so that eq:alpha_dominance holds.
|
||||
RHO_E, M_E, ALPHA_E = 8.0, 3.0, 1.00
|
||||
RHO_G, M_G, ALPHA_G = 9.0, 3.0, 0.28
|
||||
LBAR_E = 1.0 # mean boundary-vertex spacing, enters U_e^max
|
||||
DS_G = 0.5 # arc-length element of the guidance-line samples
|
||||
DELTA = 0.25 # field grid resolution
|
||||
|
||||
|
||||
def kernel(d, rho, m):
|
||||
out = np.zeros_like(d)
|
||||
ins = d < rho
|
||||
out[ins] = (1.0 - d[ins] / rho) ** m
|
||||
return out
|
||||
|
||||
|
||||
def source_field(Z, S, alpha, rho, m, ds):
|
||||
U = np.zeros(Z.shape[0])
|
||||
for i in range(0, S.shape[0], 256):
|
||||
blk = S[i:i + 256]
|
||||
d = np.linalg.norm(Z[:, None, :] - blk[None, :, :], axis=2)
|
||||
U += (kernel(d, rho, m) * ds).sum(axis=1)
|
||||
return alpha * U
|
||||
|
||||
|
||||
def resample_closed(poly, step):
|
||||
pts = np.vstack([poly, poly[:1]])
|
||||
seg = np.diff(pts, axis=0)
|
||||
L = np.hypot(seg[:, 0], seg[:, 1])
|
||||
cum = np.concatenate([[0.0], np.cumsum(L)])
|
||||
s = np.arange(0.0, cum[-1], step)
|
||||
out = np.empty((s.size, 2))
|
||||
for i, si in enumerate(s):
|
||||
k = min(np.searchsorted(cum, si, side="right") - 1, len(seg) - 1)
|
||||
t = (si - cum[k]) / L[k]
|
||||
out[i] = pts[k] + t * seg[k]
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
g = sg.build_centerline()
|
||||
center, park_line, exit_line = g["center"], g["park_line"], g["exit_line"]
|
||||
|
||||
# ---- source sets -------------------------------------------------------
|
||||
S_e = resample_closed(sg.SITE, LBAR_E)
|
||||
S_p = sg.resample(park_line, DS_G)
|
||||
S_x = sg.resample(exit_line, DS_G)
|
||||
|
||||
# ---- grid --------------------------------------------------------------
|
||||
x0, x1 = sg.SITE[:, 0].min() - 1.0, sg.SITE[:, 0].max() + 1.0
|
||||
y0, y1 = sg.SITE[:, 1].min() - 1.0, sg.SITE[:, 1].max() + 1.0
|
||||
xs = np.arange(x0, x1, DELTA)
|
||||
ys = np.arange(y0, y1, DELTA)
|
||||
XX, YY = np.meshgrid(xs, ys)
|
||||
Z = np.column_stack([XX.ravel(), YY.ravel()])
|
||||
|
||||
U_e = source_field(Z, S_e, ALPHA_E, RHO_E, M_E, 1.0)
|
||||
U_p = source_field(Z, S_p, ALPHA_G, RHO_G, M_G, DS_G)
|
||||
U_x = source_field(Z, S_x, ALPHA_G, RHO_G, M_G, DS_G)
|
||||
|
||||
Ug_max = 2.0 * ALPHA_G * RHO_G / (M_G + 1.0)
|
||||
Ue_max = ALPHA_E * (2.0 * RHO_E / LBAR_E + 1.0)
|
||||
assert Ue_max > Ug_max, "eq:alpha_dominance violated"
|
||||
|
||||
def norm(U):
|
||||
return np.clip((U + Ug_max) / (Ue_max + 2.0 * Ug_max), 0.0, 1.0)
|
||||
|
||||
Up_raw, Ux_raw = U_e - U_p + U_x, U_e + U_p - U_x
|
||||
U_park = norm(Up_raw).reshape(XX.shape)
|
||||
U_exit = norm(Ux_raw).reshape(XX.shape)
|
||||
|
||||
inside = Path(sg.SITE).contains_points(Z).reshape(XX.shape)
|
||||
U_parkm = np.ma.array(U_park, mask=~inside)
|
||||
U_exitm = np.ma.array(U_exit, mask=~inside)
|
||||
|
||||
# ---- numeric checks reported to stdout ---------------------------------
|
||||
print("U_g^max = %.4f U_e^max = %.4f" % (Ug_max, Ue_max))
|
||||
print("Uhat_park in [%.4f, %.4f]" % (U_parkm.min(), U_parkm.max()))
|
||||
print("Uhat_exit in [%.4f, %.4f]" % (U_exitm.min(), U_exitm.max()))
|
||||
print("mirror residual |(U_park+U_exit) - 2 U_e| = %.2e"
|
||||
% np.abs(Up_raw + Ux_raw - 2 * U_e).max())
|
||||
# the park field must be lower on the park line than on the exit line
|
||||
def sample(F, P):
|
||||
ii = np.clip(((P[:, 0] - x0) / DELTA).astype(int), 0, len(xs) - 1)
|
||||
jj = np.clip(((P[:, 1] - y0) / DELTA).astype(int), 0, len(ys) - 1)
|
||||
return F[jj, ii]
|
||||
print("mean Uhat_park on Lambda_park = %.4f, on Lambda_exit = %.4f"
|
||||
% (sample(U_park, park_line).mean(), sample(U_park, exit_line).mean()))
|
||||
print("mean Uhat_exit on Lambda_park = %.4f, on Lambda_exit = %.4f"
|
||||
% (sample(U_exit, park_line).mean(), sample(U_exit, exit_line).mean()))
|
||||
|
||||
# A common display ceiling for both panels: the analytic bound U_e^max is a
|
||||
# worst-case estimate, so the realized field only reaches ~0.34. Both
|
||||
# panels share one scale, which is what makes the mirror relation visible;
|
||||
# the colorbar is annotated with the true values, not rescaled to [0,1].
|
||||
vmax = float(max(U_parkm.max(), U_exitm.max()))
|
||||
|
||||
# ---- plot --------------------------------------------------------------
|
||||
plt.rcParams.update({
|
||||
"font.family": "serif",
|
||||
"font.serif": ["Nimbus Roman", "Times New Roman", "DejaVu Serif"],
|
||||
"font.size": 7,
|
||||
"mathtext.fontset": "stix",
|
||||
"axes.linewidth": 0.5,
|
||||
"pdf.fonttype": 42,
|
||||
})
|
||||
fig, axes = plt.subplots(1, 3, figsize=(7.16, 1.75),
|
||||
constrained_layout=True)
|
||||
|
||||
ax = axes[0]
|
||||
ax.add_patch(MplPolygon(sg.SITE, closed=True, facecolor="white",
|
||||
edgecolor="#585858", lw=0.7, zorder=1))
|
||||
ax.plot(center[:, 0], center[:, 1], color="#703A94", lw=0.9,
|
||||
ls=(0, (2.2, 1.2)), label=r"$\Lambda_{\mathrm{center}}$", zorder=3)
|
||||
ax.plot(park_line[:, 0], park_line[:, 1], color="#00549F", lw=1.0,
|
||||
label=r"$\Lambda_{\mathrm{park}}$", zorder=3)
|
||||
ax.plot(exit_line[:, 0], exit_line[:, 1], color="#C44E0A", lw=1.0,
|
||||
label=r"$\Lambda_{\mathrm{exit}}$", zorder=3)
|
||||
ax.plot(*sg.LOAD_POSE, marker="o", ms=2.8, color="#222222", zorder=4)
|
||||
ax.annotate(r"$\boldsymbol{\eta}_{\mathrm{load}}$", sg.LOAD_POSE,
|
||||
textcoords="offset points", xytext=(2.5, 2.5), fontsize=6)
|
||||
ax.legend(loc="lower right", fontsize=5.2, frameon=False,
|
||||
handlelength=1.4, borderaxespad=0.1, labelspacing=0.2)
|
||||
ax.set_title(r"(a) boundary and guidance sources", fontsize=6.6, pad=2)
|
||||
|
||||
for ax, U, name in ((axes[1], U_parkm, r"(b) $\hat U_{\mathrm{park}}$"),
|
||||
(axes[2], U_exitm, r"(c) $\hat U_{\mathrm{exit}}$")):
|
||||
im = ax.pcolormesh(XX, YY, U, cmap="viridis", vmin=0.0, vmax=vmax,
|
||||
shading="auto", rasterized=True)
|
||||
ax.contour(XX, YY, U.filled(np.nan),
|
||||
levels=np.linspace(0.06, vmax * 0.92, 5),
|
||||
colors="white", linewidths=0.25, alpha=0.7)
|
||||
ax.add_patch(MplPolygon(sg.SITE, closed=True, facecolor="none",
|
||||
edgecolor="#585858", lw=0.7))
|
||||
ax.set_title(name, fontsize=6.6, pad=2)
|
||||
|
||||
cb = fig.colorbar(im, ax=axes[2], fraction=0.046, pad=0.02)
|
||||
cb.set_ticks([0.0, vmax / 2.0, vmax])
|
||||
cb.set_ticklabels(["0", "%.2f" % (vmax / 2.0), "%.2f" % vmax])
|
||||
cb.ax.tick_params(labelsize=5.4, width=0.4, length=1.8)
|
||||
cb.outline.set_linewidth(0.4)
|
||||
|
||||
for ax in axes:
|
||||
ax.set_aspect("equal")
|
||||
ax.set_xlim(x0, x1)
|
||||
ax.set_ylim(y0, y1)
|
||||
ax.set_xticks([])
|
||||
ax.set_yticks([])
|
||||
for sp in ax.spines.values():
|
||||
sp.set_visible(False)
|
||||
|
||||
fig.savefig("fig_igf.pdf", dpi=600)
|
||||
print("wrote fig_igf.pdf")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user