☰
python的机器人基础原理工业场景模拟第十二篇:工业机械臂仿真,编写函数,输入关节角度,计算雅可比矩阵,用于解析末端运动速度。
2026/10/14 1:17:45 网站建设 项目流程

《Python机器人基础原理》第十二篇:工业机械臂仿真——输入关节角度计算雅可比矩阵,解析末端运动速度

1. 实际工业应用场景

周三下午,某汽车零部件焊接产线。一台4-DOF关节型工业机械臂正在对变速箱壳体进行点焊作业。机器人构型:3旋转+1垂直平移(SCARA变体),大臂长500 mm,小臂长400 mm,基座安装高度d₁=350 mm,z轴行程±150 mm。关节硬件限位:θ₁ ±160°、θ₂ ±120°、θ₃ ±180°、d₄ ±150 mm。额定负载12 kg,末端焊枪质量1.5 kg。焊接工艺要求末端线速度控制在80~120 mm/s之间。

下午两点半,控制工程师小王盯着示波器上末端实际速度曲线,眉头拧成了疙瘩。

"又是这样。"他对旁边的运动规划工程师小赵说,"轨迹规划给的关节速度曲线看起来很平滑,但末端实际速度在靠近工作空间边界的时候忽快忽慢,焊点质量不稳定。"

小赵凑过来看了一眼:"你用的末端速度是直接拿关节速度乘以臂长粗算的?"

"对,我给θ₂_dot设了0.8 rad/s,按小臂力臂0.4 m算出来末端线速度应该是320 mm/s——结果实际焊的时候速度飘到了工艺窗口外面。"

"问题就在这。0.8 rad/s的肘关节速度,在r=850 mm的位置末端速度确实大约是320 mm/s——但那是纯肘关节贡献的分量。你没算基座旋转和肘关节的耦合项。真正的总末端速度需要雅可比矩阵把四个关节的速度全部线性叠加。"

"雅可比?那不是课本第3章的东西吗?"

"第3章连杆参数和运动学求解的延伸。你现在做离线仿真验证,正好可以把雅可比算出来,看看每个工作点的速度放大倍数到底是多少,哪些位形下末端速度会突破80~120 mm/s的焊接窗口。"

本篇解决的问题:给定一组关节角度,计算该位形下的解析雅可比矩阵(6×4),将关节空间速度向量(θ₁dot, θ₂dot, θ₃dot, d₄dot)映射到末端笛卡尔速度(v_x, v_y, v_z, ω_x, ω_y, ω_z)。通过雅可比矩阵的条件数和各列向量模长,分析不同工作区域内末端速度的放大/缩小效应,为轨迹规划阶段的关节速度限幅提供依据。

⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于雅可比矩阵计算与速度映射分析。不可直接用于真实机器人控制器、伺服驱动器或焊接产线。真实工业现场需结合实时动力学补偿、关节扭矩限幅与安全监控。

2. 痛点分析

课程模块 本篇对应的工程痛点

第3章 连杆参数及运动学求解 → 3.5 速度雅可比 关节空间速度到末端笛卡尔速度的线性映射,需要解析雅可比矩阵

第3章 连杆参数及运动学求解 → 3.3 正运动学 雅可比矩阵各列由正运动学齐次变换矩阵推导,依赖D-H参数链

第6章 轨迹规划与控制 → 6.2 关节空间规划 轨迹规划阶段需要逆雅可比(或伪逆)将末端速度需求转换为关节速度

第2章 位姿描述与坐标变换 雅可比矩阵各列涉及旋转轴方向向量在不同坐标系间的变换

本篇代码解决 L3 离线运动学分析层。输入一组关节角度,输出该位形下的6×4雅可比矩阵,以及给定关节速度下的末端速度向量。代码不是设备实时控制器,不做伺服闭环,不做轨迹插补。它做的事情是:告诉运动规划工程师"在这个位形下,关节速度到末端速度的映射关系是什么,哪些方向被放大了,哪些被缩小了",并量化哪些工作区域满足80~120 mm/s的焊接速度窗口。

