news 2026/10/14 14:19:53

python的机器人基础原理工业场景模拟第二十篇:重载抓取仿真,在末端执行器附加工件质量,对比空载与负载状态下关节力矩差异。

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
python的机器人基础原理工业场景模拟第二十篇:重载抓取仿真,在末端执行器附加工件质量,对比空载与负载状态下关节力矩差异。

《Python机器人基础原理》第二十篇:机械臂仿真——重载抓取仿真,在末端执行器附加工件质量,对比空载与负载状态下关节力矩差异

1. 实际工业应用场景

周三下午,某重型锻造车间的自动化上下料工位。一台3-DOF垂直关节机械臂(基座偏航+肘部俯仰+末端旋转)正在执行热锻件从传送带到液压机的转运任务。机器人构型:大臂长800 mm,小臂长600 mm,末端旋转轴位于小臂末端。连杆质量:大臂8.5 kg(质心距基座0.35 m),小臂5.2 kg(质心距肘部0.28 m),末端夹具2.3 kg(质心距肘部0.15 m,质心到旋转轴线横向偏置0.02 m)。关节额定扭矩:基座偏航轴80 N·m,肘部俯仰轴300 N·m,末端旋转轴45 N·m。

调试工程师小王盯着HMI屏幕上的力矩监控画面,额头上渗出汗珠。

"师傅,这段轨迹在空载调试时Joint2峰值才62 N·m,加上这个45 kg的锻件后仿真里直接飙到278 N·m——离额定300 N·m只剩7%余量。我是不是该把加速度再降一降?"

老周放下手里的工艺卡:"你先别急着改参数。你现在的仿真模型里,负载质量加在哪了?质心怎么算的?"

"加在末端执行器上。夹具2.3 kg @ 0.15 m,锻件45 kg @ 0.75 m,组合质心我按加权平均算的:(2.3×0.15 + 45×0.75) / 47.3 ≈ 0.721 m。"

"对,加权平均是对的。那M₂₂你算过吗?空载时M₂₂ = m₂·com₂² + m₃empty·com₃empty² = 5.2×0.28² + 2.3×0.15² ≈ 0.46 kg·m²。加载后m₃变成47.3 kg,com₃变成0.721 m,M₂₂ ≈ 47.3×0.721² ≈ 24.6 kg·m²——涨了53倍。你再算重力项:θ₂=-45°时cos(-45°)=0.707,Δτ_G2 ≈ g·m_load·com₃_combined·cos(-45°) ≈ 9.81×45×0.721×0.707 ≈ 224 N·m。加上惯性项……"

小王在草稿纸上快速列式子:"θ̈₂峰值大约0.55 rad/s²,τ_M2 ≈ 24.6×0.55 ≈ 13.5 N·m。科氏项h从空载1.9涨到约28 kg·m²,但速度低,τ_C2峰值也就个位数。加总:重力约224 + 惯性约14 + 科氏约3 + 空载原有约35 ≈ 276 N·m。跟仿真输出的278 N·m对得上。"

"好。那你看——重力项占了总力矩的80%以上。重载抓取的主要负担是静态重力,不是动态加速。如果你把轨迹分两段——先低速移到目标上方,再缓慢下探——重力项不变但惯性项可以大幅压缩。"

小王把duration从3秒改到4.5秒重新跑:"Joint2峰值降到197 N·m,安全系数1.52。够了。"

本篇解决的问题:在动力学求解器中,通过修改末端执行器的质量和质心位置参数(加权平均),模拟空载与重载两种工况。给定相同的关节状态序列,分别计算全套力矩τ = M(q)q¨ + C(q,q˙)q˙ + G(q),对比惯性项、重力项、合力矩的差异。用于重载抓取前的离线力矩校核、轨迹可行性评估。

⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于重载抓取工况下的关节力矩计算与可视化。不可直接用于真实机器人控制器、伺服驱动器或锻造产线设备。真实工业现场需结合负载称重传感器、实时力矩监控、安全PLC联锁。代码作者不对直接使用本代码于真实设备造成的任何后果负责。

2. 痛点分析

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

