在数值模拟或几何建模中,经常需要将离散的曲线段拼接成完整的空间轨迹。例如,给定一条主曲线(由参数 ttt 生成的离散点 (xw,yw,zw)(x_w, y_w, z_w)(xw,yw,zw))及其切向矢量场 (i,j,k)(i, j, k)(i,j,k),再构造一段附加曲线(如抛物线过渡段),最后将二者拼接。本文首先推导附加曲线的参数方程,然后给出多种独立可运行的 Python 实现,展示数组拼接方法,并讨论背后的几何意义。
附加曲线的参数构造
设主曲线末端点坐标为 (xw−1,yw−1,zw−1)(x_w-1, y_w-1, z_w-1)(xw−1,yw−1,zw−1),末端单位切矢为 (i−1,j−1,k−1)(i-1, j-1, k-1)(i−1,j−1,k−1)。定义局部坐标系:沿主曲线切向为 x′x'x′ 轴,垂直于切向且指向某特定方向(如重力反方向)为 z′z'z′ 轴,y′y'y′ 轴由右手定则确定。附加曲线在局部坐标系 x′O′z′x'O'z'x′O′z′ 平面内满足抛物线关系:
x′=−z′tanθ−tan2θ4L(z′)2 x' = -z' \tan\theta - \frac{\tan^2\theta}{4L} (z')^2 x′=−z′tanθ−4Ltan2θ(z′)2
其中 θ\thetaθ 为初始偏角,LLL 为特征长度。当 z′z'z′ 从 000 线性变化至 −W-W−W(W>0W>0W>0)时,x′x'x′ 形成一段向右弯曲的抛物线。之后通过旋转变换回到全局坐标系:
{xad=xw−1+x′cosβ−z′sinβyad=yw−1+Δyzad=zw−1+x′sinβ+z′cosβ \begin{cases} x_{\text{ad}} = x_w-1 + x'\cos\beta - z'\sin\beta \\ y_{\text{ad}} = y_w-1 + \Delta y \\ z_{\text{ad}} = z_w-1 + x'\sin\beta + z'\cos\beta \end{cases} ⎩ ⎨ ⎧xad=xw−1+x′cosβ−z′sinβyad=yw−1+Δyzad=zw−1+x′sinβ+z′cosβ
其中 β\betaβ 为旋转角(局部 x′x'x′ 轴与全局 xxx 轴的夹角),Δy\Delta yΔy 表示沿主曲线副法向的偏移量(通常为线性数组)。
独立代码示例 1:基于 NumPy 的完整拼接流程
以下代码块完全独立,包含所有必要的函数定义和参数设定。它生成主曲线的模拟数据,构造附加曲线,然后分别拼接位置分量与方向分量。
python
import numpy as np
def pn_numpy(t):
"""模拟主曲线参数方程,返回 3×N 数组"""
t = np.asarray(t)
x = np.cos(t)
y = np.sin(t)
z = -0.1 * t
return np.array([x, y, z])
def dny_numpy(t):
"""y 方向导数(模拟)"""
return np.cos(t)
def dnz_numpy(t):
"""z 方向导数(模拟)"""
return -0.1 * np.ones_like(t)
# 参数设置
theta = np.radians(30) # 30度
beta_val = np.radians(45) # 旋转角 45度
W = 2.0 # 垂直下降总高度
L = 5.0 # 抛物线特征长度
dup = 1.5 # y 方向附加偏移总量
num = 300 # 附加曲线离散点数
# 主曲线数据(假设 t = 0..2π,取 100 点)
t_vals = np.linspace(0, 2*np.pi, 100)
p_vals = pn_numpy(t_vals) # shape (3, 100)
dn_sub = dny_numpy(t_vals) # 仅用于说明,实际取其第一个元素
fi_vals = t_vals # 临时辅助变量
# 主曲线上的方向矢量(一维示例,实际可多维)
i = np.full_like(fi_vals, float(dn_sub[0])) # 用常量填充
j = dny_numpy(fi_vals)
k = dnz_numpy(fi_vals)
# 主曲线上的位置分量(取第一组)
xw = p_vals[0, :] # shape (100,)
yw = p_vals[1, :]
zw = p_vals[2, :]
# 构造附加曲线(抛物线过渡段)
tmpz = np.linspace(0, -W, num)
tmpx = -tmpz * np.tan(theta) - (np.tan(theta)**2) * tmpz**2 / (4 * L)
# 全局坐标变换
xad = xw[-1] + tmpx * np.cos(beta_val) - tmpz * np.sin(beta_val)
yad = yw[-1] + np.linspace(0, dup, num)
zad = zw[-1] + tmpx * np.sin(beta_val) + tmpz * np.cos(beta_val)
# 附加曲线上的方向矢量(与主曲线末端相同)
iad = np.full_like(xad, i[-1])
jad = np.full_like(xad, j[-1])
kad = np.full_like(xad, k[-1])
# 拼接位置分量
x_combined = np.concatenate([xw, xad])
y_combined = np.concatenate([yw, yad])
z_combined = np.concatenate([zw, zad])
# 拼接方向分量
i_combined = np.concatenate([i, iad])
j_combined = np.concatenate([j, jad])
k_combined = np.concatenate([k, kad])
print("拼接后 x 长度:", len(x_combined))
print("拼接后 i 长度:", len(i_combined))
print("x 最后 5 个值:", x_combined[-5:])
print("i 最后 5 个值:", i_combined[-5:])
独立代码示例 2:不同拼接轴与高维数组处理
实际应用中,方向矢量 (i,j,k)(i,j,k)(i,j,k) 可能保持与主曲线相同的二维形状(如若干条曲线)。下面展示如何处理多维数组并沿第一轴以外的方向拼接。
python
import numpy as np
# 模拟 3 条曲线,每条曲线有 50 个点
n_curves = 3
n_points = 50
n_add = 120 # 附加曲线点数
# 主曲线方向矢量场 (3, n_curves, n_points)
i_main = np.random.randn(3, n_curves, n_points)
j_main = np.random.randn(3, n_curves, n_points)
k_main = np.random.randn(3, n_curves, n_points)
# 位置数据 (n_curves, n_points)
x_main = np.random.randn(n_curves, n_points)
y_main = np.random.randn(n_curves, n_points)
z_main = np.random.randn(n_curves, n_points)
# 构造附加曲线数据(为简化,取每条曲线的末端值扩展)
x_add = np.full((n_curves, n_add), x_main[:, -1:]) + np.linspace(0, 1, n_add)
y_add = np.full((n_curves, n_add), y_main[:, -1:]) + np.linspace(0, 0.5, n_add)
z_add = np.full((n_curves, n_add), z_main[:, -1:]) - np.linspace(0, 2, n_add)
i_add = np.full((3, n_curves, n_add), i_main[:, :, -1:]) # 扩展末端方向
j_add = np.full((3, n_curves, n_add), j_main[:, :, -1:])
k_add = np.full((3, n_curves, n_add), k_main[:, :, -1:])
# 沿 points 轴(轴 = 2)拼接
x_comb = np.concatenate([x_main, x_add], axis=1) # shape (n_curves, n_points+n_add)
y_comb = np.concatenate([y_main, y_add], axis=1)
z_comb = np.concatenate([z_main, z_add], axis=1)
i_comb = np.concatenate([i_main, i_add], axis=2) # shape (3, n_curves, n_points+n_add)
j_comb = np.concatenate([j_main, j_add], axis=2)
k_comb = np.concatenate([k_main, k_add], axis=2)
print("x_comb 形状:", x_comb.shape)
print("i_comb 形状:", i_comb.shape)
独立代码示例 3:几何意义与连续可微性验证
本示例重点验证在拼接点处位置与切向的连续性。构造一条真实的三维螺旋线作为主曲线,并设计平滑抛物线过度段,最后通过数值差分检查拼接后的光滑性。
python
import numpy as np
import matplotlib.pyplot as plt
def helix(t, r=1.0, pitch=0.5):
"""参数螺旋线"""
x = r * np.cos(t)
y = r * np.sin(t)
z = pitch * t
return np.array([x, y, z])
def tangent_helix(t, r=1.0, pitch=0.5):
"""螺旋线切向(未单位化)"""
dx = -r * np.sin(t)
dy = r * np.cos(t)
dz = pitch
return np.array([dx, dy, dz])
# 参数
theta = np.radians(20)
beta = np.radians(30)
W = 1.2
L = 3.0
dup = 0.8
num = 200
# 主曲线:t ∈ [0, 4π]
t_main = np.linspace(0, 4*np.pi, 500)
main_pts = helix(t_main)
main_tan = tangent_helix(t_main)
xw, yw, zw = main_pts
i, j, k = main_tan # 实际使用中需单位化,此处为简便
# 附加曲线参数
tmpz = np.linspace(0, -W, num)
tmpx = -tmpz * np.tan(theta) - (np.tan(theta)**2) * tmpz**2 / (4*L)
xad = xw[-1] + tmpx * np.cos(beta) - tmpz * np.sin(beta)
yad = yw[-1] + np.linspace(0, dup, num)
zad = zw[-1] + tmpx * np.sin(beta) + tmpz * np.cos(beta)
# 方向矢量(取末端值)
iad = np.full_like(xad, i[-1])
jad = np.full_like(xad, j[-1])
kad = np.full_like(xad, k[-1])
# 拼接
x_full = np.concatenate([xw, xad])
y_full = np.concatenate([yw, yad])
z_full = np.concatenate([zw, zad])
i_full = np.concatenate([i, iad])
j_full = np.concatenate([j, jad])
k_full = np.concatenate([k, kad])
# 连续性检查:拼接点位置偏差
n_main = len(xw)
print("位置连续性(拼接点前后差):")
print(f"Δx = {x_full[n_main] - x_full[n_main-1]:.2e}")
print(f"Δy = {y_full[n_main] - y_full[n_main-1]:.2e}")
print(f"Δz = {z_full[n_main] - z_full[n_main-1]:.2e}")
# 方向连续性(切向)
print("\n方向连续性(切向分量相对变化):")
print(f"Δi / i[-1] = {(i_full[n_main] - i_full[n_main-1])/i_full[n_main-1]:.2e}")
print(f"Δj / j[-1] = {(j_full[n_main] - j_full[n_main-1])/j_full[n_main-1]:.2e}")
print(f"Δk / k[-1] = {(k_full[n_main] - k_full[n_main-1])/k_full[n_main-1]:.2e}")
# 可视化拼接结果(可选)
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111, projection='3d')
ax.plot(x_full, y_full, z_full, 'b-', lw=1, label='Combined curve')
ax.scatter([xw[-1]], [yw[-1]], [zw[-1]], color='r', s=50, label='Joint point')
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
ax.legend()
plt.title("Spatial Curve with Parabolic Transition")
plt.show()
数学背景解析
附加曲线的抛物线方程来源于"等斜率变化率"假设。设局部坐标系中 x′(z′)x'(z')x′(z′) 满足:
dx′dz′=tanθ+tan2θ2Lz′ \frac{dx'}{dz'} = \tan\theta + \frac{\tan^2\theta}{2L} z' dz′dx′=tanθ+2Ltan2θz′
积分得 x′(z′)=z′tanθ+tan2θ4L(z′)2+constx'(z') = z'\tan\theta + \frac{\tan^2\theta}{4L}(z')^2 + \text{const}x′(z′)=z′tanθ+4Ltan2θ(z′)2+const。取 x′(0)=0x'(0)=0x′(0)=0 可得常数项为零。又因为附加曲线从主曲线末端开始并向下延伸,通常取 z′z'z′ 负向,故令 z′=−sz' = -sz′=−s(s≥0s\ge0s≥0)并代入即得原表达式。该形式保证了 z′=0z'=0z′=0 处斜率连续(与主曲线切向夹角为 θ\thetaθ),且曲线曲率线性变化,适合作为平滑过渡段。
将局部坐标 (x′,y′,z′)(x',y',z')(x′,y′,z′) 变换至全局坐标时,β\betaβ 角决定了过渡段在水平面内的偏转方向,而 yyy 方向的线性偏移 Δy\Delta yΔy 可用于模拟侧向扩展。
上述三个独立代码块分别从基础拼接、高维数组处理、光滑性验证三个角度给出了完整解决方案。读者可根据自己的数据格式和精度要求选用或组合其中的方法。