不覆盖:逆雅可比求解(伪逆/阻尼最小二乘)、动力学计算、碰撞检测、实时控制、6轴冗余机械臂、力控制。

3. 核心逻辑讲解

物理直觉

雅可比矩阵本质上是"速度版的传动比表"。想象一辆自行车:你蹬踏板的角速度和车轮线速度之间有一个固定的比例关系,这个比例由齿轮比和轮径决定。机械臂的雅可比矩阵就是每一对"关节运动→末端运动"之间的传动比——只不过机械臂有4个关节、末端有6个速度分量(3个线速度+3个角速度),所以传动比表是一个6行4列的矩阵。

每一列代表"如果只让第i个关节以单位速度运动、其他关节不动,末端会产生怎样的速度"。第1列告诉你基座旋转对末端的影响(力臂=末端到基座z轴的水平距离),第2列告诉你肘关节旋转的影响(力臂≈小臂长度),以此类推。把4个关节的速度同时乘以各自那列再加起来,就得到末端的总速度。

业务逻辑 → 代码映射

输入:一组关节角度 q = [θ₁, θ₂, θ₃, d₄]

↓

步骤1:正运动学计算各连杆变换矩阵 T₀¹, T₁², T₂³, T₃⁴

↓

步骤2:提取各连杆坐标系原点位置 p_i 和旋转轴方向 z_i

↓

步骤3:构建雅可比矩阵各列——

线速度列 jv_i = z_{i-1} × (p_end - p_{i-1}) (旋转关节)

线速度列 jv_i = z_3 (平移关节,沿第3连杆z轴)

角速度列 jw_i = z_{i-1} (旋转关节)

角速度列 jw_i = [0,0,0] (平移关节,不产生角速度)

↓

步骤4:组装 J = [jv₁ jv₂ jv₃ jv₄; jw₁ jw₂ jw₃ jw₄]

↓

步骤5:输入关节速度 q_dot,计算末端速度 v_end = J × q_dot

↓

步骤6:计算条件数 cond(J) = σ_max / σ_min 评估该位形的速度映射"健康度"

关键公式文字翻译

旋转关节线速度列:z{i-1} × (p_end - p{i-1})。z{i-1}是第i个关节的旋转轴方向向量(在基座标系中表示),p_end是末端位置,p{i-1}是第i-1个连杆坐标系原点。叉乘的物理含义:绕z轴旋转时,距离旋转轴|p_end - p_{i-1}|远的位置,线速度大小等于角速度乘以力臂长度,方向垂直于"从旋转轴到末端的连线"和"旋转轴"构成的平面。

旋转关节角速度列:直接就是z_{i-1}。因为绕某个轴旋转时,末端角速度就等于该关节的角速度向量(叠加原理)。

平移关节:线速度列等于第3连杆的z轴方向单位向量(因为第4级D-H变换中α₄=-π/2会翻转z轴,平移方向应取翻转前的第3连杆z轴),角速度列为全零(平移不产生旋转)。

条件数:cond(J) = σ_max / σ_min,即最大奇异值除以最小奇异值。不做奇异值过滤——当构型接近奇异时σ_min→0,cond→∞,这正是需要观测的物理趋势。过滤掉趋零奇异值会掩盖奇异,与"条件数越大越接近奇异"的核心叙事矛盾。

4. OOP代码实现

技术栈

严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。sympy仅用于符号推导验证,本篇核心计算全部使用数值方法。

项目结构树

robot_jacobian_sim/

├── run_jacobian_sim.py

├── requirements.txt

├── jacobian/

│ ├── __init__.py

│ ├── models.py

│ ├── forward_kinematics.py

│ ├── jacobian_solver.py

│ └── visualize.py

├── tests/

│ ├── __init__.py