第4章 机器人动力学建模 → 4.4 关节空间动力学方程 τ = M(q)q¨ + C(q,q˙)q˙ + G(q) 负载质量与质心变化同时影响M、G两项,需定量分离惯性增量与重力增量

第5章 机器人关节驱动与控制 → 5.2 伺服驱动选型 重载工况下关节扭矩需求可能接近额定值,需离线校核安全系数

第6章 轨迹规划与控制 → 6.1 轨迹约束 负载增大时需在力矩约束下重新规划加速度曲线

第7章 机器人非线性控制 → 7.1 计算力矩控制 负载变化导致模型参数漂移,前馈补偿需在线更新

本篇代码解决 L2 离线动力学校核层。输入:机器人参数 + 空载/负载质量属性 + 同一组关节状态序列。输出:两种工况下的τ_M、τ_C、τ_G、τ_total对比。代码不是设备实时控制器,不做伺服闭环。它做的事情是:给定相同轨迹,对比有无负载时的力矩差异。

不覆盖:实时负载辨识、自适应控制、碰撞检测、多轴联动插补、6轴完整动力学。

3. 核心逻辑讲解

物理直觉

你有没有试过空手甩绳子和甩系着重物的绳子?空手时几乎感觉不到阻力,但系上一块砖头后,不仅启动时要花大力气(惯性),就算匀速转,你也得一直用力拉着不让它掉下去(重力)。机械臂抓取重物也一样——负载同时增加"启动难度"(惯性项M变大)和"持续负担"(重力项G变大)。

关键洞察:负载加在末端,对基座偏航轴的影响主要通过耦合项M₁₂间接传递;对肘部俯仰轴则是直接影响——重力矩 ∝ m_load·com₃·cosθ₂,惯性矩 ∝ m₃·com₃²。质心越远,影响越大,而且是非线性的(二次方)。

业务逻辑 → 代码映射

输入:机器人参数 + 空载质量属性 / 负载质量属性(从空载配置+负载参数生成) + 同一组关节状态序列

↓

步骤1:用空载参数创建DynamicsSolver,计算全套力矩(M, C, G, total)

↓

步骤2:从空载配置出发,叠加负载质量和质心,创建新MassPropertySet,再创建新DynamicsSolver

↓

步骤3:计算全套力矩,逐项对比——Δτ_M, Δτ_C, Δτ_G, Δτ_total

↓

步骤4:统计峰值,计算安全系数(额定扭矩 / 负载峰值)

↓

步骤5:输出4张对比图表

关键公式文字翻译

重力项增量 Δτ_G:主要来自肘部俯仰轴。加载后m₃从2.3 kg变为47.3 kg,com₃从0.15 m变为0.721 m(加权平均)。增量≈ g·m_load·com₃_combined·cosθ₂ = 9.81×45×0.721×cos(-45°) ≈ 224 N·m。这是负载力矩的主要来源。

惯性项增量 Δτ_M:主要来自M₂₂ = m₂com₂² + m₃com₃²。空载M₂₂ ≈ 0.46 kg·m²;加载后M₂₂ ≈ 24.6 kg·m²——涨了53倍。同样θ̈₂≈0.55 rad/s²,τ_M2从约0.25 N·m涨到约13.5 N·m。

科氏项增量 Δτ_C:耦合系数h = m₂L₁com₂ + m₃L₁com₃。空载h≈1.9 kg·m²,加载后h≈28.4 kg·m²(涨了15倍)。但本篇轨迹速度较低,科氏力矩峰值仍在个位数N·m——重申第十五篇的结论:重载低速时科氏项可忽略,惯性+重力才是主项。

末端旋转轴:质点模型下I₃ = m₃·r_offset²,r_offset统一为0.02 m。负载下I₃ ≈ 47.3×0.02² ≈ 0.019 kg·m²,τ_M3峰值约0.01 N·m——图①第三个子图基本是平线,这是正确物理结果(末端旋转轴轻载),不是画图错误。

单位:力矩 N·m,质量 kg,长度 m,角度 rad。

4. OOP代码实现

技术栈

严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。

项目结构树

heavy_load_sim/

├── run_heavy_load_sim.py

├── requirements.txt

├── dynamics/

│ ├── __init__.py

│ ├── models.py

│ ├── trajectory.py

