extract explicit mesh with topology information from implicit surfaces with boolean operations, and do surface/volume integrating on them.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
6.4 KiB

6 days ago
import math
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
def arc_gwn_batch(a, b, bulge, P):
"""
向量化批处理接口 (N, 2) 的点阵 P 同时计算单段圆弧的广义绕数 W
"""
a = np.asarray(a, dtype=float)
b = np.asarray(b, dtype=float)
P = np.asarray(P, dtype=float)
half_ab = 0.5 * (a + b)
bisec_vec = 0.5 * np.array([a[1] - b[1], b[0] - a[0]])
center = half_ab + bisec_vec / bulge
ca = a - center
cb = b - center
cp = P - center
D = np.linalg.norm(cp, axis=1)
r = np.linalg.norm(ca)
theta = 2.0 * math.atan(bulge)
W = np.empty(len(P))
mask_center = D < 1e-12
mask_circle = np.abs(D - r) < 1e-12
mask_normal = ~(mask_circle)
# W[mask_center] = theta / (2.0 * math.pi)
W[mask_circle] = theta / (4.0 * math.pi)
cp_n = cp[mask_normal]
D_n = D[mask_normal]
sigma = np.where(r > D_n, 1.0, -1.0)
Rd = r * D_n
r_p_D = r + D_n
abs_r_D = np.abs(r - D_n)
dot_a = ca.dot(cp_n.T)
cross1 = cp_n[:, 0] * ca[1] - ca[0] * cp_n[:, 1]
Ga = np.arctan2(r_p_D * cross1, abs_r_D * (Rd + dot_a))
dot_b = cb.dot(cp_n.T)
cross2 = cp_n[:, 0] * cb[1] - cb[0] * cp_n[:, 1]
Gb = np.arctan2(r_p_D * cross2, abs_r_D * (Rd + dot_b))
delta = Gb - Ga
fix = delta * theta < 0.0
delta[fix] += math.copysign(math.pi, theta)
two_pi_W = 0.5 * theta + sigma * delta
W[mask_normal] = two_pi_W / (2.0 * math.pi)
return W
def plot_multi_arc_gwn_field(arcs, margin=0.5, nx=400, ny=400):
"""
绘制多段圆弧的广义绕数之和
参数
----
arcs : list of tuple
每段圆弧表示为 (a, b, bulge)其中 a, b 为长度为 2 的序列
margin : float
AABB 外扩边界
nx, ny : int
采样分辨率
"""
arcs = [(np.asarray(a, dtype=float), np.asarray(b, dtype=float), float(bulge))
for a, b, bulge in arcs]
# ---- 1. 计算所有圆弧的几何信息,确定全局 AABB ----
all_arc_x = []
all_arc_y = []
all_centers = []
all_rs = []
for a, b, bulge in arcs:
half_ab = 0.5 * (a + b)
bisec_vec = 0.5 * np.array([a[1] - b[1], b[0] - a[0]])
center = half_ab + bisec_vec / bulge
r = np.linalg.norm(a - center)
theta = 2.0 * math.atan(bulge)
start_angle = math.atan2(a[1] - center[1], a[0] - center[0])
end_angle = start_angle + theta
t_arc = np.linspace(start_angle, end_angle, 200)
arc_x = center[0] + r * np.cos(t_arc)
arc_y = center[1] + r * np.sin(t_arc)
all_arc_x.append(arc_x)
all_arc_y.append(arc_y)
all_centers.append(center)
all_rs.append(r)
# 全局 AABB
x_min = min(ax.min() for ax in all_arc_x) - margin
x_max = max(ax.max() for ax in all_arc_x) + margin
y_min = min(ay.min() for ay in all_arc_y) - margin
y_max = max(ay.max() for ay in all_arc_y) + margin
# ---- 2. 均匀采样并累加所有圆弧的 GWN ----
xs = np.linspace(x_min, x_max, nx)
ys = np.linspace(y_min, y_max, ny)
X, Y = np.meshgrid(xs, ys)
points = np.stack([X.ravel(), Y.ravel()], axis=1)
W = np.zeros(len(points))
for a, b, bulge in arcs:
W += arc_gwn_batch(a, b, bulge, points)
W = W.reshape(X.shape)
# ---- 3. 三分区独立归一化 ----
eps = np.finfo(float).eps * 1e6
bound_low = -eps
bound_high = eps
# 负区
W_neg = W.copy()
W_neg[W >= bound_low] = np.nan
W_min = np.nanmin(W_neg)
if not np.isnan(W_min):
W_neg = (bound_low - W_neg) / (bound_low - W_min)
# 零区
W_zero = W.copy()
W_zero[(W < bound_low) | (W > bound_high)] = np.nan
W_zero = (W_zero - bound_low) / (2.0 * eps)
# 正区
W_pos = W.copy()
W_pos[W <= bound_high] = np.nan
W_max = np.nanmax(W_pos)
if not np.isnan(W_max):
W_pos = (W_pos - bound_high) / (W_max - bound_high)
# ---- 4. 绘图 ----
fig, ax = plt.subplots(figsize=(10, 8))
im_neg = ax.pcolormesh(X, Y, W_neg, cmap='Blues', vmin=0, vmax=1, shading='auto')
im_zero = ax.pcolormesh(X, Y, W_zero, cmap='Greys', vmin=0, vmax=1, shading='auto')
im_pos = ax.pcolormesh(X, Y, W_pos, cmap='Reds', vmin=0, vmax=1, shading='auto')
# 叠加所有圆弧
for idx, (a, b, bulge) in enumerate(arcs):
ax.plot(all_arc_x[idx], all_arc_y[idx], 'k-', linewidth=2.5, zorder=4)
ax.plot(a[0], a[1], 'go', markersize=7, zorder=5)
ax.plot(b[0], b[1], 'mo', markersize=7, zorder=5)
ax.plot(all_centers[idx][0], all_centers[idx][1], 'k+', markersize=8, mew=1.5, zorder=5)
ax.set_aspect('equal')
ax.set_xlim(x_min, x_max)
ax.set_ylim(y_min, y_max)
ax.set_title(f'Multi-Arc GWN Sum ({len(arcs)} arcs)')
# ---- 5. 三个独立 colorbar ----
divider = make_axes_locatable(ax)
cax_neg = divider.append_axes("left", size="5%", pad=0.4)
cbar_neg = fig.colorbar(im_neg, cax=cax_neg, orientation='vertical')
cbar_neg.ax.yaxis.set_ticks_position('left')
cbar_neg.ax.yaxis.set_label_position('left')
cbar_neg.set_label('Negative $W$ (normalized)', rotation=90, labelpad=10)
cbar_neg.set_ticks([0, 1])
cbar_neg.set_ticklabels([f'{bound_low:.2e}', f'{W_min:.3f}' if not np.isnan(W_min) else 'N/A'])
cax_pos = divider.append_axes("right", size="5%", pad=0.4)
cbar_pos = fig.colorbar(im_pos, cax=cax_pos, orientation='vertical')
cbar_pos.set_label('Positive $W$ (normalized)', rotation=270, labelpad=15)
cbar_pos.set_ticks([0, 1])
cbar_pos.set_ticklabels([f'{bound_high:.2e}', f'{W_max:.3f}' if not np.isnan(W_max) else 'N/A'])
cax_zero = divider.append_axes("bottom", size="5%", pad=0.6)
cbar_zero = fig.colorbar(im_zero, cax=cax_zero, orientation='horizontal')
cbar_zero.set_label(f'Near-zero $W$ (normalized) [{bound_low:.2e}, {bound_high:.2e}]', labelpad=5)
cbar_zero.set_ticks([0, 1])
cbar_zero.set_ticklabels([f'{bound_low:.2e}', f'{bound_high:.2e}'])
plt.tight_layout()
plt.show()
# ==================== 主程序 ====================
if __name__ == '__main__':
# 示例 1:两段圆弧拼成近似整圆
arcs = [
([2.0, 0.0], [0.0, 0.0], float("inf")), # 上半圆
([0.0, 0.0], [2.0, 0.0], float("inf")), # 下半圆(注意 bulge>0 表示逆时针)
]
plot_multi_arc_gwn_field(arcs, margin=0.5, nx=1024, ny=1024)