│ └── test_jacobian_sim.py

└── results/

核心源码

"jacobian/models.py" — dataclass数据模型

"""

4-DOF工业机械臂数据模型

连杆参数、D-H表、关节限位、速度向量结构体

"""

from dataclasses import dataclass, field

from typing import List, Tuple

import numpy as np

@dataclass

class JointLimit:

"""

单关节硬件限位

min_val: 最小允许值 (rad 或 m)

max_val: 最大允许值 (rad 或 m)

"""

min_val: float

max_val: float

joint_name: str = ""

def __post_init__(self):

if self.min_val >= self.max_val:

raise ValueError(

f"JointLimit: min_val({self.min_val})必须严格小于"

f"max_val({self.max_val})")

if not np.isfinite(self.min_val) or not np.isfinite(self.max_val):

raise ValueError("JointLimit: 限位值必须为有限数值")

def contains(self, value: float) -> bool:

"""

判断关节值是否在限位区间内

浮点比较使用1e-10容差,避免边界舍入误判

"""

return (self.min_val - 1e-10) <= value <= (self.max_val + 1e-10)

@dataclass

class JointLimitSet:

"""

完整关节限位集合(4-DOF工业机械臂)

"""

limits: List[JointLimit]

def __post_init__(self):

if len(self.limits) != 4:

raise ValueError(f"需要4个关节限位, 实际{len(self.limits)}个")

@classmethod

def default_4dof_welding(cls):

"""

默认4-DOF焊接机械臂关节限位

θ1: ±160° θ2: ±120° θ3: ±180° d4: ±150 mm

"""

pi = 3.141592653589793

return cls(limits=[

JointLimit(-160 * pi / 180, 160 * pi / 180, 'joint1_base_yaw'),

JointLimit(-120 * pi / 180, 120 * pi / 180, 'joint2_elbow_yaw'),

JointLimit(-180 * pi / 180, 180 * pi / 180, 'joint3_wrist_roll'),

JointLimit(-0.150, 0.150, 'joint4_z_lift'),

])

def check_all_with_indices(self, joint_values: List[float]) -> Tuple[bool, List[int]]:

"""

检查全部关节是否在限位内

返回:(全部通过?, 超限关节索引列表)

使用索引而非字符串匹配,避免子串匹配脆弱性

"""

if len(joint_values) != 4:

raise ValueError(f"需要4个关节值, 实际{len(joint_values)}个")

all_pass = True

violation_indices = []

for i, (val, limit) in enumerate(zip(joint_values, self.limits)):

if not limit.contains(val):

all_pass = False

violation_indices.append(i)

return all_pass, violation_indices

@dataclass

class DHParam:

"""标准D-H参数"""

theta: float

d: float

a: float

alpha: float

def __post_init__(self):

for name, val in [('theta', self.theta), ('d', self.d),

('a', self.a), ('alpha', self.alpha)]:

if not np.isfinite(val):

raise ValueError(f"DHParam.{name}必须为有限数值")

@dataclass

class SCARAParams:

"""

4-DOF工业机械臂完整参数

大臂500mm, 小臂400mm, d1=350mm

"""

links: List[DHParam]

joint_names: List[str] = field(default_factory=lambda: [

'joint1_base_yaw', 'joint2_elbow_yaw',

'joint3_wrist_roll', 'joint4_z_lift'

])

@classmethod

def default_welding(cls):

pi = 3.141592653589793

links = [

DHParam(0.0, 0.350, 0.500, 0.0),

DHParam(0.0, 0.0, 0.400, 0.0),

DHParam(0.0, 0.0, 0.0, 0.0),

DHParam(0.0, 0.0, 0.0, -pi/2),

]

return cls(links=links)

@dataclass

class JointVelocity:

"""

关节速度向量 (rad/s 或 m/s)

"""

values: List[float]

def __post_init__(self):

if len(self.values) != 4:

raise ValueError(f"需要4个关节速度值, 实际{len(self.values)}个")

for i, v in enumerate(self.values):

if not np.isfinite(v):

raise ValueError(f"JointVelocity.values[{i}]必须为有限数值")

def as_array(self) -> np.ndarray:

return np.array(self.values, dtype=float)

"jacobian/forward_kinematics.py" — 正运动学求解器

"""

正运动学求解器:计算各连杆变换矩阵,供雅可比计算使用

"""

import numpy as np

from jacobian.models import SCARAParams

class ForwardKinematicsSCARA:

"""

4-DOF SCARA正运动学求解器

输出各连杆在基座标系中的变换矩阵列表

"""

def __init__(self, params: SCARAParams):

self.params = params

def compute_all_transforms(self, joint_values):

"""

计算从基座到各连杆的齐次变换矩阵

返回:T_list[i] = T_0^{i+1},i=0,1,2,3

同时返回各连杆坐标系原点在基座标系中的位置 p_list[i]

以及各连杆z轴方向在基座标系中的单位向量 z_list[i]

注意:z_list[3]是第4连杆系z轴(经α₄=-π/2翻转),

不是关节4平移方向;平移方向用z_list[2](第3连杆z轴)

"""

if len(joint_values) != 4:

raise ValueError(f"需要4个关节值, 实际{len(joint_values)}个")

T_list = []

p_list = []

z_list = []

T = np.eye(4)

for i in range(3):

self.params.links[i].theta = joint_values[i]

T_i = self._dh_matrix(self.params.links[i])

T = T @ T_i

T_list.append(T.copy())

p_list.append(T[0:3, 3].copy())

z_list.append(T[0:3, 2].copy())

# 第4关节:平移,α₄=-π/2

self.params.links[3].d = joint_values[3]

T_3 = self._dh_matrix(self.params.links[3])

T = T @ T_3

T_list.append(T.copy())

p_list.append(T[0:3, 3].copy())

# 注意:z_list[3]是第4连杆系z轴(经α₄=-π/2翻转),

# 不是关节4平移方向;平移方向用z_list[2]

z_list.append(T[0:3, 2].copy())

return T_list, p_list, z_list

def compute_end_effector_pose(self, joint_values):

"""计算末端执行器位姿"""

T_list, p_list, z_list = self.compute_all_transforms(joint_values)

T_end = T_list[-1]

x, y, z = T_end[0, 3], T_end[1, 3], T_end[2, 3]

yaw = joint_values[0] + joint_values[1] + joint_values[2]

yaw = np.arctan2(np.sin(yaw), np.cos(yaw))

return x, y, z, yaw

@staticmethod

def _dh_matrix(param):

ct, st = np.cos(param.theta), np.sin(param.theta)

ca, sa = np.cos(param.alpha), np.sin(param.alpha)

return np.array([

[ct, -st*ca, st*sa, param.a*ct],

[st, ct*ca, -ct*sa, param.a*st],

[0, sa, ca, param.d],

[0, 0, 0, 1]

], dtype=float)

"jacobian/jacobian_solver.py" — 雅可比矩阵求解器(核心业务类)

"""

雅可比矩阵求解器:输入关节角度,计算解析雅可比矩阵

基于运动学递推公式:J_i = [z_{i-1} × (p_end - p_{i-1}); z_{i-1}] (旋转关节)

本篇计算正映射 J·q_dot → 末端速度

逆映射(伪逆/DLS)为下一步,本篇不覆盖

"""

import numpy as np

from jacobian.models import SCARAParams

from jacobian.forward_kinematics import ForwardKinematicsSCARA

class JacobianSolver:

"""

4-DOF SCARA雅可比矩阵解析求解器

输入:关节角度 q = [θ1, θ2, θ3, d4]

输出:6×4雅可比矩阵 J

"""

def __init__(self, params: SCARAParams):

self.params = params