│ ├── dynamics_solver.py

│ └── visualize.py

├── tests/

│ ├── __init__.py

│ └── test_heavy_load_sim.py

└── results/

核心源码

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

"""

3-DOF垂直关节机械臂数据模型

重载抓取仿真:支持空载/负载两种质量属性配置

构型说明:基座偏航(θ1, 竖直轴) + 肘部俯仰(θ2, 水平轴) + 末端旋转(θ3)

物理声明:

- θ1绕竖直轴旋转,重力力矩恒为零

- θ2为水平俯仰轴,重力矩 = -g·(Σm_i·com_i)·cos(θ₂)

- 负载通过从空载配置叠加质量和质心实现,质心用加权平均

- 末端旋转惯量 I₃ = m₃·r_offset²,r_offset统一为0.02 m

- gravity_enabled控制重力项开关;g为物理输入,仅在enabled=True时使用

"""

from dataclasses import dataclass, field

from typing import List, Tuple, Optional

import numpy as np

@dataclass

class JointLimit:

"""单关节硬件限位"""

min_val: float

max_val: float

joint_name: str = ""

torque_limit: float = 0.0

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:

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

@dataclass

class JointLimitSet:

"""3-DOF机械臂关节限位集合"""

limits: List[JointLimit]

def __post_init__(self):

if len(self.limits) != 3:

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

@classmethod

def default_3dof_arm(cls):

"""

默认3-DOF机械臂限位

θ1: ±170° (基座偏航, 额定80 N·m)

θ2: ±140° (肘部俯仰, 额定300 N·m)

θ3: ±180° (末端旋转, 额定45 N·m)

"""

pi = 3.141592653589793

return cls(limits=[

JointLimit(-170 * pi / 180, 170 * pi / 180, 'joint1_base_yaw', 80.0),

JointLimit(-140 * pi / 180, 140 * pi / 180, 'joint2_elbow_pitch', 300.0),

JointLimit(-180 * pi / 180, 180 * pi / 180, 'joint3_end_rotate', 45.0),

])

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

if len(joint_values) != 3:

raise ValueError(f"需要3个关节值, 实际{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

def get_torque_limits(self) -> List[float]:

return [lim.torque_limit for lim in self.limits]

@dataclass

class LinkMassProps:

"""

连杆质量与质心属性

mass: 质量 (kg)

com_x: 质心沿连杆方向距近端关节的距离 (m)

com_y, com_z: 垂直于连杆方向的质心偏移 (m)

r_offset_end: 末端质心到旋转轴线的横向距离 (m),用于I₃计算

"""

mass: float

com_x: float

com_y: float

com_z: float

link_name: str = ""

max_com_x: Optional[float] = None

r_offset_end: float = 0.02 # 统一默认值0.02 m

def __post_init__(self):

if self.mass < 0:

raise ValueError(f"LinkMassProps: 质量不能为负, 实际{self.mass}")

for name, val in [('com_x', self.com_x), ('com_y', self.com_y),

('com_z', self.com_z), ('r_offset_end', self.r_offset_end)]:

if not np.isfinite(val):

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

if self.max_com_x is not None and self.com_x > self.max_com_x:

raise ValueError(

f"LinkMassProps({self.link_name}): 质心x={self.com_x}m "

f"超出连杆长度上限{self.max_com_x}m")

@dataclass

class MassPropertySet:

"""完整连杆质量属性集合"""

link_props: List[LinkMassProps]

link_lengths: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])

def __post_init__(self):

if len(self.link_props) != 3:

raise ValueError(f"需要3个连杆质量属性, 实际{len(self.link_props)}个")

for i, (prop, length) in enumerate(zip(self.link_props, self.link_lengths)):

if length > 0 and prop.com_x > length:

raise ValueError(

f"连杆{i}({prop.link_name}): 质心{prop.com_x}m超出长度{length}m")

@classmethod

def empty_end_effector(cls):

"""

空载配置:末端仅含夹具,无工件

大臂: 8.5 kg, com=0.35 m (L1=0.80m)

小臂: 5.2 kg, com=0.28 m (L2=0.60m)

末端夹具: 2.3 kg, com=0.15 m, r_offset=0.02 m

"""