self.fk = ForwardKinematicsSCARA(params)

def compute_jacobian(self, joint_values) -> np.ndarray:

"""

计算给定关节角度下的6×4雅可比矩阵

返回:J (6×4 numpy array)

"""

if len(joint_values) != 4:

raise ValueError(f"需要4个关节值, 实际{len(joint_values)}个")

T_list, p_list, z_list = self.fk.compute_all_transforms(joint_values)

p_end = p_list[-1]

z_base = np.array([0.0, 0.0, 1.0])

J = np.zeros((6, 4))

# 第1列:joint1 (旋转关节,绕基座z轴)

J[0:3, 0] = np.cross(z_base, p_end - np.zeros(3))

J[3:6, 0] = z_base

# 第2列:joint2 (旋转关节,绕第1连杆z轴)

J[0:3, 1] = np.cross(z_list[0], p_end - p_list[0])

J[3:6, 1] = z_list[0]

# 第3列:joint3 (旋转关节,绕第2连杆z轴)

J[0:3, 2] = np.cross(z_list[1], p_end - p_list[1])

J[3:6, 2] = z_list[1]

# 第4列:joint4 (平移关节,沿第3连杆z轴方向)

# 注意:用z_list[2](第3连杆z轴),不用z_list[3](经α₄翻转)

J[0:3, 3] = z_list[2]

J[3:6, 3] = np.array([0.0, 0.0, 0.0])

return J

def compute_end_velocity(self, joint_values, joint_velocities):

"""

给定关节角度和关节速度,计算末端笛卡尔速度

v_end = J(q) × q_dot

返回:(v_end (6×1), J (6×4))

"""

J = self.compute_jacobian(joint_values)

q_dot = np.array(joint_velocities, dtype=float)

if q_dot.shape[0] != 4:

raise ValueError(f"需要4个关节速度值, 实际{q_dot.shape[0]}个")

v_end = J @ q_dot

return v_end, J

def compute_condition_number(self, joint_values) -> float:

"""

计算雅可比矩阵的条件数(使用2-范数奇异值)

cond(J) = σ_max / σ_min

不做奇异值过滤——σ_min→0时cond→∞正是接近奇异的物理趋势

过滤趋零奇异值会掩盖奇异,与核心叙事矛盾

"""

J = self.compute_jacobian(joint_values)

singular_values = np.linalg.svd(J, compute_uv=False)

# 6×4矩阵最多4个非零奇异值,直接取max/min

if singular_values[-1] < 1e-15:

return float('inf')

return float(singular_values[0] / singular_values[-1])

def get_min_singular_value(self, joint_values) -> float:

"""

返回最小奇异值(辅助指标,独立输出)

"""

J = self.compute_jacobian(joint_values)

singular_values = np.linalg.svd(J, compute_uv=False)

return float(singular_values[-1])

def analyze_jacobian_columns(self, joint_values) -> dict:

"""

分析雅可比各列向量的模长

反映每个关节对末端各方向速度的贡献大小

"""

J = self.compute_jacobian(joint_values)

analysis = {

'column_norms': [],

'linear_velocity_contributions': [],

'angular_velocity_contributions': []

}

for i in range(4):

col = J[:, i]

analysis['column_norms'].append(float(np.linalg.norm(col)))

analysis['linear_velocity_contributions'].append(

float(np.linalg.norm(col[0:3])))

analysis['angular_velocity_contributions'].append(

float(np.linalg.norm(col[3:6])))

return analysis

"jacobian/visualize.py" — 可视化模块

"""

雅可比仿真结果可视化:4张核心图表

"""

import numpy as np

import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']

plt.rcParams['axes.unicode_minus'] = False

class Visualizer:

def __init__(self, results_dir="results"):

self.results_dir = results_dir

import os

os.makedirs(results_dir, exist_ok=True)

def plot_condition_number_vs_reach(self, reach_distances, condition_numbers,

min_singular_values=None):