link_lengths = [0.80, 0.60, 0.0]

props = [

LinkMassProps(8.5, 0.35, 0.0, 0.0, 'link1_upper_arm', link_lengths[0]),

LinkMassProps(5.2, 0.28, 0.0, 0.0, 'link2_forearm', link_lengths[1]),

LinkMassProps(2.3, 0.15, 0.0, 0.0, 'link3_end_effector_empty', None, 0.02),

]

return cls(link_props=props, link_lengths=link_lengths)

@classmethod

def with_load(cls, base_props: 'MassPropertySet',

load_mass: float, load_com_x: float,

load_r_offset: float = 0.02):

"""

从空载配置出发,叠加负载生成重载配置

质心按质量加权平均:(m_empty·com_empty + m_load·com_load) / (m_empty + m_load)

这是本篇的核心物理修正:负载质心不能简单替换,必须加权平均

"""

if load_mass < 0:

raise ValueError(f"负载质量不能为负, 实际{load_mass}")

new_props = []

for i, prop in enumerate(base_props.link_props):

if i == 2:

# 末端连杆:合并夹具+负载

total_mass = prop.mass + load_mass

combined_com = (prop.mass * prop.com_x + load_mass * load_com_x) / total_mass

new_props.append(LinkMassProps(

total_mass, combined_com, prop.com_y, prop.com_z,

f'link3_with_{load_mass}kg_load', prop.max_com_x,

load_r_offset # 统一使用传入的r_offset

))

else:

new_props.append(LinkMassProps(

prop.mass, prop.com_x, prop.com_y, prop.com_z,

prop.link_name, prop.max_com_x, prop.r_offset_end

))

return cls(link_props=new_props, link_lengths=base_props.link_lengths)

def total_mass(self) -> float:

return sum(prop.mass for prop in self.link_props)

@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 RobotParams:

"""

3-DOF机械臂完整参数

大臂800mm, 小臂600mm

gravity_enabled: 重力项计算开关。False时G(q)返回全零。

g: 重力加速度值。仅在gravity_enabled=True时参与计算。

"""

links: List[DHParam]

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

'joint1_base_yaw', 'joint2_elbow_pitch', 'joint3_end_rotate'

])

g: float = 9.81

gravity_enabled: bool = True

@classmethod

def default_3dof_arm(cls):

links = [

DHParam(0.0, 0.0, 0.800, 0.0),

DHParam(0.0, 0.0, 0.600, 0.0),

DHParam(0.0, 0.0, 0.0, 0.0),

]

return cls(links=links)

def zero_gravity(self):

"""关闭重力:返回新副本,gravity_enabled=False, g=0"""

new_links = [DHParam(l.theta, l.d, l.a, l.alpha) for l in self.links]

return RobotParams(links=new_links, joint_names=self.joint_names,

g=0.0, gravity_enabled=False)

"dynamics/trajectory.py" — 关节状态序列生成器

"""

关节状态序列生成器:重载抓取仿真用轨迹

轨迹设计:从传送带取件位置→液压机工位

θ1: 0° → 90°(水平转)

θ2: 0° → -45°(下探取件,cos(-45°)=0.707)

θ3: 0° → 180°(末端翻转)

"""

import numpy as np

from dynamics.models import JointLimitSet

class JointStateSequence:

"""

关节状态序列:存储多组(q, q˙, q¨)用于批量力矩计算

"""

def __init__(self, times, q_array, q_dot_array, q_ddot_array,

limits: JointLimitSet = None):

self.times = np.array(times, dtype=float)

self.q_array = np.array(q_array, dtype=float)

self.q_dot_array = np.array(q_dot_array, dtype=float)

self.q_ddot_array = np.array(q_ddot_array, dtype=float)

self.limits = limits

n = len(times)

if self.q_array.shape != (n, 3):

raise ValueError(f"q_array形状应为({n}, 3), 实际{self.q_array.shape}")

if self.q_dot_array.shape != (n, 3):

raise ValueError(f"q_dot_array形状应为({n}, 3)")

if self.q_ddot_array.shape != (n, 3):

raise ValueError(f"q_ddot_array形状应为({n}, 3)")