"""

① 条件数随水平距离变化曲线

工程结论:距离工作空间边界越近,条件数越大,速度映射越不均匀

"""

fig, ax1 = plt.subplots(figsize=(10, 5))

color1 = 'tab:blue'

ax1.plot(np.array(reach_distances) * 1000, condition_numbers,

color=color1, linewidth=1.5, marker='o', markersize=4)

ax1.set_xlabel('Horizontal Reach Distance r (mm)')

ax1.set_ylabel('Jacobian Condition Number', color=color1)

ax1.tick_params(axis='y', labelcolor=color1)

ax1.grid(True, alpha=0.3)

# 标注工作空间边界

max_reach = max(reach_distances) * 1000

min_reach = min(reach_distances) * 1000

ax1.axvline(x=max_reach, color='red', linestyle='--',

linewidth=1, alpha=0.7, label=f'Max Reach {max_reach:.0f}mm')

ax1.axvline(x=min_reach, color='red', linestyle='--',

linewidth=1, alpha=0.7, label=f'Min Reach {min_reach:.0f}mm')

# 双y轴:最小奇异值

if min_singular_values is not None:

ax2 = ax1.twinx()

color2 = 'tab:orange'

ax2.plot(np.array(reach_distances) * 1000, min_singular_values,

color=color2, linewidth=1, marker='s', markersize=3,

alpha=0.7)

ax2.set_ylabel('Min Singular Value', color=color2)

ax2.tick_params(axis='y', labelcolor=color2)

plt.title('Jacobian Condition Number vs. Horizontal Reach Distance')

fig.tight_layout()

path = f"{self.results_dir}/01_condition_number_vs_reach.png"

plt.savefig(path, dpi=150)

plt.close()

return path

def plot_jacobian_column_norms(self, test_configs, column_data):

"""

② 雅可比各列模长对比柱状图

工程结论:展示各关节在不同位形下对末端速度的贡献大小

"""

fig, axes = plt.subplots(2, 2, figsize=(12, 9))

axes = axes.flatten()

joint_names = ['Joint1 (Base Yaw)', 'Joint2 (Elbow)',

'Joint3 (Wrist)', 'Joint4 (Z Lift)']

for j in range(4):

ax = axes[j]

norms = [col_data[i][j] for i in range(len(col_data))]

labels = [f'C{cfg["label"]}' for cfg in test_configs]

bars = ax.bar(range(len(norms)), norms, alpha=0.8,

edgecolor='black')

for bar, norm in zip(bars, norms):

if norm > 1.5:

bar.set_color('red')

elif norm > 0.8:

bar.set_color('orange')

else:

bar.set_color('steelblue')

ax.set_xticks(range(len(labels)))

ax.set_xticklabels(labels, rotation=45, ha='right', fontsize=7)

ax.set_ylabel('Column Norm ||J_i||')

ax.set_title(f'{joint_names[j]}\nVelocity Contribution Magnitude')

ax.grid(True, alpha=0.3, axis='y')

plt.tight_layout()

path = f"{self.results_dir}/02_jacobian_column_norms.png"

plt.savefig(path, dpi=150)

plt.close()

return path

def plot_end_velocity_vectors(self, test_configs, velocities,

velocity_magnitudes):

"""

③ 末端速度向量分量对比 + 速度窗口分析

工程结论:给定相同关节速度,不同位形下末端速度差异巨大;

标注80~120 mm/s焊接速度窗口

"""

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(11, 10))

# 上图:速度分量

n = len(test_configs)

x = np.arange(n)

width = 0.12

vx = [vel[0] for vel in velocities]

vy = [vel[1] for vel in velocities]

vz = [vel[2] for vel in velocities]

wx = [vel[3] for vel in velocities]

wy = [vel[4] for vel in velocities]

wz = [vel[5] for vel in velocities]

ax1.bar(x - 2.5*width, vx, width, label='v_x (m/s)', alpha=0.8)

ax1.bar(x - 1.5*width, vy, width, label='v_y (m/s)', alpha=0.8)

ax1.bar(x - 0.5*width, vz, width, label='v_z (m/s)', alpha=0.8)

ax1.bar(x + 0.5*width, wx, width, label='ω_x (rad/s)', alpha=0.8)

ax1.bar(x + 1.5*width, wy, width, label='ω_y (rad/s)', alpha=0.8)

ax1.bar(x + 2.5*width, wz, width, label='ω_z (rad/s)', alpha=0.8)

ax1.set_xticks(x)

ax1.set_xticklabels([cfg['label'] for cfg in test_configs],

rotation=45, ha='right')

ax1.set_ylabel('Velocity Component Magnitude')

ax1.set_title('End-Effector Velocity Components under Identical Joint Speeds'

'\n([θ1_dot=0.5, θ2_dot=0.3, θ3_dot=0.2, d4_dot=0.1])')

ax1.legend(fontsize=7)

ax1.grid(True, alpha=0.3, axis='y')

# 下图:末端线速度模长 + 焊接窗口

speed_mm_s = [mag * 1000 for mag in velocity_magnitudes]

colors = ['green' if 80 <= s <= 120 else 'red' for s in speed_mm_s]

ax2.bar(x, speed_mm_s, color=colors, alpha=0.8, edgecolor='black')

ax2.axhline(y=80, color='green', linestyle='--', linewidth=1.5,

label='Welding Window Min (80 mm/s)')

ax2.axhline(y=120, color='green', linestyle='--', linewidth=1.5,

label='Welding Window Max (120 mm/s)')

ax2.set_xticks(x)

ax2.set_xticklabels([cfg['label'] for cfg in test_configs],

rotation=45, ha='right')

ax2.set_ylabel('End-Effector Linear Speed (mm/s)')

ax2.set_title('End-Effector Speed vs. Welding Window (80~120 mm/s)')

ax2.legend(fontsize=8)

ax2.grid(True, alpha=0.3, axis='y')

plt.tight_layout()

path = f"{self.results_dir}/03_end_velocity_vectors.png"

plt.savefig(path, dpi=150)

plt.close()

return path

def plot_singular_value_spectrum(self, test_configs, svd_data):

"""

④ 雅可比矩阵奇异值谱

工程结论:奇异值分布越不均匀(最小奇异值趋近0),构型越接近奇异

"""

fig, ax = plt.subplots(figsize=(10, 6))

n = len(test_configs)

x = np.arange(n)

width = 0.2

colors = ['steelblue', 'orange', 'green', 'red']

labels = ['σ₁ (max)', 'σ₂', 'σ₃', 'σ₄ (min)']

for i in range(4):

sv = [svd_data[j][i] if i < len(svd_data[j]) else 0

for j in range(n)]

ax.bar(x + i*width, sv, width,

label=labels[i], color=colors[i], alpha=0.8,

edgecolor='black')

ax.set_xlabel('Test Configuration')

ax.set_ylabel('Singular Value Magnitude')

ax.set_title('Jacobian Singular Value Spectrum per Configuration')

ax.set_xticks(x + 1.5*width)

ax.set_xticklabels([cfg['label'] for cfg in test_configs],

rotation=45, ha='right')

ax.legend(fontsize=8)

ax.grid(True, alpha=0.3, axis='y')

plt.tight_layout()

path = f"{self.results_dir}/04_singular_value_spectrum.png"

plt.savefig(path, dpi=150)

plt.close()

return path

5. 可视化要求

序号 图表文件 服务结论

①

"01_condition_number_vs_reach.png" 条件数+最小奇异值随水平距离变化,展示工作空间边界附近的奇异趋势

②

"02_jacobian_column_norms.png" 各关节在不同位形下对末端速度的贡献大小对比

③

"03_end_ve

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询