for name, arr in [('q_array', self.q_array),

('q_dot_array', self.q_dot_array),

('q_ddot_array', self.q_ddot_array)]:

if not np.all(np.isfinite(arr)):

raise ValueError(f"{name}包含非有限值(NaN或Inf)")

if limits is not None:

for i, t in enumerate(times):

rot_angles = [self.q_array[i, 0], self.q_array[i, 1], self.q_array[i, 2]]

all_pass, violations = limits.check_all_with_indices(rot_angles)

if not all_pass:

err_msgs = []

for vi in violations:

err_msgs.append(

f"时刻t={t:.3f}s: 关节'{limits.limits[vi].joint_name}' "

f"值={rot_angles[vi]:.3f} 超出限位 "

f"[{limits.limits[vi].min_val:.3f}, "

f"{limits.limits[vi].max_val:.3f}]"

)

raise ValueError(";".join(err_msgs))

@classmethod

def pick_and_place_trajectory(cls, n_points=100, duration=3.0,

limits: JointLimitSet = None):

"""

重载取放轨迹:从传送带取件→放置到液压机

θ1: 0° → 90°(水平旋转)

θ2: 0° → -45°(下探,cos(-45°)=0.707用于重力矩计算)

θ3: 0° → 180°(末端翻转使工件就位)

使用五次多项式插值确保位置、速度、加速度连续

"""

times = np.linspace(0, duration, n_points)

pi = 3.141592653589793

q1_start, q1_end = 0.0, 90 * pi / 180

q2_start, q2_end = 0.0, -45 * pi / 180

q3_start, q3_end = 0.0, 180 * pi / 180

q_array = np.zeros((n_points, 3))

q_dot_array = np.zeros((n_points, 3))

q_ddot_array = np.zeros((n_points, 3))

for i, t in enumerate(times):

if duration > 0:

s_norm = t / duration

else:

s_norm = 0.0

# 五次多项式:s = 10t³ - 15t⁴ + 6t⁵

s = 10*s_norm**3 - 15*s_norm**4 + 6*s_norm**5

s_dot = 30*s_norm**2 - 60*s_norm**3 + 30*s_norm**4

s_ddot = 60*s_norm - 180*s_norm**2 + 120*s_norm**3

q_array[i, 0] = q1_start + s * (q1_end - q1_start)

q_array[i, 1] = q2_start + s * (q2_end - q2_start)

q_array[i, 2] = q3_start + s * (q3_end - q3_start)

if duration > 0:

q_dot_array[i, 0] = s_dot * (q1_end - q1_start) / duration

q_dot_array[i, 1] = s_dot * (q2_end - q2_start) / duration

q_dot_array[i, 2] = s_dot * (q3_end - q3_start) / duration

q_ddot_array[i, 0] = s_ddot * (q1_end - q1_start) / (duration**2)

q_ddot_array[i, 1] = s_ddot * (q2_end - q2_start) / (duration**2)

q_ddot_array[i, 2] = s_ddot * (q3_end - q3_start) / (duration**2)

return cls(times, q_array, q_dot_array, q_ddot_array, limits)

def __len__(self):

return len(self.times)

"dynamics/dynamics_solver.py" — 动力学求解器

"""

动力学求解器:重载抓取仿真,计算完整动力学方程各项

τ = M(q)q¨ + C(q,q˙)q˙ + G(q)

支持空载/负载两种质量属性配置,对比力矩差异

重力公式:τ_G2 = -g·(m₂·com₂ + m₃·com₃)·cos(θ₂)(修正:不是cos(θ₁+θ₂))

末端惯量:I₃ = m₃·r_offset²(修正:不是m₃·com₃²)

"""

import numpy as np

from dynamics.models import RobotParams, MassPropertySet

class DynamicsSolver:

"""

基于拉格朗日方程的3-DOF机械臂动力学求解器

"""

@classmethod

def from_mass_props(cls, params: RobotParams, mass_props: MassPropertySet):

"""工厂方法"""

return cls(params, mass_props)

def __init__(self, params: RobotParams, mass_props: MassPropertySet):

self.params = params

self.mass_props = mass_props

self.gravity_enabled = params.gravity_enabled

self.g = params.g if self.gravity_enabled else 0.0

self.L1 = params.links[0].a

self.L2 = params.links[1].a

self.m1 = mass_props.link_props[0].mass

self.m2 = mass_props.link_props[1].mass

self.m3 = mass_props.link_props[2].mass

self.com1 = mass_props.link_props[0].com_x

self.com2 = mass_props.link_props[1].com_x

self.com3 = mass_props.link_props[2].com_x

self.r_offset3 = mass_props.link_props[2].r_offset_end

# 科氏力耦合系数 h = m₂·L₁·com₂ + m₃·L₁·com₃

self.h = self.m2 * self.L1 * self.com2 + self.m3 * self.L1 * self.com3

# 末端旋转惯量:I₃ = m₃·r_offset²

self.I3 = self.m3 * (self.r_offset3 ** 2)

def inertia_matrix(self, theta2):

"""计算3×3惯性矩阵 M(q)"""

c2 = np.cos(theta2)

M11 = (self.m1 * self.com1**2

+ self.m2 * (self.L1**2 + self.com2**2 + 2*self.L1*self.com2*c2)

+ self.m3 * (self.L1**2 + self.com3**2 + 2*self.L1*self.com3*c2))

M12 = (self.m2 * (self.com2**2 + self.L1*self.com2*c2)

+ self.m3 * (self.com3**2 + self.L1*self.com3*c2))

M22 = self.m2 * self.com2**2 + self.m3 * self.com3**2

M = np.zeros((3, 3))

M[0, 0] = M11

M[0, 1] = M12

M[1, 0] = M12

M[1, 1] = M22

M[2, 2] = self.I3

return M

def coriolis_vector(self, theta2, theta1_dot, theta2_dot, theta3_dot):

"""计算科氏力/离心力向量"""

s2 = np.sin(theta2)

tau_C1 = -self.h * s2 * (2 * theta1_dot * theta2_dot + theta2_dot**2)

tau_C2 = self.h * s2 * theta1_dot**2

tau_C3 = 0.0 # 质点模型下末端旋转无科氏耦合

return np.array([tau_C1, tau_C2, tau_C3], dtype=float)

def gravity_vector(self, theta1, theta2):

"""

计算重力项向量

τ_G1 = 0(竖直偏航轴,恒为零)

τ_G2 = -g·(m₂·com₂ + m₃·com₃)·cos(θ₂) ← 修正:cos(θ₂)不是cos(θ₁+θ₂)

τ_G3 = 0(末端旋转轴)

"""

if not self.gravity_enabled:

return np.zeros(3, dtype=float)

tau_G1 = 0.0

tau_G2 = -self.g * (self.m2 * self.com2 + self.m3 * self.com3) * np.cos(theta2)

tau_G3 = 0.0

return np.array([tau_G1, tau_G2, tau_G3], dtype=float)

def compute_all_terms(self, q, q_dot, q_ddot):

"""计算完整动力学方程的各项分解"""

if len(q) < 3 or len(q_dot) < 3 or len(q_ddot) < 3:

raise ValueError("需要3个关节的角度、速度、加速度")

theta1 = q[0]

theta2 = q[1]

theta3 = q[2]

theta1_dot = q_dot[0]

theta2_dot = q_dot[1]

theta3_dot = q_dot[2]

theta1_ddot = q_ddot[0]

theta2_ddot = q_ddot[1]

theta3_ddot = q_ddot[2]

M = self.inertia_matrix(theta2)

tau_M = M @ np.array([theta1_ddot, theta2_ddot, theta3_ddot])

tau_C = self.coriolis_vector(theta2, theta1_dot, theta2_dot, theta3_dot)

tau_G = self.gravity_vector(theta1, theta2)

tau_total = tau_M + tau_C + tau_G

return tau_M, tau_C, tau_G, tau_total

def compute_sequence(self, state_sequence):

"""对状态序列批量计算力矩"""

n = len(state_sequence)

tau_M_hist = np.zeros((n, 3))

tau_C_hist = np.zeros((n, 3))

tau_G_hist = np.zeros((n, 3))

tau_total_hist = np.zeros((n, 3))

for i in range(n):

tau_M, tau_C, tau_G, tau_total = self.compute_all_terms(

state_sequence.q_array[i],

state_sequence.q_dot_array[i],

state_sequence.q_ddot_array[i]

)

tau_M_hist[i, :] = tau_M

tau_C_hist[i, :] = tau_C

tau_G_hist[i, :] = tau_G

tau_total_hist[i, :] = tau_total

peaks = {

'tau_M_peak': np.max(np.abs(tau_M_hist), axis=0),

'tau_C_peak': np.max(np.abs(tau_C_hist), axis=0),

'tau_G_peak': np.max(np.abs(tau_G_hist), axis=0),

'tau_total_peak': np.max(np.abs(tau_total_hist), axis=0),

}

return {

'peaks': peaks,

'tau_M_hist': tau_M_hist,

'tau_C_hist': tau_C_hist,

'tau_G_hist': tau_G_hist,

'tau_total_hist': tau_total_hist,

}

"dynamics/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_torque_comparison_overlay(self, times, empty_total, loaded_total):

"""

① 空载vs负载 合力矩曲线叠加对比

"""

fig, axes = plt.subplots(3, 1, figsize=(10, 9), sharex=True)

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 14:18:31

C语言实现Renju五子棋AI:禁手检测与搜索优化实战

简介&#xff1a;一份以纯C语言实现的Renju规则五子棋AI程序包&#xff0c;主要面向对博弈树搜索和棋类AI感兴趣的开发者、算法学习者及竞赛选手&#xff0c;可帮助读者解决五子棋AI的走棋决策、搜索效率与规则适配等实际问题。程序将Minimax、启发式评估、Alpha-Beta剪枝、预评…

作者头像 李华
网站建设 2026/10/14 14:18:30

免硬件免云免编程的计算机视觉实训软件,破解大班教学难题

这两年我帮好几所院校做过计算机视觉课程建设&#xff0c;最深的感触是&#xff1a;方案评审时大家都在聊算法、模型、创新点&#xff0c;真正落到地上时&#xff0c;十个有九个卡在实训环境上。GPU机房建不建&#xff1f;云平台预算批不批&#xff1f;学生电脑配置高低不一&am…

作者头像 李华
网站建设 2026/10/14 14:18:24

Maya材质种类详解:从Lambert到Arnold的表面性格与选择逻辑

把“材质种类”这四个字写进笔记的第三年&#xff0c;我才承认自己一直在背一本用不起来的字典。Lambert、Blinn、Phong、Phong E、Anisotropic&#xff0c;单独拎出来每个都眼熟&#xff0c;一放进材质编辑器就全变天书。后来我把同一个球翻来覆去换材质&#xff0c;又关掉所有…

作者头像 李华
网站建设 2026/10/14 14:18:18

3100张番茄叶片YOLO数据集:智慧农业病害检测全流程解析

搞智慧农业的朋友应该都遇到过这种尴尬&#xff1a;植株出了问题&#xff0c;最先有反应的往往是叶片&#xff0c;但等肉眼能确诊的时候&#xff0c;病害很可能已经传染开一片了。番茄健康叶片检测这个课题&#xff0c;我前前后后做了大半年&#xff0c;最大的瓶颈反而不是模型…

作者头像 李华
网站建设 2026/10/14 14:18:17

ARM服务器安装MySQL 5.7.44:aarch64预编译tar包全流程详解

简介&#xff1a;面向ARM架构Linux系统的MySQL 5.7.44数据库压缩包&#xff0c;专为国产麒麟V10等平台设计&#xff0c;支持AArch64指令集与64位地址空间&#xff0c;适用于服务器、边缘计算和嵌入式设备。该版本在5.7系列基础上修复了已知漏洞并优化性能&#xff0c;是数据库运…

作者头像 李华
网站建设 2026/10/14 14:17:44

PSO算法优化PID控制器参数整定实践

1. 项目背景与核心价值在工业控制领域&#xff0c;PID控制器作为最经典的控制算法&#xff0c;其参数整定一直是工程师面临的挑战。传统方法依赖经验试凑&#xff0c;耗时且难以获得最优解。这个项目将粒子群优化算法&#xff08;PSO&#xff09;引入PID参数整定过程&#xff0…

作者头像 李华